autumnplot-gl 2.1.0 → 2.2.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/lib/Paintball.js CHANGED
@@ -1,6 +1,5 @@
1
- import { isWebGL2Ctx } from "./AutumnTypes";
2
- import { PlotComponent } from "./PlotComponent";
3
- import { hex2rgba } from "./utils";
1
+ import { PlotComponent, getGLFormatTypeAlignment } from "./PlotComponent";
2
+ import { Cache, hex2rgba } from "./utils";
4
3
  import { WGLProgram, WGLTexture } from "autumn-wgl";
5
4
  const paintball_vertex_shader_src = `uniform mat4 u_matrix;
6
5
 
@@ -42,6 +41,7 @@ void main() {
42
41
  color.a = color.a * u_opacity;
43
42
  gl_FragColor = color;
44
43
  }`
44
+ const program_cache = new Cache((gl) => new WGLProgram(gl, paintball_vertex_shader_src, paintball_fragment_shader_src));
45
45
  /**
46
46
  * A class representing a paintball plot, which is a plot of objects in every member of an ensemble. Objects are usually defined by a single threshold on
47
47
  * a field (such as simulated reflectivity greater than 40 dBZ), but could in theory be defined by any arbitrarily complicated method. In autumnplot-gl,
@@ -52,10 +52,10 @@ void main() {
52
52
  class Paintball extends PlotComponent {
53
53
  /**
54
54
  * Create a paintball plot
55
- * &param field - A scalar field containing the member objects encoded as "bits." The numerical value of each grid point can be constructed like
55
+ * @param field - A scalar field containing the member objects encoded as "bits." The numerical value of each grid point can be constructed like
56
56
  * `1.0 * M1 + 2.0 * M2 + 4.0 * M3 + 8.0 * M4 ...`, where `M1` is 1 if that grid point is in an object in member 1 and 0 otherwise,
57
57
  * `M2` is the same thing for member 2, and `M3` and `M4` and up to `Mn` are the same thing for the rest of the members.
58
- * &param opts - Options for creating the paintball plot
58
+ * @param opts - Options for creating the paintball plot
59
59
  */
