原文链接及内容

运行界面

main.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
import DataTileSource from '../src/ol/source/DataTile.js';
import Flow from '../src/ol/layer/Flow.js';
import GeoJSON from '../src/ol/format/GeoJSON.js';
import Layer from '../src/ol/layer/Layer.js';
import Map from '../src/ol/Map.js';
import VectorSource from '../src/ol/source/Vector.js';
import View from '../src/ol/View.js';
import WebGLVectorLayerRenderer from '../src/ol/renderer/webgl/VectorLayer.js';
import colormap from 'colormap';
import {createXYZ, wrapX} from '../src/ol/tilegrid.js';
import {get as getProjection, transform} from '../src/ol/proj.js';

const windData = new Promise((resolve, reject) => {
const image = new Image();
image.onload = () => {
const canvas = document.createElement('canvas');
const width = image.width;
const height = image.height;
canvas.width = width;
canvas.height = height;

const context = canvas.getContext('2d');
context.drawImage(image, 0, 0);
const data = context.getImageData(0, 0, width, height).data;
resolve({data, width, height});
};
image.onerror = () => {
reject(new Error('failed to load'));
};
image.src = './data/wind.png';
});

// bilinear Interpolation翻译为双线性差值,具体算法可参考:https://zh.wikipedia.org/wiki/%E5%8F%8C%E7%BA%BF%E6%80%A7%E6%8F%92%E5%80%BC
function bilinearInterpolation(xAlong, yAlong, v11, v21, v12, v22) {
const q11 = (1 - xAlong) * (1 - yAlong) * v11;
const q21 = xAlong * (1 - yAlong) * v21;
const q12 = (1 - xAlong) * yAlong * v12;
const q22 = xAlong * yAlong * v22;
return q11 + q21 + q12 + q22;
}

function interpolatePixels(xAlong, yAlong, p11, p21, p12, p22) {
return p11.map((_, i) =>
bilinearInterpolation(xAlong, yAlong, p11[i], p21[i], p12[i], p22[i]),
);
}

const dataTileGrid = createXYZ();
const dataTileSize = 256;

const inputImageProjection = getProjection('EPSG:4326');
const dataTileProjection = getProjection('EPSG:3857');

const inputBands = 4;
const dataBands = 3;

// range of wind velocities
// these values are stretched between 0 and 255 in the png
const minU = -21.32;
const maxU = 26.8;
const deltaU = maxU - minU;
const minV = -21.57;
const maxV = 21.42;
const deltaV = maxV - minV;

const wind = new DataTileSource({
// transition must be 0, see https://github.com/openlayers/openlayers/issues/16119
transition: 0,
wrapX: true,
async loader(z, x, y) {
const {
data: inputData,
width: inputWidth,
height: inputHeight,
} = await windData;

const tileCoord = wrapX(dataTileGrid, [z, x, y], dataTileProjection);
const extent = dataTileGrid.getTileCoordExtent(tileCoord);
const resolution = dataTileGrid.getResolution(z);
const data = new Float32Array(dataTileSize * dataTileSize * dataBands);
for (let row = 0; row < dataTileSize; ++row) {
let offset = row * dataTileSize * dataBands;
const mapY = extent[3] - row * resolution;
for (let col = 0; col < dataTileSize; ++col) {
const mapX = extent[0] + col * resolution;
const [lon, lat] = transform(
[mapX, mapY],
dataTileProjection,
inputImageProjection,
);

const x = (inputWidth * (lon + 180)) / 360;
let x1 = Math.floor(x);
let x2 = Math.ceil(x);
const xAlong = x - x1;
if (x1 < 0) {
x1 += inputWidth;
}
if (x2 >= inputWidth) {
x2 -= inputWidth;
}

const y = (inputHeight * (90 - lat)) / 180;
let y1 = Math.floor(y);
let y2 = Math.ceil(y);
const yAlong = y - y1;
if (y1 < 0) {
y1 = 0;
}
if (y2 >= inputHeight) {
y2 = inputHeight - 1;
}

const corners = [
[x1, y1],
[x2, y1],
[x1, y2],
[x2, y2],
];

const pixels = corners.map(([cx, cy]) => {
const inputOffset = (cy * 360 + cx) * inputBands;
return [inputData[inputOffset], inputData[inputOffset + 1]];
});

const interpolated = interpolatePixels(xAlong, yAlong, ...pixels);
const u = minU + (deltaU * interpolated[0]) / 255;
const v = minV + (deltaV * interpolated[1]) / 255;

data[offset] = u;
data[offset + 1] = v;
offset += dataBands;
}
}
return data;
},
});

class WebGLLayer extends Layer {
createRenderer() {
return new WebGLVectorLayerRenderer(this, {
style: {
'fill-color': '#555555',
},
});
}
}

const maxSpeed = 20;
const colors = colormap({
colormap: 'viridis',
nshades: 10,
alpha: 0.75,
format: 'rgba',
});
const colorStops = [];
for (let i = 0; i < colors.length; ++i) {
colorStops.push((i * maxSpeed) / (colors.length - 1));
colorStops.push(colors[i]);
}

const map = new Map({
target: 'map',
pixelRatio: 2,
layers: [
new WebGLLayer({
source: new VectorSource({
url: 'https://openlayers.org/data/vector/ocean.json',
format: new GeoJSON(),
}),
}),
new Flow({
source: wind,
maxSpeed,
style: {
color: ['interpolate', ['linear'], ['get', 'speed'], ...colorStops],
},
}),
],
view: new View({
center: [0, 0],
zoom: 0,
}),
});

界面布局文件index.html代码如下:

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
---
layout: example-verbatim.html
title: Wind
shortdesc: Rendering wind velocities with a flow layer.
docs: >
This example uses a flow layer to render wind velocity. The input wind velocity data is encoded
in a PNG, and much of the example code is related to sampling that PNG and generating data tiles
representing a velocity field. The first band (or the red channel) of the data tile is the east-west
component vector of the wind velocity, and the second band (or green channel) is the north-south
component vector. The flow layer is configured with this data tile source and a style for applying
a color ramp based on wind speed.
这个例子使用一个Flow图层对象来渲染风速。输入的风速数据被编码
在PNG中,许多示例代码都与对PNG进行采样和生成数据块有关
表示速度场。数据块的第一个波段(或红色通道)是东西方向
风速分量矢量,第二波段(或绿色通道)为南北方向
组件的向量。Flow图层配置了此数据源和基于风速的颜色渐变的样式。
tags: "wind"
---
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="initial-scale=1.0, user-scalable=no, width=device-width">
<title>Wind</title>
<link rel="stylesheet" type="text/css" href="/theme/ol.css">
<link rel="stylesheet" type="text/css" href="/theme/site.css">
<style>
html, body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
#map {
width: 100%;
height: 100%;
background-color: #a8a8a8;
}
</style>
</head>
<body>
<div id="map"></div>
<script src="wind.js"></script>
<script src="common.js"></script>
</body>
</html>