其实数据是来源于 NOAA 的 Grid 格点风力数据,分辨率是 1 度,全球共 360*180 个格点 (总计 64,800 个). 那么就我之前的了解,风力数据一般都是分为 U/V 两个方向,包括 NetCDF 数据也是,坐标信息是隐含在 grid 的索引中,风力记录在了 U/ V 两个垂直的方向.
把水平 U 方向的风力和 垂直 V 方向的风力数值分别作为 RGB 波段的 R,G 两个数值,合成一张 png 就如图所示。
由于我没有采用官网的数据下载 API,所以没有拿到原始的 json 数据,只拿到一张 png,所以无法反演出 U/V 数值的符号,只有正值. 所以最后渲染出来只有第一象限的风向,当然作为演示应该没问题。
- 根据UV图提取出每个格点的tmpCanvas.width = windImage.width;
- tmpCanvas.height = windImage.height;
- tmpCtx.drawImage(windImage, 0, 0);
- // imageData.data.length: width*height*4
- let imageData = tmpCtx.getImageData(0, 0, tmpCanvas.width, tmpCanvas.height),
- dataLength = imageData.data.byteLength;
- if (compressRatio == undefined || (compressRatio !== undefined && compressRatio < 1)) {
- console.warn("Input compressRatio invalid, use default 1.");
- compressRatio = 1;
- }
- for (let i = 1; i < tmpCanvas.height - 1; i += compressRatio) {
- // i:0~180, j:0~360
- for (let j = 0; j < tmpCanvas.width; j += compressRatio) {
- let particle = {
- 'lon': -180 + j,
- 'lat': -90 + i,
- };
- let uIndex = (i * 360 + j) * 4,
- vIndex = uIndex + 1;
- let uVal = imageData.data[uIndex],
- vVal = imageData.data[vIndex],
- // 根据UV计算风力,和风向.
- windPow = Math.pow(uVal, 2) + Math.pow(vVal, 2),
- angle = Number(Math.atan(vVal / uVal).toFixed(2)),
- color = 'rgba(' + (windPow / 255).toFixed(0) + ', 255, 100, 0.7)';
- // return geojson dataSource for mapboxgl.vector layer.
- if (geojson) {
- particle = {
- "type": "Feature",
- "properties": {
- "angle": angle,
- "color": color
- },
- "geometry": {
- "type": "Point",
- "coordinates": [ - 180 + j, -90 + i]
- }
- }
- features.push(particle);
- } else {
- // 如果不用geojson 的数据,就用自定义的canvas 去渲染,为了动画方便,我们采用这种方式.
- particle.color = color;
- particle.angle = angle;
- particle.radius = radius;
- particles.push(particle);
- }
- }
- }
当解析出这么多点之后,就可以开始写渲染的函数了,可以用 mapbox 原生的 fill layer 来做渲染(基于 webgl,效率更高)。但是为了方便动画,这里就采用自己之前写的动画控制器 Alex.myTween 和 CanvasOverLayer 扩展来渲染
- // genWindTarget 函数其实就是根据风向来模拟一个风粒子的动画效果,产生动画的目标对象,Alex.myTween为根据source 和target 自动计算中间坐标和状态.
- objs = windlayer.particles; targets = genWindTarget(objs);
- // calc targets depend on its angle, use 6 degree as dist.
- Alex.myTween.get(objs).to(targets, 8000, windlayer.redraw);
- map.on('moveend', function(){
- windlayer.redraw(objs);
- });
渲染的效果如下图:
其实就是做起来好玩,这个项目起源于对动画的实验,API 全部采用 ES6 + webpack,为了自己写起来方便,采用的给 mapbox 叠加自定义 CanvasOverlayer 的形式 来快速开发自己想要的效果。之后还是打算切换到 webgl,毕竟那个效率才高。 项目中还有些 其他插件,欢迎大家把玩,提 issue
昨天刚更新的将 Chart.js 集成到地图 的 dom 叠加层,直接配置插件中的 domOverlay 即可,简单好用。
来源: https://juejin.im/post/5a44407451882566e416479a