前言
效果实现需要用到一款开源插件:leaflet-velocity,
来自Github:https://github.com/onaci/leaflet-velocity
演示地址:https://onaci.github.io/leaflet-velocity/
参考实现:https://www.windy.com/
本文只是针对个人实际项目做出的开发记录,无其他参考意义。
一、部分代码摘录
(一)velocity插件代码.js
风向粒子参数设置
// 粒子颜色数组为设置或只设置单值时,以下两个设置无效
// 粒子最小速度时颜色设置
var MIN_VELOCITY_INTENSITY = params.minVelocity || 0;
// 粒子最大速度时颜色设置
var MAX_VELOCITY_INTENSITY = params.maxVelocity || 20;
// 风速比例,实际效果为粒子的拖尾长度
var VELOCITY_SCALE = (params.velocityScale || 0.0015) * (Math.pow(window.devicePixelRatio, 1 / 3) || 1);
// 粒子生命周期
var MAX_PARTICLE_AGE = params.particleAge || 90;
// 粒子的宽度
var PARTICLE_LINE_WIDTH = params.lineWidth || 1.5;
// 粒子的密度
var PARTICLE_MULTIPLIER = params.particleMultiplier || 0.9 / 300;
// 用于移动端减少粒子
ar PARTICLE_REDUCTION = Math.pow(window.devicePixelRatio, 1 / 3) || 1.6;
// 刷新帧率以及渲染时间,实际影响粒子速度
// 用于控制帧的频率,越大,频率越快,例子速度越快
var FRAME_RATE = params.frameRate || 60;
// 渲染时间
var FRAME_TIME = 1000 / FRAME_RATE;
//不透明度
var OPACITY = 1;
//粒子颜色(单值)
var colorScale = ["rgb(255,255,255)"];
//粒子颜色(数组)
var defaulColorScale = ["rgb(36,104, 180)", "rgb(60,157, 194)", "rgb(128,205,193 )", "rgb(151,218,168 )", "rgb(198,231,181)", "rgb(238,247,217)", "rgb(255,238,159)", "rgb(252,217,125)", "rgb(255,182,100)", "rgb(252,150,75)", "rgb(250,112,52)", "rgb(245,64,32)", "rgb(237,45,28)", "rgb(220,24,32)", "rgb(180,0,35)"];
// 根据风俗设置粒子颜色
var colorScale = params.colorScale || defaulColorScale;
// 无风时默认设置
var NULL_WIND_VECTOR = [NaN, NaN, null];
鼠标事件代码L.Control.Velocity.js
L.Control.Velocity = L.Control.extend({
options: {
position: "bottomleft",
emptyString: "Unavailable",
// Could be any combination of 'bearing' (angle toward which the flow goes) or 'meteo' (angle from which the flow comes)
// and 'CW' (angle value increases clock-wise) or 'CCW' (angle value increases counter clock-wise)
angleConvention: "bearingCCW",
showCardinal: false,
// Could be 'm/s' for meter per second, 'k/h' for kilometer per hour, 'mph' for miles per hour or 'kt' for knots
speedUnit: "m/s",
directionString: "Direction",
speedString: "Speed",
onAdd: null,
onRemove: null
},
onAdd: function onAdd(map) {
this._container = L.DomUtil.create("div", "leaflet-control-velocity");
L.DomEvent.disableClickPropagation(this._container);
map.on("mousemove", this._onMouseMove, this);
this._container.innerHTML = this.options.emptyString;
if (this.options.leafletVelocity.options.onAdd) this.options.leafletVelocity.options.onAdd();
return this._container;
},
onRemove: function onRemove(map) {
map.off("mousemove", this._onMouseMove, this);
if (this.options.leafletVelocity.options.onRemove) this.options.leafletVelocity.options.onRemove();
},
vectorToSpeed: function vectorToSpeed(uMs, vMs, unit) {
var velocityAbs = Math.sqrt(Math.pow(uMs, 2) + Math.pow(vMs, 2)); // Default is m/s
if (unit === "k/h") {
return this.meterSec2kilometerHour(velocityAbs);
} else if (unit === "kt") {
return this.meterSec2Knots(velocityAbs);
} else if (unit === "mph") {
return this.meterSec2milesHour(velocityAbs);
} else {
return velocityAbs;
}
},
vectorToDegrees: function vectorToDegrees(uMs, vMs, angleConvention) {
// Default angle convention is CW
if (angleConvention.endsWith("CCW")) {
// vMs comes out upside-down..
vMs = vMs > 0 ? vMs = -vMs : Math.abs(vMs);
}
var velocityAbs = Math.sqrt(Math.pow(uMs, 2) + Math.pow(vMs, 2));
var velocityDir = Math.atan2(uMs / velocityAbs, vMs / velocityAbs);
var velocityDirToDegrees = velocityDir * 180 / Math.PI + 180;
if (angleConvention === "bearingCW" || angleConvention === "meteoCCW") {
velocityDirToDegrees += 180;
if (velocityDirToDegrees >= 360) velocityDirToDegrees -= 360;
}
return velocityDirToDegrees;
},
degreesToCardinalDirection: function degreesToCardinalDirection(deg) {
var cardinalDirection = '';
if (deg >= 0 && deg < 11.25 || deg >= 348.75) {
cardinalDirection = 'N';
} else if (deg >= 11.25 && deg < 33.75) {
cardinalDirection = 'NNW';
} else if (deg >= 33.75 && deg < 56.25) {
cardinalDirection = 'NW';
} else if (deg >= 56.25 && deg < 78.75) {
cardinalDirection = 'WNW';
} else if (deg >= 78.25 && deg < 101.25) {
cardinalDirection = 'W';
} else if (deg >= 101.25 && deg < 123.75) {
cardinalDirection = 'WSW';
} else if (deg >= 123.75 && deg < 146.25) {
cardinalDirection = 'SW';
} else if (deg >= 146.25 && deg < 168.75) {
cardinalDirection = 'SSW';
} else if (deg >= 168.75 && deg < 191.25) {
cardinalDirection = 'S';
} else if (deg >= 191.25 && deg < 213.75) {
cardinalDirection = 'SSE';
} else if (deg >= 213.75 && deg < 236.25) {
cardinalDirection = 'SE';
} else if (deg >= 236.25 && deg < 258.75) {
cardinalDirection = 'ESE';
} else if (deg >= 258.75 && deg < 281.25) {
cardinalDirection = 'E';
} else if (deg >= 281.25 && deg < 303.75) {
cardinalDirection = 'ENE';
} else if (deg >= 303.75 && deg < 326.25) {
cardinalDirection = 'NE';
} else if (deg >= 326.25 && deg < 348.75) {
cardinalDirection = 'NNE';
}
return cardinalDirection;
},
meterSec2Knots: function meterSec2Knots(meters) {
return meters / 0.514;
},
meterSec2kilometerHour: function meterSec2kilometerHour(meters) {
return meters * 3.6;
},
meterSec2milesHour: function meterSec2milesHour(meters) {
return meters * 2.23694;
},
_onMouseMove: function _onMouseMove(e) {
var self = this;
var pos = this.options.leafletVelocity._map.containerPointToLatLng(L.point(e.containerPoint.x, e.containerPoint.y));
var gridValue = this.options.leafletVelocity._windy.interpolatePoint(pos.lng, pos.lat);
var htmlOut = "";
if (gridValue && !isNaN(gridValue[0]) && !isNaN(gridValue[1]) && gridValue[2]) {
var deg = self.vectorToDegrees(gridValue[0], gridValue[1], this.options.angleConvention);
var cardinal = this.options.showCardinal ? " (".concat(self.degreesToCardinalDirection(deg), ") ") : '';
htmlOut = "<strong> ".concat(this.options.velocityType, " ").concat(this.options.directionString, ": </strong> ").concat(deg.toFixed(2), "\xB0").concat(cardinal, ", <strong> ").concat(this.options.velocityType, " ").concat(this.options.speedString, ": </strong> ").concat(self.vectorToSpeed(gridValue[0], gridValue[1], this.options.speedUnit).toFixed(2), " ").concat(this.options.speedUnit);
} else {
htmlOut = this.options.emptyString;
}
self._container.innerHTML = htmlOut;
}
});
L.Map.mergeOptions({
positionControl: false
});
L.Map.addInitHook(function () {
if (this.options.positionControl) {
this.positionControl = new L.Control.MousePosition();
this.addControl(this.positionControl);
}
});
L.CanvasLayer.js
{...}
L.VelocityLayer.js
{...}
wind.js
(二)插件使用示例代码
velocity.js
import '../leaflet-velocity'
import '../leaflet-velocity.css'
import '../windGlobal.json'
export function createVelocityWindLayer(map) {
var globalWindData = windGlobal
var velocityLayer = L.velocityLayer({
displayValues: true, // 数据显示事件
data: globalWindData,
maxVelocity: 15
})
map.addLayer(velocityLayer)
}
wind.vue
import {createVelocityWindLayer} from '../velocity.js'
export default {
components: {
},
mounted() {
this.getMap()
},
methods: {
// 生成地图
getMap() {
createVelocityWindLayer(this.map)
}
}
二、leaflet-velocity数据源格式
leaflet-velocity插件数据源为json格式数据,以下为部分必要数据结构
[
{
"header": {
// 时间
"refTime": "2022-05-20T12:00:00",
// 配置了数据记录内容,风力数据默认为2
"parameterCategory": 2,
// 风向数据默认:Momentum
"parameterCategoryName": "Momentum",
// 记录了数据方向:U向为2,V向为3
"parameterNumber": 2,
// 风力方向,U方向为经度方向的风力值,以正东为正方向
"parameterNumberName": "2-component_of_wind",
// 单位
"parameterUnit": "m.s-1",
// 数据点数量,data中风力数值的个数,与dx和dy有关
"numberPoints": 65160,
//栅格数量和步长以经纬度为标准划分,当步长为1时,直接划分经纬度,即划分为360*181=65160个栅格
//横向划分栅格数量
"nx": 360,
//纵向划分栅格数量
"ny": 181,
//横向步长
"dx": 1,
// 纵向步长
"dy": 1,
// 横向起点
"lo1": 0.0,
// 纵向起点
"la1": 90.0,
// 横向终点,根据步长有所不同
"lo2": 359.0,
// 纵向终点
"la2": -90
//velocity插件经度方向计算时按由西向东计算,纬度方向按由北向南计算
},
// data数量与numberPoints一致
"data": [...]
},
{
"header": {
"refTime": "2022-05-20T12:00:00",
"parameterCategory": 2,
"parameterCategoryName": "Momentum",
"parameterNumber": 3,
"parameterNumberName": "3-component_of_wind",
"parameterUnit": "m.s-1",
"numberPoints": 65160,
"nx": 360,
"ny": 181,
"dx": 1,
"dy": 1,
"lo1": 0.0,
"la1": 90.0,
"lo2": 359.0,
"la2": -90.0
},
"data": [...]
}
]
本文介绍了如何使用开源插件leaflet-velocity在web前端实现风场动态粒子效果。详细讲述了风向粒子参数设置、插件的代码示例,以及数据源的json格式。文章还提供了参考实现链接和相关资源。
1万+

被折叠的 条评论
为什么被折叠?



