autumnplot-gl 3.1.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.
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, normalizeOptions } 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,13 +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
44
  const hodograph_opt_defaults = {
45
45
  bgcolor: '#000000',
46
- thin_fac: 1
46
+ thin_fac: 1,
47
+ hodo_line_width: 2.5,
48
+ background_line_width: 1.5,
49
+ height_cmap: HODO_CMAP
47
50
  };
48
- /** A class representing a a field of hodograph plots */
51
+ /** A class representing a field of hodograph plots */
49
52
  class Hodographs extends PlotComponent {
50
53
  /**
51
54
  * Create a field of hodographs
@@ -56,7 +59,8 @@ class Hodographs extends PlotComponent {
56
59
  super();
57
60
  this.profile_field = profile_field;
58
61
  this.opts = normalizeOptions(opts, hodograph_opt_defaults);
59
- this.hodo_scale = (HODO_BG_DIMS.BB_TEX_WIDTH - LINE_WIDTH / 2) / (HODO_BG_DIMS.BB_TEX_WIDTH * BG_MAX_RING_MAG);
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);
60
64
  this.bg_size = 140;
61
65
  this.gl_elems = null;
62
66
  this.line_elems = null;
@@ -82,7 +86,7 @@ class Hodographs extends PlotComponent {
82
86
  'data': [...prof['z']],
83
87
  };
84
88
  });
85
- 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 });
86
90
  const sm_polyline = profiles.map(prof => {
87
91
  const zoom = getMinZoom(prof['jlat'], prof['ilon'], this.opts.thin_fac);
88
92
  const sm_mag = Math.hypot(prof['smu'], prof['smv']);
@@ -95,7 +99,7 @@ class Hodographs extends PlotComponent {
95
99
  'zoom': zoom
96
100
  };
97
101
  });
98
- const sm_line = await PolylineCollection.make(gl, sm_polyline, { line_width: 1.5, color: this.opts.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 });
99
103
  this.line_elems = {
100
104
  hodo_line: hodo_line, sm_line: sm_line
101
105
  };
@@ -105,12 +109,9 @@ class Hodographs extends PlotComponent {
105
109
  * Add the hodographs to a map
106
110
  */
107
111
  async onAdd(map, gl) {
108
- if (HODO_BG_TEXTURE === null) {
109
- HODO_BG_TEXTURE = _createHodoBackgroundTexture();
110
- }
111
- 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 };
112
113
  const max_zoom = map.getMaxZoom();
113
- const bg_billboard = new BillboardCollection(this.profile_field.getStormMotionGrid(), this.opts.thin_fac, max_zoom, bg_image, HODO_BG_DIMS, hex2rgb(this.opts.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) });
114
115
  await bg_billboard.setup(gl);
115
116
  this.gl_elems = {
116
117
  gl: gl, map: map, bg_billboard: bg_billboard
package/lib/Paintball.js CHANGED
@@ -1,5 +1,6 @@
1
+ import { Color } from "./Color";
1
2
  import { PlotComponent, getGLFormatTypeAlignment } from "./PlotComponent";
2
- import { hex2rgba, normalizeOptions } 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;
@@ -68,7 +69,7 @@ class Paintball extends PlotComponent {
68
69
  super();
69
70
  this.field = field;
70
71
  this.opts = normalizeOptions(opts, paintball_opt_defaults);
71
- this.color_components = [...this.opts.colors].reverse().map(color => hex2rgba(color)).flat();
72
+ this.color_components = [...this.opts.colors].reverse().map(color => Color.fromHex(color).toRGBATuple()).flat();
72
73
  this.gl_elems = null;
73
74
  this.fill_texture = null;
74
75
  }
@@ -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;
@@ -288,10 +288,10 @@ function makePolylines(lines) {
288
288
  return [v_ll.x, v_ll.y];
289
289
  });
290
290
  const has_offsets = line.offsets !== undefined;
291
- const extrusion_verts = has_offsets ? line.offsets : verts;
291
+ const extrusion_verts = line.offsets !== undefined ? line.offsets : verts;
292
292
  let pt_prev, pt_this = verts[0], pt_next = verts[1];
