Cesium实战记录(八)三维风场+风速热力图(水平+垂直)

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

目录

老规矩首先看下效果

一、风场

1、数据

2、原理剖析

首先第一步就是构造网格数据

 然后撒粒子

再然后起风吧

二、热力场


老规矩首先看下效果

风场v1.0平面版只有U V 方向风速

三维风场不带高度

如果观看不了就点击链接三维风场不带高度_哔哩哔哩_bilibili

风场v2.0高度版加入W垂直风速

三维风场带垂直风速

如果观看不了就点击链接三维风场带垂直风速_哔哩哔哩_bilibili

风场v1.0结合风速热力图效果风速只有计算U V 方向

三维风场叠加风速热力图

如果观看不了就点击链接三维风场叠加风速热力图_哔哩哔哩_bilibili



看完效果那就开始风场吧

一、风场

1、数据

首先是风场的数据格式

气象数据之风向数据json格式解析 - 莫小龙 - 博客园

气象数据之风向数据展示原理 - 莫小龙 - 博客园

这篇博客详细介绍了风场的数据格式但这个是解析过后的原始数据一般是netcdf数据或grib2数据然后解析成类似上面展示的json格式。

其实那个我也没用他的我只用了东西方向 V 的风速数据 、南北方向 U 的风速数据垂直方向W 的风速数据 一般你们不要求高度也不用这个数据现在网上还没看到用垂直风速的网格数宽度+长度网格的四至范围 这些就够了。

其中U 和 V 以及W 的数据是数组格式的单位是m/s数据长度是 网格数宽*高。

什么意思呢你就想象成一个矩形的面然后划分成多少行多少列宽的意思就是多少列cols高的意思就是多少行rows,也就是网上大家普遍说的构造棋盘格。每个格子都有初始的风速东西方向和南北方向的。

看下我的数据吧

 

 比如说上面风速有2256条数据就是cols * rows 的数代表每个47*48个网格里面的风速分东西和南北的。

max和min就是这个方向上 数组里最大最小值不是必须的我是因为业务需要




2、原理剖析

根据网上大部队来吧

首先第一步就是构造网格数据

造好了上述的json数据后还需要做一步事就是计算水平面上南北和东西方向结合的矢量数据算向量模长 u*u + v*v 开根号就是朝这个方向的风速用来计算下一步风的经纬度的。这个没啥说的吧就是算向量。

看下我的棋盘格数据吧

是47*48条然后每条数据是【uvw, Math.sqrt(u * u + v * v)】,没有高度的就没有w

 然后撒粒子

有了这么多格子那我们就往这些个格子里面撒点吧随机撒以为我们也不知道风的起点在哪里撒上就算作风的起点了只需要计算下一步风的走向就行。

按随机撒5000个点吧让这5000个点落在棋盘格内随机数落在四至内不说了

 其中 age 表述粒子的生命周期

lnglatheight表示当前位置的经纬度高度

tlng,tlat,theight表示下一步的位置经纬度高度

speed表示水平方向的风速

如果你们用不上高度那就不要。

为什么不能用speed直接成风速呢因为这个speed是水平方向的风速他的风速不是m/s而是每一步所走的经纬度如果你看其他人的源码就知道了大家都是这么去做的为什么不看我的源码呢因为我不准备放我的源码

所以高程点只能通过水平距离去算移动的时间然后再 乘 垂直方向的风速 m/s 这是我所理解的算法或许有大佬可以直接算三维向量的模长算三维方向的风速那也是可以的但是我就不麻烦了我分开水平是水平的垂直另算

再然后起风吧

用canvas去做比cesium 的 primitive 效率会高上很多我也是用canvas去做的。这里用到的是循环去 实现 canvas的画线以及浏览器的刷新机制 requestAnimationFrame 。

好了到此就全部完成了。具体的源码我就不放了因为我也是站在别人的肩膀去完善的网上也能找到源码csdn上就有 大家的源码都是这样的我的被我改的乱七八糟的给了也只会误导你们如果你看了网上还不太明白的那你联系我有空我给你解答哈