60
60
  constructor(field, opts) {
61
61
  super();
@@ -67,19 +67,20 @@ class Paintball extends PlotComponent {
67
67
  this.gl_elems = null;
68
68
  }
69
69
  /**
70
- * &internal
70
+ * @internal
71
71
  * Add the paintball plot to a map.
72
72
  */
73
73
  async onAdd(map, gl) {
74
74
  gl.getExtension('OES_texture_float');
75
- const program = new WGLProgram(gl, paintball_vertex_shader_src, paintball_fragment_shader_src);
75
+ const program = program_cache.getValue(gl);
76
76
  const { vertices: verts_buf, texcoords: tex_coords_buf } = await this.field.grid.getWGLBuffers(gl);
77
77
  const vertices = verts_buf;
78
78
  const texcoords = tex_coords_buf;
79
- const format = isWebGL2Ctx(gl) ? gl.R32F : gl.LUMINANCE;
80
- const fill_image = { 'format': format, 'type': gl.FLOAT,
81
- 'width': this.field.grid.ni, 'height': this.field.grid.nj, 'image': this.field.data,
82
- 'mag_filter': gl.NEAREST,
79
+ gl.pixelStorei(gl.UNPACK_ALIGNMENT, 2);
80
+ const { format, type, row_alignment } = getGLFormatTypeAlignment(gl, this.field.isFloat16());
81
+ const fill_image = { 'format': format, 'type': type,
82
+ 'width': this.field.grid.ni, 'height': this.field.grid.nj, 'image': this.field.getTextureData(),
83
+ 'mag_filter': gl.NEAREST, 'row_alignment': row_alignment,
83
84
  };
84
85
  const fill_texture = new WGLTexture(gl, fill_image);
85
86
  this.gl_elems = {
@@ -87,7 +88,7 @@ class Paintball extends PlotComponent {
87
88
  };
88
89
  }
89
90
  /**
90
- * &internal
91
+ * @internal
91
92
  * Render the paintball plot
92
93
  */
93
94
  render(gl, matrix) {
@@ -17,4 +17,9 @@ declare abstract class PlotComponent {
17
17
  abstract onAdd(map: MapType, gl: WebGLAnyRenderingContext): Promise<void>;
18
18
  abstract render(gl: WebGLAnyRenderingContext, matrix: number[]): void;
19
19
  }
20
- export { PlotComponent, layer_worker };
20
+ declare function getGLFormatTypeAlignment(gl: WebGLAnyRenderingContext, is_float16: boolean): {
21
+ format: number;
22
+ type: number;
23
+ row_alignment: number;
24
+ };
25
+ export { PlotComponent, layer_worker, getGLFormatTypeAlignment };
@@ -1,6 +1,39 @@
1
1
  import * as Comlink from 'comlink';
2
+ import { getOS } from "./utils";
3
+ import { isWebGL2Ctx } from './AutumnTypes';
2
4
  const worker = new Worker(new URL('./PlotLayer.worker', import.meta.url));
3
5
  const layer_worker = Comlink.wrap(worker);
4
6
  class PlotComponent {
5
7
  }
6
- export { PlotComponent, layer_worker };
8
+ function getGLFormatTypeAlignment(gl, is_float16) {
9
+ let format, type, row_alignment;
10
+ const is_webgl2 = isWebGL2Ctx(gl);
11
+ if (is_float16) {
12
+ const ext = gl.getExtension('OES_texture_half_float');
13
+ const ext_lin = gl.getExtension('OES_texture_half_float_linear');
14
+ if ((!is_webgl2 && ext === null) || (!is_webgl2 && ext_lin === null)) {
15
+ throw "Float16 data are not supported on this hardware. Try Float32 data instead.";
16
+ }
17
+ format = is_webgl2 ? gl.R16F : gl.LUMINANCE;
18
+ type = is_webgl2 ? gl.HALF_FLOAT : ext.HALF_FLOAT_OES;
19
+ row_alignment = 2;
20
+ }
21
+ else {
22
+ const ext = gl.getExtension('OES_texture_float');
23
+ const ext_lin = gl.getExtension('OES_texture_float_linear');
24
+ // As of 11/3/2023, Safari/WebKit on iOS reports as supporting float textures,
25
+ // but really doesn't. It just silently fails. In WebGL 1, Safari/Webkit returns
26
+ // null for ext_lin here, but in WebGL2, both ext vars are null because they were
27
+ // merged into the standard. This means that to properly fail for iOS devices using
28
+ // WebGL2, we have to hard code an OS check... this OS check also works when uers
29
+ // request desktop-mode websites.
30
+ if ((!is_webgl2 && ext === null) || (!is_webgl2 && ext_lin === null) || (getOS() === 'iOS')) {
31
+ throw "Float32 data are not supported on this hardware. Try Float16 data instead.";
32
+ }
33
+ format = is_webgl2 ? gl.R32F : gl.LUMINANCE;
34
+ type = gl.FLOAT;
35
+ row_alignment = 4;
36
+ }
37
+ return { format: format, type: type, row_alignment: row_alignment };
38
+ }
39
+ export { PlotComponent, layer_worker, getGLFormatTypeAlignment };
@@ -18,7 +18,7 @@ declare abstract class PlotLayerBase {
18
18
  * const barb_layer = new PlotLayer('barbs', wind_barbs);
19
19
  */
20
20
  declare class PlotLayer extends PlotLayerBase {
21
- readonly field: PlotComponent;
21
+ private readonly field;
22
22
  /**
23
23
  * Create a map layer from a field
24
24
  * @param id - A unique id for this layer
@@ -51,14 +51,10 @@ declare class PlotLayer extends PlotLayerBase {
51
51
  * height_layer.setActiveKey('20230112_1200');
52
52
  */
53
53
  declare class MultiPlotLayer extends PlotLayerBase {
54
- /** @private */
55
- fields: Record<string, PlotComponent>;
56
- /** @private */
57
- field_key: string | null;
58
- /** @private */
59
- map: MapType | null;
60
- /** @private */
61
- gl: WebGLAnyRenderingContext | null;
54
+ private fields;
55
+ private field_key;
56
+ private map;
57
+ private gl;
62
58
  /**
63
59
  * Create a time-varying map layer
64
60
  * @param id - A unique id for this layer
@@ -90,7 +86,6 @@ declare class MultiPlotLayer extends PlotLayerBase {
90
86
  * @param dt - The date/time at which the field is valid
91
87
  */
92
88
  addField(field: PlotComponent, key: string): void;
93
- /** @private */
94
- _repaintIfNecessary(old_field_key: string | null): void;
89
+ private repaintIfNecessary;
95
90
  }
96
91
  export { PlotLayer, MultiPlotLayer };
package/lib/PlotLayer.js CHANGED
@@ -73,10 +73,10 @@ class MultiPlotLayer extends PlotLayerBase {
73
73
  this.gl = gl;
74
74
  Object.values(this.fields).forEach(field => {
75
75
  field.onAdd(map, gl).then(res => {
76
- this._repaintIfNecessary(null);
76
+ this.repaintIfNecessary(null);
77
77
  });
78
78
  });
79
- this._repaintIfNecessary(null);
79
+ this.repaintIfNecessary(null);
80
80
  }
81
81
  /**
82
82
  * @internal
@@ -95,7 +95,7 @@ class MultiPlotLayer extends PlotLayerBase {
95
95
  setActiveKey(key) {
96
96
  const old_field_key = this.field_key;
97
97
  this.field_key = key;
98
- this._repaintIfNecessary(old_field_key);
98
+ this.repaintIfNecessary(old_field_key);
99
99
  }
100
100
  /**
101
101
  * Get a list of all dates/times that have been added to the layer
@@ -113,7 +113,7 @@ class MultiPlotLayer extends PlotLayerBase {
113
113
  const old_field_key = this.field_key;
114
114
  if (this.map !== null && this.gl !== null && field !== null) {
115
115
  field.onAdd(this.map, this.gl).then(res => {
116
- this._repaintIfNecessary(null);
116
+ this.repaintIfNecessary(null);
117
117
  });
118
118
  }
119
119
  this.fields[key] = field;
@@ -121,8 +121,7 @@ class MultiPlotLayer extends PlotLayerBase {
121
121
  this.field_key = key;
122
122
  }
123
123
  }
124
- /** @private */
125
- _repaintIfNecessary(old_field_key) {
124
+ repaintIfNecessary(old_field_key) {
126
125
  if (this.map !== null && old_field_key !== this.field_key) {
127
126
  this.map.triggerRepaint();
128
127
  }
@@ -1,15 +1,15 @@
1
- import { WGLBuffer, WGLProgram, WGLTexture, WGLTextureSpec } from "autumn-wgl";
1
+ import { WGLTextureSpec } from "autumn-wgl";
2
2
  import { PolylineSpec, LineSpec, WebGLAnyRenderingContext } from "./AutumnTypes";
3
3
  declare class PolylineCollection {
4
4
  readonly width: number;
5
5
  readonly scale: number;
6
- readonly program: WGLProgram;
7
- readonly origin: WGLBuffer;
8
- readonly offset: WGLBuffer;
9
- readonly extrusion: WGLBuffer;
10
- readonly min_zoom: WGLBuffer;
11
- readonly texture: WGLTexture;
12
- readonly texcoords: WGLBuffer;
6
+ private readonly program;
7
+ private readonly origin;
8
+ private readonly offset;
9
+ private readonly extrusion;
10
+ private readonly min_zoom;
11
+ private readonly texture;
12
+ private readonly texcoords;
13
13
  constructor(gl: WebGLAnyRenderingContext, polyline: PolylineSpec, tex_image: WGLTextureSpec, line_width: number, offset_scale: number);
14
14
  render(gl: WebGLAnyRenderingContext, matrix: number[], [map_width, map_height]: [number, number], map_zoom: number, map_bearing: number, map_pitch: number): void;
15
15
  }
@@ -1,4 +1,5 @@
1
1
  import { WGLBuffer, WGLProgram, WGLTexture } from "autumn-wgl";
2
+ import { Cache } from "./utils";
2
3
  const polyline_vertex_src = `uniform mat4 u_matrix;
3
4
 
4
5
  attribute vec2 a_pos;
@@ -69,11 +70,12 @@ void main() {
69
70
  lowp vec4 tex_color = texture2D(u_sampler, v_tex_coord);
70
71
  gl_FragColor = tex_color;
71
72
  }`
73
+ const program_cache = new Cache((gl) => new WGLProgram(gl, polyline_vertex_src, polyline_fragment_src));
72
74
  class PolylineCollection {
73
75
  constructor(gl, polyline, tex_image, line_width, offset_scale) {
74
76
  this.width = line_width;
75
77
  this.scale = offset_scale;
76
- this.program = new WGLProgram(gl, polyline_vertex_src, polyline_fragment_src);
78
+ this.program = program_cache.getValue(gl);
77
79
  this.origin = new WGLBuffer(gl, polyline['origin'], 2, gl.TRIANGLE_STRIP);
78
80
  this.offset = new WGLBuffer(gl, polyline['verts'], 2, gl.TRIANGLE_STRIP);
79
81
  this.extrusion = new WGLBuffer(gl, polyline['extrusion'], 2, gl.TRIANGLE_STRIP);
package/lib/RawField.d.ts CHANGED
@@ -1,30 +1,18 @@
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
4
  interface Coords {
10
5
  lons: Float32Array;
11
6
  lats: Float32Array;
12
7
  }
13
- type GridType = 'latlon' | 'lcc';
8
+ type GridType = 'latlon' | 'latlonrot' | 'lcc';
14
9
  declare abstract class Grid {
15
10
  readonly type: GridType;
16
11
  readonly ni: number;
17
12
  readonly nj: number;
18
13
  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
- }>>;
14
+ private readonly buffer_cache;
15
+ private readonly billboard_buffer_cache;
28
16
  constructor(type: GridType, is_conformal: boolean, ni: number, nj: number);
29
17
  abstract copy(opts?: {
30
18
  ni?: number;
@@ -51,8 +39,7 @@ declare class PlateCarreeGrid extends Grid {
51
39
  readonly ll_lat: number;
52
40
  readonly ur_lon: number;
53
41
  readonly ur_lat: number;
54
- /** @private */
55
- readonly _ll_cache: Cache<[], Coords>;
42
+ private readonly ll_cache;
56
43
  /**
57
44
  * Create a plate carree grid
58
45
  * @param ni - The number of grid points in the i (longitude) direction
@@ -80,6 +67,47 @@ declare class PlateCarreeGrid extends Grid {
80
67
  }): [number, number];
81
68
  getThinnedGrid(thin_x: number, thin_y: number): PlateCarreeGrid;
82
69
  }
70
+ /** A rotated lat-lon (plate carree) grid with uniform grid spacing */
71
+ declare class PlateCarreeRotatedGrid extends Grid {
72
+ readonly np_lon: number;
73
+ readonly np_lat: number;
74
+ readonly lon_shift: number;
75
+ readonly ll_lon: number;
76
+ readonly ll_lat: number;
77
+ readonly ur_lon: number;
78
+ readonly ur_lat: number;
79
+ private readonly llrot;
80
+ private readonly ll_cache;
81
+ /**
82
+ * Create a Lambert conformal conic grid
83
+ * @param ni - The number of grid points in the i (longitude) direction
84
+ * @param nj - The number of grid points in the j (latitude) direction
85
+ * @param np_lon - The longitude of the north pole for the rotated grid
86
+ * @param np_lat - The latitude of the north pole for the rotated grid
87
+ * @param lon_shift - The angle around the rotated north pole to shift the central meridian
88
+ * @param ll_lon - The longitude of the lower left corner of the grid (on the rotated earth)
89
+ * @param ll_lat - The latitude of the lower left corner of the grid (on the rotated earth)
90
+ * @param ur_lon - The longitude of the upper right corner of the grid (on the rotated earth)
91
+ * @param ur_lat - The latitude of the upper right corner of the grid (on the rotated earth)
92
+ */
93
+ 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);
94
+ copy(opts?: {
95
+ ni?: number;
96
+ nj?: number;
97
+ ll_lon?: number;
98
+ ll_lat?: number;
99
+ ur_lon?: number;
100
+ ur_lat?: number;
101
+ }): PlateCarreeRotatedGrid;
102
+ /**
103
+ * Get a list of longitudes and latitudes on the grid (internal method)
104
+ */
105
+ getCoords(): Coords;
106
+ transform(x: number, y: number, opts?: {
107
+ inverse?: boolean;
108
+ }): [number, number];
109
+ getThinnedGrid(thin_x: number, thin_y: number): PlateCarreeRotatedGrid;
110
+ }
83
111
  /** A Lambert conformal conic grid with uniform grid spacing */
84
112
  declare class LambertGrid extends Grid {
85
113
  readonly lon_0: number;
@@ -89,12 +117,8 @@ declare class LambertGrid extends Grid {
89
117
  readonly ll_y: number;
90
118
  readonly ur_x: number;
91
119
  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>;
120
+ private readonly lcc;
121
+ private readonly ll_cache;
98
122
  /**
99
123
  * Create a Lambert conformal conic grid
100
124
  * @param ni - The number of grid points in the i (longitude) direction
@@ -125,17 +149,21 @@ declare class LambertGrid extends Grid {
125
149
  }): [number, number];
126
150
  getThinnedGrid(thin_x: number, thin_y: number): LambertGrid;
127
151
  }
152
+ type TextureDataType<ArrayType> = ArrayType extends Float32Array ? Float32Array : Uint16Array;
128
153
  /** A class representing a raw 2D field of gridded data, such as height or u wind. */
129
- declare class RawScalarField {
154
+ declare class RawScalarField<ArrayType extends TypedArray> {
130
155
  readonly grid: Grid;
131
- readonly data: Float32Array;
156
+ readonly data: ArrayType;
132
157
  /**
133
158
  * Create a data field.
134
159
  * @param grid - The grid on which the data are defined
135
160
  * @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
161
  */
137
- constructor(grid: Grid, data: Float32Array);
138
- getThinnedField(thin_x: number, thin_y: number): RawScalarField;
162
+ constructor(grid: Grid, data: ArrayType);
163
+ /** @internal */
164
+ getTextureData(): TextureDataType<ArrayType>;
165
+ isFloat16(): boolean;
166
+ getThinnedField(thin_x: number, thin_y: number): RawScalarField<ArrayType>;
139
167
  /**
140
168
  * Create a new field by aggregating a number of fields using a specific function
141
169
  * @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 +173,7 @@ declare class RawScalarField {
145
173
  * // Compute wind speed from u and v
146
174
  * wind_speed_field = RawScalarField.aggreateFields(Math.hypot, u_field, v_field);
147
175
  */
148
- static aggregateFields(func: (...args: number[]) => number, ...args: RawScalarField[]): RawScalarField;
176
+ static aggregateFields<ArrayType extends TypedArray>(func: (...args: number[]) => number, ...args: RawScalarField<ArrayType>[]): RawScalarField<ArrayType>;
149
177
  }
150
178
  type VectorRelativeTo = 'earth' | 'grid';
151
179
  interface RawVectorFieldOptions {
@@ -156,15 +184,11 @@ interface RawVectorFieldOptions {
156
184
  relative_to?: VectorRelativeTo;
157
185
  }
158
186
  /** A class representing a 2D gridded field of vectors */
159
- declare class RawVectorField {
160
- readonly u: RawScalarField;
161
- readonly v: RawScalarField;
187
+ declare class RawVectorField<ArrayType extends TypedArray> {
188
+ readonly u: RawScalarField<ArrayType>;
189
+ readonly v: RawScalarField<ArrayType>;
162
190
  readonly relative_to: VectorRelativeTo;
163
- /** @private */
164
- readonly _rotate_cache: Cache<[], {
165
- u: RawScalarField;
166
- v: RawScalarField;
167
- }>;
191
+ private readonly rotate_cache;
168
192
  /**
169
193
  * Create a vector field.
170
194
  * @param grid - The grid on which the vector components are defined
@@ -172,10 +196,10 @@ declare class RawVectorField {
172
196
  * @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
197
  * @param opts - Options for creating the vector field.
174
198
  */
175
- constructor(grid: Grid, u: Float32Array, v: Float32Array, opts?: RawVectorFieldOptions);
176
- getThinnedField(thin_x: number, thin_y: number): RawVectorField;
199
+ constructor(grid: Grid, u: ArrayType, v: ArrayType, opts?: RawVectorFieldOptions);
200
+ getThinnedField(thin_x: number, thin_y: number): RawVectorField<ArrayType>;
177
201
  get grid(): Grid;
178
- toEarthRelative(): RawVectorField;
202
+ toEarthRelative(): RawVectorField<ArrayType>;
179
203
  }
180
204
  /** A class grid of wind profiles */
181
205
  declare class RawProfileField {
@@ -188,7 +212,7 @@ declare class RawProfileField {
188
212
  */
189
213
  constructor(grid: Grid, profiles: WindProfile[]);
190
214
  /** Get the gridded storm motion vector field (internal method) */
191
- getStormMotionGrid(): RawVectorField;
215
+ getStormMotionGrid(): RawVectorField<Float16Array>;
192
216
  }
193
- export { RawScalarField, RawVectorField, RawProfileField, PlateCarreeGrid, LambertGrid, Grid };
194
- export type { GridType, RawVectorFieldOptions, VectorRelativeTo };
217
+ export { RawScalarField, RawVectorField, RawProfileField, PlateCarreeGrid, PlateCarreeRotatedGrid, LambertGrid, Grid };
218
+ export type { GridType, RawVectorFieldOptions, VectorRelativeTo, TextureDataType };