autumnplot-gl 2.0.0 → 2.2.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.
@@ -1,7 +1,8 @@
1
- import { PlotComponent } from './PlotComponent';
1
+ import { PlotComponent, getGLFormatTypeAlignment } from './PlotComponent';
2
2
  import { makeTextureImage } from './Colormap';
3
3
  import { WGLProgram, WGLTexture } from 'autumn-wgl';
4
- import { isWebGL2Ctx } from './AutumnTypes';
4
+ import { Float16Array } from '@petamoriken/float16';
5
+ import { Cache } from './utils';
5
6
  const contourfill_vertex_shader_src = `uniform mat4 u_matrix;
6
7
 
7
8
  attribute vec2 a_pos;
@@ -38,18 +39,8 @@ void main() {
38
39
  color.a = color.a * u_opacity;
39
40
  gl_FragColor = color;
40
41
  }`
41
- /**
42
- * A filled contoured field
43
- * &example
44
- * // Create a field of filled contours with the provided color map
45
- * const fill = new ContourFill(wind_speed_field, {cmap: color_map});
46
- */
47
- class ContourFill extends PlotComponent {
48
- /**
49
- * Create a filled contoured field
50
- * &param field - The field to create filled contours from
51
- * &param opts - Options for creating the filled contours
52
- */
42
+ const program_cache = new Cache((gl) => new WGLProgram(gl, contourfill_vertex_shader_src, contourfill_fragment_shader_src));
43
+ class PlotComponentFill extends PlotComponent {
53
44
  constructor(field, opts) {
54
45
  super();
55
46
  this.field = field;
@@ -73,33 +64,33 @@ class ContourFill extends PlotComponent {
73
64
  const alpha = (lev - cmap_norm[jlev]) / (cmap_norm[jlev + 1] - cmap_norm[jlev]);
74
65
  return input_norm[jlev] * (1 - alpha) + input_norm[jlev + 1] * alpha;
75
66
  });
76
- this.index_map = new Float32Array(inv_cmap_norm);
67
+ this.index_map = new Float16Array(inv_cmap_norm);
77
68
  this.gl_elems = null;
69
+ this.image_mag_filter = null;
70
+ this.cmap_mag_filter = null;
78
71
  }
79
- /**
80
- * &internal
81
- * Add the filled contours to a map
82
- */
83
72
  async onAdd(map, gl) {
84
73
  // Basic procedure for the filled contours inspired by https://blog.mbq.me/webgl-weather-globe/
85
- gl.getExtension('OES_texture_float');
86
- gl.getExtension('OES_texture_float_linear');
87
- const program = new WGLProgram(gl, contourfill_vertex_shader_src, contourfill_fragment_shader_src);
74
+ if (this.image_mag_filter === null || this.cmap_mag_filter === null) {
75
+ throw `Implement magnification filtes in a subclass`;
76
+ }
77
+ const program = program_cache.getValue(gl);
88
78
  const { vertices: verts_buf, texcoords: tex_coords_buf } = await this.field.grid.getWGLBuffers(gl);
89
79
  const vertices = verts_buf;
90
80
  const texcoords = tex_coords_buf;
91
- const format = isWebGL2Ctx(gl) ? gl.R32F : gl.LUMINANCE;
92
- const fill_image = { 'format': format, 'type': gl.FLOAT,
93
- 'width': this.field.grid.ni, 'height': this.field.grid.nj, 'image': this.field.data,
94
- 'mag_filter': gl.LINEAR,
81
+ const { format, type, row_alignment } = getGLFormatTypeAlignment(gl, this.field.isFloat16());
82
+ const fill_image = { 'format': format, 'type': type,
83
+ 'width': this.field.grid.ni, 'height': this.field.grid.nj, 'image': this.field.getTextureData(),
84
+ 'mag_filter': this.image_mag_filter, 'row_alignment': row_alignment,
95
85
  };
96
86
  const fill_texture = new WGLTexture(gl, fill_image);
97
- const cmap_image = { 'format': gl.RGBA, 'type': gl.UNSIGNED_BYTE, 'image': this.cmap_image, 'mag_filter': gl.NEAREST };
87
+ const cmap_image = { 'format': gl.RGBA, 'type': gl.UNSIGNED_BYTE, 'image': this.cmap_image, 'mag_filter': this.cmap_mag_filter };
98
88
  const cmap_texture = new WGLTexture(gl, cmap_image);
99
- const cmap_nonlin_image = { 'format': format, 'type': gl.FLOAT,
89
+ const { format: format_nonlin, type: type_nonlin, row_alignment: row_alignment_nonlin } = getGLFormatTypeAlignment(gl, true);
90
+ const cmap_nonlin_image = { 'format': format_nonlin, 'type': type_nonlin,
100
91
  'width': this.index_map.length, 'height': 1,
101
- 'image': this.index_map,
102
- 'mag_filter': gl.LINEAR
92
+ 'image': new Uint16Array(this.index_map.buffer),
93
+ 'mag_filter': gl.LINEAR, 'row_alignment': row_alignment_nonlin,
103
94
  };
104
95
  const cmap_nonlin_texture = new WGLTexture(gl, cmap_nonlin_image);
105
96
  this.gl_elems = {
@@ -107,10 +98,6 @@ class ContourFill extends PlotComponent {
107
98
  fill_texture: fill_texture, cmap_texture: cmap_texture, cmap_nonlin_texture: cmap_nonlin_texture,
108
99
  };
109
100
  }
110
- /**
111
- * &internal
112
- * Render the filled contours
113
- */
114
101
  render(gl, matrix) {
115
102
  if (this.gl_elems === null)
116
103
  return;
@@ -122,4 +109,68 @@ class ContourFill extends PlotComponent {
122
109
  gl_elems.program.draw();
123
110
  }
124
111
  }
125
- export default ContourFill;
112
+ /**
113
+ * A raster (i.e. pixel) plot
114
+ * @example
115
+ * // Create a raster plot with the provided color map
116
+ * const raster = new Raster(wind_speed_field, {cmap: color_map});
117
+ */
118
+ class Raster extends PlotComponentFill {
119
+ /**
120
+ * Create a raster plot
121
+ * @param field - The field to create the raster plot from
122
+ * @param opts - Options for creating the raster plot
123
+ */
124
+ constructor(field, opts) {
125
+ super(field, opts);
126
+ }
127
+ /**
128
+ * @internal
129
+ * Add the raster plot to a map
130
+ */
131
+ async onAdd(map, gl) {
132
+ this.image_mag_filter = gl.NEAREST;
133
+ this.cmap_mag_filter = gl.LINEAR;
134
+ super.onAdd(map, gl);
135
+ }
136
+ /**
137
+ * @internal
138
+ * Render the raster plot
139
+ */
140
+ render(gl, matrix) {
141
+ super.render(gl, matrix);
142
+ }
143
+ }
144
+ /**
145
+ * A filled contoured field
146
+ * @example
147
+ * // Create a field of filled contours with the provided color map
148
+ * const fill = new ContourFill(wind_speed_field, {cmap: color_map});
149
+ */
150
+ class ContourFill extends PlotComponentFill {
151
+ /**
152
+ * Create a filled contoured field
153
+ * @param field - The field to create filled contours from
154
+ * @param opts - Options for creating the filled contours
155
+ */
156
+ constructor(field, opts) {
157
+ super(field, opts);
158
+ }
159
+ /**
160
+ * @internal
161
+ * Add the filled contours to a map
162
+ */
163
+ async onAdd(map, gl) {
164
+ this.image_mag_filter = gl.LINEAR;
165
+ this.cmap_mag_filter = gl.NEAREST;
166
+ super.onAdd(map, gl);
167
+ }
168
+ /**
169
+ * @internal
170
+ * Render the filled contours
171
+ */
172
+ render(gl, matrix) {
173
+ super.render(gl, matrix);
174
+ }
175
+ }
176
+ export { ContourFill, Raster };
@@ -1,8 +1,5 @@
1
1
  /// <reference types="mapbox-gl" />
2
2
  import { PlotComponent } from "./PlotComponent";
3
- import { PolylineCollection } from "./PolylineCollection";
4
- import { BillboardCollection } from "./BillboardCollection";
5
- import { MapType } from "./Map";
6
3
  import { RawProfileField } from "./RawField";
7
4
  import { WebGLAnyRenderingContext } from "./AutumnTypes";
8
5
  interface HodographOptions {
@@ -17,19 +14,12 @@ interface HodographOptions {
17
14
  */
18
15
  thin_fac?: number;
19
16
  }
20
- interface HodographGLElems {
21
- map: MapType;
22
- bg_billboard: BillboardCollection | null;
23
- hodo_line: PolylineCollection | null;
24
- sm_line: PolylineCollection | null;
25
- }
26
17
  /** A class representing a a field of hodograph plots */
27
18
  declare class Hodographs extends PlotComponent {
28
- readonly profile_field: RawProfileField;
19
+ private readonly profile_field;
29
20
  readonly bgcolor: [number, number, number];
30
21
  readonly thin_fac: number;
31
- /** @private */
32
- gl_elems: HodographGLElems;
22
+ private gl_elems;
33
23
  /**
34
24
  * Create a field of hodographs
35
25
  * @param profile_field - The grid of profiles to plot
package/lib/Map.d.ts CHANGED
@@ -8,6 +8,14 @@ interface LambertConformalConicParameters {
8
8
  declare function lambertConformalConic(params: LambertConformalConicParameters): (a: number, b: number, opts?: {
9
9
  inverse: boolean;
10
10
  }) => [number, number];
11
+ interface RotateSphereParams {
12
+ np_lon: number;
13
+ np_lat: number;
14
+ lon_shift: number;
15
+ }
16
+ declare function rotateSphere(params: RotateSphereParams): (a: number, b: number, opts?: {
17
+ inverse: boolean;
18
+ }) => [number, number];
11
19
  /**
12
20
  * A `LngLat` object represents a given longitude and latitude coordinate, measured in degrees.
13
21
  * These coordinates are based on the [WGS84 (EPSG:4326) standard](https://en.wikipedia.org/wiki/World_Geodetic_System#WGS84).
@@ -30,5 +38,5 @@ declare class LngLat {
30
38
  y: number;
31
39
  };
32
40
  }
33
- export { LngLat, lambertConformalConic };
41
+ export { LngLat, lambertConformalConic, rotateSphere };
34
42
  export type { MapType };
package/lib/Map.js CHANGED
@@ -83,6 +83,44 @@ function lambertConformalConic(params) {
83
83
  return opts.inverse ? compute_lcc_inverse(a, b) : compute_lcc(a, b);
84
84
  };
85
85
  }
86
+ function rotateSphere(params) {
87
+ const radians = Math.PI / 180;
88
+ const np_lat = params.np_lat * radians;
89
+ const np_lon = params.np_lon * radians;
90
+ const lon_shift = params.lon_shift * radians;
91
+ const sin_np_lat = Math.sin(np_lat);
92
+ const cos_np_lat = Math.cos(np_lat);
93
+ const compute_rotation = (lon, lat) => {
94
+ lon *= radians;
95
+ lat *= radians;
96
+ const sin_lat = Math.sin(lat);
97
+ const cos_lat = Math.cos(lat);
98
+ const sin_lon_diff = Math.sin(lon - lon_shift);
99
+ const cos_lon_diff = Math.cos(lon - lon_shift);
100
+ const lat_p = Math.asin(sin_np_lat * sin_lat - cos_np_lat * cos_lat * cos_lon_diff);
101
+ let lon_p = np_lon + Math.atan2((cos_lat * sin_lon_diff), (sin_np_lat * cos_lat * cos_lon_diff + cos_np_lat * sin_lat));
102
+ if (lon_p > Math.PI)
103
+ lon_p -= 2 * Math.PI;
104
+ return [lon_p / radians, lat_p / radians];
105
+ };
106
+ const compute_rotation_inverse = (lon_p, lat_p) => {
107
+ lon_p *= radians;
108
+ lat_p *= radians;
109
+ const sin_lat_p = Math.sin(lat_p);
110
+ const cos_lat_p = Math.cos(lat_p);
111
+ const sin_lon_p_diff = Math.sin(lon_p - np_lon);
112
+ const cos_lon_p_diff = Math.cos(lon_p - np_lon);
113
+ const lat = Math.asin(sin_np_lat * sin_lat_p + cos_np_lat * cos_lat_p * cos_lon_p_diff);
114
+ let lon = lon_shift + Math.atan2((cos_lat_p * sin_lon_p_diff), (sin_np_lat * cos_lat_p * cos_lon_p_diff - cos_np_lat * sin_lat_p));
115
+ if (lon_p > Math.PI)
116
+ lon_p -= 2 * Math.PI;
117
+ return [lon / radians, lat / radians];
118
+ };
119
+ return (a, b, opts) => {
120
+ opts = opts === undefined ? { inverse: false } : opts;
121
+ return opts.inverse ? compute_rotation_inverse(a, b) : compute_rotation(a, b);
122
+ };
123
+ }
86
124
  function mercatorXfromLng(lng) {
87
125
  return (180 + lng) / 360;
88
126
  }
@@ -117,4 +155,4 @@ class LngLat {
117
155
  return { x: mercatorXfromLng(this.lng), y: mercatorYfromLat(this.lat) };
118
156
  }
119
157
  }
120
- export { LngLat, lambertConformalConic };
158
+ export { LngLat, lambertConformalConic, rotateSphere };
@@ -1,8 +1,7 @@
1
- import { WebGLAnyRenderingContext } from "./AutumnTypes";
1
+ import { TypedArray, WebGLAnyRenderingContext } from "./AutumnTypes";
2
2
  import { MapType } from "./Map";
3
3
  import { PlotComponent } from "./PlotComponent";
4
4
  import { RawScalarField } from "./RawField";
5
- import { WGLBuffer, WGLProgram, WGLTexture } from "autumn-wgl";
6
5
  interface PaintballOptions {
7
6
  /**
8
7
  * 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.
@@ -14,12 +13,6 @@ interface PaintballOptions {
14
13
  */
15
14
  opacity?: number;
16
15
  }
17
- interface PaintballGLElems {
18
- program: WGLProgram;
19
- vertices: WGLBuffer;
20
- fill_texture: WGLTexture;
21
- texcoords: WGLBuffer;
22
- }
23
16
  /**
24
17
  * A class representing a paintball plot, which is a plot of objects in every member of an ensemble. Objects are usually defined by a single threshold on
25
18
  * a field (such as simulated reflectivity greater than 40 dBZ), but could in theory be defined by any arbitrarily complicated method. In autumnplot-gl,
@@ -27,12 +20,11 @@ interface PaintballGLElems {
27
20
  * 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
28
21
  * significand of an IEEE 754 float.)
29
22
  */
30
- declare class Paintball extends PlotComponent {
31
- readonly field: RawScalarField;
23
+ declare class Paintball<ArrayType extends TypedArray> extends PlotComponent {
24
+ private readonly field;
32
25
  readonly colors: number[];
33
26
  readonly opacity: number;
34
- /** @private */
35
- gl_elems: PaintballGLElems | null;
27
+ private gl_elems;
36
28
  /**
37
29
  * Create a paintball plot
38
30
  * @param field - A scalar field containing the member objects encoded as "bits." The numerical value of each grid point can be constructed like
@@ -40,7 +32,7 @@ declare class Paintball extends PlotComponent {
40
32
  * `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.
41
33
  * @param opts - Options for creating the paintball plot
42
34
  */
43
- constructor(field: RawScalarField, opts?: PaintballOptions);
35
+ constructor(field: RawScalarField<ArrayType>, opts?: PaintballOptions);
44
36
  /**
45
37
  * @internal
46
38
  * Add the paintball plot to a map.
package/lib/Paintball.js CHANGED
@@ -1,6 +1,5 @@
1
- import { isWebGL2Ctx } from "./AutumnTypes";
2
- import { PlotComponent } from "./PlotComponent";
3
- import { hex2rgba } from "./utils";
1
+ import { PlotComponent, getGLFormatTypeAlignment } from "./PlotComponent";
2
+ import { Cache, hex2rgba } from "./utils";
4
3
  import { WGLProgram, WGLTexture } from "autumn-wgl";
5
4
  const paintball_vertex_shader_src = `uniform mat4 u_matrix;
6
5
 
@@ -42,6 +41,7 @@ void main() {
42
41
  color.a = color.a * u_opacity;
43
42
  gl_FragColor = color;
44
43
  }`
44
+ const program_cache = new Cache((gl) => new WGLProgram(gl, paintball_vertex_shader_src, paintball_fragment_shader_src));
45
45
  /**
46
46
  * A class representing a paintball plot, which is a plot of objects in every member of an ensemble. Objects are usually defined by a single threshold on
47
47
  * a field (such as simulated reflectivity greater than 40 dBZ), but could in theory be defined by any arbitrarily complicated method. In autumnplot-gl,
@@ -52,10 +52,10 @@ void main() {
52
52
  class Paintball extends PlotComponent {
53
53
  /**
54
54
  * Create a paintball plot
55
- * &param field - A scalar field containing the member objects encoded as "bits." The numerical value of each grid point can be constructed like
55
+ * @param field - A scalar field containing the member objects encoded as "bits." The numerical value of each grid point can be constructed like
56
56
  * `1.0 * M1 + 2.0 * M2 + 4.0 * M3 + 8.0 * M4 ...`, where `M1` is 1 if that grid point is in an object in member 1 and 0 otherwise,
57
57
  * `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.
58
- * &param opts - Options for creating the paintball plot
58
+ * @param opts - Options for creating the paintball plot
59
59
  */
60
60
  constructor(field, opts) {
61
61
  super();
@@ -67,19 +67,20 @@ class Paintball extends PlotComponent {
67
67
  this.gl_elems = null;
68
68
  }
69
69
  /**
70
- * &internal
70
+ * @internal
71
71
  * Add the paintball plot to a map.
72
72
  */
73
73
  async onAdd(map, gl) {
74
74
  gl.getExtension('OES_texture_float');
75
- const program = new WGLProgram(gl, paintball_vertex_shader_src, paintball_fragment_shader_src);
75
+ const program = program_cache.getValue(gl);
76
76
  const { vertices: verts_buf, texcoords: tex_coords_buf } = await this.field.grid.getWGLBuffers(gl);
77
77
  const vertices = verts_buf;
78
78
  const texcoords = tex_coords_buf;
79
- const format = isWebGL2Ctx(gl) ? gl.R32F : gl.LUMINANCE;
80
- const fill_image = { 'format': format, 'type': gl.FLOAT,
81
- 'width': this.field.grid.ni, 'height': this.field.grid.nj, 'image': this.field.data,
82
- 'mag_filter': gl.NEAREST,
79
+ gl.pixelStorei(gl.UNPACK_ALIGNMENT, 2);
80
+ const { format, type, row_alignment } = getGLFormatTypeAlignment(gl, this.field.isFloat16());
81
+ const fill_image = { 'format': format, 'type': type,
82
+ 'width': this.field.grid.ni, 'height': this.field.grid.nj, 'image': this.field.getTextureData(),
83
+ 'mag_filter': gl.NEAREST, 'row_alignment': row_alignment,
83
84
  };
84
85
  const fill_texture = new WGLTexture(gl, fill_image);
85
86
  this.gl_elems = {
@@ -87,7 +88,7 @@ class Paintball extends PlotComponent {
87
88
  };
88
89
  }
89
90
  /**
90
- * &internal
91
+ * @internal
91
92
  * Render the paintball plot
92
93
  */
93
94
  render(gl, matrix) {
@@ -17,4 +17,9 @@ declare abstract class PlotComponent {
17
17
  abstract onAdd(map: MapType, gl: WebGLAnyRenderingContext): Promise<void>;
18
18
  abstract render(gl: WebGLAnyRenderingContext, matrix: number[]): void;
19
19
  }
20
- export { PlotComponent, layer_worker };
20
+ declare function getGLFormatTypeAlignment(gl: WebGLAnyRenderingContext, is_float16: boolean): {
21
+ format: number;
22
+ type: number;
23
+ row_alignment: number;
24
+ };
25
+ export { PlotComponent, layer_worker, getGLFormatTypeAlignment };
@@ -1,6 +1,39 @@
1
1
  import * as Comlink from 'comlink';
2
+ import { getOS } from "./utils";
3
+ import { isWebGL2Ctx } from './AutumnTypes';
2
4
  const worker = new Worker(new URL('./PlotLayer.worker', import.meta.url));
3
5
  const layer_worker = Comlink.wrap(worker);
4
6
  class PlotComponent {
5
7
  }
6
- export { PlotComponent, layer_worker };
8
+ function getGLFormatTypeAlignment(gl, is_float16) {
9
+ let format, type, row_alignment;
10
+ const is_webgl2 = isWebGL2Ctx(gl);
11
+ if (is_float16) {
12
+ const ext = gl.getExtension('OES_texture_half_float');
13
+ const ext_lin = gl.getExtension('OES_texture_half_float_linear');
14
+ if ((!is_webgl2 && ext === null) || (!is_webgl2 && ext_lin === null)) {
15
+ throw "Float16 data are not supported on this hardware. Try Float32 data instead.";
16
+ }
17
+ format = is_webgl2 ? gl.R16F : gl.LUMINANCE;
18
+ type = is_webgl2 ? gl.HALF_FLOAT : ext.HALF_FLOAT_OES;
19
+ row_alignment = 2;
20
+ }
21
+ else {
22
+ const ext = gl.getExtension('OES_texture_float');
23
+ const ext_lin = gl.getExtension('OES_texture_float_linear');
24
+ // As of 11/3/2023, Safari/WebKit on iOS reports as supporting float textures,
25
+ // but really doesn't. It just silently fails. In WebGL 1, Safari/Webkit returns
26
+ // null for ext_lin here, but in WebGL2, both ext vars are null because they were
27
+ // merged into the standard. This means that to properly fail for iOS devices using
28
+ // WebGL2, we have to hard code an OS check... this OS check also works when uers
29
+ // request desktop-mode websites.
30
+ if ((!is_webgl2 && ext === null) || (!is_webgl2 && ext_lin === null) || (getOS() === 'iOS')) {
31
+ throw "Float32 data are not supported on this hardware. Try Float16 data instead.";
32
+ }
33
+ format = is_webgl2 ? gl.R32F : gl.LUMINANCE;
34
+ type = gl.FLOAT;
35
+ row_alignment = 4;
36
+ }
37
+ return { format: format, type: type, row_alignment: row_alignment };
38
+ }
39
+ export { PlotComponent, layer_worker, getGLFormatTypeAlignment };
@@ -18,7 +18,7 @@ declare abstract class PlotLayerBase {
18
18
  * const barb_layer = new PlotLayer('barbs', wind_barbs);
19
19
  */
20
20
  declare class PlotLayer extends PlotLayerBase {
21
- readonly field: PlotComponent;
21
+ private readonly field;
22
22
  /**
23
23
  * Create a map layer from a field
24
24
  * @param id - A unique id for this layer
@@ -51,14 +51,10 @@ declare class PlotLayer extends PlotLayerBase {
51
51
  * height_layer.setActiveKey('20230112_1200');
52
52
  */
53
53
  declare class MultiPlotLayer extends PlotLayerBase {
54
- /** @private */
55
- fields: Record<string, PlotComponent>;
56
- /** @private */
57
- field_key: string | null;
58
- /** @private */
59
- map: MapType | null;
60
- /** @private */
61
- gl: WebGLAnyRenderingContext | null;
54
+ private fields;
55
+ private field_key;
56
+ private map;
57
+ private gl;
62
58
  /**
63
59
  * Create a time-varying map layer
64
60
  * @param id - A unique id for this layer
@@ -90,7 +86,6 @@ declare class MultiPlotLayer extends PlotLayerBase {
90
86
  * @param dt - The date/time at which the field is valid
91
87
  */
92
88
  addField(field: PlotComponent, key: string): void;
93
- /** @private */
94
- _repaintIfNecessary(old_field_key: string | null): void;
89
+ private repaintIfNecessary;
95
90
  }
96
91
  export { PlotLayer, MultiPlotLayer };
package/lib/PlotLayer.js CHANGED
@@ -73,10 +73,10 @@ class MultiPlotLayer extends PlotLayerBase {
73
73
  this.gl = gl;
74
74
  Object.values(this.fields).forEach(field => {
75
75
  field.onAdd(map, gl).then(res => {
76
- this._repaintIfNecessary(null);
76
+ this.repaintIfNecessary(null);
77
77
  });
78
78
  });
79
- this._repaintIfNecessary(null);
79
+ this.repaintIfNecessary(null);
80
80
  }
81
81
  /**
82
82
  * @internal
@@ -95,7 +95,7 @@ class MultiPlotLayer extends PlotLayerBase {
95
95
  setActiveKey(key) {
96
96
  const old_field_key = this.field_key;
97
97
  this.field_key = key;
98
- this._repaintIfNecessary(old_field_key);
98
+ this.repaintIfNecessary(old_field_key);
99
99
  }
100
100
  /**
101
101
  * Get a list of all dates/times that have been added to the layer
@@ -113,7 +113,7 @@ class MultiPlotLayer extends PlotLayerBase {
113
113
  const old_field_key = this.field_key;
114
114
  if (this.map !== null && this.gl !== null && field !== null) {
115
115
  field.onAdd(this.map, this.gl).then(res => {
116
- this._repaintIfNecessary(null);
116
+ this.repaintIfNecessary(null);
117
117
  });
118
118
  }
119
119
  this.fields[key] = field;
@@ -121,8 +121,7 @@ class MultiPlotLayer extends PlotLayerBase {
121
121
  this.field_key = key;
122
122
  }
123
123
  }
124
- /** @private */
125
- _repaintIfNecessary(old_field_key) {
124
+ repaintIfNecessary(old_field_key) {
126
125
  if (this.map !== null && old_field_key !== this.field_key) {
127
126
  this.map.triggerRepaint();
128
127
  }
@@ -1,15 +1,15 @@
1
- import { WGLBuffer, WGLProgram, WGLTexture, WGLTextureSpec } from "autumn-wgl";
1
+ import { WGLTextureSpec } from "autumn-wgl";
2
2
  import { PolylineSpec, LineSpec, WebGLAnyRenderingContext } from "./AutumnTypes";
3
3
  declare class PolylineCollection {
4
4
  readonly width: number;
5
5
  readonly scale: number;
6
- readonly program: WGLProgram;
7
- readonly origin: WGLBuffer;
8
- readonly offset: WGLBuffer;
9
- readonly extrusion: WGLBuffer;
10
- readonly min_zoom: WGLBuffer;
11
- readonly texture: WGLTexture;
12
- readonly texcoords: WGLBuffer;
6
+ private readonly program;
7
+ private readonly origin;
8
+ private readonly offset;
9
+ private readonly extrusion;
10
+ private readonly min_zoom;
11
+ private readonly texture;
12
+ private readonly texcoords;
13
13
  constructor(gl: WebGLAnyRenderingContext, polyline: PolylineSpec, tex_image: WGLTextureSpec, line_width: number, offset_scale: number);
14
14
  render(gl: WebGLAnyRenderingContext, matrix: number[], [map_width, map_height]: [number, number], map_zoom: number, map_bearing: number, map_pitch: number): void;
15
15
  }
@@ -1,4 +1,5 @@
1
1
  import { WGLBuffer, WGLProgram, WGLTexture } from "autumn-wgl";
2
+ import { Cache } from "./utils";
2
3
  const polyline_vertex_src = `uniform mat4 u_matrix;
3
4
 
4
5
  attribute vec2 a_pos;
@@ -69,11 +70,12 @@ void main() {
69
70
  lowp vec4 tex_color = texture2D(u_sampler, v_tex_coord);
70
71
  gl_FragColor = tex_color;
71
72
  }`
73
+ const program_cache = new Cache((gl) => new WGLProgram(gl, polyline_vertex_src, polyline_fragment_src));
72
74
  class PolylineCollection {
73
75
  constructor(gl, polyline, tex_image, line_width, offset_scale) {
74
76
  this.width = line_width;
75
77
  this.scale = offset_scale;
76
- this.program = new WGLProgram(gl, polyline_vertex_src, polyline_fragment_src);
78
+ this.program = program_cache.getValue(gl);
77
79
  this.origin = new WGLBuffer(gl, polyline['origin'], 2, gl.TRIANGLE_STRIP);
78
80
  this.offset = new WGLBuffer(gl, polyline['verts'], 2, gl.TRIANGLE_STRIP);
79
81
  this.extrusion = new WGLBuffer(gl, polyline['extrusion'], 2, gl.TRIANGLE_STRIP);