autumnplot-gl 3.2.0 → 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.
Files changed (59) hide show
  1. package/README.md +11 -207
  2. package/dist/983.autumnplot-gl.js +2 -0
  3. package/dist/983.autumnplot-gl.js.map +1 -0
  4. package/dist/autumnplot-gl.js +1 -1
  5. package/dist/autumnplot-gl.js.map +1 -1
  6. package/dist/marchingsquares.wasm +0 -0
  7. package/lib/AutumnTypes.d.ts +85 -6
  8. package/lib/AutumnTypes.js +28 -1
  9. package/lib/Barbs.d.ts +7 -5
  10. package/lib/BillboardCollection.d.ts +8 -7
  11. package/lib/BillboardCollection.js +69 -58
  12. package/lib/Color.d.ts +2 -0
  13. package/lib/Color.js +4 -0
  14. package/lib/ColorBar.d.ts +19 -0
  15. package/lib/ColorBar.js +9 -6
  16. package/lib/Colormap.d.ts +1 -0
  17. package/lib/Colormap.js +8 -8
  18. package/lib/Contour.d.ts +28 -8
  19. package/lib/Contour.js +27 -54
  20. package/lib/ContourCreator.d.ts +9 -1
  21. package/lib/ContourCreator.js +5 -4
  22. package/lib/Fill.d.ts +28 -14
  23. package/lib/Fill.js +97 -50
  24. package/lib/Grid.d.ts +132 -30
  25. package/lib/Grid.js +355 -99
  26. package/lib/Hodographs.d.ts +15 -8
  27. package/lib/Hodographs.js +48 -30
  28. package/lib/Map.d.ts +14 -1
  29. package/lib/Map.js +60 -4
  30. package/lib/Paintball.d.ts +7 -5
  31. package/lib/Paintball.js +36 -31
  32. package/lib/ParticleTracer.d.ts +19 -0
  33. package/lib/ParticleTracer.js +37 -0
  34. package/lib/PlotComponent.d.ts +7 -7
  35. package/lib/PlotComponent.js +9 -3
  36. package/lib/PlotLayer.d.ts +5 -5
  37. package/lib/PlotLayer.js +2 -2
  38. package/lib/PlotLayer.worker.d.ts +1 -2
  39. package/lib/PlotLayer.worker.js +22 -51
  40. package/lib/PolylineCollection.d.ts +5 -3
  41. package/lib/PolylineCollection.js +60 -37
  42. package/lib/RawField.d.ts +78 -23
  43. package/lib/RawField.js +147 -31
  44. package/lib/ShaderManager.d.ts +12 -0
  45. package/lib/ShaderManager.js +58 -0
  46. package/lib/StationPlot.d.ts +187 -25
  47. package/lib/StationPlot.js +209 -60
  48. package/lib/TextCollection.d.ts +9 -6
  49. package/lib/TextCollection.js +97 -62
  50. package/lib/cpp/marchingsquares.js +483 -585
  51. package/lib/cpp/marchingsquares.wasm +0 -0
  52. package/lib/cpp/marchingsquares_embind.d.ts +23 -3
  53. package/lib/index.d.ts +12 -5
  54. package/lib/index.js +8 -5
  55. package/lib/utils.d.ts +4 -1
  56. package/lib/utils.js +12 -1
  57. package/package.json +3 -3
  58. package/dist/110.autumnplot-gl.js +0 -2
  59. package/dist/110.autumnplot-gl.js.map +0 -1
@@ -1,8 +1,10 @@
1
1
  import { PlotComponent } from "./PlotComponent";
2
2
  import { MapLikeType } from "./Map";
3
3
  import { RawProfileField } from "./RawField";
4
- import { WebGLAnyRenderingContext } from "./AutumnTypes";
4
+ import { RenderMethodArg, WebGLAnyRenderingContext } from "./AutumnTypes";
5
5
  import { ColorMap } from "./Colormap";
