autumnplot-gl 4.0.0-beta → 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 +13 -207
- package/dist/812.autumnplot-gl.js +2 -0
- package/dist/812.autumnplot-gl.js.map +1 -0
- package/dist/983.autumnplot-gl.js +2 -0
- package/dist/983.autumnplot-gl.js.map +1 -0
- 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 +38 -5
- package/lib/AutumnTypes.js +7 -1
- package/lib/Barbs.d.ts +12 -2
- package/lib/Barbs.js +9 -0
- package/lib/BillboardCollection.d.ts +2 -2
- package/lib/BillboardCollection.js +14 -14
- package/lib/Color.d.ts +1 -0
- package/lib/Color.js +1 -0
- package/lib/ColorBar.d.ts +14 -0
- package/lib/ColorBar.js +15 -8
- package/lib/Colormap.d.ts +9 -1
- package/lib/Colormap.js +24 -1
- package/lib/Contour.d.ts +26 -1
- package/lib/Contour.js +24 -2
- package/lib/ContourCreator.worker.d.ts +25 -0
- package/lib/{ContourCreator.js → ContourCreator.worker.js} +15 -14
- package/lib/Fill.d.ts +31 -11
- package/lib/Fill.js +38 -18
- package/lib/Hodographs.d.ts +19 -3
- package/lib/Hodographs.js +45 -20
- package/lib/Map.d.ts +13 -1
- package/lib/Map.js +62 -8
- package/lib/Paintball.d.ts +14 -5
- package/lib/Paintball.js +96 -46
- package/lib/PlotComponent.d.ts +9 -3
- package/lib/PlotComponent.js +36 -1
- package/lib/PlotLayer.d.ts +2 -2
- package/lib/PlotLayer.js +2 -2
- package/lib/PlotLayer.worker.js +9 -3
- package/lib/RawField.d.ts +223 -27
- package/lib/RawField.js +413 -59
- package/lib/StationPlot.d.ts +78 -11
- package/lib/StationPlot.js +113 -30
- package/lib/TextCollection.d.ts +5 -0
- package/lib/TextCollection.js +82 -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 +23 -6
- package/lib/index.js +18 -8
- package/lib/utils.d.ts +11 -2
- package/lib/utils.js +63 -1
- package/package.json +4 -3
- package/dist/110.autumnplot-gl.js +0 -2
- package/dist/110.autumnplot-gl.js.map +0 -1
- package/lib/ContourCreator.d.ts +0 -22
- package/lib/Grid.d.ts +0 -263
- package/lib/Grid.js +0 -547
- package/lib/ParticleTracer.d.ts +0 -19
- package/lib/ParticleTracer.js +0 -37
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { GridCoords } from './grids/Grid';
|
|
2
|
+
import { ContourData, ContourableTypedArray } from "./AutumnTypes";
|
|
3
|
+
declare function init(wasm_base_url: string | undefined): Promise<void>;
|
|
4
|
+
/** Options for contouring data via {@link RawScalarField.getContours | RawScalarField.getContours()} */
|
|
5
|
+
interface FieldContourOpts {
|
|
6
|
+
/**
|
|
7
|
+
* The interval at which to create contours. The field will be contoured at this interval from its minimum to its maximum.
|
|
8
|
+
*/
|
|
9
|
+
interval?: number;
|
|
10
|
+
/**
|
|
11
|
+
* Contour the field at these specific levels.
|
|
12
|
+
*/
|
|
13
|
+
levels?: number[];
|
|
14
|
+
/**
|
|
15
|
+
* Add triangles in the contouring, which takes longer and generates more detailed (not necessarily smoother or better) contours
|
|
16
|
+
*/
|
|
17
|
+
quad_as_tri?: boolean;
|
|
18
|
+
}
|
|
19
|
+
declare function contourCreator(data: ContourableTypedArray, grid_coords: GridCoords, opts: FieldContourOpts): Promise<ContourData>;
|
|
20
|
+
declare const ep_interface: {
|
|
21
|
+
contourCreator: typeof contourCreator;
|
|
22
|
+
init: typeof init;
|
|
23
|
+
};
|
|
24
|
+
type ContourCreatorWorker = typeof ep_interface;
|
|
25
|
+
export type { ContourCreatorWorker, FieldContourOpts };
|
|
@@ -1,24 +1,25 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
let
|
|
4
|
-
function
|
|
5
|
-
|
|
6
|
-
msm_promise = Module();
|
|
7
|
-
}
|
|
8
|
-
return msm_promise;
|
|
1
|
+
import * as Comlink from 'comlink';
|
|
2
|
+
import { initMSModule } from "./WasmInterface";
|
|
3
|
+
let _msm = null;
|
|
4
|
+
async function init(wasm_base_url) {
|
|
5
|
+
_msm = await initMSModule({ document_script: wasm_base_url });
|
|
9
6
|
}
|
|
10
|
-
async function contourCreator(data,
|
|
7
|
+
async function contourCreator(data, grid_coords, opts) {
|
|
11
8
|
if (opts.interval === undefined && opts.levels === undefined) {
|
|
12
9
|
throw "Must supply either an interval or levels to contourCreator()";
|
|
13
10
|
}
|
|
14
11
|
const interval = opts.interval === undefined ? 0 : opts.interval;
|
|
15
12
|
const quad_as_tri = opts.quad_as_tri === undefined ? false : opts.quad_as_tri;
|
|
16
|
-
const msm = await initMSModule();
|
|
17
|
-
|
|
13
|
+
const msm = _msm === null ? await initMSModule({}) : _msm;
|
|
14
|
+
_msm = msm;
|
|
18
15
|
const getContourLevels = data instanceof Float32Array ? msm.getContourLevelsFloat32 : msm.getContourLevelsFloat16;
|
|
19
16
|
const makeContours = data instanceof Float32Array ? msm.makeContoursFloat32 : msm.makeContoursFloat16;
|
|
20
|
-
const levels = opts.levels === undefined ? getContourLevels(data,
|
|
21
|
-
const contours = makeContours(data, grid_coords.x, grid_coords.y, levels,
|
|
17
|
+
const levels = opts.levels === undefined ? getContourLevels(data, grid_coords.x.length, grid_coords.y.length, interval) : opts.levels;
|
|
18
|
+
const contours = makeContours(data, grid_coords.x, grid_coords.y, levels, quad_as_tri);
|
|
22
19
|
return contours;
|
|
23
20
|
}
|
|
24
|
-
|
|
21
|
+
const ep_interface = {
|
|
22
|
+
'contourCreator': contourCreator,
|
|
23
|
+
'init': init,
|
|
24
|
+
};
|
|
25
|
+
Comlink.expose(ep_interface);
|
package/lib/Fill.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { PlotComponent } from './PlotComponent';
|
|
2
2
|
import { ColorMap } from './Colormap';
|
|
3
|
-
import {
|
|
3
|
+
import { ExpressionScalarField } from './RawField';
|
|
4
4
|
import { MapLikeType } from './Map';
|
|
5
5
|
import { RenderMethodArg, TypedArray, WebGLAnyRenderingContext } from './AutumnTypes';
|
|
6
|
-
import {
|
|
6
|
+
import { DomainBufferGrid } from './grids/DomainBuffer';
|
|
7
|
+
/** Options for {@link ContourFill} components */
|
|
7
8
|
interface ContourFillOptions {
|
|
8
9
|
/** The color maps to use when creating the fills */
|
|
9
10
|
cmap: ColorMap | ColorMap[];
|
|
@@ -18,6 +19,7 @@ interface ContourFillOptions {
|
|
|
18
19
|
*/
|
|
19
20
|
opacity?: number;
|
|
20
21
|
}
|
|
22
|
+
/** Options for {@link Raster} components */
|
|
21
23
|
interface RasterOptions {
|
|
22
24
|
/** The color map to use when creating the raster plot */
|
|
23
25
|
cmap: ColorMap | ColorMap[];
|
|
@@ -32,7 +34,7 @@ interface RasterOptions {
|
|
|
32
34
|
*/
|
|
33
35
|
opacity?: number;
|
|
34
36
|
}
|
|
35
|
-
declare class PlotComponentFill<ArrayType extends TypedArray, GridType extends
|
|
37
|
+
declare class PlotComponentFill<ArrayType extends TypedArray, GridType extends DomainBufferGrid, MapType extends MapLikeType> extends PlotComponent<MapType> {
|
|
36
38
|
private field;
|
|
37
39
|
readonly opts: Required<ContourFillOptions>;
|
|
38
40
|
private readonly cmap_gpu;
|
|
@@ -41,29 +43,38 @@ declare class PlotComponentFill<ArrayType extends TypedArray, GridType extends S
|
|
|
41
43
|
private mask_texture;
|
|
42
44
|
protected image_mag_filter: number | null;
|
|
43
45
|
protected cmap_mag_filter: number | null;
|
|
44
|
-
constructor(field:
|
|
45
|
-
updateField(field:
|
|
46
|
+
constructor(field: ExpressionScalarField<ArrayType, GridType>, opts: ContourFillOptions);
|
|
47
|
+
updateField(field: ExpressionScalarField<ArrayType, GridType>, mask?: Uint8Array): Promise<void>;
|
|
46
48
|
onAdd(map: MapType, gl: WebGLAnyRenderingContext): Promise<void>;
|
|
47
49
|
render(gl: WebGLAnyRenderingContext, arg: RenderMethodArg): void;
|
|
48
50
|
}
|
|
49
51
|
/**
|
|
50
52
|
* A raster (i.e. pixel) plot
|
|
53
|
+
*
|
|
54
|
+
* ## Grid Compatibility
|
|
55
|
+
* - :white_check_mark: `PlateCarreeGrid`
|
|
56
|
+
* - :white_check_mark: `PlateCarreeRotatedGrid`
|
|
57
|
+
* - :white_check_mark: `LambertGrid`
|
|
58
|
+
* - :x: `UnstructuredGrid`
|
|
59
|
+
* - :white_check_mark: `RadarSweepGrid`
|
|
60
|
+
* - :white_check_mark: `Geostationary`
|
|
61
|
+
*
|
|
51
62
|
* @example
|
|
52
63
|
* // Create a raster plot with the provided color map
|
|
53
64
|
* const raster = new Raster(wind_speed_field, {cmap: color_map});
|
|
54
65
|
*/
|
|
55
|
-
declare class Raster<ArrayType extends TypedArray, GridType extends
|
|
66
|
+
declare class Raster<ArrayType extends TypedArray, GridType extends DomainBufferGrid, MapType extends MapLikeType> extends PlotComponentFill<ArrayType, GridType, MapType> {
|
|
56
67
|
/**
|
|
57
68
|
* Create a raster plot
|
|
58
69
|
* @param field - The field to create the raster plot from
|
|
59
70
|
* @param opts - Options for creating the raster plot
|
|
60
71
|
*/
|
|
61
|
-
constructor(field:
|
|
72
|
+
constructor(field: ExpressionScalarField<ArrayType, GridType>, opts: RasterOptions);
|
|
62
73
|
/**
|
|
63
74
|
* Update the data displayed as a raster plot
|
|
64
75
|
* @param field - The new field to display as a raster plot
|
|
65
76
|
*/
|
|
66
|
-
updateField(field:
|
|
77
|
+
updateField(field: ExpressionScalarField<ArrayType, GridType>, mask?: Uint8Array): Promise<void>;
|
|
67
78
|
/**
|
|
68
79
|
* @internal
|
|
69
80
|
* Add the raster plot to a map
|
|
@@ -77,22 +88,31 @@ declare class Raster<ArrayType extends TypedArray, GridType extends StructuredGr
|
|
|
77
88
|
}
|
|
78
89
|
/**
|
|
79
90
|
* A filled contoured field
|
|
91
|
+
*
|
|
92
|
+
* ## Grid Compatibility
|
|
93
|
+
* - :white_check_mark: `PlateCarreeGrid`
|
|
94
|
+
* - :white_check_mark: `PlateCarreeRotatedGrid`
|
|
95
|
+
* - :white_check_mark: `LambertGrid`
|
|
96
|
+
* - :x: `UnstructuredGrid`
|
|
97
|
+
* - :white_check_mark: `RadarSweepGrid`
|
|
98
|
+
* - :white_check_mark: `Geostationary`
|
|
99
|
+
*
|
|
80
100
|
* @example
|
|
81
101
|
* // Create a field of filled contours with the provided color map
|
|
82
102
|
* const fill = new ContourFill(wind_speed_field, {cmap: color_map});
|
|
83
103
|
*/
|
|
84
|
-
declare class ContourFill<ArrayType extends TypedArray, GridType extends
|
|
104
|
+
declare class ContourFill<ArrayType extends TypedArray, GridType extends DomainBufferGrid, MapType extends MapLikeType> extends PlotComponentFill<ArrayType, GridType, MapType> {
|
|
85
105
|
/**
|
|
86
106
|
* Create a filled contoured field
|
|
87
107
|
* @param field - The field to create filled contours from
|
|
88
108
|
* @param opts - Options for creating the filled contours
|
|
89
109
|
*/
|
|
90
|
-
constructor(field:
|
|
110
|
+
constructor(field: ExpressionScalarField<ArrayType, GridType>, opts: ContourFillOptions);
|
|
91
111
|
/**
|
|
92
112
|
* Update the data displayed as filled contours
|
|
93
113
|
* @param field - The new field to display as filled contours
|
|
94
114
|
*/
|
|
95
|
-
updateField(field:
|
|
115
|
+
updateField(field: ExpressionScalarField<ArrayType, GridType>, mask?: Uint8Array): Promise<void>;
|
|
96
116
|
/**
|
|
97
117
|
* @internal
|
|
98
118
|
* Add the filled contours to a map
|
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,8 @@ 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
|
+
/** Options for {@link Hodographs} components */
|
|
7
8
|
interface HodographOptions {
|
|
8
9
|
/**
|
|
9
10
|
* The color of the hodograph plot background as a hex string
|
|
@@ -30,9 +31,24 @@ interface HodographOptions {
|
|
|
30
31
|
* The colormap to use for the heights on the hodograph. Default is a yellow-blue colormap.
|
|
31
32
|
*/
|
|
32
33
|
height_cmap?: ColorMap;
|
|
34
|
+
/**
|
|
35
|
+
* The wind speed (in kts) of the largest ring on the hodograph background
|
|
36
|
+
* @default 80
|
|
37
|
+
*/
|
|
38
|
+
max_wind_speed_ring?: number;
|
|
33
39
|
}
|
|
34
|
-
/**
|
|
35
|
-
|
|
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> {
|
|
36
52
|
private profile_field;
|
|
37
53
|
readonly opts: Required<HodographOptions>;
|
|
38
54
|
private gl_elems;
|
package/lib/Hodographs.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { PlotComponent } from "./PlotComponent";
|
|
2
2
|
import { PolylineCollection } from "./PolylineCollection";
|
|
3
3
|
import { BillboardCollection } from "./BillboardCollection";
|
|
4
|
-
import {
|
|
4
|
+
import { normalizeOptions } from './utils';
|
|
5
|
+
import { isStormRelativeWindProfile } from "./AutumnTypes";
|
|
5
6
|
import { ColorMap } from "./Colormap";
|
|
6
7
|
import { Color } from "./Color";
|
|
7
8
|
const LINE_WIDTH_MULTIPLIER = 2.5;
|
|
8
|
-
const BG_MAX_RING_MAG = 40;
|
|
9
9
|
const HODO_BG_DIMS = {
|
|
10
10
|
BB_WIDTH: 256,
|
|
11
11
|
BB_HEIGHT: 256,
|
|
@@ -15,7 +15,7 @@ const HODO_BG_DIMS = {
|
|
|
15
15
|
BB_MAG_WRAP: 1000,
|
|
16
16
|
BB_MAG_BIN_SIZE: 1000,
|
|
17
17
|
};
|
|
18
|
-
function _createHodoBackgroundTexture(line_width) {
|
|
18
|
+
function _createHodoBackgroundTexture(line_width, arrow_head) {
|
|
19
19
|
let canvas = document.createElement('canvas');
|
|
20
20
|
canvas.width = HODO_BG_DIMS.BB_TEX_WIDTH;
|
|
21
21
|
canvas.height = HODO_BG_DIMS.BB_TEX_HEIGHT;
|
|
@@ -29,14 +29,21 @@ function _createHodoBackgroundTexture(line_width) {
|
|
|
29
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);
|
|
30
30
|
ctx.stroke();
|
|
31
31
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
32
|
+
if (arrow_head) {
|
|
33
|
+
const ctr_x = HODO_BG_DIMS.BB_TEX_WIDTH / 2, ctr_y = HODO_BG_DIMS.BB_TEX_WIDTH / 2;
|
|
34
|
+
const arrow_size = 20;
|
|
35
|
+
ctx.beginPath();
|
|
36
|
+
ctx.moveTo(ctr_x, ctr_y);
|
|
37
|
+
ctx.lineTo(ctr_x + arrow_size / 2, ctr_y + arrow_size);
|
|
38
|
+
ctx.lineTo(ctr_x - arrow_size / 2, ctr_y + arrow_size);
|
|
39
|
+
ctx.lineTo(ctr_x, ctr_y);
|
|
40
|
+
ctx.fill();
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
ctx.beginPath();
|
|
44
|
+
ctx.arc(HODO_BG_DIMS.BB_TEX_WIDTH / 2, HODO_BG_DIMS.BB_TEX_WIDTH / 2, line_width, 0, 2 * Math.PI);
|
|
45
|
+
ctx.fill();
|
|
46
|
+
}
|
|
40
47
|
return canvas;
|
|
41
48
|
}
|
|
42
49
|
;
|
|
@@ -46,9 +53,20 @@ const hodograph_opt_defaults = {
|
|
|
46
53
|
thin_fac: 1,
|
|
47
54
|
hodo_line_width: 2.5,
|
|
48
55
|
background_line_width: 1.5,
|
|
49
|
-
height_cmap: HODO_CMAP
|
|
56
|
+
height_cmap: HODO_CMAP,
|
|
57
|
+
max_wind_speed_ring: 80
|
|
50
58
|
};
|
|
51
|
-
/**
|
|
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
|
+
*/
|
|
52
70
|
class Hodographs extends PlotComponent {
|
|
53
71
|
/**
|
|
54
72
|
* Create a field of hodographs
|
|
@@ -59,8 +77,8 @@ class Hodographs extends PlotComponent {
|
|
|
59
77
|
super();
|
|
60
78
|
this.profile_field = profile_field;
|
|
61
79
|
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 *
|
|
80
|
+
this.hodo_bg_texture = _createHodoBackgroundTexture(this.opts.background_line_width * LINE_WIDTH_MULTIPLIER, isStormRelativeWindProfile(profile_field.profiles[0]));
|
|
81
|
+
this.hodo_scale = (HODO_BG_DIMS.BB_TEX_WIDTH - this.opts.background_line_width / 2) / (HODO_BG_DIMS.BB_TEX_WIDTH * this.opts.max_wind_speed_ring);
|
|
64
82
|
this.bg_size = 140;
|
|
65
83
|
this.gl_elems = null;
|
|
66
84
|
this.line_elems = null;
|
|
@@ -78,19 +96,26 @@ class Hodographs extends PlotComponent {
|
|
|
78
96
|
this.gl_elems.bg_billboard.updateField(field.getStormMotionGrid());
|
|
79
97
|
const profiles = this.profile_field.profiles;
|
|
80
98
|
const { lats, lons } = this.profile_field.getProfileCoords();
|
|
99
|
+
const min_visible_zoom = this.profile_field.grid.getMinVisibleZoom(this.opts.thin_fac);
|
|
81
100
|
const hodo_polyline = profiles.map((prof, iprof) => {
|
|
82
|
-
const zoom = getMinZoom(prof['jlat'], prof['ilon'], this.opts.thin_fac);
|
|
83
101
|
return {
|
|
84
|
-
'offsets': [...prof['u']].map((u, ipt) =>
|
|
102
|
+
'offsets': [...prof['u']].map((u, ipt) => {
|
|
103
|
+
if (isStormRelativeWindProfile(prof)) {
|
|
104
|
+
return [u - prof['smu'], prof['v'][ipt] - prof['smv']];
|
|
105
|
+
}
|
|
106
|
+
return [u, prof['v'][ipt]];
|
|
107
|
+
}),
|
|
85
108
|
'vertices': [...prof['u']].map(u => [lons[iprof], lats[iprof]]),
|
|
86
|
-
'zoom':
|
|
109
|
+
'zoom': min_visible_zoom[iprof],
|
|
87
110
|
'data': [...prof['z']],
|
|
88
111
|
};
|
|
89
112
|
});
|
|
90
113
|
const hodo_line = await PolylineCollection.make(gl, hodo_polyline, { line_width: this.opts.hodo_line_width, cmap: this.opts.height_cmap,
|
|
91
114
|
offset_scale: this.hodo_scale * this.bg_size, offset_rotates_with_map: false });
|
|
92
115
|
const sm_polyline = profiles.map((prof, iprof) => {
|
|
93
|
-
|
|
116
|
+
if (!isStormRelativeWindProfile(prof)) {
|
|
117
|
+
return { vertices: [] };
|
|
118
|
+
}
|
|
94
119
|
const sm_mag = Math.hypot(prof['smu'], prof['smv']);
|
|
95
120
|
const sm_ang = Math.PI / 2 - Math.atan2(-prof['smv'], -prof['smu']);
|
|
96
121
|
const buffer = 2;
|
|
@@ -98,7 +123,7 @@ class Hodographs extends PlotComponent {
|
|
|
98
123
|
'offsets': [[buffer * Math.sin(sm_ang), buffer * Math.cos(sm_ang)],
|
|
99
124
|
[sm_mag * Math.sin(sm_ang), sm_mag * Math.cos(sm_ang)]],
|
|
100
125
|
'vertices': [[lons[iprof], lats[iprof]], [lons[iprof], lats[iprof]]],
|
|
101
|
-
'zoom':
|
|
126
|
+
'zoom': min_visible_zoom[iprof]
|
|
102
127
|
};
|
|
103
128
|
});
|
|
104
129
|
const sm_line = await PolylineCollection.make(gl, sm_polyline, { line_width: this.opts.background_line_width, color: this.opts.bgcolor,
|
package/lib/Map.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
type StyleSpecification = {
|
|
2
2
|
glyphs?: string;
|
|
3
3
|
};
|
|
4
|
+
/** Type with the required methods for mapping libraries */
|
|
4
5
|
type MapLikeType = {
|
|
5
6
|
triggerRepaint: () => void;
|
|
6
7
|
getCanvas: () => HTMLCanvasElement;
|
|
@@ -14,6 +15,8 @@ interface LambertConformalConicParameters {
|
|
|
14
15
|
lon_0: number;
|
|
15
16
|
lat_0: number;
|
|
16
17
|
lat_std: [number, number] | number;
|
|
18
|
+
a: number;
|
|
19
|
+
b: number;
|
|
17
20
|
}
|
|
18
21
|
declare function lambertConformalConic(params: LambertConformalConicParameters): (a: number, b: number, opts?: {
|
|
19
22
|
inverse: boolean;
|
|
@@ -26,6 +29,15 @@ interface RotateSphereParams {
|
|
|
26
29
|
declare function rotateSphere(params: RotateSphereParams): (a: number, b: number, opts?: {
|
|
27
30
|
inverse: boolean;
|
|
28
31
|
}) => [number, number];
|
|
32
|
+
interface GeostationaryProjectionParams {
|
|
33
|
+
lon_0: number;
|
|
34
|
+
alt: number;
|
|
35
|
+
a: number;
|
|
36
|
+
b: number;
|
|
37
|
+
}
|
|
38
|
+
declare function geostationaryProjection(params: GeostationaryProjectionParams): (a: number, b: number, opts?: {
|
|
39
|
+
inverse: boolean;
|
|
40
|
+
}) => [number, number];
|
|
29
41
|
/**
|
|
30
42
|
* A `LngLat` object represents a given longitude and latitude coordinate, measured in degrees.
|
|
31
43
|
* These coordinates are based on the [WGS84 (EPSG:4326) standard](https://en.wikipedia.org/wiki/World_Geodetic_System#WGS84).
|
|
@@ -49,5 +61,5 @@ declare class LngLat {
|
|
|
49
61
|
};
|
|
50
62
|
static fromMercatorCoord(x: number, y: number): LngLat;
|
|
51
63
|
}
|
|
52
|
-
export { LngLat, lambertConformalConic, rotateSphere };
|
|
64
|
+
export { LngLat, lambertConformalConic, rotateSphere, geostationaryProjection };
|
|
53
65
|
export type { MapLikeType };
|
package/lib/Map.js
CHANGED
|
@@ -9,8 +9,8 @@ function lambertConformalConic(params) {
|
|
|
9
9
|
return Math.cos(lat) / Math.sqrt(1 - eccen * eccen * sin_lat * sin_lat);
|
|
10
10
|
};
|
|
11
11
|
// WGS 84 spheroid
|
|
12
|
-
const semimajor =
|
|
13
|
-
const semiminor =
|
|
12
|
+
const semimajor = params.a;
|
|
13
|
+
const semiminor = params.b;
|
|
14
14
|
const eccen = Math.sqrt(1 - (semiminor * semiminor) / (semimajor * semimajor));
|
|
15
15
|
const radians = Math.PI / 180;
|
|
16
16
|
let { lon_0, lat_0, lat_std } = params;
|
|
@@ -121,6 +121,53 @@ function rotateSphere(params) {
|
|
|
121
121
|
return opts.inverse ? compute_rotation_inverse(a, b) : compute_rotation(a, b);
|
|
122
122
|
};
|
|
123
123
|
}
|
|
124
|
+
// Changes to the geostationaryProjection to make sure the coastlines and satellite line up
|
|
125
|
+
function geostationaryProjection(params) {
|
|
126
|
+
const radians = Math.PI / 180;
|
|
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) => {
|
|
132
|
+
lon *= radians;
|
|
133
|
+
lat *= radians;
|
|
134
|
+
const cos_lat = Math.cos(lat);
|
|
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));
|
|
144
|
+
return [x, y];
|
|
145
|
+
};
|
|
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)));
|
|
165
|
+
return [lon / radians, lat / radians];
|
|
166
|
+
};
|
|
167
|
+
return (a, b, opts) => {
|
|
168
|
+
return opts?.inverse ? inverse(a, b) : forward(a, b);
|
|
169
|
+
};
|
|
170
|
+
}
|
|
124
171
|
function mercatorXfromLng(lng) {
|
|
125
172
|
return (180 + lng) / 360;
|
|
126
173
|
}
|
|
@@ -151,19 +198,26 @@ function latFromMercatorY(y) {
|
|
|
151
198
|
class LngLat {
|
|
152
199
|
constructor(lng, lat) {
|
|
153
200
|
if (isNaN(lng) || isNaN(lat)) {
|
|
154
|
-
|
|
201
|
+
this.lng = NaN;
|
|
202
|
+
this.lat = NaN;
|
|
155
203
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
+
}
|
|
160
210
|
}
|
|
161
211
|
}
|
|
162
212
|
toMercatorCoord() {
|
|
213
|
+
if (isNaN(this.lng) || isNaN(this.lng))
|
|
214
|
+
return { x: NaN, y: NaN };
|
|
163
215
|
return { x: mercatorXfromLng(this.lng), y: mercatorYfromLat(this.lat) };
|
|
164
216
|
}
|
|
165
217
|
static fromMercatorCoord(x, y) {
|
|
218
|
+
if (isNaN(x) || isNaN(y))
|
|
219
|
+
return new LngLat(NaN, NaN);
|
|
166
220
|
return new LngLat(lngFromMercatorX(x), latFromMercatorY(y));
|
|
167
221
|
}
|
|
168
222
|
}
|
|
169
|
-
export { LngLat, lambertConformalConic, rotateSphere };
|
|
223
|
+
export { LngLat, lambertConformalConic, rotateSphere, geostationaryProjection };
|
package/lib/Paintball.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
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
|
+
/** Options for {@link Paintball} components */
|
|
6
7
|
interface PaintballOptions {
|
|
7
8
|
/**
|
|
8
9
|
* The list of colors (as hex strings) to use for each member in the paintball plot. The first color corresponds to member 1, the second to member 2, etc.
|
|
@@ -20,8 +21,16 @@ interface PaintballOptions {
|
|
|
20
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
|
|
21
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
|
|
22
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`
|
|
23
32
|
*/
|
|
24
|
-
declare class Paintball<ArrayType extends TypedArray, GridType extends
|
|
33
|
+
declare class Paintball<ArrayType extends TypedArray, GridType extends DomainBufferGrid, MapType extends MapLikeType> extends PlotComponent<MapType> {
|
|
25
34
|
private field;
|
|
26
35
|
readonly opts: Required<PaintballOptions>;
|
|
27
36
|
private readonly color_components;
|
|
@@ -34,12 +43,12 @@ declare class Paintball<ArrayType extends TypedArray, GridType extends Structure
|
|
|
34
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.
|
|
35
44
|
* @param opts - Options for creating the paintball plot
|
|
36
45
|
*/
|
|
37
|
-
constructor(field:
|
|
46
|
+
constructor(field: ExpressionScalarField<ArrayType, GridType>, opts?: PaintballOptions);
|
|
38
47
|
/**
|
|
39
48
|
* Update the field displayed as a paintball plot
|
|
40
49
|
* @param field - The new field to display as a paintball plot
|
|
41
50
|
*/
|
|
42
|
-
updateField(field:
|
|
51
|
+
updateField(field: ExpressionScalarField<ArrayType, GridType>): Promise<void>;
|
|
43
52
|
/**
|
|
44
53
|
* @internal
|
|
45
54
|
* Add the paintball plot to a map.
|