最后在插一嘴实现高度的方法因为网上我没找到有实现高度的用的都是canvas 2d 的方式我最后实现了但其实也是2d方法的不过我觉得结合three.js去实现 应该可以完美复现三维风场垂直风速的那种效果。

现在说下高度的获取方法吧

如果用上高度那我多一嘴这是网上其他都没有说到的也是我自己胡JB想的不知道对不对。

高度计算方法首先撒完点后拿到经纬度和下一步的经纬度后算当前点的贴地高程算当前点和下一个点的空间距离计算出距离比上速度算出移动的时间在根据时间 * 当前网格的垂直风速就是这个点移动到下一个位置所移动的高度最后再加上当前点的贴地高程即是下个点的高程数据。




二、热力场

热力场怎么做呢在上面我们已经构建好网格数据了那热力数据就展示每个网格的风速数据这里我就只展示了uv方向的数据。

算好uv方向的数据然后撒5000个点计算每个点落在的网格数据然后利用二分插值算法找到这个网格内的风速把这个风速就赋给这个点作为这个点的value。这样一个热力图的数据就做好了。

接下来就是构建热力图层然后叠加了这个网上都很完善了我就不写了哈。

最后看下我的热力点位数据吧





如果你看到了这里那么恭喜你我最终还是决定把源码放上供大家参考下但是我事先说明啊我写的和网上大差不差但是被我乱改一通。也不设置积分下载了纯纯福利好不啦~ 

不带高度的

/**
 * @Description:风场
 * @author MrKuang
 * @date 2023/1/13 0013
 */

export default class CanvasWindy {
    constructor(json, params) {
        this._windData = json;
        this._viewer = params.viewer;
        this._canvas = params.canvas;
        //可配置的参数
        this._canvasContext = params.canvas.getContext('2d')// canvas上下文
        this._canvasWidth = params.canvasWidth;//画布宽度
        this._canvasHeight = params.canvasHeight;//画布高度
        this._speedRate = params.speedRate || 50;
        this._particlesNumber = params.particlesNumber || 5000;//粒子数
        this._maxAge = params.maxAge || 120; //粒子生命周期
        this._frameTime = params.frameRate || 1;// 每秒刷新次数因为requestAnimationFrame固定每秒60次的渲染所以如果不想这么快就把该数值调小一些
        this._color = params.color || '#ffffff';
        this._lineWidth = params.lineWidth || 1// 线宽度
        // 内置参数
        this._grid = [];
        this._initExtent = []// 风场初始范围
        this._calc_speedRate = [0, 0]// 根据speedRate参数计算经纬度步进长度
        this._windField = null
        this._particles = []
        this._animateFrame = null// requestAnimationFrame事件句柄用来清除操作
        this.isdistory = false// 是否销毁进行删除操作
        this._init();
    }

     _init() {
        //创建棋盘格子
        this._createWindField();
        this._calcStep();
        // 创建风场粒子
        for (var i = 0; i < this._particlesNumber; i++) {
            this._particles.push(this._randomParticle(new CanvasParticle()));
        }
        this._canvasContext.fillStyle = 'rgba(0, 0, 0, 0.97)'
        this._canvasContext.globalAlpha = 0.6
        this._animate();

        var then = Date.now();
        let that = this;
        (function frame() {
            if (!that.isdistory) {
                that.animateFrame = requestAnimationFrame(frame)
                var now = Date.now()
                var delta = now - then
                if (delta > that._frameTime) {
                    then = Date.now();
                    that._animate()
                }
            } else {
                that.removeLines()
            }
        })()
    }

