autumnplot-gl 3.2.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -207
- package/dist/983.autumnplot-gl.js +2 -0
- package/dist/983.autumnplot-gl.js.map +1 -0
- 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 +85 -6
- package/lib/AutumnTypes.js +28 -1
- package/lib/Barbs.d.ts +7 -5
- package/lib/BillboardCollection.d.ts +8 -7
- package/lib/BillboardCollection.js +69 -58
- package/lib/Color.d.ts +2 -0
- package/lib/Color.js +4 -0
- package/lib/ColorBar.d.ts +19 -0
- package/lib/ColorBar.js +9 -6
- package/lib/Colormap.d.ts +1 -0
- package/lib/Colormap.js +8 -8
- package/lib/Contour.d.ts +28 -8
- package/lib/Contour.js +27 -54
- package/lib/ContourCreator.d.ts +9 -1
- package/lib/ContourCreator.js +5 -4
- package/lib/Fill.d.ts +28 -14
- package/lib/Fill.js +97 -50
- package/lib/Grid.d.ts +132 -30
- package/lib/Grid.js +355 -99
- package/lib/Hodographs.d.ts +15 -8
- package/lib/Hodographs.js +48 -30
- package/lib/Map.d.ts +14 -1
- package/lib/Map.js +60 -4
- package/lib/Paintball.d.ts +7 -5
- package/lib/Paintball.js +36 -31
- package/lib/ParticleTracer.d.ts +19 -0
- package/lib/ParticleTracer.js +37 -0
- package/lib/PlotComponent.d.ts +7 -7
- package/lib/PlotComponent.js +9 -3
- package/lib/PlotLayer.d.ts +5 -5
- package/lib/PlotLayer.js +2 -2
- package/lib/PlotLayer.worker.d.ts +1 -2
- package/lib/PlotLayer.worker.js +22 -51
- package/lib/PolylineCollection.d.ts +5 -3
- package/lib/PolylineCollection.js +60 -37
- package/lib/RawField.d.ts +78 -23
- package/lib/RawField.js +147 -31
- package/lib/ShaderManager.d.ts +12 -0
- package/lib/ShaderManager.js +58 -0
- package/lib/StationPlot.d.ts +187 -25
- package/lib/StationPlot.js +209 -60
- package/lib/TextCollection.d.ts +9 -6
- package/lib/TextCollection.js +97 -62
- 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 +12 -5
- package/lib/index.js +8 -5
- package/lib/utils.d.ts +4 -1
- package/lib/utils.js +12 -1
- package/package.json +3 -3
- package/dist/110.autumnplot-gl.js +0 -2
- package/dist/110.autumnplot-gl.js.map +0 -1
package/lib/Contour.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { TypedArray, WebGLAnyRenderingContext } from './AutumnTypes';
|
|
1
|
+
import { RenderMethodArg, TypedArray, WebGLAnyRenderingContext } from './AutumnTypes';
|
|
2
2
|
import { MapLikeType } from './Map';
|
|
3
3
|
import { PlotComponent } from './PlotComponent';
|
|
4
4
|
import { RawScalarField } from './RawField';
|
|
5
5
|
import { LineStyle } from './PolylineCollection';
|
|
6
6
|
import { ColorMap } from './Colormap';
|
|
7
|
+
import { StructuredGrid } from './Grid';
|
|
8
|
+
/** Options for {@link Contour} components */
|
|
7
9
|
interface ContourOptions {
|
|
8
10
|
/**
|
|
9
11
|
* The color of the contours as a hex color string
|
|
@@ -39,6 +41,10 @@ interface ContourOptions {
|
|
|
39
41
|
* @default '-'
|
|
40
42
|
*/
|
|
41
43
|
line_style?: LineStyle | ((level: number) => LineStyle);
|
|
44
|
+
/**
|
|
45
|
+
*
|
|
46
|
+
*/
|
|
47
|
+
quad_as_tri?: boolean;
|
|
42
48
|
}
|
|
43
49
|
/**
|
|
44
50
|
* A field of contoured data.
|
|
@@ -47,7 +53,7 @@ interface ContourOptions {
|
|
|
47
53
|
* // meters).
|
|
48
54
|
* const contours = new Contour(height_field, {color: '#000000', interval: 30});
|
|
49
55
|
*/
|
|
50
|
-
declare class Contour<ArrayType extends TypedArray, MapType extends MapLikeType> extends PlotComponent<MapType> {
|
|
56
|
+
declare class Contour<ArrayType extends TypedArray, GridType extends StructuredGrid, MapType extends MapLikeType> extends PlotComponent<MapType> {
|
|
51
57
|
private field;
|
|
52
58
|
readonly opts: Required<ContourOptions>;
|
|
53
59
|
private gl_elems;
|
|
@@ -57,12 +63,12 @@ declare class Contour<ArrayType extends TypedArray, MapType extends MapLikeType>
|
|
|
57
63
|
* @param field - The field to contour
|
|
58
64
|
* @param opts - Options for creating the contours
|
|
59
65
|
*/
|
|
60
|
-
constructor(field: RawScalarField<ArrayType>, opts: ContourOptions);
|
|
66
|
+
constructor(field: RawScalarField<ArrayType, GridType>, opts: ContourOptions);
|
|
61
67
|
/**
|
|
62
68
|
* Update the data displayed as contours
|
|
63
69
|
* @param field - The new field to contour
|
|
64
70
|
*/
|
|
65
|
-
updateField(field: RawScalarField<ArrayType>): Promise<void>;
|
|
71
|
+
updateField(field: RawScalarField<ArrayType, GridType>): Promise<void>;
|
|
66
72
|
getContours(): Promise<import("./AutumnTypes").ContourData>;
|
|
67
73
|
/**
|
|
68
74
|
* @internal
|
|
@@ -73,8 +79,9 @@ declare class Contour<ArrayType extends TypedArray, MapType extends MapLikeType>
|
|
|
73
79
|
* @internal
|
|
74
80
|
* Render the contours
|
|
75
81
|
*/
|
|
76
|
-
render(gl: WebGLAnyRenderingContext,
|
|
82
|
+
render(gl: WebGLAnyRenderingContext, arg: RenderMethodArg): void;
|
|
77
83
|
}
|
|
84
|
+
/** Options for {@link ContourLabels} */
|
|
78
85
|
interface ContourLabelOptions {
|
|
79
86
|
/**
|
|
80
87
|
* Number of decimal places to use in the contour labels
|
|
@@ -110,13 +117,26 @@ interface ContourLabelOptions {
|
|
|
110
117
|
* @default false
|
|
111
118
|
*/
|
|
112
119
|
halo?: boolean;
|
|
120
|
+
/**
|
|
121
|
+
* Label density. 2 makes the labels twice as dense, 0.5 makes them half as dense.
|
|
122
|
+
* @default 1
|
|
123
|
+
*/
|
|
124
|
+
density?: number;
|
|
113
125
|
}
|
|
114
|
-
|
|
126
|
+
/**
|
|
127
|
+
* Label the contours on a plot
|
|
128
|
+
* @example
|
|
129
|
+
* // Contour some data
|
|
130
|
+
* const contours = new Contour(height_field, {color: '#000000', interval: 30});
|
|
131
|
+
* // Label the contours
|
|
132
|
+
* const labels = new ContourLabels(contours, {text_color: '#ffffff', halo: true});
|
|
133
|
+
*/
|
|
134
|
+
declare class ContourLabels<ArrayType extends TypedArray, GridType extends StructuredGrid, MapType extends MapLikeType> extends PlotComponent<MapType> {
|
|
115
135
|
private readonly contours;
|
|
116
136
|
private gl_elems;
|
|
117
137
|
private text_collection;
|
|
118
138
|
private readonly opts;
|
|
119
|
-
constructor(contours: Contour<ArrayType, MapType>, opts?: ContourLabelOptions);
|
|
139
|
+
constructor(contours: Contour<ArrayType, GridType, MapType>, opts?: ContourLabelOptions);
|
|
120
140
|
/**
|
|
121
141
|
* Update contour labels when the field for the associated Contour object has been changed.
|
|
122
142
|
*/
|
|
@@ -130,7 +150,7 @@ declare class ContourLabels<ArrayType extends TypedArray, MapType extends MapLik
|
|
|
130
150
|
* @internal
|
|
131
151
|
* Render the contour labels
|
|
132
152
|
*/
|
|
133
|
-
render(gl: WebGLAnyRenderingContext,
|
|
153
|
+
render(gl: WebGLAnyRenderingContext, arg: RenderMethodArg): void;
|
|
134
154
|
}
|
|
135
155
|
export default Contour;
|
|
136
156
|
export { ContourLabels };
|
package/lib/Contour.js
CHANGED
|
@@ -4,14 +4,15 @@ import { PolylineCollection, isLineStyle } from './PolylineCollection';
|
|
|
4
4
|
import { TextCollection } from './TextCollection';
|
|
5
5
|
import { Color } from './Color';
|
|
6
6
|
import { normalizeOptions } from './utils';
|
|
7
|
-
import {
|
|
7
|
+
import { UnstructuredGrid } from './Grid';
|
|
8
8
|
const contour_opt_defaults = {
|
|
9
9
|
color: '#000000',
|
|
10
10
|
cmap: null,
|
|
11
11
|
interval: 1,
|
|
12
12
|
levels: null,
|
|
13
13
|
line_width: 2,
|
|
14
|
-
line_style: '-'
|
|
14
|
+
line_style: '-',
|
|
15
|
+
quad_as_tri: false
|
|
15
16
|
};
|
|
16
17
|
/**
|
|
17
18
|
* A field of contoured data.
|
|
@@ -87,7 +88,7 @@ class Contour extends PlotComponent {
|
|
|
87
88
|
}
|
|
88
89
|
async getContours() {
|
|
89
90
|
const levels = this.opts.levels === null ? undefined : this.opts.levels;
|
|
90
|
-
return await this.field.getContours({ interval: this.opts.interval, levels: levels });
|
|
91
|
+
return await this.field.getContours({ interval: this.opts.interval, levels: levels, quad_as_tri: this.opts.quad_as_tri });
|
|
91
92
|
}
|
|
92
93
|
/**
|
|
93
94
|
* @internal
|
|
@@ -103,18 +104,16 @@ class Contour extends PlotComponent {
|
|
|
103
104
|
* @internal
|
|
104
105
|
* Render the contours
|
|
105
106
|
*/
|
|
106
|
-
render(gl,
|
|
107
|
+
render(gl, arg) {
|
|
107
108
|
if (this.gl_elems === null || this.contours === null)
|
|
108
109
|
return;
|
|
109
110
|
const gl_elems = this.gl_elems;
|
|
110
|
-
if (matrix instanceof Float32Array)
|
|
111
|
-
matrix = [...matrix];
|
|
112
111
|
const zoom = gl_elems.map.getZoom();
|
|
113
112
|
const map_width = gl_elems.map.getCanvas().width;
|
|
114
113
|
const map_height = gl_elems.map.getCanvas().height;
|
|
115
114
|
const bearing = gl_elems.map.getBearing();
|
|
116
115
|
const pitch = gl_elems.map.getPitch();
|
|
117
|
-
this.contours.forEach(cnt => cnt.render(gl,
|
|
116
|
+
this.contours.forEach(cnt => cnt.render(gl, arg, [map_width, map_height], zoom, bearing, pitch));
|
|
118
117
|
}
|
|
119
118
|
}
|
|
120
119
|
const contour_label_opt_defaults = {
|
|
@@ -124,8 +123,17 @@ const contour_label_opt_defaults = {
|
|
|
124
123
|
font_url_template: '',
|
|
125
124
|
text_color: '#000000',
|
|
126
125
|
halo_color: '#000000',
|
|
127
|
-
halo: false
|
|
126
|
+
halo: false,
|
|
127
|
+
density: 1
|
|
128
128
|
};
|
|
129
|
+
/**
|
|
130
|
+
* Label the contours on a plot
|
|
131
|
+
* @example
|
|
132
|
+
* // Contour some data
|
|
133
|
+
* const contours = new Contour(height_field, {color: '#000000', interval: 30});
|
|
134
|
+
* // Label the contours
|
|
135
|
+
* const labels = new ContourLabels(contours, {text_color: '#ffffff', halo: true});
|
|
136
|
+
*/
|
|
129
137
|
class ContourLabels extends PlotComponent {
|
|
130
138
|
constructor(contours, opts) {
|
|
131
139
|
super();
|
|
@@ -146,14 +154,13 @@ class ContourLabels extends PlotComponent {
|
|
|
146
154
|
const font_url_template = this.opts.font_url_template == '' ? map_style.glyphs : this.opts.font_url_template;
|
|
147
155
|
if (font_url_template === undefined)
|
|
148
156
|
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('{
|
|
157
|
+
const font_url = font_url_template.replace('{fontstack}', this.opts.font_face);
|
|
150
158
|
const label_pos = [];
|
|
151
159
|
const contour_data = await this.contours.getContours();
|
|
152
160
|
const contour_levels = Object.keys(contour_data).map(parseFloat);
|
|
153
161
|
contour_levels.sort((a, b) => a - b);
|
|
154
162
|
const map_max_zoom = map.getMaxZoom();
|
|
155
|
-
const contour_label_spacing = 0.
|
|
156
|
-
let min_label_lat = null, max_label_lat = null, min_label_lon = null, max_label_lon = null;
|
|
163
|
+
const contour_label_spacing = 0.006 / this.opts.density * Math.pow(2, 7 - map_max_zoom);
|
|
157
164
|
Object.entries(contour_data).forEach(([level, contours]) => {
|
|
158
165
|
const icntr = (parseFloat(level) - contour_levels[0]);
|
|
159
166
|
const level_str = level.toString();
|
|
@@ -176,63 +183,29 @@ class ContourLabels extends PlotComponent {
|
|
|
176
183
|
let n_labels_placed = 0;
|
|
177
184
|
for (let idist = 1; idist < dist.length; idist++) {
|
|
178
185
|
const target_dist = contour_label_spacing * (n_labels_placed + (icntr / 2) % 1);
|
|
186
|
+
// This works fine when contour_label_spacing > the spacing between points along a contour, but when you allow the map to zoom in
|
|
187
|
+
// (and therefore contour_label_spacing gets small), dist[idist] outruns target_dist, so it doesn't put any more labels after the first.
|
|
179
188
|
if (dist[idist - 1] <= target_dist && target_dist < dist[idist]) {
|
|
180
189
|
const pt1 = contour[idist - 1];
|
|
181
190
|
const pt2 = contour[idist];
|
|
182
191
|
const alpha = (target_dist - dist[idist - 1]) / (dist[idist] - dist[idist - 1]);
|
|
183
192
|
const pt_lon = (1 - alpha) * pt1[0] + alpha * pt2[0];
|
|
184
193
|
const pt_lat = (1 - alpha) * pt1[1] + alpha * pt2[1];
|
|
185
|
-
|
|
186
|
-
min_label_lon = pt_lon;
|
|
187
|
-
if (max_label_lon === null || pt_lon > max_label_lon)
|
|
188
|
-
max_label_lon = pt_lon;
|
|
189
|
-
if (min_label_lat === null || pt_lat < min_label_lat)
|
|
190
|
-
min_label_lat = pt_lat;
|
|
191
|
-
if (max_label_lat === null || pt_lat > max_label_lat)
|
|
192
|
-
max_label_lat = pt_lat;
|
|
193
|
-
label_pos.push({ lon: pt_lon, lat: pt_lat, min_zoom: map_max_zoom, text: level_str });
|
|
194
|
+
label_pos.push({ coord: { lon: pt_lon, lat: pt_lat }, text: level_str });
|
|
194
195
|
n_labels_placed++;
|
|
195
196
|
}
|
|
196
197
|
}
|
|
197
198
|
});
|
|
198
199
|
});
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
}
|
|
203
|
-
const { x: min_label_x, y: max_label_y } = new LngLat(min_label_lon, min_label_lat).toMercatorCoord();
|
|
204
|
-
const { x: max_label_x, y: min_label_y } = new LngLat(max_label_lon, max_label_lat).toMercatorCoord();
|
|
205
|
-
const thin_grid_width = max_label_x - min_label_x;
|
|
206
|
-
const thin_grid_height = max_label_y - min_label_y;
|
|
207
|
-
const ni_thin_grid = Math.round(4 * thin_grid_width / contour_label_spacing);
|
|
208
|
-
const nj_thin_grid = Math.round(4 * thin_grid_height / contour_label_spacing);
|
|
209
|
-
const thin_grid_xs = [];
|
|
210
|
-
const thin_grid_ys = [];
|
|
211
|
-
for (let idx = 0; idx < ni_thin_grid; idx++) {
|
|
212
|
-
thin_grid_xs.push(min_label_x + (idx / ni_thin_grid) * thin_grid_width);
|
|
213
|
-
}
|
|
214
|
-
for (let jdy = 0; jdy < nj_thin_grid; jdy++) {
|
|
215
|
-
thin_grid_ys.push(min_label_y + (jdy / nj_thin_grid) * thin_grid_height);
|
|
216
|
-
}
|
|
217
|
-
let skip = 1;
|
|
218
|
-
for (let zoom = map_max_zoom - 1; zoom >= 0; zoom--) {
|
|
219
|
-
for (let idx = 0; idx < ni_thin_grid; idx += skip) {
|
|
220
|
-
for (let jdy = 0; jdy < nj_thin_grid; jdy += skip) {
|
|
221
|
-
const grid_x = thin_grid_xs[idx];
|
|
222
|
-
const grid_y = thin_grid_ys[jdy];
|
|
223
|
-
const ll = LngLat.fromMercatorCoord(grid_x, grid_y);
|
|
224
|
-
const [label, dist] = tree.nearest({ lon: ll.lng, lat: ll.lat, min_zoom: 0, text: "" }, 1)[0];
|
|
225
|
-
label.min_zoom = zoom;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
skip *= 2;
|
|
229
|
-
}
|
|
200
|
+
const label_grid = new UnstructuredGrid(label_pos.map(lp => lp.coord));
|
|
201
|
+
const min_zoom = label_grid.getMinVisibleZoom(4);
|
|
202
|
+
const text_specs = label_pos.map((lp, ilp) => ({ ...lp.coord, min_zoom: min_zoom[ilp], text: lp.text }));
|
|
230
203
|
const tc_opts = {
|
|
231
204
|
horizontal_align: 'center', vertical_align: 'middle', font_size: this.opts.font_size,
|
|
232
205
|
halo: this.opts.halo,
|
|
233
206
|
text_color: Color.fromHex(this.opts.text_color), halo_color: Color.fromHex(this.opts.halo_color),
|
|
234
207
|
};
|
|
235
|
-
this.text_collection = await TextCollection.make(gl,
|
|
208
|
+
this.text_collection = await TextCollection.make(gl, text_specs, font_url, tc_opts);
|
|
236
209
|
map.triggerRepaint();
|
|
237
210
|
}
|
|
238
211
|
/**
|
|
@@ -249,14 +222,14 @@ class ContourLabels extends PlotComponent {
|
|
|
249
222
|
* @internal
|
|
250
223
|
* Render the contour labels
|
|
251
224
|
*/
|
|
252
|
-
render(gl,
|
|
225
|
+
render(gl, arg) {
|
|
253
226
|
if (this.gl_elems === null || this.text_collection === null)
|
|
254
227
|
return;
|
|
255
228
|
const gl_elems = this.gl_elems;
|
|
256
229
|
const map_width = gl_elems.map.getCanvas().width;
|
|
257
230
|
const map_height = gl_elems.map.getCanvas().height;
|
|
258
231
|
const map_zoom = gl_elems.map.getZoom();
|
|
259
|
-
this.text_collection.render(gl,
|
|
232
|
+
this.text_collection.render(gl, arg, [map_width, map_height], map_zoom);
|
|
260
233
|
}
|
|
261
234
|
}
|
|
262
235
|
export default Contour;
|
package/lib/ContourCreator.d.ts
CHANGED
|
@@ -2,7 +2,11 @@ import { MarchingSquaresModule } from './cpp/marchingsquares';
|
|
|
2
2
|
import './cpp/marchingsquares.wasm';
|
|
3
3
|
import { Grid } from "./Grid";
|
|
4
4
|
import { ContourData, TypedArray } from "./AutumnTypes";
|
|
5
|
-
|
|
5
|
+
interface InitMSModuleOpts {
|
|
6
|
+
document_script?: string;
|
|
7
|
+
}
|
|
8
|
+
declare function initMSModule(opts: InitMSModuleOpts): Promise<MarchingSquaresModule>;
|
|
9
|
+
/** Options for contouring data via {@link RawScalarField.getContours | RawScalarField.getContours()} */
|
|
6
10
|
interface FieldContourOpts {
|
|
7
11
|
/**
|
|
8
12
|
* The interval at which to create contours. The field will be contoured at this interval from its minimum to its maximum.
|
|
@@ -12,6 +16,10 @@ interface FieldContourOpts {
|
|
|
12
16
|
* Contour the field at these specific levels.
|
|
13
17
|
*/
|
|
14
18
|
levels?: number[];
|
|
19
|
+
/**
|
|
20
|
+
* Add triangles in the contouring, which takes longer and generates more detailed (not necessarily smoother or better) contours
|
|
21
|
+
*/
|
|
22
|
+
quad_as_tri?: boolean;
|
|
15
23
|
}
|
|
16
24
|
declare function contourCreator<ArrayType extends TypedArray>(data: ArrayType, grid: Grid, opts: FieldContourOpts): Promise<ContourData>;
|
|
17
25
|
export { contourCreator, initMSModule };
|
package/lib/ContourCreator.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import Module from './cpp/marchingsquares';
|
|
2
2
|
import './cpp/marchingsquares.wasm';
|
|
3
3
|
let msm_promise = null;
|
|
4
|
-
function initMSModule() {
|
|
4
|
+
function initMSModule(opts) {
|
|
5
5
|
if (msm_promise === null) {
|
|
6
|
-
msm_promise = Module();
|
|
6
|
+
msm_promise = Module({ 'locateFile': (fname, dir) => (opts.document_script === undefined ? dir : opts.document_script) + fname });
|
|
7
7
|
}
|
|
8
8
|
return msm_promise;
|
|
9
9
|
}
|
|
@@ -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
|
|
15
|
+
const quad_as_tri = opts.quad_as_tri === undefined ? false : opts.quad_as_tri;
|
|
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,37 +2,51 @@ 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';
|
|
7
|
+
/** Options for {@link ContourFill} components */
|
|
6
8
|
interface ContourFillOptions {
|
|
7
|
-
/** The color
|
|
8
|
-
cmap: ColorMap;
|
|
9
|
+
/** The color maps to use when creating the fills */
|
|
10
|
+
cmap: ColorMap | ColorMap[];
|
|
11
|
+
/**
|
|
12
|
+
* A mask specifying where to use each color map. This should be on the same grid as the RawScalarField passed.
|
|
13
|
+
* A 1 in the mask means to use the first colormap, a 2 means to use the second colormap, etc.
|
|
14
|
+
*/
|
|
15
|
+
cmap_mask?: Uint8Array | null;
|
|
9
16
|
/**
|
|
10
17
|
* The opacity for the filled contours
|
|
11
18
|
* @default 1
|
|
12
19
|
*/
|
|
13
20
|
opacity?: number;
|
|
14
21
|
}
|
|
22
|
+
/** Options for {@link Raster} components */
|
|
15
23
|
interface RasterOptions {
|
|
16
24
|
/** The color map to use when creating the raster plot */
|
|
17
|
-
cmap: ColorMap;
|
|
25
|
+
cmap: ColorMap | ColorMap[];
|
|
26
|
+
/**
|
|
27
|
+
* A mask specifying where to use each color map. This should be on the same grid as the RawScalarField passed.
|
|
28
|
+
* A 1 in the mask means to use the first colormap, a 2 means to use the second colormap, etc.
|
|
29
|
+
*/
|
|
30
|
+
cmap_mask?: Uint8Array | null;
|
|
18
31
|
/**
|
|
19
32
|
* The opacity for the raster plot
|
|
20
33
|
* @default 1
|
|
21
34
|
*/
|
|
22
35
|
opacity?: number;
|
|
23
36
|
}
|
|
24
|
-
declare class PlotComponentFill<ArrayType extends TypedArray, MapType extends MapLikeType> extends PlotComponent<MapType> {
|
|
37
|
+
declare class PlotComponentFill<ArrayType extends TypedArray, GridType extends StructuredGrid, MapType extends MapLikeType> extends PlotComponent<MapType> {
|
|
25
38
|
private field;
|
|
26
39
|
readonly opts: Required<ContourFillOptions>;
|
|
27
40
|
private readonly cmap_gpu;
|
|
28
41
|
private gl_elems;
|
|
29
42
|
private fill_texture;
|
|
43
|
+
private mask_texture;
|
|
30
44
|
protected image_mag_filter: number | null;
|
|
31
45
|
protected cmap_mag_filter: number | null;
|
|
32
|
-
constructor(field: RawScalarField<ArrayType>, opts: ContourFillOptions);
|
|
33
|
-
updateField(field: RawScalarField<ArrayType
|
|
46
|
+
constructor(field: RawScalarField<ArrayType, GridType>, opts: ContourFillOptions);
|
|
47
|
+
updateField(field: RawScalarField<ArrayType, GridType>, mask?: Uint8Array): Promise<void>;
|
|
34
48
|
onAdd(map: MapType, gl: WebGLAnyRenderingContext): Promise<void>;
|
|
35
|
-
render(gl: WebGLAnyRenderingContext,
|
|
49
|
+
render(gl: WebGLAnyRenderingContext, arg: RenderMethodArg): void;
|
|
36
50
|
}
|
|
37
51
|
/**
|
|
38
52
|
* A raster (i.e. pixel) plot
|
|
@@ -40,18 +54,18 @@ declare class PlotComponentFill<ArrayType extends TypedArray, MapType extends Ma
|
|
|
40
54
|
* // Create a raster plot with the provided color map
|
|
41
55
|
* const raster = new Raster(wind_speed_field, {cmap: color_map});
|
|
42
56
|
*/
|
|
43
|
-
declare class Raster<ArrayType extends TypedArray, MapType extends MapLikeType> extends PlotComponentFill<ArrayType, MapType> {
|
|
57
|
+
declare class Raster<ArrayType extends TypedArray, GridType extends StructuredGrid, MapType extends MapLikeType> extends PlotComponentFill<ArrayType, GridType, MapType> {
|
|
44
58
|
/**
|
|
45
59
|
* Create a raster plot
|
|
46
60
|
* @param field - The field to create the raster plot from
|
|
47
61
|
* @param opts - Options for creating the raster plot
|
|
48
62
|
*/
|
|
49
|
-
constructor(field: RawScalarField<ArrayType>, opts: RasterOptions);
|
|
63
|
+
constructor(field: RawScalarField<ArrayType, GridType>, opts: RasterOptions);
|
|
50
64
|
/**
|
|
51
65
|
* Update the data displayed as a raster plot
|
|
52
66
|
* @param field - The new field to display as a raster plot
|
|
53
67
|
*/
|
|
54
|
-
updateField(field: RawScalarField<ArrayType
|
|
68
|
+
updateField(field: RawScalarField<ArrayType, GridType>, mask?: Uint8Array): Promise<void>;
|
|
55
69
|
/**
|
|
56
70
|
* @internal
|
|
57
71
|
* Add the raster plot to a map
|
|
@@ -69,18 +83,18 @@ declare class Raster<ArrayType extends TypedArray, MapType extends MapLikeType>
|
|
|
69
83
|
* // Create a field of filled contours with the provided color map
|
|
70
84
|
* const fill = new ContourFill(wind_speed_field, {cmap: color_map});
|
|
71
85
|
*/
|
|
72
|
-
declare class ContourFill<ArrayType extends TypedArray, MapType extends MapLikeType> extends PlotComponentFill<ArrayType, MapType> {
|
|
86
|
+
declare class ContourFill<ArrayType extends TypedArray, GridType extends StructuredGrid, MapType extends MapLikeType> extends PlotComponentFill<ArrayType, GridType, MapType> {
|
|
73
87
|
/**
|
|
74
88
|
* Create a filled contoured field
|
|
75
89
|
* @param field - The field to create filled contours from
|
|
76
90
|
* @param opts - Options for creating the filled contours
|
|
77
91
|
*/
|
|
78
|
-
constructor(field: RawScalarField<ArrayType>, opts: ContourFillOptions);
|
|
92
|
+
constructor(field: RawScalarField<ArrayType, GridType>, opts: ContourFillOptions);
|
|
79
93
|
/**
|
|
80
94
|
* Update the data displayed as filled contours
|
|
81
95
|
* @param field - The new field to display as filled contours
|
|
82
96
|
*/
|
|
83
|
-
updateField(field: RawScalarField<ArrayType
|
|
97
|
+
updateField(field: RawScalarField<ArrayType, GridType>, mask?: Uint8Array): Promise<void>;
|
|
84
98
|
/**
|
|
85
99
|
* @internal
|
|
86
100
|
* Add the filled contours to a map
|
package/lib/Fill.js
CHANGED
|
@@ -1,49 +1,70 @@
|
|
|
1
|
-
import { PlotComponent
|
|
1
|
+
import { PlotComponent } from './PlotComponent';
|
|
2
2
|
import { ColorMap, ColorMapGPUInterface } from './Colormap';
|
|
3
|
-
import {
|
|
3
|
+
import { WGLTexture } from 'autumn-wgl';
|
|
4
|
+
import { RawScalarField } from './RawField';
|
|
5
|
+
import { getRendererData } from './AutumnTypes';
|
|
4
6
|
import { normalizeOptions } from './utils';
|
|
5
|
-
|
|
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;
|
|
29
|
+
|
|
30
|
+
#ifdef MASK
|
|
31
|
+
uniform sampler2D u_mask_sampler;
|
|
32
|
+
#endif
|
|
33
|
+
|
|
23
34
|
uniform highp float u_opacity;
|
|
35
|
+
#ifdef MASK
|
|
36
|
+
uniform int u_mask_val;
|
|
37
|
+
#endif
|
|
24
38
|
|
|
25
|
-
|
|
26
|
-
return ( val < 0.0 || 0.0 < val || val == 0.0 ) ? false : true;
|
|
27
|
-
}
|
|
39
|
+
out highp vec4 fragColor;
|
|
28
40
|
|
|
29
41
|
void main() {
|
|
30
|
-
highp float fill_val =
|
|
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
|
|
31
50
|
|
|
32
|
-
if (isnan(fill_val)) {
|
|
51
|
+
if (isnan(fill_val) || draw_mask == 0) {
|
|
33
52
|
discard;
|
|
34
53
|
}
|
|
35
54
|
|
|
36
55
|
lowp vec4 color = apply_colormap(fill_val);
|
|
37
56
|
color.a = color.a * u_opacity;
|
|
38
|
-
|
|
57
|
+
fragColor = color;
|
|
39
58
|
}`
|
|
40
59
|
const default_cmap = new ColorMap([0, 1], ['#000000'], { overflow_color: '#000000', underflow_color: '#000000' });
|
|
41
60
|
const contour_fill_opt_defaults = {
|
|
42
|
-
cmap: default_cmap,
|
|
61
|
+
cmap: [default_cmap],
|
|
62
|
+
cmap_mask: null,
|
|
43
63
|
opacity: 1,
|
|
44
64
|
};
|
|
45
65
|
const raster_opt_defaults = {
|
|
46
|
-
cmap: default_cmap,
|
|
66
|
+
cmap: [default_cmap],
|
|
67
|
+
cmap_mask: null,
|
|
47
68
|
opacity: 1,
|
|
48
69
|
};
|
|
49
70
|
class PlotComponentFill extends PlotComponent {
|
|
@@ -51,65 +72,91 @@ class PlotComponentFill extends PlotComponent {
|
|
|
51
72
|
super();
|
|
52
73
|
this.field = field;
|
|
53
74
|
this.opts = normalizeOptions(opts, contour_fill_opt_defaults);
|
|
54
|
-
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));
|
|
55
77
|
this.gl_elems = null;
|
|
56
78
|
this.fill_texture = null;
|
|
79
|
+
this.mask_texture = null;
|
|
57
80
|
this.image_mag_filter = null;
|
|
58
81
|
this.cmap_mag_filter = null;
|
|
59
82
|
}
|
|
60
|
-
async updateField(field) {
|
|
83
|
+
async updateField(field, mask) {
|
|
61
84
|
this.field = field;
|
|
62
85
|
if (this.image_mag_filter === null || this.cmap_mag_filter === null) {
|
|
63
|
-
throw `Implement magnification
|
|
86
|
+
throw `Implement magnification filters in a subclass`;
|
|
64
87
|
}
|
|
65
88
|
if (this.gl_elems === null)
|
|
66
89
|
return;
|
|
67
90
|
const gl = this.gl_elems.gl;
|
|
68
91
|
const map = this.gl_elems.map;
|
|
69
|
-
const
|
|
70
|
-
const { format, type, row_alignment } = getGLFormatTypeAlignment(gl, !(tex_data instanceof Float32Array));
|
|
71
|
-
const fill_image = { 'format': format, 'type': type,
|
|
72
|
-
'width': this.field.grid.ni, 'height': this.field.grid.nj, 'image': tex_data,
|
|
73
|
-
'mag_filter': this.image_mag_filter, 'row_alignment': row_alignment,
|
|
74
|
-
};
|
|
92
|
+
const fill_image = this.field.getWGLTextureSpec(gl, this.image_mag_filter);
|
|
75
93
|
if (this.fill_texture === null) {
|
|
76
94
|
this.fill_texture = new WGLTexture(gl, fill_image);
|
|
77
95
|
}
|
|
78
96
|
else {
|
|
79
97
|
this.fill_texture.setImageData(fill_image);
|
|
80
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
|
+
}
|
|
81
113
|
map.triggerRepaint();
|
|
82
114
|
}
|
|
83
115
|
async onAdd(map, gl) {
|
|
84
116
|
// Basic procedure for the filled contours inspired by https://blog.mbq.me/webgl-weather-globe/
|
|
85
|
-
if (this.image_mag_filter === null || this.cmap_mag_filter === null) {
|
|
86
|
-
throw `Implement magnification filtes in a subclass`;
|
|
87
|
-
}
|
|
88
|
-
const program = new WGLProgram(gl, contourfill_vertex_shader_src, ColorMapGPUInterface.applyShader(contourfill_fragment_shader_src));
|
|
89
117
|
const { vertices: vertices, texcoords: texcoords } = await this.field.grid.getWGLBuffers(gl);
|
|
90
|
-
this.cmap_gpu.
|
|
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');
|
|
127
|
+
}
|
|
128
|
+
const shader_manger = new ShaderProgramManager(contourfill_vertex_shader_src, ColorMapGPUInterface.applyShader(contourfill_fragment_shader_src), shader_defines);
|
|
91
129
|
this.gl_elems = {
|
|
92
|
-
gl: gl,
|
|
130
|
+
gl: gl, shader_manager: shader_manger, map: map, vertices: vertices, texcoords: texcoords,
|
|
93
131
|
};
|
|
94
|
-
this.updateField(this.field);
|
|
132
|
+
this.updateField(this.field, this.opts.cmap_mask === null ? undefined : this.opts.cmap_mask);
|
|
95
133
|
}
|
|
96
|
-
render(gl,
|
|
134
|
+
render(gl, arg) {
|
|
97
135
|
if (this.gl_elems === null || this.fill_texture === null)
|
|
98
136
|
return;
|
|
99
137
|
const gl_elems = this.gl_elems;
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
this.cmap_gpu.
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
+
});
|
|
113
160
|
}
|
|
114
161
|
}
|
|
115
162
|
/**
|
|
@@ -131,8 +178,8 @@ class Raster extends PlotComponentFill {
|
|
|
131
178
|
* Update the data displayed as a raster plot
|
|
132
179
|
* @param field - The new field to display as a raster plot
|
|
133
180
|
*/
|
|
134
|
-
async updateField(field) {
|
|
135
|
-
await super.updateField(field);
|
|
181
|
+
async updateField(field, mask) {
|
|
182
|
+
await super.updateField(field, mask);
|
|
136
183
|
}
|
|
137
184
|
/**
|
|
138
185
|
* @internal
|
|
@@ -170,8 +217,8 @@ class ContourFill extends PlotComponentFill {
|
|
|
170
217
|
* Update the data displayed as filled contours
|
|
171
218
|
* @param field - The new field to display as filled contours
|
|
172
219
|
*/
|
|
173
|
-
async updateField(field) {
|
|
174
|
-
await super.updateField(field);
|
|
220
|
+
async updateField(field, mask) {
|
|
221
|
+
await super.updateField(field, mask);
|
|
175
222
|
}
|
|
176
223
|
/**
|
|
177
224
|
* @internal
|