GIS风场及其相关技术研究一

标签: Gis 分类: Gis 创建时间:2019-10-25 07:44:06 更新时间:2025-01-17 10:39:22

这篇文章,是一篇探究性的文章,不见得有什么结果,就是要写篇文章记录下。先上一张图:

一看这张图,有没有觉得就很炫,很牛逼,也很实用?这是一种动态图,上面的小点都是可以流动的,描述了全球大气的运动情况,看动图,可以去找:earth.nullschool.net

有一些比较有名的风场、流体常的网站,比如:windy.com和earth.nullschool.net

参考文章:
1.利用原生canvas在浏览器端绘制风场动画
2.根据上面的内容实现的网站 (里面可以F12看源码)
2.使用自定义数据绘制风场图 (这里是使用echart实现的风场效果)

根据上面 windy 这个网站,通过查看他的源代码,然后自己实验,出现了一个简单的动画效果。

主页代码:其中的加载了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
<!DOCTYPE html>
<html>
<head>
<title>风场测试</title>
<meta charset="utf-8"/>
<link rel="stylesheet" href="../lib/ol/ol.css" type="text/css">
<script src="../lib/ol/ol.js"></script>
<script src="https://code.jquery.com/jquery-3.4.1.min.js" integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script>
<style>
body,html{
width:100%;
margin:0;
padding:0;
height: 100%;
position:absolute;
}
.map{
width:100%;
height:100%;
position:absolute;
}
.save{
position: absolute;
left: 200px;
z-index: 10;
}
</style>
</head>
<body>
<canvas id="canvas" style="position: absolute; z-index: 10; pointer-events: none;"></canvas>
<div class="map" id="map"></div>
<script>
var myView={
modalSelect:'gfs'
}
var asideVue={
projection: "EPSG:3857"
}
</script>
<script src="./js/wind.js"></script>
<script>
var DARK_BLUE = new ol.source.XYZ({
url: "http://map.geoq.cn/ArcGIS/rest/services/ChinaOnlineStreetPurplishBlue/MapServer/tile/{z}/{y}/{x}"
});

var basicMap = new ol.layer.Tile({source: DARK_BLUE});
var projection = "EPSG:3857"; // "EPSG:3857"; //"EPSG:10035";
var center = new ol.proj.fromLonLat([110, 24], projection);
var mapExtent = ol.proj.transformExtent([-180, -85, 180, 85],'EPSG:4326', projection);
var monitorMap = new ol.Map({
controls: ol.control.defaults({
attributionOptions:
({
collapsible: false
})
}),
layers:[basicMap],
target: 'map',
view: new ol.View({
projection: projection,
center: center,
extent: mapExtent,
zoom: 4,
maxZoom: 15,
minZoom: 3,
})
});
var display = new MotionDisplay(document.getElementById('canvas'), new VectorField([[]], -179.5, 89.5, 179.5, -89.5));
// display.start();
</script>
<script src="./js/wind2.js"></script>
</body>
</html>

