@versatiles/svg-renderer 0.5.1 → 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 +393 -41
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +393 -41
- package/dist/index.js.map +1 -1
- package/dist/maplibre.cjs +416 -53
- package/dist/maplibre.cjs.map +1 -1
- package/dist/maplibre.d.ts +1 -2
- package/dist/maplibre.js +416 -53
- package/dist/maplibre.js.map +1 -1
- package/dist/maplibre.umd.js +416 -53
- package/dist/maplibre.umd.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -2828,6 +2828,23 @@ var paint_raster = {
|
|
|
2828
2828
|
},
|
|
2829
2829
|
"property-type": "data-constant"
|
|
2830
2830
|
},
|
|
2831
|
+
resampling: {
|
|
2832
|
+
type: "enum",
|
|
2833
|
+
values: {
|
|
2834
|
+
linear: {
|
|
2835
|
+
},
|
|
2836
|
+
nearest: {
|
|
2837
|
+
}
|
|
2838
|
+
},
|
|
2839
|
+
"default": "linear",
|
|
2840
|
+
expression: {
|
|
2841
|
+
interpolated: false,
|
|
2842
|
+
parameters: [
|
|
2843
|
+
"zoom"
|
|
2844
|
+
]
|
|
2845
|
+
},
|
|
2846
|
+
"property-type": "data-constant"
|
|
2847
|
+
},
|
|
2831
2848
|
"raster-resampling": {
|
|
2832
2849
|
type: "enum",
|
|
2833
2850
|
values: {
|
|
@@ -2978,6 +2995,23 @@ var paint_hillshade = {
|
|
|
2978
2995
|
]
|
|
2979
2996
|
},
|
|
2980
2997
|
"property-type": "data-constant"
|
|
2998
|
+
},
|
|
2999
|
+
resampling: {
|
|
3000
|
+
type: "enum",
|
|
3001
|
+
values: {
|
|
3002
|
+
linear: {
|
|
3003
|
+
},
|
|
3004
|
+
nearest: {
|
|
3005
|
+
}
|
|
3006
|
+
},
|
|
3007
|
+
"default": "linear",
|
|
3008
|
+
expression: {
|
|
3009
|
+
interpolated: false,
|
|
3010
|
+
parameters: [
|
|
3011
|
+
"zoom"
|
|
3012
|
+
]
|
|
3013
|
+
},
|
|
3014
|
+
"property-type": "data-constant"
|
|
2981
3015
|
}
|
|
2982
3016
|
};
|
|
2983
3017
|
var paint_background = {
|
|
@@ -3398,6 +3432,23 @@ var v8Spec = {
|
|
|
3398
3432
|
]
|
|
3399
3433
|
},
|
|
3400
3434
|
"property-type": "color-ramp"
|
|
3435
|
+
},
|
|
3436
|
+
resampling: {
|
|
3437
|
+
type: "enum",
|
|
3438
|
+
values: {
|
|
3439
|
+
linear: {
|
|
3440
|
+
},
|
|
3441
|
+
nearest: {
|
|
3442
|
+
}
|
|
3443
|
+
},
|
|
3444
|
+
"default": "linear",
|
|
3445
|
+
expression: {
|
|
3446
|
+
interpolated: false,
|
|
3447
|
+
parameters: [
|
|
3448
|
+
"zoom"
|
|
3449
|
+
]
|
|
3450
|
+
},
|
|
3451
|
+
"property-type": "data-constant"
|
|
3401
3452
|
}
|
|
3402
3453
|
},
|
|
3403
3454
|
paint_background: paint_background,
|
|
@@ -6242,11 +6293,12 @@ class CollatorExpression {
|
|
|
6242
6293
|
}
|
|
6243
6294
|
|
|
6244
6295
|
class NumberFormat {
|
|
6245
|
-
constructor(number, locale, currency, minFractionDigits, maxFractionDigits) {
|
|
6296
|
+
constructor(number, locale, currency, unit, minFractionDigits, maxFractionDigits) {
|
|
6246
6297
|
this.type = StringType;
|
|
6247
6298
|
this.number = number;
|
|
6248
6299
|
this.locale = locale;
|
|
6249
6300
|
this.currency = currency;
|
|
6301
|
+
this.unit = unit;
|
|
6250
6302
|
this.minFractionDigits = minFractionDigits;
|
|
6251
6303
|
this.maxFractionDigits = maxFractionDigits;
|
|
6252
6304
|
}
|
|
@@ -6271,6 +6323,15 @@ class NumberFormat {
|
|
|
6271
6323
|
if (!currency)
|
|
6272
6324
|
return null;
|
|
6273
6325
|
}
|
|
6326
|
+
let unit = null;
|
|
6327
|
+
if (options['unit']) {
|
|
6328
|
+
unit = context.parse(options['unit'], 1, StringType);
|
|
6329
|
+
if (!unit)
|
|
6330
|
+
return null;
|
|
6331
|
+
}
|
|
6332
|
+
if (currency && unit) {
|
|
6333
|
+
return context.error('NumberFormat options `currency` and `unit` are mutually exclusive');
|
|
6334
|
+
}
|
|
6274
6335
|
let minFractionDigits = null;
|
|
6275
6336
|
if (options['min-fraction-digits']) {
|
|
6276
6337
|
minFractionDigits = context.parse(options['min-fraction-digits'], 1, NumberType);
|
|
@@ -6283,12 +6344,13 @@ class NumberFormat {
|
|
|
6283
6344
|
if (!maxFractionDigits)
|
|
6284
6345
|
return null;
|
|
6285
6346
|
}
|
|
6286
|
-
return new NumberFormat(number, locale, currency, minFractionDigits, maxFractionDigits);
|
|
6347
|
+
return new NumberFormat(number, locale, currency, unit, minFractionDigits, maxFractionDigits);
|
|
6287
6348
|
}
|
|
6288
6349
|
evaluate(ctx) {
|
|
6289
6350
|
return new Intl.NumberFormat(this.locale ? this.locale.evaluate(ctx) : [], {
|
|
6290
|
-
style: this.currency ? 'currency' : 'decimal',
|
|
6351
|
+
style: this.currency ? 'currency' : this.unit ? 'unit' : 'decimal',
|
|
6291
6352
|
currency: this.currency ? this.currency.evaluate(ctx) : undefined,
|
|
6353
|
+
unit: this.unit ? this.unit.evaluate(ctx) : undefined,
|
|
6292
6354
|
minimumFractionDigits: this.minFractionDigits
|
|
6293
6355
|
? this.minFractionDigits.evaluate(ctx)
|
|
6294
6356
|
: undefined,
|
|
@@ -6305,6 +6367,9 @@ class NumberFormat {
|
|
|
6305
6367
|
if (this.currency) {
|
|
6306
6368
|
fn(this.currency);
|
|
6307
6369
|
}
|
|
6370
|
+
if (this.unit) {
|
|
6371
|
+
fn(this.unit);
|
|
6372
|
+
}
|
|
6308
6373
|
if (this.minFractionDigits) {
|
|
6309
6374
|
fn(this.minFractionDigits);
|
|
6310
6375
|
}
|
|
@@ -8043,6 +8108,16 @@ CompoundExpression.register(expressions$1, {
|
|
|
8043
8108
|
varargs(ValueType),
|
|
8044
8109
|
(ctx, args) => args.map((arg) => valueToString(arg.evaluate(ctx))).join('')
|
|
8045
8110
|
],
|
|
8111
|
+
split: [
|
|
8112
|
+
array(StringType),
|
|
8113
|
+
[StringType, StringType],
|
|
8114
|
+
(ctx, [s, delim]) => s.evaluate(ctx).split(delim.evaluate(ctx))
|
|
8115
|
+
],
|
|
8116
|
+
join: [
|
|
8117
|
+
StringType,
|
|
8118
|
+
[array(StringType), StringType],
|
|
8119
|
+
(ctx, [arr, delim]) => arr.value.join(delim.evaluate(ctx))
|
|
8120
|
+
],
|
|
8046
8121
|
'resolved-locale': [
|
|
8047
8122
|
StringType,
|
|
8048
8123
|
[CollatorType],
|
|
@@ -9187,13 +9262,11 @@ class SVGRenderer {
|
|
|
9187
9262
|
width;
|
|
9188
9263
|
height;
|
|
9189
9264
|
#svg;
|
|
9190
|
-
#scale;
|
|
9191
9265
|
#backgroundColor;
|
|
9192
9266
|
constructor(opt) {
|
|
9193
9267
|
this.width = opt.width;
|
|
9194
9268
|
this.height = opt.height;
|
|
9195
9269
|
this.#svg = [];
|
|
9196
|
-
this.#scale = opt.scale;
|
|
9197
9270
|
this.#backgroundColor = Color.transparent;
|
|
9198
9271
|
}
|
|
9199
9272
|
drawBackgroundFill(style) {
|
|
@@ -9201,7 +9274,7 @@ class SVGRenderer {
|
|
|
9201
9274
|
color.alpha *= style.opacity;
|
|
9202
9275
|
this.#backgroundColor = color;
|
|
9203
9276
|
}
|
|
9204
|
-
drawPolygons(features) {
|
|
9277
|
+
drawPolygons(id, features) {
|
|
9205
9278
|
if (features.length === 0)
|
|
9206
9279
|
return;
|
|
9207
9280
|
const groups = new Map();
|
|
@@ -9213,7 +9286,7 @@ class SVGRenderer {
|
|
|
9213
9286
|
return;
|
|
9214
9287
|
const translate = style.translate[0] === 0 && style.translate[1] === 0
|
|
9215
9288
|
? ''
|
|
9216
|
-
: ` transform="translate(${formatPoint(style.translate
|
|
9289
|
+
: ` transform="translate(${formatPoint(style.translate)})"`;
|
|
9217
9290
|
const opacityAttr = style.opacity < 1 ? ` opacity="${style.opacity.toFixed(3)}"` : '';
|
|
9218
9291
|
const key = color.hex + translate + opacityAttr;
|
|
9219
9292
|
let group = groups.get(key);
|
|
@@ -9222,15 +9295,17 @@ class SVGRenderer {
|
|
|
9222
9295
|
groups.set(key, group);
|
|
9223
9296
|
}
|
|
9224
9297
|
feature.geometry.forEach((ring) => {
|
|
9225
|
-
group.segments.push(ring.map((p) => roundXY(p.x, p.y
|
|
9298
|
+
group.segments.push(ring.map((p) => roundXY(p.x, p.y)));
|
|
9226
9299
|
});
|
|
9227
9300
|
});
|
|
9301
|
+
this.#svg.push(`<g id="${escapeXml(id)}">`);
|
|
9228
9302
|
for (const { segments, attrs } of groups.values()) {
|
|
9229
9303
|
const d = segmentsToPath(segments, true);
|
|
9230
9304
|
this.#svg.push(`<path d="${d}" ${attrs} />`);
|
|
9231
9305
|
}
|
|
9306
|
+
this.#svg.push('</g>');
|
|
9232
9307
|
}
|
|
9233
|
-
drawLineStrings(features) {
|
|
9308
|
+
drawLineStrings(id, features) {
|
|
9234
9309
|
if (features.length === 0)
|
|
9235
9310
|
return;
|
|
9236
9311
|
const groups = new Map();
|
|
@@ -9242,10 +9317,10 @@ class SVGRenderer {
|
|
|
9242
9317
|
return;
|
|
9243
9318
|
const translate = style.translate[0] === 0 && style.translate[1] === 0
|
|
9244
9319
|
? ''
|
|
9245
|
-
: ` transform="translate(${formatPoint(style.translate
|
|
9246
|
-
const roundedWidth = formatScaled(style.width
|
|
9320
|
+
: ` transform="translate(${formatPoint(style.translate)})"`;
|
|
9321
|
+
const roundedWidth = formatScaled(style.width);
|
|
9247
9322
|
const dasharrayStr = style.dasharray
|
|
9248
|
-
? style.dasharray.map((v) => formatScaled(v * style.width
|
|
9323
|
+
? style.dasharray.map((v) => formatScaled(v * style.width)).join(',')
|
|
9249
9324
|
: '';
|
|
9250
9325
|
const opacityAttr = style.opacity < 1 ? ` opacity="${style.opacity.toFixed(3)}"` : '';
|
|
9251
9326
|
const key = [
|
|
@@ -9276,16 +9351,18 @@ class SVGRenderer {
|
|
|
9276
9351
|
groups.set(key, group);
|
|
9277
9352
|
}
|
|
9278
9353
|
feature.geometry.forEach((line) => {
|
|
9279
|
-
group.segments.push(line.map((p) => roundXY(p.x, p.y
|
|
9354
|
+
group.segments.push(line.map((p) => roundXY(p.x, p.y)));
|
|
9280
9355
|
});
|
|
9281
9356
|
});
|
|
9357
|
+
this.#svg.push(`<g id="${escapeXml(id)}">`);
|
|
9282
9358
|
for (const { segments, attrs } of groups.values()) {
|
|
9283
9359
|
const chains = chainSegments(segments);
|
|
9284
9360
|
const d = segmentsToPath(chains);
|
|
9285
9361
|
this.#svg.push(`<path d="${d}" ${attrs} />`);
|
|
9286
9362
|
}
|
|
9363
|
+
this.#svg.push('</g>');
|
|
9287
9364
|
}
|
|
9288
|
-
drawCircles(features) {
|
|
9365
|
+
drawCircles(id, features) {
|
|
9289
9366
|
if (features.length === 0)
|
|
9290
9367
|
return;
|
|
9291
9368
|
const groups = new Map();
|
|
@@ -9297,12 +9374,10 @@ class SVGRenderer {
|
|
|
9297
9374
|
return;
|
|
9298
9375
|
const translate = style.translate[0] === 0 && style.translate[1] === 0
|
|
9299
9376
|
? ''
|
|
9300
|
-
: ` transform="translate(${formatPoint(style.translate
|
|
9301
|
-
const roundedRadius = formatScaled(style.radius
|
|
9377
|
+
: ` transform="translate(${formatPoint(style.translate)})"`;
|
|
9378
|
+
const roundedRadius = formatScaled(style.radius);
|
|
9302
9379
|
const strokeColor = new Color(style.strokeColor);
|
|
9303
|
-
const strokeAttrs = style.strokeWidth > 0
|
|
9304
|
-
? ` ${strokeAttr(strokeColor, formatScaled(style.strokeWidth, this.#scale))}`
|
|
9305
|
-
: '';
|
|
9380
|
+
const strokeAttrs = style.strokeWidth > 0 ? ` ${strokeAttr(strokeColor, formatScaled(style.strokeWidth))}` : '';
|
|
9306
9381
|
const opacityAttr = style.opacity < 1 ? ` opacity="${style.opacity.toFixed(3)}"` : '';
|
|
9307
9382
|
const key = [color.hex, roundedRadius, strokeAttrs, opacityAttr, translate].join('\0');
|
|
9308
9383
|
let group = groups.get(key);
|
|
@@ -9316,16 +9391,111 @@ class SVGRenderer {
|
|
|
9316
9391
|
feature.geometry.forEach((ring) => {
|
|
9317
9392
|
const p = ring[0];
|
|
9318
9393
|
if (p)
|
|
9319
|
-
group.points.push(roundXY(p.x, p.y
|
|
9394
|
+
group.points.push(roundXY(p.x, p.y));
|
|
9320
9395
|
});
|
|
9321
9396
|
});
|
|
9397
|
+
this.#svg.push(`<g id="${escapeXml(id)}">`);
|
|
9322
9398
|
for (const { points, attrs } of groups.values()) {
|
|
9323
9399
|
for (const [x, y] of points) {
|
|
9324
9400
|
this.#svg.push(`<circle cx="${formatNum(x)}" cy="${formatNum(y)}" ${attrs} />`);
|
|
9325
9401
|
}
|
|
9326
9402
|
}
|
|
9403
|
+
this.#svg.push('</g>');
|
|
9404
|
+
}
|
|
9405
|
+
drawLabels(id, features) {
|
|
9406
|
+
if (features.length === 0)
|
|
9407
|
+
return;
|
|
9408
|
+
this.#svg.push(`<g id="${escapeXml(id)}">`);
|
|
9409
|
+
for (const [feature, style] of features) {
|
|
9410
|
+
if (style.opacity <= 0 || !style.text)
|
|
9411
|
+
continue;
|
|
9412
|
+
const color = new Color(style.color);
|
|
9413
|
+
if (color.alpha <= 0)
|
|
9414
|
+
continue;
|
|
9415
|
+
const ring = feature.geometry[0];
|
|
9416
|
+
if (!ring || ring.length === 0)
|
|
9417
|
+
continue;
|
|
9418
|
+
const point = ring[Math.floor(ring.length / 2)];
|
|
9419
|
+
const [px, py] = roundXY(point.x, point.y);
|
|
9420
|
+
const fontSize = formatScaled(style.size);
|
|
9421
|
+
const fontFamily = style.font.join(', ') + ', Helvetica, Arial, sans-serif';
|
|
9422
|
+
const [svgAnchor, baseline] = mapTextAnchor(style.anchor);
|
|
9423
|
+
const offsetX = style.offset[0] * style.size;
|
|
9424
|
+
const offsetY = style.offset[1] * style.size;
|
|
9425
|
+
const [dx, dy] = roundXY(offsetX, offsetY);
|
|
9426
|
+
const attrs = [
|
|
9427
|
+
`x="${formatNum(px)}"`,
|
|
9428
|
+
`y="${formatNum(py)}"`,
|
|
9429
|
+
`font-family="${escapeXml(fontFamily)}"`,
|
|
9430
|
+
`font-size="${fontSize}"`,
|
|
9431
|
+
`text-anchor="${svgAnchor}"`,
|
|
9432
|
+
`dominant-baseline="${baseline}"`,
|
|
9433
|
+
];
|
|
9434
|
+
if (dx !== 0)
|
|
9435
|
+
attrs.push(`dx="${formatNum(dx)}"`);
|
|
9436
|
+
if (dy !== 0)
|
|
9437
|
+
attrs.push(`dy="${formatNum(dy)}"`);
|
|
9438
|
+
if (style.rotate !== 0) {
|
|
9439
|
+
attrs.push(`transform="rotate(${String(style.rotate)},${formatNum(px)},${formatNum(py)})"`);
|
|
9440
|
+
}
|
|
9441
|
+
const haloColor = new Color(style.haloColor);
|
|
9442
|
+
if (style.haloWidth > 0 && haloColor.alpha > 0) {
|
|
9443
|
+
const haloWidth = formatScaled(style.haloWidth);
|
|
9444
|
+
attrs.push('paint-order="stroke fill"', `stroke="${haloColor.rgb}"`, `stroke-width="${haloWidth}"`, 'stroke-linejoin="round"');
|
|
9445
|
+
if (haloColor.alpha < 255)
|
|
9446
|
+
attrs.push(`stroke-opacity="${haloColor.opacity.toFixed(3)}"`);
|
|
9447
|
+
}
|
|
9448
|
+
attrs.push(fillAttr(color));
|
|
9449
|
+
if (style.opacity < 1)
|
|
9450
|
+
attrs.push(`opacity="${style.opacity.toFixed(3)}"`);
|
|
9451
|
+
this.#svg.push(`<text ${attrs.join(' ')}>${escapeXml(style.text)}</text>`);
|
|
9452
|
+
}
|
|
9453
|
+
this.#svg.push('</g>');
|
|
9454
|
+
}
|
|
9455
|
+
drawIcons(id, features, spriteAtlas) {
|
|
9456
|
+
if (features.length === 0)
|
|
9457
|
+
return;
|
|
9458
|
+
this.#svg.push(`<g id="${escapeXml(id)}">`);
|
|
9459
|
+
for (const [feature, style] of features) {
|
|
9460
|
+
if (style.opacity <= 0)
|
|
9461
|
+
continue;
|
|
9462
|
+
const sprite = spriteAtlas.get(style.image);
|
|
9463
|
+
if (!sprite)
|
|
9464
|
+
continue;
|
|
9465
|
+
const ring = feature.geometry[0];
|
|
9466
|
+
if (!ring || ring.length === 0)
|
|
9467
|
+
continue;
|
|
9468
|
+
const point = ring[Math.floor(ring.length / 2)];
|
|
9469
|
+
const scale = style.size / sprite.pixelRatio;
|
|
9470
|
+
const iconW = sprite.width * scale;
|
|
9471
|
+
const iconH = sprite.height * scale;
|
|
9472
|
+
const [anchorDx, anchorDy] = mapIconAnchor(style.anchor, iconW, iconH);
|
|
9473
|
+
const ox = style.offset[0] * style.size + anchorDx;
|
|
9474
|
+
const oy = style.offset[1] * style.size + anchorDy;
|
|
9475
|
+
const x = point.x + ox;
|
|
9476
|
+
const y = point.y + oy;
|
|
9477
|
+
const [sx, sy] = roundXY(x, y);
|
|
9478
|
+
const [sw, sh] = roundXY(iconW, iconH);
|
|
9479
|
+
const viewBox = `${String(sprite.x)} ${String(sprite.y)} ${String(sprite.width)} ${String(sprite.height)}`;
|
|
9480
|
+
const attrs = [
|
|
9481
|
+
`x="${formatNum(sx)}"`,
|
|
9482
|
+
`y="${formatNum(sy)}"`,
|
|
9483
|
+
`width="${formatNum(sw)}"`,
|
|
9484
|
+
`height="${formatNum(sh)}"`,
|
|
9485
|
+
];
|
|
9486
|
+
if (style.opacity < 1)
|
|
9487
|
+
attrs.push(`opacity="${style.opacity.toFixed(3)}"`);
|
|
9488
|
+
if (style.rotate !== 0) {
|
|
9489
|
+
const [cx, cy] = roundXY(point.x + style.offset[0] * style.size, point.y + style.offset[1] * style.size);
|
|
9490
|
+
attrs.push(`transform="rotate(${String(style.rotate)},${formatNum(cx)},${formatNum(cy)})"`);
|
|
9491
|
+
}
|
|
9492
|
+
this.#svg.push(`<svg ${attrs.join(' ')} viewBox="${viewBox}" xmlns="http://www.w3.org/2000/svg">` +
|
|
9493
|
+
`<image width="${String(sprite.sheetWidth)}" height="${String(sprite.sheetHeight)}" href="${sprite.sheetDataUri}" />` +
|
|
9494
|
+
`</svg>`);
|
|
9495
|
+
}
|
|
9496
|
+
this.#svg.push('</g>');
|
|
9327
9497
|
}
|
|
9328
|
-
drawRasterTiles(tiles, style) {
|
|
9498
|
+
drawRasterTiles(id, tiles, style) {
|
|
9329
9499
|
if (tiles.length === 0)
|
|
9330
9500
|
return;
|
|
9331
9501
|
if (style.opacity <= 0)
|
|
@@ -9341,15 +9511,14 @@ class SVGRenderer {
|
|
|
9341
9511
|
const brightness = (style.brightnessMin + style.brightnessMax) / 2;
|
|
9342
9512
|
filters.push(`brightness(${String(brightness)})`);
|
|
9343
9513
|
}
|
|
9344
|
-
let gAttrs = `opacity="${String(style.opacity)}"`;
|
|
9514
|
+
let gAttrs = `id="${escapeXml(id)}" opacity="${String(style.opacity)}"`;
|
|
9345
9515
|
if (filters.length > 0)
|
|
9346
9516
|
gAttrs += ` filter="${filters.join(' ')}"`;
|
|
9347
9517
|
this.#svg.push(`<g ${gAttrs}>`);
|
|
9348
9518
|
const pixelated = style.resampling === 'nearest';
|
|
9349
9519
|
for (const tile of tiles) {
|
|
9350
9520
|
const overlap = Math.min(tile.width, tile.height) / 10000; // slight overlap to prevent sub-pixel gaps between tiles
|
|
9351
|
-
|
|
9352
|
-
let attrs = `x="${formatScaled(tile.x - overlap, s)}" y="${formatScaled(tile.y - overlap, s)}" width="${formatScaled(tile.width + overlap * 2, s)}" height="${formatScaled(tile.height + overlap * 2, s)}" href="${tile.dataUri}"`;
|
|
9521
|
+
let attrs = `x="${formatScaled(tile.x - overlap)}" y="${formatScaled(tile.y - overlap)}" width="${formatScaled(tile.width + overlap * 2)}" height="${formatScaled(tile.height + overlap * 2)}" href="${tile.dataUri}"`;
|
|
9353
9522
|
if (pixelated)
|
|
9354
9523
|
attrs += ' style="image-rendering:pixelated"';
|
|
9355
9524
|
this.#svg.push(`<image ${attrs} />`);
|
|
@@ -9383,16 +9552,67 @@ function strokeAttr(color, width) {
|
|
|
9383
9552
|
attr += ` stroke-opacity="${color.opacity.toFixed(3)}"`;
|
|
9384
9553
|
return attr;
|
|
9385
9554
|
}
|
|
9386
|
-
function formatScaled(v
|
|
9387
|
-
return formatNum(Math.round(v *
|
|
9555
|
+
function formatScaled(v) {
|
|
9556
|
+
return formatNum(Math.round(v * 10));
|
|
9388
9557
|
}
|
|
9389
|
-
function roundXY(x, y
|
|
9390
|
-
return [Math.round(x *
|
|
9558
|
+
function roundXY(x, y) {
|
|
9559
|
+
return [Math.round(x * 10), Math.round(y * 10)];
|
|
9391
9560
|
}
|
|
9392
|
-
function formatPoint(p
|
|
9393
|
-
const [x, y] = roundXY(p[0], p[1]
|
|
9561
|
+
function formatPoint(p) {
|
|
9562
|
+
const [x, y] = roundXY(p[0], p[1]);
|
|
9394
9563
|
return formatNum(x) + ',' + formatNum(y);
|
|
9395
9564
|
}
|
|
9565
|
+
function mapTextAnchor(anchor) {
|
|
9566
|
+
switch (anchor) {
|
|
9567
|
+
case 'left':
|
|
9568
|
+
return ['start', 'central'];
|
|
9569
|
+
case 'right':
|
|
9570
|
+
return ['end', 'central'];
|
|
9571
|
+
case 'top':
|
|
9572
|
+
return ['middle', 'text-before-edge'];
|
|
9573
|
+
case 'bottom':
|
|
9574
|
+
return ['middle', 'text-after-edge'];
|
|
9575
|
+
case 'top-left':
|
|
9576
|
+
return ['start', 'text-before-edge'];
|
|
9577
|
+
case 'top-right':
|
|
9578
|
+
return ['end', 'text-before-edge'];
|
|
9579
|
+
case 'bottom-left':
|
|
9580
|
+
return ['start', 'text-after-edge'];
|
|
9581
|
+
case 'bottom-right':
|
|
9582
|
+
return ['end', 'text-after-edge'];
|
|
9583
|
+
default:
|
|
9584
|
+
return ['middle', 'central'];
|
|
9585
|
+
}
|
|
9586
|
+
}
|
|
9587
|
+
function mapIconAnchor(anchor, w, h) {
|
|
9588
|
+
switch (anchor) {
|
|
9589
|
+
case 'left':
|
|
9590
|
+
return [0, -h / 2];
|
|
9591
|
+
case 'right':
|
|
9592
|
+
return [-w, -h / 2];
|
|
9593
|
+
case 'top':
|
|
9594
|
+
return [-w / 2, 0];
|
|
9595
|
+
case 'bottom':
|
|
9596
|
+
return [-w / 2, -h];
|
|
9597
|
+
case 'top-left':
|
|
9598
|
+
return [0, 0];
|
|
9599
|
+
case 'top-right':
|
|
9600
|
+
return [-w, 0];
|
|
9601
|
+
case 'bottom-left':
|
|
9602
|
+
return [0, -h];
|
|
9603
|
+
case 'bottom-right':
|
|
9604
|
+
return [-w, -h];
|
|
9605
|
+
default:
|
|
9606
|
+
return [-w / 2, -h / 2];
|
|
9607
|
+
}
|
|
9608
|
+
}
|
|
9609
|
+
function escapeXml(s) {
|
|
9610
|
+
return s
|
|
9611
|
+
.replace(/&/g, '&')
|
|
9612
|
+
.replace(/</g, '<')
|
|
9613
|
+
.replace(/>/g, '>')
|
|
9614
|
+
.replace(/"/g, '"');
|
|
9615
|
+
}
|
|
9396
9616
|
|
|
9397
9617
|
/*
|
|
9398
9618
|
* bignumber.js v9.3.1
|
|
@@ -15941,6 +16161,65 @@ async function getRasterTiles(job, sourceName) {
|
|
|
15941
16161
|
return rasterTiles.filter((tile) => tile !== null);
|
|
15942
16162
|
}
|
|
15943
16163
|
|
|
16164
|
+
async function loadSpriteAtlas(style) {
|
|
16165
|
+
const atlas = new Map();
|
|
16166
|
+
const sprite = style.sprite;
|
|
16167
|
+
if (!sprite)
|
|
16168
|
+
return atlas;
|
|
16169
|
+
const sources = [];
|
|
16170
|
+
if (typeof sprite === 'string') {
|
|
16171
|
+
sources.push({ id: 'default', url: sprite });
|
|
16172
|
+
}
|
|
16173
|
+
else if (Array.isArray(sprite)) {
|
|
16174
|
+
for (const s of sprite) {
|
|
16175
|
+
sources.push({
|
|
16176
|
+
id: s.id,
|
|
16177
|
+
url: s.url,
|
|
16178
|
+
});
|
|
16179
|
+
}
|
|
16180
|
+
}
|
|
16181
|
+
await Promise.all(sources.map(async ({ id, url }) => {
|
|
16182
|
+
try {
|
|
16183
|
+
const [jsonResponse, imageResponse] = await Promise.all([
|
|
16184
|
+
fetch(`${url}.json`),
|
|
16185
|
+
fetch(`${url}.png`),
|
|
16186
|
+
]);
|
|
16187
|
+
if (!jsonResponse.ok || !imageResponse.ok)
|
|
16188
|
+
return;
|
|
16189
|
+
const json = (await jsonResponse.json());
|
|
16190
|
+
const imageBuffer = await imageResponse.arrayBuffer();
|
|
16191
|
+
const base64 = typeof Buffer !== 'undefined'
|
|
16192
|
+
? Buffer.from(imageBuffer).toString('base64')
|
|
16193
|
+
: btoa(String.fromCharCode(...new Uint8Array(imageBuffer)));
|
|
16194
|
+
const sheetDataUri = `data:image/png;base64,${base64}`;
|
|
16195
|
+
// Estimate sheet dimensions from sprite entries
|
|
16196
|
+
let sheetWidth = 0;
|
|
16197
|
+
let sheetHeight = 0;
|
|
16198
|
+
for (const entry of Object.values(json)) {
|
|
16199
|
+
sheetWidth = Math.max(sheetWidth, entry.x + entry.width);
|
|
16200
|
+
sheetHeight = Math.max(sheetHeight, entry.y + entry.height);
|
|
16201
|
+
}
|
|
16202
|
+
const prefix = id === 'default' ? '' : `${id}:`;
|
|
16203
|
+
for (const [name, entry] of Object.entries(json)) {
|
|
16204
|
+
atlas.set(`${prefix}${name}`, {
|
|
16205
|
+
width: entry.width,
|
|
16206
|
+
height: entry.height,
|
|
16207
|
+
x: entry.x,
|
|
16208
|
+
y: entry.y,
|
|
16209
|
+
pixelRatio: entry.pixelRatio ?? 1,
|
|
16210
|
+
sheetDataUri,
|
|
16211
|
+
sheetWidth,
|
|
16212
|
+
sheetHeight,
|
|
16213
|
+
});
|
|
16214
|
+
}
|
|
16215
|
+
}
|
|
16216
|
+
catch {
|
|
16217
|
+
// Silently skip failed sprite loads
|
|
16218
|
+
}
|
|
16219
|
+
}));
|
|
16220
|
+
return atlas;
|
|
16221
|
+
}
|
|
16222
|
+
|
|
15944
16223
|
async function getLayerFeatures(job) {
|
|
15945
16224
|
const { width, height } = job.renderer;
|
|
15946
16225
|
const { zoom, center } = job.view;
|
|
@@ -16101,6 +16380,12 @@ function getLayerStyles(layers) {
|
|
|
16101
16380
|
return layers.map(createStyleLayer);
|
|
16102
16381
|
}
|
|
16103
16382
|
|
|
16383
|
+
function resolveTokens(text, properties) {
|
|
16384
|
+
return text.replace(/\{([^}]+)\}/g, (_, key) => {
|
|
16385
|
+
const value = properties[key];
|
|
16386
|
+
return value != null ? String(value) : '';
|
|
16387
|
+
});
|
|
16388
|
+
}
|
|
16104
16389
|
async function renderMap(job) {
|
|
16105
16390
|
await render(job);
|
|
16106
16391
|
return job.renderer.getString();
|
|
@@ -16111,9 +16396,12 @@ function getFeatures(layerFeatures, layerStyle) {
|
|
|
16111
16396
|
async function render(job) {
|
|
16112
16397
|
const { renderer } = job;
|
|
16113
16398
|
const { zoom } = job.view;
|
|
16114
|
-
const layerFeatures = await
|
|
16399
|
+
const [layerFeatures, spriteAtlas] = await Promise.all([
|
|
16400
|
+
getLayerFeatures(job),
|
|
16401
|
+
job.renderLabels ? loadSpriteAtlas(job.style) : Promise.resolve(new Map()),
|
|
16402
|
+
]);
|
|
16115
16403
|
const layerStyles = getLayerStyles(job.style.layers);
|
|
16116
|
-
const availableImages = [];
|
|
16404
|
+
const availableImages = [...spriteAtlas.keys()];
|
|
16117
16405
|
const featureState = {};
|
|
16118
16406
|
for (const layerStyle of layerStyles) {
|
|
16119
16407
|
if (layerStyle.isHidden(zoom))
|
|
@@ -16134,6 +16422,7 @@ async function render(job) {
|
|
|
16134
16422
|
function getLayout(key, feature) {
|
|
16135
16423
|
return getStyleValue(layerStyle.layout, key, feature);
|
|
16136
16424
|
}
|
|
16425
|
+
const layerId = layerStyle.id;
|
|
16137
16426
|
switch (layerStyle.type) {
|
|
16138
16427
|
case 'background':
|
|
16139
16428
|
{
|
|
@@ -16153,7 +16442,7 @@ async function render(job) {
|
|
|
16153
16442
|
: polygons;
|
|
16154
16443
|
if (polygonFeatures.length === 0)
|
|
16155
16444
|
continue;
|
|
16156
|
-
renderer.drawPolygons(polygonFeatures.map((feature) => [
|
|
16445
|
+
renderer.drawPolygons(layerId, polygonFeatures.map((feature) => [
|
|
16157
16446
|
feature,
|
|
16158
16447
|
{
|
|
16159
16448
|
color: getPaint('fill-color', feature),
|
|
@@ -16173,7 +16462,7 @@ async function render(job) {
|
|
|
16173
16462
|
: lineStrings;
|
|
16174
16463
|
if (lineStringFeatures.length === 0)
|
|
16175
16464
|
continue;
|
|
16176
|
-
renderer.drawLineStrings(lineStringFeatures.map((feature) => [
|
|
16465
|
+
renderer.drawLineStrings(layerId, lineStringFeatures.map((feature) => [
|
|
16177
16466
|
feature,
|
|
16178
16467
|
{
|
|
16179
16468
|
color: getPaint('line-color', feature),
|
|
@@ -16192,7 +16481,7 @@ async function render(job) {
|
|
|
16192
16481
|
case 'raster':
|
|
16193
16482
|
{
|
|
16194
16483
|
const tiles = await getRasterTiles(job, layerStyle.source);
|
|
16195
|
-
renderer.drawRasterTiles(tiles, {
|
|
16484
|
+
renderer.drawRasterTiles(layerId, tiles, {
|
|
16196
16485
|
opacity: getPaint('raster-opacity'),
|
|
16197
16486
|
hueRotate: getPaint('raster-hue-rotate'),
|
|
16198
16487
|
brightnessMin: getPaint('raster-brightness-min'),
|
|
@@ -16213,7 +16502,7 @@ async function render(job) {
|
|
|
16213
16502
|
: points;
|
|
16214
16503
|
if (pointFeatures.length === 0)
|
|
16215
16504
|
continue;
|
|
16216
|
-
renderer.drawCircles(pointFeatures.map((feature) => [
|
|
16505
|
+
renderer.drawCircles(layerId, pointFeatures.map((feature) => [
|
|
16217
16506
|
feature,
|
|
16218
16507
|
{
|
|
16219
16508
|
color: getPaint('circle-color', feature),
|
|
@@ -16226,11 +16515,76 @@ async function render(job) {
|
|
|
16226
16515
|
]));
|
|
16227
16516
|
}
|
|
16228
16517
|
continue;
|
|
16518
|
+
case 'symbol':
|
|
16519
|
+
{
|
|
16520
|
+
if (!job.renderLabels)
|
|
16521
|
+
continue;
|
|
16522
|
+
const features = getFeatures(layerFeatures, layerStyle);
|
|
16523
|
+
const allFeatures = [
|
|
16524
|
+
...(features?.points ?? []),
|
|
16525
|
+
...(features?.linestrings ?? []),
|
|
16526
|
+
...(features?.polygons ?? []),
|
|
16527
|
+
];
|
|
16528
|
+
if (allFeatures.length === 0)
|
|
16529
|
+
continue;
|
|
16530
|
+
const symbolFeatures = layerStyle.filterFn
|
|
16531
|
+
? allFeatures.filter((feature) => layerStyle.filterFn.filter({ zoom }, feature))
|
|
16532
|
+
: allFeatures;
|
|
16533
|
+
if (symbolFeatures.length === 0)
|
|
16534
|
+
continue;
|
|
16535
|
+
// Render icons first (underneath text)
|
|
16536
|
+
renderer.drawIcons(`${layerId}-icons`, symbolFeatures.flatMap((feature) => {
|
|
16537
|
+
const iconImage = getLayout('icon-image', feature);
|
|
16538
|
+
const iconName = iconImage != null
|
|
16539
|
+
? resolveTokens(iconImage.toString(), feature.properties)
|
|
16540
|
+
: '';
|
|
16541
|
+
if (!iconName || !spriteAtlas.has(iconName))
|
|
16542
|
+
return [];
|
|
16543
|
+
return [
|
|
16544
|
+
[
|
|
16545
|
+
feature,
|
|
16546
|
+
{
|
|
16547
|
+
image: iconName,
|
|
16548
|
+
size: getLayout('icon-size', feature),
|
|
16549
|
+
anchor: getLayout('icon-anchor', feature),
|
|
16550
|
+
offset: getLayout('icon-offset', feature),
|
|
16551
|
+
rotate: getLayout('icon-rotate', feature),
|
|
16552
|
+
opacity: getPaint('icon-opacity', feature),
|
|
16553
|
+
},
|
|
16554
|
+
],
|
|
16555
|
+
];
|
|
16556
|
+
}), spriteAtlas);
|
|
16557
|
+
// Render text labels on top
|
|
16558
|
+
renderer.drawLabels(`${layerId}-labels`, symbolFeatures.flatMap((feature) => {
|
|
16559
|
+
const textField = getLayout('text-field', feature);
|
|
16560
|
+
const textRaw = textField != null ? textField.toString() : '';
|
|
16561
|
+
const text = resolveTokens(textRaw, feature.properties);
|
|
16562
|
+
if (!text)
|
|
16563
|
+
return [];
|
|
16564
|
+
return [
|
|
16565
|
+
[
|
|
16566
|
+
feature,
|
|
16567
|
+
{
|
|
16568
|
+
text,
|
|
16569
|
+
size: getLayout('text-size', feature),
|
|
16570
|
+
font: getLayout('text-font', feature),
|
|
16571
|
+
anchor: getLayout('text-anchor', feature),
|
|
16572
|
+
offset: getLayout('text-offset', feature),
|
|
16573
|
+
rotate: getLayout('text-rotate', feature),
|
|
16574
|
+
color: getPaint('text-color', feature),
|
|
16575
|
+
opacity: getPaint('text-opacity', feature),
|
|
16576
|
+
haloColor: getPaint('text-halo-color', feature),
|
|
16577
|
+
haloWidth: getPaint('text-halo-width', feature),
|
|
16578
|
+
},
|
|
16579
|
+
],
|
|
16580
|
+
];
|
|
16581
|
+
}));
|
|
16582
|
+
}
|
|
16583
|
+
continue;
|
|
16229
16584
|
case 'color-relief':
|
|
16230
16585
|
case 'fill-extrusion':
|
|
16231
16586
|
case 'heatmap':
|
|
16232
16587
|
case 'hillshade':
|
|
16233
|
-
case 'symbol':
|
|
16234
16588
|
continue;
|
|
16235
16589
|
default:
|
|
16236
16590
|
throw Error('layerStyle.type: ' + String(layerStyle.type));
|
|
@@ -16241,20 +16595,18 @@ async function render(job) {
|
|
|
16241
16595
|
async function renderToSVG(options) {
|
|
16242
16596
|
const width = options.width ?? 1024;
|
|
16243
16597
|
const height = options.height ?? 1024;
|
|
16244
|
-
const scale = options.scale ?? 1;
|
|
16245
16598
|
if (width <= 0)
|
|
16246
16599
|
throw new Error('width must be positive');
|
|
16247
16600
|
if (height <= 0)
|
|
16248
16601
|
throw new Error('height must be positive');
|
|
16249
|
-
if (scale <= 0)
|
|
16250
|
-
throw new Error('scale must be positive');
|
|
16251
16602
|
return await renderMap({
|
|
16252
|
-
renderer: new SVGRenderer({ width, height
|
|
16603
|
+
renderer: new SVGRenderer({ width, height }),
|
|
16253
16604
|
style: options.style,
|
|
16254
16605
|
view: {
|
|
16255
16606
|
center: [options.lon ?? 0, options.lat ?? 0],
|
|
16256
16607
|
zoom: options.zoom ?? 2,
|
|
16257
16608
|
},
|
|
16609
|
+
renderLabels: options.renderLabels ?? false,
|
|
16258
16610
|
});
|
|
16259
16611
|
}
|
|
16260
16612
|
|