基于cesium+canvas构建小区内部风场图

1、应用说明

去年项目中用到了风场显示,最近回顾了一下,重拾了一下记忆,也记录一下。当时需求就是比如在某城市一个小区的真实动态风场进行模拟显示,主要模拟小区内不同季节、不同高度下的主导风的走向,和不同高度建筑周围的环流效果,因为建筑物会影响风向的走势变化,所以风场显示要突出建筑物的影响等。风场图的和洋流图的流动效果大差不差,两个如果实现,也是如出一辙。

2、风场图原理

关于风场效果可视化的博客文章也很多,有leaflet、openlayer、cesium等常见webgis地图框架的都有。查阅了许多关于风场图可视化的原理、代码实现的文章,初步了解风场实现的基本原理。关于原理部分,非常感谢这位大佬的文章里的介绍:数据可视化之风向图。感兴趣的可以查看一下,
那位大佬的文章里面形象的把需要显示的风场区域形象的比喻成一个棋盘:棋盘(风场范围)就是由多个小方格组成的,你随手拿一个棋子(风的起点),随机放在一个格子上,这就是当做风的起点,然后如果每个小格子上都定义了这个棋子该怎么走(风的风向),那么当棋子落在小格子里时,已经确定了下一次移动棋子的位置,棋子不停的跟着小格子里的指定方向移动,那么这个棋子就动了起来,对比风的显示效果,如果棋盘足够大,每次有很多个棋子随机的落在棋盘内的某个小格子里,然后都按照格内的方向一次接一次的移动,这个风就刮起来了。这里面当然也需要考虑很多细节的问题,大佬文章里也指出了,比如棋子移动到了边界怎么处理,棋子的生命周期的设置,老棋子的消亡,新棋子的诞生等等。这些问题其实都是可以自定义处理的,如果使用代码来实现,完全可以自由发挥,例如棋子到了边界,可以让它消亡,重新在随机产生一个棋子,也可以让它回归原始位置,从新在执行自己的运动轨迹。

3、准备风向数据

风向数据的准备和处理很重要,因为它最终影响着风场的显示效果。风场数据就和向量一样,分为南北方向(U)、东西方向(V);要有具体的格式供前端调用,那无非就是构造成json数据了。参考文章:气象数据之风向数据展示原理和气象数据之风向数据json格式解析。我使用的风场源数据不是上面文章中所说的netcdf数据或grib2数据;所以我转换的时候用到了其他办法,不过最终定义的数据类型是他那种。
其实我在准备数据的时候,还要贴合我的实际使用需求,得到风场数据源还要进一步处理,源数据风场还分为了不同高度下的风场要进行纠正和精细化处理,这里说一下精细化处理,精细化处理是要更细致的表现风场的分布,拿到的数据一般不是很精细,需要结合楼宇数据等其他辅助数据进行精细化,直接点就像栅格把分辨率缩小,因为从风场的原理可知道,风在运动的时候是按照指定的‘格子’进行变化,如果‘格子’的范围太大,比楼宇之间的距离还大,那么就没办法保障风场在局部范围的细节展示了,所以要做精细化处理,最起码楼宇之间可以多放几个格子,精细化后比较头疼的是区域不大,但最终结果数据很大,精细化不够显示效果不好。
其中包括了插值处理,插值过的风场在小区内每个位置都是有风向的,那么我要尽量保证在风场高度不超过小区楼层高度的时候,风场显示的时候尽量不能覆盖超过它的楼层,这么一来对坐标要求就很苛刻了,要确定建筑边界的位置,在处理风场数据时候要把在建筑区域内的风进行标识处理(0,0),意思就是风到这里就没了,实际情况也是如此,因为建筑会挡住风,改变风的走向,模拟的时候如果风到了建筑边界,要把它消亡,这样就不会覆盖小区建筑了(其实最终的结果处理看,还是因为坐标精确的问题,导致了一部分低于建筑高度的5米和10米高度下的风会显示在建筑区域上面,可能是三维显示的原因,在转动动地图时,感觉有一部分风就是在建筑上面,如果把视角垂直90度成二维地图的视角,那就好很多,风穿梭在楼宇之间的效果还是有的)。一些必要操作处理完后,直接转成json格式就作为模拟风场的真正数据了。
风向数据json格式实例,具体可以参考上面文章链接:

1、参数含义:
parameterCategory
配置了数据记录内容,风力数据默认为2
parameterCategoryName
风向数据默认:Momentum
parameterNumber
记录了数据方向:U向为2V向为3
parameterNumberName
U-component_of_wind/V-component_of_wind
numberPoints
数据点数量
nx
横向划分栅格数量
ny
纵向划分栅格数量
dx
横向步长
dy
纵向步长
lo1
横向起点,全球默认为0
la1
纵向起点,全球默认为-90
lo2
横向终点,全球默认359.5,根据步长有所不同
la2
纵向终点,全球默认-90

