@maplibre/geojson-vt 6.1.1 → 6.2.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 +3 -0
- package/dist/cluster-tile-index.d.ts +41 -6
- package/dist/cluster-tile-index.d.ts.map +1 -1
- package/dist/convert.d.ts +4 -4
- package/dist/convert.d.ts.map +1 -1
- package/dist/deconvert.d.ts +7 -7
- package/dist/deconvert.d.ts.map +1 -1
- package/dist/definitions.d.ts +17 -5
- package/dist/definitions.d.ts.map +1 -1
- package/dist/geojson-vt-dev.js +315 -154
- package/dist/geojson-vt.js +1 -1
- package/dist/geojson-vt.mjs +305 -146
- package/dist/geojson-vt.mjs.map +1 -1
- package/dist/geojsonvt.d.ts.map +1 -1
- package/dist/simplify.d.ts.map +1 -1
- package/dist/tile-index.d.ts +6 -0
- package/dist/tile-index.d.ts.map +1 -1
- package/dist/wrap.d.ts.map +1 -1
- package/package.json +9 -9
- package/src/clip.ts +2 -2
- package/src/cluster-tile-index.test.ts +103 -0
- package/src/cluster-tile-index.ts +242 -97
- package/src/convert.ts +35 -28
- package/src/deconvert.ts +20 -18
- package/src/definitions.ts +17 -5
- package/src/difference.test.ts +71 -0
- package/src/difference.ts +3 -3
- package/src/geojsonvt.test.ts +236 -1
- package/src/geojsonvt.ts +5 -4
- package/src/simplify.test.ts +18 -0
- package/src/simplify.ts +12 -2
- package/src/tile-index.ts +17 -3
- package/src/wrap.ts +2 -0
package/dist/geojson-vt-dev.js
CHANGED
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory((global.geojsonvt = {})));
|
|
5
5
|
})(this, function(exports) {
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
7
|
-
|
|
8
7
|
//#region src/simplify.ts
|
|
9
8
|
/**
|
|
10
9
|
* calculate simplification data using optimized Douglas-Peucker algorithm
|
|
@@ -14,6 +13,14 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
14
13
|
* @param sqTolerance - square tolerance value
|
|
15
14
|
*/
|
|
16
15
|
function simplify(coords, first, last, sqTolerance) {
|
|
16
|
+
const stack = [first, last];
|
|
17
|
+
while (stack.length) {
|
|
18
|
+
last = stack.pop();
|
|
19
|
+
first = stack.pop();
|
|
20
|
+
simplifySegment(coords, first, last, sqTolerance, stack);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function simplifySegment(coords, first, last, sqTolerance, stack) {
|
|
17
24
|
let maxSqDist = sqTolerance;
|
|
18
25
|
const mid = first + (last - first >> 1);
|
|
19
26
|
let minPosToMid = last - first;
|
|
@@ -38,9 +45,9 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
38
45
|
}
|
|
39
46
|
}
|
|
40
47
|
if (maxSqDist > sqTolerance) {
|
|
41
|
-
if (index - first > 3) simplify(coords, first, index, sqTolerance);
|
|
42
48
|
coords[index + 2] = maxSqDist;
|
|
43
|
-
if (
|
|
49
|
+
if (index - first > 3) stack.push(first, index);
|
|
50
|
+
if (last - index > 3) stack.push(index, last);
|
|
44
51
|
}
|
|
45
52
|
}
|
|
46
53
|
/**
|
|
@@ -110,9 +117,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
110
117
|
case "MultiLineString":
|
|
111
118
|
for (const line of data.geom) calcLineBBox(feature, line.points);
|
|
112
119
|
break;
|
|
113
|
-
case "MultiPolygon":
|
|
114
|
-
for (const polygon of data.geom) calcLineBBox(feature, polygon[0].points);
|
|
115
|
-
break;
|
|
120
|
+
case "MultiPolygon": for (const polygon of data.geom) calcLineBBox(feature, polygon[0].points);
|
|
116
121
|
}
|
|
117
122
|
return feature;
|
|
118
123
|
}
|
|
@@ -165,24 +170,25 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
165
170
|
if (!geojson.geometry.coordinates?.length) return;
|
|
166
171
|
const id = getFeatureId(geojson, options, index);
|
|
167
172
|
const tolerance = Math.pow(options.tolerance / ((1 << options.maxZoom) * options.extent), 2);
|
|
173
|
+
const worldCoordinates = options.worldCoordinates === true;
|
|
168
174
|
switch (geojson.geometry.type) {
|
|
169
175
|
case "Point":
|
|
170
|
-
convertPointFeature(features, id, geojson.geometry, geojson.properties);
|
|
176
|
+
convertPointFeature(features, id, geojson.geometry, worldCoordinates, geojson.properties);
|
|
171
177
|
return;
|
|
172
178
|
case "MultiPoint":
|
|
173
|
-
convertMultiPointFeature(features, id, geojson.geometry, geojson.properties);
|
|
179
|
+
convertMultiPointFeature(features, id, geojson.geometry, worldCoordinates, geojson.properties);
|
|
174
180
|
return;
|
|
175
181
|
case "LineString":
|
|
176
|
-
convertLineStringFeature(features, id, geojson.geometry, tolerance, geojson.properties);
|
|
182
|
+
convertLineStringFeature(features, id, geojson.geometry, tolerance, worldCoordinates, geojson.properties);
|
|
177
183
|
return;
|
|
178
184
|
case "MultiLineString":
|
|
179
|
-
convertMultiLineStringFeature(features, id, geojson.geometry, tolerance, options, geojson.properties);
|
|
185
|
+
convertMultiLineStringFeature(features, id, geojson.geometry, tolerance, worldCoordinates, options, geojson.properties);
|
|
180
186
|
return;
|
|
181
187
|
case "Polygon":
|
|
182
|
-
convertPolygonFeature(features, id, geojson.geometry, tolerance, geojson.properties);
|
|
188
|
+
convertPolygonFeature(features, id, geojson.geometry, tolerance, worldCoordinates, geojson.properties);
|
|
183
189
|
return;
|
|
184
190
|
case "MultiPolygon":
|
|
185
|
-
convertMultiPolygonFeature(features, id, geojson.geometry, tolerance, geojson.properties);
|
|
191
|
+
convertMultiPolygonFeature(features, id, geojson.geometry, tolerance, worldCoordinates, geojson.properties);
|
|
186
192
|
return;
|
|
187
193
|
default: throw new Error("Input data is not a valid GeoJSON object.");
|
|
188
194
|
}
|
|
@@ -200,56 +206,60 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
200
206
|
properties: geojson.properties
|
|
201
207
|
}, options, index, depth);
|
|
202
208
|
}
|
|
203
|
-
function convertPointFeature(features, id, geom, properties) {
|
|
209
|
+
function convertPointFeature(features, id, geom, worldCoordinates, properties) {
|
|
204
210
|
const out = [];
|
|
205
|
-
out.push(projectX(geom.coordinates[0]), projectY(geom.coordinates[1]), 0);
|
|
211
|
+
out.push(projectX(geom.coordinates[0], worldCoordinates), projectY(geom.coordinates[1], worldCoordinates), 0);
|
|
206
212
|
features.push(createFeature(id, "Point", out, properties));
|
|
207
213
|
}
|
|
208
|
-
function convertMultiPointFeature(features, id, geom, properties) {
|
|
214
|
+
function convertMultiPointFeature(features, id, geom, worldCoordinates, properties) {
|
|
209
215
|
const out = [];
|
|
210
|
-
for (const coords of geom.coordinates) out.push(projectX(coords[0]), projectY(coords[1]), 0);
|
|
216
|
+
for (const coords of geom.coordinates) out.push(projectX(coords[0], worldCoordinates), projectY(coords[1], worldCoordinates), 0);
|
|
211
217
|
features.push(createFeature(id, "MultiPoint", out, properties));
|
|
212
218
|
}
|
|
213
|
-
function convertLineStringFeature(features, id, geom, tolerance, properties) {
|
|
219
|
+
function convertLineStringFeature(features, id, geom, tolerance, worldCoordinates, properties) {
|
|
214
220
|
const out = { points: [] };
|
|
215
|
-
convertLine(geom.coordinates, out, tolerance, false);
|
|
221
|
+
convertLine(geom.coordinates, out, tolerance, false, worldCoordinates);
|
|
216
222
|
features.push(createFeature(id, "LineString", out, properties));
|
|
217
223
|
}
|
|
218
|
-
function convertMultiLineStringFeature(features, id, geom, tolerance, options, properties) {
|
|
224
|
+
function convertMultiLineStringFeature(features, id, geom, tolerance, worldCoordinates, options, properties) {
|
|
219
225
|
if (options.lineMetrics) for (const line of geom.coordinates) {
|
|
220
226
|
const out = { points: [] };
|
|
221
|
-
convertLine(line, out, tolerance, false);
|
|
227
|
+
convertLine(line, out, tolerance, false, worldCoordinates);
|
|
222
228
|
features.push(createFeature(id, "LineString", out, properties));
|
|
223
229
|
}
|
|
224
230
|
else {
|
|
225
231
|
const out = [];
|
|
226
|
-
convertLines(geom.coordinates, out, tolerance, false);
|
|
232
|
+
convertLines(geom.coordinates, out, tolerance, false, worldCoordinates);
|
|
227
233
|
features.push(createFeature(id, "MultiLineString", out, properties));
|
|
228
234
|
}
|
|
229
235
|
}
|
|
230
|
-
function convertPolygonFeature(features, id, geom, tolerance, properties) {
|
|
236
|
+
function convertPolygonFeature(features, id, geom, tolerance, worldCoordinates, properties) {
|
|
231
237
|
const out = [];
|
|
232
|
-
convertLines(geom.coordinates, out, tolerance, true);
|
|
238
|
+
convertLines(geom.coordinates, out, tolerance, true, worldCoordinates);
|
|
233
239
|
features.push(createFeature(id, "Polygon", out, properties));
|
|
234
240
|
}
|
|
235
|
-
function convertMultiPolygonFeature(features, id, geom, tolerance, properties) {
|
|
241
|
+
function convertMultiPolygonFeature(features, id, geom, tolerance, worldCoordinates, properties) {
|
|
236
242
|
const out = [];
|
|
237
243
|
for (const polygon of geom.coordinates) {
|
|
238
244
|
const polygonOut = [];
|
|
239
|
-
convertLines(polygon, polygonOut, tolerance, true);
|
|
245
|
+
convertLines(polygon, polygonOut, tolerance, true, worldCoordinates);
|
|
246
|
+
if (!polygonOut.length) continue;
|
|
240
247
|
out.push(polygonOut);
|
|
241
248
|
}
|
|
249
|
+
if (!out.length) return;
|
|
242
250
|
features.push(createFeature(id, "MultiPolygon", out, properties));
|
|
243
251
|
}
|
|
244
|
-
function convertLine(ring, out, tolerance, isPolygon) {
|
|
252
|
+
function convertLine(ring, out, tolerance, isPolygon, worldCoordinates) {
|
|
245
253
|
let x0, y0;
|
|
246
254
|
let size = 0;
|
|
247
255
|
for (let j = 0; j < ring.length; j++) {
|
|
248
|
-
const x = projectX(ring[j][0]);
|
|
249
|
-
const y = projectY(ring[j][1]);
|
|
256
|
+
const x = projectX(ring[j][0], worldCoordinates);
|
|
257
|
+
const y = projectY(ring[j][1], worldCoordinates);
|
|
250
258
|
out.points.push(x, y, 0);
|
|
251
|
-
if (j > 0)
|
|
252
|
-
|
|
259
|
+
if (j > 0) {
|
|
260
|
+
if (isPolygon) size += (x0 * y - x * y0) / 2;
|
|
261
|
+
else size += Math.sqrt(Math.pow(x - x0, 2) + Math.pow(y - y0, 2));
|
|
262
|
+
}
|
|
253
263
|
x0 = x;
|
|
254
264
|
y0 = y;
|
|
255
265
|
}
|
|
@@ -262,23 +272,25 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
262
272
|
out.start = 0;
|
|
263
273
|
out.end = out.size;
|
|
264
274
|
}
|
|
265
|
-
function convertLines(rings, out, tolerance, isPolygon) {
|
|
275
|
+
function convertLines(rings, out, tolerance, isPolygon, worldCoordinates) {
|
|
266
276
|
for (let i = 0; i < rings.length; i++) {
|
|
267
277
|
const geom = { points: [] };
|
|
268
|
-
convertLine(rings[i], geom, tolerance, isPolygon);
|
|
278
|
+
convertLine(rings[i], geom, tolerance, isPolygon, worldCoordinates);
|
|
269
279
|
out.push(geom);
|
|
270
280
|
}
|
|
271
281
|
}
|
|
272
282
|
/**
|
|
273
|
-
* Convert longitude to spherical mercator in [0..1] range
|
|
283
|
+
* Convert longitude to spherical mercator in [0..1] range, or keep a world coordinate as it is
|
|
274
284
|
*/
|
|
275
|
-
function projectX(x) {
|
|
285
|
+
function projectX(x, worldCoordinates = false) {
|
|
286
|
+
if (worldCoordinates) return x;
|
|
276
287
|
return x / 360 + .5;
|
|
277
288
|
}
|
|
278
289
|
/**
|
|
279
|
-
* Convert latitude to spherical mercator in [0..1] range
|
|
290
|
+
* Convert latitude to spherical mercator in [0..1] range, or keep a world coordinate as it is
|
|
280
291
|
*/
|
|
281
|
-
function projectY(y) {
|
|
292
|
+
function projectY(y, worldCoordinates = false) {
|
|
293
|
+
if (worldCoordinates) return y;
|
|
282
294
|
const sin = Math.sin(y * Math.PI / 180);
|
|
283
295
|
const y2 = .5 - .25 * Math.log((1 + sin) / (1 - sin)) / Math.PI;
|
|
284
296
|
return y2 < 0 ? 0 : y2 > 1 ? 1 : y2;
|
|
@@ -289,19 +301,19 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
289
301
|
/**
|
|
290
302
|
* Converts internal source features back to GeoJSON format.
|
|
291
303
|
*/
|
|
292
|
-
function convertToGeoJSON(source) {
|
|
304
|
+
function convertToGeoJSON(source, worldCoordinates = false) {
|
|
293
305
|
return {
|
|
294
306
|
type: "FeatureCollection",
|
|
295
|
-
features: source.map((feature) => featureToGeoJSON(feature))
|
|
307
|
+
features: source.map((feature) => featureToGeoJSON(feature, worldCoordinates))
|
|
296
308
|
};
|
|
297
309
|
}
|
|
298
310
|
/**
|
|
299
311
|
* Converts a single internal feature to GeoJSON format.
|
|
300
312
|
*/
|
|
301
|
-
function featureToGeoJSON(feature) {
|
|
313
|
+
function featureToGeoJSON(feature, worldCoordinates = false) {
|
|
302
314
|
const geojsonFeature = {
|
|
303
315
|
type: "Feature",
|
|
304
|
-
geometry: geometryToGeoJSON(feature),
|
|
316
|
+
geometry: geometryToGeoJSON(feature, worldCoordinates),
|
|
305
317
|
properties: feature.tags
|
|
306
318
|
};
|
|
307
319
|
if (feature.id != null) geojsonFeature.id = feature.id;
|
|
@@ -310,50 +322,52 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
310
322
|
/**
|
|
311
323
|
* Converts a single internal feature geometry to GeoJSON format.
|
|
312
324
|
*/
|
|
313
|
-
function geometryToGeoJSON(feature) {
|
|
325
|
+
function geometryToGeoJSON(feature, worldCoordinates) {
|
|
314
326
|
const { type, geometry } = feature;
|
|
315
327
|
switch (type) {
|
|
316
328
|
case "Point": return {
|
|
317
329
|
type,
|
|
318
|
-
coordinates: unprojectPoint(geometry[0], geometry[1])
|
|
330
|
+
coordinates: unprojectPoint(geometry[0], geometry[1], worldCoordinates)
|
|
319
331
|
};
|
|
320
332
|
case "MultiPoint": return {
|
|
321
333
|
type,
|
|
322
|
-
coordinates: unprojectPoints(geometry)
|
|
334
|
+
coordinates: unprojectPoints(geometry, worldCoordinates)
|
|
323
335
|
};
|
|
324
336
|
case "LineString": return {
|
|
325
337
|
type,
|
|
326
|
-
coordinates: unprojectPoints(geometry.points)
|
|
338
|
+
coordinates: unprojectPoints(geometry.points, worldCoordinates)
|
|
327
339
|
};
|
|
328
340
|
case "MultiLineString":
|
|
329
341
|
case "Polygon": return {
|
|
330
342
|
type,
|
|
331
|
-
coordinates: geometry.map((ring) => unprojectPoints(ring.points))
|
|
343
|
+
coordinates: geometry.map((ring) => unprojectPoints(ring.points, worldCoordinates))
|
|
332
344
|
};
|
|
333
345
|
case "MultiPolygon": return {
|
|
334
346
|
type,
|
|
335
|
-
coordinates: geometry.map((polygon) => polygon.map((ring) => unprojectPoints(ring.points)))
|
|
347
|
+
coordinates: geometry.map((polygon) => polygon.map((ring) => unprojectPoints(ring.points, worldCoordinates)))
|
|
336
348
|
};
|
|
337
349
|
}
|
|
338
350
|
}
|
|
339
|
-
function unprojectPoints(coords) {
|
|
351
|
+
function unprojectPoints(coords, worldCoordinates = false) {
|
|
340
352
|
const result = [];
|
|
341
|
-
for (let i = 0; i < coords.length; i += 3) result.push(unprojectPoint(coords[i], coords[i + 1]));
|
|
353
|
+
for (let i = 0; i < coords.length; i += 3) result.push(unprojectPoint(coords[i], coords[i + 1], worldCoordinates));
|
|
342
354
|
return result;
|
|
343
355
|
}
|
|
344
|
-
function unprojectPoint(x, y) {
|
|
345
|
-
return [unprojectX(x), unprojectY(y)];
|
|
356
|
+
function unprojectPoint(x, y, worldCoordinates) {
|
|
357
|
+
return [unprojectX(x, worldCoordinates), unprojectY(y, worldCoordinates)];
|
|
346
358
|
}
|
|
347
359
|
/**
|
|
348
|
-
* Convert spherical mercator in [0..1] range to longitude
|
|
360
|
+
* Convert spherical mercator in [0..1] range to longitude, or keep a world coordinate as it is
|
|
349
361
|
*/
|
|
350
|
-
function unprojectX(x) {
|
|
362
|
+
function unprojectX(x, worldCoordinates = false) {
|
|
363
|
+
if (worldCoordinates) return x;
|
|
351
364
|
return (x - .5) * 360;
|
|
352
365
|
}
|
|
353
366
|
/**
|
|
354
|
-
* Convert spherical mercator in [0..1] range to latitude
|
|
367
|
+
* Convert spherical mercator in [0..1] range to latitude, or keep a world coordinate as it is
|
|
355
368
|
*/
|
|
356
|
-
function unprojectY(y) {
|
|
369
|
+
function unprojectY(y, worldCoordinates = false) {
|
|
370
|
+
if (worldCoordinates) return y;
|
|
357
371
|
const y2 = (180 - y * 360) * Math.PI / 180;
|
|
358
372
|
return 360 * Math.atan(Math.exp(y2)) / Math.PI - 90;
|
|
359
373
|
}
|
|
@@ -481,12 +495,12 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
481
495
|
let exited = false;
|
|
482
496
|
if (trackMetrics) segLen = Math.sqrt(Math.pow(ax - bx, 2) + Math.pow(ay - by, 2));
|
|
483
497
|
if (a < start) {
|
|
484
|
-
if (b
|
|
498
|
+
if (b >= start) {
|
|
485
499
|
t = intersect(slice, ax, ay, bx, by, start);
|
|
486
500
|
if (trackMetrics) slice.start = len + segLen * t;
|
|
487
501
|
}
|
|
488
502
|
} else if (a > end) {
|
|
489
|
-
if (b
|
|
503
|
+
if (b <= end) {
|
|
490
504
|
t = intersect(slice, ax, ay, bx, by, end);
|
|
491
505
|
if (trackMetrics) slice.start = len + segLen * t;
|
|
492
506
|
}
|
|
@@ -547,6 +561,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
547
561
|
//#endregion
|
|
548
562
|
//#region src/wrap.ts
|
|
549
563
|
function wrap(features, options) {
|
|
564
|
+
if (options.worldCoordinates) return features;
|
|
550
565
|
const buffer = options.buffer / options.extent;
|
|
551
566
|
let merged = features;
|
|
552
567
|
const left = clip(features, 1, -1 - buffer, buffer, 0, -1, 2, options);
|
|
@@ -668,22 +683,27 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
668
683
|
* @param vtFeatures - the original features
|
|
669
684
|
* @param update - the update object to apply
|
|
670
685
|
* @param options - the options to use for the wrap method
|
|
671
|
-
* @returns Updated features. If geometry is updated, returns new feature(s) converted from geojson and wrapped. If only properties are updated, returns feature(s) with tags updated.
|
|
686
|
+
* @returns Updated features. If geometry is updated, returns new feature(s) converted from geojson and wrapped, with the id the feature already has. If only properties are updated, returns feature(s) with tags updated.
|
|
672
687
|
*/
|
|
673
688
|
function getUpdatedFeatures(vtFeatures, update, options) {
|
|
674
689
|
const changeGeometry = !!update.newGeometry;
|
|
675
690
|
const changeProps = update.removeAllProperties || update.removeProperties?.length > 0 || update.addOrUpdateProperties?.length > 0;
|
|
676
691
|
if (changeGeometry) {
|
|
677
692
|
const vtFeature = vtFeatures[0];
|
|
693
|
+
const geojsonFeature = {
|
|
694
|
+
type: "Feature",
|
|
695
|
+
id: vtFeature.id,
|
|
696
|
+
geometry: update.newGeometry,
|
|
697
|
+
properties: changeProps ? applyPropertyUpdates(vtFeature.tags, update) : vtFeature.tags
|
|
698
|
+
};
|
|
678
699
|
let features = convertToInternal({
|
|
679
700
|
type: "FeatureCollection",
|
|
680
|
-
features: [
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
}, options);
|
|
701
|
+
features: [geojsonFeature]
|
|
702
|
+
}, {
|
|
703
|
+
...options,
|
|
704
|
+
promoteId: void 0,
|
|
705
|
+
generateId: false
|
|
706
|
+
});
|
|
687
707
|
features = wrap(features, options);
|
|
688
708
|
return features;
|
|
689
709
|
}
|
|
@@ -720,7 +740,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
720
740
|
return {
|
|
721
741
|
removeAll: diff.removeAll,
|
|
722
742
|
remove: new Set(diff.remove || []),
|
|
723
|
-
add: new Map(diff.add?.map((feature) => [options.promoteId ? feature.properties[options.promoteId] : feature.id, feature])),
|
|
743
|
+
add: new Map(diff.add?.map((feature) => [options.promoteId ? feature.properties?.[options.promoteId] : feature.id, feature])),
|
|
724
744
|
update: new Map(diff.update?.map((update) => [update.id, update]))
|
|
725
745
|
};
|
|
726
746
|
}
|
|
@@ -791,7 +811,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
791
811
|
this.coords = new ArrayType(data, HEADER_SIZE + idsByteSize + padCoords, numItems * 2);
|
|
792
812
|
this._pos = 0;
|
|
793
813
|
this._finished = false;
|
|
794
|
-
new Uint8Array(data, 0, 2).set([219,
|
|
814
|
+
new Uint8Array(data, 0, 2).set([219, 16 + arrayTypeIndex]);
|
|
795
815
|
new Uint16Array(data, 2, 1)[0] = nodeSize;
|
|
796
816
|
new Uint32Array(data, 4, 1)[0] = numItems;
|
|
797
817
|
}
|
|
@@ -1025,52 +1045,101 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
1025
1045
|
reduce: null,
|
|
1026
1046
|
map: (props) => props
|
|
1027
1047
|
};
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
const
|
|
1032
|
-
|
|
1048
|
+
/**
|
|
1049
|
+
* The highest zoom level to cluster on. A cluster id keeps its zoom level in five bits.
|
|
1050
|
+
*/
|
|
1051
|
+
const MAX_ZOOM = 30;
|
|
1052
|
+
/**
|
|
1053
|
+
* Projected coordinates, which are 0 to 1 inside the world, are stored as integers with this many steps per world,
|
|
1054
|
+
* so that they take half the memory of doubles. One step is ~4 cm at the equator.
|
|
1055
|
+
*/
|
|
1056
|
+
const SCALE = 1073741824;
|
|
1057
|
+
/**
|
|
1058
|
+
* The largest value a 32-bit integer holds.
|
|
1059
|
+
*/
|
|
1060
|
+
const MAX_INTEGER = 2147483647;
|
|
1061
|
+
/**
|
|
1062
|
+
* A projected coordinate in steps from the center of the world, see `SCALE`.
|
|
1063
|
+
*/
|
|
1064
|
+
function encode(coord) {
|
|
1065
|
+
return (coord - .5) * SCALE;
|
|
1066
|
+
}
|
|
1067
|
+
function decode(value) {
|
|
1068
|
+
return value / SCALE + .5;
|
|
1069
|
+
}
|
|
1070
|
+
/**
|
|
1071
|
+
* Whether a projected coordinate fits in a 32-bit integer. One that is more than two worlds from the center of the
|
|
1072
|
+
* world does not, and neither does one that is not a number.
|
|
1073
|
+
*/
|
|
1074
|
+
function fitsInteger(coord) {
|
|
1075
|
+
return Math.abs(encode(coord)) < MAX_INTEGER;
|
|
1076
|
+
}
|
|
1077
|
+
const OFFSET_ZOOM = 0;
|
|
1078
|
+
const OFFSET_ID = 1;
|
|
1079
|
+
const OFFSET_PARENT = 2;
|
|
1080
|
+
const OFFSET_NUM = 3;
|
|
1081
|
+
const OFFSET_PROP = 4;
|
|
1033
1082
|
/**
|
|
1034
1083
|
* This class allow clustering of geojson points.
|
|
1035
1084
|
*/
|
|
1036
1085
|
var ClusterTileIndex = class {
|
|
1037
|
-
constructor(options) {
|
|
1038
|
-
this.options =
|
|
1086
|
+
constructor(options, worldCoordinates = false) {
|
|
1087
|
+
this.options = createOptions(options);
|
|
1088
|
+
this.worldCoordinates = worldCoordinates;
|
|
1039
1089
|
this.trees = new Array(this.options.maxZoom + 1);
|
|
1040
|
-
this.stride = this.options.reduce ?
|
|
1090
|
+
this.stride = this.options.reduce ? 5 : 4;
|
|
1041
1091
|
this.clusterProps = [];
|
|
1042
1092
|
this.points = [];
|
|
1043
1093
|
}
|
|
1044
1094
|
/**
|
|
1045
|
-
* Loads GeoJSON
|
|
1046
|
-
*
|
|
1095
|
+
* Loads GeoJSON Point and MultiPoint features and builds the internal clustering index.
|
|
1096
|
+
* Each position of a MultiPoint is clustered as a point of its own, with the id and properties of its feature.
|
|
1097
|
+
* @param points - GeoJSON Point and MultiPoint features to cluster.
|
|
1047
1098
|
*/
|
|
1048
1099
|
load(points) {
|
|
1049
1100
|
const features = [];
|
|
1050
1101
|
for (const point of points) {
|
|
1051
1102
|
if (!point.geometry) continue;
|
|
1052
|
-
const
|
|
1053
|
-
const [
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1103
|
+
const positions = point.geometry.type === "MultiPoint" ? point.geometry.coordinates : [point.geometry.coordinates];
|
|
1104
|
+
for (const [lng, lat] of positions) {
|
|
1105
|
+
const [x, y] = [projectX(lng, this.worldCoordinates), projectY(lat, this.worldCoordinates)];
|
|
1106
|
+
if (this.worldCoordinates && !isInWorld([x, y])) continue;
|
|
1107
|
+
const feature = {
|
|
1108
|
+
id: point.id,
|
|
1109
|
+
type: "Point",
|
|
1110
|
+
geometry: [x, y],
|
|
1111
|
+
tags: point.properties
|
|
1112
|
+
};
|
|
1113
|
+
features.push(feature);
|
|
1114
|
+
}
|
|
1061
1115
|
}
|
|
1062
1116
|
this.createIndex(features);
|
|
1063
1117
|
}
|
|
1064
1118
|
/**
|
|
1065
1119
|
* @internal
|
|
1066
1120
|
* Loads internal GeoJSONVT point features from a data source and builds the clustering index.
|
|
1121
|
+
* Each position of a MultiPoint feature becomes a point of its own, with the id and properties of its feature.
|
|
1067
1122
|
* @param features - {@link GeoJSONVTInternalFeature} data source features to filter and cluster.
|
|
1068
1123
|
*/
|
|
1069
1124
|
initialize(features) {
|
|
1070
1125
|
const points = [];
|
|
1071
1126
|
for (const feature of features) {
|
|
1072
|
-
if (feature.type
|
|
1073
|
-
|
|
1127
|
+
if (feature.type === "Point") {
|
|
1128
|
+
if (this.worldCoordinates && !isInWorld(feature.geometry)) continue;
|
|
1129
|
+
points.push(feature);
|
|
1130
|
+
continue;
|
|
1131
|
+
}
|
|
1132
|
+
if (feature.type !== "MultiPoint") continue;
|
|
1133
|
+
for (let i = 0; i < feature.geometry.length; i += 3) {
|
|
1134
|
+
const geometry = [feature.geometry[i], feature.geometry[i + 1]];
|
|
1135
|
+
if (this.worldCoordinates && !isInWorld(geometry)) continue;
|
|
1136
|
+
points.push({
|
|
1137
|
+
id: feature.id,
|
|
1138
|
+
type: "Point",
|
|
1139
|
+
geometry,
|
|
1140
|
+
tags: feature.tags
|
|
1141
|
+
});
|
|
1142
|
+
}
|
|
1074
1143
|
}
|
|
1075
1144
|
this.createIndex(points);
|
|
1076
1145
|
}
|
|
@@ -1080,41 +1149,59 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
1080
1149
|
* @param features
|
|
1081
1150
|
*/
|
|
1082
1151
|
updateIndex(features, _affected, options) {
|
|
1083
|
-
this.options =
|
|
1152
|
+
this.options = createOptions(options.clusterOptions);
|
|
1084
1153
|
this.initialize(features);
|
|
1085
1154
|
}
|
|
1155
|
+
/**
|
|
1156
|
+
* Builds a tree per zoom level from the points. A point whose position does not fit in an integer is left out,
|
|
1157
|
+
* see `fitsInteger`.
|
|
1158
|
+
*/
|
|
1086
1159
|
createIndex(points) {
|
|
1087
1160
|
const { log, minZoom, maxZoom } = this.options;
|
|
1161
|
+
this.trees = new Array(maxZoom + 1);
|
|
1162
|
+
this.stride = this.options.reduce ? 5 : 4;
|
|
1163
|
+
this.clusterProps = [];
|
|
1088
1164
|
if (log) console.time("total time");
|
|
1089
1165
|
const timerId = `prepare ${points.length} points`;
|
|
1090
1166
|
if (log) console.time(timerId);
|
|
1091
1167
|
this.points = points;
|
|
1092
|
-
const
|
|
1168
|
+
const stride = this.stride;
|
|
1169
|
+
const notProcessed = maxZoom + 1;
|
|
1170
|
+
const coords = new Int32Array(points.length * 2);
|
|
1171
|
+
const data = new Int32Array(points.length * stride);
|
|
1172
|
+
let numItems = 0;
|
|
1093
1173
|
for (let i = 0; i < points.length; i++) {
|
|
1094
1174
|
const p = points[i];
|
|
1095
|
-
if (!p?.geometry) continue;
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
data
|
|
1100
|
-
|
|
1175
|
+
if (!p?.geometry || !fitsInteger(p.geometry[0]) || !fitsInteger(p.geometry[1])) continue;
|
|
1176
|
+
coords[numItems * 2] = Math.round(encode(p.geometry[0]));
|
|
1177
|
+
coords[numItems * 2 + 1] = Math.round(encode(p.geometry[1]));
|
|
1178
|
+
const k = numItems * stride;
|
|
1179
|
+
data[k + OFFSET_ZOOM] = notProcessed;
|
|
1180
|
+
data[k + OFFSET_ID] = i;
|
|
1181
|
+
data[k + OFFSET_PARENT] = -1;
|
|
1182
|
+
data[k + OFFSET_NUM] = 1;
|
|
1183
|
+
numItems++;
|
|
1101
1184
|
}
|
|
1102
|
-
let tree = this.trees[maxZoom + 1] = this.createTree(data);
|
|
1185
|
+
let tree = this.trees[maxZoom + 1] = this.createTree(coords, data, numItems);
|
|
1103
1186
|
if (log) console.timeEnd(timerId);
|
|
1187
|
+
const nextCoords = new Int32Array(numItems * 2);
|
|
1188
|
+
const nextData = new Int32Array(numItems * stride);
|
|
1104
1189
|
for (let z = maxZoom; z >= minZoom; z--) {
|
|
1105
1190
|
const now = Date.now();
|
|
1106
|
-
|
|
1191
|
+
const nextNumItems = this.cluster(tree, z, nextCoords, nextData);
|
|
1192
|
+
if (!this.isSameAsTree(tree, nextData, nextNumItems)) tree = this.createTree(nextCoords.slice(0, nextNumItems * 2), nextData.slice(0, nextNumItems * stride), nextNumItems);
|
|
1193
|
+
this.trees[z] = tree;
|
|
1107
1194
|
if (log) console.log("z%d: %d clusters in %dms", z, tree.numItems, Date.now() - now);
|
|
1108
1195
|
}
|
|
1109
1196
|
if (log) console.timeEnd("total time");
|
|
1110
1197
|
}
|
|
1111
1198
|
/**
|
|
1112
1199
|
* Returns clusters and/or points within a bounding box at a given zoom level.
|
|
1113
|
-
* @param bbox - Bounding box in `[westLng, southLat, eastLng, northLat]` order.
|
|
1200
|
+
* @param bbox - Bounding box in `[westLng, southLat, eastLng, northLat]` order, or `[minX, minY, maxX, maxY]` in world coordinates.
|
|
1114
1201
|
* @param zoom - Zoom level to query.
|
|
1115
1202
|
*/
|
|
1116
1203
|
getClusters(bbox, zoom) {
|
|
1117
|
-
return this.getClustersInternal(bbox, zoom).map((f) => featureToGeoJSON(f));
|
|
1204
|
+
return (this.worldCoordinates ? this.getClustersInRange(bbox, zoom) : this.getClustersInternal(bbox, zoom)).map((f) => featureToGeoJSON(f, this.worldCoordinates));
|
|
1118
1205
|
}
|
|
1119
1206
|
getClustersInternal(bbox, zoom) {
|
|
1120
1207
|
let minLng = ((bbox[0] + 180) % 360 + 360) % 360 - 180;
|
|
@@ -1139,13 +1226,25 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
1139
1226
|
], zoom);
|
|
1140
1227
|
return easternHem.concat(westernHem);
|
|
1141
1228
|
}
|
|
1229
|
+
return this.getClustersInRange([
|
|
1230
|
+
projectX(minLng),
|
|
1231
|
+
projectY(maxLat),
|
|
1232
|
+
projectX(maxLng),
|
|
1233
|
+
projectY(minLat)
|
|
1234
|
+
], zoom);
|
|
1235
|
+
}
|
|
1236
|
+
/**
|
|
1237
|
+
* The clusters and points in a box of the internal coordinates, `[minX, minY, maxX, maxY]`.
|
|
1238
|
+
*/
|
|
1239
|
+
getClustersInRange([minX, minY, maxX, maxY], zoom) {
|
|
1142
1240
|
const tree = this.trees[this.limitZoom(zoom)];
|
|
1143
|
-
const ids = tree.range(
|
|
1241
|
+
const ids = tree.range(encode(minX), encode(minY), encode(maxX), encode(maxY));
|
|
1242
|
+
const coords = tree.flatCoords;
|
|
1144
1243
|
const data = tree.flatData;
|
|
1145
1244
|
const clusters = [];
|
|
1146
1245
|
for (const id of ids) {
|
|
1147
1246
|
const k = this.stride * id;
|
|
1148
|
-
clusters.push(data[k + OFFSET_NUM] > 1 ? getClusterFeature(data, k, this.clusterProps) : this.points[data[k + OFFSET_ID]]);
|
|
1247
|
+
clusters.push(data[k + OFFSET_NUM] > 1 ? getClusterFeature(coords, id, data, k, this.clusterProps) : this.points[data[k + OFFSET_ID]]);
|
|
1149
1248
|
}
|
|
1150
1249
|
return clusters;
|
|
1151
1250
|
}
|
|
@@ -1159,16 +1258,17 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
1159
1258
|
const clusterError = /* @__PURE__ */ new Error("No cluster with the specified id: " + clusterId);
|
|
1160
1259
|
const tree = this.trees[originZoom];
|
|
1161
1260
|
if (!tree) throw clusterError;
|
|
1261
|
+
const coords = tree.flatCoords;
|
|
1162
1262
|
const data = tree.flatData;
|
|
1163
|
-
if (originId
|
|
1164
|
-
const r = this.options.radius / (this.options.extent * Math.pow(2, originZoom - 1));
|
|
1165
|
-
const x =
|
|
1166
|
-
const y =
|
|
1263
|
+
if (originId >= tree.numItems) throw clusterError;
|
|
1264
|
+
const r = this.options.radius / (this.options.extent * Math.pow(2, originZoom - 1)) * SCALE;
|
|
1265
|
+
const x = coords[originId * 2];
|
|
1266
|
+
const y = coords[originId * 2 + 1];
|
|
1167
1267
|
const ids = tree.within(x, y, r);
|
|
1168
1268
|
const children = [];
|
|
1169
1269
|
for (const id of ids) {
|
|
1170
1270
|
const k = id * this.stride;
|
|
1171
|
-
if (data[k + OFFSET_PARENT] === clusterId) children.push(data[k + OFFSET_NUM] > 1 ? getClusterGeoJSON(data, k, this.clusterProps) : featureToGeoJSON(this.points[data[k + OFFSET_ID]]));
|
|
1271
|
+
if (data[k + OFFSET_PARENT] === clusterId) children.push(data[k + OFFSET_NUM] > 1 ? getClusterGeoJSON(coords, id, data, k, this.clusterProps, this.worldCoordinates) : featureToGeoJSON(this.points[data[k + OFFSET_ID]], this.worldCoordinates));
|
|
1172
1272
|
}
|
|
1173
1273
|
if (children.length === 0) throw clusterError;
|
|
1174
1274
|
return children;
|
|
@@ -1196,10 +1296,11 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
1196
1296
|
const tree = this.trees[this.limitZoom(z)];
|
|
1197
1297
|
if (!tree) return null;
|
|
1198
1298
|
const z2 = Math.pow(2, z);
|
|
1299
|
+
if (this.worldCoordinates && (x < 0 || x >= z2 || y < 0 || y >= z2)) return null;
|
|
1199
1300
|
const { extent, radius } = this.options;
|
|
1200
1301
|
const p = radius / extent;
|
|
1201
|
-
const top = (y - p) / z2;
|
|
1202
|
-
const bottom = (y + 1 + p) / z2;
|
|
1302
|
+
const top = encode((y - p) / z2);
|
|
1303
|
+
const bottom = encode((y + 1 + p) / z2);
|
|
1203
1304
|
const tile = {
|
|
1204
1305
|
transformed: true,
|
|
1205
1306
|
features: [],
|
|
@@ -1208,9 +1309,9 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
1208
1309
|
y,
|
|
1209
1310
|
z
|
|
1210
1311
|
};
|
|
1211
|
-
this.addTileFeatures(tree.range((x - p) / z2, top, (x + 1 + p) / z2, bottom), tree
|
|
1212
|
-
if (x === 0) this.addTileFeatures(tree.range(1 - p / z2, top, 1, bottom), tree
|
|
1213
|
-
if (x === z2 - 1) this.addTileFeatures(tree.range(0, top, p / z2, bottom), tree
|
|
1312
|
+
this.addTileFeatures(tree.range(encode((x - p) / z2), top, encode((x + 1 + p) / z2), bottom), tree, x, y, z2, tile);
|
|
1313
|
+
if (!this.worldCoordinates && x === 0) this.addTileFeatures(tree.range(encode(1 - p / z2), top, encode(1), bottom), tree, z2, y, z2, tile);
|
|
1314
|
+
if (!this.worldCoordinates && x === z2 - 1) this.addTileFeatures(tree.range(encode(0), top, encode(p / z2), bottom), tree, -1, y, z2, tile);
|
|
1214
1315
|
return tile;
|
|
1215
1316
|
}
|
|
1216
1317
|
/**
|
|
@@ -1224,23 +1325,36 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
1224
1325
|
const children = this.getChildren(clusterId);
|
|
1225
1326
|
for (const child of children) {
|
|
1226
1327
|
const props = child.properties;
|
|
1227
|
-
if (props?.cluster)
|
|
1228
|
-
|
|
1229
|
-
|
|
1328
|
+
if (props?.cluster) {
|
|
1329
|
+
if (skipped + props.point_count <= offset) skipped += props.point_count;
|
|
1330
|
+
else skipped = this.appendLeaves(result, props.cluster_id, limit, offset, skipped);
|
|
1331
|
+
} else if (skipped < offset) skipped++;
|
|
1230
1332
|
else result.push(child);
|
|
1231
1333
|
if (result.length === limit) break;
|
|
1232
1334
|
}
|
|
1233
1335
|
return skipped;
|
|
1234
1336
|
}
|
|
1235
|
-
createTree(data) {
|
|
1236
|
-
const tree = new KDBush(
|
|
1237
|
-
for (let i = 0; i <
|
|
1337
|
+
createTree(coords, data, numItems) {
|
|
1338
|
+
const tree = new KDBush(numItems, this.options.nodeSize, Int32Array);
|
|
1339
|
+
for (let i = 0; i < numItems; i++) tree.add(coords[i * 2], coords[i * 2 + 1]);
|
|
1238
1340
|
tree.finish();
|
|
1341
|
+
tree.flatCoords = coords;
|
|
1239
1342
|
tree.flatData = data;
|
|
1240
1343
|
tree.data = null;
|
|
1241
1344
|
return tree;
|
|
1242
1345
|
}
|
|
1243
|
-
|
|
1346
|
+
/**
|
|
1347
|
+
* Whether clustering a tree left all its points and clusters as they were, in the same order.
|
|
1348
|
+
*/
|
|
1349
|
+
isSameAsTree(tree, nextData, nextNumItems) {
|
|
1350
|
+
if (nextNumItems !== tree.numItems) return false;
|
|
1351
|
+
const data = tree.flatData;
|
|
1352
|
+
for (let k = OFFSET_ID; k < nextNumItems * this.stride; k += this.stride) if (nextData[k] !== data[k]) return false;
|
|
1353
|
+
return true;
|
|
1354
|
+
}
|
|
1355
|
+
addTileFeatures(ids, tree, x, y, z2, tile) {
|
|
1356
|
+
const coords = tree.flatCoords;
|
|
1357
|
+
const data = tree.flatData;
|
|
1244
1358
|
for (const i of ids) {
|
|
1245
1359
|
const k = i * this.stride;
|
|
1246
1360
|
const isCluster = data[k + OFFSET_NUM] > 1;
|
|
@@ -1249,8 +1363,8 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
1249
1363
|
let py;
|
|
1250
1364
|
if (isCluster) {
|
|
1251
1365
|
tags = getClusterProperties(data, k, this.clusterProps);
|
|
1252
|
-
px =
|
|
1253
|
-
py =
|
|
1366
|
+
px = decode(coords[i * 2]);
|
|
1367
|
+
py = decode(coords[i * 2 + 1]);
|
|
1254
1368
|
} else {
|
|
1255
1369
|
const p = this.points[data[k + OFFSET_ID]];
|
|
1256
1370
|
tags = p.tags;
|
|
@@ -1271,22 +1385,30 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
1271
1385
|
limitZoom(z) {
|
|
1272
1386
|
return Math.max(this.options.minZoom, Math.min(Math.floor(+z), this.options.maxZoom + 1));
|
|
1273
1387
|
}
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1388
|
+
/**
|
|
1389
|
+
* Clusters the points of a tree on a zoom level, writing the result to `nextCoords` and `nextData`.
|
|
1390
|
+
* @returns the number of points and clusters written
|
|
1391
|
+
*/
|
|
1392
|
+
cluster(tree, zoom, nextCoords, nextData) {
|
|
1393
|
+
const { radius, extent, reduce, minPoints, maxZoom } = this.options;
|
|
1394
|
+
const r = radius / (extent * Math.pow(2, zoom)) * SCALE;
|
|
1395
|
+
const notProcessed = maxZoom + 1;
|
|
1396
|
+
const coords = tree.flatCoords;
|
|
1277
1397
|
const data = tree.flatData;
|
|
1278
|
-
const nextData = [];
|
|
1279
1398
|
const stride = this.stride;
|
|
1280
|
-
|
|
1399
|
+
const neighborIds = new Uint32Array(tree.numItems);
|
|
1400
|
+
let nextNumItems = 0;
|
|
1401
|
+
for (let index = 0; index < tree.numItems; index++) {
|
|
1402
|
+
const i = index * stride;
|
|
1281
1403
|
if (data[i + OFFSET_ZOOM] <= zoom) continue;
|
|
1282
1404
|
data[i + OFFSET_ZOOM] = zoom;
|
|
1283
|
-
const x =
|
|
1284
|
-
const y =
|
|
1285
|
-
const
|
|
1405
|
+
const x = coords[index * 2];
|
|
1406
|
+
const y = coords[index * 2 + 1];
|
|
1407
|
+
const numNeighbors = tree.withinInto(x, y, r, neighborIds);
|
|
1286
1408
|
const numPointsOrigin = data[i + OFFSET_NUM];
|
|
1287
1409
|
let numPoints = numPointsOrigin;
|
|
1288
|
-
for (
|
|
1289
|
-
const k =
|
|
1410
|
+
for (let n = 0; n < numNeighbors; n++) {
|
|
1411
|
+
const k = neighborIds[n] * stride;
|
|
1290
1412
|
if (data[k + OFFSET_ZOOM] > zoom) numPoints += data[k + OFFSET_NUM];
|
|
1291
1413
|
}
|
|
1292
1414
|
if (numPoints > numPointsOrigin && numPoints >= minPoints) {
|
|
@@ -1294,14 +1416,15 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
1294
1416
|
let wy = y * numPointsOrigin;
|
|
1295
1417
|
let clusterProperties;
|
|
1296
1418
|
let clusterPropIndex = -1;
|
|
1297
|
-
const id = (
|
|
1298
|
-
for (
|
|
1299
|
-
const
|
|
1419
|
+
const id = (index << 5) + (zoom + 1) + this.points.length;
|
|
1420
|
+
for (let n = 0; n < numNeighbors; n++) {
|
|
1421
|
+
const neighborIndex = neighborIds[n];
|
|
1422
|
+
const k = neighborIndex * stride;
|
|
1300
1423
|
if (data[k + OFFSET_ZOOM] <= zoom) continue;
|
|
1301
1424
|
data[k + OFFSET_ZOOM] = zoom;
|
|
1302
1425
|
const numPoints2 = data[k + OFFSET_NUM];
|
|
1303
|
-
wx +=
|
|
1304
|
-
wy +=
|
|
1426
|
+
wx += coords[neighborIndex * 2] * numPoints2;
|
|
1427
|
+
wy += coords[neighborIndex * 2 + 1] * numPoints2;
|
|
1305
1428
|
data[k + OFFSET_PARENT] = id;
|
|
1306
1429
|
if (reduce) {
|
|
1307
1430
|
if (!clusterProperties) {
|
|
@@ -1313,19 +1436,33 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
1313
1436
|
}
|
|
1314
1437
|
}
|
|
1315
1438
|
data[i + OFFSET_PARENT] = id;
|
|
1316
|
-
|
|
1317
|
-
|
|
1439
|
+
nextCoords[nextNumItems * 2] = Math.round(wx / numPoints);
|
|
1440
|
+
nextCoords[nextNumItems * 2 + 1] = Math.round(wy / numPoints);
|
|
1441
|
+
const next = nextNumItems * stride;
|
|
1442
|
+
nextData[next + OFFSET_ZOOM] = notProcessed;
|
|
1443
|
+
nextData[next + OFFSET_ID] = id;
|
|
1444
|
+
nextData[next + OFFSET_PARENT] = -1;
|
|
1445
|
+
nextData[next + OFFSET_NUM] = numPoints;
|
|
1446
|
+
if (reduce) nextData[next + OFFSET_PROP] = clusterPropIndex;
|
|
1447
|
+
nextNumItems++;
|
|
1318
1448
|
} else {
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1449
|
+
nextCoords[nextNumItems * 2] = x;
|
|
1450
|
+
nextCoords[nextNumItems * 2 + 1] = y;
|
|
1451
|
+
for (let j = 0; j < stride; j++) nextData[nextNumItems * stride + j] = data[i + j];
|
|
1452
|
+
nextNumItems++;
|
|
1453
|
+
if (numPoints > 1) for (let n = 0; n < numNeighbors; n++) {
|
|
1454
|
+
const neighborIndex = neighborIds[n];
|
|
1455
|
+
const k = neighborIndex * stride;
|
|
1322
1456
|
if (data[k + OFFSET_ZOOM] <= zoom) continue;
|
|
1323
1457
|
data[k + OFFSET_ZOOM] = zoom;
|
|
1324
|
-
|
|
1458
|
+
nextCoords[nextNumItems * 2] = coords[neighborIndex * 2];
|
|
1459
|
+
nextCoords[nextNumItems * 2 + 1] = coords[neighborIndex * 2 + 1];
|
|
1460
|
+
for (let j = 0; j < stride; j++) nextData[nextNumItems * stride + j] = data[k + j];
|
|
1461
|
+
nextNumItems++;
|
|
1325
1462
|
}
|
|
1326
1463
|
}
|
|
1327
1464
|
}
|
|
1328
|
-
return
|
|
1465
|
+
return nextNumItems;
|
|
1329
1466
|
}
|
|
1330
1467
|
getOriginId(clusterId) {
|
|
1331
1468
|
return clusterId - this.points.length >> 5;
|
|
@@ -1343,22 +1480,28 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
1343
1480
|
return clone && result === original ? Object.assign({}, result) : result;
|
|
1344
1481
|
}
|
|
1345
1482
|
};
|
|
1346
|
-
function
|
|
1483
|
+
function createOptions(options) {
|
|
1484
|
+
const result = Object.assign(Object.create(defaultClusterOptions), options);
|
|
1485
|
+
result.minZoom = Math.min(result.minZoom, MAX_ZOOM);
|
|
1486
|
+
result.maxZoom = Math.min(result.maxZoom, MAX_ZOOM);
|
|
1487
|
+
return result;
|
|
1488
|
+
}
|
|
1489
|
+
function getClusterFeature(coords, index, data, i, clusterProps) {
|
|
1347
1490
|
return {
|
|
1348
1491
|
id: data[i + OFFSET_ID],
|
|
1349
1492
|
type: "Point",
|
|
1350
1493
|
tags: getClusterProperties(data, i, clusterProps),
|
|
1351
|
-
geometry: [
|
|
1494
|
+
geometry: [decode(coords[index * 2]), decode(coords[index * 2 + 1])]
|
|
1352
1495
|
};
|
|
1353
1496
|
}
|
|
1354
|
-
function getClusterGeoJSON(data, i, clusterProps) {
|
|
1497
|
+
function getClusterGeoJSON(coords, index, data, i, clusterProps, worldCoordinates) {
|
|
1355
1498
|
return {
|
|
1356
1499
|
type: "Feature",
|
|
1357
1500
|
id: data[i + OFFSET_ID],
|
|
1358
1501
|
properties: getClusterProperties(data, i, clusterProps),
|
|
1359
1502
|
geometry: {
|
|
1360
1503
|
type: "Point",
|
|
1361
|
-
coordinates: [unprojectX(
|
|
1504
|
+
coordinates: [unprojectX(decode(coords[index * 2]), worldCoordinates), unprojectY(decode(coords[index * 2 + 1]), worldCoordinates)]
|
|
1362
1505
|
}
|
|
1363
1506
|
};
|
|
1364
1507
|
}
|
|
@@ -1374,6 +1517,9 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
1374
1517
|
point_count_abbreviated: abbrev
|
|
1375
1518
|
});
|
|
1376
1519
|
}
|
|
1520
|
+
function isInWorld(geometry) {
|
|
1521
|
+
return geometry[0] >= 0 && geometry[0] <= 1 && geometry[1] >= 0 && geometry[1] <= 1;
|
|
1522
|
+
}
|
|
1377
1523
|
|
|
1378
1524
|
//#endregion
|
|
1379
1525
|
//#region src/tile.ts
|
|
@@ -1585,7 +1731,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
1585
1731
|
this.total = 0;
|
|
1586
1732
|
}
|
|
1587
1733
|
initialize(features) {
|
|
1588
|
-
this.splitTile(features, 0, 0, 0);
|
|
1734
|
+
this.splitTile(this.clipToWorld(features), 0, 0, 0);
|
|
1589
1735
|
if (this.options.debug) {
|
|
1590
1736
|
if (features.length) console.log("features: %d, points: %d", this.tiles[0].numFeatures, this.tiles[0].numPoints);
|
|
1591
1737
|
console.timeEnd("generate tiles");
|
|
@@ -1605,8 +1751,9 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
1605
1751
|
0,
|
|
1606
1752
|
0
|
|
1607
1753
|
];
|
|
1608
|
-
const
|
|
1609
|
-
rootTile
|
|
1754
|
+
const rootSource = this.clipToWorld(source);
|
|
1755
|
+
const rootTile = createTile(rootSource, z, x, y, options);
|
|
1756
|
+
rootTile.source = rootSource;
|
|
1610
1757
|
const id = toID(z, x, y);
|
|
1611
1758
|
this.tiles[id] = rootTile;
|
|
1612
1759
|
this.tileCoords.push({
|
|
@@ -1637,6 +1784,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
1637
1784
|
getTile(z, x, y) {
|
|
1638
1785
|
const { extent, debug } = this.options;
|
|
1639
1786
|
const z2 = 1 << z;
|
|
1787
|
+
if (this.options.worldCoordinates && (x < 0 || x >= z2)) return null;
|
|
1640
1788
|
x = x + z2 & z2 - 1;
|
|
1641
1789
|
const id = toID(z, x, y);
|
|
1642
1790
|
if (this.tiles[id]) return transformTile(this.tiles[id], extent);
|
|
@@ -1662,6 +1810,17 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
1662
1810
|
return transformTile(this.tiles[id], extent);
|
|
1663
1811
|
}
|
|
1664
1812
|
/**
|
|
1813
|
+
* The features the top tile starts from: all of them in a wrapping world, where wrapping already put them inside,
|
|
1814
|
+
* and only what lies inside the world square and its buffer with `worldCoordinates`. The index keeps the rest, so
|
|
1815
|
+
* an update can still move a feature in from outside.
|
|
1816
|
+
*/
|
|
1817
|
+
clipToWorld(features) {
|
|
1818
|
+
if (!this.options.worldCoordinates) return features;
|
|
1819
|
+
const buffer = this.options.buffer / this.options.extent;
|
|
1820
|
+
const inX = clip(features, 1, -buffer, 1 + buffer, 0, -Infinity, Infinity, this.options) || [];
|
|
1821
|
+
return clip(inX, 1, -buffer, 1 + buffer, 1, -Infinity, Infinity, this.options) || [];
|
|
1822
|
+
}
|
|
1823
|
+
/**
|
|
1665
1824
|
* splits features from a parent tile to sub-tiles.
|
|
1666
1825
|
* z, x, and y are the coordinates of the parent tile
|
|
1667
1826
|
* cz, cx, and cy are the coordinates of the target tile
|
|
@@ -1815,7 +1974,8 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
1815
1974
|
updateable: false,
|
|
1816
1975
|
cluster: false,
|
|
1817
1976
|
clusterOptions: defaultClusterOptions,
|
|
1818
|
-
debug: 0
|
|
1977
|
+
debug: 0,
|
|
1978
|
+
worldCoordinates: false
|
|
1819
1979
|
};
|
|
1820
1980
|
/**
|
|
1821
1981
|
* Main class for creating and managing a vector tile index from GeoJSON data.
|
|
@@ -1838,7 +1998,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
1838
1998
|
this.initializeIndex(features, options);
|
|
1839
1999
|
}
|
|
1840
2000
|
initializeIndex(features, options) {
|
|
1841
|
-
this.tileIndex = options.cluster ? new ClusterTileIndex(options.clusterOptions) : new TileIndex(options);
|
|
2001
|
+
this.tileIndex = options.cluster ? new ClusterTileIndex(options.clusterOptions, options.worldCoordinates) : new TileIndex(options);
|
|
1842
2002
|
if (!features.length) return;
|
|
1843
2003
|
this.tileIndex.initialize(features);
|
|
1844
2004
|
}
|
|
@@ -1876,7 +2036,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
1876
2036
|
const removeIds = /* @__PURE__ */ new Set();
|
|
1877
2037
|
for (const feature of source) {
|
|
1878
2038
|
if (feature.id == void 0) continue;
|
|
1879
|
-
if (predicate(featureToGeoJSON(feature))) continue;
|
|
2039
|
+
if (predicate(featureToGeoJSON(feature, this.options.worldCoordinates))) continue;
|
|
1880
2040
|
affected.push(feature);
|
|
1881
2041
|
removeIds.add(feature.id);
|
|
1882
2042
|
}
|
|
@@ -1891,7 +2051,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
1891
2051
|
*/
|
|
1892
2052
|
getData() {
|
|
1893
2053
|
if (!this.options.updateable) throw new Error("to retrieve data the `updateable` option must be set to true");
|
|
1894
|
-
return convertToGeoJSON(this.source);
|
|
2054
|
+
return convertToGeoJSON(this.source, this.options.worldCoordinates);
|
|
1895
2055
|
}
|
|
1896
2056
|
/**
|
|
1897
2057
|
* Update supercluster options and regenerate the index.
|
|
@@ -1981,7 +2141,8 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
1981
2141
|
if (shouldClip || options.lineMetrics) {
|
|
1982
2142
|
const pow2 = 1 << z;
|
|
1983
2143
|
const buffer = options.buffer / options.extent;
|
|
1984
|
-
|
|
2144
|
+
const left = clip(features, pow2, x - buffer, x + 1 + buffer, 0, -1, 2, options);
|
|
2145
|
+
features = clip(left || [], pow2, y - buffer, y + 1 + buffer, 1, -1, 2, options);
|
|
1985
2146
|
}
|
|
1986
2147
|
return transformTile(createTile(features ?? [], z, x, y, options), options.extent);
|
|
1987
2148
|
}
|