autumnplot-gl 4.0.0 → 4.1.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.
Files changed (77) hide show
  1. package/README.md +2 -0
  2. package/dist/812.autumnplot-gl.js +2 -0
  3. package/dist/812.autumnplot-gl.js.map +1 -0
  4. package/dist/983.autumnplot-gl.js +1 -1
  5. package/dist/983.autumnplot-gl.js.map +1 -1
  6. package/dist/autumnplot-gl.js +1 -1
  7. package/dist/autumnplot-gl.js.map +1 -1
  8. package/dist/marchingsquares.wasm +0 -0
  9. package/lib/AutumnTypes.d.ts +6 -4
  10. package/lib/AutumnTypes.js +4 -1
  11. package/lib/Barbs.d.ts +11 -2
  12. package/lib/Barbs.js +9 -0
  13. package/lib/BillboardCollection.d.ts +2 -2
  14. package/lib/BillboardCollection.js +14 -14
  15. package/lib/ColorBar.d.ts +6 -1
  16. package/lib/ColorBar.js +10 -4
  17. package/lib/Colormap.d.ts +8 -1
  18. package/lib/Colormap.js +24 -1
  19. package/lib/Contour.d.ts +16 -1
  20. package/lib/Contour.js +14 -2
  21. package/lib/{ContourCreator.d.ts → ContourCreator.worker.d.ts} +10 -11
  22. package/lib/{ContourCreator.js → ContourCreator.worker.js} +15 -14
  23. package/lib/Fill.d.ts +29 -11
  24. package/lib/Fill.js +38 -18
  25. package/lib/Hodographs.d.ts +13 -3
  26. package/lib/Hodographs.js +11 -1
  27. package/lib/Map.d.ts +3 -4
  28. package/lib/Map.js +49 -51
  29. package/lib/Paintball.d.ts +13 -5
  30. package/lib/Paintball.js +96 -46
  31. package/lib/PlotComponent.d.ts +8 -3
  32. package/lib/PlotComponent.js +35 -1
  33. package/lib/PlotLayer.worker.js +1 -1
  34. package/lib/RawField.d.ts +221 -27
  35. package/lib/RawField.js +405 -58
  36. package/lib/StationPlot.d.ts +22 -6
  37. package/lib/StationPlot.js +88 -22
  38. package/lib/TextCollection.d.ts +5 -0
  39. package/lib/TextCollection.js +79 -9
  40. package/lib/WasmInterface.d.ts +7 -0
  41. package/lib/WasmInterface.js +11 -0
  42. package/lib/WorkerPool.d.ts +8 -0
  43. package/lib/WorkerPool.js +77 -0
  44. package/lib/cpp/marchingsquares.js +127 -13
  45. package/lib/cpp/marchingsquares.wasm +0 -0
  46. package/lib/cpp/marchingsquares_embind.d.ts +16 -3
  47. package/lib/grids/AutoZoom.d.ts +21 -0
  48. package/lib/grids/AutoZoom.js +63 -0
  49. package/lib/grids/DomainBuffer.d.ts +14 -0
  50. package/lib/grids/DomainBuffer.js +16 -0
  51. package/lib/grids/Geostationary.d.ts +35 -0
  52. package/lib/grids/Geostationary.js +47 -0
  53. package/lib/grids/Grid.d.ts +36 -0
  54. package/lib/grids/Grid.js +12 -0
  55. package/lib/grids/GridCoordinates.d.ts +10 -0
  56. package/lib/grids/GridCoordinates.js +64 -0
  57. package/lib/grids/LambertGrid.d.ts +73 -0
  58. package/lib/grids/LambertGrid.js +92 -0
  59. package/lib/grids/PlateCarreeGrid.d.ts +46 -0
  60. package/lib/grids/PlateCarreeGrid.js +55 -0
  61. package/lib/grids/PlateCarreeRotatedGrid.d.ts +53 -0
  62. package/lib/grids/PlateCarreeRotatedGrid.js +65 -0
  63. package/lib/grids/RadarSweepGrid.d.ts +46 -0
  64. package/lib/grids/RadarSweepGrid.js +74 -0
  65. package/lib/grids/StructuredGrid.d.ts +49 -0
  66. package/lib/grids/StructuredGrid.js +103 -0
  67. package/lib/grids/UnstructuredGrid.d.ts +56 -0
  68. package/lib/grids/UnstructuredGrid.js +102 -0
  69. package/lib/index.d.ts +15 -4
  70. package/lib/index.js +15 -7
  71. package/lib/utils.d.ts +11 -2
  72. package/lib/utils.js +63 -1
  73. package/package.json +3 -2
  74. package/lib/Grid.d.ts +0 -270
  75. package/lib/Grid.js +0 -600
  76. package/lib/ParticleTracer.d.ts +0 -19
  77. package/lib/ParticleTracer.js +0 -37
package/lib/Fill.js CHANGED
@@ -1,9 +1,8 @@
1
- import { PlotComponent } from './PlotComponent';
1
+ import { PlotComponent, getGLFormatTypeAlignment } from './PlotComponent';
2
2
  import { ColorMap, ColorMapGPUInterface } from './Colormap';
3
3
  import { WGLTexture } from 'autumn-wgl';