     _animate() {
        var nextLng = null
        var nextLat = null
        var uv = null
         this._graphicData = [];
        this._particles.forEach( (particle) => {
            if (particle.age <= 0) {
                 this._randomParticle(particle);
            }
            if (particle.age > 0) {
                var tlng = particle.tlng
                var tlat = particle.tlat
                let height = particle.theight;
                var gridpos = this._togrid(tlng, tlat)
                var tx = gridpos[0]
                var ty = gridpos[1]
                if (!this._isInExtent(tlng, tlat)) {
                    particle.age = 0
                } else {
                    uv = this._getIn(tx, ty)
                    nextLng = tlng + this.calc_speedRate[0] * uv[0]
                    nextLat = tlat + this.calc_speedRate[1] * uv[1]
                    particle.lng = tlng
                    particle.lat = tlat
                    particle.x = tx
                    particle.y = ty
                    particle.tlng = nextLng
                    particle.tlat = nextLat
                    particle.age--
                }
            }
        })
        if (this._particles.length <= 0) this.removeLines()

        this._drawLines()
    }
    _drawLines() {
        var particles = this._particles
        this._canvasContext.lineWidth = this._lineWidth
        // 后绘制的图形和前绘制的图形如果发生遮挡的话只显示后绘制的图形跟前一个绘制的图形重合的前绘制的图形部分示例https://www.w3school.com.cn/tiy/t.asp?f=html5_canvas_globalcompop_all
        this._canvasContext.globalCompositeOperation = 'destination-in'
        this._canvasContext.fillRect(0, 0, this._canvasWidth, this._canvasHeight)
        this._canvasContext.globalCompositeOperation = 'lighter'// 重叠部分的颜色会被重新计算
        this._canvasContext.globalAlpha = 0.9
        this._canvasContext.beginPath()
        this._canvasContext.strokeStyle = this._color
        particles.forEach((particle) => {
            var movetopos = this._tomap(particle.lng, particle.lat, particle)
            var linetopos = this._tomap(particle.tlng, particle.tlat, particle)
            if (movetopos != null && linetopos != null) {
                this._canvasContext.moveTo(movetopos[0], movetopos[1])
                this._canvasContext.lineTo(linetopos[0], linetopos[1])
            }
        })
        this._canvasContext.stroke()
    }

    // 根据粒子当前所处的位置(棋盘网格位置)计算经纬度在根据经纬度返回屏幕坐标
    _tomap(lng, lat, particle) {
        if (!lng || !lat) {
            return null
        }
        var ct3 = Cesium.Cartesian3.fromDegrees(lng, lat, 0)
        // 判断当前点是否在地球可见端
        var isVisible = new Cesium.EllipsoidalOccluder(Cesium.Ellipsoid.WGS84, this._viewer.camera.position).isPointVisible(ct3)
        var pos = Cesium.SceneTransforms.wgs84ToWindowCoordinates(this._viewer.scene, ct3)
        if (!isVisible) {
            particle.age = 0
        }
        return pos ? [pos.x, pos.y] : null
    }

    // 粒子是否在地图范围内
    _isInExtent(lng, lat) {
        if ((lng >= this._windData.xmin && lng <= this._windData.xmax) && (lat >= this._windData.ymin && lat <= this._windData.ymax)) return true
        return false
    }

    //创建棋盘格子
    _createWindField() {
        var k = 0
        var rows = null
        var uv = null
        for (var j = 0; j < this._windData.rows; j++) {
            rows = []
            for (var i = 0; i < this._windData.cols; i++, k++) {
                uv = this._calcUV(this._windData.udata[k], this._windData.vdata[k])
                rows.push(uv)
            }
            this._grid.push(rows)
        }
        console.log(this._grid)

    }

    //计算风场向量
    _calcUV(u, v) {
        return [+u, +v, Math.sqrt(u * u + v * v)]
    }

    // 计算经纬度步进长度
    _calcStep() {
        var calcSpeed = this._speedRate;
        this.calc_speedRate = [(this._windData.xmax - this._windData.xmin) / calcSpeed, (this._windData.ymax - this._windData.ymin) / calcSpeed]
    }

