autumnplot-gl 3.0.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 +18 -2
- package/lib/Barbs.js +25 -19
- 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 +25 -6
- package/lib/Contour.js +61 -12
- package/lib/ContourCreator.js +4 -40
- package/lib/Fill.d.ts +2 -4
- package/lib/Fill.js +29 -45
- package/lib/Grid.d.ts +1 -0
- package/lib/Grid.js +6 -1
- package/lib/Hodographs.d.ts +19 -3
- package/lib/Hodographs.js +23 -20
- package/lib/Paintball.d.ts +2 -2
- package/lib/Paintball.js +9 -6
- package/lib/PlotComponent.js +9 -4
- package/lib/PlotLayer.d.ts +1 -1
- package/lib/PlotLayer.worker.js +10 -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.js +558 -1261
- package/lib/cpp/marchingsquares.wasm +0 -0
- package/lib/cpp/marchingsquares_embind.d.ts +4 -45
- 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.d.ts
CHANGED
|
@@ -2,9 +2,11 @@ import { PlotComponent } from "./PlotComponent";
|
|
|
2
2
|
import { MapLikeType } from "./Map";
|
|
3
3
|
import { RawProfileField } from "./RawField";
|
|
4
4
|
import { WebGLAnyRenderingContext } from "./AutumnTypes";
|
|
5
|
+
import { ColorMap } from "./Colormap";
|
|
5
6
|
interface HodographOptions {
|
|
6
7
|
/**
|
|
7
8
|
* The color of the hodograph plot background as a hex string
|
|
9
|
+
* @default '#000000'
|
|
8
10
|
*/
|
|
9
11
|
bgcolor?: string;
|
|
10
12
|
/**
|
|
@@ -13,14 +15,28 @@ interface HodographOptions {
|
|
|
13
15
|
* @default 1
|
|
14
16
|
*/
|
|
15
17
|
thin_fac?: number;
|
|
18
|
+
/**
|
|
19
|
+
* The width of the hodograph line in pixels
|
|
20
|
+
* @default 2.5
|
|
21
|
+
*/
|
|
22
|
+
hodo_line_width: number;
|
|
23
|
+
/**
|
|
24
|
+
* The width of the lines on the background in pixels
|
|
25
|
+
* @default 1.5
|
|
26
|
+
*/
|
|
27
|
+
background_line_width: number;
|
|
28
|
+
/**
|
|
29
|
+
* The colormap to use for the heights on the hodograph. Default is a yellow-blue colormap.
|
|
30
|
+
*/
|
|
31
|
+
height_cmap: ColorMap;
|
|
16
32
|
}
|
|
17
|
-
/** A class representing a
|
|
33
|
+
/** A class representing a field of hodograph plots */
|
|
18
34
|
declare class Hodographs<MapType extends MapLikeType> extends PlotComponent<MapType> {
|
|
19
35
|
private profile_field;
|
|
20
|
-
readonly
|
|
21
|
-
readonly thin_fac: number;
|
|
36
|
+
readonly opts: Required<HodographOptions>;
|
|
22
37
|
private gl_elems;
|
|
23
38
|
private line_elems;
|
|
39
|
+
private hodo_bg_texture;
|
|
24
40
|
private readonly hodo_scale;
|
|
25
41
|
private readonly bg_size;
|
|
26
42
|
/**
|
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,9 +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
|
+
bgcolor: '#000000',
|
|
46
|
+
thin_fac: 1,
|
|
47
|
+
hodo_line_width: 2.5,
|
|
48
|
+
background_line_width: 1.5,
|
|
49
|
+
height_cmap: HODO_CMAP
|
|
50
|
+
};
|
|
51
|
+
/** A class representing a field of hodograph plots */
|
|
45
52
|
class Hodographs extends PlotComponent {
|
|
46
53
|
/**
|
|
47
54
|
* Create a field of hodographs
|
|
@@ -50,11 +57,10 @@ class Hodographs extends PlotComponent {
|
|
|
50
57
|
*/
|
|
51
58
|
constructor(profile_field, opts) {
|
|
52
59
|
super();
|
|
53
|
-
opts = opts || {};
|
|
54
60
|
this.profile_field = profile_field;
|
|
55
|
-
this.
|
|
56
|
-
this.
|
|
57
|
-
this.hodo_scale = (HODO_BG_DIMS.BB_TEX_WIDTH -
|
|
61
|
+
this.opts = normalizeOptions(opts, hodograph_opt_defaults);
|
|
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);
|
|
58
64
|
this.bg_size = 140;
|
|
59
65
|
this.gl_elems = null;
|
|
60
66
|
this.line_elems = null;
|
|
@@ -72,7 +78,7 @@ class Hodographs extends PlotComponent {
|
|
|
72
78
|
this.gl_elems.bg_billboard.updateField(field.getStormMotionGrid());
|
|
73
79
|
const profiles = this.profile_field.profiles;
|
|
74
80
|
const hodo_polyline = profiles.map(prof => {
|
|
75
|
-
const zoom = getMinZoom(prof['jlat'], prof['ilon'], this.thin_fac);
|
|
81
|
+
const zoom = getMinZoom(prof['jlat'], prof['ilon'], this.opts.thin_fac);
|
|
76
82
|
return {
|
|
77
83
|
'offsets': [...prof['u']].map((u, ipt) => [u - prof['smu'], prof['v'][ipt] - prof['smv']]),
|
|
78
84
|
'vertices': [...prof['u']].map(u => [prof['lon'], prof['lat']]),
|
|
@@ -80,9 +86,9 @@ class Hodographs extends PlotComponent {
|
|
|
80
86
|
'data': [...prof['z']],
|
|
81
87
|
};
|
|
82
88
|
});
|
|
83
|
-
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 });
|
|
84
90
|
const sm_polyline = profiles.map(prof => {
|
|
85
|
-
const zoom = getMinZoom(prof['jlat'], prof['ilon'], this.thin_fac);
|
|
91
|
+
const zoom = getMinZoom(prof['jlat'], prof['ilon'], this.opts.thin_fac);
|
|
86
92
|
const sm_mag = Math.hypot(prof['smu'], prof['smv']);
|
|
87
93
|
const sm_ang = Math.PI / 2 - Math.atan2(-prof['smv'], -prof['smu']);
|
|
88
94
|
const buffer = 2;
|
|
@@ -93,7 +99,7 @@ class Hodographs extends PlotComponent {
|
|
|
93
99
|
'zoom': zoom
|
|
94
100
|
};
|
|
95
101
|
});
|
|
96
|
-
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 });
|
|
97
103
|
this.line_elems = {
|
|
98
104
|
hodo_line: hodo_line, sm_line: sm_line
|
|
99
105
|
};
|
|
@@ -103,12 +109,9 @@ class Hodographs extends PlotComponent {
|
|
|
103
109
|
* Add the hodographs to a map
|
|
104
110
|
*/
|
|
105
111
|
async onAdd(map, gl) {
|
|
106
|
-
|
|
107
|
-
HODO_BG_TEXTURE = _createHodoBackgroundTexture();
|
|
108
|
-
}
|
|
109
|
-
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 };
|
|
110
113
|
const max_zoom = map.getMaxZoom();
|
|
111
|
-
const bg_billboard = new BillboardCollection(this.profile_field.getStormMotionGrid(), this.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) });
|
|
112
115
|
await bg_billboard.setup(gl);
|
|
113
116
|
this.gl_elems = {
|
|
114
117
|
gl: gl, map: map, bg_billboard: bg_billboard
|
package/lib/Paintball.d.ts
CHANGED
|
@@ -22,8 +22,8 @@ interface PaintballOptions {
|
|
|
22
22
|
*/
|
|
23
23
|
declare class Paintball<ArrayType extends TypedArray, MapType extends MapLikeType> extends PlotComponent<MapType> {
|
|
24
24
|
private field;
|
|
25
|
-
readonly
|
|
26
|
-
readonly
|
|
25
|
+
readonly opts: Required<PaintballOptions>;
|
|
26
|
+
private readonly color_components;
|
|
27
27
|
private gl_elems;
|
|
28
28
|
private fill_texture;
|
|
29
29
|
/**
|
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;
|
|
@@ -45,6 +46,10 @@ void main() {
|
|
|
45
46
|
color.a = color.a * u_opacity;
|
|
46
47
|
gl_FragColor = color;
|
|
47
48
|
}`
|
|
49
|
+
const paintball_opt_defaults = {
|
|
50
|
+
colors: ['#000000'],
|
|
51
|
+
opacity: 1
|
|
52
|
+
};
|
|
48
53
|
/**
|
|
49
54
|
* A class representing a paintball plot, which is a plot of objects in every member of an ensemble. Objects are usually defined by a single threshold on
|
|
50
55
|
* a field (such as simulated reflectivity greater than 40 dBZ), but could in theory be defined by any arbitrarily complicated method. In autumnplot-gl,
|
|
@@ -63,10 +68,8 @@ class Paintball extends PlotComponent {
|
|
|
63
68
|
constructor(field, opts) {
|
|
64
69
|
super();
|
|
65
70
|
this.field = field;
|
|
66
|
-
opts = opts
|
|
67
|
-
|
|
68
|
-
this.colors = colors.reverse().map(color => hex2rgba(color)).flat();
|
|
69
|
-
this.opacity = opts.opacity !== undefined ? opts.opacity : 1.;
|
|
71
|
+
this.opts = normalizeOptions(opts, paintball_opt_defaults);
|
|
72
|
+
this.color_components = [...this.opts.colors].reverse().map(color => Color.fromHex(color).toRGBATuple()).flat();
|
|
70
73
|
this.gl_elems = null;
|
|
71
74
|
this.fill_texture = null;
|
|
72
75
|
}
|
|
@@ -119,7 +122,7 @@ class Paintball extends PlotComponent {
|
|
|
119
122
|
if (matrix instanceof Float32Array)
|
|
120
123
|
matrix = [...matrix];
|
|
121
124
|
// Render to framebuffer
|
|
122
|
-
gl_elems.program.use({ 'a_pos': gl_elems.vertices, 'a_tex_coord': gl_elems.texcoords }, { 'u_matrix': matrix, 'u_opacity': this.opacity, 'u_colors': this.
|
|
125
|
+
gl_elems.program.use({ 'a_pos': gl_elems.vertices, 'a_tex_coord': gl_elems.texcoords }, { 'u_matrix': matrix, 'u_opacity': this.opts.opacity, 'u_colors': this.color_components, 'u_num_colors': this.opts.colors.length, 'u_offset': 0 }, { 'u_fill_sampler': this.fill_texture });
|
|
123
126
|
gl.enable(gl.BLEND);
|
|
124
127
|
gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
|
125
128
|
gl_elems.program.draw();
|
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
|
@@ -239,6 +239,9 @@ function makePolylinesMiter(lines) {
|
|
|
239
239
|
}
|
|
240
240
|
*/
|
|
241
241
|
function makePolylines(lines) {
|
|
242
|
+
if (lines.length == 0) {
|
|
243
|
+
return { vertices: new Float32Array([]), extrusion: new Float32Array([]) };
|
|
244
|
+
}
|
|
242
245
|
const n_points_per_vert = Object.fromEntries(Object.entries(lines[0]).map(([k, v]) => {
|
|
243
246
|
let n_verts;
|
|
244
247
|
if (typeof v === 'number') {
|
|
@@ -285,10 +288,10 @@ function makePolylines(lines) {
|
|
|
285
288
|
return [v_ll.x, v_ll.y];
|
|
286
289
|
});
|
|
287
290
|
const has_offsets = line.offsets !== undefined;
|
|
288
|
-
const extrusion_verts =
|
|
291
|
+
const extrusion_verts = line.offsets !== undefined ? line.offsets : verts;
|
|
289
292
|
let pt_prev, pt_this = verts[0], pt_next = verts[1];
|
|
290
293
|
let ept_prev, ept_this = extrusion_verts[0], ept_next = extrusion_verts[1];
|
|
291
|
-
let len_prev, len_this = 0;
|
|
294
|
+
let len_prev, len_this = 0.0001;
|
|
292
295
|
let [ext_x, ext_y] = compute_normal_vec(ept_this, ept_next, !has_offsets);
|
|
293
296
|
ret.vertices[ilns.vertices++] = pt_this[0];
|
|
294
297
|
ret.vertices[ilns.vertices++] = pt_this[1];
|
|
@@ -305,13 +308,13 @@ function makePolylines(lines) {
|
|
|
305
308
|
len_this += Math.hypot(verts[ivt - 1][0] - verts[ivt][0], verts[ivt - 1][1] - verts[ivt][1]);
|
|
306
309
|
ret.vertices[ilns.vertices++] = pt_prev[0];
|
|
307
310
|
ret.vertices[ilns.vertices++] = pt_prev[1];
|
|
308
|
-
ret.vertices[ilns.vertices++] = len_prev;
|
|
311
|
+
ret.vertices[ilns.vertices++] = -len_prev;
|
|
309
312
|
ret.vertices[ilns.vertices++] = pt_prev[0];
|
|
310
313
|
ret.vertices[ilns.vertices++] = pt_prev[1];
|
|
311
314
|
ret.vertices[ilns.vertices++] = len_prev;
|
|
312
315
|
ret.vertices[ilns.vertices++] = pt_this[0];
|
|
313
316
|
ret.vertices[ilns.vertices++] = pt_this[1];
|
|
314
|
-
ret.vertices[ilns.vertices++] = len_this;
|
|
317
|
+
ret.vertices[ilns.vertices++] = -len_this;
|
|
315
318
|
ret.vertices[ilns.vertices++] = pt_this[0];
|
|
316
319
|
ret.vertices[ilns.vertices++] = pt_this[1];
|
|
317
320
|
ret.vertices[ilns.vertices++] = len_this;
|
|
@@ -329,7 +332,7 @@ function makePolylines(lines) {
|
|
|
329
332
|
ret.vertices[ilns.vertices++] = len_this;
|
|
330
333
|
ret.extrusion[ilns.extrusion++] = -ext_x;
|
|
331
334
|
ret.extrusion[ilns.extrusion++] = -ext_y;
|
|
332
|
-
if (
|
|
335
|
+
if (ret.offsets !== undefined && line.offsets !== undefined) {
|
|
333
336
|
const offsets = line.offsets;
|
|
334
337
|
let off_prev, off_this = offsets[0];
|
|
335
338
|
ret.offsets[ilns.offsets++] = off_this[0];
|
|
@@ -349,7 +352,7 @@ function makePolylines(lines) {
|
|
|
349
352
|
ret.offsets[ilns.offsets++] = off_this[0];
|
|
350
353
|
ret.offsets[ilns.offsets++] = off_this[1];
|
|
351
354
|
}
|
|
352
|
-
if (
|
|
355
|
+
if (ret.data !== undefined && line.data !== undefined) {
|
|
353
356
|
const data = line.data;
|
|
354
357
|
let data_prev, data_this = data[0];
|
|
355
358
|
ret.data[ilns.data++] = data_this;
|
|
@@ -363,7 +366,7 @@ function makePolylines(lines) {
|
|
|
363
366
|
}
|
|
364
367
|
ret.data[ilns.data++] = data_this;
|
|
365
368
|
}
|
|
366
|
-
if (
|
|
369
|
+
if (ret.zoom !== undefined && line.zoom !== undefined) {
|
|
367
370
|
for (let ivt = 0; ivt < verts.length * 4 - 2; ivt++) {
|
|
368
371
|
ret.zoom[ilns.zoom++] = line['zoom'];
|
|
369
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 };
|