autumnplot-gl 4.0.0-beta → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/README.md +13 -207
  2. package/dist/812.autumnplot-gl.js +2 -0
  3. package/dist/812.autumnplot-gl.js.map +1 -0
  4. package/dist/983.autumnplot-gl.js +2 -0
  5. package/dist/983.autumnplot-gl.js.map +1 -0
  6. package/dist/autumnplot-gl.js +1 -1
  7. package/dist/autumnplot-gl.js.map +1 -1
  8. package/dist/marchingsquares.wasm +0 -0
  9. package/lib/AutumnTypes.d.ts +38 -5
  10. package/lib/AutumnTypes.js +7 -1
  11. package/lib/Barbs.d.ts +12 -2
  12. package/lib/Barbs.js +9 -0
  13. package/lib/BillboardCollection.d.ts +2 -2
  14. package/lib/BillboardCollection.js +14 -14
  15. package/lib/Color.d.ts +1 -0
  16. package/lib/Color.js +1 -0
  17. package/lib/ColorBar.d.ts +14 -0
  18. package/lib/ColorBar.js +15 -8
  19. package/lib/Colormap.d.ts +9 -1
  20. package/lib/Colormap.js +24 -1
  21. package/lib/Contour.d.ts +26 -1
  22. package/lib/Contour.js +24 -2
  23. package/lib/ContourCreator.worker.d.ts +25 -0
  24. package/lib/{ContourCreator.js → ContourCreator.worker.js} +15 -14
  25. package/lib/Fill.d.ts +31 -11
  26. package/lib/Fill.js +38 -18
  27. package/lib/Hodographs.d.ts +19 -3
  28. package/lib/Hodographs.js +45 -20
  29. package/lib/Map.d.ts +13 -1
  30. package/lib/Map.js +62 -8
  31. package/lib/Paintball.d.ts +14 -5
  32. package/lib/Paintball.js +96 -46
  33. package/lib/PlotComponent.d.ts +9 -3
  34. package/lib/PlotComponent.js +36 -1
  35. package/lib/PlotLayer.d.ts +2 -2
  36. package/lib/PlotLayer.js +2 -2
  37. package/lib/PlotLayer.worker.js +9 -3
  38. package/lib/RawField.d.ts +223 -27
  39. package/lib/RawField.js +413 -59
  40. package/lib/StationPlot.d.ts +78 -11
  41. package/lib/StationPlot.js +113 -30
  42. package/lib/TextCollection.d.ts +5 -0
  43. package/lib/TextCollection.js +82 -9
  44. package/lib/WasmInterface.d.ts +7 -0
  45. package/lib/WasmInterface.js +11 -0
  46. package/lib/WorkerPool.d.ts +8 -0
  47. package/lib/WorkerPool.js +77 -0
  48. package/lib/cpp/marchingsquares.js +127 -13
  49. package/lib/cpp/marchingsquares.wasm +0 -0
  50. package/lib/cpp/marchingsquares_embind.d.ts +16 -3
  51. package/lib/grids/AutoZoom.d.ts +21 -0
  52. package/lib/grids/AutoZoom.js +63 -0
  53. package/lib/grids/DomainBuffer.d.ts +14 -0
  54. package/lib/grids/DomainBuffer.js +16 -0
  55. package/lib/grids/Geostationary.d.ts +35 -0
  56. package/lib/grids/Geostationary.js +47 -0
  57. package/lib/grids/Grid.d.ts +36 -0
  58. package/lib/grids/Grid.js +12 -0
  59. package/lib/grids/GridCoordinates.d.ts +10 -0
  60. package/lib/grids/GridCoordinates.js +64 -0
  61. package/lib/grids/LambertGrid.d.ts +73 -0
  62. package/lib/grids/LambertGrid.js +92 -0
  63. package/lib/grids/PlateCarreeGrid.d.ts +46 -0
  64. package/lib/grids/PlateCarreeGrid.js +55 -0
  65. package/lib/grids/PlateCarreeRotatedGrid.d.ts +53 -0
  66. package/lib/grids/PlateCarreeRotatedGrid.js +65 -0
  67. package/lib/grids/RadarSweepGrid.d.ts +46 -0
  68. package/lib/grids/RadarSweepGrid.js +74 -0
  69. package/lib/grids/StructuredGrid.d.ts +49 -0
  70. package/lib/grids/StructuredGrid.js +103 -0
  71. package/lib/grids/UnstructuredGrid.d.ts +56 -0
  72. package/lib/grids/UnstructuredGrid.js +102 -0
  73. package/lib/index.d.ts +23 -6
  74. package/lib/index.js +18 -8
  75. package/lib/utils.d.ts +11 -2
  76. package/lib/utils.js +63 -1
  77. package/package.json +4 -3
  78. package/dist/110.autumnplot-gl.js +0 -2
  79. package/dist/110.autumnplot-gl.js.map +0 -1
  80. package/lib/ContourCreator.d.ts +0 -22
  81. package/lib/Grid.d.ts +0 -263
  82. package/lib/Grid.js +0 -547
  83. package/lib/ParticleTracer.d.ts +0 -19
  84. package/lib/ParticleTracer.js +0 -37
