autumnplot-gl 3.1.0 → 4.0.0-beta

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 (56) hide show
  1. package/README.md +6 -11
  2. package/dist/110.autumnplot-gl.js +1 -1
  3. package/dist/110.autumnplot-gl.js.map +1 -1
  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 +53 -5
  8. package/lib/AutumnTypes.js +25 -1
  9. package/lib/Barbs.d.ts +23 -6
  10. package/lib/Barbs.js +20 -18
  11. package/lib/BillboardCollection.d.ts +16 -8
  12. package/lib/BillboardCollection.js +107 -59
  13. package/lib/Color.d.ts +57 -0
  14. package/lib/Color.js +163 -0
  15. package/lib/ColorBar.d.ts +12 -1
  16. package/lib/ColorBar.js +9 -7
  17. package/lib/Colormap.d.ts +19 -18
  18. package/lib/Colormap.js +84 -23
  19. package/lib/Contour.d.ts +42 -11
  20. package/lib/Contour.js +67 -58
  21. package/lib/ContourCreator.d.ts +4 -0
  22. package/lib/ContourCreator.js +2 -1
  23. package/lib/Fill.d.ts +27 -16
  24. package/lib/Fill.js +105 -83
  25. package/lib/Grid.d.ts +125 -29
  26. package/lib/Grid.js +303 -95
  27. package/lib/Hodographs.d.ts +24 -6
  28. package/lib/Hodographs.js +28 -24
  29. package/lib/Map.js +1 -1
  30. package/lib/Paintball.d.ts +6 -5
  31. package/lib/Paintball.js +38 -32
  32. package/lib/ParticleTracer.d.ts +19 -0
  33. package/lib/ParticleTracer.js +37 -0
  34. package/lib/PlotComponent.d.ts +6 -7
  35. package/lib/PlotComponent.js +17 -7
  36. package/lib/PlotLayer.d.ts +4 -4
  37. package/lib/PlotLayer.worker.d.ts +1 -2
  38. package/lib/PlotLayer.worker.js +22 -57
  39. package/lib/PolylineCollection.d.ts +18 -9
  40. package/lib/PolylineCollection.js +124 -89
  41. package/lib/RawField.d.ts +76 -23
  42. package/lib/RawField.js +138 -29
  43. package/lib/ShaderManager.d.ts +12 -0
  44. package/lib/ShaderManager.js +58 -0
  45. package/lib/StationPlot.d.ts +145 -0
  46. package/lib/StationPlot.js +205 -0
  47. package/lib/TextCollection.d.ts +12 -8
  48. package/lib/TextCollection.js +113 -71
  49. package/lib/cpp/marchingsquares.js +483 -585
  50. package/lib/cpp/marchingsquares.wasm +0 -0
  51. package/lib/cpp/marchingsquares_embind.d.ts +23 -3
  52. package/lib/index.d.ts +7 -4
  53. package/lib/index.js +5 -3
  54. package/lib/utils.d.ts +5 -8
  55. package/lib/utils.js +12 -83
  56. package/package.json +2 -2
package/lib/Contour.js CHANGED
@@ -1,13 +1,18 @@
1
1
  import { LngLat } from './Map';
2
2
  import { PlotComponent } from './PlotComponent';
3
- import { PolylineCollection } from './PolylineCollection';
3
+ import { PolylineCollection, isLineStyle } from './PolylineCollection';
4
4
  import { TextCollection } from './TextCollection';
5
- import { hex2rgb, normalizeOptions } from './utils';
6
- import { kdTree } from 'kd-tree-javascript';
5
+ import { Color } from './Color';
6
+ import { normalizeOptions } from './utils';
7
+ import { UnstructuredGrid } from './Grid';
7
8
  const contour_opt_defaults = {
8
9
  color: '#000000',
10
+ cmap: null,
9
11
  interval: 1,
10
- levels: undefined
12
+ levels: null,
13
+ line_width: 2,
14
+ line_style: '-',
15
+ quad_as_tri: false
11
16
  };