4
- import { RawScalarField } from './RawField';
5
4
  import { getRendererData } from './AutumnTypes';
6
- import { normalizeOptions } from './utils';
5
+ import { applySamplerCodeScalar, normalizeOptions } from './utils';
7
6
  import { ShaderProgramManager } from './ShaderManager';
8
7
  const contourfill_vertex_shader_src = `#version 300 es
9
8
 
@@ -25,8 +24,6 @@ const contourfill_fragment_shader_src = `#version 300 es
25
24
 
26
25
  in highp vec2 v_tex_coord;
27
26
 
28
- uniform sampler2D u_fill_sampler;
29
-
30
27
  #ifdef MASK
31
28
  uniform sampler2D u_mask_sampler;
32
29
  #endif
@@ -39,7 +36,7 @@ uniform int u_mask_val;
39
36
  out highp vec4 fragColor;
40
37
 
41
38
  void main() {
42
- highp float fill_val = texture(u_fill_sampler, v_tex_coord).r;
39
+ highp float fill_val = get_field_value(v_tex_coord);
43
40
 
44
41
  int draw_mask = 1;
45
42
 
@@ -89,20 +86,17 @@ class PlotComponentFill extends PlotComponent {
89
86
  return;
90
87
  const gl = this.gl_elems.gl;
91
88
  const map = this.gl_elems.map;
92
- const fill_image = this.field.getWGLTextureSpec(gl, this.image_mag_filter);
93
- if (this.fill_texture === null) {
94
- this.fill_texture = new WGLTexture(gl, fill_image);
95
- }
96
- else {
97
- this.fill_texture.setImageData(fill_image);
98
- }
89
+ this.fill_texture = this.field.updateTexImageData(gl, this.image_mag_filter, this.fill_texture);
99
90
  if (mask !== undefined) {
100
91
  if (this.opts.cmap_mask === null) {
101
92
  console.warn("A mask was passed to updateField on a Fill component that didn't have a mask. The updated mask will be ignored.");
102
93
  return;
103
94
  }
104
- const mask_field = new RawScalarField(this.field.grid, mask);
105
- const mask_image = mask_field.getWGLTextureSpec(gl, gl.NEAREST);
95
+ const { format, type, row_alignment } = getGLFormatTypeAlignment(gl, 'uint8');
96
+ const mask_image = { 'format': format, 'type': type,
97
+ 'width': this.field.grid.ni, 'height': this.field.grid.nj, 'image': mask,
98
+ 'mag_filter': gl.NEAREST, 'row_alignment': row_alignment,
99
+ };
106
100
  if (this.mask_texture === null) {
107
101
  this.mask_texture = new WGLTexture(gl, mask_image);
108
102
  }
@@ -114,7 +108,7 @@ class PlotComponentFill extends PlotComponent {
114
108
  }
115
109
  async onAdd(map, gl) {
116
110
  // Basic procedure for the filled contours inspired by https://blog.mbq.me/webgl-weather-globe/
117
- const { vertices: vertices, texcoords: texcoords } = await this.field.grid.getWGLBuffers(gl);
111
+ const { vertices: vertices, texcoords: texcoords } = await this.field.grid.getDomainBuffers(gl);
118
112
  this.cmap_gpu.forEach(cmg => {
119
113
  if (this.image_mag_filter === null || this.cmap_mag_filter === null) {
120
114
  throw `Implement magnification filters in a subclass`;
@@ -125,7 +119,14 @@ class PlotComponentFill extends PlotComponent {
125
119
  if (this.opts.cmap_mask !== null) {
126
120
  shader_defines.push('MASK');
127
121
  }
128
- const shader_manger = new ShaderProgramManager(contourfill_vertex_shader_src, ColorMapGPUInterface.applyShader(contourfill_fragment_shader_src), shader_defines);
122
+ if (this.image_mag_filter === null || this.cmap_mag_filter === null) {
123
+ throw `Implement magnification filters in a subclass`;
124
+ }
125
+ const sampler_keys = this.field.getSamplerIds();
126
+ const sampler_expression = this.field.getExpression();
127
+ const data_types = this.field.dtypes;
128
+ const frag_shader_src = applySamplerCodeScalar(ColorMapGPUInterface.applyShader(contourfill_fragment_shader_src), sampler_keys, sampler_expression, data_types);
129
+ const shader_manger = new ShaderProgramManager(contourfill_vertex_shader_src, frag_shader_src, shader_defines);
129
130
  this.gl_elems = {
130
131
  gl: gl, shader_manager: shader_manger, map: map, vertices: vertices, texcoords: texcoords,
131
132
  };
@@ -137,7 +138,8 @@ class PlotComponentFill extends PlotComponent {
137
138
  const gl_elems = this.gl_elems;
138
139
  const render_data = getRendererData(arg);
139
140
  const program = this.gl_elems.shader_manager.getShaderProgram(gl, render_data.shaderData);
140
- program.use({ 'a_pos': gl_elems.vertices, 'a_tex_coord': gl_elems.texcoords }, { 'u_opacity': this.opts.opacity, ...this.gl_elems.shader_manager.getShaderUniforms(render_data) }, { 'u_fill_sampler': this.fill_texture });
141
+ const samplers = Object.fromEntries([...this.fill_texture.entries()]);
142
+ program.use({ 'a_pos': gl_elems.vertices, 'a_tex_coord': gl_elems.texcoords }, { 'u_opacity': this.opts.opacity, ...this.gl_elems.shader_manager.getShaderUniforms(render_data) }, samplers);
141
143
  this.cmap_gpu.forEach((cmg, icmg) => {
142
144
  program.setUniforms({ 'u_offset': 0 });
143
145
  if (this.opts.cmap_mask !== null && this.mask_texture !== null) {
@@ -161,6 +163,15 @@ class PlotComponentFill extends PlotComponent {
161
163
  }
162
164
  /**
163
165
  * A raster (i.e. pixel) plot
166
+ *
167
+ * ## Grid Compatibility
168
+ * - :white_check_mark: `PlateCarreeGrid`
169
+ * - :white_check_mark: `PlateCarreeRotatedGrid`
170
+ * - :white_check_mark: `LambertGrid`
171
+ * - :x: `UnstructuredGrid`
172
+ * - :white_check_mark: `RadarSweepGrid`
173
+ * - :white_check_mark: `Geostationary`
174
+ *
164
175
  * @example
165
176
  * // Create a raster plot with the provided color map
166
177
  * const raster = new Raster(wind_speed_field, {cmap: color_map});
@@ -200,6 +211,15 @@ class Raster extends PlotComponentFill {
200
211
  }
201
212
  /**
202
213
  * A filled contoured field
214
+ *
215
+ * ## Grid Compatibility
216
+ * - :white_check_mark: `PlateCarreeGrid`
217
+ * - :white_check_mark: `PlateCarreeRotatedGrid`
218
+ * - :white_check_mark: `LambertGrid`
219
+ * - :x: `UnstructuredGrid`
220
+ * - :white_check_mark: `RadarSweepGrid`
221
+ * - :white_check_mark: `Geostationary`
222
+ *
203
223
  * @example
204
224
  * // Create a field of filled contours with the provided color map
205
225
  * const fill = new ContourFill(wind_speed_field, {cmap: color_map});
@@ -3,7 +3,7 @@ import { MapLikeType } from "./Map";
3
3
  import { RawProfileField } from "./RawField";
4
4
  import { RenderMethodArg, WebGLAnyRenderingContext } from "./AutumnTypes";
5
5
  import { ColorMap } from "./Colormap";
6
- import { Grid } from "./Grid";
6
+ import { AutoZoomGrid } from "./grids/AutoZoom";
7
7
  /** Options for {@link Hodographs} components */
8
8
  interface HodographOptions {
9
9
  /**
@@ -37,8 +37,18 @@ interface HodographOptions {
37
37
  */
38
38
  max_wind_speed_ring?: number;
39
39
  }
40
- /** A class representing a field of hodograph plots */
41
- declare class Hodographs<GridType extends Grid, MapType extends MapLikeType> extends PlotComponent<MapType> {
40
+ /**
41
+ * A class representing a field of hodograph plots
42
+ *
43
+ * ## Grid Compatibility
44
+ * - :white_check_mark: `PlateCarreeGrid`
45
+ * - :white_check_mark: `PlateCarreeRotatedGrid`
46
+ * - :white_check_mark: `LambertGrid`
47
+ * - :white_check_mark: `UnstructuredGrid`
48
+ * - :x: `RadarSweepGrid`
49
+ * - :x: `Geostationary`
50
+ */
51
+ declare class Hodographs<GridType extends AutoZoomGrid, MapType extends MapLikeType> extends PlotComponent<MapType> {
42
52
  private profile_field;
43
53
  readonly opts: Required<HodographOptions>;
44
54
  private gl_elems;
package/lib/Hodographs.js CHANGED
@@ -56,7 +56,17 @@ const hodograph_opt_defaults = {
56
56
  height_cmap: HODO_CMAP,
57
57
  max_wind_speed_ring: 80
58
58
  };
59
- /** A class representing a field of hodograph plots */
59
+ /**
60
+ * A class representing a field of hodograph plots
61
+ *
62
+ * ## Grid Compatibility
63
+ * - :white_check_mark: `PlateCarreeGrid`
64
+ * - :white_check_mark: `PlateCarreeRotatedGrid`
65
+ * - :white_check_mark: `LambertGrid`
66
+ * - :white_check_mark: `UnstructuredGrid`
67
+ * - :x: `RadarSweepGrid`
68
+ * - :x: `Geostationary`
69
+ */
60
70
  class Hodographs extends PlotComponent {
61
71
  /**
62
72
  * Create a field of hodographs
package/lib/Map.d.ts CHANGED
@@ -29,14 +29,13 @@ interface RotateSphereParams {
29
29
  declare function rotateSphere(params: RotateSphereParams): (a: number, b: number, opts?: {
30
30
  inverse: boolean;
31
31
  }) => [number, number];
32
- interface VerticalPerspectiveParams {
33
- lat_0: number;
32
+ interface GeostationaryProjectionParams {
34
33
  lon_0: number;
35
34
  alt: number;
36
35
  a: number;
37
36
  b: number;
38
37
  }
39
- declare function verticalPerspective(params: VerticalPerspectiveParams): (a: number, b: number, opts?: {
38
+ declare function geostationaryProjection(params: GeostationaryProjectionParams): (a: number, b: number, opts?: {
40
39
  inverse: boolean;
41
40
  }) => [number, number];
42
41
  /**
@@ -62,5 +61,5 @@ declare class LngLat {
62
61
  };
63
62
  static fromMercatorCoord(x: number, y: number): LngLat;
64
63
  }
65
- export { LngLat, lambertConformalConic, rotateSphere, verticalPerspective };
64
+ export { LngLat, lambertConformalConic, rotateSphere, geostationaryProjection };
66
65
  export type { MapLikeType };
package/lib/Map.js CHANGED
@@ -121,60 +121,51 @@ function rotateSphere(params) {
121
121
  return opts.inverse ? compute_rotation_inverse(a, b) : compute_rotation(a, b);
122
122
  };
123
123
  }
124
- function verticalPerspective(params) {
125
- // WGS 84 spheroid
126
- const semimajor = params.a;
127
- const semiminor = params.b;
128
- const eccen = Math.sqrt(1 - (semiminor * semiminor) / (semimajor * semimajor));
124
+ // Changes to the geostationaryProjection to make sure the coastlines and satellite line up
125
+ function geostationaryProjection(params) {
129
126
  const radians = Math.PI / 180;
130
- let { lat_0, lon_0, alt } = params;
131
- const alt_0 = 0;
132
- const alt_00 = 0;
133
- const eccen2 = eccen * eccen;
134
- lat_0 *= radians;
135
- lon_0 *= radians;
136
- const sin_lat_0 = Math.sin(lat_0);
137
- const cos_lat_0 = Math.cos(lat_0);
138
- const N1 = semimajor / Math.sqrt(1 - (eccen * sin_lat_0) ** 2);
139
- let lat_g = lat_0, P = 0;
140
- for (let i = 0; i < 2; i++) {
141
- P = Math.cos(lat_0) / Math.cos(lat_g) * (alt + N1 + alt_00) / semimajor;
142
- lat_g = lat_0 - Math.asin(N1 * eccen * eccen * sin_lat_0 * cos_lat_0 / (P * semimajor));
143
- }
144
- const compute_perspective = (lon, lat) => {
127
+ const { lon_0, alt, a, b } = params;
128
+ const lambda_0 = lon_0 * radians;
129
+ const a2 = a * a;
130
+ const b2 = b * b;
131
+ const forward = (lon, lat) => {
145
132
  lon *= radians;
146
133
  lat *= radians;
147
- const sin_lat = Math.sin(lat);
148
134
  const cos_lat = Math.cos(lat);
149
- const N = semimajor / Math.sqrt(1 - (eccen * sin_lat) ** 2);
150
- const C = (N + alt_0) / semimajor * cos_lat;
151
- const S = ((N * (1 - eccen * eccen) + alt_0) / semimajor) * sin_lat;
152
- const K = alt / (P * Math.cos(lat_0 - lat_g) - S * sin_lat_0 - C * cos_lat_0 * Math.cos(lon - lon_0));
153
- const x = K * C * Math.sin(lon - lon_0);
154
- const y = K * (P * Math.sin(lat_0 - lat_g) + S * cos_lat_0 - C * sin_lat_0 * Math.cos(lon - lon_0));
135
+ const sin_lat = Math.sin(lat);
136
+ const cos_lon = Math.cos(lon - lambda_0);
137
+ const sin_lon = Math.sin(lon - lambda_0);
138
+ const rc = b / Math.sqrt(1 - (1 - (b2 / a2)) * cos_lat * cos_lat);
139
+ const sx = rc * cos_lat * cos_lon;
140
+ const sy = rc * cos_lat * sin_lon;
141
+ const sz = rc * sin_lat;
142
+ const x = Math.atan(-sy / (alt - sx));
143
+ const y = Math.atan(sz / Math.sqrt((alt - sx) * (alt - sx) + sy * sy));
155
144
  return [x, y];
156
145
  };
157
- const B = P * Math.cos(lat_0 - lat_g);
158
- const D = P * Math.sin(lat_0 - lat_g);
159
- const L = 1 - eccen2 * cos_lat_0 * cos_lat_0;
160
- const G = 1 - eccen2 * sin_lat_0 * sin_lat_0;
161
- const J = 2 * eccen2 * sin_lat_0 * cos_lat_0;
162
- const E = 1; // If alt_0 = 0, set E = 1
163
- const t = P * P * (1 - (eccen * Math.cos(lat_g)) ** 2) - E * (1 - eccen2);
164
- const compute_perspective_inverse = (x, y) => {
165
- const u = -2 * B * L * alt - 2 * D * G * y + B * J * y + D * J * alt;
166
- const v = L * alt * alt + G * y * y - alt * J * y + (1 - eccen2) * x * x;
167
- const K_prime = (-u + Math.sqrt(u * u - 4 * t * v)) / (2 * t);
168
- const X = semimajor * ((B - alt / K_prime) * cos_lat_0 - (y / K_prime - D) * sin_lat_0);
169
- const Y = semimajor * x / K_prime;
170
- const S = (y / K_prime - D) * cos_lat_0 + (B - alt / K_prime) * sin_lat_0;
171
- const lon = lon_0 + Math.atan2(Y, X);
172
- const lat = Math.atan2(S, Math.sqrt((1 - eccen2) * (1 - eccen2 - S * S)));
146
+ const inverse = (x, y) => {
147
+ // x = E-W scan angle (radians), y = N-S scan angle (radians)
148
+ // GOES-R PUG-L1B-vol3 Section 4.2 / Table 4.2.8-1
149
+ const sin_x = Math.sin(x);
150
+ const cos_x = Math.cos(x);
151
+ const sin_y = Math.sin(y);
152
+ const cos_y = Math.cos(y);
153
+ const a_var = sin_x * sin_x + cos_x * cos_x * (cos_y * cos_y + (a2 / b2) * sin_y * sin_y);
154
+ const b_var = -2 * alt * cos_x * cos_y;
155
+ const c_var = alt * alt - a2;
156
+ const disc = b_var * b_var - 4 * a_var * c_var;
157
+ if (disc < 0)
158
+ return [NaN, NaN]; // off-Earth scan angle
159
+ const rs = (-b_var - Math.sqrt(disc)) / (2 * a_var);
160
+ const sx = rs * cos_x * cos_y;
161
+ const sy = -rs * sin_x; // no cos_y factor
162
+ const sz = rs * cos_x * sin_y; // needs cos_x factor
163
+ const lon = lambda_0 - Math.atan(sy / (alt - sx));
164
+ const lat = Math.atan((a2 / b2) * (sz / Math.sqrt((alt - sx) * (alt - sx) + sy * sy)));
173
165
  return [lon / radians, lat / radians];
174
166
  };
175
167
  return (a, b, opts) => {
176
- opts = opts === undefined ? { inverse: false } : opts;
177
- return opts.inverse ? compute_perspective_inverse(a, b) : compute_perspective(a, b);
168
+ return opts?.inverse ? inverse(a, b) : forward(a, b);
178
169
  };
179
170
  }
180
171
  function mercatorXfromLng(lng) {
@@ -207,19 +198,26 @@ function latFromMercatorY(y) {
207
198
  class LngLat {
208
199
  constructor(lng, lat) {
209
200
  if (isNaN(lng) || isNaN(lat)) {
210
- throw new Error(`Invalid LngLat object: (${lng}, ${lat})`);
201
+ this.lng = NaN;
202
+ this.lat = NaN;
211
203
  }
212
- this.lng = +lng;
213
- this.lat = +lat;
214
- if (this.lat > 90 || this.lat < -90) {
215
- throw new Error('Invalid LngLat latitude value: must be between -90 and 90');
204
+ else {
205
+ this.lng = +lng;
206
+ this.lat = +lat;
207
+ if (this.lat > 90 || this.lat < -90) {
208
+ throw new Error('Invalid LngLat latitude value: must be between -90 and 90');
209
+ }
216
210
  }
217
211
  }
218
212
  toMercatorCoord() {
213
+ if (isNaN(this.lng) || isNaN(this.lng))
214
+ return { x: NaN, y: NaN };
219
215
  return { x: mercatorXfromLng(this.lng), y: mercatorYfromLat(this.lat) };
220
216
  }
221
217
  static fromMercatorCoord(x, y) {
218
+ if (isNaN(x) || isNaN(y))
219
+ return new LngLat(NaN, NaN);
222
220
  return new LngLat(lngFromMercatorX(x), latFromMercatorY(y));
223
221
  }
224
222
  }
225
- export { LngLat, lambertConformalConic, rotateSphere, verticalPerspective };
223
+ export { LngLat, lambertConformalConic, rotateSphere, geostationaryProjection };
@@ -1,8 +1,8 @@
1
1
  import { RenderMethodArg, TypedArray, WebGLAnyRenderingContext } from "./AutumnTypes";
2
- import { StructuredGrid } from "./Grid";
2
+ import { DomainBufferGrid } from "./grids/DomainBuffer";
3
3
  import { MapLikeType } from "./Map";
4
4
  import { PlotComponent } from "./PlotComponent";
5
- import { RawScalarField } from "./RawField";
5
+ import { ExpressionScalarField } from "./RawField";
6
6
  /** Options for {@link Paintball} components */
7
7
  interface PaintballOptions {
8
8
  /**
@@ -21,8 +21,16 @@ interface PaintballOptions {
21
21
  * the data for the paintball plot is given as a single field with the objects from each member encoded as "bits" in the field. Because the field is made up
22
22
  * of single-precision floats, this works for up to 24 members. (Technically speaking, I don't need the quotes around "bits", as they're bits of the
23
23
  * significand of an IEEE 754 float.)
24
+ *
25
+ * ## Grid Compatibility
26
+ * - :white_check_mark: `PlateCarreeGrid`
27
+ * - :white_check_mark: `PlateCarreeRotatedGrid`
28
+ * - :white_check_mark: `LambertGrid`
29
+ * - :x: `UnstructuredGrid`
30
+ * - :white_check_mark: `RadarSweepGrid`
31
+ * - :white_check_mark: `Geostationary`
24
32
  */
25
- declare class Paintball<ArrayType extends TypedArray, GridType extends StructuredGrid, MapType extends MapLikeType> extends PlotComponent<MapType> {
33
+ declare class Paintball<ArrayType extends TypedArray, GridType extends DomainBufferGrid, MapType extends MapLikeType> extends PlotComponent<MapType> {
26
34
  private field;
27
35
  readonly opts: Required<PaintballOptions>;
28
36
  private readonly color_components;
@@ -35,12 +43,12 @@ declare class Paintball<ArrayType extends TypedArray, GridType extends Structure
35
43
  * `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.
36
44
  * @param opts - Options for creating the paintball plot
37
45
  */
38
- constructor(field: RawScalarField<ArrayType, GridType>, opts?: PaintballOptions);
46
+ constructor(field: ExpressionScalarField<ArrayType, GridType>, opts?: PaintballOptions);
39
47
  /**
40
48
  * Update the field displayed as a paintball plot
41
49
  * @param field - The new field to display as a paintball plot
42
50
  */
43
- updateField(field: RawScalarField<ArrayType, GridType>): Promise<void>;
51
+ updateField(field: ExpressionScalarField<ArrayType, GridType>): Promise<void>;
44
52
  /**
45
53
  * @internal
46
54
  * Add the paintball plot to a map.
package/lib/Paintball.js CHANGED
@@ -1,10 +1,20 @@
1
1
  import { getRendererData } from "./AutumnTypes";
2
2
  import { Color } from "./Color";
3
- import { PlotComponent } from "./PlotComponent";
3
+ import { PlotComponent, getGLFormatTypeAlignment } from "./PlotComponent";
4
4
  import { ShaderProgramManager } from "./ShaderManager";
5
- import { normalizeOptions } from "./utils";
6
- import { WGLTexture } from "autumn-wgl";
7
- const paintball_vertex_shader_src = `#version 300 es
5
+ import { applySamplerCodeScalar, normalizeOptions } from "./utils";
6
+ import { WGLBuffer, WGLFramebuffer, WGLProgram, WGLTexture } from "autumn-wgl";
7
+ const paintball_step1_vertex_shader_src = `#version 300 es
8
+
9
+ in vec2 a_pos;
10
+
11
+ out highp vec2 v_tex_coord;
12
+
13
+ void main() {
14
+ gl_Position = vec4(a_pos.xy, 0., 1.);
15
+ v_tex_coord = 0.5 * a_pos.xy + vec2(0.5, 0.5);
16
+ }`
17
+ const paintball_step2_vertex_shader_src = `#version 300 es
8
18
 
9
19
  uniform int u_offset;
10
20
 
@@ -20,15 +30,34 @@ void main() {
20
30
  gl_Position = projectTile(a_pos.xy + globe_offset);
21
31
  v_tex_coord = a_tex_coord;
22
32
  }`
23
- const paintball_fragment_shader_src = `#version 300 es
33
+ const paintball_step1_fragment_shader_src = `#version 300 es
34
+
35
+ in highp vec2 v_tex_coord;
36
+
37
+ uniform int u_imem;
38
+
39
+ out highp vec4 fragColor;
40
+
41
+ void main() {
42
+ uint fill_val = uint(get_field_value(v_tex_coord));
43
+
44
+ if (fill_val < uint(1)) {
45
+ discard;
46
+ }
24
47
 
25
- #define MAX_N_COLORS 24
48
+ lowp vec4 off_color = vec4(0., 0., 0., 1.);
49
+ lowp vec4 on_color = vec4(1., 1., 1., 1.);
50
+
51
+ uint mask = uint(1) << u_imem;
52
+ lowp float mem_active = float((fill_val & mask) > uint(0));
53
+ fragColor = mix(off_color, on_color, mem_active);
54
+ }`
55
+ const paintball_step2_fragment_shader_src = `#version 300 es
26
56
 
27
57
  in highp vec2 v_tex_coord;
28
58
 
29
59
  uniform sampler2D u_fill_sampler;
30
- uniform lowp vec4 u_colors[MAX_N_COLORS];
31
- uniform int u_num_colors;
60
+ uniform lowp vec4 u_color;
32
61
  uniform highp float u_opacity;
33
62
 
34
63
  out highp vec4 fragColor;
@@ -36,19 +65,11 @@ out highp vec4 fragColor;
36
65
  void main() {
37
66
  highp float fill_val = texture(u_fill_sampler, v_tex_coord).r;
38
67
 
39
- if (fill_val < 0.5) {
68
+ if (fill_val < 0.4) {
40
69
  discard;
41
70
  }
42
71
 
43
- lowp vec4 color = vec4(0., 0., 0., 0.);
44
-
45
- for (int nclr = 0; nclr < MAX_N_COLORS ; nclr++) {
46
- if (nclr >= u_num_colors || fill_val < 0.99) { break; }
47
-
48
- lowp float mem_active = mod(fill_val, 2.);
49
- color = mix(color, u_colors[nclr], mem_active);
50
- fill_val = floor(fill_val / 2.);
51
- }
72
+ lowp vec4 color = u_color;
52
73
 
53
74
  color.a = color.a * u_opacity;
54
75
  fragColor = color;
@@ -63,6 +84,14 @@ const paintball_opt_defaults = {
63
84
  * the data for the paintball plot is given as a single field with the objects from each member encoded as "bits" in the field. Because the field is made up
64
85
  * of single-precision floats, this works for up to 24 members. (Technically speaking, I don't need the quotes around "bits", as they're bits of the
65
86
  * significand of an IEEE 754 float.)
87
+ *
88
+ * ## Grid Compatibility
89
+ * - :white_check_mark: `PlateCarreeGrid`
90
+ * - :white_check_mark: `PlateCarreeRotatedGrid`
91
+ * - :white_check_mark: `LambertGrid`
92
+ * - :x: `UnstructuredGrid`
93
+ * - :white_check_mark: `RadarSweepGrid`
94
+ * - :white_check_mark: `Geostationary`
66
95
  */
67
96
  class Paintball extends PlotComponent {
68
97
  /**
@@ -76,7 +105,7 @@ class Paintball extends PlotComponent {
76
105
  super();
77
106
  this.field = field;
78
107
  this.opts = normalizeOptions(opts, paintball_opt_defaults);
79
- this.color_components = this.opts.colors.map(color => Color.fromHex(color).toRGBATuple()).flat();
108
+ this.color_components = this.opts.colors.map(color => Color.fromHex(color).toRGBATuple());
80
109
  this.gl_elems = null;
81
110
  this.fill_texture = null;
82
111
  }
@@ -90,13 +119,7 @@ class Paintball extends PlotComponent {
90
119
  return;
91
120
  const gl = this.gl_elems.gl;
92
121
  gl.pixelStorei(gl.UNPACK_ALIGNMENT, 2);
93
- const fill_image = this.field.getWGLTextureSpec(gl, gl.NEAREST);
94
- if (this.fill_texture === null) {
95
- this.fill_texture = new WGLTexture(gl, fill_image);
96
- }
97
- else {
98
- this.fill_texture.setImageData(fill_image);
99
- }
122
+ this.fill_texture = this.field.updateTexImageData(gl, gl.NEAREST, this.fill_texture);
100
123
  }
101
124
  /**
102
125
  * @internal
@@ -104,12 +127,23 @@ class Paintball extends PlotComponent {
104
127
  */
105
128
  async onAdd(map, gl) {
106
129
  gl.getExtension('OES_texture_float');
107
- const { vertices: verts_buf, texcoords: tex_coords_buf } = await this.field.grid.getWGLBuffers(gl);
108
- const vertices = verts_buf;
109
- const texcoords = tex_coords_buf;
110
- const shader_manager = new ShaderProgramManager(paintball_vertex_shader_src, paintball_fragment_shader_src, []);
130
+ const vertices_step1 = new WGLBuffer(gl, new Float32Array([-1., -1., 1., -1., -1., 1., 1., 1.]), 2, gl.TRIANGLE_STRIP);
131
+ const { vertices: vertices_step2, texcoords: texcoords_step2 } = await this.field.grid.getDomainBuffers(gl);
132
+ const sampler_keys = this.field.getSamplerIds();
133
+ const sampler_expression = this.field.getExpression();
134
+ const dtypes = this.field.dtypes;
135
+ const step1_frag_shader_src = applySamplerCodeScalar(paintball_step1_fragment_shader_src, sampler_keys, sampler_expression, dtypes);
136
+ const shader_program_step1 = new WGLProgram(gl, paintball_step1_vertex_shader_src, step1_frag_shader_src);
137
+ const shader_manager_step2 = new ShaderProgramManager(paintball_step2_vertex_shader_src, paintball_step2_fragment_shader_src, []);
138
+ const { format, type, row_alignment } = getGLFormatTypeAlignment(gl, 'float16');
139
+ const fb_texture = new WGLTexture(gl, { format: format, type: type,
140
+ width: this.field.grid.ni, height: this.field.grid.nj, image: null,
141
+ mag_filter: gl.LINEAR, row_alignment: row_alignment });
142
+ const fb = new WGLFramebuffer(gl, fb_texture);
111
143
  this.gl_elems = {
112
- gl: gl, shader_manager: shader_manager, vertices: vertices, texcoords: texcoords,
144
+ gl: gl, map: map, shader_program_1: shader_program_step1, shader_manager_2: shader_manager_step2,
145
+ vertices_step1: vertices_step1, vertices_step2: vertices_step2, texcoords_step2: texcoords_step2,
146
+ framebuffer: fb
113
147
  };
114
148
  this.updateField(this.field);
115
149
  }
@@ -121,22 +155,38 @@ class Paintball extends PlotComponent {
121
155
  if (this.gl_elems === null || this.fill_texture === null)
122
156
  return;
123
157
  const gl_elems = this.gl_elems;
158
+ const fill_texture = this.fill_texture;
159
+ const map = gl_elems.map;
160
+ const viewport_width = map.getCanvas().width;
161
+ const viewport_height = map.getCanvas().height;
124
162
  const render_data = getRendererData(arg);
125
- const program = this.gl_elems.shader_manager.getShaderProgram(gl, render_data.shaderData);
126
- // Render to framebuffer
127
- program.use({ 'a_pos': gl_elems.vertices, 'a_tex_coord': gl_elems.texcoords }, { 'u_opacity': this.opts.opacity, 'u_colors': this.color_components, 'u_num_colors': this.opts.colors.length, 'u_offset': 0,
128
- ...this.gl_elems.shader_manager.getShaderUniforms(render_data) }, { 'u_fill_sampler': this.fill_texture });
129
- gl.enable(gl.BLEND);
130
- gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
131
- program.draw();
132
- if (render_data.type != 'maplibre' || !render_data.shaderData.define.includes('GLOBE')) {
133
- program.setUniforms({ 'u_offset': -2 });
134
- program.draw();
135
- program.setUniforms({ 'u_offset': -1 });
136
- program.draw();
137
- program.setUniforms({ 'u_offset': 1 });
138
- program.draw();
139
- }
163
+ const program_step1 = this.gl_elems.shader_program_1;
164
+ const program_step2 = this.gl_elems.shader_manager_2.getShaderProgram(gl, render_data.shaderData);
165
+ const samplers = Object.fromEntries([...this.fill_texture.entries()]);
166
+ this.color_components.forEach((color, icolor) => {
167
+ // Render to framebuffer to pull out an individual member from the packed field
168
+ program_step1.use({ 'a_pos': gl_elems.vertices_step1 }, { 'u_imem': icolor }, samplers);
169
+ gl_elems.framebuffer.clear([0, 0, 0, 1]);
170
+ gl_elems.framebuffer.renderTo(0, 0, this.field.grid.ni, this.field.grid.nj);
171
+ gl.enable(gl.BLEND);
172
+ gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
173
+ program_step1.draw();
174
+ // Now render the framebuffer as a filled contour
175
+ program_step2.use({ 'a_pos': gl_elems.vertices_step2, 'a_tex_coord': gl_elems.texcoords_step2 }, { 'u_opacity': this.opts.opacity, 'u_color': color, 'u_offset': 0,
176
+ ...gl_elems.shader_manager_2.getShaderUniforms(render_data) }, { 'u_fill_sampler': gl_elems.framebuffer.texture });
177
+ WGLFramebuffer.screen(gl).renderTo(0, 0, viewport_width, viewport_height);
178
+ gl.enable(gl.BLEND);
179
+ gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
180
+ program_step2.draw();
181
+ if (render_data.type != 'maplibre' || !render_data.shaderData.define.includes('GLOBE')) {
182
+ program_step2.setUniforms({ 'u_offset': -2 });
183
+ program_step2.draw();
184
+ program_step2.setUniforms({ 'u_offset': -1 });
185
+ program_step2.draw();
186
+ program_step2.setUniforms({ 'u_offset': 1 });
187
+ program_step2.draw();
188
+ }
189
+ });
140
190
  }
141
191
  }
142
192
  export default Paintball;
@@ -1,6 +1,7 @@
1
1
  import * as Comlink from 'comlink';
2
2
  import { MapLikeType } from './Map';
3
3
  import { RenderMethodArg, TypedArrayStr, WebGLAnyRenderingContext } from './AutumnTypes';
4
+ import { WorkerPool } from './WorkerPool';
4
5
  declare const layer_worker: Comlink.Remote<{
5
6
  makeBBElements: (field_lats: Float32Array, field_lons: Float32Array, min_zoom: Uint8Array, field_ni: number, field_nj: number, map_max_zoom: number) => {
6
7
  pts: Float32Array;
@@ -12,14 +13,18 @@ declare const layer_worker: Comlink.Remote<{
12
13
  };
13
14
  makePolyLines: (lines: import("./AutumnTypes").LineData[]) => import("./AutumnTypes").Polyline;
14
15
  }>;
16
+ declare function getContourWorkerPool(wasm_base_url: string | undefined, n_workers: number): WorkerPool<{
17
+ contourCreator: (data: import("./AutumnTypes").ContourableTypedArray, grid_coords: import("./grids/Grid").GridCoords, opts: import("./ContourCreator.worker").FieldContourOpts) => Promise<import("./AutumnTypes").ContourData>;
18
+ init: (wasm_base_url: string | undefined) => Promise<void>;
19
+ }>;
15
20
  /** Base class for all plot components */
16
21
  declare abstract class PlotComponent<MapType extends MapLikeType> {
17
22
  abstract onAdd(map: MapType, gl: WebGLAnyRenderingContext): Promise<void>;
18
23
  abstract render(gl: WebGLAnyRenderingContext, arg: RenderMethodArg): void;
19
24
  }
20
25
  declare function getGLFormatTypeAlignment(gl: WebGLAnyRenderingContext, array_dtype: TypedArrayStr): {
21
- format: 33321 | 33325 | 33326 | 6409;
22
- type: 36193 | 5131 | 5121 | 5126;
26
+ format: 33321 | 33325 | 33326 | 33331 | 33332 | 33333 | 33334 | 6409;
27
+ type: 36193 | 5131 | 5121 | 5122 | 5123 | 5124 | 5125 | 5126;
23
28
  row_alignment: number;
24
29
  };
25
- export { PlotComponent, layer_worker, getGLFormatTypeAlignment };
30
+ export { PlotComponent, layer_worker, getContourWorkerPool, getGLFormatTypeAlignment };