@sakuzu/maplibre-gl-draw 2.1.0 → 2.1.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.
- package/CHANGELOG.md +25 -0
- package/dist/dataset/retained.js +19 -18
- package/dist/dataset/selection.d.ts +3 -3
- package/dist/dataset/selection.js +3 -7
- package/dist/dataset/table-source.js +13 -11
- package/dist/view/layer/custom-layer.js +2 -1
- package/dist/view/layer/drape-planner.d.ts +3 -0
- package/dist/view/layer/drape-planner.js +9 -7
- package/dist/view/layer/frame-render.js +2 -3
- package/dist/view/layer/frame-state.d.ts +3 -0
- package/dist/view/layer/frame-state.js +2 -1
- package/dist/view/layer/gl-state.d.ts +2 -3
- package/dist/view/layer/gl-state.js +2 -3
- package/dist/view/renderers/line/dash.d.ts +27 -0
- package/dist/view/renderers/line/dash.js +23 -12
- package/dist/view/terrain/drape/bin-store.d.ts +11 -2
- package/dist/view/terrain/drape/bin-store.js +26 -2
- package/dist/view/terrain/drape/binning.d.ts +32 -0
- package/dist/view/terrain/drape/binning.js +15 -7
- package/dist/view/terrain/drape/geometry.d.ts +27 -0
- package/dist/view/terrain/drape/geometry.js +57 -6
- package/dist/view/terrain/drape/pass.d.ts +8 -3
- package/dist/view/terrain/drape/pass.js +39 -28
- package/dist/view/terrain/drape/renderer.d.ts +13 -0
- package/dist/view/terrain/drape/renderer.js +157 -30
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: 2026 SAKAIDA Atsushi
|
|
2
2
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
3
|
-
import { DRAPE_BLOCK_EDGES, drapeQuantizedGeometry } from './geometry.js';
|
|
3
|
+
import { DRAPE_BLOCK_EDGES, drapePathStarts, drapeQuantizedGeometry } from './geometry.js';
|
|
4
4
|
/** The minimum number of divisions of the grid (per side) */
|
|
5
5
|
export const DRAPE_MIN_CELLS = 8;
|
|
6
6
|
/** The maximum number of divisions of the grid (per side) */
|
|
@@ -245,6 +245,7 @@ function emptyBins(grid, edgeEstimate, overflow) {
|
|
|
245
245
|
cells: new Int32Array(grid * grid * 2),
|
|
246
246
|
runs: new Float32Array(0),
|
|
247
247
|
edges: new Float32Array(0),
|
|
248
|
+
edgeStarts: new Float32Array(0),
|
|
248
249
|
overflow,
|
|
249
250
|
truncatedCells: 0,
|
|
250
251
|
cellWork: 0,
|
|
@@ -264,6 +265,8 @@ function assembleBins(elements, picked, tile, scale, grid, zoom) {
|
|
|
264
265
|
const cells = new Int32Array(cellCount * 2);
|
|
265
266
|
/** The edges in in-tile coordinates (four numbers per edge). Stacked across elements */
|
|
266
267
|
const edgeXY = [];
|
|
268
|
+
/** Where each edge starts along its path (one number per edge) */
|
|
269
|
+
const edgeS = [];
|
|
267
270
|
/** The runs per cell. Triples of (elementIndex, inside, edgeListIndex) laid out in a row */
|
|
268
271
|
const cellRuns = new Array(cellCount);
|
|
269
272
|
/** The lists of edge indices that the runs point to */
|
|
@@ -299,8 +302,9 @@ function assembleBins(elements, picked, tile, scale, grid, zoom) {
|
|
|
299
302
|
scratch.clear();
|
|
300
303
|
touchedRows.length = 0;
|
|
301
304
|
rowTouched.fill(0);
|
|
305
|
+
const dashed = element.dash != null;
|
|
302
306
|
for (const path of picked.geometries[p].paths) {
|
|
303
|
-
scanPath(path, element.kind, tileX, tileY, scale, margin, grid, cellSize, edgeXY, scratch, rowDiff, rowTouched, touchedRows);
|
|
307
|
+
scanPath(path, element.kind, tileX, tileY, scale, margin, grid, cellSize, { xy: edgeXY, starts: edgeS }, dashed ? drapePathStarts(path) : null, scratch, rowDiff, rowTouched, touchedRows);
|
|
304
308
|
}
|
|
305
309
|
// Turn the cells that are inside and the cells the edges touch into runs (they are stacked
|
|
306
310
|
// in the order of the elements, so this comes out as the draw order)
|
|
@@ -328,6 +332,7 @@ function assembleBins(elements, picked, tile, scale, grid, zoom) {
|
|
|
328
332
|
// Repacking (laid out in the order cell -> run -> edge)
|
|
329
333
|
const runs = [];
|
|
330
334
|
const edges = [];
|
|
335
|
+
const edgeStarts = [];
|
|
331
336
|
for (let c = 0; c < cellCount; c++) {
|
|
332
337
|
const list = cellRuns[c];
|
|
333
338
|
cells[c * 2] = runs.length / 4;
|
|
@@ -352,6 +357,7 @@ function assembleBins(elements, picked, tile, scale, grid, zoom) {
|
|
|
352
357
|
break;
|
|
353
358
|
}
|
|
354
359
|
edges.push(edgeXY[at * 4], edgeXY[at * 4 + 1], edgeXY[at * 4 + 2], edgeXY[at * 4 + 3]);
|
|
360
|
+
edgeStarts.push(edgeS[at]);
|
|
355
361
|
edgeCount++;
|
|
356
362
|
}
|
|
357
363
|
}
|
|
@@ -371,6 +377,7 @@ function assembleBins(elements, picked, tile, scale, grid, zoom) {
|
|
|
371
377
|
cells,
|
|
372
378
|
runs: new Float32Array(runs),
|
|
373
379
|
edges: new Float32Array(edges),
|
|
380
|
+
edgeStarts: new Float32Array(edgeStarts),
|
|
374
381
|
overflow: false,
|
|
375
382
|
truncatedCells,
|
|
376
383
|
cellWork: (runs.length / 4 + edges.length / 4) / cellCount,
|
|
@@ -430,7 +437,7 @@ function pickElements(elements, candidates, tile, scale, quantizeLevel, zoom) {
|
|
|
430
437
|
* right of the origin, so an edge outside the tile on the right also counts (it becomes a flip
|
|
431
438
|
* of a whole row). An edge outside on the left flips nothing, so it can be dropped.
|
|
432
439
|
*/
|
|
433
|
-
function scanPath(path, kind, tileX, tileY, scale, margin, grid, cellSize,
|
|
440
|
+
function scanPath(path, kind, tileX, tileY, scale, margin, grid, cellSize, out, starts, scratch, rowDiff, rowTouched, touchedRows) {
|
|
434
441
|
const xy = path.xy;
|
|
435
442
|
const vertexCount = xy.length / 2;
|
|
436
443
|
const blockCount = path.blocks.length / 4;
|
|
@@ -458,7 +465,7 @@ function scanPath(path, kind, tileX, tileY, scale, margin, grid, cellSize, edgeX
|
|
|
458
465
|
if (kind === 0) {
|
|
459
466
|
accumulateInside(x0, y0, x1, y1, grid, cellSize, rowDiff, rowTouched, touchedRows);
|
|
460
467
|
}
|
|
461
|
-
scatterEdge(x0, y0, x1, y1, margin, grid, cellSize,
|
|
468
|
+
scatterEdge(x0, y0, x1, y1, starts ? starts[e] : 0, margin, grid, cellSize, out, scratch);
|
|
462
469
|
}
|
|
463
470
|
}
|
|
464
471
|
}
|
|
@@ -511,7 +518,7 @@ function accumulateInside(x0, y0, x1, y1, grid, cellSize, rowDiff, rowTouched, t
|
|
|
511
518
|
* that crosses the tile diagonally. The segment is cut per row to obtain the range of x, and
|
|
512
519
|
* only the columns in that range are touched.
|
|
513
520
|
*/
|
|
514
|
-
function scatterEdge(x0, y0, x1, y1, margin, grid, cellSize,
|
|
521
|
+
function scatterEdge(x0, y0, x1, y1, start, margin, grid, cellSize, out, scratch) {
|
|
515
522
|
const exLo = Math.min(x0, x1) - margin;
|
|
516
523
|
const exHi = Math.max(x0, x1) + margin;
|
|
517
524
|
const eyLo = Math.min(y0, y1) - margin;
|
|
@@ -526,8 +533,9 @@ function scatterEdge(x0, y0, x1, y1, margin, grid, cellSize, edgeXY, scratch) {
|
|
|
526
533
|
jHi = grid - 1;
|
|
527
534
|
if (jLo > jHi)
|
|
528
535
|
return;
|
|
529
|
-
const at =
|
|
530
|
-
|
|
536
|
+
const at = out.starts.length;
|
|
537
|
+
out.xy.push(x0, y0, x1, y1);
|
|
538
|
+
out.starts.push(start);
|
|
531
539
|
const horizontal = y0 === y1;
|
|
532
540
|
const inv = horizontal ? 0 : 1 / (y1 - y0);
|
|
533
541
|
for (let j = jLo; j <= jHi; j++) {
|
|
@@ -29,6 +29,20 @@ export interface DrapeGeometryPath {
|
|
|
29
29
|
readonly closed: boolean;
|
|
30
30
|
/** The bounding box of each block (minX, minY, maxX, maxY) */
|
|
31
31
|
readonly blocks: Float64Array;
|
|
32
|
+
/**
|
|
33
|
+
* Where the vertices of a quantized path came from (absent on a path of the original
|
|
34
|
+
* geometry)
|
|
35
|
+
*
|
|
36
|
+
* A dashed line keeps the pattern of the original path on a coarse tile: the length along
|
|
37
|
+
* the path at a vertex is that of the original vertex it was rounded from
|
|
38
|
+
* ({@link drapePathStarts}).
|
|
39
|
+
*/
|
|
40
|
+
readonly origin?: DrapePathOrigin;
|
|
41
|
+
}
|
|
42
|
+
/** The original path of a quantized path, and the original vertex of each of its vertices */
|
|
43
|
+
export interface DrapePathOrigin {
|
|
44
|
+
readonly path: DrapeGeometryPath;
|
|
45
|
+
readonly vertices: Int32Array;
|
|
32
46
|
}
|
|
33
47
|
/** The geometry of a single feature */
|
|
34
48
|
export interface DrapeGeometry {
|
|
@@ -93,3 +107,16 @@ export declare function buildDrapeGeometry(paths: ReadonlyArray<ReadonlyArray<Co
|
|
|
93
107
|
* @param level The fineness of the grid. The grid spacing is 2^-level (in Mercator units)
|
|
94
108
|
*/
|
|
95
109
|
export declare function drapeQuantizedGeometry(geometry: DrapeGeometry, level: number): DrapeGeometry;
|
|
110
|
+
/**
|
|
111
|
+
* The length along a path from its first vertex to each of its vertices (Mercator units)
|
|
112
|
+
*
|
|
113
|
+
* Entry `e` is where edge `e` starts on the path, so a dashed line can place its pattern on
|
|
114
|
+
* every edge on its own (on a closed path the last edge, back to the first vertex, starts at
|
|
115
|
+
* the last vertex). It is built only for dashed lines and polygons with a dashed outline, the
|
|
116
|
+
* first time a tile needs it, so a solid line pays nothing for it.
|
|
117
|
+
*
|
|
118
|
+
* A quantized path takes the lengths of the original vertices it was rounded from, so the
|
|
119
|
+
* pattern on a coarse tile stays in step with the pattern on the fine tiles next to it. A
|
|
120
|
+
* square standing in for a collapsed polygon has no original and measures itself.
|
|
121
|
+
*/
|
|
122
|
+
export declare function drapePathStarts(path: DrapeGeometryPath): Float64Array;
|
|
@@ -103,15 +103,18 @@ export function buildDrapeGeometry(paths, closed) {
|
|
|
103
103
|
}
|
|
104
104
|
/**
|
|
105
105
|
* Assembles the geometry from paths in Mercator coordinates
|
|
106
|
+
*
|
|
107
|
+
* @param origins The origin of each path, for quantized geometry (null for a path that has none)
|
|
106
108
|
*/
|
|
107
|
-
function buildFromMercatorPaths(paths, closed) {
|
|
109
|
+
function buildFromMercatorPaths(paths, closed, origins) {
|
|
108
110
|
const out = [];
|
|
109
111
|
let minX = Number.POSITIVE_INFINITY;
|
|
110
112
|
let minY = Number.POSITIVE_INFINITY;
|
|
111
113
|
let maxX = Number.NEGATIVE_INFINITY;
|
|
112
114
|
let maxY = Number.NEGATIVE_INFINITY;
|
|
113
115
|
let edgeCount = 0;
|
|
114
|
-
for (
|
|
116
|
+
for (let p = 0; p < paths.length; p++) {
|
|
117
|
+
const xy = paths[p];
|
|
115
118
|
const count = xy.length / 2;
|
|
116
119
|
if (count < 2)
|
|
117
120
|
continue;
|
|
@@ -156,7 +159,10 @@ function buildFromMercatorPaths(paths, closed) {
|
|
|
156
159
|
blocks[b * 4 + 2] = bx1;
|
|
157
160
|
blocks[b * 4 + 3] = by1;
|
|
158
161
|
}
|
|
159
|
-
|
|
162
|
+
const origin = origins?.[p];
|
|
163
|
+
out.push(origin
|
|
164
|
+
? { xy, edgeCount: edges, closed, blocks, origin }
|
|
165
|
+
: { xy, edgeCount: edges, closed, blocks });
|
|
160
166
|
edgeCount += edges;
|
|
161
167
|
}
|
|
162
168
|
if (out.length === 0) {
|
|
@@ -193,10 +199,13 @@ export function drapeQuantizedGeometry(geometry, level) {
|
|
|
193
199
|
return hit;
|
|
194
200
|
const step = 2 ** -level;
|
|
195
201
|
const paths = [];
|
|
202
|
+
const origins = [];
|
|
196
203
|
for (const path of geometry.paths) {
|
|
197
204
|
const source = path.xy;
|
|
198
205
|
const count = source.length / 2;
|
|
199
206
|
const out = [];
|
|
207
|
+
/** The original vertex of each vertex kept */
|
|
208
|
+
const kept = [];
|
|
200
209
|
let lastX = Number.NaN;
|
|
201
210
|
let lastY = Number.NaN;
|
|
202
211
|
for (let i = 0; i < count; i++) {
|
|
@@ -205,6 +214,7 @@ export function drapeQuantizedGeometry(geometry, level) {
|
|
|
205
214
|
if (qx === lastX && qy === lastY)
|
|
206
215
|
continue;
|
|
207
216
|
out.push(qx, qy);
|
|
217
|
+
kept.push(i);
|
|
208
218
|
lastX = qx;
|
|
209
219
|
lastY = qy;
|
|
210
220
|
}
|
|
@@ -213,16 +223,20 @@ export function drapeQuantizedGeometry(geometry, level) {
|
|
|
213
223
|
// dropped
|
|
214
224
|
while (out.length >= 6 && out[out.length - 2] === out[0] && out[out.length - 1] === out[1]) {
|
|
215
225
|
out.length -= 2;
|
|
226
|
+
kept.length -= 1;
|
|
216
227
|
}
|
|
217
228
|
if (out.length / 2 < 3) {
|
|
218
229
|
// A polygon collapsed by the rounding is replaced with a square of one grid cell.
|
|
219
230
|
// Dropping it would leave the fill missing just there and produce spotty white
|
|
220
231
|
// blotches (confirmed hands-on, the city center at z8.7; it was pronounced in areas
|
|
221
232
|
// where small polygons at the chome level line up).
|
|
222
|
-
// The error stays within one grid cell (1/256 of a tile = about 2 pixels on screen)
|
|
233
|
+
// The error stays within one grid cell (1/256 of a tile = about 2 pixels on screen).
|
|
234
|
+
// It has no origin: a dash pattern on it would be smaller than a pixel anyway
|
|
223
235
|
const box = squareAt(source, step);
|
|
224
|
-
if (box)
|
|
236
|
+
if (box) {
|
|
225
237
|
paths.push(box);
|
|
238
|
+
origins.push(null);
|
|
239
|
+
}
|
|
226
240
|
continue;
|
|
227
241
|
}
|
|
228
242
|
}
|
|
@@ -230,8 +244,9 @@ export function drapeQuantizedGeometry(geometry, level) {
|
|
|
230
244
|
continue;
|
|
231
245
|
}
|
|
232
246
|
paths.push(new Float64Array(out));
|
|
247
|
+
origins.push({ path, vertices: new Int32Array(kept) });
|
|
233
248
|
}
|
|
234
|
-
const built = buildFromMercatorPaths(paths, geometry.paths[0]?.closed ?? true);
|
|
249
|
+
const built = buildFromMercatorPaths(paths, geometry.paths[0]?.closed ?? true, origins);
|
|
235
250
|
cache.set(level, built);
|
|
236
251
|
return built;
|
|
237
252
|
}
|
|
@@ -258,3 +273,39 @@ function squareAt(source, step) {
|
|
|
258
273
|
const cy = Math.round(sy / count / step) * step;
|
|
259
274
|
return new Float64Array([cx, cy, cx + step, cy, cx + step, cy + step, cx, cy + step]);
|
|
260
275
|
}
|
|
276
|
+
/** The lengths along the paths (path -> the length at each vertex) */
|
|
277
|
+
const startsCache = new WeakMap();
|
|
278
|
+
/**
|
|
279
|
+
* The length along a path from its first vertex to each of its vertices (Mercator units)
|
|
280
|
+
*
|
|
281
|
+
* Entry `e` is where edge `e` starts on the path, so a dashed line can place its pattern on
|
|
282
|
+
* every edge on its own (on a closed path the last edge, back to the first vertex, starts at
|
|
283
|
+
* the last vertex). It is built only for dashed lines and polygons with a dashed outline, the
|
|
284
|
+
* first time a tile needs it, so a solid line pays nothing for it.
|
|
285
|
+
*
|
|
286
|
+
* A quantized path takes the lengths of the original vertices it was rounded from, so the
|
|
287
|
+
* pattern on a coarse tile stays in step with the pattern on the fine tiles next to it. A
|
|
288
|
+
* square standing in for a collapsed polygon has no original and measures itself.
|
|
289
|
+
*/
|
|
290
|
+
export function drapePathStarts(path) {
|
|
291
|
+
const hit = startsCache.get(path);
|
|
292
|
+
if (hit)
|
|
293
|
+
return hit;
|
|
294
|
+
const count = path.xy.length / 2;
|
|
295
|
+
const starts = new Float64Array(count);
|
|
296
|
+
if (path.origin) {
|
|
297
|
+
const original = drapePathStarts(path.origin.path);
|
|
298
|
+
for (let i = 0; i < count; i++)
|
|
299
|
+
starts[i] = original[path.origin.vertices[i]];
|
|
300
|
+
}
|
|
301
|
+
else {
|
|
302
|
+
const xy = path.xy;
|
|
303
|
+
let total = 0;
|
|
304
|
+
for (let i = 1; i < count; i++) {
|
|
305
|
+
total += Math.hypot(xy[i * 2] - xy[i * 2 - 2], xy[i * 2 + 1] - xy[i * 2 - 1]);
|
|
306
|
+
starts[i] = total;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
startsCache.set(path, starts);
|
|
310
|
+
return starts;
|
|
311
|
+
}
|
|
@@ -25,8 +25,8 @@ export declare const DRAPE_MAX_VERTICES = 4000000;
|
|
|
25
25
|
export interface DrapeCollectResult {
|
|
26
26
|
/** The elements in draw order */
|
|
27
27
|
readonly elements: DrapeElement[];
|
|
28
|
-
/** The IDs of the Store features that cannot be put on the drape (points, images
|
|
29
|
-
*
|
|
28
|
+
/** The IDs of the Store features that cannot be put on the drape (points, images and so
|
|
29
|
+
* on) */
|
|
30
30
|
readonly excluded: Set<string>;
|
|
31
31
|
/** The breaks in the stacking order (the positions of the images; dataset order =
|
|
32
32
|
* ascending afterElements) */
|
|
@@ -98,8 +98,13 @@ export interface DrapeDatasetSource {
|
|
|
98
98
|
* It is not narrowed down by the view. Narrowing it down would change the element order every
|
|
99
99
|
* time the view moves and require rebuilding the tile indices (the narrowing down within a
|
|
100
100
|
* tile is done by `binTile` using the bounding boxes).
|
|
101
|
+
*
|
|
102
|
+
* @param pixelRatio The rendering pixel ratio, for widths fixed in screen pixels (the rendering
|
|
103
|
+
* scale included; `resolvePixelRatio`)
|
|
104
|
+
* @param contentPixelRatio The rendering pixel ratio for widths that follow the zoom (the
|
|
105
|
+
* rendering scale not applied; `resolveContentPixelRatio`)
|
|
101
106
|
*/
|
|
102
|
-
export declare function collectDrapeElements(store: Store, styles: RetainedStyleResolver, pixelRatio: number, datasets: readonly DrapeDatasetSource[]): DrapeCollectResult;
|
|
107
|
+
export declare function collectDrapeElements(store: Store, styles: RetainedStyleResolver, pixelRatio: number, contentPixelRatio: number, datasets: readonly DrapeDatasetSource[]): DrapeCollectResult;
|
|
103
108
|
/**
|
|
104
109
|
* Whether the analytic drape may be used
|
|
105
110
|
*
|
|
@@ -20,6 +20,7 @@ import { coordinatesOf } from '../../../shared/utils/coordinates.js';
|
|
|
20
20
|
import { getCreatedZoom } from '../../../shared/utils/property.js';
|
|
21
21
|
import { getDisplayFeatures, isLocallyHidden } from '../../../store/local-visibility.js';
|
|
22
22
|
import { layerDrawFactors } from '../../renderers/draw-factors.js';
|
|
23
|
+
import { dashCoefficientsOf } from '../../renderers/line/dash.js';
|
|
23
24
|
import { drapeSelectionKey, } from './binning.js';
|
|
24
25
|
import { drapeBoundsOf, drapeGeometryOf } from './geometry.js';
|
|
25
26
|
import { DRAPE_MAX_SOURCES } from './renderer.js';
|
|
@@ -74,8 +75,13 @@ export function drapeLayerSource(layerIndex, datasetCount) {
|
|
|
74
75
|
* It is not narrowed down by the view. Narrowing it down would change the element order every
|
|
75
76
|
* time the view moves and require rebuilding the tile indices (the narrowing down within a
|
|
76
77
|
* tile is done by `binTile` using the bounding boxes).
|
|
78
|
+
*
|
|
79
|
+
* @param pixelRatio The rendering pixel ratio, for widths fixed in screen pixels (the rendering
|
|
80
|
+
* scale included; `resolvePixelRatio`)
|
|
81
|
+
* @param contentPixelRatio The rendering pixel ratio for widths that follow the zoom (the
|
|
82
|
+
* rendering scale not applied; `resolveContentPixelRatio`)
|
|
77
83
|
*/
|
|
78
|
-
export function collectDrapeElements(store, styles, pixelRatio, datasets) {
|
|
84
|
+
export function collectDrapeElements(store, styles, pixelRatio, contentPixelRatio, datasets) {
|
|
79
85
|
const visible = getDisplayFeatures(store);
|
|
80
86
|
const byLayer = new Map();
|
|
81
87
|
for (const feature of visible) {
|
|
@@ -97,6 +103,7 @@ export function collectDrapeElements(store, styles, pixelRatio, datasets) {
|
|
|
97
103
|
drapedDatasets: new Set(),
|
|
98
104
|
styles,
|
|
99
105
|
pixelRatio,
|
|
106
|
+
contentPixelRatio,
|
|
100
107
|
layerSources: new Map(),
|
|
101
108
|
};
|
|
102
109
|
const byId = new Map();
|
|
@@ -168,21 +175,13 @@ function collectStoreFeature(context, feature, layer, source, bakedOpacity) {
|
|
|
168
175
|
}
|
|
169
176
|
if (feature.type === 'Polygon' || feature.type === 'MultiPolygon') {
|
|
170
177
|
const { fillColor, strokeStyle } = context.styles.getPolygonStyles(feature, layer);
|
|
171
|
-
// Dashed lines are not put through the analytic evaluation (solving the accumulated
|
|
172
|
-
// distance along the outline from the pixel is not implemented). Drawing them as solid
|
|
173
|
-
// lines would change the appearance, so features with dashed lines are routed to the
|
|
174
|
-
// previous vertex displacement path.
|
|
175
|
-
if (strokeStyle.lineStyle !== 'solid') {
|
|
176
|
-
context.excluded.add(feature.id);
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
178
|
const hasStroke = strokeStyle.opacity > 0 && strokeStyle.width > 0;
|
|
180
179
|
const rings = polygonRings(feature);
|
|
181
180
|
if (rings.length === 0)
|
|
182
181
|
return;
|
|
183
182
|
if (fillColor[3] <= 0 && !hasStroke)
|
|
184
183
|
return;
|
|
185
|
-
const polygonWidth = widthOf(feature, strokeStyle.width, context
|
|
184
|
+
const polygonWidth = widthOf(feature, strokeStyle.width, context);
|
|
186
185
|
pushElement(context, feature, 0, rings, {
|
|
187
186
|
fill: [fillColor[0], fillColor[1], fillColor[2], fillColor[3] * bakedOpacity],
|
|
188
187
|
stroke: [
|
|
@@ -192,6 +191,7 @@ function collectStoreFeature(context, feature, layer, source, bakedOpacity) {
|
|
|
192
191
|
strokeStyle.color[3] * (hasStroke ? strokeStyle.opacity : 0) * bakedOpacity,
|
|
193
192
|
],
|
|
194
193
|
strokeWidthPx: hasStroke ? polygonWidth.px : 0,
|
|
194
|
+
dash: dashOf(strokeStyle.lineStyle, polygonWidth),
|
|
195
195
|
source,
|
|
196
196
|
widthZoom: polygonWidth.zoom,
|
|
197
197
|
// A Store selection is expressed by the selection UI (the box and the handles). The
|
|
@@ -201,16 +201,12 @@ function collectStoreFeature(context, feature, layer, source, bakedOpacity) {
|
|
|
201
201
|
return;
|
|
202
202
|
}
|
|
203
203
|
const strokeStyle = context.styles.getLineStringStrokeStyle(feature, layer);
|
|
204
|
-
if (strokeStyle.lineStyle !== 'solid') {
|
|
205
|
-
context.excluded.add(feature.id);
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
204
|
if (strokeStyle.opacity <= 0 || strokeStyle.width <= 0)
|
|
209
205
|
return;
|
|
210
206
|
const paths = linePaths(feature);
|
|
211
207
|
if (paths.length === 0)
|
|
212
208
|
return;
|
|
213
|
-
const lineWidth = widthOf(feature, strokeStyle.width, context
|
|
209
|
+
const lineWidth = widthOf(feature, strokeStyle.width, context);
|
|
214
210
|
pushElement(context, feature, 1, paths, {
|
|
215
211
|
fill: [0, 0, 0, 0],
|
|
216
212
|
stroke: [
|
|
@@ -220,6 +216,7 @@ function collectStoreFeature(context, feature, layer, source, bakedOpacity) {
|
|
|
220
216
|
strokeStyle.color[3] * strokeStyle.opacity * bakedOpacity,
|
|
221
217
|
],
|
|
222
218
|
strokeWidthPx: lineWidth.px,
|
|
219
|
+
dash: dashOf(strokeStyle.lineStyle, lineWidth),
|
|
223
220
|
source,
|
|
224
221
|
widthZoom: lineWidth.zoom,
|
|
225
222
|
selectionKey: '',
|
|
@@ -243,16 +240,12 @@ function collectDataset(context, dataset, source) {
|
|
|
243
240
|
if (feature.type === 'Polygon' || feature.type === 'MultiPolygon') {
|
|
244
241
|
const { fillColor, strokeStyle } = context.styles.getPolygonStyles(feature);
|
|
245
242
|
const hasStroke = strokeStyle.opacity > 0 && strokeStyle.width > 0;
|
|
246
|
-
// A dashed outline is drawn by the immediate path on the retained batch side (the
|
|
247
|
-
// paired test)
|
|
248
|
-
if (hasStroke && strokeStyle.lineStyle !== 'solid')
|
|
249
|
-
continue;
|
|
250
243
|
if (!hasStroke && fillColor[3] <= 0)
|
|
251
244
|
continue;
|
|
252
245
|
const rings = polygonRings(feature);
|
|
253
246
|
if (rings.length === 0)
|
|
254
247
|
continue;
|
|
255
|
-
const width = widthOf(feature, strokeStyle.width, context
|
|
248
|
+
const width = widthOf(feature, strokeStyle.width, context);
|
|
256
249
|
pushElement(context, coordinatesOf(feature), 0, rings, {
|
|
257
250
|
fill: fillColor,
|
|
258
251
|
stroke: [
|
|
@@ -262,6 +255,7 @@ function collectDataset(context, dataset, source) {
|
|
|
262
255
|
hasStroke ? strokeStyle.color[3] : 0,
|
|
263
256
|
],
|
|
264
257
|
strokeWidthPx: hasStroke ? width.px : 0,
|
|
258
|
+
dash: dashOf(strokeStyle.lineStyle, width),
|
|
265
259
|
source,
|
|
266
260
|
widthZoom: width.zoom,
|
|
267
261
|
selectionKey: drapeSelectionKey(dataset.id, feature.id),
|
|
@@ -274,14 +268,12 @@ function collectDataset(context, dataset, source) {
|
|
|
274
268
|
continue;
|
|
275
269
|
}
|
|
276
270
|
const strokeStyle = context.styles.getLineStringStrokeStyle(feature);
|
|
277
|
-
if (strokeStyle.lineStyle !== 'solid')
|
|
278
|
-
continue;
|
|
279
271
|
if (strokeStyle.opacity <= 0 || strokeStyle.width <= 0)
|
|
280
272
|
continue;
|
|
281
273
|
const paths = linePaths(feature);
|
|
282
274
|
if (paths.length === 0)
|
|
283
275
|
continue;
|
|
284
|
-
const width = widthOf(feature, strokeStyle.width, context
|
|
276
|
+
const width = widthOf(feature, strokeStyle.width, context);
|
|
285
277
|
pushElement(context, coordinatesOf(feature), 1, paths, {
|
|
286
278
|
fill: [0, 0, 0, 0],
|
|
287
279
|
stroke: [
|
|
@@ -291,6 +283,7 @@ function collectDataset(context, dataset, source) {
|
|
|
291
283
|
strokeStyle.color[3] * strokeStyle.opacity,
|
|
292
284
|
],
|
|
293
285
|
strokeWidthPx: width.px,
|
|
286
|
+
dash: dashOf(strokeStyle.lineStyle, width),
|
|
294
287
|
source,
|
|
295
288
|
widthZoom: width.zoom,
|
|
296
289
|
selectionKey: drapeSelectionKey(dataset.id, feature.id),
|
|
@@ -301,15 +294,31 @@ function collectDataset(context, dataset, source) {
|
|
|
301
294
|
* Resolves the line width with the same convention as the retained batch path
|
|
302
295
|
*
|
|
303
296
|
* A feature without a `createdZoom` is fixed in screen pixels (the rendering scale applies),
|
|
304
|
-
* and a feature with one scales by `2^(zoom - createdZoom)
|
|
305
|
-
*
|
|
297
|
+
* and a feature with one scales by `2^(zoom - createdZoom)` (the device pixel ratio applies,
|
|
298
|
+
* the rendering scale does not). So that the thickness does not change with the presence of
|
|
299
|
+
* terrain, the drape follows the same convention.
|
|
306
300
|
*/
|
|
307
|
-
function widthOf(feature, width,
|
|
301
|
+
function widthOf(feature, width, ratios) {
|
|
308
302
|
const createdZoom = getCreatedZoom(feature);
|
|
309
303
|
if (createdZoom !== undefined && Number.isFinite(createdZoom)) {
|
|
310
|
-
|
|
304
|
+
const ratio = ratios.contentPixelRatio;
|
|
305
|
+
return { px: width * ratio, zoom: createdZoom, ratio };
|
|
311
306
|
}
|
|
312
|
-
return { px: width * pixelRatio, zoom: -1 };
|
|
307
|
+
return { px: width * ratios.pixelRatio, zoom: -1, ratio: ratios.pixelRatio };
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* The dash pattern of an outline, or null for a solid one
|
|
311
|
+
*
|
|
312
|
+
* The coefficients come from the table the subdividing path uses (`dash.ts`), and the
|
|
313
|
+
* shader turns them into the dash and the gap of the width the line has in the frame, in
|
|
314
|
+
* CSS pixels as the subdividing path measures them: the pixel ratio the width was resolved
|
|
315
|
+
* with goes along to take back out.
|
|
316
|
+
*/
|
|
317
|
+
function dashOf(lineStyle, width) {
|
|
318
|
+
const coefficients = dashCoefficientsOf(lineStyle);
|
|
319
|
+
if (!coefficients || width.ratio <= 0)
|
|
320
|
+
return null;
|
|
321
|
+
return { ...coefficients, pixelRatio: width.ratio };
|
|
313
322
|
}
|
|
314
323
|
/** The rings of a polygon (with the closing point dropped) */
|
|
315
324
|
function polygonRings(feature) {
|
|
@@ -343,6 +352,8 @@ function pushElement(context, cacheKey, kind, paths, style) {
|
|
|
343
352
|
// is not converted)
|
|
344
353
|
geometry: () => drapeGeometryOf(cacheKey, paths, kind === 0),
|
|
345
354
|
...style,
|
|
355
|
+
// An outline that is not drawn has no pattern to lay out
|
|
356
|
+
dash: style.strokeWidthPx > 0 ? style.dash : null,
|
|
346
357
|
});
|
|
347
358
|
context.edgeCount += edges;
|
|
348
359
|
}
|
|
@@ -26,6 +26,13 @@ import { type DrapePackBuilder } from './bin-store.js';
|
|
|
26
26
|
import { EDGE_UNITS } from './edge-constrain.js';
|
|
27
27
|
import type { ResolvedTileEdges } from './stitch.js';
|
|
28
28
|
export { EDGE_UNITS };
|
|
29
|
+
/**
|
|
30
|
+
* The texture unit of the starts of the edges along their paths
|
|
31
|
+
*
|
|
32
|
+
* 5 and 7 to 9 hold the DEMs of the edges (`EDGE_UNITS`) and 6 the DEM atlas, so it comes after
|
|
33
|
+
* them (WebGL2 guarantees 16 units to a fragment shader).
|
|
34
|
+
*/
|
|
35
|
+
export declare const DRAPE_EDGE_START_UNIT = 10;
|
|
29
36
|
/**
|
|
30
37
|
* Upper limit on the sources of the factors
|
|
31
38
|
*
|
|
@@ -92,6 +99,8 @@ export interface DrapeTileDraw {
|
|
|
92
99
|
readonly grid: number;
|
|
93
100
|
/** The ground size of this tile (meters). Used to compute the shading gradient */
|
|
94
101
|
readonly groundMeters: number;
|
|
102
|
+
/** The zoom of this tile (it scales the lengths of the edges into dash pattern pixels) */
|
|
103
|
+
readonly tileZ: number;
|
|
95
104
|
/**
|
|
96
105
|
* The constraint for each edge (resolving T-junctions)
|
|
97
106
|
*
|
|
@@ -123,10 +132,12 @@ export declare class DrapeRenderer {
|
|
|
123
132
|
private variantName;
|
|
124
133
|
private mesh;
|
|
125
134
|
private edgeTex;
|
|
135
|
+
private edgeStartTex;
|
|
126
136
|
private runTex;
|
|
127
137
|
private cellTex;
|
|
128
138
|
private styleTex;
|
|
129
139
|
private edgeTexSize;
|
|
140
|
+
private edgeStartTexSize;
|
|
130
141
|
private runTexSize;
|
|
131
142
|
private cellTexSize;
|
|
132
143
|
private styleTexSize;
|
|
@@ -179,6 +190,8 @@ export declare class DrapeRenderer {
|
|
|
179
190
|
*
|
|
180
191
|
* texStorage2D is immutable, so the texture is recreated whenever the required
|
|
181
192
|
* size changes (once recreated, everything is sent again).
|
|
193
|
+
*
|
|
194
|
+
* @param channels The numbers per texel: 4 (RGBA32F) or 1 (R32F)
|
|
182
195
|
*/
|
|
183
196
|
private uploadTexture;
|
|
184
197
|
}
|