package/lib/Paintball.js CHANGED
@@ -1,10 +1,20 @@
1
1
  import { getRendererData } from "./AutumnTypes";
2
2
  import { Color } from "./Color";
3
- import { PlotComponent } from "./PlotComponent";
3
+ import { PlotComponent, getGLFormatTypeAlignment } from "./PlotComponent";
4
4
  import { ShaderProgramManager } from "./ShaderManager";
5
- import { normalizeOptions } from "./utils";
6
- import { WGLTexture } from "autumn-wgl";
7
- const paintball_vertex_shader_src = `#version 300 es
5
+ import { applySamplerCodeScalar, normalizeOptions } from "./utils";
6
+ import { WGLBuffer, WGLFramebuffer, WGLProgram, WGLTexture } from "autumn-wgl";
7
+ const paintball_step1_vertex_shader_src = `#version 300 es
8
+
9
+ in vec2 a_pos;
10
+
11
+ out highp vec2 v_tex_coord;
12
+
13
+ void main() {
14
+ gl_Position = vec4(a_pos.xy, 0., 1.);
15
+ v_tex_coord = 0.5 * a_pos.xy + vec2(0.5, 0.5);
16
+ }`
17
+ const paintball_step2_vertex_shader_src = `#version 300 es
8
18
 
9
19
  uniform int u_offset;
10
20
 