    //根据风场范围随机生成粒子
    _randomParticle(particle) {
            let safe = 30;
            let x = -1;
            let y = -1;
            let lng = null;
            let lat = null;
            do {
                try {
                    lng = this._fRandomByfloat(this._windData.xmin, this._windData.xmax)
                    lat = this._fRandomByfloat(this._windData.ymin, this._windData.ymax)
                } catch (e) {
                    // console.log(e)
                }
                if (lng) {
                    //找到随机生成的粒子的经纬度在棋盘的位置 x y
                    var gridpos = this._togrid(lng, lat);
                    x = gridpos[0];
                    y = gridpos[1];
                }
            } while (this._getIn(x, y)[2] <= 0 && safe++ < 30)
            let uv = this._getIn(x, y);
            var nextLng = lng + this.calc_speedRate[0] * uv[0]
            var nextLat = lat + this.calc_speedRate[1] * uv[1]
            particle.lng = lng
            particle.lat = lat
        particle.x = x
        particle.y = y
        particle.tlng = nextLng
        particle.tlat = nextLat
        particle.speed = uv[2]
        particle.age = Math.round(Math.random() * this._maxAge)// 每一次生成都不一样
        return particle;
    }
    _getIn(x, y) {
        //  局部风场使用
        if (x < 0 || x >= this._windData.cols || y >= this._windData.rows || y <= 0) {
            return [0, 0, 0]
        }
        var x0 = Math.floor(x)//向下取整
        var y0 = Math.floor(y)
        var x1;
        var y1
        if (x0 === x && y0 === y) return this._grid[y][x]

        x1 = x0 + 1
        y1 = y0 + 1

        var g00 = this._getIn(x0, y0)
        var g10 = this._getIn(x1, y0)
        var g01 = this._getIn(x0, y1)
        var g11 = this._getIn(x1, y1)
        var result = null
        // console.log(x - x0, y - y0, g00, g10, g01, g11)
        try {
            result = this._bilinearInterpolation(x - x0, y - y0, g00, g10, g01, g11)
            // console.log(result)
        } catch (e) {
            console.log(x, y)
        }
        return result
    }

    // 二分差值算法计算给定节点的速度
    _bilinearInterpolation(x, y, g00, g10, g01, g11) {
        var rx = (1 - x)
        var ry = (1 - y)
        var a = rx * ry;
        var b = x * ry;
        var c = rx * y;
        var d = x * y
        var u = g00[0] * a + g10[0] * b + g01[0] * c + g11[0] * d
        var v = g00[1] * a + g10[1] * b + g01[1] * c + g11[1] * d
        return this._calcUV(u, v)
    }

    // 随机数生成器小数
    _fRandomByfloat(under, over) {
        return under + Math.random() * (over - under)
    }

    // 根据经纬度算出棋盘格位置
    _togrid(lng, lat) {
        var x = (lng - this._windData.xmin) / (this._windData.xmax - this._windData.xmin) * (this._windData.cols - 1)
        var y = (this._windData.ymax - lat) / (this._windData.ymax - this._windData.ymin) * (this._windData.rows - 1)
        return [x, y]
    }

    _resize(width, height) {
        this.canvasWidth = width
        this.canvasHeight = height
    }

    removeLines() {
        window.cancelAnimationFrame(this._animateFrame)
        this.isdistory = true
        this._canvas.width = 1
        document.getElementById('box').removeChild(this._canvas)
    }
}

function CanvasParticle() {
    this.lng = null// 粒子初始经度
    this.lat = null// 粒子初始纬度
    this.x = null// 粒子初始x位置(相对于棋盘网格比如x方向有360个格x取值就是0-360这个是初始化时随机生成的)
    this.y = null// 粒子初始y位置(同上)
    this.tlng = null// 粒子下一步将要移动的经度这个需要计算得来
    this.tlat = null// 粒子下一步将要移动的y纬度这个需要计算得来
    this.age = null// 粒子生命周期计时器每次-1
    this.speed = null// 粒子移动速度可以根据速度渲染不同颜色
}

带高度的

/**
 * @Description:风场
 * @author MrKuang
 * @date 2023/1/13 0013
 */

