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.
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,62 @@ 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));
129
+ 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) => {
145
+ lon *= radians;
146
+ lat *= radians;
147
+ const sin_lat = Math.sin(lat);
148
+ 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));
155
+ return [x, y];
156
+ };
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)));
173
+ return [lon / radians, lat / radians];
174
+ };
175
+ return (a, b, opts) => {
176
+ opts = opts === undefined ? { inverse: false } : opts;
177
+ return opts.inverse ? compute_perspective_inverse(a, b) : compute_perspective(a, b);
178
+ };
179
+ }
124
180
  function mercatorXfromLng(lng) {
125
181
  return (180 + lng) / 360;
126
182
  }
@@ -166,4 +222,4 @@ class LngLat {
166
222
  return new LngLat(lngFromMercatorX(x), latFromMercatorY(y));
167
223
  }
168
224
  }
169
- export { LngLat, lambertConformalConic, rotateSphere };
225
+ export { LngLat, lambertConformalConic, rotateSphere, verticalPerspective };
@@ -3,6 +3,7 @@ import { StructuredGrid } from "./Grid";
3
3
  import { MapLikeType } from "./Map";
4
4
  import { PlotComponent } from "./PlotComponent";
5
5
  import { RawScalarField } 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.
package/lib/Paintball.js CHANGED
@@ -76,7 +76,7 @@ class Paintball extends PlotComponent {
76
76
  super();
77
77
  this.field = field;
78
78
  this.opts = normalizeOptions(opts, paintball_opt_defaults);
79
- this.color_components = [...this.opts.colors].reverse().map(color => Color.fromHex(color).toRGBATuple()).flat();
79
+ this.color_components = this.opts.colors.map(color => Color.fromHex(color).toRGBATuple()).flat();
80
80
  this.gl_elems = null;
81
81
  this.fill_texture = null;
82
82
  }
@@ -12,6 +12,7 @@ declare const layer_worker: Comlink.Remote<{
12
12
  };
13
13
  makePolyLines: (lines: import("./AutumnTypes").LineData[]) => import("./AutumnTypes").Polyline;
14
14
  }>;
