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

news2024/11/26 20:27:00

目录

老规矩首先看下效果

一、风场

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条,然后每条数据是【u,v,w, Math.sqrt(u * u + v * v)】,没有高度的就没有w

 然后,撒粒子

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

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

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

lng,lat,height表示当前位置的经纬度高度,

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平方和开根号的风速值作为当前点的热力数值,即可!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/167141.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

JS逆向之补环境过瑞数详解

JS逆向之补环境过瑞数详解 “瑞数” 是逆向路上的一座大山&#xff0c;是许多JS逆向者绕不开的一堵围墙&#xff0c;也是跳槽简历上的一个亮点&#xff0c;我们必须得在下次跳槽前攻克它&#xff01;&#xff01; 好在现在网上有很多讲解瑞数相关的文章&#xff0c;贴心的一步…

英伟达Flex-unity插件

好像有这么个库&#xff0c;可以用来流体仿真 官网是这个&#xff1a;NVIDIA FleX | NVIDIA 开发者 问题 但是现在好像没了&#xff1a; NVIDIA FleX for Unity (1.0 BETA) | 物理 | Unity Asset Store 百度 Unity 8.Oct.2021 Unfortunately, NVIDIA FleX for Unity (1.0 …

完美Crack:Froala Editor 4.0.16 Patch

基于 Web 的 JavaScript/HTML WYSIWYG 文本编辑器。 Froala Editor 是一个用 JavaScript 编写的轻量级 WYSIWYG HTML 编辑器&#xff0c;可为您的应用程序启用富文本编辑功能。使用简单&#xff0c;许多功能不必用数百个按钮让用户不知所措。编辑器的智能工具栏可以在一个简单的…

ESP-IDF:基于企业链表实现的循环链表实例,实现了初始,插入,循环打印的功能

例程&#xff1a; /* circle链表基于企业链表*/ // 链表节点 typedef struct CIRCLENODE { CIRCLENODE *next; } circleNode; typedef struct CIRCLELIST { circleNode head; int size; } circleList; circleList *Inital_CircleList() { circleList *cp (circleList *)mal…

LeakCanary简要原理

首先需要知道ReferenceQueue&#xff08;引用队列&#xff09;&#xff1a;public static void main(String[] args) {Activity activity new Activity(); // 模拟ActivityReferenceQueue<Activity> queue new ReferenceQueue<>(); // 引用队列WeakReference<…

从汇编的角度了解C++原理——new和malloc的区别

文章目录1、new和malloc的区别1.1、例程1.2、总结本文用到的反汇编工具是objconv&#xff0c;使用方法可以看我另一篇文章https://blog.csdn.net/weixin_45001971/article/details/128660642。 其它文章&#xff1a; 从汇编的角度了解C原理——类的储存结构和函数调用 从汇编的…

【索引】引用A survey on pickup and delivery problems的文章

1. Dynamic container drayage with uncertain request arrival times and service time windows⭐️ 具有不确定请求到达时间和服务时间窗口的动态集装箱运输 Container drayage plays a critical role in intermodal global container transportation, as it accomplishes th…

hud 1846巴什博弈(简单的解法 或 Sprague-Grundy解法)

各位勇敢者要玩的第一个游戏是什么呢&#xff1f;很简单&#xff0c;它是这样定义的&#xff1a; 1、 本游戏是一个二人游戏; 2、 有一堆石子一共有n个&#xff1b; 3、 两人轮流进行; 4、 每走一步可以取走1…m个石子&#xff1b; 5、 最先取光石子的一方为胜&#xff1b; 如…

不懂Token,就是别说自己是中级测试工程师

官方回答&#xff1a; Token 是在服务端产生的。如果前端使用用户名/密码向服务端请求认证&#xff0c;服务端认证成功&#xff0c;那么在服务端会返回 Token 给前端。前端可以在每次请求的时候带上 Token 证明自己的合法地位。 基于工程师的理解&#xff1a; token就相当于客户…

Go语言初始

"当我回头&#xff0c;妄想找到幸福的线索&#xff0c;那束光拉长我的影子&#xff0c;逃离我。"一、Go简介Go&#xff08;又称 Golang&#xff09;是 Google 的 Robert Griesemer&#xff0c;Rob Pike 及 Ken Thompson 开发的一种静态强乐西、编译型语言。Go 语言语…

Unicorn反混淆:恢复被OLLVM保护的程序(一)