12
17
  /**
13
18
  * A field of contoured data.
@@ -39,14 +44,51 @@ class Contour extends PlotComponent {
39
44
  return;
40
45
  const gl = this.gl_elems.gl;
41
46
  const contour_data = await this.getContours();
42
- const line_data = Object.values(contour_data).flat().map(c => {
43
- return { vertices: c };
47
+ const line_data = [];
48
+ // Make contour data and sort them by line width and line style
49
+ Object.entries(contour_data).forEach(([cv, cd]) => {
50
+ const cv_ = parseFloat(cv);
51
+ const contour_style = isLineStyle(this.opts.line_style) ? this.opts.line_style : this.opts.line_style(cv_);
52
+ const contour_width = typeof this.opts.line_width === 'number' ? this.opts.line_width : this.opts.line_width(cv_);
53
+ const polyline_data = cd.map(c => {
54
+ const ld = { vertices: c };
55
+ if (this.opts.cmap !== null) {
56
+ ld.data = c.map(() => cv_);
57
+ }
58
+ return ld;
59
+ });
60
+ const line_data_filtered = line_data.filter(ld => ld.line_style == contour_style && ld.line_width == contour_width);
61
+ let contour_line_data;
62
+ if (line_data_filtered.length == 0) {
63
+ contour_line_data = { data: [], line_width: contour_width, line_style: contour_style };
64
+ line_data.push(contour_line_data);
65
+ }
66
+ else {
67
+ contour_line_data = line_data_filtered[0];
68
+ }
69
+ contour_line_data.data = contour_line_data.data.concat(polyline_data);
70
+ });
71
+ // Make one PolylineCollection for each combination of line width and line style
72
+ const promises = line_data.map(async (ld) => {
73
+ const plc_opts = { line_width: ld.line_width, line_style: ld.line_style };
74
+ if (this.opts.cmap !== null) {
75
+ plc_opts.cmap = this.opts.cmap;
76
+ }
77
+ else {
78
+ plc_opts.color = this.opts.color;
79
+ }
80
+ return await PolylineCollection.make(gl, ld.data, plc_opts);
81
+ });
82
+ Promise.all(promises).then(values => {
83
+ if (this.gl_elems === null)
84
+ return;
85
+ this.contours = values;
86
+ this.gl_elems.map.triggerRepaint();
44
87
  });
45
- this.contours = await PolylineCollection.make(gl, line_data, { line_width: 2, color: this.opts.color });
46
- this.gl_elems.map.triggerRepaint();
47
88
  }
48
89
  async getContours() {
49
- return await this.field.getContours({ interval: this.opts.interval, levels: this.opts.levels });
90
+ const levels = this.opts.levels === null ? undefined : this.opts.levels;
91
+ return await this.field.getContours({ interval: this.opts.interval, levels: levels, quad_as_tri: this.opts.quad_as_tri });
50
92
  }
51
93
  /**
52
94
  * @internal
@@ -62,18 +104,16 @@ class Contour extends PlotComponent {
62
104
  * @internal
63
105
  * Render the contours
64
106
  */
65
- render(gl, matrix) {
107
+ render(gl, arg) {
66
108
  if (this.gl_elems === null || this.contours === null)
67
109
  return;
68
110
  const gl_elems = this.gl_elems;
69
- if (matrix instanceof Float32Array)
70
- matrix = [...matrix];
71
111
  const zoom = gl_elems.map.getZoom();
72
112
  const map_width = gl_elems.map.getCanvas().width;
73
113
  const map_height = gl_elems.map.getCanvas().height;
74
114
  const bearing = gl_elems.map.getBearing();
75
115
  const pitch = gl_elems.map.getPitch();
76
- this.contours.render(gl, matrix, [map_width, map_height], zoom, bearing, pitch);
116
+ this.contours.forEach(cnt => cnt.render(gl, arg, [map_width, map_height], zoom, bearing, pitch));
77
117
  }
78
118
  }
