@versatiles/png-renderer 2.2.1 → 2.3.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/dist/index.d.ts +22 -2
- package/dist/index.js +185 -53
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -2741,6 +2741,12 @@ declare class Feature implements Feature$1 {
|
|
|
2741
2741
|
* clipped edges along the tile border are not part of the outline.
|
|
2742
2742
|
*/
|
|
2743
2743
|
readonly outline?: Geometry;
|
|
2744
|
+
/**
|
|
2745
|
+
* For each line of the geometry, how far along the line in its tile it starts, in pixels
|
|
2746
|
+
* — where the line was clipped to its tile, which cut off its start. A dash pattern
|
|
2747
|
+
* starts where the line in the tile does.
|
|
2748
|
+
*/
|
|
2749
|
+
readonly lineStarts?: number[];
|
|
2744
2750
|
constructor(opt: {
|
|
2745
2751
|
type: 'LineString' | 'Point' | 'Polygon';
|
|
2746
2752
|
id?: unknown;
|
|
@@ -2748,6 +2754,7 @@ declare class Feature implements Feature$1 {
|
|
|
2748
2754
|
patterns?: Patterns;
|
|
2749
2755
|
geometry: Geometry;
|
|
2750
2756
|
outline?: Geometry;
|
|
2757
|
+
lineStarts?: number[];
|
|
2751
2758
|
});
|
|
2752
2759
|
getBbox(): Bbox;
|
|
2753
2760
|
doesOverlap(bbox: Bbox): boolean;
|
|
@@ -2774,6 +2781,18 @@ interface Padding {
|
|
|
2774
2781
|
left?: number;
|
|
2775
2782
|
}
|
|
2776
2783
|
|
|
2784
|
+
/**
|
|
2785
|
+
* The dashes of a line with `line-dasharray`, shared by every backend, as MapLibre GL JS
|
|
2786
|
+
* draws them (`line_sdf.vertex.glsl`, `LineAtlas`).
|
|
2787
|
+
*/
|
|
2788
|
+
/** A dash pattern in pixels. */
|
|
2789
|
+
interface LineDash {
|
|
2790
|
+
/** The lengths of dashes and gaps in turn, starting with a dash: an even number of them. */
|
|
2791
|
+
lengths: number[];
|
|
2792
|
+
/** How far into the pattern a line starts. */
|
|
2793
|
+
offset: number;
|
|
2794
|
+
}
|
|
2795
|
+
|
|
2777
2796
|
/**
|
|
2778
2797
|
* A drawing surface the render pipeline paints a map onto. It only describes *how* to
|
|
2779
2798
|
* draw; how the result is handed back is up to the backend, because those differ in
|
|
@@ -2890,14 +2909,15 @@ interface LineStyle {
|
|
|
2890
2909
|
blur: number;
|
|
2891
2910
|
cap: 'butt' | 'round' | 'square';
|
|
2892
2911
|
color: Color;
|
|
2893
|
-
dasharray
|
|
2912
|
+
/** `line-dasharray`, in pixels (see `dashPattern`). */
|
|
2913
|
+
dash?: LineDash;
|
|
2894
2914
|
join: 'bevel' | 'miter' | 'round';
|
|
2895
2915
|
miterLimit: number;
|
|
2896
2916
|
offset: number;
|
|
2897
2917
|
opacity: number;
|
|
2898
2918
|
translate: [number, number];
|
|
2899
2919
|
width: number;
|
|
2900
|
-
/** `line-pattern`: drawn instead of `color` and `
|
|
2920
|
+
/** `line-pattern`: drawn instead of `color` and `dash`. */
|
|
2901
2921
|
pattern?: LinePattern;
|
|
2902
2922
|
}
|
|
2903
2923
|
/**
|
package/dist/index.js
CHANGED
|
@@ -119,6 +119,12 @@ class Feature {
|
|
|
119
119
|
* clipped edges along the tile border are not part of the outline.
|
|
120
120
|
*/
|
|
121
121
|
outline;
|
|
122
|
+
/**
|
|
123
|
+
* For each line of the geometry, how far along the line in its tile it starts, in pixels
|
|
124
|
+
* — where the line was clipped to its tile, which cut off its start. A dash pattern
|
|
125
|
+
* starts where the line in the tile does.
|
|
126
|
+
*/
|
|
127
|
+
lineStarts;
|
|
122
128
|
#bbox;
|
|
123
129
|
constructor(opt) {
|
|
124
130
|
this.type = opt.type;
|
|
@@ -127,6 +133,7 @@ class Feature {
|
|
|
127
133
|
this.patterns = opt.patterns;
|
|
128
134
|
this.geometry = opt.geometry;
|
|
129
135
|
this.outline = opt.outline;
|
|
136
|
+
this.lineStarts = opt.lineStarts;
|
|
130
137
|
}
|
|
131
138
|
getBbox() {
|
|
132
139
|
if (this.#bbox)
|
|
@@ -2133,32 +2140,49 @@ function clipPolygonOutline(rings, min, max) {
|
|
|
2133
2140
|
}
|
|
2134
2141
|
/** Clips a polyline to the square [min, max]², returning the parts inside of it. */
|
|
2135
2142
|
function clipLine(line, min, max) {
|
|
2143
|
+
return clipLineMeasured(line, min, max).map(({ points }) => points);
|
|
2144
|
+
}
|
|
2145
|
+
/**
|
|
2146
|
+
* Clips a polyline to the square [min, max]², like `clipLine`, and measures where each part
|
|
2147
|
+
* starts: its distance from the polyline's start, which is where MapLibre starts a dash
|
|
2148
|
+
* pattern. MapLibre stores that distance in a few bits and starts again from zero at a
|
|
2149
|
+
* vertex further along than `resetAfter` (`LineBucket.addCurrentVertex`).
|
|
2150
|
+
*/
|
|
2151
|
+
function clipLineMeasured(line, min, max, resetAfter = Infinity) {
|
|
2136
2152
|
const parts = [];
|
|
2137
2153
|
let current = [];
|
|
2154
|
+
let start = 0;
|
|
2155
|
+
let distance = 0;
|
|
2156
|
+
const flush = () => {
|
|
2157
|
+
if (current.length > 1)
|
|
2158
|
+
parts.push({ points: current, start });
|
|
2159
|
+
current = [];
|
|
2160
|
+
};
|
|
2138
2161
|
for (let i = 0; i + 1 < line.length; i++) {
|
|
2139
|
-
const
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2162
|
+
const from = line[i];
|
|
2163
|
+
const to = line[i + 1];
|
|
2164
|
+
const segment = clipSegment(from, to, min, max);
|
|
2165
|
+
if (segment) {
|
|
2166
|
+
const [a, b] = segment;
|
|
2167
|
+
const last = current[current.length - 1];
|
|
2168
|
+
if (last?.x !== a.x || last.y !== a.y) {
|
|
2169
|
+
flush();
|
|
2170
|
+
current = [a];
|
|
2171
|
+
start = distance + Math.hypot(a.x - from.x, a.y - from.y);
|
|
2172
|
+
}
|
|
2173
|
+
current.push(b);
|
|
2174
|
+
// The segment left the square: the line continues elsewhere.
|
|
2175
|
+
if (b !== to)
|
|
2176
|
+
flush();
|
|
2152
2177
|
}
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
if (b !== line[i + 1]) {
|
|
2156
|
-
parts.push(current);
|
|
2157
|
-
current = [];
|
|
2178
|
+
else {
|
|
2179
|
+
flush();
|
|
2158
2180
|
}
|
|
2181
|
+
distance += Math.hypot(to.x - from.x, to.y - from.y);
|
|
2182
|
+
if (distance > resetAfter)
|
|
2183
|
+
distance = 0;
|
|
2159
2184
|
}
|
|
2160
|
-
|
|
2161
|
-
parts.push(current);
|
|
2185
|
+
flush();
|
|
2162
2186
|
return parts;
|
|
2163
2187
|
}
|
|
2164
2188
|
/** Liang–Barsky: the part of the segment a→b inside the square, or undefined. */
|
|
@@ -2218,6 +2242,11 @@ function lerpAtY(a, b, y) {
|
|
|
2218
2242
|
}
|
|
2219
2243
|
|
|
2220
2244
|
const TILE_EXTENT = 4096;
|
|
2245
|
+
/**
|
|
2246
|
+
* How far along a line MapLibre measures before it starts again from zero: two tiles
|
|
2247
|
+
* (`MAX_LINE_DISTANCE / 2` in its `line_bucket.ts`, for its tile extent of 8192).
|
|
2248
|
+
*/
|
|
2249
|
+
const MAX_LINE_DISTANCE = 2 * TILE_EXTENT;
|
|
2221
2250
|
const VTFeatureType = { Unknown: 0, Point: 1, LineString: 2, Polygon: 3 };
|
|
2222
2251
|
async function loadVectorSource(source, job, layerFeatures, loadTile = getTile) {
|
|
2223
2252
|
const tiles = source.tiles;
|
|
@@ -2236,7 +2265,7 @@ async function loadVectorSource(source, job, layerFeatures, loadTile = getTile)
|
|
|
2236
2265
|
});
|
|
2237
2266
|
}
|
|
2238
2267
|
/** Decodes one vector tile and adds its features, projected to the screen. */
|
|
2239
|
-
function addTileFeatures(buffer, { project, clipToTile }, layerFeatures, width, height) {
|
|
2268
|
+
function addTileFeatures(buffer, { project, clipToTile, pixelsPerUnit }, layerFeatures, width, height) {
|
|
2240
2269
|
const vectorTile = new VectorTile(new PbfReader(buffer));
|
|
2241
2270
|
for (const [name, layer] of Object.entries(vectorTile.layers)) {
|
|
2242
2271
|
let features = layerFeatures.get(name);
|
|
@@ -2266,6 +2295,7 @@ function addTileFeatures(buffer, { project, clipToTile }, layerFeatures, width,
|
|
|
2266
2295
|
}
|
|
2267
2296
|
let rings = featureSrc.loadGeometry();
|
|
2268
2297
|
let outline;
|
|
2298
|
+
let lineStarts;
|
|
2269
2299
|
// Clip polygons and lines to the tile, like MapLibre's stencil clipping: otherwise
|
|
2270
2300
|
// the parts in the tile buffer are drawn twice, which shows when translucent.
|
|
2271
2301
|
if (clipToTile && type !== 'Point' && exceedsSquare(rings, 0, TILE_EXTENT)) {
|
|
@@ -2274,7 +2304,11 @@ function addTileFeatures(buffer, { project, clipToTile }, layerFeatures, width,
|
|
|
2274
2304
|
rings = clipPolygon(rings, 0, TILE_EXTENT);
|
|
2275
2305
|
}
|
|
2276
2306
|
else {
|
|
2277
|
-
|
|
2307
|
+
const parts = rings.flatMap((line) => clipLineMeasured(line, 0, TILE_EXTENT, MAX_LINE_DISTANCE));
|
|
2308
|
+
rings = parts.map(({ points }) => points);
|
|
2309
|
+
if (pixelsPerUnit !== undefined) {
|
|
2310
|
+
lineStarts = parts.map(({ start }) => start * pixelsPerUnit);
|
|
2311
|
+
}
|
|
2278
2312
|
}
|
|
2279
2313
|
}
|
|
2280
2314
|
const geometry = project(type, rings);
|
|
@@ -2298,6 +2332,7 @@ function addTileFeatures(buffer, { project, clipToTile }, layerFeatures, width,
|
|
|
2298
2332
|
type,
|
|
2299
2333
|
geometry,
|
|
2300
2334
|
outline,
|
|
2335
|
+
lineStarts,
|
|
2301
2336
|
id: featureSrc.id,
|
|
2302
2337
|
properties: featureSrc.properties,
|
|
2303
2338
|
});
|
|
@@ -2352,6 +2387,7 @@ function getTileProjections(source, job) {
|
|
|
2352
2387
|
y,
|
|
2353
2388
|
z: zoomLevel,
|
|
2354
2389
|
clipToTile: true,
|
|
2390
|
+
pixelsPerUnit: scale,
|
|
2355
2391
|
project: (_type, rings) => rings.map((ring) => ring.map((point) => {
|
|
2356
2392
|
const p = new Point2D(point.x, point.y).scale(scale).translate(offset);
|
|
2357
2393
|
return moved ? moved.fromNorthUp(p.x, p.y) : p;
|
|
@@ -11749,6 +11785,55 @@ function stretchZonesToCuts(zones, fixedSize, stretchSize) {
|
|
|
11749
11785
|
return cuts;
|
|
11750
11786
|
}
|
|
11751
11787
|
|
|
11788
|
+
/**
|
|
11789
|
+
* The dashes of a line with `line-dasharray`, shared by every backend, as MapLibre GL JS
|
|
11790
|
+
* draws them (`line_sdf.vertex.glsl`, `LineAtlas`).
|
|
11791
|
+
*/
|
|
11792
|
+
/**
|
|
11793
|
+
* The dash pattern of `dasharray`, or undefined for a line without dashes.
|
|
11794
|
+
*
|
|
11795
|
+
* MapLibre measures a dash array in line widths, but not in the width the line is drawn
|
|
11796
|
+
* with: in its width at the zoom level's integer part (`floorWidth`), scaled with the map
|
|
11797
|
+
* from that integer zoom level up to `zoom`, as its tiles are. And where a dash array has
|
|
11798
|
+
* an odd number of lengths, it is not repeated to an even one, as SVG and the canvas do: its
|
|
11799
|
+
* last and first dash are joined to one.
|
|
11800
|
+
*/
|
|
11801
|
+
function dashPattern(dasharray, floorWidth, zoom) {
|
|
11802
|
+
if (!dasharray || dasharray.length === 0)
|
|
11803
|
+
return undefined;
|
|
11804
|
+
const unit = floorWidth * 2 ** (zoom - Math.floor(zoom));
|
|
11805
|
+
if (!(unit > 0))
|
|
11806
|
+
return undefined;
|
|
11807
|
+
const lengths = dasharray.map((length) => Math.max(length, 0) * unit);
|
|
11808
|
+
let offset = 0;
|
|
11809
|
+
if (lengths.length % 2 === 1) {
|
|
11810
|
+
// A single length is a dash without a gap.
|
|
11811
|
+
if (lengths.length === 1)
|
|
11812
|
+
return undefined;
|
|
11813
|
+
// The last dash goes before the first: the pattern starts that far into their sum.
|
|
11814
|
+
offset = lengths.pop();
|
|
11815
|
+
lengths[0] += offset;
|
|
11816
|
+
}
|
|
11817
|
+
// Without a gap, the line is solid.
|
|
11818
|
+
const hasGap = lengths.some((length, i) => i % 2 === 1 && length > 0);
|
|
11819
|
+
return hasGap ? { lengths, offset } : undefined;
|
|
11820
|
+
}
|
|
11821
|
+
/** `offset` within one period of `dash`, for a line that starts `start` pixels in. */
|
|
11822
|
+
function dashOffset(dash, start) {
|
|
11823
|
+
const period = dash.lengths.reduce((sum, length) => sum + length, 0);
|
|
11824
|
+
const offset = (dash.offset + start) % period;
|
|
11825
|
+
return offset < 0 ? offset + period : offset;
|
|
11826
|
+
}
|
|
11827
|
+
/**
|
|
11828
|
+
* How far along a ring its first vertex is: MapLibre measures a ring from its last vertex,
|
|
11829
|
+
* so by the length of its closing segment (`LineBucket.addLine`).
|
|
11830
|
+
*/
|
|
11831
|
+
function closingLength(ring) {
|
|
11832
|
+
const first = ring[0];
|
|
11833
|
+
const last = ring[ring.length - 1];
|
|
11834
|
+
return first && last ? Math.hypot(first.x - last.x, first.y - last.y) : 0;
|
|
11835
|
+
}
|
|
11836
|
+
|
|
11752
11837
|
/**
|
|
11753
11838
|
* Geometry for drawing a line with `line-pattern`, shared by every backend: the sprite image
|
|
11754
11839
|
* repeated along the line, its height across the line's width, as MapLibre GL JS draws it
|
|
@@ -12219,9 +12304,10 @@ async function renderLineLayer(layer) {
|
|
|
12219
12304
|
if (lineStringFeatures.length === 0)
|
|
12220
12305
|
return;
|
|
12221
12306
|
const { getPaint, getLayout } = evaluateLayer(layer);
|
|
12222
|
-
// A pattern
|
|
12307
|
+
// A pattern and the dashes are scaled to the line's width at the zoom level's integer
|
|
12308
|
+
// part, as in MapLibre.
|
|
12223
12309
|
const { zoom } = layer.job.view;
|
|
12224
|
-
const atFloorZoom = layer.layerStyle.usesPattern
|
|
12310
|
+
const atFloorZoom = layer.layerStyle.usesPattern || getPaint('line-dasharray') !== undefined
|
|
12225
12311
|
? evaluateLayer(layer, Math.floor(zoom))
|
|
12226
12312
|
: undefined;
|
|
12227
12313
|
const sorted = sortByKey(lineStringFeatures, (feature) => getLayout('line-sort-key', feature));
|
|
@@ -12230,21 +12316,26 @@ async function renderLineLayer(layer) {
|
|
|
12230
12316
|
// A pattern whose image is not in the sprite draws nothing, as in MapLibre.
|
|
12231
12317
|
if (found === null)
|
|
12232
12318
|
return [];
|
|
12319
|
+
const width = getPaint('line-width', feature);
|
|
12320
|
+
const floorWidth = atFloorZoom?.getPaint('line-width', feature) ?? width;
|
|
12321
|
+
const dash = dashPattern(getPaint('line-dasharray', feature), floorWidth, zoom);
|
|
12322
|
+
const cap = getLayout('line-cap', feature);
|
|
12233
12323
|
const style = {
|
|
12234
12324
|
blur: getPaint('line-blur', feature),
|
|
12235
12325
|
color: getPaint('line-color', feature),
|
|
12236
12326
|
translate: screenTranslate(layer.job, getPaint('line-translate', feature), getPaint('line-translate-anchor', feature)),
|
|
12237
|
-
|
|
12238
|
-
|
|
12327
|
+
// MapLibre's dashes end flat unless the caps are round: with square caps, an SVG or
|
|
12328
|
+
// a canvas would lengthen every dash. (That leaves out the caps at the line's ends.)
|
|
12329
|
+
cap: dash && cap === 'square' ? 'butt' : cap,
|
|
12330
|
+
dash,
|
|
12239
12331
|
join: getLayout('line-join', feature),
|
|
12240
12332
|
miterLimit: getLayout('line-miter-limit', feature),
|
|
12241
12333
|
offset: getPaint('line-offset', feature),
|
|
12242
12334
|
opacity: getPaint('line-opacity', feature),
|
|
12243
|
-
width
|
|
12335
|
+
width,
|
|
12244
12336
|
};
|
|
12245
12337
|
if (found) {
|
|
12246
12338
|
const { sprite } = found;
|
|
12247
|
-
const floorWidth = atFloorZoom?.getPaint('line-width', feature) ?? style.width;
|
|
12248
12339
|
style.pattern = {
|
|
12249
12340
|
name: found.name,
|
|
12250
12341
|
sprite,
|
|
@@ -14045,6 +14136,10 @@ function strokeAttr(color, width) {
|
|
|
14045
14136
|
function formatScaled(v) {
|
|
14046
14137
|
return formatNum(Math.round(v * 10));
|
|
14047
14138
|
}
|
|
14139
|
+
/** How far into its dash pattern a line starts, in hundredths of a pixel. */
|
|
14140
|
+
function formatDash(v) {
|
|
14141
|
+
return (Math.round(v * 100) / 100).toString();
|
|
14142
|
+
}
|
|
14048
14143
|
function formatUnit(v) {
|
|
14049
14144
|
return (Math.round(v * 100000) / 100000).toString();
|
|
14050
14145
|
}
|
|
@@ -14690,9 +14785,9 @@ class SVGRenderer {
|
|
|
14690
14785
|
? ''
|
|
14691
14786
|
: ` transform="translate(${formatPoint(style.translate)})"`;
|
|
14692
14787
|
const roundedWidth = formatScaled(style.width);
|
|
14693
|
-
const
|
|
14694
|
-
|
|
14695
|
-
|
|
14788
|
+
const { dash } = style;
|
|
14789
|
+
// The lengths are written precisely: what is rounded off adds up along the line.
|
|
14790
|
+
const dasharrayStr = dash ? dash.lengths.map(formatUnit).join(',') : '';
|
|
14696
14791
|
// MapLibre's line-blur fades the line as the blur approaches its width
|
|
14697
14792
|
// (the feather eats into the opaque core). A Gaussian conserves total ink,
|
|
14698
14793
|
// so approximate that fade with an extra opacity factor; it tends to 1 for
|
|
@@ -14715,33 +14810,55 @@ class SVGRenderer {
|
|
|
14715
14810
|
filterAttr,
|
|
14716
14811
|
].join('\0');
|
|
14717
14812
|
const translucent = effectiveOpacity < 1 || color.alpha < 255;
|
|
14718
|
-
|
|
14719
|
-
|
|
14720
|
-
|
|
14721
|
-
|
|
14722
|
-
|
|
14723
|
-
|
|
14724
|
-
|
|
14725
|
-
|
|
14726
|
-
|
|
14727
|
-
|
|
14728
|
-
|
|
14729
|
-
|
|
14730
|
-
|
|
14731
|
-
|
|
14732
|
-
|
|
14813
|
+
// The group of paths with these attributes; `dashOffsetStr` is how far into the dash
|
|
14814
|
+
// pattern they start.
|
|
14815
|
+
const groupFor = (dashOffsetStr) => {
|
|
14816
|
+
const groupKey = key + '\0' + dashOffsetStr;
|
|
14817
|
+
if (translucent || groupKey !== currentKey) {
|
|
14818
|
+
const attrs = [
|
|
14819
|
+
'fill="none"',
|
|
14820
|
+
strokeAttr(color, roundedWidth),
|
|
14821
|
+
`stroke-linecap="${style.cap}"`,
|
|
14822
|
+
`stroke-linejoin="${style.join}"`,
|
|
14823
|
+
`stroke-miterlimit="${String(style.miterLimit)}"`,
|
|
14824
|
+
];
|
|
14825
|
+
if (dasharrayStr)
|
|
14826
|
+
attrs.push(`stroke-dasharray="${dasharrayStr}"`);
|
|
14827
|
+
if (dashOffsetStr !== '0')
|
|
14828
|
+
attrs.push(`stroke-dashoffset="${dashOffsetStr}"`);
|
|
14829
|
+
groups.push({
|
|
14830
|
+
segments: [],
|
|
14831
|
+
rings: [],
|
|
14832
|
+
attrs: attrs.join(' ') + translate + opacityAttr + filterAttr,
|
|
14833
|
+
separate: translucent,
|
|
14834
|
+
dashed: dash !== undefined,
|
|
14835
|
+
});
|
|
14836
|
+
currentKey = translucent ? undefined : groupKey;
|
|
14837
|
+
}
|
|
14838
|
+
return groups[groups.length - 1];
|
|
14839
|
+
};
|
|
14840
|
+
const { open, closed } = strokeLines(feature.geometry, feature.type === 'Polygon', style.offset);
|
|
14841
|
+
if (dash) {
|
|
14842
|
+
// Every part has its dashes where MapLibre draws them: from the start of the line
|
|
14843
|
+
// in its tile, and a ring from the start of its closing segment.
|
|
14844
|
+
open.forEach((line, i) => {
|
|
14845
|
+
const offset = formatDash(dashOffset(dash, feature.lineStarts?.[i] ?? 0));
|
|
14846
|
+
groupFor(offset).segments.push(line.map((p) => roundXY(p.x, p.y)));
|
|
14733
14847
|
});
|
|
14734
|
-
|
|
14848
|
+
for (const ring of closed) {
|
|
14849
|
+
const offset = formatDash(dashOffset(dash, closingLength(ring)));
|
|
14850
|
+
groupFor(offset).rings.push(ring.map((p) => roundXY(p.x, p.y)));
|
|
14851
|
+
}
|
|
14852
|
+
return;
|
|
14735
14853
|
}
|
|
14736
|
-
const group =
|
|
14737
|
-
const { open, closed } = strokeLines(feature.geometry, feature.type === 'Polygon', style.offset);
|
|
14854
|
+
const group = groupFor('0');
|
|
14738
14855
|
for (const line of open)
|
|
14739
14856
|
group.segments.push(line.map((p) => roundXY(p.x, p.y)));
|
|
14740
14857
|
for (const ring of closed)
|
|
14741
14858
|
group.rings.push(ring.map((p) => roundXY(p.x, p.y)));
|
|
14742
14859
|
});
|
|
14743
14860
|
this.#svg.push(`<g id="${escapeXml(id)}">`);
|
|
14744
|
-
for (const { segments, rings, attrs, separate, patterned } of groups) {
|
|
14861
|
+
for (const { segments, rings, attrs, separate, dashed, patterned } of groups) {
|
|
14745
14862
|
if (patterned) {
|
|
14746
14863
|
this.#svg.push(patterned);
|
|
14747
14864
|
continue;
|
|
@@ -14755,7 +14872,7 @@ class SVGRenderer {
|
|
|
14755
14872
|
}
|
|
14756
14873
|
continue;
|
|
14757
14874
|
}
|
|
14758
|
-
const chains = chainSegments(segments);
|
|
14875
|
+
const chains = dashed ? segments : chainSegments(segments);
|
|
14759
14876
|
const d = segmentsToPath(chains) + segmentsToPath(rings, true);
|
|
14760
14877
|
this.#svg.push(`<path d="${d}" ${attrs} />`);
|
|
14761
14878
|
}
|
|
@@ -16087,8 +16204,23 @@ class CanvasRenderer {
|
|
|
16087
16204
|
ctx.lineCap = style.cap;
|
|
16088
16205
|
ctx.lineJoin = style.join;
|
|
16089
16206
|
ctx.miterLimit = style.miterLimit;
|
|
16090
|
-
|
|
16091
|
-
|
|
16207
|
+
const { dash } = style;
|
|
16208
|
+
if (dash) {
|
|
16209
|
+
// Every part has its dashes where MapLibre draws them: from the start of the
|
|
16210
|
+
// line in its tile, and a ring from the start of its closing segment.
|
|
16211
|
+
ctx.setLineDash(dash.lengths);
|
|
16212
|
+
segments.forEach((segment, i) => {
|
|
16213
|
+
ctx.lineDashOffset = dashOffset(dash, feature.lineStarts?.[i] ?? 0);
|
|
16214
|
+
this.#trace(ctx, [segment], false);
|
|
16215
|
+
ctx.stroke();
|
|
16216
|
+
});
|
|
16217
|
+
lines.closed.forEach((ring, i) => {
|
|
16218
|
+
ctx.lineDashOffset = dashOffset(dash, closingLength(ring));
|
|
16219
|
+
this.#trace(ctx, [rings[i]], true);
|
|
16220
|
+
ctx.stroke();
|
|
16221
|
+
});
|
|
16222
|
+
return;
|
|
16223
|
+
}
|
|
16092
16224
|
if (translucent) {
|
|
16093
16225
|
for (const segment of segments) {
|
|
16094
16226
|
this.#trace(ctx, [segment], false);
|