autumnplot-gl 4.0.0 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/812.autumnplot-gl.js +2 -0
- package/dist/812.autumnplot-gl.js.map +1 -0
- package/dist/983.autumnplot-gl.js +1 -1
- package/dist/983.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/AutumnTypes.d.ts +6 -4
- package/lib/AutumnTypes.js +4 -1
- package/lib/Barbs.d.ts +11 -2
- package/lib/Barbs.js +9 -0
- package/lib/BillboardCollection.d.ts +2 -2
- package/lib/BillboardCollection.js +14 -14
- package/lib/ColorBar.d.ts +6 -1
- package/lib/ColorBar.js +10 -4
- package/lib/Colormap.d.ts +8 -1
- package/lib/Colormap.js +24 -1
- package/lib/Contour.d.ts +16 -1
- package/lib/Contour.js +14 -2
- package/lib/{ContourCreator.d.ts → ContourCreator.worker.d.ts} +10 -11
- package/lib/{ContourCreator.js → ContourCreator.worker.js} +15 -14
- package/lib/Fill.d.ts +29 -11
- package/lib/Fill.js +38 -18
- package/lib/Hodographs.d.ts +13 -3
- package/lib/Hodographs.js +11 -1
- package/lib/Map.d.ts +3 -4
- package/lib/Map.js +49 -51
- package/lib/Paintball.d.ts +13 -5
- package/lib/Paintball.js +96 -46
- package/lib/PlotComponent.d.ts +8 -3
- package/lib/PlotComponent.js +35 -1
- package/lib/PlotLayer.worker.js +1 -1
- package/lib/RawField.d.ts +221 -27
- package/lib/RawField.js +405 -58
- package/lib/StationPlot.d.ts +22 -6
- package/lib/StationPlot.js +88 -22
- package/lib/TextCollection.d.ts +5 -0
- package/lib/TextCollection.js +79 -9
- package/lib/WasmInterface.d.ts +7 -0
- package/lib/WasmInterface.js +11 -0
- package/lib/WorkerPool.d.ts +8 -0
- package/lib/WorkerPool.js +77 -0
- package/lib/cpp/marchingsquares.js +127 -13
- package/lib/cpp/marchingsquares.wasm +0 -0
- package/lib/cpp/marchingsquares_embind.d.ts +16 -3
- package/lib/grids/AutoZoom.d.ts +21 -0
- package/lib/grids/AutoZoom.js +63 -0
- package/lib/grids/DomainBuffer.d.ts +14 -0
- package/lib/grids/DomainBuffer.js +16 -0
- package/lib/grids/Geostationary.d.ts +35 -0
- package/lib/grids/Geostationary.js +47 -0
- package/lib/grids/Grid.d.ts +36 -0
- package/lib/grids/Grid.js +12 -0
- package/lib/grids/GridCoordinates.d.ts +10 -0
- package/lib/grids/GridCoordinates.js +64 -0
- package/lib/grids/LambertGrid.d.ts +73 -0
- package/lib/grids/LambertGrid.js +92 -0
- package/lib/grids/PlateCarreeGrid.d.ts +46 -0
- package/lib/grids/PlateCarreeGrid.js +55 -0
- package/lib/grids/PlateCarreeRotatedGrid.d.ts +53 -0
- package/lib/grids/PlateCarreeRotatedGrid.js +65 -0
- package/lib/grids/RadarSweepGrid.d.ts +46 -0
- package/lib/grids/RadarSweepGrid.js +74 -0
- package/lib/grids/StructuredGrid.d.ts +49 -0
- package/lib/grids/StructuredGrid.js +103 -0
- package/lib/grids/UnstructuredGrid.d.ts +56 -0
- package/lib/grids/UnstructuredGrid.js +102 -0
- package/lib/index.d.ts +15 -4
- package/lib/index.js +15 -7
- package/lib/utils.d.ts +11 -2
- package/lib/utils.js +63 -1
- package/package.json +3 -2
- package/lib/Grid.d.ts +0 -270
- package/lib/Grid.js +0 -600
- package/lib/ParticleTracer.d.ts +0 -19
- package/lib/ParticleTracer.js +0 -37
package/lib/Fill.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { PlotComponent } from './PlotComponent';
|
|
1
|
+
import { PlotComponent, getGLFormatTypeAlignment } from './PlotComponent';
|
|
2
2
|
import { ColorMap, ColorMapGPUInterface } from './Colormap';
|
|
3
3
|
import { WGLTexture } from 'autumn-wgl';
|
|
4
|
-
import { RawScalarField } from './RawField';
|
|
5
4
|
import { getRendererData } from './AutumnTypes';
|
|
6
|
-
import { normalizeOptions } from './utils';
|
|
5
|
+
import { applySamplerCodeScalar, normalizeOptions } from './utils';
|
|
7
6
|
import { ShaderProgramManager } from './ShaderManager';
|
|
8
7
|
const contourfill_vertex_shader_src = `#version 300 es
|
|
9
8
|
|
|
@@ -25,8 +24,6 @@ const contourfill_fragment_shader_src = `#version 300 es
|
|
|
25
24
|
|
|
26
25
|
in highp vec2 v_tex_coord;
|
|
27
26
|
|
|
28
|
-
uniform sampler2D u_fill_sampler;
|
|
29
|
-
|
|
30
27
|
#ifdef MASK
|
|
31
28
|
uniform sampler2D u_mask_sampler;
|
|
32
29
|
#endif
|
|
@@ -39,7 +36,7 @@ uniform int u_mask_val;
|
|
|
39
36
|
out highp vec4 fragColor;
|
|
40
37
|
|
|
41
38
|
void main() {
|
|
42
|
-
highp float fill_val =
|
|
39
|
+
highp float fill_val = get_field_value(v_tex_coord);
|
|
43
40
|
|
|
44
41
|
int draw_mask = 1;
|
|
45
42
|
|
|
@@ -89,20 +86,17 @@ class PlotComponentFill extends PlotComponent {
|
|
|
89
86
|
return;
|
|
90
87
|
const gl = this.gl_elems.gl;
|
|
91
88
|
const map = this.gl_elems.map;
|
|
92
|
-
|
|
93
|
-
if (this.fill_texture === null) {
|
|
94
|
-
this.fill_texture = new WGLTexture(gl, fill_image);
|
|
95
|
-
}
|
|
96
|
-
else {
|
|
97
|
-
this.fill_texture.setImageData(fill_image);
|
|
98
|
-
}
|
|
89
|
+
this.fill_texture = this.field.updateTexImageData(gl, this.image_mag_filter, this.fill_texture);
|
|
99
90
|
if (mask !== undefined) {
|
|
100
91
|
if (this.opts.cmap_mask === null) {
|
|
101
92
|
console.warn("A mask was passed to updateField on a Fill component that didn't have a mask. The updated mask will be ignored.");
|
|
102
93
|
return;
|
|
103
94
|
}
|
|
104
|
-
const
|
|
105
|
-
const mask_image =
|
|
95
|
+
const { format, type, row_alignment } = getGLFormatTypeAlignment(gl, 'uint8');
|
|
96
|
+
const mask_image = { 'format': format, 'type': type,
|
|
97
|
+
'width': this.field.grid.ni, 'height': this.field.grid.nj, 'image': mask,
|
|
98
|
+
'mag_filter': gl.NEAREST, 'row_alignment': row_alignment,
|
|
99
|
+
};
|
|
106
100
|
if (this.mask_texture === null) {
|
|
107
101
|
this.mask_texture = new WGLTexture(gl, mask_image);
|
|
108
102
|
}
|
|
@@ -114,7 +108,7 @@ class PlotComponentFill extends PlotComponent {
|
|
|
114
108
|
}
|
|
115
109
|
async onAdd(map, gl) {
|
|
116
110
|
// Basic procedure for the filled contours inspired by https://blog.mbq.me/webgl-weather-globe/
|
|
117
|
-
const { vertices: vertices, texcoords: texcoords } = await this.field.grid.
|
|
111
|
+
const { vertices: vertices, texcoords: texcoords } = await this.field.grid.getDomainBuffers(gl);
|
|
118
112
|
this.cmap_gpu.forEach(cmg => {
|
|
119
113
|
if (this.image_mag_filter === null || this.cmap_mag_filter === null) {
|
|
120
114
|
throw `Implement magnification filters in a subclass`;
|
|
@@ -125,7 +119,14 @@ class PlotComponentFill extends PlotComponent {
|
|
|
125
119
|
if (this.opts.cmap_mask !== null) {
|
|
126
120
|
shader_defines.push('MASK');
|
|
127
121
|
}
|
|
128
|
-
|
|
122
|
+
if (this.image_mag_filter === null || this.cmap_mag_filter === null) {
|
|
123
|
+
throw `Implement magnification filters in a subclass`;
|
|
124
|
+
}
|
|
125
|
+
const sampler_keys = this.field.getSamplerIds();
|
|
126
|
+
const sampler_expression = this.field.getExpression();
|
|
127
|
+
const data_types = this.field.dtypes;
|
|
128
|
+
const frag_shader_src = applySamplerCodeScalar(ColorMapGPUInterface.applyShader(contourfill_fragment_shader_src), sampler_keys, sampler_expression, data_types);
|
|
129
|
+
const shader_manger = new ShaderProgramManager(contourfill_vertex_shader_src, frag_shader_src, shader_defines);
|
|
129
130
|
this.gl_elems = {
|
|
130
131
|
gl: gl, shader_manager: shader_manger, map: map, vertices: vertices, texcoords: texcoords,
|
|
131
132
|
};
|
|
@@ -137,7 +138,8 @@ class PlotComponentFill extends PlotComponent {
|
|
|
137
138
|
const gl_elems = this.gl_elems;
|
|
138
139
|
const render_data = getRendererData(arg);
|
|
139
140
|
const program = this.gl_elems.shader_manager.getShaderProgram(gl, render_data.shaderData);
|
|
140
|
-
|
|
141
|
+
const samplers = Object.fromEntries([...this.fill_texture.entries()]);
|
|
142
|
+
program.use({ 'a_pos': gl_elems.vertices, 'a_tex_coord': gl_elems.texcoords }, { 'u_opacity': this.opts.opacity, ...this.gl_elems.shader_manager.getShaderUniforms(render_data) }, samplers);
|
|
141
143
|
this.cmap_gpu.forEach((cmg, icmg) => {
|
|
142
144
|
program.setUniforms({ 'u_offset': 0 });
|
|
143
145
|
if (this.opts.cmap_mask !== null && this.mask_texture !== null) {
|
|
@@ -161,6 +163,15 @@ class PlotComponentFill extends PlotComponent {
|
|
|
161
163
|
}
|
|
162
164
|
/**
|
|
163
165
|
* A raster (i.e. pixel) plot
|
|
166
|
+
*
|
|
167
|
+
* ## Grid Compatibility
|
|
168
|
+
* - :white_check_mark: `PlateCarreeGrid`
|
|
169
|
+
* - :white_check_mark: `PlateCarreeRotatedGrid`
|
|
170
|
+
* - :white_check_mark: `LambertGrid`
|
|
171
|
+
* - :x: `UnstructuredGrid`
|
|
172
|
+
* - :white_check_mark: `RadarSweepGrid`
|
|
173
|
+
* - :white_check_mark: `Geostationary`
|
|
174
|
+
*
|
|
164
175
|
* @example
|
|
165
176
|
* // Create a raster plot with the provided color map
|
|
166
177
|
* const raster = new Raster(wind_speed_field, {cmap: color_map});
|
|
@@ -200,6 +211,15 @@ class Raster extends PlotComponentFill {
|
|
|
200
211
|
}
|
|
201
212
|
/**
|
|
202
213
|
* A filled contoured field
|
|
214
|
+
*
|
|
215
|
+
* ## Grid Compatibility
|
|
216
|
+
* - :white_check_mark: `PlateCarreeGrid`
|
|
217
|
+
* - :white_check_mark: `PlateCarreeRotatedGrid`
|
|
218
|
+
* - :white_check_mark: `LambertGrid`
|
|
219
|
+
* - :x: `UnstructuredGrid`
|
|
220
|
+
* - :white_check_mark: `RadarSweepGrid`
|
|
221
|
+
* - :white_check_mark: `Geostationary`
|
|
222
|
+
*
|
|
203
223
|
* @example
|
|
204
224
|
* // Create a field of filled contours with the provided color map
|
|
205
225
|
* const fill = new ContourFill(wind_speed_field, {cmap: color_map});
|
package/lib/Hodographs.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { MapLikeType } from "./Map";
|
|
|
3
3
|
import { RawProfileField } from "./RawField";
|
|
4
4
|
import { RenderMethodArg, WebGLAnyRenderingContext } from "./AutumnTypes";
|
|
5
5
|
import { ColorMap } from "./Colormap";
|
|
6
|
-
import {
|
|
6
|
+
import { AutoZoomGrid } from "./grids/AutoZoom";
|
|
7
7
|
/** Options for {@link Hodographs} components */
|
|
8
8
|
interface HodographOptions {
|
|
9
9
|
/**
|
|
@@ -37,8 +37,18 @@ interface HodographOptions {
|
|
|
37
37
|
*/
|
|
38
38
|
max_wind_speed_ring?: number;
|
|
39
39
|
}
|
|
40
|
-
/**
|
|
41
|
-
|
|
40
|
+
/**
|
|
41
|
+
* A class representing a field of hodograph plots
|
|
42
|
+
*
|
|
43
|
+
* ## Grid Compatibility
|
|
44
|
+
* - :white_check_mark: `PlateCarreeGrid`
|
|
45
|
+
* - :white_check_mark: `PlateCarreeRotatedGrid`
|
|
46
|
+
* - :white_check_mark: `LambertGrid`
|
|
47
|
+
* - :white_check_mark: `UnstructuredGrid`
|
|
48
|
+
* - :x: `RadarSweepGrid`
|
|
49
|
+
* - :x: `Geostationary`
|
|
50
|
+
*/
|
|
51
|
+
declare class Hodographs<GridType extends AutoZoomGrid, MapType extends MapLikeType> extends PlotComponent<MapType> {
|
|
42
52
|
private profile_field;
|
|
43
53
|
readonly opts: Required<HodographOptions>;
|
|
44
54
|
private gl_elems;
|
package/lib/Hodographs.js
CHANGED
|
@@ -56,7 +56,17 @@ const hodograph_opt_defaults = {
|
|
|
56
56
|
height_cmap: HODO_CMAP,
|
|
57
57
|
max_wind_speed_ring: 80
|
|
58
58
|
};
|
|
59
|
-
/**
|
|
59
|
+
/**
|
|
60
|
+
* A class representing a field of hodograph plots
|
|
61
|
+
*
|
|
62
|
+
* ## Grid Compatibility
|
|
63
|
+
* - :white_check_mark: `PlateCarreeGrid`
|
|
64
|
+
* - :white_check_mark: `PlateCarreeRotatedGrid`
|
|
65
|
+
* - :white_check_mark: `LambertGrid`
|
|
66
|
+
* - :white_check_mark: `UnstructuredGrid`
|
|
67
|
+
* - :x: `RadarSweepGrid`
|
|
68
|
+
* - :x: `Geostationary`
|
|
69
|
+
*/
|
|
60
70
|
class Hodographs extends PlotComponent {
|
|
61
71
|
/**
|
|
62
72
|
* Create a field of hodographs
|
package/lib/Map.d.ts
CHANGED
|
@@ -29,14 +29,13 @@ interface RotateSphereParams {
|
|
|
29
29
|
declare function rotateSphere(params: RotateSphereParams): (a: number, b: number, opts?: {
|
|
30
30
|
inverse: boolean;
|
|
31
31
|
}) => [number, number];
|
|
32
|
-
interface
|
|
33
|
-
lat_0: number;
|
|
32
|
+
interface GeostationaryProjectionParams {
|
|
34
33
|
lon_0: number;
|
|
35
34
|
alt: number;
|
|
36
35
|
a: number;
|
|
37
36
|
b: number;
|
|
38
37
|
}
|
|
39
|
-
declare function
|
|
38
|
+
declare function geostationaryProjection(params: GeostationaryProjectionParams): (a: number, b: number, opts?: {
|
|
40
39
|
inverse: boolean;
|
|
41
40
|
}) => [number, number];
|
|
42
41
|
/**
|
|
@@ -62,5 +61,5 @@ declare class LngLat {
|
|
|
62
61
|
};
|
|
63
62
|
static fromMercatorCoord(x: number, y: number): LngLat;
|
|
64
63
|
}
|
|
65
|
-
export { LngLat, lambertConformalConic, rotateSphere,
|
|
64
|
+
export { LngLat, lambertConformalConic, rotateSphere, geostationaryProjection };
|
|
66
65
|
export type { MapLikeType };
|
package/lib/Map.js
CHANGED
|
@@ -121,60 +121,51 @@ function rotateSphere(params) {
|
|
|
121
121
|
return opts.inverse ? compute_rotation_inverse(a, b) : compute_rotation(a, b);
|
|
122
122
|
};
|
|
123
123
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
const semimajor = params.a;
|
|
127
|
-
const semiminor = params.b;
|
|
128
|
-
const eccen = Math.sqrt(1 - (semiminor * semiminor) / (semimajor * semimajor));
|
|
124
|
+
// Changes to the geostationaryProjection to make sure the coastlines and satellite line up
|
|
125
|
+
function geostationaryProjection(params) {
|
|
129
126
|
const radians = Math.PI / 180;
|
|
130
|
-
|
|
131
|
-
const
|
|
132
|
-
const
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
lon_0 *= radians;
|
|
136
|
-
const sin_lat_0 = Math.sin(lat_0);
|
|
137
|
-
const cos_lat_0 = Math.cos(lat_0);
|
|
138
|
-
const N1 = semimajor / Math.sqrt(1 - (eccen * sin_lat_0) ** 2);
|
|
139
|
-
let lat_g = lat_0, P = 0;
|
|
140
|
-
for (let i = 0; i < 2; i++) {
|
|
141
|
-
P = Math.cos(lat_0) / Math.cos(lat_g) * (alt + N1 + alt_00) / semimajor;
|
|
142
|
-
lat_g = lat_0 - Math.asin(N1 * eccen * eccen * sin_lat_0 * cos_lat_0 / (P * semimajor));
|
|
143
|
-
}
|
|
144
|
-
const compute_perspective = (lon, lat) => {
|
|
127
|
+
const { lon_0, alt, a, b } = params;
|
|
128
|
+
const lambda_0 = lon_0 * radians;
|
|
129
|
+
const a2 = a * a;
|
|
130
|
+
const b2 = b * b;
|
|
131
|
+
const forward = (lon, lat) => {
|
|
145
132
|
lon *= radians;
|
|
146
133
|
lat *= radians;
|
|
147
|
-
const sin_lat = Math.sin(lat);
|
|
148
134
|
const cos_lat = Math.cos(lat);
|
|
149
|
-
const
|
|
150
|
-
const
|
|
151
|
-
const
|
|
152
|
-
const
|
|
153
|
-
const
|
|
154
|
-
const
|
|
135
|
+
const sin_lat = Math.sin(lat);
|
|
136
|
+
const cos_lon = Math.cos(lon - lambda_0);
|
|
137
|
+
const sin_lon = Math.sin(lon - lambda_0);
|
|
138
|
+
const rc = b / Math.sqrt(1 - (1 - (b2 / a2)) * cos_lat * cos_lat);
|
|
139
|
+
const sx = rc * cos_lat * cos_lon;
|
|
140
|
+
const sy = rc * cos_lat * sin_lon;
|
|
141
|
+
const sz = rc * sin_lat;
|
|
142
|
+
const x = Math.atan(-sy / (alt - sx));
|
|
143
|
+
const y = Math.atan(sz / Math.sqrt((alt - sx) * (alt - sx) + sy * sy));
|
|
155
144
|
return [x, y];
|
|
156
145
|
};
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
const
|
|
166
|
-
const
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
const
|
|
171
|
-
const
|
|
172
|
-
const
|
|
146
|
+
const inverse = (x, y) => {
|
|
147
|
+
// x = E-W scan angle (radians), y = N-S scan angle (radians)
|
|
148
|
+
// GOES-R PUG-L1B-vol3 Section 4.2 / Table 4.2.8-1
|
|
149
|
+
const sin_x = Math.sin(x);
|
|
150
|
+
const cos_x = Math.cos(x);
|
|
151
|
+
const sin_y = Math.sin(y);
|
|
152
|
+
const cos_y = Math.cos(y);
|
|
153
|
+
const a_var = sin_x * sin_x + cos_x * cos_x * (cos_y * cos_y + (a2 / b2) * sin_y * sin_y);
|
|
154
|
+
const b_var = -2 * alt * cos_x * cos_y;
|
|
155
|
+
const c_var = alt * alt - a2;
|
|
156
|
+
const disc = b_var * b_var - 4 * a_var * c_var;
|
|
157
|
+
if (disc < 0)
|
|
158
|
+
return [NaN, NaN]; // off-Earth scan angle
|
|
159
|
+
const rs = (-b_var - Math.sqrt(disc)) / (2 * a_var);
|
|
160
|
+
const sx = rs * cos_x * cos_y;
|
|
161
|
+
const sy = -rs * sin_x; // no cos_y factor
|
|
162
|
+
const sz = rs * cos_x * sin_y; // needs cos_x factor
|
|
163
|
+
const lon = lambda_0 - Math.atan(sy / (alt - sx));
|
|
164
|
+
const lat = Math.atan((a2 / b2) * (sz / Math.sqrt((alt - sx) * (alt - sx) + sy * sy)));
|
|
173
165
|
return [lon / radians, lat / radians];
|
|
174
166
|
};
|
|
175
167
|
return (a, b, opts) => {
|
|
176
|
-
|
|
177
|
-
return opts.inverse ? compute_perspective_inverse(a, b) : compute_perspective(a, b);
|
|
168
|
+
return opts?.inverse ? inverse(a, b) : forward(a, b);
|
|
178
169
|
};
|
|
179
170
|
}
|
|
180
171
|
function mercatorXfromLng(lng) {
|
|
@@ -207,19 +198,26 @@ function latFromMercatorY(y) {
|
|
|
207
198
|
class LngLat {
|
|
208
199
|
constructor(lng, lat) {
|
|
209
200
|
if (isNaN(lng) || isNaN(lat)) {
|
|
210
|
-
|
|
201
|
+
this.lng = NaN;
|
|
202
|
+
this.lat = NaN;
|
|
211
203
|
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
204
|
+
else {
|
|
205
|
+
this.lng = +lng;
|
|
206
|
+
this.lat = +lat;
|
|
207
|
+
if (this.lat > 90 || this.lat < -90) {
|
|
208
|
+
throw new Error('Invalid LngLat latitude value: must be between -90 and 90');
|
|
209
|
+
}
|
|
216
210
|
}
|
|
217
211
|
}
|
|
218
212
|
toMercatorCoord() {
|
|
213
|
+
if (isNaN(this.lng) || isNaN(this.lng))
|
|
214
|
+
return { x: NaN, y: NaN };
|
|
219
215
|
return { x: mercatorXfromLng(this.lng), y: mercatorYfromLat(this.lat) };
|
|
220
216
|
}
|
|
221
217
|
static fromMercatorCoord(x, y) {
|
|
218
|
+
if (isNaN(x) || isNaN(y))
|
|
219
|
+
return new LngLat(NaN, NaN);
|
|
222
220
|
return new LngLat(lngFromMercatorX(x), latFromMercatorY(y));
|
|
223
221
|
}
|
|
224
222
|
}
|
|
225
|
-
export { LngLat, lambertConformalConic, rotateSphere,
|
|
223
|
+
export { LngLat, lambertConformalConic, rotateSphere, geostationaryProjection };
|
package/lib/Paintball.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { RenderMethodArg, TypedArray, WebGLAnyRenderingContext } from "./AutumnTypes";
|
|
2
|
-
import {
|
|
2
|
+
import { DomainBufferGrid } from "./grids/DomainBuffer";
|
|
3
3
|
import { MapLikeType } from "./Map";
|
|
4
4
|
import { PlotComponent } from "./PlotComponent";
|
|
5
|
-
import {
|
|
5
|
+
import { ExpressionScalarField } from "./RawField";
|
|
6
6
|
/** Options for {@link Paintball} components */
|
|
7
7
|
interface PaintballOptions {
|
|
8
8
|
/**
|
|
@@ -21,8 +21,16 @@ interface PaintballOptions {
|
|
|
21
21
|
* 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
|
|
22
22
|
* 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
|
|
23
23
|
* significand of an IEEE 754 float.)
|
|
24
|
+
*
|
|
25
|
+
* ## Grid Compatibility
|
|
26
|
+
* - :white_check_mark: `PlateCarreeGrid`
|
|
27
|
+
* - :white_check_mark: `PlateCarreeRotatedGrid`
|
|
28
|
+
* - :white_check_mark: `LambertGrid`
|
|
29
|
+
* - :x: `UnstructuredGrid`
|
|
30
|
+
* - :white_check_mark: `RadarSweepGrid`
|
|
31
|
+
* - :white_check_mark: `Geostationary`
|
|
24
32
|
*/
|
|
25
|
-
declare class Paintball<ArrayType extends TypedArray, GridType extends
|
|
33
|
+
declare class Paintball<ArrayType extends TypedArray, GridType extends DomainBufferGrid, MapType extends MapLikeType> extends PlotComponent<MapType> {
|
|
26
34
|
private field;
|
|
27
35
|
readonly opts: Required<PaintballOptions>;
|
|
28
36
|
private readonly color_components;
|
|
@@ -35,12 +43,12 @@ declare class Paintball<ArrayType extends TypedArray, GridType extends Structure
|
|
|
35
43
|
* `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.
|
|
36
44
|
* @param opts - Options for creating the paintball plot
|
|
37
45
|
*/
|
|
38
|
-
constructor(field:
|
|
46
|
+
constructor(field: ExpressionScalarField<ArrayType, GridType>, opts?: PaintballOptions);
|
|
39
47
|
/**
|
|
40
48
|
* Update the field displayed as a paintball plot
|
|
41
49
|
* @param field - The new field to display as a paintball plot
|
|
42
50
|
*/
|
|
43
|
-
updateField(field:
|
|
51
|
+
updateField(field: ExpressionScalarField<ArrayType, GridType>): Promise<void>;
|
|
44
52
|
/**
|
|
45
53
|
* @internal
|
|
46
54
|
* Add the paintball plot to a map.
|
package/lib/Paintball.js
CHANGED
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
import { getRendererData } from "./AutumnTypes";
|
|
2
2
|
import { Color } from "./Color";
|
|
3
|
-
import { PlotComponent } from "./PlotComponent";
|
|
3
|
+
import { PlotComponent, getGLFormatTypeAlignment } from "./PlotComponent";
|
|
4
4
|
import { ShaderProgramManager } from "./ShaderManager";
|
|
5
|
-
import { normalizeOptions } from "./utils";
|
|
6
|
-
import { WGLTexture } from "autumn-wgl";
|
|
7
|
-
const
|
|
5
|
+
import { applySamplerCodeScalar, normalizeOptions } from "./utils";
|
|
6
|
+
import { WGLBuffer, WGLFramebuffer, WGLProgram, WGLTexture } from "autumn-wgl";
|
|
7
|
+
const paintball_step1_vertex_shader_src = `#version 300 es
|
|
8
|
+
|
|
9
|
+
in vec2 a_pos;
|
|
10
|
+
|
|
11
|
+
out highp vec2 v_tex_coord;
|
|
12
|
+
|
|
13
|
+
void main() {
|
|
14
|
+
gl_Position = vec4(a_pos.xy, 0., 1.);
|
|
15
|
+
v_tex_coord = 0.5 * a_pos.xy + vec2(0.5, 0.5);
|
|
16
|
+
}`
|
|
17
|
+
const paintball_step2_vertex_shader_src = `#version 300 es
|
|
8
18
|
|
|
9
19
|
uniform int u_offset;
|
|
10
20
|
|
|
@@ -20,15 +30,34 @@ void main() {
|
|
|
20
30
|
gl_Position = projectTile(a_pos.xy + globe_offset);
|
|
21
31
|
v_tex_coord = a_tex_coord;
|
|
22
32
|
}`
|
|
23
|
-
const
|
|
33
|
+
const paintball_step1_fragment_shader_src = `#version 300 es
|
|
34
|
+
|
|
35
|
+
in highp vec2 v_tex_coord;
|
|
36
|
+
|
|
37
|
+
uniform int u_imem;
|
|
38
|
+
|
|
39
|
+
out highp vec4 fragColor;
|
|
40
|
+
|
|
41
|
+
void main() {
|
|
42
|
+
uint fill_val = uint(get_field_value(v_tex_coord));
|
|
43
|
+
|
|
44
|
+
if (fill_val < uint(1)) {
|
|
45
|
+
discard;
|
|
46
|
+
}
|
|
24
47
|
|
|
25
|
-
|
|
48
|
+
lowp vec4 off_color = vec4(0., 0., 0., 1.);
|
|
49
|
+
lowp vec4 on_color = vec4(1., 1., 1., 1.);
|
|
50
|
+
|
|
51
|
+
uint mask = uint(1) << u_imem;
|
|
52
|
+
lowp float mem_active = float((fill_val & mask) > uint(0));
|
|
53
|
+
fragColor = mix(off_color, on_color, mem_active);
|
|
54
|
+
}`
|
|
55
|
+
const paintball_step2_fragment_shader_src = `#version 300 es
|
|
26
56
|
|
|
27
57
|
in highp vec2 v_tex_coord;
|
|
28
58
|
|
|
29
59
|
uniform sampler2D u_fill_sampler;
|
|
30
|
-
uniform lowp vec4
|
|
31
|
-
uniform int u_num_colors;
|
|
60
|
+
uniform lowp vec4 u_color;
|
|
32
61
|
uniform highp float u_opacity;
|
|
33
62
|
|
|
34
63
|
out highp vec4 fragColor;
|
|
@@ -36,19 +65,11 @@ out highp vec4 fragColor;
|
|
|
36
65
|
void main() {
|
|
37
66
|
highp float fill_val = texture(u_fill_sampler, v_tex_coord).r;
|
|
38
67
|
|
|
39
|
-
if (fill_val < 0.
|
|
68
|
+
if (fill_val < 0.4) {
|
|
40
69
|
discard;
|
|
41
70
|
}
|
|
42
71
|
|
|
43
|
-
lowp vec4 color =
|
|
44
|
-
|
|
45
|
-
for (int nclr = 0; nclr < MAX_N_COLORS ; nclr++) {
|
|
46
|
-
if (nclr >= u_num_colors || fill_val < 0.99) { break; }
|
|
47
|
-
|
|
48
|
-
lowp float mem_active = mod(fill_val, 2.);
|
|
49
|
-
color = mix(color, u_colors[nclr], mem_active);
|
|
50
|
-
fill_val = floor(fill_val / 2.);
|
|
51
|
-
}
|
|
72
|
+
lowp vec4 color = u_color;
|
|
52
73
|
|
|
53
74
|
color.a = color.a * u_opacity;
|
|
54
75
|
fragColor = color;
|
|
@@ -63,6 +84,14 @@ const paintball_opt_defaults = {
|
|
|
63
84
|
* 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
|
|
64
85
|
* 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
|
|
65
86
|
* significand of an IEEE 754 float.)
|
|
87
|
+
*
|
|
88
|
+
* ## Grid Compatibility
|
|
89
|
+
* - :white_check_mark: `PlateCarreeGrid`
|
|
90
|
+
* - :white_check_mark: `PlateCarreeRotatedGrid`
|
|
91
|
+
* - :white_check_mark: `LambertGrid`
|
|
92
|
+
* - :x: `UnstructuredGrid`
|
|
93
|
+
* - :white_check_mark: `RadarSweepGrid`
|
|
94
|
+
* - :white_check_mark: `Geostationary`
|
|
66
95
|
*/
|
|
67
96
|
class Paintball extends PlotComponent {
|
|
68
97
|
/**
|
|
@@ -76,7 +105,7 @@ class Paintball extends PlotComponent {
|
|
|
76
105
|
super();
|
|
77
106
|
this.field = field;
|
|
78
107
|
this.opts = normalizeOptions(opts, paintball_opt_defaults);
|
|
79
|
-
this.color_components = this.opts.colors.map(color => Color.fromHex(color).toRGBATuple())
|
|
108
|
+
this.color_components = this.opts.colors.map(color => Color.fromHex(color).toRGBATuple());
|
|
80
109
|
this.gl_elems = null;
|
|
81
110
|
this.fill_texture = null;
|
|
82
111
|
}
|
|
@@ -90,13 +119,7 @@ class Paintball extends PlotComponent {
|
|
|
90
119
|
return;
|
|
91
120
|
const gl = this.gl_elems.gl;
|
|
92
121
|
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 2);
|
|
93
|
-
|
|
94
|
-
if (this.fill_texture === null) {
|
|
95
|
-
this.fill_texture = new WGLTexture(gl, fill_image);
|
|
96
|
-
}
|
|
97
|
-
else {
|
|
98
|
-
this.fill_texture.setImageData(fill_image);
|
|
99
|
-
}
|
|
122
|
+
this.fill_texture = this.field.updateTexImageData(gl, gl.NEAREST, this.fill_texture);
|
|
100
123
|
}
|
|
101
124
|
/**
|
|
102
125
|
* @internal
|
|
@@ -104,12 +127,23 @@ class Paintball extends PlotComponent {
|
|
|
104
127
|
*/
|
|
105
128
|
async onAdd(map, gl) {
|
|
106
129
|
gl.getExtension('OES_texture_float');
|
|
107
|
-
const
|
|
108
|
-
const vertices =
|
|
109
|
-
const
|
|
110
|
-
const
|
|
130
|
+
const vertices_step1 = new WGLBuffer(gl, new Float32Array([-1., -1., 1., -1., -1., 1., 1., 1.]), 2, gl.TRIANGLE_STRIP);
|
|
131
|
+
const { vertices: vertices_step2, texcoords: texcoords_step2 } = await this.field.grid.getDomainBuffers(gl);
|
|
132
|
+
const sampler_keys = this.field.getSamplerIds();
|
|
133
|
+
const sampler_expression = this.field.getExpression();
|
|
134
|
+
const dtypes = this.field.dtypes;
|
|
135
|
+
const step1_frag_shader_src = applySamplerCodeScalar(paintball_step1_fragment_shader_src, sampler_keys, sampler_expression, dtypes);
|
|
136
|
+
const shader_program_step1 = new WGLProgram(gl, paintball_step1_vertex_shader_src, step1_frag_shader_src);
|
|
137
|
+
const shader_manager_step2 = new ShaderProgramManager(paintball_step2_vertex_shader_src, paintball_step2_fragment_shader_src, []);
|
|
138
|
+
const { format, type, row_alignment } = getGLFormatTypeAlignment(gl, 'float16');
|
|
139
|
+
const fb_texture = new WGLTexture(gl, { format: format, type: type,
|
|
140
|
+
width: this.field.grid.ni, height: this.field.grid.nj, image: null,
|
|
141
|
+
mag_filter: gl.LINEAR, row_alignment: row_alignment });
|
|
142
|
+
const fb = new WGLFramebuffer(gl, fb_texture);
|
|
111
143
|
this.gl_elems = {
|
|
112
|
-
gl: gl,
|
|
144
|
+
gl: gl, map: map, shader_program_1: shader_program_step1, shader_manager_2: shader_manager_step2,
|
|
145
|
+
vertices_step1: vertices_step1, vertices_step2: vertices_step2, texcoords_step2: texcoords_step2,
|
|
146
|
+
framebuffer: fb
|
|
113
147
|
};
|
|
114
148
|
this.updateField(this.field);
|
|
115
149
|
}
|
|
@@ -121,22 +155,38 @@ class Paintball extends PlotComponent {
|
|
|
121
155
|
if (this.gl_elems === null || this.fill_texture === null)
|
|
122
156
|
return;
|
|
123
157
|
const gl_elems = this.gl_elems;
|
|
158
|
+
const fill_texture = this.fill_texture;
|
|
159
|
+
const map = gl_elems.map;
|
|
160
|
+
const viewport_width = map.getCanvas().width;
|
|
161
|
+
const viewport_height = map.getCanvas().height;
|
|
124
162
|
const render_data = getRendererData(arg);
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
163
|
+
const program_step1 = this.gl_elems.shader_program_1;
|
|
164
|
+
const program_step2 = this.gl_elems.shader_manager_2.getShaderProgram(gl, render_data.shaderData);
|
|
165
|
+
const samplers = Object.fromEntries([...this.fill_texture.entries()]);
|
|
166
|
+
this.color_components.forEach((color, icolor) => {
|
|
167
|
+
// Render to framebuffer to pull out an individual member from the packed field
|
|
168
|
+
program_step1.use({ 'a_pos': gl_elems.vertices_step1 }, { 'u_imem': icolor }, samplers);
|
|
169
|
+
gl_elems.framebuffer.clear([0, 0, 0, 1]);
|
|
170
|
+
gl_elems.framebuffer.renderTo(0, 0, this.field.grid.ni, this.field.grid.nj);
|
|
171
|
+
gl.enable(gl.BLEND);
|
|
172
|
+
gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
|
173
|
+
program_step1.draw();
|
|
174
|
+
// Now render the framebuffer as a filled contour
|
|
175
|
+
program_step2.use({ 'a_pos': gl_elems.vertices_step2, 'a_tex_coord': gl_elems.texcoords_step2 }, { 'u_opacity': this.opts.opacity, 'u_color': color, 'u_offset': 0,
|
|
176
|
+
...gl_elems.shader_manager_2.getShaderUniforms(render_data) }, { 'u_fill_sampler': gl_elems.framebuffer.texture });
|
|
177
|
+
WGLFramebuffer.screen(gl).renderTo(0, 0, viewport_width, viewport_height);
|
|
178
|
+
gl.enable(gl.BLEND);
|
|
179
|
+
gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
|
180
|
+
program_step2.draw();
|
|
181
|
+
if (render_data.type != 'maplibre' || !render_data.shaderData.define.includes('GLOBE')) {
|
|
182
|
+
program_step2.setUniforms({ 'u_offset': -2 });
|
|
183
|
+
program_step2.draw();
|
|
184
|
+
program_step2.setUniforms({ 'u_offset': -1 });
|
|
185
|
+
program_step2.draw();
|
|
186
|
+
program_step2.setUniforms({ 'u_offset': 1 });
|
|
187
|
+
program_step2.draw();
|
|
188
|
+
}
|
|
189
|
+
});
|
|
140
190
|
}
|
|
141
191
|
}
|
|
142
192
|
export default Paintball;
|
package/lib/PlotComponent.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as Comlink from 'comlink';
|
|
2
2
|
import { MapLikeType } from './Map';
|
|
3
3
|
import { RenderMethodArg, TypedArrayStr, WebGLAnyRenderingContext } from './AutumnTypes';
|
|
4
|
+
import { WorkerPool } from './WorkerPool';
|
|
4
5
|
declare const layer_worker: Comlink.Remote<{
|
|
5
6
|
makeBBElements: (field_lats: Float32Array, field_lons: Float32Array, min_zoom: Uint8Array, field_ni: number, field_nj: number, map_max_zoom: number) => {
|
|
6
7
|
pts: Float32Array;
|
|
@@ -12,14 +13,18 @@ declare const layer_worker: Comlink.Remote<{
|
|
|
12
13
|
};
|
|
13
14
|
makePolyLines: (lines: import("./AutumnTypes").LineData[]) => import("./AutumnTypes").Polyline;
|
|
14
15
|
}>;
|
|
16
|
+
declare function getContourWorkerPool(wasm_base_url: string | undefined, n_workers: number): WorkerPool<{
|
|
17
|
+
contourCreator: (data: import("./AutumnTypes").ContourableTypedArray, grid_coords: import("./grids/Grid").GridCoords, opts: import("./ContourCreator.worker").FieldContourOpts) => Promise<import("./AutumnTypes").ContourData>;
|
|
18
|
+
init: (wasm_base_url: string | undefined) => Promise<void>;
|
|
19
|
+
}>;
|
|
15
20
|
/** Base class for all plot components */
|
|
16
21
|
declare abstract class PlotComponent<MapType extends MapLikeType> {
|
|
17
22
|
abstract onAdd(map: MapType, gl: WebGLAnyRenderingContext): Promise<void>;
|
|
18
23
|
abstract render(gl: WebGLAnyRenderingContext, arg: RenderMethodArg): void;
|
|
19
24
|
}
|
|
20
25
|
declare function getGLFormatTypeAlignment(gl: WebGLAnyRenderingContext, array_dtype: TypedArrayStr): {
|
|
21
|
-
format: 33321 | 33325 | 33326 | 6409;
|
|
22
|
-
type: 36193 | 5131 | 5121 | 5126;
|
|
26
|
+
format: 33321 | 33325 | 33326 | 33331 | 33332 | 33333 | 33334 | 6409;
|
|
27
|
+
type: 36193 | 5131 | 5121 | 5122 | 5123 | 5124 | 5125 | 5126;
|
|
23
28
|
row_alignment: number;
|
|
24
29
|
};
|
|
25
|
-
export { PlotComponent, layer_worker, getGLFormatTypeAlignment };
|
|
30
|
+
export { PlotComponent, layer_worker, getContourWorkerPool, getGLFormatTypeAlignment };
|