autumnplot-gl 3.1.0 → 3.2.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 +6 -11
- package/dist/110.autumnplot-gl.js +1 -1
- package/dist/110.autumnplot-gl.js.map +1 -1
- package/dist/autumnplot-gl.js +1 -1
- package/dist/autumnplot-gl.js.map +1 -1
- package/dist/marchingsquares.wasm +0 -0
- package/lib/Barbs.d.ts +17 -1
- package/lib/Barbs.js +20 -18
- package/lib/BillboardCollection.d.ts +9 -2
- package/lib/BillboardCollection.js +46 -9
- package/lib/Color.d.ts +56 -0
- package/lib/Color.js +160 -0
- package/lib/ColorBar.d.ts +2 -1
- package/lib/ColorBar.js +5 -5
- package/lib/Colormap.d.ts +19 -18
- package/lib/Colormap.js +81 -20
- package/lib/Contour.d.ts +24 -3
- package/lib/Contour.js +56 -10
- package/lib/Fill.d.ts +1 -2
- package/lib/Fill.js +19 -44
- package/lib/Grid.d.ts +1 -0
- package/lib/Grid.js +6 -1
- package/lib/Hodographs.d.ts +18 -1
- package/lib/Hodographs.js +17 -16
- package/lib/Paintball.js +3 -2
- package/lib/PlotComponent.js +9 -4
- package/lib/PlotLayer.d.ts +1 -1
- package/lib/PlotLayer.worker.js +7 -7
- package/lib/PolylineCollection.d.ts +13 -6
- package/lib/PolylineCollection.js +76 -64
- package/lib/StationPlot.d.ts +34 -0
- package/lib/StationPlot.js +73 -0
- package/lib/TextCollection.d.ts +3 -2
- package/lib/TextCollection.js +21 -11
- package/lib/cpp/marchingsquares.wasm +0 -0
- package/lib/index.d.ts +4 -2
- package/lib/index.js +2 -1
- package/lib/utils.d.ts +2 -8
- package/lib/utils.js +1 -83
- package/package.json +2 -2
package/lib/Hodographs.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { PlotComponent } from "./PlotComponent";
|
|
2
2
|
import { PolylineCollection } from "./PolylineCollection";
|
|
3
3
|
import { BillboardCollection } from "./BillboardCollection";
|
|
4
|
-
import { getMinZoom,
|
|
4
|
+
import { getMinZoom, normalizeOptions } from './utils';
|
|
5
5
|
import { ColorMap } from "./Colormap";
|
|
6
|
-
|
|
6
|
+
import { Color } from "./Color";
|
|
7
|
+
const LINE_WIDTH_MULTIPLIER = 2.5;
|
|
7
8
|
const BG_MAX_RING_MAG = 40;
|
|
8
9
|
const HODO_BG_DIMS = {
|
|
9
10
|
BB_WIDTH: 256,
|
|
@@ -14,7 +15,7 @@ const HODO_BG_DIMS = {
|
|
|
14
15
|
BB_MAG_WRAP: 1000,
|
|
15
16
|
BB_MAG_BIN_SIZE: 1000,
|
|
16
17
|
};
|
|
17
|
-
function _createHodoBackgroundTexture() {
|
|
18
|
+
function _createHodoBackgroundTexture(line_width) {
|
|
18
19
|
let canvas = document.createElement('canvas');
|
|
19
20
|
canvas.width = HODO_BG_DIMS.BB_TEX_WIDTH;
|
|
20
21
|
canvas.height = HODO_BG_DIMS.BB_TEX_HEIGHT;
|
|
@@ -22,10 +23,10 @@ function _createHodoBackgroundTexture() {
|
|
|
22
23
|
if (ctx === null) {
|
|
23
24
|
throw "Could not get rendering context for the hodograph background canvas";
|
|
24
25
|
}
|
|
25
|
-
ctx.lineWidth =
|
|
26
|
+
ctx.lineWidth = line_width;
|
|
26
27
|
for (let irng = HODO_BG_DIMS.BB_TEX_WIDTH / 4; irng <= HODO_BG_DIMS.BB_TEX_WIDTH / 2; irng += HODO_BG_DIMS.BB_TEX_WIDTH / 4) {
|
|
27
28
|
ctx.beginPath();
|
|
28
|
-
ctx.arc(HODO_BG_DIMS.BB_TEX_WIDTH / 2, HODO_BG_DIMS.BB_TEX_WIDTH / 2, irng -
|
|
29
|
+
ctx.arc(HODO_BG_DIMS.BB_TEX_WIDTH / 2, HODO_BG_DIMS.BB_TEX_WIDTH / 2, irng - line_width / 2, 0, 2 * Math.PI);
|
|
29
30
|
ctx.stroke();
|
|
30
31
|
}
|
|
31
32
|
const ctr_x = HODO_BG_DIMS.BB_TEX_WIDTH / 2, ctr_y = HODO_BG_DIMS.BB_TEX_WIDTH / 2;
|
|
@@ -39,13 +40,15 @@ function _createHodoBackgroundTexture() {
|
|
|
39
40
|
return canvas;
|
|
40
41
|
}
|
|
41
42
|
;
|
|
42
|
-
let HODO_BG_TEXTURE = null;
|
|
43
43
|
const HODO_CMAP = new ColorMap([0, 1, 3, 6, 9], ['#ffffcc', '#a1dab4', '#41b6c4', '#225ea8']);
|
|
44
44
|
const hodograph_opt_defaults = {
|
|
45
45
|
bgcolor: '#000000',
|
|
46
|
-
thin_fac: 1
|
|
46
|
+
thin_fac: 1,
|
|
47
|
+
hodo_line_width: 2.5,
|
|
48
|
+
background_line_width: 1.5,
|
|
49
|
+
height_cmap: HODO_CMAP
|
|
47
50
|
};
|
|
48
|
-
/** A class representing a
|
|
51
|
+
/** A class representing a field of hodograph plots */
|
|
49
52
|
class Hodographs extends PlotComponent {
|
|
50
53
|
/**
|
|
51
54
|
* Create a field of hodographs
|
|
@@ -56,7 +59,8 @@ class Hodographs extends PlotComponent {
|
|
|
56
59
|
super();
|
|
57
60
|
this.profile_field = profile_field;
|
|
58
61
|
this.opts = normalizeOptions(opts, hodograph_opt_defaults);
|
|
59
|
-
this.
|
|
62
|
+
this.hodo_bg_texture = _createHodoBackgroundTexture(this.opts.background_line_width * LINE_WIDTH_MULTIPLIER);
|
|
63
|
+
this.hodo_scale = (HODO_BG_DIMS.BB_TEX_WIDTH - this.opts.background_line_width / 2) / (HODO_BG_DIMS.BB_TEX_WIDTH * BG_MAX_RING_MAG);
|
|
60
64
|
this.bg_size = 140;
|
|
61
65
|
this.gl_elems = null;
|
|
62
66
|
this.line_elems = null;
|
|
@@ -82,7 +86,7 @@ class Hodographs extends PlotComponent {
|
|
|
82
86
|
'data': [...prof['z']],
|
|
83
87
|
};
|
|
84
88
|
});
|
|
85
|
-
const hodo_line = await PolylineCollection.make(gl, hodo_polyline, { line_width:
|
|
89
|
+
const hodo_line = await PolylineCollection.make(gl, hodo_polyline, { line_width: this.opts.hodo_line_width, cmap: this.opts.height_cmap, offset_scale: this.hodo_scale * this.bg_size });
|
|
86
90
|
const sm_polyline = profiles.map(prof => {
|
|
87
91
|
const zoom = getMinZoom(prof['jlat'], prof['ilon'], this.opts.thin_fac);
|
|
88
92
|
const sm_mag = Math.hypot(prof['smu'], prof['smv']);
|
|
@@ -95,7 +99,7 @@ class Hodographs extends PlotComponent {
|
|
|
95
99
|
'zoom': zoom
|
|
96
100
|
};
|
|
97
101
|
});
|
|
98
|
-
const sm_line = await PolylineCollection.make(gl, sm_polyline, { line_width:
|
|
102
|
+
const sm_line = await PolylineCollection.make(gl, sm_polyline, { line_width: this.opts.background_line_width, color: this.opts.bgcolor, offset_scale: this.hodo_scale * this.bg_size });
|
|
99
103
|
this.line_elems = {
|
|
100
104
|
hodo_line: hodo_line, sm_line: sm_line
|
|
101
105
|
};
|
|
@@ -105,12 +109,9 @@ class Hodographs extends PlotComponent {
|
|
|
105
109
|
* Add the hodographs to a map
|
|
106
110
|
*/
|
|
107
111
|
async onAdd(map, gl) {
|
|
108
|
-
|
|
109
|
-
HODO_BG_TEXTURE = _createHodoBackgroundTexture();
|
|
110
|
-
}
|
|
111
|
-
const bg_image = { 'format': gl.RGBA, 'type': gl.UNSIGNED_BYTE, 'image': HODO_BG_TEXTURE, 'mag_filter': gl.NEAREST };
|
|
112
|
+
const bg_image = { 'format': gl.RGBA, 'type': gl.UNSIGNED_BYTE, 'image': this.hodo_bg_texture, 'mag_filter': gl.NEAREST };
|
|
112
113
|
const max_zoom = map.getMaxZoom();
|
|
113
|
-
const bg_billboard = new BillboardCollection(this.profile_field.getStormMotionGrid(), this.opts.thin_fac, max_zoom, bg_image, HODO_BG_DIMS,
|
|
114
|
+
const bg_billboard = new BillboardCollection(this.profile_field.getStormMotionGrid(), this.opts.thin_fac, max_zoom, bg_image, HODO_BG_DIMS, this.bg_size * 0.004, { color: Color.fromHex(this.opts.bgcolor) });
|
|
114
115
|
await bg_billboard.setup(gl);
|
|
115
116
|
this.gl_elems = {
|
|
116
117
|
gl: gl, map: map, bg_billboard: bg_billboard
|
package/lib/Paintball.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { Color } from "./Color";
|
|
1
2
|
import { PlotComponent, getGLFormatTypeAlignment } from "./PlotComponent";
|
|
2
|
-
import {
|
|
3
|
+
import { normalizeOptions } from "./utils";
|
|
3
4
|
import { WGLProgram, WGLTexture } from "autumn-wgl";
|
|
4
5
|
const paintball_vertex_shader_src = `uniform mat4 u_matrix;
|
|
5
6
|
uniform int u_offset;
|
|
@@ -68,7 +69,7 @@ class Paintball extends PlotComponent {
|
|
|
68
69
|
super();
|
|
69
70
|
this.field = field;
|
|
70
71
|
this.opts = normalizeOptions(opts, paintball_opt_defaults);
|
|
71
|
-
this.color_components = [...this.opts.colors].reverse().map(color =>
|
|
72
|
+
this.color_components = [...this.opts.colors].reverse().map(color => Color.fromHex(color).toRGBATuple()).flat();
|
|
72
73
|
this.gl_elems = null;
|
|
73
74
|
this.fill_texture = null;
|
|
74
75
|
}
|
package/lib/PlotComponent.js
CHANGED
|
@@ -11,11 +11,16 @@ function getGLFormatTypeAlignment(gl, is_float16) {
|
|
|
11
11
|
if (is_float16) {
|
|
12
12
|
const ext = gl.getExtension('OES_texture_half_float');
|
|
13
13
|
const ext_lin = gl.getExtension('OES_texture_half_float_linear');
|
|
14
|
-
if (
|
|
15
|
-
|
|
14
|
+
if (is_webgl2) {
|
|
15
|
+
format = gl.R16F;
|
|
16
|
+
type = gl.HALF_FLOAT;
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
if (ext === null || ext_lin === null)
|
|
20
|
+
throw "Float16 data are not supported on this hardware. Try Float32 data instead.";
|
|
21
|
+
format = gl.LUMINANCE;
|
|
22
|
+
type = ext.HALF_FLOAT_OES;
|
|
16
23
|
}
|
|
17
|
-
format = is_webgl2 ? gl.R16F : gl.LUMINANCE;
|
|
18
|
-
type = is_webgl2 ? gl.HALF_FLOAT : ext.HALF_FLOAT_OES;
|
|
19
24
|
row_alignment = 2;
|
|
20
25
|
}
|
|
21
26
|
else {
|
package/lib/PlotLayer.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { PlotComponent } from './PlotComponent';
|
|
|
4
4
|
declare abstract class PlotLayerBase<MapType extends MapLikeType> {
|
|
5
5
|
readonly type: 'custom';
|
|
6
6
|
readonly id: string;
|
|
7
|
-
protected map: MapType;
|
|
7
|
+
protected map: MapType | null;
|
|
8
8
|
constructor(id: string);
|
|
9
9
|
abstract onAdd(map: MapType, gl: WebGLAnyRenderingContext): void;
|
|
10
10
|
abstract render(gl: WebGLAnyRenderingContext, matrix: number[] | Float32Array): void;
|
package/lib/PlotLayer.worker.js
CHANGED
|
@@ -288,10 +288,10 @@ function makePolylines(lines) {
|
|
|
288
288
|
return [v_ll.x, v_ll.y];
|
|
289
289
|
});
|
|
290
290
|
const has_offsets = line.offsets !== undefined;
|
|
291
|
-
const extrusion_verts =
|
|
291
|
+
const extrusion_verts = line.offsets !== undefined ? line.offsets : verts;
|
|
292
292
|
let pt_prev, pt_this = verts[0], pt_next = verts[1];
|
|
293
293
|
let ept_prev, ept_this = extrusion_verts[0], ept_next = extrusion_verts[1];
|
|
294
|
-
let len_prev, len_this = 0;
|
|
294
|
+
let len_prev, len_this = 0.0001;
|
|
295
295
|
let [ext_x, ext_y] = compute_normal_vec(ept_this, ept_next, !has_offsets);
|
|
296
296
|
ret.vertices[ilns.vertices++] = pt_this[0];
|
|
297
297
|
ret.vertices[ilns.vertices++] = pt_this[1];
|
|
@@ -308,13 +308,13 @@ function makePolylines(lines) {
|
|
|
308
308
|
len_this += Math.hypot(verts[ivt - 1][0] - verts[ivt][0], verts[ivt - 1][1] - verts[ivt][1]);
|
|
309
309
|
ret.vertices[ilns.vertices++] = pt_prev[0];
|
|
310
310
|
ret.vertices[ilns.vertices++] = pt_prev[1];
|
|
311
|
-
ret.vertices[ilns.vertices++] = len_prev;
|
|
311
|
+
ret.vertices[ilns.vertices++] = -len_prev;
|
|
312
312
|
ret.vertices[ilns.vertices++] = pt_prev[0];
|
|
313
313
|
ret.vertices[ilns.vertices++] = pt_prev[1];
|
|
314
314
|
ret.vertices[ilns.vertices++] = len_prev;
|
|
315
315
|
ret.vertices[ilns.vertices++] = pt_this[0];
|
|
316
316
|
ret.vertices[ilns.vertices++] = pt_this[1];
|
|
317
|
-
ret.vertices[ilns.vertices++] = len_this;
|
|
317
|
+
ret.vertices[ilns.vertices++] = -len_this;
|
|
318
318
|
ret.vertices[ilns.vertices++] = pt_this[0];
|
|
319
319
|
ret.vertices[ilns.vertices++] = pt_this[1];
|
|
320
320
|
ret.vertices[ilns.vertices++] = len_this;
|
|
@@ -332,7 +332,7 @@ function makePolylines(lines) {
|
|
|
332
332
|
ret.vertices[ilns.vertices++] = len_this;
|
|
333
333
|
ret.extrusion[ilns.extrusion++] = -ext_x;
|
|
334
334
|
ret.extrusion[ilns.extrusion++] = -ext_y;
|
|
335
|
-
if (
|
|
335
|
+
if (ret.offsets !== undefined && line.offsets !== undefined) {
|
|
336
336
|
const offsets = line.offsets;
|
|
337
337
|
let off_prev, off_this = offsets[0];
|
|
338
338
|
ret.offsets[ilns.offsets++] = off_this[0];
|
|
@@ -352,7 +352,7 @@ function makePolylines(lines) {
|
|
|
352
352
|
ret.offsets[ilns.offsets++] = off_this[0];
|
|
353
353
|
ret.offsets[ilns.offsets++] = off_this[1];
|
|
354
354
|
}
|
|
355
|
-
if (
|
|
355
|
+
if (ret.data !== undefined && line.data !== undefined) {
|
|
356
356
|
const data = line.data;
|
|
357
357
|
let data_prev, data_this = data[0];
|
|
358
358
|
ret.data[ilns.data++] = data_this;
|
|
@@ -366,7 +366,7 @@ function makePolylines(lines) {
|
|
|
366
366
|
}
|
|
367
367
|
ret.data[ilns.data++] = data_this;
|
|
368
368
|
}
|
|
369
|
-
if (
|
|
369
|
+
if (ret.zoom !== undefined && line.zoom !== undefined) {
|
|
370
370
|
for (let ivt = 0; ivt < verts.length * 4 - 2; ivt++) {
|
|
371
371
|
ret.zoom[ilns.zoom++] = line['zoom'];
|
|
372
372
|
}
|
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
import { LineData, WebGLAnyRenderingContext } from "./AutumnTypes";
|
|
2
2
|
import { ColorMap } from "./Colormap";
|
|
3
|
+
/**
|
|
4
|
+
* A style to use to draw lines. The possible options are '-' for a solid line, '--' for a dashed line, ':' for a
|
|
5
|
+
* dotted line, '-.' for a dash-dot line, or you could pass a list of numbers (e.g., [1, 1, 1, 0, 1, 0]) to
|
|
6
|
+
* specify a custom dash scheme.
|
|
7
|
+
*/
|
|
8
|
+
type LineStyle = "-" | "--" | ":" | "-." | number[];
|
|
9
|
+
declare function isLineStyle(obj: any): obj is LineStyle;
|
|
3
10
|
interface PolylineCollectionOpts {
|
|
4
11
|
offset_scale?: number;
|
|
5
12
|
color?: string;
|
|
6
13
|
cmap?: ColorMap;
|
|
7
14
|
line_width?: number;
|
|
15
|
+
line_style?: LineStyle | number[];
|
|
8
16
|
}
|
|
9
17
|
declare class PolylineCollection {
|
|
10
18
|
readonly width: number;
|
|
@@ -15,14 +23,13 @@ declare class PolylineCollection {
|
|
|
15
23
|
private readonly offset;
|
|
16
24
|
private readonly min_zoom;
|
|
17
25
|
private readonly line_data;
|
|
18
|
-
private readonly line_texture;
|
|
19
26
|
private readonly color;
|
|
20
|
-
private readonly
|
|
21
|
-
private readonly
|
|
22
|
-
private readonly
|
|
23
|
-
private readonly cmap_nonlin_texture;
|
|
27
|
+
private readonly cmap_gpu;
|
|
28
|
+
private readonly dash_texture;
|
|
29
|
+
private readonly n_dash;
|
|
24
30
|
private constructor();
|
|
25
31
|
static make(gl: WebGLAnyRenderingContext, lines: LineData[], opts?: PolylineCollectionOpts): Promise<PolylineCollection>;
|
|
26
32
|
render(gl: WebGLAnyRenderingContext, matrix: number[] | Float32Array, [map_width, map_height]: [number, number], map_zoom: number, map_bearing: number, map_pitch: number): void;
|
|
27
33
|
}
|
|
28
|
-
export { PolylineCollection };
|
|
34
|
+
export { PolylineCollection, isLineStyle };
|
|
35
|
+
export type { PolylineCollectionOpts, LineStyle };
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { Float16Array } from "@petamoriken/float16";
|
|
2
1
|
import { WGLBuffer, WGLProgram, WGLTexture } from "autumn-wgl";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
2
|
+
import { isWebGL2Ctx } from "./AutumnTypes";
|
|
3
|
+
import { ColorMap, ColorMapGPUInterface } from "./Colormap";
|
|
4
|
+
import { Color } from "./Color";
|
|
5
|
+
import { layer_worker } from "./PlotComponent";
|
|
6
6
|
const polyline_vertex_src = `uniform mat4 u_matrix;
|
|
7
7
|
uniform int u_offset;
|
|
8
8
|
|
|
9
|
-
attribute
|
|
9
|
+
attribute vec3 a_pos;
|
|
10
10
|
attribute vec2 a_extrusion;
|
|
11
11
|
attribute float a_data;
|
|
12
12
|
|
|
@@ -35,6 +35,9 @@ uniform lowp float u_offset_scale;
|
|
|
35
35
|
varying highp float v_data;
|
|
36
36
|
#endif
|
|
37
37
|
|
|
38
|
+
varying highp float v_dist;
|
|
39
|
+
varying lowp float v_cross;
|
|
40
|
+
|
|
38
41
|
mat4 scalingMatrix(float x_scale, float y_scale, float z_scale) {
|
|
39
42
|
return mat4(x_scale, 0.0, 0.0, 0.0,
|
|
40
43
|
0.0, y_scale, 0.0, 0.0,
|
|
@@ -66,6 +69,8 @@ void main() {
|
|
|
66
69
|
float globe_width = 1.;
|
|
67
70
|
vec2 globe_offset = vec2(globe_width * float(u_offset), 0.);
|
|
68
71
|
|
|
72
|
+
v_dist = abs(a_pos.z);
|
|
73
|
+
v_cross = sign(a_pos.z);
|
|
69
74
|
vec4 center_pos = u_matrix * vec4(a_pos.xy + globe_offset, 0.0, 1.0);
|
|
70
75
|
vec4 offset = vec4(0.0, 0.0, 0.0, 0.0);
|
|
71
76
|
|
|
@@ -73,7 +78,7 @@ void main() {
|
|
|
73
78
|
if (u_zoom >= a_min_zoom) {
|
|
74
79
|
#endif
|
|
75
80
|
|
|
76
|
-
vec2 offset_ext = u_line_width *
|
|
81
|
+
vec2 offset_ext = u_line_width * 2. * a_extrusion;
|
|
77
82
|
|
|
78
83
|
mat4 map_stretch_matrix = scalingMatrix(u_map_height / u_map_width, 1., 1.);
|
|
79
84
|
mat4 rotation_matrix = rotationZMatrix(radians(u_map_bearing));
|
|
@@ -95,41 +100,68 @@ void main() {
|
|
|
95
100
|
v_data = a_data;
|
|
96
101
|
#endif
|
|
97
102
|
}`
|
|
98
|
-
const polyline_fragment_src =
|
|
99
|
-
|
|
103
|
+
const polyline_fragment_src = `
|
|
104
|
+
uniform sampler2D u_dash_sampler;
|
|
100
105
|
|
|
101
|
-
|
|
102
|
-
uniform sampler2D u_cmap_nonlin_sampler;
|
|
103
|
-
uniform highp float u_cmap_min;
|
|
104
|
-
uniform highp float u_cmap_max;
|
|
105
|
-
uniform int u_n_index;
|
|
106
|
-
#else
|
|
106
|
+
#ifndef DATA
|
|
107
107
|
uniform lowp vec4 u_color;
|
|
108
108
|
#endif
|
|
109
109
|
|
|
110
|
-
|
|
111
|
-
|
|
110
|
+
uniform lowp float u_line_width;
|
|
111
|
+
uniform lowp float u_zoom;
|
|
112
|
+
uniform int u_dash_pattern_length;
|
|
113
|
+
|
|
112
114
|
#ifdef DATA
|
|
113
|
-
|
|
114
|
-
|
|
115
|
+
varying highp float v_data;
|
|
116
|
+
#endif
|
|
115
117
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
118
|
+
varying highp float v_dist;
|
|
119
|
+
varying lowp float v_cross;
|
|
119
120
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
121
|
+
void main() {
|
|
122
|
+
lowp float dash_x = fract(v_dist * 2e2 * pow(2., floor(u_zoom)) / float(u_dash_pattern_length));
|
|
123
|
+
lowp float dash = texture2D(u_dash_sampler, vec2(dash_x, 0.5)).r;
|
|
124
|
+
|
|
125
|
+
lowp vec4 color;
|
|
126
|
+
#ifdef DATA
|
|
127
|
+
color = apply_colormap(v_data);
|
|
123
128
|
#else
|
|
124
129
|
color = u_color;
|
|
125
130
|
#endif
|
|
131
|
+
|
|
132
|
+
lowp float feather = clamp((1. - abs(v_cross)) * u_line_width, 0., 1.);
|
|
133
|
+
color.a *= (dash >= 1. ? 1. : 0.) * feather;
|
|
134
|
+
|
|
126
135
|
gl_FragColor = color;
|
|
127
136
|
}`
|
|
137
|
+
const dash_arrays = {
|
|
138
|
+
"-": [1],
|
|
139
|
+
"--": [1, 1, 1, 1, 0, 0],
|
|
140
|
+
":": [1, 0],
|
|
141
|
+
"-.": [1, 1, 1, 0, 1, 0],
|
|
142
|
+
};
|
|
143
|
+
function isLineStyle(obj) {
|
|
144
|
+
return obj in dash_arrays || Array.isArray(obj) && obj.length > 0 && obj.map(e => typeof e === 'number').reduce((a, b) => a && b, true);
|
|
145
|
+
}
|
|
146
|
+
function makeDashTexture(gl, line_style) {
|
|
147
|
+
const dash_array = Array.isArray(line_style) ? line_style : dash_arrays[line_style];
|
|
148
|
+
const is_webgl2 = isWebGL2Ctx(gl);
|
|
149
|
+
const format = is_webgl2 ? gl.R8 : gl.LUMINANCE;
|
|
150
|
+
const type = gl.UNSIGNED_BYTE;
|
|
151
|
+
const row_alignment = 1;
|
|
152
|
+
const fill_image = { 'format': format, 'type': type,
|
|
153
|
+
'width': dash_array.length, 'height': 1, 'image': new Uint8Array(dash_array.map(d => d > 0 ? 255 : 0)),
|
|
154
|
+
'mag_filter': gl.NEAREST, 'row_alignment': row_alignment,
|
|
155
|
+
};
|
|
156
|
+
return [dash_array.length, new WGLTexture(gl, fill_image)];
|
|
157
|
+
}
|
|
128
158
|
class PolylineCollection {
|
|
129
159
|
constructor(gl, polyline, opts) {
|
|
130
160
|
opts = opts === undefined ? {} : opts;
|
|
131
|
-
|
|
161
|
+
const color_hex = opts.color === undefined ? '#000000' : opts.color;
|
|
162
|
+
this.color = Color.fromHex(color_hex);
|
|
132
163
|
const line_width = opts.line_width === undefined ? 1 : opts.line_width;
|
|
164
|
+
const line_style = opts.line_style === undefined ? '-' : opts.line_style;
|
|
133
165
|
this.width = line_width;
|
|
134
166
|
const shader_defines = [];
|
|
135
167
|
this.vertices = new WGLBuffer(gl, polyline['vertices'], 3, gl.TRIANGLE_STRIP);
|
|
@@ -150,39 +182,21 @@ class PolylineCollection {
|
|
|
150
182
|
else {
|
|
151
183
|
this.min_zoom = null;
|
|
152
184
|
}
|
|
153
|
-
|
|
154
|
-
this.cmap_max = 3.40282347e+38;
|
|
185
|
+
let fragment_src = polyline_fragment_src;
|
|
155
186
|
if (polyline.data !== undefined) {
|
|
156
|
-
// TAS: this needs some cleanup (there's some repated code between here and the contour fills that should be combined?)
|
|
157
|
-
this.min_zoom = new WGLBuffer(gl, polyline.zoom, 1, gl.TRIANGLE_STRIP);
|
|
158
187
|
shader_defines.push('DATA');
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}
|
|
165
|
-
else {
|
|
166
|
-
tex_image = { 'format': gl.RGBA, 'type': gl.UNSIGNED_BYTE, 'image': makeTextureImage(opts.cmap), 'mag_filter': gl.NEAREST };
|
|
167
|
-
this.cmap_min = opts.cmap.levels[0];
|
|
168
|
-
this.cmap_max = opts.cmap.levels[opts.cmap.levels.length - 1];
|
|
169
|
-
this.index_map = makeIndexMap(opts.cmap);
|
|
170
|
-
}
|
|
171
|
-
const cmap_nonlin_image = { 'format': format_nonlin, 'type': type_nonlin,
|
|
172
|
-
'width': this.index_map.length, 'height': 1,
|
|
173
|
-
'image': new Uint16Array(this.index_map.buffer),
|
|
174
|
-
'mag_filter': gl.LINEAR, 'row_alignment': row_alignment_nonlin,
|
|
175
|
-
};
|
|
176
|
-
this.cmap_nonlin_texture = new WGLTexture(gl, cmap_nonlin_image);
|
|
177
|
-
this.line_texture = new WGLTexture(gl, tex_image);
|
|
178
|
-
this.line_data = new WGLBuffer(gl, polyline['data'], 1, gl.TRIANGLE_STRIP);
|
|
188
|
+
this.line_data = new WGLBuffer(gl, polyline.data, 1, gl.TRIANGLE_STRIP);
|
|
189
|
+
const cmap = opts.cmap === undefined ? new ColorMap([0, 1], [color_hex], { overflow_color: color_hex, underflow_color: color_hex }) : opts.cmap;
|
|
190
|
+
this.cmap_gpu = new ColorMapGPUInterface(cmap);
|
|
191
|
+
this.cmap_gpu.setupShaderVariables(gl, gl.NEAREST);
|
|
192
|
+
fragment_src = ColorMapGPUInterface.applyShader(fragment_src);
|
|
179
193
|
}
|
|
180
194
|
else {
|
|
181
|
-
this.line_texture = null;
|
|
182
195
|
this.line_data = null;
|
|
183
|
-
this.
|
|
196
|
+
this.cmap_gpu = null;
|
|
184
197
|
}
|
|
185
|
-
this.
|
|
198
|
+
[this.n_dash, this.dash_texture] = makeDashTexture(gl, line_style);
|
|
199
|
+
this.program = new WGLProgram(gl, polyline_vertex_src, fragment_src, { define: shader_defines });
|
|
186
200
|
}
|
|
187
201
|
static async make(gl, lines, opts) {
|
|
188
202
|
const polylines = await layer_worker.makePolyLines(lines);
|
|
@@ -193,29 +207,27 @@ class PolylineCollection {
|
|
|
193
207
|
matrix = [...matrix];
|
|
194
208
|
const attributes = { 'a_pos': this.vertices, 'a_extrusion': this.extrusion };
|
|
195
209
|
const uniforms = {
|
|
196
|
-
'u_matrix': matrix, 'u_line_width': this.width, 'u_map_width': map_width, 'u_map_height': map_height, 'u_map_bearing': map_bearing, 'u_offset': 0
|
|
210
|
+
'u_matrix': matrix, 'u_line_width': this.width, 'u_map_width': map_width, 'u_map_height': map_height, 'u_map_bearing': map_bearing, 'u_offset': 0, 'u_zoom': map_zoom,
|
|
211
|
+
'u_dash_pattern_length': this.n_dash
|
|
197
212
|
};
|
|
198
|
-
const textures = {};
|
|
199
|
-
if (this.offset !== null) {
|
|
213
|
+
const textures = { 'u_dash_sampler': this.dash_texture };
|
|
214
|
+
if (this.offset !== null && this.scale !== null) {
|
|
200
215
|
attributes['a_offset'] = this.offset;
|
|
201
216
|
uniforms['u_offset_scale'] = this.scale * (map_height / map_width);
|
|
202
217
|
}
|
|
203
218
|
if (this.min_zoom !== null) {
|
|
204
219
|
attributes['a_min_zoom'] = this.min_zoom;
|
|
205
|
-
uniforms['u_zoom'] = map_zoom;
|
|
206
220
|
}
|
|
207
|
-
if (this.line_data !== null
|
|
221
|
+
if (this.line_data !== null) {
|
|
208
222
|
attributes['a_data'] = this.line_data;
|
|
209
|
-
textures['u_cmap_sampler'] = this.line_texture;
|
|
210
|
-
textures['u_cmap_nonlin_sampler'] = this.cmap_nonlin_texture;
|
|
211
|
-
uniforms['u_cmap_min'] = this.cmap_min;
|
|
212
|
-
uniforms['u_cmap_max'] = this.cmap_max;
|
|
213
|
-
uniforms['u_n_index'] = this.index_map.length;
|
|
214
223
|
}
|
|
215
224
|
else {
|
|
216
|
-
uniforms['u_color'] = this.color;
|
|
225
|
+
uniforms['u_color'] = this.color.toRGBATuple();
|
|
217
226
|
}
|
|
218
227
|
this.program.use(attributes, uniforms, textures);
|
|
228
|
+
if (this.cmap_gpu !== null) {
|
|
229
|
+
this.cmap_gpu.bindShaderVariables(this.program);
|
|
230
|
+
}
|
|
219
231
|
gl.enable(gl.BLEND);
|
|
220
232
|
gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
|
221
233
|
this.program.draw();
|
|
@@ -227,4 +239,4 @@ class PolylineCollection {
|
|
|
227
239
|
this.program.draw();
|
|
228
240
|
}
|
|
229
241
|
}
|
|
230
|
-
export { PolylineCollection };
|
|
242
|
+
export { PolylineCollection, isLineStyle };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { WebGLAnyRenderingContext } from "./AutumnTypes";
|
|
2
|
+
import { MapLikeType } from "./Map";
|
|
3
|
+
import { PlotComponent } from "./PlotComponent";
|
|
4
|
+
interface EarthCoord {
|
|
5
|
+
lon: number;
|
|
6
|
+
lat: number;
|
|
7
|
+
}
|
|
8
|
+
interface ObsRawData<ObsFieldName extends string> {
|
|
9
|
+
coord: EarthCoord;
|
|
10
|
+
data: Record<ObsFieldName, number>;
|
|
11
|
+
}
|
|
12
|
+
type SPPositions = 'cl' | 'll' | 'lc' | 'lr' | 'cr' | 'ur' | 'uc' | 'ul';
|
|
13
|
+
type SPDataPosition<ObsFieldName extends string> = Record<ObsFieldName, SPPositions>;
|
|
14
|
+
declare class UnstructuredGrid {
|
|
15
|
+
readonly coords: EarthCoord[];
|
|
16
|
+
constructor(coords: EarthCoord[]);
|
|
17
|
+
getZoomThinning(thin_fac: number, map_max_zoom: number): number[];
|
|
18
|
+
}
|
|
19
|
+
declare class ObsField<ObsFieldName extends string> {
|
|
20
|
+
readonly grid: UnstructuredGrid;
|
|
21
|
+
readonly data: ObsRawData<ObsFieldName>[];
|
|
22
|
+
constructor(data: ObsRawData<ObsFieldName>[]);
|
|
23
|
+
}
|
|
24
|
+
interface StationPlotOpts {
|
|
25
|
+
}
|
|
26
|
+
declare class StationPlot<ObsFieldName extends string, MapType extends MapLikeType> extends PlotComponent<MapType> {
|
|
27
|
+
readonly field: ObsField<ObsFieldName>;
|
|
28
|
+
readonly data_positions: SPDataPosition<ObsFieldName>;
|
|
29
|
+
readonly opts: Required<StationPlotOpts>;
|
|
30
|
+
constructor(field: ObsField<ObsFieldName>, data_positions: SPDataPosition<ObsFieldName>, opts: StationPlotOpts);
|
|
31
|
+
onAdd(map: MapType, gl: WebGLAnyRenderingContext): Promise<void>;
|
|
32
|
+
render(gl: WebGLAnyRenderingContext, matrix: Float32Array | number[]): void;
|
|
33
|
+
}
|
|
34
|
+
export { StationPlot };
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { kdTree } from "kd-tree-javascript";
|
|
2
|
+
import { LngLat } from "./Map";
|
|
3
|
+
import { PlotComponent } from "./PlotComponent";
|
|
4
|
+
import { getMinZoom, normalizeOptions } from "./utils";
|
|
5
|
+
class UnstructuredGrid {
|
|
6
|
+
constructor(coords) {
|
|
7
|
+
this.coords = coords;
|
|
8
|
+
}
|
|
9
|
+
getZoomThinning(thin_fac, map_max_zoom) {
|
|
10
|
+
let min_label_lon = null, min_label_lat = null, max_label_lon = null, max_label_lat = null;
|
|
11
|
+
this.coords.forEach(coord => {
|
|
12
|
+
if (min_label_lon === null || coord.lon < min_label_lon)
|
|
13
|
+
min_label_lon = coord.lon;
|
|
14
|
+
if (max_label_lon === null || coord.lon > max_label_lon)
|
|
15
|
+
max_label_lon = coord.lon;
|
|
16
|
+
if (min_label_lat === null || coord.lat < min_label_lat)
|
|
17
|
+
min_label_lat = coord.lat;
|
|
18
|
+
if (max_label_lat === null || coord.lat > max_label_lat)
|
|
19
|
+
max_label_lat = coord.lat;
|
|
20
|
+
});
|
|
21
|
+
if (min_label_lon === null || min_label_lat === null || max_label_lon === null || max_label_lat === null)
|
|
22
|
+
return [];
|
|
23
|
+
const kd_nodes = this.coords.map(c => ({ lon: c.lon, lat: c.lat, min_zoom: map_max_zoom }));
|
|
24
|
+
const tree = new kdTree(kd_nodes, (a, b) => Math.hypot(a.lon - b.lon, a.lat - b.lat), ['lon', 'lat']);
|
|
25
|
+
const { x: min_label_x, y: max_label_y } = new LngLat(min_label_lon, min_label_lat).toMercatorCoord();
|
|
26
|
+
const { x: max_label_x, y: min_label_y } = new LngLat(max_label_lon, max_label_lat).toMercatorCoord();
|
|
27
|
+
const thin_grid_width = max_label_x - min_label_x;
|
|
28
|
+
const thin_grid_height = max_label_y - min_label_y;
|
|
29
|
+
const ni_thin_grid = Math.round(thin_grid_width * thin_fac); // thin_fac was 4 / contour_label_spacing for the contour labels
|
|
30
|
+
const nj_thin_grid = Math.round(thin_grid_height * thin_fac);
|
|
31
|
+
const thin_grid_xs = [];
|
|
32
|
+
const thin_grid_ys = [];
|
|
33
|
+
for (let idx = 0; idx < ni_thin_grid; idx++) {
|
|
34
|
+
thin_grid_xs.push(min_label_x + (idx / ni_thin_grid) * thin_grid_width);
|
|
35
|
+
}
|
|
36
|
+
for (let jdy = 0; jdy < nj_thin_grid; jdy++) {
|
|
37
|
+
thin_grid_ys.push(min_label_y + (jdy / nj_thin_grid) * thin_grid_height);
|
|
38
|
+
}
|
|
39
|
+
for (let idx = 0; idx < ni_thin_grid; idx++) {
|
|
40
|
+
for (let jdy = 0; jdy < nj_thin_grid; jdy++) {
|
|
41
|
+
const zoom = getMinZoom(jdy, idx, Math.pow(2, map_max_zoom));
|
|
42
|
+
const grid_x = thin_grid_xs[idx];
|
|
43
|
+
const grid_y = thin_grid_ys[jdy];
|
|
44
|
+
const ll = LngLat.fromMercatorCoord(grid_x, grid_y);
|
|
45
|
+
const [label, dist] = tree.nearest({ lon: ll.lng, lat: ll.lat, min_zoom: 0 }, 1)[0];
|
|
46
|
+
label.min_zoom = zoom;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return kd_nodes.map(n => n.min_zoom);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
class ObsField {
|
|
53
|
+
constructor(data) {
|
|
54
|
+
this.grid = new UnstructuredGrid(data.map(d => d.coord));
|
|
55
|
+
this.data = data;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const station_plot_opts_defaults = {};
|
|
59
|
+
class StationPlot extends PlotComponent {
|
|
60
|
+
constructor(field, data_positions, opts) {
|
|
61
|
+
super();
|
|
62
|
+
this.field = field;
|
|
63
|
+
this.data_positions = data_positions;
|
|
64
|
+
this.opts = normalizeOptions(opts, station_plot_opts_defaults);
|
|
65
|
+
}
|
|
66
|
+
async onAdd(map, gl) {
|
|
67
|
+
}
|
|
68
|
+
render(gl, matrix) {
|
|
69
|
+
if (matrix instanceof Float32Array)
|
|
70
|
+
matrix = [...matrix];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
export { StationPlot };
|
package/lib/TextCollection.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { WebGLAnyRenderingContext } from "./AutumnTypes";
|
|
2
|
+
import { Color } from "./Color";
|
|
2
3
|
import { WGLBuffer, WGLProgram, WGLTexture } from "autumn-wgl";
|
|
3
4
|
interface TextSpec {
|
|
4
5
|
lat: number;
|
|
@@ -12,8 +13,8 @@ interface TextCollectionOptions {
|
|
|
12
13
|
horizontal_align?: HorizontalAlign;
|
|
13
14
|
vertical_align?: VerticalAlign;
|
|
14
15
|
font_size?: number;
|
|
15
|
-
text_color?:
|
|
16
|
-
halo_color?:
|
|
16
|
+
text_color?: Color;
|
|
17
|
+
halo_color?: Color;
|
|
17
18
|
halo?: boolean;
|
|
18
19
|
}
|
|
19
20
|
declare class TextCollection {
|