export default class CanvasWindy {
    constructor(json, params) {
        this._windData = json;
        this._viewer = params.viewer;
        this._canvas = params.canvas;
        //可配置的参数
        this._canvasContext = params.canvas.getContext('2d')// canvas上下文
        this._canvasWidth = params.canvasWidth;//画布宽度
        this._canvasHeight = params.canvasHeight;//画布高度
        this._speedRate = params.speedRate || 50;
        this._particlesNumber = params.particlesNumber || 5000;//粒子数
        this._maxAge = params.maxAge || 120; //粒子生命周期
        this._frameTime = params.frameRate || 1// 每秒刷新次数
        this._color = params.color || '#ffffff';
        this._lineWidth = params.lineWidth || 1// 线宽度
        // 内置参数
        this._grid = [];
        this._initExtent = []// 风场初始范围
        this._calc_speedRate = [0, 0]// 根据speedRate参数计算经纬度步进长度
        this._windField = null
        this._particles = []
        this._animateFrame = null// requestAnimationFrame事件句柄用来清除操作
        this.isdistory = false// 是否销毁进行删除操作
        this._zspeed = 0;
        this._init();
    }

    _init() {
        //创建棋盘格子
        this._createWindField();
        this._calcStep();
        // 创建风场粒子
        for (var i = 0; i < this._particlesNumber; i++) {
            this._particles.push(this._randomParticle(new CanvasParticle()));
        }
        this._canvasContext.fillStyle = 'rgba(0, 0, 0, 0.97)'
        this._canvasContext.globalAlpha = 0.6
        this._animate();

        var then = Date.now();
        let that = this;
        (function frame() {
            if (!that.isdistory) {
                that._animateFrame = requestAnimationFrame(frame)
                var now = Date.now()
                var delta = now - then
                if ((delta * 1000) > that._frameTime) {
                    then = Date.now();
                    that._animate()
                }
            } else {
                that.removeLines()
            }
        })()
    }

    _animate() {
        var nextLng = null
        var nextLat = null
        var uvw = null;
        this._graphicData = [];
        this._particles.forEach((particle) => {
            if (particle.age <= 0) {
                this._randomParticle(particle);
            }
            if (particle.age > 0) {
                var tlng = particle.tlng
                var tlat = particle.tlat
                let height = particle.theight;
                var gridpos = this._togrid(tlng, tlat)
                var tx = gridpos[0]
                var ty = gridpos[1]
                if (!this._isInExtent(tlng, tlat)) {
                    particle.age = 0
                } else {
                    uvw = this._getIn(tx, ty)
                    nextLng = tlng + this._calc_speedRate[0] * uvw[0]
                    nextLat = tlat + this._calc_speedRate[1] * uvw[1]
                    particle.lng = tlng
                    particle.lat = tlat
                    particle.x = tx
                    particle.y = ty
                    particle.tlng = nextLng
                    particle.tlat = nextLat
                    particle.height = height;
                    //计算空间距离
                    let d = mars3d.MeasureUtil.getDistance([new mars3d.LngLatPoint(particle.lng, particle.lat, 0), new mars3d.LngLatPoint(particle.tlng, particle.tlat, 0)])
                    let t = d / uvw[3];
                    particle.theight = particle.height + t * uvw[2];
                    particle.age--
                }
            }
        })
        if (this._particles.length <= 0) this.removeLines()

        this._drawLines()
    }

    _drawLines() {
        var particles = this._particles
        this._canvasContext.lineWidth = this._lineWidth
        // 后绘制的图形和前绘制的图形如果发生遮挡的话只显示后绘制的图形跟前一个绘制的图形重合的前绘制的图形部分示例https://www.w3school.com.cn/tiy/t.asp?f=html5_canvas_globalcompop_all
        this._canvasContext.globalCompositeOperation = 'destination-in'
        this._canvasContext.fillRect(0, 0, this._canvasWidth, this._canvasHeight)
        this._canvasContext.globalCompositeOperation = 'lighter'// 重叠部分的颜色会被重新计算
        this._canvasContext.globalAlpha = 0.9
        this._canvasContext.beginPath()
        this._canvasContext.strokeStyle = this._color
        particles.forEach((particle) => {
            var movetopos = this._tomap(particle.lng, particle.lat, particle)
            var linetopos = this._tomap1(particle.tlng, particle.tlat, particle)
            if (movetopos != null && linetopos != null) {
                this._canvasContext.moveTo(movetopos[0], movetopos[1])
                this._canvasContext.lineTo(linetopos[0], linetopos[1])
            }
        })
        this._canvasContext.stroke()
    }