293
293
  let ept_prev, ept_this = extrusion_verts[0], ept_next = extrusion_verts[1];
294
- let len_prev, len_this = 0;
294
+ let len_prev, len_this = 0.0001;
295
295
  let [ext_x, ext_y] = compute_normal_vec(ept_this, ept_next, !has_offsets);
296
296
  ret.vertices[ilns.vertices++] = pt_this[0];
297
297
  ret.vertices[ilns.vertices++] = pt_this[1];
@@ -308,13 +308,13 @@ function makePolylines(lines) {
308
308
  len_this += Math.hypot(verts[ivt - 1][0] - verts[ivt][0], verts[ivt - 1][1] - verts[ivt][1]);
309
309
  ret.vertices[ilns.vertices++] = pt_prev[0];
310
310
  ret.vertices[ilns.vertices++] = pt_prev[1];
311
- ret.vertices[ilns.vertices++] = len_prev;
311
+ ret.vertices[ilns.vertices++] = -len_prev;
312
312
  ret.vertices[ilns.vertices++] = pt_prev[0];
313
313
  ret.vertices[ilns.vertices++] = pt_prev[1];
314
314
  ret.vertices[ilns.vertices++] = len_prev;
315
315
  ret.vertices[ilns.vertices++] = pt_this[0];
316
316
  ret.vertices[ilns.vertices++] = pt_this[1];
317
- ret.vertices[ilns.vertices++] = len_this;
317
+ ret.vertices[ilns.vertices++] = -len_this;
318
318
  ret.vertices[ilns.vertices++] = pt_this[0];
319
319
  ret.vertices[ilns.vertices++] = pt_this[1];
320
320
  ret.vertices[ilns.vertices++] = len_this;
@@ -332,7 +332,7 @@ function makePolylines(lines) {
332
332
  ret.vertices[ilns.vertices++] = len_this;
333
333
  ret.extrusion[ilns.extrusion++] = -ext_x;
334
334
  ret.extrusion[ilns.extrusion++] = -ext_y;
335
- if ('offsets' in ret) {
335
+ if (ret.offsets !== undefined && line.offsets !== undefined) {
336
336
  const offsets = line.offsets;
337
337
  let off_prev, off_this = offsets[0];
338
338
  ret.offsets[ilns.offsets++] = off_this[0];
@@ -352,7 +352,7 @@ function makePolylines(lines) {
352
352
  ret.offsets[ilns.offsets++] = off_this[0];
353
353
  ret.offsets[ilns.offsets++] = off_this[1];
354
354
  }
355
- if ('data' in ret) {
355
+ if (ret.data !== undefined && line.data !== undefined) {
356
356
  const data = line.data;
357
357
  let data_prev, data_this = data[0];
358
358
  ret.data[ilns.data++] = data_this;
@@ -366,7 +366,7 @@ function makePolylines(lines) {
366
366
  }
367
367
  ret.data[ilns.data++] = data_this;
368
368
  }
369
- if ('zoom' in ret) {
369
+ if (ret.zoom !== undefined && line.zoom !== undefined) {
370
370
  for (let ivt = 0; ivt < verts.length * 4 - 2; ivt++) {
371
371
  ret.zoom[ilns.zoom++] = line['zoom'];
372
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 };
@@ -0,0 +1,73 @@
1
+ import { kdTree } from "kd-tree-javascript";
2
+ import { LngLat } from "./Map";
3
+ import { PlotComponent } from "./PlotComponent";
4
+ import { getMinZoom, normalizeOptions } from "./utils";
5
+ class UnstructuredGrid {
6
+ constructor(coords) {
7
+ this.coords = coords;
8
+ }
9
+ getZoomThinning(thin_fac, map_max_zoom) {
10
+ let min_label_lon = null, min_label_lat = null, max_label_lon = null, max_label_lat = null;
11
+ this.coords.forEach(coord => {
12
+ if (min_label_lon === null || coord.lon < min_label_lon)
13
+ min_label_lon = coord.lon;
14
+ if (max_label_lon === null || coord.lon > max_label_lon)
15
+ max_label_lon = coord.lon;
16
+ if (min_label_lat === null || coord.lat < min_label_lat)
17
+ min_label_lat = coord.lat;
18
+ if (max_label_lat === null || coord.lat > max_label_lat)
19
+ max_label_lat = coord.lat;
20
+ });
21
+ if (min_label_lon === null || min_label_lat === null || max_label_lon === null || max_label_lat === null)
22
+ return [];
23
+ const kd_nodes = this.coords.map(c => ({ lon: c.lon, lat: c.lat, min_zoom: map_max_zoom }));
24
+ const tree = new kdTree(kd_nodes, (a, b) => Math.hypot(a.lon - b.lon, a.lat - b.lat), ['lon', 'lat']);
25
+ const { x: min_label_x, y: max_label_y } = new LngLat(min_label_lon, min_label_lat).toMercatorCoord();
26
+ const { x: max_label_x, y: min_label_y } = new LngLat(max_label_lon, max_label_lat).toMercatorCoord();
27
+ const thin_grid_width = max_label_x - min_label_x;
28
+ const thin_grid_height = max_label_y - min_label_y;
29
+ const ni_thin_grid = Math.round(thin_grid_width * thin_fac); // thin_fac was 4 / contour_label_spacing for the contour labels
30
+ const nj_thin_grid = Math.round(thin_grid_height * thin_fac);
31
+ const thin_grid_xs = [];
32
+ const thin_grid_ys = [];
33
+ for (let idx = 0; idx < ni_thin_grid; idx++) {
34
+ thin_grid_xs.push(min_label_x + (idx / ni_thin_grid) * thin_grid_width);
35
+ }
36
+ for (let jdy = 0; jdy < nj_thin_grid; jdy++) {
37
+ thin_grid_ys.push(min_label_y + (jdy / nj_thin_grid) * thin_grid_height);
38
+ }
39
+ for (let idx = 0; idx < ni_thin_grid; idx++) {
40
+ for (let jdy = 0; jdy < nj_thin_grid; jdy++) {
41
+ const zoom = getMinZoom(jdy, idx, Math.pow(2, map_max_zoom));
42
+ const grid_x = thin_grid_xs[idx];
43
+ const grid_y = thin_grid_ys[jdy];
44
+ const ll = LngLat.fromMercatorCoord(grid_x, grid_y);
45
+ const [label, dist] = tree.nearest({ lon: ll.lng, lat: ll.lat, min_zoom: 0 }, 1)[0];
46
+ label.min_zoom = zoom;
47
+ }
48
+ }
49
+ return kd_nodes.map(n => n.min_zoom);
50
+ }
51
+ }
52
+ class ObsField {
53
+ constructor(data) {
54
+ this.grid = new UnstructuredGrid(data.map(d => d.coord));
55
+ this.data = data;
56
+ }
57
+ }
58
+ const station_plot_opts_defaults = {};
59
+ class StationPlot extends PlotComponent {
60
+ constructor(field, data_positions, opts) {
61
+ super();
62
+ this.field = field;
63
+ this.data_positions = data_positions;
64
+ this.opts = normalizeOptions(opts, station_plot_opts_defaults);
65
+ }
66
+ async onAdd(map, gl) {
67
+ }
68
+ render(gl, matrix) {
69
+ if (matrix instanceof Float32Array)
70
+ matrix = [...matrix];
71
+ }
72
+ }
73
+ export { StationPlot };
@@ -1,4 +1,5 @@
1
1
  import { WebGLAnyRenderingContext } from "./AutumnTypes";
2
+ import { Color } from "./Color";
2
3
  import { WGLBuffer, WGLProgram, WGLTexture } from "autumn-wgl";
3
4
  interface TextSpec {
4
5
  lat: number;
@@ -12,8 +13,8 @@ interface TextCollectionOptions {
12
13
  horizontal_align?: HorizontalAlign;
13
14
  vertical_align?: VerticalAlign;
14
15
  font_size?: number;
15
- text_color?: [number, number, number];
16
- halo_color?: [number, number, number];
16
+ text_color?: Color;
17
+ halo_color?: Color;
17
18
  halo?: boolean;
18
19
  }
19
20
  declare class TextCollection {