@versatiles/svg-renderer 0.5.2 → 0.6.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 +26 -23
- package/dist/index.cjs +375 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +375 -15
- package/dist/index.js.map +1 -1
- package/dist/maplibre.cjs +396 -21
- package/dist/maplibre.cjs.map +1 -1
- package/dist/maplibre.d.ts +1 -0
- package/dist/maplibre.js +396 -21
- package/dist/maplibre.js.map +1 -1
- package/dist/maplibre.umd.js +396 -21
- package/dist/maplibre.umd.js.map +1 -1
- package/package.json +2 -2
package/dist/maplibre.umd.js
CHANGED
|
@@ -69,6 +69,10 @@
|
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
.svg-export-panel .panel-inputs {
|
|
72
|
+
margin-bottom: 12px;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.svg-export-panel .panel-inputs .grid {
|
|
72
76
|
display: grid;
|
|
73
77
|
grid-template-columns: 1fr 1fr;
|
|
74
78
|
gap: 8px;
|
|
@@ -76,11 +80,15 @@
|
|
|
76
80
|
}
|
|
77
81
|
|
|
78
82
|
.svg-export-panel .panel-inputs label {
|
|
83
|
+
font-size: 12px;
|
|
84
|
+
color: #666;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.svg-export-panel .panel-inputs .grid label {
|
|
79
88
|
display: flex;
|
|
80
89
|
flex-direction: column;
|
|
81
90
|
gap: 4px;
|
|
82
|
-
|
|
83
|
-
color: #666;
|
|
91
|
+
width: 100%;
|
|
84
92
|
}
|
|
85
93
|
|
|
86
94
|
.svg-export-panel .panel-inputs input {
|
|
@@ -88,7 +96,6 @@
|
|
|
88
96
|
border: 1px solid #ccc;
|
|
89
97
|
border-radius: 4px;
|
|
90
98
|
font-size: 13px;
|
|
91
|
-
width: 100%;
|
|
92
99
|
box-sizing: border-box;
|
|
93
100
|
}
|
|
94
101
|
|
|
@@ -3006,6 +3013,23 @@
|
|
|
3006
3013
|
},
|
|
3007
3014
|
"property-type": "data-constant"
|
|
3008
3015
|
},
|
|
3016
|
+
resampling: {
|
|
3017
|
+
type: "enum",
|
|
3018
|
+
values: {
|
|
3019
|
+
linear: {
|
|
3020
|
+
},
|
|
3021
|
+
nearest: {
|
|
3022
|
+
}
|
|
3023
|
+
},
|
|
3024
|
+
"default": "linear",
|
|
3025
|
+
expression: {
|
|
3026
|
+
interpolated: false,
|
|
3027
|
+
parameters: [
|
|
3028
|
+
"zoom"
|
|
3029
|
+
]
|
|
3030
|
+
},
|
|
3031
|
+
"property-type": "data-constant"
|
|
3032
|
+
},
|
|
3009
3033
|
"raster-resampling": {
|
|
3010
3034
|
type: "enum",
|
|
3011
3035
|
values: {
|
|
@@ -3156,6 +3180,23 @@
|
|
|
3156
3180
|
]
|
|
3157
3181
|
},
|
|
3158
3182
|
"property-type": "data-constant"
|
|
3183
|
+
},
|
|
3184
|
+
resampling: {
|
|
3185
|
+
type: "enum",
|
|
3186
|
+
values: {
|
|
3187
|
+
linear: {
|
|
3188
|
+
},
|
|
3189
|
+
nearest: {
|
|
3190
|
+
}
|
|
3191
|
+
},
|
|
3192
|
+
"default": "linear",
|
|
3193
|
+
expression: {
|
|
3194
|
+
interpolated: false,
|
|
3195
|
+
parameters: [
|
|
3196
|
+
"zoom"
|
|
3197
|
+
]
|
|
3198
|
+
},
|
|
3199
|
+
"property-type": "data-constant"
|
|
3159
3200
|
}
|
|
3160
3201
|
};
|
|
3161
3202
|
var paint_background = {
|
|
@@ -3576,6 +3617,23 @@
|
|
|
3576
3617
|
]
|
|
3577
3618
|
},
|
|
3578
3619
|
"property-type": "color-ramp"
|
|
3620
|
+
},
|
|
3621
|
+
resampling: {
|
|
3622
|
+
type: "enum",
|
|
3623
|
+
values: {
|
|
3624
|
+
linear: {
|
|
3625
|
+
},
|
|
3626
|
+
nearest: {
|
|
3627
|
+
}
|
|
3628
|
+
},
|
|
3629
|
+
"default": "linear",
|
|
3630
|
+
expression: {
|
|
3631
|
+
interpolated: false,
|
|
3632
|
+
parameters: [
|
|
3633
|
+
"zoom"
|
|
3634
|
+
]
|
|
3635
|
+
},
|
|
3636
|
+
"property-type": "data-constant"
|
|
3579
3637
|
}
|
|
3580
3638
|
},
|
|
3581
3639
|
paint_background: paint_background,
|
|
@@ -6420,11 +6478,12 @@
|
|
|
6420
6478
|
}
|
|
6421
6479
|
|
|
6422
6480
|
class NumberFormat {
|
|
6423
|
-
constructor(number, locale, currency, minFractionDigits, maxFractionDigits) {
|
|
6481
|
+
constructor(number, locale, currency, unit, minFractionDigits, maxFractionDigits) {
|
|
6424
6482
|
this.type = StringType;
|
|
6425
6483
|
this.number = number;
|
|
6426
6484
|
this.locale = locale;
|
|
6427
6485
|
this.currency = currency;
|
|
6486
|
+
this.unit = unit;
|
|
6428
6487
|
this.minFractionDigits = minFractionDigits;
|
|
6429
6488
|
this.maxFractionDigits = maxFractionDigits;
|
|
6430
6489
|
}
|
|
@@ -6449,6 +6508,15 @@
|
|
|
6449
6508
|
if (!currency)
|
|
6450
6509
|
return null;
|
|
6451
6510
|
}
|
|
6511
|
+
let unit = null;
|
|
6512
|
+
if (options['unit']) {
|
|
6513
|
+
unit = context.parse(options['unit'], 1, StringType);
|
|
6514
|
+
if (!unit)
|
|
6515
|
+
return null;
|
|
6516
|
+
}
|
|
6517
|
+
if (currency && unit) {
|
|
6518
|
+
return context.error('NumberFormat options `currency` and `unit` are mutually exclusive');
|
|
6519
|
+
}
|
|
6452
6520
|
let minFractionDigits = null;
|
|
6453
6521
|
if (options['min-fraction-digits']) {
|
|
6454
6522
|
minFractionDigits = context.parse(options['min-fraction-digits'], 1, NumberType);
|
|
@@ -6461,12 +6529,13 @@
|
|
|
6461
6529
|
if (!maxFractionDigits)
|
|
6462
6530
|
return null;
|
|
6463
6531
|
}
|
|
6464
|
-
return new NumberFormat(number, locale, currency, minFractionDigits, maxFractionDigits);
|
|
6532
|
+
return new NumberFormat(number, locale, currency, unit, minFractionDigits, maxFractionDigits);
|
|
6465
6533
|
}
|
|
6466
6534
|
evaluate(ctx) {
|
|
6467
6535
|
return new Intl.NumberFormat(this.locale ? this.locale.evaluate(ctx) : [], {
|
|
6468
|
-
style: this.currency ? 'currency' : 'decimal',
|
|
6536
|
+
style: this.currency ? 'currency' : this.unit ? 'unit' : 'decimal',
|
|
6469
6537
|
currency: this.currency ? this.currency.evaluate(ctx) : undefined,
|
|
6538
|
+
unit: this.unit ? this.unit.evaluate(ctx) : undefined,
|
|
6470
6539
|
minimumFractionDigits: this.minFractionDigits
|
|
6471
6540
|
? this.minFractionDigits.evaluate(ctx)
|
|
6472
6541
|
: undefined,
|
|
@@ -6483,6 +6552,9 @@
|
|
|
6483
6552
|
if (this.currency) {
|
|
6484
6553
|
fn(this.currency);
|
|
6485
6554
|
}
|
|
6555
|
+
if (this.unit) {
|
|
6556
|
+
fn(this.unit);
|
|
6557
|
+
}
|
|
6486
6558
|
if (this.minFractionDigits) {
|
|
6487
6559
|
fn(this.minFractionDigits);
|
|
6488
6560
|
}
|
|
@@ -8221,6 +8293,16 @@
|
|
|
8221
8293
|
varargs(ValueType),
|
|
8222
8294
|
(ctx, args) => args.map((arg) => valueToString(arg.evaluate(ctx))).join('')
|
|
8223
8295
|
],
|
|
8296
|
+
split: [
|
|
8297
|
+
array(StringType),
|
|
8298
|
+
[StringType, StringType],
|
|
8299
|
+
(ctx, [s, delim]) => s.evaluate(ctx).split(delim.evaluate(ctx))
|
|
8300
|
+
],
|
|
8301
|
+
join: [
|
|
8302
|
+
StringType,
|
|
8303
|
+
[array(StringType), StringType],
|
|
8304
|
+
(ctx, [arr, delim]) => arr.value.join(delim.evaluate(ctx))
|
|
8305
|
+
],
|
|
8224
8306
|
'resolved-locale': [
|
|
8225
8307
|
StringType,
|
|
8226
8308
|
[CollatorType],
|
|
@@ -9377,7 +9459,7 @@
|
|
|
9377
9459
|
color.alpha *= style.opacity;
|
|
9378
9460
|
this.#backgroundColor = color;
|
|
9379
9461
|
}
|
|
9380
|
-
drawPolygons(features) {
|
|
9462
|
+
drawPolygons(id, features) {
|
|
9381
9463
|
if (features.length === 0)
|
|
9382
9464
|
return;
|
|
9383
9465
|
const groups = new Map();
|
|
@@ -9401,12 +9483,14 @@
|
|
|
9401
9483
|
group.segments.push(ring.map((p) => roundXY(p.x, p.y)));
|
|
9402
9484
|
});
|
|
9403
9485
|
});
|
|
9486
|
+
this.#svg.push(`<g id="${escapeXml(id)}">`);
|
|
9404
9487
|
for (const { segments, attrs } of groups.values()) {
|
|
9405
9488
|
const d = segmentsToPath(segments, true);
|
|
9406
9489
|
this.#svg.push(`<path d="${d}" ${attrs} />`);
|
|
9407
9490
|
}
|
|
9491
|
+
this.#svg.push('</g>');
|
|
9408
9492
|
}
|
|
9409
|
-
drawLineStrings(features) {
|
|
9493
|
+
drawLineStrings(id, features) {
|
|
9410
9494
|
if (features.length === 0)
|
|
9411
9495
|
return;
|
|
9412
9496
|
const groups = new Map();
|
|
@@ -9455,13 +9539,15 @@
|
|
|
9455
9539
|
group.segments.push(line.map((p) => roundXY(p.x, p.y)));
|
|
9456
9540
|
});
|
|
9457
9541
|
});
|
|
9542
|
+
this.#svg.push(`<g id="${escapeXml(id)}">`);
|
|
9458
9543
|
for (const { segments, attrs } of groups.values()) {
|
|
9459
9544
|
const chains = chainSegments(segments);
|
|
9460
9545
|
const d = segmentsToPath(chains);
|
|
9461
9546
|
this.#svg.push(`<path d="${d}" ${attrs} />`);
|
|
9462
9547
|
}
|
|
9548
|
+
this.#svg.push('</g>');
|
|
9463
9549
|
}
|
|
9464
|
-
drawCircles(features) {
|
|
9550
|
+
drawCircles(id, features) {
|
|
9465
9551
|
if (features.length === 0)
|
|
9466
9552
|
return;
|
|
9467
9553
|
const groups = new Map();
|
|
@@ -9493,13 +9579,108 @@
|
|
|
9493
9579
|
group.points.push(roundXY(p.x, p.y));
|
|
9494
9580
|
});
|
|
9495
9581
|
});
|
|
9582
|
+
this.#svg.push(`<g id="${escapeXml(id)}">`);
|
|
9496
9583
|
for (const { points, attrs } of groups.values()) {
|
|
9497
9584
|
for (const [x, y] of points) {
|
|
9498
9585
|
this.#svg.push(`<circle cx="${formatNum(x)}" cy="${formatNum(y)}" ${attrs} />`);
|
|
9499
9586
|
}
|
|
9500
9587
|
}
|
|
9588
|
+
this.#svg.push('</g>');
|
|
9501
9589
|
}
|
|
9502
|
-
|
|
9590
|
+
drawLabels(id, features) {
|
|
9591
|
+
if (features.length === 0)
|
|
9592
|
+
return;
|
|
9593
|
+
this.#svg.push(`<g id="${escapeXml(id)}">`);
|
|
9594
|
+
for (const [feature, style] of features) {
|
|
9595
|
+
if (style.opacity <= 0 || !style.text)
|
|
9596
|
+
continue;
|
|
9597
|
+
const color = new Color(style.color);
|
|
9598
|
+
if (color.alpha <= 0)
|
|
9599
|
+
continue;
|
|
9600
|
+
const ring = feature.geometry[0];
|
|
9601
|
+
if (!ring || ring.length === 0)
|
|
9602
|
+
continue;
|
|
9603
|
+
const point = ring[Math.floor(ring.length / 2)];
|
|
9604
|
+
const [px, py] = roundXY(point.x, point.y);
|
|
9605
|
+
const fontSize = formatScaled(style.size);
|
|
9606
|
+
const fontFamily = style.font.join(', ') + ', Helvetica, Arial, sans-serif';
|
|
9607
|
+
const [svgAnchor, baseline] = mapTextAnchor(style.anchor);
|
|
9608
|
+
const offsetX = style.offset[0] * style.size;
|
|
9609
|
+
const offsetY = style.offset[1] * style.size;
|
|
9610
|
+
const [dx, dy] = roundXY(offsetX, offsetY);
|
|
9611
|
+
const attrs = [
|
|
9612
|
+
`x="${formatNum(px)}"`,
|
|
9613
|
+
`y="${formatNum(py)}"`,
|
|
9614
|
+
`font-family="${escapeXml(fontFamily)}"`,
|
|
9615
|
+
`font-size="${fontSize}"`,
|
|
9616
|
+
`text-anchor="${svgAnchor}"`,
|
|
9617
|
+
`dominant-baseline="${baseline}"`,
|
|
9618
|
+
];
|
|
9619
|
+
if (dx !== 0)
|
|
9620
|
+
attrs.push(`dx="${formatNum(dx)}"`);
|
|
9621
|
+
if (dy !== 0)
|
|
9622
|
+
attrs.push(`dy="${formatNum(dy)}"`);
|
|
9623
|
+
if (style.rotate !== 0) {
|
|
9624
|
+
attrs.push(`transform="rotate(${String(style.rotate)},${formatNum(px)},${formatNum(py)})"`);
|
|
9625
|
+
}
|
|
9626
|
+
const haloColor = new Color(style.haloColor);
|
|
9627
|
+
if (style.haloWidth > 0 && haloColor.alpha > 0) {
|
|
9628
|
+
const haloWidth = formatScaled(style.haloWidth);
|
|
9629
|
+
attrs.push('paint-order="stroke fill"', `stroke="${haloColor.rgb}"`, `stroke-width="${haloWidth}"`, 'stroke-linejoin="round"');
|
|
9630
|
+
if (haloColor.alpha < 255)
|
|
9631
|
+
attrs.push(`stroke-opacity="${haloColor.opacity.toFixed(3)}"`);
|
|
9632
|
+
}
|
|
9633
|
+
attrs.push(fillAttr(color));
|
|
9634
|
+
if (style.opacity < 1)
|
|
9635
|
+
attrs.push(`opacity="${style.opacity.toFixed(3)}"`);
|
|
9636
|
+
this.#svg.push(`<text ${attrs.join(' ')}>${escapeXml(style.text)}</text>`);
|
|
9637
|
+
}
|
|
9638
|
+
this.#svg.push('</g>');
|
|
9639
|
+
}
|
|
9640
|
+
drawIcons(id, features, spriteAtlas) {
|
|
9641
|
+
if (features.length === 0)
|
|
9642
|
+
return;
|
|
9643
|
+
this.#svg.push(`<g id="${escapeXml(id)}">`);
|
|
9644
|
+
for (const [feature, style] of features) {
|
|
9645
|
+
if (style.opacity <= 0)
|
|
9646
|
+
continue;
|
|
9647
|
+
const sprite = spriteAtlas.get(style.image);
|
|
9648
|
+
if (!sprite)
|
|
9649
|
+
continue;
|
|
9650
|
+
const ring = feature.geometry[0];
|
|
9651
|
+
if (!ring || ring.length === 0)
|
|
9652
|
+
continue;
|
|
9653
|
+
const point = ring[Math.floor(ring.length / 2)];
|
|
9654
|
+
const scale = style.size / sprite.pixelRatio;
|
|
9655
|
+
const iconW = sprite.width * scale;
|
|
9656
|
+
const iconH = sprite.height * scale;
|
|
9657
|
+
const [anchorDx, anchorDy] = mapIconAnchor(style.anchor, iconW, iconH);
|
|
9658
|
+
const ox = style.offset[0] * style.size + anchorDx;
|
|
9659
|
+
const oy = style.offset[1] * style.size + anchorDy;
|
|
9660
|
+
const x = point.x + ox;
|
|
9661
|
+
const y = point.y + oy;
|
|
9662
|
+
const [sx, sy] = roundXY(x, y);
|
|
9663
|
+
const [sw, sh] = roundXY(iconW, iconH);
|
|
9664
|
+
const viewBox = `${String(sprite.x)} ${String(sprite.y)} ${String(sprite.width)} ${String(sprite.height)}`;
|
|
9665
|
+
const attrs = [
|
|
9666
|
+
`x="${formatNum(sx)}"`,
|
|
9667
|
+
`y="${formatNum(sy)}"`,
|
|
9668
|
+
`width="${formatNum(sw)}"`,
|
|
9669
|
+
`height="${formatNum(sh)}"`,
|
|
9670
|
+
];
|
|
9671
|
+
if (style.opacity < 1)
|
|
9672
|
+
attrs.push(`opacity="${style.opacity.toFixed(3)}"`);
|
|
9673
|
+
if (style.rotate !== 0) {
|
|
9674
|
+
const [cx, cy] = roundXY(point.x + style.offset[0] * style.size, point.y + style.offset[1] * style.size);
|
|
9675
|
+
attrs.push(`transform="rotate(${String(style.rotate)},${formatNum(cx)},${formatNum(cy)})"`);
|
|
9676
|
+
}
|
|
9677
|
+
this.#svg.push(`<svg ${attrs.join(' ')} viewBox="${viewBox}" xmlns="http://www.w3.org/2000/svg">` +
|
|
9678
|
+
`<image width="${String(sprite.sheetWidth)}" height="${String(sprite.sheetHeight)}" href="${sprite.sheetDataUri}" />` +
|
|
9679
|
+
`</svg>`);
|
|
9680
|
+
}
|
|
9681
|
+
this.#svg.push('</g>');
|
|
9682
|
+
}
|
|
9683
|
+
drawRasterTiles(id, tiles, style) {
|
|
9503
9684
|
if (tiles.length === 0)
|
|
9504
9685
|
return;
|
|
9505
9686
|
if (style.opacity <= 0)
|
|
@@ -9515,7 +9696,7 @@
|
|
|
9515
9696
|
const brightness = (style.brightnessMin + style.brightnessMax) / 2;
|
|
9516
9697
|
filters.push(`brightness(${String(brightness)})`);
|
|
9517
9698
|
}
|
|
9518
|
-
let gAttrs = `opacity="${String(style.opacity)}"`;
|
|
9699
|
+
let gAttrs = `id="${escapeXml(id)}" opacity="${String(style.opacity)}"`;
|
|
9519
9700
|
if (filters.length > 0)
|
|
9520
9701
|
gAttrs += ` filter="${filters.join(' ')}"`;
|
|
9521
9702
|
this.#svg.push(`<g ${gAttrs}>`);
|
|
@@ -9566,6 +9747,57 @@
|
|
|
9566
9747
|
const [x, y] = roundXY(p[0], p[1]);
|
|
9567
9748
|
return formatNum(x) + ',' + formatNum(y);
|
|
9568
9749
|
}
|
|
9750
|
+
function mapTextAnchor(anchor) {
|
|
9751
|
+
switch (anchor) {
|
|
9752
|
+
case 'left':
|
|
9753
|
+
return ['start', 'central'];
|
|
9754
|
+
case 'right':
|
|
9755
|
+
return ['end', 'central'];
|
|
9756
|
+
case 'top':
|
|
9757
|
+
return ['middle', 'text-before-edge'];
|
|
9758
|
+
case 'bottom':
|
|
9759
|
+
return ['middle', 'text-after-edge'];
|
|
9760
|
+
case 'top-left':
|
|
9761
|
+
return ['start', 'text-before-edge'];
|
|
9762
|
+
case 'top-right':
|
|
9763
|
+
return ['end', 'text-before-edge'];
|
|
9764
|
+
case 'bottom-left':
|
|
9765
|
+
return ['start', 'text-after-edge'];
|
|
9766
|
+
case 'bottom-right':
|
|
9767
|
+
return ['end', 'text-after-edge'];
|
|
9768
|
+
default:
|
|
9769
|
+
return ['middle', 'central'];
|
|
9770
|
+
}
|
|
9771
|
+
}
|
|
9772
|
+
function mapIconAnchor(anchor, w, h) {
|
|
9773
|
+
switch (anchor) {
|
|
9774
|
+
case 'left':
|
|
9775
|
+
return [0, -h / 2];
|
|
9776
|
+
case 'right':
|
|
9777
|
+
return [-w, -h / 2];
|
|
9778
|
+
case 'top':
|
|
9779
|
+
return [-w / 2, 0];
|
|
9780
|
+
case 'bottom':
|
|
9781
|
+
return [-w / 2, -h];
|
|
9782
|
+
case 'top-left':
|
|
9783
|
+
return [0, 0];
|
|
9784
|
+
case 'top-right':
|
|
9785
|
+
return [-w, 0];
|
|
9786
|
+
case 'bottom-left':
|
|
9787
|
+
return [0, -h];
|
|
9788
|
+
case 'bottom-right':
|
|
9789
|
+
return [-w, -h];
|
|
9790
|
+
default:
|
|
9791
|
+
return [-w / 2, -h / 2];
|
|
9792
|
+
}
|
|
9793
|
+
}
|
|
9794
|
+
function escapeXml(s) {
|
|
9795
|
+
return s
|
|
9796
|
+
.replace(/&/g, '&')
|
|
9797
|
+
.replace(/</g, '<')
|
|
9798
|
+
.replace(/>/g, '>')
|
|
9799
|
+
.replace(/"/g, '"');
|
|
9800
|
+
}
|
|
9569
9801
|
|
|
9570
9802
|
/*
|
|
9571
9803
|
* bignumber.js v9.3.1
|
|
@@ -16114,6 +16346,65 @@
|
|
|
16114
16346
|
return rasterTiles.filter((tile) => tile !== null);
|
|
16115
16347
|
}
|
|
16116
16348
|
|
|
16349
|
+
async function loadSpriteAtlas(style) {
|
|
16350
|
+
const atlas = new Map();
|
|
16351
|
+
const sprite = style.sprite;
|
|
16352
|
+
if (!sprite)
|
|
16353
|
+
return atlas;
|
|
16354
|
+
const sources = [];
|
|
16355
|
+
if (typeof sprite === 'string') {
|
|
16356
|
+
sources.push({ id: 'default', url: sprite });
|
|
16357
|
+
}
|
|
16358
|
+
else if (Array.isArray(sprite)) {
|
|
16359
|
+
for (const s of sprite) {
|
|
16360
|
+
sources.push({
|
|
16361
|
+
id: s.id,
|
|
16362
|
+
url: s.url,
|
|
16363
|
+
});
|
|
16364
|
+
}
|
|
16365
|
+
}
|
|
16366
|
+
await Promise.all(sources.map(async ({ id, url }) => {
|
|
16367
|
+
try {
|
|
16368
|
+
const [jsonResponse, imageResponse] = await Promise.all([
|
|
16369
|
+
fetch(`${url}.json`),
|
|
16370
|
+
fetch(`${url}.png`),
|
|
16371
|
+
]);
|
|
16372
|
+
if (!jsonResponse.ok || !imageResponse.ok)
|
|
16373
|
+
return;
|
|
16374
|
+
const json = (await jsonResponse.json());
|
|
16375
|
+
const imageBuffer = await imageResponse.arrayBuffer();
|
|
16376
|
+
const base64 = typeof Buffer !== 'undefined'
|
|
16377
|
+
? Buffer.from(imageBuffer).toString('base64')
|
|
16378
|
+
: btoa(String.fromCharCode(...new Uint8Array(imageBuffer)));
|
|
16379
|
+
const sheetDataUri = `data:image/png;base64,${base64}`;
|
|
16380
|
+
// Estimate sheet dimensions from sprite entries
|
|
16381
|
+
let sheetWidth = 0;
|
|
16382
|
+
let sheetHeight = 0;
|
|
16383
|
+
for (const entry of Object.values(json)) {
|
|
16384
|
+
sheetWidth = Math.max(sheetWidth, entry.x + entry.width);
|
|
16385
|
+
sheetHeight = Math.max(sheetHeight, entry.y + entry.height);
|
|
16386
|
+
}
|
|
16387
|
+
const prefix = id === 'default' ? '' : `${id}:`;
|
|
16388
|
+
for (const [name, entry] of Object.entries(json)) {
|
|
16389
|
+
atlas.set(`${prefix}${name}`, {
|
|
16390
|
+
width: entry.width,
|
|
16391
|
+
height: entry.height,
|
|
16392
|
+
x: entry.x,
|
|
16393
|
+
y: entry.y,
|
|
16394
|
+
pixelRatio: entry.pixelRatio ?? 1,
|
|
16395
|
+
sheetDataUri,
|
|
16396
|
+
sheetWidth,
|
|
16397
|
+
sheetHeight,
|
|
16398
|
+
});
|
|
16399
|
+
}
|
|
16400
|
+
}
|
|
16401
|
+
catch {
|
|
16402
|
+
// Silently skip failed sprite loads
|
|
16403
|
+
}
|
|
16404
|
+
}));
|
|
16405
|
+
return atlas;
|
|
16406
|
+
}
|
|
16407
|
+
|
|
16117
16408
|
async function getLayerFeatures(job) {
|
|
16118
16409
|
const { width, height } = job.renderer;
|
|
16119
16410
|
const { zoom, center } = job.view;
|
|
@@ -16274,6 +16565,12 @@
|
|
|
16274
16565
|
return layers.map(createStyleLayer);
|
|
16275
16566
|
}
|
|
16276
16567
|
|
|
16568
|
+
function resolveTokens(text, properties) {
|
|
16569
|
+
return text.replace(/\{([^}]+)\}/g, (_, key) => {
|
|
16570
|
+
const value = properties[key];
|
|
16571
|
+
return value != null ? String(value) : '';
|
|
16572
|
+
});
|
|
16573
|
+
}
|
|
16277
16574
|
async function renderMap(job) {
|
|
16278
16575
|
await render(job);
|
|
16279
16576
|
return job.renderer.getString();
|
|
@@ -16284,9 +16581,12 @@
|
|
|
16284
16581
|
async function render(job) {
|
|
16285
16582
|
const { renderer } = job;
|
|
16286
16583
|
const { zoom } = job.view;
|
|
16287
|
-
const layerFeatures = await
|
|
16584
|
+
const [layerFeatures, spriteAtlas] = await Promise.all([
|
|
16585
|
+
getLayerFeatures(job),
|
|
16586
|
+
job.renderLabels ? loadSpriteAtlas(job.style) : Promise.resolve(new Map()),
|
|
16587
|
+
]);
|
|
16288
16588
|
const layerStyles = getLayerStyles(job.style.layers);
|
|
16289
|
-
const availableImages = [];
|
|
16589
|
+
const availableImages = [...spriteAtlas.keys()];
|
|
16290
16590
|
const featureState = {};
|
|
16291
16591
|
for (const layerStyle of layerStyles) {
|
|
16292
16592
|
if (layerStyle.isHidden(zoom))
|
|
@@ -16307,6 +16607,7 @@
|
|
|
16307
16607
|
function getLayout(key, feature) {
|
|
16308
16608
|
return getStyleValue(layerStyle.layout, key, feature);
|
|
16309
16609
|
}
|
|
16610
|
+
const layerId = layerStyle.id;
|
|
16310
16611
|
switch (layerStyle.type) {
|
|
16311
16612
|
case 'background':
|
|
16312
16613
|
{
|
|
@@ -16326,7 +16627,7 @@
|
|
|
16326
16627
|
: polygons;
|
|
16327
16628
|
if (polygonFeatures.length === 0)
|
|
16328
16629
|
continue;
|
|
16329
|
-
renderer.drawPolygons(polygonFeatures.map((feature) => [
|
|
16630
|
+
renderer.drawPolygons(layerId, polygonFeatures.map((feature) => [
|
|
16330
16631
|
feature,
|
|
16331
16632
|
{
|
|
16332
16633
|
color: getPaint('fill-color', feature),
|
|
@@ -16346,7 +16647,7 @@
|
|
|
16346
16647
|
: lineStrings;
|
|
16347
16648
|
if (lineStringFeatures.length === 0)
|
|
16348
16649
|
continue;
|
|
16349
|
-
renderer.drawLineStrings(lineStringFeatures.map((feature) => [
|
|
16650
|
+
renderer.drawLineStrings(layerId, lineStringFeatures.map((feature) => [
|
|
16350
16651
|
feature,
|
|
16351
16652
|
{
|
|
16352
16653
|
color: getPaint('line-color', feature),
|
|
@@ -16365,7 +16666,7 @@
|
|
|
16365
16666
|
case 'raster':
|
|
16366
16667
|
{
|
|
16367
16668
|
const tiles = await getRasterTiles(job, layerStyle.source);
|
|
16368
|
-
renderer.drawRasterTiles(tiles, {
|
|
16669
|
+
renderer.drawRasterTiles(layerId, tiles, {
|
|
16369
16670
|
opacity: getPaint('raster-opacity'),
|
|
16370
16671
|
hueRotate: getPaint('raster-hue-rotate'),
|
|
16371
16672
|
brightnessMin: getPaint('raster-brightness-min'),
|
|
@@ -16386,7 +16687,7 @@
|
|
|
16386
16687
|
: points;
|
|
16387
16688
|
if (pointFeatures.length === 0)
|
|
16388
16689
|
continue;
|
|
16389
|
-
renderer.drawCircles(pointFeatures.map((feature) => [
|
|
16690
|
+
renderer.drawCircles(layerId, pointFeatures.map((feature) => [
|
|
16390
16691
|
feature,
|
|
16391
16692
|
{
|
|
16392
16693
|
color: getPaint('circle-color', feature),
|
|
@@ -16399,11 +16700,76 @@
|
|
|
16399
16700
|
]));
|
|
16400
16701
|
}
|
|
16401
16702
|
continue;
|
|
16703
|
+
case 'symbol':
|
|
16704
|
+
{
|
|
16705
|
+
if (!job.renderLabels)
|
|
16706
|
+
continue;
|
|
16707
|
+
const features = getFeatures(layerFeatures, layerStyle);
|
|
16708
|
+
const allFeatures = [
|
|
16709
|
+
...(features?.points ?? []),
|
|
16710
|
+
...(features?.linestrings ?? []),
|
|
16711
|
+
...(features?.polygons ?? []),
|
|
16712
|
+
];
|
|
16713
|
+
if (allFeatures.length === 0)
|
|
16714
|
+
continue;
|
|
16715
|
+
const symbolFeatures = layerStyle.filterFn
|
|
16716
|
+
? allFeatures.filter((feature) => layerStyle.filterFn.filter({ zoom }, feature))
|
|
16717
|
+
: allFeatures;
|
|
16718
|
+
if (symbolFeatures.length === 0)
|
|
16719
|
+
continue;
|
|
16720
|
+
// Render icons first (underneath text)
|
|
16721
|
+
renderer.drawIcons(`${layerId}-icons`, symbolFeatures.flatMap((feature) => {
|
|
16722
|
+
const iconImage = getLayout('icon-image', feature);
|
|
16723
|
+
const iconName = iconImage != null
|
|
16724
|
+
? resolveTokens(iconImage.toString(), feature.properties)
|
|
16725
|
+
: '';
|
|
16726
|
+
if (!iconName || !spriteAtlas.has(iconName))
|
|
16727
|
+
return [];
|
|
16728
|
+
return [
|
|
16729
|
+
[
|
|
16730
|
+
feature,
|
|
16731
|
+
{
|
|
16732
|
+
image: iconName,
|
|
16733
|
+
size: getLayout('icon-size', feature),
|
|
16734
|
+
anchor: getLayout('icon-anchor', feature),
|
|
16735
|
+
offset: getLayout('icon-offset', feature),
|
|
16736
|
+
rotate: getLayout('icon-rotate', feature),
|
|
16737
|
+
opacity: getPaint('icon-opacity', feature),
|
|
16738
|
+
},
|
|
16739
|
+
],
|
|
16740
|
+
];
|
|
16741
|
+
}), spriteAtlas);
|
|
16742
|
+
// Render text labels on top
|
|
16743
|
+
renderer.drawLabels(`${layerId}-labels`, symbolFeatures.flatMap((feature) => {
|
|
16744
|
+
const textField = getLayout('text-field', feature);
|
|
16745
|
+
const textRaw = textField != null ? textField.toString() : '';
|
|
16746
|
+
const text = resolveTokens(textRaw, feature.properties);
|
|
16747
|
+
if (!text)
|
|
16748
|
+
return [];
|
|
16749
|
+
return [
|
|
16750
|
+
[
|
|
16751
|
+
feature,
|
|
16752
|
+
{
|
|
16753
|
+
text,
|
|
16754
|
+
size: getLayout('text-size', feature),
|
|
16755
|
+
font: getLayout('text-font', feature),
|
|
16756
|
+
anchor: getLayout('text-anchor', feature),
|
|
16757
|
+
offset: getLayout('text-offset', feature),
|
|
16758
|
+
rotate: getLayout('text-rotate', feature),
|
|
16759
|
+
color: getPaint('text-color', feature),
|
|
16760
|
+
opacity: getPaint('text-opacity', feature),
|
|
16761
|
+
haloColor: getPaint('text-halo-color', feature),
|
|
16762
|
+
haloWidth: getPaint('text-halo-width', feature),
|
|
16763
|
+
},
|
|
16764
|
+
],
|
|
16765
|
+
];
|
|
16766
|
+
}));
|
|
16767
|
+
}
|
|
16768
|
+
continue;
|
|
16402
16769
|
case 'color-relief':
|
|
16403
16770
|
case 'fill-extrusion':
|
|
16404
16771
|
case 'heatmap':
|
|
16405
16772
|
case 'hillshade':
|
|
16406
|
-
case 'symbol':
|
|
16407
16773
|
continue;
|
|
16408
16774
|
default:
|
|
16409
16775
|
throw Error('layerStyle.type: ' + String(layerStyle.type));
|
|
@@ -16425,6 +16791,7 @@
|
|
|
16425
16791
|
center: [options.lon ?? 0, options.lat ?? 0],
|
|
16426
16792
|
zoom: options.zoom ?? 2,
|
|
16427
16793
|
},
|
|
16794
|
+
renderLabels: options.renderLabels ?? false,
|
|
16428
16795
|
});
|
|
16429
16796
|
}
|
|
16430
16797
|
|
|
@@ -16520,11 +16887,14 @@
|
|
|
16520
16887
|
<div class="panel-notice">
|
|
16521
16888
|
Note:<br>
|
|
16522
16889
|
<span class="panel-attribution"></span><br>
|
|
16523
|
-
|
|
16890
|
+
Text labels are rendered without collision detection, so labels may overlap. You can improve me on <a href="https://github.com/versatiles-org/versatiles-svg-renderer" target="_blank" rel="noopener noreferrer">GitHub</a>.<br>
|
|
16524
16891
|
</div>
|
|
16525
16892
|
<div class="panel-inputs">
|
|
16526
|
-
<
|
|
16527
|
-
|
|
16893
|
+
<div class="grid">
|
|
16894
|
+
<label>Width<input type="number" class="input-width" value="${String(this.options.defaultWidth)}" min="1" max="8192"></label>
|
|
16895
|
+
<label>Height<input type="number" class="input-height" value="${String(this.options.defaultHeight)}" min="1" max="8192"></label>
|
|
16896
|
+
</div>
|
|
16897
|
+
<label class="label-checkbox"><input type="checkbox" class="input-labels"> Include labels and icons (buggy)</label>
|
|
16528
16898
|
</div>
|
|
16529
16899
|
<div class="preview-container">
|
|
16530
16900
|
<span class="preview-loading">Rendering preview\u2026</span>
|
|
@@ -16557,6 +16927,9 @@
|
|
|
16557
16927
|
input.addEventListener('input', () => {
|
|
16558
16928
|
this.schedulePreview();
|
|
16559
16929
|
});
|
|
16930
|
+
input.addEventListener('change', () => {
|
|
16931
|
+
this.schedulePreview();
|
|
16932
|
+
});
|
|
16560
16933
|
});
|
|
16561
16934
|
querySelector(this.panel, '.btn-download').addEventListener('click', () => {
|
|
16562
16935
|
this.downloadSVG();
|
|
@@ -16622,6 +16995,7 @@
|
|
|
16622
16995
|
openBtn.disabled = true;
|
|
16623
16996
|
const width = Number(querySelector(panel, '.input-width').value);
|
|
16624
16997
|
const height = Number(querySelector(panel, '.input-height').value);
|
|
16998
|
+
const renderLabels = querySelector(panel, '.input-labels').checked;
|
|
16625
16999
|
if (!width || !height || width < 1 || height < 1) {
|
|
16626
17000
|
previewContainer.innerHTML = '<span class="preview-loading">Invalid input values</span>';
|
|
16627
17001
|
return;
|
|
@@ -16637,6 +17011,7 @@
|
|
|
16637
17011
|
lon: center.lng,
|
|
16638
17012
|
lat: center.lat,
|
|
16639
17013
|
zoom,
|
|
17014
|
+
renderLabels,
|
|
16640
17015
|
});
|
|
16641
17016
|
if (this.renderGeneration !== generation)
|
|
16642
17017
|
return;
|