    // 根据粒子当前所处的位置(棋盘网格位置)计算经纬度在根据经纬度返回屏幕坐标
    _tomap(lng, lat, particle) {
        if (!lng || !lat) {
            return null
        }
        var ct3 = Cesium.Cartesian3.fromDegrees(lng, lat, particle.height)
        // 判断当前点是否在地球可见端
        var isVisible = new Cesium.EllipsoidalOccluder(Cesium.Ellipsoid.WGS84, this._viewer.camera.position).isPointVisible(ct3)
        var pos = Cesium.SceneTransforms.wgs84ToWindowCoordinates(this._viewer.scene, ct3)
        if (!isVisible) {
            particle.age = 0
        }
        return pos ? [pos.x, pos.y] : null
    }

    _tomap1(lng, lat, particle) {
        if (!lng || !lat) {
            return null
        }
        var ct3 = Cesium.Cartesian3.fromDegrees(lng, lat, particle.theight)
        // 判断当前点是否在地球可见端
        var isVisible = new Cesium.EllipsoidalOccluder(Cesium.Ellipsoid.WGS84, this._viewer.camera.position).isPointVisible(ct3)
        var pos = Cesium.SceneTransforms.wgs84ToWindowCoordinates(this._viewer.scene, ct3)
        if (!isVisible) {
            particle.age = 0
        }
        return pos ? [pos.x, pos.y] : null
    }

    // 粒子是否在地图范围内
    _isInExtent(lng, lat) {
        if ((lng >= this._windData.xmin && lng <= this._windData.xmax) && (lat >= this._windData.ymin && lat <= this._windData.ymax)) return true
        return false
    }

    //创建棋盘格子
    _createWindField() {
        var k = 0
        var rows = null
        var uvw = null
        for (var j = 0; j < this._windData.rows; j++) {
            rows = []
            for (var i = 0; i < this._windData.cols; i++, k++) {
                uvw = this._calcUVW(this._windData.udata[k], this._windData.vdata[k], this._windData.wdata[k])
                rows.push(uvw)
            }
            this._grid.push(rows)
        }
        console.log(this._grid)

    }

    //计算风场向量
    _calcUVW(u, v, w) {
        return [+u, +v, +w, Math.sqrt(u * u + v * v)]
    }

    // 计算经纬度步进长度
    _calcStep() {
        var calcSpeed = this._speedRate;
        this._calc_speedRate = [(this._windData.xmax - this._windData.xmin) / calcSpeed, (this._windData.ymax - this._windData.ymin) / calcSpeed]
    }

    //根据风场范围随机生成粒子
    _randomParticle(particle) {
        let safe = 30;
        let x = -1;
        let y = -1;
        let lng = null;
        let lat = null;
        do {
            try {
                lng = this._fRandomByfloat(this._windData.xmin, this._windData.xmax)
                lat = this._fRandomByfloat(this._windData.ymin, this._windData.ymax)
            } catch (e) {
                // console.log(e)
            }
            if (lng) {
                //找到随机生成的粒子的经纬度在棋盘的位置 x y
                var gridpos = this._togrid(lng, lat);
                x = gridpos[0];
                y = gridpos[1];
            }
        } while (this._getIn(x, y)[2] <= 0 && safe++ < 30)
        let uvw = this._getIn(x, y);
        var nextLng = lng + this._calc_speedRate[0] * uvw[0]
        var nextLat = lat + this._calc_speedRate[1] * uvw[1]
        particle.lng = lng
        particle.lat = lat
        //计算随机点的高程
        particle.height = mars3d.PointUtil.getHeight(this._viewer.scene, new mars3d.LngLatPoint(particle.lng, particle.lat, 0));
        particle.x = x
        particle.y = y
        particle.tlng = nextLng
        particle.tlat = nextLat
        particle.speed = uvw[3]
        particle.age = Math.round(Math.random() * this._maxAge)// 每一次生成都不一样
        //计算空间距离
        let d = mars3d.MeasureUtil.getDistance([new mars3d.LngLatPoint(particle.lng, particle.lat, 0), new mars3d.LngLatPoint(particle.tlng, particle.tlat, 0)])
        let t = d / uvw[3];
        // console.log("距离"+d)
        // console.log("时间"+t)
        // console.log("速度"+particle.speed)
        particle.theight = particle.height + t * uvw[2];
        // console.log("垂直风速"+uvw[2])
        // console.log("一开始垂直高度"+particle.height)
        // console.log("垂直高度"+particle.theight)
        return particle;
    }

