autumnplot-gl 2.2.0 → 2.2.2
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 +11 -5
- package/lib/Barbs.d.ts +1 -1
- package/lib/BillboardCollection.d.ts +1 -1
- package/lib/BillboardCollection.js +16 -6
- package/lib/Contour.d.ts +1 -1
- package/lib/Contour.js +16 -5
- package/lib/Fill.d.ts +3 -3
- package/lib/Fill.js +15 -5
- package/lib/Hodographs.d.ts +1 -1
- package/lib/Map.js +3 -1
- package/lib/Paintball.d.ts +1 -1
- package/lib/Paintball.js +16 -5
- package/lib/PlotComponent.d.ts +1 -1
- package/lib/PlotLayer.d.ts +3 -3
- package/lib/PolylineCollection.d.ts +1 -1
- package/lib/PolylineCollection.js +15 -5
- package/lib/RawField.d.ts +16 -5
- package/lib/RawField.js +65 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ npm i autumnplot-gl
|
|
|
19
19
|
Additionally, pre-built autumnplot-gl javascript files area available [here](https://tsupinie.github.io/autumnplot-gl/dist/). Adding them to your page exposes the API via the `apgl` global variable (e.g., instead of `new PlateCarreeGrid(...)` in the examples, you'd call `new apgl.PlateCarreeGrid(...)`).
|
|
20
20
|
|
|
21
21
|
### A basic contour plot
|
|
22
|
-
The first step in plotting data is to create a grid. Currently, the only supported grids are
|
|
22
|
+
The first step in plotting data is to create a grid. Currently, the only supported grids are PlateCarreeGrid (a.k.a. Lat/Lon), RotatedPlateCarreeGrid, and LambertGrid (a.k.a. Lambert Conformal Conic).
|
|
23
23
|
|
|
24
24
|
```javascript
|
|
25
25
|
// Create a grid object that covers the continental United States
|
|
@@ -27,7 +27,7 @@ const nx = 121, ny = 61;
|
|
|
27
27
|
const grid = new PlateCarreeGrid(nx, ny, -130, 20, -65, 55);
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
-
Next, create a RawScalarField with the data. autumnplot-gl doesn't care about how data get to the browser, but it should end up in a Float32Array in row-major order with the first element being at the southwest corner of the grid.
|
|
30
|
+
Next, create a RawScalarField with the data. autumnplot-gl doesn't care about how data get to the browser, but it should end up in a `Float32Array` or `Float16Array` in row-major order with the first element being at the southwest corner of the grid. If you're using [zarr.js](https://github.com/gzuidhof/zarr.js/), you can use the `getRaw()` function on a `ZarrArray` to get data in the correct format. Also, `Float16Array`s are not in the Javascript standard library (for now), so for the time being, you'll need to use [this library](https://github.com/petamoriken/float16). However, the nice part about using a `Float16Array` is that your data will be stored as float16s in VRAM, so they'll take up half the space as the same data as float32s. Once you have your data in that format, to create the raw data field:
|
|
31
31
|
|
|
32
32
|
```javascript
|
|
33
33
|
// Create the raw data field
|
|
@@ -81,7 +81,7 @@ map.on('load', () => {
|
|
|
81
81
|
|
|
82
82
|
The wind barbs are automatically rotated based on the grid projection. Also, the density of the wind barbs is automatically varied based on the map zoom level. The `'thin_fac': 16` option means to plot every 16th wind barb in the i and j directions, and this is defined at zoom level 1. So at zoom level 2, it will plot every 8th wind barb, and at zoom level 3 every 4th wind barb, and so on. Because it divides in 2 for every deeper zoom level, `'thin_fac'` should be a power of 2.
|
|
83
83
|
|
|
84
|
-
### Filled contours
|
|
84
|
+
### Filled contours or raster plots
|
|
85
85
|
|
|
86
86
|
Plotting filled contours is also similar to plotting regular contours, but there's some additional steps for the color map. A couple color maps are available by default (see [here](#built-in-color-maps) for more details), but if you have the colors you want, creating your own is (relatively) painless (hopefully). First, set up the colormap. Here, we'll just use the bluered colormap included by default.
|
|
87
87
|
|
|
@@ -96,7 +96,13 @@ map.on('load', () => {
|
|
|
96
96
|
});
|
|
97
97
|
```
|
|
98
98
|
|
|
99
|
-
|
|
99
|
+
Making a raster plot is very similar (the two classes support the same options):
|
|
100
|
+
|
|
101
|
+
```javascript
|
|
102
|
+
const raster = new Raster(height, {cmap: colormap});
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Normally, when you have a color fill, you have a color bar on the plot. To create an SVG color bar:
|
|
100
106
|
|
|
101
107
|
```javascript
|
|
102
108
|
const colorbar_svg = makeColorBar(colormap, {label: "Height Perturbation (m)",
|
|
@@ -154,7 +160,7 @@ The above exmple uses map tiles from [Maptiler](https://www.maptiler.com/). Map
|
|
|
154
160
|
So, I've created some [less-detailed map tiles](https://tsupinie.github.io/autumnplot-gl/tiles/) that are small enough that they can be hosted without dedicated hardware. However the tradeoff is that they're only useful down to zoom level 8 or 9 on the map, such that the viewport is somewhere between half a US state and a few counties in size. If that's good enough for you, then these tiles could be useful.
|
|
155
161
|
|
|
156
162
|
## Conspicuous absences
|
|
157
|
-
A few capabilities are missing from this library as of v2.
|
|
163
|
+
A few capabilities are missing from this library as of v2.2.
|
|
158
164
|
* Helper functions for reading from specific data formats. For instance, I'd like to add support for reading from a zarr file.
|
|
159
165
|
* A whole bunch of little things that ought to be fairly straightforward like tweaking the size of the wind barbs and contour thicknesses.
|
|
160
166
|
* Support for contour labeling. I'd like to add it, but I'm not really sure how I'd do it with the contours as I've implemented them. Any WebGL gurus, get in touch.
|
package/lib/Barbs.d.ts
CHANGED
|
@@ -44,7 +44,7 @@ declare class Barbs<ArrayType extends TypedArray> extends PlotComponent {
|
|
|
44
44
|
* @internal
|
|
45
45
|
* Render the barb field
|
|
46
46
|
*/
|
|
47
|
-
render(gl: WebGLAnyRenderingContext, matrix: number[]): void;
|
|
47
|
+
render(gl: WebGLAnyRenderingContext, matrix: number[] | Float32Array): void;
|
|
48
48
|
}
|
|
49
49
|
export default Barbs;
|
|
50
50
|
export type { BarbsOptions };
|
|
@@ -12,6 +12,6 @@ declare class BillboardCollection<ArrayType extends TypedArray> {
|
|
|
12
12
|
private readonly u_texture;
|
|
13
13
|
private readonly v_texture;
|
|
14
14
|
constructor(gl: WebGLAnyRenderingContext, field: RawVectorField<ArrayType>, thin_fac: number, max_zoom: number, billboard_image: WGLTextureSpec, billboard_spec: BillboardSpec, billboard_color: [number, number, number], billboard_size_mult: number);
|
|
15
|
-
render(gl: WebGLAnyRenderingContext, matrix: number[], [map_width, map_height]: [number, number], map_zoom: number, map_bearing: number, map_pitch: number): void;
|
|
15
|
+
render(gl: WebGLAnyRenderingContext, matrix: number[] | Float32Array, [map_width, map_height]: [number, number], map_zoom: number, map_bearing: number, map_pitch: number): void;
|
|
16
16
|
}
|
|
17
17
|
export { BillboardCollection };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { getGLFormatTypeAlignment } from "./PlotComponent";
|
|
2
2
|
import { WGLProgram, WGLTexture } from "autumn-wgl";
|
|
3
|
-
import { Cache } from "./utils";
|
|
4
3
|
const billboard_vertex_shader_src = `uniform mat4 u_matrix;
|
|
4
|
+
uniform int u_offset;
|
|
5
5
|
|
|
6
6
|
attribute vec3 a_pos;
|
|
7
7
|
attribute vec2 a_tex_coord;
|
|
@@ -48,7 +48,10 @@ mat4 rotationXMatrix(float angle) {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
void main() {
|
|
51
|
-
|
|
51
|
+
float globe_width = 1.;
|
|
52
|
+
vec2 globe_offset = vec2(globe_width * float(u_offset), 0.);
|
|
53
|
+
|
|
54
|
+
vec4 pivot_pos = u_matrix * vec4(a_pos.xy + globe_offset, 0.0, 1.0);
|
|
52
55
|
highp float zoom_corner = a_pos.z;
|
|
53
56
|
lowp float min_zoom = floor(zoom_corner / 4.0);
|
|
54
57
|
lowp float corner = mod(zoom_corner, 4.0);
|
|
@@ -57,7 +60,7 @@ void main() {
|
|
|
57
60
|
highp float v = texture2D(u_v_sampler, a_tex_coord).r;
|
|
58
61
|
|
|
59
62
|
lowp float bb_aspect = u_bb_width / u_bb_height;
|
|
60
|
-
lowp float ang = atan(v, u) - 3.141592654 / 2.0;
|
|
63
|
+
lowp float ang = (abs(u) < 1e-6 && abs(v) < 1e-6) ? 0. : atan(v, u) - 3.141592654 / 2.0;
|
|
61
64
|
highp float mag = length(vec2(u, v));
|
|
62
65
|
mag = floor(mag / u_bb_mag_bin_size + 0.5) * u_bb_mag_bin_size;
|
|
63
66
|
|
|
@@ -101,13 +104,12 @@ void main() {
|
|
|
101
104
|
lowp vec4 tex_color = texture2D(u_sampler, v_tex_coord);
|
|
102
105
|
gl_FragColor = vec4(u_bb_color, tex_color.a);
|
|
103
106
|
}`
|
|
104
|
-
const program_cache = new Cache((gl) => new WGLProgram(gl, billboard_vertex_shader_src, billboard_fragment_shader_src));
|
|
105
107
|
class BillboardCollection {
|
|
106
108
|
constructor(gl, field, thin_fac, max_zoom, billboard_image, billboard_spec, billboard_color, billboard_size_mult) {
|
|
107
109
|
this.spec = billboard_spec;
|
|
108
110
|
this.color = billboard_color;
|
|
109
111
|
this.size_multiplier = billboard_size_mult;
|
|
110
|
-
this.program =
|
|
112
|
+
this.program = new WGLProgram(gl, billboard_vertex_shader_src, billboard_fragment_shader_src);
|
|
111
113
|
this.vertices = null;
|
|
112
114
|
this.texcoords = null;
|
|
113
115
|
const n_density_tiers = Math.log2(thin_fac);
|
|
@@ -137,15 +139,23 @@ class BillboardCollection {
|
|
|
137
139
|
render(gl, matrix, [map_width, map_height], map_zoom, map_bearing, map_pitch) {
|
|
138
140
|
if (this.vertices === null || this.texcoords === null)
|
|
139
141
|
return;
|
|
142
|
+
if (matrix instanceof Float32Array)
|
|
143
|
+
matrix = [...matrix];
|
|
140
144
|
const bb_size = this.spec.BB_HEIGHT * (map_height / map_width) * this.size_multiplier;
|
|
141
145
|
const bb_width = this.spec.BB_WIDTH / this.spec.BB_TEX_WIDTH;
|
|
142
146
|
const bb_height = this.spec.BB_HEIGHT / this.spec.BB_TEX_HEIGHT;
|
|
143
147
|
this.program.use({ 'a_pos': this.vertices, 'a_tex_coord': this.texcoords }, { 'u_bb_size': bb_size, 'u_bb_width': bb_width, 'u_bb_height': bb_height,
|
|
144
|
-
'u_bb_mag_bin_size': this.spec.BB_MAG_BIN_SIZE, 'u_bb_mag_wrap': this.spec.BB_MAG_WRAP,
|
|
148
|
+
'u_bb_mag_bin_size': this.spec.BB_MAG_BIN_SIZE, 'u_bb_mag_wrap': this.spec.BB_MAG_WRAP, 'u_offset': 0,
|
|
145
149
|
'u_bb_color': this.color, 'u_matrix': matrix, 'u_map_aspect': map_height / map_width, 'u_zoom': map_zoom, 'u_map_bearing': map_bearing }, { 'u_sampler': this.texture, 'u_u_sampler': this.u_texture, 'u_v_sampler': this.v_texture });
|
|
146
150
|
gl.enable(gl.BLEND);
|
|
147
151
|
gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
|
148
152
|
this.program.draw();
|
|
153
|
+
this.program.setUniforms({ 'u_offset': -2 });
|
|
154
|
+
this.program.draw();
|
|
155
|
+
this.program.setUniforms({ 'u_offset': -1 });
|
|
156
|
+
this.program.draw();
|
|
157
|
+
this.program.setUniforms({ 'u_offset': 1 });
|
|
158
|
+
this.program.draw();
|
|
149
159
|
}
|
|
150
160
|
}
|
|
151
161
|
export { BillboardCollection };
|
package/lib/Contour.d.ts
CHANGED
|
@@ -55,7 +55,7 @@ declare class Contour<ArrayType extends TypedArray> extends PlotComponent {
|
|
|
55
55
|
* @internal
|
|
56
56
|
* Render the contours
|
|
57
57
|
*/
|
|
58
|
-
render(gl: WebGLAnyRenderingContext, matrix: number[]): void;
|
|
58
|
+
render(gl: WebGLAnyRenderingContext, matrix: number[] | Float32Array): void;
|
|
59
59
|
}
|
|
60
60
|
export default Contour;
|
|
61
61
|
export type { ContourOptions };
|
package/lib/Contour.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { PlotComponent, getGLFormatTypeAlignment } from './PlotComponent';
|
|
2
|
-
import {
|
|
2
|
+
import { hex2rgba } from './utils';
|
|
3
3
|
import { WGLProgram, WGLTexture } from 'autumn-wgl';
|
|
4
4
|
const contour_vertex_shader_src = `uniform mat4 u_matrix;
|
|
5
|
+
uniform int u_offset;
|
|
5
6
|
|
|
6
7
|
attribute vec2 a_pos;
|
|
7
8
|
attribute float a_grid_cell_size;
|
|
@@ -12,7 +13,10 @@ varying highp float v_grid_cell_size;
|
|
|
12
13
|
varying highp float v_map_scale_fac;
|
|
13
14
|
|
|
14
15
|
void main() {
|
|
15
|
-
|
|
16
|
+
float globe_width = 1.;
|
|
17
|
+
vec2 globe_offset = vec2(globe_width * float(u_offset), 0.);
|
|
18
|
+
|
|
19
|
+
gl_Position = u_matrix * vec4(a_pos + globe_offset, 0.0, 1.0);
|
|
16
20
|
v_tex_coord = a_tex_coord;
|
|
17
21
|
v_grid_cell_size = a_grid_cell_size;
|
|
18
22
|
|
|
@@ -100,7 +104,6 @@ void main() {
|
|
|
100
104
|
|
|
101
105
|
gl_FragColor = vec4(u_color, 1. - (plot_val * plot_val / (u_line_cutoff * u_line_cutoff)));
|
|
102
106
|
}`
|
|
103
|
-
const program_cache = new Cache((gl) => new WGLProgram(gl, contour_vertex_shader_src, contour_fragment_shader_src));
|
|
104
107
|
/**
|
|
105
108
|
* A field of contoured data. The contours can optionally be thinned based on map zoom level.
|
|
106
109
|
* @example
|
|
@@ -132,7 +135,7 @@ class Contour extends PlotComponent {
|
|
|
132
135
|
async onAdd(map, gl) {
|
|
133
136
|
// Basic procedure for these contours from https://www.shadertoy.com/view/lltBWM
|
|
134
137
|
gl.getExtension("OES_standard_derivatives");
|
|
135
|
-
const program =
|
|
138
|
+
const program = new WGLProgram(gl, contour_vertex_shader_src, contour_fragment_shader_src);
|
|
136
139
|
const { vertices: verts_buf, texcoords: tex_coords_buf, cellsize: cellsize_buf } = await this.field.grid.getWGLBuffers(gl);
|
|
137
140
|
const vertices = verts_buf;
|
|
138
141
|
const texcoords = tex_coords_buf;
|
|
@@ -155,13 +158,15 @@ class Contour extends PlotComponent {
|
|
|
155
158
|
if (this.gl_elems === null)
|
|
156
159
|
return;
|
|
157
160
|
const gl_elems = this.gl_elems;
|
|
161
|
+
if (matrix instanceof Float32Array)
|
|
162
|
+
matrix = [...matrix];
|
|
158
163
|
const zoom = gl_elems.map.getZoom();
|
|
159
164
|
const intv = this.thinner(zoom) * this.interval;
|
|
160
165
|
const cutoff = 0.3 / intv;
|
|
161
166
|
const step_size = [1 / this.field.grid.ni, 1 / this.field.grid.nj];
|
|
162
167
|
const zoom_fac = Math.pow(2, zoom);
|
|
163
168
|
let uniforms = { 'u_contour_interval': intv, 'u_line_cutoff': cutoff, 'u_color': this.color, 'u_step_size': step_size, 'u_zoom_fac': zoom_fac,
|
|
164
|
-
'u_matrix': matrix, 'u_num_contours': 0, 'u_contour_levels': [0] };
|
|
169
|
+
'u_matrix': matrix, 'u_num_contours': 0, 'u_contour_levels': [0], 'u_offset': 0 };
|
|
165
170
|
if (this.levels.length > 0) {
|
|
166
171
|
uniforms = { ...uniforms, 'u_num_contours': this.levels.length, 'u_contour_levels': this.levels };
|
|
167
172
|
}
|
|
@@ -169,6 +174,12 @@ class Contour extends PlotComponent {
|
|
|
169
174
|
gl.enable(gl.BLEND);
|
|
170
175
|
gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
|
171
176
|
gl_elems.program.draw();
|
|
177
|
+
gl_elems.program.setUniforms({ 'u_offset': -2 });
|
|
178
|
+
gl_elems.program.draw();
|
|
179
|
+
gl_elems.program.setUniforms({ 'u_offset': -1 });
|
|
180
|
+
gl_elems.program.draw();
|
|
181
|
+
gl_elems.program.setUniforms({ 'u_offset': 1 });
|
|
182
|
+
gl_elems.program.draw();
|
|
172
183
|
}
|
|
173
184
|
}
|
|
174
185
|
export default Contour;
|
package/lib/Fill.d.ts
CHANGED
|
@@ -32,7 +32,7 @@ declare class PlotComponentFill<ArrayType extends TypedArray> extends PlotCompon
|
|
|
32
32
|
protected cmap_mag_filter: number | null;
|
|
33
33
|
constructor(field: RawScalarField<ArrayType>, opts: ContourFillOptions);
|
|
34
34
|
onAdd(map: MapType, gl: WebGLAnyRenderingContext): Promise<void>;
|
|
35
|
-
render(gl: WebGLAnyRenderingContext, matrix: number[]): void;
|
|
35
|
+
render(gl: WebGLAnyRenderingContext, matrix: number[] | Float32Array): void;
|
|
36
36
|
}
|
|
37
37
|
/**
|
|
38
38
|
* A raster (i.e. pixel) plot
|
|
@@ -56,7 +56,7 @@ declare class Raster<ArrayType extends TypedArray> extends PlotComponentFill<Arr
|
|
|
56
56
|
* @internal
|
|
57
57
|
* Render the raster plot
|
|
58
58
|
*/
|
|
59
|
-
render(gl: WebGLAnyRenderingContext, matrix: number[]): void;
|
|
59
|
+
render(gl: WebGLAnyRenderingContext, matrix: number[] | Float32Array): void;
|
|
60
60
|
}
|
|
61
61
|
/**
|
|
62
62
|
* A filled contoured field
|
|
@@ -80,7 +80,7 @@ declare class ContourFill<ArrayType extends TypedArray> extends PlotComponentFil
|
|
|
80
80
|
* @internal
|
|
81
81
|
* Render the filled contours
|
|
82
82
|
*/
|
|
83
|
-
render(gl: WebGLAnyRenderingContext, matrix: number[]): void;
|
|
83
|
+
render(gl: WebGLAnyRenderingContext, matrix: number[] | Float32Array): void;
|
|
84
84
|
}
|
|
85
85
|
export { ContourFill, Raster };
|
|
86
86
|
export type { ContourFillOptions, RasterOptions };
|
package/lib/Fill.js
CHANGED
|
@@ -2,8 +2,8 @@ import { PlotComponent, getGLFormatTypeAlignment } from './PlotComponent';
|
|
|
2
2
|
import { makeTextureImage } from './Colormap';
|
|
3
3
|
import { WGLProgram, WGLTexture } from 'autumn-wgl';
|
|
4
4
|
import { Float16Array } from '@petamoriken/float16';
|
|
5
|
-
import { Cache } from './utils';
|
|
6
5
|
const contourfill_vertex_shader_src = `uniform mat4 u_matrix;
|
|
6
|
+
uniform int u_offset;
|
|
7
7
|
|
|
8
8
|
attribute vec2 a_pos;
|
|
9
9
|
attribute vec2 a_tex_coord;
|
|
@@ -11,7 +11,10 @@ attribute vec2 a_tex_coord;
|
|
|
11
11
|
varying highp vec2 v_tex_coord;
|
|
12
12
|
|
|
13
13
|
void main() {
|
|
14
|
-
|
|
14
|
+
float globe_width = 1.;
|
|
15
|
+
vec2 globe_offset = vec2(globe_width * float(u_offset), 0.);
|
|
16
|
+
|
|
17
|
+
gl_Position = u_matrix * vec4(a_pos + globe_offset, 0.0, 1.0);
|
|
15
18
|
v_tex_coord = a_tex_coord;
|
|
16
19
|
}`
|
|
17
20
|
const contourfill_fragment_shader_src = `varying highp vec2 v_tex_coord;
|
|
@@ -39,7 +42,6 @@ void main() {
|
|
|
39
42
|
color.a = color.a * u_opacity;
|
|
40
43
|
gl_FragColor = color;
|
|
41
44
|
}`
|
|
42
|
-
const program_cache = new Cache((gl) => new WGLProgram(gl, contourfill_vertex_shader_src, contourfill_fragment_shader_src));
|
|
43
45
|
class PlotComponentFill extends PlotComponent {
|
|
44
46
|
constructor(field, opts) {
|
|
45
47
|
super();
|
|
@@ -74,7 +76,7 @@ class PlotComponentFill extends PlotComponent {
|
|
|
74
76
|
if (this.image_mag_filter === null || this.cmap_mag_filter === null) {
|
|
75
77
|
throw `Implement magnification filtes in a subclass`;
|
|
76
78
|
}
|
|
77
|
-
const program =
|
|
79
|
+
const program = new WGLProgram(gl, contourfill_vertex_shader_src, contourfill_fragment_shader_src);
|
|
78
80
|
const { vertices: verts_buf, texcoords: tex_coords_buf } = await this.field.grid.getWGLBuffers(gl);
|
|
79
81
|
const vertices = verts_buf;
|
|
80
82
|
const texcoords = tex_coords_buf;
|
|
@@ -102,11 +104,19 @@ class PlotComponentFill extends PlotComponent {
|
|
|
102
104
|
if (this.gl_elems === null)
|
|
103
105
|
return;
|
|
104
106
|
const gl_elems = this.gl_elems;
|
|
107
|
+
if (matrix instanceof Float32Array)
|
|
108
|
+
matrix = [...matrix];
|
|
105
109
|
gl_elems.program.use({ 'a_pos': gl_elems.vertices, 'a_tex_coord': gl_elems.texcoords }, { 'u_cmap_min': this.cmap.levels[0], 'u_cmap_max': this.cmap.levels[this.cmap.levels.length - 1], 'u_matrix': matrix, 'u_opacity': this.opacity,
|
|
106
|
-
'u_n_index': this.index_map.length }, { 'u_fill_sampler': gl_elems.fill_texture, 'u_cmap_sampler': gl_elems.cmap_texture, 'u_cmap_nonlin_sampler': gl_elems.cmap_nonlin_texture });
|
|
110
|
+
'u_n_index': this.index_map.length, 'u_offset': 0 }, { 'u_fill_sampler': gl_elems.fill_texture, 'u_cmap_sampler': gl_elems.cmap_texture, 'u_cmap_nonlin_sampler': gl_elems.cmap_nonlin_texture });
|
|
107
111
|
gl.enable(gl.BLEND);
|
|
108
112
|
gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
|
109
113
|
gl_elems.program.draw();
|
|
114
|
+
gl_elems.program.setUniforms({ 'u_offset': -2 });
|
|
115
|
+
gl_elems.program.draw();
|
|
116
|
+
gl_elems.program.setUniforms({ 'u_offset': -1 });
|
|
117
|
+
gl_elems.program.draw();
|
|
118
|
+
gl_elems.program.setUniforms({ 'u_offset': 1 });
|
|
119
|
+
gl_elems.program.draw();
|
|
110
120
|
}
|
|
111
121
|
}
|
|
112
122
|
/**
|
package/lib/Hodographs.d.ts
CHANGED
|
@@ -35,7 +35,7 @@ declare class Hodographs extends PlotComponent {
|
|
|
35
35
|
* @internal
|
|
36
36
|
* Render the hodographs
|
|
37
37
|
*/
|
|
38
|
-
render(gl: WebGLAnyRenderingContext, matrix: number[]): void;
|
|
38
|
+
render(gl: WebGLAnyRenderingContext, matrix: number[] | Float32Array): void;
|
|
39
39
|
}
|
|
40
40
|
export default Hodographs;
|
|
41
41
|
export type { HodographOptions };
|
package/lib/Map.js
CHANGED
|
@@ -125,7 +125,9 @@ function mercatorXfromLng(lng) {
|
|
|
125
125
|
return (180 + lng) / 360;
|
|
126
126
|
}
|
|
127
127
|
function mercatorYfromLat(lat) {
|
|
128
|
-
|
|
128
|
+
const sin_lat = Math.sin(lat * Math.PI / 180);
|
|
129
|
+
const y = (180 - (90 / Math.PI * Math.log((1 + sin_lat) / (1 - sin_lat)))) / 360;
|
|
130
|
+
return Math.min(2, Math.max(-2, y));
|
|
129
131
|
}
|
|
130
132
|
/**
|
|
131
133
|
* A `LngLat` object represents a given longitude and latitude coordinate, measured in degrees.
|
package/lib/Paintball.d.ts
CHANGED
|
@@ -42,7 +42,7 @@ declare class Paintball<ArrayType extends TypedArray> extends PlotComponent {
|
|
|
42
42
|
* @internal
|
|
43
43
|
* Render the paintball plot
|
|
44
44
|
*/
|
|
45
|
-
render(gl: WebGLAnyRenderingContext, matrix: number[]): void;
|
|
45
|
+
render(gl: WebGLAnyRenderingContext, matrix: number[] | Float32Array): void;
|
|
46
46
|
}
|
|
47
47
|
export default Paintball;
|
|
48
48
|
export type { PaintballOptions };
|
package/lib/Paintball.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { PlotComponent, getGLFormatTypeAlignment } from "./PlotComponent";
|
|
2
|
-
import {
|
|
2
|
+
import { hex2rgba } from "./utils";
|
|
3
3
|
import { WGLProgram, WGLTexture } from "autumn-wgl";
|
|
4
4
|
const paintball_vertex_shader_src = `uniform mat4 u_matrix;
|
|
5
|
+
uniform int u_offset;
|
|
5
6
|
|
|
6
7
|
attribute vec2 a_pos;
|
|
7
8
|
attribute vec2 a_tex_coord;
|
|
@@ -9,7 +10,10 @@ attribute vec2 a_tex_coord;
|
|
|
9
10
|
varying highp vec2 v_tex_coord;
|
|
10
11
|
|
|
11
12
|
void main() {
|
|
12
|
-
|
|
13
|
+
float globe_width = 1.;
|
|
14
|
+
vec2 globe_offset = vec2(globe_width * float(u_offset), 0.);
|
|
15
|
+
|
|
16
|
+
gl_Position = u_matrix * vec4(a_pos + globe_offset, 0.0, 1.0);
|
|
13
17
|
v_tex_coord = a_tex_coord;
|
|
14
18
|
}`
|
|
15
19
|
const paintball_fragment_shader_src = `#define MAX_N_COLORS 24
|
|
@@ -41,7 +45,6 @@ void main() {
|
|
|
41
45
|
color.a = color.a * u_opacity;
|
|
42
46
|
gl_FragColor = color;
|
|
43
47
|
}`
|
|
44
|
-
const program_cache = new Cache((gl) => new WGLProgram(gl, paintball_vertex_shader_src, paintball_fragment_shader_src));
|
|
45
48
|
/**
|
|
46
49
|
* 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
50
|
* a field (such as simulated reflectivity greater than 40 dBZ), but could in theory be defined by any arbitrarily complicated method. In autumnplot-gl,
|
|
@@ -72,7 +75,7 @@ class Paintball extends PlotComponent {
|
|
|
72
75
|
*/
|
|
73
76
|
async onAdd(map, gl) {
|
|
74
77
|
gl.getExtension('OES_texture_float');
|
|
75
|
-
const program =
|
|
78
|
+
const program = new WGLProgram(gl, paintball_vertex_shader_src, paintball_fragment_shader_src);
|
|
76
79
|
const { vertices: verts_buf, texcoords: tex_coords_buf } = await this.field.grid.getWGLBuffers(gl);
|
|
77
80
|
const vertices = verts_buf;
|
|
78
81
|
const texcoords = tex_coords_buf;
|
|
@@ -95,11 +98,19 @@ class Paintball extends PlotComponent {
|
|
|
95
98
|
if (this.gl_elems === null)
|
|
96
99
|
return;
|
|
97
100
|
const gl_elems = this.gl_elems;
|
|
101
|
+
if (matrix instanceof Float32Array)
|
|
102
|
+
matrix = [...matrix];
|
|
98
103
|
// Render to framebuffer
|
|
99
|
-
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 });
|
|
104
|
+
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_offset': 0 }, { 'u_fill_sampler': gl_elems.fill_texture });
|
|
100
105
|
gl.enable(gl.BLEND);
|
|
101
106
|
gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
|
102
107
|
gl_elems.program.draw();
|
|
108
|
+
gl_elems.program.setUniforms({ 'u_offset': -2 });
|
|
109
|
+
gl_elems.program.draw();
|
|
110
|
+
gl_elems.program.setUniforms({ 'u_offset': -1 });
|
|
111
|
+
gl_elems.program.draw();
|
|
112
|
+
gl_elems.program.setUniforms({ 'u_offset': 1 });
|
|
113
|
+
gl_elems.program.draw();
|
|
103
114
|
}
|
|
104
115
|
}
|
|
105
116
|
export default Paintball;
|
package/lib/PlotComponent.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ declare const layer_worker: Comlink.Remote<{
|
|
|
15
15
|
}>;
|
|
16
16
|
declare abstract class PlotComponent {
|
|
17
17
|
abstract onAdd(map: MapType, gl: WebGLAnyRenderingContext): Promise<void>;
|
|
18
|
-
abstract render(gl: WebGLAnyRenderingContext, matrix: number[]): void;
|
|
18
|
+
abstract render(gl: WebGLAnyRenderingContext, matrix: number[] | Float32Array): void;
|
|
19
19
|
}
|
|
20
20
|
declare function getGLFormatTypeAlignment(gl: WebGLAnyRenderingContext, is_float16: boolean): {
|
|
21
21
|
format: number;
|
package/lib/PlotLayer.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ declare abstract class PlotLayerBase {
|
|
|
6
6
|
readonly id: string;
|
|
7
7
|
constructor(id: string);
|
|
8
8
|
abstract onAdd(map: MapType, gl: WebGLAnyRenderingContext): void;
|
|
9
|
-
abstract render(gl: WebGLAnyRenderingContext, matrix: number[]): void;
|
|
9
|
+
abstract render(gl: WebGLAnyRenderingContext, matrix: number[] | Float32Array): void;
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
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}
|
|
@@ -34,7 +34,7 @@ declare class PlotLayer extends PlotLayerBase {
|
|
|
34
34
|
* @internal
|
|
35
35
|
* Render this layer
|
|
36
36
|
*/
|
|
37
|
-
render(gl: WebGLAnyRenderingContext, matrix: number[]): void;
|
|
37
|
+
render(gl: WebGLAnyRenderingContext, matrix: number[] | Float32Array): void;
|
|
38
38
|
}
|
|
39
39
|
/**
|
|
40
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.
|
|
@@ -69,7 +69,7 @@ declare class MultiPlotLayer extends PlotLayerBase {
|
|
|
69
69
|
* @internal
|
|
70
70
|
* Render this layer
|
|
71
71
|
*/
|
|
72
|
-
render(gl: WebGLAnyRenderingContext, matrix: number[]): void;
|
|
72
|
+
render(gl: WebGLAnyRenderingContext, matrix: number[] | Float32Array): void;
|
|
73
73
|
/**
|
|
74
74
|
* Set the active key
|
|
75
75
|
* @param key - The new key
|
|
@@ -11,7 +11,7 @@ declare class PolylineCollection {
|
|
|
11
11
|
private readonly texture;
|
|
12
12
|
private readonly texcoords;
|
|
13
13
|
constructor(gl: WebGLAnyRenderingContext, polyline: PolylineSpec, tex_image: WGLTextureSpec, line_width: number, offset_scale: number);
|
|
14
|
-
render(gl: WebGLAnyRenderingContext, matrix: number[], [map_width, map_height]: [number, number], map_zoom: number, map_bearing: number, map_pitch: number): void;
|
|
14
|
+
render(gl: WebGLAnyRenderingContext, matrix: number[] | Float32Array, [map_width, map_height]: [number, number], map_zoom: number, map_bearing: number, map_pitch: number): void;
|
|
15
15
|
}
|
|
16
16
|
export { PolylineCollection };
|
|
17
17
|
export type { PolylineSpec, LineSpec };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { WGLBuffer, WGLProgram, WGLTexture } from "autumn-wgl";
|
|
2
|
-
import { Cache } from "./utils";
|
|
3
2
|
const polyline_vertex_src = `uniform mat4 u_matrix;
|
|
3
|
+
uniform int u_offset;
|
|
4
4
|
|
|
5
5
|
attribute vec2 a_pos;
|
|
6
6
|
attribute float a_min_zoom;
|
|
@@ -44,7 +44,10 @@ mat4 rotationXMatrix(float angle) {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
void main() {
|
|
47
|
-
|
|
47
|
+
float globe_width = 1.;
|
|
48
|
+
vec2 globe_offset = vec2(globe_width * float(u_offset), 0.);
|
|
49
|
+
|
|
50
|
+
vec4 center_pos = u_matrix * vec4(a_pos.xy + globe_offset, 0.0, 1.0);
|
|
48
51
|
vec4 offset = vec4(0.0, 0.0, 0.0, 0.0);
|
|
49
52
|
|
|
50
53
|
if (u_zoom >= a_min_zoom) {
|
|
@@ -70,12 +73,11 @@ void main() {
|
|
|
70
73
|
lowp vec4 tex_color = texture2D(u_sampler, v_tex_coord);
|
|
71
74
|
gl_FragColor = tex_color;
|
|
72
75
|
}`
|
|
73
|
-
const program_cache = new Cache((gl) => new WGLProgram(gl, polyline_vertex_src, polyline_fragment_src));
|
|
74
76
|
class PolylineCollection {
|
|
75
77
|
constructor(gl, polyline, tex_image, line_width, offset_scale) {
|
|
76
78
|
this.width = line_width;
|
|
77
79
|
this.scale = offset_scale;
|
|
78
|
-
this.program =
|
|
80
|
+
this.program = new WGLProgram(gl, polyline_vertex_src, polyline_fragment_src);
|
|
79
81
|
this.origin = new WGLBuffer(gl, polyline['origin'], 2, gl.TRIANGLE_STRIP);
|
|
80
82
|
this.offset = new WGLBuffer(gl, polyline['verts'], 2, gl.TRIANGLE_STRIP);
|
|
81
83
|
this.extrusion = new WGLBuffer(gl, polyline['extrusion'], 2, gl.TRIANGLE_STRIP);
|
|
@@ -84,11 +86,19 @@ class PolylineCollection {
|
|
|
84
86
|
this.texcoords = new WGLBuffer(gl, polyline['texcoords'], 2, gl.TRIANGLE_STRIP);
|
|
85
87
|
}
|
|
86
88
|
render(gl, matrix, [map_width, map_height], map_zoom, map_bearing, map_pitch) {
|
|
89
|
+
if (matrix instanceof Float32Array)
|
|
90
|
+
matrix = [...matrix];
|
|
87
91
|
this.program.use({ 'a_pos': this.origin, 'a_offset': this.offset, 'a_extrusion': this.extrusion, 'a_min_zoom': this.min_zoom, 'a_tex_coord': this.texcoords }, { 'u_offset_scale': this.scale * (map_height / map_width), 'u_line_width': this.width, 'u_matrix': matrix,
|
|
88
|
-
'u_map_aspect': map_height / map_width, 'u_zoom': map_zoom, 'u_map_bearing': map_bearing }, { 'u_sampler': this.texture });
|
|
92
|
+
'u_map_aspect': map_height / map_width, 'u_zoom': map_zoom, 'u_map_bearing': map_bearing, 'u_offset': 0 }, { 'u_sampler': this.texture });
|
|
89
93
|
gl.enable(gl.BLEND);
|
|
90
94
|
gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
|
91
95
|
this.program.draw();
|
|
96
|
+
this.program.setUniforms({ 'u_offset': -2 });
|
|
97
|
+
this.program.draw();
|
|
98
|
+
this.program.setUniforms({ 'u_offset': -1 });
|
|
99
|
+
this.program.draw();
|
|
100
|
+
this.program.setUniforms({ 'u_offset': 1 });
|
|
101
|
+
this.program.draw();
|
|
92
102
|
}
|
|
93
103
|
}
|
|
94
104
|
export { PolylineCollection };
|
package/lib/RawField.d.ts
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { Float16Array } from "@petamoriken/float16";
|
|
2
2
|
import { TypedArray, WebGLAnyRenderingContext, WindProfile } from "./AutumnTypes";
|
|
3
3
|
import { WGLBuffer } from "autumn-wgl";
|
|
4
|
-
interface
|
|
4
|
+
interface EarthCoords {
|
|
5
5
|
lons: Float32Array;
|
|
6
6
|
lats: Float32Array;
|
|
7
7
|
}
|
|
8
|
+
interface GridCoords {
|
|
9
|
+
x: Float32Array;
|
|
10
|
+
y: Float32Array;
|
|
11
|
+
}
|
|
8
12
|
type GridType = 'latlon' | 'latlonrot' | 'lcc';
|
|
9
13
|
declare abstract class Grid {
|
|
10
14
|
readonly type: GridType;
|
|
@@ -18,7 +22,8 @@ declare abstract class Grid {
|
|
|
18
22
|
ni?: number;
|
|
19
23
|
nj?: number;
|
|
20
24
|
}): Grid;
|
|
21
|
-
abstract
|
|
25
|
+
abstract getEarthCoords(): EarthCoords;
|
|
26
|
+
abstract getGridCoords(): GridCoords;
|
|
22
27
|
abstract transform(x: number, y: number, opts?: {
|
|
23
28
|
inverse?: boolean;
|
|
24
29
|
}): [number, number];
|
|
@@ -40,6 +45,7 @@ declare class PlateCarreeGrid extends Grid {
|
|
|
40
45
|
readonly ur_lon: number;
|
|
41
46
|
readonly ur_lat: number;
|
|
42
47
|
private readonly ll_cache;
|
|
48
|
+
private readonly gc_cache;
|
|
43
49
|
/**
|
|
44
50
|
* Create a plate carree grid
|
|
45
51
|
* @param ni - The number of grid points in the i (longitude) direction
|
|
@@ -61,7 +67,8 @@ declare class PlateCarreeGrid extends Grid {
|
|
|
61
67
|
/**
|
|
62
68
|
* Get a list of longitudes and latitudes on the grid (internal method)
|
|
63
69
|
*/
|
|
64
|
-
|
|
70
|
+
getEarthCoords(): EarthCoords;
|
|
71
|
+
getGridCoords(): GridCoords;
|
|
65
72
|
transform(x: number, y: number, opts?: {
|
|
66
73
|
inverse?: boolean;
|
|
67
74
|
}): [number, number];
|
|
@@ -78,6 +85,7 @@ declare class PlateCarreeRotatedGrid extends Grid {
|
|
|
78
85
|
readonly ur_lat: number;
|
|
79
86
|
private readonly llrot;
|
|
80
87
|
private readonly ll_cache;
|
|
88
|
+
private readonly gc_cache;
|
|
81
89
|
/**
|
|
82
90
|
* Create a Lambert conformal conic grid
|
|
83
91
|
* @param ni - The number of grid points in the i (longitude) direction
|
|
@@ -102,7 +110,8 @@ declare class PlateCarreeRotatedGrid extends Grid {
|
|
|
102
110
|
/**
|
|
103
111
|
* Get a list of longitudes and latitudes on the grid (internal method)
|
|
104
112
|
*/
|
|
105
|
-
|
|
113
|
+
getEarthCoords(): EarthCoords;
|
|
114
|
+
getGridCoords(): GridCoords;
|
|
106
115
|
transform(x: number, y: number, opts?: {
|
|
107
116
|
inverse?: boolean;
|
|
108
117
|
}): [number, number];
|
|
@@ -119,6 +128,7 @@ declare class LambertGrid extends Grid {
|
|
|
119
128
|
readonly ur_y: number;
|
|
120
129
|
private readonly lcc;
|
|
121
130
|
private readonly ll_cache;
|
|
131
|
+
private readonly gc_cache;
|
|
122
132
|
/**
|
|
123
133
|
* Create a Lambert conformal conic grid
|
|
124
134
|
* @param ni - The number of grid points in the i (longitude) direction
|
|
@@ -143,7 +153,8 @@ declare class LambertGrid extends Grid {
|
|
|
143
153
|
/**
|
|
144
154
|
* Get a list of longitudes and latitudes on the grid (internal method)
|
|
145
155
|
*/
|
|
146
|
-
|
|
156
|
+
getEarthCoords(): EarthCoords;
|
|
157
|
+
getGridCoords(): GridCoords;
|
|
147
158
|
transform(x: number, y: number, opts?: {
|
|
148
159
|
inverse?: boolean;
|
|
149
160
|
}): [number, number];
|
package/lib/RawField.js
CHANGED
|
@@ -8,7 +8,7 @@ async function makeWGLDomainBuffers(gl, grid, native_grid) {
|
|
|
8
8
|
const texcoord_margin_r = 1 / (2 * native_grid.ni);
|
|
9
9
|
const texcoord_margin_s = 1 / (2 * native_grid.nj);
|
|
10
10
|
const grid_cell_size_multiplier = (grid.ni * grid.nj) / (native_grid.ni * native_grid.nj);
|
|
11
|
-
const { lats: field_lats, lons: field_lons } = grid.
|
|
11
|
+
const { lats: field_lats, lons: field_lons } = grid.getEarthCoords();
|
|
12
12
|
const domain_coords = await layer_worker.makeDomainVerticesAndTexCoords(field_lats, field_lons, grid.ni, grid.nj, texcoord_margin_r, texcoord_margin_s);
|
|
13
13
|
for (let icd = 0; icd < domain_coords['grid_cell_size'].length; icd++) {
|
|
14
14
|
domain_coords['grid_cell_size'][icd] *= grid_cell_size_multiplier;
|
|
@@ -19,7 +19,7 @@ async function makeWGLDomainBuffers(gl, grid, native_grid) {
|
|
|
19
19
|
return { 'vertices': vertices, 'texcoords': texcoords, 'cellsize': grid_cell_size };
|
|
20
20
|
}
|
|
21
21
|
async function makeWGLBillboardBuffers(gl, grid, thin_fac, max_zoom) {
|
|
22
|
-
const { lats: field_lats, lons: field_lons } = grid.
|
|
22
|
+
const { lats: field_lats, lons: field_lons } = grid.getEarthCoords();
|
|
23
23
|
const bb_elements = await layer_worker.makeBBElements(field_lats, field_lons, grid.ni, grid.nj, thin_fac, max_zoom);
|
|
24
24
|
const vertices = new WGLBuffer(gl, bb_elements['pts'], 3, gl.TRIANGLE_STRIP);
|
|
25
25
|
const texcoords = new WGLBuffer(gl, bb_elements['tex_coords'], 2, gl.TRIANGLE_STRIP);
|
|
@@ -64,9 +64,9 @@ class PlateCarreeGrid extends Grid {
|
|
|
64
64
|
this.ll_lat = ll_lat;
|
|
65
65
|
this.ur_lon = ur_lon;
|
|
66
66
|
this.ur_lat = ur_lat;
|
|
67
|
+
const dlon = (this.ur_lon - this.ll_lon) / (this.ni - 1);
|
|
68
|
+
const dlat = (this.ur_lat - this.ll_lat) / (this.nj - 1);
|
|
67
69
|
this.ll_cache = new Cache(() => {
|
|
68
|
-
const dlon = (this.ur_lon - this.ll_lon) / (this.ni - 1);
|
|
69
|
-
const dlat = (this.ur_lat - this.ll_lat) / (this.nj - 1);
|
|
70
70
|
const lons = new Float32Array(this.ni * this.nj);
|
|
71
71
|
const lats = new Float32Array(this.ni * this.nj);
|
|
72
72
|
for (let i = 0; i < this.ni; i++) {
|
|
@@ -78,6 +78,17 @@ class PlateCarreeGrid extends Grid {
|
|
|
78
78
|
}
|
|
79
79
|
return { 'lons': lons, 'lats': lats };
|
|
80
80
|
});
|
|
81
|
+
this.gc_cache = new Cache(() => {
|
|
82
|
+
const x = new Float32Array(this.ni);
|
|
83
|
+
const y = new Float32Array(this.nj);
|
|
84
|
+
for (let i = 0; i < this.ni; i++) {
|
|
85
|
+
x[i] = this.ll_lon + i * dlon;
|
|
86
|
+
}
|
|
87
|
+
for (let j = 0; j < this.nj; j++) {
|
|
88
|
+
y[j] = this.ll_lat + j * dlat;
|
|
89
|
+
}
|
|
90
|
+
return { x: x, y: y };
|
|
91
|
+
});
|
|
81
92
|
}
|
|
82
93
|
copy(opts) {
|
|
83
94
|
opts = opts !== undefined ? opts : {};
|
|
@@ -92,9 +103,12 @@ class PlateCarreeGrid extends Grid {
|
|
|
92
103
|
/**
|
|
93
104
|
* Get a list of longitudes and latitudes on the grid (internal method)
|
|
94
105
|
*/
|
|
95
|
-
|
|
106
|
+
getEarthCoords() {
|
|
96
107
|
return this.ll_cache.getValue();
|
|
97
108
|
}
|
|
109
|
+
getGridCoords() {
|
|
110
|
+
return this.gc_cache.getValue();
|
|
111
|
+
}
|
|
98
112
|
transform(x, y, opts) {
|
|
99
113
|
return [x, y];
|
|
100
114
|
}
|
|
@@ -136,13 +150,15 @@ class PlateCarreeRotatedGrid extends Grid {
|
|
|
136
150
|
this.ur_lon = ur_lon;
|
|
137
151
|
this.ur_lat = ur_lat;
|
|
138
152
|
this.llrot = rotateSphere({ np_lon: np_lon, np_lat: np_lat, lon_shift: lon_shift });
|
|
153
|
+
const dlon = (this.ur_lon - this.ll_lon) / (this.ni - 1);
|
|
154
|
+
const dlat = (this.ur_lat - this.ll_lat) / (this.nj - 1);
|
|
139
155
|
this.ll_cache = new Cache(() => {
|
|
140
156
|
const lons = new Float32Array(this.ni * this.nj);
|
|
141
157
|
const lats = new Float32Array(this.ni * this.nj);
|
|
142
158
|
for (let i = 0; i < this.ni; i++) {
|
|
143
|
-
const lon_p = this.ll_lon +
|
|
159
|
+
const lon_p = this.ll_lon + i * dlon;
|
|
144
160
|
for (let j = 0; j < this.nj; j++) {
|
|
145
|
-
const lat_p = this.ll_lat +
|
|
161
|
+
const lat_p = this.ll_lat + j * dlat;
|
|
146
162
|
const [lon, lat] = this.llrot(lon_p, lat_p);
|
|
147
163
|
const idx = i + j * this.ni;
|
|
148
164
|
lons[idx] = lon;
|
|
@@ -151,6 +167,17 @@ class PlateCarreeRotatedGrid extends Grid {
|
|
|
151
167
|
}
|
|
152
168
|
return { lons: lons, lats: lats };
|
|
153
169
|
});
|
|
170
|
+
this.gc_cache = new Cache(() => {
|
|
171
|
+
const x = new Float32Array(this.ni);
|
|
172
|
+
const y = new Float32Array(this.nj);
|
|
173
|
+
for (let i = 0; i < this.ni; i++) {
|
|
174
|
+
x[i] = this.ll_lon + i * dlon;
|
|
175
|
+
}
|
|
176
|
+
for (let j = 0; j < this.nj; j++) {
|
|
177
|
+
y[j] = this.ll_lat + j * dlat;
|
|
178
|
+
}
|
|
179
|
+
return { x: x, y: y };
|
|
180
|
+
});
|
|
154
181
|
}
|
|
155
182
|
copy(opts) {
|
|
156
183
|
opts = opts !== undefined ? opts : {};
|
|
@@ -165,9 +192,12 @@ class PlateCarreeRotatedGrid extends Grid {
|
|
|
165
192
|
/**
|
|
166
193
|
* Get a list of longitudes and latitudes on the grid (internal method)
|
|
167
194
|
*/
|
|
168
|
-
|
|
195
|
+
getEarthCoords() {
|
|
169
196
|
return this.ll_cache.getValue();
|
|
170
197
|
}
|
|
198
|
+
getGridCoords() {
|
|
199
|
+
return this.gc_cache.getValue();
|
|
200
|
+
}
|
|
171
201
|
transform(x, y, opts) {
|
|
172
202
|
opts = opts === undefined ? {} : opts;
|
|
173
203
|
const inverse = 'inverse' in opts ? opts.inverse : false;
|
|
@@ -211,13 +241,15 @@ class LambertGrid extends Grid {
|
|
|
211
241
|
this.ur_x = ur_x;
|
|
212
242
|
this.ur_y = ur_y;
|
|
213
243
|
this.lcc = lambertConformalConic({ lon_0: lon_0, lat_0: lat_0, lat_std: lat_std });
|
|
244
|
+
const dx = (this.ur_x - this.ll_x) / (this.ni - 1);
|
|
245
|
+
const dy = (this.ur_y - this.ll_y) / (this.nj - 1);
|
|
214
246
|
this.ll_cache = new Cache(() => {
|
|
215
247
|
const lons = new Float32Array(this.ni * this.nj);
|
|
216
248
|
const lats = new Float32Array(this.ni * this.nj);
|
|
217
249
|
for (let i = 0; i < this.ni; i++) {
|
|
218
|
-
const x = this.ll_x +
|
|
250
|
+
const x = this.ll_x + i * dx;
|
|
219
251
|
for (let j = 0; j < this.nj; j++) {
|
|
220
|
-
const y = this.ll_y +
|
|
252
|
+
const y = this.ll_y + j * dy;
|
|
221
253
|
const [lon, lat] = this.lcc(x, y, { inverse: true });
|
|
222
254
|
const idx = i + j * this.ni;
|
|
223
255
|
lons[idx] = lon;
|
|
@@ -226,6 +258,17 @@ class LambertGrid extends Grid {
|
|
|
226
258
|
}
|
|
227
259
|
return { lons: lons, lats: lats };
|
|
228
260
|
});
|
|
261
|
+
this.gc_cache = new Cache(() => {
|
|
262
|
+
const x = new Float32Array(this.ni);
|
|
263
|
+
const y = new Float32Array(this.nj);
|
|
264
|
+
for (let i = 0; i < this.ni; i++) {
|
|
265
|
+
x[i] = this.ll_x + i * dx;
|
|
266
|
+
}
|
|
267
|
+
for (let j = 0; j < this.nj; j++) {
|
|
268
|
+
y[j] = this.ll_y + j * dy;
|
|
269
|
+
}
|
|
270
|
+
return { x: x, y: y };
|
|
271
|
+
});
|
|
229
272
|
}
|
|
230
273
|
copy(opts) {
|
|
231
274
|
opts = opts !== undefined ? opts : {};
|
|
@@ -240,9 +283,12 @@ class LambertGrid extends Grid {
|
|
|
240
283
|
/**
|
|
241
284
|
* Get a list of longitudes and latitudes on the grid (internal method)
|
|
242
285
|
*/
|
|
243
|
-
|
|
286
|
+
getEarthCoords() {
|
|
244
287
|
return this.ll_cache.getValue();
|
|
245
288
|
}
|
|
289
|
+
getGridCoords() {
|
|
290
|
+
return this.gc_cache.getValue();
|
|
291
|
+
}
|
|
246
292
|
transform(x, y, opts) {
|
|
247
293
|
opts = opts === undefined ? {} : opts;
|
|
248
294
|
const inverse = 'inverse' in opts ? opts.inverse : false;
|
|
@@ -345,7 +391,7 @@ class RawVectorField {
|
|
|
345
391
|
this.relative_to = opts.relative_to === undefined ? 'grid' : opts.relative_to;
|
|
346
392
|
this.rotate_cache = new Cache(() => {
|
|
347
393
|
const grid = this.u.grid;
|
|
348
|
-
const coords = grid.
|
|
394
|
+
const coords = grid.getEarthCoords();
|
|
349
395
|
const u_rot = new arrayType(coords.lats.length);
|
|
350
396
|
const v_rot = new arrayType(coords.lats.length);
|
|
351
397
|
for (let icd = 0; icd < coords.lats.length; icd++) {
|
|
@@ -353,6 +399,11 @@ class RawVectorField {
|
|
|
353
399
|
const lat = coords.lats[icd];
|
|
354
400
|
const u = this.u.data[icd];
|
|
355
401
|
const v = this.v.data[icd];
|
|
402
|
+
if (Math.abs(u) < 1e-6 && Math.abs(v) < 1e-6) {
|
|
403
|
+
u_rot[icd] = 0;
|
|
404
|
+
v_rot[icd] = 0;
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
356
407
|
const [x, y] = grid.transform(lon, lat);
|
|
357
408
|
const [x_pertlon, y_pertlon] = grid.transform(lon + 0.01, lat);
|
|
358
409
|
const mag_pertlon = Math.hypot(x - x_pertlon, y - y_pertlon);
|
|
@@ -412,8 +463,8 @@ class RawProfileField {
|
|
|
412
463
|
}
|
|
413
464
|
/** Get the gridded storm motion vector field (internal method) */
|
|
414
465
|
getStormMotionGrid() {
|
|
415
|
-
const u = new Float16Array(this.grid.ni * this.grid.nj);
|
|
416
|
-
const v = new Float16Array(this.grid.ni * this.grid.nj);
|
|
466
|
+
const u = new Float16Array(this.grid.ni * this.grid.nj).fill(parseFloat('nan'));
|
|
467
|
+
const v = new Float16Array(this.grid.ni * this.grid.nj).fill(parseFloat('nan'));
|
|
417
468
|
this.profiles.forEach(prof => {
|
|
418
469
|
const idx = prof.ilon + this.grid.ni * prof.jlat;
|
|
419
470
|
u[idx] = prof.smu;
|