autumnplot-gl 2.0.0-beta.1
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/LICENSE +674 -0
- package/README.md +156 -0
- package/lib/AutumnTypes.d.ts +37 -0
- package/lib/AutumnTypes.js +4 -0
- package/lib/Barbs.d.ts +56 -0
- package/lib/Barbs.js +155 -0
- package/lib/BillboardCollection.d.ts +17 -0
- package/lib/BillboardCollection.js +149 -0
- package/lib/ColorBar.d.ts +70 -0
- package/lib/ColorBar.js +183 -0
- package/lib/Colormap.d.ts +70 -0
- package/lib/Colormap.js +137 -0
- package/lib/Contour.d.ts +71 -0
- package/lib/Contour.js +172 -0
- package/lib/ContourFill.d.ts +58 -0
- package/lib/ContourFill.js +125 -0
- package/lib/Hodographs.d.ts +51 -0
- package/lib/Hodographs.js +152 -0
- package/lib/Map.d.ts +34 -0
- package/lib/Map.js +120 -0
- package/lib/Paintball.d.ts +56 -0
- package/lib/Paintball.js +104 -0
- package/lib/PlotComponent.d.ts +20 -0
- package/lib/PlotComponent.js +6 -0
- package/lib/PlotLayer.d.ts +96 -0
- package/lib/PlotLayer.js +131 -0
- package/lib/PlotLayer.worker.d.ts +18 -0
- package/lib/PlotLayer.worker.js +343 -0
- package/lib/PolylineCollection.d.ts +17 -0
- package/lib/PolylineCollection.js +92 -0
- package/lib/RawField.d.ts +194 -0
- package/lib/RawField.js +340 -0
- package/lib/index.d.ts +22 -0
- package/lib/index.js +20 -0
- package/lib/utils.d.ts +9 -0
- package/lib/utils.js +103 -0
- package/package.json +37 -0
package/lib/RawField.js
ADDED
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
import { lambertConformalConic } from "./Map";
|
|
2
|
+
import { layer_worker } from "./PlotComponent";
|
|
3
|
+
import { zip } from "./utils";
|
|
4
|
+
import { WGLBuffer } from "autumn-wgl";
|
|
5
|
+
class Cache {
|
|
6
|
+
constructor(compute_value) {
|
|
7
|
+
this.cached_value = null;
|
|
8
|
+
this.compute_value = compute_value;
|
|
9
|
+
}
|
|
10
|
+
getValue(...args) {
|
|
11
|
+
if (this.cached_value === null) {
|
|
12
|
+
this.cached_value = this.compute_value(...args);
|
|
13
|
+
}
|
|
14
|
+
return this.cached_value;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
async function makeWGLDomainBuffers(gl, grid, native_grid) {
|
|
18
|
+
native_grid = native_grid !== undefined ? native_grid : grid;
|
|
19
|
+
const texcoord_margin_r = 1 / (2 * native_grid.ni);
|
|
20
|
+
const texcoord_margin_s = 1 / (2 * native_grid.nj);
|
|
21
|
+
const grid_cell_size_multiplier = (grid.ni * grid.nj) / (native_grid.ni * native_grid.nj);
|
|
22
|
+
const { lats: field_lats, lons: field_lons } = grid.getCoords();
|
|
23
|
+
const domain_coords = await layer_worker.makeDomainVerticesAndTexCoords(field_lats, field_lons, grid.ni, grid.nj, texcoord_margin_r, texcoord_margin_s);
|
|
24
|
+
for (let icd = 0; icd < domain_coords['grid_cell_size'].length; icd++) {
|
|
25
|
+
domain_coords['grid_cell_size'][icd] *= grid_cell_size_multiplier;
|
|
26
|
+
}
|
|
27
|
+
const vertices = new WGLBuffer(gl, domain_coords['vertices'], 2, gl.TRIANGLE_STRIP);
|
|
28
|
+
const texcoords = new WGLBuffer(gl, domain_coords['tex_coords'], 2, gl.TRIANGLE_STRIP);
|
|
29
|
+
const grid_cell_size = new WGLBuffer(gl, domain_coords['grid_cell_size'], 1, gl.TRIANGLE_STRIP);
|
|
30
|
+
return { 'vertices': vertices, 'texcoords': texcoords, 'cellsize': grid_cell_size };
|
|
31
|
+
}
|
|
32
|
+
async function makeWGLBillboardBuffers(gl, grid, thin_fac, max_zoom) {
|
|
33
|
+
const { lats: field_lats, lons: field_lons } = grid.getCoords();
|
|
34
|
+
const bb_elements = await layer_worker.makeBBElements(field_lats, field_lons, grid.ni, grid.nj, thin_fac, max_zoom);
|
|
35
|
+
const vertices = new WGLBuffer(gl, bb_elements['pts'], 3, gl.TRIANGLE_STRIP);
|
|
36
|
+
const texcoords = new WGLBuffer(gl, bb_elements['tex_coords'], 2, gl.TRIANGLE_STRIP);
|
|
37
|
+
return { 'vertices': vertices, 'texcoords': texcoords };
|
|
38
|
+
}
|
|
39
|
+
class Grid {
|
|
40
|
+
constructor(type, is_conformal, ni, nj) {
|
|
41
|
+
this.type = type;
|
|
42
|
+
this.is_conformal = is_conformal;
|
|
43
|
+
this.ni = ni;
|
|
44
|
+
this.nj = nj;
|
|
45
|
+
this._buffer_cache = new Cache((gl) => {
|
|
46
|
+
const new_ni = Math.max(Math.floor(this.ni / 50), 20);
|
|
47
|
+
const new_nj = Math.max(Math.floor(this.nj / 50), 20);
|
|
48
|
+
return makeWGLDomainBuffers(gl, this.copy({ ni: new_ni, nj: new_nj }), this);
|
|
49
|
+
});
|
|
50
|
+
this._billboard_buffer_cache = new Cache((gl, thin_fac, max_zoom) => {
|
|
51
|
+
return makeWGLBillboardBuffers(gl, this, thin_fac, max_zoom);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
async getWGLBuffers(gl) {
|
|
55
|
+
return await this._buffer_cache.getValue(gl);
|
|
56
|
+
}
|
|
57
|
+
async getWGLBillboardBuffers(gl, thin_fac, max_zoom) {
|
|
58
|
+
return await this._billboard_buffer_cache.getValue(gl, thin_fac, max_zoom);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/** A plate carree (a.k.a. lat/lon) grid with uniform grid spacing */
|
|
62
|
+
class PlateCarreeGrid extends Grid {
|
|
63
|
+
/**
|
|
64
|
+
* Create a plate carree grid
|
|
65
|
+
* @param ni - The number of grid points in the i (longitude) direction
|
|
66
|
+
* @param nj - The number of grid points in the j (latitude) direction
|
|
67
|
+
* @param ll_lon - The longitude of the lower left corner of the grid
|
|
68
|
+
* @param ll_lat - The latitude of the lower left corner of the grid
|
|
69
|
+
* @param ur_lon - The longitude of the upper right corner of the grid
|
|
70
|
+
* @param ur_lat - The latitude of the upper right corner of the grid
|
|
71
|
+
*/
|
|
72
|
+
constructor(ni, nj, ll_lon, ll_lat, ur_lon, ur_lat) {
|
|
73
|
+
super('latlon', true, ni, nj);
|
|
74
|
+
this.ll_lon = ll_lon;
|
|
75
|
+
this.ll_lat = ll_lat;
|
|
76
|
+
this.ur_lon = ur_lon;
|
|
77
|
+
this.ur_lat = ur_lat;
|
|
78
|
+
this._ll_cache = new Cache(() => {
|
|
79
|
+
const dlon = (this.ur_lon - this.ll_lon) / (this.ni - 1);
|
|
80
|
+
const dlat = (this.ur_lat - this.ll_lat) / (this.nj - 1);
|
|
81
|
+
const lons = new Float32Array(this.ni * this.nj);
|
|
82
|
+
const lats = new Float32Array(this.ni * this.nj);
|
|
83
|
+
for (let i = 0; i < this.ni; i++) {
|
|
84
|
+
for (let j = 0; j < this.nj; j++) {
|
|
85
|
+
const idx = i + j * this.ni;
|
|
86
|
+
lons[idx] = this.ll_lon + i * dlon;
|
|
87
|
+
lats[idx] = this.ll_lat + j * dlat;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return { 'lons': lons, 'lats': lats };
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
copy(opts) {
|
|
94
|
+
opts = opts !== undefined ? opts : {};
|
|
95
|
+
const ni = opts.ni !== undefined ? opts.ni : this.ni;
|
|
96
|
+
const nj = opts.nj !== undefined ? opts.nj : this.nj;
|
|
97
|
+
const ll_lon = opts.ll_lon !== undefined ? opts.ll_lon : this.ll_lon;
|
|
98
|
+
const ll_lat = opts.ll_lat !== undefined ? opts.ll_lat : this.ll_lat;
|
|
99
|
+
const ur_lon = opts.ur_lon !== undefined ? opts.ur_lon : this.ur_lon;
|
|
100
|
+
const ur_lat = opts.ur_lat !== undefined ? opts.ur_lat : this.ur_lat;
|
|
101
|
+
return new PlateCarreeGrid(ni, nj, ll_lon, ll_lat, ur_lon, ur_lat);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Get a list of longitudes and latitudes on the grid (internal method)
|
|
105
|
+
*/
|
|
106
|
+
getCoords() {
|
|
107
|
+
return this._ll_cache.getValue();
|
|
108
|
+
}
|
|
109
|
+
transform(x, y, opts) {
|
|
110
|
+
return [x, y];
|
|
111
|
+
}
|
|
112
|
+
getThinnedGrid(thin_x, thin_y) {
|
|
113
|
+
const dlon = (this.ur_lon - this.ll_lon) / this.ni;
|
|
114
|
+
const dlat = (this.ur_lat - this.ll_lat) / this.nj;
|
|
115
|
+
const ni = Math.ceil(this.ni / thin_x);
|
|
116
|
+
const nj = Math.ceil(this.nj / thin_y);
|
|
117
|
+
const ni_remove = (this.ni - 1) % thin_x;
|
|
118
|
+
const nj_remove = (this.nj - 1) % thin_y;
|
|
119
|
+
const ll_lon = this.ll_lon;
|
|
120
|
+
const ll_lat = this.ll_lat;
|
|
121
|
+
const ur_lon = this.ur_lon - ni_remove * dlon;
|
|
122
|
+
const ur_lat = this.ur_lat - nj_remove * dlat;
|
|
123
|
+
return new PlateCarreeGrid(ni, nj, ll_lon, ll_lat, ur_lon, ur_lat);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/** A Lambert conformal conic grid with uniform grid spacing */
|
|
127
|
+
class LambertGrid extends Grid {
|
|
128
|
+
/**
|
|
129
|
+
* Create a Lambert conformal conic grid
|
|
130
|
+
* @param ni - The number of grid points in the i (longitude) direction
|
|
131
|
+
* @param nj - The number of grid points in the j (latitude) direction
|
|
132
|
+
* @param lon_0 - The standard longitude for the projection; this is also the center longitude for the projection
|
|
133
|
+
* @param lat_0 - The center latitude for the projection
|
|
134
|
+
* @param lat_std - The standard latitudes for the projection
|
|
135
|
+
* @param ll_x - The x coordinate in projection space of the lower-left corner of the grid
|
|
136
|
+
* @param ll_y - The y coordinate in projection space of the lower-left corner of the grid
|
|
137
|
+
* @param ur_x - The x coordinate in projection space of the upper-right corner of the grid
|
|
138
|
+
* @param ur_y - The y coordinate in projection space of the upper-right corner of the grid
|
|
139
|
+
*/
|
|
140
|
+
constructor(ni, nj, lon_0, lat_0, lat_std, ll_x, ll_y, ur_x, ur_y) {
|
|
141
|
+
super('lcc', true, ni, nj);
|
|
142
|
+
this.lon_0 = lon_0;
|
|
143
|
+
this.lat_0 = lat_0;
|
|
144
|
+
this.lat_std = lat_std;
|
|
145
|
+
this.ll_x = ll_x;
|
|
146
|
+
this.ll_y = ll_y;
|
|
147
|
+
this.ur_x = ur_x;
|
|
148
|
+
this.ur_y = ur_y;
|
|
149
|
+
this.lcc = lambertConformalConic({ lon_0: lon_0, lat_0: lat_0, lat_std: lat_std });
|
|
150
|
+
this._ll_cache = new Cache(() => {
|
|
151
|
+
const lons = new Float32Array(this.ni * this.nj);
|
|
152
|
+
const lats = new Float32Array(this.ni * this.nj);
|
|
153
|
+
for (let i = 0; i < this.ni; i++) {
|
|
154
|
+
const x = this.ll_x + (this.ur_x - this.ll_x) * i / (this.ni - 1);
|
|
155
|
+
for (let j = 0; j < this.nj; j++) {
|
|
156
|
+
const y = this.ll_y + (this.ur_y - this.ll_y) * j / (this.nj - 1);
|
|
157
|
+
const [lon, lat] = this.lcc(x, y, { inverse: true });
|
|
158
|
+
const idx = i + j * this.ni;
|
|
159
|
+
lons[idx] = lon;
|
|
160
|
+
lats[idx] = lat;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return { lons: lons, lats: lats };
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
copy(opts) {
|
|
167
|
+
opts = opts !== undefined ? opts : {};
|
|
168
|
+
const ni = opts.ni !== undefined ? opts.ni : this.ni;
|
|
169
|
+
const nj = opts.nj !== undefined ? opts.nj : this.nj;
|
|
170
|
+
const ll_x = opts.ll_x !== undefined ? opts.ll_x : this.ll_x;
|
|
171
|
+
const ll_y = opts.ll_y !== undefined ? opts.ll_y : this.ll_y;
|
|
172
|
+
const ur_x = opts.ur_x !== undefined ? opts.ur_x : this.ur_x;
|
|
173
|
+
const ur_y = opts.ur_y !== undefined ? opts.ur_y : this.ur_y;
|
|
174
|
+
return new LambertGrid(ni, nj, this.lon_0, this.lat_0, this.lat_std, ll_x, ll_y, ur_x, ur_y);
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Get a list of longitudes and latitudes on the grid (internal method)
|
|
178
|
+
*/
|
|
179
|
+
getCoords() {
|
|
180
|
+
return this._ll_cache.getValue();
|
|
181
|
+
}
|
|
182
|
+
transform(x, y, opts) {
|
|
183
|
+
opts = opts === undefined ? {} : opts;
|
|
184
|
+
const inverse = 'inverse' in opts ? opts.inverse : false;
|
|
185
|
+
return this.lcc(x, y, { inverse: inverse });
|
|
186
|
+
}
|
|
187
|
+
getThinnedGrid(thin_x, thin_y) {
|
|
188
|
+
const dx = (this.ur_x - this.ll_x) / this.ni;
|
|
189
|
+
const dy = (this.ur_y - this.ll_y) / this.nj;
|
|
190
|
+
const ni = Math.ceil(this.ni / thin_x);
|
|
191
|
+
const nj = Math.ceil(this.nj / thin_y);
|
|
192
|
+
const ni_remove = (this.ni - 1) % thin_x;
|
|
193
|
+
const nj_remove = (this.nj - 1) % thin_y;
|
|
194
|
+
const ll_x = this.ll_x;
|
|
195
|
+
const ll_y = this.ll_y;
|
|
196
|
+
const ur_x = this.ur_x - ni_remove * dx;
|
|
197
|
+
const ur_y = this.ur_y - nj_remove * dy;
|
|
198
|
+
return new LambertGrid(ni, nj, this.lon_0, this.lat_0, this.lat_std, ll_x, ll_y, ur_x, ur_y);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
/** A class representing a raw 2D field of gridded data, such as height or u wind. */
|
|
202
|
+
class RawScalarField {
|
|
203
|
+
/**
|
|
204
|
+
* Create a data field.
|
|
205
|
+
* @param grid - The grid on which the data are defined
|
|
206
|
+
* @param data - The data, which should be given as a 1D array in row-major order, with the first element being at the lower-left corner of the grid.
|
|
207
|
+
*/
|
|
208
|
+
constructor(grid, data) {
|
|
209
|
+
this.grid = grid;
|
|
210
|
+
this.data = data;
|
|
211
|
+
if (grid.ni * grid.nj != data.length) {
|
|
212
|
+
throw `Data size (${data.length}) doesn't match the grid dimensions (${grid.ni} x ${grid.nj}; expected ${grid.ni * grid.nj} points)`;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
getThinnedField(thin_x, thin_y) {
|
|
216
|
+
const new_grid = this.grid.getThinnedGrid(thin_x, thin_y);
|
|
217
|
+
const new_data = new Float32Array(new_grid.ni * new_grid.nj);
|
|
218
|
+
for (let i = 0; i < new_grid.ni; i++) {
|
|
219
|
+
for (let j = 0; j < new_grid.nj; j++) {
|
|
220
|
+
const idx_old = i * thin_x + this.grid.ni * j * thin_y;
|
|
221
|
+
const idx = i + new_grid.ni * j;
|
|
222
|
+
new_data[idx] = this.data[idx_old];
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return new RawScalarField(new_grid, new_data);
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Create a new field by aggregating a number of fields using a specific function
|
|
229
|
+
* @param func - A function that will be applied each element of the field. It should take the same number of arguments as fields you have and return a single number.
|
|
230
|
+
* @param args - The RawScalarFields to aggregate
|
|
231
|
+
* @returns a new gridded field
|
|
232
|
+
* @example
|
|
233
|
+
* // Compute wind speed from u and v
|
|
234
|
+
* wind_speed_field = RawScalarField.aggreateFields(Math.hypot, u_field, v_field);
|
|
235
|
+
*/
|
|
236
|
+
static aggregateFields(func, ...args) {
|
|
237
|
+
function* mapGenerator(gen, func) {
|
|
238
|
+
for (const elem of gen) {
|
|
239
|
+
yield func(elem);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
const zipped_args = zip(...args.map(a => a.data));
|
|
243
|
+
const agg_data = new Float32Array(mapGenerator(zipped_args, (a) => func(...a)));
|
|
244
|
+
return new RawScalarField(args[0].grid, agg_data);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
/** A class representing a 2D gridded field of vectors */
|
|
248
|
+
class RawVectorField {
|
|
249
|
+
/**
|
|
250
|
+
* Create a vector field.
|
|
251
|
+
* @param grid - The grid on which the vector components are defined
|
|
252
|
+
* @param u - The u (east/west) component of the vectors, which should be given as a 1D array in row-major order, with the first element being at the lower-left corner of the grid
|
|
253
|
+
* @param v - The v (north/south) component of the vectors, which should be given as a 1D array in row-major order, with the first element being at the lower-left corner of the grid
|
|
254
|
+
* @param opts - Options for creating the vector field.
|
|
255
|
+
*/
|
|
256
|
+
constructor(grid, u, v, opts) {
|
|
257
|
+
opts = opts === undefined ? {} : opts;
|
|
258
|
+
this.u = new RawScalarField(grid, u);
|
|
259
|
+
this.v = new RawScalarField(grid, v);
|
|
260
|
+
this.relative_to = opts.relative_to === undefined ? 'grid' : opts.relative_to;
|
|
261
|
+
this._rotate_cache = new Cache(() => {
|
|
262
|
+
const grid = this.u.grid;
|
|
263
|
+
const coords = grid.getCoords();
|
|
264
|
+
const u_rot = new Float32Array(coords.lats.length);
|
|
265
|
+
const v_rot = new Float32Array(coords.lats.length);
|
|
266
|
+
for (let icd = 0; icd < coords.lats.length; icd++) {
|
|
267
|
+
const lon = coords.lons[icd];
|
|
268
|
+
const lat = coords.lats[icd];
|
|
269
|
+
const u = this.u.data[icd];
|
|
270
|
+
const v = this.v.data[icd];
|
|
271
|
+
const [x, y] = grid.transform(lon, lat);
|
|
272
|
+
const [x_pertlon, y_pertlon] = grid.transform(lon + 0.01, lat);
|
|
273
|
+
const mag_pertlon = Math.hypot(x - x_pertlon, y - y_pertlon);
|
|
274
|
+
const x_dotlon = (x_pertlon - x) / mag_pertlon;
|
|
275
|
+
const y_dotlon = (y_pertlon - y) / mag_pertlon;
|
|
276
|
+
let x_dotlat, y_dotlat;
|
|
277
|
+
if (grid.is_conformal) {
|
|
278
|
+
// If the grid is conformal, v and u are rotated by the same amount in the same direction.
|
|
279
|
+
x_dotlat = -y_dotlon;
|
|
280
|
+
y_dotlat = x_dotlon;
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
// If the grid is non-conformal, we need a fully general change of basis from grid coordinates to earth coordinates.
|
|
284
|
+
const [x_pertlat, y_pertlat] = grid.transform(lon, lat + 0.01);
|
|
285
|
+
const mag_pertlat = Math.hypot(x - x_pertlat, y - y_pertlat);
|
|
286
|
+
x_dotlat = (x_pertlat - x) / mag_pertlat;
|
|
287
|
+
y_dotlat = (y_pertlat - y) / mag_pertlat;
|
|
288
|
+
}
|
|
289
|
+
u_rot[icd] = x_dotlon * u + y_dotlon * v;
|
|
290
|
+
v_rot[icd] = x_dotlat * u + y_dotlat * v;
|
|
291
|
+
}
|
|
292
|
+
return { u: new RawScalarField(grid, u_rot), v: new RawScalarField(grid, v_rot) };
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
getThinnedField(thin_x, thin_y) {
|
|
296
|
+
const thin_u = this.u.getThinnedField(thin_x, thin_y);
|
|
297
|
+
const thin_v = this.v.getThinnedField(thin_x, thin_y);
|
|
298
|
+
return new RawVectorField(thin_u.grid, thin_u.data, thin_v.data, { relative_to: this.relative_to });
|
|
299
|
+
}
|
|
300
|
+
get grid() {
|
|
301
|
+
return this.u.grid;
|
|
302
|
+
}
|
|
303
|
+
toEarthRelative() {
|
|
304
|
+
let u, v;
|
|
305
|
+
if (this.relative_to == 'earth') {
|
|
306
|
+
u = this.u;
|
|
307
|
+
v = this.v;
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
const { u: u_, v: v_ } = this._rotate_cache.getValue();
|
|
311
|
+
u = u_;
|
|
312
|
+
v = v_;
|
|
313
|
+
}
|
|
314
|
+
return new RawVectorField(u.grid, u.data, v.data, { relative_to: 'earth' });
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
/** A class grid of wind profiles */
|
|
318
|
+
class RawProfileField {
|
|
319
|
+
/**
|
|
320
|
+
* Create a grid of wind profiles
|
|
321
|
+
* @param grid - The grid on which the profiles are defined
|
|
322
|
+
* @param profiles - The wind profiles themselves, which should be given as a 1D array in row-major order, with the first profile being at the lower-left corner of the grid
|
|
323
|
+
*/
|
|
324
|
+
constructor(grid, profiles) {
|
|
325
|
+
this.profiles = profiles;
|
|
326
|
+
this.grid = grid;
|
|
327
|
+
}
|
|
328
|
+
/** Get the gridded storm motion vector field (internal method) */
|
|
329
|
+
getStormMotionGrid() {
|
|
330
|
+
const u = new Float32Array(this.grid.ni * this.grid.nj);
|
|
331
|
+
const v = new Float32Array(this.grid.ni * this.grid.nj);
|
|
332
|
+
this.profiles.forEach(prof => {
|
|
333
|
+
const idx = prof.ilon + this.grid.ni * prof.jlat;
|
|
334
|
+
u[idx] = prof.smu;
|
|
335
|
+
v[idx] = prof.smv;
|
|
336
|
+
});
|
|
337
|
+
return new RawVectorField(this.grid, u, v, { relative_to: 'grid' });
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
export { RawScalarField, RawVectorField, RawProfileField, PlateCarreeGrid, LambertGrid, Grid };
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { PlotComponent } from "./PlotComponent";
|
|
2
|
+
import Contour, { ContourOptions } from "./Contour";
|
|
3
|
+
import ContourFill, { ContourFillOptions } from "./ContourFill";
|
|
4
|
+
import Barbs, { BarbsOptions } from "./Barbs";
|
|
5
|
+
import Paintball, { PaintballOptions } from "./Paintball";
|
|
6
|
+
import Hodographs, { HodographOptions } from './Hodographs';
|
|
7
|
+
import { PlotLayer, MultiPlotLayer } from './PlotLayer';
|
|
8
|
+
import { WindProfile } from "./AutumnTypes";
|
|
9
|
+
import { MapType } from "./Map";
|
|
10
|
+
import { ColorMap, Color } from './Colormap';
|
|
11
|
+
import { makeColorBar, makePaintballKey, ColorbarOrientation, ColorbarTickDirection, ColorBarOptions, PaintballKeyOptions } from "./ColorBar";
|
|
12
|
+
import { RawScalarField, RawVectorField, RawProfileField, Grid, GridType, VectorRelativeTo, RawVectorFieldOptions, PlateCarreeGrid, LambertGrid } from "./RawField";
|
|
13
|
+
declare const colormaps: {
|
|
14
|
+
bluered: (level_min: number, level_max: number, n_colors: number) => ColorMap;
|
|
15
|
+
redblue: (level_min: number, level_max: number, n_colors: number) => ColorMap;
|
|
16
|
+
pw_speed500mb: ColorMap;
|
|
17
|
+
pw_speed850mb: ColorMap;
|
|
18
|
+
pw_cape: ColorMap;
|
|
19
|
+
pw_t2m: ColorMap;
|
|
20
|
+
pw_td2m: ColorMap;
|
|
21
|
+
};
|
|
22
|
+
export { PlotComponent, Barbs, BarbsOptions, Contour, ContourOptions, ContourFill, ContourFillOptions, Paintball, PaintballOptions, Hodographs, HodographOptions, WindProfile, PlotLayer, MultiPlotLayer, MapType, ColorMap, colormaps, makeColorBar, makePaintballKey, Color, ColorbarOrientation, ColorbarTickDirection, ColorBarOptions, PaintballKeyOptions, RawScalarField, RawVectorField, RawProfileField, Grid, GridType, VectorRelativeTo, RawVectorFieldOptions, PlateCarreeGrid, LambertGrid };
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { PlotComponent } from "./PlotComponent";
|
|
2
|
+
import Contour from "./Contour";
|
|
3
|
+
import ContourFill from "./ContourFill";
|
|
4
|
+
import Barbs from "./Barbs";
|
|
5
|
+
import Paintball from "./Paintball";
|
|
6
|
+
import Hodographs from './Hodographs';
|
|
7
|
+
import { PlotLayer, MultiPlotLayer } from './PlotLayer';
|
|
8
|
+
import { ColorMap, bluered, redblue, pw_speed500mb, pw_speed850mb, pw_cape, pw_t2m, pw_td2m } from './Colormap';
|
|
9
|
+
import { makeColorBar, makePaintballKey } from "./ColorBar";
|
|
10
|
+
import { RawScalarField, RawVectorField, RawProfileField, Grid, PlateCarreeGrid, LambertGrid } from "./RawField";
|
|
11
|
+
const colormaps = {
|
|
12
|
+
bluered: bluered,
|
|
13
|
+
redblue: redblue,
|
|
14
|
+
pw_speed500mb: pw_speed500mb,
|
|
15
|
+
pw_speed850mb: pw_speed850mb,
|
|
16
|
+
pw_cape: pw_cape,
|
|
17
|
+
pw_t2m: pw_t2m,
|
|
18
|
+
pw_td2m: pw_td2m
|
|
19
|
+
};
|
|
20
|
+
export { PlotComponent, Barbs, Contour, ContourFill, Paintball, Hodographs, PlotLayer, MultiPlotLayer, ColorMap, colormaps, makeColorBar, makePaintballKey, RawScalarField, RawVectorField, RawProfileField, Grid, PlateCarreeGrid, LambertGrid };
|
package/lib/utils.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
declare const hex2rgba: (hexstr: string, out_type?: string) => [number, number, number, number];
|
|
2
|
+
declare const rgba2hex: (rgba: [number, number, number, number], in_type?: string) => string;
|
|
3
|
+
declare const hex2rgb: (hexstr: string, out_type?: string) => [number, number, number];
|
|
4
|
+
declare const rgb2hex: (rgb: [number, number, number], in_type?: string) => string;
|
|
5
|
+
declare const rgb2hsv: (rgb: [number, number, number]) => [number, number, number];
|
|
6
|
+
declare const hsv2rgb: (hsv: [number, number, number]) => [number, number, number];
|
|
7
|
+
declare function getMinZoom(jlat: number, ilon: number, thin_fac_base: number): number;
|
|
8
|
+
declare function zip(...args: any[]): Generator<any[], void, unknown>;
|
|
9
|
+
export { hex2rgba, rgba2hex, hex2rgb, rgb2hex, rgb2hsv, hsv2rgb, zip, getMinZoom };
|
package/lib/utils.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
const hex2rgba = (hexstr, out_type) => {
|
|
2
|
+
out_type = out_type === undefined ? 'float' : out_type;
|
|
3
|
+
const match = hexstr.match(/#([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})?/i);
|
|
4
|
+
if (match === null) {
|
|
5
|
+
throw `Got '${hexstr}' in hex2rgba, which does not look like a hex color`;
|
|
6
|
+
}
|
|
7
|
+
let rgba = match.slice(1).filter(c => c !== undefined).map(c => parseInt(c, 16));
|
|
8
|
+
if (out_type == 'float') {
|
|
9
|
+
rgba = rgba.map(c => c / 255);
|
|
10
|
+
}
|
|
11
|
+
return rgba[3] === undefined ? [rgba[0], rgba[1], rgba[2], 1] : [rgba[0], rgba[1], rgba[2], rgba[3]];
|
|
12
|
+
};
|
|
13
|
+
const rgba2hex = (rgba, in_type) => {
|
|
14
|
+
in_type = in_type === undefined ? 'float' : in_type;
|
|
15
|
+
let rgba_ = rgba;
|
|
16
|
+
if (in_type == 'float') {
|
|
17
|
+
rgba_ = rgba_.map(c => Math.round(c * 255));
|
|
18
|
+
}
|
|
19
|
+
return '#' + rgba_.map(c => c.toString(16).padStart(2, '0').toUpperCase()).join('');
|
|
20
|
+
};
|
|
21
|
+
const hex2rgb = (hexstr, out_type) => {
|
|
22
|
+
const [r, g, b, a] = hex2rgba(hexstr, out_type);
|
|
23
|
+
return [r, g, b];
|
|
24
|
+
};
|
|
25
|
+
const rgb2hex = (rgb, in_type) => {
|
|
26
|
+
const [r, g, b] = rgb;
|
|
27
|
+
return rgba2hex([r, g, b, 0], in_type).slice(0, -2);
|
|
28
|
+
};
|
|
29
|
+
const rgb2hsv = (rgb) => {
|
|
30
|
+
const [r, g, b] = rgb;
|
|
31
|
+
const Cmax = Math.max(r, g, b);
|
|
32
|
+
const Cmin = Math.min(r, g, b);
|
|
33
|
+
const Delta = Cmax - Cmin;
|
|
34
|
+
let H;
|
|
35
|
+
if (Delta == 0) {
|
|
36
|
+
H = 0;
|
|
37
|
+
}
|
|
38
|
+
else if (Cmax == r) {
|
|
39
|
+
H = 60 * ((g - b) / Delta) % 6;
|
|
40
|
+
}
|
|
41
|
+
else if (Cmax == g) {
|
|
42
|
+
H = 60 * ((b - r) / Delta + 2);
|
|
43
|
+
}
|
|
44
|
+
else if (Cmax == b) {
|
|
45
|
+
H = 60 * ((r - g) / Delta + 4);
|
|
46
|
+
}
|
|
47
|
+
let S = Cmax == 0 ? 0 : Delta / Cmax;
|
|
48
|
+
let V = Cmax;
|
|
49
|
+
return [H, S, V];
|
|
50
|
+
};
|
|
51
|
+
const hsv2rgb = (hsv) => {
|
|
52
|
+
const [H, S, V] = hsv;
|
|
53
|
+
const C = V * S;
|
|
54
|
+
const X = C * (1 - Math.abs(H / 60 % 2 - 1));
|
|
55
|
+
const m = V - C;
|
|
56
|
+
let r_prime, g_prime, b_prime;
|
|
57
|
+
if (0 <= H && H < 60) {
|
|
58
|
+
r_prime = C;
|
|
59
|
+
g_prime = X, b_prime = 0;
|
|
60
|
+
}
|
|
61
|
+
else if (60 <= H && H < 120) {
|
|
62
|
+
r_prime = X;
|
|
63
|
+
g_prime = C, b_prime = 0;
|
|
64
|
+
}
|
|
65
|
+
else if (120 <= H && H < 180) {
|
|
66
|
+
r_prime = 0;
|
|
67
|
+
g_prime = C, b_prime = X;
|
|
68
|
+
}
|
|
69
|
+
else if (180 <= H && H < 240) {
|
|
70
|
+
r_prime = 0;
|
|
71
|
+
g_prime = X, b_prime = C;
|
|
72
|
+
}
|
|
73
|
+
else if (240 <= H && H < 300) {
|
|
74
|
+
r_prime = X;
|
|
75
|
+
g_prime = 0, b_prime = C;
|
|
76
|
+
}
|
|
77
|
+
else if (300 <= H && H < 360) {
|
|
78
|
+
r_prime = C;
|
|
79
|
+
g_prime = 0, b_prime = X;
|
|
80
|
+
}
|
|
81
|
+
return [r_prime + m, g_prime + m, b_prime + m];
|
|
82
|
+
};
|
|
83
|
+
function getMinZoom(jlat, ilon, thin_fac_base) {
|
|
84
|
+
const zoom_base = 1;
|
|
85
|
+
let zoom = zoom_base;
|
|
86
|
+
let thin_fac = thin_fac_base;
|
|
87
|
+
while (((jlat % thin_fac) != 0) || ((ilon % thin_fac) != 0)) {
|
|
88
|
+
zoom += 1;
|
|
89
|
+
thin_fac /= 2;
|
|
90
|
+
}
|
|
91
|
+
return zoom;
|
|
92
|
+
}
|
|
93
|
+
function* zip(...args) {
|
|
94
|
+
const iterators = args.map(x => x[Symbol.iterator]());
|
|
95
|
+
while (true) {
|
|
96
|
+
const current = iterators.map(x => x.next());
|
|
97
|
+
if (current.some(x => x.done)) {
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
yield current.map(x => x.value);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
export { hex2rgba, rgba2hex, hex2rgb, rgb2hex, rgb2hsv, hsv2rgb, zip, getMinZoom };
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "autumnplot-gl",
|
|
3
|
+
"version": "2.0.0-beta.1",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "lib/index.js",
|
|
6
|
+
"types": "lib/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"lib/**/*"
|
|
9
|
+
],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
12
|
+
"docs": "npx typedoc --options typedoc.json",
|
|
13
|
+
"start": "webpack serve --open --mode=development",
|
|
14
|
+
"build-dist": "webpack --mode=production",
|
|
15
|
+
"build-npm": "./scripts/build_npm.sh"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [],
|
|
18
|
+
"author": "Tim Supinie",
|
|
19
|
+
"license": "GPL-3.0-only",
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@types/luxon": "^3.2.0",
|
|
22
|
+
"@types/mapbox-gl": "^2.7.10",
|
|
23
|
+
"@types/maplibre-gl": "^1.14.0",
|
|
24
|
+
"ts-loader": "^9.4.2",
|
|
25
|
+
"typedoc": "^0.23.24",
|
|
26
|
+
"typedoc-plugin-markdown": "^3.14.0",
|
|
27
|
+
"typescript": "^4.9.4",
|
|
28
|
+
"webpack": "^5.75.0",
|
|
29
|
+
"webpack-cli": "^5.0.1",
|
|
30
|
+
"webpack-dev-server": "^4.11.1",
|
|
31
|
+
"webpack-glsl-loader": "^1.0.1"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"autumn-wgl": "^1.1.0",
|
|
35
|
+
"comlink": "^4.3.1"
|
|
36
|
+
}
|
|
37
|
+
}
|