Cesium 制作风流场,制作风场可视化

news2024/11/24 10:57:44

需求

Cesium 制作风场

  • 预览
    在这里插入图片描述

分析

以下是开发中参考的几个博主的案例

  • 博客一:风场+热力图
  • 博客二
  • 博客三

风场数据准备,data.json

由于数据量过大,我这边只做格式展示,想要完整 json 文件,可以在我的资源里获取

[
  {
    "header":{
            "discipline":0,
            "disciplineName":"Meteorological products",
            "gribEdition":2,
            "gribLength":73286,
            "center":7,
            "centerName":"US National Weather Service - NCEP(WMC)",
            "subcenter":2,
            "refTime":"2018-06-25T00:00:00.000Z",
            "significanceOfRT":1,
            "significanceOfRTName":"Start of forecast",
            "productStatus":0,
            "productStatusName":"Operational products",
            "productType":3,
            "productTypeName":"Control Forecast products",
            "productDefinitionTemplate":1,
            "productDefinitionTemplateName":"Individual ensemble forecast at a point in time",
            "parameterCategory":2,
            "parameterCategoryName":"Momentum",
            "parameterNumber":2,
            "parameterNumberName":"U-component_of_wind",
            "parameterUnit":"m.s-1",
            "genProcessType":4,
            "genProcessTypeName":"Ensemble Forecast",
            "forecastTime":0,
            "surface1Type":103,
            "surface1TypeName":"Specified height level above ground",
            "surface1Value":10.0,
            "surface2Type":255,
            "surface2TypeName":"Missing",
            "surface2Value":0.0,
            "gridDefinitionTemplate":0,
            "gridDefinitionTemplateName":"Latitude_Longitude",
            "numberPoints":65160,
            "shape":6,
            "shapeName":"Earth spherical with radius of 6,371,229.0 m",
            "gridUnits":"degrees",
            "resolution":48,
            "winds":"true",
            "scanMode":0,
            "nx":360,
            "ny":181,
            "basicAngle":0,
            "subDivisions":0,
            "lo1":0.0,
            "la1":90.0,
            "lo2":359.0,
            "la2":-90.0,
            "dx":1.0,
            "dy":1.0
        },
    "meta": { "date": "2022.01.11 00:00:00 UTC" },
    "data": [
       -5.76,
       -5.7,
       -5.63,
       -5.57,
       -5.51,
       -5.44,
       -5.37,
       -5.3,
       -5.23,
       -5.16,
       -5.08,
       -5.0,
       -4.93,
       -4.85,
       -4.77,
       -4.68,
       -4.6,
       -4.51,
       -4.43,
       -4.34,
       -4.25,
       -4.16,
       -4.07,
       -3.98,
       -3.88,
       -3.79,
       -3.69,
       -3.59,
       -3.49,
       -3.39,
       -3.29,
       -3.19,
       -3.09,
       -2.98,
       -2.88,
       -2.77,
       -2.67,
       -2.56,
       -2.45,
       -2.34,
       -2.23,
       -2.12,
       -2.01,
       -1.9,
       -1.79,
       -1.68,
       -1.56,
       -1.45,
      ...
      ...
      3.49,
      3.58,
      3.68,
      3.78
    ]
  },
  {
    "header":{
	       "discipline":0,
	       "disciplineName":"Meteorological products",
	       "gribEdition":2,
	       "gribLength":73503,
	       "center":7,
	       "centerName":"US National Weather Service - NCEP(WMC)",
	       "subcenter":2,
	       "refTime":"2018-06-25T00:00:00.000Z",
	       "significanceOfRT":1,
	       "significanceOfRTName":"Start of forecast",
	       "productStatus":0,
	       "productStatusName":"Operational products",
	       "productType":3,
	       "productTypeName":"Control Forecast products",
	       "productDefinitionTemplate":1,
	       "productDefinitionTemplateName":"Individual ensemble forecast at a point in time",
	       "parameterCategory":2,
	       "parameterCategoryName":"Momentum",
	       "parameterNumber":3,
	       "parameterNumberName":"V-component_of_wind",
	       "parameterUnit":"m.s-1",
	       "genProcessType":4,
	       "genProcessTypeName":"Ensemble Forecast",
	       "forecastTime":0,
	       "surface1Type":103,
	       "surface1TypeName":"Specified height level above ground",
	       "surface1Value":10.0,
	       "surface2Type":255,
	       "surface2TypeName":"Missing",
	       "surface2Value":0.0,
	       "gridDefinitionTemplate":0,
	       "gridDefinitionTemplateName":"Latitude_Longitude",
	       "numberPoints":65160,
	       "shape":6,
	       "shapeName":"Earth spherical with radius of 6,371,229.0 m",
	       "gridUnits":"degrees",
	       "resolution":48,
	       "winds":"true",
	       "scanMode":0,
	       "nx":360,
	       "ny":181,
	       "basicAngle":0,
	       "subDivisions":0,
	       "lo1":0.0,
	       "la1":90.0,
	       "lo2":359.0,
	       "la2":-90.0,
	       "dx":1.0,
	       "dy":1.0
	   },
	"meta": { "date": "2022.01.11 00:00:00 UTC" },
    "data": [
      3.39,
	  3.49,
      3.58,
      3.68,
      3.78,
      3.87,
      3.97,
      4.06,
      4.15,
      4.24,
      4.33,
      4.42,
      4.51,
      4.59,
      4.68,
      4.76,
      4.84,
      4.92,
      5.0,
      5.08,
      5.15,
      5.22,
      5.3,
      5.37,
      5.43,
      5.5,
      5.57,
      5.63,
      5.69,
      5.75,
      5.81,
      5.87,
      5.92,
      5.97,
      6.03,
      6.08,
      6.12,
      6.17,
      6.21,
      6.25,
      ...
      ...
      6.59,
	  6.6,
      6.62,
      6.64,
      6.65,
      6.66,
    ]
  }
]

