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.
- package/README.md +6 -11
- package/dist/110.autumnplot-gl.js +1 -1
- package/dist/110.autumnplot-gl.js.map +1 -1
- package/dist/autumnplot-gl.js +1 -1
- package/dist/autumnplot-gl.js.map +1 -1
- package/dist/marchingsquares.wasm +0 -0
- package/lib/AutumnTypes.d.ts +53 -5
- package/lib/AutumnTypes.js +25 -1
- package/lib/Barbs.d.ts +23 -6
- package/lib/Barbs.js +20 -18
- package/lib/BillboardCollection.d.ts +16 -8
- package/lib/BillboardCollection.js +107 -59
- package/lib/Color.d.ts +57 -0
- package/lib/Color.js +163 -0
- package/lib/ColorBar.d.ts +12 -1
- package/lib/ColorBar.js +9 -7
- package/lib/Colormap.d.ts +19 -18
- package/lib/Colormap.js +84 -23
- package/lib/Contour.d.ts +42 -11
- package/lib/Contour.js +67 -58
- package/lib/ContourCreator.d.ts +4 -0
- package/lib/ContourCreator.js +2 -1
- package/lib/Fill.d.ts +27 -16
- package/lib/Fill.js +105 -83
- package/lib/Grid.d.ts +125 -29
- package/lib/Grid.js +303 -95
- package/lib/Hodographs.d.ts +24 -6
- package/lib/Hodographs.js +28 -24
- package/lib/Map.js +1 -1
- package/lib/Paintball.d.ts +6 -5
- package/lib/Paintball.js +38 -32
- package/lib/ParticleTracer.d.ts +19 -0
- package/lib/ParticleTracer.js +37 -0
- package/lib/PlotComponent.d.ts +6 -7
- package/lib/PlotComponent.js +17 -7
- package/lib/PlotLayer.d.ts +4 -4
- package/lib/PlotLayer.worker.d.ts +1 -2
- package/lib/PlotLayer.worker.js +22 -57
- package/lib/PolylineCollection.d.ts +18 -9
- package/lib/PolylineCollection.js +124 -89
- package/lib/RawField.d.ts +76 -23
- package/lib/RawField.js +138 -29
- package/lib/ShaderManager.d.ts +12 -0
- package/lib/ShaderManager.js +58 -0
- package/lib/StationPlot.d.ts +145 -0
- package/lib/StationPlot.js +205 -0
- package/lib/TextCollection.d.ts +12 -8
- package/lib/TextCollection.js +113 -71
- package/lib/cpp/marchingsquares.js +483 -585
- package/lib/cpp/marchingsquares.wasm +0 -0
- package/lib/cpp/marchingsquares_embind.d.ts +23 -3
- package/lib/index.d.ts +7 -4
- package/lib/index.js +5 -3
- package/lib/utils.d.ts +5 -8
- package/lib/utils.js +12 -83
- 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 {
|
|
6
|
-
import {
|
|
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:
|
|
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 =
|
|
43
|
-
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
157
|
-
const
|
|
158
|
-
const
|
|
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:
|
|
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,
|
|
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,
|
|
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,
|
|
222
|
+
this.text_collection.render(gl, arg, [map_width, map_height], map_zoom);
|
|
214
223
|
}
|
|
215
224
|
}
|
|
216
225
|
export default Contour;
|
package/lib/ContourCreator.d.ts
CHANGED
|
@@ -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 };
|
package/lib/ContourCreator.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
2
|
-
import { ColorMap,
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
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
|
-
|
|
9
|
-
|
|
12
|
+
in vec2 a_pos;
|
|
13
|
+
in vec2 a_tex_coord;
|
|
10
14
|
|
|
11
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
uniform
|
|
26
|
-
|
|
29
|
+
|
|
30
|
+
#ifdef MASK
|
|
31
|
+
uniform sampler2D u_mask_sampler;
|
|
32
|
+
#endif
|
|
33
|
+
|
|
27
34
|
uniform highp float u_opacity;
|
|
28
|
-
|
|
29
|
-
uniform
|
|
30
|
-
|
|
35
|
+
#ifdef MASK
|
|
36
|
+
uniform int u_mask_val;
|
|
37
|
+
#endif
|
|
38
|
+
|
|
39
|
+
out highp vec4 fragColor;
|
|
31
40
|
|
|
32
41
|
void main() {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
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.
|
|
68
|
-
this.
|
|
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
|
|
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
|
-
|
|
97
|
-
|
|
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
|
|
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,
|
|
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,
|
|
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
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|