2、json格式示例
[
  {
    "header": {
      "discipline": 0,
      "disciplineName": "Meteorological products",
      "gribEdition": 2,
      "gribLength": 251674,
      "center": 7,
      "centerName": "US National Weather Service - NCEP(WMC)",
      "subcenter": 0,
      "refTime": "2017-12-27T00:00:00.000Z",
      "significanceOfRT": 1,
      "significanceOfRTName": "Start of forecast",
      "productStatus": 0,
      "productStatusName": "Operational products",
      "productType": 1,
      "productTypeName": "Forecast products",
      "productDefinitionTemplate": 0,
      "productDefinitionTemplateName": "Analysis/forecast at horizontal level/layer at a point in time",
      "parameterCategory": 2,
      "parameterCategoryName": "Momentum",
      "parameterNumber": 2,
      "parameterNumberName": "U-component_of_wind",
      "parameterUnit": "m.s-1",
      "genProcessType": 2,
      "genProcessTypeName": "Forecast",
      "forecastTime": 0,
      "surface1Type": 103,
      "surface1TypeName": "Specified height level above ground",
      "surface1Value": 10,
      "surface2Type": 255,
      "surface2TypeName": "Missing",
      "surface2Value": 0,
      "gridDefinitionTemplate": 0,
      "gridDefinitionTemplateName": "Latitude_Longitude",
      "numberPoints": 259920,
      "shape": 6,
      "shapeName": "Earth spherical with radius of 6,371,229.0 m",
      "gridUnits": "degrees",
      "resolution": 48,
      "winds": "true",
      "scanMode": 0,
      "nx": 720,
      "ny": 361,
      "basicAngle": 0,
      "lo1": 0,
      "la1": 90,
      "lo2": 359.5,
      "la2": -90,
      "dx": 0.5,
      "dy": 0.5
    },
    "data": [
      2.3011596,
      2.3011596,
      2.3011596,
      2.2811596,
      ......
      1.1211597,
      1.1511596,
      1.1711596
    ]
  },
  {
    "header": {
      "discipline": 0,
      "disciplineName": "Meteorological products",
      "gribEdition": 2,
      "gribLength": 245626,
      "center": 7,
      "centerName": "US National Weather Service - NCEP(WMC)",
      "subcenter": 0,
      "refTime": "2017-12-27T00:00:00.000Z",
      "significanceOfRT": 1,
      "significanceOfRTName": "Start of forecast",
      "productStatus": 0,
      "productStatusName": "Operational products",
      "productType": 1,
      "productTypeName": "Forecast products",
      "productDefinitionTemplate": 0,
      "productDefinitionTemplateName": "Analysis/forecast at horizontal level/layer at a point in time",
      "parameterCategory": 2,
      "parameterCategoryName": "Momentum",
      "parameterNumber": 3,
      "parameterNumberName": "V-component_of_wind",
      "parameterUnit": "m.s-1",
      "genProcessType": 2,
      "genProcessTypeName": "Forecast",
      "forecastTime": 0,
      "surface1Type": 103,
      "surface1TypeName": "Specified height level above ground",
      "surface1Value": 10,
      "surface2Type": 255,
      "surface2TypeName": "Missing",
      "surface2Value": 0,
      "gridDefinitionTemplate": 0,
      "gridDefinitionTemplateName": "Latitude_Longitude",
      "numberPoints": 259920,
      "shape": 6,
      "shapeName": "Earth spherical with radius of 6,371,229.0 m",
      "gridUnits": "degrees",
      "resolution": 48,
      "winds": "true",
      "scanMode": 0,
      "nx": 720,
      "ny": 361,
      "basicAngle": 0,
      "lo1": 0,
      "la1": 90,
      "lo2": 359.5,
      "la2": -90,
      "dx": 0.5,
      "dy": 0.5
    },
    "data": [
      -0.12500733,
      -0.14500733,
      -0.16500732,
      ......
      -2.9750073,
      -2.9650073
    ]
  }
]

4、代码思路

知道了风场原理和数据后,接下来就可以整理编码思路了,看了大部分的代码都是在绘制风的时候用的canvas 2d画线,试验证明这种方式是不错的,canvas 3d绘图没试过,以后有时间可以试一下,虽然不能有效表达风场的高度,但绘制的效率很高,因为我试验过通过cesium中添加Entity实体或者Primitive的方式进行显示,目的就是想突出风场的高度,毕竟是三维地图,建筑物也是三维模型,但是显示效果不太好,风场动起来效率很慢,尤其是Entity,超过三百个点浏览器就已经卡半天不能动了,感兴趣的可以尝试一下。最终还是用了canvas绘制,原理明白后,代码思路就简单了,在前人的基础上总结一下代码思路,总的来说分四步:
1、创建风场区域网格
风场网格区域(棋盘)的创建要根据所输入的数据范围来创建,棋盘的行列数据就是输入风场数据的行列,棋盘里每个格子里的信息就是风场的uv方向上的风速。
2、初始化风场粒子
风场粒子就是棋子,这个可以自定义个数,让它随机的分布在棋盘里,在随机放入棋盘里时,要初始化它的基本信息。基本信息主要包括当前坐标和要移动位置的坐标已经运动速度,其他的比如生命周期这个东西其实可要可不要,很多风场实现代码里都有。
3、绘制风场
绘制的话就是使用canvas来绘制,风场流动是线来表示的,绘制时利用粒子的当前坐标和目标坐标确定这条线,同时可以加入线条的粗细颜色等。
4、更新粒子运动
更新粒子运动就是为了使风动起来,可以使用requestAnimationFrame函数来进行每一帧的重绘,重绘的间隔可以自定义,只要保证效率和画面的流程就ok了,重绘的内容就是更新粒子的位置信息,粒子会在棋盘里安装指定速度进行运动,你重绘的时间越短,画面就越细腻,当然也增加了浏览器的负担。

5、代码

链接:https://pan.baidu.com/s/10vCmogCVz-WQmCUWaEjFGw
提取码:1234

6 风场效果图

最终的效果图,虽然可以大概显示小区区域内风场的流动情况以及楼宇之间导致的风向环流效果;但总归是基于canvas2d画图显示,也就是这个风场不是真3d的,而地图和建筑是三维的,要想做到很好的显示还需要继续研究:1是从数据处理上,数据是一切的入口;2是从渲染加载方式上。以后有时间再慢慢研究一下。




你可能感兴趣的:(gis,js)