ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

Leaflet风向粒子资源:js与json实现动态风场可视化

Leaflet风向粒子资源:js与json实现动态风场可视化 简介这份资源面向需要在 Web 地图中呈现动态风场的开发者尤其是使用 Leaflet 构建气象可视化页面的前端与 GIS 学习者。包内共 3 个文件包含 1 个 js 插件脚本、1 个 css 样式文件和 1 个 json 数据文件压缩包约 231KB分别承担粒子动画逻辑、图层视觉样式与全球风向风速网格数据的职责。其中 js 插件借助 D3 解析流体动力学数据将风速与风向转化为直观的粒子流动效果css 用于调整粒子大小、颜色与透明度json 则以网格形式存储各点位的风向风速信息加载后即可驱动图层动态渲染。目前已有 873 人学习下载适合希望快速搭建风向粒子演示、理解数据驱动地图渲染流程的读者参考也可作为气象类地图应用的原型素材。1. 从一张“会呼吸”的风场图说起leaflet 风向粒子资源能干什么如果你在地图项目里见过那种密密麻麻、顺着风向流动的白色粒子线第一反应大概率是“这玩意儿怎么做的”。它不像普通 Marker 那样一个个点上去也不像热力图那样铺一层色块而是用成千上万个粒子在 canvas 上实时演算把风向、风速用流动轨迹直接画出来。这套 leaflet 风向粒子调用的 js 和 json 资源解决的就是这个问题它把 leaflet-velocity 这类粒子图层所需的脚本文件和风场数据文件打包在一起让你不用从零去啃 GRIB 或 NetCDF 气象格式直接拿 json 就能在 Leaflet 地图上叠一层动态风场。适合谁用做气象可视化、环境监测大屏、物流航线示意、甚至游戏地图氛围的开发者都能直接上手。尤其是那些已经用 Leaflet 搭好底图、只想加一层风场动效的前端这份资源省掉了数据转换和图层适配的重复劳动。json 里存的是规则网格上的 U/V 分量js 负责把分量插值成粒子运动两者配合就是一套能跑的风场渲染方案。2. 资源构成与运行链路js 脚本和 json 数据怎么对上2.1 目录里到底放了什么拿到这份资源先别急着往项目里塞。我一般会先扫一眼文件构成因为 leaflet-velocity 对数据格式和脚本加载顺序都有要求放错位置就是白屏。典型的结构是这样leaflet-velocity/ ├── leaflet-velocity.js # 粒子图层核心脚本 ├── leaflet-velocity.css # 图层控件与 canvas 样式 ├── data/ │ ├── wind-global.json # 全球风场 U/V 分量数据 │ └── wind-region.json # 区域高分辨率风场数据 └── demo/ └── index.html # 最小可运行示例leaflet-velocity.js是主脚本内部实现了粒子生成、速度插值、canvas 重绘和动画循环。leaflet-velocity.css管的是图层切换按钮和 canvas 容器的定位少了它粒子会盖住地图控件。data目录下的 json 才是重点它们不是随便的键值对而是有固定 schema 的网格数据。2.2 json 数据的字段含义风场 json 的结构直接决定粒子能不能画出来。常见做法是采用类似下面的格式{ header: { parameterCategory: 2, parameterNumber: 2, nx: 360, ny: 181, lo1: 0, la1: 90, lo2: 359, la2: -90, dx: 1, dy: 1 }, data: [ { header: { parameterNumber: 2, parameterCategory: 2 }, data: [ ... ] }, { header: { parameterNumber: 3, parameterCategory: 2 }, data: [ ... ] } ] }这里header描述网格范围nx、ny是经纬方向格点数lo1、la1是起始经纬度dx、dy是步长。data数组里通常放两组第一组对应 U 分量东西向风速第二组对应 V 分量南北向风速。leaflet-velocity 会按parameterNumber区分2 是 U3 是 V。如果 json 里只有一组数据粒子就只会朝一个方向跑看起来像被风吹歪的直线。提示json 里的data数组长度必须等于nx * ny顺序一般是从北到南、从西到东。顺序错了粒子会出现在错误经纬度上排查时优先核对这个。2.3 最小运行示例把资源放进项目后一个能跑起来的页面长这样!DOCTYPE html html head meta charsetutf-8 link relstylesheet hrefhttps://unpkg.com/leaflet1.9.4/dist/leaflet.css / link relstylesheet href./leaflet-velocity.css / style #map { width: 100%; height: 600px; } /style /head body div idmap/div script srchttps://unpkg.com/leaflet1.9.4/dist/leaflet.js/script script src./leaflet-velocity.js/script script // 初始化底图 var map L.map(map).setView([35, 105], 4); L.tileLayer(https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png).addTo(map); // 加载风场 json 并创建粒子图层 fetch(./data/wind-global.json) .then(function (res) { return res.json(); }) .then(function (data) { var velocityLayer L.velocityLayer({ displayValues: true, // 鼠标悬停显示风速风向 displayOptions: { velocityType: Global Wind, position: bottomleft, emptyString: 无风场数据, angleConvention: bearingCCW, speedUnit: m/s }, data: data, maxVelocity: 15, // 最大风速影响粒子颜色映射 colorScale: [#3288bd, #66c2a5, #abdda4, #e6f598, #fee08b, #fdae61, #f46d43], particleAge: 90, // 粒子寿命帧数 lineWidth: 1.2, particleMultiplier: 1 / 300, frameRate: 15, opacity: 0.9 }).addTo(map); }); /script /body /html这段代码的关键在L.velocityLayer的配置。data直接吃 jsonmaxVelocity决定颜色映射上限超过这个值的风速会被截断成最深的颜色。particleAge控制粒子存活帧数太小粒子刚生成就消失太大又会拖尾严重。particleMultiplier是粒子密度系数数值越小粒子越多但性能压力也越大。frameRate是每秒重绘次数15 帧在普通笔记本上比较稳调到 30 会明显吃 CPU。2.4 脚本加载顺序与依赖leaflet-velocity 依赖 Leaflet 的L.Layer扩展机制所以 Leaflet 必须在它之前加载。如果用的是模块打包工具常见做法是import L from leaflet; import leaflet-velocity; import leaflet-velocity/dist/leaflet-velocity.css;注意leaflet-velocity在 npm 上的包名和文件路径可能因版本不同有差异资源里给的 js 是已经构建好的 UMD 版本直接 script 引入最省事。如果项目用 webpack要确保 Leaflet 的全局变量L在 velocity 脚本执行前已经挂到 window 上否则会报L is not defined。3. 核心模块拆解粒子从生成到绘制的完整链路3.1 数据插值把网格点变成任意经纬度的风速json 里存的是离散网格点上的 U/V 分量但粒子可能出现在任意经纬度所以第一步是插值。leaflet-velocity 内部用的是双线性插值核心逻辑可以简化为// 根据经纬度找到网格索引并做双线性插值 function interpolateField(lon, lat, header, uData, vData) { var i Math.floor((lon - header.lo1) / header.dx); var j Math.floor((header.la1 - lat) / header.dy); // 边界保护防止越界 if (i 0 || j 0 || i header.nx - 1 || j header.ny - 1) { return { u: 0, v: 0 }; } var x (lon - header.lo1) / header.dx - i; var y (header.la1 - lat) / header.dy - j; var idx j * header.nx i; // 四个角点加权 var u (1 - x) * (1 - y) * uData[idx] x * (1 - y) * uData[idx 1] (1 - x) * y * uData[idx header.nx] x * y * uData[idx header.nx 1]; var v (1 - x) * (1 - y) * vData[idx] x * (1 - y) * vData[idx 1] (1 - x) * y * vData[idx header.nx] x * y * vData[idx header.nx 1]; return { u: u, v: v }; }header提供网格原点、步长和格点数uData、vData是从 json 里拆出来的两个一维数组。i、j是左上角格点索引x、y是落在格子内的比例。四个角点按距离加权求和得到该经纬度的风速分量。如果粒子跑到网格范围外直接返回零风速粒子就会原地消散不会出现乱飞的情况。3.2 粒子运动速度、寿命与边界处理每个粒子有当前位置、年龄和速度。每一帧根据插值得到的 U/V 更新位置// 粒子单帧更新 function updateParticle(particle, field, dt) { var wind interpolateField(particle.lon, particle.lat, field.header, field.u, field.v); // 经纬度增量0.01 是经验缩放系数让粒子速度看起来自然 particle.lon wind.u * dt * 0.01; particle.lat wind.v * dt * 0.01; particle.age 1; // 超出寿命或跑出地图范围就重置 if (particle.age field.particleAge || particle.lon field.bounds.west || particle.lon field.bounds.east || particle.lat field.bounds.south || particle.lat field.bounds.north) { resetParticle(particle, field); } }dt是帧间隔系数0.01这个缩放值不是固定的风速单位是 m/s 时经纬度变化太快会像闪电太慢又像静止。我一般会先跑一遍看效果再微调这个系数。resetParticle会把粒子随机撒回地图范围内年龄归零这样粒子流看起来是连续的。3.3 canvas 绘制与颜色映射粒子位置算好后用 canvas 的fillRect或lineTo画出来。颜色根据风速大小从colorScale里取// 根据风速取颜色 function getColor(speed, maxVelocity, colorScale) { var index Math.floor((speed / maxVelocity) * (colorScale.length - 1)); index Math.max(0, Math.min(index, colorScale.length - 1)); return colorScale[index]; } // 绘制单帧 function drawFrame(ctx, particles, field) { ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); ctx.globalAlpha field.opacity; particles.forEach(function (p) { var wind interpolateField(p.lon, p.lat, field.header, field.u, field.v); var speed Math.sqrt(wind.u * wind.u wind.v * wind.v); ctx.fillStyle getColor(speed, field.maxVelocity, field.colorScale); var point field.map.latLngToContainerPoint([p.lat, p.lon]); ctx.fillRect(point.x, point.y, field.lineWidth, field.lineWidth); }); }latLngToContainerPoint把经纬度转成 canvas 像素坐标这一步依赖 Leaflet 的地图投影。clearRect每帧清空画布所以粒子是重绘而不是叠加看起来才有流动感。globalAlpha控制整体透明度太低会看不清太高又会盖住底图。3.4 性能取舍粒子数量与帧率的平衡粒子图层最怕的就是卡。particleMultiplier和frameRate是两个主要调节旋钮。我一般会按地图容器面积估算粒子数参数建议值影响particleMultiplier1/300 1/500值越小粒子越多越吃 CPUframeRate12 20越高越流畅但重绘压力大particleAge60 120越大拖尾越长粒子数累积越多lineWidth1 2太粗会糊太细看不清如果页面还有别的动画或图表建议把frameRate压到 12particleMultiplier调到 1/500先保证不卡再谈效果。移动端更要保守粒子数控制在 2000 以内比较稳。4. 避坑与排查风场粒子不显示、乱飞、卡顿的常见原因4.1 粒子完全不显示现象地图出来了但没有任何粒子线。原因通常是 json 没加载成功或者data字段结构不对。先看浏览器 Network 面板确认 json 返回 200 且内容完整。如果 json 里data是空数组或者header缺少nx、ny粒子初始化时就会直接跳过。解决方法是拿资源里的示例 json 对比字段确保data数组长度等于nx * ny且两组数据分别对应 U 和 V。4.2 粒子朝一个方向直线跑现象所有粒子都往同一个方向飞没有旋转和弯曲。原因一般是 json 里只放了一组数据或者两组数据的parameterNumber都是 2。leaflet-velocity 靠parameterNumber区分 U/V2 是 U3 是 V。如果两组都是 2V 分量就是零粒子只受东西向风影响。解决方法是检查 json 第二组数据的parameterNumber是否为 3不是就手动改过来。4.3 粒子位置偏移或跑到地图外现象粒子集中在某个角落或者跑到没有数据的区域还在动。原因通常是header里的lo1、la1和实际数据顺序不匹配。有些气象数据是从南到北存有些是从北到南存la1要对应第一行数据的纬度。如果顺序反了插值出来的风速会整体翻转。解决方法是拿一个已知坐标点比如北京附近手动算一下索引看插值结果是否合理。4.4 页面卡顿、风扇狂转现象粒子图层一开浏览器 CPU 飙升滚动地图明显掉帧。原因是粒子数太多或帧率太高。先看particleMultiplier是不是设成了 1/100 以下再检查frameRate是否超过 25。解决方法是把particleMultiplier调到 1/400 以上frameRate降到 15particleAge降到 80。如果还卡考虑只在缩放级别大于 4 时才显示粒子图层小比例尺下用静态风场箭头代替。4.5 鼠标悬停不显示风速值现象displayValues设为 true但鼠标放上去没有数值提示。原因是displayOptions里的velocityType和position没配对或者 css 文件没引入。leaflet-velocity 的数值面板依赖leaflet-velocity.css里的定位样式少了它面板会跑到页面底部看不见。解决方法是确认 css 已加载并且position用bottomleft或bottomright这类 Leaflet 控件位置。5. 二次开发与改造换数据源、调样式、接业务字段5.1 换成自己的风场数据资源里的 json 是示例数据实际项目多半要接自己的气象接口。改造思路是保持 schema 不变只替换data内容。如果后端返回的是 GRIB 或 NetCDF常见做法是用 Python 的xarray或cfgrib转成 jsonimport xarray as xr import json # 读取 GRIB 文件 ds xr.open_dataset(wind.grib, enginecfgrib) u ds[u10].values.flatten().tolist() v ds[v10].values.flatten().tolist() # 构造 leaflet-velocity 需要的结构 header { nx: ds.dims[longitude], ny: ds.dims[latitude], lo1: float(ds.longitude.min()), la1: float(ds.latitude.max()), dx: float(ds.longitude[1] - ds.longitude[0]), dy: float(ds.latitude[0] - ds.latitude[1]) } result { header: header, data: [ {header: {parameterNumber: 2, parameterCategory: 2}, data: u}, {header: {parameterNumber: 3, parameterCategory: 2}, data: v} ] } with open(wind-custom.json, w) as f: json.dump(result, f)注意la1要取最大纬度因为 leaflet-velocity 默认第一行是北边。dx、dy用相邻格点差值算别硬编码。转出来的 json 直接喂给L.velocityLayer就能用。5.2 自定义颜色和粒子样式colorScale是数组按风速从小到大映射。想改成业务色系直接换数组值就行。比如用蓝色系表示低风速、红色系表示高风速colorScale: [#313695, #4575b4, #74add1, #abd9e9, #e0f3f8, #ffffbf, #fee090, #fdae61, #f46d43, #d73027]lineWidth和opacity也可以按地图底图深浅调。深色底图用亮色粒子、opacity0.8 左右浅色底图用深色粒子、opacity0.6 左右避免盖住路网。5.3 把风速值接到业务面板displayValues只显示鼠标位置的风速如果要在侧边栏展示区域平均风速可以监听地图mousemove事件自己调interpolateFieldmap.on(mousemove, function (e) { var wind interpolateField(e.latlng.lng, e.latlng.lat, header, uData, vData); var speed Math.sqrt(wind.u * wind.u wind.v * wind.v); document.getElementById(wind-speed).innerText speed.toFixed(2) m/s; });这样就能把风场数据和业务指标绑在一起比如超过某个阈值就触发告警样式。5.4 一个我踩过的坑最早做风场图层时我直接把 json 里的data数组当成[u, v]二维数组用结果粒子全挤在左上角。后来才发现 leaflet-velocity 要的是两个独立对象每个对象里再包一层data数组。从那以后我每次接新数据源都先拿一个已知坐标点手动算一遍插值结果确认 U/V 方向没反、网格原点没偏再往地图上叠。这个习惯帮我省了不少返工时间。如果你也在做地图风场可视化这份 leaflet 风向粒子调用的 js 和 json 资源可以直接拿去跑改数据源和调样式都有现成的入口。希望帮到你。本文还有配套的精品资源点击获取
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表