Cesium实战记录(八)三维风场+风速热力图(水平+垂直)
目录
老规矩首先看下效果
一、风场
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 = 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 = nullthis._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.6this._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 = nullvar nextLat = nullvar uv = nullthis._graphicData = [];this._particles.forEach( (particle) => {if (particle.age <= 0) {this._randomParticle(particle);}if (particle.age > 0) {var tlng = particle.tlngvar tlat = particle.tlatlet 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 = tlngparticle.lat = tlatparticle.x = txparticle.y = typarticle.tlng = nextLngparticle.tlat = nextLatparticle.age--}}})if (this._particles.length <= 0) this.removeLines()this._drawLines()}_drawLines() {var particles = this._particlesthis._canvasContext.lineWidth = this._lineWidth// 后绘制的图形和前绘制的图形如果发生遮挡的话,只显示后绘制的图形跟前一个绘制的图形重合的前绘制的图形部分,示例:https://www.w3school.com.cn/tiy/t.asp?f=html5_canvas_globalcompop_allthis._canvasContext.globalCompositeOperation = 'destination-in'this._canvasContext.fillRect(0, 0, this._canvasWidth, this._canvasHeight)this._canvasContext.globalCompositeOperation = 'lighter'// 重叠部分的颜色会被重新计算this._canvasContext.globalAlpha = 0.9this._canvasContext.beginPath()this._canvasContext.strokeStyle = this._colorparticles.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 truereturn false}//创建棋盘格子_createWindField() {var k = 0var rows = nullvar uv = nullfor (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 yvar 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 = lngparticle.lat = latparticle.x = xparticle.y = yparticle.tlng = nextLngparticle.tlat = nextLatparticle.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 y1if (x0 === x && y0 === y) return this._grid[y][x]x1 = x0 + 1y1 = y0 + 1var 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 * yvar u = g00[0] * a + g10[0] * b + g01[0] * c + g11[0] * dvar v = g00[1] * a + g10[1] * b + g01[1] * c + g11[1] * dreturn 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 = widththis.canvasHeight = height}removeLines() {window.cancelAnimationFrame(this._animateFrame)this.isdistory = truethis._canvas.width = 1document.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// 粒子生命周期计时器,每次-1this.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 = nullthis._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.6this._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 = nullvar nextLat = nullvar uvw = null;this._graphicData = [];this._particles.forEach((particle) => {if (particle.age <= 0) {this._randomParticle(particle);}if (particle.age > 0) {var tlng = particle.tlngvar tlat = particle.tlatlet 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 = tlngparticle.lat = tlatparticle.x = txparticle.y = typarticle.tlng = nextLngparticle.tlat = nextLatparticle.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._particlesthis._canvasContext.lineWidth = this._lineWidth// 后绘制的图形和前绘制的图形如果发生遮挡的话,只显示后绘制的图形跟前一个绘制的图形重合的前绘制的图形部分,示例:https://www.w3school.com.cn/tiy/t.asp?f=html5_canvas_globalcompop_allthis._canvasContext.globalCompositeOperation = 'destination-in'this._canvasContext.fillRect(0, 0, this._canvasWidth, this._canvasHeight)this._canvasContext.globalCompositeOperation = 'lighter'// 重叠部分的颜色会被重新计算this._canvasContext.globalAlpha = 0.9this._canvasContext.beginPath()this._canvasContext.strokeStyle = this._colorparticles.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 truereturn false}//创建棋盘格子_createWindField() {var k = 0var rows = nullvar uvw = nullfor (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 yvar 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 = lngparticle.lat = lat//计算随机点的高程particle.height = mars3d.PointUtil.getHeight(this._viewer.scene, new mars3d.LngLatPoint(particle.lng, particle.lat, 0));particle.x = xparticle.y = yparticle.tlng = nextLngparticle.tlat = nextLatparticle.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 y1if (x0 === x && y0 === y) return this._grid[y][x]x1 = x0 + 1y1 = y0 + 1var 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 * yvar u = g00[0] * a + g10[0] * b + g01[0] * c + g11[0] * dvar v = g00[1] * a + g10[1] * b + g01[1] * c + g11[1] * dvar w = g00[2] * a + g10[2] * b + g01[2] * c + g11[2] * dreturn 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 = widththis.canvasHeight = height}removeLines() {window.cancelAnimationFrame(this._animateFrame)this.isdistory = truethis._canvas.width = 1document.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// 粒子生命周期计时器,每次-1this.speed = null// 粒子移动速度,可以根据速度渲染不同颜色
}
然后页面调用
初始化球和数据重构后
网上有些加了重新绘制,但是我这个数据算起来比较麻烦不是静态的都是走接口然后自己拼起来的,加了比较耗时,我就去了,也不影响。
热力场我就不放了,你把数据搞好了就很简单了,数据怎么做我上面都说了,上面方法也有,你就从里面找。核心代码就是撒完点找到对应的网格,然后利用插值算法算出uv平方和开根号的风速值作为当前点的热力数值,即可!
Cesium实战记录(八)三维风场+风速热力图(水平+垂直)相关推荐
- python ui自动化配置文件,python UI自动化实战记录八:添加配置
添加配置文件写入测试地址等,当环境切换时只需修改配置文件即可. 1 在项目目录下添加文件 config.ini 写入: [Domain] domain = http://test.domain.cn ...
- cesium 实战记录(六)地图通用工具方法的封装
目录 1.放大 2.缩小 3.回归初始位置 4.全屏 5.前一视图和后一视图 地图通用工具:包括放大.缩小.二三维切换.回归初始位置.前一视图.后一视图等等 老规矩看下效果:... 这有啥好看的,直接 ...
- Cesium实战记录(五)天际线分析
老规矩看下效果:(版本是1.94的) 实现思路:(引用网上大佬的) 利用Cesium 的边缘检测和后期处理效果的叠加.即在地图上检测此视角下的地形边缘,叠加标价效果层,叠加针对边缘实例和前一个边缘效果 ...
- cesium 实战记录(一) 底图动态配置,前端实现切换
上帝希望: 有个底图管理功能,用户配置好需要展示的底图后,在地图页面会出现相应的底图可供切换: 大概就这样吧底图切换. 思考: 1.首先要想动态管理底图,地图页面动态渲染底部的basemap组件. 那 ...
- Cesium 实战记录(四) 绘制点线面的工具封装
老样子先看效果: 画点: (tips:我的代码里手动将点的高度 提高了50m) 画线: 画面: 看下封装的类结构吧: 看下调用的操作页面: vue页面: 引入工具类: js:初始化工具,然后激活点线面 ...
- (B站云e办)SpringBoot开发项目实战记录(八)(Easy poi 完成excel导出导入)
(B站云e办)SpringBoot开发项目实战记录(八) 一. pom依赖 二. 下载文件 2.1 jopo注释注解@Excel与@ExcelEntry 2.2 controller层 (完成exce ...
- PCL 实战记录 (一)
PCL 实战记录 (一) 目录 PCL 实战记录 (一) 1.读取.显示.保存 2.KD-TREE 搜索 3.八叉树 点云采样 4.OCTREE 空间分割 5.滤波 6.采样 6.1 下采样 6.2 ...
- Cesium 实战 - 最新版(1.104.0)通过异步方式初始化地球,加载影像以及高程图层
Cesium 实战-最新版(1.104.0)通过异步方式初始化地球,加载影像以及高程图层 遇到问题 初始化底图 初始化高程(监听载入完成事件,开启关闭高程) 初始化 3dtile 在线示例 Cesiu ...
- python数据分析与挖掘项目实战记录
python数据挖掘项目实战记录 取自<Python数据分析与挖掘实战>一书,整理各个项目中用到的数据处理方法: 数据预处理方法 建立模型方法 绘制图形 对于分类问题:用模型分类:混淆矩阵 ...
最新文章
- IT界顶级大咖讲解如何获得月薪5万以上的秘诀干货!
- 通过数组名遍历整个数组
- CentOS 6.5系统安装配置图解教程(详细图文)
- win7 64位下如何安装配置mysql-5.7.4-m14-winx64(安装记录)
- sample等价是什么错误_一个复制粘贴引发的有趣小错误及思考
- 0宽字符加密_艺术鬼才!Unicode 字符还能这么玩?
- redis和oracle同步方案,redis与oracle之间怎么实现数据同步?
- 洛谷P5300 与或和(全1子矩阵/单调栈)
- 播音主持必练的绕口令
- IOS OC UIKit基本使用
- 老旧小区改造浪潮下的智慧安防市场发展机遇
- java不完全教程附编码示例
- Win10 wusa命令卸载系统更新
- 电磁场与仿真软件(29)
- 股票ctp交易接口是什么?
- 【AP】On the Bayesian interpretation of Black-Litterman(2)
- 信用卡收单业务--银行业务(七)
- Python 一键获取全国市县级行政单元Shapefile文件
- 成功解决:You are using pip version 9.0.3, however version 20.3.3 is available. You should consider upgra
- Redis安装启动和配置文件