@versatiles/svg-renderer 0.3.0 → 0.4.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 -29
- package/dist/index.cjs +312 -110
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +312 -110
- package/dist/index.js.map +1 -1
- package/dist/maplibre.cjs +312 -110
- package/dist/maplibre.cjs.map +1 -1
- package/dist/maplibre.js +312 -110
- package/dist/maplibre.js.map +1 -1
- package/dist/maplibre.umd.js +312 -110
- package/dist/maplibre.umd.js.map +1 -1
- package/package.json +15 -13
package/README.md
CHANGED
|
@@ -136,47 +136,55 @@ subgraph 0["src"]
|
|
|
136
136
|
subgraph 3["lib"]
|
|
137
137
|
4["geometry.ts"]
|
|
138
138
|
7["color.ts"]
|
|
139
|
-
|
|
139
|
+
G["style_layer.ts"]
|
|
140
140
|
end
|
|
141
141
|
subgraph 5["processor"]
|
|
142
142
|
6["render.ts"]
|
|
143
|
-
8["
|
|
144
|
-
9["
|
|
145
|
-
|
|
146
|
-
C["
|
|
147
|
-
D["
|
|
143
|
+
subgraph 8["sources"]
|
|
144
|
+
9["index.ts"]
|
|
145
|
+
B["geojson.ts"]
|
|
146
|
+
C["raster.ts"]
|
|
147
|
+
D["tiles.ts"]
|
|
148
|
+
E["vector.ts"]
|
|
149
|
+
N["types.ts"]
|
|
148
150
|
end
|
|
149
|
-
|
|
150
|
-
F["
|
|
151
|
+
A["helper.ts"]
|
|
152
|
+
F["styles.ts"]
|
|
151
153
|
end
|
|
152
|
-
subgraph
|
|
153
|
-
|
|
154
|
-
I["styles.ts"]
|
|
155
|
-
J["index.ts"]
|
|
154
|
+
subgraph H["renderer"]
|
|
155
|
+
I["renderer_svg.ts"]
|
|
156
156
|
end
|
|
157
|
-
|
|
157
|
+
subgraph J["maplibre"]
|
|
158
|
+
K["control.ts"]
|
|
159
|
+
L["styles.ts"]
|
|
160
|
+
M["index.ts"]
|
|
161
|
+
end
|
|
162
|
+
O["types.ts"]
|
|
158
163
|
end
|
|
159
164
|
1-->2
|
|
160
165
|
2-->4
|
|
161
166
|
2-->6
|
|
162
|
-
2-->
|
|
167
|
+
2-->I
|
|
163
168
|
6-->7
|
|
164
169
|
6-->4
|
|
165
|
-
6-->
|
|
166
|
-
6-->
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
170
|
+
6-->9
|
|
171
|
+
6-->F
|
|
172
|
+
9-->A
|
|
173
|
+
9-->B
|
|
174
|
+
9-->C
|
|
175
|
+
9-->E
|
|
176
|
+
A-->4
|
|
177
|
+
B-->4
|
|
171
178
|
C-->D
|
|
172
|
-
|
|
173
|
-
D
|
|
174
|
-
F-->
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
179
|
+
E-->4
|
|
180
|
+
E-->D
|
|
181
|
+
F-->G
|
|
182
|
+
I-->7
|
|
183
|
+
K-->2
|
|
184
|
+
K-->L
|
|
185
|
+
M-->2
|
|
186
|
+
M-->K
|
|
187
|
+
|
|
188
|
+
class 0,3,5,8,H,J subgraphs;
|
|
181
189
|
classDef subgraphs fill-opacity:0.1, fill:#888, color:#888, stroke:#888;
|
|
182
190
|
```
|
package/dist/index.cjs
CHANGED
|
@@ -9052,6 +9052,20 @@ class Color {
|
|
|
9052
9052
|
return str.length < 2 ? '0' + str : str;
|
|
9053
9053
|
}
|
|
9054
9054
|
}
|
|
9055
|
+
get rgb() {
|
|
9056
|
+
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
|
+
}
|
|
9066
|
+
get opacity() {
|
|
9067
|
+
return this.values[3] / 255;
|
|
9068
|
+
}
|
|
9055
9069
|
get alpha() {
|
|
9056
9070
|
return this.values[3];
|
|
9057
9071
|
}
|
|
@@ -9097,7 +9111,7 @@ class SVGRenderer {
|
|
|
9097
9111
|
const key = style.color.hex + translate;
|
|
9098
9112
|
let group = groups.get(key);
|
|
9099
9113
|
if (!group) {
|
|
9100
|
-
group = { segments: [], attrs:
|
|
9114
|
+
group = { segments: [], attrs: `${fillAttr(style.color)}${translate}` };
|
|
9101
9115
|
groups.set(key, group);
|
|
9102
9116
|
}
|
|
9103
9117
|
feature.geometry.forEach((ring) => {
|
|
@@ -9138,8 +9152,7 @@ class SVGRenderer {
|
|
|
9138
9152
|
segments: [],
|
|
9139
9153
|
attrs: [
|
|
9140
9154
|
'fill="none"',
|
|
9141
|
-
|
|
9142
|
-
`stroke-width="${roundedWidth}"`,
|
|
9155
|
+
strokeAttr(style.color, roundedWidth),
|
|
9143
9156
|
`stroke-linecap="${style.cap}"`,
|
|
9144
9157
|
`stroke-linejoin="${style.join}"`,
|
|
9145
9158
|
`stroke-miterlimit="${String(style.miterLimit)}"`,
|
|
@@ -9158,6 +9171,43 @@ class SVGRenderer {
|
|
|
9158
9171
|
}
|
|
9159
9172
|
this.#svg.push('</g>');
|
|
9160
9173
|
}
|
|
9174
|
+
drawCircles(features, opacity) {
|
|
9175
|
+
if (features.length === 0)
|
|
9176
|
+
return;
|
|
9177
|
+
if (opacity <= 0)
|
|
9178
|
+
return;
|
|
9179
|
+
this.#svg.push(`<g opacity="${String(opacity)}">`);
|
|
9180
|
+
const groups = new Map();
|
|
9181
|
+
features.forEach(([feature, style]) => {
|
|
9182
|
+
if (style.radius <= 0 || style.color.alpha <= 0)
|
|
9183
|
+
return;
|
|
9184
|
+
const translate = style.translate.isZero()
|
|
9185
|
+
? ''
|
|
9186
|
+
: ` transform="translate(${formatPoint(style.translate, this.#scale)})"`;
|
|
9187
|
+
const roundedRadius = roundValue(style.radius, this.#scale);
|
|
9188
|
+
const strokeAttrs = style.strokeWidth > 0
|
|
9189
|
+
? ` ${strokeAttr(style.strokeColor, roundValue(style.strokeWidth, this.#scale))}`
|
|
9190
|
+
: '';
|
|
9191
|
+
const key = [style.color.hex, roundedRadius, strokeAttrs, translate].join('\0');
|
|
9192
|
+
let group = groups.get(key);
|
|
9193
|
+
if (!group) {
|
|
9194
|
+
group = {
|
|
9195
|
+
points: [],
|
|
9196
|
+
attrs: `r="${roundedRadius}" ${fillAttr(style.color)}${strokeAttrs}${translate}`,
|
|
9197
|
+
};
|
|
9198
|
+
groups.set(key, group);
|
|
9199
|
+
}
|
|
9200
|
+
feature.geometry.forEach((ring) => {
|
|
9201
|
+
group.points.push(roundXY(ring[0], this.#scale));
|
|
9202
|
+
});
|
|
9203
|
+
});
|
|
9204
|
+
for (const { points, attrs } of groups.values()) {
|
|
9205
|
+
for (const [x, y] of points) {
|
|
9206
|
+
this.#svg.push(`<circle cx="${formatNum(x)}" cy="${formatNum(y)}" ${attrs} />`);
|
|
9207
|
+
}
|
|
9208
|
+
}
|
|
9209
|
+
this.#svg.push('</g>');
|
|
9210
|
+
}
|
|
9161
9211
|
drawRasterTiles(tiles, style) {
|
|
9162
9212
|
if (tiles.length === 0)
|
|
9163
9213
|
return;
|
|
@@ -9190,13 +9240,32 @@ class SVGRenderer {
|
|
|
9190
9240
|
this.#svg.push('</g>');
|
|
9191
9241
|
}
|
|
9192
9242
|
getString() {
|
|
9193
|
-
|
|
9194
|
-
|
|
9195
|
-
|
|
9196
|
-
|
|
9197
|
-
|
|
9243
|
+
const w = this.width.toFixed(0);
|
|
9244
|
+
const h = this.height.toFixed(0);
|
|
9245
|
+
const parts = [
|
|
9246
|
+
`<svg viewBox="0 0 ${w} ${h}" width="${w}" height="${h}" xmlns="http://www.w3.org/2000/svg">`,
|
|
9247
|
+
`<defs><clipPath id="vb"><rect width="${w}" height="${h}"/></clipPath></defs>`,
|
|
9248
|
+
`<g clip-path="url(#vb)">`,
|
|
9249
|
+
];
|
|
9250
|
+
if (this.#backgroundColor.alpha > 0) {
|
|
9251
|
+
parts.push(`<rect x="-1" y="-1" width="${(this.width + 2).toFixed(0)}" height="${(this.height + 2).toFixed(0)}" ${fillAttr(this.#backgroundColor)} />`);
|
|
9252
|
+
}
|
|
9253
|
+
parts.push(...this.#svg, '</g>', '</svg>');
|
|
9254
|
+
return parts.join('\n');
|
|
9198
9255
|
}
|
|
9199
9256
|
}
|
|
9257
|
+
function fillAttr(color) {
|
|
9258
|
+
let attr = `fill="${color.rgb}"`;
|
|
9259
|
+
if (color.alpha < 255)
|
|
9260
|
+
attr += ` fill-opacity="${color.opacity.toFixed(3)}"`;
|
|
9261
|
+
return attr;
|
|
9262
|
+
}
|
|
9263
|
+
function strokeAttr(color, width) {
|
|
9264
|
+
let attr = `stroke="${color.rgb}" stroke-width="${width}"`;
|
|
9265
|
+
if (color.alpha < 255)
|
|
9266
|
+
attr += ` stroke-opacity="${color.opacity.toFixed(3)}"`;
|
|
9267
|
+
return attr;
|
|
9268
|
+
}
|
|
9200
9269
|
function roundValue(v, scale) {
|
|
9201
9270
|
return (v * scale).toFixed(3);
|
|
9202
9271
|
}
|
|
@@ -9307,77 +9376,6 @@ function formatNum(tenths) {
|
|
|
9307
9376
|
return (negative ? '-' : '') + String(whole) + '.' + String(frac);
|
|
9308
9377
|
}
|
|
9309
9378
|
|
|
9310
|
-
class Point2D {
|
|
9311
|
-
x;
|
|
9312
|
-
y;
|
|
9313
|
-
constructor(x, y) {
|
|
9314
|
-
this.x = x;
|
|
9315
|
-
this.y = y;
|
|
9316
|
-
}
|
|
9317
|
-
isZero() {
|
|
9318
|
-
return this.x === 0 && this.y === 0;
|
|
9319
|
-
}
|
|
9320
|
-
scale(factor) {
|
|
9321
|
-
this.x *= factor;
|
|
9322
|
-
this.y *= factor;
|
|
9323
|
-
return this;
|
|
9324
|
-
}
|
|
9325
|
-
translate(offset) {
|
|
9326
|
-
this.x += offset.x;
|
|
9327
|
-
this.y += offset.y;
|
|
9328
|
-
return this;
|
|
9329
|
-
}
|
|
9330
|
-
getProject2Pixel() {
|
|
9331
|
-
const s = Math.sin((this.y * Math.PI) / 180.0);
|
|
9332
|
-
return new Point2D(this.x / 360.0 + 0.5, 0.5 - (0.25 * Math.log((1 + s) / (1 - s))) / Math.PI);
|
|
9333
|
-
}
|
|
9334
|
-
}
|
|
9335
|
-
class Feature {
|
|
9336
|
-
type;
|
|
9337
|
-
id;
|
|
9338
|
-
properties;
|
|
9339
|
-
patterns;
|
|
9340
|
-
geometry;
|
|
9341
|
-
constructor(opt) {
|
|
9342
|
-
this.type = opt.type;
|
|
9343
|
-
this.id = opt.id;
|
|
9344
|
-
this.properties = opt.properties;
|
|
9345
|
-
this.patterns = opt.patterns;
|
|
9346
|
-
this.geometry = opt.geometry;
|
|
9347
|
-
}
|
|
9348
|
-
getBbox() {
|
|
9349
|
-
let xMin = Infinity;
|
|
9350
|
-
let yMin = Infinity;
|
|
9351
|
-
let xMax = -Infinity;
|
|
9352
|
-
let yMax = -Infinity;
|
|
9353
|
-
this.geometry.forEach((ring) => {
|
|
9354
|
-
ring.forEach((point) => {
|
|
9355
|
-
if (xMin > point.x)
|
|
9356
|
-
xMin = point.x;
|
|
9357
|
-
if (yMin > point.y)
|
|
9358
|
-
yMin = point.y;
|
|
9359
|
-
if (xMax < point.x)
|
|
9360
|
-
xMax = point.x;
|
|
9361
|
-
if (yMax < point.y)
|
|
9362
|
-
yMax = point.y;
|
|
9363
|
-
});
|
|
9364
|
-
});
|
|
9365
|
-
return [xMin, yMin, xMax, yMax];
|
|
9366
|
-
}
|
|
9367
|
-
doesOverlap(bbox) {
|
|
9368
|
-
const featureBbox = this.getBbox();
|
|
9369
|
-
if (featureBbox[0] > bbox[2])
|
|
9370
|
-
return false;
|
|
9371
|
-
if (featureBbox[1] > bbox[3])
|
|
9372
|
-
return false;
|
|
9373
|
-
if (featureBbox[2] < bbox[0])
|
|
9374
|
-
return false;
|
|
9375
|
-
if (featureBbox[3] < bbox[1])
|
|
9376
|
-
return false;
|
|
9377
|
-
return true;
|
|
9378
|
-
}
|
|
9379
|
-
}
|
|
9380
|
-
|
|
9381
9379
|
/*
|
|
9382
9380
|
* bignumber.js v9.3.1
|
|
9383
9381
|
* A JavaScript library for arbitrary-precision arithmetic.
|
|
@@ -14009,6 +14007,77 @@ function union2(features, options = {}) {
|
|
|
14009
14007
|
else return multiPolygon(unioned, options.properties);
|
|
14010
14008
|
}
|
|
14011
14009
|
|
|
14010
|
+
class Point2D {
|
|
14011
|
+
x;
|
|
14012
|
+
y;
|
|
14013
|
+
constructor(x, y) {
|
|
14014
|
+
this.x = x;
|
|
14015
|
+
this.y = y;
|
|
14016
|
+
}
|
|
14017
|
+
isZero() {
|
|
14018
|
+
return this.x === 0 && this.y === 0;
|
|
14019
|
+
}
|
|
14020
|
+
scale(factor) {
|
|
14021
|
+
this.x *= factor;
|
|
14022
|
+
this.y *= factor;
|
|
14023
|
+
return this;
|
|
14024
|
+
}
|
|
14025
|
+
translate(offset) {
|
|
14026
|
+
this.x += offset.x;
|
|
14027
|
+
this.y += offset.y;
|
|
14028
|
+
return this;
|
|
14029
|
+
}
|
|
14030
|
+
getProject2Pixel() {
|
|
14031
|
+
const s = Math.sin((this.y * Math.PI) / 180.0);
|
|
14032
|
+
return new Point2D(this.x / 360.0 + 0.5, 0.5 - (0.25 * Math.log((1 + s) / (1 - s))) / Math.PI);
|
|
14033
|
+
}
|
|
14034
|
+
}
|
|
14035
|
+
class Feature {
|
|
14036
|
+
type;
|
|
14037
|
+
id;
|
|
14038
|
+
properties;
|
|
14039
|
+
patterns;
|
|
14040
|
+
geometry;
|
|
14041
|
+
constructor(opt) {
|
|
14042
|
+
this.type = opt.type;
|
|
14043
|
+
this.id = opt.id;
|
|
14044
|
+
this.properties = opt.properties;
|
|
14045
|
+
this.patterns = opt.patterns;
|
|
14046
|
+
this.geometry = opt.geometry;
|
|
14047
|
+
}
|
|
14048
|
+
getBbox() {
|
|
14049
|
+
let xMin = Infinity;
|
|
14050
|
+
let yMin = Infinity;
|
|
14051
|
+
let xMax = -Infinity;
|
|
14052
|
+
let yMax = -Infinity;
|
|
14053
|
+
this.geometry.forEach((ring) => {
|
|
14054
|
+
ring.forEach((point) => {
|
|
14055
|
+
if (xMin > point.x)
|
|
14056
|
+
xMin = point.x;
|
|
14057
|
+
if (yMin > point.y)
|
|
14058
|
+
yMin = point.y;
|
|
14059
|
+
if (xMax < point.x)
|
|
14060
|
+
xMax = point.x;
|
|
14061
|
+
if (yMax < point.y)
|
|
14062
|
+
yMax = point.y;
|
|
14063
|
+
});
|
|
14064
|
+
});
|
|
14065
|
+
return [xMin, yMin, xMax, yMax];
|
|
14066
|
+
}
|
|
14067
|
+
doesOverlap(bbox) {
|
|
14068
|
+
const featureBbox = this.getBbox();
|
|
14069
|
+
if (featureBbox[0] > bbox[2])
|
|
14070
|
+
return false;
|
|
14071
|
+
if (featureBbox[1] > bbox[3])
|
|
14072
|
+
return false;
|
|
14073
|
+
if (featureBbox[2] < bbox[0])
|
|
14074
|
+
return false;
|
|
14075
|
+
if (featureBbox[3] < bbox[1])
|
|
14076
|
+
return false;
|
|
14077
|
+
return true;
|
|
14078
|
+
}
|
|
14079
|
+
}
|
|
14080
|
+
|
|
14012
14081
|
function geojsonToFeature(id, polygonFeature) {
|
|
14013
14082
|
const geometry = polygonFeature.geometry.coordinates.map((ring) => {
|
|
14014
14083
|
return ring.map((coord) => new Point2D(coord[0], coord[1]));
|
|
@@ -14042,15 +14111,13 @@ function mergePolygons(featureList) {
|
|
|
14042
14111
|
const turfFeatures = [];
|
|
14043
14112
|
features.forEach((f) => {
|
|
14044
14113
|
const rings = f.geometry.map((ring) => ring.map((p) => [p.x, p.y]));
|
|
14045
|
-
|
|
14046
|
-
|
|
14047
|
-
|
|
14048
|
-
|
|
14049
|
-
|
|
14050
|
-
|
|
14051
|
-
|
|
14052
|
-
properties: f.properties,
|
|
14053
|
-
});
|
|
14114
|
+
turfFeatures.push({
|
|
14115
|
+
type: 'Feature',
|
|
14116
|
+
geometry: {
|
|
14117
|
+
type: 'Polygon',
|
|
14118
|
+
coordinates: rings,
|
|
14119
|
+
},
|
|
14120
|
+
properties: f.properties,
|
|
14054
14121
|
});
|
|
14055
14122
|
});
|
|
14056
14123
|
const merged = union2({
|
|
@@ -15659,24 +15726,16 @@ function writeUtf8(buf, str, pos) {
|
|
|
15659
15726
|
}
|
|
15660
15727
|
|
|
15661
15728
|
const TILE_EXTENT = 4096;
|
|
15662
|
-
async function
|
|
15729
|
+
async function loadVectorSource(source, job, layerFeatures) {
|
|
15730
|
+
const tiles = source.tiles;
|
|
15731
|
+
if (!tiles)
|
|
15732
|
+
return;
|
|
15663
15733
|
const { width, height } = job.renderer;
|
|
15664
15734
|
const { zoom, center } = job.view;
|
|
15665
|
-
const { sources } = job.style;
|
|
15666
|
-
const source = sources['versatiles-shortbread'];
|
|
15667
|
-
if (!source)
|
|
15668
|
-
return new Map();
|
|
15669
|
-
if (source.type !== 'vector' || !source.tiles) {
|
|
15670
|
-
console.error('Invalid source configuration. Expected a vector source with tile URLs.');
|
|
15671
|
-
console.error('Source config:', source);
|
|
15672
|
-
throw Error('Invalid source');
|
|
15673
|
-
}
|
|
15674
|
-
const sourceUrl = source.tiles[0];
|
|
15675
15735
|
const { zoomLevel, tileSize, tiles: tileCoordinates, } = calculateTileGrid(width, height, center, zoom, source.maxzoom);
|
|
15676
|
-
const layerFeatures = new Map();
|
|
15677
15736
|
await Promise.all(tileCoordinates.map(async ({ x, y, offsetX, offsetY }) => {
|
|
15678
15737
|
const offset = new Point2D(offsetX, offsetY);
|
|
15679
|
-
const tile = await getTile(
|
|
15738
|
+
const tile = await getTile(tiles[0], zoomLevel, x, y);
|
|
15680
15739
|
if (!tile)
|
|
15681
15740
|
return;
|
|
15682
15741
|
const vectorTile = new VectorTile(new Pbf(tile.buffer));
|
|
@@ -15720,14 +15779,108 @@ async function getLayerFeatures(job) {
|
|
|
15720
15779
|
}
|
|
15721
15780
|
}
|
|
15722
15781
|
}));
|
|
15723
|
-
|
|
15724
|
-
|
|
15725
|
-
|
|
15726
|
-
|
|
15727
|
-
|
|
15728
|
-
|
|
15782
|
+
}
|
|
15783
|
+
|
|
15784
|
+
function loadGeoJSONSource(sourceName, data, width, height, zoom, center, layerFeatures) {
|
|
15785
|
+
const existing = layerFeatures.get(sourceName);
|
|
15786
|
+
const features = existing ?? { points: [], linestrings: [], polygons: [] };
|
|
15787
|
+
if (!existing)
|
|
15788
|
+
layerFeatures.set(sourceName, features);
|
|
15789
|
+
const worldSize = 512 * 2 ** zoom;
|
|
15790
|
+
const centerMercator = center.getProject2Pixel();
|
|
15791
|
+
function projectCoord(coord) {
|
|
15792
|
+
const mercator = new Point2D(coord[0], coord[1]).getProject2Pixel();
|
|
15793
|
+
return new Point2D((mercator.x - centerMercator.x) * worldSize + width / 2, (mercator.y - centerMercator.y) * worldSize + height / 2);
|
|
15794
|
+
}
|
|
15795
|
+
function makeFeature(type, geometry, id, properties) {
|
|
15796
|
+
const feature = new Feature({ type, geometry, id, properties });
|
|
15797
|
+
if (!feature.doesOverlap([0, 0, width, height]))
|
|
15798
|
+
return null;
|
|
15799
|
+
return feature;
|
|
15800
|
+
}
|
|
15801
|
+
function extractPoints(geometry) {
|
|
15802
|
+
return geometry.flatMap((ring) => ring.map((p) => [p]));
|
|
15803
|
+
}
|
|
15804
|
+
function addFeature(type, geometry, id, properties) {
|
|
15805
|
+
switch (type) {
|
|
15806
|
+
case 'Point': {
|
|
15807
|
+
const f = makeFeature('Point', geometry, id, properties);
|
|
15808
|
+
if (f)
|
|
15809
|
+
features.points.push(f);
|
|
15810
|
+
break;
|
|
15811
|
+
}
|
|
15812
|
+
case 'LineString': {
|
|
15813
|
+
const f = makeFeature('LineString', geometry, id, properties);
|
|
15814
|
+
if (f) {
|
|
15815
|
+
features.linestrings.push(f);
|
|
15816
|
+
features.points.push(new Feature({ type: 'Point', geometry: extractPoints(geometry), id, properties }));
|
|
15817
|
+
}
|
|
15818
|
+
break;
|
|
15819
|
+
}
|
|
15820
|
+
case 'Polygon': {
|
|
15821
|
+
geometry.forEach((ring, ringIndex) => {
|
|
15822
|
+
const needsCW = ringIndex === 0;
|
|
15823
|
+
let area = 0;
|
|
15824
|
+
for (let i = 0; i < ring.length; i++) {
|
|
15825
|
+
const j = (i + 1) % ring.length;
|
|
15826
|
+
area += ring[i].x * ring[j].y;
|
|
15827
|
+
area -= ring[j].x * ring[i].y;
|
|
15828
|
+
}
|
|
15829
|
+
if (area < 0 !== needsCW)
|
|
15830
|
+
ring.reverse();
|
|
15831
|
+
});
|
|
15832
|
+
const f = makeFeature('Polygon', geometry, id, properties);
|
|
15833
|
+
if (f) {
|
|
15834
|
+
features.polygons.push(f);
|
|
15835
|
+
features.linestrings.push(new Feature({ type: 'LineString', geometry, id, properties }));
|
|
15836
|
+
features.points.push(new Feature({ type: 'Point', geometry: extractPoints(geometry), id, properties }));
|
|
15837
|
+
}
|
|
15838
|
+
break;
|
|
15839
|
+
}
|
|
15840
|
+
}
|
|
15841
|
+
}
|
|
15842
|
+
function processGeometry(geom, id, properties) {
|
|
15843
|
+
const coords = geom.coordinates;
|
|
15844
|
+
switch (geom.type) {
|
|
15845
|
+
case 'Point':
|
|
15846
|
+
addFeature('Point', [[projectCoord(coords)]], id, properties);
|
|
15847
|
+
break;
|
|
15848
|
+
case 'MultiPoint':
|
|
15849
|
+
addFeature('Point', coords.map((c) => [projectCoord(c)]), id, properties);
|
|
15850
|
+
break;
|
|
15851
|
+
case 'LineString':
|
|
15852
|
+
addFeature('LineString', [coords.map((c) => projectCoord(c))], id, properties);
|
|
15853
|
+
break;
|
|
15854
|
+
case 'MultiLineString':
|
|
15855
|
+
addFeature('LineString', coords.map((line) => line.map((c) => projectCoord(c))), id, properties);
|
|
15856
|
+
break;
|
|
15857
|
+
case 'Polygon':
|
|
15858
|
+
addFeature('Polygon', coords.map((ring) => ring.map((c) => projectCoord(c))), id, properties);
|
|
15859
|
+
break;
|
|
15860
|
+
case 'MultiPolygon':
|
|
15861
|
+
addFeature('Polygon', coords.flatMap((polygon) => polygon.map((ring) => ring.map((c) => projectCoord(c)))), id, properties);
|
|
15862
|
+
break;
|
|
15863
|
+
case 'GeometryCollection':
|
|
15864
|
+
for (const g of geom.geometries) {
|
|
15865
|
+
processGeometry(g, id, properties);
|
|
15866
|
+
}
|
|
15867
|
+
break;
|
|
15868
|
+
}
|
|
15869
|
+
}
|
|
15870
|
+
const geojson = data;
|
|
15871
|
+
switch (geojson.type) {
|
|
15872
|
+
case 'FeatureCollection':
|
|
15873
|
+
for (const f of geojson.features) {
|
|
15874
|
+
processGeometry(f.geometry, f.id, (f.properties ?? {}));
|
|
15875
|
+
}
|
|
15876
|
+
break;
|
|
15877
|
+
case 'Feature':
|
|
15878
|
+
processGeometry(geojson.geometry, geojson.id, (geojson.properties ?? {}));
|
|
15879
|
+
break;
|
|
15880
|
+
default:
|
|
15881
|
+
processGeometry(geojson, undefined, {});
|
|
15882
|
+
break;
|
|
15729
15883
|
}
|
|
15730
|
-
return layerFeatures;
|
|
15731
15884
|
}
|
|
15732
15885
|
|
|
15733
15886
|
async function getRasterTiles(job, sourceName) {
|
|
@@ -15758,6 +15911,34 @@ async function getRasterTiles(job, sourceName) {
|
|
|
15758
15911
|
return rasterTiles.filter((tile) => tile !== null);
|
|
15759
15912
|
}
|
|
15760
15913
|
|
|
15914
|
+
async function getLayerFeatures(job) {
|
|
15915
|
+
const { width, height } = job.renderer;
|
|
15916
|
+
const { zoom, center } = job.view;
|
|
15917
|
+
const { sources } = job.style;
|
|
15918
|
+
const layerFeatures = new Map();
|
|
15919
|
+
for (const [sourceName, sourceSpec] of Object.entries(sources)) {
|
|
15920
|
+
const source = sourceSpec;
|
|
15921
|
+
switch (source.type) {
|
|
15922
|
+
case 'vector':
|
|
15923
|
+
await loadVectorSource(source, job, layerFeatures);
|
|
15924
|
+
break;
|
|
15925
|
+
case 'geojson':
|
|
15926
|
+
if (source.data) {
|
|
15927
|
+
loadGeoJSONSource(sourceName, source.data, width, height, zoom, center, layerFeatures);
|
|
15928
|
+
}
|
|
15929
|
+
break;
|
|
15930
|
+
}
|
|
15931
|
+
}
|
|
15932
|
+
for (const [name, features] of layerFeatures) {
|
|
15933
|
+
layerFeatures.set(name, {
|
|
15934
|
+
points: features.points,
|
|
15935
|
+
linestrings: features.linestrings,
|
|
15936
|
+
polygons: mergePolygons(features.polygons),
|
|
15937
|
+
});
|
|
15938
|
+
}
|
|
15939
|
+
return layerFeatures;
|
|
15940
|
+
}
|
|
15941
|
+
|
|
15761
15942
|
/**
|
|
15762
15943
|
* Wraps a source/composite expression for per-feature evaluation.
|
|
15763
15944
|
*/
|
|
@@ -15923,7 +16104,7 @@ async function render(job) {
|
|
|
15923
16104
|
continue;
|
|
15924
16105
|
case 'fill':
|
|
15925
16106
|
{
|
|
15926
|
-
const polygons = layerFeatures.get(layerStyle.sourceLayer)?.polygons;
|
|
16107
|
+
const polygons = (layerFeatures.get(layerStyle.sourceLayer) ?? layerFeatures.get(layerStyle.source))?.polygons;
|
|
15927
16108
|
if (!polygons || polygons.length === 0)
|
|
15928
16109
|
continue;
|
|
15929
16110
|
const filter = featureFilter(layerStyle.filter);
|
|
@@ -15941,7 +16122,7 @@ async function render(job) {
|
|
|
15941
16122
|
continue;
|
|
15942
16123
|
case 'line':
|
|
15943
16124
|
{
|
|
15944
|
-
const lineStrings = layerFeatures.get(layerStyle.sourceLayer)?.linestrings;
|
|
16125
|
+
const lineStrings = (layerFeatures.get(layerStyle.sourceLayer) ?? layerFeatures.get(layerStyle.source))?.linestrings;
|
|
15945
16126
|
if (!lineStrings || lineStrings.length === 0)
|
|
15946
16127
|
continue;
|
|
15947
16128
|
const filter = featureFilter(layerStyle.filter);
|
|
@@ -15981,6 +16162,27 @@ async function render(job) {
|
|
|
15981
16162
|
}
|
|
15982
16163
|
continue;
|
|
15983
16164
|
case 'circle':
|
|
16165
|
+
{
|
|
16166
|
+
const points = (layerFeatures.get(layerStyle.sourceLayer) ?? layerFeatures.get(layerStyle.source))?.points;
|
|
16167
|
+
if (!points || points.length === 0)
|
|
16168
|
+
continue;
|
|
16169
|
+
const filter = featureFilter(layerStyle.filter);
|
|
16170
|
+
const pointFeatures = points.filter((feature) => filter.filter({ zoom }, feature));
|
|
16171
|
+
if (pointFeatures.length === 0)
|
|
16172
|
+
continue;
|
|
16173
|
+
renderer.drawCircles(pointFeatures.map((feature) => [
|
|
16174
|
+
feature,
|
|
16175
|
+
{
|
|
16176
|
+
color: new Color(getPaint('circle-color', feature)),
|
|
16177
|
+
radius: getPaint('circle-radius', feature),
|
|
16178
|
+
blur: getPaint('circle-blur', feature),
|
|
16179
|
+
translate: new Point2D(...getPaint('circle-translate', feature)),
|
|
16180
|
+
strokeWidth: getPaint('circle-stroke-width', feature),
|
|
16181
|
+
strokeColor: new Color(getPaint('circle-stroke-color', feature)),
|
|
16182
|
+
},
|
|
16183
|
+
]), getPaint('circle-opacity', pointFeatures[0]));
|
|
16184
|
+
}
|
|
16185
|
+
continue;
|
|
15984
16186
|
case 'color-relief':
|
|
15985
16187
|
case 'fill-extrusion':
|
|
15986
16188
|
case 'heatmap':
|