autumnplot-gl 2.0.0-beta.1

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.
@@ -0,0 +1,343 @@
1
+ import { getMinZoom } from "./utils";
2
+ import * as Comlink from 'comlink';
3
+ import { LngLat } from "./Map";
4
+ function makeBBElements(field_lats, field_lons, field_ni, field_nj, thin_fac_base, max_zoom) {
5
+ const n_pts_per_poly = 6;
6
+ const n_coords_per_pt_pts = 3;
7
+ const n_coords_per_pt_tc = 2;
8
+ const n_density_tiers = Math.log2(thin_fac_base);
9
+ const n_inaccessible_tiers = Math.max(n_density_tiers + 1 - max_zoom, 0);
10
+ const trim_inaccessible = Math.pow(2, n_inaccessible_tiers);
11
+ const field_ni_access = Math.floor((field_ni - 1) / trim_inaccessible) + 1;
12
+ const field_nj_access = Math.floor((field_nj - 1) / trim_inaccessible) + 1;
13
+ const n_elems_pts = field_ni_access * field_nj_access * n_pts_per_poly * n_coords_per_pt_pts;
14
+ const n_elems_tc = field_ni_access * field_nj_access * n_pts_per_poly * n_coords_per_pt_tc;
15
+ let pts = new Float32Array(n_elems_pts);
16
+ let tex_coords = new Float32Array(n_elems_tc);
17
+ let istart_pts = 0;
18
+ let istart_tc = 0;
19
+ for (let ilat = 0; ilat < field_nj; ilat++) {
20
+ for (let ilon = 0; ilon < field_ni; ilon++) {
21
+ const idx = ilat * field_ni + ilon;
22
+ const lat = field_lats[idx];
23
+ const lon = field_lons[idx];
24
+ const zoom = getMinZoom(ilat, ilon, thin_fac_base);
25
+ if (zoom > max_zoom)
26
+ continue;
27
+ const pt_ll = new LngLat(lon, lat).toMercatorCoord();
28
+ // These contain a degenerate triangle on either end to imitate primitive restarting
29
+ // (see https://groups.google.com/g/webgl-dev-list/c/KLfiwj4jax0/m/cKiezrhRz8MJ?pli=1)
30
+ for (let icrnr = 0; icrnr < n_pts_per_poly; icrnr++) {
31
+ const actual_icrnr = Math.max(0, Math.min(icrnr - 1, 3));
32
+ pts[istart_pts + icrnr * n_coords_per_pt_pts + 0] = pt_ll.x;
33
+ pts[istart_pts + icrnr * n_coords_per_pt_pts + 1] = pt_ll.y;
34
+ pts[istart_pts + icrnr * n_coords_per_pt_pts + 2] = zoom * 4 + actual_icrnr;
35
+ tex_coords[istart_tc + icrnr * n_coords_per_pt_tc + 0] = ilon / (field_ni - 1);
36
+ tex_coords[istart_tc + icrnr * n_coords_per_pt_tc + 1] = ilat / (field_nj - 1);
37
+ }
38
+ istart_pts += (n_pts_per_poly * n_coords_per_pt_pts);
39
+ istart_tc += (n_pts_per_poly * n_coords_per_pt_tc);
40
+ }
41
+ }
42
+ return { 'pts': pts, 'tex_coords': tex_coords };
43
+ }
44
+ function makeDomainVerticesAndTexCoords(field_lats, field_lons, field_ni, field_nj, texcoord_margin_r, texcoord_margin_s) {
45
+ const verts = new Float32Array(2 * 2 * (field_ni - 1) * (field_nj + 1)).fill(0);
46
+ const tex_coords = new Float32Array(2 * 2 * (field_ni - 1) * (field_nj + 1)).fill(0);
47
+ const grid_cell_size = new Float32Array(1 * 2 * (field_ni - 1) * (field_nj + 1)).fill(0);
48
+ let ivert = 0;
49
+ let itexcoord = 0;
50
+ for (let i = 0; i < field_ni - 1; i++) {
51
+ for (let j = 0; j < field_nj; j++) {
52
+ const idx = i + j * field_ni;
53
+ const pt = new LngLat(field_lons[idx], field_lats[idx]).toMercatorCoord();
54
+ const pt_ip1 = new LngLat(field_lons[idx + 1], field_lats[idx + 1]).toMercatorCoord();
55
+ const r = i / (field_ni - 1) * (1 - 2 * texcoord_margin_r) + texcoord_margin_r;
56
+ const rp1 = (i + 1) / (field_ni - 1) * (1 - 2 * texcoord_margin_r) + texcoord_margin_r;
57
+ const s = j / (field_nj - 1) * (1 - 2 * texcoord_margin_s) + texcoord_margin_s;
58
+ if (j == 0) {
59
+ verts[ivert] = pt.x;
60
+ verts[ivert + 1] = pt.y;
61
+ ivert += 2;
62
+ tex_coords[itexcoord] = r;
63
+ tex_coords[itexcoord + 1] = s;
64
+ itexcoord += 2;
65
+ }
66
+ verts[ivert] = pt.x;
67
+ verts[ivert + 1] = pt.y;
68
+ verts[ivert + 2] = pt_ip1.x;
69
+ verts[ivert + 3] = pt_ip1.y;
70
+ ivert += 4;
71
+ tex_coords[itexcoord] = r;
72
+ tex_coords[itexcoord + 1] = s;
73
+ tex_coords[itexcoord + 2] = rp1;
74
+ tex_coords[itexcoord + 3] = s;
75
+ itexcoord += 4;
76
+ if (j == field_nj - 1) {
77
+ verts[ivert] = pt_ip1.x;
78
+ verts[ivert + 1] = pt_ip1.y;
79
+ ivert += 2;
80
+ tex_coords[itexcoord] = rp1;
81
+ tex_coords[itexcoord + 1] = s;
82
+ itexcoord += 2;
83
+ }
84
+ }
85
+ }
86
+ let igcs = 0;
87
+ for (let i = 0; i < field_ni - 1; i++) {
88
+ for (let j = 0; j < field_nj - 1; j++) {
89
+ const ivert = j == 0 ? 2 * (igcs + 1) : 2 * igcs;
90
+ const x_ll = verts[ivert], y_ll = verts[ivert + 1], x_lr = verts[ivert + 2], y_lr = verts[ivert + 3], x_ul = verts[ivert + 4], y_ul = verts[ivert + 5], x_ur = verts[ivert + 6], y_ur = verts[ivert + 7];
91
+ const area = 0.5 * Math.abs(x_ll * (y_lr - y_ul) + x_lr * (y_ul - y_ll) + x_ul * (y_ll - y_lr) +
92
+ x_ur * (y_ul - y_lr) + x_ul * (y_lr - y_ur) + x_lr * (y_ur - y_ul));
93
+ if (j == 0) {
94
+ grid_cell_size[igcs] = area;
95
+ igcs += 1;
96
+ }
97
+ grid_cell_size[igcs] = area;
98
+ grid_cell_size[igcs + 1] = area;
99
+ igcs += 2;
100
+ if (j == field_nj - 2) {
101
+ grid_cell_size[igcs] = area;
102
+ grid_cell_size[igcs + 1] = area;
103
+ grid_cell_size[igcs + 2] = area;
104
+ igcs += 3;
105
+ }
106
+ }
107
+ }
108
+ return { 'vertices': verts, 'tex_coords': tex_coords, 'grid_cell_size': grid_cell_size };
109
+ }
110
+ /*
111
+ function makePolylinesMiter(lines) {
112
+ const n_points_per_vert = Object.fromEntries(Object.entries(lines[0]).map(([k, v]) => {
113
+ let n_verts;
114
+ if (v.length === undefined) {
115
+ n_verts = 1;
116
+ }
117
+ else {
118
+ n_verts = k == 'verts' ? v[0].length : v.length;
119
+ }
120
+ return [k, n_verts];
121
+ }));
122
+ n_points_per_vert['extrusion'] = 2;
123
+
124
+ const n_verts = lines.map(l => l['verts'].length).reduce((a, b) => a + b);
125
+ const ary_lens = Object.fromEntries(Object.entries(n_points_per_vert).map(([k, nppv]) => [k, (n_verts * 2 + lines.length * 2) * nppv]));
126
+
127
+ let ret = Object.fromEntries(Object.entries(ary_lens).map(([k, v]) => [k, new Float32Array(v)]));
128
+
129
+ let ilns = Object.fromEntries(Object.keys(ary_lens).map(k => [k, 0]));
130
+
131
+ const is_cw_winding = (pt_prev, pt_this, pt_next) => {
132
+ const winding = (pt_this[0] - pt_prev[0]) * (pt_this[1] + pt_prev[1])
133
+ + (pt_next[0] - pt_this[0]) * (pt_next[1] + pt_this[1])
134
+ + (pt_prev[0] - pt_next[0]) * (pt_prev[1] + pt_next[1]);
135
+
136
+ return winding > 0;
137
+ }
138
+
139
+ const calculate_extrusion = (pt_prev, pt_this, pt_next) => {
140
+ let line_vec_x_prev, line_vec_y_prev, line_vec_mag_prev,
141
+ line_vec_x_next, line_vec_y_next, line_vec_mag_next;
142
+ let ext_x, ext_y;
143
+
144
+ if (pt_prev !== null) {
145
+ line_vec_x_prev = pt_this[0] - pt_prev[0];
146
+ line_vec_y_prev = pt_this[1] - pt_prev[1];
147
+ line_vec_mag_prev = Math.hypot(line_vec_x_prev, line_vec_y_prev);
148
+ line_vec_x_prev /= line_vec_mag_prev;
149
+ line_vec_y_prev /= line_vec_mag_prev;
150
+ }
151
+
152
+ if (pt_next !== null) {
153
+ line_vec_x_next = pt_next[0] - pt_this[0];
154
+ line_vec_y_next = pt_next[1] - pt_this[1];
155
+ line_vec_mag_next = Math.hypot(line_vec_x_next, line_vec_y_next);
156
+ line_vec_x_next /= line_vec_mag_next;
157
+ line_vec_y_next /= line_vec_mag_next;
158
+ }
159
+
160
+ if (pt_prev === null) {
161
+ // First point in the line gets just the normal for the first segment
162
+ ext_x = line_vec_y_next; ext_y = -line_vec_x_next;
163
+ }
164
+ else if (pt_this === null) {
165
+ // Last point in the line gets just the normal for the last segment
166
+ ext_x = line_vec_y_prev; ext_y = -line_vec_x_prev;
167
+ }
168
+ else {
169
+ // Miter join: compute the extrusion vector halfway between the next and previous normal
170
+ const dot = line_vec_x_prev * line_vec_x_next + line_vec_y_prev * line_vec_y_next;
171
+ const ext_fac = Math.sqrt((1 - dot) / (1 + dot));
172
+ const sign = is_cw_winding(pt_prev, pt_this, pt_this) ? -1 : 1;
173
+ ext_x = line_vec_y_prev + sign * line_vec_x_prev * ext_fac;
174
+ ext_y = sign * line_vec_y_prev * ext_fac - line_vec_x_prev;
175
+ }
176
+
177
+ return [ext_x, ext_y];
178
+ }
179
+
180
+ lines.forEach(line => {
181
+ const verts = line['verts'];
182
+ let ext_x, ext_y;
183
+
184
+ let ivt = 0;
185
+ ret['verts'][ilns['verts']] = verts[ivt][0]; ret['verts'][ilns['verts'] + 1] = verts[ivt][1];
186
+
187
+ [ext_x, ext_y] = calculate_extrusion(null, verts[ivt], verts[ivt + 1]);
188
+ ret['extrusion'][ilns['extrusion']] = ext_x; ret['extrusion'][ilns['extrusion'] + 1] = ext_y;
189
+
190
+ for (ivt = 0; ivt < verts.length; ivt++) {
191
+ const ary_ivt = ilns['verts'] + 2 * (2 * ivt + 1);
192
+ ret['verts'][ary_ivt + 0] = verts[ivt][0]; ret['verts'][ary_ivt + 1] = verts[ivt][1];
193
+ ret['verts'][ary_ivt + 2] = verts[ivt][0]; ret['verts'][ary_ivt + 3] = verts[ivt][1];
194
+
195
+ if (ivt == 0) {
196
+ [ext_x, ext_y] = calculate_extrusion(null, verts[ivt], verts[ivt + 1]);
197
+ }
198
+ else if (ivt == verts.length - 1) {
199
+ [ext_x, ext_y] = calculate_extrusion(verts[ivt - 1], verts[ivt], null);
200
+ }
201
+ else {
202
+ [ext_x, ext_y] = calculate_extrusion(verts[ivt - 1], verts[ivt], verts[ivt + 1]);
203
+ }
204
+
205
+ ret['extrusion'][ary_ivt + 0] = ext_x; ret['extrusion'][ary_ivt + 1] = ext_y;
206
+ ret['extrusion'][ary_ivt + 2] = -ext_x; ret['extrusion'][ary_ivt + 3] = -ext_y;
207
+ }
208
+
209
+ ivt = verts.length - 1;
210
+ ret['verts'][ilns['verts'] + 2 * (2 * ivt + 1) + 4] = verts[ivt][0];
211
+ ret['verts'][ilns['verts'] + 2 * (2 * ivt + 1) + 5] = verts[ivt][1];
212
+
213
+ [ext_x, ext_y] = calculate_extrusion(verts[ivt - 1], verts[ivt], null);
214
+
215
+ ret['extrusion'][ilns['extrusion'] + 2 * (2 * ivt + 1) + 4] = -ext_x;
216
+ ret['extrusion'][ilns['extrusion'] + 2 * (2 * ivt + 1) + 5] = -ext_y;
217
+
218
+ for (let key in ret) {
219
+ if (key == 'verts' || key == 'extrusion') continue;
220
+
221
+ for (ivt = 0; ivt < (verts.length * 2 + 2) * n_points_per_vert[key]; ivt += n_points_per_vert[key]) {
222
+ if (line[key].length !== undefined) {
223
+ line[key].forEach((cd, icd) => {
224
+ ret[key][ilns[key] + ivt + icd] = cd;
225
+ })
226
+ }
227
+ else {
228
+ ret[key][ilns[key] + ivt] = line[key];
229
+ }
230
+ }
231
+ }
232
+
233
+ Object.keys(ilns).forEach(k => {
234
+ ilns[k] += (verts.length * 2 + 2) * n_points_per_vert[k];
235
+ })
236
+ })
237
+
238
+ return ret;
239
+ }
240
+ */
241
+ function makePolylines(lines) {
242
+ const n_points_per_vert = Object.fromEntries(Object.entries(lines[0]).map(([k, v]) => {
243
+ let n_verts;
244
+ if (v.length === undefined) {
245
+ n_verts = 1;
246
+ }
247
+ else if (v[0].length === undefined) {
248
+ n_verts = v.length;
249
+ }
250
+ else {
251
+ n_verts = v[0].length;
252
+ }
253
+ return [k, n_verts];
254
+ }));
255
+ n_points_per_vert['extrusion'] = 2;
256
+ const n_verts = lines.map(l => l['verts'].length).reduce((a, b) => a + b);
257
+ const n_out_verts = (n_verts - lines.length) * 6;
258
+ const ary_lens = Object.fromEntries(Object.entries(n_points_per_vert).map(([k, nppv]) => [k, n_out_verts * nppv]));
259
+ let ret = {
260
+ 'verts': new Float32Array(ary_lens['verts']),
261
+ 'origin': new Float32Array(ary_lens['origin']),
262
+ 'extrusion': new Float32Array(ary_lens['extrusion']),
263
+ 'zoom': new Float32Array(ary_lens['zoom']),
264
+ 'texcoords': new Float32Array(ary_lens['texcoords']),
265
+ };
266
+ let ilns = Object.fromEntries(Object.keys(ary_lens).map(k => [k, 0]));
267
+ const compute_normal_vec = (pt1, pt2) => {
268
+ const line_vec_x = pt2[0] - pt1[0];
269
+ const line_vec_y = pt2[1] - pt1[1];
270
+ const line_vec_mag = Math.hypot(line_vec_x, line_vec_y);
271
+ return [line_vec_y / line_vec_mag, -line_vec_x / line_vec_mag];
272
+ };
273
+ lines.forEach(line => {
274
+ const verts = line['verts'];
275
+ const texcoords = line['texcoords'];
276
+ let ary_ivt = ilns['verts'];
277
+ let pt_prev, pt_this = verts[0], pt_next = verts[1];
278
+ let tc_prev, tc_this = texcoords[0], tc_next = texcoords[1];
279
+ let [ext_x, ext_y] = compute_normal_vec(pt_this, pt_next);
280
+ ret['verts'][ary_ivt + 0] = pt_this[0];
281
+ ret['verts'][ary_ivt + 1] = pt_this[1];
282
+ ret['texcoords'][ary_ivt + 0] = tc_this[0];
283
+ ret['texcoords'][ary_ivt + 1] = tc_this[1];
284
+ ret['extrusion'][ary_ivt + 0] = ext_x;
285
+ ret['extrusion'][ary_ivt + 1] = ext_y;
286
+ for (let ivt = 1; ivt < verts.length; ivt++) {
287
+ pt_this = verts[ivt];
288
+ pt_prev = verts[ivt - 1];
289
+ tc_this = texcoords[ivt];
290
+ tc_prev = texcoords[ivt - 1];
291
+ [ext_x, ext_y] = compute_normal_vec(pt_prev, pt_this);
292
+ ary_ivt = ilns['verts'] + (1 + (ivt - 1) * 4) * n_points_per_vert['verts'];
293
+ ret['verts'][ary_ivt + 0] = pt_prev[0];
294
+ ret['verts'][ary_ivt + 1] = pt_prev[1];
295
+ ret['verts'][ary_ivt + 2] = pt_prev[0];
296
+ ret['verts'][ary_ivt + 3] = pt_prev[1];
297
+ ret['verts'][ary_ivt + 4] = pt_this[0];
298
+ ret['verts'][ary_ivt + 5] = pt_this[1];
299
+ ret['verts'][ary_ivt + 6] = pt_this[0];
300
+ ret['verts'][ary_ivt + 7] = pt_this[1];
301
+ ret['texcoords'][ary_ivt + 0] = tc_prev[0];
302
+ ret['texcoords'][ary_ivt + 1] = tc_prev[1];
303
+ ret['texcoords'][ary_ivt + 2] = tc_prev[0];
304
+ ret['texcoords'][ary_ivt + 3] = tc_prev[1];
305
+ ret['texcoords'][ary_ivt + 4] = tc_this[0];
306
+ ret['texcoords'][ary_ivt + 5] = tc_this[1];
307
+ ret['texcoords'][ary_ivt + 6] = tc_this[0];
308
+ ret['texcoords'][ary_ivt + 7] = tc_this[1];
309
+ ret['extrusion'][ary_ivt + 0] = ext_x;
310
+ ret['extrusion'][ary_ivt + 1] = ext_y;
311
+ ret['extrusion'][ary_ivt + 2] = -ext_x;
312
+ ret['extrusion'][ary_ivt + 3] = -ext_y;
313
+ ret['extrusion'][ary_ivt + 4] = ext_x;
314
+ ret['extrusion'][ary_ivt + 5] = ext_y;
315
+ ret['extrusion'][ary_ivt + 6] = -ext_x;
316
+ ret['extrusion'][ary_ivt + 7] = -ext_y;
317
+ }
318
+ ret['verts'][ary_ivt + 8] = pt_this[0];
319
+ ret['verts'][ary_ivt + 9] = pt_this[1];
320
+ ret['texcoords'][ary_ivt + 8] = tc_this[0];
321
+ ret['texcoords'][ary_ivt + 9] = tc_this[1];
322
+ ret['extrusion'][ary_ivt + 8] = -ext_x;
323
+ ret['extrusion'][ary_ivt + 9] = -ext_y;
324
+ for (let ivt = 0; ivt < (verts.length - 1) * 6 * n_points_per_vert['origin']; ivt += n_points_per_vert['origin']) {
325
+ line['origin'].forEach((cd, icd) => {
326
+ ret['origin'][ilns['origin'] + ivt + icd] = cd;
327
+ });
328
+ }
329
+ for (let ivt = 0; ivt < (verts.length - 1) * 6 * n_points_per_vert['zoom']; ivt += n_points_per_vert['zoom']) {
330
+ ret['zoom'][ilns['zoom'] + ivt] = line['zoom'];
331
+ }
332
+ Object.keys(ilns).forEach(k => {
333
+ ilns[k] += (verts.length - 1) * 6 * n_points_per_vert[k];
334
+ });
335
+ });
336
+ return ret;
337
+ }
338
+ const ep_interface = {
339
+ 'makeBBElements': makeBBElements,
340
+ 'makeDomainVerticesAndTexCoords': makeDomainVerticesAndTexCoords,
341
+ 'makePolyLines': makePolylines
342
+ };
343
+ Comlink.expose(ep_interface);
@@ -0,0 +1,17 @@
1
+ import { WGLBuffer, WGLProgram, WGLTexture, WGLTextureSpec } from "autumn-wgl";
2
+ import { PolylineSpec, LineSpec, WebGLAnyRenderingContext } from "./AutumnTypes";
3
+ declare class PolylineCollection {
4
+ readonly width: number;
5
+ readonly scale: number;
6
+ readonly program: WGLProgram;
7
+ readonly origin: WGLBuffer;
8
+ readonly offset: WGLBuffer;
9
+ readonly extrusion: WGLBuffer;
10
+ readonly min_zoom: WGLBuffer;
11
+ readonly texture: WGLTexture;
12
+ readonly texcoords: WGLBuffer;
13
+ constructor(gl: WebGLAnyRenderingContext, polyline: PolylineSpec, tex_image: WGLTextureSpec, line_width: number, offset_scale: number);
14
+ render(gl: WebGLAnyRenderingContext, matrix: number[], [map_width, map_height]: [number, number], map_zoom: number, map_bearing: number, map_pitch: number): void;
15
+ }
16
+ export { PolylineCollection };
17
+ export type { PolylineSpec, LineSpec };
@@ -0,0 +1,92 @@
1
+ import { WGLBuffer, WGLProgram, WGLTexture } from "autumn-wgl";
2
+ const polyline_vertex_src = `uniform mat4 u_matrix;
3
+
4
+ attribute vec2 a_pos;
5
+ attribute float a_min_zoom;
6
+ attribute vec2 a_extrusion;
7
+ attribute vec2 a_offset;
8
+ attribute vec2 a_tex_coord;
9
+ uniform lowp float u_offset_scale;
10
+ uniform lowp float u_map_aspect;
11
+ uniform lowp float u_zoom;
12
+ uniform lowp float u_line_width;
13
+ uniform highp float u_map_bearing;
14
+
15
+
16
+ varying highp vec2 v_tex_coord;
17
+
18
+ mat4 scalingMatrix(float x_scale, float y_scale, float z_scale) {
19
+ return mat4(x_scale, 0.0, 0.0, 0.0,
20
+ 0.0, y_scale, 0.0, 0.0,
21
+ 0.0, 0.0, z_scale, 0.0,
22
+ 0.0, 0.0, 0.0, 1.0);
23
+ }
24
+
25
+ mat4 rotationZMatrix(float angle) {
26
+ float s = sin(angle);
27
+ float c = cos(angle);
28
+
29
+ return mat4( c, s, 0., 0.,
30
+ -s, c, 0., 0.,
31
+ 0., 0., 1., 0.,
32
+ 0., 0., 0., 1.);
33
+ }
34
+
35
+ mat4 rotationXMatrix(float angle) {
36
+ float s = sin(angle);
37
+ float c = cos(angle);
38
+
39
+ return mat4( 1., 0., 0., 0.,
40
+ 0., c, s, 0.,
41
+ 0., -s, c, 0.,
42
+ 0., 0., 0., 1.);
43
+ }
44
+
45
+ void main() {
46
+ vec4 center_pos = u_matrix * vec4(a_pos.xy, 0.0, 1.0);
47
+ vec4 offset = vec4(0.0, 0.0, 0.0, 0.0);
48
+
49
+ if (u_zoom >= a_min_zoom) {
50
+ vec2 offset_2d = a_offset + u_line_width * a_extrusion;
51
+
52
+ mat4 rotation_matrix = rotationZMatrix(radians(u_map_bearing));
53
+ mat4 map_stretch_matrix = scalingMatrix(u_offset_scale, u_offset_scale / u_map_aspect, 1.);
54
+ offset = map_stretch_matrix * rotation_matrix * vec4(offset_2d, 0., 0.);
55
+ }
56
+
57
+ gl_Position = center_pos + offset;
58
+ v_tex_coord = a_tex_coord;
59
+ }`
60
+ const polyline_fragment_src = `varying highp vec2 v_tex_coord;
61
+
62
+ uniform sampler2D u_sampler;
63
+
64
+ void main() {
65
+ if (v_tex_coord.x > 1.0) {
66
+ discard;
67
+ }
68
+
69
+ lowp vec4 tex_color = texture2D(u_sampler, v_tex_coord);
70
+ gl_FragColor = tex_color;
71
+ }`
72
+ class PolylineCollection {
73
+ constructor(gl, polyline, tex_image, line_width, offset_scale) {
74
+ this.width = line_width;
75
+ this.scale = offset_scale;
76
+ this.program = new WGLProgram(gl, polyline_vertex_src, polyline_fragment_src);
77
+ this.origin = new WGLBuffer(gl, polyline['origin'], 2, gl.TRIANGLE_STRIP);
78
+ this.offset = new WGLBuffer(gl, polyline['verts'], 2, gl.TRIANGLE_STRIP);
79
+ this.extrusion = new WGLBuffer(gl, polyline['extrusion'], 2, gl.TRIANGLE_STRIP);
80
+ this.min_zoom = new WGLBuffer(gl, polyline['zoom'], 1, gl.TRIANGLE_STRIP);
81
+ this.texture = new WGLTexture(gl, tex_image);
82
+ this.texcoords = new WGLBuffer(gl, polyline['texcoords'], 2, gl.TRIANGLE_STRIP);
83
+ }
84
+ render(gl, matrix, [map_width, map_height], map_zoom, map_bearing, map_pitch) {
85
+ this.program.use({ 'a_pos': this.origin, 'a_offset': this.offset, 'a_extrusion': this.extrusion, 'a_min_zoom': this.min_zoom, 'a_tex_coord': this.texcoords }, { 'u_offset_scale': this.scale * (map_height / map_width), 'u_line_width': this.width, 'u_matrix': matrix,
86
+ 'u_map_aspect': map_height / map_width, 'u_zoom': map_zoom, 'u_map_bearing': map_bearing }, { 'u_sampler': this.texture });
87
+ gl.enable(gl.BLEND);
88
+ gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
89
+ this.program.draw();
90
+ }
91
+ }
92
+ export { PolylineCollection };
@@ -0,0 +1,194 @@
1
+ import { WebGLAnyRenderingContext, WindProfile } from "./AutumnTypes";
2
+ import { WGLBuffer } from "autumn-wgl";
3
+ declare class Cache<A extends unknown[], R> {
4
+ cached_value: R | null;
5
+ compute_value: (...args: A) => R;
6
+ constructor(compute_value: (...args: A) => R);
7
+ getValue(...args: A): R;
8
+ }
9
+ interface Coords {
10
+ lons: Float32Array;
11
+ lats: Float32Array;
12
+ }
13
+ type GridType = 'latlon' | 'lcc';
14
+ declare abstract class Grid {
15
+ readonly type: GridType;
16
+ readonly ni: number;
17
+ readonly nj: number;
18
+ readonly is_conformal: boolean;
19
+ readonly _buffer_cache: Cache<[WebGLAnyRenderingContext], Promise<{
20
+ 'vertices': WGLBuffer;
21
+ 'texcoords': WGLBuffer;
22
+ 'cellsize': WGLBuffer;
23
+ }>>;
24
+ readonly _billboard_buffer_cache: Cache<[WebGLAnyRenderingContext, number, number], Promise<{
25
+ 'vertices': WGLBuffer;
26
+ 'texcoords': WGLBuffer;
27
+ }>>;
28
+ constructor(type: GridType, is_conformal: boolean, ni: number, nj: number);
29
+ abstract copy(opts?: {
30
+ ni?: number;
31
+ nj?: number;
32
+ }): Grid;
33
+ abstract getCoords(): Coords;
34
+ abstract transform(x: number, y: number, opts?: {
35
+ inverse?: boolean;
36
+ }): [number, number];
37
+ abstract getThinnedGrid(thin_x: number, thin_y: number): Grid;
38
+ getWGLBuffers(gl: WebGLAnyRenderingContext): Promise<{
39
+ vertices: WGLBuffer;
40
+ texcoords: WGLBuffer;
41
+ cellsize: WGLBuffer;
42
+ }>;
43
+ getWGLBillboardBuffers(gl: WebGLAnyRenderingContext, thin_fac: number, max_zoom: number): Promise<{
44
+ vertices: WGLBuffer;
45
+ texcoords: WGLBuffer;
46
+ }>;
47
+ }
48
+ /** A plate carree (a.k.a. lat/lon) grid with uniform grid spacing */
49
+ declare class PlateCarreeGrid extends Grid {
50
+ readonly ll_lon: number;
51
+ readonly ll_lat: number;
52
+ readonly ur_lon: number;
53
+ readonly ur_lat: number;
54
+ /** @private */
55
+ readonly _ll_cache: Cache<[], Coords>;
56
+ /**
57
+ * Create a plate carree grid
58
+ * @param ni - The number of grid points in the i (longitude) direction
59
+ * @param nj - The number of grid points in the j (latitude) direction
60
+ * @param ll_lon - The longitude of the lower left corner of the grid
61
+ * @param ll_lat - The latitude of the lower left corner of the grid
62
+ * @param ur_lon - The longitude of the upper right corner of the grid
63
+ * @param ur_lat - The latitude of the upper right corner of the grid
64
+ */
65
+ constructor(ni: number, nj: number, ll_lon: number, ll_lat: number, ur_lon: number, ur_lat: number);
66
+ copy(opts?: {
67
+ ni?: number;
68
+ nj?: number;
69
+ ll_lon?: number;
70
+ ll_lat?: number;
71
+ ur_lon?: number;
72
+ ur_lat?: number;
73
+ }): PlateCarreeGrid;
74
+ /**
75
+ * Get a list of longitudes and latitudes on the grid (internal method)
76
+ */
77
+ getCoords(): Coords;
78
+ transform(x: number, y: number, opts?: {
79
+ inverse?: boolean;
80
+ }): [number, number];
81
+ getThinnedGrid(thin_x: number, thin_y: number): PlateCarreeGrid;
82
+ }
83
+ /** A Lambert conformal conic grid with uniform grid spacing */
84
+ declare class LambertGrid extends Grid {
85
+ readonly lon_0: number;
86
+ readonly lat_0: number;
87
+ readonly lat_std: [number, number];
88
+ readonly ll_x: number;
89
+ readonly ll_y: number;
90
+ readonly ur_x: number;
91
+ readonly ur_y: number;
92
+ /** @private */
93
+ readonly lcc: (a: number, b: number, opts?: {
94
+ inverse: boolean;
95
+ }) => [number, number];
96
+ /** @private */
97
+ readonly _ll_cache: Cache<[], Coords>;
98
+ /**
99
+ * Create a Lambert conformal conic grid
100
+ * @param ni - The number of grid points in the i (longitude) direction
101
+ * @param nj - The number of grid points in the j (latitude) direction
102
+ * @param lon_0 - The standard longitude for the projection; this is also the center longitude for the projection
103
+ * @param lat_0 - The center latitude for the projection
104
+ * @param lat_std - The standard latitudes for the projection
105
+ * @param ll_x - The x coordinate in projection space of the lower-left corner of the grid
106
+ * @param ll_y - The y coordinate in projection space of the lower-left corner of the grid
107
+ * @param ur_x - The x coordinate in projection space of the upper-right corner of the grid
108
+ * @param ur_y - The y coordinate in projection space of the upper-right corner of the grid
109
+ */
110
+ constructor(ni: number, nj: number, lon_0: number, lat_0: number, lat_std: [number, number], ll_x: number, ll_y: number, ur_x: number, ur_y: number);
111
+ copy(opts?: {
112
+ ni?: number;
113
+ nj?: number;
114
+ ll_x?: number;
115
+ ll_y?: number;
116
+ ur_x?: number;
117
+ ur_y?: number;
118
+ }): LambertGrid;
119
+ /**
120
+ * Get a list of longitudes and latitudes on the grid (internal method)
121
+ */
122
+ getCoords(): Coords;
123
+ transform(x: number, y: number, opts?: {
124
+ inverse?: boolean;
125
+ }): [number, number];
126
+ getThinnedGrid(thin_x: number, thin_y: number): LambertGrid;
127
+ }
128
+ /** A class representing a raw 2D field of gridded data, such as height or u wind. */
129
+ declare class RawScalarField {
130
+ readonly grid: Grid;
131
+ readonly data: Float32Array;
132
+ /**
133
+ * Create a data field.
134
+ * @param grid - The grid on which the data are defined
135
+ * @param data - The data, which should be given as a 1D array in row-major order, with the first element being at the lower-left corner of the grid.
136
+ */
137
+ constructor(grid: Grid, data: Float32Array);
138
+ getThinnedField(thin_x: number, thin_y: number): RawScalarField;
139
+ /**
140
+ * Create a new field by aggregating a number of fields using a specific function
141
+ * @param func - A function that will be applied each element of the field. It should take the same number of arguments as fields you have and return a single number.
142
+ * @param args - The RawScalarFields to aggregate
143
+ * @returns a new gridded field
144
+ * @example
145
+ * // Compute wind speed from u and v
146
+ * wind_speed_field = RawScalarField.aggreateFields(Math.hypot, u_field, v_field);
147
+ */
148
+ static aggregateFields(func: (...args: number[]) => number, ...args: RawScalarField[]): RawScalarField;
149
+ }
150
+ type VectorRelativeTo = 'earth' | 'grid';
151
+ interface RawVectorFieldOptions {
152
+ /**
153
+ * Whether the vectors are relative to the grid ('grid') or Earth ('earth')
154
+ * @default 'grid'
155
+ */
156
+ relative_to?: VectorRelativeTo;
157
+ }
158
+ /** A class representing a 2D gridded field of vectors */
159
+ declare class RawVectorField {
160
+ readonly u: RawScalarField;
161
+ readonly v: RawScalarField;
162
+ readonly relative_to: VectorRelativeTo;
163
+ /** @private */
164
+ readonly _rotate_cache: Cache<[], {
165
+ u: RawScalarField;
166
+ v: RawScalarField;
167
+ }>;
168
+ /**
169
+ * Create a vector field.
170
+ * @param grid - The grid on which the vector components are defined
171
+ * @param u - The u (east/west) component of the vectors, which should be given as a 1D array in row-major order, with the first element being at the lower-left corner of the grid
172
+ * @param v - The v (north/south) component of the vectors, which should be given as a 1D array in row-major order, with the first element being at the lower-left corner of the grid
173
+ * @param opts - Options for creating the vector field.
174
+ */
175
+ constructor(grid: Grid, u: Float32Array, v: Float32Array, opts?: RawVectorFieldOptions);
176
+ getThinnedField(thin_x: number, thin_y: number): RawVectorField;
177
+ get grid(): Grid;
178
+ toEarthRelative(): RawVectorField;
179
+ }
180
+ /** A class grid of wind profiles */
181
+ declare class RawProfileField {
182
+ readonly profiles: WindProfile[];
183
+ readonly grid: Grid;
184
+ /**
185
+ * Create a grid of wind profiles
186
+ * @param grid - The grid on which the profiles are defined
187
+ * @param profiles - The wind profiles themselves, which should be given as a 1D array in row-major order, with the first profile being at the lower-left corner of the grid
188
+ */
189
+ constructor(grid: Grid, profiles: WindProfile[]);
190
+ /** Get the gridded storm motion vector field (internal method) */
191
+ getStormMotionGrid(): RawVectorField;
192
+ }
193
+ export { RawScalarField, RawVectorField, RawProfileField, PlateCarreeGrid, LambertGrid, Grid };
194
+ export type { GridType, RawVectorFieldOptions, VectorRelativeTo };