
Cesium+vue 制作风流场
vue
vuejs/vue: 是一个用于构建用户界面的 JavaScript 框架,具有简洁的语法和丰富的组件库,可以用于开发单页面应用程序和多页面应用程序。
项目地址:https://gitcode.com/gh_mirrors/vu/vue

·
项目制作大气污染相关模块需要知道风的走向,提出风流场的需求,在此记录。
引用风场类
/****
*风场类
****/
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; //粒子移动速度,可以根据速度渲染不同颜色
};
调用类、设置类:
//风场显示
const showWindy = function () {
$('#windycanvas').show();
};
//风场隐藏
const hideWindy = function () {
$('#windycanvas').hide();
};
//设置地图操作,便于使用风场
const 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) {
console.log(windy);
clearTimeout(refreshTimer);
//每次鼠标滚轮旋转后不显示风场
// hideWindy();
setTimeout(function () {
windy.extent = globalExtent;
windy.redraw();
showWindy();
}, 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) {
//每次鼠标移动后不显示风场
// hideWindy();
mouse_move = true;
}
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
//鼠标左键、右键抬起
handler.setInputAction(function (e) {
if (mouse_down && mouse_move) {
windy.extent = globalExtent;
windy.redraw();
}
showWindy();
mouse_down = false;
mouse_move = false;
}, Cesium.ScreenSpaceEventType.LEFT_UP);
handler.setInputAction(function (e) {
if (mouse_down && mouse_move) {
windy.extent = globalExtent;
windy.redraw();
}
showWindy();
mouse_down = false;
mouse_move = false;
}, Cesium.ScreenSpaceEventType.RIGHT_UP);
}
//调整风场canvas大小
const resizeCanvas = function (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);
}
};
//获取风场数据、生成风场
let getwindData = () => {
// let Data_Promise = Service(`http://139.9.141.68:11111/weather_data/sn/grid/20220818180000/108.5000/109.0000/33.5000/34.0000`)
let Data_Promise = axios(`/data/jsonData/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('cesiumContainer').appendChild(windycanvas);
resizeCanvas(windy);
window.onresize = resizeCanvas;
//风场参数设置
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);
showWindy()
//初始化风场设置
initWindy(windy)
console.log('风场已经执行');
}, 2000);
})
}
vue
vuejs/vue: 是一个用于构建用户界面的 JavaScript 框架,具有简洁的语法和丰富的组件库,可以用于开发单页面应用程序和多页面应用程序。
项目地址:https://gitcode.com/gh_mirrors/vu/vue
风场数据格式:
[
{
"header": {
"productDefinitionTemplate": 0,
"surface1Type": 100,
"productStatus": 0,
"disciplineName": "Meteorological products",
"subDivisions": 0,
"winds": "true",
"parameterNumberName": "U-component_of_wind",
"discipline": 0,
"surface2Value": 0,
"significanceOfRTName": "Start of forecast",
"surface2TypeName": "Missing",
"surface2Type": 255,
"productTypeName": "Forecast products",
"dx": 0.25,
"dy": 0.25,
"numberPoints": 62101,
"productStatusName": "Operational products",
"gribEdition": 2,
"parameterUnit": "m.s-1",
"forecastTime": 9,
"refTime": "2022.01.11 00:00:00 UTC",
"productType": 1,
"genProcessTypeName": "Forecast",
"scanMode": 0,
"genProcessType": 2,
"gribLength": 132662,
"significanceOfRT": 1,
"productDefinitionTemplateName": "Analysis/forecast at horizontal level/layer at a point in time",
"centerName": "US National Weather Service - NCEP(WMC)",
"la1": 55.0,
"la2": 0.0,
"lo2": 140.0,
"gridUnits": "degrees",
"surface1Value": 50000,
"shapeName": "Earth spherical with radius of 6,371,229.0 m",
"surface1TypeName": "Isobaric surface",
"nx": 281,
"ny": 221,
"lo1": 70.0,
"basicAngle": 0,
"gridDefinitionTemplate": 0,
"shape": 6,
"parameterNumber": 2,
"subcenter": 0,
"parameterCategoryName": "Momentum",
"parameterCategory": 2,
"center": 7,
"gridDefinitionTemplateName": "Latitude_Longitude",
"resolution": 48
},
"meta": { "date": "2022.01.11 00:00:00 UTC" },
"data": [
"0.72",
"0.33",
"0.04",
"-0.19",
"-0.30",
"-0.44",
"-0.27",
"-0.18",
"-0.24",
"0.39",
"0.75",
"0.38",
"0.28",
...
...
"-3.79",
"-3.67",
"-3.62",
"-3.91"
]
}
]
推荐内容
阅读全文
AI总结




vuejs/vue: 是一个用于构建用户界面的 JavaScript 框架,具有简洁的语法和丰富的组件库,可以用于开发单页面应用程序和多页面应用程序。
最近提交(Master分支:8 个月前 )
9e887079
[skip ci] 6 个月前
73486cb5
* chore: fix link broken
Signed-off-by: snoppy <michaleli@foxmail.com>
* Update packages/template-compiler/README.md [skip ci]
---------
Signed-off-by: snoppy <michaleli@foxmail.com>
Co-authored-by: Eduardo San Martin Morote <posva@users.noreply.github.com> 10 个月前
更多推荐
相关推荐
查看更多
vue

Vue.js 是一款构建用户界面的 JavaScript 框架,以其简洁的语法和丰富的组件库著称,适用于开发单页面及多页面应用程序。
vue

Javascript Powered forms and JSON form builder for Vue.js
vue

A flexible icon family for Vue
热门开源项目
活动日历
查看更多
直播时间 2025-04-25 15:00:00


直播时间 2025-04-23 19:00:00

GitTalk:国内首个微服务编排框架Juggle实战解析
直播时间 2025-04-22 18:31:56

字节AI 黑科技!从 Manus Agent 入门 Eino
直播时间 2025-04-09 14:34:18

樱花限定季|G-Star校园行&华中师范大学专场
直播时间 2025-04-07 14:51:20

樱花限定季|G-Star校园行&华中农业大学专场
所有评论(0)