其中引用的wind.js代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
/**
*
利用地图框架openlayer和canvas API实现了动画效果
栅格数据后端定时处理,发布到服务器,跨域调用,前端提取渲染
动画会耗费大量计算资源,为了达到流畅效果
涉及到很多JavaScript性能调优,
为了达到显示的美观,涉及到插值的算法
风场图的粒子速度在不同缩放级别下的移动速度
作者:崔叶孟( email: 2270172201@qq.com )
*/
var Vector = function(x, y) {
this.x = x;
this.y = y;
};
Vector.prototype.length = function() {
return Math.sqrt(this.x * this.x + this.y * this.y);
};
Vector.prototype.setLength = function(length) {
var current = this.length();
if (current) {
var scale = length / current;
this.x *= scale;
this.y *= scale;
}
return this;
};
var Particle = function(x, y, age) {
this.x = x;
this.y = y;
this.oldX = -1;
this.oldY = -1;
this.age = age;
this.rnd = Math.random();
};
/**
*
* @param field
* 2D array of Vectors
*
* next params are corners of region.
* @param x0
* @param y0
* @param x1
* @param y1
*/
var VectorField = function(field, x0, y0, x1, y1) {
this.x0 = x0;
this.x1 = x1;
this.y0 = y0;
this.y1 = y1;
this.field = field;
this.w = field.length;
this.h = field[0].length;
this.maxLength = 0;
var mx = 0;
var my = 0;
for (var i = 0; i < this.w; i++) {
for (var j = 0; j < this.h; j++) {
if (field[i][j].length() > this.maxLength) {
mx = i;
my = j;
}
if(!isNaN(field[i][j].length())){
this.maxLength = Math.max(this.maxLength, field[i][j].length());
}
}
}
mx = (mx / this.w) * (x1 - x0) + x0;
my = (my / this.h) * (y1 - y0) + y0;

};
VectorField.read = function(data) {
var field = [];
var w = data[0].header.nx;
var h = data[0].header.ny;
var n = 2 * w * h;
var i = 0;

var total = 0;
var weight = 0;
for (var x = 0; x < w; x++) {
field[x] = [];
for (var y = 0; y < h; y++) {
var vx = data[0].data[w*y+x];
var vy = data[1].data[w*y+x];
var v = new Vector(vx, vy);
var ux = x / (w - 1);
var uy = y / (h - 1);
var lon = data[0].header.lo1 * (1 - ux) + data[0].header.lo2 * ux;
var lat = data[0].header.la1 * (1 - uy) + data[0].header.la2 * uy;
var m = Math.PI * lat / 180;
var length = v.length();
if (length) {
total += length * m;
weight += m;
}
v.x /= Math.cos(m);
v.setLength(length);
field[x].push(v);
}
}
var result = new VectorField(field, data[0].header.lo1, data[0].header.la1, data[0].header.lo2, data[0].header.la2);
// window.console.log('total = ' + total);
// window.console.log('weight = ' + weight);
if (total && weight) {

result.averageLength = total / weight;
}
return result;
};


VectorField.readImg = function(datau,datav) {
if (datav && datau) {
var field = [];
var w = 720;
var h = 360;
var n = 2 * w * h;
var i = 0;

var total = 0;
var weight = 0;
for (var x = 0; x < w; x++) {
field[x] = [];
for (var y = 0; y < h; y++) {
var vx = (datau[(w*y+x)*4]-128)/1.6;
var vy = (datav[(w*y+x)*4]-128)/1.6;
var v = new Vector(vx, vy);
var ux = x / (w - 1);
var uy = y / (h - 1);
var lon = -179.5 * (1 - ux) + 179.5 * ux;
var lat = 89.5 * (1 - uy) + -89.5 * uy;
var m = Math.PI * lat / 180;
var length = v.length();
if (length) {
total += length * m;
weight += m;
}
v.x /= Math.cos(m);
v.setLength(length);
field[x].push(v);
}
}
var result = new VectorField(field, -179.5, 89.5, 179.5, -89.5);
// window.console.log('total = ' + total);
// window.console.log('weight = ' + weight);
if (total && weight) {

result.averageLength = total / weight;
}
return result;
}

};

VectorField.readImgArr = function(imageArr) {
var field = [];
var w = 720;
var h = 360;
var n = 2 * w * h;
var i = 0;

var total = 0;
var weight = 0;
for (var x = 0; x < w; x++) {
field[x] = [];
for (var y = 0; y < h; y++) {
var vx = (imageArr[(w*y+x)*4+0]-128)/1.6;
var vy = (imageArr[(w*y+x)*4+1]-128)/1.6;
var v = new Vector(vx, vy);
var ux = x / (w - 1);
var uy = y / (h - 1);
var lon = -179.5 * (1 - ux) + 179.5 * ux;
var lat = 89.5 * (1 - uy) + -89.5 * uy;
var m = Math.PI * lat / 180;
var length = v.length();
if (length) {
total += length * m;
weight += m;
}
v.x /= Math.cos(m);
v.setLength(length);
field[x].push(v);
}
}
var result = new VectorField(field, -179.5, 89.5, 179.5, -89.5);
// window.console.log('total = ' + total);
// window.console.log('weight = ' + weight);
if (total && weight) {

result.averageLength = total / weight;
}
return result;

};



VectorField.prototype.inBounds = function(x, y) {
return x >= -1 && x < this.width && y >= -1 && y < this.height;
};