79
119
  const contour_label_opt_defaults = {
@@ -83,7 +123,8 @@ const contour_label_opt_defaults = {
83
123
  font_url_template: '',
84
124
  text_color: '#000000',
85
125
  halo_color: '#000000',
86
- halo: false
126
+ halo: false,
127
+ density: 1
87
128
  };
88
129
  class ContourLabels extends PlotComponent {
89
130
  constructor(contours, opts) {
@@ -103,14 +144,15 @@ class ContourLabels extends PlotComponent {
103
144
  const gl = this.gl_elems.gl;
104
145
  const map_style = map.getStyle();
105
146
  const font_url_template = this.opts.font_url_template == '' ? map_style.glyphs : this.opts.font_url_template;
106
- const font_url = font_url_template.replace('{range}', '0-255').replace('{fontstack}', this.opts.font_face);
147
+ if (font_url_template === undefined)
148
+ throw "The map style doesn't have any glyph information. Please pass the font_url_template option to ContourLabels";
149
+ const font_url = font_url_template.replace('{fontstack}', this.opts.font_face);
107
150
  const label_pos = [];
108
151
  const contour_data = await this.contours.getContours();
109
152
  const contour_levels = Object.keys(contour_data).map(parseFloat);
110
153
  contour_levels.sort((a, b) => a - b);
111
154
  const map_max_zoom = map.getMaxZoom();
112
- const contour_label_spacing = 0.01 * Math.pow(2, 7 - map_max_zoom);
113
- let min_label_lat = null, max_label_lat = null, min_label_lon = null, max_label_lon = null;
155
+ const contour_label_spacing = 0.006 / this.opts.density * Math.pow(2, 7 - map_max_zoom);
114
156
  Object.entries(contour_data).forEach(([level, contours]) => {
115
157
  const icntr = (parseFloat(level) - contour_levels[0]);
116
158
  const level_str = level.toString();
@@ -139,54 +181,21 @@ class ContourLabels extends PlotComponent {
139
181
  const alpha = (target_dist - dist[idist - 1]) / (dist[idist] - dist[idist - 1]);
140
182
  const pt_lon = (1 - alpha) * pt1[0] + alpha * pt2[0];
141
183
  const pt_lat = (1 - alpha) * pt1[1] + alpha * pt2[1];
142
- if (min_label_lon === null || pt_lon < min_label_lon)
143
- min_label_lon = pt_lon;
144
- if (max_label_lon === null || pt_lon > max_label_lon)
145
- max_label_lon = pt_lon;
146
- if (min_label_lat === null || pt_lat < min_label_lat)
147
- min_label_lat = pt_lat;
148
- if (max_label_lat === null || pt_lat > max_label_lat)
149
- max_label_lat = pt_lat;
150
- label_pos.push({ lon: pt_lon, lat: pt_lat, min_zoom: map_max_zoom, text: level_str });
184
+ label_pos.push({ coord: { lon: pt_lon, lat: pt_lat }, text: level_str });
151
185
  n_labels_placed++;
152
186
  }
153
187
  }
154
188
  });
155
189
  });
156
- const tree = new kdTree(label_pos, (a, b) => Math.hypot(a.lon - b.lon, a.lat - b.lat), ['lon', 'lat']);
157
- const { x: min_label_x, y: max_label_y } = new LngLat(min_label_lon, min_label_lat).toMercatorCoord();
158
- const { x: max_label_x, y: min_label_y } = new LngLat(max_label_lon, max_label_lat).toMercatorCoord();
159
- const thin_grid_width = max_label_x - min_label_x;
160
- const thin_grid_height = max_label_y - min_label_y;
161
- const ni_thin_grid = Math.round(4 * thin_grid_width / contour_label_spacing);
162
- const nj_thin_grid = Math.round(4 * thin_grid_height / contour_label_spacing);
163
- const thin_grid_xs = [];
164
- const thin_grid_ys = [];
165
- for (let idx = 0; idx < ni_thin_grid; idx++) {
166
- thin_grid_xs.push(min_label_x + (idx / ni_thin_grid) * thin_grid_width);
167
- }
168
- for (let jdy = 0; jdy < nj_thin_grid; jdy++) {
169
- thin_grid_ys.push(min_label_y + (jdy / nj_thin_grid) * thin_grid_height);
170
- }
171
- let skip = 1;
172
- for (let zoom = map_max_zoom - 1; zoom >= 0; zoom--) {
173
- for (let idx = 0; idx < ni_thin_grid; idx += skip) {
174
- for (let jdy = 0; jdy < nj_thin_grid; jdy += skip) {
175
- const grid_x = thin_grid_xs[idx];
176
- const grid_y = thin_grid_ys[jdy];
177
- const ll = LngLat.fromMercatorCoord(grid_x, grid_y);
178
- const [label, dist] = tree.nearest({ lon: ll.lng, lat: ll.lat, min_zoom: 0, text: "" }, 1)[0];
179
- label.min_zoom = zoom;
180
- }
181
- }
182
- skip *= 2;
183
- }
190
+ const label_grid = new UnstructuredGrid(label_pos.map(lp => lp.coord));
191
+ const min_zoom = label_grid.getMinVisibleZoom(4);
192
+ const text_specs = label_pos.map((lp, ilp) => ({ ...lp.coord, min_zoom: min_zoom[ilp], text: lp.text }));
184
193
  const tc_opts = {
185
194
  horizontal_align: 'center', vertical_align: 'middle', font_size: this.opts.font_size,
186
195
  halo: this.opts.halo,
187
- text_color: hex2rgb(this.opts.text_color), halo_color: hex2rgb(this.opts.halo_color),
196
+ text_color: Color.fromHex(this.opts.text_color), halo_color: Color.fromHex(this.opts.halo_color),
188
197
  };
189
- this.text_collection = await TextCollection.make(gl, label_pos, font_url, tc_opts);
198
+ this.text_collection = await TextCollection.make(gl, text_specs, font_url, tc_opts);
190
199
  map.triggerRepaint();
191
200
  }
