autumnplot-gl 2.2.3 → 3.1.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 +65 -8
- package/dist/110.autumnplot-gl.js +2 -0
- package/dist/110.autumnplot-gl.js.map +1 -0
- package/dist/autumnplot-gl.js +3 -0
- package/dist/autumnplot-gl.js.LICENSE.txt +12 -0
- package/dist/autumnplot-gl.js.map +1 -0
- package/dist/marchingsquares.wasm +0 -0
- package/lib/AutumnTypes.d.ts +14 -13
- package/lib/Barbs.d.ts +10 -5
- package/lib/Barbs.js +22 -5
- package/lib/BillboardCollection.d.ts +11 -7
- package/lib/BillboardCollection.js +52 -30
- package/lib/ColorBar.js +51 -7
- package/lib/Colormap.d.ts +14 -3
- package/lib/Colormap.js +63 -12
- package/lib/Contour.d.ts +73 -18
- package/lib/Contour.js +180 -148
- package/lib/ContourCreator.d.ts +18 -0
- package/lib/ContourCreator.js +23 -0
- package/lib/Fill.d.ts +18 -7
- package/lib/Fill.js +73 -41
- package/lib/Grid.d.ts +167 -0
- package/lib/Grid.js +339 -0
- package/lib/Hodographs.d.ts +13 -6
- package/lib/Hodographs.js +58 -71
- package/lib/Map.d.ts +14 -3
- package/lib/Map.js +9 -0
- package/lib/Paintball.d.ts +11 -5
- package/lib/Paintball.js +35 -15
- package/lib/PlotComponent.d.ts +5 -5
- package/lib/PlotLayer.d.ts +12 -12
- package/lib/PlotLayer.js +16 -14
- package/lib/PlotLayer.worker.d.ts +2 -2
- package/lib/PlotLayer.worker.js +105 -66
- package/lib/PolylineCollection.d.ts +20 -9
- package/lib/PolylineCollection.js +158 -32
- package/lib/RawField.d.ts +11 -167
- package/lib/RawField.js +37 -383
- package/lib/TextCollection.d.ts +31 -0
- package/lib/TextCollection.js +295 -0
- package/lib/cpp/marchingsquares.d.ts +6 -0
- package/lib/cpp/marchingsquares.js +3449 -0
- package/lib/cpp/marchingsquares.wasm +0 -0
- package/lib/cpp/marchingsquares_embind.d.ts +6 -0
- package/lib/index.d.ts +13 -6
- package/lib/index.js +12 -3
- package/lib/utils.d.ts +5 -3
- package/lib/utils.js +17 -6
- package/package.json +14 -9
package/lib/Contour.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { TypedArray, WebGLAnyRenderingContext } from './AutumnTypes';
|
|
2
|
-
import {
|
|
2
|
+
import { MapLikeType } from './Map';
|
|
3
3
|
import { PlotComponent } from './PlotComponent';
|
|
4
4
|
import { RawScalarField } from './RawField';
|
|
5
5
|
interface ContourOptions {
|
|
@@ -18,34 +18,31 @@ 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.
|
|
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)
|
|
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
|
|
38
|
-
readonly
|
|
39
|
-
readonly interval: number;
|
|
40
|
-
readonly levels: number[];
|
|
41
|
-
readonly thinner: (zoom: number) => number;
|
|
29
|
+
declare class Contour<ArrayType extends TypedArray, MapType extends MapLikeType> extends PlotComponent<MapType> {
|
|
30
|
+
private field;
|
|
31
|
+
readonly opts: Required<ContourOptions>;
|
|
42
32
|
private gl_elems;
|
|
33
|
+
private contours;
|
|
43
34
|
/**
|
|
44
35
|
* Create a contoured field
|
|
45
36
|
* @param field - The field to contour
|
|
46
37
|
* @param opts - Options for creating the contours
|
|
47
38
|
*/
|
|
48
39
|
constructor(field: RawScalarField<ArrayType>, opts: ContourOptions);
|
|
40
|
+
/**
|
|
41
|
+
* Update the data displayed as contours
|
|
42
|
+
* @param field - The new field to contour
|
|
43
|
+
*/
|
|
44
|
+
updateField(field: RawScalarField<ArrayType>): Promise<void>;
|
|
45
|
+
getContours(): Promise<import("./AutumnTypes").ContourData>;
|
|
49
46
|
/**
|
|
50
47
|
* @internal
|
|
51
48
|
* Add the contours to a map
|
|
@@ -57,5 +54,63 @@ declare class Contour<ArrayType extends TypedArray> extends PlotComponent {
|
|
|
57
54
|
*/
|
|
58
55
|
render(gl: WebGLAnyRenderingContext, matrix: number[] | Float32Array): void;
|
|
59
56
|
}
|
|
57
|
+
interface ContourLabelOptions {
|
|
58
|
+
/**
|
|
59
|
+
* Number of decimal places to use in the contour labels
|
|
60
|
+
* @default 0
|
|
61
|
+
*/
|
|
62
|
+
n_decimal_places?: number;
|
|
63
|
+
/**
|
|
64
|
+
* Font face to use for the contour labels
|
|
65
|
+
* @default 'Trebuchet MS'
|
|
66
|
+
*/
|
|
67
|
+
font_face?: string;
|
|
68
|
+
/**
|
|
69
|
+
* Font size in points to use for the contour labels
|
|
70
|
+
* @default 12
|
|
71
|
+
*/
|
|
72
|
+
font_size?: number;
|
|
73
|
+
/**
|
|
74
|
+
* URL template to use in retrieving the font data for the labels. The default is to use the template from the map style.
|
|
75
|
+
*/
|
|
76
|
+
font_url_template?: string;
|
|
77
|
+
/**
|
|
78
|
+
* Text color for the contour labels
|
|
79
|
+
* @default '#000000'
|
|
80
|
+
*/
|
|
81
|
+
text_color?: string;
|
|
82
|
+
/**
|
|
83
|
+
* Halo (outline) color for the contour labels
|
|
84
|
+
* @default '#000000'
|
|
85
|
+
*/
|
|
86
|
+
halo_color?: string;
|
|
87
|
+
/**
|
|
88
|
+
* Whether to draw the halo (outline) on the contour labels
|
|
89
|
+
* @default false
|
|
90
|
+
*/
|
|
91
|
+
halo?: boolean;
|
|
92
|
+
}
|
|
93
|
+
declare class ContourLabels<ArrayType extends TypedArray, MapType extends MapLikeType> extends PlotComponent<MapType> {
|
|
94
|
+
private readonly contours;
|
|
95
|
+
private gl_elems;
|
|
96
|
+
private text_collection;
|
|
97
|
+
private readonly opts;
|
|
98
|
+
constructor(contours: Contour<ArrayType, MapType>, opts?: ContourLabelOptions);
|
|
99
|
+
/**
|
|
100
|
+
* Update contour labels when the field for the associated Contour object has been changed.
|
|
101
|
+
*/
|
|
102
|
+
updateField(): Promise<void>;
|
|
103
|
+
/**
|
|
104
|
+
* @internal
|
|
105
|
+
* Add the contour labels to a map
|
|
106
|
+
*/
|
|
107
|
+
onAdd(map: MapType, gl: WebGLAnyRenderingContext): Promise<void>;
|
|
108
|
+
/**
|
|
109
|
+
* @internal
|
|
110
|
+
* Render the contour labels
|
|
111
|
+
*/
|
|
112
|
+
render(gl: WebGLAnyRenderingContext, matrix: number[]): void;
|
|
113
|
+
}
|
|
60
114
|
export default Contour;
|
|
61
|
-
export
|
|
115
|
+
export { ContourLabels };
|
|
116
|
+
export type { ContourOptions, ContourLabelOptions };
|
package/lib/Contour.js
CHANGED
|
@@ -1,116 +1,20 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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';
|
|
7
|
+
const contour_opt_defaults = {
|
|
8
|
+
color: '#000000',
|
|
9
|
+
interval: 1,
|
|
10
|
+
levels: undefined
|
|
11
|
+
};
|
|
107
12
|
/**
|
|
108
|
-
* A field of contoured data.
|
|
13
|
+
* A field of contoured data.
|
|
109
14
|
* @example
|
|
110
15
|
* // Create a contoured height field, with black contours every 30 m (assuming the height field is in
|
|
111
|
-
* // meters)
|
|
112
|
-
* const contours = new Contour(height_field, {color: '#000000', interval: 30
|
|
113
|
-
* thinner: zoom => zoom < 5 ? 2 : 1});
|
|
16
|
+
* // meters).
|
|
17
|
+
* const contours = new Contour(height_field, {color: '#000000', interval: 30});
|
|
114
18
|
*/
|
|
115
19
|
class Contour extends PlotComponent {
|
|
116
20
|
/**
|
|
@@ -121,65 +25,193 @@ class Contour extends PlotComponent {
|
|
|
121
25
|
constructor(field, opts) {
|
|
122
26
|
super();
|
|
123
27
|
this.field = field;
|
|
124
|
-
this.
|
|
125
|
-
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);
|
|
28
|
+
this.opts = normalizeOptions(opts, contour_opt_defaults);
|
|
129
29
|
this.gl_elems = null;
|
|
30
|
+
this.contours = null;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Update the data displayed as contours
|
|
34
|
+
* @param field - The new field to contour
|
|
35
|
+
*/
|
|
36
|
+
async updateField(field) {
|
|
37
|
+
this.field = field;
|
|
38
|
+
if (this.gl_elems === null)
|
|
39
|
+
return;
|
|
40
|
+
const gl = this.gl_elems.gl;
|
|
41
|
+
const contour_data = await this.getContours();
|
|
42
|
+
const line_data = Object.values(contour_data).flat().map(c => {
|
|
43
|
+
return { vertices: c };
|
|
44
|
+
});
|
|
45
|
+
this.contours = await PolylineCollection.make(gl, line_data, { line_width: 2, color: this.opts.color });
|
|
46
|
+
this.gl_elems.map.triggerRepaint();
|
|
47
|
+
}
|
|
48
|
+
async getContours() {
|
|
49
|
+
return await this.field.getContours({ interval: this.opts.interval, levels: this.opts.levels });
|
|
130
50
|
}
|
|
131
51
|
/**
|
|
132
52
|
* @internal
|
|
133
53
|
* Add the contours to a map
|
|
134
54
|
*/
|
|
135
55
|
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
56
|
this.gl_elems = {
|
|
150
|
-
|
|
57
|
+
gl: gl, map: map
|
|
151
58
|
};
|
|
59
|
+
await this.updateField(this.field);
|
|
152
60
|
}
|
|
153
61
|
/**
|
|
154
62
|
* @internal
|
|
155
63
|
* Render the contours
|
|
156
64
|
*/
|
|
157
65
|
render(gl, matrix) {
|
|
158
|
-
if (this.gl_elems === null)
|
|
66
|
+
if (this.gl_elems === null || this.contours === null)
|
|
159
67
|
return;
|
|
160
68
|
const gl_elems = this.gl_elems;
|
|
161
69
|
if (matrix instanceof Float32Array)
|
|
162
70
|
matrix = [...matrix];
|
|
163
71
|
const zoom = gl_elems.map.getZoom();
|
|
164
|
-
const
|
|
165
|
-
const
|
|
166
|
-
const
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
72
|
+
const map_width = gl_elems.map.getCanvas().width;
|
|
73
|
+
const map_height = gl_elems.map.getCanvas().height;
|
|
74
|
+
const bearing = gl_elems.map.getBearing();
|
|
75
|
+
const pitch = gl_elems.map.getPitch();
|
|
76
|
+
this.contours.render(gl, matrix, [map_width, map_height], zoom, bearing, pitch);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const contour_label_opt_defaults = {
|
|
80
|
+
n_decimal_places: 0,
|
|
81
|
+
font_face: 'Trebuchet MS',
|
|
82
|
+
font_size: 12,
|
|
83
|
+
font_url_template: '',
|
|
84
|
+
text_color: '#000000',
|
|
85
|
+
halo_color: '#000000',
|
|
86
|
+
halo: false
|
|
87
|
+
};
|
|
88
|
+
class ContourLabels extends PlotComponent {
|
|
89
|
+
constructor(contours, opts) {
|
|
90
|
+
super();
|
|
91
|
+
this.opts = normalizeOptions(opts, contour_label_opt_defaults);
|
|
92
|
+
this.contours = contours;
|
|
93
|
+
this.text_collection = null;
|
|
94
|
+
this.gl_elems = null;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Update contour labels when the field for the associated Contour object has been changed.
|
|
98
|
+
*/
|
|
99
|
+
async updateField() {
|
|
100
|
+
if (this.gl_elems === null)
|
|
101
|
+
return;
|
|
102
|
+
const map = this.gl_elems.map;
|
|
103
|
+
const gl = this.gl_elems.gl;
|
|
104
|
+
const map_style = map.getStyle();
|
|
105
|
+
const font_url_template = this.opts.font_url_template == '' ? map_style.glyphs : this.opts.font_url_template;
|
|
106
|
+
const font_url = font_url_template.replace('{range}', '0-255').replace('{fontstack}', this.opts.font_face);
|
|
107
|
+
const label_pos = [];
|
|
108
|
+
const contour_data = await this.contours.getContours();
|
|
109
|
+
const contour_levels = Object.keys(contour_data).map(parseFloat);
|
|
110
|
+
contour_levels.sort((a, b) => a - b);
|
|
111
|
+
const map_max_zoom = map.getMaxZoom();
|
|
112
|
+
const contour_label_spacing = 0.01 * Math.pow(2, 7 - map_max_zoom);
|
|
113
|
+
let min_label_lat = null, max_label_lat = null, min_label_lon = null, max_label_lon = null;
|
|
114
|
+
Object.entries(contour_data).forEach(([level, contours]) => {
|
|
115
|
+
const icntr = (parseFloat(level) - contour_levels[0]);
|
|
116
|
+
const level_str = level.toString();
|
|
117
|
+
contours.forEach(contour => {
|
|
118
|
+
const c_map = contour.map(v => {
|
|
119
|
+
const v_ll = new LngLat(...v).toMercatorCoord();
|
|
120
|
+
return [v_ll.x, v_ll.y];
|
|
121
|
+
});
|
|
122
|
+
const dist = [];
|
|
123
|
+
c_map.forEach((v, i) => {
|
|
124
|
+
if (i == 0) {
|
|
125
|
+
dist.push(0);
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
const v_last = c_map[i - 1];
|
|
129
|
+
const this_dist = Math.hypot(v_last[0] - v[0], v_last[1] - v[1]);
|
|
130
|
+
dist.push(dist[i - 1] + this_dist);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
let n_labels_placed = 0;
|
|
134
|
+
for (let idist = 1; idist < dist.length; idist++) {
|
|
135
|
+
const target_dist = contour_label_spacing * (n_labels_placed + (icntr / 2) % 1);
|
|
136
|
+
if (dist[idist - 1] <= target_dist && target_dist < dist[idist]) {
|
|
137
|
+
const pt1 = contour[idist - 1];
|
|
138
|
+
const pt2 = contour[idist];
|
|
139
|
+
const alpha = (target_dist - dist[idist - 1]) / (dist[idist] - dist[idist - 1]);
|
|
140
|
+
const pt_lon = (1 - alpha) * pt1[0] + alpha * pt2[0];
|
|
141
|
+
const pt_lat = (1 - alpha) * pt1[1] + alpha * pt2[1];
|
|
142
|
+
if (min_label_lon === null || pt_lon < min_label_lon)
|
|
143
|
+
min_label_lon = pt_lon;
|
|
144
|
+
if (max_label_lon === null || pt_lon > max_label_lon)
|
|
145
|
+
max_label_lon = pt_lon;
|
|
146
|
+
if (min_label_lat === null || pt_lat < min_label_lat)
|
|
147
|
+
min_label_lat = pt_lat;
|
|
148
|
+
if (max_label_lat === null || pt_lat > max_label_lat)
|
|
149
|
+
max_label_lat = pt_lat;
|
|
150
|
+
label_pos.push({ lon: pt_lon, lat: pt_lat, min_zoom: map_max_zoom, text: level_str });
|
|
151
|
+
n_labels_placed++;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
const tree = new kdTree(label_pos, (a, b) => Math.hypot(a.lon - b.lon, a.lat - b.lat), ['lon', 'lat']);
|
|
157
|
+
const { x: min_label_x, y: max_label_y } = new LngLat(min_label_lon, min_label_lat).toMercatorCoord();
|
|
158
|
+
const { x: max_label_x, y: min_label_y } = new LngLat(max_label_lon, max_label_lat).toMercatorCoord();
|
|
159
|
+
const thin_grid_width = max_label_x - min_label_x;
|
|
160
|
+
const thin_grid_height = max_label_y - min_label_y;
|
|
161
|
+
const ni_thin_grid = Math.round(4 * thin_grid_width / contour_label_spacing);
|
|
162
|
+
const nj_thin_grid = Math.round(4 * thin_grid_height / contour_label_spacing);
|
|
163
|
+
const thin_grid_xs = [];
|
|
164
|
+
const thin_grid_ys = [];
|
|
165
|
+
for (let idx = 0; idx < ni_thin_grid; idx++) {
|
|
166
|
+
thin_grid_xs.push(min_label_x + (idx / ni_thin_grid) * thin_grid_width);
|
|
167
|
+
}
|
|
168
|
+
for (let jdy = 0; jdy < nj_thin_grid; jdy++) {
|
|
169
|
+
thin_grid_ys.push(min_label_y + (jdy / nj_thin_grid) * thin_grid_height);
|
|
172
170
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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
|
+
}
|
|
184
|
+
const tc_opts = {
|
|
185
|
+
horizontal_align: 'center', vertical_align: 'middle', font_size: this.opts.font_size,
|
|
186
|
+
halo: this.opts.halo,
|
|
187
|
+
text_color: hex2rgb(this.opts.text_color), halo_color: hex2rgb(this.opts.halo_color),
|
|
188
|
+
};
|
|
189
|
+
this.text_collection = await TextCollection.make(gl, label_pos, font_url, tc_opts);
|
|
190
|
+
map.triggerRepaint();
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* @internal
|
|
194
|
+
* Add the contour labels to a map
|
|
195
|
+
*/
|
|
196
|
+
async onAdd(map, gl) {
|
|
197
|
+
this.gl_elems = {
|
|
198
|
+
gl: gl, map: map,
|
|
199
|
+
};
|
|
200
|
+
this.updateField();
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* @internal
|
|
204
|
+
* Render the contour labels
|
|
205
|
+
*/
|
|
206
|
+
render(gl, matrix) {
|
|
207
|
+
if (this.gl_elems === null || this.text_collection === null)
|
|
208
|
+
return;
|
|
209
|
+
const gl_elems = this.gl_elems;
|
|
210
|
+
const map_width = gl_elems.map.getCanvas().width;
|
|
211
|
+
const map_height = gl_elems.map.getCanvas().height;
|
|
212
|
+
const map_zoom = gl_elems.map.getZoom();
|
|
213
|
+
this.text_collection.render(gl, matrix, [map_width, map_height], map_zoom);
|
|
183
214
|
}
|
|
184
215
|
}
|
|
185
216
|
export default Contour;
|
|
217
|
+
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,23 @@
|
|
|
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 msm = await initMSModule();
|
|
16
|
+
const grid_coords = grid.getGridCoords();
|
|
17
|
+
const getContourLevels = data instanceof Float32Array ? msm.getContourLevelsFloat32 : msm.getContourLevelsFloat16;
|
|
18
|
+
const makeContours = data instanceof Float32Array ? msm.makeContoursFloat32 : msm.makeContoursFloat16;
|
|
19
|
+
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
|
+
return contours;
|
|
22
|
+
}
|
|
23
|
+
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 {
|
|
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,17 @@ interface RasterOptions {
|
|
|
21
21
|
*/
|
|
22
22
|
opacity?: number;
|
|
23
23
|
}
|
|
24
|
-
declare class PlotComponentFill<ArrayType extends TypedArray> extends PlotComponent {
|
|
25
|
-
private
|
|
26
|
-
readonly
|
|
27
|
-
readonly opacity: number;
|
|
24
|
+
declare class PlotComponentFill<ArrayType extends TypedArray, MapType extends MapLikeType> extends PlotComponent<MapType> {
|
|
25
|
+
private field;
|
|
26
|
+
readonly opts: Required<ContourFillOptions>;
|
|
28
27
|
private readonly cmap_image;
|
|
29
28
|
private readonly index_map;
|
|
30
29
|
private gl_elems;
|
|
30
|
+
private fill_texture;
|
|
31
31
|
protected image_mag_filter: number | null;
|
|
32
32
|
protected cmap_mag_filter: number | null;
|
|
33
33
|
constructor(field: RawScalarField<ArrayType>, opts: ContourFillOptions);
|
|
34
|
+
updateField(field: RawScalarField<ArrayType>): Promise<void>;
|
|
34
35
|
onAdd(map: MapType, gl: WebGLAnyRenderingContext): Promise<void>;
|
|
35
36
|
render(gl: WebGLAnyRenderingContext, matrix: number[] | Float32Array): void;
|
|
36
37
|
}
|
|
@@ -40,13 +41,18 @@ declare class PlotComponentFill<ArrayType extends TypedArray> extends PlotCompon
|
|
|
40
41
|
* // Create a raster plot with the provided color map
|
|
41
42
|
* const raster = new Raster(wind_speed_field, {cmap: color_map});
|
|
42
43
|
*/
|
|
43
|
-
declare class Raster<ArrayType extends TypedArray> extends PlotComponentFill<ArrayType> {
|
|
44
|
+
declare class Raster<ArrayType extends TypedArray, MapType extends MapLikeType> extends PlotComponentFill<ArrayType, MapType> {
|
|
44
45
|
/**
|
|
45
46
|
* Create a raster plot
|
|
46
47
|
* @param field - The field to create the raster plot from
|
|
47
48
|
* @param opts - Options for creating the raster plot
|
|
48
49
|
*/
|
|
49
50
|
constructor(field: RawScalarField<ArrayType>, opts: RasterOptions);
|
|
51
|
+
/**
|
|
52
|
+
* Update the data displayed as a raster plot
|
|
53
|
+
* @param field - The new field to display as a raster plot
|
|
54
|
+
*/
|
|
55
|
+
updateField(field: RawScalarField<ArrayType>): Promise<void>;
|
|
50
56
|
/**
|
|
51
57
|
* @internal
|
|
52
58
|
* Add the raster plot to a map
|
|
@@ -64,13 +70,18 @@ declare class Raster<ArrayType extends TypedArray> extends PlotComponentFill<Arr
|
|
|
64
70
|
* // Create a field of filled contours with the provided color map
|
|
65
71
|
* const fill = new ContourFill(wind_speed_field, {cmap: color_map});
|
|
66
72
|
*/
|
|
67
|
-
declare class ContourFill<ArrayType extends TypedArray> extends PlotComponentFill<ArrayType> {
|
|
73
|
+
declare class ContourFill<ArrayType extends TypedArray, MapType extends MapLikeType> extends PlotComponentFill<ArrayType, MapType> {
|
|
68
74
|
/**
|
|
69
75
|
* Create a filled contoured field
|
|
70
76
|
* @param field - The field to create filled contours from
|
|
71
77
|
* @param opts - Options for creating the filled contours
|
|
72
78
|
*/
|
|
73
79
|
constructor(field: RawScalarField<ArrayType>, opts: ContourFillOptions);
|
|
80
|
+
/**
|
|
81
|
+
* Update the data displayed as filled contours
|
|
82
|
+
* @param field - The new field to display as filled contours
|
|
83
|
+
*/
|
|
84
|
+
updateField(field: RawScalarField<ArrayType>): Promise<void>;
|
|
74
85
|
/**
|
|
75
86
|
* @internal
|
|
76
87
|
* Add the filled contours to a map
|