VectorField.prototype.bilinear = function(coord, a, b) {
while (a<-360) {
a += 720
}
while (a>360){
a -= 720
}
while (b<-180) {
a += 360
}
while (b>180){
a -= 360
}
var na = Math.floor(a);
var nb = Math.floor(b);
var ma = Math.ceil(a);
var mb = Math.ceil(b);
var fa = a - na;
var fb = b - nb;
try {
var value= this.field[na+360][180-nb][coord] * (1 - fa) * (1 - fb) +
this.field[ma+360][180-nb][coord] * fa * (1 - fb) +
this.field[na+360][180-mb][coord] * (1 - fa) * fb +
this.field[ma+360][180-mb][coord] * fa * fb;
return value
} catch (e) {
// return ;
// TODO: handle exception
}
};
function getValue(data, coord){
var a = coord[0];
var b = coord[1];
var na = Math.floor(a)<-360?na+=720:Math.floor(a);
var nb = Math.floor(b)<-180?nb+=360:Math.floor(b);
var ma = Math.ceil(a)>360?ma-=720:Math.ceil(a);
var mb = Math.ceil(b)>180?mb-=360:Math.ceil(b);
var fa = a - na;
var fb = b - nb;
var index = ((90-nb)*2*720+(na+180)*2);
var value= data[((90-nb)*2*720+(na+180)*2)] * (1 - fa) * (1 - fb) +
data[((90-nb)*2*720+(ma+180)*2)] * fa * (1 - fb) +
data[((90-mb)*2*720+(na+180)*2)] * (1 - fa) * fb +
data[((90-mb)*2*720+(ma+180)*2)] * fa * fb;
return value;
}

VectorField.prototype.getValue = function(x, y, opt_result) {
var vx = this.bilinear('x', x*2, y*2);
var vy = this.bilinear('y', x*2, y*2);
if (opt_result) {
opt_result.x = vx;
opt_result.y = vy;
return opt_result;
}
return new Vector(vx, vy);
};

var MotionDisplay = function(canvas, field, numParticles) {
this.canvas = canvas;
this.field = field;
this.numParticles = numParticles||3224.55;
this.first = true;
this.maxLength = field.maxLength;
this.speedScale = 1;
this.renderState = 'normal';
this.x0 = this.field.x0;
this.x1 = this.field.x1;
this.y0 = this.field.y0;
this.y1 = this.field.y1;
this.state = 'startMove';
//this.makeNewParticles(null, true);
this.colors = [];
this.rgb = '40, 40, 40';
this.background = 'rgb(' + this.rgb + ')';
this.backgroundAlpha = 'rgba(' + this.rgb + ', 0.02)';
this.outsideColor = '#fff';
for (var i = 0; i < 256; i++) {
this.colors[i] = 'rgb(' + i + ',' + i + ',' + i + ')';
}
};
MotionDisplay.prototype.makeParticle = function() {
var dx = 0;
var dy = 0;
var scale = 1;
var safecount = 0;
for (;;) {
var a = Math.random();
var b = Math.random();
/*
* var x = a * this.x0 + (1 - a) * this.x1; var y = b * this.y0 + (1 - b) *
* this.y1;
*/
var x1 = this.field.x0+ (this.field.x1-this.field.x0)*a;
var y1 = this.field.y0+ (this.field.y1-this.field.y0)*b;
var v = this.field.getValue(x1, y1);
var m = v.length() / this.field.maxLength;
// The random factor here is designed to ensure that
// more particles are placed in slower areas; this makes the
// overall distribution appear more even.
if ((v.x || v.y) && (++safecount > 10 || Math.random() > m * .9)) {
// var proj = this.projection.project(x, y);
// var proj = map.getPixelFromCoordinate(new
// ol.proj.fromLonLat([x,y]))
var sx = x1 * scale + dx;
var sy = y1 * scale + dy;
return new Particle(x1, y1, 1 + 40 * Math.random());
if (++safecount > 10 || !(sx < 0 || sy < 0 || sx > this.canvas.width || sy > this.canvas.height)) {
return new Particle(x1, y1, 1 + 40 * Math.random());
}
}
}
};
MotionDisplay.prototype.makeNewParticles = function() {
this.particles = [];
for (var i = 0; i < this.numParticles; i++) {
this.particles.push(this.makeParticle());
}
};
MotionDisplay.prototype.start = function() {
monitorMap.updateSize();
var self = this;
function go() {
self.loop();
self.animationId = window.requestAnimationFrame(go);
}
go();
};

