autumnplot-gl 2.2.3 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +65 -8
- package/dist/110.autumnplot-gl.js +2 -0
- package/dist/110.autumnplot-gl.js.map +1 -0
- package/dist/autumnplot-gl.js +3 -0
- package/dist/autumnplot-gl.js.LICENSE.txt +12 -0
- package/dist/autumnplot-gl.js.map +1 -0
- package/dist/marchingsquares.wasm +0 -0
- package/lib/AutumnTypes.d.ts +14 -13
- package/lib/Barbs.d.ts +10 -5
- package/lib/Barbs.js +22 -5
- package/lib/BillboardCollection.d.ts +11 -7
- package/lib/BillboardCollection.js +52 -30
- package/lib/ColorBar.js +51 -7
- package/lib/Colormap.d.ts +14 -3
- package/lib/Colormap.js +63 -12
- package/lib/Contour.d.ts +73 -18
- package/lib/Contour.js +180 -148
- package/lib/ContourCreator.d.ts +18 -0
- package/lib/ContourCreator.js +23 -0
- package/lib/Fill.d.ts +18 -7
- package/lib/Fill.js +73 -41
- package/lib/Grid.d.ts +167 -0
- package/lib/Grid.js +339 -0
- package/lib/Hodographs.d.ts +13 -6
- package/lib/Hodographs.js +58 -71
- package/lib/Map.d.ts +14 -3
- package/lib/Map.js +9 -0
- package/lib/Paintball.d.ts +11 -5
- package/lib/Paintball.js +35 -15
- package/lib/PlotComponent.d.ts +5 -5
- package/lib/PlotLayer.d.ts +12 -12
- package/lib/PlotLayer.js +16 -14
- package/lib/PlotLayer.worker.d.ts +2 -2
- package/lib/PlotLayer.worker.js +105 -66
- package/lib/PolylineCollection.d.ts +20 -9
- package/lib/PolylineCollection.js +158 -32
- package/lib/RawField.d.ts +11 -167
- package/lib/RawField.js +37 -383
- package/lib/TextCollection.d.ts +31 -0
- package/lib/TextCollection.js +295 -0
- package/lib/cpp/marchingsquares.d.ts +6 -0
- package/lib/cpp/marchingsquares.js +3449 -0
- package/lib/cpp/marchingsquares.wasm +0 -0
- package/lib/cpp/marchingsquares_embind.d.ts +6 -0
- package/lib/index.d.ts +13 -6
- package/lib/index.js +12 -3
- package/lib/utils.d.ts +5 -3
- package/lib/utils.js +17 -6
- package/package.json +14 -9
package/lib/Grid.js
ADDED
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
import { Float16Array } from "@petamoriken/float16";
|
|
2
|
+
import { WGLBuffer, WGLTexture } from "autumn-wgl";
|
|
3
|
+
import { lambertConformalConic, rotateSphere } from "./Map";
|
|
4
|
+
import { getGLFormatTypeAlignment, layer_worker } from "./PlotComponent";
|
|
5
|
+
import { Cache } from "./utils";
|
|
6
|
+
async function makeWGLDomainBuffers(gl, grid, native_grid) {
|
|
7
|
+
native_grid = native_grid !== undefined ? native_grid : grid;
|
|
8
|
+
const texcoord_margin_r = 1 / (2 * native_grid.ni);
|
|
9
|
+
const texcoord_margin_s = 1 / (2 * native_grid.nj);
|
|
10
|
+
const grid_cell_size_multiplier = (grid.ni * grid.nj) / (native_grid.ni * native_grid.nj);
|
|
11
|
+
const { lats: field_lats, lons: field_lons } = grid.getEarthCoords();
|
|
12
|
+
const domain_coords = await layer_worker.makeDomainVerticesAndTexCoords(field_lats, field_lons, grid.ni, grid.nj, texcoord_margin_r, texcoord_margin_s);
|
|
13
|
+
for (let icd = 0; icd < domain_coords['grid_cell_size'].length; icd++) {
|
|
14
|
+
domain_coords['grid_cell_size'][icd] *= grid_cell_size_multiplier;
|
|
15
|
+
}
|
|
16
|
+
const vertices = new WGLBuffer(gl, domain_coords['vertices'], 2, gl.TRIANGLE_STRIP);
|
|
17
|
+
const texcoords = new WGLBuffer(gl, domain_coords['tex_coords'], 2, gl.TRIANGLE_STRIP);
|
|
18
|
+
const grid_cell_size = new WGLBuffer(gl, domain_coords['grid_cell_size'], 1, gl.TRIANGLE_STRIP);
|
|
19
|
+
return { 'vertices': vertices, 'texcoords': texcoords, 'cellsize': grid_cell_size };
|
|
20
|
+
}
|
|
21
|
+
async function makeWGLBillboardBuffers(gl, grid, thin_fac, max_zoom) {
|
|
22
|
+
const { lats: field_lats, lons: field_lons } = grid.getEarthCoords();
|
|
23
|
+
const bb_elements = await layer_worker.makeBBElements(field_lats, field_lons, grid.ni, grid.nj, thin_fac, max_zoom);
|
|
24
|
+
const vertices = new WGLBuffer(gl, bb_elements['pts'], 3, gl.TRIANGLE_STRIP);
|
|
25
|
+
const texcoords = new WGLBuffer(gl, bb_elements['tex_coords'], 2, gl.TRIANGLE_STRIP);
|
|
26
|
+
return { 'vertices': vertices, 'texcoords': texcoords };
|
|
27
|
+
}
|
|
28
|
+
function makeVectorRotationTexture(gl, grid) {
|
|
29
|
+
const coords = grid.getEarthCoords();
|
|
30
|
+
if (!grid.is_conformal) {
|
|
31
|
+
// If the grid is non-conformal, we need a fully general change of basis from grid coordinates to earth coordinates. This is not supported for now, so warn about it.
|
|
32
|
+
console.warn('Vector rotations for non-conformal projections are not supported. The output may look incorrect.');
|
|
33
|
+
}
|
|
34
|
+
const rot_vals = new Float16Array(coords.lats.length);
|
|
35
|
+
for (let icd = 0; icd < coords.lats.length; icd++) {
|
|
36
|
+
const lon = coords.lons[icd];
|
|
37
|
+
const lat = coords.lats[icd];
|
|
38
|
+
const [x, y] = grid.transform(lon, lat);
|
|
39
|
+
const [x_pertlon, y_pertlon] = grid.transform(lon + 0.01, lat);
|
|
40
|
+
rot_vals[icd] = Math.atan2(y_pertlon - y, x_pertlon - x);
|
|
41
|
+
}
|
|
42
|
+
const { format, type, row_alignment } = getGLFormatTypeAlignment(gl, true);
|
|
43
|
+
const rot_img = {
|
|
44
|
+
format: format, type: type, row_alignment: row_alignment, image: new Uint16Array(rot_vals.buffer),
|
|
45
|
+
width: grid.ni, height: grid.nj, mag_filter: gl.LINEAR
|
|
46
|
+
};
|
|
47
|
+
const rot_tex = new WGLTexture(gl, rot_img);
|
|
48
|
+
return { 'rotation': rot_tex };
|
|
49
|
+
}
|
|
50
|
+
class Grid {
|
|
51
|
+
constructor(type, is_conformal, ni, nj) {
|
|
52
|
+
this.type = type;
|
|
53
|
+
this.is_conformal = is_conformal;
|
|
54
|
+
this.ni = ni;
|
|
55
|
+
this.nj = nj;
|
|
56
|
+
this.buffer_cache = new Cache((gl) => {
|
|
57
|
+
const new_ni = Math.max(Math.floor(this.ni / 50), 20);
|
|
58
|
+
const new_nj = Math.max(Math.floor(this.nj / 50), 20);
|
|
59
|
+
return makeWGLDomainBuffers(gl, this.copy({ ni: new_ni, nj: new_nj }), this);
|
|
60
|
+
});
|
|
61
|
+
this.billboard_buffer_cache = new Cache((gl, thin_fac, max_zoom) => {
|
|
62
|
+
return makeWGLBillboardBuffers(gl, this, thin_fac, max_zoom);
|
|
63
|
+
});
|
|
64
|
+
this.vector_rotation_cache = new Cache((gl) => {
|
|
65
|
+
return makeVectorRotationTexture(gl, this);
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
async getWGLBuffers(gl) {
|
|
69
|
+
return await this.buffer_cache.getValue(gl);
|
|
70
|
+
}
|
|
71
|
+
async getWGLBillboardBuffers(gl, thin_fac, max_zoom) {
|
|
72
|
+
return await this.billboard_buffer_cache.getValue(gl, thin_fac, max_zoom);
|
|
73
|
+
}
|
|
74
|
+
getVectorRotationTexture(gl) {
|
|
75
|
+
return this.vector_rotation_cache.getValue(gl);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/** A plate carree (a.k.a. lat/lon) grid with uniform grid spacing */
|
|
79
|
+
class PlateCarreeGrid extends Grid {
|
|
80
|
+
/**
|
|
81
|
+
* Create a plate carree grid
|
|
82
|
+
* @param ni - The number of grid points in the i (longitude) direction
|
|
83
|
+
* @param nj - The number of grid points in the j (latitude) direction
|
|
84
|
+
* @param ll_lon - The longitude of the lower left corner of the grid
|
|
85
|
+
* @param ll_lat - The latitude of the lower left corner of the grid
|
|
86
|
+
* @param ur_lon - The longitude of the upper right corner of the grid
|
|
87
|
+
* @param ur_lat - The latitude of the upper right corner of the grid
|
|
88
|
+
*/
|
|
89
|
+
constructor(ni, nj, ll_lon, ll_lat, ur_lon, ur_lat) {
|
|
90
|
+
super('latlon', true, ni, nj);
|
|
91
|
+
this.ll_lon = ll_lon;
|
|
92
|
+
this.ll_lat = ll_lat;
|
|
93
|
+
this.ur_lon = ur_lon;
|
|
94
|
+
this.ur_lat = ur_lat;
|
|
95
|
+
const dlon = (this.ur_lon - this.ll_lon) / (this.ni - 1);
|
|
96
|
+
const dlat = (this.ur_lat - this.ll_lat) / (this.nj - 1);
|
|
97
|
+
this.ll_cache = new Cache(() => {
|
|
98
|
+
const lons = new Float32Array(this.ni * this.nj);
|
|
99
|
+
const lats = new Float32Array(this.ni * this.nj);
|
|
100
|
+
for (let i = 0; i < this.ni; i++) {
|
|
101
|
+
for (let j = 0; j < this.nj; j++) {
|
|
102
|
+
const idx = i + j * this.ni;
|
|
103
|
+
lons[idx] = this.ll_lon + i * dlon;
|
|
104
|
+
lats[idx] = this.ll_lat + j * dlat;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return { 'lons': lons, 'lats': lats };
|
|
108
|
+
});
|
|
109
|
+
this.gc_cache = new Cache(() => {
|
|
110
|
+
const x = new Float32Array(this.ni);
|
|
111
|
+
const y = new Float32Array(this.nj);
|
|
112
|
+
for (let i = 0; i < this.ni; i++) {
|
|
113
|
+
x[i] = this.ll_lon + i * dlon;
|
|
114
|
+
}
|
|
115
|
+
for (let j = 0; j < this.nj; j++) {
|
|
116
|
+
y[j] = this.ll_lat + j * dlat;
|
|
117
|
+
}
|
|
118
|
+
return { x: x, y: y };
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
copy(opts) {
|
|
122
|
+
opts = opts !== undefined ? opts : {};
|
|
123
|
+
const ni = opts.ni !== undefined ? opts.ni : this.ni;
|
|
124
|
+
const nj = opts.nj !== undefined ? opts.nj : this.nj;
|
|
125
|
+
const ll_lon = opts.ll_lon !== undefined ? opts.ll_lon : this.ll_lon;
|
|
126
|
+
const ll_lat = opts.ll_lat !== undefined ? opts.ll_lat : this.ll_lat;
|
|
127
|
+
const ur_lon = opts.ur_lon !== undefined ? opts.ur_lon : this.ur_lon;
|
|
128
|
+
const ur_lat = opts.ur_lat !== undefined ? opts.ur_lat : this.ur_lat;
|
|
129
|
+
return new PlateCarreeGrid(ni, nj, ll_lon, ll_lat, ur_lon, ur_lat);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Get a list of longitudes and latitudes on the grid (internal method)
|
|
133
|
+
*/
|
|
134
|
+
getEarthCoords() {
|
|
135
|
+
return this.ll_cache.getValue();
|
|
136
|
+
}
|
|
137
|
+
getGridCoords() {
|
|
138
|
+
return this.gc_cache.getValue();
|
|
139
|
+
}
|
|
140
|
+
transform(x, y, opts) {
|
|
141
|
+
return [x, y];
|
|
142
|
+
}
|
|
143
|
+
getThinnedGrid(thin_x, thin_y) {
|
|
144
|
+
const dlon = (this.ur_lon - this.ll_lon) / this.ni;
|
|
145
|
+
const dlat = (this.ur_lat - this.ll_lat) / this.nj;
|
|
146
|
+
const ni = Math.ceil(this.ni / thin_x);
|
|
147
|
+
const nj = Math.ceil(this.nj / thin_y);
|
|
148
|
+
const ni_remove = (this.ni - 1) % thin_x;
|
|
149
|
+
const nj_remove = (this.nj - 1) % thin_y;
|
|
150
|
+
const ll_lon = this.ll_lon;
|
|
151
|
+
const ll_lat = this.ll_lat;
|
|
152
|
+
const ur_lon = this.ur_lon - ni_remove * dlon;
|
|
153
|
+
const ur_lat = this.ur_lat - nj_remove * dlat;
|
|
154
|
+
return new PlateCarreeGrid(ni, nj, ll_lon, ll_lat, ur_lon, ur_lat);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/** A rotated lat-lon (plate carree) grid with uniform grid spacing */
|
|
158
|
+
class PlateCarreeRotatedGrid extends Grid {
|
|
159
|
+
/**
|
|
160
|
+
* Create a Lambert conformal conic grid
|
|
161
|
+
* @param ni - The number of grid points in the i (longitude) direction
|
|
162
|
+
* @param nj - The number of grid points in the j (latitude) direction
|
|
163
|
+
* @param np_lon - The longitude of the north pole for the rotated grid
|
|
164
|
+
* @param np_lat - The latitude of the north pole for the rotated grid
|
|
165
|
+
* @param lon_shift - The angle around the rotated north pole to shift the central meridian
|
|
166
|
+
* @param ll_lon - The longitude of the lower left corner of the grid (on the rotated earth)
|
|
167
|
+
* @param ll_lat - The latitude of the lower left corner of the grid (on the rotated earth)
|
|
168
|
+
* @param ur_lon - The longitude of the upper right corner of the grid (on the rotated earth)
|
|
169
|
+
* @param ur_lat - The latitude of the upper right corner of the grid (on the rotated earth)
|
|
170
|
+
*/
|
|
171
|
+
constructor(ni, nj, np_lon, np_lat, lon_shift, ll_lon, ll_lat, ur_lon, ur_lat) {
|
|
172
|
+
super('latlonrot', true, ni, nj);
|
|
173
|
+
this.np_lon = np_lon;
|
|
174
|
+
this.np_lat = np_lat;
|
|
175
|
+
this.lon_shift = lon_shift;
|
|
176
|
+
this.ll_lon = ll_lon;
|
|
177
|
+
this.ll_lat = ll_lat;
|
|
178
|
+
this.ur_lon = ur_lon;
|
|
179
|
+
this.ur_lat = ur_lat;
|
|
180
|
+
this.llrot = rotateSphere({ np_lon: np_lon, np_lat: np_lat, lon_shift: lon_shift });
|
|
181
|
+
const dlon = (this.ur_lon - this.ll_lon) / (this.ni - 1);
|
|
182
|
+
const dlat = (this.ur_lat - this.ll_lat) / (this.nj - 1);
|
|
183
|
+
this.ll_cache = new Cache(() => {
|
|
184
|
+
const lons = new Float32Array(this.ni * this.nj);
|
|
185
|
+
const lats = new Float32Array(this.ni * this.nj);
|
|
186
|
+
for (let i = 0; i < this.ni; i++) {
|
|
187
|
+
const lon_p = this.ll_lon + i * dlon;
|
|
188
|
+
for (let j = 0; j < this.nj; j++) {
|
|
189
|
+
const lat_p = this.ll_lat + j * dlat;
|
|
190
|
+
const [lon, lat] = this.llrot(lon_p, lat_p);
|
|
191
|
+
const idx = i + j * this.ni;
|
|
192
|
+
lons[idx] = lon;
|
|
193
|
+
lats[idx] = lat;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return { lons: lons, lats: lats };
|
|
197
|
+
});
|
|
198
|
+
this.gc_cache = new Cache(() => {
|
|
199
|
+
const x = new Float32Array(this.ni);
|
|
200
|
+
const y = new Float32Array(this.nj);
|
|
201
|
+
for (let i = 0; i < this.ni; i++) {
|
|
202
|
+
x[i] = this.ll_lon + i * dlon;
|
|
203
|
+
}
|
|
204
|
+
for (let j = 0; j < this.nj; j++) {
|
|
205
|
+
y[j] = this.ll_lat + j * dlat;
|
|
206
|
+
}
|
|
207
|
+
return { x: x, y: y };
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
copy(opts) {
|
|
211
|
+
opts = opts !== undefined ? opts : {};
|
|
212
|
+
const ni = opts.ni !== undefined ? opts.ni : this.ni;
|
|
213
|
+
const nj = opts.nj !== undefined ? opts.nj : this.nj;
|
|
214
|
+
const ll_lon = opts.ll_lon !== undefined ? opts.ll_lon : this.ll_lon;
|
|
215
|
+
const ll_lat = opts.ll_lat !== undefined ? opts.ll_lat : this.ll_lat;
|
|
216
|
+
const ur_lon = opts.ur_lon !== undefined ? opts.ur_lon : this.ur_lon;
|
|
217
|
+
const ur_lat = opts.ur_lat !== undefined ? opts.ur_lat : this.ur_lat;
|
|
218
|
+
return new PlateCarreeRotatedGrid(ni, nj, this.np_lon, this.np_lat, this.lon_shift, ll_lon, ll_lat, ur_lon, ur_lat);
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Get a list of longitudes and latitudes on the grid (internal method)
|
|
222
|
+
*/
|
|
223
|
+
getEarthCoords() {
|
|
224
|
+
return this.ll_cache.getValue();
|
|
225
|
+
}
|
|
226
|
+
getGridCoords() {
|
|
227
|
+
return this.gc_cache.getValue();
|
|
228
|
+
}
|
|
229
|
+
transform(x, y, opts) {
|
|
230
|
+
opts = opts === undefined ? {} : opts;
|
|
231
|
+
const inverse = 'inverse' in opts ? opts.inverse : false;
|
|
232
|
+
return this.llrot(x, y, { inverse: !inverse });
|
|
233
|
+
}
|
|
234
|
+
getThinnedGrid(thin_x, thin_y) {
|
|
235
|
+
const dlon = (this.ur_lon - this.ll_lon) / this.ni;
|
|
236
|
+
const dlat = (this.ur_lat - this.ll_lat) / this.nj;
|
|
237
|
+
const ni = Math.ceil(this.ni / thin_x);
|
|
238
|
+
const nj = Math.ceil(this.nj / thin_y);
|
|
239
|
+
const ni_remove = (this.ni - 1) % thin_x;
|
|
240
|
+
const nj_remove = (this.nj - 1) % thin_y;
|
|
241
|
+
const ll_lon = this.ll_lon;
|
|
242
|
+
const ll_lat = this.ll_lat;
|
|
243
|
+
const ur_lon = this.ur_lon - ni_remove * dlon;
|
|
244
|
+
const ur_lat = this.ur_lat - nj_remove * dlat;
|
|
245
|
+
return new PlateCarreeRotatedGrid(ni, nj, this.np_lon, this.np_lat, this.lon_shift, ll_lon, ll_lat, ur_lon, ur_lat);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
/** A Lambert conformal conic grid with uniform grid spacing */
|
|
249
|
+
class LambertGrid extends Grid {
|
|
250
|
+
/**
|
|
251
|
+
* Create a Lambert conformal conic grid
|
|
252
|
+
* @param ni - The number of grid points in the i (longitude) direction
|
|
253
|
+
* @param nj - The number of grid points in the j (latitude) direction
|
|
254
|
+
* @param lon_0 - The standard longitude for the projection; this is also the center longitude for the projection
|
|
255
|
+
* @param lat_0 - The center latitude for the projection
|
|
256
|
+
* @param lat_std - The standard latitudes for the projection
|
|
257
|
+
* @param ll_x - The x coordinate in projection space of the lower-left corner of the grid
|
|
258
|
+
* @param ll_y - The y coordinate in projection space of the lower-left corner of the grid
|
|
259
|
+
* @param ur_x - The x coordinate in projection space of the upper-right corner of the grid
|
|
260
|
+
* @param ur_y - The y coordinate in projection space of the upper-right corner of the grid
|
|
261
|
+
*/
|
|
262
|
+
constructor(ni, nj, lon_0, lat_0, lat_std, ll_x, ll_y, ur_x, ur_y) {
|
|
263
|
+
super('lcc', true, ni, nj);
|
|
264
|
+
this.lon_0 = lon_0;
|
|
265
|
+
this.lat_0 = lat_0;
|
|
266
|
+
this.lat_std = lat_std;
|
|
267
|
+
this.ll_x = ll_x;
|
|
268
|
+
this.ll_y = ll_y;
|
|
269
|
+
this.ur_x = ur_x;
|
|
270
|
+
this.ur_y = ur_y;
|
|
271
|
+
this.lcc = lambertConformalConic({ lon_0: lon_0, lat_0: lat_0, lat_std: lat_std });
|
|
272
|
+
const dx = (this.ur_x - this.ll_x) / (this.ni - 1);
|
|
273
|
+
const dy = (this.ur_y - this.ll_y) / (this.nj - 1);
|
|
274
|
+
this.ll_cache = new Cache(() => {
|
|
275
|
+
const lons = new Float32Array(this.ni * this.nj);
|
|
276
|
+
const lats = new Float32Array(this.ni * this.nj);
|
|
277
|
+
for (let i = 0; i < this.ni; i++) {
|
|
278
|
+
const x = this.ll_x + i * dx;
|
|
279
|
+
for (let j = 0; j < this.nj; j++) {
|
|
280
|
+
const y = this.ll_y + j * dy;
|
|
281
|
+
const [lon, lat] = this.lcc(x, y, { inverse: true });
|
|
282
|
+
const idx = i + j * this.ni;
|
|
283
|
+
lons[idx] = lon;
|
|
284
|
+
lats[idx] = lat;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return { lons: lons, lats: lats };
|
|
288
|
+
});
|
|
289
|
+
this.gc_cache = new Cache(() => {
|
|
290
|
+
const x = new Float32Array(this.ni);
|
|
291
|
+
const y = new Float32Array(this.nj);
|
|
292
|
+
for (let i = 0; i < this.ni; i++) {
|
|
293
|
+
x[i] = this.ll_x + i * dx;
|
|
294
|
+
}
|
|
295
|
+
for (let j = 0; j < this.nj; j++) {
|
|
296
|
+
y[j] = this.ll_y + j * dy;
|
|
297
|
+
}
|
|
298
|
+
return { x: x, y: y };
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
copy(opts) {
|
|
302
|
+
opts = opts !== undefined ? opts : {};
|
|
303
|
+
const ni = opts.ni !== undefined ? opts.ni : this.ni;
|
|
304
|
+
const nj = opts.nj !== undefined ? opts.nj : this.nj;
|
|
305
|
+
const ll_x = opts.ll_x !== undefined ? opts.ll_x : this.ll_x;
|
|
306
|
+
const ll_y = opts.ll_y !== undefined ? opts.ll_y : this.ll_y;
|
|
307
|
+
const ur_x = opts.ur_x !== undefined ? opts.ur_x : this.ur_x;
|
|
308
|
+
const ur_y = opts.ur_y !== undefined ? opts.ur_y : this.ur_y;
|
|
309
|
+
return new LambertGrid(ni, nj, this.lon_0, this.lat_0, this.lat_std, ll_x, ll_y, ur_x, ur_y);
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Get a list of longitudes and latitudes on the grid (internal method)
|
|
313
|
+
*/
|
|
314
|
+
getEarthCoords() {
|
|
315
|
+
return this.ll_cache.getValue();
|
|
316
|
+
}
|
|
317
|
+
getGridCoords() {
|
|
318
|
+
return this.gc_cache.getValue();
|
|
319
|
+
}
|
|
320
|
+
transform(x, y, opts) {
|
|
321
|
+
opts = opts === undefined ? {} : opts;
|
|
322
|
+
const inverse = 'inverse' in opts ? opts.inverse : false;
|
|
323
|
+
return this.lcc(x, y, { inverse: inverse });
|
|
324
|
+
}
|
|
325
|
+
getThinnedGrid(thin_x, thin_y) {
|
|
326
|
+
const dx = (this.ur_x - this.ll_x) / this.ni;
|
|
327
|
+
const dy = (this.ur_y - this.ll_y) / this.nj;
|
|
328
|
+
const ni = Math.ceil(this.ni / thin_x);
|
|
329
|
+
const nj = Math.ceil(this.nj / thin_y);
|
|
330
|
+
const ni_remove = (this.ni - 1) % thin_x;
|
|
331
|
+
const nj_remove = (this.nj - 1) % thin_y;
|
|
332
|
+
const ll_x = this.ll_x;
|
|
333
|
+
const ll_y = this.ll_y;
|
|
334
|
+
const ur_x = this.ur_x - ni_remove * dx;
|
|
335
|
+
const ur_y = this.ur_y - nj_remove * dy;
|
|
336
|
+
return new LambertGrid(ni, nj, this.lon_0, this.lat_0, this.lat_std, ll_x, ll_y, ur_x, ur_y);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
export { Grid, PlateCarreeGrid, PlateCarreeRotatedGrid, LambertGrid };
|
package/lib/Hodographs.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/// <reference types="mapbox-gl" />
|
|
2
1
|
import { PlotComponent } from "./PlotComponent";
|
|
2
|
+
import { MapLikeType } from "./Map";
|
|
3
3
|
import { RawProfileField } from "./RawField";
|
|
4
4
|
import { WebGLAnyRenderingContext } from "./AutumnTypes";
|
|
5
5
|
interface HodographOptions {
|
|
@@ -15,22 +15,29 @@ interface HodographOptions {
|
|
|
15
15
|
thin_fac?: number;
|
|
16
16
|
}
|
|
17
17
|
/** A class representing a a field of hodograph plots */
|
|
18
|
-
declare class Hodographs extends PlotComponent {
|
|
19
|
-
private
|
|
20
|
-
readonly
|
|
21
|
-
readonly thin_fac: number;
|
|
18
|
+
declare class Hodographs<MapType extends MapLikeType> extends PlotComponent<MapType> {
|
|
19
|
+
private profile_field;
|
|
20
|
+
readonly opts: Required<HodographOptions>;
|
|
22
21
|
private gl_elems;
|
|
22
|
+
private line_elems;
|
|
23
|
+
private readonly hodo_scale;
|
|
24
|
+
private readonly bg_size;
|
|
23
25
|
/**
|
|
24
26
|
* Create a field of hodographs
|
|
25
27
|
* @param profile_field - The grid of profiles to plot
|
|
26
28
|
* @param opts - Various options to use when creating the hodographs
|
|
27
29
|
*/
|
|
28
30
|
constructor(profile_field: RawProfileField, opts?: HodographOptions);
|
|
31
|
+
/**
|
|
32
|
+
* Update the profiles displayed
|
|
33
|
+
* @param field - The new profiles to display as hodographs
|
|
34
|
+
*/
|
|
35
|
+
updateField(field: RawProfileField): Promise<void>;
|
|
29
36
|
/**
|
|
30
37
|
* @internal
|
|
31
38
|
* Add the hodographs to a map
|
|
32
39
|
*/
|
|
33
|
-
onAdd(map:
|
|
40
|
+
onAdd(map: MapType, gl: WebGLAnyRenderingContext): Promise<void>;
|
|
34
41
|
/**
|
|
35
42
|
* @internal
|
|
36
43
|
* Render the hodographs
|
package/lib/Hodographs.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { PlotComponent
|
|
1
|
+
import { PlotComponent } from "./PlotComponent";
|
|
2
2
|
import { PolylineCollection } from "./PolylineCollection";
|
|
3
3
|
import { BillboardCollection } from "./BillboardCollection";
|
|
4
|
-
import { getMinZoom,
|
|
5
|
-
import {
|
|
4
|
+
import { getMinZoom, hex2rgb, normalizeOptions } from './utils';
|
|
5
|
+
import { ColorMap } from "./Colormap";
|
|
6
6
|
const LINE_WIDTH = 4;
|
|
7
7
|
const BG_MAX_RING_MAG = 40;
|
|
8
8
|
const HODO_BG_DIMS = {
|
|
@@ -40,28 +40,11 @@ function _createHodoBackgroundTexture() {
|
|
|
40
40
|
}
|
|
41
41
|
;
|
|
42
42
|
let HODO_BG_TEXTURE = null;
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
];
|
|
49
|
-
function _createHodoHeightTexture() {
|
|
50
|
-
let canvas = document.createElement('canvas');
|
|
51
|
-
canvas.width = Math.max(...HODO_COLORS.map(s => Math.max(...s['bounds'])));
|
|
52
|
-
canvas.height = 1;
|
|
53
|
-
let ctx = canvas.getContext('2d');
|
|
54
|
-
HODO_COLORS.forEach(stop => {
|
|
55
|
-
if (ctx === null) {
|
|
56
|
-
throw "Could not get rendering context for the hodograph height texture canvas";
|
|
57
|
-
}
|
|
58
|
-
const [clb, cub] = stop['bounds'];
|
|
59
|
-
ctx.fillStyle = stop['color'];
|
|
60
|
-
ctx.fillRect(clb, 0, cub - clb, 1);
|
|
61
|
-
});
|
|
62
|
-
return canvas;
|
|
63
|
-
}
|
|
64
|
-
let HODO_HEIGHT_TEXTURE = null;
|
|
43
|
+
const HODO_CMAP = new ColorMap([0, 1, 3, 6, 9], ['#ffffcc', '#a1dab4', '#41b6c4', '#225ea8']);
|
|
44
|
+
const hodograph_opt_defaults = {
|
|
45
|
+
bgcolor: '#000000',
|
|
46
|
+
thin_fac: 1
|
|
47
|
+
};
|
|
65
48
|
/** A class representing a a field of hodograph plots */
|
|
66
49
|
class Hodographs extends PlotComponent {
|
|
67
50
|
/**
|
|
@@ -71,81 +54,85 @@ class Hodographs extends PlotComponent {
|
|
|
71
54
|
*/
|
|
72
55
|
constructor(profile_field, opts) {
|
|
73
56
|
super();
|
|
74
|
-
opts = opts || {};
|
|
75
57
|
this.profile_field = profile_field;
|
|
76
|
-
|
|
77
|
-
this.
|
|
78
|
-
this.
|
|
58
|
+
this.opts = normalizeOptions(opts, hodograph_opt_defaults);
|
|
59
|
+
this.hodo_scale = (HODO_BG_DIMS.BB_TEX_WIDTH - LINE_WIDTH / 2) / (HODO_BG_DIMS.BB_TEX_WIDTH * BG_MAX_RING_MAG);
|
|
60
|
+
this.bg_size = 140;
|
|
79
61
|
this.gl_elems = null;
|
|
62
|
+
this.line_elems = null;
|
|
80
63
|
}
|
|
81
64
|
/**
|
|
82
|
-
*
|
|
83
|
-
*
|
|
65
|
+
* Update the profiles displayed
|
|
66
|
+
* @param field - The new profiles to display as hodographs
|
|
84
67
|
*/
|
|
85
|
-
async
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
const pt_ll = new LngLat(prof['lon'], prof['lat']).toMercatorCoord();
|
|
96
|
-
const zoom = getMinZoom(prof['jlat'], prof['ilon'], this.thin_fac);
|
|
97
|
-
const max_tex_z = Math.max(...HODO_COLORS.map(s => Math.max(...s['bounds'])));
|
|
68
|
+
async updateField(field) {
|
|
69
|
+
this.profile_field = field;
|
|
70
|
+
if (this.gl_elems === null)
|
|
71
|
+
return;
|
|
72
|
+
// XXX: This might leak VRAM
|
|
73
|
+
const gl = this.gl_elems.gl;
|
|
74
|
+
this.gl_elems.bg_billboard.updateField(field.getStormMotionGrid());
|
|
75
|
+
const profiles = this.profile_field.profiles;
|
|
76
|
+
const hodo_polyline = profiles.map(prof => {
|
|
77
|
+
const zoom = getMinZoom(prof['jlat'], prof['ilon'], this.opts.thin_fac);
|
|
98
78
|
return {
|
|
99
|
-
'
|
|
100
|
-
'
|
|
79
|
+
'offsets': [...prof['u']].map((u, ipt) => [u - prof['smu'], prof['v'][ipt] - prof['smv']]),
|
|
80
|
+
'vertices': [...prof['u']].map(u => [prof['lon'], prof['lat']]),
|
|
101
81
|
'zoom': zoom,
|
|
102
|
-
'
|
|
82
|
+
'data': [...prof['z']],
|
|
103
83
|
};
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const height_image = { 'format': gl.RGBA, 'type': gl.UNSIGNED_BYTE, 'image': HODO_HEIGHT_TEXTURE, 'mag_filter': gl.NEAREST };
|
|
109
|
-
const hodo_line = new PolylineCollection(gl, hodo_polyline, height_image, 1.5, hodo_scale * bg_size);
|
|
110
|
-
const sm_polyline = await layer_worker.makePolyLines(this.profile_field.profiles.map(prof => {
|
|
111
|
-
const pt_ll = new LngLat(prof['lon'], prof['lat']).toMercatorCoord();
|
|
112
|
-
let ret = {};
|
|
113
|
-
const zoom = getMinZoom(prof['jlat'], prof['ilon'], this.thin_fac);
|
|
84
|
+
});
|
|
85
|
+
const hodo_line = await PolylineCollection.make(gl, hodo_polyline, { line_width: 2.5, cmap: HODO_CMAP, offset_scale: this.hodo_scale * this.bg_size });
|
|
86
|
+
const sm_polyline = profiles.map(prof => {
|
|
87
|
+
const zoom = getMinZoom(prof['jlat'], prof['ilon'], this.opts.thin_fac);
|
|
114
88
|
const sm_mag = Math.hypot(prof['smu'], prof['smv']);
|
|
115
89
|
const sm_ang = Math.PI / 2 - Math.atan2(-prof['smv'], -prof['smu']);
|
|
116
90
|
const buffer = 2;
|
|
117
91
|
return {
|
|
118
|
-
'
|
|
92
|
+
'offsets': [[buffer * Math.sin(sm_ang), buffer * Math.cos(sm_ang)],
|
|
119
93
|
[sm_mag * Math.sin(sm_ang), sm_mag * Math.cos(sm_ang)]],
|
|
120
|
-
'
|
|
121
|
-
'zoom': zoom
|
|
122
|
-
'texcoords': [[0.5, 0.5], [0.5, 0.5]]
|
|
94
|
+
'vertices': [[prof['lon'], prof['lat']], [prof['lon'], prof['lat']]],
|
|
95
|
+
'zoom': zoom
|
|
123
96
|
};
|
|
124
|
-
})
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
97
|
+
});
|
|
98
|
+
const sm_line = await PolylineCollection.make(gl, sm_polyline, { line_width: 1.5, color: this.opts.bgcolor, offset_scale: this.hodo_scale * this.bg_size });
|
|
99
|
+
this.line_elems = {
|
|
100
|
+
hodo_line: hodo_line, sm_line: sm_line
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* @internal
|
|
105
|
+
* Add the hodographs to a map
|
|
106
|
+
*/
|
|
107
|
+
async onAdd(map, gl) {
|
|
108
|
+
if (HODO_BG_TEXTURE === null) {
|
|
109
|
+
HODO_BG_TEXTURE = _createHodoBackgroundTexture();
|
|
110
|
+
}
|
|
111
|
+
const bg_image = { 'format': gl.RGBA, 'type': gl.UNSIGNED_BYTE, 'image': HODO_BG_TEXTURE, 'mag_filter': gl.NEAREST };
|
|
112
|
+
const max_zoom = map.getMaxZoom();
|
|
113
|
+
const bg_billboard = new BillboardCollection(this.profile_field.getStormMotionGrid(), this.opts.thin_fac, max_zoom, bg_image, HODO_BG_DIMS, hex2rgb(this.opts.bgcolor), this.bg_size * 0.004);
|
|
114
|
+
await bg_billboard.setup(gl);
|
|
130
115
|
this.gl_elems = {
|
|
131
|
-
map: map, bg_billboard: bg_billboard
|
|
116
|
+
gl: gl, map: map, bg_billboard: bg_billboard
|
|
132
117
|
};
|
|
118
|
+
this.updateField(this.profile_field);
|
|
133
119
|
}
|
|
134
120
|
/**
|
|
135
121
|
* @internal
|
|
136
122
|
* Render the hodographs
|
|
137
123
|
*/
|
|
138
124
|
render(gl, matrix) {
|
|
139
|
-
if (this.gl_elems === null)
|
|
125
|
+
if (this.gl_elems === null || this.line_elems === null)
|
|
140
126
|
return;
|
|
141
127
|
const gl_elems = this.gl_elems;
|
|
128
|
+
const line_elems = this.line_elems;
|
|
142
129
|
const zoom = gl_elems.map.getZoom();
|
|
143
130
|
const map_width = gl_elems.map.getCanvas().width;
|
|
144
131
|
const map_height = gl_elems.map.getCanvas().height;
|
|
145
132
|
const bearing = gl_elems.map.getBearing();
|
|
146
133
|
const pitch = gl_elems.map.getPitch();
|
|
147
|
-
|
|
148
|
-
|
|
134
|
+
line_elems.hodo_line.render(gl, matrix, [map_width, map_height], zoom, bearing, pitch);
|
|
135
|
+
line_elems.sm_line.render(gl, matrix, [map_width, map_height], zoom, bearing, bearing);
|
|
149
136
|
gl_elems.bg_billboard.render(gl, matrix, [map_width, map_height], zoom, bearing, pitch);
|
|
150
137
|
}
|
|
151
138
|
}
|
package/lib/Map.d.ts
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
type StyleSpecification = {
|
|
2
|
+
glyphs?: string;
|
|
3
|
+
};
|
|
4
|
+
type MapLikeType = {
|
|
5
|
+
triggerRepaint: () => void;
|
|
6
|
+
getCanvas: () => HTMLCanvasElement;
|
|
7
|
+
getStyle: () => StyleSpecification;
|
|
8
|
+
getZoom: () => number;
|
|
9
|
+
getMaxZoom: () => number;
|
|
10
|
+
getBearing: () => number;
|
|
11
|
+
getPitch: () => number;
|
|
12
|
+
};
|
|
3
13
|
interface LambertConformalConicParameters {
|
|
4
14
|
lon_0: number;
|
|
5
15
|
lat_0: number;
|
|
@@ -37,6 +47,7 @@ declare class LngLat {
|
|
|
37
47
|
x: number;
|
|
38
48
|
y: number;
|
|
39
49
|
};
|
|
50
|
+
static fromMercatorCoord(x: number, y: number): LngLat;
|
|
40
51
|
}
|
|
41
52
|
export { LngLat, lambertConformalConic, rotateSphere };
|
|
42
|
-
export type {
|
|
53
|
+
export type { MapLikeType };
|
package/lib/Map.js
CHANGED
|
@@ -124,11 +124,17 @@ function rotateSphere(params) {
|
|
|
124
124
|
function mercatorXfromLng(lng) {
|
|
125
125
|
return (180 + lng) / 360;
|
|
126
126
|
}
|
|
127
|
+
function lngFromMercatorX(x) {
|
|
128
|
+
return 360 * x - 180;
|
|
129
|
+
}
|
|
127
130
|
function mercatorYfromLat(lat) {
|
|
128
131
|
const sin_lat = Math.sin(lat * Math.PI / 180);
|
|
129
132
|
const y = (180 - (90 / Math.PI * Math.log((1 + sin_lat) / (1 - sin_lat)))) / 360;
|
|
130
133
|
return Math.min(2, Math.max(-2, y));
|
|
131
134
|
}
|
|
135
|
+
function latFromMercatorY(y) {
|
|
136
|
+
return Math.atan(Math.sinh((180 - y * 360) * Math.PI / 180)) * 180 / Math.PI;
|
|
137
|
+
}
|
|
132
138
|
/**
|
|
133
139
|
* A `LngLat` object represents a given longitude and latitude coordinate, measured in degrees.
|
|
134
140
|
* These coordinates are based on the [WGS84 (EPSG:4326) standard](https://en.wikipedia.org/wiki/World_Geodetic_System#WGS84).
|
|
@@ -156,5 +162,8 @@ class LngLat {
|
|
|
156
162
|
toMercatorCoord() {
|
|
157
163
|
return { x: mercatorXfromLng(this.lng), y: mercatorYfromLat(this.lat) };
|
|
158
164
|
}
|
|
165
|
+
static fromMercatorCoord(x, y) {
|
|
166
|
+
return new LngLat(lngFromMercatorX(x), latFromMercatorY(y));
|
|
167
|
+
}
|
|
159
168
|
}
|
|
160
169
|
export { LngLat, lambertConformalConic, rotateSphere };
|
package/lib/Paintball.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { TypedArray, WebGLAnyRenderingContext } from "./AutumnTypes";
|
|
2
|
-
import {
|
|
2
|
+
import { MapLikeType } from "./Map";
|
|
3
3
|
import { PlotComponent } from "./PlotComponent";
|
|
4
4
|
import { RawScalarField } from "./RawField";
|
|
5
5
|
interface PaintballOptions {
|
|
@@ -20,11 +20,12 @@ interface PaintballOptions {
|
|
|
20
20
|
* of single-precision floats, this works for up to 24 members. (Technically speaking, I don't need the quotes around "bits", as they're bits of the
|
|
21
21
|
* significand of an IEEE 754 float.)
|
|
22
22
|
*/
|
|
23
|
-
declare class Paintball<ArrayType extends TypedArray> extends PlotComponent {
|
|
24
|
-
private
|
|
25
|
-
readonly
|
|
26
|
-
readonly
|
|
23
|
+
declare class Paintball<ArrayType extends TypedArray, MapType extends MapLikeType> extends PlotComponent<MapType> {
|
|
24
|
+
private field;
|
|
25
|
+
readonly opts: Required<PaintballOptions>;
|
|
26
|
+
private readonly color_components;
|
|
27
27
|
private gl_elems;
|
|
28
|
+
private fill_texture;
|
|
28
29
|
/**
|
|
29
30
|
* Create a paintball plot
|
|
30
31
|
* @param field - A scalar field containing the member objects encoded as "bits." The numerical value of each grid point can be constructed like
|
|
@@ -33,6 +34,11 @@ declare class Paintball<ArrayType extends TypedArray> extends PlotComponent {
|
|
|
33
34
|
* @param opts - Options for creating the paintball plot
|
|
34
35
|
*/
|
|
35
36
|
constructor(field: RawScalarField<ArrayType>, opts?: PaintballOptions);
|
|
37
|
+
/**
|
|
38
|
+
* Update the field displayed as a paintball plot
|
|
39
|
+
* @param field - The new field to display as a paintball plot
|
|
40
|
+
*/
|
|
41
|
+
updateField(field: RawScalarField<ArrayType>): Promise<void>;
|
|
36
42
|
/**
|
|
37
43
|
* @internal
|
|
38
44
|
* Add the paintball plot to a map.
|