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.
Files changed (48) hide show
  1. package/lib/AutumnTypes.d.ts +53 -5
  2. package/lib/AutumnTypes.js +25 -1
  3. package/lib/Barbs.d.ts +6 -5
  4. package/lib/BillboardCollection.d.ts +8 -7
  5. package/lib/BillboardCollection.js +69 -58
  6. package/lib/Color.d.ts +1 -0
  7. package/lib/Color.js +3 -0
  8. package/lib/ColorBar.d.ts +10 -0
  9. package/lib/ColorBar.js +4 -2
  10. package/lib/Colormap.js +8 -8
  11. package/lib/Contour.d.ts +18 -8
  12. package/lib/Contour.js +17 -54
  13. package/lib/ContourCreator.d.ts +4 -0
  14. package/lib/ContourCreator.js +2 -1
  15. package/lib/Fill.d.ts +26 -14
  16. package/lib/Fill.js +97 -50
  17. package/lib/Grid.d.ts +124 -29
  18. package/lib/Grid.js +297 -94
  19. package/lib/Hodographs.d.ts +9 -8
  20. package/lib/Hodographs.js +14 -11
  21. package/lib/Map.js +1 -1
  22. package/lib/Paintball.d.ts +6 -5
  23. package/lib/Paintball.js +35 -30
  24. package/lib/ParticleTracer.d.ts +19 -0
  25. package/lib/ParticleTracer.js +37 -0
  26. package/lib/PlotComponent.d.ts +6 -7
  27. package/lib/PlotComponent.js +8 -3
  28. package/lib/PlotLayer.d.ts +3 -3
  29. package/lib/PlotLayer.worker.d.ts +1 -2
  30. package/lib/PlotLayer.worker.js +15 -50
  31. package/lib/PolylineCollection.d.ts +5 -3
  32. package/lib/PolylineCollection.js +60 -37
  33. package/lib/RawField.d.ts +76 -23
  34. package/lib/RawField.js +138 -29
  35. package/lib/ShaderManager.d.ts +12 -0
  36. package/lib/ShaderManager.js +58 -0
  37. package/lib/StationPlot.d.ts +136 -25
  38. package/lib/StationPlot.js +192 -60
  39. package/lib/TextCollection.d.ts +9 -6
  40. package/lib/TextCollection.js +94 -62
  41. package/lib/cpp/marchingsquares.js +483 -585
  42. package/lib/cpp/marchingsquares.wasm +0 -0
  43. package/lib/cpp/marchingsquares_embind.d.ts +23 -3
  44. package/lib/index.d.ts +4 -3
  45. package/lib/index.js +4 -3
  46. package/lib/utils.d.ts +4 -1
  47. package/lib/utils.js +12 -1
  48. package/package.json +2 -2
@@ -1,42 +1,45 @@
1
- import { WGLBuffer, WGLProgram, WGLTexture } from "autumn-wgl";
2
- import { isWebGL2Ctx } from "./AutumnTypes";
1
+ import { WGLBuffer, WGLTexture } from "autumn-wgl";
2
+ import { isWebGL2Ctx, getRendererData } from "./AutumnTypes";
3
3
  import { ColorMap, ColorMapGPUInterface } from "./Colormap";
4
4
  import { Color } from "./Color";
5
5
  import { layer_worker } from "./PlotComponent";
6
- const polyline_vertex_src = `uniform mat4 u_matrix;
6
+ import { ShaderProgramManager } from "./ShaderManager";
7
+ const polyline_vertex_src = `#version 300 es
8
+
7
9
  uniform int u_offset;
8
10
 
9
- attribute vec3 a_pos;
10
- attribute vec2 a_extrusion;
11
- attribute float a_data;
11
+ in vec3 a_pos;
12
+ in vec2 a_extrusion;
13
+ in float a_data;
12
14
 
13
15
  #ifdef ZOOM
14
- attribute float a_min_zoom;
16
+ in float a_min_zoom;
15
17
  #endif
16
18
 
17
19
  #ifdef OFFSET
18
- attribute vec2 a_offset;
20
+ in vec2 a_offset;
19
21
  #endif
20
22
 
21
23
  uniform lowp float u_line_width;
22
24
  uniform lowp float u_map_width;
23
25
  uniform lowp float u_map_height;
24
- uniform highp float u_map_bearing;
26
+
25
27
 
26
28
  #ifdef ZOOM
27
29
  uniform lowp float u_zoom;
28
30
  #endif
29
31
 
30
32
  #ifdef OFFSET
33
+ uniform int u_offset_rotates_with_map;
31
34
  uniform lowp float u_offset_scale;
