autumnplot-gl 2.2.2 → 3.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 (49) hide show
  1. package/README.md +65 -8
  2. package/dist/110.autumnplot-gl.js +2 -0
  3. package/dist/110.autumnplot-gl.js.map +1 -0
  4. package/dist/autumnplot-gl.js +3 -0
  5. package/dist/autumnplot-gl.js.LICENSE.txt +12 -0
  6. package/dist/autumnplot-gl.js.map +1 -0
  7. package/dist/marchingsquares.wasm +0 -0
  8. package/lib/AutumnTypes.d.ts +14 -13
  9. package/lib/Barbs.d.ts +8 -3
  10. package/lib/Barbs.js +14 -1
  11. package/lib/BillboardCollection.d.ts +11 -7
  12. package/lib/BillboardCollection.js +54 -31
  13. package/lib/ColorBar.js +51 -7
  14. package/lib/Colormap.d.ts +14 -3
  15. package/lib/Colormap.js +63 -12
  16. package/lib/Contour.d.ts +73 -16
  17. package/lib/Contour.js +175 -146
  18. package/lib/ContourCreator.d.ts +18 -0
  19. package/lib/ContourCreator.js +59 -0
  20. package/lib/Fill.d.ts +17 -5
  21. package/lib/Fill.js +60 -37
  22. package/lib/Grid.d.ts +167 -0
  23. package/lib/Grid.js +339 -0
  24. package/lib/Hodographs.d.ts +13 -5
  25. package/lib/Hodographs.js +52 -67
  26. package/lib/Map.d.ts +14 -3
  27. package/lib/Map.js +9 -0
  28. package/lib/Paintball.d.ts +9 -3
  29. package/lib/Paintball.js +28 -10
  30. package/lib/PlotComponent.d.ts +5 -5
  31. package/lib/PlotLayer.d.ts +12 -12
  32. package/lib/PlotLayer.js +16 -14
  33. package/lib/PlotLayer.worker.d.ts +2 -2
  34. package/lib/PlotLayer.worker.js +102 -66
  35. package/lib/PolylineCollection.d.ts +20 -9
  36. package/lib/PolylineCollection.js +158 -32
  37. package/lib/RawField.d.ts +11 -167
  38. package/lib/RawField.js +37 -383
  39. package/lib/TextCollection.d.ts +31 -0
  40. package/lib/TextCollection.js +295 -0
  41. package/lib/cpp/marchingsquares.d.ts +6 -0
  42. package/lib/cpp/marchingsquares.js +4152 -0
  43. package/lib/cpp/marchingsquares.wasm +0 -0
  44. package/lib/cpp/marchingsquares_embind.d.ts +47 -0
  45. package/lib/index.d.ts +13 -6
  46. package/lib/index.js +12 -3
  47. package/lib/utils.d.ts +5 -3
  48. package/lib/utils.js +17 -6
  49. package/package.json +14 -9
package/lib/Contour.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { TypedArray, WebGLAnyRenderingContext } from './AutumnTypes';
2
- import { MapType } from './Map';
2
+ import { MapLikeType } from './Map';
3
3
  import { PlotComponent } from './PlotComponent';
4
4
  import { RawScalarField } from './RawField';
