autumnplot-gl 3.2.0 → 4.0.0-beta
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/lib/AutumnTypes.d.ts +53 -5
- package/lib/AutumnTypes.js +25 -1
- package/lib/Barbs.d.ts +6 -5
- package/lib/BillboardCollection.d.ts +8 -7
- package/lib/BillboardCollection.js +69 -58
- package/lib/Color.d.ts +1 -0
- package/lib/Color.js +3 -0
- package/lib/ColorBar.d.ts +10 -0
- package/lib/ColorBar.js +4 -2
- package/lib/Colormap.js +8 -8
- package/lib/Contour.d.ts +18 -8
- package/lib/Contour.js +17 -54
- package/lib/ContourCreator.d.ts +4 -0
- package/lib/ContourCreator.js +2 -1
- package/lib/Fill.d.ts +26 -14
- package/lib/Fill.js +97 -50
- package/lib/Grid.d.ts +124 -29
- package/lib/Grid.js +297 -94
- package/lib/Hodographs.d.ts +9 -8
- package/lib/Hodographs.js +14 -11
- package/lib/Map.js +1 -1
- package/lib/Paintball.d.ts +6 -5
- package/lib/Paintball.js +35 -30
- package/lib/ParticleTracer.d.ts +19 -0
- package/lib/ParticleTracer.js +37 -0
- package/lib/PlotComponent.d.ts +6 -7
- package/lib/PlotComponent.js +8 -3
- package/lib/PlotLayer.d.ts +3 -3
- package/lib/PlotLayer.worker.d.ts +1 -2
- package/lib/PlotLayer.worker.js +15 -50
- package/lib/PolylineCollection.d.ts +5 -3
- package/lib/PolylineCollection.js +60 -37
- package/lib/RawField.d.ts +76 -23
- package/lib/RawField.js +138 -29
- package/lib/ShaderManager.d.ts +12 -0
- package/lib/ShaderManager.js +58 -0
- package/lib/StationPlot.d.ts +136 -25
- package/lib/StationPlot.js +192 -60
- package/lib/TextCollection.d.ts +9 -6
- package/lib/TextCollection.js +94 -62
- package/lib/cpp/marchingsquares.js +483 -585
- package/lib/cpp/marchingsquares.wasm +0 -0
- package/lib/cpp/marchingsquares_embind.d.ts +23 -3
- package/lib/index.d.ts +4 -3
- package/lib/index.js +4 -3
- package/lib/utils.d.ts +4 -1
- package/lib/utils.js +12 -1
- package/package.json +2 -2
package/lib/Grid.js
CHANGED
|
@@ -1,45 +1,58 @@
|
|
|
1
1
|
import { Float16Array } from "@petamoriken/float16";
|
|
2
2
|
import { WGLBuffer, WGLTexture } from "autumn-wgl";
|
|
3
|
-
import { lambertConformalConic, rotateSphere } from "./Map";
|
|
3
|
+
import { LngLat, lambertConformalConic, rotateSphere } from "./Map";
|
|
4
4
|
import { getGLFormatTypeAlignment, layer_worker } from "./PlotComponent";
|
|
5
|
-
import { Cache } from "./utils";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
for (let
|
|
14
|
-
|
|
5
|
+
import { Cache, getArrayConstructor, getMinZoom } from "./utils";
|
|
6
|
+
import { kdTree } from "kd-tree-javascript";
|
|
7
|
+
function argMin(ary) {
|
|
8
|
+
if (ary.length === 0) {
|
|
9
|
+
return -1;
|
|
10
|
+
}
|
|
11
|
+
let min = ary[0];
|
|
12
|
+
let minIndex = 0;
|
|
13
|
+
for (let i = 1; i < ary.length; i++) {
|
|
14
|
+
if (ary[i] < min) {
|
|
15
|
+
minIndex = i;
|
|
16
|
+
min = ary[i];
|
|
17
|
+
}
|
|
15
18
|
}
|
|
19
|
+
return minIndex;
|
|
20
|
+
}
|
|
21
|
+
async function makeWGLDomainBuffers(gl, grid, simplify_ni, simplify_nj) {
|
|
22
|
+
const texcoord_margin_r = 1 / (2 * grid.ni);
|
|
23
|
+
const texcoord_margin_s = 1 / (2 * grid.nj);
|
|
24
|
+
const { lats: field_lats, lons: field_lons } = grid.getEarthCoords(simplify_ni, simplify_nj);
|
|
25
|
+
const domain_coords = await layer_worker.makeDomainVerticesAndTexCoords(field_lats, field_lons, simplify_ni, simplify_nj, texcoord_margin_r, texcoord_margin_s);
|
|
16
26
|
const vertices = new WGLBuffer(gl, domain_coords['vertices'], 2, gl.TRIANGLE_STRIP);
|
|
17
27
|
const texcoords = new WGLBuffer(gl, domain_coords['tex_coords'], 2, gl.TRIANGLE_STRIP);
|
|
18
|
-
|
|
19
|
-
return { 'vertices': vertices, 'texcoords': texcoords, 'cellsize': grid_cell_size };
|
|
28
|
+
return { 'vertices': vertices, 'texcoords': texcoords };
|
|
20
29
|
}
|
|
21
|
-
async function makeWGLBillboardBuffers(gl, grid, thin_fac,
|
|
30
|
+
async function makeWGLBillboardBuffers(gl, grid, thin_fac, map_max_zoom) {
|
|
22
31
|
const { lats: field_lats, lons: field_lons } = grid.getEarthCoords();
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
const
|
|
32
|
+
const min_zoom = grid.getMinVisibleZoom(thin_fac);
|
|
33
|
+
const bb_elements = await layer_worker.makeBBElements(field_lats, field_lons, min_zoom, grid.ni, grid.nj, map_max_zoom);
|
|
34
|
+
const vertices = new WGLBuffer(gl, bb_elements['pts'], 2, gl.POINTS, { per_instance: true });
|
|
35
|
+
const texcoords = new WGLBuffer(gl, bb_elements['tex_coords'], 2, gl.POINTS, { per_instance: true });
|
|
26
36
|
return { 'vertices': vertices, 'texcoords': texcoords };
|
|
27
37
|
}
|
|
28
|
-
function makeVectorRotationTexture(gl, grid) {
|
|
38
|
+
function makeVectorRotationTexture(gl, grid, data_are_earth_relative) {
|
|
29
39
|
const coords = grid.getEarthCoords();
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
const rot_vals = new Float16Array(grid.ni * grid.nj).fill(parseFloat('nan'));
|
|
41
|
+
if (data_are_earth_relative) {
|
|
42
|
+
rot_vals.fill(0);
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
if (!grid.is_conformal) {
|
|
46
|
+
// 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.
|
|
47
|
+
console.warn('Vector rotations for non-conformal projections are not supported. The output may look incorrect.');
|
|
48
|
+
}
|
|
49
|
+
for (let icd = 0; icd < coords.lats.length; icd++) {
|
|
50
|
+
const lon = coords.lons[icd];
|
|
51
|
+
const lat = coords.lats[icd];
|
|
52
|
+
rot_vals[icd] = grid.getVectorRotationAtPoint(lon, lat);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const { format, type, row_alignment } = getGLFormatTypeAlignment(gl, 'float16');
|
|
43
56
|
const rot_img = {
|
|
44
57
|
format: format, type: type, row_alignment: row_alignment, image: new Uint16Array(rot_vals.buffer),
|
|
45
58
|
width: grid.ni, height: grid.nj, mag_filter: gl.LINEAR
|
|
@@ -53,30 +66,93 @@ class Grid {
|
|
|
53
66
|
this.is_conformal = is_conformal;
|
|
54
67
|
this.ni = ni;
|
|
55
68
|
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
69
|
this.billboard_buffer_cache = new Cache((gl, thin_fac, max_zoom) => {
|
|
62
70
|
return makeWGLBillboardBuffers(gl, this, thin_fac, max_zoom);
|
|
63
71
|
});
|
|
64
|
-
this.vector_rotation_cache = new Cache((gl) => {
|
|
65
|
-
return makeVectorRotationTexture(gl, this);
|
|
72
|
+
this.vector_rotation_cache = new Cache((gl, data_are_earth_relative) => {
|
|
73
|
+
return makeVectorRotationTexture(gl, this, data_are_earth_relative);
|
|
66
74
|
});
|
|
67
75
|
}
|
|
68
|
-
async getWGLBuffers(gl) {
|
|
69
|
-
return await this.buffer_cache.getValue(gl);
|
|
70
|
-
}
|
|
71
76
|
async getWGLBillboardBuffers(gl, thin_fac, max_zoom) {
|
|
72
77
|
return await this.billboard_buffer_cache.getValue(gl, thin_fac, max_zoom);
|
|
73
78
|
}
|
|
74
|
-
|
|
75
|
-
|
|
79
|
+
getVectorRotationAtPoint(lon, lat) {
|
|
80
|
+
const [x, y] = this.transform(lon, lat);
|
|
81
|
+
const [x_pertlon, y_pertlon] = this.transform(lon + 0.01, lat);
|
|
82
|
+
return Math.atan2(y_pertlon - y, x_pertlon - x);
|
|
83
|
+
}
|
|
84
|
+
getVectorRotationTexture(gl, data_are_earth_relative) {
|
|
85
|
+
return this.vector_rotation_cache.getValue(gl, data_are_earth_relative);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/** A structured grid (in this case meaning a cartesian grid with i and j coordinates) */
|
|
89
|
+
class StructuredGrid extends Grid {
|
|
90
|
+
constructor(type, is_conformal, ni, nj, thin_x, thin_y) {
|
|
91
|
+
super(type, is_conformal, ni, nj);
|
|
92
|
+
this.thin_x = thin_x === undefined ? 1 : thin_x;
|
|
93
|
+
this.thin_y = thin_y === undefined ? 1 : thin_y;
|
|
94
|
+
this.buffer_cache = new Cache((gl) => {
|
|
95
|
+
const new_ni = Math.max(Math.floor(this.ni / 20), 20);
|
|
96
|
+
const new_nj = Math.max(Math.floor(this.nj / 20), 20);
|
|
97
|
+
return makeWGLDomainBuffers(gl, this, new_ni, new_nj);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
/** @internal */
|
|
101
|
+
xyThinFromMaxZoom(thin_fac, map_max_zoom) {
|
|
102
|
+
const n_density_tiers = Math.log2(thin_fac);
|
|
103
|
+
const n_inaccessible_tiers = Math.max(n_density_tiers + 1 - map_max_zoom, 0);
|
|
104
|
+
const xy_thin = Math.pow(2, n_inaccessible_tiers);
|
|
105
|
+
return [xy_thin, xy_thin];
|
|
106
|
+
}
|
|
107
|
+
/** @internal */
|
|
108
|
+
getMinVisibleZoom(thin_fac) {
|
|
109
|
+
const min_zoom = new Uint8Array(this.ni * this.nj);
|
|
110
|
+
const zoom_thin_fac = thin_fac / Math.max(this.thin_x, this.thin_y);
|
|
111
|
+
for (let ilat = 0; ilat < this.nj * this.thin_y; ilat++) {
|
|
112
|
+
for (let ilon = 0; ilon < this.ni * this.thin_x; ilon++) {
|
|
113
|
+
const idx = ilat * this.ni + ilon;
|
|
114
|
+
min_zoom[idx] = getMinZoom(ilat, ilon, zoom_thin_fac);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return min_zoom;
|
|
118
|
+
}
|
|
119
|
+
/** @internal */
|
|
120
|
+
thinDataArray(original_grid, ary) {
|
|
121
|
+
const arrayType = getArrayConstructor(ary);
|
|
122
|
+
const new_data = new arrayType(this.ni * this.nj);
|
|
123
|
+
for (let i = 0; i < this.ni; i++) {
|
|
124
|
+
for (let j = 0; j < this.nj; j++) {
|
|
125
|
+
const idx_old = i * this.thin_x + original_grid.ni * j * this.thin_y;
|
|
126
|
+
const idx = i + this.ni * j;
|
|
127
|
+
new_data[idx] = ary[idx_old];
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return new_data;
|
|
131
|
+
}
|
|
132
|
+
async getWGLBuffers(gl) {
|
|
133
|
+
return await this.buffer_cache.getValue(gl);
|
|
134
|
+
}
|
|
135
|
+
sampleNearestGridPoint(lon, lat, ary) {
|
|
136
|
+
const [x, y] = this.transform(lon, lat);
|
|
137
|
+
const { x: xs, y: ys } = this.getGridCoords();
|
|
138
|
+
const ll_x = xs[0];
|
|
139
|
+
const ur_x = xs[xs.length - 1];
|
|
140
|
+
const dx = xs[1] - xs[0];
|
|
141
|
+
const ll_y = ys[0];
|
|
142
|
+
const ur_y = ys[ys.length - 1];
|
|
143
|
+
const dy = ys[1] - ys[0];
|
|
144
|
+
if (x < ll_x - 0.5 * dx || x > ur_x + 0.5 * dx || y < ll_y - 0.5 * dy || y > ur_y + 0.5 * dy) {
|
|
145
|
+
return { sample: NaN, sample_lon: NaN, sample_lat: NaN };
|
|
146
|
+
}
|
|
147
|
+
const i_min = argMin(xs.map(xv => Math.abs(xv - x)));
|
|
148
|
+
const j_min = argMin(ys.map(yv => Math.abs(yv - y)));
|
|
149
|
+
const idx = i_min + j_min * this.ni;
|
|
150
|
+
const [lon_min, lat_min] = this.transform(xs[i_min], ys[j_min], { inverse: true });
|
|
151
|
+
return { sample: ary[idx], sample_lon: lon_min, sample_lat: lat_min };
|
|
76
152
|
}
|
|
77
153
|
}
|
|
78
154
|
/** A plate carree (a.k.a. lat/lon) grid with uniform grid spacing */
|
|
79
|
-
class PlateCarreeGrid extends
|
|
155
|
+
class PlateCarreeGrid extends StructuredGrid {
|
|
80
156
|
/**
|
|
81
157
|
* Create a plate carree grid
|
|
82
158
|
* @param ni - The number of grid points in the i (longitude) direction
|
|
@@ -86,22 +162,24 @@ class PlateCarreeGrid extends Grid {
|
|
|
86
162
|
* @param ur_lon - The longitude of the upper right corner of the grid
|
|
87
163
|
* @param ur_lat - The latitude of the upper right corner of the grid
|
|
88
164
|
*/
|
|
89
|
-
constructor(ni, nj, ll_lon, ll_lat, ur_lon, ur_lat) {
|
|
90
|
-
super('latlon', true, ni, nj);
|
|
165
|
+
constructor(ni, nj, ll_lon, ll_lat, ur_lon, ur_lat, thin_x, thin_y) {
|
|
166
|
+
super('latlon', true, ni, nj, thin_x, thin_y);
|
|
91
167
|
this.ll_lon = ll_lon;
|
|
92
168
|
this.ll_lat = ll_lat;
|
|
93
169
|
this.ur_lon = ur_lon;
|
|
94
170
|
this.ur_lat = ur_lat;
|
|
95
171
|
const dlon = (this.ur_lon - this.ll_lon) / (this.ni - 1);
|
|
96
172
|
const dlat = (this.ur_lat - this.ll_lat) / (this.nj - 1);
|
|
97
|
-
this.ll_cache = new Cache(() => {
|
|
98
|
-
const lons = new Float32Array(
|
|
99
|
-
const lats = new Float32Array(
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
173
|
+
this.ll_cache = new Cache((ni, nj) => {
|
|
174
|
+
const lons = new Float32Array(ni * nj);
|
|
175
|
+
const lats = new Float32Array(ni * nj);
|
|
176
|
+
const dlon_req = (this.ni - 1) / (ni - 1) * dlon;
|
|
177
|
+
const dlat_req = (this.nj - 1) / (nj - 1) * dlat;
|
|
178
|
+
for (let i = 0; i < ni; i++) {
|
|
179
|
+
for (let j = 0; j < nj; j++) {
|
|
180
|
+
const idx = i + j * ni;
|
|
181
|
+
lons[idx] = this.ll_lon + i * dlon_req;
|
|
182
|
+
lats[idx] = this.ll_lat + j * dlat_req;
|
|
105
183
|
}
|
|
106
184
|
}
|
|
107
185
|
return { 'lons': lons, 'lats': lats };
|
|
@@ -118,6 +196,7 @@ class PlateCarreeGrid extends Grid {
|
|
|
118
196
|
return { x: x, y: y };
|
|
119
197
|
});
|
|
120
198
|
}
|
|
199
|
+
/** @internal */
|
|
121
200
|
copy(opts) {
|
|
122
201
|
opts = opts !== undefined ? opts : {};
|
|
123
202
|
const ni = opts.ni !== undefined ? opts.ni : this.ni;
|
|
@@ -129,18 +208,25 @@ class PlateCarreeGrid extends Grid {
|
|
|
129
208
|
return new PlateCarreeGrid(ni, nj, ll_lon, ll_lat, ur_lon, ur_lat);
|
|
130
209
|
}
|
|
131
210
|
/**
|
|
211
|
+
* @internal
|
|
132
212
|
* Get a list of longitudes and latitudes on the grid (internal method)
|
|
133
213
|
*/
|
|
134
|
-
getEarthCoords() {
|
|
135
|
-
|
|
214
|
+
getEarthCoords(ni, nj) {
|
|
215
|
+
ni = ni === undefined ? this.ni : ni;
|
|
216
|
+
nj = nj === undefined ? this.nj : nj;
|
|
217
|
+
return this.ll_cache.getValue(ni, nj);
|
|
136
218
|
}
|
|
219
|
+
/** @internal */
|
|
137
220
|
getGridCoords() {
|
|
138
221
|
return this.gc_cache.getValue();
|
|
139
222
|
}
|
|
223
|
+
/** @internal */
|
|
140
224
|
transform(x, y, opts) {
|
|
141
225
|
return [x, y];
|
|
142
226
|
}
|
|
143
|
-
|
|
227
|
+
/** @internal */
|
|
228
|
+
getThinnedGrid(thin_fac, map_max_zoom) {
|
|
229
|
+
const [thin_x, thin_y] = this.xyThinFromMaxZoom(thin_fac, map_max_zoom);
|
|
144
230
|
const dlon = (this.ur_lon - this.ll_lon) / this.ni;
|
|
145
231
|
const dlat = (this.ur_lat - this.ll_lat) / this.nj;
|
|
146
232
|
const ni = Math.ceil(this.ni / thin_x);
|
|
@@ -151,11 +237,11 @@ class PlateCarreeGrid extends Grid {
|
|
|
151
237
|
const ll_lat = this.ll_lat;
|
|
152
238
|
const ur_lon = this.ur_lon - ni_remove * dlon;
|
|
153
239
|
const ur_lat = this.ur_lat - nj_remove * dlat;
|
|
154
|
-
return new PlateCarreeGrid(ni, nj, ll_lon, ll_lat, ur_lon, ur_lat);
|
|
240
|
+
return new PlateCarreeGrid(ni, nj, ll_lon, ll_lat, ur_lon, ur_lat, this.thin_x * thin_x, this.thin_y * thin_y);
|
|
155
241
|
}
|
|
156
242
|
}
|
|
157
243
|
/** A rotated lat-lon (plate carree) grid with uniform grid spacing */
|
|
158
|
-
class PlateCarreeRotatedGrid extends
|
|
244
|
+
class PlateCarreeRotatedGrid extends StructuredGrid {
|
|
159
245
|
/**
|
|
160
246
|
* Create a Lambert conformal conic grid
|
|
161
247
|
* @param ni - The number of grid points in the i (longitude) direction
|
|
@@ -168,8 +254,8 @@ class PlateCarreeRotatedGrid extends Grid {
|
|
|
168
254
|
* @param ur_lon - The longitude of the upper right corner of the grid (on the rotated earth)
|
|
169
255
|
* @param ur_lat - The latitude of the upper right corner of the grid (on the rotated earth)
|
|
170
256
|
*/
|
|
171
|
-
constructor(ni, nj, np_lon, np_lat, lon_shift, ll_lon, ll_lat, ur_lon, ur_lat) {
|
|
172
|
-
super('latlonrot', true, ni, nj);
|
|
257
|
+
constructor(ni, nj, np_lon, np_lat, lon_shift, ll_lon, ll_lat, ur_lon, ur_lat, thin_x, thin_y) {
|
|
258
|
+
super('latlonrot', true, ni, nj, thin_x, thin_y);
|
|
173
259
|
this.np_lon = np_lon;
|
|
174
260
|
this.np_lat = np_lat;
|
|
175
261
|
this.lon_shift = lon_shift;
|
|
@@ -180,15 +266,17 @@ class PlateCarreeRotatedGrid extends Grid {
|
|
|
180
266
|
this.llrot = rotateSphere({ np_lon: np_lon, np_lat: np_lat, lon_shift: lon_shift });
|
|
181
267
|
const dlon = (this.ur_lon - this.ll_lon) / (this.ni - 1);
|
|
182
268
|
const dlat = (this.ur_lat - this.ll_lat) / (this.nj - 1);
|
|
183
|
-
this.ll_cache = new Cache(() => {
|
|
184
|
-
const lons = new Float32Array(
|
|
185
|
-
const lats = new Float32Array(
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
269
|
+
this.ll_cache = new Cache((ni, nj) => {
|
|
270
|
+
const lons = new Float32Array(ni * nj);
|
|
271
|
+
const lats = new Float32Array(ni * nj);
|
|
272
|
+
const dlon_req = (this.ni - 1) / (ni - 1) * dlon;
|
|
273
|
+
const dlat_req = (this.nj - 1) / (nj - 1) * dlat;
|
|
274
|
+
for (let i = 0; i < ni; i++) {
|
|
275
|
+
const lon_p = this.ll_lon + i * dlon_req;
|
|
276
|
+
for (let j = 0; j < nj; j++) {
|
|
277
|
+
const lat_p = this.ll_lat + j * dlat_req;
|
|
190
278
|
const [lon, lat] = this.llrot(lon_p, lat_p);
|
|
191
|
-
const idx = i + j *
|
|
279
|
+
const idx = i + j * ni;
|
|
192
280
|
lons[idx] = lon;
|
|
193
281
|
lats[idx] = lat;
|
|
194
282
|
}
|
|
@@ -207,6 +295,7 @@ class PlateCarreeRotatedGrid extends Grid {
|
|
|
207
295
|
return { x: x, y: y };
|
|
208
296
|
});
|
|
209
297
|
}
|
|
298
|
+
/** @internal */
|
|
210
299
|
copy(opts) {
|
|
211
300
|
opts = opts !== undefined ? opts : {};
|
|
212
301
|
const ni = opts.ni !== undefined ? opts.ni : this.ni;
|
|
@@ -218,20 +307,27 @@ class PlateCarreeRotatedGrid extends Grid {
|
|
|
218
307
|
return new PlateCarreeRotatedGrid(ni, nj, this.np_lon, this.np_lat, this.lon_shift, ll_lon, ll_lat, ur_lon, ur_lat);
|
|
219
308
|
}
|
|
220
309
|
/**
|
|
221
|
-
*
|
|
310
|
+
* @internal
|
|
311
|
+
* Get a list of longitudes and latitudes on the grid
|
|
222
312
|
*/
|
|
223
|
-
getEarthCoords() {
|
|
224
|
-
|
|
313
|
+
getEarthCoords(ni, nj) {
|
|
314
|
+
ni = ni === undefined ? this.ni : ni;
|
|
315
|
+
nj = nj === undefined ? this.nj : nj;
|
|
316
|
+
return this.ll_cache.getValue(ni, nj);
|
|
225
317
|
}
|
|
318
|
+
/** @internal */
|
|
226
319
|
getGridCoords() {
|
|
227
320
|
return this.gc_cache.getValue();
|
|
228
321
|
}
|
|
322
|
+
/** @internal */
|
|
229
323
|
transform(x, y, opts) {
|
|
230
324
|
opts = opts === undefined ? {} : opts;
|
|
231
325
|
const inverse = 'inverse' in opts ? opts.inverse : false;
|
|
232
326
|
return this.llrot(x, y, { inverse: !inverse });
|
|
233
327
|
}
|
|
234
|
-
|
|
328
|
+
/** @internal */
|
|
329
|
+
getThinnedGrid(thin_fac, map_max_zoom) {
|
|
330
|
+
const [thin_x, thin_y] = this.xyThinFromMaxZoom(thin_fac, map_max_zoom);
|
|
235
331
|
const dlon = (this.ur_lon - this.ll_lon) / this.ni;
|
|
236
332
|
const dlat = (this.ur_lat - this.ll_lat) / this.nj;
|
|
237
333
|
const ni = Math.ceil(this.ni / thin_x);
|
|
@@ -242,13 +338,13 @@ class PlateCarreeRotatedGrid extends Grid {
|
|
|
242
338
|
const ll_lat = this.ll_lat;
|
|
243
339
|
const ur_lon = this.ur_lon - ni_remove * dlon;
|
|
244
340
|
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);
|
|
341
|
+
return new PlateCarreeRotatedGrid(ni, nj, this.np_lon, this.np_lat, this.lon_shift, ll_lon, ll_lat, ur_lon, ur_lat, this.thin_x * thin_x, this.thin_y * thin_y);
|
|
246
342
|
}
|
|
247
343
|
}
|
|
248
344
|
/** A Lambert conformal conic grid with uniform grid spacing */
|
|
249
|
-
class LambertGrid extends
|
|
345
|
+
class LambertGrid extends StructuredGrid {
|
|
250
346
|
/**
|
|
251
|
-
* Create a Lambert conformal conic grid
|
|
347
|
+
* Create a Lambert conformal conic grid from the lower-left and upper-right corner x/y values.
|
|
252
348
|
* @param ni - The number of grid points in the i (longitude) direction
|
|
253
349
|
* @param nj - The number of grid points in the j (latitude) direction
|
|
254
350
|
* @param lon_0 - The standard longitude for the projection; this is also the center longitude for the projection
|
|
@@ -259,8 +355,8 @@ class LambertGrid extends Grid {
|
|
|
259
355
|
* @param ur_x - The x coordinate in projection space of the upper-right corner of the grid
|
|
260
356
|
* @param ur_y - The y coordinate in projection space of the upper-right corner of the grid
|
|
261
357
|
*/
|
|
262
|
-
constructor(ni, nj, lon_0, lat_0, lat_std, ll_x, ll_y, ur_x, ur_y) {
|
|
263
|
-
super('lcc', true, ni, nj);
|
|
358
|
+
constructor(ni, nj, lon_0, lat_0, lat_std, ll_x, ll_y, ur_x, ur_y, thin_x, thin_y) {
|
|
359
|
+
super('lcc', true, ni, nj, thin_x, thin_y);
|
|
264
360
|
this.lon_0 = lon_0;
|
|
265
361
|
this.lat_0 = lat_0;
|
|
266
362
|
this.lat_std = lat_std;
|
|
@@ -269,17 +365,19 @@ class LambertGrid extends Grid {
|
|
|
269
365
|
this.ur_x = ur_x;
|
|
270
366
|
this.ur_y = ur_y;
|
|
271
367
|
this.lcc = lambertConformalConic({ lon_0: lon_0, lat_0: lat_0, lat_std: lat_std });
|
|
272
|
-
const dx = (this.ur_x - this.ll_x) /
|
|
273
|
-
const dy = (this.ur_y - this.ll_y) /
|
|
274
|
-
this.ll_cache = new Cache(() => {
|
|
275
|
-
const lons = new Float32Array(
|
|
276
|
-
const lats = new Float32Array(
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
368
|
+
const dx = (this.ur_x - this.ll_x) / this.ni;
|
|
369
|
+
const dy = (this.ur_y - this.ll_y) / this.nj;
|
|
370
|
+
this.ll_cache = new Cache((ni, nj) => {
|
|
371
|
+
const lons = new Float32Array(ni * nj);
|
|
372
|
+
const lats = new Float32Array(ni * nj);
|
|
373
|
+
const dx_req = (this.ni - 1) / (ni - 1) * dx;
|
|
374
|
+
const dy_req = (this.nj - 1) / (nj - 1) * dy;
|
|
375
|
+
for (let i = 0; i < ni; i++) {
|
|
376
|
+
const x = this.ll_x + i * dx_req;
|
|
377
|
+
for (let j = 0; j < nj; j++) {
|
|
378
|
+
const y = this.ll_y + j * dy_req;
|
|
281
379
|
const [lon, lat] = this.lcc(x, y, { inverse: true });
|
|
282
|
-
const idx = i + j *
|
|
380
|
+
const idx = i + j * ni;
|
|
283
381
|
lons[idx] = lon;
|
|
284
382
|
lats[idx] = lat;
|
|
285
383
|
}
|
|
@@ -298,11 +396,25 @@ class LambertGrid extends Grid {
|
|
|
298
396
|
return { x: x, y: y };
|
|
299
397
|
});
|
|
300
398
|
}
|
|
399
|
+
/**
|
|
400
|
+
* Create a Lambert conformal conic grid from the lower-left grid point coordinate and a dx and dy.
|
|
401
|
+
* @param ni - The number of grid points in the i (longitude) direction
|
|
402
|
+
* @param nj - The number of grid points in the j (latitude) direction
|
|
403
|
+
* @param lon_0 - The standard longitude for the projection; this is also the center longitude for the projection
|
|
404
|
+
* @param lat_0 - The center latitude for the projection
|
|
405
|
+
* @param lat_std - The standard latitudes for the projection
|
|
406
|
+
* @param ll_lon - The longitude of the lower-left corner of the grid
|
|
407
|
+
* @param ll_lat - The latitude of the lower-left corner of the grid
|
|
408
|
+
* @param dx - The grid dx in meters
|
|
409
|
+
* @param dy - The grid dy in meters
|
|
410
|
+
* @returns
|
|
411
|
+
*/
|
|
301
412
|
static fromLLCornerLonLat(ni, nj, lon_0, lat_0, lat_std, ll_lon, ll_lat, dx, dy) {
|
|
302
413
|
const lcc = lambertConformalConic({ lon_0: lon_0, lat_0: lat_0, lat_std: lat_std });
|
|
303
414
|
const [ll_x, ll_y] = lcc(ll_lon, ll_lat);
|
|
304
415
|
return new LambertGrid(ni, nj, lon_0, lat_0, lat_std, ll_x, ll_y, ll_x + ni * dx, ll_y + nj * dy);
|
|
305
416
|
}
|
|
417
|
+
/** @internal */
|
|
306
418
|
copy(opts) {
|
|
307
419
|
opts = opts !== undefined ? opts : {};
|
|
308
420
|
const ni = opts.ni !== undefined ? opts.ni : this.ni;
|
|
@@ -314,20 +426,27 @@ class LambertGrid extends Grid {
|
|
|
314
426
|
return new LambertGrid(ni, nj, this.lon_0, this.lat_0, this.lat_std, ll_x, ll_y, ur_x, ur_y);
|
|
315
427
|
}
|
|
316
428
|
/**
|
|
317
|
-
*
|
|
429
|
+
* @internal
|
|
430
|
+
* Get a list of longitudes and latitudes on the grid
|
|
318
431
|
*/
|
|
319
|
-
getEarthCoords() {
|
|
320
|
-
|
|
432
|
+
getEarthCoords(ni, nj) {
|
|
433
|
+
ni = ni === undefined ? this.ni : ni;
|
|
434
|
+
nj = nj === undefined ? this.nj : nj;
|
|
435
|
+
return this.ll_cache.getValue(ni, nj);
|
|
321
436
|
}
|
|
437
|
+
/** @internal */
|
|
322
438
|
getGridCoords() {
|
|
323
439
|
return this.gc_cache.getValue();
|
|
324
440
|
}
|
|
441
|
+
/** @internal */
|
|
325
442
|
transform(x, y, opts) {
|
|
326
443
|
opts = opts === undefined ? {} : opts;
|
|
327
444
|
const inverse = opts.inverse === undefined ? false : opts.inverse;
|
|
328
445
|
return this.lcc(x, y, { inverse: inverse });
|
|
329
446
|
}
|
|
330
|
-
|
|
447
|
+
/** @internal */
|
|
448
|
+
getThinnedGrid(thin_fac, map_max_zoom) {
|
|
449
|
+
const [thin_x, thin_y] = this.xyThinFromMaxZoom(thin_fac, map_max_zoom);
|
|
331
450
|
const dx = (this.ur_x - this.ll_x) / this.ni;
|
|
332
451
|
const dy = (this.ur_y - this.ll_y) / this.nj;
|
|
333
452
|
const ni = Math.ceil(this.ni / thin_x);
|
|
@@ -338,7 +457,91 @@ class LambertGrid extends Grid {
|
|
|
338
457
|
const ll_y = this.ll_y;
|
|
339
458
|
const ur_x = this.ur_x - ni_remove * dx;
|
|
340
459
|
const ur_y = this.ur_y - nj_remove * dy;
|
|
341
|
-
return new LambertGrid(ni, nj, this.lon_0, this.lat_0, this.lat_std, ll_x, ll_y, ur_x, ur_y);
|
|
460
|
+
return new LambertGrid(ni, nj, this.lon_0, this.lat_0, this.lat_std, ll_x, ll_y, ur_x, ur_y, this.thin_x * thin_x, this.thin_y * thin_y);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
/** An unstructured grid */
|
|
464
|
+
class UnstructuredGrid extends Grid {
|
|
465
|
+
/**
|
|
466
|
+
* Create an unstructured grid
|
|
467
|
+
* @param coords - The lat/lon coordinates of the grid points
|
|
468
|
+
*/
|
|
469
|
+
constructor(coords, zoom) {
|
|
470
|
+
const MAX_DIM = 4096;
|
|
471
|
+
super('unstructured', true, Math.min(coords.length, MAX_DIM), Math.floor(coords.length / MAX_DIM) + 1);
|
|
472
|
+
this.coords = coords;
|
|
473
|
+
this.zoom_arg = zoom === undefined ? null : zoom;
|
|
474
|
+
this.zoom_cache = new Cache((thin_fac) => {
|
|
475
|
+
const MAP_MAX_ZOOM = 24;
|
|
476
|
+
const offset = Math.log2(thin_fac);
|
|
477
|
+
const kd_nodes = this.coords.map(c => ({ ...new LngLat(c.lon, c.lat).toMercatorCoord(), min_zoom: MAP_MAX_ZOOM }));
|
|
478
|
+
const tree = new kdTree([...kd_nodes], (a, b) => Math.max(Math.abs(a.x - b.x), Math.abs(a.y - b.y)), ['x', 'y']);
|
|
479
|
+
const recursiveThin = (x, y, depth) => {
|
|
480
|
+
const size = Math.pow(0.5, depth + 1);
|
|
481
|
+
const nodes = tree.nearest({ x: x, y: y, min_zoom: 0 }, 2, size);
|
|
482
|
+
if (nodes.length > 0) {
|
|
483
|
+
const [node, dist] = nodes.sort((a, b) => a[1] - b[1])[0];
|
|
484
|
+
if (node.min_zoom == MAP_MAX_ZOOM) {
|
|
485
|
+
node.min_zoom = Math.max(depth - offset, 0);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
if (nodes.length > 1 && depth < MAP_MAX_ZOOM + offset) {
|
|
489
|
+
recursiveThin(x - size / 2, y - size / 2, depth + 1);
|
|
490
|
+
recursiveThin(x + size / 2, y - size / 2, depth + 1);
|
|
491
|
+
recursiveThin(x - size / 2, y + size / 2, depth + 1);
|
|
492
|
+
recursiveThin(x + size / 2, y + size / 2, depth + 1);
|
|
493
|
+
}
|
|
494
|
+
};
|
|
495
|
+
recursiveThin(0.5, 0.5, 0);
|
|
496
|
+
return new Uint8Array(kd_nodes.map(n => n.min_zoom));
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
/** @internal */
|
|
500
|
+
copy() {
|
|
501
|
+
return new UnstructuredGrid(this.coords);
|
|
502
|
+
}
|
|
503
|
+
/** @internal */
|
|
504
|
+
getEarthCoords() {
|
|
505
|
+
return { lons: new Float32Array(this.coords.map(c => c.lon)), lats: new Float32Array(this.coords.map(c => c.lat)) };
|
|
506
|
+
}
|
|
507
|
+
/** @internal */
|
|
508
|
+
getGridCoords() {
|
|
509
|
+
const { lons, lats } = this.getEarthCoords();
|
|
510
|
+
return { x: lons, y: lats };
|
|
511
|
+
}
|
|
512
|
+
/** @internal */
|
|
513
|
+
transform(x, y, opts) {
|
|
514
|
+
return [x, y];
|
|
515
|
+
}
|
|
516
|
+
/** @internal */
|
|
517
|
+
getMinVisibleZoom(thin_fac) {
|
|
518
|
+
if (this.zoom_arg !== null)
|
|
519
|
+
return this.zoom_arg;
|
|
520
|
+
return this.zoom_cache.getValue(thin_fac);
|
|
521
|
+
}
|
|
522
|
+
/** @internal */
|
|
523
|
+
getThinnedGrid(thin_fac, map_max_zoom) {
|
|
524
|
+
const min_zoom = this.getMinVisibleZoom(thin_fac);
|
|
525
|
+
return new UnstructuredGrid(this.coords.filter((ll, ill) => min_zoom[ill] <= map_max_zoom), min_zoom.filter(ll => ll <= map_max_zoom));
|
|
526
|
+
}
|
|
527
|
+
/** @internal */
|
|
528
|
+
thinDataArray(original_grid, ary) {
|
|
529
|
+
let i_new = 0;
|
|
530
|
+
const arrayType = getArrayConstructor(ary);
|
|
531
|
+
const new_data = new arrayType(this.ni * this.nj);
|
|
532
|
+
for (let i = 0; i < original_grid.coords.length; i++) {
|
|
533
|
+
if (this.coords[i_new].lat == original_grid.coords[i].lat && this.coords[i_new].lon == original_grid.coords[i].lon) {
|
|
534
|
+
new_data[i_new++] = ary[i];
|
|
535
|
+
if (i_new >= this.coords.length)
|
|
536
|
+
break;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
return new_data;
|
|
540
|
+
}
|
|
541
|
+
sampleNearestGridPoint(lon, lat, ary) {
|
|
542
|
+
// TAS: This is gonna be slow. Need to think about using the kdTree here.
|
|
543
|
+
const idx = argMin(this.coords.map(c => (c.lon - lon) * (c.lon - lon) + (c.lat - lat) * (c.lat - lat)));
|
|
544
|
+
return { sample: ary[idx], sample_lon: this.coords[idx].lon, sample_lat: this.coords[idx].lat };
|
|
342
545
|
}
|
|
343
546
|
}
|
|
344
|
-
export { Grid, PlateCarreeGrid, PlateCarreeRotatedGrid, LambertGrid };
|
|
547
|
+
export { Grid, StructuredGrid, PlateCarreeGrid, PlateCarreeRotatedGrid, LambertGrid, UnstructuredGrid };
|
package/lib/Hodographs.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { PlotComponent } from "./PlotComponent";
|
|
2
2
|
import { MapLikeType } from "./Map";
|
|
3
3
|
import { RawProfileField } from "./RawField";
|
|
4
|
-
import { WebGLAnyRenderingContext } from "./AutumnTypes";
|
|
4
|
+
import { RenderMethodArg, WebGLAnyRenderingContext } from "./AutumnTypes";
|
|
5
5
|
import { ColorMap } from "./Colormap";
|
|
6
|
+
import { Grid } from "./Grid";
|
|
6
7
|
interface HodographOptions {
|
|
7
8
|
/**
|
|
8
9
|
* The color of the hodograph plot background as a hex string
|
|
@@ -19,19 +20,19 @@ interface HodographOptions {
|
|
|
19
20
|
* The width of the hodograph line in pixels
|
|
20
21
|
* @default 2.5
|
|
21
22
|
*/
|
|
22
|
-
hodo_line_width
|
|
23
|
+
hodo_line_width?: number;
|
|
23
24
|
/**
|
|
24
25
|
* The width of the lines on the background in pixels
|
|
25
26
|
* @default 1.5
|
|
26
27
|
*/
|
|
27
|
-
background_line_width
|
|
28
|
+
background_line_width?: number;
|
|
28
29
|
/**
|
|
29
30
|
* The colormap to use for the heights on the hodograph. Default is a yellow-blue colormap.
|
|
30
31
|
*/
|
|
31
|
-
height_cmap
|
|
32
|
+
height_cmap?: ColorMap;
|
|
32
33
|
}
|
|
33
34
|
/** A class representing a field of hodograph plots */
|
|
34
|
-
declare class Hodographs<MapType extends MapLikeType> extends PlotComponent<MapType> {
|
|
35
|
+
declare class Hodographs<GridType extends Grid, MapType extends MapLikeType> extends PlotComponent<MapType> {
|
|
35
36
|
private profile_field;
|
|
36
37
|
readonly opts: Required<HodographOptions>;
|
|
37
38
|
private gl_elems;
|
|
@@ -44,12 +45,12 @@ declare class Hodographs<MapType extends MapLikeType> extends PlotComponent<MapT
|
|
|
44
45
|
* @param profile_field - The grid of profiles to plot
|
|
45
46
|
* @param opts - Various options to use when creating the hodographs
|
|
46
47
|
*/
|
|
47
|
-
constructor(profile_field: RawProfileField
|
|
48
|
+
constructor(profile_field: RawProfileField<GridType>, opts?: HodographOptions);
|
|
48
49
|
/**
|
|
49
50
|
* Update the profiles displayed
|
|
50
51
|
* @param field - The new profiles to display as hodographs
|
|
51
52
|
*/
|
|
52
|
-
updateField(field: RawProfileField): Promise<void>;
|
|
53
|
+
updateField(field: RawProfileField<GridType>): Promise<void>;
|
|
53
54
|
/**
|
|
54
55
|
* @internal
|
|
55
56
|
* Add the hodographs to a map
|
|
@@ -59,7 +60,7 @@ declare class Hodographs<MapType extends MapLikeType> extends PlotComponent<MapT
|
|
|
59
60
|
* @internal
|
|
60
61
|
* Render the hodographs
|
|
61
62
|
*/
|
|
62
|
-
render(gl: WebGLAnyRenderingContext,
|
|
63
|
+
render(gl: WebGLAnyRenderingContext, arg: RenderMethodArg): void;
|
|
63
64
|
}
|
|
64
65
|
export default Hodographs;
|
|
65
66
|
export type { HodographOptions };
|