老规矩首先看下效果
风场v1.0(平面版,只有U V 方向风速)
三维风场(不带高度)
(如果观看不了就点击链接:三维风场(不带高度)_哔哩哔哩_bilibili)
风场v2.0(高度版,加入W垂直风速)
三维风场(带垂直风速)
(如果观看不了就点击链接:三维风场(带垂直风速)_哔哩哔哩_bilibili)
风场v1.0结合风速热力图效果(风速只有计算U V 方向)
三维风场叠加风速热力图
(如果观看不了就点击链接:三维风场叠加风速热力图_哔哩哔哩_bilibili)
看完效果,那就开始风场吧
一、风场
1、数据
首先是风场的数据格式
这篇博客详细介绍了风场的数据格式,但这个是解析过后的,原始数据一般是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 = 1000/(params.frameRate || 10) ;// 每秒刷新次数 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 = now - delta % that._frameTime; 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 = 1000/(params.frameRate || 10) ;// 每秒刷新次数 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> that._frameTime) { then = now - delta % that._frameTime; 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平方和开根号的风速值作为当前点的热力数值,即可!
本文转自 https://blog.csdn.net/KK_bluebule/article/details/128702207,如有侵权,请联系删除。