autumnplot-gl 2.1.0 → 2.2.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/lib/RawField.d.ts CHANGED
@@ -1,36 +1,29 @@
1
- import { WebGLAnyRenderingContext, WindProfile } from "./AutumnTypes";
1
+ import { Float16Array } from "@petamoriken/float16";
2
+ import { TypedArray, WebGLAnyRenderingContext, WindProfile } from "./AutumnTypes";
2
3
  import { WGLBuffer } from "autumn-wgl";
3
- declare class Cache<A extends unknown[], R> {
4
- cached_value: R | null;
5
- compute_value: (...args: A) => R;
6
- constructor(compute_value: (...args: A) => R);
7
- getValue(...args: A): R;
8
- }
9
- interface Coords {
4
+ interface EarthCoords {
10
5
  lons: Float32Array;
11
6
  lats: Float32Array;
12
7
  }
13
- type GridType = 'latlon' | 'lcc';
8
+ interface GridCoords {
9
+ x: Float32Array;
10
+ y: Float32Array;
11
+ }
12
+ type GridType = 'latlon' | 'latlonrot' | 'lcc';
14
13
  declare abstract class Grid {
15
14
  readonly type: GridType;
16
15
  readonly ni: number;
17
16
  readonly nj: number;
18
17
  readonly is_conformal: boolean;
19
- readonly _buffer_cache: Cache<[WebGLAnyRenderingContext], Promise<{
20
- 'vertices': WGLBuffer;
21
- 'texcoords': WGLBuffer;
22
- 'cellsize': WGLBuffer;
23
- }>>;
24
- readonly _billboard_buffer_cache: Cache<[WebGLAnyRenderingContext, number, number], Promise<{
25
- 'vertices': WGLBuffer;
26
- 'texcoords': WGLBuffer;
27
- }>>;
18
+ private readonly buffer_cache;
19
+ private readonly billboard_buffer_cache;
28
20
  constructor(type: GridType, is_conformal: boolean, ni: number, nj: number);
29
21
  abstract copy(opts?: {
30
22
  ni?: number;
31
23
  nj?: number;
32
24
  }): Grid;
33
- abstract getCoords(): Coords;
25
+ abstract getEarthCoords(): EarthCoords;
26
+ abstract getGridCoords(): GridCoords;
34
27
  abstract transform(x: number, y: number, opts?: {
35
28
  inverse?: boolean;
36
29
  }): [number, number];
@@ -51,8 +44,8 @@ declare class PlateCarreeGrid extends Grid {
51
44
  readonly ll_lat: number;
52
45
  readonly ur_lon: number;
53
46
  readonly ur_lat: number;
54
- /** @private */
55
- readonly _ll_cache: Cache<[], Coords>;
47
+ private readonly ll_cache;
48
+ private readonly gc_cache;
56
49
  /**
57
50
  * Create a plate carree grid
58
51
  * @param ni - The number of grid points in the i (longitude) direction
@@ -74,12 +67,56 @@ declare class PlateCarreeGrid extends Grid {
74
67
  /**
75
68
  * Get a list of longitudes and latitudes on the grid (internal method)
76
69
  */
77
- getCoords(): Coords;
70
+ getEarthCoords(): EarthCoords;
71
+ getGridCoords(): GridCoords;
78
72
  transform(x: number, y: number, opts?: {
79
73
  inverse?: boolean;
80
74
  }): [number, number];
81
75
  getThinnedGrid(thin_x: number, thin_y: number): PlateCarreeGrid;
82
76
  }
77
+ /** A rotated lat-lon (plate carree) grid with uniform grid spacing */
78
+ declare class PlateCarreeRotatedGrid extends Grid {
79
+ readonly np_lon: number;
80
+ readonly np_lat: number;
81
+ readonly lon_shift: number;
82
+ readonly ll_lon: number;
83
+ readonly ll_lat: number;
84
+ readonly ur_lon: number;
85
+ readonly ur_lat: number;
86
+ private readonly llrot;
87
+ private readonly ll_cache;
88
+ private readonly gc_cache;
89
+ /**
90
+ * Create a Lambert conformal conic grid
91
+ * @param ni - The number of grid points in the i (longitude) direction
92
+ * @param nj - The number of grid points in the j (latitude) direction
93
+ * @param np_lon - The longitude of the north pole for the rotated grid
94
+ * @param np_lat - The latitude of the north pole for the rotated grid
95
+ * @param lon_shift - The angle around the rotated north pole to shift the central meridian
96
+ * @param ll_lon - The longitude of the lower left corner of the grid (on the rotated earth)
97
+ * @param ll_lat - The latitude of the lower left corner of the grid (on the rotated earth)
98
+ * @param ur_lon - The longitude of the upper right corner of the grid (on the rotated earth)
99
+ * @param ur_lat - The latitude of the upper right corner of the grid (on the rotated earth)
100
+ */
101
+ constructor(ni: number, nj: number, np_lon: number, np_lat: number, lon_shift: number, ll_lon: number, ll_lat: number, ur_lon: number, ur_lat: number);
102
+ copy(opts?: {
103
+ ni?: number;
104
+ nj?: number;
105
+ ll_lon?: number;
106
+ ll_lat?: number;
107
+ ur_lon?: number;
108
+ ur_lat?: number;
109
+ }): PlateCarreeRotatedGrid;
110
+ /**
111
+ * Get a list of longitudes and latitudes on the grid (internal method)
112
+ */
113
+ getEarthCoords(): EarthCoords;
114
+ getGridCoords(): GridCoords;
115
+ transform(x: number, y: number, opts?: {
116
+ inverse?: boolean;
117
+ }): [number, number];
118
+ getThinnedGrid(thin_x: number, thin_y: number): PlateCarreeRotatedGrid;
119
+ }
83
120
  /** A Lambert conformal conic grid with uniform grid spacing */
84
121
  declare class LambertGrid extends Grid {
85
122
  readonly lon_0: number;
@@ -89,12 +126,9 @@ declare class LambertGrid extends Grid {
89
126
  readonly ll_y: number;
90
127
  readonly ur_x: number;
91
128
  readonly ur_y: number;
92
- /** @private */
93
- readonly lcc: (a: number, b: number, opts?: {
94
- inverse: boolean;
95
- }) => [number, number];
96
- /** @private */
97
- readonly _ll_cache: Cache<[], Coords>;
129
+ private readonly lcc;
130
+ private readonly ll_cache;
131
+ private readonly gc_cache;
98
132
  /**
99
133
  * Create a Lambert conformal conic grid
100
134
  * @param ni - The number of grid points in the i (longitude) direction
@@ -119,23 +153,28 @@ declare class LambertGrid extends Grid {
119
153
  /**
120
154
  * Get a list of longitudes and latitudes on the grid (internal method)
121
155
  */
122
- getCoords(): Coords;
156
+ getEarthCoords(): EarthCoords;
157
+ getGridCoords(): GridCoords;
123
158
  transform(x: number, y: number, opts?: {
124
159
  inverse?: boolean;
125
160
  }): [number, number];
126
161
  getThinnedGrid(thin_x: number, thin_y: number): LambertGrid;
127
162
  }
163
+ type TextureDataType<ArrayType> = ArrayType extends Float32Array ? Float32Array : Uint16Array;
128
164
  /** A class representing a raw 2D field of gridded data, such as height or u wind. */
129
- declare class RawScalarField {
165
+ declare class RawScalarField<ArrayType extends TypedArray> {
130
166
  readonly grid: Grid;
131
- readonly data: Float32Array;
167
+ readonly data: ArrayType;
132
168
  /**
133
169
  * Create a data field.
134
170
  * @param grid - The grid on which the data are defined
135
171
  * @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.
136
172
  */
137
- constructor(grid: Grid, data: Float32Array);
138
- getThinnedField(thin_x: number, thin_y: number): RawScalarField;
173
+ constructor(grid: Grid, data: ArrayType);
174
+ /** @internal */
175
+ getTextureData(): TextureDataType<ArrayType>;
176
+ isFloat16(): boolean;
177
+ getThinnedField(thin_x: number, thin_y: number): RawScalarField<ArrayType>;
139
178
  /**
140
179
  * Create a new field by aggregating a number of fields using a specific function
141
180
  * @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.
@@ -145,7 +184,7 @@ declare class RawScalarField {
145
184
  * // Compute wind speed from u and v
146
185
  * wind_speed_field = RawScalarField.aggreateFields(Math.hypot, u_field, v_field);
147
186
  */
148
- static aggregateFields(func: (...args: number[]) => number, ...args: RawScalarField[]): RawScalarField;
187
+ static aggregateFields<ArrayType extends TypedArray>(func: (...args: number[]) => number, ...args: RawScalarField<ArrayType>[]): RawScalarField<ArrayType>;
149
188
  }
150
189
  type VectorRelativeTo = 'earth' | 'grid';
151
190
  interface RawVectorFieldOptions {
@@ -156,15 +195,11 @@ interface RawVectorFieldOptions {
156
195
  relative_to?: VectorRelativeTo;
157
196
  }
158
197
  /** A class representing a 2D gridded field of vectors */
159
- declare class RawVectorField {
160
- readonly u: RawScalarField;
161
- readonly v: RawScalarField;
198
+ declare class RawVectorField<ArrayType extends TypedArray> {
199
+ readonly u: RawScalarField<ArrayType>;
200
+ readonly v: RawScalarField<ArrayType>;
162
201
  readonly relative_to: VectorRelativeTo;
163
- /** @private */
164
- readonly _rotate_cache: Cache<[], {
165
- u: RawScalarField;
166
- v: RawScalarField;
167
- }>;
202
+ private readonly rotate_cache;
168
203
  /**
169
204
  * Create a vector field.
170
205
  * @param grid - The grid on which the vector components are defined
@@ -172,10 +207,10 @@ declare class RawVectorField {
172
207
  * @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
173
208
  * @param opts - Options for creating the vector field.
174
209
  */
175
- constructor(grid: Grid, u: Float32Array, v: Float32Array, opts?: RawVectorFieldOptions);
176
- getThinnedField(thin_x: number, thin_y: number): RawVectorField;
210
+ constructor(grid: Grid, u: ArrayType, v: ArrayType, opts?: RawVectorFieldOptions);
211
+ getThinnedField(thin_x: number, thin_y: number): RawVectorField<ArrayType>;
177
212
  get grid(): Grid;
178
- toEarthRelative(): RawVectorField;
213
+ toEarthRelative(): RawVectorField<ArrayType>;
179
214
  }
180
215
  /** A class grid of wind profiles */
181
216
  declare class RawProfileField {
@@ -188,7 +223,7 @@ declare class RawProfileField {
188
223
  */
189
224
  constructor(grid: Grid, profiles: WindProfile[]);
190
225
  /** Get the gridded storm motion vector field (internal method) */
191
- getStormMotionGrid(): RawVectorField;
226
+ getStormMotionGrid(): RawVectorField<Float16Array>;
192
227
  }
193
- export { RawScalarField, RawVectorField, RawProfileField, PlateCarreeGrid, LambertGrid, Grid };
194
- export type { GridType, RawVectorFieldOptions, VectorRelativeTo };
228
+ export { RawScalarField, RawVectorField, RawProfileField, PlateCarreeGrid, PlateCarreeRotatedGrid, LambertGrid, Grid };
229
+ export type { GridType, RawVectorFieldOptions, VectorRelativeTo, TextureDataType };
package/lib/RawField.js CHANGED
@@ -1,25 +1,14 @@
1
- import { lambertConformalConic } from "./Map";
1
+ import { Float16Array } from "@petamoriken/float16";
2
+ import { lambertConformalConic, rotateSphere } from "./Map";
2
3
  import { layer_worker } from "./PlotComponent";
3
- import { zip } from "./utils";
4
+ import { Cache, zip } from "./utils";
4
5
  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
6
  async function makeWGLDomainBuffers(gl, grid, native_grid) {
18
7
  native_grid = native_grid !== undefined ? native_grid : grid;
19
8
  const texcoord_margin_r = 1 / (2 * native_grid.ni);
20
9
  const texcoord_margin_s = 1 / (2 * native_grid.nj);
21
10
  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();
11
+ const { lats: field_lats, lons: field_lons } = grid.getEarthCoords();
23
12
  const domain_coords = await layer_worker.makeDomainVerticesAndTexCoords(field_lats, field_lons, grid.ni, grid.nj, texcoord_margin_r, texcoord_margin_s);
24
13
  for (let icd = 0; icd < domain_coords['grid_cell_size'].length; icd++) {
25
14
  domain_coords['grid_cell_size'][icd] *= grid_cell_size_multiplier;
@@ -30,7 +19,7 @@ async function makeWGLDomainBuffers(gl, grid, native_grid) {
30
19
  return { 'vertices': vertices, 'texcoords': texcoords, 'cellsize': grid_cell_size };
31
20
  }
32
21
  async function makeWGLBillboardBuffers(gl, grid, thin_fac, max_zoom) {
33
- const { lats: field_lats, lons: field_lons } = grid.getCoords();
22
+ const { lats: field_lats, lons: field_lons } = grid.getEarthCoords();
34
23
  const bb_elements = await layer_worker.makeBBElements(field_lats, field_lons, grid.ni, grid.nj, thin_fac, max_zoom);
35
24
  const vertices = new WGLBuffer(gl, bb_elements['pts'], 3, gl.TRIANGLE_STRIP);
36
25
  const texcoords = new WGLBuffer(gl, bb_elements['tex_coords'], 2, gl.TRIANGLE_STRIP);
@@ -42,20 +31,20 @@ class Grid {
42
31
  this.is_conformal = is_conformal;
43
32
  this.ni = ni;
44
33
  this.nj = nj;
45
- this._buffer_cache = new Cache((gl) => {
34
+ this.buffer_cache = new Cache((gl) => {
46
35
  const new_ni = Math.max(Math.floor(this.ni / 50), 20);
47
36
  const new_nj = Math.max(Math.floor(this.nj / 50), 20);
48
37
  return makeWGLDomainBuffers(gl, this.copy({ ni: new_ni, nj: new_nj }), this);
49
38
  });
50
- this._billboard_buffer_cache = new Cache((gl, thin_fac, max_zoom) => {
39
+ this.billboard_buffer_cache = new Cache((gl, thin_fac, max_zoom) => {
51
40
  return makeWGLBillboardBuffers(gl, this, thin_fac, max_zoom);
52
41
  });
53
42
  }
54
43
  async getWGLBuffers(gl) {
55
- return await this._buffer_cache.getValue(gl);
44
+ return await this.buffer_cache.getValue(gl);
56
45
  }
57
46
  async getWGLBillboardBuffers(gl, thin_fac, max_zoom) {
58
- return await this._billboard_buffer_cache.getValue(gl, thin_fac, max_zoom);
47
+ return await this.billboard_buffer_cache.getValue(gl, thin_fac, max_zoom);
59
48
  }
60
49
  }
61
50
  /** A plate carree (a.k.a. lat/lon) grid with uniform grid spacing */
@@ -75,9 +64,9 @@ class PlateCarreeGrid extends Grid {
75
64
  this.ll_lat = ll_lat;
76
65
  this.ur_lon = ur_lon;
77
66
  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);
67
+ const dlon = (this.ur_lon - this.ll_lon) / (this.ni - 1);
68
+ const dlat = (this.ur_lat - this.ll_lat) / (this.nj - 1);
69
+ this.ll_cache = new Cache(() => {
81
70
  const lons = new Float32Array(this.ni * this.nj);
82
71
  const lats = new Float32Array(this.ni * this.nj);
83
72
  for (let i = 0; i < this.ni; i++) {
@@ -89,6 +78,17 @@ class PlateCarreeGrid extends Grid {
89
78
  }
90
79
  return { 'lons': lons, 'lats': lats };
91
80
  });
81
+ this.gc_cache = new Cache(() => {
82
+ const x = new Float32Array(this.ni);
83
+ const y = new Float32Array(this.nj);
84
+ for (let i = 0; i < this.ni; i++) {
85
+ x[i] = this.ll_lon + i * dlon;
86
+ }
87
+ for (let j = 0; j < this.nj; j++) {
88
+ y[j] = this.ll_lat + j * dlat;
89
+ }
90
+ return { x: x, y: y };
91
+ });
92
92
  }
93
93
  copy(opts) {
94
94
  opts = opts !== undefined ? opts : {};
@@ -103,8 +103,11 @@ class PlateCarreeGrid extends Grid {
103
103
  /**
104
104
  * Get a list of longitudes and latitudes on the grid (internal method)
105
105
  */
106
- getCoords() {
107
- return this._ll_cache.getValue();
106
+ getEarthCoords() {
107
+ return this.ll_cache.getValue();
108
+ }
109
+ getGridCoords() {
110
+ return this.gc_cache.getValue();
108
111
  }
109
112
  transform(x, y, opts) {
110
113
  return [x, y];
@@ -123,6 +126,97 @@ class PlateCarreeGrid extends Grid {
123
126
  return new PlateCarreeGrid(ni, nj, ll_lon, ll_lat, ur_lon, ur_lat);
124
127
  }
125
128
  }
129
+ /** A rotated lat-lon (plate carree) grid with uniform grid spacing */
130
+ class PlateCarreeRotatedGrid extends Grid {
131
+ /**
132
+ * Create a Lambert conformal conic grid
133
+ * @param ni - The number of grid points in the i (longitude) direction
134
+ * @param nj - The number of grid points in the j (latitude) direction
135
+ * @param np_lon - The longitude of the north pole for the rotated grid
136
+ * @param np_lat - The latitude of the north pole for the rotated grid
137
+ * @param lon_shift - The angle around the rotated north pole to shift the central meridian
138
+ * @param ll_lon - The longitude of the lower left corner of the grid (on the rotated earth)
139
+ * @param ll_lat - The latitude of the lower left corner of the grid (on the rotated earth)
140
+ * @param ur_lon - The longitude of the upper right corner of the grid (on the rotated earth)
141
+ * @param ur_lat - The latitude of the upper right corner of the grid (on the rotated earth)
142
+ */
143
+ constructor(ni, nj, np_lon, np_lat, lon_shift, ll_lon, ll_lat, ur_lon, ur_lat) {
144
+ super('latlonrot', true, ni, nj);
145
+ this.np_lon = np_lon;
146
+ this.np_lat = np_lat;
147
+ this.lon_shift = lon_shift;
148
+ this.ll_lon = ll_lon;
149
+ this.ll_lat = ll_lat;
150
+ this.ur_lon = ur_lon;
151
+ this.ur_lat = ur_lat;
152
+ this.llrot = rotateSphere({ np_lon: np_lon, np_lat: np_lat, lon_shift: lon_shift });
153
+ const dlon = (this.ur_lon - this.ll_lon) / (this.ni - 1);
154
+ const dlat = (this.ur_lat - this.ll_lat) / (this.nj - 1);
155
+ this.ll_cache = new Cache(() => {
156
+ const lons = new Float32Array(this.ni * this.nj);
157
+ const lats = new Float32Array(this.ni * this.nj);
158
+ for (let i = 0; i < this.ni; i++) {
159
+ const lon_p = this.ll_lon + i * dlon;
160
+ for (let j = 0; j < this.nj; j++) {
161
+ const lat_p = this.ll_lat + j * dlat;
162
+ const [lon, lat] = this.llrot(lon_p, lat_p);
163
+ const idx = i + j * this.ni;
164
+ lons[idx] = lon;
165
+ lats[idx] = lat;
166
+ }
167
+ }
168
+ return { lons: lons, lats: lats };
169
+ });
170
+ this.gc_cache = new Cache(() => {
171
+ const x = new Float32Array(this.ni);
172
+ const y = new Float32Array(this.nj);
173
+ for (let i = 0; i < this.ni; i++) {
174
+ x[i] = this.ll_lon + i * dlon;
175
+ }
176
+ for (let j = 0; j < this.nj; j++) {
177
+ y[j] = this.ll_lat + j * dlat;
178
+ }
179
+ return { x: x, y: y };
180
+ });
181
+ }
182
+ copy(opts) {
183
+ opts = opts !== undefined ? opts : {};
184
+ const ni = opts.ni !== undefined ? opts.ni : this.ni;
185
+ const nj = opts.nj !== undefined ? opts.nj : this.nj;
186
+ const ll_lon = opts.ll_lon !== undefined ? opts.ll_lon : this.ll_lon;
187
+ const ll_lat = opts.ll_lat !== undefined ? opts.ll_lat : this.ll_lat;
188
+ const ur_lon = opts.ur_lon !== undefined ? opts.ur_lon : this.ur_lon;
189
+ const ur_lat = opts.ur_lat !== undefined ? opts.ur_lat : this.ur_lat;
190
+ return new PlateCarreeRotatedGrid(ni, nj, this.np_lon, this.np_lat, this.lon_shift, ll_lon, ll_lat, ur_lon, ur_lat);
191
+ }
192
+ /**
193
+ * Get a list of longitudes and latitudes on the grid (internal method)
194
+ */
195
+ getEarthCoords() {
196
+ return this.ll_cache.getValue();
197
+ }
198
+ getGridCoords() {
199
+ return this.gc_cache.getValue();
200
+ }
201
+ transform(x, y, opts) {
202
+ opts = opts === undefined ? {} : opts;
203
+ const inverse = 'inverse' in opts ? opts.inverse : false;
204
+ return this.llrot(x, y, { inverse: !inverse });
205
+ }
206
+ getThinnedGrid(thin_x, thin_y) {
207
+ const dlon = (this.ur_lon - this.ll_lon) / this.ni;
208
+ const dlat = (this.ur_lat - this.ll_lat) / this.nj;
209
+ const ni = Math.ceil(this.ni / thin_x);
210
+ const nj = Math.ceil(this.nj / thin_y);
211
+ const ni_remove = (this.ni - 1) % thin_x;
212
+ const nj_remove = (this.nj - 1) % thin_y;
213
+ const ll_lon = this.ll_lon;
214
+ const ll_lat = this.ll_lat;
215
+ const ur_lon = this.ur_lon - ni_remove * dlon;
216
+ const ur_lat = this.ur_lat - nj_remove * dlat;
217
+ return new PlateCarreeRotatedGrid(ni, nj, this.np_lon, this.np_lat, this.lon_shift, ll_lon, ll_lat, ur_lon, ur_lat);
218
+ }
219
+ }
126
220
  /** A Lambert conformal conic grid with uniform grid spacing */
127
221
  class LambertGrid extends Grid {
128
222
  /**
@@ -147,13 +241,15 @@ class LambertGrid extends Grid {
147
241
  this.ur_x = ur_x;
148
242
  this.ur_y = ur_y;
149
243
  this.lcc = lambertConformalConic({ lon_0: lon_0, lat_0: lat_0, lat_std: lat_std });
150
- this._ll_cache = new Cache(() => {
244
+ const dx = (this.ur_x - this.ll_x) / (this.ni - 1);
245
+ const dy = (this.ur_y - this.ll_y) / (this.nj - 1);
246
+ this.ll_cache = new Cache(() => {
151
247
  const lons = new Float32Array(this.ni * this.nj);
152
248
  const lats = new Float32Array(this.ni * this.nj);
153
249
  for (let i = 0; i < this.ni; i++) {
154
- const x = this.ll_x + (this.ur_x - this.ll_x) * i / (this.ni - 1);
250
+ const x = this.ll_x + i * dx;
155
251
  for (let j = 0; j < this.nj; j++) {
156
- const y = this.ll_y + (this.ur_y - this.ll_y) * j / (this.nj - 1);
252
+ const y = this.ll_y + j * dy;
157
253
  const [lon, lat] = this.lcc(x, y, { inverse: true });
158
254
  const idx = i + j * this.ni;
159
255
  lons[idx] = lon;
@@ -162,6 +258,17 @@ class LambertGrid extends Grid {
162
258
  }
163
259
  return { lons: lons, lats: lats };
164
260
  });
261
+ this.gc_cache = new Cache(() => {
262
+ const x = new Float32Array(this.ni);
263
+ const y = new Float32Array(this.nj);
264
+ for (let i = 0; i < this.ni; i++) {
265
+ x[i] = this.ll_x + i * dx;
266
+ }
267
+ for (let j = 0; j < this.nj; j++) {
268
+ y[j] = this.ll_y + j * dy;
269
+ }
270
+ return { x: x, y: y };
271
+ });
165
272
  }
166
273
  copy(opts) {
167
274
  opts = opts !== undefined ? opts : {};
@@ -176,8 +283,11 @@ class LambertGrid extends Grid {
176
283
  /**
177
284
  * Get a list of longitudes and latitudes on the grid (internal method)
178
285
  */
179
- getCoords() {
180
- return this._ll_cache.getValue();
286
+ getEarthCoords() {
287
+ return this.ll_cache.getValue();
288
+ }
289
+ getGridCoords() {
290
+ return this.gc_cache.getValue();
181
291
  }
182
292
  transform(x, y, opts) {
183
293
  opts = opts === undefined ? {} : opts;
@@ -198,6 +308,9 @@ class LambertGrid extends Grid {
198
308
  return new LambertGrid(ni, nj, this.lon_0, this.lat_0, this.lat_std, ll_x, ll_y, ur_x, ur_y);
199
309
  }
200
310
  }
311
+ function getArrayConstructor(ary) {
312
+ return ary.constructor;
313
+ }
201
314
  /** A class representing a raw 2D field of gridded data, such as height or u wind. */
202
315
  class RawScalarField {
203
316
  /**
@@ -212,9 +325,25 @@ class RawScalarField {
212
325
  throw `Data size (${data.length}) doesn't match the grid dimensions (${grid.ni} x ${grid.nj}; expected ${grid.ni * grid.nj} points)`;
213
326
  }
214
327
  }
328
+ /** @internal */
329
+ getTextureData() {
330
+ // Need to give float16 data as uint16s to make WebGL happy: https://github.com/petamoriken/float16/issues/105
331
+ let data;
332
+ if (this.data instanceof Float32Array) {
333
+ data = this.data;
334
+ }
335
+ else {
336
+ data = new Uint16Array(this.data.buffer);
337
+ }
338
+ return data;
339
+ }
340
+ isFloat16() {
341
+ return !(this.data instanceof Float32Array);
342
+ }
215
343
  getThinnedField(thin_x, thin_y) {
344
+ const arrayType = getArrayConstructor(this.data);
216
345
  const new_grid = this.grid.getThinnedGrid(thin_x, thin_y);
217
- const new_data = new Float32Array(new_grid.ni * new_grid.nj);
346
+ const new_data = new arrayType(new_grid.ni * new_grid.nj);
218
347
  for (let i = 0; i < new_grid.ni; i++) {
219
348
  for (let j = 0; j < new_grid.nj; j++) {
220
349
  const idx_old = i * thin_x + this.grid.ni * j * thin_y;
@@ -239,8 +368,9 @@ class RawScalarField {
239
368
  yield func(elem);
240
369
  }
241
370
  }
371
+ const arrayType = getArrayConstructor(args[0].data);
242
372
  const zipped_args = zip(...args.map(a => a.data));
243
- const agg_data = new Float32Array(mapGenerator(zipped_args, (a) => func(...a)));
373
+ const agg_data = new arrayType(mapGenerator(zipped_args, (a) => func(...a)));
244
374
  return new RawScalarField(args[0].grid, agg_data);
245
375
  }
246
376
  }
@@ -255,19 +385,25 @@ class RawVectorField {
255
385
  */
256
386
  constructor(grid, u, v, opts) {
257
387
  opts = opts === undefined ? {} : opts;
388
+ const arrayType = getArrayConstructor(u);
258
389
  this.u = new RawScalarField(grid, u);
259
390
  this.v = new RawScalarField(grid, v);
260
391
  this.relative_to = opts.relative_to === undefined ? 'grid' : opts.relative_to;
261
- this._rotate_cache = new Cache(() => {
392
+ this.rotate_cache = new Cache(() => {
262
393
  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);
394
+ const coords = grid.getEarthCoords();
395
+ const u_rot = new arrayType(coords.lats.length);
396
+ const v_rot = new arrayType(coords.lats.length);
266
397
  for (let icd = 0; icd < coords.lats.length; icd++) {
267
398
  const lon = coords.lons[icd];
268
399
  const lat = coords.lats[icd];
269
400
  const u = this.u.data[icd];
270
401
  const v = this.v.data[icd];
402
+ if (Math.abs(u) < 1e-6 && Math.abs(v) < 1e-6) {
403
+ u_rot[icd] = 0;
404
+ v_rot[icd] = 0;
405
+ continue;
406
+ }
271
407
  const [x, y] = grid.transform(lon, lat);
272
408
  const [x_pertlon, y_pertlon] = grid.transform(lon + 0.01, lat);
273
409
  const mag_pertlon = Math.hypot(x - x_pertlon, y - y_pertlon);
@@ -307,7 +443,7 @@ class RawVectorField {
307
443
  v = this.v;
308
444
  }
309
445
  else {
310
- const { u: u_, v: v_ } = this._rotate_cache.getValue();
446
+ const { u: u_, v: v_ } = this.rotate_cache.getValue();
311
447
  u = u_;
312
448
  v = v_;
313
449
  }
@@ -327,8 +463,8 @@ class RawProfileField {
327
463
  }
328
464
  /** Get the gridded storm motion vector field (internal method) */
329
465
  getStormMotionGrid() {
330
- const u = new Float32Array(this.grid.ni * this.grid.nj);
331
- const v = new Float32Array(this.grid.ni * this.grid.nj);
466
+ const u = new Float16Array(this.grid.ni * this.grid.nj);
467
+ const v = new Float16Array(this.grid.ni * this.grid.nj);
332
468
  this.profiles.forEach(prof => {
333
469
  const idx = prof.ilon + this.grid.ni * prof.jlat;
334
470
  u[idx] = prof.smu;
@@ -337,4 +473,4 @@ class RawProfileField {
337
473
  return new RawVectorField(this.grid, u, v, { relative_to: 'grid' });
338
474
  }
339
475
  }
340
- export { RawScalarField, RawVectorField, RawProfileField, PlateCarreeGrid, LambertGrid, Grid };
476
+ export { RawScalarField, RawVectorField, RawProfileField, PlateCarreeGrid, PlateCarreeRotatedGrid, LambertGrid, Grid };
package/lib/index.d.ts CHANGED
@@ -5,11 +5,11 @@ import Barbs, { BarbsOptions } from "./Barbs";
5
5
  import Paintball, { PaintballOptions } from "./Paintball";
6
6
  import Hodographs, { HodographOptions } from './Hodographs';
7
7
  import { PlotLayer, MultiPlotLayer } from './PlotLayer';
8
- import { WindProfile, WebGLAnyRenderingContext } from "./AutumnTypes";
8
+ import { WindProfile, WebGLAnyRenderingContext, TypedArray } from "./AutumnTypes";
9
9
  import { MapType } from "./Map";
10
10
  import { ColorMap, Color } from './Colormap';
11
11
  import { makeColorBar, makePaintballKey, ColorbarOrientation, ColorbarTickDirection, ColorBarOptions, PaintballKeyOptions } from "./ColorBar";
12
- import { RawScalarField, RawVectorField, RawProfileField, Grid, GridType, VectorRelativeTo, RawVectorFieldOptions, PlateCarreeGrid, LambertGrid } from "./RawField";
12
+ import { RawScalarField, RawVectorField, RawProfileField, Grid, GridType, VectorRelativeTo, RawVectorFieldOptions, PlateCarreeGrid, PlateCarreeRotatedGrid, LambertGrid } from "./RawField";
13
13
  declare const colormaps: {
14
14
  bluered: (level_min: number, level_max: number, n_colors: number) => ColorMap;
15
15
  redblue: (level_min: number, level_max: number, n_colors: number) => ColorMap;
@@ -20,4 +20,4 @@ declare const colormaps: {
20
20
  pw_td2m: ColorMap;
21
21
  nws_storm_clear_refl: ColorMap;
22
22
  };
23
- export { PlotComponent, Barbs, BarbsOptions, Contour, ContourOptions, ContourFill, Raster, ContourFillOptions, RasterOptions, 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, WebGLAnyRenderingContext };
23
+ export { PlotComponent, Barbs, BarbsOptions, Contour, ContourOptions, ContourFill, Raster, ContourFillOptions, RasterOptions, 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, PlateCarreeRotatedGrid, LambertGrid, WebGLAnyRenderingContext, TypedArray };
package/lib/index.js CHANGED
@@ -7,7 +7,7 @@ import Hodographs from './Hodographs';
7
7
  import { PlotLayer, MultiPlotLayer } from './PlotLayer';
8
8
  import { ColorMap, bluered, redblue, pw_speed500mb, pw_speed850mb, pw_cape, pw_t2m, pw_td2m, nws_storm_clear_refl } from './Colormap';
9
9
  import { makeColorBar, makePaintballKey } from "./ColorBar";
10
- import { RawScalarField, RawVectorField, RawProfileField, Grid, PlateCarreeGrid, LambertGrid } from "./RawField";
10
+ import { RawScalarField, RawVectorField, RawProfileField, Grid, PlateCarreeGrid, PlateCarreeRotatedGrid, LambertGrid } from "./RawField";
11
11
  const colormaps = {
12
12
  bluered: bluered,
13
13
  redblue: redblue,
@@ -18,4 +18,4 @@ const colormaps = {
18
18
  pw_td2m: pw_td2m,
19
19
  nws_storm_clear_refl: nws_storm_clear_refl,
20
20
  };
21
- export { PlotComponent, Barbs, Contour, ContourFill, Raster, Paintball, Hodographs, PlotLayer, MultiPlotLayer, ColorMap, colormaps, makeColorBar, makePaintballKey, RawScalarField, RawVectorField, RawProfileField, Grid, PlateCarreeGrid, LambertGrid };
21
+ export { PlotComponent, Barbs, Contour, ContourFill, Raster, Paintball, Hodographs, PlotLayer, MultiPlotLayer, ColorMap, colormaps, makeColorBar, makePaintballKey, RawScalarField, RawVectorField, RawProfileField, Grid, PlateCarreeGrid, PlateCarreeRotatedGrid, LambertGrid };
package/lib/utils.d.ts CHANGED
@@ -6,4 +6,11 @@ declare const rgb2hsv: (rgb: [number, number, number]) => [number, number, numbe
6
6
  declare const hsv2rgb: (hsv: [number, number, number]) => [number, number, number];
7
7
  declare function getMinZoom(jlat: number, ilon: number, thin_fac_base: number): number;
8
8
  declare function zip(...args: any[]): Generator<any[], void, unknown>;
9
- export { hex2rgba, rgba2hex, hex2rgb, rgb2hex, rgb2hsv, hsv2rgb, zip, getMinZoom };
9
+ declare function getOS(): string;
10
+ declare class Cache<A extends unknown[], R> {
11
+ private cached_value;
12
+ private readonly compute_value;
13
+ constructor(compute_value: (...args: A) => R);
14
+ getValue(...args: A): R;
15
+ }
16
+ export { hex2rgba, rgba2hex, hex2rgb, rgb2hex, rgb2hsv, hsv2rgb, zip, getMinZoom, getOS, Cache };
package/lib/utils.js CHANGED
@@ -100,4 +100,36 @@ function* zip(...args) {
100
100
  yield current.map(x => x.value);
101
101
  }
102
102
  }
103
- export { hex2rgba, rgba2hex, hex2rgb, rgb2hex, rgb2hsv, hsv2rgb, zip, getMinZoom };
103
+ function getOS() {
104
+ const userAgent = window.navigator.userAgent, platform = window.navigator.platform, macosPlatforms = ['Macintosh', 'MacIntel', 'MacPPC', 'Mac68K'], windowsPlatforms = ['Win32', 'Win64', 'Windows', 'WinCE'], iosPlatforms = ['iPhone', 'iPad', 'iPod'];
105
+ let os = null;
106
+ if (macosPlatforms.indexOf(platform) !== -1 && navigator.maxTouchPoints <= 1) {
107
+ os = 'Mac OS';
108
+ }
109
+ else if (iosPlatforms.indexOf(platform) !== -1 || (macosPlatforms.indexOf(platform) !== -1 && navigator.maxTouchPoints > 1)) {
110
+ os = 'iOS';
111
+ }
112
+ else if (windowsPlatforms.indexOf(platform) !== -1) {
113
+ os = 'Windows';
114
+ }
115
+ else if (/Android/.test(userAgent)) {
116
+ os = 'Android';
117
+ }
118
+ else if (/Linux/.test(platform)) {
119
+ os = 'Linux';
120
+ }
121
+ return os;
122
+ }
123
+ class Cache {
124
+ constructor(compute_value) {
125
+ this.cached_value = null;
126
+ this.compute_value = compute_value;
127
+ }
128
+ getValue(...args) {
129
+ if (this.cached_value === null) {
130
+ this.cached_value = this.compute_value(...args);
131
+ }
132
+ return this.cached_value;
133
+ }
134
+ }
135
+ export { hex2rgba, rgba2hex, hex2rgb, rgb2hex, rgb2hsv, hsv2rgb, zip, getMinZoom, getOS, Cache };