6
+ import { Grid } from "./Grid";
7
+ /** Options for {@link Hodographs} components */
6
8
  interface HodographOptions {
7
9
  /**
8
10
  * The color of the hodograph plot background as a hex string
@@ -19,19 +21,24 @@ interface HodographOptions {
19
21
  * The width of the hodograph line in pixels
20
22
  * @default 2.5
21
23
  */
22
- hodo_line_width: number;
24
+ hodo_line_width?: number;
23
25
  /**
24
26
  * The width of the lines on the background in pixels
25
27
  * @default 1.5
26
28
  */
27
- background_line_width: number;
29
+ background_line_width?: number;
28
30
  /**
29
31
  * The colormap to use for the heights on the hodograph. Default is a yellow-blue colormap.
30
32
  */
31
- height_cmap: ColorMap;
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;
32
39
  }
33
40
  /** A class representing a field of hodograph plots */
34
- declare class Hodographs<MapType extends MapLikeType> extends PlotComponent<MapType> {
41
+ declare class Hodographs<GridType extends Grid, MapType extends MapLikeType> extends PlotComponent<MapType> {
35
42
  private profile_field;
36
43
  readonly opts: Required<HodographOptions>;
37
44
  private gl_elems;
@@ -44,12 +51,12 @@ declare class Hodographs<MapType extends MapLikeType> extends PlotComponent<MapT
44
51
  * @param profile_field - The grid of profiles to plot
45
52
  * @param opts - Various options to use when creating the hodographs
46
53
  */
47
- constructor(profile_field: RawProfileField, opts?: HodographOptions);
54
+ constructor(profile_field: RawProfileField<GridType>, opts?: HodographOptions);
48
55
  /**
49
56
  * Update the profiles displayed
50
57
  * @param field - The new profiles to display as hodographs
51
58
  */
52
- updateField(field: RawProfileField): Promise<void>;
59
+ updateField(field: RawProfileField<GridType>): Promise<void>;
53
60
  /**
54
61
  * @internal
55
62
  * Add the hodographs to a map
@@ -59,7 +66,7 @@ declare class Hodographs<MapType extends MapLikeType> extends PlotComponent<MapT
59
66
  * @internal
60
67
  * Render the hodographs
61
68
  */
62
- render(gl: WebGLAnyRenderingContext, matrix: number[] | Float32Array): void;
69
+ render(gl: WebGLAnyRenderingContext, arg: RenderMethodArg): void;
63
70
  }
64
71
  export default Hodographs;
65
72
  export type { HodographOptions };
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;
@@ -77,29 +85,39 @@ class Hodographs extends PlotComponent {
77
85
  const gl = this.gl_elems.gl;
78
86
  this.gl_elems.bg_billboard.updateField(field.getStormMotionGrid());
79
87
  const profiles = this.profile_field.profiles;
80
- const hodo_polyline = profiles.map(prof => {
81
- const zoom = getMinZoom(prof['jlat'], prof['ilon'], this.opts.thin_fac);
88
+ const { lats, lons } = this.profile_field.getProfileCoords();
89
+ const min_visible_zoom = this.profile_field.grid.getMinVisibleZoom(this.opts.thin_fac);
90
+ const hodo_polyline = profiles.map((prof, iprof) => {
82
91
  return {
83
- 'offsets': [...prof['u']].map((u, ipt) => [u - prof['smu'], prof['v'][ipt] - prof['smv']]),
84
- 'vertices': [...prof['u']].map(u => [prof['lon'], prof['lat']]),
85
- 'zoom': zoom,
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
+ }),
98
+ 'vertices': [...prof['u']].map(u => [lons[iprof], lats[iprof]]),
99
+ 'zoom': min_visible_zoom[iprof],
86
100
  'data': [...prof['z']],
87
101
  };
88
102
  });
89
- const hodo_line = await PolylineCollection.make(gl, hodo_polyline, { line_width: this.opts.hodo_line_width, cmap: this.opts.height_cmap, offset_scale: this.hodo_scale * this.bg_size });
90
- const sm_polyline = profiles.map(prof => {
91
- const zoom = getMinZoom(prof['jlat'], prof['ilon'], this.opts.thin_fac);
103
+ const hodo_line = await PolylineCollection.make(gl, hodo_polyline, { line_width: this.opts.hodo_line_width, cmap: this.opts.height_cmap,
104
+ offset_scale: this.hodo_scale * this.bg_size, offset_rotates_with_map: false });
105
+ const sm_polyline = profiles.map((prof, iprof) => {
106
+ if (!isStormRelativeWindProfile(prof)) {
107
+ return { vertices: [] };
108
+ }
92
109
  const sm_mag = Math.hypot(prof['smu'], prof['smv']);
93
110
  const sm_ang = Math.PI / 2 - Math.atan2(-prof['smv'], -prof['smu']);
94
111
  const buffer = 2;
95
112
  return {
96
113
  'offsets': [[buffer * Math.sin(sm_ang), buffer * Math.cos(sm_ang)],
97
114
  [sm_mag * Math.sin(sm_ang), sm_mag * Math.cos(sm_ang)]],
98
- 'vertices': [[prof['lon'], prof['lat']], [prof['lon'], prof['lat']]],
99
- 'zoom': zoom
115
+ 'vertices': [[lons[iprof], lats[iprof]], [lons[iprof], lats[iprof]]],
116
+ 'zoom': min_visible_zoom[iprof]
100
117
  };
101
118
  });
102
- const sm_line = await PolylineCollection.make(gl, sm_polyline, { line_width: this.opts.background_line_width, color: this.opts.bgcolor, offset_scale: this.hodo_scale * this.bg_size });
119
+ const sm_line = await PolylineCollection.make(gl, sm_polyline, { line_width: this.opts.background_line_width, color: this.opts.bgcolor,
120
+ offset_scale: this.hodo_scale * this.bg_size, offset_rotates_with_map: false });
103
121
  this.line_elems = {
104
122
  hodo_line: hodo_line, sm_line: sm_line
105
123
  };
@@ -111,7 +129,7 @@ class Hodographs extends PlotComponent {
111
129
  async onAdd(map, gl) {
112
130
  const bg_image = { 'format': gl.RGBA, 'type': gl.UNSIGNED_BYTE, 'image': this.hodo_bg_texture, 'mag_filter': gl.NEAREST };
113
131
  const max_zoom = map.getMaxZoom();
114
- const bg_billboard = new BillboardCollection(this.profile_field.getStormMotionGrid(), this.opts.thin_fac, max_zoom, bg_image, HODO_BG_DIMS, this.bg_size * 0.004, { color: Color.fromHex(this.opts.bgcolor) });
132
+ const bg_billboard = new BillboardCollection(this.profile_field.getStormMotionGrid(), this.opts.thin_fac, max_zoom, bg_image, HODO_BG_DIMS, this.bg_size * 0.004, { color: Color.fromHex(this.opts.bgcolor), rotate_with_map: false });
115
133
  await bg_billboard.setup(gl);
116
134
  this.gl_elems = {
117
135
  gl: gl, map: map, bg_billboard: bg_billboard
@@ -122,7 +140,7 @@ class Hodographs extends PlotComponent {
122
140
  * @internal
123
141
  * Render the hodographs
124
142
  */
125
- render(gl, matrix) {
143
+ render(gl, arg) {
126
144
  if (this.gl_elems === null || this.line_elems === null)
127
145
  return;
128
146
  const gl_elems = this.gl_elems;
@@ -132,9 +150,9 @@ class Hodographs extends PlotComponent {
132
150
  const map_height = gl_elems.map.getCanvas().height;
133
151
  const bearing = gl_elems.map.getBearing();
134
152
  const pitch = gl_elems.map.getPitch();
135
- line_elems.hodo_line.render(gl, matrix, [map_width, map_height], zoom, bearing, pitch);
136
- line_elems.sm_line.render(gl, matrix, [map_width, map_height], zoom, bearing, bearing);
137
- gl_elems.bg_billboard.render(gl, matrix, [map_width, map_height], zoom, bearing, pitch);
153
+ line_elems.hodo_line.render(gl, arg, [map_width, map_height], zoom, bearing, pitch);
154
+ line_elems.sm_line.render(gl, arg, [map_width, map_height], zoom, bearing, bearing);
155
+ gl_elems.bg_billboard.render(gl, arg, [map_width, map_height], zoom, bearing, pitch);
138
156
  }
139
157
  }
140
158
  export default Hodographs;
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 };
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
  }
@@ -130,7 +186,7 @@ function lngFromMercatorX(x) {
130
186
  function mercatorYfromLat(lat) {
131
187
  const sin_lat = Math.sin(lat * Math.PI / 180);
132
188
  const y = (180 - (90 / Math.PI * Math.log((1 + sin_lat) / (1 - sin_lat)))) / 360;
133
- return Math.min(2, Math.max(-2, y));
189
+ return Math.min(1.5, Math.max(-0.5, y));
134
190
  }
135
191
  function latFromMercatorY(y) {
136
192
  return Math.atan(Math.sinh((180 - y * 360) * Math.PI / 180)) * 180 / Math.PI;
@@ -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 };
@@ -1,7 +1,9 @@
1
- import { TypedArray, WebGLAnyRenderingContext } from "./AutumnTypes";
1
+ import { RenderMethodArg, TypedArray, WebGLAnyRenderingContext } from "./AutumnTypes";
2
+ import { StructuredGrid } from "./Grid";
2
3
  import { MapLikeType } from "./Map";
3
4
  import { PlotComponent } from "./PlotComponent";
4
5
  import { RawScalarField } from "./RawField";
6
+ /** Options for {@link Paintball} components */
5
7
  interface PaintballOptions {
6
8
  /**
7
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,7 +22,7 @@ interface PaintballOptions {
20
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
21
23
  * significand of an IEEE 754 float.)
22
24
  */
23
- declare class Paintball<ArrayType extends TypedArray, MapType extends MapLikeType> extends PlotComponent<MapType> {
25
+ declare class Paintball<ArrayType extends TypedArray, GridType extends StructuredGrid, MapType extends MapLikeType> extends PlotComponent<MapType> {
24
26
  private field;
25
27
  readonly opts: Required<PaintballOptions>;
26
28
  private readonly color_components;
@@ -33,12 +35,12 @@ declare class Paintball<ArrayType extends TypedArray, MapType extends MapLikeTyp
33
35
  * `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.
34
36
  * @param opts - Options for creating the paintball plot
35
37
  */
36
- constructor(field: RawScalarField<ArrayType>, opts?: PaintballOptions);
38
+ constructor(field: RawScalarField<ArrayType, GridType>, opts?: PaintballOptions);
37
39
  /**
38
40
  * Update the field displayed as a paintball plot
39
41
  * @param field - The new field to display as a paintball plot
40
42
  */
41
- updateField(field: RawScalarField<ArrayType>): Promise<void>;
43
+ updateField(field: RawScalarField<ArrayType, GridType>): Promise<void>;
42
44
  /**
43
45
  * @internal
44
46
  * Add the paintball plot to a map.
@@ -48,7 +50,7 @@ declare class Paintball<ArrayType extends TypedArray, MapType extends MapLikeTyp
48
50
  * @internal
49
51
  * Render the paintball plot
50
52
  */
51
- render(gl: WebGLAnyRenderingContext, matrix: number[] | Float32Array): void;
53
+ render(gl: WebGLAnyRenderingContext, arg: RenderMethodArg): void;
52
54
  }
53
55
  export default Paintball;
54
56
  export type { PaintballOptions };
package/lib/Paintball.js CHANGED
@@ -1,33 +1,40 @@
1
+ import { getRendererData } from "./AutumnTypes";
1
2
  import { Color } from "./Color";
2
- import { PlotComponent, getGLFormatTypeAlignment } from "./PlotComponent";
3
+ import { PlotComponent } from "./PlotComponent";
4
+ import { ShaderProgramManager } from "./ShaderManager";
3
5
  import { normalizeOptions } from "./utils";
4
- import { WGLProgram, WGLTexture } from "autumn-wgl";
5
- const paintball_vertex_shader_src = `uniform mat4 u_matrix;
6
+ import { WGLTexture } from "autumn-wgl";
7
+ const paintball_vertex_shader_src = `#version 300 es
8
+
6
9
  uniform int u_offset;
7
10
 
8
- attribute vec2 a_pos;
9
- attribute vec2 a_tex_coord;
11
+ in vec2 a_pos;
12
+ in vec2 a_tex_coord;
10
13
 
11
- varying highp vec2 v_tex_coord;
14
+ out highp vec2 v_tex_coord;
12
15
 
13
16
  void main() {
14
17
  float globe_width = 1.;
15
18
  vec2 globe_offset = vec2(globe_width * float(u_offset), 0.);
16
19
 
17
- gl_Position = u_matrix * vec4(a_pos + globe_offset, 0.0, 1.0);
20
+ gl_Position = projectTile(a_pos.xy + globe_offset);
18
21
  v_tex_coord = a_tex_coord;
19
22
  }`
20
- const paintball_fragment_shader_src = `#define MAX_N_COLORS 24
23
+ const paintball_fragment_shader_src = `#version 300 es
24
+
25
+ #define MAX_N_COLORS 24
21
26
 
22
- varying highp vec2 v_tex_coord;
27
+ in highp vec2 v_tex_coord;
23
28
 
24
29
  uniform sampler2D u_fill_sampler;
25
30
  uniform lowp vec4 u_colors[MAX_N_COLORS];
26
31
  uniform int u_num_colors;
27
32
  uniform highp float u_opacity;
28
33
 
34
+ out highp vec4 fragColor;
35
+
29
36
  void main() {
30
- highp float fill_val = texture2D(u_fill_sampler, v_tex_coord).r;
37
+ highp float fill_val = texture(u_fill_sampler, v_tex_coord).r;
31
38
 
32
39
  if (fill_val < 0.5) {
33
40
  discard;
@@ -44,7 +51,7 @@ void main() {
44
51
  }
45
52
 
46
53
  color.a = color.a * u_opacity;
47
- gl_FragColor = color;
54
+ fragColor = color;
48
55
  }`
49
56
  const paintball_opt_defaults = {
50
57
  colors: ['#000000'],
@@ -69,7 +76,7 @@ class Paintball extends PlotComponent {
69
76
  super();
70
77
  this.field = field;
71
78
  this.opts = normalizeOptions(opts, paintball_opt_defaults);
72
- 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();
73
80
  this.gl_elems = null;
74
81
  this.fill_texture = null;
75
82
  }
@@ -83,12 +90,7 @@ class Paintball extends PlotComponent {
83
90
  return;
84
91
  const gl = this.gl_elems.gl;
85
92
  gl.pixelStorei(gl.UNPACK_ALIGNMENT, 2);
86
- const tex_data = this.field.getTextureData();
87
- const { format, type, row_alignment } = getGLFormatTypeAlignment(gl, !(tex_data instanceof Float32Array));
88
- const fill_image = { 'format': format, 'type': type,
89
- 'width': this.field.grid.ni, 'height': this.field.grid.nj, 'image': tex_data,
90
- 'mag_filter': gl.NEAREST, 'row_alignment': row_alignment,
91
- };
93
+ const fill_image = this.field.getWGLTextureSpec(gl, gl.NEAREST);
92
94
  if (this.fill_texture === null) {
93
95
  this.fill_texture = new WGLTexture(gl, fill_image);
94
96
  }
@@ -102,12 +104,12 @@ class Paintball extends PlotComponent {
102
104
  */
103
105
  async onAdd(map, gl) {
104
106
  gl.getExtension('OES_texture_float');
105
- const program = new WGLProgram(gl, paintball_vertex_shader_src, paintball_fragment_shader_src);
106
107
  const { vertices: verts_buf, texcoords: tex_coords_buf } = await this.field.grid.getWGLBuffers(gl);
107
108
  const vertices = verts_buf;
108
109
  const texcoords = tex_coords_buf;
110
+ const shader_manager = new ShaderProgramManager(paintball_vertex_shader_src, paintball_fragment_shader_src, []);
109
111
  this.gl_elems = {
110
- gl: gl, program: program, vertices: vertices, texcoords: texcoords,
112
+ gl: gl, shader_manager: shader_manager, vertices: vertices, texcoords: texcoords,
111
113
  };
112
114
  this.updateField(this.field);
113
115
  }
@@ -115,23 +117,26 @@ class Paintball extends PlotComponent {
115
117
  * @internal
116
118
  * Render the paintball plot
117
119
  */
118
- render(gl, matrix) {
120
+ render(gl, arg) {
119
121
  if (this.gl_elems === null || this.fill_texture === null)
120
122
  return;
121
123
  const gl_elems = this.gl_elems;
122
- if (matrix instanceof Float32Array)
123
- matrix = [...matrix];
124
+ const render_data = getRendererData(arg);
125
+ const program = this.gl_elems.shader_manager.getShaderProgram(gl, render_data.shaderData);
124
126
  // Render to framebuffer
125
- gl_elems.program.use({ 'a_pos': gl_elems.vertices, 'a_tex_coord': gl_elems.texcoords }, { 'u_matrix': matrix, 'u_opacity': this.opts.opacity, 'u_colors': this.color_components, 'u_num_colors': this.opts.colors.length, 'u_offset': 0 }, { 'u_fill_sampler': this.fill_texture });
127
+ program.use({ 'a_pos': gl_elems.vertices, 'a_tex_coord': gl_elems.texcoords }, { 'u_opacity': this.opts.opacity, 'u_colors': this.color_components, 'u_num_colors': this.opts.colors.length, 'u_offset': 0,
128
+ ...this.gl_elems.shader_manager.getShaderUniforms(render_data) }, { 'u_fill_sampler': this.fill_texture });
126
129
  gl.enable(gl.BLEND);
127
130
  gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
128
- gl_elems.program.draw();
129
- gl_elems.program.setUniforms({ 'u_offset': -2 });
130
- gl_elems.program.draw();
131
- gl_elems.program.setUniforms({ 'u_offset': -1 });
132
- gl_elems.program.draw();
133
- gl_elems.program.setUniforms({ 'u_offset': 1 });
134
- gl_elems.program.draw();
131
+ program.draw();
132
+ if (render_data.type != 'maplibre' || !render_data.shaderData.define.includes('GLOBE')) {
133
+ program.setUniforms({ 'u_offset': -2 });
134
+ program.draw();
135
+ program.setUniforms({ 'u_offset': -1 });
136
+ program.draw();
137
+ program.setUniforms({ 'u_offset': 1 });
138
+ program.draw();
139
+ }
135
140
  }
136
141
  }
137
142
  export default Paintball;
@@ -0,0 +1,19 @@
1
+ import { RenderMethodArg, TypedArray, WebGLAnyRenderingContext } from "./AutumnTypes";
2
+ import { Grid } from "./Grid";
3
+ import { MapLikeType } from "./Map";
4
+ import { PlotComponent } from "./PlotComponent";
5
+ import { RawVectorField } from "./RawField";
6
+ interface ParticleTracerOptions {
7
+ }
8
+ declare class ParticleTracer<ArrayType extends TypedArray, GridType extends Grid, MapType extends MapLikeType> extends PlotComponent<MapType> {
9
+ field: RawVectorField<ArrayType, GridType>;
10
+ readonly opts: Required<ParticleTracerOptions>;
11
+ private gl_elems;
12
+ private wind_textures;
13
+ constructor(fields: RawVectorField<ArrayType, GridType>, opts?: ParticleTracerOptions);
14
+ updateField(field: RawVectorField<ArrayType, GridType>): void;
15
+ onAdd(map: MapType, gl: WebGLAnyRenderingContext): Promise<void>;
16
+ onRemove(map: MapType, gl: WebGLAnyRenderingContext): void;
17
+ render(gl: WebGLAnyRenderingContext, args: RenderMethodArg): void;
18
+ }
19
+ export { ParticleTracer };
@@ -0,0 +1,37 @@
1
+ import { WGLTexture } from "autumn-wgl";
2
+ import { PlotComponent } from "./PlotComponent";
3
+ import { normalizeOptions } from "./utils";
4
+ const particle_tracer_opt_defaults = {};
5
+ class ParticleTracer extends PlotComponent {
6
+ constructor(fields, opts) {
7
+ super();
8
+ this.field = fields;
9
+ this.opts = normalizeOptions(opts, particle_tracer_opt_defaults);
10
+ this.gl_elems = null;
11
+ this.wind_textures = null;
12
+ }
13
+ updateField(field) {
14
+ this.field = field;
15
+ if (this.gl_elems === null)
16
+ return;
17
+ const gl = this.gl_elems.gl;
18
+ const { u: u_image, v: v_image } = this.field.getWGLTextureSpecs(gl, gl.NEAREST);
19
+ if (this.wind_textures === null) {
20
+ this.wind_textures = { u: new WGLTexture(gl, u_image), v: new WGLTexture(gl, v_image) };
21
+ }
22
+ else {
23
+ this.wind_textures.u.setImageData(u_image);
24
+ this.wind_textures.v.setImageData(v_image);
25
+ }
26
+ }
27
+ async onAdd(map, gl) {
28
+ const { rotation: proj_rotation_tex } = this.field.grid.getVectorRotationTexture(gl, this.field.relative_to == 'earth');
29
+ this.gl_elems = { gl: gl, proj_rot_texture: proj_rotation_tex };
30
+ this.updateField(this.field);
31
+ }
32
+ onRemove(map, gl) {
33
+ }
34
+ render(gl, args) {
35
+ }
36
+ }
37
+ export { ParticleTracer };
@@ -1,25 +1,25 @@
1
1
  import * as Comlink from 'comlink';
2
2
  import { MapLikeType } from './Map';
3
- import { WebGLAnyRenderingContext } from './AutumnTypes';
3
+ import { RenderMethodArg, TypedArrayStr, WebGLAnyRenderingContext } from './AutumnTypes';
4
4
  declare const layer_worker: Comlink.Remote<{
5
- makeBBElements: (field_lats: Float32Array, field_lons: Float32Array, field_ni: number, field_nj: number, thin_fac_base: number, max_zoom: number) => {
5
+ makeBBElements: (field_lats: Float32Array, field_lons: Float32Array, min_zoom: Uint8Array, field_ni: number, field_nj: number, map_max_zoom: number) => {
6
6
  pts: Float32Array;
7
7
  tex_coords: Float32Array;
8
8
  };
9
9
  makeDomainVerticesAndTexCoords: (field_lats: Float32Array, field_lons: Float32Array, field_ni: number, field_nj: number, texcoord_margin_r: number, texcoord_margin_s: number) => {
10
10
  vertices: Float32Array;
11
11
  tex_coords: Float32Array;
12
- grid_cell_size: Float32Array;
13
12
  };
14
13
  makePolyLines: (lines: import("./AutumnTypes").LineData[]) => import("./AutumnTypes").Polyline;
15
14
  }>;
15
+ /** Base class for all plot components */
16
16
  declare abstract class PlotComponent<MapType extends MapLikeType> {
17
17
  abstract onAdd(map: MapType, gl: WebGLAnyRenderingContext): Promise<void>;
18
- abstract render(gl: WebGLAnyRenderingContext, matrix: number[] | Float32Array): void;
18
+ abstract render(gl: WebGLAnyRenderingContext, arg: RenderMethodArg): void;
19
19
  }
20
- declare function getGLFormatTypeAlignment(gl: WebGLAnyRenderingContext, is_float16: boolean): {
21
- format: 33325 | 33326 | 6409;
22
- type: 36193 | 5131 | 5126;
20
+ declare function getGLFormatTypeAlignment(gl: WebGLAnyRenderingContext, array_dtype: TypedArrayStr): {
21
+ format: 33321 | 33325 | 33326 | 6409;
22
+ type: 36193 | 5131 | 5121 | 5126;
23
23
  row_alignment: number;
24
24
  };
25
25
  export { PlotComponent, layer_worker, getGLFormatTypeAlignment };
@@ -3,12 +3,13 @@ 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
- function getGLFormatTypeAlignment(gl, is_float16) {
9
+ function getGLFormatTypeAlignment(gl, array_dtype) {
9
10
  let format, type, row_alignment;
10
11
  const is_webgl2 = isWebGL2Ctx(gl);
11
- if (is_float16) {
12
+ if (array_dtype == 'float16') {
12
13
  const ext = gl.getExtension('OES_texture_half_float');
13
14
  const ext_lin = gl.getExtension('OES_texture_half_float_linear');
14
15
  if (is_webgl2) {
@@ -23,7 +24,7 @@ function getGLFormatTypeAlignment(gl, is_float16) {
23
24
  }
24
25
  row_alignment = 2;
25
26
  }
26
- else {
27
+ else if (array_dtype == 'float32') {
27
28
  const ext = gl.getExtension('OES_texture_float');
28
29
  const ext_lin = gl.getExtension('OES_texture_float_linear');
29
30
  // As of 11/3/2023, Safari/WebKit on iOS reports as supporting float textures,
@@ -39,6 +40,11 @@ function getGLFormatTypeAlignment(gl, is_float16) {
39
40
  type = gl.FLOAT;
40
41
  row_alignment = 4;
41
42
  }
43
+ else {
44
+ format = is_webgl2 ? gl.R8 : gl.LUMINANCE;
45
+ type = gl.UNSIGNED_BYTE;
46
+ row_alignment = 1;
47
+ }
42
48
  return { format: format, type: type, row_alignment: row_alignment };
43
49
  }
44
50
  export { PlotComponent, layer_worker, getGLFormatTypeAlignment };