192
201
  /**
@@ -203,14 +212,14 @@ class ContourLabels extends PlotComponent {
203
212
  * @internal
204
213
  * Render the contour labels
205
214
  */
206
- render(gl, matrix) {
215
+ render(gl, arg) {
207
216
  if (this.gl_elems === null || this.text_collection === null)
208
217
  return;
209
218
  const gl_elems = this.gl_elems;
210
219
  const map_width = gl_elems.map.getCanvas().width;
211
220
  const map_height = gl_elems.map.getCanvas().height;
212
221
  const map_zoom = gl_elems.map.getZoom();
213
- this.text_collection.render(gl, matrix, [map_width, map_height], map_zoom);
222
+ this.text_collection.render(gl, arg, [map_width, map_height], map_zoom);
214
223
  }
215
224
  }
216
225
  export default Contour;
@@ -12,6 +12,10 @@ interface FieldContourOpts {
12
12
  * Contour the field at these specific levels.
13
13
  */
14
14
  levels?: number[];
15
+ /**
16
+ * Add triangles in the contouring, which takes longer and generates more detailed (not necessarily smoother or better) contours
17
+ */
18
+ quad_as_tri?: boolean;
15
19
  }
16
20
  declare function contourCreator<ArrayType extends TypedArray>(data: ArrayType, grid: Grid, opts: FieldContourOpts): Promise<ContourData>;
17
21
  export { contourCreator, initMSModule };
@@ -12,12 +12,13 @@ async function contourCreator(data, grid, opts) {
12
12
  throw "Must supply either an interval or levels to contourCreator()";
13
13
  }
14
14
  const interval = opts.interval === undefined ? 0 : opts.interval;
15
+ const quad_as_tri = opts.quad_as_tri === undefined ? false : opts.quad_as_tri;
15
16
  const msm = await initMSModule();
16
17
  const grid_coords = grid.getGridCoords();
17
18
  const getContourLevels = data instanceof Float32Array ? msm.getContourLevelsFloat32 : msm.getContourLevelsFloat16;
18
19
  const makeContours = data instanceof Float32Array ? msm.makeContoursFloat32 : msm.makeContoursFloat16;
19
20
  const levels = opts.levels === undefined ? getContourLevels(data, grid.ni, grid.nj, interval) : opts.levels;
20
- const contours = makeContours(data, grid_coords.x, grid_coords.y, levels, (x, y) => grid.transform(x, y, { inverse: true }));
21
+ const contours = makeContours(data, grid_coords.x, grid_coords.y, levels, (x, y) => grid.transform(x, y, { inverse: true }), quad_as_tri);
21
22
  return contours;
22
23
  }
23
24
  export { contourCreator, initMSModule };
package/lib/Fill.d.ts CHANGED
@@ -2,10 +2,16 @@ import { PlotComponent } from './PlotComponent';
2
2
  import { ColorMap } from './Colormap';
3
3
  import { RawScalarField } from './RawField';
4
4
  import { MapLikeType } from './Map';
5
- import { TypedArray, WebGLAnyRenderingContext } from './AutumnTypes';
5
+ import { RenderMethodArg, TypedArray, WebGLAnyRenderingContext } from './AutumnTypes';
6
+ import { StructuredGrid } from './Grid';
6
7
  interface ContourFillOptions {
7
- /** The color map to use when creating the fills */
8
- cmap: ColorMap;
8
+ /** The color maps to use when creating the fills */
9
+ cmap: ColorMap | ColorMap[];
10
+ /**
11
+ * A mask specifying where to use each color map. This should be on the same grid as the RawScalarField passed.
12
+ * A 1 in the mask means to use the first colormap, a 2 means to use the second colormap, etc.
13
+ */
14
+ cmap_mask?: Uint8Array | null;
9
15
  /**
10
16
  * The opacity for the filled contours
11
17
  * @default 1
@@ -14,26 +20,31 @@ interface ContourFillOptions {
14
20
  }
15
21
  interface RasterOptions {
16
22
  /** The color map to use when creating the raster plot */
17
- cmap: ColorMap;
23
+ cmap: ColorMap | ColorMap[];
24
+ /**
25
+ * A mask specifying where to use each color map. This should be on the same grid as the RawScalarField passed.
26
+ * A 1 in the mask means to use the first colormap, a 2 means to use the second colormap, etc.
27
+ */
28
+ cmap_mask?: Uint8Array | null;
18
29
  /**
19
30
  * The opacity for the raster plot
20
31
  * @default 1
21
32
  */
22
33
  opacity?: number;
23
34
  }