    _getIn(x, y) {
        //  局部风场使用
        if (x < 0 || x >= this._windData.cols || y >= this._windData.rows || y <= 0) {
            return [0, 0, 0]
        }
        var x0 = Math.floor(x)//向下取整
        var y0 = Math.floor(y)
        var x1;
        var y1
        if (x0 === x && y0 === y) return this._grid[y][x]

        x1 = x0 + 1
        y1 = y0 + 1

        var g00 = this._getIn(x0, y0)
        var g10 = this._getIn(x1, y0)
        var g01 = this._getIn(x0, y1)
        var g11 = this._getIn(x1, y1)
        var result = null
        // console.log(x - x0, y - y0, g00, g10, g01, g11)
        try {
            result = this._bilinearInterpolation(x - x0, y - y0, g00, g10, g01, g11)
            // console.log(result)
        } catch (e) {
            console.log(x, y)
        }
        return result
    }

    // 根据现有参数重新生成风场
    redraw() {
        window.cancelAnimationFrame(this._animateFrame)
        this._particles = [];
        this._grid = [];
        this._init()
    }

    // 二分差值算法计算给定节点的速度
    _bilinearInterpolation(x, y, g00, g10, g01, g11) {
        var rx = (1 - x)
        var ry = (1 - y)
        var a = rx * ry;
        var b = x * ry;
        var c = rx * y;
        var d = x * y
        var u = g00[0] * a + g10[0] * b + g01[0] * c + g11[0] * d
        var v = g00[1] * a + g10[1] * b + g01[1] * c + g11[1] * d
        var w = g00[2] * a + g10[2] * b + g01[2] * c + g11[2] * d
        return this._calcUVW(u, v, w)
    }

    // 随机数生成器小数
    _fRandomByfloat(under, over) {
        return under + Math.random() * (over - under)
    }

    // 根据经纬度算出棋盘格位置
    _togrid(lng, lat) {
        var x = (lng - this._windData.xmin) / (this._windData.xmax - this._windData.xmin) * (this._windData.cols - 1)
        var y = (this._windData.ymax - lat) / (this._windData.ymax - this._windData.ymin) * (this._windData.rows - 1)
        return [x, y]
    }

    _resize(width, height) {
        this.canvasWidth = width
        this.canvasHeight = height
    }

    removeLines() {
        window.cancelAnimationFrame(this._animateFrame)
        this.isdistory = true
        this._canvas.width = 1
        document.getElementById('box').removeChild(this._canvas)
    }
}

function CanvasParticle() {
    this.lng = null// 粒子初始经度
    this.lat = null// 粒子初始纬度
    this.x = null// 粒子初始x位置(相对于棋盘网格比如x方向有360个格x取值就是0-360这个是初始化时随机生成的)
    this.y = null// 粒子初始y位置(同上)
    this.tlng = null// 粒子下一步将要移动的经度这个需要计算得来
    this.tlat = null// 粒子下一步将要移动的y纬度这个需要计算得来
    this.age = null// 粒子生命周期计时器每次-1
    this.speed = null// 粒子移动速度可以根据速度渲染不同颜色
}

然后页面调用

 

 初始化球和数据重构后

 网上有些加了重新绘制但是我这个数据算起来比较麻烦不是静态的都是走接口然后自己拼起来的加了比较耗时我就去了也不影响。

热力场我就不放了你把数据搞好了就很简单了数据怎么做我上面都说了上面方法也有你就从里面找。核心代码就是撒完点找到对应的网格然后利用插值算法算出uv平方和开根号的风速值作为当前点的热力数值即可

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6