autumnplot-gl 4.0.0-beta → 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 (84) hide show
  1. package/README.md +13 -207
  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 +2 -0
  5. package/dist/983.autumnplot-gl.js.map +1 -0
  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 +38 -5
  10. package/lib/AutumnTypes.js +7 -1
  11. package/lib/Barbs.d.ts +12 -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/Color.d.ts +1 -0
  16. package/lib/Color.js +1 -0
  17. package/lib/ColorBar.d.ts +14 -0
  18. package/lib/ColorBar.js +15 -8
  19. package/lib/Colormap.d.ts +9 -1
  20. package/lib/Colormap.js +24 -1
  21. package/lib/Contour.d.ts +26 -1
  22. package/lib/Contour.js +24 -2
  23. package/lib/ContourCreator.worker.d.ts +25 -0
  24. package/lib/{ContourCreator.js → ContourCreator.worker.js} +15 -14
  25. package/lib/Fill.d.ts +31 -11
  26. package/lib/Fill.js +38 -18
  27. package/lib/Hodographs.d.ts +19 -3
  28. package/lib/Hodographs.js +45 -20
  29. package/lib/Map.d.ts +13 -1
  30. package/lib/Map.js +62 -8
  31. package/lib/Paintball.d.ts +14 -5
  32. package/lib/Paintball.js +96 -46
  33. package/lib/PlotComponent.d.ts +9 -3
  34. package/lib/PlotComponent.js +36 -1
  35. package/lib/PlotLayer.d.ts +2 -2
  36. package/lib/PlotLayer.js +2 -2
  37. package/lib/PlotLayer.worker.js +9 -3
  38. package/lib/RawField.d.ts +223 -27
  39. package/lib/RawField.js +413 -59
  40. package/lib/StationPlot.d.ts +78 -11
  41. package/lib/StationPlot.js +113 -30
  42. package/lib/TextCollection.d.ts +5 -0
  43. package/lib/TextCollection.js +82 -9
  44. package/lib/WasmInterface.d.ts +7 -0
  45. package/lib/WasmInterface.js +11 -0
  46. package/lib/WorkerPool.d.ts +8 -0
  47. package/lib/WorkerPool.js +77 -0
  48. package/lib/cpp/marchingsquares.js +127 -13
  49. package/lib/cpp/marchingsquares.wasm +0 -0
  50. package/lib/cpp/marchingsquares_embind.d.ts +16 -3
  51. package/lib/grids/AutoZoom.d.ts +21 -0
  52. package/lib/grids/AutoZoom.js +63 -0
  53. package/lib/grids/DomainBuffer.d.ts +14 -0
  54. package/lib/grids/DomainBuffer.js +16 -0
  55. package/lib/grids/Geostationary.d.ts +35 -0
  56. package/lib/grids/Geostationary.js +47 -0
  57. package/lib/grids/Grid.d.ts +36 -0
  58. package/lib/grids/Grid.js +12 -0
  59. package/lib/grids/GridCoordinates.d.ts +10 -0
  60. package/lib/grids/GridCoordinates.js +64 -0
  61. package/lib/grids/LambertGrid.d.ts +73 -0
  62. package/lib/grids/LambertGrid.js +92 -0
  63. package/lib/grids/PlateCarreeGrid.d.ts +46 -0
  64. package/lib/grids/PlateCarreeGrid.js +55 -0
  65. package/lib/grids/PlateCarreeRotatedGrid.d.ts +53 -0
  66. package/lib/grids/PlateCarreeRotatedGrid.js +65 -0
  67. package/lib/grids/RadarSweepGrid.d.ts +46 -0
  68. package/lib/grids/RadarSweepGrid.js +74 -0
  69. package/lib/grids/StructuredGrid.d.ts +49 -0
  70. package/lib/grids/StructuredGrid.js +103 -0
  71. package/lib/grids/UnstructuredGrid.d.ts +56 -0
  72. package/lib/grids/UnstructuredGrid.js +102 -0
  73. package/lib/index.d.ts +23 -6
  74. package/lib/index.js +18 -8
  75. package/lib/utils.d.ts +11 -2
  76. package/lib/utils.js +63 -1
  77. package/package.json +4 -3
  78. package/dist/110.autumnplot-gl.js +0 -2
  79. package/dist/110.autumnplot-gl.js.map +0 -1
  80. package/lib/ContourCreator.d.ts +0 -22
  81. package/lib/Grid.d.ts +0 -263
  82. package/lib/Grid.js +0 -547
  83. package/lib/ParticleTracer.d.ts +0 -19
  84. package/lib/ParticleTracer.js +0 -37
