这张酷似《星月夜》的气象数据——风场可视化地图是怎么做的? 在网上搜集了很多篇文章,今天总结出了使用最简单的方法,源码都在这了

核心代码

​
var Windy = function Windy(params) {var MIN_VELOCITY_INTENSITY = params.minVelocity || 0; // velocity at which particle intensity is minimum (m/s)var MAX_VELOCITY_INTENSITY = params.maxVelocity || 10; // velocity at which particle intensity is maximum (m/s)var VELOCITY_SCALE = (params.velocityScale || 0.005) * (Math.pow(window.devicePixelRatio, 1 / 3) || 1); // scale for wind velocity (completely arbitrary--this value looks nice)var MAX_PARTICLE_AGE = params.particleAge || 90; // max number of frames a particle is drawn before regenerationvar PARTICLE_LINE_WIDTH = params.lineWidth || 1; // line width of a drawn particlevar PARTICLE_MULTIPLIER = params.particleMultiplier || 1 / 300; // particle count scalar (completely arbitrary--this values looks nice)var PARTICLE_REDUCTION = Math.pow(window.devicePixelRatio, 1 / 3) || 1.6; // multiply particle count for mobiles by this amountvar FRAME_RATE = params.frameRate || 15;var FRAME_TIME = 1000 / FRAME_RATE; // desired frames per secondvar OPACITY = 0.97;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]; // singleton for no wind in the form: [u, v, magnitude]var builder;var grid;var gridData = params.data;var date;var λ0, φ0, Δλ, Δφ, ni, nj;var setData = function setData(data) {gridData = data;};var setOptions = function setOptions(options) {if (options.hasOwnProperty("minVelocity")) MIN_VELOCITY_INTENSITY = options.minVelocity;if (options.hasOwnProperty("maxVelocity")) MAX_VELOCITY_INTENSITY = options.maxVelocity;if (options.hasOwnProperty("velocityScale")) VELOCITY_SCALE = (options.velocityScale || 0.005) * (Math.pow(window.devicePixelRatio, 1 / 3) || 1);if (options.hasOwnProperty("particleAge")) MAX_PARTICLE_AGE = options.particleAge;if (options.hasOwnProperty("lineWidth")) PARTICLE_LINE_WIDTH = options.lineWidth;if (options.hasOwnProperty("particleMultiplier")) PARTICLE_MULTIPLIER = options.particleMultiplier;if (options.hasOwnProperty("opacity")) OPACITY = +options.opacity;if (options.hasOwnProperty("frameRate")) FRAME_RATE = options.frameRate;FRAME_TIME = 1000 / FRAME_RATE;}; // interpolation for vectors like wind (u,v,m)var bilinearInterpolateVector = function bilinearInterpolateVector(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 [u, v, Math.sqrt(u * u + v * v)];};var createWindBuilder = function createWindBuilder(uComp, vComp) {var uData = uComp.data,vData = vComp.data;return {header: uComp.header,//recipe: recipeFor("wind-" + uComp.header.surface1Value),data: function data(i) {return [uData[i], vData[i]];},interpolate: bilinearInterpolateVector};};var createBuilder = function createBuilder(data) {var uComp = null,vComp = null,scalar = null;data.forEach(function (record) {switch (record.header.parameterCategory + "," + record.header.parameterNumber) {case "1,2":case "2,2":uComp = record;break;case "1,3":case "2,3":vComp = record;break;default:scalar = record;}});return createWindBuilder(uComp, vComp);};var buildGrid = function buildGrid(data, callback) {var supported = true;if (data.length < 2) supported = false;if (!supported) console.log("Windy Error: data must have at least two components (u,v)");builder = createBuilder(data);var header = builder.header;if (header.hasOwnProperty("gridDefinitionTemplate") && header.gridDefinitionTemplate != 0) supported = false;if (!supported) {console.log("Windy Error: Only data with Latitude_Longitude coordinates is supported");}supported = true; // reset for futher checksλ0 = header.lo1;φ0 = header.la1; // the grid's origin (e.g., 0.0E, 90.0N)Δλ = header.dx;Δφ = header.dy; // distance between grid points (e.g., 2.5 deg lon, 2.5 deg lat)ni = header.nx;nj = header.ny; // number of grid points W-E and N-S (e.g., 144 x 73)if (header.hasOwnProperty("scanMode")) {var scanModeMask = header.scanMode.toString(2);scanModeMask = ('0' + scanModeMask).slice(-8);var scanModeMaskArray = scanModeMask.split('').map(Number).map(Boolean);if (scanModeMaskArray[0]) Δλ = -Δλ;if (scanModeMaskArray[1]) Δφ = -Δφ;if (scanModeMaskArray[2]) supported = false;if (scanModeMaskArray[3]) supported = false;if (scanModeMaskArray[4]) supported = false;if (scanModeMaskArray[5]) supported = false;if (scanModeMaskArray[6]) supported = false;if (scanModeMaskArray[7]) supported = false;if (!supported) console.log("Windy Error: Data with scanMode: " + header.scanMode + " is not supported.");}date = new Date(header.refTime);date.setHours(date.getHours() + header.forecastTime); // Scan modes 0, 64 allowed.// http://www.nco.ncep.noaa.gov/pmb/docs/grib2/grib2_table3-4.shtmlgrid = [];var p = 0;var isContinuous = Math.floor(ni * Δλ) >= 360;for (var j = 0; j < nj; j++) {var row = [];for (var i = 0; i < ni; i++, p++) {row[i] = builder.data(p);}if (isContinuous) {// For wrapped grids, duplicate first column as last column to simplify interpolation logicrow.push(row[0]);}grid[j] = row;}callback({date: date,interpolate: interpolate});};/*** Get interpolated grid value from Lon/Lat position* @param λ {Float} Longitude* @param φ {Float} Latitude* @returns {Object}*/var interpolate = function interpolate(λ, φ) {if (!grid) return null;var i = floorMod(λ - λ0, 360) / Δλ; // calculate longitude index in wrapped range [0, 360)var j = (φ0 - φ) / Δφ; // calculate latitude index in direction +90 to -90var fi = Math.floor(i),ci = fi + 1;var fj = Math.floor(j),cj = fj + 1;var row;if (row = grid[fj]) {var g00 = row[fi];var g10 = row[ci];if (isValue(g00) && isValue(g10) && (row = grid[cj])) {var g01 = row[fi];var g11 = row[ci];if (isValue(g01) && isValue(g11)) {// All four points found, so interpolate the value.return builder.interpolate(i - fi, j - fj, g00, g10, g01, g11);}}}return null;};/*** @returns {Boolean} true if the specified value is not null and not undefined.*/var isValue = function isValue(x) {return x !== null && x !== undefined;};/*** @returns {Number} returns remainder of floored division, i.e., floor(a / n). Useful for consistent modulo*          of negative numbers. See http://en.wikipedia.org/wiki/Modulo_operation.*/var floorMod = function floorMod(a, n) {return a - n * Math.floor(a / n);};/*** @returns {Number} the value x clamped to the range [low, high].*/var clamp = function clamp(x, range) {return Math.max(range[0], Math.min(x, range[1]));};/*** @returns {Boolean} true if agent is probably a mobile device. Don't really care if this is accurate.*/var isMobile = function isMobile() {return /android|blackberry|iemobile|ipad|iphone|ipod|opera mini|webos/i.test(navigator.userAgent);};/*** Calculate distortion of the wind vector caused by the shape of the projection at point (x, y). The wind* vector is modified in place and returned by this function.*/var distort = function distort(projection, λ, φ, x, y, scale, wind) {var u = wind[0] * scale;var v = wind[1] * scale;var d = distortion(projection, λ, φ, x, y); // Scale distortion vectors by u and v, then add.wind[0] = d[0] * u + d[2] * v;wind[1] = d[1] * u + d[3] * v;return wind;};var distortion = function distortion(projection, λ, φ, x, y) {var τ = 2 * Math.PI; //    var H = Math.pow(10, -5.2); // 0.00000630957344480193//    var H = 0.0000360;          // 0.0000360°φ ~= 4m  (from https://github.com/cambecc/earth/blob/master/public/libs/earth/1.0.0/micro.js#L13)var H = 5; // ToDo:   Why does this work?var hλ = λ < 0 ? H : -H;var hφ = φ < 0 ? H : -H;var pλ = project(φ, λ + hλ);var pφ = project(φ + hφ, λ); // Meridian scale factor (see Snyder, equation 4-3), where R = 1. This handles issue where length of 1º λ// changes depending on φ. Without this, there is a pinching effect at the poles.var k = Math.cos(φ / 360 * τ);return [(pλ[0] - x) / hλ / k, (pλ[1] - y) / hλ / k, (pφ[0] - x) / hφ, (pφ[1] - y) / hφ];};var createField = function createField(columns, bounds, callback) {/*** @returns {Array} wind vector [u, v, magnitude] at the point (x, y), or [NaN, NaN, null] if wind*          is undefined at that point.*/function field(x, y) {var column = columns[Math.round(x)];return column && column[Math.round(y)] || NULL_WIND_VECTOR;} // Frees the massive "columns" array for GC. Without this, the array is leaked (in Chrome) each time a new// field is interpolated because the field closure's context is leaked, for reasons that defy explanation.field.release = function () {columns = [];};field.randomize = function (o) {// UNDONE: this method is terriblevar x, y;var safetyNet = 0;do {x = Math.round(Math.floor(Math.random() * bounds.width) + bounds.x);y = Math.round(Math.floor(Math.random() * bounds.height) + bounds.y);} while (field(x, y)[2] === null && safetyNet++ < 30);o.x = x;o.y = y;return o;};callback(bounds, field);};var buildBounds = function buildBounds(bounds, width, height) {var upperLeft = bounds[0];var lowerRight = bounds[1];var x = Math.round(upperLeft[0]); //Math.max(Math.floor(upperLeft[0], 0), 0);var y = Math.max(Math.floor(upperLeft[1], 0), 0);var xMax = Math.min(Math.ceil(lowerRight[0], width), width - 1);var yMax = Math.min(Math.ceil(lowerRight[1], height), height - 1);return {x: x,y: y,xMax: width,yMax: yMax,width: width,height: height};};var deg2rad = function deg2rad(deg) {return deg / 180 * Math.PI;};var invert = function invert(x, y, windy) {var latlon = params.map.unproject([x, y]);return [latlon.lng, latlon.lat];};var project = function project(lat, lon, windy) {var xy = params.map.project([lon, lat]);return [xy.x, xy.y];};var interpolateField = function interpolateField(grid, bounds, extent, callback) {var projection = {}; // map.crs used insteadvar mapArea = (extent.south - extent.north) * (extent.west - extent.east);var velocityScale = VELOCITY_SCALE * Math.pow(mapArea, 0.4);var columns = [];var x = bounds.x;function interpolateColumn(x) {var column = [];for (var y = bounds.y; y <= bounds.yMax; y += 2) {var coord = invert(x, y);if (coord) {var λ = coord[0],φ = coord[1];if (isFinite(λ)) {var wind = grid.interpolate(λ, φ);if (wind) {wind = distort(projection, λ, φ, x, y, velocityScale, wind);column[y + 1] = column[y] = wind;}}}}columns[x + 1] = columns[x] = column;}(function batchInterpolate() {var start = Date.now();while (x < bounds.width) {interpolateColumn(x);x += 2;if (Date.now() - start > 1000) {//MAX_TASK_TIME) {setTimeout(batchInterpolate, 25);return;}}createField(columns, bounds, callback);})();};var animationLoop;var animate = function animate(bounds, field) {function windIntensityColorScale(min, max) {colorScale.indexFor = function (m) {// map velocity speed to a stylereturn Math.max(0, Math.min(colorScale.length - 1, Math.round((m - min) / (max - min) * (colorScale.length - 1))));};return colorScale;}var colorStyles = windIntensityColorScale(MIN_VELOCITY_INTENSITY, MAX_VELOCITY_INTENSITY);var buckets = colorStyles.map(function () {return [];});var particleCount = Math.round(bounds.width * bounds.height * PARTICLE_MULTIPLIER);if (isMobile()) {particleCount *= PARTICLE_REDUCTION;}var fadeFillStyle = "rgba(0, 0, 0, ".concat(OPACITY, ")");var particles = [];for (var i = 0; i < particleCount; i++) {particles.push(field.randomize({age: Math.floor(Math.random() * MAX_PARTICLE_AGE) + 0}));}function evolve() {buckets.forEach(function (bucket) {bucket.length = 0;});particles.forEach(function (particle) {if (particle.age > MAX_PARTICLE_AGE) {field.randomize(particle).age = 0;}var x = particle.x;var y = particle.y;var v = field(x, y); // vector at current positionvar m = v[2];if (m === null) {particle.age = MAX_PARTICLE_AGE; // particle has escaped the grid, never to return...} else {var xt = x + v[0];var yt = y + v[1];if (field(xt, yt)[2] !== null) {// Path from (x,y) to (xt,yt) is visible, so add this particle to the appropriate draw bucket.particle.xt = xt;particle.yt = yt;buckets[colorStyles.indexFor(m)].push(particle);} else {// Particle isn't visible, but it still moves through the field.particle.x = xt;particle.y = yt;}}particle.age += 1;});}var g = params.canvas.getContext("2d");g.lineWidth = PARTICLE_LINE_WIDTH;g.fillStyle = fadeFillStyle;g.globalAlpha = 0.6;function draw() {// Fade existing particle trails.var prev = "lighter";g.globalCompositeOperation = "destination-in";g.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);g.globalCompositeOperation = prev;g.globalAlpha = OPACITY === 0 ? 0 : OPACITY * 0.9; // Draw new particle trails.buckets.forEach(function (bucket, i) {if (bucket.length > 0) {g.beginPath();g.strokeStyle = colorStyles[i];bucket.forEach(function (particle) {g.moveTo(particle.x, particle.y);g.lineTo(particle.xt, particle.yt);particle.x = particle.xt;particle.y = particle.yt;});g.stroke();}});}var then = Date.now();(function frame() {animationLoop = requestAnimationFrame(frame);var now = Date.now();var delta = now - then;if (delta > FRAME_TIME) {then = now - delta % FRAME_TIME;evolve();draw();}})();};var start = function start(bounds, width, height, extent) {var mapBounds = {south: deg2rad(extent[0][1]),north: deg2rad(extent[1][1]),east: deg2rad(extent[1][0]),west: deg2rad(extent[0][0]),width: width,height: height};stop(); // build gridbuildGrid(gridData, function (grid) {// interpolateFieldinterpolateField(grid, buildBounds(bounds, width, height), mapBounds, function (bounds, field) {// animate the canvas with random pointswindy.field = field;animate(bounds, field);});});};var stop = function stop() {if (windy.field) windy.field.release();if (animationLoop) cancelAnimationFrame(animationLoop);};var windy = {params: params,start: start,stop: stop,createField: createField,interpolatePoint: interpolate,setData: setData,setOptions: setOptions};return windy;};if (!window.cancelAnimationFrame) {window.cancelAnimationFrame = function (id) {clearTimeout(id);};}​

加载方法

var windyMap = {windy: null,map: null,visible: true,context: null,timer: 0,initWindy(data, map) {const self = this;self.visible = true;self.map = map;// 删除domself.hideWind();let canvas = document.createElement('canvas');canvas.id = 'windCanvas';canvas.width = map.getCanvas().width;canvas.height = map.getCanvas().height;canvas.style.position = 'absolute';canvas.style.top = 0;canvas.style.left = 0;map.getCanvasContainer().appendChild(canvas);this.context = canvas.getContext("2d");self.windy = new Windy({canvas: canvas,data: data,map: map});if (self.timer) clearTimeout(self.timer);this.timer = setTimeout(function () {self._refreshWindy();}, 750);map.on("dragstart", function(){if(self.context) self.context.clearRect(0, 0, 3000, 3000);self.windy.stop();});map.on("dragend", function() {self._refreshWindy();});map.on("zoomstart", function(){if(self.context) self.context.clearRect(0, 0, 3000, 3000);self.windy.stop();});map.on("zoomend", function() {self._refreshWindy();});map.on("resize", function() {self.clearWind();});},_refreshWindy: function() {const self = this;const _canvas = self.windy.params.canvas;if (!self.windy) return;let bounds = self.map.getBounds();let extent = [bounds._sw.lng,bounds._sw.lat,bounds._ne.lng,bounds._ne.lat];_canvas.width = map.getCanvas().width;_canvas.height = map.getCanvas().height;self.windy.start([[0, 0], [_canvas.width, _canvas.height]],_canvas.width,_canvas.height,[[extent[0], extent[1]], [extent[2], extent[3]]]);},hideWind: function() {if(this.context) this.context.clearRect(0, 0, 3000, 3000);let dom = document.getElementById('windCanvas');if (dom) dom.parentNode.removeChild(dom);},clearWind: function() {if (this.windy) this.windy.stop();if(this.context) this.context.clearRect(0, 0, 3000, 3000);},setVisible: function(flag) {const self = this;self.visible = flag;let dom = document.getElementById('windCanvas');if (!dom) return;if (flag) {dom.style.display = 'block';self._refreshWindy();} else {if (self.windy) self.windy.stop();dom.style.display = 'none';}}
};

调用

map.on('load', function() {axios.get(`https://datacenter.istrongcloud.com/data/gfs/time_fc.json`, function(res) {axios.get(`https://datacenter.istrongcloud.com/data/gfs/fcdata/${res.data.data['024'].url}`).then(windRes => {console.log('windRes: ', windRes.data);windyMap.initWindy(Object.values(windRes.data)[0], map);});})
});

可视化,mapboxGL加载台风风场动画相关推荐

  1. div 重新加载_JS之 加载模糊文本动画

    阅读本文约需要5分钟 大家好,我是你们的导师,我每天都会在这里给大家分享一些干货内容(当然了,周末也要允许老师休息一下哈).上次老师跟大家分享了JS之 创建反射效果的知识,今天跟大家分享下JS之 加载 ...

  2. html怎么帮图片占位,css+html实现Skeleton Screen 加载占位图动画效果(带动画)

    效果 自上而下渐隐渐现 源码 html,用的angular语法,只要做简单的修改就可以成为你需要的语法 css .skeletonItem { padding: 16px; border-bottom ...

  3. mapboxgl 加载常用图层汇总

    mapboxgl 加载矢量.影像.geojson等图层及添加专题图层(热力图.唯一值)汇总 1.加载常用图层 1.1 wmts服务 1.2 矢量服务 1.2.1 加载矢量切片数据源 1.2.2 加载g ...

  4. rviz进行kitti数据集可视化时加载小车模型报错

    最在进行kitti数据可视化时加载小车失败出现如下错误: Mesh resource marker [/-1] could not load [package://kitti_tutorials/me ...

  5. mapboxgl加载google地图、高德地图的在线切片地图

    mapboxgl加载google地图.高德地图的在线切片地图 1.加载google地图,只需将将style换为如下内容,并根据需要更换地图类型(注释部分)即可. style: {"versi ...

  6. 第八章 DirectX 3D模型加载和骨骼动画(下)

    接下来,我们介绍一些骨骼动画.我们之前大致讲过骨骼动画,存储骨骼动画的网格文件要比普通的文件复杂一下.主要是增加了骨骼信息,蒙皮信息以及动画帧信息.骨骼动画的实现原理是仿照人体运动学,将3D模型由一种 ...

  7. CSS3超酷网页Loading加载进度条动画效果

    在jQuery之家上发现的一款css3效果. CSS3 animation超酷网页Loading加载进度条动画效果 >>查看演示                           > ...

  8. java 显示网页gif_Java在窗口上加载显示GIF动画图像

    Java在窗口上加载显示GIF动画图像,将多个独立的GIF图像串联在一起显示,形成GIF特有的动画形式.主要代码如下: ImageIcon[] images; //用于动画的图标数组 Timer an ...

  9. cocos studio 制作csd动画文件 并 加载使用嵌套动画

    直接使用cocos studio 制作动画,动画过程细腻,可以根据自己的想法方便调节.并且可以随意取用. 本次记录是我需要一个头像动画,放在不同的头像上,而且头像的大小各不相同. 解决方案就是: 1. ...

最新文章

  1. 程序员期末试卷_第三部分复习提纲.doc下载
  2. python libusb多线程_一些有助于理解libusb1.0和ctypes的异步USB操作
  3. 《IBM-PC汇编语言程序设计》(第2版)【沈美明 温冬婵】——第六章——自编解析与答案
  4. 元旦好礼50份免费送:示波器、开发板、焊台等!
  5. 论文学习16-Going out on a limb: without Dependency Trees(联合实体关系抽取2017)
  6. 2014.8.20日结
  7. 每天一道算法题(13)——使用递归颠倒栈
  8. 一步一步手绘Spring IOC运行时序图一(Spring 核心容器 IOC初始化过程)
  9. 计算机软件自学基础,计算机软件基础一高等教育自学考试试题与答案
  10. win 7 电脑错误676、734、678、651等解决办法
  11. 语音识别技术原理是什么 讯飞语音识别技术特点介绍【详解】
  12. 将 instance 连接到 second_local_net - 每天5分钟玩转 OpenStack(85)
  13. mysql etimedout_Node.js获取请求ETIMEDOUT和ESOCKETTIMEDOUT
  14. C语言程序设计,流程图
  15. saltstack的探索-利用脚本增加用户
  16. 修复 VM Player 断网问题 20121215T1402
  17. 大麦 Android 选座场景性能优化全解析
  18. 如何卸载deepfreeze(冰点还原)
  19. oracle数据文件recover,又遇BUG-ORA-01148:数据文件忽然变为recover状态
  20. 解决Namecheap域名转入确认邮件重发及邮件收不到

热门文章

  1. PS基础及选框工具笔记(CS6)
  2. Flutter36,资深Android面试题
  3. oracle查看所有表信息和字段信息以及注释信息等
  4. MaxENT完整操作
  5. WinBUGS对多元随机波动率模型:贝叶斯估计与模型比较
  6. maya计算机内存不足请保存,Word突然出现无法保存内存不足怎么办
  7. ae绘图未指定错误怎么办_设计高手总结47个快捷键50个CAD使用技巧,助你神速绘图拒绝加班!...
  8. R语言并行计算 deviation of null beta diversity(beta多样性零偏差)
  9. Python基础复习09_面向对象特性
  10. 那些年,磕磕碰碰的BUG