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 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 PlateCarree (a.k.a. Lat/Lon) and Lambert Conformal Conic.
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. A future version might include support for reading from, say, a Zarr file. Once you have your data in that format, to create the raw data field:
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
- Normally, when you have color-filled contours, you have a color bar on the plot. To create an SVG color bar:
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.0.
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
- vec4 pivot_pos = u_matrix * vec4(a_pos.xy, 0.0, 1.0);
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 = program_cache.getValue(gl);
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 { Cache, hex2rgba } from './utils';
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
- gl_Position = u_matrix * vec4(a_pos, 0.0, 1.0);
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 = program_cache.getValue(gl);
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
- gl_Position = u_matrix * vec4(a_pos, 0.0, 1.0);
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 = program_cache.getValue(gl);
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
  /**
@@ -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
- return (180 - (180 / Math.PI * Math.log(Math.tan(Math.PI / 4 + lat * Math.PI / 360)))) / 360;
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.
@@ -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 { Cache, hex2rgba } from "./utils";
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
- gl_Position = u_matrix * vec4(a_pos, 0.0, 1.0);
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 = program_cache.getValue(gl);
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;
@@ -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;
@@ -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
- vec4 center_pos = u_matrix * vec4(a_pos.xy, 0.0, 1.0);
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 = program_cache.getValue(gl);
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 Coords {
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 getCoords(): Coords;
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
- getCoords(): Coords;
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
- getCoords(): Coords;
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
- getCoords(): Coords;
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.getCoords();
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.getCoords();
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
- getCoords() {
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 + (this.ur_lon - this.ll_lon) * i / (this.ni - 1);
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 + (this.ur_lat - this.ll_lat) * j / (this.nj - 1);
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
- getCoords() {
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 + (this.ur_x - this.ll_x) * i / (this.ni - 1);
250
+ const x = this.ll_x + i * dx;
219
251
  for (let j = 0; j < this.nj; j++) {
220
- const y = this.ll_y + (this.ur_y - this.ll_y) * j / (this.nj - 1);
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
- getCoords() {
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.getCoords();
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "autumnplot-gl",
3
- "version": "2.2.0",
3
+ "version": "2.2.2",
4
4
  "description": "",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",