@@ -20,15 +30,34 @@ void main() {
20
30
  gl_Position = projectTile(a_pos.xy + globe_offset);
21
31
  v_tex_coord = a_tex_coord;
22
32
  }`
23
- const paintball_fragment_shader_src = `#version 300 es
33
+ const paintball_step1_fragment_shader_src = `#version 300 es
34
+
35
+ in highp vec2 v_tex_coord;
36
+
37
+ uniform int u_imem;
38
+
39
+ out highp vec4 fragColor;
40
+
41
+ void main() {
42
+ uint fill_val = uint(get_field_value(v_tex_coord));
43
+
44
+ if (fill_val < uint(1)) {
45
+ discard;
46
+ }
24
47
 
25
- #define MAX_N_COLORS 24
48
+ lowp vec4 off_color = vec4(0., 0., 0., 1.);
49
+ lowp vec4 on_color = vec4(1., 1., 1., 1.);
50
+
51
+ uint mask = uint(1) << u_imem;
52
+ lowp float mem_active = float((fill_val & mask) > uint(0));
53
+ fragColor = mix(off_color, on_color, mem_active);
54
+ }`
55
+ const paintball_step2_fragment_shader_src = `#version 300 es
26
56
 
27
57
  in highp vec2 v_tex_coord;
28
58
 
29
59
  uniform sampler2D u_fill_sampler;
30
- uniform lowp vec4 u_colors[MAX_N_COLORS];
31
- uniform int u_num_colors;
60
+ uniform lowp vec4 u_color;
32
61
  uniform highp float u_opacity;
33
62
 
34
63
  out highp vec4 fragColor;
@@ -36,19 +65,11 @@ out highp vec4 fragColor;
36
65
  void main() {
37
66
  highp float fill_val = texture(u_fill_sampler, v_tex_coord).r;
38
67
 
39
- if (fill_val < 0.5) {
68
+ if (fill_val < 0.4) {
40
69
  discard;
41
70
  }
42
71
 
43
- lowp vec4 color = vec4(0., 0., 0., 0.);
44
-
45
- for (int nclr = 0; nclr < MAX_N_COLORS ; nclr++) {
46
- if (nclr >= u_num_colors || fill_val < 0.99) { break; }
47
-
48
- lowp float mem_active = mod(fill_val, 2.);
49
- color = mix(color, u_colors[nclr], mem_active);
50
- fill_val = floor(fill_val / 2.);
51
- }
72
+ lowp vec4 color = u_color;
52
73
 
53
74
  color.a = color.a * u_opacity;
54
75
  fragColor = color;
@@ -63,6 +84,14 @@ const paintball_opt_defaults = {
63
84
  * the data for the paintball plot is given as a single field with the objects from each member encoded as "bits" in the field. Because the field is made up
64
85
  * of single-precision floats, this works for up to 24 members. (Technically speaking, I don't need the quotes around "bits", as they're bits of the
65
86
  * significand of an IEEE 754 float.)
87
+ *
88
+ * ## Grid Compatibility
89
+ * - :white_check_mark: `PlateCarreeGrid`
90
+ * - :white_check_mark: `PlateCarreeRotatedGrid`
91
+ * - :white_check_mark: `LambertGrid`
92
+ * - :x: `UnstructuredGrid`
93
+ * - :white_check_mark: `RadarSweepGrid`
94
+ * - :white_check_mark: `Geostationary`
66
95
  */
67
96
  class Paintball extends PlotComponent {
68
97
  /**
@@ -76,7 +105,7 @@ class Paintball extends PlotComponent {
76
105
  super();
77
106
  this.field = field;
78
107
  this.opts = normalizeOptions(opts, paintball_opt_defaults);
79
- this.color_components = [...this.opts.colors].reverse().map(color => Color.fromHex(color).toRGBATuple()).flat();
108
+ this.color_components = this.opts.colors.map(color => Color.fromHex(color).toRGBATuple());
80
109
  this.gl_elems = null;
81
110
  this.fill_texture = null;
82
111
  }
@@ -90,13 +119,7 @@ class Paintball extends PlotComponent {
90
119
  return;
91
120
  const gl = this.gl_elems.gl;
92
121
  gl.pixelStorei(gl.UNPACK_ALIGNMENT, 2);
93
- const fill_image = this.field.getWGLTextureSpec(gl, gl.NEAREST);
94
- if (this.fill_texture === null) {
95
- this.fill_texture = new WGLTexture(gl, fill_image);
96
- }
97
- else {
98
- this.fill_texture.setImageData(fill_image);
99
- }
122
+ this.fill_texture = this.field.updateTexImageData(gl, gl.NEAREST, this.fill_texture);
100
123
  }
101
124
  /**
102
125
  * @internal
@@ -104,12 +127,23 @@ class Paintball extends PlotComponent {
104
127
  */
105
128
  async onAdd(map, gl) {
106
129
  gl.getExtension('OES_texture_float');
107
- const { vertices: verts_buf, texcoords: tex_coords_buf } = await this.field.grid.getWGLBuffers(gl);
108
- const vertices = verts_buf;
109
- const texcoords = tex_coords_buf;
110
- const shader_manager = new ShaderProgramManager(paintball_vertex_shader_src, paintball_fragment_shader_src, []);
130
+ const vertices_step1 = new WGLBuffer(gl, new Float32Array([-1., -1., 1., -1., -1., 1., 1., 1.]), 2, gl.TRIANGLE_STRIP);
131
+ const { vertices: vertices_step2, texcoords: texcoords_step2 } = await this.field.grid.getDomainBuffers(gl);
132
+ const sampler_keys = this.field.getSamplerIds();
133
+ const sampler_expression = this.field.getExpression();
134
+ const dtypes = this.field.dtypes;
135
+ const step1_frag_shader_src = applySamplerCodeScalar(paintball_step1_fragment_shader_src, sampler_keys, sampler_expression, dtypes);
136
+ const shader_program_step1 = new WGLProgram(gl, paintball_step1_vertex_shader_src, step1_frag_shader_src);
137
+ const shader_manager_step2 = new ShaderProgramManager(paintball_step2_vertex_shader_src, paintball_step2_fragment_shader_src, []);
138
+ const { format, type, row_alignment } = getGLFormatTypeAlignment(gl, 'float16');
139
+ const fb_texture = new WGLTexture(gl, { format: format, type: type,
140
+ width: this.field.grid.ni, height: this.field.grid.nj, image: null,
141
+ mag_filter: gl.LINEAR, row_alignment: row_alignment });
142
+ const fb = new WGLFramebuffer(gl, fb_texture);
111
143
  this.gl_elems = {
112
- gl: gl, shader_manager: shader_manager, vertices: vertices, texcoords: texcoords,
144
+ gl: gl, map: map, shader_program_1: shader_program_step1, shader_manager_2: shader_manager_step2,
145
+ vertices_step1: vertices_step1, vertices_step2: vertices_step2, texcoords_step2: texcoords_step2,
146
+ framebuffer: fb
113
147
  };
114
148
  this.updateField(this.field);
115
149
  }
@@ -121,22 +155,38 @@ class Paintball extends PlotComponent {
121
155
  if (this.gl_elems === null || this.fill_texture === null)
122
156
  return;
123
157
  const gl_elems = this.gl_elems;
158
+ const fill_texture = this.fill_texture;
159
+ const map = gl_elems.map;
160
+ const viewport_width = map.getCanvas().width;
161
+ const viewport_height = map.getCanvas().height;
124
162
  const render_data = getRendererData(arg);
125
- const program = this.gl_elems.shader_manager.getShaderProgram(gl, render_data.shaderData);
126
- // Render to framebuffer
127
- program.use({ 'a_pos': gl_elems.vertices, 'a_tex_coord': gl_elems.texcoords }, { 'u_opacity': this.opts.opacity, 'u_colors': this.color_components, 'u_num_colors': this.opts.colors.length, 'u_offset': 0,
128
- ...this.gl_elems.shader_manager.getShaderUniforms(render_data) }, { 'u_fill_sampler': this.fill_texture });
129
- gl.enable(gl.BLEND);
130
- gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
131
- program.draw();
132
- if (render_data.type != 'maplibre' || !render_data.shaderData.define.includes('GLOBE')) {
133
- program.setUniforms({ 'u_offset': -2 });
134
- program.draw();
135
- program.setUniforms({ 'u_offset': -1 });
136
- program.draw();
137
- program.setUniforms({ 'u_offset': 1 });
138
- program.draw();
139
- }
163
+ const program_step1 = this.gl_elems.shader_program_1;
164
+ const program_step2 = this.gl_elems.shader_manager_2.getShaderProgram(gl, render_data.shaderData);
165
+ const samplers = Object.fromEntries([...this.fill_texture.entries()]);
166
+ this.color_components.forEach((color, icolor) => {
167
+ // Render to framebuffer to pull out an individual member from the packed field
168
+ program_step1.use({ 'a_pos': gl_elems.vertices_step1 }, { 'u_imem': icolor }, samplers);
169
+ gl_elems.framebuffer.clear([0, 0, 0, 1]);
170
+ gl_elems.framebuffer.renderTo(0, 0, this.field.grid.ni, this.field.grid.nj);
171
+ gl.enable(gl.BLEND);
172
+ gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
173
+ program_step1.draw();
174
+ // Now render the framebuffer as a filled contour
175
+ program_step2.use({ 'a_pos': gl_elems.vertices_step2, 'a_tex_coord': gl_elems.texcoords_step2 }, { 'u_opacity': this.opts.opacity, 'u_color': color, 'u_offset': 0,
176
+ ...gl_elems.shader_manager_2.getShaderUniforms(render_data) }, { 'u_fill_sampler': gl_elems.framebuffer.texture });
177
+ WGLFramebuffer.screen(gl).renderTo(0, 0, viewport_width, viewport_height);
178
+ gl.enable(gl.BLEND);
179
+ gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
180
+ program_step2.draw();
181
+ if (render_data.type != 'maplibre' || !render_data.shaderData.define.includes('GLOBE')) {
182
+ program_step2.setUniforms({ 'u_offset': -2 });
183
+ program_step2.draw();
184
+ program_step2.setUniforms({ 'u_offset': -1 });
185
+ program_step2.draw();
186
+ program_step2.setUniforms({ 'u_offset': 1 });
187
+ program_step2.draw();
188
+ }
189
+ });
140
190
  }
141
191
  }
142
192
  export default Paintball;
@@ -1,6 +1,7 @@
1
1
  import * as Comlink from 'comlink';
2
2
  import { MapLikeType } from './Map';
3
3
  import { RenderMethodArg, TypedArrayStr, WebGLAnyRenderingContext } from './AutumnTypes';
4
+ import { WorkerPool } from './WorkerPool';
4
5
  declare const layer_worker: Comlink.Remote<{
5
6
  makeBBElements: (field_lats: Float32Array, field_lons: Float32Array, min_zoom: Uint8Array, field_ni: number, field_nj: number, map_max_zoom: number) => {
6
7
  pts: Float32Array;
@@ -12,13 +13,18 @@ declare const layer_worker: Comlink.Remote<{
12
13
  };
13
14
  makePolyLines: (lines: import("./AutumnTypes").LineData[]) => import("./AutumnTypes").Polyline;
14
15
  }>;
16
+ declare function getContourWorkerPool(wasm_base_url: string | undefined, n_workers: number): WorkerPool<{
17
+ contourCreator: (data: import("./AutumnTypes").ContourableTypedArray, grid_coords: import("./grids/Grid").GridCoords, opts: import("./ContourCreator.worker").FieldContourOpts) => Promise<import("./AutumnTypes").ContourData>;
18
+ init: (wasm_base_url: string | undefined) => Promise<void>;
19
+ }>;
20
+ /** Base class for all plot components */
15
21
  declare abstract class PlotComponent<MapType extends MapLikeType> {
16
22
  abstract onAdd(map: MapType, gl: WebGLAnyRenderingContext): Promise<void>;
17
23
  abstract render(gl: WebGLAnyRenderingContext, arg: RenderMethodArg): void;
18
24
  }
19
25
  declare function getGLFormatTypeAlignment(gl: WebGLAnyRenderingContext, array_dtype: TypedArrayStr): {
20
- format: 33321 | 33325 | 33326 | 6409;
21
- type: 36193 | 5131 | 5121 | 5126;
26
+ format: 33321 | 33325 | 33326 | 33331 | 33332 | 33333 | 33334 | 6409;
27
+ type: 36193 | 5131 | 5121 | 5122 | 5123 | 5124 | 5125 | 5126;
22
28
  row_alignment: number;
23
29
  };
24
- export { PlotComponent, layer_worker, getGLFormatTypeAlignment };
30
+ export { PlotComponent, layer_worker, getContourWorkerPool, getGLFormatTypeAlignment };
@@ -1,8 +1,23 @@
1
1
  import * as Comlink from 'comlink';
2
2
  import { getOS } from "./utils";
3
3
  import { isWebGL2Ctx } from './AutumnTypes';
4
+ import { createWorkerPool } from './WorkerPool';
4
5
  const worker = new Worker(new URL('./PlotLayer.worker', import.meta.url));
5
6
  const layer_worker = Comlink.wrap(worker);
7
+ let c_worker_pool = null;
8
+ function getContourWorkerPool(wasm_base_url, n_workers) {
9
+ if (c_worker_pool !== null) {
10
+ return c_worker_pool;
11
+ }
12
+ const c_workers = [];
13
+ for (let iw = 0; iw < n_workers; iw++) {
14
+ c_workers.push(new Worker(new URL('./ContourCreator.worker', import.meta.url)));
15
+ }
16
+ const pool = createWorkerPool(c_workers, wkr => wkr.init(wasm_base_url));
17
+ c_worker_pool = pool;
18
+ return pool;
19
+ }
20
+ /** Base class for all plot components */
6
21
  class PlotComponent {
7
22
  }
8
23
  function getGLFormatTypeAlignment(gl, array_dtype) {
@@ -39,6 +54,26 @@ function getGLFormatTypeAlignment(gl, array_dtype) {
39
54
  type = gl.FLOAT;
40
55
  row_alignment = 4;
41
56
  }
57
+ else if (array_dtype == 'uint16') {
58
+ format = is_webgl2 ? gl.R16UI : gl.LUMINANCE;
59
+ type = gl.UNSIGNED_SHORT;
60
+ row_alignment = 2;
61
+ }
62
+ else if (array_dtype == 'uint32') {
63
+ format = is_webgl2 ? gl.R32UI : gl.LUMINANCE;
64
+ type = gl.UNSIGNED_INT;
65
+ row_alignment = 4;
66
+ }
67
+ else if (array_dtype == 'int16') {
68
+ format = is_webgl2 ? gl.R16I : gl.LUMINANCE;
69
+ type = gl.SHORT;
70
+ row_alignment = 2;
71
+ }
72
+ else if (array_dtype == 'int32') {
73
+ format = is_webgl2 ? gl.R32I : gl.LUMINANCE;
74
+ type = gl.INT;
75
+ row_alignment = 4;
76
+ }
42
77
  else {
43
78
  format = is_webgl2 ? gl.R8 : gl.LUMINANCE;
44
79
  type = gl.UNSIGNED_BYTE;
@@ -46,4 +81,4 @@ function getGLFormatTypeAlignment(gl, array_dtype) {
46
81
  }
47
82
  return { format: format, type: type, row_alignment: row_alignment };
48
83
  }
49
- export { PlotComponent, layer_worker, getGLFormatTypeAlignment };
84
+ export { PlotComponent, layer_worker, getContourWorkerPool, getGLFormatTypeAlignment };
@@ -11,7 +11,7 @@ declare abstract class PlotLayerBase<MapType extends MapLikeType> {
11
11
  protected repaint(): void;
12
12
  }
13
13
  /**
14
- * 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}
14
+ * 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), a {@link MultiPlotLayer}
15
15
  * may be more appropriate.
16
16
  * @example
17
17
  * // Create map layers from provided fields
@@ -39,7 +39,7 @@ declare class PlotLayer<MapType extends MapLikeType> extends PlotLayerBase<MapTy
39
39
  render(gl: WebGLAnyRenderingContext, matrix: RenderMethodArg): void;
40
40
  }
41
41
  /**
42
- * 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.
42
+ * A varying map layer. If the data don't have a varying component, such as over time, it might be easier to use a {@link PlotLayer} instead.
43
43
  * @example
44
44
  * // Create a varying map layer
45
45
  * height_layer = new MultiPlotLayer('height-contours');
package/lib/PlotLayer.js CHANGED
@@ -11,7 +11,7 @@ class PlotLayerBase {
11
11
  }
12
12
  }
13
13
  /**
14
- * 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}
14
+ * 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), a {@link MultiPlotLayer}
15
15
  * may be more appropriate.
16
16
  * @example
17
17
  * // Create map layers from provided fields
@@ -46,7 +46,7 @@ class PlotLayer extends PlotLayerBase {
46
46
  }
47
47
  }
48
48
  /**
49
- * 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.
49
+ * A varying map layer. If the data don't have a varying component, such as over time, it might be easier to use a {@link PlotLayer} instead.
50
50
  * @example
51
51
  * // Create a varying map layer
52
52
  * height_layer = new MultiPlotLayer('height-contours');
@@ -21,7 +21,10 @@ function makeBBElements(field_lats, field_lons, min_zoom, field_ni, field_nj, ma
21
21
  const pt_ll = new LngLat(lon, lat).toMercatorCoord();
22
22
  pts[istart_pts + 0] = pt_ll.x;
23
23
  pts[istart_pts + 1] = pt_ll.y;
24
- tex_coords[istart_tc + 0] = ilon / (field_ni - 1) + zoom; // Pack the min zoom in with the texture coordinates; only works because the min zoom is always an integer
24
+ // Pack the min zoom in with the texture coordinates; only works because the min zoom is always an integer
25
+ // Another gotcha is that if the i texcoord is 1, then that bump up the zoom that gets unpacked on the GPU, which causes may cause the last column of billboards to
26
+ // disappear. To fix this, cap the texcoord at 0.99999, which should be good for textures up to 10^5 pixels in size.
27
+ tex_coords[istart_tc + 0] = Math.min(ilon / (field_ni - 1), 0.99999) + zoom;
25
28
  tex_coords[istart_tc + 1] = ilat / (field_nj - 1);
26
29
  istart_pts += n_coords_per_pt_pts;
27
30
  istart_tc += n_coords_per_pt_tc;
@@ -204,7 +207,7 @@ function makePolylinesMiter(lines) {
204
207
  }
205
208
  */
206
209
  function makePolylines(lines) {
207
- if (lines.length == 0) {
210
+ if (lines.length == 0 || lines[0].vertices.length == 0) {
208
211
  return { vertices: new Float32Array([]), extrusion: new Float32Array([]) };
209
212
  }
210
213
  const n_points_per_vert = Object.fromEntries(Object.entries(lines[0]).map(([k, v]) => {
@@ -252,6 +255,9 @@ function makePolylines(lines) {
252
255
  const v_ll = new LngLat(...v).toMercatorCoord();
253
256
  return [v_ll.x, v_ll.y];
254
257
  });
258
+ if (line.vertices.length == 0) {
259
+ return;
260
+ }
255
261
  const has_offsets = line.offsets !== undefined;
256
262
  const extrusion_verts = line.offsets !== undefined ? line.offsets : verts;
257
263
  let pt_prev, pt_this = verts[0], pt_next = verts[1];
@@ -342,6 +348,6 @@ function makePolylines(lines) {
342
348
  const ep_interface = {
343
349
  'makeBBElements': makeBBElements,
344
350
  'makeDomainVerticesAndTexCoords': makeDomainVerticesAndTexCoords,
345
- 'makePolyLines': makePolylines
351
+ 'makePolyLines': makePolylines,
346
352
  };
347
353
  Comlink.expose(ep_interface);