@versatiles/svg-renderer 0.4.0 → 0.5.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 +37 -42
- package/dist/index.cjs +278 -230
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +278 -230
- package/dist/index.js.map +1 -1
- package/dist/maplibre.cjs +322 -231
- package/dist/maplibre.cjs.map +1 -1
- package/dist/maplibre.js +322 -231
- package/dist/maplibre.js.map +1 -1
- package/dist/maplibre.umd.js +322 -231
- package/dist/maplibre.umd.js.map +1 -1
- package/package.json +3 -2
package/dist/index.cjs
CHANGED
|
@@ -9033,7 +9033,9 @@ class Color {
|
|
|
9033
9033
|
this.values = [args[0], args[1], args[2], args[3]];
|
|
9034
9034
|
return;
|
|
9035
9035
|
}
|
|
9036
|
-
throw Error('Unsupported Color arguments: ' +
|
|
9036
|
+
throw Error('Unsupported Color arguments: ' +
|
|
9037
|
+
JSON.stringify(args) +
|
|
9038
|
+
'. Expected a MaplibreColor, hex string (#RRGGBB or #RRGGBBAA), or 3-4 numeric components.');
|
|
9037
9039
|
function h2d(text) {
|
|
9038
9040
|
return parseInt(text, 16);
|
|
9039
9041
|
}
|
|
@@ -9043,25 +9045,9 @@ class Color {
|
|
|
9043
9045
|
}
|
|
9044
9046
|
get hex() {
|
|
9045
9047
|
return `#${d2h(this.values[0])}${d2h(this.values[1])}${d2h(this.values[2])}${this.values[3] === 255 ? '' : d2h(this.values[3])}`;
|
|
9046
|
-
function d2h(num) {
|
|
9047
|
-
if (num < 0)
|
|
9048
|
-
num = 0;
|
|
9049
|
-
if (num > 255)
|
|
9050
|
-
num = 255;
|
|
9051
|
-
const str = Math.round(num).toString(16).toUpperCase();
|
|
9052
|
-
return str.length < 2 ? '0' + str : str;
|
|
9053
|
-
}
|
|
9054
9048
|
}
|
|
9055
9049
|
get rgb() {
|
|
9056
9050
|
return `#${d2h(this.values[0])}${d2h(this.values[1])}${d2h(this.values[2])}`;
|
|
9057
|
-
function d2h(num) {
|
|
9058
|
-
if (num < 0)
|
|
9059
|
-
num = 0;
|
|
9060
|
-
if (num > 255)
|
|
9061
|
-
num = 255;
|
|
9062
|
-
const str = Math.round(num).toString(16).toUpperCase();
|
|
9063
|
-
return str.length < 2 ? '0' + str : str;
|
|
9064
|
-
}
|
|
9065
9051
|
}
|
|
9066
9052
|
get opacity() {
|
|
9067
9053
|
return this.values[3] / 255;
|
|
@@ -9076,6 +9062,126 @@ class Color {
|
|
|
9076
9062
|
return new Color(...this.values);
|
|
9077
9063
|
}
|
|
9078
9064
|
}
|
|
9065
|
+
function d2h(num) {
|
|
9066
|
+
if (num < 0)
|
|
9067
|
+
num = 0;
|
|
9068
|
+
if (num > 255)
|
|
9069
|
+
num = 255;
|
|
9070
|
+
const str = Math.round(num).toString(16).toUpperCase();
|
|
9071
|
+
return str.length < 2 ? '0' + str : str;
|
|
9072
|
+
}
|
|
9073
|
+
|
|
9074
|
+
function chainSegments(segments) {
|
|
9075
|
+
// Phase 1: normalize segments left-to-right, then chain
|
|
9076
|
+
normalizeSegments(segments, 0);
|
|
9077
|
+
let chains = greedyChain(segments);
|
|
9078
|
+
// Phase 2: normalize remaining chains top-to-bottom, then chain again
|
|
9079
|
+
normalizeSegments(chains, 1);
|
|
9080
|
+
chains = greedyChain(chains);
|
|
9081
|
+
return chains;
|
|
9082
|
+
}
|
|
9083
|
+
function normalizeSegments(segments, coordIndex) {
|
|
9084
|
+
for (const seg of segments) {
|
|
9085
|
+
const first = seg[0];
|
|
9086
|
+
const last = seg[seg.length - 1];
|
|
9087
|
+
if (first && last && last[coordIndex] < first[coordIndex])
|
|
9088
|
+
seg.reverse();
|
|
9089
|
+
}
|
|
9090
|
+
}
|
|
9091
|
+
function greedyChain(segments) {
|
|
9092
|
+
const byStart = new Map();
|
|
9093
|
+
for (const seg of segments) {
|
|
9094
|
+
const start = seg[0];
|
|
9095
|
+
if (!start)
|
|
9096
|
+
continue;
|
|
9097
|
+
const key = String(start[0]) + ',' + String(start[1]);
|
|
9098
|
+
let list = byStart.get(key);
|
|
9099
|
+
if (!list) {
|
|
9100
|
+
list = [];
|
|
9101
|
+
byStart.set(key, list);
|
|
9102
|
+
}
|
|
9103
|
+
list.push(seg);
|
|
9104
|
+
}
|
|
9105
|
+
const visited = new Set();
|
|
9106
|
+
const chains = [];
|
|
9107
|
+
for (const seg of segments) {
|
|
9108
|
+
if (visited.has(seg))
|
|
9109
|
+
continue;
|
|
9110
|
+
visited.add(seg);
|
|
9111
|
+
const chain = [...seg];
|
|
9112
|
+
let endPoint = chain[chain.length - 1];
|
|
9113
|
+
let candidates = endPoint
|
|
9114
|
+
? byStart.get(String(endPoint[0]) + ',' + String(endPoint[1]))
|
|
9115
|
+
: undefined;
|
|
9116
|
+
while (candidates) {
|
|
9117
|
+
let next;
|
|
9118
|
+
for (const c of candidates) {
|
|
9119
|
+
if (!visited.has(c)) {
|
|
9120
|
+
next = c;
|
|
9121
|
+
break;
|
|
9122
|
+
}
|
|
9123
|
+
}
|
|
9124
|
+
if (!next)
|
|
9125
|
+
break;
|
|
9126
|
+
visited.add(next);
|
|
9127
|
+
for (let i = 1; i < next.length; i++)
|
|
9128
|
+
chain.push(next[i]);
|
|
9129
|
+
endPoint = chain[chain.length - 1];
|
|
9130
|
+
candidates = endPoint
|
|
9131
|
+
? byStart.get(String(endPoint[0]) + ',' + String(endPoint[1]))
|
|
9132
|
+
: undefined;
|
|
9133
|
+
}
|
|
9134
|
+
chains.push(chain);
|
|
9135
|
+
}
|
|
9136
|
+
return chains;
|
|
9137
|
+
}
|
|
9138
|
+
function segmentsToPath(chains, close = false) {
|
|
9139
|
+
let d = '';
|
|
9140
|
+
for (const chain of chains) {
|
|
9141
|
+
const first = chain[0];
|
|
9142
|
+
if (!first)
|
|
9143
|
+
continue;
|
|
9144
|
+
d += 'M' + formatNum(first[0]) + ',' + formatNum(first[1]);
|
|
9145
|
+
let px = first[0];
|
|
9146
|
+
let py = first[1];
|
|
9147
|
+
for (let i = 1; i < chain.length; i++) {
|
|
9148
|
+
const x = chain[i][0];
|
|
9149
|
+
const y = chain[i][1];
|
|
9150
|
+
const dx = x - px;
|
|
9151
|
+
const dy = y - py;
|
|
9152
|
+
if (dy === 0) {
|
|
9153
|
+
const rel = 'h' + formatNum(dx);
|
|
9154
|
+
const abs = 'H' + formatNum(x);
|
|
9155
|
+
d += rel.length <= abs.length ? rel : abs;
|
|
9156
|
+
}
|
|
9157
|
+
else if (dx === 0) {
|
|
9158
|
+
const rel = 'v' + formatNum(dy);
|
|
9159
|
+
const abs = 'V' + formatNum(y);
|
|
9160
|
+
d += rel.length <= abs.length ? rel : abs;
|
|
9161
|
+
}
|
|
9162
|
+
else {
|
|
9163
|
+
const rel = 'l' + formatNum(dx) + ',' + formatNum(dy);
|
|
9164
|
+
const abs = 'L' + formatNum(x) + ',' + formatNum(y);
|
|
9165
|
+
d += rel.length <= abs.length ? rel : abs;
|
|
9166
|
+
}
|
|
9167
|
+
px = x;
|
|
9168
|
+
py = y;
|
|
9169
|
+
}
|
|
9170
|
+
if (close)
|
|
9171
|
+
d += 'z';
|
|
9172
|
+
}
|
|
9173
|
+
return d;
|
|
9174
|
+
}
|
|
9175
|
+
function formatNum(tenths) {
|
|
9176
|
+
if (tenths % 10 === 0)
|
|
9177
|
+
return String(tenths / 10);
|
|
9178
|
+
const negative = tenths < 0;
|
|
9179
|
+
if (negative)
|
|
9180
|
+
tenths = -tenths;
|
|
9181
|
+
const whole = Math.floor(tenths / 10);
|
|
9182
|
+
const frac = tenths % 10;
|
|
9183
|
+
return (negative ? '-' : '') + String(whole) + '.' + String(frac);
|
|
9184
|
+
}
|
|
9079
9185
|
|
|
9080
9186
|
class SVGRenderer {
|
|
9081
9187
|
width;
|
|
@@ -9091,77 +9197,86 @@ class SVGRenderer {
|
|
|
9091
9197
|
this.#backgroundColor = Color.transparent;
|
|
9092
9198
|
}
|
|
9093
9199
|
drawBackgroundFill(style) {
|
|
9094
|
-
const color = style.color
|
|
9200
|
+
const color = new Color(style.color);
|
|
9095
9201
|
color.alpha *= style.opacity;
|
|
9096
9202
|
this.#backgroundColor = color;
|
|
9097
9203
|
}
|
|
9098
|
-
drawPolygons(features
|
|
9204
|
+
drawPolygons(features) {
|
|
9099
9205
|
if (features.length === 0)
|
|
9100
9206
|
return;
|
|
9101
|
-
if (opacity <= 0)
|
|
9102
|
-
return;
|
|
9103
|
-
this.#svg.push(`<g opacity="${String(opacity)}">`);
|
|
9104
9207
|
const groups = new Map();
|
|
9105
9208
|
features.forEach(([feature, style]) => {
|
|
9106
|
-
if (style.
|
|
9209
|
+
if (style.opacity <= 0)
|
|
9210
|
+
return;
|
|
9211
|
+
const color = new Color(style.color);
|
|
9212
|
+
if (color.alpha <= 0)
|
|
9107
9213
|
return;
|
|
9108
|
-
const translate = style.translate.
|
|
9214
|
+
const translate = style.translate[0] === 0 && style.translate[1] === 0
|
|
9109
9215
|
? ''
|
|
9110
9216
|
: ` transform="translate(${formatPoint(style.translate, this.#scale)})"`;
|
|
9111
|
-
const
|
|
9217
|
+
const opacityAttr = style.opacity < 1 ? ` opacity="${style.opacity.toFixed(3)}"` : '';
|
|
9218
|
+
const key = color.hex + translate + opacityAttr;
|
|
9112
9219
|
let group = groups.get(key);
|
|
9113
9220
|
if (!group) {
|
|
9114
|
-
group = { segments: [], attrs: `${fillAttr(
|
|
9221
|
+
group = { segments: [], attrs: `${fillAttr(color)}${translate}${opacityAttr}` };
|
|
9115
9222
|
groups.set(key, group);
|
|
9116
9223
|
}
|
|
9117
9224
|
feature.geometry.forEach((ring) => {
|
|
9118
|
-
group.segments.push(ring.map((p) => roundXY(p, this.#scale)));
|
|
9225
|
+
group.segments.push(ring.map((p) => roundXY(p.x, p.y, this.#scale)));
|
|
9119
9226
|
});
|
|
9120
9227
|
});
|
|
9121
9228
|
for (const { segments, attrs } of groups.values()) {
|
|
9122
9229
|
const d = segmentsToPath(segments, true);
|
|
9123
9230
|
this.#svg.push(`<path d="${d}" ${attrs} />`);
|
|
9124
9231
|
}
|
|
9125
|
-
this.#svg.push('</g>');
|
|
9126
9232
|
}
|
|
9127
|
-
drawLineStrings(features
|
|
9233
|
+
drawLineStrings(features) {
|
|
9128
9234
|
if (features.length === 0)
|
|
9129
9235
|
return;
|
|
9130
|
-
if (opacity <= 0)
|
|
9131
|
-
return;
|
|
9132
|
-
this.#svg.push(`<g opacity="${String(opacity)}">`);
|
|
9133
9236
|
const groups = new Map();
|
|
9134
9237
|
features.forEach(([feature, style]) => {
|
|
9135
|
-
if (style.
|
|
9238
|
+
if (style.opacity <= 0)
|
|
9239
|
+
return;
|
|
9240
|
+
const color = new Color(style.color);
|
|
9241
|
+
if (style.width <= 0 || color.alpha <= 0)
|
|
9136
9242
|
return;
|
|
9137
|
-
const translate = style.translate.
|
|
9243
|
+
const translate = style.translate[0] === 0 && style.translate[1] === 0
|
|
9138
9244
|
? ''
|
|
9139
9245
|
: ` transform="translate(${formatPoint(style.translate, this.#scale)})"`;
|
|
9140
|
-
const roundedWidth =
|
|
9246
|
+
const roundedWidth = formatScaled(style.width, this.#scale);
|
|
9247
|
+
const dasharrayStr = style.dasharray
|
|
9248
|
+
? style.dasharray.map((v) => formatScaled(v * style.width, this.#scale)).join(',')
|
|
9249
|
+
: '';
|
|
9250
|
+
const opacityAttr = style.opacity < 1 ? ` opacity="${style.opacity.toFixed(3)}"` : '';
|
|
9141
9251
|
const key = [
|
|
9142
|
-
|
|
9252
|
+
color.hex,
|
|
9143
9253
|
roundedWidth,
|
|
9144
9254
|
style.cap,
|
|
9145
9255
|
style.join,
|
|
9146
9256
|
String(style.miterLimit),
|
|
9257
|
+
dasharrayStr,
|
|
9258
|
+
opacityAttr,
|
|
9147
9259
|
translate,
|
|
9148
9260
|
].join('\0');
|
|
9149
9261
|
let group = groups.get(key);
|
|
9150
9262
|
if (!group) {
|
|
9263
|
+
const attrs = [
|
|
9264
|
+
'fill="none"',
|
|
9265
|
+
strokeAttr(color, roundedWidth),
|
|
9266
|
+
`stroke-linecap="${style.cap}"`,
|
|
9267
|
+
`stroke-linejoin="${style.join}"`,
|
|
9268
|
+
`stroke-miterlimit="${String(style.miterLimit)}"`,
|
|
9269
|
+
];
|
|
9270
|
+
if (dasharrayStr)
|
|
9271
|
+
attrs.push(`stroke-dasharray="${dasharrayStr}"`);
|
|
9151
9272
|
group = {
|
|
9152
9273
|
segments: [],
|
|
9153
|
-
attrs:
|
|
9154
|
-
'fill="none"',
|
|
9155
|
-
strokeAttr(style.color, roundedWidth),
|
|
9156
|
-
`stroke-linecap="${style.cap}"`,
|
|
9157
|
-
`stroke-linejoin="${style.join}"`,
|
|
9158
|
-
`stroke-miterlimit="${String(style.miterLimit)}"`,
|
|
9159
|
-
].join(' ') + translate,
|
|
9274
|
+
attrs: attrs.join(' ') + translate + opacityAttr,
|
|
9160
9275
|
};
|
|
9161
9276
|
groups.set(key, group);
|
|
9162
9277
|
}
|
|
9163
9278
|
feature.geometry.forEach((line) => {
|
|
9164
|
-
group.segments.push(line.map((p) => roundXY(p, this.#scale)));
|
|
9279
|
+
group.segments.push(line.map((p) => roundXY(p.x, p.y, this.#scale)));
|
|
9165
9280
|
});
|
|
9166
9281
|
});
|
|
9167
9282
|
for (const { segments, attrs } of groups.values()) {
|
|
@@ -9169,36 +9284,39 @@ class SVGRenderer {
|
|
|
9169
9284
|
const d = segmentsToPath(chains);
|
|
9170
9285
|
this.#svg.push(`<path d="${d}" ${attrs} />`);
|
|
9171
9286
|
}
|
|
9172
|
-
this.#svg.push('</g>');
|
|
9173
9287
|
}
|
|
9174
|
-
drawCircles(features
|
|
9288
|
+
drawCircles(features) {
|
|
9175
9289
|
if (features.length === 0)
|
|
9176
9290
|
return;
|
|
9177
|
-
if (opacity <= 0)
|
|
9178
|
-
return;
|
|
9179
|
-
this.#svg.push(`<g opacity="${String(opacity)}">`);
|
|
9180
9291
|
const groups = new Map();
|
|
9181
9292
|
features.forEach(([feature, style]) => {
|
|
9182
|
-
if (style.
|
|
9293
|
+
if (style.opacity <= 0)
|
|
9294
|
+
return;
|
|
9295
|
+
const color = new Color(style.color);
|
|
9296
|
+
if (style.radius <= 0 || color.alpha <= 0)
|
|
9183
9297
|
return;
|
|
9184
|
-
const translate = style.translate.
|
|
9298
|
+
const translate = style.translate[0] === 0 && style.translate[1] === 0
|
|
9185
9299
|
? ''
|
|
9186
9300
|
: ` transform="translate(${formatPoint(style.translate, this.#scale)})"`;
|
|
9187
|
-
const roundedRadius =
|
|
9301
|
+
const roundedRadius = formatScaled(style.radius, this.#scale);
|
|
9302
|
+
const strokeColor = new Color(style.strokeColor);
|
|
9188
9303
|
const strokeAttrs = style.strokeWidth > 0
|
|
9189
|
-
? ` ${strokeAttr(
|
|
9304
|
+
? ` ${strokeAttr(strokeColor, formatScaled(style.strokeWidth, this.#scale))}`
|
|
9190
9305
|
: '';
|
|
9191
|
-
const
|
|
9306
|
+
const opacityAttr = style.opacity < 1 ? ` opacity="${style.opacity.toFixed(3)}"` : '';
|
|
9307
|
+
const key = [color.hex, roundedRadius, strokeAttrs, opacityAttr, translate].join('\0');
|
|
9192
9308
|
let group = groups.get(key);
|
|
9193
9309
|
if (!group) {
|
|
9194
9310
|
group = {
|
|
9195
9311
|
points: [],
|
|
9196
|
-
attrs: `r="${roundedRadius}" ${fillAttr(
|
|
9312
|
+
attrs: `r="${roundedRadius}" ${fillAttr(color)}${strokeAttrs}${translate}${opacityAttr}`,
|
|
9197
9313
|
};
|
|
9198
9314
|
groups.set(key, group);
|
|
9199
9315
|
}
|
|
9200
9316
|
feature.geometry.forEach((ring) => {
|
|
9201
|
-
|
|
9317
|
+
const p = ring[0];
|
|
9318
|
+
if (p)
|
|
9319
|
+
group.points.push(roundXY(p.x, p.y, this.#scale));
|
|
9202
9320
|
});
|
|
9203
9321
|
});
|
|
9204
9322
|
for (const { points, attrs } of groups.values()) {
|
|
@@ -9206,7 +9324,6 @@ class SVGRenderer {
|
|
|
9206
9324
|
this.#svg.push(`<circle cx="${formatNum(x)}" cy="${formatNum(y)}" ${attrs} />`);
|
|
9207
9325
|
}
|
|
9208
9326
|
}
|
|
9209
|
-
this.#svg.push('</g>');
|
|
9210
9327
|
}
|
|
9211
9328
|
drawRasterTiles(tiles, style) {
|
|
9212
9329
|
if (tiles.length === 0)
|
|
@@ -9232,7 +9349,7 @@ class SVGRenderer {
|
|
|
9232
9349
|
for (const tile of tiles) {
|
|
9233
9350
|
const overlap = Math.min(tile.width, tile.height) / 10000; // slight overlap to prevent sub-pixel gaps between tiles
|
|
9234
9351
|
const s = this.#scale;
|
|
9235
|
-
let attrs = `x="${
|
|
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}"`;
|
|
9236
9353
|
if (pixelated)
|
|
9237
9354
|
attrs += ' style="image-rendering:pixelated"';
|
|
9238
9355
|
this.#svg.push(`<image ${attrs} />`);
|
|
@@ -9266,115 +9383,16 @@ function strokeAttr(color, width) {
|
|
|
9266
9383
|
attr += ` stroke-opacity="${color.opacity.toFixed(3)}"`;
|
|
9267
9384
|
return attr;
|
|
9268
9385
|
}
|
|
9269
|
-
function
|
|
9270
|
-
return (v * scale)
|
|
9386
|
+
function formatScaled(v, scale) {
|
|
9387
|
+
return formatNum(Math.round(v * scale * 10));
|
|
9271
9388
|
}
|
|
9272
|
-
function roundXY(
|
|
9273
|
-
return [Math.round(
|
|
9389
|
+
function roundXY(x, y, scale) {
|
|
9390
|
+
return [Math.round(x * scale * 10), Math.round(y * scale * 10)];
|
|
9274
9391
|
}
|
|
9275
9392
|
function formatPoint(p, scale) {
|
|
9276
|
-
const [x, y] = roundXY(p, scale);
|
|
9393
|
+
const [x, y] = roundXY(p[0], p[1], scale);
|
|
9277
9394
|
return formatNum(x) + ',' + formatNum(y);
|
|
9278
9395
|
}
|
|
9279
|
-
function chainSegments(segments) {
|
|
9280
|
-
// Phase 1: normalize segments left-to-right, then chain
|
|
9281
|
-
normalizeSegments(segments, 0);
|
|
9282
|
-
let chains = greedyChain(segments);
|
|
9283
|
-
// Phase 2: normalize remaining chains top-to-bottom, then chain again
|
|
9284
|
-
normalizeSegments(chains, 1);
|
|
9285
|
-
chains = greedyChain(chains);
|
|
9286
|
-
return chains;
|
|
9287
|
-
}
|
|
9288
|
-
function normalizeSegments(segments, coordIndex) {
|
|
9289
|
-
for (const seg of segments) {
|
|
9290
|
-
if (seg[seg.length - 1][coordIndex] < seg[0][coordIndex])
|
|
9291
|
-
seg.reverse();
|
|
9292
|
-
}
|
|
9293
|
-
}
|
|
9294
|
-
function greedyChain(segments) {
|
|
9295
|
-
const byStart = new Map();
|
|
9296
|
-
for (const seg of segments) {
|
|
9297
|
-
const key = String(seg[0][0]) + ',' + String(seg[0][1]);
|
|
9298
|
-
let list = byStart.get(key);
|
|
9299
|
-
if (!list) {
|
|
9300
|
-
list = [];
|
|
9301
|
-
byStart.set(key, list);
|
|
9302
|
-
}
|
|
9303
|
-
list.push(seg);
|
|
9304
|
-
}
|
|
9305
|
-
const visited = new Set();
|
|
9306
|
-
const chains = [];
|
|
9307
|
-
for (const seg of segments) {
|
|
9308
|
-
if (visited.has(seg))
|
|
9309
|
-
continue;
|
|
9310
|
-
visited.add(seg);
|
|
9311
|
-
const chain = [...seg];
|
|
9312
|
-
let endPoint = chain[chain.length - 1];
|
|
9313
|
-
let candidates = byStart.get(String(endPoint[0]) + ',' + String(endPoint[1]));
|
|
9314
|
-
while (candidates) {
|
|
9315
|
-
let next;
|
|
9316
|
-
for (const c of candidates) {
|
|
9317
|
-
if (!visited.has(c)) {
|
|
9318
|
-
next = c;
|
|
9319
|
-
break;
|
|
9320
|
-
}
|
|
9321
|
-
}
|
|
9322
|
-
if (!next)
|
|
9323
|
-
break;
|
|
9324
|
-
visited.add(next);
|
|
9325
|
-
for (let i = 1; i < next.length; i++)
|
|
9326
|
-
chain.push(next[i]);
|
|
9327
|
-
endPoint = chain[chain.length - 1];
|
|
9328
|
-
candidates = byStart.get(String(endPoint[0]) + ',' + String(endPoint[1]));
|
|
9329
|
-
}
|
|
9330
|
-
chains.push(chain);
|
|
9331
|
-
}
|
|
9332
|
-
return chains;
|
|
9333
|
-
}
|
|
9334
|
-
function segmentsToPath(chains, close = false) {
|
|
9335
|
-
let d = '';
|
|
9336
|
-
for (const chain of chains) {
|
|
9337
|
-
d += 'M' + formatNum(chain[0][0]) + ',' + formatNum(chain[0][1]);
|
|
9338
|
-
let px = chain[0][0];
|
|
9339
|
-
let py = chain[0][1];
|
|
9340
|
-
for (let i = 1; i < chain.length; i++) {
|
|
9341
|
-
const x = chain[i][0];
|
|
9342
|
-
const y = chain[i][1];
|
|
9343
|
-
const dx = x - px;
|
|
9344
|
-
const dy = y - py;
|
|
9345
|
-
if (dy === 0) {
|
|
9346
|
-
const rel = 'h' + formatNum(dx);
|
|
9347
|
-
const abs = 'H' + formatNum(x);
|
|
9348
|
-
d += rel.length <= abs.length ? rel : abs;
|
|
9349
|
-
}
|
|
9350
|
-
else if (dx === 0) {
|
|
9351
|
-
const rel = 'v' + formatNum(dy);
|
|
9352
|
-
const abs = 'V' + formatNum(y);
|
|
9353
|
-
d += rel.length <= abs.length ? rel : abs;
|
|
9354
|
-
}
|
|
9355
|
-
else {
|
|
9356
|
-
const rel = 'l' + formatNum(dx) + ',' + formatNum(dy);
|
|
9357
|
-
const abs = 'L' + formatNum(x) + ',' + formatNum(y);
|
|
9358
|
-
d += rel.length <= abs.length ? rel : abs;
|
|
9359
|
-
}
|
|
9360
|
-
px = x;
|
|
9361
|
-
py = y;
|
|
9362
|
-
}
|
|
9363
|
-
if (close)
|
|
9364
|
-
d += 'z';
|
|
9365
|
-
}
|
|
9366
|
-
return d;
|
|
9367
|
-
}
|
|
9368
|
-
function formatNum(tenths) {
|
|
9369
|
-
if (tenths % 10 === 0)
|
|
9370
|
-
return String(tenths / 10);
|
|
9371
|
-
const negative = tenths < 0;
|
|
9372
|
-
if (negative)
|
|
9373
|
-
tenths = -tenths;
|
|
9374
|
-
const whole = Math.floor(tenths / 10);
|
|
9375
|
-
const frac = tenths % 10;
|
|
9376
|
-
return (negative ? '-' : '') + String(whole) + '.' + String(frac);
|
|
9377
|
-
}
|
|
9378
9396
|
|
|
9379
9397
|
/*
|
|
9380
9398
|
* bignumber.js v9.3.1
|
|
@@ -14038,6 +14056,7 @@ class Feature {
|
|
|
14038
14056
|
properties;
|
|
14039
14057
|
patterns;
|
|
14040
14058
|
geometry;
|
|
14059
|
+
#bbox;
|
|
14041
14060
|
constructor(opt) {
|
|
14042
14061
|
this.type = opt.type;
|
|
14043
14062
|
this.id = opt.id;
|
|
@@ -14046,6 +14065,8 @@ class Feature {
|
|
|
14046
14065
|
this.geometry = opt.geometry;
|
|
14047
14066
|
}
|
|
14048
14067
|
getBbox() {
|
|
14068
|
+
if (this.#bbox)
|
|
14069
|
+
return this.#bbox;
|
|
14049
14070
|
let xMin = Infinity;
|
|
14050
14071
|
let yMin = Infinity;
|
|
14051
14072
|
let xMax = -Infinity;
|
|
@@ -14062,7 +14083,8 @@ class Feature {
|
|
|
14062
14083
|
yMax = point.y;
|
|
14063
14084
|
});
|
|
14064
14085
|
});
|
|
14065
|
-
|
|
14086
|
+
this.#bbox = [xMin, yMin, xMax, yMax];
|
|
14087
|
+
return this.#bbox;
|
|
14066
14088
|
}
|
|
14067
14089
|
doesOverlap(bbox) {
|
|
14068
14090
|
const featureBbox = this.getBbox();
|
|
@@ -14080,7 +14102,7 @@ class Feature {
|
|
|
14080
14102
|
|
|
14081
14103
|
function geojsonToFeature(id, polygonFeature) {
|
|
14082
14104
|
const geometry = polygonFeature.geometry.coordinates.map((ring) => {
|
|
14083
|
-
return ring.map((coord) => new Point2D(coord[0], coord[1]));
|
|
14105
|
+
return ring.map((coord) => new Point2D(coord[0] ?? 0, coord[1] ?? 0));
|
|
14084
14106
|
});
|
|
14085
14107
|
return new Feature({
|
|
14086
14108
|
type: 'Polygon',
|
|
@@ -14089,7 +14111,7 @@ function geojsonToFeature(id, polygonFeature) {
|
|
|
14089
14111
|
properties: polygonFeature.properties ?? {},
|
|
14090
14112
|
});
|
|
14091
14113
|
}
|
|
14092
|
-
function
|
|
14114
|
+
function mergePolygonsByFeatureId(featureList) {
|
|
14093
14115
|
const featuresById = new Map();
|
|
14094
14116
|
let nextId = -1;
|
|
14095
14117
|
for (const feature of featureList) {
|
|
@@ -14149,7 +14171,9 @@ function mergePolygons(featureList) {
|
|
|
14149
14171
|
|
|
14150
14172
|
function calculateTileGrid(width, height, center, zoom, maxzoom) {
|
|
14151
14173
|
const zoomLevel = Math.min(Math.floor(zoom), maxzoom ?? Infinity);
|
|
14152
|
-
const tileCenterCoordinate = center
|
|
14174
|
+
const tileCenterCoordinate = new Point2D(center[0], center[1])
|
|
14175
|
+
.getProject2Pixel()
|
|
14176
|
+
.scale(2 ** zoomLevel);
|
|
14153
14177
|
const tileSize = 2 ** (zoom - zoomLevel + 9); // 512 (2^9) is the standard tile size
|
|
14154
14178
|
const tileCols = width / tileSize;
|
|
14155
14179
|
const tileRows = height / tileSize;
|
|
@@ -14157,11 +14181,15 @@ function calculateTileGrid(width, height, center, zoom, maxzoom) {
|
|
|
14157
14181
|
const tileMinY = Math.floor(tileCenterCoordinate.y - tileRows / 2);
|
|
14158
14182
|
const tileMaxX = Math.floor(tileCenterCoordinate.x + tileCols / 2);
|
|
14159
14183
|
const tileMaxY = Math.floor(tileCenterCoordinate.y + tileRows / 2);
|
|
14184
|
+
const tilesPerZoom = 2 ** zoomLevel;
|
|
14160
14185
|
const tiles = [];
|
|
14161
14186
|
for (let x = tileMinX; x <= tileMaxX; x++) {
|
|
14187
|
+
const wrappedX = ((x % tilesPerZoom) + tilesPerZoom) % tilesPerZoom;
|
|
14162
14188
|
for (let y = tileMinY; y <= tileMaxY; y++) {
|
|
14189
|
+
if (y < 0 || y >= tilesPerZoom)
|
|
14190
|
+
continue;
|
|
14163
14191
|
tiles.push({
|
|
14164
|
-
x,
|
|
14192
|
+
x: wrappedX,
|
|
14165
14193
|
y,
|
|
14166
14194
|
offsetX: width / 2 + (x - tileCenterCoordinate.x) * tileSize,
|
|
14167
14195
|
offsetY: height / 2 + (y - tileCenterCoordinate.y) * tileSize,
|
|
@@ -14180,8 +14208,8 @@ async function getTile(url, z, x, y) {
|
|
|
14180
14208
|
const contentType = response.headers.get('content-type') ?? 'application/octet-stream';
|
|
14181
14209
|
return { buffer, contentType };
|
|
14182
14210
|
}
|
|
14183
|
-
catch {
|
|
14184
|
-
console.warn(`Failed to load tile: ${tileUrl}
|
|
14211
|
+
catch (error) {
|
|
14212
|
+
console.warn(`Failed to load tile: ${tileUrl}`, error);
|
|
14185
14213
|
return null;
|
|
14186
14214
|
}
|
|
14187
14215
|
}
|
|
@@ -15726,6 +15754,7 @@ function writeUtf8(buf, str, pos) {
|
|
|
15726
15754
|
}
|
|
15727
15755
|
|
|
15728
15756
|
const TILE_EXTENT = 4096;
|
|
15757
|
+
const VTFeatureType = { Unknown: 0, Point: 1, LineString: 2, Polygon: 3 };
|
|
15729
15758
|
async function loadVectorSource(source, job, layerFeatures) {
|
|
15730
15759
|
const tiles = source.tiles;
|
|
15731
15760
|
if (!tiles)
|
|
@@ -15753,17 +15782,17 @@ async function loadVectorSource(source, job, layerFeatures) {
|
|
|
15753
15782
|
let type;
|
|
15754
15783
|
let list;
|
|
15755
15784
|
switch (featureSrc.type) {
|
|
15756
|
-
case
|
|
15785
|
+
case VTFeatureType.Unknown:
|
|
15757
15786
|
throw Error('Unknown feature type in vector tile');
|
|
15758
|
-
case
|
|
15787
|
+
case VTFeatureType.Point:
|
|
15759
15788
|
type = 'Point';
|
|
15760
15789
|
list = features.points;
|
|
15761
15790
|
break;
|
|
15762
|
-
case
|
|
15791
|
+
case VTFeatureType.LineString:
|
|
15763
15792
|
type = 'LineString';
|
|
15764
15793
|
list = features.linestrings;
|
|
15765
15794
|
break;
|
|
15766
|
-
case
|
|
15795
|
+
case VTFeatureType.Polygon:
|
|
15767
15796
|
type = 'Polygon';
|
|
15768
15797
|
list = features.polygons;
|
|
15769
15798
|
break;
|
|
@@ -15781,13 +15810,14 @@ async function loadVectorSource(source, job, layerFeatures) {
|
|
|
15781
15810
|
}));
|
|
15782
15811
|
}
|
|
15783
15812
|
|
|
15784
|
-
function loadGeoJSONSource(
|
|
15813
|
+
function loadGeoJSONSource(options) {
|
|
15814
|
+
const { sourceName, data, width, height, zoom, center, layerFeatures } = options;
|
|
15785
15815
|
const existing = layerFeatures.get(sourceName);
|
|
15786
15816
|
const features = existing ?? { points: [], linestrings: [], polygons: [] };
|
|
15787
15817
|
if (!existing)
|
|
15788
15818
|
layerFeatures.set(sourceName, features);
|
|
15789
15819
|
const worldSize = 512 * 2 ** zoom;
|
|
15790
|
-
const centerMercator = center.getProject2Pixel();
|
|
15820
|
+
const centerMercator = new Point2D(center[0], center[1]).getProject2Pixel();
|
|
15791
15821
|
function projectCoord(coord) {
|
|
15792
15822
|
const mercator = new Point2D(coord[0], coord[1]).getProject2Pixel();
|
|
15793
15823
|
return new Point2D((mercator.x - centerMercator.x) * worldSize + width / 2, (mercator.y - centerMercator.y) * worldSize + height / 2);
|
|
@@ -15840,25 +15870,26 @@ function loadGeoJSONSource(sourceName, data, width, height, zoom, center, layerF
|
|
|
15840
15870
|
}
|
|
15841
15871
|
}
|
|
15842
15872
|
function processGeometry(geom, id, properties) {
|
|
15843
|
-
const coords = geom.coordinates;
|
|
15844
15873
|
switch (geom.type) {
|
|
15845
15874
|
case 'Point':
|
|
15846
|
-
addFeature('Point', [[projectCoord(
|
|
15875
|
+
addFeature('Point', [[projectCoord(geom.coordinates)]], id, properties);
|
|
15847
15876
|
break;
|
|
15848
15877
|
case 'MultiPoint':
|
|
15849
|
-
addFeature('Point',
|
|
15878
|
+
addFeature('Point', geom.coordinates.map((c) => [projectCoord(c)]), id, properties);
|
|
15850
15879
|
break;
|
|
15851
15880
|
case 'LineString':
|
|
15852
|
-
addFeature('LineString', [
|
|
15881
|
+
addFeature('LineString', [geom.coordinates.map((c) => projectCoord(c))], id, properties);
|
|
15853
15882
|
break;
|
|
15854
15883
|
case 'MultiLineString':
|
|
15855
|
-
addFeature('LineString',
|
|
15884
|
+
addFeature('LineString', geom.coordinates.map((line) => line.map((c) => projectCoord(c))), id, properties);
|
|
15856
15885
|
break;
|
|
15857
15886
|
case 'Polygon':
|
|
15858
|
-
addFeature('Polygon',
|
|
15887
|
+
addFeature('Polygon', geom.coordinates.map((ring) => ring.map((c) => projectCoord(c))), id, properties);
|
|
15859
15888
|
break;
|
|
15860
15889
|
case 'MultiPolygon':
|
|
15861
|
-
|
|
15890
|
+
for (const polygon of geom.coordinates) {
|
|
15891
|
+
addFeature('Polygon', polygon.map((ring) => ring.map((c) => projectCoord(c))), id, properties);
|
|
15892
|
+
}
|
|
15862
15893
|
break;
|
|
15863
15894
|
case 'GeometryCollection':
|
|
15864
15895
|
for (const g of geom.geometries) {
|
|
@@ -15867,18 +15898,17 @@ function loadGeoJSONSource(sourceName, data, width, height, zoom, center, layerF
|
|
|
15867
15898
|
break;
|
|
15868
15899
|
}
|
|
15869
15900
|
}
|
|
15870
|
-
|
|
15871
|
-
switch (geojson.type) {
|
|
15901
|
+
switch (data.type) {
|
|
15872
15902
|
case 'FeatureCollection':
|
|
15873
|
-
for (const f of
|
|
15903
|
+
for (const f of data.features) {
|
|
15874
15904
|
processGeometry(f.geometry, f.id, (f.properties ?? {}));
|
|
15875
15905
|
}
|
|
15876
15906
|
break;
|
|
15877
15907
|
case 'Feature':
|
|
15878
|
-
processGeometry(
|
|
15908
|
+
processGeometry(data.geometry, data.id, (data.properties ?? {}));
|
|
15879
15909
|
break;
|
|
15880
15910
|
default:
|
|
15881
|
-
processGeometry(
|
|
15911
|
+
processGeometry(data, undefined, {});
|
|
15882
15912
|
break;
|
|
15883
15913
|
}
|
|
15884
15914
|
}
|
|
@@ -15888,7 +15918,7 @@ async function getRasterTiles(job, sourceName) {
|
|
|
15888
15918
|
const { zoom, center } = job.view;
|
|
15889
15919
|
const source = job.style.sources[sourceName];
|
|
15890
15920
|
if (source?.type !== 'raster' || !source.tiles) {
|
|
15891
|
-
throw Error(
|
|
15921
|
+
throw Error(`Invalid raster source "${sourceName}": expected type "raster" with a "tiles" array`);
|
|
15892
15922
|
}
|
|
15893
15923
|
const sourceUrl = source.tiles[0];
|
|
15894
15924
|
const { zoomLevel, tileSize, tiles } = calculateTileGrid(width, height, center, zoom, source.maxzoom);
|
|
@@ -15916,24 +15946,34 @@ async function getLayerFeatures(job) {
|
|
|
15916
15946
|
const { zoom, center } = job.view;
|
|
15917
15947
|
const { sources } = job.style;
|
|
15918
15948
|
const layerFeatures = new Map();
|
|
15949
|
+
const loadPromises = [];
|
|
15919
15950
|
for (const [sourceName, sourceSpec] of Object.entries(sources)) {
|
|
15920
15951
|
const source = sourceSpec;
|
|
15921
15952
|
switch (source.type) {
|
|
15922
15953
|
case 'vector':
|
|
15923
|
-
|
|
15954
|
+
loadPromises.push(loadVectorSource(source, job, layerFeatures));
|
|
15924
15955
|
break;
|
|
15925
15956
|
case 'geojson':
|
|
15926
15957
|
if (source.data) {
|
|
15927
|
-
loadGeoJSONSource(
|
|
15958
|
+
loadGeoJSONSource({
|
|
15959
|
+
sourceName,
|
|
15960
|
+
data: source.data,
|
|
15961
|
+
width,
|
|
15962
|
+
height,
|
|
15963
|
+
zoom,
|
|
15964
|
+
center,
|
|
15965
|
+
layerFeatures,
|
|
15966
|
+
});
|
|
15928
15967
|
}
|
|
15929
15968
|
break;
|
|
15930
15969
|
}
|
|
15931
15970
|
}
|
|
15971
|
+
await Promise.all(loadPromises);
|
|
15932
15972
|
for (const [name, features] of layerFeatures) {
|
|
15933
15973
|
layerFeatures.set(name, {
|
|
15934
15974
|
points: features.points,
|
|
15935
15975
|
linestrings: features.linestrings,
|
|
15936
|
-
polygons:
|
|
15976
|
+
polygons: mergePolygonsByFeatureId(features.polygons),
|
|
15937
15977
|
});
|
|
15938
15978
|
}
|
|
15939
15979
|
return layerFeatures;
|
|
@@ -15981,6 +16021,7 @@ class StyleLayer {
|
|
|
15981
16021
|
minzoom;
|
|
15982
16022
|
maxzoom;
|
|
15983
16023
|
filter;
|
|
16024
|
+
filterFn;
|
|
15984
16025
|
paint;
|
|
15985
16026
|
layout;
|
|
15986
16027
|
paintExpressions;
|
|
@@ -15999,6 +16040,7 @@ class StyleLayer {
|
|
|
15999
16040
|
this.source = spec.source;
|
|
16000
16041
|
this.sourceLayer = spec['source-layer'];
|
|
16001
16042
|
this.filter = spec.filter;
|
|
16043
|
+
this.filterFn = featureFilter(this.filter);
|
|
16002
16044
|
}
|
|
16003
16045
|
this.visibility = (spec.layout?.visibility ?? 'visible');
|
|
16004
16046
|
// Initialize paint property expressions
|
|
@@ -16036,7 +16078,7 @@ class StyleLayer {
|
|
|
16036
16078
|
this.layout = new EvaluatedProperties();
|
|
16037
16079
|
for (const [name, expr] of this.paintExpressions) {
|
|
16038
16080
|
if (expr.kind === 'constant' || expr.kind === 'camera') {
|
|
16039
|
-
this.paint.set(name, expr.evaluate(params,
|
|
16081
|
+
this.paint.set(name, expr.evaluate(params, undefined, {}, undefined, availableImages));
|
|
16040
16082
|
}
|
|
16041
16083
|
else {
|
|
16042
16084
|
this.paint.set(name, new PossiblyEvaluatedPropertyValue(expr, params));
|
|
@@ -16044,7 +16086,7 @@ class StyleLayer {
|
|
|
16044
16086
|
}
|
|
16045
16087
|
for (const [name, expr] of this.layoutExpressions) {
|
|
16046
16088
|
if (expr.kind === 'constant' || expr.kind === 'camera') {
|
|
16047
|
-
this.layout.set(name, expr.evaluate(params,
|
|
16089
|
+
this.layout.set(name, expr.evaluate(params, undefined, {}, undefined, availableImages));
|
|
16048
16090
|
}
|
|
16049
16091
|
else {
|
|
16050
16092
|
this.layout.set(name, new PossiblyEvaluatedPropertyValue(expr, params));
|
|
@@ -16055,18 +16097,17 @@ class StyleLayer {
|
|
|
16055
16097
|
function createStyleLayer(spec) {
|
|
16056
16098
|
return new StyleLayer(spec);
|
|
16057
16099
|
}
|
|
16058
|
-
|
|
16059
16100
|
function getLayerStyles(layers) {
|
|
16060
|
-
return layers.map(
|
|
16061
|
-
const styleLayer = createStyleLayer(layerSpecification);
|
|
16062
|
-
return styleLayer;
|
|
16063
|
-
});
|
|
16101
|
+
return layers.map(createStyleLayer);
|
|
16064
16102
|
}
|
|
16065
16103
|
|
|
16066
|
-
async function
|
|
16104
|
+
async function renderMap(job) {
|
|
16067
16105
|
await render(job);
|
|
16068
16106
|
return job.renderer.getString();
|
|
16069
16107
|
}
|
|
16108
|
+
function getFeatures(layerFeatures, layerStyle) {
|
|
16109
|
+
return layerFeatures.get(layerStyle.sourceLayer) ?? layerFeatures.get(layerStyle.source);
|
|
16110
|
+
}
|
|
16070
16111
|
async function render(job) {
|
|
16071
16112
|
const { renderer } = job;
|
|
16072
16113
|
const { zoom } = job.view;
|
|
@@ -16097,54 +16138,55 @@ async function render(job) {
|
|
|
16097
16138
|
case 'background':
|
|
16098
16139
|
{
|
|
16099
16140
|
renderer.drawBackgroundFill({
|
|
16100
|
-
color:
|
|
16141
|
+
color: getPaint('background-color'),
|
|
16101
16142
|
opacity: getPaint('background-opacity'),
|
|
16102
16143
|
});
|
|
16103
16144
|
}
|
|
16104
16145
|
continue;
|
|
16105
16146
|
case 'fill':
|
|
16106
16147
|
{
|
|
16107
|
-
const polygons = (layerFeatures
|
|
16148
|
+
const polygons = getFeatures(layerFeatures, layerStyle)?.polygons;
|
|
16108
16149
|
if (!polygons || polygons.length === 0)
|
|
16109
16150
|
continue;
|
|
16110
|
-
const
|
|
16111
|
-
|
|
16151
|
+
const polygonFeatures = layerStyle.filterFn
|
|
16152
|
+
? polygons.filter((feature) => layerStyle.filterFn.filter({ zoom }, feature))
|
|
16153
|
+
: polygons;
|
|
16112
16154
|
if (polygonFeatures.length === 0)
|
|
16113
16155
|
continue;
|
|
16114
16156
|
renderer.drawPolygons(polygonFeatures.map((feature) => [
|
|
16115
16157
|
feature,
|
|
16116
16158
|
{
|
|
16117
|
-
color:
|
|
16118
|
-
|
|
16159
|
+
color: getPaint('fill-color', feature),
|
|
16160
|
+
opacity: getPaint('fill-opacity', feature),
|
|
16161
|
+
translate: getPaint('fill-translate', feature),
|
|
16119
16162
|
},
|
|
16120
|
-
])
|
|
16163
|
+
]));
|
|
16121
16164
|
}
|
|
16122
16165
|
continue;
|
|
16123
16166
|
case 'line':
|
|
16124
16167
|
{
|
|
16125
|
-
const lineStrings = (layerFeatures
|
|
16168
|
+
const lineStrings = getFeatures(layerFeatures, layerStyle)?.linestrings;
|
|
16126
16169
|
if (!lineStrings || lineStrings.length === 0)
|
|
16127
16170
|
continue;
|
|
16128
|
-
const
|
|
16129
|
-
|
|
16171
|
+
const lineStringFeatures = layerStyle.filterFn
|
|
16172
|
+
? lineStrings.filter((feature) => layerStyle.filterFn.filter({ zoom }, feature))
|
|
16173
|
+
: lineStrings;
|
|
16130
16174
|
if (lineStringFeatures.length === 0)
|
|
16131
16175
|
continue;
|
|
16132
16176
|
renderer.drawLineStrings(lineStringFeatures.map((feature) => [
|
|
16133
16177
|
feature,
|
|
16134
16178
|
{
|
|
16135
|
-
color:
|
|
16136
|
-
translate:
|
|
16137
|
-
blur: getPaint('line-blur', feature),
|
|
16179
|
+
color: getPaint('line-color', feature),
|
|
16180
|
+
translate: getPaint('line-translate', feature),
|
|
16138
16181
|
cap: getLayout('line-cap', feature),
|
|
16139
16182
|
dasharray: getPaint('line-dasharray', feature),
|
|
16140
|
-
gapWidth: getPaint('line-gap-width', feature),
|
|
16141
16183
|
join: getLayout('line-join', feature),
|
|
16142
16184
|
miterLimit: getLayout('line-miter-limit', feature),
|
|
16143
16185
|
offset: getPaint('line-offset', feature),
|
|
16144
|
-
|
|
16186
|
+
opacity: getPaint('line-opacity', feature),
|
|
16145
16187
|
width: getPaint('line-width', feature),
|
|
16146
16188
|
},
|
|
16147
|
-
])
|
|
16189
|
+
]));
|
|
16148
16190
|
}
|
|
16149
16191
|
continue;
|
|
16150
16192
|
case 'raster':
|
|
@@ -16163,24 +16205,25 @@ async function render(job) {
|
|
|
16163
16205
|
continue;
|
|
16164
16206
|
case 'circle':
|
|
16165
16207
|
{
|
|
16166
|
-
const points = (layerFeatures
|
|
16208
|
+
const points = getFeatures(layerFeatures, layerStyle)?.points;
|
|
16167
16209
|
if (!points || points.length === 0)
|
|
16168
16210
|
continue;
|
|
16169
|
-
const
|
|
16170
|
-
|
|
16211
|
+
const pointFeatures = layerStyle.filterFn
|
|
16212
|
+
? points.filter((feature) => layerStyle.filterFn.filter({ zoom }, feature))
|
|
16213
|
+
: points;
|
|
16171
16214
|
if (pointFeatures.length === 0)
|
|
16172
16215
|
continue;
|
|
16173
16216
|
renderer.drawCircles(pointFeatures.map((feature) => [
|
|
16174
16217
|
feature,
|
|
16175
16218
|
{
|
|
16176
|
-
color:
|
|
16219
|
+
color: getPaint('circle-color', feature),
|
|
16220
|
+
opacity: getPaint('circle-opacity', feature),
|
|
16177
16221
|
radius: getPaint('circle-radius', feature),
|
|
16178
|
-
|
|
16179
|
-
translate: new Point2D(...getPaint('circle-translate', feature)),
|
|
16222
|
+
translate: getPaint('circle-translate', feature),
|
|
16180
16223
|
strokeWidth: getPaint('circle-stroke-width', feature),
|
|
16181
|
-
strokeColor:
|
|
16224
|
+
strokeColor: getPaint('circle-stroke-color', feature),
|
|
16182
16225
|
},
|
|
16183
|
-
])
|
|
16226
|
+
]));
|
|
16184
16227
|
}
|
|
16185
16228
|
continue;
|
|
16186
16229
|
case 'color-relief':
|
|
@@ -16196,15 +16239,20 @@ async function render(job) {
|
|
|
16196
16239
|
}
|
|
16197
16240
|
|
|
16198
16241
|
async function renderToSVG(options) {
|
|
16199
|
-
|
|
16200
|
-
|
|
16201
|
-
|
|
16202
|
-
|
|
16203
|
-
|
|
16204
|
-
|
|
16242
|
+
const width = options.width ?? 1024;
|
|
16243
|
+
const height = options.height ?? 1024;
|
|
16244
|
+
const scale = options.scale ?? 1;
|
|
16245
|
+
if (width <= 0)
|
|
16246
|
+
throw new Error('width must be positive');
|
|
16247
|
+
if (height <= 0)
|
|
16248
|
+
throw new Error('height must be positive');
|
|
16249
|
+
if (scale <= 0)
|
|
16250
|
+
throw new Error('scale must be positive');
|
|
16251
|
+
return await renderMap({
|
|
16252
|
+
renderer: new SVGRenderer({ width, height, scale }),
|
|
16205
16253
|
style: options.style,
|
|
16206
16254
|
view: {
|
|
16207
|
-
center:
|
|
16255
|
+
center: [options.lon ?? 0, options.lat ?? 0],
|
|
16208
16256
|
zoom: options.zoom ?? 2,
|
|
16209
16257
|
},
|
|
16210
16258
|
});
|