web前端利用leaflet生成粒子风场,类似windy
wind.js如下:
$(function() {
var dixing = L.tileLayer.chinaProvider('Google.Satellite.Map', {
maxZoom: 18,
minZoom: 2
});
var map = L.map("map", {
center: [33.5, 114.6],
zoom: 10,
maxZoom: 20,
minZoom: 3,
layers: dixing,
zoomControl: false,
attributionControl: false,
// crs:L.CRS.EPSG4326
});
map.setView([-19, 150], 5);
//各种定义和参数
var grid = []; //定义经纬度网格数组
var particles = []; //存放粒子数组
var PARTICLE_MULTIPLIER = 0.4 / 300; //粒子数量参数,默认1/300,可以根据实际调
var max_age = 100; //定义粒子的最大运动次数
//没有风的情况
var NULL_WIND_VECTOR = [NaN, NaN, null];
var min_color = 0; // 风速为0使用的颜色
var max_color = 10; // 风速最大使用的颜色
var linewidth = 1.5; //定义粒子粗细
var FRAME_RATE = 35, //定义每秒执行的次数
FRAME_TIME = 1000 / FRAME_RATE; // desired frames per second
var galpha = 0.92; //定义透明度,透明度越大,尾巴越长
//存放颜色的数组
var colorScale = ["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)"];
//粒子动画的速度,为参数乘以屏幕的像素密度开三次方
//如果不支持像素密度,就乘以1,
//默认0.0005
// var vscale = (0.01) * (Math.pow(window.devicePixelRatio, 1 / 3) || 1); // scale for wind velocity (completely arbitrary--this value looks nice)
var vscale = 0.01;
var animationLoop; //定义动画
//第一步生成经纬度网格///////////////////////////////////////////////////
$.getJSON("leaflet-velocity-master/demo/wind-global.json", function(data) {
setTimeout(function() {
buildGrid(data);
map.fire("moveend");
}, 100)
// bulid_grid();
// create_new_lizi();
// 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();
// }
// })();
// map.fire("moveend");
})
var lon_min, lat_min, x_kuadu, y_kuadu, ni, nj; 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]];
}
};
}; 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);
}; function buildGrid(data, callback) {
builder = createBuilder(data);
var header = builder.header;
lon_min = header.lo1; //小的经度
lat_min = header.la1; // the grid's origin (e.g., 0.0E, 90.0N)
//小纬度
x_kuadu = header.dx; //x方向的跨度
y_kuadu = header.dy; // distance between grid points (e.g., 2.5 deg lon, 2.5 deg lat)
//y方向的跨度
ni = header.nx; //x方向的总数
nj = header.ny; // number of grid points W-E and N-S (e.g., 144 x 73)
// //y方向的总数
// date = new Date(header.refTime);
// date.setHours(date.getHours() + header.forecastTime);
//
// // Scan mode 0 assumed. Longitude increases from lon_min, and latitude decreases from lat_min.
// // http://www.nco.ncep.noaa.gov/pmb/docs/grib2/grib2_table3-4.shtml
var p = 0;
var isContinuous = Math.floor(ni * x_kuadu) >= 360;
//x方向的跨度乘以x方向的数量是否大于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 logic
row.push(row[0]);
}
grid[j] = row;
}
//grid是一个三维数组
//第一纬表示行数
//第二纬表示列数
//第三纬表示每一个网格点的uv
};
////////////////
//第二步生成插值网格//////////////////////////////////////
var columns = []; //存放插值网格数组
var start = Date.now();
var bwidth = map.getSize().x,
bheight = map.getSize().y;
var bx = 0,
by = 0;
var projection = {};
var mbounds = map.getBounds();
var now_bounds = {
east: deg2rad(mbounds._northEast.lng),
west: deg2rad(mbounds._southWest.lng),
north: deg2rad(mbounds._northEast.lat),
south: deg2rad(mbounds._southWest.lat),
height: bheight,
width: bwidth
}
var mapArea = (now_bounds.south - now_bounds.north) * (now_bounds.west - now_bounds.east);
var velocityScale = vscale * Math.pow(mapArea, 0.4); function bulid_grid() {
columns = []; //存放插值网格数组
start = Date.now();
bx = 0,
by = 0;
projection = {};
mbounds = map.getBounds();
now_bounds = {
east: deg2rad(mbounds._northEast.lng),
west: deg2rad(mbounds._southWest.lng),
north: deg2rad(mbounds._northEast.lat),
south: deg2rad(mbounds._southWest.lat),
height: bheight,
width: bwidth
}
mapArea = (now_bounds.south - now_bounds.north) * (now_bounds.west - now_bounds.east);
velocityScale = vscale * Math.pow(mapArea, 0.4);
batchInterpolate()
} function batchInterpolate() {
while(bx < bwidth) {
interpolateColumn(bx);
bx += 2;
if(Date.now() - start > 1000) {
//MAX_TASK_TIME) {
setTimeout(batchInterpolate, 25); //如果粒子生成时间大于1秒了,就不再生成
return;
}
}
} function interpolateColumn(x) {
var column = [];
//画布上的每两个像素是一个格点
for(var y = 0; y <= bheight; y += 2) {
var coord = invert(x, y, now_bounds);
//求出每一个格点所对应的经纬度
if(coord) {
var λ = coord[0], //经度
φ = coord[1]; //纬度
//监测经度是不是数字
if(isFinite(λ)) {
var wind = interpolate(λ, φ);
//每一个格点的uv和风速大小
if(wind) {
wind = distort(projection, λ, φ, x, y, velocityScale, wind, now_bounds);
//wind 表示粒子x方向的像素速度,y方向上的像素速度和风速
column[y + 1] = column[y] = wind;
}
}
}
}
//相邻两个格点用相同的速度
columns[x + 1] = columns[x] = column;
} function invert(x, y, windy) {
var mapLonDelta = windy.east - windy.west; //经度跨度
var worldMapRadius = windy.width / rad2deg(mapLonDelta) * 360 / (2 * Math.PI);
var mapOffsetY = worldMapRadius / 2 * Math.log((1 + Math.sin(windy.south)) / (1 - Math.sin(windy.south)));
var equatorY = windy.height + mapOffsetY;
var a = (equatorY - y) / worldMapRadius; var lat = 180 / Math.PI * (2 * Math.atan(Math.exp(a)) - Math.PI / 2);
var lon = rad2deg(windy.west) + x / windy.width * rad2deg(mapLonDelta);
return [lon, lat];
}; function deg2rad(deg) {
return deg / 180 * Math.PI;
}; function rad2deg(ang) {
return ang / (Math.PI / 180.0);
}; function floorMod(a, n) {
return a - n * Math.floor(a / n);
};
var isValue = function isValue(x) {
return x !== null && x !== undefined;
};
var mercY = function mercY(lat) {
return Math.log(Math.tan(lat / 2 + Math.PI / 4));
}; function interpolate(λ, φ) {
//如果风场网格数据不存在,就不执行
if(!grid) return null;
//λ, φ分别是每个网格点的经度和纬度
var i = floorMod(λ - lon_min, 360) / x_kuadu; // calculate longitude index in wrapped range [0, 360)
//lat_min应该是上面的纬度,大的一个
var j = (lat_min - φ) / y_kuadu; // calculate latitude index in direction +90 to -90
//计算网格点在从上到下,从左到右,以最小刻度为0的第几个经纬度格点上
var 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];
//计算出格点周围4个点
if(isValue(g01) && isValue(g11)) {
// All four points found, so interpolate the value.
return bilinearInterpolateVector(i - fi, j - fj, g00, g10, g01, g11);
}
}
}
return null;
}; function bilinearInterpolateVector(x, y, g00, g10, g01, g11) {
//y表示格点距离上一个经纬度格点的距离
//x表示格点距离前一个经纬度给点的距离
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)];
//返回格点插值出来u和v还有速度
}; function distort(projection, λ, φ, x, y, scale, wind, windy) {
//projection是一个空的对象
// λ, φ格点的经纬度
//x, y格点所在的像素点
//格点的风向风速
//windy
//scale 一个参数,每次粒子运动的距离
var u = wind[0] * scale;
var v = wind[1] * scale;
var d = distortion(projection, λ, φ, x, y, windy); // 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;
}; function distortion(projection, λ, φ, x, y, windy) {
var τ = 2 * Math.PI;
var H = Math.pow(10, -5.2);
var hλ = λ < 0 ? H : -H;
var hφ = φ < 0 ? H : -H; var pλ = project(φ, λ + hλ, windy);
var pφ = project(φ + hφ, λ, windy); // 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 project = function project(lat, lon, windy) {
// both in radians, use deg2rad if neccessary
var ymin = mercY(windy.south);
var ymax = mercY(windy.north);
var xFactor = windy.width / (windy.east - windy.west);
var yFactor = windy.height / (ymax - ymin); var y = mercY(deg2rad(lat));
var x = (deg2rad(lon) - windy.west) * xFactor;
var y = (ymax - y) * yFactor; // y points south
return [x, y];
};
//第三步初始化生成粒子///////////////////////////////////
randomize = function(o) {
// UNDONE: this method is terrible
var x, y;
var safetyNet = 0;
do {
x = Math.round(Math.floor(Math.random() * bwidth));
y = Math.round(Math.floor(Math.random() * bheight));
} while (field(x, y)[2] === null && safetyNet++ < 30);
o.x = x;
o.y = y;
return o;
}; function field(x, y) {
var column = columns[Math.round(x)];
return column && column[Math.round(y)] || NULL_WIND_VECTOR;
} function create_new_lizi() {
particles.length = 0;
var particleCount = Math.round(bwidth * bheight * PARTICLE_MULTIPLIER);
//计算粒子的数量
for(var i = 0; i < particleCount; i++) {
particles.push(randomize({
age: Math.floor(Math.random() * max_age)
}));
}
}
//////////////////////
//第四步,定义每次粒子的变化
var colorStyles = windIntensityColorScale(min_color, max_color);
var buckets = colorStyles.map(function() {
return [];
}); function windIntensityColorScale(min, max) { colorScale.indexFor = function(m) {
// map velocity speed to a style
return Math.max(0, Math.min(colorScale.length - 1, Math.round((m - min) / (max - min) * (colorScale.length - 1))));
}; return colorScale;
} function evolve() {
//清除风点数组
buckets.forEach(function(bucket) {
bucket.length = 0;
});
//particles是存放所有点的数组
particles.forEach(function(particle) {
//如果粒子运行的次数大于设定的最大次数
//重新随机粒子的位置,并把次数定义成0
if(particle.age > max_age) {
randomize(particle).age = 0;
}
var x = particle.x;
var y = particle.y;
var v = field(x, y); // vector at current position
var m = v[2];
if(m === null) {
//如果没有速度,就让age是最大值,重新随机点
particle.age = max_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 canvas = L.DomUtil.create("canvas", "can");
canvas.height = bheight;
canvas.width = bwidth;
// var wind_pane = map.getPane("markerPane");
// // canvas.style.background = "red";
// wind_pane.appendChild(canvas);
var animated = map.options.zoomAnimation && L.Browser.any3d;
//添加下面的class之后,图层可以随着地图缩放变化
L.DomUtil.addClass(canvas, 'leaflet-zoom-' + (animated ? 'animated' : 'hide'));
//把画布添加到overlayPane图层中
map._panes.overlayPane.appendChild(canvas);
var fadeFillStyle = "rgba(0, 0, 0, 0.97)";
var g = canvas.getContext("2d");
g.lineWidth = linewidth;
g.fillStyle = fadeFillStyle;
g.globalAlpha = 0.6;
map.on("zoomanim",function(e){
var scale =map.getZoomScale(e.zoom);
var bound = map.getBounds();
var yun_lat1 = bound._northEast.lat;
var yun_lon1 = bound._northEast.lng;
var yun_lat2 = bound._southWest.lat;
var yun_lon2 = bound._southWest.lng;
var new_position = map.latLngToLayerPoint([yun_lat1, yun_lon2]);
// -- different calc of offset in leaflet 1.0.0 and 0.0.7 thanks for 1.0.0-rc2 calc @jduggan1
var offset = L.Layer ? map._latLngToNewLayerPoint(map.getBounds().getNorthWest(), e.zoom, e.center) : map.getCenterOffset(e.center).multiplyBy(-scale).subtract(map.getMapPanePos()); L.DomUtil.setTransform(canvas, offset, scale);
})
map.on("moveend", function() {
window.cancelAnimationFrame(animationLoop);
bwidth = map.getSize().x,
bheight = map.getSize().y;
g.clearRect(0, 0, bwidth, bheight);
canvas.height = bheight;
canvas.width = bwidth; var bound = map.getBounds();
var yun_lat1 = bound._northEast.lat;
var yun_lon1 = bound._northEast.lng;
var yun_lat2 = bound._southWest.lat;
var yun_lon2 = bound._southWest.lng;
var new_position = map.latLngToLayerPoint([yun_lat1, yun_lon2]);
L.DomUtil.setPosition(canvas, new_position);
bulid_grid();
create_new_lizi();
setTimeout(function() {
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();
}
})();
},200) }) function draw() {
// Fade existing particle trails.
var prev = "lighter";
g.globalCompositeOperation = "destination-in";
g.fillRect(0, 0, bwidth, bheight);
g.globalCompositeOperation = prev;
g.globalAlpha = galpha; // Draw new particle trails.
//buckets是把风点按照不同颜色分级,分成的数组
//数组的每一项是一个对象,
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();
}
});
}
})
其他学习网站:
https://blog.csdn.net/qq_35131811/article/details/82761238
web前端利用leaflet生成粒子风场,类似windy的更多相关文章
- web前端利用HSTS(新的Web安全协议HTTP Strict Transport Security)漏洞的超级Cookie(HSTS Super Cookie)
web前端如果想实现cookie跨站点,跨浏览器,清除浏览器cookie该cookie也不会被删除这似乎有点难,下面的教程让你完全摆脱document.cookie 1.服务器端设置HSTS 如PHP ...
- web前端利用turf.js生成等值线、等值面
样例如下: <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> ...
- Vue前端利用qrcode生成二维码
<div id="qrcode" style="width: 560px;height: 560px;background-color: white;"& ...
- web前端利用HTML代码显示符号
HTML常用符号代码: ´ ´ © © > > µ µ ® ® & & ° ° ¡ ¡ » » ¦ ¦ ÷ ÷ ¿ ¿ ...
- 整理六百篇web前端知识混总
9个有用的和免费的工具来支持动态网页开发 8个基本的引导工具的网页设计师 11款CSS3动画工具的开发 2016年某前端群题目答案参考 9最好的JavaScript压缩工具 创建响应式布局的10款优秀 ...
- 【图解】Web前端实现类似Excel的电子表格
本文将通过图解的方式,使用纯前端表格控件 SpreadJS 来一步一步实现在线的电子表格产品(例如可构建Office 365 Excel产品.Google的在线SpreadSheet). 工具简介: ...
- 利用Swashbuckle生成Web API Help Pages
利用Swashbuckle生成Web API Help Pages 这系列文章是参考了.NET Core文档和源码,可能有人要问,直接看官方的英文文档不就可以了吗,为什么还要写这些文章呢? 原因如下: ...
- (喷血分享)利用.NET生成数据库表的创建脚本,类似SqlServer编写表的CREATE语句
(喷血分享)利用.NET生成数据库表的创建脚本,类似SqlServer编写表的CREATE语句 在我们RDIFramework.NET代码生成器中,有这样一个应用,就是通过数据库表自动生成表的CREA ...
- Web前端开发如何利用css样式来控制Html中的h1/h2/h3标签不换行
H1/H2/H3/H4标题标签常常使用在一个网页中唯一标题.重要栏目.重要标题等情形下. H1在一个网页中最好只使用一次,如对一个网页唯一标题使用.H2.H3.H4标签则可以在一个网页中多次出现, ...
随机推荐
- C#生成exe、dll版本号自动增加
修改AssemblyInfo.cs 1.注释[assembly: AssemblyFileVersion("1.0.0.0")] 2.[assembly: AssemblyVers ...
- Redis缓存机制一为什么要用Redis
1.持久化数据库的缺点 1)存储在部署数据库的硬盘上 平时我们使用的关系型数据库有MySql,Oracle以及SqlServer等,通常通过数据驱动来链接数据库进行增删改查. 那么 ...
- OSError:[Errno 13] Permission denied:'my_library' 问题解决方法
出现问题: 执行 rosrun rosserial_windows make_libraries.py my_library 命令时出现OSError:[Errno 13] Permission de ...
- Tensorflow安装记录
一.安装Ubantu环境 下载ios 网址:http://cn.ubuntu.com/download/ 2.配合虚拟机进行安装环境 虚拟机直接百度下载即可 虚拟机采用 具体安装,虚拟机百度中很多记录 ...
- cobbler自动安装操作系统
cobbler介绍 快速网络安装linux操作系统的服务,支持众多的Linux发行版: Red Hat, Fedora,CentOS,Debian,Ubuntu和SuSE 也可以支持网络安装windo ...
- grub启动流程和配置
grub stage 1 MBR中前 446个字节,如果把这里面的内容损坏,那么系统会认为当前磁盘没有启动引导功能,会尝试从光盘或者网络启动系统 grub stage 1.5 存放识别/boot ...
- map+case结构使用技巧
people.txt文本如下 lyzx1, lyzx2, lyzx3, lyzx4, lyzx5, lyzx6, lyzx7, lyzx7,,哈哈 托塔天王 import org.apache.spa ...
- 利用Tensorflow实现逻辑回归模型
官方mnist代码: #下载Mnist数据集 import tensorflow.examples.tutorials.mnist.input_data mnist = input_data.read ...
- linux 下查看c 函数帮助
帮助文档 man man MANUAL SECTIONS The standard sections of the manual include: User Commands System Calls ...
- .NET拾忆:EventLog(Windows事件日志监控)
操作Windows日志:EventLog 1:事件日志名(logName):“事件查看器”中的每一项,如“应用程序”.“Internet Explorer”.“安全性”和“系统”都是日志(严格地说是日 ...