风场封装 wind.js

/****
 *风场类
 ****/
export var CanvasWindy = function (json, params) {
  //风场json数据
  this.windData = json;
  //可配置参数
  this.viewer = params.viewer;
  this.canvas = params.canvas;
  this.extent = params.extent || []; //风场绘制时的地图范围,范围不应该大于风场数据的范围,顺序:west/east/south/north,有正负区分,如:[110,120,30,36]
  this.canvasContext = params.canvas.getContext("2d"); //canvas上下文
  this.canvasWidth = params.canvasWidth || 300; //画板宽度
  this.canvasHeight = params.canvasHeight || 180; //画板高度
  this.speedRate = params.speedRate || 100; //风前进速率,意思是将当前风场横向纵向分成100份,再乘以风速就能得到移动位置,无论地图缩放到哪一级别都是一样的速度,可以用该数值控制线流动的快慢,值越大,越慢,
  this.particlesNumber = params.particlesNumber || 20000; //初始粒子总数,根据实际需要进行调节
  this.maxAge = params.maxAge || 120; //每个粒子的最大生存周期
  this.frameTime = 1000 / (params.frameRate || 10); //每秒刷新次数,因为requestAnimationFrame固定每秒60次的渲染,所以如果不想这么快,就把该数值调小一些
  this.color = params.color || "#ffffff"; //线颜色,提供几个示例颜色['#14208e','#3ac32b','#e0761a']
  this.lineWidth = params.lineWidth || 1; //线宽度
  //内置参数
  this.initExtent = []; //风场初始范围
  this.calc_speedRate = [0, 0]; //根据speedRate参数计算经纬度步进长度
  this.windField = null;
  this.particles = [];
  this.animateFrame = null; //requestAnimationFrame事件句柄,用来清除操作
  this.isdistory = false; //是否销毁,进行删除操作
  this._init();
};
CanvasWindy.prototype = {
  constructor: CanvasWindy,
  _init: function () {
    var self = this;
    // 创建风场网格
    this.windField = this.createField();
    this.initExtent = [
      this.windField.west,
      this.windField.east,
      this.windField.south,
      this.windField.north,
    ];
    //如果风场创建时,传入的参数有extent,就根据给定的extent,让随机生成的粒子落在extent范围内
    if (this.extent.length != 0) {
      this.extent = [
        Math.max(this.initExtent[0], this.extent[0]),
        Math.min(this.initExtent[1], this.extent[1]),
        Math.max(this.initExtent[2], this.extent[2]),
        Math.min(this.initExtent[3], this.extent[3]),
      ];
    }
    // console.log(this.extent);
    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();
    (function frame() {
      if (!self.isdistory) {
        self.animateFrame = requestAnimationFrame(frame);
        var now = Date.now();
        var delta = now - then;
        if (delta > self.frameTime) {
          then = now - (delta % self.frameTime);
          self.animate();
        }
      } else {
        self.removeLines();
      }
    })();
  },
  //计算经纬度步进长度
  _calcStep: function () {
    var isextent = this.extent.length != 0;
    var calcExtent = isextent ? this.extent : this.initExtent;
    var calcSpeed = this.speedRate;
    this.calc_speedRate = [
      (calcExtent[1] - calcExtent[0]) / calcSpeed,
      (calcExtent[3] - calcExtent[2]) / calcSpeed,
    ];
  },
  //根据现有参数重新生成风场
  redraw: function () {
    window.cancelAnimationFrame(this.animateFrame);
    this.particles = [];
    this._init();
  },
  createField: function () {
    var data = this._parseWindJson();
    return new CanvasWindField(data);
  },
  animate: function () {
    var self = this,
      field = self.windField;
    var nextLng = null,
      nextLat = null,
      uv = null;
    self.particles.forEach(function (particle) {
      if (particle.age <= 0) {
        self.randomParticle(particle);
      }
      if (particle.age > 0) {
        var x = particle.x,
          y = particle.y,
          tlng = particle.tlng,
          tlat = particle.tlat;
        var gridpos = self._togrid(tlng, tlat);
        var tx = gridpos[0];
        var ty = gridpos[1];
        if (!self.isInExtent(tlng, tlat)) {
          particle.age = 0;
        } else {
          uv = field.getIn(tx, ty);
          nextLng = tlng + self.calc_speedRate[0] * uv[0];
          nextLat = tlat + self.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 (self.particles.length <= 0) this.removeLines();
    self._drawLines();
  },
  //粒子是否在地图范围内
  isInExtent: function (lng, lat) {
    var calcExtent = this.initExtent;
    if (
      lng >= calcExtent[0] &&
      lng <= calcExtent[1] &&
      lat >= calcExtent[2] &&
      lat <= calcExtent[3]
    )
      return true;
    return false;
  },
  _resize: function (width, height) {
    this.canvasWidth = width;
    this.canvasHeight = height;
  },
  _parseWindJson: function () {
    var uComponent = null,
      vComponent = null,
      header = null;
      this.windData.forEach(function (record) {
      var type =
        record.header.parameterCategory + "," + record.header.parameterNumber;
      switch (type) {
        case "2,2":
          uComponent = record["data"];
          header = record["header"];
          break;
        case "2,3":
          vComponent = record["data"];
          break;
        default:
          break;
      }
    });
    return {
      header: header,
      uComponent: uComponent,
      vComponent: vComponent,
    };
  },
  removeLines: function () {
    window.cancelAnimationFrame(this.animateFrame);
    this.isdistory = true;
    this.canvas.width = 1;
    document.getElementById("content").removeChild(this.canvas);
  },
  //根据粒子当前所处的位置(棋盘网格位置),计算经纬度,在根据经纬度返回屏幕坐标
  _tomap: function (lng, lat, particle) {
    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;
    }
    // console.log(pos);
    return pos ? [pos.x, pos.y] : null;
  },
  //根据经纬度,算出棋盘格位置
  _togrid: function (lng, lat) {
    var field = this.windField;
    var x =
      ((lng - this.initExtent[0]) / (this.initExtent[1] - this.initExtent[0])) *
      (field.cols - 1);
    var y =
      ((this.initExtent[3] - lat) / (this.initExtent[3] - this.initExtent[2])) *
      (field.rows - 1);
    return [x, y];
  },
  _drawLines: function () {
    var self = this;
    var particles = this.particles;
    this.canvasContext.lineWidth = self.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(function (particle) {
      var movetopos = self._tomap(particle.lng, particle.lat, particle);
      var linetopos = self._tomap(particle.tlng, particle.tlat, particle);
      // console.log(movetopos,linetopos);
      if (movetopos != null && linetopos != null) {
        self.canvasContext.moveTo(movetopos[0], movetopos[1]);
        self.canvasContext.lineTo(linetopos[0], linetopos[1]);
      }
    });
    this.canvasContext.stroke();
  },
  //随机数生成器(小数)
  fRandomByfloat: function (under, over) {
    return under + Math.random() * (over - under);
  },
  //随机数生成器(整数)
  fRandomBy: function (under, over) {
    switch (arguments.length) {
      case 1:
        return parseInt(Math.random() * under + 1);
      case 2:
        return parseInt(Math.random() * (over - under + 1) + under);
      default:
        return 0;
    }
  },
  //根据当前风场extent随机生成粒子
  randomParticle: function (particle) {
    var safe = 30,
      x = -1,
      y = -1,
      lng = null,
      lat = null;
    var hasextent = this.extent.length != 0;
    var calc_extent = hasextent ? this.extent : this.initExtent;
    do {
      try {
        if (hasextent) {
          var pos_x = this.fRandomBy(0, this.canvasWidth);
          var pos_y = this.fRandomBy(0, this.canvasHeight);
          var cartesian = this.viewer.camera.pickEllipsoid(
            new Cesium.Cartesian2(pos_x, pos_y),
            this.viewer.scene.globe.ellipsoid
          );
          var cartographic =
            this.viewer.scene.globe.ellipsoid.cartesianToCartographic(
              cartesian
            );
          if (cartographic) {
            //将弧度转为度的十进制度表示
            lng = Cesium.Math.toDegrees(cartographic.longitude);
            lat = Cesium.Math.toDegrees(cartographic.latitude);
          }
        } else {
          lng = this.fRandomByfloat(calc_extent[0], calc_extent[1]);
          lat = this.fRandomByfloat(calc_extent[2], calc_extent[3]);
        }
      } catch (e) {}
      if (lng) {
        var gridpos = this._togrid(lng, lat);
        x = gridpos[0];
        y = gridpos[1];
      }
    } while (this.windField.getIn(x, y)[2] <= 0 && safe++ < 30);
    var field = this.windField;
    var uv = field.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;
  },
};
 
/****
 *棋盘类
 *根据风场数据生产风场棋盘网格
 ****/
var CanvasWindField = function (obj) {
  this.west = null;
  this.east = null;
  this.south = null;
  this.north = null;
  this.rows = null;
  this.cols = null;
  this.dx = null;
  this.dy = null;
  this.unit = null;
  this.date = null;
 
  this.grid = null;
  this._init(obj);
};
CanvasWindField.prototype = {
  constructor: CanvasWindField,
  _init: function (obj) {
    var header = obj.header,
    uComponent = obj["uComponent"],
    vComponent = obj["vComponent"];
 
    // debugger;
    this.west = +header["lo1"];
    this.east = +header["lo2"];
    this.south = +header["la2"];
    this.north = +header["la1"];
    this.rows = +header["ny"];
    this.cols = +header["nx"];
    this.dx = +header["dx"];
    this.dy = +header["dy"];
    this.unit = header["parameterUnit"];
    this.date = header["refTime"];
 
    this.grid = [];
    var k = 0,
      rows = null,
      uv = null;
    for (var j = 0; j < this.rows; j++) {
      rows = [];
      for (var i = 0; i < this.cols; i++, k++) {
        uv = this._calcUV(uComponent[k], vComponent[k]);
        rows.push(uv);
      }
      this.grid.push(rows);
    }
  },
  _calcUV: function (u, v) {
    return [+u, +v, Math.sqrt(u * u + v * v)];
  },
  //二分差值算法计算给定节点的速度
  _bilinearInterpolation: function (x, y, g00, g10, g01, g11) {
    var rx = 1 - x;
    var ry = 1 - y;
    var a = rx * ry,
      b = x * ry,
      c = rx * y,
      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);
  },
  getIn: function (x, y) {
    if (x < 0 || x >= 359 || y >= 180) {
      return [0, 0, 0];
    }
    var x0 = Math.floor(x),
      y0 = Math.floor(y),
      x1,
      y1;
    if (x0 === x && y0 === y) return this.grid[y][x];
 
    x1 = x0 + 1;
    y1 = y0 + 1;
 
    var g00 = this.getIn(x0, y0),
      g10 = this.getIn(x1, y0),
      g01 = this.getIn(x0, y1),
      g11 = this.getIn(x1, y1);
    var result = null;
    try {
      result = this._bilinearInterpolation(x - x0, y - y0, g00, g10, g01, g11);
    } catch (e) {
      console.log(x, y);
    }
    return result;
  },
  isInBound: function (x, y) {
    if (x >= 0 && x < this.cols - 1 && y >= 0 && y < this.rows - 1) return true;
    return false;
  },
};
 
/****
 *粒子对象
 ****/
var CanvasParticle = function () {
  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; //粒子移动速度,可以根据速度渲染不同颜色
};

风场显示

import {CanvasWindy} from '../server/wind.js'

// 风场显示
showWindy() {
  $('#windycanvas').show();
},
//风场隐藏
hideWindy() {
  $('#windycanvas').hide();
},
//设置地图操作,便于使用风场
initWindy(windy) {
  /**
  *如果处于全球状态就设置为[](只要有一个角获取不到坐标就表示全球状态,实时计算)
  **/
  var globalExtent = [];

  //获取当前三维窗口左上、右上、左下、右下坐标
  var getCesiumExtent = function () {
    var canvaswidth = window.innerWidth,
      canvasheight = window.innerHeight - 50;

    var left_top_pt = new Cesium.Cartesian2(0, 0);
    var left_bottom_pt = new Cesium.Cartesian2(0, canvasheight);
    var right_top_pt = new Cesium.Cartesian2(canvaswidth, 0);
    var right_bottom_pt = new Cesium.Cartesian2(canvaswidth, canvasheight);

    var pick1 = viewer.scene.globe.pick(viewer.camera.getPickRay(left_top_pt), viewer.scene);
    var pick2 = viewer.scene.globe.pick(viewer.camera.getPickRay(left_bottom_pt), viewer.scene);
    var pick3 = viewer.scene.globe.pick(viewer.camera.getPickRay(right_top_pt), viewer.scene);
    var pick4 = viewer.scene.globe.pick(viewer.camera.getPickRay(right_bottom_pt), viewer.scene);
    if (pick1 && pick2 && pick3 && pick4) {
      //将三维坐标转成地理坐标---只需计算左下右上的坐标即可
      var geoPt1 = viewer.scene.globe.ellipsoid.cartesianToCartographic(pick2);
      var geoPt2 = viewer.scene.globe.ellipsoid.cartesianToCartographic(pick3);
      //地理坐标转换为经纬度坐标
      var point1 = [geoPt1.longitude / Math.PI * 180, geoPt1.latitude / Math.PI * 180];
      var 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 = [];
    }
  };
  // 开启监听器--无论对当前地球做的任何操作都会监听到
  let postRender = viewer.scene.postRender.addEventListener(() => {
    getCesiumExtent();
  });
  var refreshTimer = -1;
  var mouse_down = false;
  var mouse_move = false;
  var handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
  //鼠标滚动、旋转后是否需要重新生成风场---如果需要,打开以下注释--旋转或者移动到北半球的时候计算会有问题
  handler.setInputAction(function (e) {
    clearTimeout(refreshTimer);
    //每次鼠标滚轮旋转后不显示风场
    // this.hideWindy();
    setTimeout(function () {
      windy.extent = globalExtent;
      windy.redraw();
      // this.showWindy();
      $('#windycanvas').show();
    }, 200);
  }, Cesium.ScreenSpaceEventType.WHEEL);
  //鼠标左键、右键按下
  handler.setInputAction(function (e) {
    mouse_down = true;
  }, Cesium.ScreenSpaceEventType.LEFT_DOWN);
  handler.setInputAction(function (e) {
    mouse_down = true;
  }, Cesium.ScreenSpaceEventType.RIGHT_DOWN);
  //鼠标移动
  handler.setInputAction(function (e) {
    if (mouse_down) {
      //每次鼠标移动后不显示风场
      // this.hideWindy();
      mouse_move = true;
    }
  }, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
  //鼠标左键、右键抬起
  handler.setInputAction(function (e) {
    if (mouse_down && mouse_move) {
      windy.extent = globalExtent;
      windy.redraw();
    }
    // this.showWindy();
    $('#windycanvas').show();
    mouse_down = false;
    mouse_move = false;
  }, Cesium.ScreenSpaceEventType.LEFT_UP);
  handler.setInputAction(function (e) {
    if (mouse_down && mouse_move) {
      windy.extent = globalExtent;
      windy.redraw();
    }
    this.showWindy();
    mouse_down = false;
    mouse_move = false;
  }, Cesium.ScreenSpaceEventType.RIGHT_UP);
},
//调整风场canvas大小
resizeCanvas(windy) {
  if (windycanvas == null) {
    return;
  }
  windycanvas.width = window.innerWidth;
  windycanvas.height = window.innerHeight;
  // console.log(windycanvas.width, windycanvas.height);
  if (windy) {
    windy._resize(windycanvas.width, windycanvas.height);
  }
},
//获取风场数据、生成风场
getwindData(){
  let Data_Promise = axios(`data.json`)
  Data_Promise.then((res) => {
    console.log('风的格点预报:', res.data);
    //设置canvas
    var windycanvas = document.createElement('canvas');
    windycanvas.setAttribute("id", "windycanvas");
    windycanvas.style.position = 'fixed'
    windycanvas.style["pointer-events"] = "none";
    windycanvas.style["z-index"] = 10;
    windycanvas.style["bottom"] = 0;
    windycanvas.style["top"] = 0;
    windycanvas.style["left"] = 0;
    windycanvas.style["right"] = 0;
    document.getElementById('earthView').appendChild(windycanvas);

    this.resizeCanvas(windy);
    window.onresize = this.resizeCanvas(windy);

    //风场参数设置
    let params = {
      viewer: viewer,
      canvas: windycanvas,
      canvasWidth: window.innerWidth,
      canvasHeight: window.innerHeight,
      speedRate: 5000,
      particlesNumber: 5000,
      maxAge: 120,
      frameRate: 10,
      color: '#3a92ff',
      lineWidth: 3,
    };

    var windy;
    setTimeout(() => {
      windy = new CanvasWindy(res.data, params);
      this.showWindy()
      //初始化风场设置
      this.initWindy(windy)
      this.$message.success('风场已经执行'),
      this.loading = false;
      this.od1Visible = false;
    }, 2000);
  })
}

其他

如果想要简单绘制一个风向风速情况,可以如下处理:
在Cesium中加载风场需要先准备好相关的数据,包括风速和方向的数据。以下是一个简单的加载风场的过程:

  1. 准备数据:将风速和方向的数据保存在对应的数组中,通常使用网格格式来表示。例如,可以使用二维数组来表示风速和方向在地球表面上的不同位置。

  2. 创建实体:在Cesium中,可以使用Entity对象来表示一个可视化的实体。可以使用BillboardModelPolyline等不同类型的图元来表示风场。我们可以在实体的position属性上指定风场的位置,然后在实体的billboardmodel属性上指定要显示的图像或模型,例如箭头或风车的图片或模型。

  3. 设置属性: 在实体的属性中,可以设置billboardmodel的颜色、大小、旋转和透明度等属性,以及箭头或风车的方向、角度等属性,使其在地球表面上正确地显示风场。

  4. 添加实体:将实体添加到Cesium的场景中,使用Viewer.entities.add()方法将实体添加到场景中。

以下是一个简单的示例代码,可以将数组中的数据显示为箭头的形式来表示风场:

// 准备数据
var windSpeedData = [...]; // 风速数据
var windDirectionData = [...]; // 风向数据

// 创建实体
var entity = viewer.entities.add({
    position: Cesium.Cartesian3.fromDegrees(longitude, latitude, height),
    billboard: {
        image: 'arrow.png',
        scale: 0.1
    }
});

// 设置属性
var windSpeed = windSpeedData[i][j];
var windDirection = windDirectionData[i][j];
var heading = Cesium.Math.toRadians(90 - windDirection); // 箭头方向为逆时针旋转90度
var pitch = 0;
var roll = 0;

entity.orientation = Cesium.Transforms.headingPitchRollQuaternion(position, heading, pitch, roll);
entity.billboard.color = Cesium.Color.fromCssColorString('#FF0000');
entity.billboard.scale = windSpeed / 10; // 根据风速调整箭头大小

注意,上面的示例代码中仅为了演示如何加载风场,实际上需要根据具体的数据格式和需求来适当地修改代码。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/993976.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

A Yet Another Remainder The 2022 ICPC Asia Regionals Online Contest (II)

PTA | 程序设计类实验辅助教学平台 题目大意&#xff1a;有一个n位长的隐藏数x&#xff0c;从高位到低位依次标号为1到n&#xff0c;sum[i][j]表示从第i为开始每j位上的数的和&#xff0c;有q次询问&#xff0c;每次给出一个100以内除了5以外的质数p&#xff0c;问这个数%p等于…

免费版Photoshop2024智能人像磨皮插件

Portraiture是一款智能磨皮插件&#xff0c;为Photoshop和Lightroom添加一键磨皮美化功能&#xff0c;快速对照片中皮肤、头发、眉毛等部位进行美化&#xff0c;无需手动调整&#xff0c;大大提高P图效率。全新4版本&#xff0c;升级AI算法&#xff0c;并独家支持多人及全身模式…

企业架构LNMP学习笔记32

企业架构LB-服务器的负载均衡之LVS实现&#xff1a; 学习目标和内容 1&#xff09;能够了解LVS的工作方式&#xff1b; 2&#xff09;能够安装和配置LVS负载均衡&#xff1b; 3&#xff09;能够了解LVS-NAT的配置方式&#xff1b; 4&#xff09;能够了解LVS-DR的配置方式&…

基于SSM的生鲜电商系统

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

Linux高性能服务器编程 学习笔记 第一章 TCP/IP协议族

现在Internet使用的主流协议族是TCP/IP协议族&#xff0c;它是一个分层、多协议的通信体系。 TCP/IP协议族包含众多协议&#xff0c;我们只详细讨论IP协议和TCP协议&#xff0c;因为它们对编写网络应用程序有最直接的影响。如果想系统学习网络协议&#xff0c;RFC&#xff08;…

LeetCode147之对链表进行插入排序(相关话题:链表)

题目描述 给定单个链表的头 head ,使用 插入排序 对链表进行排序,并返回 排序后链表的头 。 插入排序 算法的步骤: 插入排序是迭代的,每次只移动一个元素,直到所有元素可以形成一个有序的输出列表。每次迭代中,插入排序只从输入数据中移除一个待排序的元素,找到它在序列…

深度解析自然语言处理之篇章分析

在本文中&#xff0c;我们深入探讨了篇章分析的概念及其在自然语言处理&#xff08;NLP&#xff09;领域中的研究主题&#xff0c;以及两种先进的话语分割方法&#xff1a;基于词汇句法树的统计模型和基于BiLSTM-CRF的神经网络模型。 关注TechLead&#xff0c;分享AI全维度知识…

LLM推理优化技术综述:KVCache、PageAttention、FlashAttention、MQA、GQA

LLM推理优化技术综述&#xff1a;KVCache、PageAttention、FlashAttention、MQA、GQA 随着大模型被越来越多的应用到不同的领域&#xff0c;随之而来的问题是应用过程中的推理优化问题&#xff0c;针对LLM推理性能优化有一些新的方向&#xff0c;最近一直在学习和研究&#xf…

react-native实现 TextInput 键盘显示搜索按钮并触发回调

<TextInput returnKeyType"search"returnKeyLabel"搜索"onSubmitEditing{e > {toSearch(keyword);}} /><SearchBarref{serachBarEl}placeholder"请输入"onChangeText{handleChangeSearch}value{search}onSubmitEditing{handleSearch…

群晖NAS教程(二十四)、利用ContainerManager安装jellyfin

群晖NAS教程(二十四)、利用ContainerManager安装jellyfin 一、下载nyamisaka/jellyfin镜像 二、运行jellyfin容器并配置 容器名称可以随便填写 这里映射端口设置为8096&#xff0c;并且映射了两个配置文件夹和一个电影的目录。 点击完成。 这里看到已经运行起来了。 三、jelly…

C# 参数名加冒号,可以打乱参数顺序

今天看到Python有这种语法&#xff0c;参数名后面跟着等号写参数&#xff0c;联想到前几天用到的Serilog&#xff0c;好像有个参数名加冒号的写法&#xff0c;搜索了一下&#xff0c;果真有这种用法。 函数特别大的时候&#xff0c;用这种方法很直观&#xff0c;而且参数可以打…

你真的了解 Docker 日志吗?

目录 前言排查总结 前言 今天服务器发送了磁盘告警通知&#xff0c;于是打开了尘封已久的电脑连上了公司服务器&#xff0c;服务器跑的是一个 Docker Swarm 集群&#xff08;正是集群中的某一台服务器发生告警&#xff09;&#xff0c;告警的服务器上运行了多个游戏后台程序。…

密室逃脱小游戏

欢迎来到程序小院 密室逃脱 玩法&#xff1a; 判断可生存的空间&#xff0c;鼠标点击屏幕进行人物左右移动&#xff0c;躲避闸道进行生存&#xff0c;每进行一次关卡都会有分数统计&#xff0c;赶紧去闯关吧^^。开始游戏https://www.ormcc.com/play/gameStart/176 html <c…

L1 项目概述与Hadoop部署

1.技术栈&#xff1a;HadoopHiveSqoopFlumeAzkaban Flume采集Nginx web服务器上的日志&#xff0c;采集完成后存储到Hadoop的平台&#xff0c;最终存储到HDFS上&#xff0c;处理和分析采用Hive的方式&#xff0c;处理完之后利用Sqoop导出到Mysql中&#xff0c;最终利用一个Java…

开源电商项目 Mall:构建高效电商系统的终极选择

文章目录 Mall 项目概览前台商城系统后台管理系统系统架构图业务架构图 模块介绍后台管理系统 mall-admin商品管理&#xff1a;功能结构图-商品订单管理&#xff1a;功能结构图-订单促销管理&#xff1a;功能结构图-促销内容管理&#xff1a;功能结构图-内容用户管理&#xff1…

日常中出现msvcp140.dll丢失的5个解决方法与msvcp140.dll详细解析

前几天&#xff0c;我在使用电脑时遇到了一个奇怪的问题&#xff1a;打开某些程序时&#xff0c;系统提示找不到msvcp140.dll文件。这让我非常困惑&#xff0c;因为我之前从未遇到过这样的问题。为了解决这个问题&#xff0c;我在网上查找了许多关于解决msvcp140.dll丢失的信息…

IDEA插件Mybatis Log Plugin的安装及其使用教程

版权声明 本文原创作者&#xff1a;谷哥的小弟作者博客地址&#xff1a;http://blog.csdn.net/lfdfhl 插件概述 Mybatis Log Plugin插件用于查看Mybatis所执行的完整SQL语句。在此教程中详细介绍IDEA插件Mybatis Log Plugin的安装及其使用。 安装过程 请搜索并安装Mybatis …

kafka学习-基本概念与简单实战

目录 1、核心概念 消息和批次 Topic和Partition Replicas Offset broker和集群 生产者和消费者 2、开发实战 2.1、消息发送 介绍 代码实现 2.2、消息消费 介绍 代码实现 2.3、SpringBoot Kafka pom application.yaml KafkaConfig producer consumer 1、核心…

C++项目实战——基于多设计模式下的同步异步日志系统-③-前置知识补充-设计模式

文章目录 专栏导读六大原则单例模式饿汉模式懒汉模式 工厂模式简单工厂模式工厂方法模式抽象工厂模式 建造者模式代理模式 专栏导读 &#x1f338;作者简介&#xff1a;花想云 &#xff0c;在读本科生一枚&#xff0c;C/C领域新星创作者&#xff0c;新星计划导师&#xff0c;阿…

LP(六十九)智能文档助手升级

本文在笔者之前研发的大模型智能文档问答项目中&#xff0c;开发更进一步&#xff0c;支持多种类型文档和URL链接&#xff0c;支持多种大模型接入&#xff0c;且使用更方便、高效。 项目介绍 在文章NLP&#xff08;六十一&#xff09;使用Baichuan-13B-Chat模型构建智能文档中…