一、目标 现在很多程序利用ollvm的控制流平坦化来增加逆向分析的难度。 控制流平坦化 (control flow flattening)的基本思想主要是通过一个主分发器来控制程序基本块的执行流程&#xff0c;例如下图是正常的执行流程 1:show1 经过控制流平坦化后的执行流程就如下图: 1:show2 …

RT-Thread系列--启动过程

一、目的RT-Thread是一个小而美的RTOS&#xff0c;所有的RTOS的都有一个特点&#xff0c;那就是存在调度器&#xff0c;像RT-Thread的调度器是一个实时抢占式调度器&#xff08;同优先级任务使用时间片调度方式&#xff09;。本篇不讲调度器相关的内容&#xff0c;着重讲解调度…

【博客591】LVS的DR和NAT模式下要注意的缺陷

LVS的DR和NAT模式下要注意的缺陷 1、DR模式和NAT模式的转发原理 2、LVS的DR和NAT模式下要注意的缺陷 缺陷1&#xff1a;DR模式下的realserver和 lvs的vip提供服务的端口必须一致。 例如vip的端口对外端口为 80&#xff0c;但后端服务的真实端口为8080&#xff0c;通过lvs的D…

SpringBoot快速上手

SpringBoot 概述 Spring Boot 可以轻松创建独立的、生产级的基于 Spring 的应用程序&#xff0c;您可以“直接运行”。 特征 创建独立的 Spring 应用程序 直接嵌入Tomcat&#xff0c;Jetty或Undertow&#xff08;无需部署WAR文件&#xff09; 提供固执己见的“入门”依赖项…

初始C语言 - 函数(2)

目录 1.函数的嵌套调用和链式访问 1&#xff09;函数嵌套调用 2&#xff09;函数的链式访问 - 函数的返回值作为另一个函数的参数 2. 函数的声明和定义 1&#xff09;变量的声明和定义 2&#xff09;函数的声明和定义 //函数必须先声明后使用//函数的声明写在头文件里 3.…

集成学习、Bagging集成原理、随机森林构造过程、随机森林api与案例、boosting集成原理、梯度提升决策树(GBDT)、XGBoost与泰勒展开式

一、集成学习 集成学习&#xff1a;通过建立几个模型来解决单一预测问题&#xff0c;工作原理是生成多个分类器/模型&#xff0c;各自独立地学习和作出预测。这些预测最后结合成组合预测&#xff0c;因此优于任何一个单分类的做出预测 机器学习的两个核心任务 集成学习中boost…

【UE4 第一人称射击游戏】51-制作手榴弹

上一篇&#xff1a;【UE4 第一人称射击游戏】50-用另一种方法实现僵尸随机漫游 僵尸攻击玩家时造成伤害本篇效果&#xff1a;按G键投掷出手榴弹&#xff0c;产生爆炸效果步骤&#xff1a;新建一个蓝图类&#xff08;父类为Actor&#xff09;&#xff0c;命名为“GrenadeActor”…

水声功率放大器模块在圆柱壳结构声源辐射研究中的应用

客户需求&#xff1a;实验需要在消声水池中对圆柱壳声源振动和远场声压进行实验测量&#xff0c;圆柱壳内部尺寸为&#xff1a;高0.5m(不含盖板)&#xff0c;壳外径0.5&#xff0c;内径0.497m&#xff0c;上下盖板高0.014m&#xff0c;所以对圆柱壳内功率放大模块的尺寸以及供电…

Qt图表操作(QCustomPlot 与 QtCharts的介绍与使用)

一、QCustomPlot简介 QCustomPlot是QT下一个方便易用的绘图工具&#xff0c;该绘图库专注于制作美观&#xff0c;出版品质的2D图表&#xff0c;图表和图表&#xff0c;以及为实时可视化应用程序提供高性能。它可以导出为各种格式&#xff0c;如矢量化的PDF文件和光栅化图像&…

Task12 数据缘何而来数据格式

目录1 常见的格式1.1 Excel文件的格式1.2 Excel数据的格式1.3 基本知识2 Excel数据格式2.1 数据类型转换3 练习1 常见的格式 1.1 Excel文件的格式 Excel文件的常见格式&#xff1a;.xls和.xlsx 1.2 Excel数据的格式 Excel数据的存储不同格式&#xff1a;xlsx、csv、txt cs…