autumnplot-gl 3.2.0 → 4.0.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/README.md +11 -207
- package/dist/983.autumnplot-gl.js +2 -0
- package/dist/983.autumnplot-gl.js.map +1 -0
- package/dist/autumnplot-gl.js +1 -1
- package/dist/autumnplot-gl.js.map +1 -1
- package/dist/marchingsquares.wasm +0 -0
- package/lib/AutumnTypes.d.ts +85 -6
- package/lib/AutumnTypes.js +28 -1
- package/lib/Barbs.d.ts +7 -5
- package/lib/BillboardCollection.d.ts +8 -7
- package/lib/BillboardCollection.js +69 -58
- package/lib/Color.d.ts +2 -0
- package/lib/Color.js +4 -0
- package/lib/ColorBar.d.ts +19 -0
- package/lib/ColorBar.js +9 -6
- package/lib/Colormap.d.ts +1 -0
- package/lib/Colormap.js +8 -8
- package/lib/Contour.d.ts +28 -8
- package/lib/Contour.js +27 -54
- package/lib/ContourCreator.d.ts +9 -1
- package/lib/ContourCreator.js +5 -4
- package/lib/Fill.d.ts +28 -14
- package/lib/Fill.js +97 -50
- package/lib/Grid.d.ts +132 -30
- package/lib/Grid.js +355 -99
- package/lib/Hodographs.d.ts +15 -8
- package/lib/Hodographs.js +48 -30
- package/lib/Map.d.ts +14 -1
- package/lib/Map.js +60 -4
- package/lib/Paintball.d.ts +7 -5
- package/lib/Paintball.js +36 -31
- package/lib/ParticleTracer.d.ts +19 -0
- package/lib/ParticleTracer.js +37 -0
- package/lib/PlotComponent.d.ts +7 -7
- package/lib/PlotComponent.js +9 -3
- package/lib/PlotLayer.d.ts +5 -5
- package/lib/PlotLayer.js +2 -2
- package/lib/PlotLayer.worker.d.ts +1 -2
- package/lib/PlotLayer.worker.js +22 -51
- package/lib/PolylineCollection.d.ts +5 -3
- package/lib/PolylineCollection.js +60 -37
- package/lib/RawField.d.ts +78 -23
- package/lib/RawField.js +147 -31
- package/lib/ShaderManager.d.ts +12 -0
- package/lib/ShaderManager.js +58 -0
- package/lib/StationPlot.d.ts +187 -25
- package/lib/StationPlot.js +209 -60
- package/lib/TextCollection.d.ts +9 -6
- package/lib/TextCollection.js +97 -62
- package/lib/cpp/marchingsquares.js +483 -585
- package/lib/cpp/marchingsquares.wasm +0 -0
- package/lib/cpp/marchingsquares_embind.d.ts +23 -3
- package/lib/index.d.ts +12 -5
- package/lib/index.js +8 -5
- package/lib/utils.d.ts +4 -1
- package/lib/utils.js +12 -1
- package/package.json +3 -3
- package/dist/110.autumnplot-gl.js +0 -2
- package/dist/110.autumnplot-gl.js.map +0 -1
package/lib/StationPlot.js
CHANGED
|
@@ -1,73 +1,222 @@
|
|
|
1
|
-
import { kdTree } from "kd-tree-javascript";
|
|
2
|
-
import { LngLat } from "./Map";
|
|
3
1
|
import { PlotComponent } from "./PlotComponent";
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
import { normalizeOptions } from "./utils";
|
|
3
|
+
import { TextCollection } from "./TextCollection";
|
|
4
|
+
import { Color } from "./Color";
|
|
5
|
+
import Barbs from "./Barbs";
|
|
6
|
+
const SYMBOLS = {
|
|
7
|
+
// Sky cover symbols
|
|
8
|
+
'0/8': 59658, '1/8': 59659, '2/8': 59660, '3/8': 59661, '4/8': 59662, '5/8': 59663, '6/8': 59664, '7/8': 59665, '8/8': 59666,
|
|
9
|
+
'clr': 59658, 'few': 59660, 'sct': 59662, 'bkn': 59664, 'ovc': 59666, 'obsc': 59667,
|
|
10
|
+
// Present weather symbols
|
|
11
|
+
'va': 59810, 'fu': 59810, 'hz': 59811, 'du': 59812, 'bldu': 59814, 'sa': 59814, 'blsa': 59814, 'vcblsa': 59814, 'vcbldu': 59814, 'blpy': 59814,
|
|
12
|
+
'po': 59816, 'vcpo': 59816, 'vcds': 59817, 'vcss': 59817,
|
|
13
|
+
'br': 59818, 'bcbr': 59818, 'bc': 59819, 'mifg': 59820,
|
|
14
|
+
'vcts': 59821, 'virga': 59822, 'vcsh': 59824, 'ts': 59825, 'thdr': 59825, 'vctshz': 59825,
|
|
15
|
+
'tsfzfg': 59825, 'tsbr': 59825, 'tsdz': 59825, 'vctsup': 59825,
|
|
16
|
+
'-tsup': 59825, 'tsup': 59825, '+tsup': 59825,
|
|
17
|
+
'sq': 59826, 'fc': 59827, '+fc': 59827,
|
|
18
|
+
'ds': 59839, 'ss': 59839, 'drsa': 59839, 'drdu': 59839, '+ds': 59842, '+ss': 59842,
|
|
19
|
+
'drsn': 59844, '+drsn': 59845, '-blsn': 59846, 'blsn': 59846, '+blsn': 59847, 'vcblsn': 59846,
|
|
20
|
+
'vcfg': 59848, 'bcfg': 59849, 'prfg': 59852, 'fg': 59853, 'fzfg': 59857,
|
|
21
|
+
'-vctsdz': 59859, '-dz': 59859, '-dzbr': 59859, 'vctsdz': 59861, 'dz': 59861, '+vctsdz': 59863, '+dz': 59863,
|
|
22
|
+
'-fzdz': 59864, '-fzdzsn': 59864, 'fzdz': 59865, '+fzdz': 59865, 'fzdzsn': 59865,
|
|
23
|
+
'-dzra': 59866, 'dzra': 59867, '+dzra': 59867, '-ra': 59869, '-rabr': 59869, 'ra': 59871, 'rabr': 59871, 'rafg': 59871, 'vcra': 59871, '+ra': 59873,
|
|
24
|
+
'-fzra': 59874, '-fzrasn': 59874, '-fzrabr': 59874, '-fzrapl': 59874, '-fzrasnpl': 59874, 'tsfzrapl': 59875, '-tsfzra': 59875,
|
|
25
|
+
'fzra': 59875, '+fzra': 59875, 'fzrasn': 59875, 'tsfzra': 59875,
|
|
26
|
+
'-dzsn': 59876, '-rasn': 59876, '-snra': 59876, '-sndz': 59876, 'rasn': 59877, '+rasn': 59877, 'snra': 59877, 'dzsn': 59877, 'sndz': 59877, '+dzsn': 59877, '+sndz': 59877,
|
|
27
|
+
'-sn': 59879, '-snbr': 59879, 'sn': 59881, '+sn': 59883, '-snsg': 59885, 'sg': 59885, '-sg': 59885, 'ic': 59886,
|
|
28
|
+
'-fzdzpl': 59887, '-fzdzplsn': 59887, 'fzdzpl': 59887, '-fzraplsn': 59887, 'fzrapl': 59887, '+fzrapl': 59887,
|
|
29
|
+
'-rapl': 59887, '-rasnpl': 59887, '-raplsn': 59887, '+rapl': 59887, 'rapl': 59887, '-snpl': 59887, 'snpl': 59887,
|
|
30
|
+
'-pl': 59887, 'pl': 59887, '-plsn': 59887, '-plra': 59887, 'plra': 59887, '-pldz': 59887, '+pl': 59887, 'plsn': 59887, 'plup': 59887, '+plsn': 59887,
|
|
31
|
+
'-sh': 59888, '-shra': 59888, 'sh': 59889, 'shra': 59889, '+sh': 59889, '+shra': 59889, '-shrasn': 59891, '-shsnra': 59891, '+shrabr': 59892,
|
|
32
|
+
'shrasn': 59892, '+shrasn': 59892, 'shsnra': 59892, '+shsnra': 59892, '-shsn': 59893, 'shsn': 59894, '+shsn': 59894,
|
|
33
|
+
'-gs': 59895, '-shgs': 59895, 'fzraplgs': 59896, '-sngs': 59896, 'gsplsn': 59896, 'gspl': 59896, 'plgssn': 59896, 'gs': 59896, 'shgs': 59896, '+gs': 59896, '+shgs': 59896,
|
|
34
|
+
'-gr': 59897, '-shgr': 59897, '-sngr': 59898, 'gr': 59898, 'shgr': 59898, '+gr': 59898, '+shgr': 59898,
|
|
35
|
+
'-tsrasn': 59907, 'tsrasn': 59907, '-tssnra': 59907, 'tssnra': 59907, '-vctsra': 59908, '-tsra': 59908, 'tsra': 59908, '-tsdz': 59908, 'vctsra': 59908,
|
|
36
|
+
'tspl': 59909, '-tssn': 59909, '-tspl': 59909, 'tssn': 59909, '-vctssn': 59909, 'vctssn': 59909, 'tsplsn': 59909, 'tssnpl': 59909, '-tssnpl': 59909, '-tsragr': 59910,
|
|
37
|
+
'tsrags': 59910, 'tsragr': 59910, 'tsgs': 59910, 'tsgr': 59910,
|
|
38
|
+
'+tsfzrapl': 59911, '+vctsra': 59912, '+tsra': 59912, '+tsfzra': 59912, '+tssn': 59913, '+tspl': 59913, '+tsplsn': 59913, '+vctssn': 59913,
|
|
39
|
+
'tssa': 59914, 'tsds': 59914, 'tsdu': 59914, '+tsgs': 59915, '+tsgr': 59915, '+tsrags': 59915, '+tsragr': 59915, 'in': 59750,
|
|
40
|
+
'-up': 59750, 'up': 59750, '+up': 59751, '-fzup': 59756, 'fzup': 59756, '+fzup': 59757,
|
|
41
|
+
};
|
|
42
|
+
const station_plot_opts_defaults = {
|
|
43
|
+
config: {},
|
|
44
|
+
thin_fac: 1,
|
|
45
|
+
font_face: 'Trebuchet MS',
|
|
46
|
+
font_size: 12,
|
|
47
|
+
font_url_template: '',
|
|
48
|
+
};
|
|
49
|
+
function positionToAlignmentAndOffset(pos, off_size) {
|
|
50
|
+
off_size = off_size === undefined ? 10 : off_size;
|
|
51
|
+
let ha, va;
|
|
52
|
+
let xoff, yoff;
|
|
53
|
+
if (pos == 'll' || pos == 'cl' || pos == 'ul') {
|
|
54
|
+
ha = 'right';
|
|
55
|
+
xoff = -off_size;
|
|
8
56
|
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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);
|
|
57
|
+
else if (pos == 'lc' || pos == 'c' || pos == 'uc') {
|
|
58
|
+
ha = 'center';
|
|
59
|
+
xoff = 0;
|
|
50
60
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
61
|
+
else {
|
|
62
|
+
ha = 'left';
|
|
63
|
+
xoff = off_size;
|
|
64
|
+
}
|
|
65
|
+
if (pos == 'll' || pos == 'lc' || pos == 'lr') {
|
|
66
|
+
va = 'top';
|
|
67
|
+
yoff = -off_size;
|
|
68
|
+
}
|
|
69
|
+
else if (pos == 'cl' || pos == 'c' || pos == 'cr') {
|
|
70
|
+
va = 'middle';
|
|
71
|
+
yoff = 0;
|
|
56
72
|
}
|
|
73
|
+
else {
|
|
74
|
+
va = 'baseline';
|
|
75
|
+
yoff = off_size;
|
|
76
|
+
}
|
|
77
|
+
return { horizontal_align: ha, vertical_align: va, offset_x: xoff, offset_y: yoff };
|
|
57
78
|
}
|
|
58
|
-
|
|
79
|
+
/**
|
|
80
|
+
* Station model plots for observed data
|
|
81
|
+
* @example
|
|
82
|
+
* // Specify how to set up the station plot
|
|
83
|
+
* const station_plot_locs = {
|
|
84
|
+
* tmpf: {type: 'number', pos: 'ul', color: '#cc0000', formatter: val => val === null ? '' : val.toFixed(0)},
|
|
85
|
+
* dwpf: {type: 'number', pos: 'll', color: '#00aa00', formatter: val => val === null ? '' : val.toFixed(0)},
|
|
86
|
+
* wind: {type: 'barb', pos: 'c'},
|
|
87
|
+
* preswx: {type: 'symbol', pos: 'cl', color: '#ff00ff'},
|
|
88
|
+
* skyc: {type: 'symbol', pos: 'c'},
|
|
89
|
+
* };
|
|
90
|
+
*
|
|
91
|
+
* // Create the station plot
|
|
92
|
+
* const station_plot = new StationPlot(obs_field, {config: station_plot_locs, thin_fac: 8, font_size: 14});
|
|
93
|
+
*/
|
|
59
94
|
class StationPlot extends PlotComponent {
|
|
60
|
-
|
|
95
|
+
/**
|
|
96
|
+
* Create station plots
|
|
97
|
+
* @param field - A field containing the observed data
|
|
98
|
+
* @param opts - Various options for the station plots
|
|
99
|
+
*/
|
|
100
|
+
constructor(field, opts) {
|
|
61
101
|
super();
|
|
62
102
|
this.field = field;
|
|
63
|
-
this.
|
|
64
|
-
this.
|
|
103
|
+
this.opts = normalizeOptions(opts, station_plot_opts_defaults); // Is there a way to do this without invoking `as`?
|
|
104
|
+
this.gl_elems = null;
|
|
105
|
+
this.text_components = null;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Update the data displayed as station plots
|
|
109
|
+
* @param field - The new field to display as station plots
|
|
110
|
+
*/
|
|
111
|
+
async updateField(field) {
|
|
112
|
+
this.field = field;
|
|
113
|
+
if (this.gl_elems === null)
|
|
114
|
+
return;
|
|
115
|
+
const map = this.gl_elems.map;
|
|
116
|
+
const gl = this.gl_elems.gl;
|
|
117
|
+
const barb_components = this.gl_elems.barb_components;
|
|
118
|
+
const map_style = map.getStyle();
|
|
119
|
+
const font_url_template = this.opts.font_url_template == '' ? map_style.glyphs : this.opts.font_url_template;
|
|
120
|
+
if (font_url_template === undefined)
|
|
121
|
+
throw "The map style doesn't have any glyph information. Please pass the font_url_template option to StationPlot";
|
|
122
|
+
const font_url = font_url_template.replace('{fontstack}', this.opts.font_face);
|
|
123
|
+
let ibarb = 0;
|
|
124
|
+
const sub_component_promises = Object.entries(this.opts.config).map(async ([k_, config]) => {
|
|
125
|
+
const k = k_;
|
|
126
|
+
if (config.type == 'number' || config.type == 'string') {
|
|
127
|
+
const pos = config.pos;
|
|
128
|
+
const color = config.color === undefined ? Color.fromHex('#000000') : Color.normalizeColor(config.color);
|
|
129
|
+
const halo_color = config.halo_color === undefined ? Color.fromHex('#ffffff') : Color.normalizeColor(config.halo_color);
|
|
130
|
+
const halo = config.halo === undefined ? true : config.halo;
|
|
131
|
+
const coords = this.field.grid.getEarthCoords();
|
|
132
|
+
const zoom = this.field.grid.getMinVisibleZoom(this.opts.thin_fac);
|
|
133
|
+
let text_specs;
|
|
134
|
+
if (config.type == 'number') {
|
|
135
|
+
const comp = this.field.getScalar(k);
|
|
136
|
+
const formatter = config.formatter === undefined ? (val) => val === null ? 'null' : val.toString() : config.formatter;
|
|
137
|
+
text_specs = comp.map((v, i) => ({ text: formatter(v), lat: coords.lats[i], lon: coords.lons[i], min_zoom: zoom[i] }));
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
const comp = this.field.getStrings(k);
|
|
141
|
+
text_specs = comp.map((v, i) => ({ text: v === null ? '' : v, lat: coords.lats[i], lon: coords.lons[i], min_zoom: zoom[i] }));
|
|
142
|
+
}
|
|
143
|
+
const tc_opts = {
|
|
144
|
+
...positionToAlignmentAndOffset(pos),
|
|
145
|
+
font_size: this.opts.font_size, halo: halo,
|
|
146
|
+
text_color: color, halo_color: halo_color,
|
|
147
|
+
};
|
|
148
|
+
return await TextCollection.make(gl, text_specs, font_url, tc_opts);
|
|
149
|
+
}
|
|
150
|
+
else if (config.type == 'barb') {
|
|
151
|
+
const comp = this.field.getVector(k);
|
|
152
|
+
const barb_comp = barb_components[ibarb++];
|
|
153
|
+
barb_comp.updateField(comp);
|
|
154
|
+
}
|
|
155
|
+
else if (config.type == 'symbol') {
|
|
156
|
+
const pos = config.pos;
|
|
157
|
+
const color = config.color === undefined ? Color.fromHex('#000000') : Color.normalizeColor(config.color);
|
|
158
|
+
const halo_color = config.halo_color === undefined ? Color.fromHex('#ffffff') : Color.normalizeColor(config.halo_color);
|
|
159
|
+
const halo = config.halo === undefined ? true : config.halo;
|
|
160
|
+
const comp = this.field.getStrings(k);
|
|
161
|
+
const coords = this.field.grid.getEarthCoords();
|
|
162
|
+
const zoom = this.field.grid.getMinVisibleZoom(this.opts.thin_fac);
|
|
163
|
+
const wxsym_font_url = font_url_template.replace('{fontstack}', 'wx_symbols');
|
|
164
|
+
const text_specs = comp.map((v, i) => ({ text: v === null ? '' : String.fromCharCode(SYMBOLS[v]),
|
|
165
|
+
lat: coords.lats[i], lon: coords.lons[i], min_zoom: zoom[i] }));
|
|
166
|
+
const tc_opts = {
|
|
167
|
+
...positionToAlignmentAndOffset(pos),
|
|
168
|
+
font_size: this.opts.font_size, halo: halo,
|
|
169
|
+
text_color: color, halo_color: halo_color,
|
|
170
|
+
};
|
|
171
|
+
if (tc_opts.offset_x !== undefined)
|
|
172
|
+
tc_opts.offset_x -= 3;
|
|
173
|
+
return await TextCollection.make(gl, text_specs, wxsym_font_url, tc_opts);
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
throw `Unknown station plot configuration type ${config.type}`;
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
this.text_components = (await Promise.all(sub_component_promises)).filter((c) => c !== undefined);
|
|
180
|
+
map.triggerRepaint();
|
|
65
181
|
}
|
|
182
|
+
/** @internal */
|
|
66
183
|
async onAdd(map, gl) {
|
|
184
|
+
const barb_promises = Object.entries(this.opts.config).map(async ([k_, config]) => {
|
|
185
|
+
const k = k_;
|
|
186
|
+
if (config.type == 'barb') {
|
|
187
|
+
const comp = this.field.getVector(k);
|
|
188
|
+
const color = config.color === undefined ? '#000000' : config.color;
|
|
189
|
+
const barb_size = config.barb_size_multipler === undefined ? 1 : config.barb_size_multipler;
|
|
190
|
+
const barb_comp = new Barbs(comp, { thin_fac: this.opts.thin_fac, color: color, barb_size_multiplier: barb_size });
|
|
191
|
+
await barb_comp.onAdd(map, gl);
|
|
192
|
+
return barb_comp;
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
const barb_components = (await Promise.all(barb_promises)).filter((c) => c !== undefined);
|
|
196
|
+
this.gl_elems = {
|
|
197
|
+
map: map, gl: gl, barb_components: barb_components
|
|
198
|
+
};
|
|
199
|
+
this.updateField(this.field);
|
|
67
200
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
201
|
+
/** @internal */
|
|
202
|
+
render(gl, arg) {
|
|
203
|
+
if (this.gl_elems === null || this.text_components === null)
|
|
204
|
+
return;
|
|
205
|
+
const gl_elems = this.gl_elems;
|
|
206
|
+
const text_components = this.text_components;
|
|
207
|
+
const barb_components = this.gl_elems.barb_components;
|
|
208
|
+
const map_width = gl_elems.map.getCanvas().width;
|
|
209
|
+
const map_height = gl_elems.map.getCanvas().height;
|
|
210
|
+
const map_zoom = gl_elems.map.getZoom();
|
|
211
|
+
let itext = 0, ibarb = 0;
|
|
212
|
+
Object.values(this.opts.config).forEach(comp => {
|
|
213
|
+
if (comp.type == 'barb') {
|
|
214
|
+
barb_components[ibarb++].render(gl, arg);
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
text_components[itext++].render(gl, arg, [map_width, map_height], map_zoom);
|
|
218
|
+
}
|
|
219
|
+
});
|
|
71
220
|
}
|
|
72
221
|
}
|
|
73
|
-
export
|
|
222
|
+
export default StationPlot;
|
package/lib/TextCollection.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { WebGLAnyRenderingContext } from "./AutumnTypes";
|
|
1
|
+
import { RenderMethodArg, WebGLAnyRenderingContext } from "./AutumnTypes";
|
|
2
2
|
import { Color } from "./Color";
|
|
3
|
-
import {
|
|
3
|
+
import { ShaderProgramManager } from "./ShaderManager";
|
|
4
|
+
import { WGLBuffer, WGLTexture } from "autumn-wgl";
|
|
4
5
|
interface TextSpec {
|
|
5
6
|
lat: number;
|
|
6
7
|
lon: number;
|
|
@@ -16,17 +17,19 @@ interface TextCollectionOptions {
|
|
|
16
17
|
text_color?: Color;
|
|
17
18
|
halo_color?: Color;
|
|
18
19
|
halo?: boolean;
|
|
20
|
+
offset_x?: number;
|
|
21
|
+
offset_y?: number;
|
|
19
22
|
}
|
|
20
23
|
declare class TextCollection {
|
|
21
|
-
readonly
|
|
24
|
+
readonly shader_manager: ShaderProgramManager;
|
|
22
25
|
readonly anchors: WGLBuffer;
|
|
23
26
|
readonly offsets: WGLBuffer;
|
|
24
27
|
readonly texcoords: WGLBuffer;
|
|
25
28
|
readonly texture: WGLTexture;
|
|
26
29
|
readonly opts: Required<TextCollectionOptions>;
|
|
27
30
|
private constructor();
|
|
28
|
-
static make(gl: WebGLAnyRenderingContext, text_locs: TextSpec[],
|
|
29
|
-
render(gl: WebGLAnyRenderingContext,
|
|
31
|
+
static make(gl: WebGLAnyRenderingContext, text_locs: TextSpec[], fontstack_url_template: string, opts?: TextCollectionOptions): Promise<TextCollection>;
|
|
32
|
+
render(gl: WebGLAnyRenderingContext, arg: RenderMethodArg, [map_width, map_height]: [number, number], map_zoom: number): void;
|
|
30
33
|
}
|
|
31
34
|
export { TextCollection };
|
|
32
|
-
export type { TextSpec, TextCollectionOptions };
|
|
35
|
+
export type { TextSpec, TextCollectionOptions, HorizontalAlign, VerticalAlign };
|
package/lib/TextCollection.js
CHANGED
|
@@ -1,23 +1,24 @@
|
|
|
1
|
-
import { isWebGL2Ctx } from "./AutumnTypes";
|
|
1
|
+
import { getRendererData, isWebGL2Ctx } from "./AutumnTypes";
|
|
2
2
|
import { Color } from "./Color";
|
|
3
3
|
import { LngLat } from "./Map";
|
|
4
|
+
import { ShaderProgramManager } from "./ShaderManager";
|
|
4
5
|
import { Cache, normalizeOptions } from "./utils";
|
|
5
|
-
import { WGLBuffer,
|
|
6
|
+
import { WGLBuffer, WGLTexture } from "autumn-wgl";
|
|
6
7
|
import Protobuf from 'pbf';
|
|
7
8
|
import potpack from "potpack";
|
|
8
|
-
const text_vertex_shader_src =
|
|
9
|
-
|
|
9
|
+
const text_vertex_shader_src = `#version 300 es
|
|
10
|
+
|
|
10
11
|
uniform int u_offset;
|
|
11
12
|
uniform highp float u_map_width;
|
|
12
13
|
uniform highp float u_map_height;
|
|
13
14
|
uniform highp float u_map_zoom;
|
|
14
15
|
uniform highp float u_font_size;
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
in vec3 a_pos;
|
|
18
|
+
in vec2 a_offset;
|
|
19
|
+
in vec2 a_tex_coord;
|
|
19
20
|
|
|
20
|
-
|
|
21
|
+
out highp vec2 v_tex_coord;
|
|
21
22
|
|
|
22
23
|
mat4 scalingMatrix(float x_scale, float y_scale, float z_scale) {
|
|
23
24
|
return mat4(x_scale, 0.0, 0.0, 0.0,
|
|
@@ -38,11 +39,12 @@ void main() {
|
|
|
38
39
|
|
|
39
40
|
mat4 map_stretch_matrix = scalingMatrix(u_map_height / u_map_width, 1., 1.);
|
|
40
41
|
|
|
41
|
-
gl_Position =
|
|
42
|
+
gl_Position = projectTile(a_pos.xy + globe_offset) + u_font_size / 12. * 1.5 * map_stretch_matrix * vec4(offset, 0., 0.);
|
|
42
43
|
v_tex_coord = a_tex_coord;
|
|
43
44
|
}`
|
|
44
|
-
const text_fragment_shader_src =
|
|
45
|
-
|
|
45
|
+
const text_fragment_shader_src = `#version 300 es
|
|
46
|
+
|
|
47
|
+
in highp vec2 v_tex_coord;
|
|
46
48
|
uniform sampler2D u_sdf_sampler;
|
|
47
49
|
uniform int u_is_halo;
|
|
48
50
|
|
|
@@ -52,8 +54,10 @@ uniform lowp vec4 u_halo_color;
|
|
|
52
54
|
#define SDF_FILL 0.75
|
|
53
55
|
#define SDF_HALO 0.45
|
|
54
56
|
|
|
57
|
+
out highp vec4 fragColor;
|
|
58
|
+
|
|
55
59
|
void main() {
|
|
56
|
-
highp float sdf_val =
|
|
60
|
+
highp float sdf_val = texture(u_sdf_sampler, v_tex_coord).r;
|
|
57
61
|
|
|
58
62
|
lowp float step_width = 0.08;
|
|
59
63
|
lowp float alpha = smoothstep(SDF_FILL - step_width, SDF_FILL + step_width, sdf_val);
|
|
@@ -65,9 +69,8 @@ void main() {
|
|
|
65
69
|
}
|
|
66
70
|
|
|
67
71
|
color.a *= alpha;
|
|
68
|
-
|
|
72
|
+
fragColor = color;
|
|
69
73
|
}`
|
|
70
|
-
const program_cache = new Cache((gl) => new WGLProgram(gl, text_vertex_shader_src, text_fragment_shader_src));
|
|
71
74
|
const PADDING = 3;
|
|
72
75
|
function parseFontPBF(data) {
|
|
73
76
|
const readGlyph = (tag, glyph, pbf) => {
|
|
@@ -125,6 +128,7 @@ function createAtlas(pbf_glyphs) {
|
|
|
125
128
|
const { w: img_width, h: img_height } = potpack(bins);
|
|
126
129
|
const atlas_data = new Uint8Array(img_width * img_height);
|
|
127
130
|
const glyphs = {};
|
|
131
|
+
let max_glyph_height = 0;
|
|
128
132
|
glyph_bins.forEach(glyph_bin => {
|
|
129
133
|
const { bin, glyph } = glyph_bin;
|
|
130
134
|
if (bin.x === undefined || bin.y === undefined)
|
|
@@ -133,6 +137,7 @@ function createAtlas(pbf_glyphs) {
|
|
|
133
137
|
id: glyph.id, width: glyph.width, height: glyph.height, left: glyph.left, top: glyph.top,
|
|
134
138
|
atlas_i: bin.x, atlas_j: bin.y, advance: glyph.advance
|
|
135
139
|
};
|
|
140
|
+
max_glyph_height = Math.max(max_glyph_height, glyph.height);
|
|
136
141
|
for (let i = 0; i < glyph.width; i++) {
|
|
137
142
|
for (let j = 0; j < glyph.height; j++) {
|
|
138
143
|
const glyph_idx = i + glyph.width * j;
|
|
@@ -142,38 +147,49 @@ function createAtlas(pbf_glyphs) {
|
|
|
142
147
|
}
|
|
143
148
|
});
|
|
144
149
|
const glyph_M = glyphs['M'.charCodeAt(0)];
|
|
145
|
-
const baseline = glyph_M.height - glyph_M.top;
|
|
146
|
-
const top = -glyph_M.top;
|
|
150
|
+
const baseline = glyph_M === undefined ? max_glyph_height : glyph_M.height - glyph_M.top;
|
|
151
|
+
const top = glyph_M === undefined ? 0 : -glyph_M.top;
|
|
147
152
|
return { atlas: atlas_data, atlas_width: img_width, atlas_height: img_height, baseline: baseline, top: top, glyph_info: glyphs };
|
|
148
153
|
}
|
|
149
|
-
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
154
|
+
const FONT_ATLAS_CACHE = new Cache(async (urls) => {
|
|
155
|
+
const promises = urls.map(async (url) => {
|
|
156
|
+
const resp = await fetch(url);
|
|
157
|
+
if (resp.status != 200) {
|
|
158
|
+
throw `Error ${resp.status} retrieving font pbf from '${url}'`;
|
|
159
|
+
}
|
|
160
|
+
const blob = await resp.blob();
|
|
161
|
+
const data_buffer = await blob.arrayBuffer();
|
|
162
|
+
// Parse the PBF and get the glyph data
|
|
163
|
+
return parseFontPBF(new Uint8Array(data_buffer));
|
|
164
|
+
});
|
|
165
|
+
const glyphs = (await Promise.all(promises)).flat();
|
|
155
166
|
// Create an atlas for the glyphs
|
|
156
167
|
return createAtlas(glyphs);
|
|
157
|
-
}
|
|
168
|
+
});
|
|
158
169
|
const text_collection_opt_defaults = {
|
|
159
170
|
horizontal_align: 'left',
|
|
160
171
|
vertical_align: 'baseline',
|
|
161
172
|
font_size: 12,
|
|
162
173
|
text_color: new Color([0, 0, 0, 1]),
|
|
163
174
|
halo_color: new Color([0, 0, 0, 1]),
|
|
164
|
-
halo: false
|
|
175
|
+
halo: false,
|
|
176
|
+
offset_x: 0,
|
|
177
|
+
offset_y: 0
|
|
165
178
|
};
|
|
166
179
|
class TextCollection {
|
|
167
180
|
constructor(gl, text_locs, font_atlas, opts) {
|
|
168
|
-
this.program = program_cache.getValue(gl);
|
|
169
181
|
this.opts = normalizeOptions(opts, text_collection_opt_defaults);
|
|
170
182
|
const is_webgl2 = isWebGL2Ctx(gl);
|
|
171
183
|
const format = is_webgl2 ? gl.R8 : gl.LUMINANCE;
|
|
172
184
|
const type = gl.UNSIGNED_BYTE;
|
|
173
185
|
const row_alignment = 1;
|
|
186
|
+
const empty_atlas = font_atlas.atlas_width == 0 || font_atlas.atlas_height == 0 || font_atlas.atlas.length == 0;
|
|
187
|
+
const atlas_width = empty_atlas ? 1 : font_atlas.atlas_width;
|
|
188
|
+
const atlas_height = empty_atlas ? 1 : font_atlas.atlas_height;
|
|
189
|
+
const atlas_data = empty_atlas ? new Uint8Array([0, 0, 0, 0]) : font_atlas.atlas;
|
|
174
190
|
const image = {
|
|
175
|
-
'format': format, 'type': type, 'width':
|
|
176
|
-
'image':
|
|
191
|
+
'format': format, 'type': type, 'width': atlas_width, 'height': atlas_height,
|
|
192
|
+
'image': atlas_data, 'row_alignment': row_alignment, 'mag_filter': gl.LINEAR
|
|
177
193
|
};
|
|
178
194
|
this.texture = new WGLTexture(gl, image);
|
|
179
195
|
const n_verts = text_locs.map(tl => tl.text.length).reduce((a, b) => a + b, 0) * 6;
|
|
@@ -185,7 +201,8 @@ class TextCollection {
|
|
|
185
201
|
const { lat, lon, text } = loc;
|
|
186
202
|
const min_zoom = loc.min_zoom === undefined ? 0 : loc.min_zoom;
|
|
187
203
|
const { x: anchor_x, y: anchor_y } = new LngLat(lon, lat).toMercatorCoord();
|
|
188
|
-
let x_offset =
|
|
204
|
+
let x_offset = this.opts.offset_x;
|
|
205
|
+
let y_offset = this.opts.offset_y;
|
|
189
206
|
const init_i_off = i_off;
|
|
190
207
|
for (let i = 0; i < text.length; i++) {
|
|
191
208
|
const glyph_code = text.charCodeAt(i);
|
|
@@ -214,17 +231,17 @@ class TextCollection {
|
|
|
214
231
|
anchor_data[i_anch++] = anchor_y;
|
|
215
232
|
anchor_data[i_anch++] = min_zoom;
|
|
216
233
|
offset_data[i_off++] = x_offset;
|
|
217
|
-
offset_data[i_off++] = font_atlas.baseline + glyph_info.top - glyph_info.height;
|
|
234
|
+
offset_data[i_off++] = y_offset + font_atlas.baseline + glyph_info.top - glyph_info.height;
|
|
218
235
|
offset_data[i_off++] = x_offset;
|
|
219
|
-
offset_data[i_off++] = font_atlas.baseline + glyph_info.top - glyph_info.height;
|
|
236
|
+
offset_data[i_off++] = y_offset + font_atlas.baseline + glyph_info.top - glyph_info.height;
|
|
220
237
|
offset_data[i_off++] = x_offset + glyph_info.width;
|
|
221
|
-
offset_data[i_off++] = font_atlas.baseline + glyph_info.top - glyph_info.height;
|
|
238
|
+
offset_data[i_off++] = y_offset + font_atlas.baseline + glyph_info.top - glyph_info.height;
|
|
222
239
|
offset_data[i_off++] = x_offset;
|
|
223
|
-
offset_data[i_off++] = font_atlas.baseline + glyph_info.top;
|
|
240
|
+
offset_data[i_off++] = y_offset + font_atlas.baseline + glyph_info.top;
|
|
224
241
|
offset_data[i_off++] = x_offset + glyph_info.width;
|
|
225
|
-
offset_data[i_off++] = font_atlas.baseline + glyph_info.top;
|
|
242
|
+
offset_data[i_off++] = y_offset + font_atlas.baseline + glyph_info.top;
|
|
226
243
|
offset_data[i_off++] = x_offset + glyph_info.width;
|
|
227
|
-
offset_data[i_off++] = font_atlas.baseline + glyph_info.top;
|
|
244
|
+
offset_data[i_off++] = y_offset + font_atlas.baseline + glyph_info.top;
|
|
228
245
|
tc_data[i_tc++] = glyph_info.atlas_i / font_atlas.atlas_width;
|
|
229
246
|
tc_data[i_tc++] = (glyph_info.atlas_j + glyph_info.height) / font_atlas.atlas_height;
|
|
230
247
|
tc_data[i_tc++] = glyph_info.atlas_i / font_atlas.atlas_width;
|
|
@@ -241,12 +258,12 @@ class TextCollection {
|
|
|
241
258
|
}
|
|
242
259
|
if (this.opts.horizontal_align == 'center') {
|
|
243
260
|
for (let i = init_i_off; i < init_i_off + text.length * 12; i += 2) {
|
|
244
|
-
offset_data[i] -= x_offset / 2;
|
|
261
|
+
offset_data[i] -= (x_offset - this.opts.offset_x) / 2;
|
|
245
262
|
}
|
|
246
263
|
}
|
|
247
264
|
else if (this.opts.horizontal_align == 'right') {
|
|
248
265
|
for (let i = init_i_off; i < init_i_off + text.length * 12; i += 2) {
|
|
249
|
-
offset_data[i] -= x_offset;
|
|
266
|
+
offset_data[i] -= (x_offset - this.opts.offset_x);
|
|
250
267
|
}
|
|
251
268
|
}
|
|
252
269
|
if (this.opts.vertical_align == 'top') {
|
|
@@ -260,45 +277,63 @@ class TextCollection {
|
|
|
260
277
|
}
|
|
261
278
|
}
|
|
262
279
|
});
|
|
280
|
+
this.shader_manager = new ShaderProgramManager(text_vertex_shader_src, text_fragment_shader_src, []);
|
|
263
281
|
this.anchors = new WGLBuffer(gl, anchor_data, 3, gl.TRIANGLE_STRIP);
|
|
264
282
|
this.offsets = new WGLBuffer(gl, offset_data, 2, gl.TRIANGLE_STRIP);
|
|
265
283
|
this.texcoords = new WGLBuffer(gl, tc_data, 2, gl.TRIANGLE_STRIP);
|
|
266
284
|
}
|
|
267
|
-
static async make(gl, text_locs,
|
|
268
|
-
const
|
|
285
|
+
static async make(gl, text_locs, fontstack_url_template, opts) {
|
|
286
|
+
const FONT_GROUP_SIZE = 256;
|
|
287
|
+
const characters = text_locs.map(tl => [...tl.text]).flat().map(c => c.charCodeAt(0)).filter((c, i, ary) => ary.indexOf(c) == i);
|
|
288
|
+
const char_code_min = Math.min(...characters);
|
|
289
|
+
const char_code_max = Math.max(...characters);
|
|
290
|
+
const stack_start = Math.floor(char_code_min / FONT_GROUP_SIZE) * FONT_GROUP_SIZE;
|
|
291
|
+
const stack_end = Math.floor(char_code_max / FONT_GROUP_SIZE) * FONT_GROUP_SIZE;
|
|
292
|
+
const fontstack_urls = [];
|
|
293
|
+
for (let istack = stack_start; istack <= stack_end; istack += FONT_GROUP_SIZE) {
|
|
294
|
+
fontstack_urls.push(fontstack_url_template.replace('{range}', `${istack}-${istack + FONT_GROUP_SIZE - 1}`));
|
|
295
|
+
}
|
|
296
|
+
const atlas = await FONT_ATLAS_CACHE.getValue(fontstack_urls);
|
|
297
|
+
if (atlas.atlas_height == 0 || atlas.atlas_width == 0 || atlas.atlas.length == 0)
|
|
298
|
+
console.warn(`No font data from '${fontstack_url_template}'`);
|
|
269
299
|
return new TextCollection(gl, text_locs, atlas, opts);
|
|
270
300
|
}
|
|
271
|
-
render(gl,
|
|
301
|
+
render(gl, arg, [map_width, map_height], map_zoom) {
|
|
302
|
+
const render_data = getRendererData(arg);
|
|
303
|
+
const program = this.shader_manager.getShaderProgram(gl, render_data.shaderData);
|
|
272
304
|
const uniforms = {
|
|
273
|
-
'
|
|
274
|
-
'u_text_color': this.opts.text_color.toRGBATuple(), 'u_halo_color': this.opts.halo_color.toRGBATuple(), 'u_offset': 0
|
|
305
|
+
'u_map_width': map_width, 'u_map_height': map_height, 'u_map_zoom': map_zoom, 'u_font_size': this.opts.font_size,
|
|
306
|
+
'u_text_color': this.opts.text_color.toRGBATuple(), 'u_halo_color': this.opts.halo_color.toRGBATuple(), 'u_offset': 0,
|
|
307
|
+
...this.shader_manager.getShaderUniforms(render_data)
|
|
275
308
|
};
|
|
276
309
|
uniforms['u_is_halo'] = this.opts.halo ? 1 : 0;
|
|
277
|
-
|
|
310
|
+
program.use({ 'a_pos': this.anchors, 'a_offset': this.offsets, 'a_tex_coord': this.texcoords }, uniforms, { 'u_sdf_sampler': this.texture });
|
|
278
311
|
gl.enable(gl.BLEND);
|
|
279
312
|
gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
|
280
|
-
|
|
313
|
+
program.draw();
|
|
281
314
|
if (this.opts.halo) {
|
|
282
|
-
|
|
283
|
-
|
|
315
|
+
program.setUniforms({ 'u_is_halo': 0 });
|
|
316
|
+
program.draw();
|
|
284
317
|
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
this.
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
this.
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
this.
|
|
301
|
-
|
|
318
|
+
if (render_data.type != 'maplibre' || !render_data.shaderData.define.includes('GLOBE')) {
|
|
319
|
+
program.setUniforms({ 'u_offset': -2, 'u_is_halo': this.opts.halo ? 1 : 0 });
|
|
320
|
+
program.draw();
|
|
321
|
+
if (this.opts.halo) {
|
|
322
|
+
program.setUniforms({ 'u_is_halo': 0 });
|
|
323
|
+
program.draw();
|
|
324
|
+
}
|
|
325
|
+
program.setUniforms({ 'u_offset': -1, 'u_is_halo': this.opts.halo ? 1 : 0 });
|
|
326
|
+
program.draw();
|
|
327
|
+
if (this.opts.halo) {
|
|
328
|
+
program.setUniforms({ 'u_is_halo': 0 });
|
|
329
|
+
program.draw();
|
|
330
|
+
}
|
|
331
|
+
program.setUniforms({ 'u_offset': 1, 'u_is_halo': this.opts.halo ? 1 : 0 });
|
|
332
|
+
program.draw();
|
|
333
|
+
if (this.opts.halo) {
|
|
334
|
+
program.setUniforms({ 'u_is_halo': 0 });
|
|
335
|
+
program.draw();
|
|
336
|
+
}
|
|
302
337
|
}
|
|
303
338
|
}
|
|
304
339
|
}
|