MotionDisplay.prototype.stop = function() {
window.cancelAnimationFrame(this.animationId)
};
MotionDisplay.prototype.loop = function() {
this[this.state].call(this, this)
};

MotionDisplay.prototype.animate = function() {
this.moveThings();
this.draw();
};

MotionDisplay.prototype.startMove = function() {
var mapSize = monitorMap.getSize();
var width = mapSize[0];
var height = mapSize[1];
this.field.width = width;
this.field.height = height;
var zoom = monitorMap.getView().getZoom();
this.numParticles = myView.modalSelect == "gfs"? 75:0;
this.numParticles >=5000? 5000:this.numParticles;
var extent = monitorMap.getView().calculateExtent(mapSize);
var ext2 = new ol.proj.transformExtent(extent,asideVue.projection, 'EPSG:4326' );
this.field.x0 = Math.floor(ext2[0]);
this.field.y0 = Math.floor(ext2[1]);
this.field.x1 = Math.ceil(ext2[2]);
this.field.y1 = Math.ceil(ext2[3]);
this.x0 = Math.floor(ext2[0]);
this.y0 = Math.floor(ext2[1]);
this.x1 = Math.ceil(ext2[2]);
this.y1 = Math.ceil(ext2[3]);
this.canvas.width=width;
this.canvas.height=height;
this.fillColor = "rgba(0, 0, 0, 0.9406060606060607)";//默认0.92
this.strokeStyle = "#ededed";
this.moveSpeed =75;
this.windThick = 0.5;
// canvas.getContext("2d").clearRect(0,0,width,height)
this.makeNewParticles(null, true);
};
MotionDisplay.prototype.moveThings = function() {
var resolution = monitorMap.getView().getResolution();
var speed = this.moveSpeed*resolution/ 3E7;
// console.log(this.state+">>"+this.particles.length)
for (var i = 0; i < this.particles.length; i++) {
var p = this.particles[i];
if (p.age > 0 && this.field.inBounds(p.oldX, p.oldY)) {
var a = this.field.getValue(p.x, p.y);
p.x += speed * a.x;
p.y += speed * a.y;
p.age--;
} else {
this.particles[i] = this.makeParticle();
}
}
};
MotionDisplay.prototype.draw = function() {
var g = this.canvas.getContext('2d');
var w = this.canvas.width;
var h = this.canvas.height;
g.fillStyle = this.fillColor;
// g.fillStyle = this.backgroundAlpha;
var dx = 0;
var dy = 0;
var scale = 1;

// Fade existing particle trails.
var prev = g.globalCompositeOperation;
g.globalCompositeOperation = "destination-in";
g.fillRect(dx, dy, w * scale,h * scale);
g.globalCompositeOperation = prev;

var proj = new Vector(0, 0);
var val = new Vector(0, 0);
g.lineWidth = this.windThick;
// console.log(this.particles.length)
for (var i = 0; i < this.particles.length; i++) {
var p = this.particles[i];
if (!this.field.inBounds(p.oldX, p.oldY)) {
p.age = -2;
continue;
}
// this.projection.project(p.x, p.y, proj);
proj = monitorMap.getPixelFromCoordinate(new ol.proj.fromLonLat([p.x, p.y] ,asideVue.projection));
proj[0] = Math.round(proj[0]) * scale + dx;
proj[1] = Math.round(proj[1]) * scale + dy;
if (proj[0] < 0 || proj[1] < 0 || proj[0] > w || proj[1] > h) {
p.age = -2;
}
if (p.oldX !== -1) {
var wind = this.field.getValue(p.x, p.y, val);
g.strokeStyle = this.strokeStyle;
g.beginPath();
g.moveTo(p.oldX, p.oldY);
g.lineTo(proj[0], proj[1]);
g.stroke();
}
p.oldX = proj[0];
p.oldY = proj[1];
}

};