24
- declare class PlotComponentFill<ArrayType extends TypedArray, MapType extends MapLikeType> extends PlotComponent<MapType> {
35
+ declare class PlotComponentFill<ArrayType extends TypedArray, GridType extends StructuredGrid, MapType extends MapLikeType> extends PlotComponent<MapType> {
25
36
  private field;
26
37
  readonly opts: Required<ContourFillOptions>;
27
- private readonly cmap_image;
28
- private readonly index_map;
38
+ private readonly cmap_gpu;
29
39
  private gl_elems;
30
40
  private fill_texture;
41
+ private mask_texture;
31
42
  protected image_mag_filter: number | null;
32
43
  protected cmap_mag_filter: number | null;
33
- constructor(field: RawScalarField<ArrayType>, opts: ContourFillOptions);
34
- updateField(field: RawScalarField<ArrayType>): Promise<void>;
44
+ constructor(field: RawScalarField<ArrayType, GridType>, opts: ContourFillOptions);
45
+ updateField(field: RawScalarField<ArrayType, GridType>, mask?: Uint8Array): Promise<void>;
35
46
  onAdd(map: MapType, gl: WebGLAnyRenderingContext): Promise<void>;
36
- render(gl: WebGLAnyRenderingContext, matrix: number[] | Float32Array): void;
47
+ render(gl: WebGLAnyRenderingContext, arg: RenderMethodArg): void;
37
48
  }
38
49
  /**
39
50
  * A raster (i.e. pixel) plot
@@ -41,18 +52,18 @@ declare class PlotComponentFill<ArrayType extends TypedArray, MapType extends Ma
41
52
  * // Create a raster plot with the provided color map
42
53
  * const raster = new Raster(wind_speed_field, {cmap: color_map});
43
54
  */
44
- declare class Raster<ArrayType extends TypedArray, MapType extends MapLikeType> extends PlotComponentFill<ArrayType, MapType> {
55
+ declare class Raster<ArrayType extends TypedArray, GridType extends StructuredGrid, MapType extends MapLikeType> extends PlotComponentFill<ArrayType, GridType, MapType> {
45
56
  /**
46
57
  * Create a raster plot
47
58
  * @param field - The field to create the raster plot from
48
59
  * @param opts - Options for creating the raster plot
49
60
  */
50
- constructor(field: RawScalarField<ArrayType>, opts: RasterOptions);
61
+ constructor(field: RawScalarField<ArrayType, GridType>, opts: RasterOptions);
51
62
  /**
52
63
  * Update the data displayed as a raster plot
53
64
  * @param field - The new field to display as a raster plot
54
65
  */
55
- updateField(field: RawScalarField<ArrayType>): Promise<void>;
66
+ updateField(field: RawScalarField<ArrayType, GridType>, mask?: Uint8Array): Promise<void>;
56
67
  /**
57
68
  * @internal
58
69
  * Add the raster plot to a map
@@ -70,18 +81,18 @@ declare class Raster<ArrayType extends TypedArray, MapType extends MapLikeType>
70
81
  * // Create a field of filled contours with the provided color map
71
82
  * const fill = new ContourFill(wind_speed_field, {cmap: color_map});
72
83
  */
73
- declare class ContourFill<ArrayType extends TypedArray, MapType extends MapLikeType> extends PlotComponentFill<ArrayType, MapType> {
84
+ declare class ContourFill<ArrayType extends TypedArray, GridType extends StructuredGrid, MapType extends MapLikeType> extends PlotComponentFill<ArrayType, GridType, MapType> {
74
85
  /**
75
86
  * Create a filled contoured field
76
87
  * @param field - The field to create filled contours from
77
88
  * @param opts - Options for creating the filled contours
78
89
  */
79
- constructor(field: RawScalarField<ArrayType>, opts: ContourFillOptions);
90
+ constructor(field: RawScalarField<ArrayType, GridType>, opts: ContourFillOptions);
80
91
  /**
81
92
  * Update the data displayed as filled contours
82
93
  * @param field - The new field to display as filled contours
83
94
  */
84
- updateField(field: RawScalarField<ArrayType>): Promise<void>;
95
+ updateField(field: RawScalarField<ArrayType, GridType>, mask?: Uint8Array): Promise<void>;
85
96
  /**
86
97
  * @internal
87
98
  * Add the filled contours to a map
package/lib/Fill.js CHANGED
@@ -1,62 +1,70 @@
1
- import { PlotComponent, getGLFormatTypeAlignment } from './PlotComponent';
2
- import { ColorMap, makeIndexMap, makeTextureImage } from './Colormap';
3
- import { WGLProgram, WGLTexture } from 'autumn-wgl';
4
- import { hex2rgb, normalizeOptions } from './utils';
5
- const contourfill_vertex_shader_src = `uniform mat4 u_matrix;
1
+ import { PlotComponent } from './PlotComponent';
2
+ import { ColorMap, ColorMapGPUInterface } from './Colormap';
3
+ import { WGLTexture } from 'autumn-wgl';
4
+ import { RawScalarField } from './RawField';
5
+ import { getRendererData } from './AutumnTypes';
6
+ import { normalizeOptions } from './utils';
7
+ import { ShaderProgramManager } from './ShaderManager';
8
+ const contourfill_vertex_shader_src = `#version 300 es
9
+
6
10
  uniform int u_offset;
7
11
 
8
- attribute vec2 a_pos;
9
- attribute vec2 a_tex_coord;
12
+ in vec2 a_pos;
13
+ in vec2 a_tex_coord;
10
14
 
11
- varying highp vec2 v_tex_coord;
15
+ out highp vec2 v_tex_coord;
12
16
 
13
17
  void main() {
14
18
  float globe_width = 1.;
15
19
  vec2 globe_offset = vec2(globe_width * float(u_offset), 0.);
16
20
 
17
- gl_Position = u_matrix * vec4(a_pos + globe_offset, 0.0, 1.0);
21
+ gl_Position = projectTile(a_pos.xy + globe_offset);
18
22
  v_tex_coord = a_tex_coord;
19
23
  }`
20
- const contourfill_fragment_shader_src = `varying highp vec2 v_tex_coord;
24
+ const contourfill_fragment_shader_src = `#version 300 es
25
+
26
+ in highp vec2 v_tex_coord;
21
27
 
22
28
  uniform sampler2D u_fill_sampler;
23
- uniform sampler2D u_cmap_sampler;
24
- uniform sampler2D u_cmap_nonlin_sampler;
25
- uniform highp float u_cmap_min;
26
- uniform highp float u_cmap_max;
29
+
30
+ #ifdef MASK
31
+ uniform sampler2D u_mask_sampler;
32
+ #endif
33
+
27
34
  uniform highp float u_opacity;
28
- uniform highp vec4 u_underflow_color;
29
- uniform highp vec4 u_overflow_color;
30
- uniform int u_n_index;
35
+ #ifdef MASK
36
+ uniform int u_mask_val;
37
+ #endif
38
+
39
+ out highp vec4 fragColor;
31
40
 
32
41
  void main() {
33
- lowp float index_buffer = 1. / (2. * float(u_n_index));
34
- highp float fill_val = texture2D(u_fill_sampler, v_tex_coord).r;
35
- lowp float normed_val = (fill_val - u_cmap_min) / (u_cmap_max - u_cmap_min);
36
-
37
- lowp vec4 color;
38
- if (normed_val < 0.0) {
39
- color = u_underflow_color;
40
- }
41
- else if (normed_val > 1.0) {
42
- color = u_overflow_color;
43
- }
44
- else {
45
- normed_val = index_buffer + normed_val * (1. - 2. * index_buffer);
46
- highp float nonlin_val = texture2D(u_cmap_nonlin_sampler, vec2(normed_val, 0.5)).r;
47
- color = texture2D(u_cmap_sampler, vec2(nonlin_val, 0.5));
42
+ highp float fill_val = texture(u_fill_sampler, v_tex_coord).r;
43
+
44
+ int draw_mask = 1;
45
+
46
+ #ifdef MASK
47
+ highp float mask_val = texture(u_mask_sampler, v_tex_coord).r;
48
+ draw_mask = int(mask_val * 255.0) == u_mask_val ? 1 : 0;
49
+ #endif
50
+
51
+ if (isnan(fill_val) || draw_mask == 0) {
52
+ discard;
48
53
  }
49
54
 
55
+ lowp vec4 color = apply_colormap(fill_val);
50
56
  color.a = color.a * u_opacity;
51
- gl_FragColor = color;
57
+ fragColor = color;
52
58
  }`
53
59
  const default_cmap = new ColorMap([0, 1], ['#000000'], { overflow_color: '#000000', underflow_color: '#000000' });
54
60
  const contour_fill_opt_defaults = {
55
- cmap: default_cmap,
61
+ cmap: [default_cmap],
62
+ cmap_mask: null,
56
63
  opacity: 1,
57
64
  };
58
65
  const raster_opt_defaults = {
59
- cmap: default_cmap,
66
+ cmap: [default_cmap],
67
+ cmap_mask: null,
60
68
  opacity: 1,
61
69
  };
62
70
  class PlotComponentFill extends PlotComponent {
@@ -64,77 +72,91 @@ class PlotComponentFill extends PlotComponent {
64
72
  super();
65
73
  this.field = field;
66
74
  this.opts = normalizeOptions(opts, contour_fill_opt_defaults);
67
- this.cmap_image = makeTextureImage(this.opts.cmap);
68
- this.index_map = makeIndexMap(this.opts.cmap);
75
+ this.opts.cmap = Array.isArray(this.opts.cmap) ? this.opts.cmap : [this.opts.cmap];
76
+ this.cmap_gpu = this.opts.cmap.map(cm => new ColorMapGPUInterface(cm));
69
77
  this.gl_elems = null;
70
78
  this.fill_texture = null;
79
+ this.mask_texture = null;
71
80
  this.image_mag_filter = null;
72
81
  this.cmap_mag_filter = null;
73
82
  }
74
- async updateField(field) {
83
+ async updateField(field, mask) {
75
84
  this.field = field;
85
+ if (this.image_mag_filter === null || this.cmap_mag_filter === null) {
86
+ throw `Implement magnification filters in a subclass`;
87
+ }
76
88
  if (this.gl_elems === null)
77
89
  return;
78
90
  const gl = this.gl_elems.gl;
79
91
  const map = this.gl_elems.map;
80
- const tex_data = this.field.getTextureData();
81
- const { format, type, row_alignment } = getGLFormatTypeAlignment(gl, !(tex_data instanceof Float32Array));
82
- const fill_image = { 'format': format, 'type': type,
83
- 'width': this.field.grid.ni, 'height': this.field.grid.nj, 'image': tex_data,
84
- 'mag_filter': this.image_mag_filter, 'row_alignment': row_alignment,
85
- };
92
+ const fill_image = this.field.getWGLTextureSpec(gl, this.image_mag_filter);
86
93
  if (this.fill_texture === null) {
87
94
  this.fill_texture = new WGLTexture(gl, fill_image);
88
95
  }
89
96
  else {
90
97
  this.fill_texture.setImageData(fill_image);
91
98
  }
99
+ if (mask !== undefined) {
100
+ if (this.opts.cmap_mask === null) {
101
+ console.warn("A mask was passed to updateField on a Fill component that didn't have a mask. The updated mask will be ignored.");
102
+ return;
103
+ }
104
+ const mask_field = new RawScalarField(this.field.grid, mask);
105
+ const mask_image = mask_field.getWGLTextureSpec(gl, gl.NEAREST);
106
+ if (this.mask_texture === null) {
107
+ this.mask_texture = new WGLTexture(gl, mask_image);
108
+ }
109
+ else {
110
+ this.mask_texture.setImageData(mask_image);
111
+ }
112
+ }
92
113
  map.triggerRepaint();
93
114
  }
94
115
  async onAdd(map, gl) {
95
116
  // Basic procedure for the filled contours inspired by https://blog.mbq.me/webgl-weather-globe/
96
- if (this.image_mag_filter === null || this.cmap_mag_filter === null) {
97
- throw `Implement magnification filtes in a subclass`;
117
+ const { vertices: vertices, texcoords: texcoords } = await this.field.grid.getWGLBuffers(gl);
118
+ this.cmap_gpu.forEach(cmg => {
119
+ if (this.image_mag_filter === null || this.cmap_mag_filter === null) {
120
+ throw `Implement magnification filters in a subclass`;
121
+ }
122
+ cmg.setupShaderVariables(gl, this.cmap_mag_filter);
123
+ });
124
+ const shader_defines = [];
125
+ if (this.opts.cmap_mask !== null) {
126
+ shader_defines.push('MASK');
98
127
  }
99
- const program = new WGLProgram(gl, contourfill_vertex_shader_src, contourfill_fragment_shader_src);
100
- const { vertices: verts_buf, texcoords: tex_coords_buf } = await this.field.grid.getWGLBuffers(gl);
101
- const vertices = verts_buf;
102
- const texcoords = tex_coords_buf;
103
- const cmap_image = { 'format': gl.RGBA, 'type': gl.UNSIGNED_BYTE, 'image': this.cmap_image, 'mag_filter': this.cmap_mag_filter };
104
- const cmap_texture = new WGLTexture(gl, cmap_image);
105
- const { format: format_nonlin, type: type_nonlin, row_alignment: row_alignment_nonlin } = getGLFormatTypeAlignment(gl, true);
106
- const cmap_nonlin_image = { 'format': format_nonlin, 'type': type_nonlin,
107
- 'width': this.index_map.length, 'height': 1,
108
- 'image': new Uint16Array(this.index_map.buffer),
109
- 'mag_filter': gl.LINEAR, 'row_alignment': row_alignment_nonlin,
110
- };
111
- const cmap_nonlin_texture = new WGLTexture(gl, cmap_nonlin_image);
128
+ const shader_manger = new ShaderProgramManager(contourfill_vertex_shader_src, ColorMapGPUInterface.applyShader(contourfill_fragment_shader_src), shader_defines);
112
129
  this.gl_elems = {
113
- gl: gl, map: map, program: program, vertices: vertices, texcoords: texcoords,
114
- cmap_texture: cmap_texture, cmap_nonlin_texture: cmap_nonlin_texture,
130
+ gl: gl, shader_manager: shader_manger, map: map, vertices: vertices, texcoords: texcoords,
115
131
  };
116
- this.updateField(this.field);
132
+ this.updateField(this.field, this.opts.cmap_mask === null ? undefined : this.opts.cmap_mask);
117
133
  }
118
- render(gl, matrix) {
134
+ render(gl, arg) {
119
135
  if (this.gl_elems === null || this.fill_texture === null)
120
136
  return;
121
137
  const gl_elems = this.gl_elems;
122
- if (matrix instanceof Float32Array)
123
- matrix = [...matrix];
124
- const cmap = this.opts.cmap;
125
- const underflow_color = cmap.underflow_color === null ? [0, 0, 0, 0] : hex2rgb(cmap.underflow_color.color).concat(cmap.underflow_color.opacity);
126
- const overflow_color = cmap.overflow_color === null ? [0, 0, 0, 0] : hex2rgb(cmap.overflow_color.color).concat(cmap.overflow_color.opacity);
127
- gl_elems.program.use({ 'a_pos': gl_elems.vertices, 'a_tex_coord': gl_elems.texcoords }, { 'u_cmap_min': cmap.levels[0], 'u_cmap_max': cmap.levels[cmap.levels.length - 1], 'u_matrix': matrix, 'u_opacity': this.opts.opacity,
128
- 'u_n_index': this.index_map.length, 'u_underflow_color': underflow_color, 'u_overflow_color': overflow_color, 'u_offset': 0 }, { 'u_fill_sampler': this.fill_texture, 'u_cmap_sampler': gl_elems.cmap_texture, 'u_cmap_nonlin_sampler': gl_elems.cmap_nonlin_texture });
129
- gl.enable(gl.BLEND);
130
- gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
131
- gl_elems.program.draw();
132
- gl_elems.program.setUniforms({ 'u_offset': -2 });
133
- gl_elems.program.draw();
134
- gl_elems.program.setUniforms({ 'u_offset': -1 });
135
- gl_elems.program.draw();
136
- gl_elems.program.setUniforms({ 'u_offset': 1 });
137
- gl_elems.program.draw();
138
+ const render_data = getRendererData(arg);
139
+ const program = this.gl_elems.shader_manager.getShaderProgram(gl, render_data.shaderData);
140
+ program.use({ 'a_pos': gl_elems.vertices, 'a_tex_coord': gl_elems.texcoords }, { 'u_opacity': this.opts.opacity, ...this.gl_elems.shader_manager.getShaderUniforms(render_data) }, { 'u_fill_sampler': this.fill_texture });
141
+ this.cmap_gpu.forEach((cmg, icmg) => {
142
+ program.setUniforms({ 'u_offset': 0 });
143
+ if (this.opts.cmap_mask !== null && this.mask_texture !== null) {
144
+ program.setUniforms({ 'u_mask_val': icmg + 1 });
145
+ program.bindTextures({ 'u_mask_sampler': this.mask_texture });
146
+ }
147
+ cmg.bindShaderVariables(program);
148
+ gl.enable(gl.BLEND);
149
+ gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
150
+ program.draw();
151
+ if (render_data.type != 'maplibre' || !render_data.shaderData.define.includes('GLOBE')) {
152
+ program.setUniforms({ 'u_offset': -2 });
153
+ program.draw();
154
+ program.setUniforms({ 'u_offset': -1 });
155
+ program.draw();
156
+ program.setUniforms({ 'u_offset': 1 });
157
+ program.draw();
158
+ }
159
+ });
138
160
  }
139
161
  }
140
162
  /**
@@ -156,8 +178,8 @@ class Raster extends PlotComponentFill {
156
178
  * Update the data displayed as a raster plot
157
179
  * @param field - The new field to display as a raster plot
158
180
  */
159
- async updateField(field) {
160
- await super.updateField(field);
181
+ async updateField(field, mask) {
182
+ await super.updateField(field, mask);
161
183
  }
162
184
  /**
163
185
  * @internal
@@ -195,8 +217,8 @@ class ContourFill extends PlotComponentFill {
195
217
  * Update the data displayed as filled contours
196
218
  * @param field - The new field to display as filled contours
197
219
  */
198
- async updateField(field) {
199
- await super.updateField(field);
220
+ async updateField(field, mask) {
221
+ await super.updateField(field, mask);
200
222
  }
201
223
  /**
202
224
  * @internal