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
@@ -1,8 +1,9 @@
1
1
  import { RenderMethodArg, WebGLAnyRenderingContext } from "./AutumnTypes";
2
2
  import { MapLikeType } from "./Map";
3
3
  import { PlotComponent } from "./PlotComponent";
4
- import { Grid } from "./Grid";
4
+ import { AutoZoomGrid } from "./grids/AutoZoom";
5
5
  import { RawObsField } from "./RawField";
6
+ import { ColorMap } from "./Colormap";
6
7
  /**
7
8
  * Positions around the station plot at which to draw the various elements
8
9
  *
@@ -19,6 +20,7 @@ import { RawObsField } from "./RawField";
19
20
  * | `'c'` | center |
20
21
  */
21
22
  type SPPosition = 'cl' | 'll' | 'lc' | 'lr' | 'cr' | 'ur' | 'uc' | 'ul' | 'c';
23
+ /** Configuration for numerical values on station plots */
22
24
  interface SPNumberConfig {
23
25
  type: 'number';
24
26
  /**
@@ -30,6 +32,21 @@ interface SPNumberConfig {
30
32
  * @default '#000000'
31
33
  */
32
34
  color?: string;
35
+ /**
36
+ * A colormap to use for coloring numeric values
37
+ * @default null
38
+ */
39
+ cmap?: ColorMap | null;
40
+ /**
41
+ * Whether to draw a halo (outline) around the number
42
+ * @default true;
43
+ */
44
+ halo?: boolean;
45
+ /**
46
+ * The color to use for the halo (outline)
47
+ * @default '#ffffff'
48
+ */
49
+ halo_color?: string;
33
50
  /**
34
51
  * A function that properly formats the number for display
35
52
  * @example (val) => val === null ? '' : val.toFixed(0)
@@ -38,22 +55,34 @@ interface SPNumberConfig {
38
55
  */
39
56
  formatter?: (val: number | null) => string;
40
57
  }
58
+ /** Configuration for strings on station plots */
41
59
  interface SPStringConfig {
42
60
  type: 'string';
43
61
  /**
44
- * The position on the station plot at which to place the number
62
+ * The position on the station plot at which to place the string
45
63
  */
46
64
  pos: SPPosition;
47
65
  /**
48
- * The color to use to draw the number
66
+ * The color to use to draw the string
49
67
  * @default '#000000'
50
68
  */
51
69
  color?: string;
70
+ /**
71
+ * Whether to draw a halo (outline) around the string
72
+ * @default true;
73
+ */
74
+ halo?: boolean;
75
+ /**
76
+ * The color to use for the halo (outline)
77
+ * @default '#ffffff'
78
+ */
79
+ halo_color?: string;
52
80
  }
