Cesium上建立风场动态粒子效果

3,940 阅读5分钟

初学 使用Canvas + NCEP数据创建动态风场效果 封装基于Cesium的canvas渲染风场API

windy.gif

甲、需要准备一个canvasCesium初始化后的图层

Cesium初始化后的图层详见:vue3.0+vite+Cesium使用记录

讲解过程

1、先初始化Cesium 2、提供NCEP风场数据 详见 cesium-windy-canvas/mock20万行风场数据,直接复制即可 3、在进行CanvasWindy初始化 CanvasWindy详见 cesium-windy-canvas 4、设置风场必要参数 5、非常简单的创建具有动态粒子效果的风场

  CanvasWindy(data, params);

乙、设置Cesiumwindy参数params

参数名参数解释类型默认值
viewerCesium初始化后赋值变量Objectnull
canvas风场画布DOMnull
canvasContextcanvas上下文DOMparams.canvas.getContext('2d')
canvasWidth画布宽度Number300
canvasHeight画布高度Number180
speedRate风前进速度Number100
extent风场绘制地图范围Array[]
particlesNumber粒子总数Number20000
maxAge每个粒子的最大生存周期Number120
frameTime每秒刷新次数Number100
lineWidth粒子线条宽度Number1
initExtent风场初始范围Array[]
calc_speedRate根据speedRate参数计算经纬度步进长度Array[0, 0]
windField风场网格Objectnull
particles风场粒子存储Array[]
animateFramerequestAnimationFrame事件句柄,用来清除操作Objectnull
isdistory是否进行销毁boolfalse
windyColor风场颜色集合(根据风速不同设置不同颜色)classnew WindyColor()

丙、初始化风场粒子

在初始化Cesiumwindy构造函数时,需要传入风场数据,以及相应的参数 执行构造函数时自动执行init()进行粒子创造


丁、讲解根据风速设置不同粒子颜色

在初始化粒子中有一个画线的操作,在画线过程中进行颜色赋值 在使用this.particles对所有粒子遍历进行画轨迹操作

创建粒子

/// randomParticle生成随机粒子
/// CanvasParticle() 粒子参数
this.particles.push(this.randomParticle(new CanvasParticle()));

CanvasParticle参数解析

参数名参数解释类型默认值
lng粒子初始经度null
lat粒子初始纬度null
tlng粒子下一步将要移动的经度,这个需要计算得来null
tlat粒子下一步将要移动的y纬度,这个需要计算得来null
x粒子初始x位置Numbernull
y粒子初始y位置Numbernull
age粒子生命周期计时器,每次-1Numbernull
speed粒子移动速度Numbernull
color粒子颜色Stringnull

给粒子赋值颜色

1、在生成随机粒子时根据风速计算不同颜色 this.windyColor.getWindColor(particle.speed) 详细见Cesiumwindy源码第333行 2、画粒子轨迹线时进行画布粒子颜色添加 this.canvasContext.strokeStyle = particle.color this.canvasContext.stroke(); 详见Cesiumwindy源码第244行

戊、至此风场粒子效果已经完成后续操作注意如下

1、粒子效果完成之后不会随着地图缩放变换,需要在操作之后重新进行渲染 (1)、电脑屏幕缩放 需要进行onresize监听

window.onresize = resizeCanvas;
const resizeCanvas = () => {
/// windycanvas = document.getElementById('windycanvas') 画布
  if (windycanvas == null) return;
  windycanvas.width = window.innerWidth;
  windycanvas.height = cesiumRef.value?.clientHeight;
  if (windy) {
  // 重新设置画布大小
    // eslint-disable-next-line no-underscore-dangle
    windy._resize(windycanvas.width, windycanvas.height);
  }
};

(2)、地图缩放 地图缩放时需要对鼠标事件,滚轮事件进行监听 参考vue3.0+vite+Cesium使用记录监听viewer操作事件讲解 伪代码

handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
  // 鼠标滚动、旋转后是否需要重新生成风场---如果需要,打开以下注释--旋转或者移动到北半球的时候计算会有问题
  handler.setInputAction(() => {
    clearTimeout(refreshTimer);
   ...设置风场隐藏
    refreshTimer = setTimeout(() => {
      if (windy) {
        windy.extent = ...获取当前三维窗口左上、右上、左下、右下坐标集合;
        windy.redraw();
      }
      
     ...设置风场显示
    }, 200);
  }, Cesium.ScreenSpaceEventType.WHEEL);
  // 鼠标左键、右键按下
  handler.setInputAction(() => {
    mouseDown = true;
  }, Cesium.ScreenSpaceEventType.LEFT_DOWN);
  handler.setInputAction(() => {
    mouseDown = true;
  }, Cesium.ScreenSpaceEventType.RIGHT_DOWN);
  // 鼠标移动
  handler.setInputAction(() => {
    if (mouseDown) {
     ...设置风场隐藏
      mouseMove = true;
    }
  }, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
  // 鼠标左键、右键抬起
  handler.setInputAction(() => {
    if (mouseDown && mouseMove) {
      if (windy) {
        windy.extent = globalExtent;
        windy.redraw();
      }
    }
    ...设置风场显示
    mouseDown = false;
    mouseMove = false;
  }, Cesium.ScreenSpaceEventType.LEFT_UP);
  handler.setInputAction(() => {
    if (mouseDown && mouseMove) {
      if (windy) {
        windy.extent = globalExtent;
        windy.redraw();
      }
     
    }
    ...设置风场显示
    mouseDown = false;
    mouseMove = false;
  }, Cesium.ScreenSpaceEventType.RIGHT_UP);

解释: ...设置风场显示:在鼠标操作以及滚轮操作后将渲染后的风场重新进行展示,属于display操作 ...设置风场隐藏:在鼠标操作以及滚轮操作时将画布进行隐藏

(3)、监听当前地球操作,时刻计算窗口坐标进行globalExtent赋值

viewer.scene.postRender.addEventListener(() => {
    getCesiumExtent();
});
// 获取当前三维窗口左上、右上、左下、右下坐标
const getCesiumExtent = () => {
  const canvaswidth = window.innerWidth;
  const canvasheight = cesiumRef.value?.clientHeight || 0 - 50;
  // eslint-disable-next-line camelcase
  const left_top_pt = new Cesium.Cartesian2(0, 0);
  // eslint-disable-next-line camelcase
  const left_bottom_pt = new Cesium.Cartesian2(0, canvasheight);
  // eslint-disable-next-line camelcase
  const right_top_pt = new Cesium.Cartesian2(canvaswidth, 0);
  // eslint-disable-next-line camelcase
  const right_bottom_pt = new Cesium.Cartesian2(canvaswidth, canvasheight);
  const pick1 = viewer.scene.globe.pick(viewer.camera.getPickRay(left_top_pt), viewer.scene);
  const pick2 = viewer.scene.globe.pick(viewer.camera.getPickRay(left_bottom_pt), viewer.scene);
  const pick3 = viewer.scene.globe.pick(viewer.camera.getPickRay(right_top_pt), viewer.scene);
  const pick4 = viewer.scene.globe.pick(viewer.camera.getPickRay(right_bottom_pt), viewer.scene);
  if (pick1 && pick2 && pick3 && pick4) {
    // 将三维坐标转成地理坐标---只需计算左下右上的坐标即可
    const geoPt1 = viewer.scene.globe.ellipsoid.cartesianToCartographic(pick2);
    const geoPt2 = viewer.scene.globe.ellipsoid.cartesianToCartographic(pick3);
    // 地理坐标转换为经纬度坐标
    const point1 = [(geoPt1.longitude / Math.PI) * 180, (geoPt1.latitude / Math.PI) * 180];
    const point2 = [(geoPt2.longitude / Math.PI) * 180, (geoPt2.latitude / Math.PI) * 180];
    // console.log(point1,point2);
    // 此时说明extent需要分为东西半球
    if (point1[0] > point2[0]) {
      globalExtent = [point1[0], 180, point1[1], point2[1], -180, point2[0], point1[1], point2[1]];
    } else {
      globalExtent = [point1[0], point2[0], point1[1], point2[1]];
    }
  } else {
    globalExtent = [];
  }
};

己、相关文档

  1. vue3.0+vite+Cesium使用记录
  2. cesium-windy-canvas源码
  3. cesium轨迹讲解