autumnplot-gl 2.1.0 → 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -5
- package/lib/AutumnTypes.d.ts +3 -1
- package/lib/Barbs.d.ts +6 -12
- package/lib/BillboardCollection.d.ts +11 -11
- package/lib/BillboardCollection.js +23 -11
- package/lib/Contour.d.ts +6 -16
- package/lib/Contour.js +38 -25
- package/lib/Fill.d.ts +16 -28
- package/lib/Fill.js +35 -24
- package/lib/Hodographs.d.ts +3 -13
- package/lib/Map.d.ts +9 -1
- package/lib/Map.js +42 -2
- package/lib/Paintball.d.ts +6 -14
- package/lib/Paintball.js +24 -12
- package/lib/PlotComponent.d.ts +7 -2
- package/lib/PlotComponent.js +34 -1
- package/lib/PlotLayer.d.ts +9 -14
- package/lib/PlotLayer.js +5 -6
- package/lib/PolylineCollection.d.ts +9 -9
- package/lib/PolylineCollection.js +14 -2
- package/lib/RawField.d.ts +83 -48
- package/lib/RawField.js +176 -40
- package/lib/index.d.ts +3 -3
- package/lib/index.js +2 -2
- package/lib/utils.d.ts +8 -1
- package/lib/utils.js +33 -1
- package/package.json +3 -2
package/lib/Fill.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { PlotComponent } from './PlotComponent';
|
|
1
|
+
import { PlotComponent, getGLFormatTypeAlignment } from './PlotComponent';
|
|
2
2
|
import { makeTextureImage } from './Colormap';
|
|
3
3
|
import { WGLProgram, WGLTexture } from 'autumn-wgl';
|
|
4
|
-
import {
|
|
4
|
+
import { Float16Array } from '@petamoriken/float16';
|
|
5
5
|
const contourfill_vertex_shader_src = `uniform mat4 u_matrix;
|
|
6
|
+
uniform int u_offset;
|
|
6
7
|
|
|
7
8
|
attribute vec2 a_pos;
|
|
8
9
|
attribute vec2 a_tex_coord;
|
|
@@ -10,7 +11,10 @@ attribute vec2 a_tex_coord;
|
|
|
10
11
|
varying highp vec2 v_tex_coord;
|
|
11
12
|
|
|
12
13
|
void main() {
|
|
13
|
-
|
|
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);
|
|
14
18
|
v_tex_coord = a_tex_coord;
|
|
15
19
|
}`
|
|
16
20
|
const contourfill_fragment_shader_src = `varying highp vec2 v_tex_coord;
|
|
@@ -62,15 +66,13 @@ class PlotComponentFill extends PlotComponent {
|
|
|
62
66
|
const alpha = (lev - cmap_norm[jlev]) / (cmap_norm[jlev + 1] - cmap_norm[jlev]);
|
|
63
67
|
return input_norm[jlev] * (1 - alpha) + input_norm[jlev + 1] * alpha;
|
|
64
68
|
});
|
|
65
|
-
this.index_map = new
|
|
69
|
+
this.index_map = new Float16Array(inv_cmap_norm);
|
|
66
70
|
this.gl_elems = null;
|
|
67
71
|
this.image_mag_filter = null;
|
|
68
72
|
this.cmap_mag_filter = null;
|
|
69
73
|
}
|
|
70
74
|
async onAdd(map, gl) {
|
|
71
75
|
// Basic procedure for the filled contours inspired by https://blog.mbq.me/webgl-weather-globe/
|
|
72
|
-
gl.getExtension('OES_texture_float');
|
|
73
|
-
gl.getExtension('OES_texture_float_linear');
|
|
74
76
|
if (this.image_mag_filter === null || this.cmap_mag_filter === null) {
|
|
75
77
|
throw `Implement magnification filtes in a subclass`;
|
|
76
78
|
}
|
|
@@ -78,18 +80,19 @@ class PlotComponentFill extends PlotComponent {
|
|
|
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;
|
|
81
|
-
const format =
|
|
82
|
-
const fill_image = { 'format': format, 'type':
|
|
83
|
-
'width': this.field.grid.ni, 'height': this.field.grid.nj, 'image': this.field.
|
|
84
|
-
'mag_filter': this.image_mag_filter,
|
|
83
|
+
const { format, type, row_alignment } = getGLFormatTypeAlignment(gl, this.field.isFloat16());
|
|
84
|
+
const fill_image = { 'format': format, 'type': type,
|
|
85
|
+
'width': this.field.grid.ni, 'height': this.field.grid.nj, 'image': this.field.getTextureData(),
|
|
86
|
+
'mag_filter': this.image_mag_filter, 'row_alignment': row_alignment,
|
|
85
87
|
};
|
|
86
88
|
const fill_texture = new WGLTexture(gl, fill_image);
|
|
87
89
|
const cmap_image = { 'format': gl.RGBA, 'type': gl.UNSIGNED_BYTE, 'image': this.cmap_image, 'mag_filter': this.cmap_mag_filter };
|
|
88
90
|
const cmap_texture = new WGLTexture(gl, cmap_image);
|
|
89
|
-
const
|
|
91
|
+
const { format: format_nonlin, type: type_nonlin, row_alignment: row_alignment_nonlin } = getGLFormatTypeAlignment(gl, true);
|
|
92
|
+
const cmap_nonlin_image = { 'format': format_nonlin, 'type': type_nonlin,
|
|
90
93
|
'width': this.index_map.length, 'height': 1,
|
|
91
|
-
'image': this.index_map,
|
|
92
|
-
'mag_filter': gl.LINEAR
|
|
94
|
+
'image': new Uint16Array(this.index_map.buffer),
|
|
95
|
+
'mag_filter': gl.LINEAR, 'row_alignment': row_alignment_nonlin,
|
|
93
96
|
};
|
|
94
97
|
const cmap_nonlin_texture = new WGLTexture(gl, cmap_nonlin_image);
|
|
95
98
|
this.gl_elems = {
|
|
@@ -101,30 +104,38 @@ class PlotComponentFill extends PlotComponent {
|
|
|
101
104
|
if (this.gl_elems === null)
|
|
102
105
|
return;
|
|
103
106
|
const gl_elems = this.gl_elems;
|
|
107
|
+
if (matrix instanceof Float32Array)
|
|
108
|
+
matrix = [...matrix];
|
|
104
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,
|
|
105
|
-
'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 });
|
|
106
111
|
gl.enable(gl.BLEND);
|
|
107
112
|
gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
|
108
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();
|
|
109
120
|
}
|
|
110
121
|
}
|
|
111
122
|
/**
|
|
112
123
|
* A raster (i.e. pixel) plot
|
|
113
|
-
*
|
|
124
|
+
* @example
|
|
114
125
|
* // Create a raster plot with the provided color map
|
|
115
126
|
* const raster = new Raster(wind_speed_field, {cmap: color_map});
|
|
116
127
|
*/
|
|
117
128
|
class Raster extends PlotComponentFill {
|
|
118
129
|
/**
|
|
119
130
|
* Create a raster plot
|
|
120
|
-
*
|
|
121
|
-
*
|
|
131
|
+
* @param field - The field to create the raster plot from
|
|
132
|
+
* @param opts - Options for creating the raster plot
|
|
122
133
|
*/
|
|
123
134
|
constructor(field, opts) {
|
|
124
135
|
super(field, opts);
|
|
125
136
|
}
|
|
126
137
|
/**
|
|
127
|
-
*
|
|
138
|
+
* @internal
|
|
128
139
|
* Add the raster plot to a map
|
|
129
140
|
*/
|
|
130
141
|
async onAdd(map, gl) {
|
|
@@ -133,7 +144,7 @@ class Raster extends PlotComponentFill {
|
|
|
133
144
|
super.onAdd(map, gl);
|
|
134
145
|
}
|
|
135
146
|
/**
|
|
136
|
-
*
|
|
147
|
+
* @internal
|
|
137
148
|
* Render the raster plot
|
|
138
149
|
*/
|
|
139
150
|
render(gl, matrix) {
|
|
@@ -142,21 +153,21 @@ class Raster extends PlotComponentFill {
|
|
|
142
153
|
}
|
|
143
154
|
/**
|
|
144
155
|
* A filled contoured field
|
|
145
|
-
*
|
|
156
|
+
* @example
|
|
146
157
|
* // Create a field of filled contours with the provided color map
|
|
147
158
|
* const fill = new ContourFill(wind_speed_field, {cmap: color_map});
|
|
148
159
|
*/
|
|
149
160
|
class ContourFill extends PlotComponentFill {
|
|
150
161
|
/**
|
|
151
162
|
* Create a filled contoured field
|
|
152
|
-
*
|
|
153
|
-
*
|
|
163
|
+
* @param field - The field to create filled contours from
|
|
164
|
+
* @param opts - Options for creating the filled contours
|
|
154
165
|
*/
|
|
155
166
|
constructor(field, opts) {
|
|
156
167
|
super(field, opts);
|
|
157
168
|
}
|
|
158
169
|
/**
|
|
159
|
-
*
|
|
170
|
+
* @internal
|
|
160
171
|
* Add the filled contours to a map
|
|
161
172
|
*/
|
|
162
173
|
async onAdd(map, gl) {
|
|
@@ -165,7 +176,7 @@ class ContourFill extends PlotComponentFill {
|
|
|
165
176
|
super.onAdd(map, gl);
|
|
166
177
|
}
|
|
167
178
|
/**
|
|
168
|
-
*
|
|
179
|
+
* @internal
|
|
169
180
|
* Render the filled contours
|
|
170
181
|
*/
|
|
171
182
|
render(gl, matrix) {
|
package/lib/Hodographs.d.ts
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
/// <reference types="mapbox-gl" />
|
|
2
2
|
import { PlotComponent } from "./PlotComponent";
|
|
3
|
-
import { PolylineCollection } from "./PolylineCollection";
|
|
4
|
-
import { BillboardCollection } from "./BillboardCollection";
|
|
5
|
-
import { MapType } from "./Map";
|
|
6
3
|
import { RawProfileField } from "./RawField";
|
|
7
4
|
import { WebGLAnyRenderingContext } from "./AutumnTypes";
|
|
8
5
|
interface HodographOptions {
|
|
@@ -17,19 +14,12 @@ interface HodographOptions {
|
|
|
17
14
|
*/
|
|
18
15
|
thin_fac?: number;
|
|
19
16
|
}
|
|
20
|
-
interface HodographGLElems {
|
|
21
|
-
map: MapType;
|
|
22
|
-
bg_billboard: BillboardCollection | null;
|
|
23
|
-
hodo_line: PolylineCollection | null;
|
|
24
|
-
sm_line: PolylineCollection | null;
|
|
25
|
-
}
|
|
26
17
|
/** A class representing a a field of hodograph plots */
|
|
27
18
|
declare class Hodographs extends PlotComponent {
|
|
28
|
-
readonly profile_field
|
|
19
|
+
private readonly profile_field;
|
|
29
20
|
readonly bgcolor: [number, number, number];
|
|
30
21
|
readonly thin_fac: number;
|
|
31
|
-
|
|
32
|
-
gl_elems: HodographGLElems;
|
|
22
|
+
private gl_elems;
|
|
33
23
|
/**
|
|
34
24
|
* Create a field of hodographs
|
|
35
25
|
* @param profile_field - The grid of profiles to plot
|
|
@@ -45,7 +35,7 @@ declare class Hodographs extends PlotComponent {
|
|
|
45
35
|
* @internal
|
|
46
36
|
* Render the hodographs
|
|
47
37
|
*/
|
|
48
|
-
render(gl: WebGLAnyRenderingContext, matrix: number[]): void;
|
|
38
|
+
render(gl: WebGLAnyRenderingContext, matrix: number[] | Float32Array): void;
|
|
49
39
|
}
|
|
50
40
|
export default Hodographs;
|
|
51
41
|
export type { HodographOptions };
|
package/lib/Map.d.ts
CHANGED
|
@@ -8,6 +8,14 @@ interface LambertConformalConicParameters {
|
|
|
8
8
|
declare function lambertConformalConic(params: LambertConformalConicParameters): (a: number, b: number, opts?: {
|
|
9
9
|
inverse: boolean;
|
|
10
10
|
}) => [number, number];
|
|
11
|
+
interface RotateSphereParams {
|
|
12
|
+
np_lon: number;
|
|
13
|
+
np_lat: number;
|
|
14
|
+
lon_shift: number;
|
|
15
|
+
}
|
|
16
|
+
declare function rotateSphere(params: RotateSphereParams): (a: number, b: number, opts?: {
|
|
17
|
+
inverse: boolean;
|
|
18
|
+
}) => [number, number];
|
|
11
19
|
/**
|
|
12
20
|
* A `LngLat` object represents a given longitude and latitude coordinate, measured in degrees.
|
|
13
21
|
* These coordinates are based on the [WGS84 (EPSG:4326) standard](https://en.wikipedia.org/wiki/World_Geodetic_System#WGS84).
|
|
@@ -30,5 +38,5 @@ declare class LngLat {
|
|
|
30
38
|
y: number;
|
|
31
39
|
};
|
|
32
40
|
}
|
|
33
|
-
export { LngLat, lambertConformalConic };
|
|
41
|
+
export { LngLat, lambertConformalConic, rotateSphere };
|
|
34
42
|
export type { MapType };
|
package/lib/Map.js
CHANGED
|
@@ -83,11 +83,51 @@ function lambertConformalConic(params) {
|
|
|
83
83
|
return opts.inverse ? compute_lcc_inverse(a, b) : compute_lcc(a, b);
|
|
84
84
|
};
|
|
85
85
|
}
|
|
86
|
+
function rotateSphere(params) {
|
|
87
|
+
const radians = Math.PI / 180;
|
|
88
|
+
const np_lat = params.np_lat * radians;
|
|
89
|
+
const np_lon = params.np_lon * radians;
|
|
90
|
+
const lon_shift = params.lon_shift * radians;
|
|
91
|
+
const sin_np_lat = Math.sin(np_lat);
|
|
92
|
+
const cos_np_lat = Math.cos(np_lat);
|
|
93
|
+
const compute_rotation = (lon, lat) => {
|
|
94
|
+
lon *= radians;
|
|
95
|
+
lat *= radians;
|
|
96
|
+
const sin_lat = Math.sin(lat);
|
|
97
|
+
const cos_lat = Math.cos(lat);
|
|
98
|
+
const sin_lon_diff = Math.sin(lon - lon_shift);
|
|
99
|
+
const cos_lon_diff = Math.cos(lon - lon_shift);
|
|
100
|
+
const lat_p = Math.asin(sin_np_lat * sin_lat - cos_np_lat * cos_lat * cos_lon_diff);
|
|
101
|
+
let lon_p = np_lon + Math.atan2((cos_lat * sin_lon_diff), (sin_np_lat * cos_lat * cos_lon_diff + cos_np_lat * sin_lat));
|
|
102
|
+
if (lon_p > Math.PI)
|
|
103
|
+
lon_p -= 2 * Math.PI;
|
|
104
|
+
return [lon_p / radians, lat_p / radians];
|
|
105
|
+
};
|
|
106
|
+
const compute_rotation_inverse = (lon_p, lat_p) => {
|
|
107
|
+
lon_p *= radians;
|
|
108
|
+
lat_p *= radians;
|
|
109
|
+
const sin_lat_p = Math.sin(lat_p);
|
|
110
|
+
const cos_lat_p = Math.cos(lat_p);
|
|
111
|
+
const sin_lon_p_diff = Math.sin(lon_p - np_lon);
|
|
112
|
+
const cos_lon_p_diff = Math.cos(lon_p - np_lon);
|
|
113
|
+
const lat = Math.asin(sin_np_lat * sin_lat_p + cos_np_lat * cos_lat_p * cos_lon_p_diff);
|
|
114
|
+
let lon = lon_shift + Math.atan2((cos_lat_p * sin_lon_p_diff), (sin_np_lat * cos_lat_p * cos_lon_p_diff - cos_np_lat * sin_lat_p));
|
|
115
|
+
if (lon_p > Math.PI)
|
|
116
|
+
lon_p -= 2 * Math.PI;
|
|
117
|
+
return [lon / radians, lat / radians];
|
|
118
|
+
};
|
|
119
|
+
return (a, b, opts) => {
|
|
120
|
+
opts = opts === undefined ? { inverse: false } : opts;
|
|
121
|
+
return opts.inverse ? compute_rotation_inverse(a, b) : compute_rotation(a, b);
|
|
122
|
+
};
|
|
123
|
+
}
|
|
86
124
|
function mercatorXfromLng(lng) {
|
|
87
125
|
return (180 + lng) / 360;
|
|
88
126
|
}
|
|
89
127
|
function mercatorYfromLat(lat) {
|
|
90
|
-
|
|
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));
|
|
91
131
|
}
|
|
92
132
|
/**
|
|
93
133
|
* A `LngLat` object represents a given longitude and latitude coordinate, measured in degrees.
|
|
@@ -117,4 +157,4 @@ class LngLat {
|
|
|
117
157
|
return { x: mercatorXfromLng(this.lng), y: mercatorYfromLat(this.lat) };
|
|
118
158
|
}
|
|
119
159
|
}
|
|
120
|
-
export { LngLat, lambertConformalConic };
|
|
160
|
+
export { LngLat, lambertConformalConic, rotateSphere };
|
package/lib/Paintball.d.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { WebGLAnyRenderingContext } from "./AutumnTypes";
|
|
1
|
+
import { TypedArray, WebGLAnyRenderingContext } from "./AutumnTypes";
|
|
2
2
|
import { MapType } from "./Map";
|
|
3
3
|
import { PlotComponent } from "./PlotComponent";
|
|
4
4
|
import { RawScalarField } from "./RawField";
|
|
5
|
-
import { WGLBuffer, WGLProgram, WGLTexture } from "autumn-wgl";
|
|
6
5
|
interface PaintballOptions {
|
|
7
6
|
/**
|
|
8
7
|
* 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.
|
|
@@ -14,12 +13,6 @@ interface PaintballOptions {
|
|
|
14
13
|
*/
|
|
15
14
|
opacity?: number;
|
|
16
15
|
}
|
|
17
|
-
interface PaintballGLElems {
|
|
18
|
-
program: WGLProgram;
|
|
19
|
-
vertices: WGLBuffer;
|
|
20
|
-
fill_texture: WGLTexture;
|
|
21
|
-
texcoords: WGLBuffer;
|
|
22
|
-
}
|
|
23
16
|
/**
|
|
24
17
|
* 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
|
|
25
18
|
* a field (such as simulated reflectivity greater than 40 dBZ), but could in theory be defined by any arbitrarily complicated method. In autumnplot-gl,
|
|
@@ -27,12 +20,11 @@ interface PaintballGLElems {
|
|
|
27
20
|
* 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
|
|
28
21
|
* significand of an IEEE 754 float.)
|
|
29
22
|
*/
|
|
30
|
-
declare class Paintball extends PlotComponent {
|
|
31
|
-
readonly field
|
|
23
|
+
declare class Paintball<ArrayType extends TypedArray> extends PlotComponent {
|
|
24
|
+
private readonly field;
|
|
32
25
|
readonly colors: number[];
|
|
33
26
|
readonly opacity: number;
|
|
34
|
-
|
|
35
|
-
gl_elems: PaintballGLElems | null;
|
|
27
|
+
private gl_elems;
|
|
36
28
|
/**
|
|
37
29
|
* Create a paintball plot
|
|
38
30
|
* @param field - A scalar field containing the member objects encoded as "bits." The numerical value of each grid point can be constructed like
|
|
@@ -40,7 +32,7 @@ declare class Paintball extends PlotComponent {
|
|
|
40
32
|
* `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.
|
|
41
33
|
* @param opts - Options for creating the paintball plot
|
|
42
34
|
*/
|
|
43
|
-
constructor(field: RawScalarField
|
|
35
|
+
constructor(field: RawScalarField<ArrayType>, opts?: PaintballOptions);
|
|
44
36
|
/**
|
|
45
37
|
* @internal
|
|
46
38
|
* Add the paintball plot to a map.
|
|
@@ -50,7 +42,7 @@ declare class Paintball extends PlotComponent {
|
|
|
50
42
|
* @internal
|
|
51
43
|
* Render the paintball plot
|
|
52
44
|
*/
|
|
53
|
-
render(gl: WebGLAnyRenderingContext, matrix: number[]): void;
|
|
45
|
+
render(gl: WebGLAnyRenderingContext, matrix: number[] | Float32Array): void;
|
|
54
46
|
}
|
|
55
47
|
export default Paintball;
|
|
56
48
|
export type { PaintballOptions };
|
package/lib/Paintball.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { PlotComponent } from "./PlotComponent";
|
|
1
|
+
import { PlotComponent, getGLFormatTypeAlignment } from "./PlotComponent";
|
|
3
2
|
import { hex2rgba } from "./utils";
|
|
4
3
|
import { WGLProgram, WGLTexture } from "autumn-wgl";
|
|
5
4
|
const paintball_vertex_shader_src = `uniform mat4 u_matrix;
|
|
5
|
+
uniform int u_offset;
|
|
6
6
|
|
|
7
7
|
attribute vec2 a_pos;
|
|
8
8
|
attribute vec2 a_tex_coord;
|
|
@@ -10,7 +10,10 @@ attribute vec2 a_tex_coord;
|
|
|
10
10
|
varying highp vec2 v_tex_coord;
|
|
11
11
|
|
|
12
12
|
void main() {
|
|
13
|
-
|
|
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);
|
|
14
17
|
v_tex_coord = a_tex_coord;
|
|
15
18
|
}`
|
|
16
19
|
const paintball_fragment_shader_src = `#define MAX_N_COLORS 24
|
|
@@ -52,10 +55,10 @@ void main() {
|
|
|
52
55
|
class Paintball extends PlotComponent {
|
|
53
56
|
/**
|
|
54
57
|
* Create a paintball plot
|
|
55
|
-
*
|
|
58
|
+
* @param field - A scalar field containing the member objects encoded as "bits." The numerical value of each grid point can be constructed like
|
|
56
59
|
* `1.0 * M1 + 2.0 * M2 + 4.0 * M3 + 8.0 * M4 ...`, where `M1` is 1 if that grid point is in an object in member 1 and 0 otherwise,
|
|
57
60
|
* `M2` is the same thing for member 2, and `M3` and `M4` and up to `Mn` are the same thing for the rest of the members.
|
|
58
|
-
*
|
|
61
|
+
* @param opts - Options for creating the paintball plot
|
|
59
62
|
*/
|
|
60
63
|
constructor(field, opts) {
|
|
61
64
|
super();
|
|
@@ -67,7 +70,7 @@ class Paintball extends PlotComponent {
|
|
|
67
70
|
this.gl_elems = null;
|
|
68
71
|
}
|
|
69
72
|
/**
|
|
70
|
-
*
|
|
73
|
+
* @internal
|
|
71
74
|
* Add the paintball plot to a map.
|
|
72
75
|
*/
|
|
73
76
|
async onAdd(map, gl) {
|
|
@@ -76,10 +79,11 @@ class Paintball extends PlotComponent {
|
|
|
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;
|
|
79
|
-
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
'
|
|
82
|
+
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 2);
|
|
83
|
+
const { format, type, row_alignment } = getGLFormatTypeAlignment(gl, this.field.isFloat16());
|
|
84
|
+
const fill_image = { 'format': format, 'type': type,
|
|
85
|
+
'width': this.field.grid.ni, 'height': this.field.grid.nj, 'image': this.field.getTextureData(),
|
|
86
|
+
'mag_filter': gl.NEAREST, 'row_alignment': row_alignment,
|
|
83
87
|
};
|
|
84
88
|
const fill_texture = new WGLTexture(gl, fill_image);
|
|
85
89
|
this.gl_elems = {
|
|
@@ -87,18 +91,26 @@ class Paintball extends PlotComponent {
|
|
|
87
91
|
};
|
|
88
92
|
}
|
|
89
93
|
/**
|
|
90
|
-
*
|
|
94
|
+
* @internal
|
|
91
95
|
* Render the paintball plot
|
|
92
96
|
*/
|
|
93
97
|
render(gl, matrix) {
|
|
94
98
|
if (this.gl_elems === null)
|
|
95
99
|
return;
|
|
96
100
|
const gl_elems = this.gl_elems;
|
|
101
|
+
if (matrix instanceof Float32Array)
|
|
102
|
+
matrix = [...matrix];
|
|
97
103
|
// Render to framebuffer
|
|
98
|
-
gl_elems.program.use({ 'a_pos': gl_elems.vertices, 'a_tex_coord': gl_elems.texcoords }, { 'u_matrix': matrix, 'u_opacity': this.opacity, 'u_colors': this.colors, 'u_num_colors': this.colors.length / 4 }, { 'u_fill_sampler': gl_elems.fill_texture });
|
|
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 });
|
|
99
105
|
gl.enable(gl.BLEND);
|
|
100
106
|
gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
|
101
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();
|
|
102
114
|
}
|
|
103
115
|
}
|
|
104
116
|
export default Paintball;
|
package/lib/PlotComponent.d.ts
CHANGED
|
@@ -15,6 +15,11 @@ 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
|
+
format: number;
|
|
22
|
+
type: number;
|
|
23
|
+
row_alignment: number;
|
|
24
|
+
};
|
|
25
|
+
export { PlotComponent, layer_worker, getGLFormatTypeAlignment };
|
package/lib/PlotComponent.js
CHANGED
|
@@ -1,6 +1,39 @@
|
|
|
1
1
|
import * as Comlink from 'comlink';
|
|
2
|
+
import { getOS } from "./utils";
|
|
3
|
+
import { isWebGL2Ctx } from './AutumnTypes';
|
|
2
4
|
const worker = new Worker(new URL('./PlotLayer.worker', import.meta.url));
|
|
3
5
|
const layer_worker = Comlink.wrap(worker);
|
|
4
6
|
class PlotComponent {
|
|
5
7
|
}
|
|
6
|
-
|
|
8
|
+
function getGLFormatTypeAlignment(gl, is_float16) {
|
|
9
|
+
let format, type, row_alignment;
|
|
10
|
+
const is_webgl2 = isWebGL2Ctx(gl);
|
|
11
|
+
if (is_float16) {
|
|
12
|
+
const ext = gl.getExtension('OES_texture_half_float');
|
|
13
|
+
const ext_lin = gl.getExtension('OES_texture_half_float_linear');
|
|
14
|
+
if ((!is_webgl2 && ext === null) || (!is_webgl2 && ext_lin === null)) {
|
|
15
|
+
throw "Float16 data are not supported on this hardware. Try Float32 data instead.";
|
|
16
|
+
}
|
|
17
|
+
format = is_webgl2 ? gl.R16F : gl.LUMINANCE;
|
|
18
|
+
type = is_webgl2 ? gl.HALF_FLOAT : ext.HALF_FLOAT_OES;
|
|
19
|
+
row_alignment = 2;
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
const ext = gl.getExtension('OES_texture_float');
|
|
23
|
+
const ext_lin = gl.getExtension('OES_texture_float_linear');
|
|
24
|
+
// As of 11/3/2023, Safari/WebKit on iOS reports as supporting float textures,
|
|
25
|
+
// but really doesn't. It just silently fails. In WebGL 1, Safari/Webkit returns
|
|
26
|
+
// null for ext_lin here, but in WebGL2, both ext vars are null because they were
|
|
27
|
+
// merged into the standard. This means that to properly fail for iOS devices using
|
|
28
|
+
// WebGL2, we have to hard code an OS check... this OS check also works when uers
|
|
29
|
+
// request desktop-mode websites.
|
|
30
|
+
if ((!is_webgl2 && ext === null) || (!is_webgl2 && ext_lin === null) || (getOS() === 'iOS')) {
|
|
31
|
+
throw "Float32 data are not supported on this hardware. Try Float16 data instead.";
|
|
32
|
+
}
|
|
33
|
+
format = is_webgl2 ? gl.R32F : gl.LUMINANCE;
|
|
34
|
+
type = gl.FLOAT;
|
|
35
|
+
row_alignment = 4;
|
|
36
|
+
}
|
|
37
|
+
return { format: format, type: type, row_alignment: row_alignment };
|
|
38
|
+
}
|
|
39
|
+
export { PlotComponent, layer_worker, getGLFormatTypeAlignment };
|
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}
|
|
@@ -18,7 +18,7 @@ declare abstract class PlotLayerBase {
|
|
|
18
18
|
* const barb_layer = new PlotLayer('barbs', wind_barbs);
|
|
19
19
|
*/
|
|
20
20
|
declare class PlotLayer extends PlotLayerBase {
|
|
21
|
-
readonly field
|
|
21
|
+
private readonly field;
|
|
22
22
|
/**
|
|
23
23
|
* Create a map layer from a field
|
|
24
24
|
* @param id - A unique id for this layer
|
|
@@ -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.
|
|
@@ -51,14 +51,10 @@ declare class PlotLayer extends PlotLayerBase {
|
|
|
51
51
|
* height_layer.setActiveKey('20230112_1200');
|
|
52
52
|
*/
|
|
53
53
|
declare class MultiPlotLayer extends PlotLayerBase {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
/** @private */
|
|
59
|
-
map: MapType | null;
|
|
60
|
-
/** @private */
|
|
61
|
-
gl: WebGLAnyRenderingContext | null;
|
|
54
|
+
private fields;
|
|
55
|
+
private field_key;
|
|
56
|
+
private map;
|
|
57
|
+
private gl;
|
|
62
58
|
/**
|
|
63
59
|
* Create a time-varying map layer
|
|
64
60
|
* @param id - A unique id for this layer
|
|
@@ -73,7 +69,7 @@ declare class MultiPlotLayer extends PlotLayerBase {
|
|
|
73
69
|
* @internal
|
|
74
70
|
* Render this layer
|
|
75
71
|
*/
|
|
76
|
-
render(gl: WebGLAnyRenderingContext, matrix: number[]): void;
|
|
72
|
+
render(gl: WebGLAnyRenderingContext, matrix: number[] | Float32Array): void;
|
|
77
73
|
/**
|
|
78
74
|
* Set the active key
|
|
79
75
|
* @param key - The new key
|
|
@@ -90,7 +86,6 @@ declare class MultiPlotLayer extends PlotLayerBase {
|
|
|
90
86
|
* @param dt - The date/time at which the field is valid
|
|
91
87
|
*/
|
|
92
88
|
addField(field: PlotComponent, key: string): void;
|
|
93
|
-
|
|
94
|
-
_repaintIfNecessary(old_field_key: string | null): void;
|
|
89
|
+
private repaintIfNecessary;
|
|
95
90
|
}
|
|
96
91
|
export { PlotLayer, MultiPlotLayer };
|
package/lib/PlotLayer.js
CHANGED
|
@@ -73,10 +73,10 @@ class MultiPlotLayer extends PlotLayerBase {
|
|
|
73
73
|
this.gl = gl;
|
|
74
74
|
Object.values(this.fields).forEach(field => {
|
|
75
75
|
field.onAdd(map, gl).then(res => {
|
|
76
|
-
this.
|
|
76
|
+
this.repaintIfNecessary(null);
|
|
77
77
|
});
|
|
78
78
|
});
|
|
79
|
-
this.
|
|
79
|
+
this.repaintIfNecessary(null);
|
|
80
80
|
}
|
|
81
81
|
/**
|
|
82
82
|
* @internal
|
|
@@ -95,7 +95,7 @@ class MultiPlotLayer extends PlotLayerBase {
|
|
|
95
95
|
setActiveKey(key) {
|
|
96
96
|
const old_field_key = this.field_key;
|
|
97
97
|
this.field_key = key;
|
|
98
|
-
this.
|
|
98
|
+
this.repaintIfNecessary(old_field_key);
|
|
99
99
|
}
|
|
100
100
|
/**
|
|
101
101
|
* Get a list of all dates/times that have been added to the layer
|
|
@@ -113,7 +113,7 @@ class MultiPlotLayer extends PlotLayerBase {
|
|
|
113
113
|
const old_field_key = this.field_key;
|
|
114
114
|
if (this.map !== null && this.gl !== null && field !== null) {
|
|
115
115
|
field.onAdd(this.map, this.gl).then(res => {
|
|
116
|
-
this.
|
|
116
|
+
this.repaintIfNecessary(null);
|
|
117
117
|
});
|
|
118
118
|
}
|
|
119
119
|
this.fields[key] = field;
|
|
@@ -121,8 +121,7 @@ class MultiPlotLayer extends PlotLayerBase {
|
|
|
121
121
|
this.field_key = key;
|
|
122
122
|
}
|
|
123
123
|
}
|
|
124
|
-
|
|
125
|
-
_repaintIfNecessary(old_field_key) {
|
|
124
|
+
repaintIfNecessary(old_field_key) {
|
|
126
125
|
if (this.map !== null && old_field_key !== this.field_key) {
|
|
127
126
|
this.map.triggerRepaint();
|
|
128
127
|
}
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { WGLTextureSpec } from "autumn-wgl";
|
|
2
2
|
import { PolylineSpec, LineSpec, WebGLAnyRenderingContext } from "./AutumnTypes";
|
|
3
3
|
declare class PolylineCollection {
|
|
4
4
|
readonly width: number;
|
|
5
5
|
readonly scale: number;
|
|
6
|
-
readonly program
|
|
7
|
-
readonly origin
|
|
8
|
-
readonly offset
|
|
9
|
-
readonly extrusion
|
|
10
|
-
readonly min_zoom
|
|
11
|
-
readonly texture
|
|
12
|
-
readonly texcoords
|
|
6
|
+
private readonly program;
|
|
7
|
+
private readonly origin;
|
|
8
|
+
private readonly offset;
|
|
9
|
+
private readonly extrusion;
|
|
10
|
+
private readonly min_zoom;
|
|
11
|
+
private readonly texture;
|
|
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,5 +1,6 @@
|
|
|
1
1
|
import { WGLBuffer, WGLProgram, WGLTexture } from "autumn-wgl";
|
|
2
2
|
const polyline_vertex_src = `uniform mat4 u_matrix;
|
|
3
|
+
uniform int u_offset;
|
|
3
4
|
|
|
4
5
|
attribute vec2 a_pos;
|
|
5
6
|
attribute float a_min_zoom;
|
|
@@ -43,7 +44,10 @@ mat4 rotationXMatrix(float angle) {
|
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
void main() {
|
|
46
|
-
|
|
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);
|
|
47
51
|
vec4 offset = vec4(0.0, 0.0, 0.0, 0.0);
|
|
48
52
|
|
|
49
53
|
if (u_zoom >= a_min_zoom) {
|
|
@@ -82,11 +86,19 @@ class PolylineCollection {
|
|
|
82
86
|
this.texcoords = new WGLBuffer(gl, polyline['texcoords'], 2, gl.TRIANGLE_STRIP);
|
|
83
87
|
}
|
|
84
88
|
render(gl, matrix, [map_width, map_height], map_zoom, map_bearing, map_pitch) {
|
|
89
|
+
if (matrix instanceof Float32Array)
|
|
90
|
+
matrix = [...matrix];
|
|
85
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,
|
|
86
|
-
'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 });
|
|
87
93
|
gl.enable(gl.BLEND);
|
|
88
94
|
gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
|
89
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();
|
|
90
102
|
}
|
|
91
103
|
}
|
|
92
104
|
export { PolylineCollection };
|