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