81
+ /** Configuration for wind barbs on station plots */
53
82
  interface SPBarbConfig {
54
83
  type: 'barb';
55
84
  /**
56
- * The color to use to draw the number
85
+ * The color to use to draw the barb
57
86
  * @default '#000000'
58
87
  */
59
88
  color?: string;
@@ -67,18 +96,30 @@ interface SPBarbConfig {
67
96
  * Accepted symbol codes for sky cover and present weather symbols
68
97
  */
69
98
  type SPSymbol = ('0/8' | '1/8' | '2/8' | '3/8' | '4/8' | '5/8' | '6/8' | '7/8' | '8/8' | 'clr' | 'few' | 'sct' | 'bkn' | 'ovc' | 'obsc' | 'va' | 'fu' | 'hz' | 'du' | 'bldu' | 'sa' | 'blsa' | 'vcblsa' | 'vcbldu' | 'blpy' | 'po' | 'vcpo' | 'vcds' | 'vcss' | 'br' | 'bcbr' | 'bc' | 'mifg' | 'vcts' | 'virga' | 'vcsh' | 'ts' | 'thdr' | 'vctshz' | 'tsfzfg' | 'tsbr' | 'tsdz' | 'vctsup' | '-tsup' | 'tsup' | '+tsup' | 'sq' | 'fc' | '+fc' | 'ds' | 'ss' | 'drsa' | 'drdu' | '+ds' | '+ss' | 'drsn' | '+drsn' | '-blsn' | 'blsn' | '+blsn' | 'vcblsn' | 'vcfg' | 'bcfg' | 'prfg' | 'fg' | 'fzfg' | '-vctsdz' | '-dz' | '-dzbr' | 'vctsdz' | 'dz' | '+vctsdz' | '+dz' | '-fzdz' | '-fzdzsn' | 'fzdz' | '+fzdz' | 'fzdzsn' | '-dzra' | 'dzra' | '+dzra' | '-ra' | '-rabr' | 'ra' | 'rabr' | 'rafg' | 'vcra' | '+ra' | '-fzra' | '-fzrasn' | '-fzrabr' | '-fzrapl' | '-fzrasnpl' | 'tsfzrapl' | '-tsfzra' | 'fzra' | '+fzra' | 'fzrasn' | 'tsfzra' | '-dzsn' | '-rasn' | '-snra' | '-sndz' | 'rasn' | '+rasn' | 'snra' | 'dzsn' | 'sndz' | '+dzsn' | '+sndz' | '-sn' | '-snbr' | 'sn' | '+sn' | '-snsg' | 'sg' | '-sg' | 'ic' | '-fzdzpl' | '-fzdzplsn' | 'fzdzpl' | '-fzraplsn' | 'fzrapl' | '+fzrapl' | '-rapl' | '-rasnpl' | '-raplsn' | '+rapl' | 'rapl' | '-snpl' | 'snpl' | '-pl' | 'pl' | '-plsn' | '-plra' | 'plra' | '-pldz' | '+pl' | 'plsn' | 'plup' | '+plsn' | '-sh' | '-shra' | 'sh' | 'shra' | '+sh' | '+shra' | '-shrasn' | '-shsnra' | '+shrabr' | 'shrasn' | '+shrasn' | 'shsnra' | '+shsnra' | '-shsn' | 'shsn' | '+shsn' | '-gs' | '-shgs' | 'fzraplgs' | '-sngs' | 'gsplsn' | 'gspl' | 'plgssn' | 'gs' | 'shgs' | '+gs' | '+shgs' | '-gr' | '-shgr' | '-sngr' | 'gr' | 'shgr' | '+gr' | '+shgr' | '-tsrasn' | 'tsrasn' | '-tssnra' | 'tssnra' | '-vctsra' | '-tsra' | 'tsra' | '-tsdz' | 'vctsra' | 'tspl' | '-tssn' | '-tspl' | 'tssn' | '-vctssn' | 'vctssn' | 'tsplsn' | 'tssnpl' | '-tssnpl' | '-tsragr' | 'tsrags' | 'tsragr' | 'tsgs' | 'tsgr' | '+tsfzrapl' | '+vctsra' | '+tsra' | '+tsfzra' | '+tssn' | '+tspl' | '+tsplsn' | '+vctssn' | 'tssa' | 'tsds' | 'tsdu' | '+tsgs' | '+tsgr' | '+tsrags' | '+tsragr' | 'in' | '-up' | 'up' | '+up' | '-fzup' | 'fzup' | '+fzup');
99
+ /** Configuration for symbols on station plots */
70
100
  interface SPSymbolConfig {
71
101
  type: 'symbol';
72
102
  /**
73
- * The position on the station plot at which to place the number
103
+ * The position on the station plot at which to place the symbol
74
104
  */
75
105
  pos: SPPosition;
76
106
  /**
77
- * The color to use to draw the number
107
+ * The color to use to draw the symbol
78
108
  * @default '#000000'
79
109
  */
80
- color?: string;
110
+ color?: string | ((symbol: SPSymbol | null, category: SPSymbolCategory) => string);
111
+ /**
112
+ * Whether to draw a halo (outline) around the string
113
+ * @default true;
114
+ */
115
+ halo?: boolean;
116
+ /**
117
+ * The color to use for the halo (outline)
118
+ * @default '#ffffff'
119
+ */
120
+ halo_color?: string;
81
121
  }
122
+ /** Configuration for station plot sub-elements */
82
123
  type SPConfig = SPNumberConfig | SPStringConfig | SPBarbConfig | SPSymbolConfig;
83
124
  /**
84
125
  * Configuration for station data plots
@@ -98,6 +139,7 @@ type SPConfig = SPNumberConfig | SPStringConfig | SPBarbConfig | SPSymbolConfig;
98
139
  * }
99
140
  */
100
141
  type SPDataConfig<ObsFieldName extends string> = Record<ObsFieldName, SPConfig>;
142
+ /** Options for {@link StationPlot} components */
101
143
  interface StationPlotOptions<ObsFieldName extends string> {
102
144
  config: SPDataConfig<ObsFieldName>;
103
145
  /**
@@ -120,15 +162,40 @@ interface StationPlotOptions<ObsFieldName extends string> {
120
162
  */
121
163
  font_url_template?: string;
122
164
  }
123
- declare class StationPlot<GridType extends Grid, MapType extends MapLikeType, ObsFieldName extends string> extends PlotComponent<MapType> {
165
+ type SPSymbolCategory = 'freezing_rain' | 'sleet' | 'snow' | 'rain' | 'blowing_dust' | 'thunder' | 'fog' | 'none';
166
+ /**
167
+ * Station model plots for observed data
168
+ *
169
+ * ## Grid Compatibility
170
+ * - :white_check_mark: `PlateCarreeGrid`
171
+ * - :white_check_mark: `PlateCarreeRotatedGrid`
172
+ * - :white_check_mark: `LambertGrid`
173
+ * - :white_check_mark: `UnstructuredGrid`
174
+ * - :x: `RadarSweepGrid`
175
+ * - :x: `Geostationary`
176
+ *
177
+ * @example
178
+ * // Specify how to set up the station plot
179
+ * const station_plot_locs = {
180
+ * tmpf: {type: 'number', pos: 'ul', color: '#cc0000', formatter: val => val === null ? '' : val.toFixed(0)},
181
+ * dwpf: {type: 'number', pos: 'll', color: '#00aa00', formatter: val => val === null ? '' : val.toFixed(0)},
182
+ * wind: {type: 'barb', pos: 'c'},
183
+ * preswx: {type: 'symbol', pos: 'cl', color: '#ff00ff'},
184
+ * skyc: {type: 'symbol', pos: 'c'},
185
+ * };
186
+ *
187
+ * // Create the station plot
188
+ * const station_plot = new StationPlot(obs_field, {config: station_plot_locs, thin_fac: 8, font_size: 14});
189
+ */
190
+ declare class StationPlot<GridType extends AutoZoomGrid, MapType extends MapLikeType, ObsFieldName extends string> extends PlotComponent<MapType> {
124
191
  private field;
125
192
  readonly opts: Required<StationPlotOptions<ObsFieldName>>;
126
193
  private gl_elems;
127
194
  private text_components;
128
195
  /**
129
- *
130
- * @param field
131
- * @param opts
196
+ * Create station plots
197
+ * @param field - A field containing the observed data
198
+ * @param opts - Various options for the station plots
132
199
  */
133
200
  constructor(field: RawObsField<GridType, ObsFieldName>, opts: StationPlotOptions<ObsFieldName>);
134
201
  /**
@@ -76,11 +76,53 @@ function positionToAlignmentAndOffset(pos, off_size) {
76
76
  }
77
77
  return { horizontal_align: ha, vertical_align: va, offset_x: xoff, offset_y: yoff };
78
78
  }
79
+ function symbolCategory(symbol) {
80
+ const sym = symbol.toLowerCase();
81
+ if (sym.includes('fzra') || (sym.includes('fz') && sym.includes('ra')))
82
+ return 'freezing_rain';
83
+ if (sym.includes('pl'))
84
+ return 'sleet';
85
+ if (sym.includes('sn') || sym.includes('sg') || sym.includes('gs'))
86
+ return 'snow';
87
+ if (sym.includes('ra') || sym.includes('dz'))
88
+ return 'rain';
89
+ if (sym.includes('du') || sym.includes('ds') || sym.includes('ss') || sym.includes('blsa') || sym.includes('bldu') || sym.includes('blpy'))
90
+ return 'blowing_dust';
91
+ if (sym.includes('ts') || sym.includes('thdr'))
92
+ return 'thunder';
93
+ if (sym.includes('fg'))
94
+ return 'fog';
95
+ return 'none';
96
+ }
97
+ /**
98
+ * Station model plots for observed data
99
+ *
100
+ * ## Grid Compatibility
101
+ * - :white_check_mark: `PlateCarreeGrid`
102
+ * - :white_check_mark: `PlateCarreeRotatedGrid`
103
+ * - :white_check_mark: `LambertGrid`
104
+ * - :white_check_mark: `UnstructuredGrid`
105
+ * - :x: `RadarSweepGrid`
106
+ * - :x: `Geostationary`
107
+ *
108
+ * @example
109
+ * // Specify how to set up the station plot
110
+ * const station_plot_locs = {
111
+ * tmpf: {type: 'number', pos: 'ul', color: '#cc0000', formatter: val => val === null ? '' : val.toFixed(0)},
112
+ * dwpf: {type: 'number', pos: 'll', color: '#00aa00', formatter: val => val === null ? '' : val.toFixed(0)},
113
+ * wind: {type: 'barb', pos: 'c'},
114
+ * preswx: {type: 'symbol', pos: 'cl', color: '#ff00ff'},
115
+ * skyc: {type: 'symbol', pos: 'c'},
116
+ * };
117
+ *
118
+ * // Create the station plot
119
+ * const station_plot = new StationPlot(obs_field, {config: station_plot_locs, thin_fac: 8, font_size: 14});
120
+ */
79
121
  class StationPlot extends PlotComponent {
80
122
  /**
81
- *
82
- * @param field
83
- * @param opts
123
+ * Create station plots
124
+ * @param field - A field containing the observed data
125
+ * @param opts - Various options for the station plots
84
126
  */
85
127
  constructor(field, opts) {
86
128
  super();
@@ -105,31 +147,51 @@ class StationPlot extends PlotComponent {
105
147
  if (font_url_template === undefined)
106
148
  throw "The map style doesn't have any glyph information. Please pass the font_url_template option to StationPlot";
107
149
  const font_url = font_url_template.replace('{fontstack}', this.opts.font_face);
150
+ const coords = this.field.grid.getEarthCoords();
151
+ const zoom = this.field.grid.getMinVisibleZoom(this.opts.thin_fac);
108
152
  let ibarb = 0;
109
- const sub_component_promises = Object.entries(this.opts.config).map(async ([k_, config]) => {
153
+ let sub_component_promises = [];
154
+ Object.entries(this.opts.config).forEach(async ([k_, config]) => {
110
155
  const k = k_;
111
156
  if (config.type == 'number' || config.type == 'string') {
112
157
  const pos = config.pos;
113
- const color_opt = config.color;
114
- const color = color_opt === undefined ? Color.fromHex('#000000') : Color.normalizeColor(color_opt);
115
- const coords = this.field.grid.getEarthCoords();
116
- const zoom = this.field.grid.getMinVisibleZoom(this.opts.thin_fac);
158
+ const color = config.color === undefined ? Color.fromHex('#000000') : Color.normalizeColor(config.color);
159
+ const halo_color = config.halo_color === undefined ? Color.fromHex('#ffffff') : Color.normalizeColor(config.halo_color);
160
+ const halo = config.halo === undefined ? true : config.halo;
161
+ let cmap = null;
117
162
  let text_specs;
118
163
  if (config.type == 'number') {
164
+ cmap = config.cmap === undefined ? null : config.cmap;
119
165
  const comp = this.field.getScalar(k);
120
166
  const formatter = config.formatter === undefined ? (val) => val === null ? 'null' : val.toString() : config.formatter;
121
- text_specs = comp.map((v, i) => ({ text: formatter(v), lat: coords.lats[i], lon: coords.lons[i], min_zoom: zoom[i] }));
167
+ text_specs = comp.map((v, i) => {
168
+ const spec = {
169
+ text: formatter(v),
170
+ lat: coords.lats[i],
171
+ lon: coords.lons[i],
172
+ min_zoom: zoom[i],
173
+ };
174
+ if (v !== null)
175
+ spec.data_value = v;
176
+ return spec;
177
+ });
122
178
  }
123
179
  else {
124
180
  const comp = this.field.getStrings(k);
125
- text_specs = comp.map((v, i) => ({ text: v === null ? '' : v, lat: coords.lats[i], lon: coords.lons[i], min_zoom: zoom[i] }));
181
+ text_specs = comp.map((v, i) => ({
182
+ text: v === null ? '' : v,
183
+ lat: coords.lats[i],
184
+ lon: coords.lons[i],
185
+ min_zoom: zoom[i],
186
+ }));
126
187
  }
127
188
  const tc_opts = {
128
189
  ...positionToAlignmentAndOffset(pos),
129
- font_size: this.opts.font_size, halo: true,
130
- text_color: color, halo_color: Color.fromHex('#ffffff'),
190
+ font_size: this.opts.font_size, halo: halo,
191
+ text_color: color, halo_color: halo_color,
192
+ cmap: cmap
131
193
  };
132
- return await TextCollection.make(gl, text_specs, font_url, tc_opts);
194
+ sub_component_promises.push([TextCollection.make(gl, text_specs, font_url, tc_opts)]);
133
195
  }
134
196
  else if (config.type == 'barb') {
135
197
  const comp = this.field.getVector(k);
@@ -138,28 +200,49 @@ class StationPlot extends PlotComponent {
138
200
  }
139
201
  else if (config.type == 'symbol') {
140
202
  const pos = config.pos;
141
- const color_opt = config.color;
142
- const color = color_opt === undefined ? Color.fromHex('#000000') : Color.normalizeColor(color_opt);
203
+ const color = config.color === undefined ? '#000000' : config.color;
204
+ const halo_color = config.halo_color === undefined ? Color.fromHex('#ffffff') : Color.normalizeColor(config.halo_color);
205
+ const halo = config.halo === undefined ? true : config.halo;
143
206
  const comp = this.field.getStrings(k);
144
- const coords = this.field.grid.getEarthCoords();
145
- const zoom = this.field.grid.getMinVisibleZoom(this.opts.thin_fac);
146
207
  const wxsym_font_url = font_url_template.replace('{fontstack}', 'wx_symbols');
147
- const text_specs = comp.map((v, i) => ({ text: v === null ? '' : String.fromCharCode(SYMBOLS[v]),
148
- lat: coords.lats[i], lon: coords.lons[i], min_zoom: zoom[i] }));
149
- const tc_opts = {
150
- ...positionToAlignmentAndOffset(pos),
151
- font_size: this.opts.font_size, halo: true,
152
- text_color: color, halo_color: Color.fromHex('#ffffff'),
153
- };
154
- if (tc_opts.offset_x !== undefined)
155
- tc_opts.offset_x -= 3;
156
- return await TextCollection.make(gl, text_specs, wxsym_font_url, tc_opts);
208
+ const text_spec_data = [];
209
+ comp.forEach((v, i) => {
210
+ const v_cat = v === null ? 'none' : symbolCategory(v);
211
+ const text_color = typeof color === 'string' ? color : color(v, v_cat);
212
+ const text_spec_data_filtered = text_spec_data.filter(tsd => tsd.color == text_color);
213
+ let text_spec_color;
214
+ if (text_spec_data_filtered.length == 0) {
215
+ text_spec_color = { specs: [], color: text_color };
216
+ text_spec_data.push(text_spec_color);
217
+ }
218
+ else {
219
+ text_spec_color = text_spec_data_filtered[0];
220
+ }
221
+ text_spec_color.specs.push({
222
+ text: v === null ? '' : String.fromCharCode(SYMBOLS[v]),
223
+ lat: coords.lats[i],
224
+ lon: coords.lons[i],
225
+ min_zoom: zoom[i],
226
+ });
227
+ });
228
+ const promises = [];
229
+ text_spec_data.forEach(tsd => {
230
+ const tc_opts = {
231
+ ...positionToAlignmentAndOffset(pos),
232
+ font_size: this.opts.font_size, halo: halo,
233
+ text_color: Color.normalizeColor(tsd.color), halo_color: halo_color,
234
+ };
235
+ if (tc_opts.offset_x !== undefined)
236
+ tc_opts.offset_x -= 3;
237
+ promises.push(TextCollection.make(gl, tsd.specs, wxsym_font_url, tc_opts));
238
+ });
239
+ sub_component_promises.push(promises);
157
240
  }
158
241
  else {
159
242
  throw `Unknown station plot configuration type ${config.type}`;
160
243
  }
161
244
  });
162
- this.text_components = (await Promise.all(sub_component_promises)).filter((c) => c !== undefined);
245
+ this.text_components = await Promise.all(sub_component_promises.map(p => Promise.all(p)));
163
246
  map.triggerRepaint();
164
247
  }
165
248
  /** @internal */
@@ -192,12 +275,12 @@ class StationPlot extends PlotComponent {
192
275
  const map_height = gl_elems.map.getCanvas().height;
193
276
  const map_zoom = gl_elems.map.getZoom();
194
277
  let itext = 0, ibarb = 0;
195
- Object.values(this.opts.config).forEach(comp => {
278
+ Object.values(this.opts.config).forEach((comp, idx) => {
196
279
  if (comp.type == 'barb') {
197
280
  barb_components[ibarb++].render(gl, arg);
198
281
  }
199
282
  else {
200
- text_components[itext++].render(gl, arg, [map_width, map_height], map_zoom);
283
+ text_components[itext++].forEach(tc => tc.render(gl, arg, [map_width, map_height], map_zoom));
201
284
  }
202
285
  });
203
286
  }
@@ -1,5 +1,6 @@
1
1
  import { RenderMethodArg, WebGLAnyRenderingContext } from "./AutumnTypes";
2
2
  import { Color } from "./Color";
3
+ import { ColorMap, ColorMapGPUInterface } from "./Colormap";
3
4
  import { ShaderProgramManager } from "./ShaderManager";
4
5
  import { WGLBuffer, WGLTexture } from "autumn-wgl";
5
6
  interface TextSpec {
@@ -7,6 +8,7 @@ interface TextSpec {
7
8
  lon: number;
8
9
  text: string;
9
10
  min_zoom?: number;
11
+ data_value?: number;
10
12
  }
11
13
  type HorizontalAlign = 'left' | 'center' | 'right';
12
14
  type VerticalAlign = 'baseline' | 'middle' | 'top';
@@ -15,6 +17,7 @@ interface TextCollectionOptions {
15
17
  vertical_align?: VerticalAlign;
16
18
  font_size?: number;
17
19
  text_color?: Color;
20
+ cmap?: ColorMap | null;
18
21
  halo_color?: Color;
19
22
  halo?: boolean;
20
23
  offset_x?: number;
@@ -25,7 +28,9 @@ declare class TextCollection {
25
28
  readonly anchors: WGLBuffer;
26
29
  readonly offsets: WGLBuffer;
27
30
  readonly texcoords: WGLBuffer;
31
+ readonly data: WGLBuffer | null;
28
32
  readonly texture: WGLTexture;
33
+ readonly cmap_gpu: ColorMapGPUInterface | null;
29
34
  readonly opts: Required<TextCollectionOptions>;
30
35
  private constructor();
31
36
  static make(gl: WebGLAnyRenderingContext, text_locs: TextSpec[], fontstack_url_template: string, opts?: TextCollectionOptions): Promise<TextCollection>;
@@ -1,5 +1,6 @@
1
1
  import { getRendererData, isWebGL2Ctx } from "./AutumnTypes";
2
2
  import { Color } from "./Color";
3
+ import { ColorMap, ColorMapGPUInterface } from "./Colormap";
3
4
  import { LngLat } from "./Map";
4
5
  import { ShaderProgramManager } from "./ShaderManager";
5
6
  import { Cache, normalizeOptions } from "./utils";
@@ -18,8 +19,16 @@ in vec3 a_pos;
18
19
  in vec2 a_offset;
19
20
  in vec2 a_tex_coord;
20
21
 
22
+ #ifdef DATA
23
+ in highp float a_value;
24
+ #endif
25
+
21
26
  out highp vec2 v_tex_coord;
22
27
 
28
+ #ifdef DATA
29
+ out highp float v_value;
30
+ #endif
31
+
23
32
  mat4 scalingMatrix(float x_scale, float y_scale, float z_scale) {
24
33
  return mat4(x_scale, 0.0, 0.0, 0.0,
25
34
  0.0, y_scale, 0.0, 0.0,
@@ -41,14 +50,25 @@ void main() {
41
50
 
42
51
  gl_Position = projectTile(a_pos.xy + globe_offset) + u_font_size / 12. * 1.5 * map_stretch_matrix * vec4(offset, 0., 0.);
43
52
  v_tex_coord = a_tex_coord;
53
+
54
+ #ifdef DATA
55
+ v_value = a_value;
56
+ #endif
44
57
  }`
45
58
  const text_fragment_shader_src = `#version 300 es
46
59
 
47
60
  in highp vec2 v_tex_coord;
61
+
62
+ #ifdef DATA
63
+ in highp float v_value;
64
+ #endif
65
+
48
66
  uniform sampler2D u_sdf_sampler;
49
67
  uniform int u_is_halo;
50
68
 
69
+ #ifndef DATA
51
70
  uniform lowp vec4 u_text_color;
71
+ #endif
52
72
  uniform lowp vec4 u_halo_color;
53
73
 
54
74
  #define SDF_FILL 0.75
@@ -62,7 +82,13 @@ void main() {
62
82
  lowp float step_width = 0.08;
63
83
  lowp float alpha = smoothstep(SDF_FILL - step_width, SDF_FILL + step_width, sdf_val);
64
84
 
65
- lowp vec4 color = u_is_halo == 1 ? u_halo_color : u_text_color;
85
+ #ifdef DATA
86
+ lowp vec4 text_color = apply_colormap(v_value);
87
+ #else
88
+ lowp vec4 text_color = u_text_color;
89
+ #endif
90
+
91
+ lowp vec4 color = u_is_halo == 1 ? u_halo_color : text_color;
66
92
 
67
93
  if (u_is_halo == 1) {
68
94
  alpha = min(smoothstep(SDF_HALO - step_width, SDF_HALO + step_width, sdf_val), 1.0 - alpha);
@@ -154,6 +180,9 @@ function createAtlas(pbf_glyphs) {
154
180
  const FONT_ATLAS_CACHE = new Cache(async (urls) => {
155
181
  const promises = urls.map(async (url) => {
156
182
  const resp = await fetch(url);
183
+ if (resp.status != 200) {
184
+ throw `Error ${resp.status} retrieving font pbf from '${url}'`;
185
+ }
157
186
  const blob = await resp.blob();
158
187
  const data_buffer = await blob.arrayBuffer();
159
188
  // Parse the PBF and get the glyph data
@@ -168,6 +197,7 @@ const text_collection_opt_defaults = {
168
197
  vertical_align: 'baseline',
169
198
  font_size: 12,
170
199
  text_color: new Color([0, 0, 0, 1]),
200
+ cmap: null,
171
201
  halo_color: new Color([0, 0, 0, 1]),
172
202
  halo: false,
173
203
  offset_x: 0,
@@ -176,6 +206,7 @@ const text_collection_opt_defaults = {
176
206
  class TextCollection {
177
207
  constructor(gl, text_locs, font_atlas, opts) {
178
208
  this.opts = normalizeOptions(opts, text_collection_opt_defaults);
209
+ const text_color_hex = this.opts.text_color === undefined ? new Color([0, 0, 0, 1]) : this.opts.text_color;
179
210
  const is_webgl2 = isWebGL2Ctx(gl);
180
211
  const format = is_webgl2 ? gl.R8 : gl.LUMINANCE;
181
212
  const type = gl.UNSIGNED_BYTE;
@@ -193,11 +224,15 @@ class TextCollection {
193
224
  const anchor_data = new Float32Array(n_verts * 3);
194
225
  const offset_data = new Float32Array(n_verts * 2);
195
226
  const tc_data = new Float32Array(n_verts * 2);
196
- let i_anch = 0, i_off = 0, i_tc = 0;
227
+ const value_data = new Float32Array(n_verts);
228
+ let i_anch = 0, i_off = 0, i_tc = 0, i_dat = 0;
229
+ let has_data = false;
197
230
  text_locs.forEach(loc => {
198
231
  const { lat, lon, text } = loc;
199
232
  const min_zoom = loc.min_zoom === undefined ? 0 : loc.min_zoom;
233
+ const data_value = loc.data_value === undefined ? NaN : loc.data_value;
200
234
  const { x: anchor_x, y: anchor_y } = new LngLat(lon, lat).toMercatorCoord();
235
+ has_data = has_data || loc.data_value !== undefined;
201
236
  let x_offset = this.opts.offset_x;
202
237
  let y_offset = this.opts.offset_y;
203
238
  const init_i_off = i_off;
@@ -251,6 +286,12 @@ class TextCollection {
251
286
  tc_data[i_tc++] = glyph_info.atlas_j / font_atlas.atlas_height;
252
287
  tc_data[i_tc++] = (glyph_info.atlas_i + glyph_info.width) / font_atlas.atlas_width;
253
288
  tc_data[i_tc++] = glyph_info.atlas_j / font_atlas.atlas_height;
289
+ value_data[i_dat++] = data_value;
290
+ value_data[i_dat++] = data_value;
291
+ value_data[i_dat++] = data_value;
292
+ value_data[i_dat++] = data_value;
293
+ value_data[i_dat++] = data_value;
294
+ value_data[i_dat++] = data_value;
254
295
  x_offset += glyph_info.advance - glyph_info.left;
255
296
  }
256
297
  if (this.opts.horizontal_align == 'center') {
@@ -274,18 +315,40 @@ class TextCollection {
274
315
  }
275
316
  }
276
317
  });
277
- this.shader_manager = new ShaderProgramManager(text_vertex_shader_src, text_fragment_shader_src, []);
318
+ const shader_defines = [];
319
+ let fragment_src = text_fragment_shader_src;
320
+ if (has_data) {
321
+ shader_defines.push('DATA');
322
+ this.data = new WGLBuffer(gl, value_data, 1, gl.TRIANGLE_STRIP);
323
+ const cmap = this.opts.cmap === null ? new ColorMap([0, 1], [text_color_hex], { overflow_color: text_color_hex, underflow_color: text_color_hex }) : this.opts.cmap;
324
+ this.cmap_gpu = new ColorMapGPUInterface(cmap);
325
+ this.cmap_gpu.setupShaderVariables(gl, gl.NEAREST);
326
+ fragment_src = ColorMapGPUInterface.applyShader(fragment_src);
327
+ }
328
+ else {
329
+ this.data = null;
330
+ this.cmap_gpu = null;
331
+ }
332
+ this.shader_manager = new ShaderProgramManager(text_vertex_shader_src, fragment_src, shader_defines);
278
333
  this.anchors = new WGLBuffer(gl, anchor_data, 3, gl.TRIANGLE_STRIP);
279
334
  this.offsets = new WGLBuffer(gl, offset_data, 2, gl.TRIANGLE_STRIP);
280
335
  this.texcoords = new WGLBuffer(gl, tc_data, 2, gl.TRIANGLE_STRIP);
336
+ this.data = has_data ? new WGLBuffer(gl, value_data, 1, gl.TRIANGLE_STRIP) : null;
281
337
  }
282
338
  static async make(gl, text_locs, fontstack_url_template, opts) {
283
339
  const FONT_GROUP_SIZE = 256;
284
340
  const characters = text_locs.map(tl => [...tl.text]).flat().map(c => c.charCodeAt(0)).filter((c, i, ary) => ary.indexOf(c) == i);
285
- const char_code_min = Math.min(...characters);
286
- const char_code_max = Math.max(...characters);
287
- const stack_start = Math.floor(char_code_min / FONT_GROUP_SIZE) * FONT_GROUP_SIZE;
288
- const stack_end = Math.floor(char_code_max / FONT_GROUP_SIZE) * FONT_GROUP_SIZE;
341
+ let stack_start, stack_end;
342
+ if (characters.length > 0) {
343
+ const char_code_min = Math.min(...characters);
344
+ const char_code_max = Math.max(...characters);
345
+ stack_start = Math.floor(char_code_min / FONT_GROUP_SIZE) * FONT_GROUP_SIZE;
346
+ stack_end = Math.floor(char_code_max / FONT_GROUP_SIZE) * FONT_GROUP_SIZE;
347
+ }
348
+ else {
349
+ stack_start = 0;
350
+ stack_end = 0;
351
+ }
289
352
  const fontstack_urls = [];
290
353
  for (let istack = stack_start; istack <= stack_end; istack += FONT_GROUP_SIZE) {
291
354
  fontstack_urls.push(fontstack_url_template.replace('{range}', `${istack}-${istack + FONT_GROUP_SIZE - 1}`));
@@ -298,13 +361,23 @@ class TextCollection {
298
361
  render(gl, arg, [map_width, map_height], map_zoom) {
299
362
  const render_data = getRendererData(arg);
300
363
  const program = this.shader_manager.getShaderProgram(gl, render_data.shaderData);
364
+ const attributes = { 'a_pos': this.anchors, 'a_offset': this.offsets, 'a_tex_coord': this.texcoords };
301
365
  const uniforms = {
302
366
  'u_map_width': map_width, 'u_map_height': map_height, 'u_map_zoom': map_zoom, 'u_font_size': this.opts.font_size,
303
- 'u_text_color': this.opts.text_color.toRGBATuple(), 'u_halo_color': this.opts.halo_color.toRGBATuple(), 'u_offset': 0,
367
+ 'u_halo_color': this.opts.halo_color.toRGBATuple(), 'u_offset': 0,
304
368
  ...this.shader_manager.getShaderUniforms(render_data)
305
369
  };
370
+ if (this.data !== null) {
371
+ attributes['a_value'] = this.data;
372
+ }
373
+ else {
374
+ uniforms['u_text_color'] = this.opts.text_color.toRGBATuple();
375
+ }
306
376
  uniforms['u_is_halo'] = this.opts.halo ? 1 : 0;
307
- program.use({ 'a_pos': this.anchors, 'a_offset': this.offsets, 'a_tex_coord': this.texcoords }, uniforms, { 'u_sdf_sampler': this.texture });
377
+ program.use(attributes, uniforms, { 'u_sdf_sampler': this.texture });
378
+ if (this.cmap_gpu !== null) {
379
+ this.cmap_gpu.bindShaderVariables(program);
380
+ }
308
381
  gl.enable(gl.BLEND);
309
382
  gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
310
383
  program.draw();
@@ -0,0 +1,7 @@
1
+ import { MarchingSquaresModule } from './cpp/marchingsquares';
2
+ import './cpp/marchingsquares.wasm';
3
+ interface InitMSModuleOpts {
4
+ document_script?: string;
5
+ }
6
+ declare function initMSModule(opts: InitMSModuleOpts): Promise<MarchingSquaresModule>;
7
+ export { initMSModule };
@@ -0,0 +1,11 @@
1
+ // Should rename this eventually
2
+ import Module from './cpp/marchingsquares';
3
+ import './cpp/marchingsquares.wasm';
4
+ let msm_promise = null;
5
+ function initMSModule(opts) {
6
+ if (msm_promise === null) {
7
+ msm_promise = Module({ 'locateFile': (fname, dir) => (opts.document_script === undefined ? dir : opts.document_script) + fname });
8
+ }
9
+ return msm_promise;
10
+ }
11
+ export { initMSModule };
@@ -0,0 +1,8 @@
1
+ import * as Comlink from 'comlink';
2
+ type Promisify<T> = (T extends (...args: infer TArguments) => infer TReturn ? (...args: TArguments) => Promise<TReturn> : unknown);
3
+ type WorkerPool<T> = {
4
+ [K in keyof T]: Promisify<T[K]>;
5
+ };
6
+ declare function createWorkerPool<T>(workers: Worker[], init?: (wkr: Comlink.Remote<T>) => void): WorkerPool<T>;
7
+ export { createWorkerPool };
8
+ export type { WorkerPool };