autumnplot-gl 3.0.0 → 3.2.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 (44) hide show
  1. package/README.md +6 -11
  2. package/dist/110.autumnplot-gl.js +1 -1
  3. package/dist/110.autumnplot-gl.js.map +1 -1
  4. package/dist/autumnplot-gl.js +1 -1
  5. package/dist/autumnplot-gl.js.map +1 -1
  6. package/dist/marchingsquares.wasm +0 -0
  7. package/lib/Barbs.d.ts +18 -2
  8. package/lib/Barbs.js +25 -19
  9. package/lib/BillboardCollection.d.ts +9 -2
  10. package/lib/BillboardCollection.js +46 -9
  11. package/lib/Color.d.ts +56 -0
  12. package/lib/Color.js +160 -0
  13. package/lib/ColorBar.d.ts +2 -1
  14. package/lib/ColorBar.js +5 -5
  15. package/lib/Colormap.d.ts +19 -18
  16. package/lib/Colormap.js +81 -20
  17. package/lib/Contour.d.ts +25 -6
  18. package/lib/Contour.js +61 -12
  19. package/lib/ContourCreator.js +4 -40
  20. package/lib/Fill.d.ts +2 -4
  21. package/lib/Fill.js +29 -45
  22. package/lib/Grid.d.ts +1 -0
  23. package/lib/Grid.js +6 -1
  24. package/lib/Hodographs.d.ts +19 -3
  25. package/lib/Hodographs.js +23 -20
  26. package/lib/Paintball.d.ts +2 -2
  27. package/lib/Paintball.js +9 -6
  28. package/lib/PlotComponent.js +9 -4
  29. package/lib/PlotLayer.d.ts +1 -1
  30. package/lib/PlotLayer.worker.js +10 -7
  31. package/lib/PolylineCollection.d.ts +13 -6
  32. package/lib/PolylineCollection.js +76 -64
  33. package/lib/StationPlot.d.ts +34 -0
  34. package/lib/StationPlot.js +73 -0
  35. package/lib/TextCollection.d.ts +3 -2
  36. package/lib/TextCollection.js +21 -11
  37. package/lib/cpp/marchingsquares.js +558 -1261
  38. package/lib/cpp/marchingsquares.wasm +0 -0
  39. package/lib/cpp/marchingsquares_embind.d.ts +4 -45
  40. package/lib/index.d.ts +4 -2
  41. package/lib/index.js +2 -1
  42. package/lib/utils.d.ts +2 -8
  43. package/lib/utils.js +1 -83
  44. package/package.json +2 -2
@@ -2,9 +2,11 @@ import { PlotComponent } from "./PlotComponent";
2
2
  import { MapLikeType } from "./Map";
3
3
  import { RawProfileField } from "./RawField";
4
4
  import { WebGLAnyRenderingContext } from "./AutumnTypes";
5
+ import { ColorMap } from "./Colormap";
5
6
  interface HodographOptions {
6
7
  /**
7
8
  * The color of the hodograph plot background as a hex string
9
+ * @default '#000000'
8
10
  */
9
11
  bgcolor?: string;
10
12
  /**
@@ -13,14 +15,28 @@ interface HodographOptions {
13
15
  * @default 1
14
16
  */
15
17
  thin_fac?: number;
18
+ /**
19
+ * The width of the hodograph line in pixels
20
+ * @default 2.5
21
+ */
22
+ hodo_line_width: number;
23
+ /**
24
+ * The width of the lines on the background in pixels
25
+ * @default 1.5
26
+ */
27
+ background_line_width: number;
28
+ /**
29
+ * The colormap to use for the heights on the hodograph. Default is a yellow-blue colormap.
30
+ */
31
+ height_cmap: ColorMap;
16
32
  }