wind2.js中的代码主要作用是加载了数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
//var utcNow =new Date( myView.datetime.getTime())
//utcNow.setHours(myView.time)
//utcNow = new Date(utcNow.getTime()-8*60*60*1000)
var imguv = new Image();
imguv.crossOrigin = "anonymous";
imguv.src="./img/06.jpg"
//imguv.src =myView.dataSource+"/GFS/win/"+utcNow.Format("yyyy/MM/dd/hh")+".jpg"; // "http://www.meteowise.msns.cn:28083/GFS/win/2017/05/01/01.jpg";//"getImageForecastData?path="+utcNow.Format("yyyy/MM/dd")+"/icon/whole_world/hour_"+utcNow.Format("hh")+"/icon_vitr_u_10_m_"+utcNow.Format("yyyyMMdd_hh")+".jpg"

imguv.onerror= function(){
if(myView.modalSelect==="gfs"){
myView.$Notice.error({ title: '未找到该时次数据',desc:"可能是时间超出范围,也有可能是网络错误"})
}
};
var winspeed = [];
imguv.onload = function() {
var canvas = document.createElement('canvas');
canvas.width = this.width;
canvas.height = this.height;
var ctx = canvas.getContext("2d");
ctx.drawImage(this, 0, 0);
var imageData = ctx.getImageData(0, 0, this.width, this.height).data;
var field = VectorField.readImgArr(imageData);
for (var i = 0; i < 720; i++) {
for (var j = 0; j < 360; j++) {
var vector = field.field[i][j];
var speed = Math.sqrt (Math.pow(vector.x,2)+Math.pow(vector.y,2));
winspeed[(j*720+i)*4] = speed;
winspeed[(j*720+i)*4+1] = speed;
winspeed[(j*720+i)*4+2] = speed;
winspeed[(j*720+i)*4+3] = speed
}
}
// canvasLayer.setSource(new ol.source.ImageCanvas({
// canvasFunction : canvasFunction,
// ratio : 1,
// projection : "epsg:3857"
// }))
display.field = field;
console.log(display);
if (display.first) {
display.start();
monitorMap.on('movestart', function(){
display.state ="startMove";
});
monitorMap.on('moveend', function() {
display.state ="animate";
});
//monitorMap.on('pointermove',pointmove)
display.first=false;
}
setTimeout(function() {
display.state ="animate"
}, 100);
};

具体的原理应该是:风场原理 这篇文章中说明的,我看着这里面的代码也是按这篇文章写的,我就不罗嗦了,具体的内容,可以查阅相关参考文献,至于其他的代码优化,抽取,等下一篇文章继续写吧。

小额赞助
本人提供免费与付费咨询服务,感谢您的支持!赞助请发邮件通知,方便公布您的善意!
**光 3.01 元
Sun 3.00 元
bibichuan 3.00 元
微信公众号
广告位
诚心邀请广大金主爸爸洽谈合作
每日一省
isNaN 和 Number.isNaN 函数的区别?

1.函数 isNaN 接收参数后,会尝试将这个参数转换为数值,任何不能被转换为数值的的值都会返回 true,因此非数字值传入也会返回 true ,会影响 NaN 的判断。

2.函数 Number.isNaN 会首先判断传入参数是否为数字,如果是数字再继续判断是否为 NaN ,不会进行数据类型的转换,这种方法对于 NaN 的判断更为准确。

每日二省
为什么0.1+0.2 ! == 0.3,如何让其相等?

一个直接的解决方法就是设置一个误差范围,通常称为“机器精度”。对JavaScript来说,这个值通常为2-52,在ES6中,提供了Number.EPSILON属性,而它的值就是2-52,只要判断0.1+0.2-0.3是否小于Number.EPSILON,如果小于,就可以判断为0.1+0.2 ===0.3。

每日三省
== 操作符的强制类型转换规则?

1.首先会判断两者类型是否**相同,**相同的话就比较两者的大小。

2.类型不相同的话,就会进行类型转换。

3.会先判断是否在对比 null 和 undefined,是的话就会返回 true。

4.判断两者类型是否为 string 和 number,是的话就会将字符串转换为 number。

5.判断其中一方是否为 boolean,是的话就会把 boolean 转为 number 再进行判断。

6.判断其中一方是否为 object 且另一方为 string、number 或者 symbol,是的话就会把 object 转为原始类型再进行判断。

每日英语
Happiness is time precipitation, smile is the lonely sad.
幸福是年华的沉淀,微笑是寂寞的悲伤。