5
5
  interface ContourOptions {
@@ -18,34 +18,33 @@ interface ContourOptions {
18
18
  * @default Draw contours at regular intervals given by the `interval` option.
19
19
  */
20
20
  levels?: number[];
21
- /**
22
- * A function to thin the contours based on zoom level. The function should take a zoom level and return a number `n` that means to only show every
23
- * `n`th contour.
24
- * @default Don't thin the contours on any zoom level
25
- */
26
- thinner?: (zoom: number) => number;
27
21
  }
28
22
  /**
29
- * A field of contoured data. The contours can optionally be thinned based on map zoom level.
23
+ * A field of contoured data.
30
24
  * @example
31
25
  * // Create a contoured height field, with black contours every 30 m (assuming the height field is in
32
- * // meters) and only using every other contour when the map zoom level is less than 5.
33
- * const contours = new Contour(height_field, {color: '#000000', interval: 30,
34
- * thinner: zoom => zoom < 5 ? 2 : 1});
26
+ * // meters).
27
+ * const contours = new Contour(height_field, {color: '#000000', interval: 30});
35
28
  */
36
- declare class Contour<ArrayType extends TypedArray> extends PlotComponent {
37
- private readonly field;
38
- readonly color: [number, number, number];
29
+ declare class Contour<ArrayType extends TypedArray, MapType extends MapLikeType> extends PlotComponent<MapType> {
30
+ private field;
31
+ readonly color: string;
39
32
  readonly interval: number;
40
33
  readonly levels: number[];
41
- readonly thinner: (zoom: number) => number;
42
34
  private gl_elems;
35
+ private contours;
43
36
  /**
44
37
  * Create a contoured field
45
38
  * @param field - The field to contour
46
39
  * @param opts - Options for creating the contours
47
40
  */
48
41
  constructor(field: RawScalarField<ArrayType>, opts: ContourOptions);
42
+ /**
43
+ * Update the data displayed as contours
44
+ * @param field - The new field to contour
45
+ */
46
+ updateField(field: RawScalarField<ArrayType>): Promise<void>;
47
+ getContours(): Promise<import("./AutumnTypes").ContourData>;
49
48
  /**
50
49
  * @internal
51
50
  * Add the contours to a map
@@ -57,5 +56,63 @@ declare class Contour<ArrayType extends TypedArray> extends PlotComponent {
57
56
  */
58
57
  render(gl: WebGLAnyRenderingContext, matrix: number[] | Float32Array): void;
59
58
  }
59
+ interface ContourLabelOptions {
60
+ /**
61
+ * Number of decimal places to use in the contour labels
62
+ * @default 0
63
+ */
64
+ n_decimal_places?: number;
65
+ /**
66
+ * Font face to use for the contour labels
67
+ * @default 'Trebuchet MS'
68
+ */
69
+ font_face?: string;
70
+ /**
71
+ * Font size in points to use for the contour labels
72
+ * @default 12
73
+ */
74
+ font_size?: number;
75
+ /**
76
+ * URL template to use in retrieving the font data for the labels. The default is to use the template from the map style.
77
+ */
78
+ font_url_template?: string;
79
+ /**
80
+ * Text color for the contour labels
81
+ * @default '#000000'
82
+ */
83
+ text_color?: string;
84
+ /**
85
+ * Halo (outline) color for the contour labels
86
+ * @default '#000000'
87
+ */
88
+ halo_color?: string;
89
+ /**
90
+ * Whether to draw the halo (outline) on the contour labels
91
+ * @default false
92
+ */
93
+ halo?: boolean;
94
+ }
95
+ declare class ContourLabels<ArrayType extends TypedArray, MapType extends MapLikeType> extends PlotComponent<MapType> {
96
+ private readonly contours;
97
+ private gl_elems;
98
+ private text_collection;
99
+ private readonly opts;
100
+ constructor(contours: Contour<ArrayType, MapType>, opts?: ContourLabelOptions);
101
+ /**
102
+ * Update contour labels when the field for the associated Contour object has been changed.
103
+ */
104
+ updateField(): Promise<void>;
105
+ /**
106
+ * @internal
107
+ * Add the contour labels to a map
108
+ */
109
+ onAdd(map: MapType, gl: WebGLAnyRenderingContext): Promise<void>;
110
+ /**
111
+ * @internal
112
+ * Render the contour labels
113
+ */
114
+ render(gl: WebGLAnyRenderingContext, matrix: number[]): void;
115
+ }
60
116
  export default Contour;
61
- export type { ContourOptions };
117
+ export { ContourLabels };
118
+ export type { ContourOptions, ContourLabelOptions };
package/lib/Contour.js CHANGED
@@ -1,116 +1,15 @@
1
- import { PlotComponent, getGLFormatTypeAlignment } from './PlotComponent';
2
- import { hex2rgba } from './utils';
3
- import { WGLProgram, WGLTexture } from 'autumn-wgl';
4
- const contour_vertex_shader_src = `uniform mat4 u_matrix;
5
- uniform int u_offset;
6
-
7
- attribute vec2 a_pos;
8
- attribute float a_grid_cell_size;
9
- attribute vec2 a_tex_coord;
10
-
11
- varying highp vec2 v_tex_coord;
12
- varying highp float v_grid_cell_size;
13
- varying highp float v_map_scale_fac;
14
-
15
- void main() {
16
- float globe_width = 1.;
17
- vec2 globe_offset = vec2(globe_width * float(u_offset), 0.);
18
-
19
- gl_Position = u_matrix * vec4(a_pos + globe_offset, 0.0, 1.0);
20
- v_tex_coord = a_tex_coord;
21
- v_grid_cell_size = a_grid_cell_size;
22
-
23
-
24
- highp float lat = 2. * atan(exp(a_pos.y / 6371229.0)) - 3.1414592654 / 2.;
25
- v_map_scale_fac = cos(lat);
26
- }`
27
- const contour_fragment_shader_src = `#extension GL_OES_standard_derivatives : enable
28
- #define MAX_N_CONTOURS 40
29
-
30
- varying highp vec2 v_tex_coord;
31
- varying highp float v_grid_cell_size;
32
- varying highp float v_map_scale_fac;
33
-
34
- uniform sampler2D u_fill_sampler;
35
- uniform highp float u_contour_interval;
36
- uniform highp float u_contour_levels[MAX_N_CONTOURS];
37
- uniform int u_num_contours;
38
- uniform lowp float u_line_cutoff;
39
- uniform lowp vec3 u_color;
40
- uniform lowp vec2 u_step_size;
41
- uniform lowp float u_zoom_fac;
42
-
43
- void main() {
44
- highp float field_val = texture2D(u_fill_sampler, v_tex_coord).r;
45
-
46
- lowp vec2 grid_point = fract(v_tex_coord / u_step_size + vec2(0.5, 0.5));
47
- highp vec2 grid_sw = v_tex_coord - grid_point * u_step_size;
48
-
49
- lowp vec2 ihat = vec2(u_step_size.x, 0.0);
50
- lowp vec2 jhat = vec2(0.0, u_step_size.y);
51
- highp float fv_sw = texture2D(u_fill_sampler, grid_sw).r;
52
- highp float fv_se = texture2D(u_fill_sampler, grid_sw + ihat).r;
53
- highp float fv_nw = texture2D(u_fill_sampler, grid_sw + jhat).r;
54
- highp float fv_ne = texture2D(u_fill_sampler, grid_sw + ihat + jhat).r;
55
-
56
- highp float dfdx = mix(fv_se, fv_ne, grid_point.y) - mix(fv_sw, fv_nw, grid_point.y);
57
- highp float dfdy = mix(fv_nw, fv_ne, grid_point.x) - mix(fv_sw, fv_se, grid_point.x);
58
- highp float fwidth_field = sqrt((dfdx * dfdx + dfdy * dfdy) / (5e5 * v_grid_cell_size));
59
-
60
- lowp float plot_val;
61
-
62
- if (u_num_contours > 0) {
63
- highp float min_contour_diff = u_contour_levels[1] - u_contour_levels[0];
64
- bool assigned_contour = false;
65
- highp float low_contour;
66
- highp float high_contour;
67
- highp float highest_contour;
68
-
69
- for (int ncnt = 0; ncnt < MAX_N_CONTOURS; ncnt++) {
70
- if (ncnt >= u_num_contours) { break; }
71
-
72
- min_contour_diff = min(min_contour_diff, u_contour_levels[ncnt + 1] - u_contour_levels[ncnt]);
73
-
74
- if (u_contour_levels[ncnt] < field_val && field_val < u_contour_levels[ncnt + 1]) {
75
- assigned_contour = true;
76
-
77
- low_contour = u_contour_levels[ncnt];
78
- high_contour = u_contour_levels[ncnt + 1];
79
- }
80
-
81
- highest_contour = u_contour_levels[ncnt];
82
- }
83
-
84
- if (!assigned_contour) {
85
- if (field_val < u_contour_levels[0]) {
86
- plot_val = (u_contour_levels[0] - field_val) / min_contour_diff;
87
- }
88
- else if (field_val > highest_contour) {
89
- plot_val = (field_val - highest_contour) / min_contour_diff;
90
- }
91
- }
92
- else {
93
- plot_val = min(field_val - low_contour, high_contour - field_val) / min_contour_diff;
94
- }
95
- }
96
- else {
97
- plot_val = fract(field_val / u_contour_interval);
98
- if (plot_val > 0.5) plot_val = 1.0 - plot_val;
99
- }
100
-
101
- plot_val = plot_val / (max(0.001, fwidth_field / (u_zoom_fac * 0.125)));
102
-
103
- if (plot_val > u_line_cutoff) discard;
104
-
105
- gl_FragColor = vec4(u_color, 1. - (plot_val * plot_val / (u_line_cutoff * u_line_cutoff)));
106
- }`
1
+ import { LngLat } from './Map';
2
+ import { PlotComponent } from './PlotComponent';
3
+ import { PolylineCollection } from './PolylineCollection';
4
+ import { TextCollection } from './TextCollection';
5
+ import { hex2rgb, normalizeOptions } from './utils';
6
+ import { kdTree } from 'kd-tree-javascript';
107
7
  /**
108
- * A field of contoured data. The contours can optionally be thinned based on map zoom level.
8
+ * A field of contoured data.
109
9
  * @example
110
10
  * // Create a contoured height field, with black contours every 30 m (assuming the height field is in
111
- * // meters) and only using every other contour when the map zoom level is less than 5.
112
- * const contours = new Contour(height_field, {color: '#000000', interval: 30,
113
- * thinner: zoom => zoom < 5 ? 2 : 1});
11
+ * // meters).
12
+ * const contours = new Contour(height_field, {color: '#000000', interval: 30});
114
13
  */
115
14
  class Contour extends PlotComponent {
116
15
  /**
@@ -123,63 +22,193 @@ class Contour extends PlotComponent {
123
22
  this.field = field;
124
23
  this.interval = opts.interval || 1;
125
24
  this.levels = opts.levels || [];
126
- const color = hex2rgba(opts.color || '#000000');
127
- this.color = [color[0], color[1], color[2]];
128
- this.thinner = opts.thinner || (() => 1);
25
+ this.color = opts.color || '#000000';
129
26
  this.gl_elems = null;
27
+ this.contours = null;
28
+ }
29
+ /**
30
+ * Update the data displayed as contours
31
+ * @param field - The new field to contour
32
+ */
33
+ async updateField(field) {
34
+ this.field = field;
35
+ if (this.gl_elems === null)
36
+ return;
37
+ const gl = this.gl_elems.gl;
38
+ const contour_data = await this.getContours();
39
+ const line_data = Object.values(contour_data).flat().map(c => {
40
+ return { vertices: c };
41
+ });
42
+ this.contours = await PolylineCollection.make(gl, line_data, { line_width: 2, color: this.color });
43
+ this.gl_elems.map.triggerRepaint();
44
+ }
45
+ async getContours() {
46
+ return await this.field.getContours({ interval: this.interval, levels: this.levels });
130
47
  }
131
48
  /**
132
49
  * @internal
133
50
  * Add the contours to a map
134
51
  */
135
52
  async onAdd(map, gl) {
136
- // Basic procedure for these contours from https://www.shadertoy.com/view/lltBWM
137
- gl.getExtension("OES_standard_derivatives");
138
- const program = new WGLProgram(gl, contour_vertex_shader_src, contour_fragment_shader_src);
139
- const { vertices: verts_buf, texcoords: tex_coords_buf, cellsize: cellsize_buf } = await this.field.grid.getWGLBuffers(gl);
140
- const vertices = verts_buf;
141
- const texcoords = tex_coords_buf;
142
- const grid_cell_size = cellsize_buf;
143
- const { format, type, row_alignment } = getGLFormatTypeAlignment(gl, this.field.isFloat16());
144
- const fill_image = { 'format': format, 'type': type,
145
- 'width': this.field.grid.ni, 'height': this.field.grid.nj, 'image': this.field.getTextureData(),
146
- 'mag_filter': gl.LINEAR, 'row_alignment': row_alignment,
147
- };
148
- const fill_texture = new WGLTexture(gl, fill_image);
149
53
  this.gl_elems = {
150
- map: map, program: program, vertices: vertices, texcoords: texcoords, grid_cell_size: grid_cell_size, fill_texture: fill_texture
54
+ gl: gl, map: map
151
55
  };
56
+ await this.updateField(this.field);
152
57
  }
153
58
  /**
154
59
  * @internal
155
60
  * Render the contours
156
61
  */
157
62
  render(gl, matrix) {
158
- if (this.gl_elems === null)
63
+ if (this.gl_elems === null || this.contours === null)
159
64
  return;
160
65
  const gl_elems = this.gl_elems;
161
66
  if (matrix instanceof Float32Array)
162
67
  matrix = [...matrix];
163
68
  const zoom = gl_elems.map.getZoom();
164
- const intv = this.thinner(zoom) * this.interval;
165
- const cutoff = 0.3 / intv;
166
- const step_size = [1 / this.field.grid.ni, 1 / this.field.grid.nj];
167
- const zoom_fac = Math.pow(2, zoom);
168
- let uniforms = { 'u_contour_interval': intv, 'u_line_cutoff': cutoff, 'u_color': this.color, 'u_step_size': step_size, 'u_zoom_fac': zoom_fac,
169
- 'u_matrix': matrix, 'u_num_contours': 0, 'u_contour_levels': [0], 'u_offset': 0 };
170
- if (this.levels.length > 0) {
171
- uniforms = { ...uniforms, 'u_num_contours': this.levels.length, 'u_contour_levels': this.levels };
69
+ const map_width = gl_elems.map.getCanvas().width;
70
+ const map_height = gl_elems.map.getCanvas().height;
71
+ const bearing = gl_elems.map.getBearing();
72
+ const pitch = gl_elems.map.getPitch();
73
+ this.contours.render(gl, matrix, [map_width, map_height], zoom, bearing, pitch);
74
+ }
75
+ }
76
+ const contour_label_opt_defaults = {
77
+ n_decimal_places: 0,
78
+ font_face: 'Trebuchet MS',
79
+ font_size: 12,
80
+ font_url_template: '',
81
+ text_color: '#000000',
82
+ halo_color: '#000000',
83
+ halo: false
84
+ };
85
+ class ContourLabels extends PlotComponent {
86
+ constructor(contours, opts) {
87
+ super();
88
+ this.opts = normalizeOptions(opts, contour_label_opt_defaults);
89
+ this.contours = contours;
90
+ this.text_collection = null;
91
+ this.gl_elems = null;
92
+ }
93
+ /**
94
+ * Update contour labels when the field for the associated Contour object has been changed.
95
+ */
96
+ async updateField() {
97
+ if (this.gl_elems === null)
98
+ return;
99
+ const map = this.gl_elems.map;
100
+ const gl = this.gl_elems.gl;
101
+ const map_style = map.getStyle();
102
+ const font_url_template = this.opts.font_url_template == '' ? map_style.glyphs : this.opts.font_url_template;
103
+ const font_url = font_url_template.replace('{range}', '0-255').replace('{fontstack}', this.opts.font_face);
104
+ const label_pos = [];
105
+ const contour_data = await this.contours.getContours();
106
+ const contour_levels = Object.keys(contour_data).map(parseFloat);
107
+ contour_levels.sort((a, b) => a - b);
108
+ const map_max_zoom = map.getMaxZoom();
109
+ const contour_label_spacing = 0.01 * Math.pow(2, 7 - map_max_zoom);
110
+ let min_label_lat = null, max_label_lat = null, min_label_lon = null, max_label_lon = null;
111
+ Object.entries(contour_data).forEach(([level, contours]) => {
112
+ const icntr = (parseFloat(level) - contour_levels[0]);
113
+ const level_str = level.toString();
114
+ contours.forEach(contour => {
115
+ const c_map = contour.map(v => {
116
+ const v_ll = new LngLat(...v).toMercatorCoord();
117
+ return [v_ll.x, v_ll.y];
118
+ });
119
+ const dist = [];
120
+ c_map.forEach((v, i) => {
121
+ if (i == 0) {
122
+ dist.push(0);
123
+ }
124
+ else {
125
+ const v_last = c_map[i - 1];
126
+ const this_dist = Math.hypot(v_last[0] - v[0], v_last[1] - v[1]);
127
+ dist.push(dist[i - 1] + this_dist);
128
+ }
129
+ });
130
+ let n_labels_placed = 0;
131
+ for (let idist = 1; idist < dist.length; idist++) {
132
+ const target_dist = contour_label_spacing * (n_labels_placed + (icntr / 2) % 1);
133
+ if (dist[idist - 1] <= target_dist && target_dist < dist[idist]) {
134
+ const pt1 = contour[idist - 1];
135
+ const pt2 = contour[idist];
136
+ const alpha = (target_dist - dist[idist - 1]) / (dist[idist] - dist[idist - 1]);
137
+ const pt_lon = (1 - alpha) * pt1[0] + alpha * pt2[0];
138
+ const pt_lat = (1 - alpha) * pt1[1] + alpha * pt2[1];
139
+ if (min_label_lon === null || pt_lon < min_label_lon)
140
+ min_label_lon = pt_lon;
141
+ if (max_label_lon === null || pt_lon > max_label_lon)
142
+ max_label_lon = pt_lon;
143
+ if (min_label_lat === null || pt_lat < min_label_lat)
144
+ min_label_lat = pt_lat;
145
+ if (max_label_lat === null || pt_lat > max_label_lat)
146
+ max_label_lat = pt_lat;
147
+ label_pos.push({ lon: pt_lon, lat: pt_lat, min_zoom: map_max_zoom, text: level_str });
148
+ n_labels_placed++;
149
+ }
150
+ }
151
+ });
152
+ });
153
+ const tree = new kdTree(label_pos, (a, b) => Math.hypot(a.lon - b.lon, a.lat - b.lat), ['lon', 'lat']);
154
+ const { x: min_label_x, y: max_label_y } = new LngLat(min_label_lon, min_label_lat).toMercatorCoord();
155
+ const { x: max_label_x, y: min_label_y } = new LngLat(max_label_lon, max_label_lat).toMercatorCoord();
156
+ const thin_grid_width = max_label_x - min_label_x;
157
+ const thin_grid_height = max_label_y - min_label_y;
158
+ const ni_thin_grid = Math.round(4 * thin_grid_width / contour_label_spacing);
159
+ const nj_thin_grid = Math.round(4 * thin_grid_height / contour_label_spacing);
160
+ const thin_grid_xs = [];
161
+ const thin_grid_ys = [];
162
+ for (let idx = 0; idx < ni_thin_grid; idx++) {
163
+ thin_grid_xs.push(min_label_x + (idx / ni_thin_grid) * thin_grid_width);
164
+ }
165
+ for (let jdy = 0; jdy < nj_thin_grid; jdy++) {
166
+ thin_grid_ys.push(min_label_y + (jdy / nj_thin_grid) * thin_grid_height);
172
167
  }
173
- gl_elems.program.use({ 'a_pos': gl_elems.vertices, 'a_grid_cell_size': gl_elems.grid_cell_size, 'a_tex_coord': gl_elems.texcoords }, uniforms, { 'u_fill_sampler': gl_elems.fill_texture });
174
- gl.enable(gl.BLEND);
175
- gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
176
- gl_elems.program.draw();
177
- gl_elems.program.setUniforms({ 'u_offset': -2 });
178
- gl_elems.program.draw();
179
- gl_elems.program.setUniforms({ 'u_offset': -1 });
180
- gl_elems.program.draw();
181
- gl_elems.program.setUniforms({ 'u_offset': 1 });
182
- gl_elems.program.draw();
168
+ let skip = 1;
169
+ for (let zoom = map_max_zoom - 1; zoom >= 0; zoom--) {
170
+ for (let idx = 0; idx < ni_thin_grid; idx += skip) {
171
+ for (let jdy = 0; jdy < nj_thin_grid; jdy += skip) {
172
+ const grid_x = thin_grid_xs[idx];
173
+ const grid_y = thin_grid_ys[jdy];
174
+ const ll = LngLat.fromMercatorCoord(grid_x, grid_y);
175
+ const [label, dist] = tree.nearest({ lon: ll.lng, lat: ll.lat, min_zoom: 0, text: "" }, 1)[0];
176
+ label.min_zoom = zoom;
177
+ }
178
+ }
179
+ skip *= 2;
180
+ }
181
+ const tc_opts = {
182
+ horizontal_align: 'center', vertical_align: 'middle', font_size: this.opts.font_size,
183
+ halo: this.opts.halo,
184
+ text_color: hex2rgb(this.opts.text_color), halo_color: hex2rgb(this.opts.halo_color),
185
+ };
186
+ this.text_collection = await TextCollection.make(gl, label_pos, font_url, tc_opts);
187
+ map.triggerRepaint();
188
+ }
189
+ /**
190
+ * @internal
191
+ * Add the contour labels to a map
192
+ */
193
+ async onAdd(map, gl) {
194
+ this.gl_elems = {
195
+ gl: gl, map: map,
196
+ };
197
+ this.updateField();
198
+ }
199
+ /**
200
+ * @internal
201
+ * Render the contour labels
202
+ */
203
+ render(gl, matrix) {
204
+ if (this.gl_elems === null || this.text_collection === null)
205
+ return;
206
+ const gl_elems = this.gl_elems;
207
+ const map_width = gl_elems.map.getCanvas().width;
208
+ const map_height = gl_elems.map.getCanvas().height;
209
+ const map_zoom = gl_elems.map.getZoom();
210
+ this.text_collection.render(gl, matrix, [map_width, map_height], map_zoom);
183
211
  }
184
212
  }
185
213
  export default Contour;
214
+ export { ContourLabels };
@@ -0,0 +1,18 @@
1
+ import { MarchingSquaresModule } from './cpp/marchingsquares';
2
+ import './cpp/marchingsquares.wasm';
3
+ import { Grid } from "./Grid";
4
+ import { ContourData, TypedArray } from "./AutumnTypes";
5
+ declare function initMSModule(): Promise<MarchingSquaresModule>;
6
+ interface FieldContourOpts {
7
+ /**
8
+ * The interval at which to create contours. The field will be contoured at this interval from its minimum to its maximum.
9
+ */
10
+ interval?: number;
11
+ /**
12
+ * Contour the field at these specific levels.
13
+ */
14
+ levels?: number[];
15
+ }
16
+ declare function contourCreator<ArrayType extends TypedArray>(data: ArrayType, grid: Grid, opts: FieldContourOpts): Promise<ContourData>;
17
+ export { contourCreator, initMSModule };
18
+ export type { FieldContourOpts };
@@ -0,0 +1,59 @@
1
+ import Module from './cpp/marchingsquares';
2
+ import './cpp/marchingsquares.wasm';
3
+ let msm_promise = null;
4
+ function initMSModule() {
5
+ if (msm_promise === null) {
6
+ msm_promise = Module();
7
+ }
8
+ return msm_promise;
9
+ }
10
+ async function contourCreator(data, grid, opts) {
11
+ if (opts.interval === undefined && opts.levels === undefined) {
12
+ throw "Must supply either an interval or levels to contourCreator()";
13
+ }
14
+ const interval = opts.interval === undefined ? 0 : opts.interval;
15
+ const levels = opts.levels === undefined ? [] : [...opts.levels];
16
+ const msm = await initMSModule();
17
+ const grid_coords = grid.getGridCoords();
18
+ const grid_data = new msm.FloatList();
19
+ grid_data.resize(grid.ni * grid.nj, 0);
20
+ const tex_data = data;
21
+ tex_data.forEach((v, i) => grid_data.set(i, v));
22
+ const grid_x = new msm.FloatList();
23
+ grid_x.resize(grid.ni, 0);
24
+ grid_coords.x.forEach((v, i) => grid_x.set(i, v));
25
+ const grid_y = new msm.FloatList();
26
+ grid_y.resize(grid.nj, 0);
27
+ grid_coords.y.forEach((v, i) => grid_y.set(i, v));
28
+ if (levels.length == 0) {
29
+ const levels_cpp = msm.getContourLevelsFloat32(grid_data, grid.ni, grid.nj, interval);
30
+ for (let ilvl = 0; ilvl < levels_cpp.size(); ilvl++) {
31
+ levels.push(levels_cpp.get(ilvl));
32
+ }
33
+ levels_cpp.delete();
34
+ }
35
+ const contours = {};
36
+ levels.forEach(v => {
37
+ const contours_ = msm.makeContoursFloat32(grid_data, grid_x, grid_y, v);
38
+ contours[v] = [];
39
+ for (let icntr = 0; icntr < contours_.size(); icntr++) {
40
+ const contour = contours_.get(icntr);
41
+ const contour_point_list = contour.point_list;
42
+ contours[v].push([]);
43
+ for (let ipt = 0; ipt < contour_point_list.size(); ipt++) {
44
+ const pt = contour_point_list.get(ipt);
45
+ const [lon, lat] = grid.transform(pt.x, pt.y, { inverse: true });
46
+ contours[v][icntr].push([lon, lat]);
47
+ pt.delete();
48
+ }
49
+ contour_point_list.delete();
50
+ contour.delete();
51
+ }
52
+ contours_.delete();
53
+ });
54
+ grid_data.delete();
55
+ grid_x.delete();
56
+ grid_y.delete();
57
+ return contours;
58
+ }
59
+ export { contourCreator, initMSModule };
package/lib/Fill.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { PlotComponent } from './PlotComponent';
2
2
  import { ColorMap } from './Colormap';
3
3
  import { RawScalarField } from './RawField';
4
- import { MapType } from './Map';
4
+ import { MapLikeType } from './Map';
5
5
  import { TypedArray, WebGLAnyRenderingContext } from './AutumnTypes';
6
6
  interface ContourFillOptions {
7
7
  /** The color map to use when creating the fills */
@@ -21,16 +21,18 @@ interface RasterOptions {
21
21
  */
22
22
  opacity?: number;
23
23
  }
24
- declare class PlotComponentFill<ArrayType extends TypedArray> extends PlotComponent {
25
- private readonly field;
24
+ declare class PlotComponentFill<ArrayType extends TypedArray, MapType extends MapLikeType> extends PlotComponent<MapType> {
25
+ private field;
26
26
  readonly cmap: ColorMap;
27
27
  readonly opacity: number;
28
28
  private readonly cmap_image;
29
29
  private readonly index_map;
30
30
  private gl_elems;
31
+ private fill_texture;
31
32
  protected image_mag_filter: number | null;
32
33
  protected cmap_mag_filter: number | null;
33
34
  constructor(field: RawScalarField<ArrayType>, opts: ContourFillOptions);
35
+ updateField(field: RawScalarField<ArrayType>): Promise<void>;
34
36
  onAdd(map: MapType, gl: WebGLAnyRenderingContext): Promise<void>;
35
37
  render(gl: WebGLAnyRenderingContext, matrix: number[] | Float32Array): void;
36
38
  }
@@ -40,13 +42,18 @@ declare class PlotComponentFill<ArrayType extends TypedArray> extends PlotCompon
40
42
  * // Create a raster plot with the provided color map
41
43
  * const raster = new Raster(wind_speed_field, {cmap: color_map});
42
44
  */
43
- declare class Raster<ArrayType extends TypedArray> extends PlotComponentFill<ArrayType> {
45
+ declare class Raster<ArrayType extends TypedArray, MapType extends MapLikeType> extends PlotComponentFill<ArrayType, MapType> {
44
46
  /**
45
47
  * Create a raster plot
46
48
  * @param field - The field to create the raster plot from
47
49
  * @param opts - Options for creating the raster plot
48
50
  */
49
51
  constructor(field: RawScalarField<ArrayType>, opts: RasterOptions);
52
+ /**
53
+ * Update the data displayed as a raster plot
54
+ * @param field - The new field to display as a raster plot
55
+ */
56
+ updateField(field: RawScalarField<ArrayType>): Promise<void>;
50
57
  /**
51
58
  * @internal
52
59
  * Add the raster plot to a map
@@ -64,13 +71,18 @@ declare class Raster<ArrayType extends TypedArray> extends PlotComponentFill<Arr
64
71
  * // Create a field of filled contours with the provided color map
65
72
  * const fill = new ContourFill(wind_speed_field, {cmap: color_map});
66
73
  */
67
- declare class ContourFill<ArrayType extends TypedArray> extends PlotComponentFill<ArrayType> {
74
+ declare class ContourFill<ArrayType extends TypedArray, MapType extends MapLikeType> extends PlotComponentFill<ArrayType, MapType> {
68
75
  /**
69
76
  * Create a filled contoured field
70
77
  * @param field - The field to create filled contours from
71
78
  * @param opts - Options for creating the filled contours
72
79
  */
73
80
  constructor(field: RawScalarField<ArrayType>, opts: ContourFillOptions);
81
+ /**
82
+ * Update the data displayed as filled contours
83
+ * @param field - The new field to display as filled contours
84
+ */
85
+ updateField(field: RawScalarField<ArrayType>): Promise<void>;
74
86
  /**
75
87
  * @internal
76
88
  * Add the filled contours to a map