15
+ /** Base class for all plot components */
15
16
  declare abstract class PlotComponent<MapType extends MapLikeType> {
16
17
  abstract onAdd(map: MapType, gl: WebGLAnyRenderingContext): Promise<void>;
17
18
  abstract render(gl: WebGLAnyRenderingContext, arg: RenderMethodArg): void;
@@ -3,6 +3,7 @@ import { getOS } from "./utils";
3
3
  import { isWebGL2Ctx } from './AutumnTypes';
4
4
  const worker = new Worker(new URL('./PlotLayer.worker', import.meta.url));
5
5
  const layer_worker = Comlink.wrap(worker);
6
+ /** Base class for all plot components */
6
7
  class PlotComponent {
7
8
  }
8
9
  function getGLFormatTypeAlignment(gl, array_dtype) {
@@ -11,7 +11,7 @@ declare abstract class PlotLayerBase<MapType extends MapLikeType> {
11
11
  protected repaint(): void;
12
12
  }
13
13
  /**
14
- * A static map layer. The data are assumed to be static in time. If the data have a time component (e.g., a model forecast), an {@link MultiPlotLayer}
14
+ * A static map layer. The data are assumed to be static in time. If the data have a time component (e.g., a model forecast), a {@link MultiPlotLayer}
15
15
  * may be more appropriate.
16
16
  * @example
17
17
  * // Create map layers from provided fields
@@ -39,7 +39,7 @@ declare class PlotLayer<MapType extends MapLikeType> extends PlotLayerBase<MapTy
39
39
  render(gl: WebGLAnyRenderingContext, matrix: RenderMethodArg): void;
40
40
  }
41
41
  /**
42
- * A varying map layer. If the data don't have a varying component, such as over time, it might be easier to use an {@link PlotLayer} instead.
42
+ * A varying map layer. If the data don't have a varying component, such as over time, it might be easier to use a {@link PlotLayer} instead.
43
43
  * @example
44
44
  * // Create a varying map layer
45
45
  * height_layer = new MultiPlotLayer('height-contours');
package/lib/PlotLayer.js CHANGED
@@ -11,7 +11,7 @@ class PlotLayerBase {
11
11
  }
12
12
  }
13
13
  /**
14
- * A static map layer. The data are assumed to be static in time. If the data have a time component (e.g., a model forecast), an {@link MultiPlotLayer}
14
+ * A static map layer. The data are assumed to be static in time. If the data have a time component (e.g., a model forecast), a {@link MultiPlotLayer}
15
15
  * may be more appropriate.
16
16
  * @example
17
17
  * // Create map layers from provided fields
@@ -46,7 +46,7 @@ class PlotLayer extends PlotLayerBase {
46
46
  }
47
47
  }
48
48
  /**
49
- * A varying map layer. If the data don't have a varying component, such as over time, it might be easier to use an {@link PlotLayer} instead.
49
+ * A varying map layer. If the data don't have a varying component, such as over time, it might be easier to use a {@link PlotLayer} instead.
50
50
  * @example
51
51
  * // Create a varying map layer
52
52
  * height_layer = new MultiPlotLayer('height-contours');
@@ -21,7 +21,10 @@ function makeBBElements(field_lats, field_lons, min_zoom, field_ni, field_nj, ma
21
21
  const pt_ll = new LngLat(lon, lat).toMercatorCoord();
22
22
  pts[istart_pts + 0] = pt_ll.x;
23
23
  pts[istart_pts + 1] = pt_ll.y;
24
- tex_coords[istart_tc + 0] = ilon / (field_ni - 1) + zoom; // Pack the min zoom in with the texture coordinates; only works because the min zoom is always an integer
24
+ // Pack the min zoom in with the texture coordinates; only works because the min zoom is always an integer
25
+ // Another gotcha is that if the i texcoord is 1, then that bump up the zoom that gets unpacked on the GPU, which causes may cause the last column of billboards to
26
+ // disappear. To fix this, cap the texcoord at 0.99999, which should be good for textures up to 10^5 pixels in size.
27
+ tex_coords[istart_tc + 0] = Math.min(ilon / (field_ni - 1), 0.99999) + zoom;
25
28
  tex_coords[istart_tc + 1] = ilat / (field_nj - 1);
26
29
  istart_pts += n_coords_per_pt_pts;
27
30
  istart_tc += n_coords_per_pt_tc;
@@ -204,7 +207,7 @@ function makePolylinesMiter(lines) {
204
207
  }
205
208
  */
206
209
  function makePolylines(lines) {
207
- if (lines.length == 0) {
210
+ if (lines.length == 0 || lines[0].vertices.length == 0) {
208
211
  return { vertices: new Float32Array([]), extrusion: new Float32Array([]) };
209
212
  }
210
213
  const n_points_per_vert = Object.fromEntries(Object.entries(lines[0]).map(([k, v]) => {
@@ -252,6 +255,9 @@ function makePolylines(lines) {
252
255
  const v_ll = new LngLat(...v).toMercatorCoord();
253
256
  return [v_ll.x, v_ll.y];
254
257
  });
258
+ if (line.vertices.length == 0) {
259
+ return;
260
+ }
255
261
  const has_offsets = line.offsets !== undefined;
256
262
  const extrusion_verts = line.offsets !== undefined ? line.offsets : verts;
257
263
  let pt_prev, pt_this = verts[0], pt_next = verts[1];
package/lib/RawField.d.ts CHANGED
@@ -36,7 +36,9 @@ declare class RawScalarField<ArrayType extends TypedArray, GridType extends Grid
36
36
  static aggregateFields<ArrayType extends TypedArray, GridType extends Grid>(func: (...args: number[]) => number, ...args: RawScalarField<ArrayType, GridType>[]): RawScalarField<ArrayType, GridType>;
37
37
  sampleField(lon: number, lat: number): number;
38
38
  }
39
+ /** The basis vectors for vector fields (i.e, whether vectors a relative to Earth or the grid) */
39
40
  type VectorRelativeTo = 'earth' | 'grid';
41
+ /** Options for {@link RawVectorField}s */
40
42
  interface RawVectorFieldOptions {
41
43
  /**
42
44
  * Whether the vectors are relative to the grid ('grid') or Earth ('earth')
package/lib/RawField.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { Float16Array } from "@petamoriken/float16";
2
+ import { isStormRelativeWindProfile } from "./AutumnTypes";
2
3
  import { contourCreator } from "./ContourCreator";
3
4
  import { Cache, getArrayConstructor, zip } from "./utils";
4
5
  import { getGLFormatTypeAlignment } from "./PlotComponent";
@@ -160,8 +161,14 @@ class RawProfileField {
160
161
  const v = new Float16Array(this.grid.ni * this.grid.nj).fill(parseFloat('nan'));
161
162
  profiles.forEach(prof => {
162
163
  const idx = prof.ilon + this.grid.ni * prof.jlat;
163
- u[idx] = prof.smu;
164
- v[idx] = prof.smv;
164
+ if (isStormRelativeWindProfile(prof)) {
165
+ u[idx] = prof.smu;
166
+ v[idx] = prof.smv;
167
+ }
168
+ else {
169
+ u[idx] = 0;
170
+ v[idx] = 0;
171
+ }
165
172
  });
166
173
  return new RawVectorField(this.grid, u, v, { relative_to: 'grid' });
167
174
  }
@@ -19,6 +19,7 @@ import { RawObsField } from "./RawField";
19
19
  * | `'c'` | center |
20
20
  */
21
21
  type SPPosition = 'cl' | 'll' | 'lc' | 'lr' | 'cr' | 'ur' | 'uc' | 'ul' | 'c';
22
+ /** Configuration for numerical values on station plots */
22
23
  interface SPNumberConfig {
23
24
  type: 'number';
24
25
  /**
@@ -30,6 +31,16 @@ interface SPNumberConfig {
30
31
  * @default '#000000'
31
32
  */
32
33
  color?: string;
34
+ /**
35
+ * Whether to draw a halo (outline) around the number
36
+ * @default true;
37
+ */
38
+ halo?: boolean;
39
+ /**
40
+ * The color to use for the halo (outline)
41
+ * @default '#ffffff'
42
+ */
43
+ halo_color?: string;
33
44
  /**
34
45
  * A function that properly formats the number for display
35
46
  * @example (val) => val === null ? '' : val.toFixed(0)
@@ -38,22 +49,34 @@ interface SPNumberConfig {
38
49
  */
39
50
  formatter?: (val: number | null) => string;
40
51
  }
52
+ /** Configuration for strings on station plots */
41
53
  interface SPStringConfig {
42
54
  type: 'string';
43
55
  /**
44
- * The position on the station plot at which to place the number
56
+ * The position on the station plot at which to place the string
45
57
  */
46
58
  pos: SPPosition;
47
59
  /**
48
- * The color to use to draw the number
60
+ * The color to use to draw the string
49
61
  * @default '#000000'
50
62
  */
51
63
  color?: string;
64
+ /**
65
+ * Whether to draw a halo (outline) around the string
66
+ * @default true;
67
+ */
68
+ halo?: boolean;
69
+ /**
70
+ * The color to use for the halo (outline)
71
+ * @default '#ffffff'
72
+ */
73
+ halo_color?: string;
52
74
  }
75
+ /** Configuration for wind barbs on station plots */
53
76
  interface SPBarbConfig {
54
77
  type: 'barb';
55
78
  /**
56
- * The color to use to draw the number
79
+ * The color to use to draw the barb
57
80
  * @default '#000000'
58
81
  */
59
82
  color?: string;
@@ -67,18 +90,30 @@ interface SPBarbConfig {
67
90
  * Accepted symbol codes for sky cover and present weather symbols
68
91
  */
69
92
  type SPSymbol = ('0/8' | '1/8' | '2/8' | '3/8' | '4/8' | '5/8' | '6/8' | '7/8' | '8/8' | 'clr' | 'few' | 'sct' | 'bkn' | 'ovc' | 'obsc' | 'va' | 'fu' | 'hz' | 'du' | 'bldu' | 'sa' | 'blsa' | 'vcblsa' | 'vcbldu' | 'blpy' | 'po' | 'vcpo' | 'vcds' | 'vcss' | 'br' | 'bcbr' | 'bc' | 'mifg' | 'vcts' | 'virga' | 'vcsh' | 'ts' | 'thdr' | 'vctshz' | 'tsfzfg' | 'tsbr' | 'tsdz' | 'vctsup' | '-tsup' | 'tsup' | '+tsup' | 'sq' | 'fc' | '+fc' | 'ds' | 'ss' | 'drsa' | 'drdu' | '+ds' | '+ss' | 'drsn' | '+drsn' | '-blsn' | 'blsn' | '+blsn' | 'vcblsn' | 'vcfg' | 'bcfg' | 'prfg' | 'fg' | 'fzfg' | '-vctsdz' | '-dz' | '-dzbr' | 'vctsdz' | 'dz' | '+vctsdz' | '+dz' | '-fzdz' | '-fzdzsn' | 'fzdz' | '+fzdz' | 'fzdzsn' | '-dzra' | 'dzra' | '+dzra' | '-ra' | '-rabr' | 'ra' | 'rabr' | 'rafg' | 'vcra' | '+ra' | '-fzra' | '-fzrasn' | '-fzrabr' | '-fzrapl' | '-fzrasnpl' | 'tsfzrapl' | '-tsfzra' | 'fzra' | '+fzra' | 'fzrasn' | 'tsfzra' | '-dzsn' | '-rasn' | '-snra' | '-sndz' | 'rasn' | '+rasn' | 'snra' | 'dzsn' | 'sndz' | '+dzsn' | '+sndz' | '-sn' | '-snbr' | 'sn' | '+sn' | '-snsg' | 'sg' | '-sg' | 'ic' | '-fzdzpl' | '-fzdzplsn' | 'fzdzpl' | '-fzraplsn' | 'fzrapl' | '+fzrapl' | '-rapl' | '-rasnpl' | '-raplsn' | '+rapl' | 'rapl' | '-snpl' | 'snpl' | '-pl' | 'pl' | '-plsn' | '-plra' | 'plra' | '-pldz' | '+pl' | 'plsn' | 'plup' | '+plsn' | '-sh' | '-shra' | 'sh' | 'shra' | '+sh' | '+shra' | '-shrasn' | '-shsnra' | '+shrabr' | 'shrasn' | '+shrasn' | 'shsnra' | '+shsnra' | '-shsn' | 'shsn' | '+shsn' | '-gs' | '-shgs' | 'fzraplgs' | '-sngs' | 'gsplsn' | 'gspl' | 'plgssn' | 'gs' | 'shgs' | '+gs' | '+shgs' | '-gr' | '-shgr' | '-sngr' | 'gr' | 'shgr' | '+gr' | '+shgr' | '-tsrasn' | 'tsrasn' | '-tssnra' | 'tssnra' | '-vctsra' | '-tsra' | 'tsra' | '-tsdz' | 'vctsra' | 'tspl' | '-tssn' | '-tspl' | 'tssn' | '-vctssn' | 'vctssn' | 'tsplsn' | 'tssnpl' | '-tssnpl' | '-tsragr' | 'tsrags' | 'tsragr' | 'tsgs' | 'tsgr' | '+tsfzrapl' | '+vctsra' | '+tsra' | '+tsfzra' | '+tssn' | '+tspl' | '+tsplsn' | '+vctssn' | 'tssa' | 'tsds' | 'tsdu' | '+tsgs' | '+tsgr' | '+tsrags' | '+tsragr' | 'in' | '-up' | 'up' | '+up' | '-fzup' | 'fzup' | '+fzup');
93
+ /** Configuration for symbols on station plots */
70
94
  interface SPSymbolConfig {
71
95
  type: 'symbol';
72
96
  /**
73
- * The position on the station plot at which to place the number
97
+ * The position on the station plot at which to place the symbol
74
98
  */
75
99
  pos: SPPosition;
76
100
  /**
77
- * The color to use to draw the number
101
+ * The color to use to draw the symbol
78
102
  * @default '#000000'
79
103
  */
80
104
  color?: string;
105
+ /**
106
+ * Whether to draw a halo (outline) around the string
107
+ * @default true;
108
+ */
109
+ halo?: boolean;
110
+ /**
111
+ * The color to use for the halo (outline)
112
+ * @default '#ffffff'
113
+ */
114
+ halo_color?: string;
81
115
  }
116
+ /** Configuration for station plot sub-elements */
82
117
  type SPConfig = SPNumberConfig | SPStringConfig | SPBarbConfig | SPSymbolConfig;
83
118
  /**
84
119
  * Configuration for station data plots
@@ -98,6 +133,7 @@ type SPConfig = SPNumberConfig | SPStringConfig | SPBarbConfig | SPSymbolConfig;
98
133
  * }
99
134
  */
100
135
  type SPDataConfig<ObsFieldName extends string> = Record<ObsFieldName, SPConfig>;
136
+ /** Options for {@link StationPlot} components */
101
137
  interface StationPlotOptions<ObsFieldName extends string> {
102
138
  config: SPDataConfig<ObsFieldName>;
103
139
  /**
@@ -120,15 +156,30 @@ interface StationPlotOptions<ObsFieldName extends string> {
120
156
  */
121
157
  font_url_template?: string;
122
158
  }
159
+ /**
160
+ * Station model plots for observed data
161
+ * @example
162
+ * // Specify how to set up the station plot
163
+ * const station_plot_locs = {
164
+ * tmpf: {type: 'number', pos: 'ul', color: '#cc0000', formatter: val => val === null ? '' : val.toFixed(0)},
165
+ * dwpf: {type: 'number', pos: 'll', color: '#00aa00', formatter: val => val === null ? '' : val.toFixed(0)},
166
+ * wind: {type: 'barb', pos: 'c'},
167
+ * preswx: {type: 'symbol', pos: 'cl', color: '#ff00ff'},
168
+ * skyc: {type: 'symbol', pos: 'c'},
169
+ * };
170
+ *
171
+ * // Create the station plot
172
+ * const station_plot = new StationPlot(obs_field, {config: station_plot_locs, thin_fac: 8, font_size: 14});
173
+ */
123
174
  declare class StationPlot<GridType extends Grid, MapType extends MapLikeType, ObsFieldName extends string> extends PlotComponent<MapType> {
124
175
  private field;
125
176
  readonly opts: Required<StationPlotOptions<ObsFieldName>>;
126
177
  private gl_elems;
127
178
  private text_components;
128
179
  /**
129
- *
130
- * @param field
131
- * @param opts
180
+ * Create station plots
181
+ * @param field - A field containing the observed data
182
+ * @param opts - Various options for the station plots
132
183
  */
133
184
  constructor(field: RawObsField<GridType, ObsFieldName>, opts: StationPlotOptions<ObsFieldName>);
134
185
  /**
@@ -76,11 +76,26 @@ function positionToAlignmentAndOffset(pos, off_size) {
76
76
  }
77
77
  return { horizontal_align: ha, vertical_align: va, offset_x: xoff, offset_y: yoff };
78
78
  }
79
+ /**
80
+ * Station model plots for observed data
81
+ * @example
82
+ * // Specify how to set up the station plot
83
+ * const station_plot_locs = {
84
+ * tmpf: {type: 'number', pos: 'ul', color: '#cc0000', formatter: val => val === null ? '' : val.toFixed(0)},
85
+ * dwpf: {type: 'number', pos: 'll', color: '#00aa00', formatter: val => val === null ? '' : val.toFixed(0)},
86
+ * wind: {type: 'barb', pos: 'c'},
87
+ * preswx: {type: 'symbol', pos: 'cl', color: '#ff00ff'},
88
+ * skyc: {type: 'symbol', pos: 'c'},
89
+ * };
90
+ *
91
+ * // Create the station plot
92
+ * const station_plot = new StationPlot(obs_field, {config: station_plot_locs, thin_fac: 8, font_size: 14});
93
+ */
79
94
  class StationPlot extends PlotComponent {
80
95
  /**
81
- *
82
- * @param field
83
- * @param opts
96
+ * Create station plots
97
+ * @param field - A field containing the observed data
98
+ * @param opts - Various options for the station plots
84
99
  */
85
100
  constructor(field, opts) {
86
101
  super();
@@ -110,8 +125,9 @@ class StationPlot extends PlotComponent {
110
125
  const k = k_;
111
126
  if (config.type == 'number' || config.type == 'string') {
112
127
  const pos = config.pos;
113
- const color_opt = config.color;
114
- const color = color_opt === undefined ? Color.fromHex('#000000') : Color.normalizeColor(color_opt);
128
+ const color = config.color === undefined ? Color.fromHex('#000000') : Color.normalizeColor(config.color);
129
+ const halo_color = config.halo_color === undefined ? Color.fromHex('#ffffff') : Color.normalizeColor(config.halo_color);
130
+ const halo = config.halo === undefined ? true : config.halo;
115
131
  const coords = this.field.grid.getEarthCoords();
116
132
  const zoom = this.field.grid.getMinVisibleZoom(this.opts.thin_fac);
117
133
  let text_specs;
@@ -126,8 +142,8 @@ class StationPlot extends PlotComponent {
126
142
  }
127
143
  const tc_opts = {
128
144
  ...positionToAlignmentAndOffset(pos),
129
- font_size: this.opts.font_size, halo: true,
130
- text_color: color, halo_color: Color.fromHex('#ffffff'),
145
+ font_size: this.opts.font_size, halo: halo,
146
+ text_color: color, halo_color: halo_color,
131
147
  };
132
148
  return await TextCollection.make(gl, text_specs, font_url, tc_opts);
133
149
  }
@@ -138,8 +154,9 @@ class StationPlot extends PlotComponent {
138
154
  }
139
155
  else if (config.type == 'symbol') {
140
156
  const pos = config.pos;
141
- const color_opt = config.color;
142
- const color = color_opt === undefined ? Color.fromHex('#000000') : Color.normalizeColor(color_opt);
157
+ const color = config.color === undefined ? Color.fromHex('#000000') : Color.normalizeColor(config.color);
158
+ const halo_color = config.halo_color === undefined ? Color.fromHex('#ffffff') : Color.normalizeColor(config.halo_color);
159
+ const halo = config.halo === undefined ? true : config.halo;
143
160
  const comp = this.field.getStrings(k);
144
161
  const coords = this.field.grid.getEarthCoords();
145
162
  const zoom = this.field.grid.getMinVisibleZoom(this.opts.thin_fac);
@@ -148,8 +165,8 @@ class StationPlot extends PlotComponent {
148
165
  lat: coords.lats[i], lon: coords.lons[i], min_zoom: zoom[i] }));
149
166
  const tc_opts = {
150
167
  ...positionToAlignmentAndOffset(pos),
151
- font_size: this.opts.font_size, halo: true,
152
- text_color: color, halo_color: Color.fromHex('#ffffff'),
168
+ font_size: this.opts.font_size, halo: halo,
169
+ text_color: color, halo_color: halo_color,
153
170
  };
154
171
  if (tc_opts.offset_x !== undefined)
155
172
  tc_opts.offset_x -= 3;
@@ -154,6 +154,9 @@ function createAtlas(pbf_glyphs) {
154
154
  const FONT_ATLAS_CACHE = new Cache(async (urls) => {
155
155
  const promises = urls.map(async (url) => {
156
156
  const resp = await fetch(url);
157
+ if (resp.status != 200) {
158
+ throw `Error ${resp.status} retrieving font pbf from '${url}'`;
159
+ }
157
160
  const blob = await resp.blob();
158
161
  const data_buffer = await blob.arrayBuffer();
159
162
  // Parse the PBF and get the glyph data
package/lib/index.d.ts CHANGED
@@ -6,7 +6,7 @@ import Paintball, { PaintballOptions } from "./Paintball";
6
6
  import Hodographs, { HodographOptions } from './Hodographs';
7
7
  import StationPlot, { StationPlotOptions, SPPosition, SPNumberConfig, SPStringConfig, SPBarbConfig, SPSymbolConfig, SPConfig, SPDataConfig, SPSymbol } from "./StationPlot";
8
8
  import { PlotLayer, MultiPlotLayer } from './PlotLayer';
9
- import { WindProfile, WebGLAnyRenderingContext, TypedArray, ContourData } from "./AutumnTypes";
9
+ import { WindProfile, StormRelativeWindProfile, GroundRelativeWindProfile, WebGLAnyRenderingContext, TypedArray, ContourData } from "./AutumnTypes";
10
10
  import { MapLikeType } from "./Map";
11
11
  import { ColorMap, ColorMapOptions } from './Colormap';
12
12
  import { Color } from "./Color";
@@ -15,6 +15,7 @@ import { LineStyle } from "./PolylineCollection";
15
15
  import { RawScalarField, RawVectorField, RawProfileField, VectorRelativeTo, RawVectorFieldOptions, RawObsField, ObsRawData } from "./RawField";
16
16
  import { Grid, GridType, StructuredGrid, PlateCarreeGrid, PlateCarreeRotatedGrid, LambertGrid, UnstructuredGrid } from './Grid';
17
17
  import { FieldContourOpts } from './ContourCreator';
18
+ /** All built-in colormaps */
18
19
  declare const colormaps: {
19
20
  bluered: (level_min: number, level_max: number, n_colors: number) => ColorMap;
20
21
  redblue: (level_min: number, level_max: number, n_colors: number) => ColorMap;
@@ -25,9 +26,14 @@ declare const colormaps: {
25
26
  pw_td2m: ColorMap;
26
27
  nws_storm_clear_refl: ColorMap;
27
28
  };
29
+ /** Options for initializing the autumnplot-gl library */
30
+ interface InitAutumnPlotOpts {
31
+ /** Base URL at which to find the WASM module (change with caution!) */
32
+ wasm_base_url?: string;
33
+ }
28
34
  /**
29
35
  * Initialize the WebAssembly module in autumnplot-gl. It's not strictly necessary to call it first, but if you call it
30
36
  * first, you can prevent races when you contour a bunch of fields at once.
31
37
  */
32
- declare function initAutumnPlot(): void;
33
- export { PlotComponent, Barbs, BarbsOptions, Contour, ContourOptions, ContourLabels, ContourLabelOptions, ContourFill, Raster, ContourFillOptions, RasterOptions, Paintball, PaintballOptions, Hodographs, HodographOptions, WindProfile, StationPlot, StationPlotOptions, SPPosition, SPNumberConfig, SPStringConfig, SPBarbConfig, SPSymbolConfig, SPConfig, SPDataConfig, SPSymbol, PlotLayer, MultiPlotLayer, MapLikeType, LineStyle, ColorMap, ColorMapOptions, colormaps, makeColorBar, makePaintballKey, Color, ColorbarOrientation, ColorbarTickDirection, ColorBarOptions, PaintballKeyOptions, RawScalarField, RawVectorField, RawProfileField, RawObsField, ObsRawData, Grid, GridType, StructuredGrid, VectorRelativeTo, RawVectorFieldOptions, PlateCarreeGrid, PlateCarreeRotatedGrid, LambertGrid, UnstructuredGrid, WebGLAnyRenderingContext, TypedArray, ContourData, initAutumnPlot, FieldContourOpts };
38
+ declare function initAutumnPlot(opts?: InitAutumnPlotOpts): void;
39
+ export { PlotComponent, Barbs, BarbsOptions, Contour, ContourOptions, ContourLabels, ContourLabelOptions, ContourFill, Raster, ContourFillOptions, RasterOptions, Paintball, PaintballOptions, Hodographs, HodographOptions, WindProfile, StormRelativeWindProfile, GroundRelativeWindProfile, StationPlot, StationPlotOptions, SPPosition, SPNumberConfig, SPStringConfig, SPBarbConfig, SPSymbolConfig, SPConfig, SPDataConfig, SPSymbol, PlotLayer, MultiPlotLayer, MapLikeType, LineStyle, ColorMap, ColorMapOptions, colormaps, makeColorBar, makePaintballKey, Color, ColorbarOrientation, ColorbarTickDirection, ColorBarOptions, PaintballKeyOptions, RawScalarField, RawVectorField, RawProfileField, RawObsField, ObsRawData, Grid, GridType, StructuredGrid, VectorRelativeTo, RawVectorFieldOptions, PlateCarreeGrid, PlateCarreeRotatedGrid, LambertGrid, UnstructuredGrid, WebGLAnyRenderingContext, TypedArray, ContourData, initAutumnPlot, InitAutumnPlotOpts, FieldContourOpts };
package/lib/index.js CHANGED
@@ -12,6 +12,7 @@ import { makeColorBar, makePaintballKey } from "./ColorBar";
12
12
  import { RawScalarField, RawVectorField, RawProfileField, RawObsField } from "./RawField";
13
13
  import { Grid, StructuredGrid, PlateCarreeGrid, PlateCarreeRotatedGrid, LambertGrid, UnstructuredGrid } from './Grid';
14
14
  import { initMSModule } from './ContourCreator';
15
+ /** All built-in colormaps */
15
16
  const colormaps = {
16
17
  bluered: bluered,
17
18
  redblue: redblue,
@@ -26,7 +27,8 @@ const colormaps = {
26
27
  * Initialize the WebAssembly module in autumnplot-gl. It's not strictly necessary to call it first, but if you call it
27
28
  * first, you can prevent races when you contour a bunch of fields at once.
28
29
  */
29
- function initAutumnPlot() {
30
- initMSModule();
30
+ function initAutumnPlot(opts) {
31
+ opts = opts === undefined ? {} : opts;
32
+ initMSModule({ document_script: opts.wasm_base_url });
31
33
  }
32
34
  export { PlotComponent, Barbs, Contour, ContourLabels, ContourFill, Raster, Paintball, Hodographs, StationPlot, PlotLayer, MultiPlotLayer, ColorMap, colormaps, makeColorBar, makePaintballKey, Color, RawScalarField, RawVectorField, RawProfileField, RawObsField, Grid, StructuredGrid, PlateCarreeGrid, PlateCarreeRotatedGrid, LambertGrid, UnstructuredGrid, initAutumnPlot };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "autumnplot-gl",
3
- "version": "4.0.0-beta",
3
+ "version": "4.0.0",
4
4
  "description": "",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -27,7 +27,7 @@
27
27
  "typedoc": "^0.25.13",
28
28
  "typedoc-plugin-markdown": "^4.0.2",
29
29
  "typescript": "^5.4.5",
30
- "webpack": "^5.75.0",
30
+ "webpack": "5.100",
31
31
  "webpack-cli": "^5.0.1",
32
32
  "webpack-dev-server": "^4.11.1",
33
33
  "webpack-glsl-loader": "^1.0.1"
@@ -1,2 +0,0 @@
1
- !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.apgl=t():e.apgl=t()}(this,(()=>(()=>{"use strict";function e(e,t,r){for(var n=1,o=r;e%o!=0||t%o!=0;)n+=1,o/=2;return n}const t=Symbol("Comlink.proxy"),r=Symbol("Comlink.endpoint"),n=Symbol("Comlink.releaseProxy"),o=Symbol("Comlink.thrown"),a=e=>"object"==typeof e&&null!==e||"function"==typeof e,s=new Map([["proxy",{canHandle:e=>a(e)&&e[t],serialize(e){const{port1:t,port2:r}=new MessageChannel;return i(e,t),[r,[r]]},deserialize:e=>(e.start(),u(e,[],undefined))}],["throw",{canHandle:e=>a(e)&&o in e,serialize({value:e}){let t;return t=e instanceof Error?{isError:!0,value:{message:e.message,name:e.name,stack:e.stack}}:{isError:!1,value:e},[t,[]]},deserialize(e){if(e.isError)throw Object.assign(new Error(e.value.message),e.value);throw e.value}}]]);function i(e,r=self){r.addEventListener("message",(function n(a){if(!a||!a.data)return;const{id:s,type:f,path:u}=Object.assign({path:[]},a.data),l=(a.data.argumentList||[]).map(h);let p;try{const r=u.slice(0,-1).reduce(((e,t)=>e[t]),e),n=u.reduce(((e,t)=>e[t]),e);switch(f){case"GET":p=n;break;case"SET":r[u.slice(-1)[0]]=h(a.data.value),p=!0;break;case"APPLY":p=n.apply(r,l);break;case"CONSTRUCT":p=function(e){return Object.assign(e,{[t]:!0})}(new n(...l));break;case"ENDPOINT":{const{port1:t,port2:r}=new MessageChannel;i(e,r),p=function(e,t){return v.set(e,t),e}(t,[t])}break;case"RELEASE":p=void 0;break;default:return}}catch(e){p={value:e,[o]:0}}Promise.resolve(p).catch((e=>({value:e,[o]:0}))).then((e=>{const[t,o]=d(e);r.postMessage(Object.assign(Object.assign({},t),{id:s}),o),"RELEASE"===f&&(r.removeEventListener("message",n),c(r))}))})),r.start&&r.start()}function c(e){(function(e){return"MessagePort"===e.constructor.name})(e)&&e.close()}function f(e){if(e)throw new Error("Proxy has been released and is not useable")}function u(e,t=[],o=function(){}){let a=!1;const s=new Proxy(o,{get(r,o){if(f(a),o===n)return()=>p(e,{type:"RELEASE",path:t.map((e=>e.toString()))}).then((()=>{c(e),a=!0}));if("then"===o){if(0===t.length)return{then:()=>s};const r=p(e,{type:"GET",path:t.map((e=>e.toString()))}).then(h);return r.then.bind(r)}return u(e,[...t,o])},set(r,n,o){f(a);const[s,i]=d(o);return p(e,{type:"SET",path:[...t,n].map((e=>e.toString())),value:s},i).then(h)},apply(n,o,s){f(a);const i=t[t.length-1];if(i===r)return p(e,{type:"ENDPOINT"}).then(h);if("bind"===i)return u(e,t.slice(0,-1));const[c,v]=l(s);return p(e,{type:"APPLY",path:t.map((e=>e.toString())),argumentList:c},v).then(h)},construct(r,n){f(a);const[o,s]=l(n);return p(e,{type:"CONSTRUCT",path:t.map((e=>e.toString())),argumentList:o},s).then(h)}});return s}function l(e){const t=e.map(d);return[t.map((e=>e[0])),(r=t.map((e=>e[1])),Array.prototype.concat.apply([],r))];var r}const v=new WeakMap;function d(e){for(const[t,r]of s)if(r.canHandle(e)){const[n,o]=r.serialize(e);return[{type:"HANDLER",name:t,value:n},o]}return[{type:"RAW",value:e},v.get(e)||[]]}function h(e){switch(e.type){case"HANDLER":return s.get(e.name).deserialize(e.value);case"RAW":return e.value}}function p(e,t,r){return new Promise((n=>{const o=new Array(4).fill(0).map((()=>Math.floor(Math.random()*Number.MAX_SAFE_INTEGER).toString(16))).join("-");e.addEventListener("message",(function t(r){r.data&&r.data.id&&r.data.id===o&&(e.removeEventListener("message",t),n(r.data))})),e.start&&e.start(),e.postMessage(Object.assign({id:o},t),r)}))}var m=function(){function e(e,t){if(isNaN(e)||isNaN(t))throw new Error("Invalid LngLat object: (".concat(e,", ").concat(t,")"));if(this.lng=+e,this.lat=+t,this.lat>90||this.lat<-90)throw new Error("Invalid LngLat latitude value: must be between -90 and 90")}return e.prototype.toMercatorCoord=function(){return{x:(n=this.lng,(180+n)/360),y:(e=this.lat,t=Math.sin(e*Math.PI/180),r=(180-90/Math.PI*Math.log((1+t)/(1-t)))/360,Math.min(2,Math.max(-2,r)))};var e,t,r,n},e.fromMercatorCoord=function(t,r){return new e(function(e){return 360*e-180}(t),function(e){return 180*Math.atan(Math.sinh((180-360*e)*Math.PI/180))/Math.PI}(r))},e}(),y=function(e,t){var r="function"==typeof Symbol&&e[Symbol.iterator];if(!r)return e;var n,o,a=r.call(e),s=[];try{for(;(void 0===t||t-- >0)&&!(n=a.next()).done;)s.push(n.value)}catch(e){o={error:e}}finally{try{n&&!n.done&&(r=a.return)&&r.call(a)}finally{if(o)throw o.error}}return s};return i({makeBBElements:function(t,r,n,o,a,s){for(var i=Math.log2(a),c=Math.max(i+1-s,0),f=Math.pow(2,c),u=Math.floor((n-1)/f)+1,l=Math.floor((o-1)/f)+1,v=u*l*6*2,d=new Float32Array(u*l*6*3),h=new Float32Array(v),p=0,y=0,g=0;g<o;g++)for(var x=0;x<n;x++){var w=g*n+x,b=t[w],E=r[w],M=e(g,x,a);if(!(M>s)){for(var A=new m(E,b).toMercatorCoord(),S=0;S<6;S++){var L=Math.max(0,Math.min(S-1,3));d[p+3*S+0]=A.x,d[p+3*S+1]=A.y,d[p+3*S+2]=4*M+L,h[y+2*S+0]=x/(n-1),h[y+2*S+1]=g/(o-1)}p+=18,y+=12}}return{pts:d,tex_coords:h}},makeDomainVerticesAndTexCoords:function(e,t,r,n,o,a){for(var s=new Float32Array(4*(r-1)*(n+1)).fill(0),i=new Float32Array(4*(r-1)*(n+1)).fill(0),c=new Float32Array(2*(r-1)*(n+1)).fill(0),f=0,u=0,l=0;l<r-1;l++)for(var v=0;v<n;v++){var d=l+v*r,h=new m(t[d],e[d]).toMercatorCoord(),p=new m(t[d+1],e[d+1]).toMercatorCoord(),y=l/(r-1)*(1-2*o)+o,g=(l+1)/(r-1)*(1-2*o)+o,x=v/(n-1)*(1-2*a)+a;0==v&&(s[f]=h.x,s[f+1]=h.y,f+=2,i[u]=y,i[u+1]=x,u+=2),s[f]=h.x,s[f+1]=h.y,s[f+2]=p.x,s[f+3]=p.y,f+=4,i[u]=y,i[u+1]=x,i[u+2]=g,i[u+3]=x,u+=4,v==n-1&&(s[f]=p.x,s[f+1]=p.y,f+=2,i[u]=g,i[u+1]=x,u+=2)}var w=0;for(l=0;l<r-1;l++)for(v=0;v<n-1;v++){var b=0==v?2*(w+1):2*w,E=s[b],M=s[b+1],A=s[b+2],S=s[b+3],L=s[b+4],j=s[b+5],k=s[b+6],C=s[b+7],P=.5*Math.abs(E*(S-j)+A*(j-M)+L*(M-S)+k*(j-S)+L*(S-C)+A*(C-j));0==v&&(c[w]=P,w+=1),c[w]=P,c[w+1]=P,w+=2,v==n-2&&(c[w]=P,c[w+1]=P,c[w+2]=P,w+=3)}return{vertices:s,tex_coords:i,grid_cell_size:c}},makePolyLines:function(e){if(0==e.length)return{vertices:new Float32Array([]),extrusion:new Float32Array([])};var t=Object.fromEntries(Object.entries(e[0]).map((function(e){var t=y(e,2),r=t[0],n=t[1];return[r,"number"==typeof n||"number"==typeof n[0]?1:n[0].length]})));t.extrusion=2,t.vertices+=1;var r=4*e.map((function(e){return e.vertices.length})).reduce((function(e,t){return e+t}))-2*e.length,n=Object.fromEntries(Object.entries(t).map((function(e){var t=y(e,2),n=t[0],o=t[1];return[n,r*o]}))),o={vertices:new Float32Array(n.vertices),extrusion:new Float32Array(n.extrusion)};"offsets"in e[0]&&(o.offsets=new Float32Array(n.offsets)),"data"in e[0]&&(o.data=new Float32Array(n.data)),"zoom"in e[0]&&(o.zoom=new Float32Array(n.zoom));var a=Object.fromEntries(Object.keys(n).map((function(e){return[e,0]}))),s=function(e,t,r){var n=t[0]-e[0],o=t[1]-e[1],a=Math.hypot(n,o),s=-n/a;return[o/a,r?-s:s]};return e.forEach((function(e){var t,r,n,i,c=e.vertices.map((function(e){var t=(new(m.bind.apply(m,function(e,t,r){if(r||2===arguments.length)for(var n,o=0,a=t.length;o<a;o++)!n&&o in t||(n||(n=Array.prototype.slice.call(t,0,o)),n[o]=t[o]);return e.concat(n||Array.prototype.slice.call(t))}([void 0],y(e),!1)))).toMercatorCoord();return[t.x,t.y]})),f=void 0!==e.offsets,u=void 0!==e.offsets?e.offsets:c,l=c[0],v=(c[1],u[0]),d=u[1],h=1e-4,p=y(s(v,d,!f),2),g=p[0],x=p[1];o.vertices[a.vertices++]=l[0],o.vertices[a.vertices++]=l[1],o.vertices[a.vertices++]=h,o.extrusion[a.extrusion++]=g,o.extrusion[a.extrusion++]=x;for(var w=1;w<c.length;w++)l=c[w],r=c[w-1],v=u[w],n=u[w-1],g=(t=y(s(n,v,!f),2))[0],x=t[1],i=h,h+=Math.hypot(c[w-1][0]-c[w][0],c[w-1][1]-c[w][1]),o.vertices[a.vertices++]=r[0],o.vertices[a.vertices++]=r[1],o.vertices[a.vertices++]=-i,o.vertices[a.vertices++]=r[0],o.vertices[a.vertices++]=r[1],o.vertices[a.vertices++]=i,o.vertices[a.vertices++]=l[0],o.vertices[a.vertices++]=l[1],o.vertices[a.vertices++]=-h,o.vertices[a.vertices++]=l[0],o.vertices[a.vertices++]=l[1],o.vertices[a.vertices++]=h,o.extrusion[a.extrusion++]=g,o.extrusion[a.extrusion++]=x,o.extrusion[a.extrusion++]=-g,o.extrusion[a.extrusion++]=-x,o.extrusion[a.extrusion++]=g,o.extrusion[a.extrusion++]=x,o.extrusion[a.extrusion++]=-g,o.extrusion[a.extrusion++]=-x;if(o.vertices[a.vertices++]=l[0],o.vertices[a.vertices++]=l[1],o.vertices[a.vertices++]=h,o.extrusion[a.extrusion++]=-g,o.extrusion[a.extrusion++]=-x,void 0!==o.offsets&&void 0!==e.offsets){var b=e.offsets,E=void 0,M=b[0];for(o.offsets[a.offsets++]=M[0],o.offsets[a.offsets++]=M[1],w=1;w<b.length;w++)M=b[w],E=b[w-1],o.offsets[a.offsets++]=E[0],o.offsets[a.offsets++]=E[1],o.offsets[a.offsets++]=E[0],o.offsets[a.offsets++]=E[1],o.offsets[a.offsets++]=M[0],o.offsets[a.offsets++]=M[1],o.offsets[a.offsets++]=M[0],o.offsets[a.offsets++]=M[1];o.offsets[a.offsets++]=M[0],o.offsets[a.offsets++]=M[1]}if(void 0!==o.data&&void 0!==e.data){var A=e.data,S=void 0,L=A[0];for(o.data[a.data++]=L,w=1;w<A.length;w++)L=A[w],S=A[w-1],o.data[a.data++]=S,o.data[a.data++]=S,o.data[a.data++]=L,o.data[a.data++]=L;o.data[a.data++]=L}if(void 0!==o.zoom&&void 0!==e.zoom)for(w=0;w<4*c.length-2;w++)o.zoom[a.zoom++]=e.zoom})),o}}),{}})()));
2
- //# sourceMappingURL=110.autumnplot-gl.js.map