@@ -0,0 +1,25 @@
1
+ import { GridCoords } from './grids/Grid';
2
+ import { ContourData, ContourableTypedArray } from "./AutumnTypes";
3
+ declare function init(wasm_base_url: string | undefined): Promise<void>;
4
+ /** Options for contouring data via {@link RawScalarField.getContours | RawScalarField.getContours()} */
5
+ interface FieldContourOpts {
6
+ /**
7
+ * The interval at which to create contours. The field will be contoured at this interval from its minimum to its maximum.
8
+ */
9
+ interval?: number;
10
+ /**
11
+ * Contour the field at these specific levels.
12
+ */
13
+ levels?: number[];
14
+ /**
15
+ * Add triangles in the contouring, which takes longer and generates more detailed (not necessarily smoother or better) contours
16
+ */
17
+ quad_as_tri?: boolean;
18
+ }
19
+ declare function contourCreator(data: ContourableTypedArray, grid_coords: GridCoords, opts: FieldContourOpts): Promise<ContourData>;
20
+ declare const ep_interface: {
21
+ contourCreator: typeof contourCreator;
22
+ init: typeof init;
23
+ };
24
+ type ContourCreatorWorker = typeof ep_interface;
25
+ export type { ContourCreatorWorker, FieldContourOpts };
@@ -1,24 +1,25 @@
1
- import Module from './cpp/marchingsquares';
2
- import './cpp/marchingsquares.wasm';
3
- let msm_promise = null;
4
- function initMSModule() {
5
- if (msm_promise === null) {
6
- msm_promise = Module();
7
- }
8
- return msm_promise;
1
+ import * as Comlink from 'comlink';
2
+ import { initMSModule } from "./WasmInterface";
3
+ let _msm = null;
4
+ async function init(wasm_base_url) {
5
+ _msm = await initMSModule({ document_script: wasm_base_url });
9
6
  }
10
- async function contourCreator(data, grid, opts) {
7
+ async function contourCreator(data, grid_coords, opts) {
11
8
  if (opts.interval === undefined && opts.levels === undefined) {
12
9
  throw "Must supply either an interval or levels to contourCreator()";
13
10
  }
14
11
  const interval = opts.interval === undefined ? 0 : opts.interval;
15
12
  const quad_as_tri = opts.quad_as_tri === undefined ? false : opts.quad_as_tri;
16
- const msm = await initMSModule();
17
- const grid_coords = grid.getGridCoords();
13
+ const msm = _msm === null ? await initMSModule({}) : _msm;
14
+ _msm = msm;
18
15
  const getContourLevels = data instanceof Float32Array ? msm.getContourLevelsFloat32 : msm.getContourLevelsFloat16;
19
16
  const makeContours = data instanceof Float32Array ? msm.makeContoursFloat32 : msm.makeContoursFloat16;
20
- const levels = opts.levels === undefined ? getContourLevels(data, grid.ni, grid.nj, interval) : opts.levels;
21
- const contours = makeContours(data, grid_coords.x, grid_coords.y, levels, (x, y) => grid.transform(x, y, { inverse: true }), quad_as_tri);
17
+ const levels = opts.levels === undefined ? getContourLevels(data, grid_coords.x.length, grid_coords.y.length, interval) : opts.levels;
18
+ const contours = makeContours(data, grid_coords.x, grid_coords.y, levels, quad_as_tri);
22
19
  return contours;
23
20
  }
24
- export { contourCreator, initMSModule };
21
+ const ep_interface = {
22
+ 'contourCreator': contourCreator,
23
+ 'init': init,
24
+ };
25
+ Comlink.expose(ep_interface);
package/lib/Fill.d.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  import { PlotComponent } from './PlotComponent';
2
2
  import { ColorMap } from './Colormap';
3
- import { RawScalarField } from './RawField';
3
+ import { ExpressionScalarField } from './RawField';
4
4
  import { MapLikeType } from './Map';
5
5
  import { RenderMethodArg, TypedArray, WebGLAnyRenderingContext } from './AutumnTypes';
6
- import { StructuredGrid } from './Grid';
6
+ import { DomainBufferGrid } from './grids/DomainBuffer';
7
+ /** Options for {@link ContourFill} components */
7
8
  interface ContourFillOptions {
8
9
  /** The color maps to use when creating the fills */
9
10
  cmap: ColorMap | ColorMap[];
@@ -18,6 +19,7 @@ interface ContourFillOptions {
18
19
  */
19
20
  opacity?: number;
20
21
  }
22
+ /** Options for {@link Raster} components */
21
23
  interface RasterOptions {
22
24
  /** The color map to use when creating the raster plot */
23
25
  cmap: ColorMap | ColorMap[];
@@ -32,7 +34,7 @@ interface RasterOptions {
32
34
  */
33
35
  opacity?: number;
34
36
  }
35
- declare class PlotComponentFill<ArrayType extends TypedArray, GridType extends StructuredGrid, MapType extends MapLikeType> extends PlotComponent<MapType> {
37
+ declare class PlotComponentFill<ArrayType extends TypedArray, GridType extends DomainBufferGrid, MapType extends MapLikeType> extends PlotComponent<MapType> {
36
38
  private field;
37
39
  readonly opts: Required<ContourFillOptions>;
38
40
  private readonly cmap_gpu;
@@ -41,29 +43,38 @@ declare class PlotComponentFill<ArrayType extends TypedArray, GridType extends S
41
43
  private mask_texture;
42
44
  protected image_mag_filter: number | null;
43
45
  protected cmap_mag_filter: number | null;
44
- constructor(field: RawScalarField<ArrayType, GridType>, opts: ContourFillOptions);
45
- updateField(field: RawScalarField<ArrayType, GridType>, mask?: Uint8Array): Promise<void>;
46
+ constructor(field: ExpressionScalarField<ArrayType, GridType>, opts: ContourFillOptions);
47
+ updateField(field: ExpressionScalarField<ArrayType, GridType>, mask?: Uint8Array): Promise<void>;
46
48
  onAdd(map: MapType, gl: WebGLAnyRenderingContext): Promise<void>;
47
49
  render(gl: WebGLAnyRenderingContext, arg: RenderMethodArg): void;
48
50
  }
49
51
  /**
50
52
  * A raster (i.e. pixel) plot
53
+ *
54
+ * ## Grid Compatibility
55
+ * - :white_check_mark: `PlateCarreeGrid`
56
+ * - :white_check_mark: `PlateCarreeRotatedGrid`
57
+ * - :white_check_mark: `LambertGrid`
58
+ * - :x: `UnstructuredGrid`
59
+ * - :white_check_mark: `RadarSweepGrid`
60
+ * - :white_check_mark: `Geostationary`
61
+ *
51
62
  * @example
52
63
  * // Create a raster plot with the provided color map
53
64
  * const raster = new Raster(wind_speed_field, {cmap: color_map});
54
65
  */
55
- declare class Raster<ArrayType extends TypedArray, GridType extends StructuredGrid, MapType extends MapLikeType> extends PlotComponentFill<ArrayType, GridType, MapType> {
66
+ declare class Raster<ArrayType extends TypedArray, GridType extends DomainBufferGrid, MapType extends MapLikeType> extends PlotComponentFill<ArrayType, GridType, MapType> {
56
67
  /**
57
68
  * Create a raster plot
58
69
  * @param field - The field to create the raster plot from
59
70
  * @param opts - Options for creating the raster plot
60
71
  */
61
- constructor(field: RawScalarField<ArrayType, GridType>, opts: RasterOptions);
72
+ constructor(field: ExpressionScalarField<ArrayType, GridType>, opts: RasterOptions);
62
73
  /**
63
74
  * Update the data displayed as a raster plot
64
75
  * @param field - The new field to display as a raster plot
65
76
  */
66
- updateField(field: RawScalarField<ArrayType, GridType>, mask?: Uint8Array): Promise<void>;
77
+ updateField(field: ExpressionScalarField<ArrayType, GridType>, mask?: Uint8Array): Promise<void>;
67
78
  /**
68
79
  * @internal
69
80
  * Add the raster plot to a map
@@ -77,22 +88,31 @@ declare class Raster<ArrayType extends TypedArray, GridType extends StructuredGr
77
88
  }
78
89
  /**
79
90
  * A filled contoured field
91
+ *
92
+ * ## Grid Compatibility
93
+ * - :white_check_mark: `PlateCarreeGrid`
94
+ * - :white_check_mark: `PlateCarreeRotatedGrid`
95
+ * - :white_check_mark: `LambertGrid`
96
+ * - :x: `UnstructuredGrid`
97
+ * - :white_check_mark: `RadarSweepGrid`
98
+ * - :white_check_mark: `Geostationary`
99
+ *
80
100
  * @example
81
101
  * // Create a field of filled contours with the provided color map
82
102
  * const fill = new ContourFill(wind_speed_field, {cmap: color_map});
83
103
  */
84
- declare class ContourFill<ArrayType extends TypedArray, GridType extends StructuredGrid, MapType extends MapLikeType> extends PlotComponentFill<ArrayType, GridType, MapType> {
104
+ declare class ContourFill<ArrayType extends TypedArray, GridType extends DomainBufferGrid, MapType extends MapLikeType> extends PlotComponentFill<ArrayType, GridType, MapType> {
85
105
  /**
86
106
  * Create a filled contoured field
87
107
  * @param field - The field to create filled contours from
88
108
  * @param opts - Options for creating the filled contours
89
109
  */
90
- constructor(field: RawScalarField<ArrayType, GridType>, opts: ContourFillOptions);
110
+ constructor(field: ExpressionScalarField<ArrayType, GridType>, opts: ContourFillOptions);
91
111
  /**
92
112
  * Update the data displayed as filled contours
93
113
  * @param field - The new field to display as filled contours
94
114
  */
95
- updateField(field: RawScalarField<ArrayType, GridType>, mask?: Uint8Array): Promise<void>;
115
+ updateField(field: ExpressionScalarField<ArrayType, GridType>, mask?: Uint8Array): Promise<void>;
96
116
  /**
97
117
  * @internal
98
118
  * Add the filled contours to a map
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,8 @@ 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
+ /** Options for {@link Hodographs} components */
7
8
  interface HodographOptions {
8
9
  /**
9
10
  * The color of the hodograph plot background as a hex string
@@ -30,9 +31,24 @@ interface HodographOptions {
30
31
  * The colormap to use for the heights on the hodograph. Default is a yellow-blue colormap.
31
32
  */
32
33
  height_cmap?: ColorMap;
34
+ /**
35
+ * The wind speed (in kts) of the largest ring on the hodograph background
36
+ * @default 80
37
+ */
38
+ max_wind_speed_ring?: number;
33
39
  }
34
- /** A class representing a field of hodograph plots */
35
- 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> {
36
52
  private profile_field;
37
53
  readonly opts: Required<HodographOptions>;
38
54
  private gl_elems;
package/lib/Hodographs.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import { PlotComponent } from "./PlotComponent";
2
2
  import { PolylineCollection } from "./PolylineCollection";
3
3
  import { BillboardCollection } from "./BillboardCollection";
4
- import { getMinZoom, normalizeOptions } from './utils';
4
+ import { normalizeOptions } from './utils';
5
+ import { isStormRelativeWindProfile } from "./AutumnTypes";
5
6
  import { ColorMap } from "./Colormap";
6
7
  import { Color } from "./Color";
7
8
  const LINE_WIDTH_MULTIPLIER = 2.5;
8
- const BG_MAX_RING_MAG = 40;
9
9
  const HODO_BG_DIMS = {
10
10
  BB_WIDTH: 256,
11
11
  BB_HEIGHT: 256,
@@ -15,7 +15,7 @@ const HODO_BG_DIMS = {
15
15
  BB_MAG_WRAP: 1000,
16
16
  BB_MAG_BIN_SIZE: 1000,
17
17
  };
18
- function _createHodoBackgroundTexture(line_width) {
18
+ function _createHodoBackgroundTexture(line_width, arrow_head) {
19
19
  let canvas = document.createElement('canvas');
20
20
  canvas.width = HODO_BG_DIMS.BB_TEX_WIDTH;
21
21
  canvas.height = HODO_BG_DIMS.BB_TEX_HEIGHT;
@@ -29,14 +29,21 @@ function _createHodoBackgroundTexture(line_width) {
29
29
  ctx.arc(HODO_BG_DIMS.BB_TEX_WIDTH / 2, HODO_BG_DIMS.BB_TEX_WIDTH / 2, irng - line_width / 2, 0, 2 * Math.PI);
30
30
  ctx.stroke();
31
31
  }
32
- const ctr_x = HODO_BG_DIMS.BB_TEX_WIDTH / 2, ctr_y = HODO_BG_DIMS.BB_TEX_WIDTH / 2;
33
- const arrow_size = 20;
34
- ctx.beginPath();
35
- ctx.moveTo(ctr_x, ctr_y);
36
- ctx.lineTo(ctr_x + arrow_size / 2, ctr_y + arrow_size);
37
- ctx.lineTo(ctr_x - arrow_size / 2, ctr_y + arrow_size);
38
- ctx.lineTo(ctr_x, ctr_y);
39
- ctx.fill();
32
+ if (arrow_head) {
33
+ const ctr_x = HODO_BG_DIMS.BB_TEX_WIDTH / 2, ctr_y = HODO_BG_DIMS.BB_TEX_WIDTH / 2;
34
+ const arrow_size = 20;
35
+ ctx.beginPath();
36
+ ctx.moveTo(ctr_x, ctr_y);
37
+ ctx.lineTo(ctr_x + arrow_size / 2, ctr_y + arrow_size);
38
+ ctx.lineTo(ctr_x - arrow_size / 2, ctr_y + arrow_size);
39
+ ctx.lineTo(ctr_x, ctr_y);
40
+ ctx.fill();
41
+ }
42
+ else {
43
+ ctx.beginPath();
44
+ ctx.arc(HODO_BG_DIMS.BB_TEX_WIDTH / 2, HODO_BG_DIMS.BB_TEX_WIDTH / 2, line_width, 0, 2 * Math.PI);
45
+ ctx.fill();
46
+ }
40
47
  return canvas;
41
48
  }
42
49
  ;
@@ -46,9 +53,20 @@ const hodograph_opt_defaults = {
46
53
  thin_fac: 1,
47
54
  hodo_line_width: 2.5,
48
55
  background_line_width: 1.5,
49
- height_cmap: HODO_CMAP
56
+ height_cmap: HODO_CMAP,
57
+ max_wind_speed_ring: 80
50
58
  };
51
- /** 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
+ */
52
70
  class Hodographs extends PlotComponent {
53
71
  /**
54
72
  * Create a field of hodographs
@@ -59,8 +77,8 @@ class Hodographs extends PlotComponent {
59
77
  super();
60
78
  this.profile_field = profile_field;
61
79
  this.opts = normalizeOptions(opts, hodograph_opt_defaults);
62
- this.hodo_bg_texture = _createHodoBackgroundTexture(this.opts.background_line_width * LINE_WIDTH_MULTIPLIER);
63
- this.hodo_scale = (HODO_BG_DIMS.BB_TEX_WIDTH - this.opts.background_line_width / 2) / (HODO_BG_DIMS.BB_TEX_WIDTH * BG_MAX_RING_MAG);
80
+ this.hodo_bg_texture = _createHodoBackgroundTexture(this.opts.background_line_width * LINE_WIDTH_MULTIPLIER, isStormRelativeWindProfile(profile_field.profiles[0]));
81
+ this.hodo_scale = (HODO_BG_DIMS.BB_TEX_WIDTH - this.opts.background_line_width / 2) / (HODO_BG_DIMS.BB_TEX_WIDTH * this.opts.max_wind_speed_ring);
64
82
  this.bg_size = 140;
65
83
  this.gl_elems = null;
66
84
  this.line_elems = null;
@@ -78,19 +96,26 @@ class Hodographs extends PlotComponent {
78
96
  this.gl_elems.bg_billboard.updateField(field.getStormMotionGrid());
79
97
  const profiles = this.profile_field.profiles;
80
98
  const { lats, lons } = this.profile_field.getProfileCoords();
99
+ const min_visible_zoom = this.profile_field.grid.getMinVisibleZoom(this.opts.thin_fac);
81
100
  const hodo_polyline = profiles.map((prof, iprof) => {
82
- const zoom = getMinZoom(prof['jlat'], prof['ilon'], this.opts.thin_fac);
83
101
  return {
84
- 'offsets': [...prof['u']].map((u, ipt) => [u - prof['smu'], prof['v'][ipt] - prof['smv']]),
102
+ 'offsets': [...prof['u']].map((u, ipt) => {
103
+ if (isStormRelativeWindProfile(prof)) {
104
+ return [u - prof['smu'], prof['v'][ipt] - prof['smv']];
105
+ }
106
+ return [u, prof['v'][ipt]];
107
+ }),
85
108
  'vertices': [...prof['u']].map(u => [lons[iprof], lats[iprof]]),
86
- 'zoom': zoom,
109
+ 'zoom': min_visible_zoom[iprof],
87
110
  'data': [...prof['z']],
88
111
  };
89
112
  });
90
113
  const hodo_line = await PolylineCollection.make(gl, hodo_polyline, { line_width: this.opts.hodo_line_width, cmap: this.opts.height_cmap,
91
114
  offset_scale: this.hodo_scale * this.bg_size, offset_rotates_with_map: false });
92
115
  const sm_polyline = profiles.map((prof, iprof) => {
93
- const zoom = getMinZoom(prof['jlat'], prof['ilon'], this.opts.thin_fac);
116
+ if (!isStormRelativeWindProfile(prof)) {
117
+ return { vertices: [] };
118
+ }
94
119
  const sm_mag = Math.hypot(prof['smu'], prof['smv']);
95
120
  const sm_ang = Math.PI / 2 - Math.atan2(-prof['smv'], -prof['smu']);
96
121
  const buffer = 2;
@@ -98,7 +123,7 @@ class Hodographs extends PlotComponent {
98
123
  'offsets': [[buffer * Math.sin(sm_ang), buffer * Math.cos(sm_ang)],
99
124
  [sm_mag * Math.sin(sm_ang), sm_mag * Math.cos(sm_ang)]],
100
125
  'vertices': [[lons[iprof], lats[iprof]], [lons[iprof], lats[iprof]]],
101
- 'zoom': zoom
126
+ 'zoom': min_visible_zoom[iprof]
102
127
  };
103
128
  });
104
129
  const sm_line = await PolylineCollection.make(gl, sm_polyline, { line_width: this.opts.background_line_width, color: this.opts.bgcolor,
package/lib/Map.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  type StyleSpecification = {
2
2
  glyphs?: string;
3
3
  };
4
+ /** Type with the required methods for mapping libraries */
4
5
  type MapLikeType = {
5
6
  triggerRepaint: () => void;
6
7
  getCanvas: () => HTMLCanvasElement;
@@ -14,6 +15,8 @@ interface LambertConformalConicParameters {
14
15
  lon_0: number;
15
16
  lat_0: number;
16
17
  lat_std: [number, number] | number;
18
+ a: number;
19
+ b: number;
17
20
  }
18
21
  declare function lambertConformalConic(params: LambertConformalConicParameters): (a: number, b: number, opts?: {
19
22
  inverse: boolean;
@@ -26,6 +29,15 @@ interface RotateSphereParams {
26
29
  declare function rotateSphere(params: RotateSphereParams): (a: number, b: number, opts?: {
27
30
  inverse: boolean;
28
31
  }) => [number, number];
32
+ interface GeostationaryProjectionParams {
33
+ lon_0: number;
34
+ alt: number;
35
+ a: number;
36
+ b: number;
37
+ }
38
+ declare function geostationaryProjection(params: GeostationaryProjectionParams): (a: number, b: number, opts?: {
39
+ inverse: boolean;
40
+ }) => [number, number];
29
41
  /**
30
42
  * A `LngLat` object represents a given longitude and latitude coordinate, measured in degrees.
31
43
  * These coordinates are based on the [WGS84 (EPSG:4326) standard](https://en.wikipedia.org/wiki/World_Geodetic_System#WGS84).
@@ -49,5 +61,5 @@ declare class LngLat {
49
61
  };
50
62
  static fromMercatorCoord(x: number, y: number): LngLat;
51
63
  }
52
- export { LngLat, lambertConformalConic, rotateSphere };
64
+ export { LngLat, lambertConformalConic, rotateSphere, geostationaryProjection };
53
65
  export type { MapLikeType };
package/lib/Map.js CHANGED
@@ -9,8 +9,8 @@ function lambertConformalConic(params) {
9
9
  return Math.cos(lat) / Math.sqrt(1 - eccen * eccen * sin_lat * sin_lat);
10
10
  };
11
11
  // WGS 84 spheroid
12
- const semimajor = 6378137.0;
13
- const semiminor = 6356752.314245;
12
+ const semimajor = params.a;
13
+ const semiminor = params.b;
14
14
  const eccen = Math.sqrt(1 - (semiminor * semiminor) / (semimajor * semimajor));
15
15
  const radians = Math.PI / 180;
16
16
  let { lon_0, lat_0, lat_std } = params;
@@ -121,6 +121,53 @@ function rotateSphere(params) {
121
121
  return opts.inverse ? compute_rotation_inverse(a, b) : compute_rotation(a, b);
122
122
  };
123
123
  }
124
+ // Changes to the geostationaryProjection to make sure the coastlines and satellite line up
125
+ function geostationaryProjection(params) {
126
+ const radians = Math.PI / 180;
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) => {
132
+ lon *= radians;
133
+ lat *= radians;
134
+ const cos_lat = Math.cos(lat);
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));
144
+ return [x, y];
145
+ };
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)));
165
+ return [lon / radians, lat / radians];
166
+ };
167
+ return (a, b, opts) => {
168
+ return opts?.inverse ? inverse(a, b) : forward(a, b);
169
+ };
170
+ }
124
171
  function mercatorXfromLng(lng) {
125
172
  return (180 + lng) / 360;
126
173
  }
@@ -151,19 +198,26 @@ function latFromMercatorY(y) {
151
198
  class LngLat {
152
199
  constructor(lng, lat) {
153
200
  if (isNaN(lng) || isNaN(lat)) {
154
- throw new Error(`Invalid LngLat object: (${lng}, ${lat})`);
201
+ this.lng = NaN;
202
+ this.lat = NaN;
155
203
  }
156
- this.lng = +lng;
157
- this.lat = +lat;
158
- if (this.lat > 90 || this.lat < -90) {
159
- 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
+ }
160
210
  }
161
211
  }
162
212
  toMercatorCoord() {
213
+ if (isNaN(this.lng) || isNaN(this.lng))
214
+ return { x: NaN, y: NaN };
163
215
  return { x: mercatorXfromLng(this.lng), y: mercatorYfromLat(this.lat) };
164
216
  }
165
217
  static fromMercatorCoord(x, y) {
218
+ if (isNaN(x) || isNaN(y))
219
+ return new LngLat(NaN, NaN);
166
220
  return new LngLat(lngFromMercatorX(x), latFromMercatorY(y));
167
221
  }
168
222
  }
169
- export { LngLat, lambertConformalConic, rotateSphere };
223
+ export { LngLat, lambertConformalConic, rotateSphere, geostationaryProjection };
@@ -1,8 +1,9 @@
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
+ /** Options for {@link Paintball} components */
6
7
  interface PaintballOptions {
7
8
  /**
8
9
  * The list of colors (as hex strings) to use for each member in the paintball plot. The first color corresponds to member 1, the second to member 2, etc.
@@ -20,8 +21,16 @@ interface PaintballOptions {
20
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
21
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
22
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`
23
32
  */
24
- 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> {
25
34
  private field;
26
35
  readonly opts: Required<PaintballOptions>;
27
36
  private readonly color_components;
@@ -34,12 +43,12 @@ declare class Paintball<ArrayType extends TypedArray, GridType extends Structure
34
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.
35
44
  * @param opts - Options for creating the paintball plot
36
45
  */
37
- constructor(field: RawScalarField<ArrayType, GridType>, opts?: PaintballOptions);
46
+ constructor(field: ExpressionScalarField<ArrayType, GridType>, opts?: PaintballOptions);
38
47
  /**
39
48
  * Update the field displayed as a paintball plot
40
49
  * @param field - The new field to display as a paintball plot
41
50
  */
42
- updateField(field: RawScalarField<ArrayType, GridType>): Promise<void>;
51
+ updateField(field: ExpressionScalarField<ArrayType, GridType>): Promise<void>;
43
52
  /**
44
53
  * @internal
45
54
  * Add the paintball plot to a map.