autumnplot-gl 4.0.0-beta → 4.0.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.
Binary file
@@ -1,13 +1,37 @@
1
1
  import { Float16Array } from "@petamoriken/float16";
2
- interface WindProfile {
2
+ /** A wind profile with a storm-motion for plotting storm-relative hodographs */
3
+ interface StormRelativeWindProfile {
4
+ /** The grid index in the j direction (ignored for unstructured grids) */
3
5
  jlat: number;
6
+ /** The grid index in the i direction */
4
7
  ilon: number;
8
+ /** u component of storm motion in kts */
5
9
  smu: number;
10
+ /** v component of storm motion in kts */
6
11
  smv: number;
12
+ /** Ground-relative u winds in kts (will be converted to storm-relative during plotting) */
7
13
  u: Float32Array;
14
+ /** Ground-relative v winds in kts (will be converted to storm-relative during plotting) */
8
15
  v: Float32Array;
16
+ /** Height of each data point in km */
9
17
  z: Float32Array;
10
18
  }
19
+ /** A wind profile without a storm motion for plotting ground-relative hodographs */
20
+ interface GroundRelativeWindProfile {
21
+ /** The grid index in the j direction (ignored for unstructured grids) */
22
+ jlat: number;
23
+ /** The grid index in the i direction */
24
+ ilon: number;
25
+ /** Ground-relative u winds in kts */
26
+ u: Float32Array;
27
+ /** Ground-relative v winds in kts */
28
+ v: Float32Array;
29
+ /** Height of each data point in km */
30
+ z: Float32Array;
31
+ }
32
+ /** Different types of wind profiles for {@link Hodographs} components */
33
+ type WindProfile = StormRelativeWindProfile | GroundRelativeWindProfile;
34
+ declare function isStormRelativeWindProfile(obj: any): obj is StormRelativeWindProfile;
11
35
  interface BillboardSpec {
12
36
  BB_WIDTH: number;
13
37
  BB_HEIGHT: number;
@@ -30,10 +54,17 @@ type Polyline = {
30
54
  data?: Float32Array;
31
55
  zoom?: Float32Array;
32
56
  };
57
+ /** WebGL rendering contexts (either WebGL1 or WebGL2) */
33
58
  type WebGLAnyRenderingContext = WebGLRenderingContext | WebGL2RenderingContext;
34
59
  declare function isWebGL2Ctx(gl: WebGLAnyRenderingContext): gl is WebGL2RenderingContext;
60
+ /** Javascript typed arrays for use in raw fields */
35
61
  type TypedArray = Float16Array | Float32Array | Uint8Array;
36
62
  type TypedArrayStr = 'float16' | 'float32' | 'uint8';
63
+ /**
64
+ * The result of contouring a field
65
+ *
66
+ * Each property is a different contour level giving an array of contours, and each contour is an array of [longitude, latitude].
67
+ */
37
68
  type ContourData = Record<number, [number, number][][]>;
38
69
  type mat4 = number[] | Float32Array | Float64Array;
39
70
  type RenderShaderData = {
@@ -84,5 +115,5 @@ type RendererDataAutumn = {
84
115
  };
85
116
  type RendererData = RendererDataMapLibre | RendererDataAutumn;
86
117
  declare function getRendererData(arg: RenderMethodArg): RendererData;
87
- export { isWebGL2Ctx, getRendererData };
88
- export type { WindProfile, BillboardSpec, Polyline, LineData, WebGLAnyRenderingContext, TypedArray, TypedArrayStr, ContourData, RenderMethodArg, RendererData, RenderShaderData };
118
+ export { isWebGL2Ctx, getRendererData, isStormRelativeWindProfile };
119
+ export type { WindProfile, StormRelativeWindProfile, GroundRelativeWindProfile, BillboardSpec, Polyline, LineData, WebGLAnyRenderingContext, TypedArray, TypedArrayStr, ContourData, RenderMethodArg, RendererData, RenderShaderData };
@@ -1,3 +1,6 @@
1
+ function isStormRelativeWindProfile(obj) {
2
+ return 'smu' in obj && 'smv' in obj;
3
+ }
1
4
  function isWebGL2Ctx(gl) {
2
5
  return gl.getParameter(gl.VERSION).includes('WebGL 2.0');
3
6
  }
@@ -25,4 +28,4 @@ function getRendererData(arg) {
25
28
  }
26
29
  return { type: 'autumn', mainMatrix: [...arg], shaderData: null };
27
30
  }
28
- export { isWebGL2Ctx, getRendererData };
31
+ export { isWebGL2Ctx, getRendererData, isStormRelativeWindProfile };
package/lib/Barbs.d.ts CHANGED
@@ -4,6 +4,7 @@ import { MapLikeType } from "./Map";
4
4
  import { RenderMethodArg, TypedArray, WebGLAnyRenderingContext } from "./AutumnTypes";
5
5
  import { ColorMap } from "./Colormap";
6
6
  import { Grid } from "./Grid";
7
+ /** Options for {@link Barbs} components */
7
8
  interface BarbsOptions {
8
9
  /**
9
10
  * The color to use for the barbs as a hex color string;.
package/lib/Color.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ /** A class for handling colors and translations between different color spaces */
1
2
  declare class Color {
2
3
  private rgba;
3
4
  /**
package/lib/Color.js CHANGED
@@ -78,6 +78,7 @@ const hsv2rgb = (hsv) => {
78
78
  }
79
79
  return [r_prime + m, g_prime + m, b_prime + m];
80
80
  };
81
+ /** A class for handling colors and translations between different color spaces */
81
82
  class Color {
82
83
  /**
83
84
  * Create a new color object
package/lib/ColorBar.d.ts CHANGED
@@ -1,7 +1,10 @@
1
1
  import { ColorMap } from "./Colormap";
2
2
  import { Color } from "./Color";
3
+ /** The orientation for color bars (horizontal or vertical) */
3
4
  type ColorbarOrientation = 'horizontal' | 'vertical';
5
+ /** Which side of a color bar the ticks are on */
4
6
  type ColorbarTickDirection = 'top' | 'bottom' | 'left' | 'right';
7
+ /** Options for {@link ColorBar}s */
5
8
  interface ColorBarOptions {
6
9
  /** The label to place along the color bar */
7
10
  label?: string;
@@ -41,6 +44,11 @@ interface ColorBarOptions {
41
44
  * @default 12
42
45
  */
43
46
  ticklabelsize?: number;
47
+ /**
48
+ * The color for the color bar outline and the text
49
+ * @default '#000000'
50
+ */
51
+ outline_and_text_color?: string;
44
52
  }
45
53
  /**
46
54
  * Make an SVG containing a color bar. The color bar can either be oriented horizontal or vertical, and a label can be provided.
@@ -56,6 +64,7 @@ interface ColorBarOptions {
56
64
  * document.getElementById('colorbar-container').appendChild(svg);
57
65
  */
58
66
  declare function makeColorBar(colormap: ColorMap, opts: ColorBarOptions): SVGElement;
67
+ /** Options for {@link makePaintballKey | makePaintballKey()} */
59
68
  interface PaintballKeyOptions {
60
69
  /**
61
70
  * The number of columns of entries in the key
package/lib/ColorBar.js CHANGED
@@ -32,6 +32,7 @@ function makeColorBar(colormap, opts) {
32
32
  const tickfontsize = opts.ticklabelsize || 12;
33
33
  const size_long = opts.size_long || 600;
34
34
  const size_short = opts.size_short || size_long / 9;
35
+ const outline_and_text_color = opts.outline_and_text_color || '#000000';
35
36
  const tick_dir = opts.tick_direction || (orientation == 'vertical' ? 'left' : 'bottom');
36
37
  if (orientation == 'vertical' && (tick_dir == 'top' || tick_dir == 'bottom') ||
37
38
  orientation == 'horizontal' && (tick_dir == 'left' || tick_dir == 'right')) {
@@ -143,7 +144,7 @@ function makeColorBar(colormap, opts) {
143
144
  else {
144
145
  lineattrs = tick_dir == 'bottom' ? { y2: 6 } : { y2: -6 };
145
146
  }
146
- createElement('line', { ...lineattrs, stroke: '#000000', 'stroke-width': 1.5 }, gtick);
147
+ createElement('line', { ...lineattrs, stroke: outline_and_text_color, 'stroke-width': 1.5 }, gtick);
147
148
  let textattrs;
148
149
  if (orientation == 'vertical') {
149
150
  textattrs = tick_dir == 'left' ? { x: -9, dy: '0.32em' } : { x: 9, dy: '0.32em' };
@@ -151,7 +152,7 @@ function makeColorBar(colormap, opts) {
151
152
  else {
152
153
  textattrs = tick_dir == 'bottom' ? { y: 9, dy: '0.8em' } : { y: -9, dy: '0em' };
153
154
  }
154
- const text = createElement('text', { ...textattrs, fill: '#000000', style: `font-family: ${fontface}; font-size: ${tickfontsize}pt` }, gtick);
155
+ const text = createElement('text', { ...textattrs, fill: outline_and_text_color, style: `font-family: ${fontface}; font-size: ${tickfontsize}pt` }, gtick);
155
156
  text.textContent = level.toString();
156
157
  });
157
158
  // Draw the outline
@@ -166,7 +167,7 @@ function makeColorBar(colormap, opts) {
166
167
  }
167
168
  const outline_attrs = {
168
169
  points: point_list,
169
- stroke: '#000000',
170
+ stroke: outline_and_text_color,
170
171
  'stroke-width': 1.5,
171
172
  fill: 'none'
172
173
  };
@@ -179,7 +180,7 @@ function makeColorBar(colormap, opts) {
179
180
  else {
180
181
  labelattrs = tick_dir == 'bottom' ? { transform: `translate(${width / 2}, ${height - 5})` } : { transform: `translate(${width / 2}, 15)` };
181
182
  }
182
- const label_elem = createElement('text', { ...labelattrs, fill: '#000000', 'text-anchor': 'middle', style: `font-family: ${fontface};` }, root);
183
+ const label_elem = createElement('text', { ...labelattrs, fill: outline_and_text_color, 'text-anchor': 'middle', style: `font-family: ${fontface};` }, root);
183
184
  label_elem.textContent = label;
184
185
  return root;
185
186
  }
package/lib/Colormap.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { WGLProgram, WGLTexture } from "autumn-wgl";
2
2
  import { WebGLAnyRenderingContext } from "./AutumnTypes";
3
3
  import { Color } from "./Color";
4
+ /** Options for {@link ColorMap}s */
4
5
  interface ColorMapOptions {
5
6
  /** The color to use for areas where the value is above the highest value in the color map */
6
7
  overflow_color?: Color | string;
package/lib/Contour.d.ts CHANGED
@@ -5,6 +5,7 @@ import { RawScalarField } from './RawField';
5
5
  import { LineStyle } from './PolylineCollection';
6
6
  import { ColorMap } from './Colormap';
7
7
  import { StructuredGrid } from './Grid';
8
+ /** Options for {@link Contour} components */
8
9
  interface ContourOptions {
9
10
  /**
10
11
  * The color of the contours as a hex color string
@@ -80,6 +81,7 @@ declare class Contour<ArrayType extends TypedArray, GridType extends StructuredG
80
81
  */
81
82
  render(gl: WebGLAnyRenderingContext, arg: RenderMethodArg): void;
82
83
  }
84
+ /** Options for {@link ContourLabels} */
83
85
  interface ContourLabelOptions {
84
86
  /**
85
87
  * Number of decimal places to use in the contour labels
@@ -121,6 +123,14 @@ interface ContourLabelOptions {
121
123
  */
122
124
  density?: number;
123
125
  }
126
+ /**
127
+ * Label the contours on a plot
128
+ * @example
129
+ * // Contour some data
130
+ * const contours = new Contour(height_field, {color: '#000000', interval: 30});
131
+ * // Label the contours
132
+ * const labels = new ContourLabels(contours, {text_color: '#ffffff', halo: true});
133
+ */
124
134
  declare class ContourLabels<ArrayType extends TypedArray, GridType extends StructuredGrid, MapType extends MapLikeType> extends PlotComponent<MapType> {
125
135
  private readonly contours;
126
136
  private gl_elems;
package/lib/Contour.js CHANGED
@@ -126,6 +126,14 @@ const contour_label_opt_defaults = {
126
126
  halo: false,
127
127
  density: 1
128
128
  };
129
+ /**
130
+ * Label the contours on a plot
131
+ * @example
132
+ * // Contour some data
133
+ * const contours = new Contour(height_field, {color: '#000000', interval: 30});
134
+ * // Label the contours
135
+ * const labels = new ContourLabels(contours, {text_color: '#ffffff', halo: true});
136
+ */
129
137
  class ContourLabels extends PlotComponent {
130
138
  constructor(contours, opts) {
131
139
  super();
@@ -175,6 +183,8 @@ class ContourLabels extends PlotComponent {
175
183
  let n_labels_placed = 0;
176
184
  for (let idist = 1; idist < dist.length; idist++) {
177
185
  const target_dist = contour_label_spacing * (n_labels_placed + (icntr / 2) % 1);
186
+ // This works fine when contour_label_spacing > the spacing between points along a contour, but when you allow the map to zoom in
187
+ // (and therefore contour_label_spacing gets small), dist[idist] outruns target_dist, so it doesn't put any more labels after the first.
178
188
  if (dist[idist - 1] <= target_dist && target_dist < dist[idist]) {
179
189
  const pt1 = contour[idist - 1];
180
190
  const pt2 = contour[idist];
@@ -2,7 +2,11 @@ import { MarchingSquaresModule } from './cpp/marchingsquares';
2
2
  import './cpp/marchingsquares.wasm';
3
3
  import { Grid } from "./Grid";
4
4
  import { ContourData, TypedArray } from "./AutumnTypes";
5
- declare function initMSModule(): Promise<MarchingSquaresModule>;
5
+ interface InitMSModuleOpts {
6
+ document_script?: string;
7
+ }
8
+ declare function initMSModule(opts: InitMSModuleOpts): Promise<MarchingSquaresModule>;
9
+ /** Options for contouring data via {@link RawScalarField.getContours | RawScalarField.getContours()} */
6
10
  interface FieldContourOpts {
7
11
  /**
8
12
  * The interval at which to create contours. The field will be contoured at this interval from its minimum to its maximum.
@@ -1,9 +1,9 @@
1
1
  import Module from './cpp/marchingsquares';
2
2
  import './cpp/marchingsquares.wasm';
3
3
  let msm_promise = null;
4
- function initMSModule() {
4
+ function initMSModule(opts) {
5
5
  if (msm_promise === null) {
6
- msm_promise = Module();
6
+ msm_promise = Module({ 'locateFile': (fname, dir) => (opts.document_script === undefined ? dir : opts.document_script) + fname });
7
7
  }
8
8
  return msm_promise;
9
9
  }
@@ -13,7 +13,7 @@ async function contourCreator(data, grid, opts) {
13
13
  }
14
14
  const interval = opts.interval === undefined ? 0 : opts.interval;
15
15
  const quad_as_tri = opts.quad_as_tri === undefined ? false : opts.quad_as_tri;
16
- const msm = await initMSModule();
16
+ const msm = await initMSModule({});
17
17
  const grid_coords = grid.getGridCoords();
18
18
  const getContourLevels = data instanceof Float32Array ? msm.getContourLevelsFloat32 : msm.getContourLevelsFloat16;
19
19
  const makeContours = data instanceof Float32Array ? msm.makeContoursFloat32 : msm.makeContoursFloat16;
package/lib/Fill.d.ts CHANGED
@@ -4,6 +4,7 @@ import { RawScalarField } from './RawField';
4
4
  import { MapLikeType } from './Map';
5
5
  import { RenderMethodArg, TypedArray, WebGLAnyRenderingContext } from './AutumnTypes';
6
6
  import { StructuredGrid } from './Grid';
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[];
package/lib/Grid.d.ts CHANGED
@@ -9,6 +9,7 @@ interface GridCoords {
9
9
  y: Float32Array;
10
10
  }
11
11
  type GridType = 'latlon' | 'latlonrot' | 'lcc' | 'unstructured';
12
+ /** The base class for grid types */
12
13
  declare abstract class Grid {
13
14
  readonly type: GridType;
14
15
  readonly ni: number;
@@ -165,6 +166,8 @@ declare class LambertGrid extends StructuredGrid {
165
166
  readonly ll_y: number;
166
167
  readonly ur_x: number;
167
168
  readonly ur_y: number;
169
+ readonly a: number;
170
+ readonly b: number;
168
171
  private readonly lcc;
169
172
  private readonly ll_cache;
170
173
  private readonly gc_cache;
@@ -179,8 +182,10 @@ declare class LambertGrid extends StructuredGrid {
179
182
  * @param ll_y - The y coordinate in projection space of the lower-left corner of the grid
180
183
  * @param ur_x - The x coordinate in projection space of the upper-right corner of the grid
181
184
  * @param ur_y - The y coordinate in projection space of the upper-right corner of the grid
185
+ * @param a - The semimajor axis of the assumed shape of Earth in meters
186
+ * @param b - The semiminor axis of the assumed shape of Earth in meters
182
187
  */
183
- constructor(ni: number, nj: number, lon_0: number, lat_0: number, lat_std: [number, number], ll_x: number, ll_y: number, ur_x: number, ur_y: number, thin_x?: number, thin_y?: number);
188
+ constructor(ni: number, nj: number, lon_0: number, lat_0: number, lat_std: [number, number], ll_x: number, ll_y: number, ur_x: number, ur_y: number, a?: number, b?: number, thin_x?: number, thin_y?: number);
184
189
  /**
185
190
  * Create a Lambert conformal conic grid from the lower-left grid point coordinate and a dx and dy.
186
191
  * @param ni - The number of grid points in the i (longitude) direction
@@ -192,9 +197,11 @@ declare class LambertGrid extends StructuredGrid {
192
197
  * @param ll_lat - The latitude of the lower-left corner of the grid
193
198
  * @param dx - The grid dx in meters
194
199
  * @param dy - The grid dy in meters
200
+ * @param a - The semimajor axis of the assumed shape of Earth in meters
201
+ * @param b - The semiminor axis of the assumed shape of Earth in meters
195
202
  * @returns
196
203
  */
197
- static fromLLCornerLonLat(ni: number, nj: number, lon_0: number, lat_0: number, lat_std: [number, number], ll_lon: number, ll_lat: number, dx: number, dy: number): LambertGrid;
204
+ static fromLLCornerLonLat(ni: number, nj: number, lon_0: number, lat_0: number, lat_std: [number, number], ll_lon: number, ll_lat: number, dx: number, dy: number, a?: number, b?: number): LambertGrid;
198
205
  /** @internal */
199
206
  copy(opts?: {
200
207
  ni?: number;
@@ -218,7 +225,7 @@ declare class LambertGrid extends StructuredGrid {
218
225
  /** @internal */
219
226
  getThinnedGrid(thin_fac: number, map_max_zoom: number): LambertGrid;
220
227
  }
221
- /** An unstructured grid */
228
+ /** An unstructured grid defined by a list of latitudes and longitudes */
222
229
  declare class UnstructuredGrid extends Grid {
223
230
  readonly coords: {
224
231
  lon: number;
package/lib/Grid.js CHANGED
@@ -4,6 +4,8 @@ import { LngLat, lambertConformalConic, rotateSphere } from "./Map";
4
4
  import { getGLFormatTypeAlignment, layer_worker } from "./PlotComponent";
5
5
  import { Cache, getArrayConstructor, getMinZoom } from "./utils";
6
6
  import { kdTree } from "kd-tree-javascript";
7
+ const WGS84_SEMIMAJOR = 6378137.0;
8
+ const WGS84_SEMIMINOR = 6356752.314245;
7
9
  function argMin(ary) {
8
10
  if (ary.length === 0) {
9
11
  return -1;
@@ -60,6 +62,7 @@ function makeVectorRotationTexture(gl, grid, data_are_earth_relative) {
60
62
  const rot_tex = new WGLTexture(gl, rot_img);
61
63
  return { 'rotation': rot_tex };
62
64
  }
65
+ /** The base class for grid types */
63
66
  class Grid {
64
67
  constructor(type, is_conformal, ni, nj) {
65
68
  this.type = type;
@@ -354,8 +357,10 @@ class LambertGrid extends StructuredGrid {
354
357
  * @param ll_y - The y coordinate in projection space of the lower-left corner of the grid
355
358
  * @param ur_x - The x coordinate in projection space of the upper-right corner of the grid
356
359
  * @param ur_y - The y coordinate in projection space of the upper-right corner of the grid
360
+ * @param a - The semimajor axis of the assumed shape of Earth in meters
361
+ * @param b - The semiminor axis of the assumed shape of Earth in meters
357
362
  */
358
- constructor(ni, nj, lon_0, lat_0, lat_std, ll_x, ll_y, ur_x, ur_y, thin_x, thin_y) {
363
+ constructor(ni, nj, lon_0, lat_0, lat_std, ll_x, ll_y, ur_x, ur_y, a, b, thin_x, thin_y) {
359
364
  super('lcc', true, ni, nj, thin_x, thin_y);
360
365
  this.lon_0 = lon_0;
361
366
  this.lat_0 = lat_0;
@@ -364,7 +369,9 @@ class LambertGrid extends StructuredGrid {
364
369
  this.ll_y = ll_y;
365
370
  this.ur_x = ur_x;
366
371
  this.ur_y = ur_y;
367
- this.lcc = lambertConformalConic({ lon_0: lon_0, lat_0: lat_0, lat_std: lat_std });
372
+ this.a = a === undefined ? WGS84_SEMIMAJOR : a;
373
+ this.b = b === undefined ? WGS84_SEMIMINOR : b;
374
+ this.lcc = lambertConformalConic({ lon_0: lon_0, lat_0: lat_0, lat_std: lat_std, a: this.a, b: this.b });
368
375
  const dx = (this.ur_x - this.ll_x) / this.ni;
369
376
  const dy = (this.ur_y - this.ll_y) / this.nj;
370
377
  this.ll_cache = new Cache((ni, nj) => {
@@ -407,12 +414,16 @@ class LambertGrid extends StructuredGrid {
407
414
  * @param ll_lat - The latitude of the lower-left corner of the grid
408
415
  * @param dx - The grid dx in meters
409
416
  * @param dy - The grid dy in meters
417
+ * @param a - The semimajor axis of the assumed shape of Earth in meters
418
+ * @param b - The semiminor axis of the assumed shape of Earth in meters
410
419
  * @returns
411
420
  */
412
- static fromLLCornerLonLat(ni, nj, lon_0, lat_0, lat_std, ll_lon, ll_lat, dx, dy) {
413
- const lcc = lambertConformalConic({ lon_0: lon_0, lat_0: lat_0, lat_std: lat_std });
421
+ static fromLLCornerLonLat(ni, nj, lon_0, lat_0, lat_std, ll_lon, ll_lat, dx, dy, a, b) {
422
+ a = a === undefined ? WGS84_SEMIMAJOR : a;
423
+ b = b === undefined ? WGS84_SEMIMINOR : b;
424
+ const lcc = lambertConformalConic({ lon_0: lon_0, lat_0: lat_0, lat_std: lat_std, a: a, b: b });
414
425
  const [ll_x, ll_y] = lcc(ll_lon, ll_lat);
415
- return new LambertGrid(ni, nj, lon_0, lat_0, lat_std, ll_x, ll_y, ll_x + ni * dx, ll_y + nj * dy);
426
+ return new LambertGrid(ni, nj, lon_0, lat_0, lat_std, ll_x, ll_y, ll_x + ni * dx, ll_y + nj * dy, a, b);
416
427
  }
417
428
  /** @internal */
418
429
  copy(opts) {
@@ -423,7 +434,7 @@ class LambertGrid extends StructuredGrid {
423
434
  const ll_y = opts.ll_y !== undefined ? opts.ll_y : this.ll_y;
424
435
  const ur_x = opts.ur_x !== undefined ? opts.ur_x : this.ur_x;
425
436
  const ur_y = opts.ur_y !== undefined ? opts.ur_y : this.ur_y;
426
- return new LambertGrid(ni, nj, this.lon_0, this.lat_0, this.lat_std, ll_x, ll_y, ur_x, ur_y);
437
+ return new LambertGrid(ni, nj, this.lon_0, this.lat_0, this.lat_std, ll_x, ll_y, ur_x, ur_y, this.a, this.b);
427
438
  }
428
439
  /**
429
440
  * @internal
@@ -457,10 +468,10 @@ class LambertGrid extends StructuredGrid {
457
468
  const ll_y = this.ll_y;
458
469
  const ur_x = this.ur_x - ni_remove * dx;
459
470
  const ur_y = this.ur_y - nj_remove * dy;
460
- return new LambertGrid(ni, nj, this.lon_0, this.lat_0, this.lat_std, ll_x, ll_y, ur_x, ur_y, this.thin_x * thin_x, this.thin_y * thin_y);
471
+ return new LambertGrid(ni, nj, this.lon_0, this.lat_0, this.lat_std, ll_x, ll_y, ur_x, ur_y, this.a, this.b, this.thin_x * thin_x, this.thin_y * thin_y);
461
472
  }
462
473
  }
463
- /** An unstructured grid */
474
+ /** An unstructured grid defined by a list of latitudes and longitudes */
464
475
  class UnstructuredGrid extends Grid {
465
476
  /**
466
477
  * Create an unstructured grid
@@ -544,4 +555,46 @@ class UnstructuredGrid extends Grid {
544
555
  return { sample: ary[idx], sample_lon: this.coords[idx].lon, sample_lat: this.coords[idx].lat };
545
556
  }
546
557
  }
558
+ // class GeostationaryImage extends Grid {
559
+ // public readonly satellite_lon: number;
560
+ // public readonly satellite_lat: number;
561
+ // private readonly vpp: (a: number, b: number, opts?: {inverse: boolean}) => [number, number];
562
+ // constructor(ni: number, nj: number, satellite_lon: number, satellite_lat: number) {
563
+ // super('geostationary', false, ni, nj);
564
+ // this.satellite_lon = satellite_lon;
565
+ // this.satellite_lat = satellite_lat;
566
+ // this.vpp = verticalPerspective({lon_0: satellite_lon, lat_0: satellite_lat, alt: 35786000.});
567
+ // }
568
+ // public getEarthCoords(): EarthCoords {}
569
+ // public getGridCoords(): GridCoords {}
570
+ // public transform(x: number, y: number, opts?: {inverse?: boolean}): [number, number] {
571
+ // opts = opts === undefined ? {}: opts;
572
+ // const inverse = opts.inverse === undefined ? false : opts.inverse;
573
+ // return this.vpp(x, y, {inverse: inverse});
574
+ // }
575
+ // public sampleNearestGridPoint(lon: number, lat: number, ary: TypedArray): {sample: number, sample_lon: number, sample_lat: number} {
576
+ // const [x, y] = this.transform(lon, lat);
577
+ // const {x: xs, y: ys} = this.getGridCoords();
578
+ // const ll_x = xs[0];
579
+ // const ur_x = xs[xs.length - 1];
580
+ // const dx = xs[1] - xs[0];
581
+ // const ll_y = ys[0];
582
+ // const ur_y = ys[ys.length - 1];
583
+ // const dy = ys[1] - ys[0];
584
+ // if (x < ll_x - 0.5 * dx || x > ur_x + 0.5 * dx || y < ll_y - 0.5 * dy || y > ur_y + 0.5 * dy) {
585
+ // return {sample: NaN, sample_lon: NaN, sample_lat: NaN};
586
+ // }
587
+ // const i_min = argMin(xs.map(xv => Math.abs(xv - x)));
588
+ // const j_min = argMin(ys.map(yv => Math.abs(yv - y)));
589
+ // const idx = i_min + j_min * this.ni;
590
+ // const [lon_min, lat_min] = this.transform(xs[i_min], ys[j_min], {inverse: true});
591
+ // return {sample: ary[idx], sample_lon: lon_min, sample_lat: lat_min};
592
+ // }
593
+ // public getThinnedGrid(thin_fac: number, map_max_zoom: number): Grid {}
594
+ // public thinDataArray<ArrayType extends TypedArray>(original_grid: Grid, ary: ArrayType): ArrayType {}
595
+ // public getMinVisibleZoom(thin_fac: number): Uint8Array {}
596
+ // public copy() {
597
+ // return new GeostationaryImage(this.ni, this.nj, this.satellite_lon, this.satellite_lat);
598
+ // }
599
+ // }
547
600
  export { Grid, StructuredGrid, PlateCarreeGrid, PlateCarreeRotatedGrid, LambertGrid, UnstructuredGrid };
@@ -4,6 +4,7 @@ import { RawProfileField } from "./RawField";
4
4
  import { RenderMethodArg, WebGLAnyRenderingContext } from "./AutumnTypes";
5
5
  import { ColorMap } from "./Colormap";
6
6
  import { Grid } from "./Grid";
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,6 +31,11 @@ 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
40
  /** A class representing a field of hodograph plots */
35
41
  declare class Hodographs<GridType extends Grid, MapType extends MapLikeType> extends PlotComponent<MapType> {
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,7 +53,8 @@ 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
59
  /** A class representing a field of hodograph plots */
52
60
  class Hodographs extends PlotComponent {
@@ -59,8 +67,8 @@ class Hodographs extends PlotComponent {
59
67
  super();
60
68
  this.profile_field = profile_field;
61
69
  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);
70
+ this.hodo_bg_texture = _createHodoBackgroundTexture(this.opts.background_line_width * LINE_WIDTH_MULTIPLIER, isStormRelativeWindProfile(profile_field.profiles[0]));
71
+ 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
72
  this.bg_size = 140;
65
73
  this.gl_elems = null;
66
74
  this.line_elems = null;
@@ -78,19 +86,26 @@ class Hodographs extends PlotComponent {
78
86
  this.gl_elems.bg_billboard.updateField(field.getStormMotionGrid());
79
87
  const profiles = this.profile_field.profiles;
80
88
  const { lats, lons } = this.profile_field.getProfileCoords();
89
+ const min_visible_zoom = this.profile_field.grid.getMinVisibleZoom(this.opts.thin_fac);
81
90
  const hodo_polyline = profiles.map((prof, iprof) => {
82
- const zoom = getMinZoom(prof['jlat'], prof['ilon'], this.opts.thin_fac);
83
91
  return {
84
- 'offsets': [...prof['u']].map((u, ipt) => [u - prof['smu'], prof['v'][ipt] - prof['smv']]),
92
+ 'offsets': [...prof['u']].map((u, ipt) => {
93
+ if (isStormRelativeWindProfile(prof)) {
94
+ return [u - prof['smu'], prof['v'][ipt] - prof['smv']];
95
+ }
96
+ return [u, prof['v'][ipt]];
97
+ }),
85
98
  'vertices': [...prof['u']].map(u => [lons[iprof], lats[iprof]]),
86
- 'zoom': zoom,
99
+ 'zoom': min_visible_zoom[iprof],
87
100
  'data': [...prof['z']],
88
101
  };
89
102
  });
90
103
  const hodo_line = await PolylineCollection.make(gl, hodo_polyline, { line_width: this.opts.hodo_line_width, cmap: this.opts.height_cmap,
91
104
  offset_scale: this.hodo_scale * this.bg_size, offset_rotates_with_map: false });
92
105
  const sm_polyline = profiles.map((prof, iprof) => {
93
- const zoom = getMinZoom(prof['jlat'], prof['ilon'], this.opts.thin_fac);
106
+ if (!isStormRelativeWindProfile(prof)) {
107
+ return { vertices: [] };
108
+ }
94
109
  const sm_mag = Math.hypot(prof['smu'], prof['smv']);
95
110
  const sm_ang = Math.PI / 2 - Math.atan2(-prof['smv'], -prof['smu']);
96
111
  const buffer = 2;
@@ -98,7 +113,7 @@ class Hodographs extends PlotComponent {
98
113
  'offsets': [[buffer * Math.sin(sm_ang), buffer * Math.cos(sm_ang)],
99
114
  [sm_mag * Math.sin(sm_ang), sm_mag * Math.cos(sm_ang)]],
100
115
  'vertices': [[lons[iprof], lats[iprof]], [lons[iprof], lats[iprof]]],
101
- 'zoom': zoom
116
+ 'zoom': min_visible_zoom[iprof]
102
117
  };
103
118
  });
104
119
  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,16 @@ interface RotateSphereParams {
26
29
  declare function rotateSphere(params: RotateSphereParams): (a: number, b: number, opts?: {
27
30
  inverse: boolean;
28
31
  }) => [number, number];
32
+ interface VerticalPerspectiveParams {
33
+ lat_0: number;
34
+ lon_0: number;
35
+ alt: number;
36
+ a: number;
37
+ b: number;
38
+ }
39
+ declare function verticalPerspective(params: VerticalPerspectiveParams): (a: number, b: number, opts?: {
40
+ inverse: boolean;
41
+ }) => [number, number];
29
42
  /**
30
43
  * A `LngLat` object represents a given longitude and latitude coordinate, measured in degrees.
31
44
  * These coordinates are based on the [WGS84 (EPSG:4326) standard](https://en.wikipedia.org/wiki/World_Geodetic_System#WGS84).
@@ -49,5 +62,5 @@ declare class LngLat {
49
62
  };
50
63
  static fromMercatorCoord(x: number, y: number): LngLat;
51
64
  }
52
- export { LngLat, lambertConformalConic, rotateSphere };
65
+ export { LngLat, lambertConformalConic, rotateSphere, verticalPerspective };
53
66
  export type { MapLikeType };