17
- /** A class representing a a field of hodograph plots */
33
+ /** A class representing a field of hodograph plots */
18
34
  declare class Hodographs<MapType extends MapLikeType> extends PlotComponent<MapType> {
19
35
  private profile_field;
20
- readonly bgcolor: string;
21
- readonly thin_fac: number;
36
+ readonly opts: Required<HodographOptions>;
22
37
  private gl_elems;
23
38
  private line_elems;
39
+ private hodo_bg_texture;
24
40
  private readonly hodo_scale;
25
41
  private readonly bg_size;
26
42
  /**
package/lib/Hodographs.js CHANGED
@@ -1,9 +1,10 @@
1
1
  import { PlotComponent } from "./PlotComponent";
2
2
  import { PolylineCollection } from "./PolylineCollection";
3
3
  import { BillboardCollection } from "./BillboardCollection";
4
- import { getMinZoom, hex2rgb } from './utils';
4
+ import { getMinZoom, normalizeOptions } from './utils';
5
5
  import { ColorMap } from "./Colormap";
6
- const LINE_WIDTH = 4;
6
+ import { Color } from "./Color";
7
+ const LINE_WIDTH_MULTIPLIER = 2.5;
7
8
  const BG_MAX_RING_MAG = 40;
8
9
  const HODO_BG_DIMS = {
9
10
  BB_WIDTH: 256,
@@ -14,7 +15,7 @@ const HODO_BG_DIMS = {
14
15
  BB_MAG_WRAP: 1000,
15
16
  BB_MAG_BIN_SIZE: 1000,
16
17
  };
17
- function _createHodoBackgroundTexture() {
18
+ function _createHodoBackgroundTexture(line_width) {
18
19
  let canvas = document.createElement('canvas');
19
20
  canvas.width = HODO_BG_DIMS.BB_TEX_WIDTH;
20
21
  canvas.height = HODO_BG_DIMS.BB_TEX_HEIGHT;
@@ -22,10 +23,10 @@ function _createHodoBackgroundTexture() {
22
23
  if (ctx === null) {
23
24
  throw "Could not get rendering context for the hodograph background canvas";
24
25
  }
25
- ctx.lineWidth = LINE_WIDTH;
26
+ ctx.lineWidth = line_width;
26
27
  for (let irng = HODO_BG_DIMS.BB_TEX_WIDTH / 4; irng <= HODO_BG_DIMS.BB_TEX_WIDTH / 2; irng += HODO_BG_DIMS.BB_TEX_WIDTH / 4) {
27
28
  ctx.beginPath();
28
- ctx.arc(HODO_BG_DIMS.BB_TEX_WIDTH / 2, HODO_BG_DIMS.BB_TEX_WIDTH / 2, irng - LINE_WIDTH / 2, 0, 2 * Math.PI);
29
+ ctx.arc(HODO_BG_DIMS.BB_TEX_WIDTH / 2, HODO_BG_DIMS.BB_TEX_WIDTH / 2, irng - line_width / 2, 0, 2 * Math.PI);
29
30
  ctx.stroke();
30
31
  }
31
32
  const ctr_x = HODO_BG_DIMS.BB_TEX_WIDTH / 2, ctr_y = HODO_BG_DIMS.BB_TEX_WIDTH / 2;
@@ -39,9 +40,15 @@ function _createHodoBackgroundTexture() {
39
40
  return canvas;
40
41
  }
41
42
  ;
42
- let HODO_BG_TEXTURE = null;
43
43
  const HODO_CMAP = new ColorMap([0, 1, 3, 6, 9], ['#ffffcc', '#a1dab4', '#41b6c4', '#225ea8']);
44
- /** A class representing a a field of hodograph plots */
44
+ const hodograph_opt_defaults = {
45
+ bgcolor: '#000000',
46
+ thin_fac: 1,
47
+ hodo_line_width: 2.5,
48
+ background_line_width: 1.5,
49
+ height_cmap: HODO_CMAP
50
+ };
51
+ /** A class representing a field of hodograph plots */
45
52
  class Hodographs extends PlotComponent {
46
53
  /**
47
54
  * Create a field of hodographs
@@ -50,11 +57,10 @@ class Hodographs extends PlotComponent {
50
57
  */
51
58
  constructor(profile_field, opts) {
52
59
  super();
53
- opts = opts || {};
54
60
  this.profile_field = profile_field;
55
- this.bgcolor = opts.bgcolor || '#000000';
56
- this.thin_fac = opts.thin_fac || 1;
57
- this.hodo_scale = (HODO_BG_DIMS.BB_TEX_WIDTH - LINE_WIDTH / 2) / (HODO_BG_DIMS.BB_TEX_WIDTH * BG_MAX_RING_MAG);
61
+ this.opts = normalizeOptions(opts, hodograph_opt_defaults);
62
+ this.hodo_bg_texture = _createHodoBackgroundTexture(this.opts.background_line_width * LINE_WIDTH_MULTIPLIER);
63
+ this.hodo_scale = (HODO_BG_DIMS.BB_TEX_WIDTH - this.opts.background_line_width / 2) / (HODO_BG_DIMS.BB_TEX_WIDTH * BG_MAX_RING_MAG);
58
64
  this.bg_size = 140;
59
65
  this.gl_elems = null;
60
66
  this.line_elems = null;
@@ -72,7 +78,7 @@ class Hodographs extends PlotComponent {
72
78
  this.gl_elems.bg_billboard.updateField(field.getStormMotionGrid());
73
79
  const profiles = this.profile_field.profiles;
74
80
  const hodo_polyline = profiles.map(prof => {
75
- const zoom = getMinZoom(prof['jlat'], prof['ilon'], this.thin_fac);
81
+ const zoom = getMinZoom(prof['jlat'], prof['ilon'], this.opts.thin_fac);
76
82
  return {
77
83
  'offsets': [...prof['u']].map((u, ipt) => [u - prof['smu'], prof['v'][ipt] - prof['smv']]),
78
84
  'vertices': [...prof['u']].map(u => [prof['lon'], prof['lat']]),
@@ -80,9 +86,9 @@ class Hodographs extends PlotComponent {
80
86
  'data': [...prof['z']],
81
87
  };
82
88
  });
83
- const hodo_line = await PolylineCollection.make(gl, hodo_polyline, { line_width: 2.5, cmap: HODO_CMAP, offset_scale: this.hodo_scale * this.bg_size });
89
+ const hodo_line = await PolylineCollection.make(gl, hodo_polyline, { line_width: this.opts.hodo_line_width, cmap: this.opts.height_cmap, offset_scale: this.hodo_scale * this.bg_size });
84
90
  const sm_polyline = profiles.map(prof => {
85
- const zoom = getMinZoom(prof['jlat'], prof['ilon'], this.thin_fac);
91
+ const zoom = getMinZoom(prof['jlat'], prof['ilon'], this.opts.thin_fac);
86
92
  const sm_mag = Math.hypot(prof['smu'], prof['smv']);
87
93
  const sm_ang = Math.PI / 2 - Math.atan2(-prof['smv'], -prof['smu']);
88
94
  const buffer = 2;
@@ -93,7 +99,7 @@ class Hodographs extends PlotComponent {
93
99
  'zoom': zoom
94
100
  };
95
101
  });
96
- const sm_line = await PolylineCollection.make(gl, sm_polyline, { line_width: 1.5, color: this.bgcolor, offset_scale: this.hodo_scale * this.bg_size });
102
+ const sm_line = await PolylineCollection.make(gl, sm_polyline, { line_width: this.opts.background_line_width, color: this.opts.bgcolor, offset_scale: this.hodo_scale * this.bg_size });
97
103
  this.line_elems = {
98
104
  hodo_line: hodo_line, sm_line: sm_line
99
105
  };
@@ -103,12 +109,9 @@ class Hodographs extends PlotComponent {
103
109
  * Add the hodographs to a map
104
110
  */
105
111
  async onAdd(map, gl) {
106
- if (HODO_BG_TEXTURE === null) {
107
- HODO_BG_TEXTURE = _createHodoBackgroundTexture();
108
- }
109
- const bg_image = { 'format': gl.RGBA, 'type': gl.UNSIGNED_BYTE, 'image': HODO_BG_TEXTURE, 'mag_filter': gl.NEAREST };
112
+ const bg_image = { 'format': gl.RGBA, 'type': gl.UNSIGNED_BYTE, 'image': this.hodo_bg_texture, 'mag_filter': gl.NEAREST };
110
113
  const max_zoom = map.getMaxZoom();
111
- const bg_billboard = new BillboardCollection(this.profile_field.getStormMotionGrid(), this.thin_fac, max_zoom, bg_image, HODO_BG_DIMS, hex2rgb(this.bgcolor), this.bg_size * 0.004);
114
+ const bg_billboard = new BillboardCollection(this.profile_field.getStormMotionGrid(), this.opts.thin_fac, max_zoom, bg_image, HODO_BG_DIMS, this.bg_size * 0.004, { color: Color.fromHex(this.opts.bgcolor) });
112
115
  await bg_billboard.setup(gl);
113
116
  this.gl_elems = {
114
117
  gl: gl, map: map, bg_billboard: bg_billboard
@@ -22,8 +22,8 @@ interface PaintballOptions {
22
22
  */
23
23
  declare class Paintball<ArrayType extends TypedArray, MapType extends MapLikeType> extends PlotComponent<MapType> {
24
24
  private field;
25
- readonly colors: number[];
26
- readonly opacity: number;
25
+ readonly opts: Required<PaintballOptions>;
26
+ private readonly color_components;
27
27
  private gl_elems;
28
28
  private fill_texture;
29
29
  /**
package/lib/Paintball.js CHANGED
@@ -1,5 +1,6 @@
1
+ import { Color } from "./Color";
1
2
  import { PlotComponent, getGLFormatTypeAlignment } from "./PlotComponent";
2
- import { hex2rgba } from "./utils";
3
+ import { normalizeOptions } from "./utils";
3
4
  import { WGLProgram, WGLTexture } from "autumn-wgl";
4
5
  const paintball_vertex_shader_src = `uniform mat4 u_matrix;
5
6
  uniform int u_offset;
@@ -45,6 +46,10 @@ void main() {
45
46
  color.a = color.a * u_opacity;
46
47
  gl_FragColor = color;
47
48
  }`
49
+ const paintball_opt_defaults = {
50
+ colors: ['#000000'],
51
+ opacity: 1
52
+ };
48
53
  /**
49
54
  * 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
50
55
  * a field (such as simulated reflectivity greater than 40 dBZ), but could in theory be defined by any arbitrarily complicated method. In autumnplot-gl,
@@ -63,10 +68,8 @@ class Paintball extends PlotComponent {
63
68
  constructor(field, opts) {
64
69
  super();
65
70
  this.field = field;
66
- opts = opts !== undefined ? opts : {};
67
- const colors = opts.colors !== undefined ? [...opts.colors] : ['#000000'];
68
- this.colors = colors.reverse().map(color => hex2rgba(color)).flat();
69
- this.opacity = opts.opacity !== undefined ? opts.opacity : 1.;
71
+ this.opts = normalizeOptions(opts, paintball_opt_defaults);
72
+ this.color_components = [...this.opts.colors].reverse().map(color => Color.fromHex(color).toRGBATuple()).flat();
70
73
  this.gl_elems = null;
71
74
  this.fill_texture = null;
72
75
  }
@@ -119,7 +122,7 @@ class Paintball extends PlotComponent {
119
122
  if (matrix instanceof Float32Array)
120
123
  matrix = [...matrix];
121
124
  // Render to framebuffer
122
- 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': this.fill_texture });
125
+ gl_elems.program.use({ 'a_pos': gl_elems.vertices, 'a_tex_coord': gl_elems.texcoords }, { 'u_matrix': matrix, 'u_opacity': this.opts.opacity, 'u_colors': this.color_components, 'u_num_colors': this.opts.colors.length, 'u_offset': 0 }, { 'u_fill_sampler': this.fill_texture });
123
126
  gl.enable(gl.BLEND);
124
127
  gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
125
128
  gl_elems.program.draw();
@@ -11,11 +11,16 @@ function getGLFormatTypeAlignment(gl, is_float16) {
11
11
  if (is_float16) {
12
12
  const ext = gl.getExtension('OES_texture_half_float');
13
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.";
14
+ if (is_webgl2) {
15
+ format = gl.R16F;
16
+ type = gl.HALF_FLOAT;
17
+ }
18
+ else {
19
+ if (ext === null || ext_lin === null)
20
+ throw "Float16 data are not supported on this hardware. Try Float32 data instead.";
21
+ format = gl.LUMINANCE;
22
+ type = ext.HALF_FLOAT_OES;
16
23
  }
17
- format = is_webgl2 ? gl.R16F : gl.LUMINANCE;
18
- type = is_webgl2 ? gl.HALF_FLOAT : ext.HALF_FLOAT_OES;
19
24
  row_alignment = 2;
20
25
  }
21
26
  else {
@@ -4,7 +4,7 @@ import { PlotComponent } from './PlotComponent';
4
4
  declare abstract class PlotLayerBase<MapType extends MapLikeType> {
5
5
  readonly type: 'custom';
6
6
  readonly id: string;
7
- protected map: MapType;
7
+ protected map: MapType | null;
8
8
  constructor(id: string);
9
9
  abstract onAdd(map: MapType, gl: WebGLAnyRenderingContext): void;
10
10
  abstract render(gl: WebGLAnyRenderingContext, matrix: number[] | Float32Array): void;
@@ -239,6 +239,9 @@ function makePolylinesMiter(lines) {
239
239
  }
240
240
  */
241
241
  function makePolylines(lines) {
242
+ if (lines.length == 0) {
243
+ return { vertices: new Float32Array([]), extrusion: new Float32Array([]) };
244
+ }
242
245
  const n_points_per_vert = Object.fromEntries(Object.entries(lines[0]).map(([k, v]) => {
243
246
  let n_verts;
244
247
  if (typeof v === 'number') {
@@ -285,10 +288,10 @@ function makePolylines(lines) {
285
288
  return [v_ll.x, v_ll.y];
286
289
  });
287
290
  const has_offsets = line.offsets !== undefined;
288
- const extrusion_verts = has_offsets ? line.offsets : verts;
291
+ const extrusion_verts = line.offsets !== undefined ? line.offsets : verts;
289
292
  let pt_prev, pt_this = verts[0], pt_next = verts[1];
290
293
  let ept_prev, ept_this = extrusion_verts[0], ept_next = extrusion_verts[1];
291
- let len_prev, len_this = 0;
294
+ let len_prev, len_this = 0.0001;
292
295
  let [ext_x, ext_y] = compute_normal_vec(ept_this, ept_next, !has_offsets);
293
296
  ret.vertices[ilns.vertices++] = pt_this[0];
294
297
  ret.vertices[ilns.vertices++] = pt_this[1];
@@ -305,13 +308,13 @@ function makePolylines(lines) {
305
308
  len_this += Math.hypot(verts[ivt - 1][0] - verts[ivt][0], verts[ivt - 1][1] - verts[ivt][1]);
306
309
  ret.vertices[ilns.vertices++] = pt_prev[0];
307
310
  ret.vertices[ilns.vertices++] = pt_prev[1];
308
- ret.vertices[ilns.vertices++] = len_prev;
311
+ ret.vertices[ilns.vertices++] = -len_prev;
309
312
  ret.vertices[ilns.vertices++] = pt_prev[0];
310
313
  ret.vertices[ilns.vertices++] = pt_prev[1];
311
314
  ret.vertices[ilns.vertices++] = len_prev;
312
315
  ret.vertices[ilns.vertices++] = pt_this[0];
313
316
  ret.vertices[ilns.vertices++] = pt_this[1];
314
- ret.vertices[ilns.vertices++] = len_this;
317
+ ret.vertices[ilns.vertices++] = -len_this;
315
318
  ret.vertices[ilns.vertices++] = pt_this[0];
316
319
  ret.vertices[ilns.vertices++] = pt_this[1];
317
320
  ret.vertices[ilns.vertices++] = len_this;
@@ -329,7 +332,7 @@ function makePolylines(lines) {
329
332
  ret.vertices[ilns.vertices++] = len_this;
330
333
  ret.extrusion[ilns.extrusion++] = -ext_x;
331
334
  ret.extrusion[ilns.extrusion++] = -ext_y;
332
- if ('offsets' in ret) {
335
+ if (ret.offsets !== undefined && line.offsets !== undefined) {
333
336
  const offsets = line.offsets;
334
337
  let off_prev, off_this = offsets[0];
335
338
  ret.offsets[ilns.offsets++] = off_this[0];
@@ -349,7 +352,7 @@ function makePolylines(lines) {
349
352
  ret.offsets[ilns.offsets++] = off_this[0];
350
353
  ret.offsets[ilns.offsets++] = off_this[1];
351
354
  }
352
- if ('data' in ret) {
355
+ if (ret.data !== undefined && line.data !== undefined) {
353
356
  const data = line.data;
354
357
  let data_prev, data_this = data[0];
355
358
  ret.data[ilns.data++] = data_this;
@@ -363,7 +366,7 @@ function makePolylines(lines) {
363
366
  }
364
367
  ret.data[ilns.data++] = data_this;
365
368
  }
366
- if ('zoom' in ret) {
369
+ if (ret.zoom !== undefined && line.zoom !== undefined) {
367
370
  for (let ivt = 0; ivt < verts.length * 4 - 2; ivt++) {
368
371
  ret.zoom[ilns.zoom++] = line['zoom'];
369
372
  }
@@ -1,10 +1,18 @@
1
1
  import { LineData, WebGLAnyRenderingContext } from "./AutumnTypes";
2
2
  import { ColorMap } from "./Colormap";
3
+ /**
4
+ * A style to use to draw lines. The possible options are '-' for a solid line, '--' for a dashed line, ':' for a
5
+ * dotted line, '-.' for a dash-dot line, or you could pass a list of numbers (e.g., [1, 1, 1, 0, 1, 0]) to
6
+ * specify a custom dash scheme.
7
+ */
8
+ type LineStyle = "-" | "--" | ":" | "-." | number[];
9
+ declare function isLineStyle(obj: any): obj is LineStyle;
3
10
  interface PolylineCollectionOpts {
4
11
  offset_scale?: number;
5
12
  color?: string;
6
13
  cmap?: ColorMap;
7
14
  line_width?: number;
15
+ line_style?: LineStyle | number[];
8
16
  }
9
17
  declare class PolylineCollection {
10
18
  readonly width: number;
@@ -15,14 +23,13 @@ declare class PolylineCollection {
15
23
  private readonly offset;
16
24
  private readonly min_zoom;
17
25
  private readonly line_data;
18
- private readonly line_texture;
19
26
  private readonly color;
20
- private readonly cmap_min;
21
- private readonly cmap_max;
22
- private readonly index_map;
23
- private readonly cmap_nonlin_texture;
27
+ private readonly cmap_gpu;
28
+ private readonly dash_texture;
29
+ private readonly n_dash;
24
30
  private constructor();
25
31
  static make(gl: WebGLAnyRenderingContext, lines: LineData[], opts?: PolylineCollectionOpts): Promise<PolylineCollection>;
26
32
  render(gl: WebGLAnyRenderingContext, matrix: number[] | Float32Array, [map_width, map_height]: [number, number], map_zoom: number, map_bearing: number, map_pitch: number): void;
27
33
  }
28
- export { PolylineCollection };
34
+ export { PolylineCollection, isLineStyle };
35
+ export type { PolylineCollectionOpts, LineStyle };
@@ -1,12 +1,12 @@
1
- import { Float16Array } from "@petamoriken/float16";
2
1
  import { WGLBuffer, WGLProgram, WGLTexture } from "autumn-wgl";
3
- import { makeIndexMap, makeTextureImage } from "./Colormap";
4
- import { getGLFormatTypeAlignment, layer_worker } from "./PlotComponent";
5
- import { hex2rgba } from "./utils";
2
+ import { isWebGL2Ctx } from "./AutumnTypes";
3
+ import { ColorMap, ColorMapGPUInterface } from "./Colormap";
4
+ import { Color } from "./Color";
5
+ import { layer_worker } from "./PlotComponent";
6
6
  const polyline_vertex_src = `uniform mat4 u_matrix;
7
7
  uniform int u_offset;
8
8
 
9
- attribute vec2 a_pos;
9
+ attribute vec3 a_pos;
10
10
  attribute vec2 a_extrusion;
11
11
  attribute float a_data;
12
12
 
@@ -35,6 +35,9 @@ uniform lowp float u_offset_scale;
35
35
  varying highp float v_data;
36
36
  #endif
37
37
 
38
+ varying highp float v_dist;
39
+ varying lowp float v_cross;
40
+
38
41
  mat4 scalingMatrix(float x_scale, float y_scale, float z_scale) {
39
42
  return mat4(x_scale, 0.0, 0.0, 0.0,
40
43
  0.0, y_scale, 0.0, 0.0,
@@ -66,6 +69,8 @@ void main() {
66
69
  float globe_width = 1.;
67
70
  vec2 globe_offset = vec2(globe_width * float(u_offset), 0.);
68
71
 
72
+ v_dist = abs(a_pos.z);
73
+ v_cross = sign(a_pos.z);
69
74
  vec4 center_pos = u_matrix * vec4(a_pos.xy + globe_offset, 0.0, 1.0);
70
75
  vec4 offset = vec4(0.0, 0.0, 0.0, 0.0);
71
76
 
@@ -73,7 +78,7 @@ void main() {
73
78
  if (u_zoom >= a_min_zoom) {
74
79
  #endif
75
80
 
76
- vec2 offset_ext = u_line_width * 1.5 * a_extrusion;
81
+ vec2 offset_ext = u_line_width * 2. * a_extrusion;
77
82
 
78
83
  mat4 map_stretch_matrix = scalingMatrix(u_map_height / u_map_width, 1., 1.);
79
84
  mat4 rotation_matrix = rotationZMatrix(radians(u_map_bearing));
@@ -95,41 +100,68 @@ void main() {
95
100
  v_data = a_data;
96
101
  #endif
97
102
  }`
98
- const polyline_fragment_src = `#ifdef DATA
99
- varying highp float v_data;
103
+ const polyline_fragment_src = `
104
+ uniform sampler2D u_dash_sampler;
100
105
 
101
- uniform sampler2D u_cmap_sampler;
102
- uniform sampler2D u_cmap_nonlin_sampler;
103
- uniform highp float u_cmap_min;
104
- uniform highp float u_cmap_max;
105
- uniform int u_n_index;
106
- #else
106
+ #ifndef DATA
107
107
  uniform lowp vec4 u_color;
108
108
  #endif
109
109
 
110
- void main() {
111
- lowp vec4 color;
110
+ uniform lowp float u_line_width;
111
+ uniform lowp float u_zoom;
112
+ uniform int u_dash_pattern_length;
113
+
112
114
  #ifdef DATA
113
- lowp float index_buffer = 1. / (2. * float(u_n_index));
114
- lowp float normed_val = (v_data - u_cmap_min) / (u_cmap_max - u_cmap_min);
115
+ varying highp float v_data;
116
+ #endif
115
117
 
116
- if (normed_val < 0.0 || normed_val > 1.0) {
117
- discard;
118
- }
118
+ varying highp float v_dist;
119
+ varying lowp float v_cross;
119
120
 
120
- normed_val = index_buffer + normed_val * (1. - 2. * index_buffer);
121
- highp float nonlin_val = texture2D(u_cmap_nonlin_sampler, vec2(normed_val, 0.5)).r;
122
- color = texture2D(u_cmap_sampler, vec2(nonlin_val, 0.5));
121
+ void main() {
122
+ lowp float dash_x = fract(v_dist * 2e2 * pow(2., floor(u_zoom)) / float(u_dash_pattern_length));
123
+ lowp float dash = texture2D(u_dash_sampler, vec2(dash_x, 0.5)).r;
124
+
125
+ lowp vec4 color;
126
+ #ifdef DATA
127
+ color = apply_colormap(v_data);
123
128
  #else
124
129
  color = u_color;
125
130
  #endif
131
+
132
+ lowp float feather = clamp((1. - abs(v_cross)) * u_line_width, 0., 1.);
133
+ color.a *= (dash >= 1. ? 1. : 0.) * feather;
134
+
126
135
  gl_FragColor = color;
127
136
  }`
137
+ const dash_arrays = {
138
+ "-": [1],
139
+ "--": [1, 1, 1, 1, 0, 0],
140
+ ":": [1, 0],
141
+ "-.": [1, 1, 1, 0, 1, 0],
142
+ };
143
+ function isLineStyle(obj) {
144
+ return obj in dash_arrays || Array.isArray(obj) && obj.length > 0 && obj.map(e => typeof e === 'number').reduce((a, b) => a && b, true);
145
+ }
146
+ function makeDashTexture(gl, line_style) {
147
+ const dash_array = Array.isArray(line_style) ? line_style : dash_arrays[line_style];
148
+ const is_webgl2 = isWebGL2Ctx(gl);
149
+ const format = is_webgl2 ? gl.R8 : gl.LUMINANCE;
150
+ const type = gl.UNSIGNED_BYTE;
151
+ const row_alignment = 1;
152
+ const fill_image = { 'format': format, 'type': type,
153
+ 'width': dash_array.length, 'height': 1, 'image': new Uint8Array(dash_array.map(d => d > 0 ? 255 : 0)),
154
+ 'mag_filter': gl.NEAREST, 'row_alignment': row_alignment,
155
+ };
156
+ return [dash_array.length, new WGLTexture(gl, fill_image)];
157
+ }
128
158
  class PolylineCollection {
129
159
  constructor(gl, polyline, opts) {
130
160
  opts = opts === undefined ? {} : opts;
131
- this.color = opts.color === undefined ? [0., 0., 0., 1.] : hex2rgba(opts.color);
161
+ const color_hex = opts.color === undefined ? '#000000' : opts.color;
162
+ this.color = Color.fromHex(color_hex);
132
163
  const line_width = opts.line_width === undefined ? 1 : opts.line_width;
164
+ const line_style = opts.line_style === undefined ? '-' : opts.line_style;
133
165
  this.width = line_width;
134
166
  const shader_defines = [];
135
167
  this.vertices = new WGLBuffer(gl, polyline['vertices'], 3, gl.TRIANGLE_STRIP);
@@ -150,39 +182,21 @@ class PolylineCollection {
150
182
  else {
151
183
  this.min_zoom = null;
152
184
  }
153
- this.cmap_min = -3.40282347e+38;
154
- this.cmap_max = 3.40282347e+38;
185
+ let fragment_src = polyline_fragment_src;
155
186
  if (polyline.data !== undefined) {
156
- // TAS: this needs some cleanup (there's some repated code between here and the contour fills that should be combined?)
157
- this.min_zoom = new WGLBuffer(gl, polyline.zoom, 1, gl.TRIANGLE_STRIP);
158
187
  shader_defines.push('DATA');
159
- const { format: format_nonlin, type: type_nonlin, row_alignment: row_alignment_nonlin } = getGLFormatTypeAlignment(gl, true);
160
- let tex_image;
161
- if (opts.cmap === undefined) {
162
- tex_image = { 'format': gl.RGBA, 'type': gl.UNSIGNED_BYTE, 'width': 1, 'height': 1, 'image': new Uint8Array(this.color), 'mag_filter': gl.NEAREST };
163
- this.index_map = new Float16Array([0., 1.]);
164
- }
165
- else {
166
- tex_image = { 'format': gl.RGBA, 'type': gl.UNSIGNED_BYTE, 'image': makeTextureImage(opts.cmap), 'mag_filter': gl.NEAREST };
167
- this.cmap_min = opts.cmap.levels[0];
168
- this.cmap_max = opts.cmap.levels[opts.cmap.levels.length - 1];
169
- this.index_map = makeIndexMap(opts.cmap);
170
- }
171
- const cmap_nonlin_image = { 'format': format_nonlin, 'type': type_nonlin,
172
- 'width': this.index_map.length, 'height': 1,
173
- 'image': new Uint16Array(this.index_map.buffer),
174
- 'mag_filter': gl.LINEAR, 'row_alignment': row_alignment_nonlin,
175
- };
176
- this.cmap_nonlin_texture = new WGLTexture(gl, cmap_nonlin_image);
177
- this.line_texture = new WGLTexture(gl, tex_image);
178
- this.line_data = new WGLBuffer(gl, polyline['data'], 1, gl.TRIANGLE_STRIP);
188
+ this.line_data = new WGLBuffer(gl, polyline.data, 1, gl.TRIANGLE_STRIP);
189
+ const cmap = opts.cmap === undefined ? new ColorMap([0, 1], [color_hex], { overflow_color: color_hex, underflow_color: color_hex }) : opts.cmap;
190
+ this.cmap_gpu = new ColorMapGPUInterface(cmap);
191
+ this.cmap_gpu.setupShaderVariables(gl, gl.NEAREST);
192
+ fragment_src = ColorMapGPUInterface.applyShader(fragment_src);
179
193
  }
180
194
  else {
181
- this.line_texture = null;
182
195
  this.line_data = null;
183
- this.index_map = null;
196
+ this.cmap_gpu = null;
184
197
  }
185
- this.program = new WGLProgram(gl, polyline_vertex_src, polyline_fragment_src, { define: shader_defines });
198
+ [this.n_dash, this.dash_texture] = makeDashTexture(gl, line_style);
199
+ this.program = new WGLProgram(gl, polyline_vertex_src, fragment_src, { define: shader_defines });
186
200
  }
187
201
  static async make(gl, lines, opts) {
188
202
  const polylines = await layer_worker.makePolyLines(lines);
@@ -193,29 +207,27 @@ class PolylineCollection {
193
207
  matrix = [...matrix];
194
208
  const attributes = { 'a_pos': this.vertices, 'a_extrusion': this.extrusion };
195
209
  const uniforms = {
196
- 'u_matrix': matrix, 'u_line_width': this.width, 'u_map_width': map_width, 'u_map_height': map_height, 'u_map_bearing': map_bearing, 'u_offset': 0
210
+ 'u_matrix': matrix, 'u_line_width': this.width, 'u_map_width': map_width, 'u_map_height': map_height, 'u_map_bearing': map_bearing, 'u_offset': 0, 'u_zoom': map_zoom,
211
+ 'u_dash_pattern_length': this.n_dash
197
212
  };
198
- const textures = {};
199
- if (this.offset !== null) {
213
+ const textures = { 'u_dash_sampler': this.dash_texture };
214
+ if (this.offset !== null && this.scale !== null) {
200
215
  attributes['a_offset'] = this.offset;
201
216
  uniforms['u_offset_scale'] = this.scale * (map_height / map_width);
202
217
  }
203
218
  if (this.min_zoom !== null) {
204
219
  attributes['a_min_zoom'] = this.min_zoom;
205
- uniforms['u_zoom'] = map_zoom;
206
220
  }
207
- if (this.line_data !== null && this.line_texture !== null) {
221
+ if (this.line_data !== null) {
208
222
  attributes['a_data'] = this.line_data;
209
- textures['u_cmap_sampler'] = this.line_texture;
210
- textures['u_cmap_nonlin_sampler'] = this.cmap_nonlin_texture;
211
- uniforms['u_cmap_min'] = this.cmap_min;
212
- uniforms['u_cmap_max'] = this.cmap_max;
213
- uniforms['u_n_index'] = this.index_map.length;
214
223
  }
215
224
  else {
216
- uniforms['u_color'] = this.color;
225
+ uniforms['u_color'] = this.color.toRGBATuple();
217
226
  }
218
227
  this.program.use(attributes, uniforms, textures);
228
+ if (this.cmap_gpu !== null) {
229
+ this.cmap_gpu.bindShaderVariables(this.program);
230
+ }
219
231
  gl.enable(gl.BLEND);
220
232
  gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
221
233
  this.program.draw();
@@ -227,4 +239,4 @@ class PolylineCollection {
227
239
  this.program.draw();
228
240
  }
229
241
  }
230
- export { PolylineCollection };
242
+ export { PolylineCollection, isLineStyle };
@@ -0,0 +1,34 @@
1
+ import { WebGLAnyRenderingContext } from "./AutumnTypes";
2
+ import { MapLikeType } from "./Map";
3
+ import { PlotComponent } from "./PlotComponent";
4
+ interface EarthCoord {
5
+ lon: number;
6
+ lat: number;
7
+ }
8
+ interface ObsRawData<ObsFieldName extends string> {
9
+ coord: EarthCoord;
10
+ data: Record<ObsFieldName, number>;
11
+ }
12
+ type SPPositions = 'cl' | 'll' | 'lc' | 'lr' | 'cr' | 'ur' | 'uc' | 'ul';
13
+ type SPDataPosition<ObsFieldName extends string> = Record<ObsFieldName, SPPositions>;
14
+ declare class UnstructuredGrid {
15
+ readonly coords: EarthCoord[];
16
+ constructor(coords: EarthCoord[]);
17
+ getZoomThinning(thin_fac: number, map_max_zoom: number): number[];
18
+ }
19
+ declare class ObsField<ObsFieldName extends string> {
20
+ readonly grid: UnstructuredGrid;
21
+ readonly data: ObsRawData<ObsFieldName>[];
22
+ constructor(data: ObsRawData<ObsFieldName>[]);
23
+ }
24
+ interface StationPlotOpts {
25
+ }
26
+ declare class StationPlot<ObsFieldName extends string, MapType extends MapLikeType> extends PlotComponent<MapType> {
27
+ readonly field: ObsField<ObsFieldName>;
28
+ readonly data_positions: SPDataPosition<ObsFieldName>;
29
+ readonly opts: Required<StationPlotOpts>;
30
+ constructor(field: ObsField<ObsFieldName>, data_positions: SPDataPosition<ObsFieldName>, opts: StationPlotOpts);
31
+ onAdd(map: MapType, gl: WebGLAnyRenderingContext): Promise<void>;
32
+ render(gl: WebGLAnyRenderingContext, matrix: Float32Array | number[]): void;
33
+ }
34
+ export { StationPlot };