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