autumnplot-gl 2.0.0-beta.1
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/LICENSE +674 -0
- package/README.md +156 -0
- package/lib/AutumnTypes.d.ts +37 -0
- package/lib/AutumnTypes.js +4 -0
- package/lib/Barbs.d.ts +56 -0
- package/lib/Barbs.js +155 -0
- package/lib/BillboardCollection.d.ts +17 -0
- package/lib/BillboardCollection.js +149 -0
- package/lib/ColorBar.d.ts +70 -0
- package/lib/ColorBar.js +183 -0
- package/lib/Colormap.d.ts +70 -0
- package/lib/Colormap.js +137 -0
- package/lib/Contour.d.ts +71 -0
- package/lib/Contour.js +172 -0
- package/lib/ContourFill.d.ts +58 -0
- package/lib/ContourFill.js +125 -0
- package/lib/Hodographs.d.ts +51 -0
- package/lib/Hodographs.js +152 -0
- package/lib/Map.d.ts +34 -0
- package/lib/Map.js +120 -0
- package/lib/Paintball.d.ts +56 -0
- package/lib/Paintball.js +104 -0
- package/lib/PlotComponent.d.ts +20 -0
- package/lib/PlotComponent.js +6 -0
- package/lib/PlotLayer.d.ts +96 -0
- package/lib/PlotLayer.js +131 -0
- package/lib/PlotLayer.worker.d.ts +18 -0
- package/lib/PlotLayer.worker.js +343 -0
- package/lib/PolylineCollection.d.ts +17 -0
- package/lib/PolylineCollection.js +92 -0
- package/lib/RawField.d.ts +194 -0
- package/lib/RawField.js +340 -0
- package/lib/index.d.ts +22 -0
- package/lib/index.js +20 -0
- package/lib/utils.d.ts +9 -0
- package/lib/utils.js +103 -0
- package/package.json +37 -0
package/lib/Paintball.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { isWebGL2Ctx } from "./AutumnTypes";
|
|
2
|
+
import { PlotComponent } from "./PlotComponent";
|
|
3
|
+
import { hex2rgba } from "./utils";
|
|
4
|
+
import { WGLProgram, WGLTexture } from "autumn-wgl";
|
|
5
|
+
const paintball_vertex_shader_src = `uniform mat4 u_matrix;
|
|
6
|
+
|
|
7
|
+
attribute vec2 a_pos;
|
|
8
|
+
attribute vec2 a_tex_coord;
|
|
9
|
+
|
|
10
|
+
varying highp vec2 v_tex_coord;
|
|
11
|
+
|
|
12
|
+
void main() {
|
|
13
|
+
gl_Position = u_matrix * vec4(a_pos, 0.0, 1.0);
|
|
14
|
+
v_tex_coord = a_tex_coord;
|
|
15
|
+
}`
|
|
16
|
+
const paintball_fragment_shader_src = `#define MAX_N_COLORS 24
|
|
17
|
+
|
|
18
|
+
varying highp vec2 v_tex_coord;
|
|
19
|
+
|
|
20
|
+
uniform sampler2D u_fill_sampler;
|
|
21
|
+
uniform lowp vec4 u_colors[MAX_N_COLORS];
|
|
22
|
+
uniform int u_num_colors;
|
|
23
|
+
uniform highp float u_opacity;
|
|
24
|
+
|
|
25
|
+
void main() {
|
|
26
|
+
highp float fill_val = texture2D(u_fill_sampler, v_tex_coord).r;
|
|
27
|
+
|
|
28
|
+
if (fill_val < 0.5) {
|
|
29
|
+
discard;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
lowp vec4 color = vec4(0., 0., 0., 0.);
|
|
33
|
+
|
|
34
|
+
for (int nclr = 0; nclr < MAX_N_COLORS ; nclr++) {
|
|
35
|
+
if (nclr >= u_num_colors || fill_val < 0.99) { break; }
|
|
36
|
+
|
|
37
|
+
lowp float mem_active = mod(fill_val, 2.);
|
|
38
|
+
color = mix(color, u_colors[nclr], mem_active);
|
|
39
|
+
fill_val = floor(fill_val / 2.);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
color.a = color.a * u_opacity;
|
|
43
|
+
gl_FragColor = color;
|
|
44
|
+
}`
|
|
45
|
+
/**
|
|
46
|
+
* 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
|
|
47
|
+
* a field (such as simulated reflectivity greater than 40 dBZ), but could in theory be defined by any arbitrarily complicated method. In autumnplot-gl,
|
|
48
|
+
* the data for the paintball plot is given as a single field with the objects from each member encoded as "bits" in the field. Because the field is made up
|
|
49
|
+
* of single-precision floats, this works for up to 24 members. (Technically speaking, I don't need the quotes around "bits", as they're bits of the
|
|
50
|
+
* significand of an IEEE 754 float.)
|
|
51
|
+
*/
|
|
52
|
+
class Paintball extends PlotComponent {
|
|
53
|
+
/**
|
|
54
|
+
* Create a paintball plot
|
|
55
|
+
* ¶m field - A scalar field containing the member objects encoded as "bits." The numerical value of each grid point can be constructed like
|
|
56
|
+
* `1.0 * M1 + 2.0 * M2 + 4.0 * M3 + 8.0 * M4 ...`, where `M1` is 1 if that grid point is in an object in member 1 and 0 otherwise,
|
|
57
|
+
* `M2` is the same thing for member 2, and `M3` and `M4` and up to `Mn` are the same thing for the rest of the members.
|
|
58
|
+
* ¶m opts - Options for creating the paintball plot
|
|
59
|
+
*/
|
|
60
|
+
constructor(field, opts) {
|
|
61
|
+
super();
|
|
62
|
+
this.field = field;
|
|
63
|
+
opts = opts !== undefined ? opts : {};
|
|
64
|
+
const colors = opts.colors !== undefined ? [...opts.colors] : ['#000000'];
|
|
65
|
+
this.colors = colors.reverse().map(color => hex2rgba(color)).flat();
|
|
66
|
+
this.opacity = opts.opacity !== undefined ? opts.opacity : 1.;
|
|
67
|
+
this.gl_elems = null;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* &internal
|
|
71
|
+
* Add the paintball plot to a map.
|
|
72
|
+
*/
|
|
73
|
+
async onAdd(map, gl) {
|
|
74
|
+
gl.getExtension('OES_texture_float');
|
|
75
|
+
const program = new WGLProgram(gl, paintball_vertex_shader_src, paintball_fragment_shader_src);
|
|
76
|
+
const { vertices: verts_buf, texcoords: tex_coords_buf } = await this.field.grid.getWGLBuffers(gl);
|
|
77
|
+
const vertices = verts_buf;
|
|
78
|
+
const texcoords = tex_coords_buf;
|
|
79
|
+
const format = isWebGL2Ctx(gl) ? gl.R32F : gl.LUMINANCE;
|
|
80
|
+
const fill_image = { 'format': format, 'type': gl.FLOAT,
|
|
81
|
+
'width': this.field.grid.ni, 'height': this.field.grid.nj, 'image': this.field.data,
|
|
82
|
+
'mag_filter': gl.NEAREST,
|
|
83
|
+
};
|
|
84
|
+
const fill_texture = new WGLTexture(gl, fill_image);
|
|
85
|
+
this.gl_elems = {
|
|
86
|
+
program: program, vertices: vertices, fill_texture: fill_texture, texcoords: texcoords,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* &internal
|
|
91
|
+
* Render the paintball plot
|
|
92
|
+
*/
|
|
93
|
+
render(gl, matrix) {
|
|
94
|
+
if (this.gl_elems === null)
|
|
95
|
+
return;
|
|
96
|
+
const gl_elems = this.gl_elems;
|
|
97
|
+
// Render to framebuffer
|
|
98
|
+
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.colors, 'u_num_colors': this.colors.length / 4 }, { 'u_fill_sampler': gl_elems.fill_texture });
|
|
99
|
+
gl.enable(gl.BLEND);
|
|
100
|
+
gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
|
101
|
+
gl_elems.program.draw();
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
export default Paintball;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import * as Comlink from 'comlink';
|
|
2
|
+
import { MapType } from './Map';
|
|
3
|
+
import { WebGLAnyRenderingContext } from './AutumnTypes';
|
|
4
|
+
declare const layer_worker: Comlink.Remote<{
|
|
5
|
+
makeBBElements: (field_lats: Float32Array, field_lons: Float32Array, field_ni: number, field_nj: number, thin_fac_base: number, max_zoom: number) => {
|
|
6
|
+
pts: Float32Array;
|
|
7
|
+
tex_coords: Float32Array;
|
|
8
|
+
};
|
|
9
|
+
makeDomainVerticesAndTexCoords: (field_lats: Float32Array, field_lons: Float32Array, field_ni: number, field_nj: number, texcoord_margin_r: number, texcoord_margin_s: number) => {
|
|
10
|
+
vertices: Float32Array;
|
|
11
|
+
tex_coords: Float32Array;
|
|
12
|
+
grid_cell_size: Float32Array;
|
|
13
|
+
};
|
|
14
|
+
makePolyLines: (lines: import("./AutumnTypes").LineSpec[]) => import("./AutumnTypes").PolylineSpec;
|
|
15
|
+
}>;
|
|
16
|
+
declare abstract class PlotComponent {
|
|
17
|
+
abstract onAdd(map: MapType, gl: WebGLAnyRenderingContext): Promise<void>;
|
|
18
|
+
abstract render(gl: WebGLAnyRenderingContext, matrix: number[]): void;
|
|
19
|
+
}
|
|
20
|
+
export { PlotComponent, layer_worker };
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { WebGLAnyRenderingContext } from './AutumnTypes';
|
|
2
|
+
import { MapType } from './Map';
|
|
3
|
+
import { PlotComponent } from './PlotComponent';
|
|
4
|
+
declare abstract class PlotLayerBase {
|
|
5
|
+
readonly type: 'custom';
|
|
6
|
+
readonly id: string;
|
|
7
|
+
constructor(id: string);
|
|
8
|
+
abstract onAdd(map: MapType, gl: WebGLAnyRenderingContext): void;
|
|
9
|
+
abstract render(gl: WebGLAnyRenderingContext, matrix: number[]): void;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* A static map layer. The data are assumed to be static in time. If the data have a time component (e.g., a model forecast), an {@link MultiPlotLayer}
|
|
13
|
+
* may be more appropriate.
|
|
14
|
+
* @example
|
|
15
|
+
* // Create map layers from provided fields
|
|
16
|
+
* const height_layer = new PlotLayer('height-contours', height_contours);
|
|
17
|
+
* const wind_speed_layer = new PlotLayer('wind-speed-fill', wind_speed_fill);
|
|
18
|
+
* const barb_layer = new PlotLayer('barbs', wind_barbs);
|
|
19
|
+
*/
|
|
20
|
+
declare class PlotLayer extends PlotLayerBase {
|
|
21
|
+
readonly field: PlotComponent;
|
|
22
|
+
/**
|
|
23
|
+
* Create a map layer from a field
|
|
24
|
+
* @param id - A unique id for this layer
|
|
25
|
+
* @param field - The field to plot in this layer
|
|
26
|
+
*/
|
|
27
|
+
constructor(id: string, field: PlotComponent);
|
|
28
|
+
/**
|
|
29
|
+
* @internal
|
|
30
|
+
* Add this layer to a map
|
|
31
|
+
*/
|
|
32
|
+
onAdd(map: MapType, gl: WebGLAnyRenderingContext): void;
|
|
33
|
+
/**
|
|
34
|
+
* @internal
|
|
35
|
+
* Render this layer
|
|
36
|
+
*/
|
|
37
|
+
render(gl: WebGLAnyRenderingContext, matrix: number[]): void;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* A varying map layer. If the data don't have a varying component, such as over time, it might be easier to use an {@link PlotLayer} instead.
|
|
41
|
+
* @example
|
|
42
|
+
* // Create a varying map layer
|
|
43
|
+
* height_layer = new MultiPlotLayer('height-contours');
|
|
44
|
+
*
|
|
45
|
+
* // Add some fields to it
|
|
46
|
+
* height_layer.addField(height_contour_f00, '20230112_1200');
|
|
47
|
+
* height_layer.addField(height_contour_f01, '20230112_1300');
|
|
48
|
+
* height_layer.addField(height_contour_f02, '20230112_1400');
|
|
49
|
+
*
|
|
50
|
+
* // Set the date/time in the map layer
|
|
51
|
+
* height_layer.setActiveKey('20230112_1200');
|
|
52
|
+
*/
|
|
53
|
+
declare class MultiPlotLayer extends PlotLayerBase {
|
|
54
|
+
/** @private */
|
|
55
|
+
fields: Record<string, PlotComponent>;
|
|
56
|
+
/** @private */
|
|
57
|
+
field_key: string | null;
|
|
58
|
+
/** @private */
|
|
59
|
+
map: MapType | null;
|
|
60
|
+
/** @private */
|
|
61
|
+
gl: WebGLAnyRenderingContext | null;
|
|
62
|
+
/**
|
|
63
|
+
* Create a time-varying map layer
|
|
64
|
+
* @param id - A unique id for this layer
|
|
65
|
+
*/
|
|
66
|
+
constructor(id: string);
|
|
67
|
+
/**
|
|
68
|
+
* @internal
|
|
69
|
+
* Add this layer to a map
|
|
70
|
+
*/
|
|
71
|
+
onAdd(map: MapType, gl: WebGLAnyRenderingContext): void;
|
|
72
|
+
/**
|
|
73
|
+
* @internal
|
|
74
|
+
* Render this layer
|
|
75
|
+
*/
|
|
76
|
+
render(gl: WebGLAnyRenderingContext, matrix: number[]): void;
|
|
77
|
+
/**
|
|
78
|
+
* Set the active key
|
|
79
|
+
* @param key - The new key
|
|
80
|
+
*/
|
|
81
|
+
setActiveKey(key: string): void;
|
|
82
|
+
/**
|
|
83
|
+
* Get a list of all dates/times that have been added to the layer
|
|
84
|
+
* @returns An array of dates/times
|
|
85
|
+
*/
|
|
86
|
+
getKeys(): string[];
|
|
87
|
+
/**
|
|
88
|
+
* Add a field valid at a specific date/time
|
|
89
|
+
* @param field - The field to add
|
|
90
|
+
* @param dt - The date/time at which the field is valid
|
|
91
|
+
*/
|
|
92
|
+
addField(field: PlotComponent, key: string): void;
|
|
93
|
+
/** @private */
|
|
94
|
+
_repaintIfNecessary(old_field_key: string | null): void;
|
|
95
|
+
}
|
|
96
|
+
export { PlotLayer, MultiPlotLayer };
|
package/lib/PlotLayer.js
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
class PlotLayerBase {
|
|
2
|
+
constructor(id) {
|
|
3
|
+
this.type = 'custom';
|
|
4
|
+
this.id = id;
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* A static map layer. The data are assumed to be static in time. If the data have a time component (e.g., a model forecast), an {@link MultiPlotLayer}
|
|
9
|
+
* may be more appropriate.
|
|
10
|
+
* @example
|
|
11
|
+
* // Create map layers from provided fields
|
|
12
|
+
* const height_layer = new PlotLayer('height-contours', height_contours);
|
|
13
|
+
* const wind_speed_layer = new PlotLayer('wind-speed-fill', wind_speed_fill);
|
|
14
|
+
* const barb_layer = new PlotLayer('barbs', wind_barbs);
|
|
15
|
+
*/
|
|
16
|
+
class PlotLayer extends PlotLayerBase {
|
|
17
|
+
/**
|
|
18
|
+
* Create a map layer from a field
|
|
19
|
+
* @param id - A unique id for this layer
|
|
20
|
+
* @param field - The field to plot in this layer
|
|
21
|
+
*/
|
|
22
|
+
constructor(id, field) {
|
|
23
|
+
super(id);
|
|
24
|
+
this.field = field;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* @internal
|
|
28
|
+
* Add this layer to a map
|
|
29
|
+
*/
|
|
30
|
+
onAdd(map, gl) {
|
|
31
|
+
this.field.onAdd(map, gl);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* @internal
|
|
35
|
+
* Render this layer
|
|
36
|
+
*/
|
|
37
|
+
render(gl, matrix) {
|
|
38
|
+
this.field.render(gl, matrix);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* A varying map layer. If the data don't have a varying component, such as over time, it might be easier to use an {@link PlotLayer} instead.
|
|
43
|
+
* @example
|
|
44
|
+
* // Create a varying map layer
|
|
45
|
+
* height_layer = new MultiPlotLayer('height-contours');
|
|
46
|
+
*
|
|
47
|
+
* // Add some fields to it
|
|
48
|
+
* height_layer.addField(height_contour_f00, '20230112_1200');
|
|
49
|
+
* height_layer.addField(height_contour_f01, '20230112_1300');
|
|
50
|
+
* height_layer.addField(height_contour_f02, '20230112_1400');
|
|
51
|
+
*
|
|
52
|
+
* // Set the date/time in the map layer
|
|
53
|
+
* height_layer.setActiveKey('20230112_1200');
|
|
54
|
+
*/
|
|
55
|
+
class MultiPlotLayer extends PlotLayerBase {
|
|
56
|
+
/**
|
|
57
|
+
* Create a time-varying map layer
|
|
58
|
+
* @param id - A unique id for this layer
|
|
59
|
+
*/
|
|
60
|
+
constructor(id) {
|
|
61
|
+
super(id);
|
|
62
|
+
this.fields = {};
|
|
63
|
+
this.field_key = null;
|
|
64
|
+
this.map = null;
|
|
65
|
+
this.gl = null;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* @internal
|
|
69
|
+
* Add this layer to a map
|
|
70
|
+
*/
|
|
71
|
+
onAdd(map, gl) {
|
|
72
|
+
this.map = map;
|
|
73
|
+
this.gl = gl;
|
|
74
|
+
Object.values(this.fields).forEach(field => {
|
|
75
|
+
field.onAdd(map, gl).then(res => {
|
|
76
|
+
this._repaintIfNecessary(null);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
this._repaintIfNecessary(null);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* @internal
|
|
83
|
+
* Render this layer
|
|
84
|
+
*/
|
|
85
|
+
render(gl, matrix) {
|
|
86
|
+
if (this.map !== null && this.gl !== null && this.field_key !== null
|
|
87
|
+
&& this.fields.hasOwnProperty(this.field_key) && this.fields[this.field_key] !== null) {
|
|
88
|
+
this.fields[this.field_key].render(gl, matrix);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Set the active key
|
|
93
|
+
* @param key - The new key
|
|
94
|
+
*/
|
|
95
|
+
setActiveKey(key) {
|
|
96
|
+
const old_field_key = this.field_key;
|
|
97
|
+
this.field_key = key;
|
|
98
|
+
this._repaintIfNecessary(old_field_key);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Get a list of all dates/times that have been added to the layer
|
|
102
|
+
* @returns An array of dates/times
|
|
103
|
+
*/
|
|
104
|
+
getKeys() {
|
|
105
|
+
return Object.keys(this.fields);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Add a field valid at a specific date/time
|
|
109
|
+
* @param field - The field to add
|
|
110
|
+
* @param dt - The date/time at which the field is valid
|
|
111
|
+
*/
|
|
112
|
+
addField(field, key) {
|
|
113
|
+
const old_field_key = this.field_key;
|
|
114
|
+
if (this.map !== null && this.gl !== null && field !== null) {
|
|
115
|
+
field.onAdd(this.map, this.gl).then(res => {
|
|
116
|
+
this._repaintIfNecessary(null);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
this.fields[key] = field;
|
|
120
|
+
if (this.field_key === null) {
|
|
121
|
+
this.field_key = key;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/** @private */
|
|
125
|
+
_repaintIfNecessary(old_field_key) {
|
|
126
|
+
if (this.map !== null && old_field_key !== this.field_key) {
|
|
127
|
+
this.map.triggerRepaint();
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
export { PlotLayer, MultiPlotLayer };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { PolylineSpec, LineSpec } from "./AutumnTypes";
|
|
2
|
+
declare function makeBBElements(field_lats: Float32Array, field_lons: Float32Array, field_ni: number, field_nj: number, thin_fac_base: number, max_zoom: number): {
|
|
3
|
+
pts: Float32Array;
|
|
4
|
+
tex_coords: Float32Array;
|
|
5
|
+
};
|
|
6
|
+
declare function makeDomainVerticesAndTexCoords(field_lats: Float32Array, field_lons: Float32Array, field_ni: number, field_nj: number, texcoord_margin_r: number, texcoord_margin_s: number): {
|
|
7
|
+
vertices: Float32Array;
|
|
8
|
+
tex_coords: Float32Array;
|
|
9
|
+
grid_cell_size: Float32Array;
|
|
10
|
+
};
|
|
11
|
+
declare function makePolylines(lines: LineSpec[]): PolylineSpec;
|
|
12
|
+
declare const ep_interface: {
|
|
13
|
+
makeBBElements: typeof makeBBElements;
|
|
14
|
+
makeDomainVerticesAndTexCoords: typeof makeDomainVerticesAndTexCoords;
|
|
15
|
+
makePolyLines: typeof makePolylines;
|
|
16
|
+
};
|
|
17
|
+
type PlotLayerWorker = typeof ep_interface;
|
|
18
|
+
export type { PlotLayerWorker };
|