32
35
  #endif
33
36
 
34
37
  #ifdef DATA
35
- varying highp float v_data;
38
+ out highp float v_data;
36
39
  #endif
37
40
 
38
- varying highp float v_dist;
39
- varying lowp float v_cross;
41
+ out highp float v_dist;
42
+ out lowp float v_cross;
40
43
 
41
44
  mat4 scalingMatrix(float x_scale, float y_scale, float z_scale) {
42
45
  return mat4(x_scale, 0.0, 0.0, 0.0,
@@ -71,7 +74,7 @@ void main() {
71
74
 
72
75
  v_dist = abs(a_pos.z);
73
76
  v_cross = sign(a_pos.z);
74
- vec4 center_pos = u_matrix * vec4(a_pos.xy + globe_offset, 0.0, 1.0);
77
+ vec4 center_pos = projectTile(a_pos.xy + globe_offset);
75
78
  vec4 offset = vec4(0.0, 0.0, 0.0, 0.0);
76
79
 
77
80
  #ifdef ZOOM
@@ -81,13 +84,26 @@ void main() {
81
84
  vec2 offset_ext = u_line_width * 2. * a_extrusion;
82
85
 
83
86
  mat4 map_stretch_matrix = scalingMatrix(u_map_height / u_map_width, 1., 1.);
84
- mat4 rotation_matrix = rotationZMatrix(radians(u_map_bearing));
87
+
88
+ vec4 center_pos_ihat = projectTile(a_pos.xy + globe_offset + vec2(1e-5, 0.));
89
+
90
+ vec2 center_east = normalize((inverse(map_stretch_matrix) * (center_pos_ihat - center_pos)).xy);
91
+ float globe_rotation = atan(center_east.x, center_east.y) - 3.141592654 / 2.0;
92
+ mat4 rotation_matrix = rotationZMatrix(-globe_rotation);
93
+
85
94
  offset = map_stretch_matrix * rotation_matrix * vec4(offset_ext, 0., 0.);
86
95
 
87
96
  #ifdef OFFSET
88
- map_stretch_matrix = scalingMatrix(1., u_map_width / u_map_height, 1.);
97
+ mat4 offset_matrix = scalingMatrix(1., u_map_width / u_map_height, 1.);
98
+ if (u_offset_rotates_with_map == 1) {
99
+ offset_matrix = offset_matrix * rotation_matrix;
100
+ }
101
+ else {
102
+ offset = map_stretch_matrix * vec4(offset_ext, 0., 0.);
103
+ }
104
+
89
105
  vec2 offset_offset = u_offset_scale * a_offset;
90
- offset += map_stretch_matrix * rotation_matrix * vec4(offset_offset, 0., 0.);
106
+ offset += offset_matrix * vec4(offset_offset, 0., 0.);
91
107
  #endif
92
108
 
93
109
  #ifdef ZOOM
@@ -100,7 +116,8 @@ void main() {
100
116
  v_data = a_data;
101
117
  #endif
102
118
  }`
103
- const polyline_fragment_src = `
119
+ const polyline_fragment_src = `#version 300 es
120
+
104
121
  uniform sampler2D u_dash_sampler;
105
122
 
106
123
  #ifndef DATA
@@ -112,15 +129,17 @@ uniform lowp float u_zoom;
112
129
  uniform int u_dash_pattern_length;
113
130
 
114
131
  #ifdef DATA
115
- varying highp float v_data;
132
+ in highp float v_data;
116
133
  #endif
117
134
 
118
- varying highp float v_dist;
119
- varying lowp float v_cross;
135
+ in highp float v_dist;
136
+ in lowp float v_cross;
137
+
138
+ out highp vec4 fragColor;
120
139
 
121
140
  void main() {
122
141
  lowp float dash_x = fract(v_dist * 2e2 * pow(2., floor(u_zoom)) / float(u_dash_pattern_length));
123
- lowp float dash = texture2D(u_dash_sampler, vec2(dash_x, 0.5)).r;
142
+ lowp float dash = texture(u_dash_sampler, vec2(dash_x, 0.5)).r;
124
143
 
125
144
  lowp vec4 color;
126
145
  #ifdef DATA
@@ -132,7 +151,7 @@ void main() {
132
151
  lowp float feather = clamp((1. - abs(v_cross)) * u_line_width, 0., 1.);
133
152
  color.a *= (dash >= 1. ? 1. : 0.) * feather;
134
153
 
135
- gl_FragColor = color;
154
+ fragColor = color;
136
155
  }`
137
156
  const dash_arrays = {
138
157
  "-": [1],
@@ -162,6 +181,7 @@ class PolylineCollection {
162
181
  this.color = Color.fromHex(color_hex);
163
182
  const line_width = opts.line_width === undefined ? 1 : opts.line_width;
164
183
  const line_style = opts.line_style === undefined ? '-' : opts.line_style;
184
+ this.offset_rotates_with_map = opts.offset_rotates_with_map === undefined ? true : opts.offset_rotates_with_map;
165
185
  this.width = line_width;
166
186
  const shader_defines = [];
167
187
  this.vertices = new WGLBuffer(gl, polyline['vertices'], 3, gl.TRIANGLE_STRIP);
@@ -196,24 +216,25 @@ class PolylineCollection {
196
216
  this.cmap_gpu = null;
197
217
  }
198
218
  [this.n_dash, this.dash_texture] = makeDashTexture(gl, line_style);
199
- this.program = new WGLProgram(gl, polyline_vertex_src, fragment_src, { define: shader_defines });
219
+ this.shader_manager = new ShaderProgramManager(polyline_vertex_src, fragment_src, shader_defines);
200
220
  }
201
221
  static async make(gl, lines, opts) {
202
222
  const polylines = await layer_worker.makePolyLines(lines);
203
223
  return new PolylineCollection(gl, polylines, opts);
204
224
  }
205
- render(gl, matrix, [map_width, map_height], map_zoom, map_bearing, map_pitch) {
206
- if (matrix instanceof Float32Array)
207
- matrix = [...matrix];
225
+ render(gl, arg, [map_width, map_height], map_zoom, map_bearing, map_pitch) {
226
+ const render_data = getRendererData(arg);
227
+ const program = this.shader_manager.getShaderProgram(gl, render_data.shaderData);
208
228
  const attributes = { 'a_pos': this.vertices, 'a_extrusion': this.extrusion };
209
229
  const uniforms = {
210
- 'u_matrix': matrix, 'u_line_width': this.width, 'u_map_width': map_width, 'u_map_height': map_height, 'u_map_bearing': map_bearing, 'u_offset': 0, 'u_zoom': map_zoom,
211
- 'u_dash_pattern_length': this.n_dash
230
+ 'u_line_width': this.width, 'u_map_width': map_width, 'u_map_height': map_height, 'u_offset': 0, 'u_zoom': map_zoom,
231
+ 'u_dash_pattern_length': this.n_dash, ...this.shader_manager.getShaderUniforms(render_data)
212
232
  };
213
233
  const textures = { 'u_dash_sampler': this.dash_texture };
214
234
  if (this.offset !== null && this.scale !== null) {
215
235
  attributes['a_offset'] = this.offset;
216
236
  uniforms['u_offset_scale'] = this.scale * (map_height / map_width);
237
+ uniforms['u_offset_rotates_with_map'] = this.offset_rotates_with_map ? 1 : 0;
217
238
  }
218
239
  if (this.min_zoom !== null) {
219
240
  attributes['a_min_zoom'] = this.min_zoom;
@@ -224,19 +245,21 @@ class PolylineCollection {
224
245
  else {
225
246
  uniforms['u_color'] = this.color.toRGBATuple();
226
247
  }
227
- this.program.use(attributes, uniforms, textures);
248
+ program.use(attributes, uniforms, textures);
228
249
  if (this.cmap_gpu !== null) {
229
- this.cmap_gpu.bindShaderVariables(this.program);
250
+ this.cmap_gpu.bindShaderVariables(program);
230
251
  }
231
252
  gl.enable(gl.BLEND);
232
253
  gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
233
- this.program.draw();
234
- this.program.setUniforms({ 'u_offset': -2 });
235
- this.program.draw();
236
- this.program.setUniforms({ 'u_offset': -1 });
237
- this.program.draw();
238
- this.program.setUniforms({ 'u_offset': 1 });
239
- this.program.draw();
254
+ program.draw();
255
+ if (render_data.type != 'maplibre' || !render_data.shaderData.define.includes('GLOBE')) {
256
+ program.setUniforms({ 'u_offset': -2 });
257
+ program.draw();
258
+ program.setUniforms({ 'u_offset': -1 });
259
+ program.draw();
260
+ program.setUniforms({ 'u_offset': 1 });
261
+ program.draw();
262
+ }
240
263
  }
241
264
  }
242
265
  export { PolylineCollection, isLineStyle };
package/lib/RawField.d.ts CHANGED
@@ -1,11 +1,12 @@
1
1
  import { Float16Array } from "@petamoriken/float16";
2
- import { ContourData, TypedArray, WindProfile } from "./AutumnTypes";
2
+ import { ContourData, TypedArray, WebGLAnyRenderingContext, WindProfile } from "./AutumnTypes";
3
3
  import { FieldContourOpts } from "./ContourCreator";
4
4
  import { Grid } from "./Grid";
5
- type TextureDataType<ArrayType> = ArrayType extends Float32Array ? Float32Array : Uint16Array;
5
+ import { WGLTextureSpec } from "autumn-wgl";
6
+ type TextureDataType<ArrayType> = ArrayType extends Float32Array ? Float32Array : (ArrayType extends Uint8Array ? Uint8Array : Uint16Array);
6
7
  /** A class representing a raw 2D field of gridded data, such as height or u wind. */
7
- declare class RawScalarField<ArrayType extends TypedArray> {
8
- readonly grid: Grid;
8
+ declare class RawScalarField<ArrayType extends TypedArray, GridType extends Grid> {
9
+ readonly grid: GridType;
9
10
  readonly data: ArrayType;
10
11
  private readonly contour_cache;
11
12
  /**
@@ -13,9 +14,15 @@ declare class RawScalarField<ArrayType extends TypedArray> {
13
14
  * @param grid - The grid on which the data are defined
14
15
  * @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.
15
16
  */
16
- constructor(grid: Grid, data: ArrayType);
17
+ constructor(grid: GridType, data: ArrayType);
17
18
  /** @internal */
18
- getTextureData(): TextureDataType<ArrayType>;
19
+ private getTextureData;
20
+ getWGLTextureSpec(gl: WebGLAnyRenderingContext, image_mag_filter: number): WGLTextureSpec;
21
+ /**
22
+ * Get contour data as an object with each contour level being a separate property.
23
+ * @param opts - Options for doing the contouring
24
+ * @returns contour data as an object
25
+ */
19
26
  getContours(opts: FieldContourOpts): Promise<ContourData>;
20
27
  /**
21
28
  * Create a new field by aggregating a number of fields using a specific function
@@ -26,7 +33,8 @@ declare class RawScalarField<ArrayType extends TypedArray> {
26
33
  * // Compute wind speed from u and v
27
34
  * wind_speed_field = RawScalarField.aggreateFields(Math.hypot, u_field, v_field);
28
35
  */
29
- static aggregateFields<ArrayType extends TypedArray>(func: (...args: number[]) => number, ...args: RawScalarField<ArrayType>[]): RawScalarField<ArrayType>;
36
+ static aggregateFields<ArrayType extends TypedArray, GridType extends Grid>(func: (...args: number[]) => number, ...args: RawScalarField<ArrayType, GridType>[]): RawScalarField<ArrayType, GridType>;
37
+ sampleField(lon: number, lat: number): number;
30
38
  }
31
39
  type VectorRelativeTo = 'earth' | 'grid';
32
40
  interface RawVectorFieldOptions {
@@ -37,9 +45,9 @@ interface RawVectorFieldOptions {
37
45
  relative_to?: VectorRelativeTo;
38
46
  }
39
47
  /** A class representing a 2D gridded field of vectors */
40
- declare class RawVectorField<ArrayType extends TypedArray> {
41
- readonly u: RawScalarField<ArrayType>;
42
- readonly v: RawScalarField<ArrayType>;
48
+ declare class RawVectorField<ArrayType extends TypedArray, GridType extends Grid> {
49
+ readonly u: RawScalarField<ArrayType, GridType>;
50
+ readonly v: RawScalarField<ArrayType, GridType>;
43
51
  readonly relative_to: VectorRelativeTo;
44
52
  /**
45
53
  * Create a vector field.
@@ -48,26 +56,71 @@ declare class RawVectorField<ArrayType extends TypedArray> {
48
56
  * @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
49
57
  * @param opts - Options for creating the vector field.
50
58
  */
51
- constructor(grid: Grid, u: ArrayType, v: ArrayType, opts?: RawVectorFieldOptions);
52
- getTextureData(): {
53
- u: TextureDataType<ArrayType>;
54
- v: TextureDataType<ArrayType>;
59
+ constructor(grid: GridType, u: ArrayType, v: ArrayType, opts?: RawVectorFieldOptions);
60
+ /** @internal */
61
+ private getTextureData;
62
+ getWGLTextureSpecs(gl: WebGLAnyRenderingContext, mag_filter: number): {
63
+ u: WGLTextureSpec;
64
+ v: WGLTextureSpec;
55
65
  };
56
- getThinnedField(thin_x: number, thin_y: number): RawVectorField<ArrayType>;
57
- get grid(): Grid;
66
+ /** @internal */
67
+ getThinnedField(thin_fac: number, map_max_zoom: number): RawVectorField<ArrayType, Grid>;
68
+ /** @internal */
69
+ get grid(): GridType;
70
+ sampleField(lon: number, lat: number): [number, number];
58
71
  }
59
72
  /** A class grid of wind profiles */
60
- declare class RawProfileField {
73
+ declare class RawProfileField<GridType extends Grid> {
61
74
  readonly profiles: WindProfile[];
62
- readonly grid: Grid;
75
+ readonly grid: GridType;
63
76
  /**
64
77
  * Create a grid of wind profiles
65
78
  * @param grid - The grid on which the profiles are defined
66
79
  * @param profiles - The wind profiles themselves, which should be given as a 1D array in row-major order, with the first profile being at the lower-left corner of the grid
67
80
  */
68
- constructor(grid: Grid, profiles: WindProfile[]);
69
- /** Get the gridded storm motion vector field (internal method) */
70
- getStormMotionGrid(): RawVectorField<Float16Array>;
81
+ constructor(grid: GridType, profiles: WindProfile[]);
82
+ /**
83
+ * @internal
84
+ * Get the gridded storm motion vector field
85
+ */
86
+ getStormMotionGrid(): RawVectorField<Float16Array, GridType>;
87
+ /** @internal */
88
+ getProfileCoords(): {
89
+ lats: Float32Array;
90
+ lons: Float32Array;
91
+ };
92
+ }
93
+ /**
94
+ * Type for an observation data point
95
+ * @example
96
+ * const obs : ObsRawData<'t' | 'td'> = {'t': 71, 'td': 66};
97
+ */
98
+ type ObsRawData<ObsFieldName extends string> = Record<ObsFieldName, string | number | [number, number] | null>;
99
+ /** Raw observation data, given as a list of objects */
100
+ declare class RawObsField<GridType extends Grid, ObsFieldName extends string> {
101
+ readonly grid: GridType;
102
+ readonly data: ObsRawData<ObsFieldName>[];
103
+ /**
104
+ * Create a field of observations
105
+ * @param grid - The grid on which the obs are defined (can be either a structured or unstructured grid)
106
+ * @param data - The observation data. Conceptually, obs are given as a list of individual observations.
107
+ */
108
+ constructor(grid: GridType, data: ObsRawData<ObsFieldName>[]);
109
+ /**
110
+ * @internal
111
+ * Get observation element as a list of scalar numbers
112
+ */
113
+ getScalar(key: ObsFieldName): (number | null)[];
114
+ /**
115
+ * @internal
116
+ * Get observed element as a list of strings (internal method)
117
+ */
118
+ getStrings(key: ObsFieldName): (string | null)[];
119
+ /**
120
+ * @internal
121
+ * Get observed element as a list of vectors (internal method)
122
+ */
123
+ getVector(key: ObsFieldName): RawVectorField<Float16Array, GridType>;
71
124
  }
72
- export { RawScalarField, RawVectorField, RawProfileField };
73
- export type { RawVectorFieldOptions, VectorRelativeTo, TextureDataType };
125
+ export { RawScalarField, RawVectorField, RawProfileField, RawObsField };
126
+ export type { RawVectorFieldOptions, VectorRelativeTo, TextureDataType, ObsRawData };
package/lib/RawField.js CHANGED
@@ -1,8 +1,15 @@
1
1
  import { Float16Array } from "@petamoriken/float16";
2
2
  import { contourCreator } from "./ContourCreator";
3
- import { Cache, zip } from "./utils";
4
- function getArrayConstructor(ary) {
5
- return ary.constructor;
3
+ import { Cache, getArrayConstructor, zip } from "./utils";
4
+ import { getGLFormatTypeAlignment } from "./PlotComponent";
5
+ function getArrayDType(ary) {
6
+ if (ary instanceof Float32Array) {
7
+ return 'float32';
8
+ }
9
+ else if (ary instanceof Uint8Array) {
10
+ return 'uint8';
11
+ }
12
+ return 'float16';
6
13
  }
7
14
  /** A class representing a raw 2D field of gridded data, such as height or u wind. */
8
15
  class RawScalarField {
@@ -25,15 +32,23 @@ class RawScalarField {
25
32
  getTextureData() {
26
33
  // Need to give float16 data as uint16s to make WebGL happy: https://github.com/petamoriken/float16/issues/105
27
34
  const raw_data = this.data;
28
- let data;
29
- if (raw_data instanceof Float32Array) {
30
- data = raw_data;
31
- }
32
- else {
33
- data = new Uint16Array(raw_data.buffer);
34
- }
35
+ const raw_data_type = getArrayDType(raw_data);
36
+ const data = (raw_data_type == 'float32' || raw_data_type == 'uint8') ? raw_data : new Uint16Array(raw_data.buffer);
35
37
  return data;
36
38
  }
39
+ getWGLTextureSpec(gl, image_mag_filter) {
40
+ const tex_data = this.getTextureData();
41
+ const { format, type, row_alignment } = getGLFormatTypeAlignment(gl, getArrayDType(this.data));
42
+ return { 'format': format, 'type': type,
43
+ 'width': this.grid.ni, 'height': this.grid.nj, 'image': tex_data,
44
+ 'mag_filter': image_mag_filter, 'row_alignment': row_alignment,
45
+ };
46
+ }
47
+ /**
48
+ * Get contour data as an object with each contour level being a separate property.
49
+ * @param opts - Options for doing the contouring
50
+ * @returns contour data as an object
51
+ */
37
52
  async getContours(opts) {
38
53
  return await this.contour_cache.getValue(opts);
39
54
  }
@@ -57,6 +72,9 @@ class RawScalarField {
57
72
  const agg_data = new arrayType(mapGenerator(zipped_args, (a) => func(...a)));
58
73
  return new RawScalarField(args[0].grid, agg_data);
59
74
  }
75
+ sampleField(lon, lat) {
76
+ return this.grid.sampleNearestGridPoint(lon, lat, this.data).sample;
77
+ }
60
78
  }
61
79
  /** A class representing a 2D gridded field of vectors */
62
80
  class RawVectorField {
@@ -73,35 +91,53 @@ class RawVectorField {
73
91
  this.v = new RawScalarField(grid, v);
74
92
  this.relative_to = opts.relative_to === undefined ? 'grid' : opts.relative_to;
75
93
  }
94
+ /** @internal */
76
95
  getTextureData() {
77
96
  // Need to give float16 data as uint16s to make WebGL happy: https://github.com/petamoriken/float16/issues/105
78
97
  const raw_u = this.u.data;
79
98
  const raw_v = this.v.data;
80
- const u = raw_u instanceof Float32Array ? raw_u : new Uint16Array(raw_u.buffer);
81
- const v = raw_v instanceof Float32Array ? raw_v : new Uint16Array(raw_v.buffer);
99
+ const u_raw_data_type = getArrayDType(raw_u);
100
+ const v_raw_data_type = getArrayDType(raw_u);
101
+ const u = (u_raw_data_type == 'float32' || u_raw_data_type == 'uint8') ? raw_u : new Uint16Array(raw_u.buffer);
102
+ const v = (v_raw_data_type == 'float32' || v_raw_data_type == 'uint8') ? raw_v : new Uint16Array(raw_v.buffer);
82
103
  return { u: u, v: v };
83
104
  }
84
- getThinnedField(thin_x, thin_y) {
85
- const new_grid = this.grid.getThinnedGrid(thin_x, thin_y);
86
- const thinGrid = (data) => {
87
- const arrayType = getArrayConstructor(data);
88
- const new_data = new arrayType(new_grid.ni * new_grid.nj);
89
- for (let i = 0; i < new_grid.ni; i++) {
90
- for (let j = 0; j < new_grid.nj; j++) {
91
- const idx_old = i * thin_x + this.grid.ni * j * thin_y;
92
- const idx = i + new_grid.ni * j;
93
- new_data[idx] = data[idx_old];
94
- }
95
- }
96
- return new_data;
105
+ getWGLTextureSpecs(gl, mag_filter) {
106
+ const { u: u_thin, v: v_thin } = this.getTextureData();
107
+ const { format, type, row_alignment } = getGLFormatTypeAlignment(gl, getArrayDType(this.u.data));
108
+ const u_image = { 'format': format, 'type': type,
109
+ 'width': this.grid.ni, 'height': this.grid.nj, 'image': u_thin,
110
+ 'mag_filter': mag_filter, 'row_alignment': row_alignment,
111
+ };
112
+ const v_image = { 'format': format, 'type': type,
113
+ 'width': this.grid.ni, 'height': this.grid.nj, 'image': v_thin,
114
+ 'mag_filter': mag_filter, 'row_alignment': row_alignment,
97
115
  };
98
- const thin_u = thinGrid(this.u.data);
99
- const thin_v = thinGrid(this.v.data);
116
+ return { u: u_image, v: v_image };
117
+ }
118
+ /** @internal */
119
+ getThinnedField(thin_fac, map_max_zoom) {
120
+ const new_grid = this.grid.getThinnedGrid(thin_fac, map_max_zoom);
121
+ const thin_u = new_grid.thinDataArray(this.grid, this.u.data);
122
+ const thin_v = new_grid.thinDataArray(this.grid, this.v.data);
100
123
  return new RawVectorField(new_grid, thin_u, thin_v, { relative_to: this.relative_to });
101
124
  }
125
+ /** @internal */
102
126
  get grid() {
103
127
  return this.u.grid;
104
128
  }
129
+ sampleField(lon, lat) {
130
+ const u_sample = this.grid.sampleNearestGridPoint(lon, lat, this.u.data);
131
+ const v_sample = this.grid.sampleNearestGridPoint(lon, lat, this.v.data);
132
+ const rot = this.relative_to == 'earth' ? 0 : this.grid.getVectorRotationAtPoint(u_sample.sample_lon, u_sample.sample_lat);
133
+ const mag = Math.hypot(u_sample.sample, v_sample.sample);
134
+ let brg = (Math.PI / 2 - Math.atan2(-v_sample.sample, -u_sample.sample) + rot) * 180 / Math.PI;
135
+ if (brg > 360)
136
+ brg -= 360;
137
+ if (brg < 0)
138
+ brg += 360;
139
+ return [brg, mag];
140
+ }
105
141
  }
106
142
  /** A class grid of wind profiles */
107
143
  class RawProfileField {
@@ -114,7 +150,10 @@ class RawProfileField {
114
150
  this.profiles = profiles;
115
151
  this.grid = grid;
116
152
  }
117
- /** Get the gridded storm motion vector field (internal method) */
153
+ /**
154
+ * @internal
155
+ * Get the gridded storm motion vector field
156
+ */
118
157
  getStormMotionGrid() {
119
158
  const profiles = this.profiles;
120
159
  const u = new Float16Array(this.grid.ni * this.grid.nj).fill(parseFloat('nan'));
@@ -126,5 +165,75 @@ class RawProfileField {
126
165
  });
127
166
  return new RawVectorField(this.grid, u, v, { relative_to: 'grid' });
128
167
  }
168
+ /** @internal */
169
+ getProfileCoords() {
170
+ const { lats, lons } = this.grid.getEarthCoords();
171
+ const prof_lats = new Float32Array(this.profiles.length);
172
+ const prof_lons = new Float32Array(this.profiles.length);
173
+ this.profiles.forEach((prof, iprof) => {
174
+ const idx = prof.ilon + prof.jlat * this.grid.ni;
175
+ prof_lats[iprof] = lats[idx];
176
+ prof_lons[iprof] = lons[idx];
177
+ });
178
+ return { lats: prof_lats, lons: prof_lons };
179
+ }
180
+ }
181
+ /** Raw observation data, given as a list of objects */
182
+ class RawObsField {
183
+ /**
184
+ * Create a field of observations
185
+ * @param grid - The grid on which the obs are defined (can be either a structured or unstructured grid)
186
+ * @param data - The observation data. Conceptually, obs are given as a list of individual observations.
187
+ */
188
+ constructor(grid, data) {
189
+ this.grid = grid;
190
+ this.data = data;
191
+ }
192
+ /**
193
+ * @internal
194
+ * Get observation element as a list of scalar numbers
195
+ */
196
+ getScalar(key) {
197
+ const field_data = this.data.map(d => d[key]);
198
+ if (!field_data.map(d => typeof d == 'number' || d === null).reduce((a, b) => a && b, true))
199
+ throw `It doesn't look like ${key} contains scalar numerical data`;
200
+ return field_data;
201
+ }
202
+ /**
203
+ * @internal
204
+ * Get observed element as a list of strings (internal method)
205
+ */
206
+ getStrings(key) {
207
+ const field_data = this.data.map(d => d[key]);
208
+ if (!field_data.map(d => typeof d == 'string' || d === null).reduce((a, b) => a && b, true))
209
+ throw `It doesn't look like ${key} contains string data`;
210
+ return field_data;
211
+ }
212
+ /**
213
+ * @internal
214
+ * Get observed element as a list of vectors (internal method)
215
+ */
216
+ getVector(key) {
217
+ const field_data = this.data.map(d => d[key]);
218
+ if (!field_data.map(d => Array.isArray(d)).reduce((a, b) => a && b, true))
219
+ throw `It doesn't look like ${key} contains vector data`;
220
+ const vector_field_data = field_data;
221
+ const vec2comp = (wspd, wdir) => {
222
+ const u = -wspd * Math.sin(wdir * Math.PI / 180);
223
+ const v = -wspd * Math.cos(wdir * Math.PI / 180);
224
+ return [u, v];
225
+ };
226
+ const u_data = new Float16Array(this.grid.ni * this.grid.nj).fill(parseFloat('nan'));
227
+ const v_data = new Float16Array(this.grid.ni * this.grid.nj).fill(parseFloat('nan'));
228
+ vector_field_data.forEach(([wspd, wdir], idat) => {
229
+ if (wspd === null || wdir === null) {
230
+ return;
231
+ }
232
+ const [u, v] = vec2comp(wspd, wdir);
233
+ u_data[idat] = u;
234
+ v_data[idat] = v;
235
+ });
236
+ return new RawVectorField(this.grid, u_data, v_data, { relative_to: 'earth' });
237
+ }
129
238
  }
130
- export { RawScalarField, RawVectorField, RawProfileField };
239
+ export { RawScalarField, RawVectorField, RawProfileField, RawObsField };
@@ -0,0 +1,12 @@
1
+ import { WGLProgram } from "autumn-wgl";
2
+ import { RenderShaderData, RendererData, WebGLAnyRenderingContext } from "./AutumnTypes";
3
+ declare class ShaderProgramManager {
4
+ readonly vertex_shader_src: string;
5
+ readonly fragment_shader_src: string;
6
+ readonly shader_defines: string[];
7
+ private readonly program_map;
8
+ constructor(vertex_shader_src: string, fragment_shader_src: string, shader_defines: string[]);
9
+ getShaderProgram(gl: WebGLAnyRenderingContext, shader_data: RenderShaderData | null): WGLProgram;
10
+ getShaderUniforms(render_data: RendererData): Record<string, number | number[]>;
11
+ }
12
+ export { ShaderProgramManager };
@@ -0,0 +1,58 @@
1
+ import { WGLProgram } from "autumn-wgl";
2
+ import { mergeShaderCode } from "./utils";
3
+ class ShaderProgramManager {
4
+ constructor(vertex_shader_src, fragment_shader_src, shader_defines) {
5
+ this.vertex_shader_src = vertex_shader_src;
6
+ this.fragment_shader_src = fragment_shader_src;
7
+ this.shader_defines = shader_defines;
8
+ this.program_map = new Map();
9
+ }
10
+ getShaderProgram(gl, shader_data) {
11
+ const shader_key = shader_data === null ? 'default' : shader_data.variantName;
12
+ const cached_program = this.program_map.get(shader_key);
13
+ if (cached_program !== undefined) {
14
+ return cached_program;
15
+ }
16
+ let vertex_src = this.vertex_shader_src;
17
+ if (shader_data !== null) {
18
+ vertex_src = mergeShaderCode(shader_data.vertexShaderPrelude + '\n' + shader_data.define, vertex_src);
19
+ }
20
+ else {
21
+ const prelude = `
22
+ uniform mat4 u_projection_matrix;
23
+
24
+ vec4 projectTile(vec2 p) {
25
+ vec4 result = u_projection_matrix * vec4(p, 0.0, 1.0);
26
+ return result;
27
+ }`;
28
+ vertex_src = mergeShaderCode(prelude, vertex_src);
29
+ }
30
+ const program = new WGLProgram(gl, vertex_src, this.fragment_shader_src, { define: this.shader_defines });
31
+ this.program_map.set(shader_key, program);
32
+ return program;
33
+ }
34
+ getShaderUniforms(render_data) {
35
+ if (render_data.type == 'maplibre') {
36
+ if (render_data.shaderData.define.includes('GLOBE')) {
37
+ return {
38
+ 'u_projection_matrix': render_data.defaultProjectionData.mainMatrix,
39
+ 'u_projection_fallback_matrix': render_data.defaultProjectionData.fallbackMatrix,
40
+ 'u_projection_tile_mercator_coords': render_data.defaultProjectionData.tileMercatorCoords,
41
+ 'u_projection_clipping_plane': render_data.defaultProjectionData.clippingPlane,
42
+ 'u_projection_transition': render_data.defaultProjectionData.projectionTransition
43
+ };
44
+ }
45
+ else {
46
+ return {
47
+ 'u_projection_matrix': render_data.defaultProjectionData.mainMatrix,
48
+ };
49
+ }
50
+ }
51
+ else {
52
+ return {
53
+ 'u_projection_matrix': render_data.mainMatrix,
54
+ };
55
+ }
56
+ }
57
+ }
58
+ export { ShaderProgramManager };