@maplibre/geojson-vt 6.1.2 → 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 +286 -135
- package/dist/geojson-vt.js +1 -1
- package/dist/geojson-vt.mjs +285 -135
- package/dist/geojson-vt.mjs.map +1 -1
- package/dist/geojsonvt.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 +5 -5
- 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/tile-index.ts +17 -3
- package/src/wrap.ts +2 -0
package/dist/geojson-vt-dev.js
CHANGED
|
@@ -170,24 +170,25 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
170
170
|
if (!geojson.geometry.coordinates?.length) return;
|
|
171
171
|
const id = getFeatureId(geojson, options, index);
|
|
172
172
|
const tolerance = Math.pow(options.tolerance / ((1 << options.maxZoom) * options.extent), 2);
|
|
173
|
+
const worldCoordinates = options.worldCoordinates === true;
|
|
173
174
|
switch (geojson.geometry.type) {
|
|
174
175
|
case "Point":
|
|
175
|
-
convertPointFeature(features, id, geojson.geometry, geojson.properties);
|
|
176
|
+
convertPointFeature(features, id, geojson.geometry, worldCoordinates, geojson.properties);
|
|
176
177
|
return;
|
|
177
178
|
case "MultiPoint":
|
|
178
|
-
convertMultiPointFeature(features, id, geojson.geometry, geojson.properties);
|
|
179
|
+
convertMultiPointFeature(features, id, geojson.geometry, worldCoordinates, geojson.properties);
|
|
179
180
|
return;
|
|
180
181
|
case "LineString":
|
|
181
|
-
convertLineStringFeature(features, id, geojson.geometry, tolerance, geojson.properties);
|
|
182
|
+
convertLineStringFeature(features, id, geojson.geometry, tolerance, worldCoordinates, geojson.properties);
|
|
182
183
|
return;
|
|
183
184
|
case "MultiLineString":
|
|
184
|
-
convertMultiLineStringFeature(features, id, geojson.geometry, tolerance, options, geojson.properties);
|
|
185
|
+
convertMultiLineStringFeature(features, id, geojson.geometry, tolerance, worldCoordinates, options, geojson.properties);
|
|
185
186
|
return;
|
|
186
187
|
case "Polygon":
|
|
187
|
-
convertPolygonFeature(features, id, geojson.geometry, tolerance, geojson.properties);
|
|
188
|
+
convertPolygonFeature(features, id, geojson.geometry, tolerance, worldCoordinates, geojson.properties);
|
|
188
189
|
return;
|
|
189
190
|
case "MultiPolygon":
|
|
190
|
-
convertMultiPolygonFeature(features, id, geojson.geometry, tolerance, geojson.properties);
|
|
191
|
+
convertMultiPolygonFeature(features, id, geojson.geometry, tolerance, worldCoordinates, geojson.properties);
|
|
191
192
|
return;
|
|
192
193
|
default: throw new Error("Input data is not a valid GeoJSON object.");
|
|
193
194
|
}
|
|
@@ -205,53 +206,55 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
205
206
|
properties: geojson.properties
|
|
206
207
|
}, options, index, depth);
|
|
207
208
|
}
|
|
208
|
-
function convertPointFeature(features, id, geom, properties) {
|
|
209
|
+
function convertPointFeature(features, id, geom, worldCoordinates, properties) {
|
|
209
210
|
const out = [];
|
|
210
|
-
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);
|
|
211
212
|
features.push(createFeature(id, "Point", out, properties));
|
|
212
213
|
}
|
|
213
|
-
function convertMultiPointFeature(features, id, geom, properties) {
|
|
214
|
+
function convertMultiPointFeature(features, id, geom, worldCoordinates, properties) {
|
|
214
215
|
const out = [];
|
|
215
|
-
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);
|
|
216
217
|
features.push(createFeature(id, "MultiPoint", out, properties));
|
|
217
218
|
}
|
|
218
|
-
function convertLineStringFeature(features, id, geom, tolerance, properties) {
|
|
219
|
+
function convertLineStringFeature(features, id, geom, tolerance, worldCoordinates, properties) {
|
|
219
220
|
const out = { points: [] };
|
|
220
|
-
convertLine(geom.coordinates, out, tolerance, false);
|
|
221
|
+
convertLine(geom.coordinates, out, tolerance, false, worldCoordinates);
|
|
221
222
|
features.push(createFeature(id, "LineString", out, properties));
|
|
222
223
|
}
|
|
223
|
-
function convertMultiLineStringFeature(features, id, geom, tolerance, options, properties) {
|
|
224
|
+
function convertMultiLineStringFeature(features, id, geom, tolerance, worldCoordinates, options, properties) {
|
|
224
225
|
if (options.lineMetrics) for (const line of geom.coordinates) {
|
|
225
226
|
const out = { points: [] };
|
|
226
|
-
convertLine(line, out, tolerance, false);
|
|
227
|
+
convertLine(line, out, tolerance, false, worldCoordinates);
|
|
227
228
|
features.push(createFeature(id, "LineString", out, properties));
|
|
228
229
|
}
|
|
229
230
|
else {
|
|
230
231
|
const out = [];
|
|
231
|
-
convertLines(geom.coordinates, out, tolerance, false);
|
|
232
|
+
convertLines(geom.coordinates, out, tolerance, false, worldCoordinates);
|
|
232
233
|
features.push(createFeature(id, "MultiLineString", out, properties));
|
|
233
234
|
}
|
|
234
235
|
}
|
|
235
|
-
function convertPolygonFeature(features, id, geom, tolerance, properties) {
|
|
236
|
+
function convertPolygonFeature(features, id, geom, tolerance, worldCoordinates, properties) {
|
|
236
237
|
const out = [];
|
|
237
|
-
convertLines(geom.coordinates, out, tolerance, true);
|
|
238
|
+
convertLines(geom.coordinates, out, tolerance, true, worldCoordinates);
|
|
238
239
|
features.push(createFeature(id, "Polygon", out, properties));
|
|
239
240
|
}
|
|
240
|
-
function convertMultiPolygonFeature(features, id, geom, tolerance, properties) {
|
|
241
|
+
function convertMultiPolygonFeature(features, id, geom, tolerance, worldCoordinates, properties) {
|
|
241
242
|
const out = [];
|
|
242
243
|
for (const polygon of geom.coordinates) {
|
|
243
244
|
const polygonOut = [];
|
|
244
|
-
convertLines(polygon, polygonOut, tolerance, true);
|
|
245
|
+
convertLines(polygon, polygonOut, tolerance, true, worldCoordinates);
|
|
246
|
+
if (!polygonOut.length) continue;
|
|
245
247
|
out.push(polygonOut);
|
|
246
248
|
}
|
|
249
|
+
if (!out.length) return;
|
|
247
250
|
features.push(createFeature(id, "MultiPolygon", out, properties));
|
|
248
251
|
}
|
|
249
|
-
function convertLine(ring, out, tolerance, isPolygon) {
|
|
252
|
+
function convertLine(ring, out, tolerance, isPolygon, worldCoordinates) {
|
|
250
253
|
let x0, y0;
|
|
251
254
|
let size = 0;
|
|
252
255
|
for (let j = 0; j < ring.length; j++) {
|
|
253
|
-
const x = projectX(ring[j][0]);
|
|
254
|
-
const y = projectY(ring[j][1]);
|
|
256
|
+
const x = projectX(ring[j][0], worldCoordinates);
|
|
257
|
+
const y = projectY(ring[j][1], worldCoordinates);
|
|
255
258
|
out.points.push(x, y, 0);
|
|
256
259
|
if (j > 0) {
|
|
257
260
|
if (isPolygon) size += (x0 * y - x * y0) / 2;
|
|
@@ -269,23 +272,25 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
269
272
|
out.start = 0;
|
|
270
273
|
out.end = out.size;
|
|
271
274
|
}
|
|
272
|
-
function convertLines(rings, out, tolerance, isPolygon) {
|
|
275
|
+
function convertLines(rings, out, tolerance, isPolygon, worldCoordinates) {
|
|
273
276
|
for (let i = 0; i < rings.length; i++) {
|
|
274
277
|
const geom = { points: [] };
|
|
275
|
-
convertLine(rings[i], geom, tolerance, isPolygon);
|
|
278
|
+
convertLine(rings[i], geom, tolerance, isPolygon, worldCoordinates);
|
|
276
279
|
out.push(geom);
|
|
277
280
|
}
|
|
278
281
|
}
|
|
279
282
|
/**
|
|
280
|
-
* 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
|
|
281
284
|
*/
|
|
282
|
-
function projectX(x) {
|
|
285
|
+
function projectX(x, worldCoordinates = false) {
|
|
286
|
+
if (worldCoordinates) return x;
|
|
283
287
|
return x / 360 + .5;
|
|
284
288
|
}
|
|
285
289
|
/**
|
|
286
|
-
* 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
|
|
287
291
|
*/
|
|
288
|
-
function projectY(y) {
|
|
292
|
+
function projectY(y, worldCoordinates = false) {
|
|
293
|
+
if (worldCoordinates) return y;
|
|
289
294
|
const sin = Math.sin(y * Math.PI / 180);
|
|
290
295
|
const y2 = .5 - .25 * Math.log((1 + sin) / (1 - sin)) / Math.PI;
|
|
291
296
|
return y2 < 0 ? 0 : y2 > 1 ? 1 : y2;
|
|
@@ -296,19 +301,19 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
296
301
|
/**
|
|
297
302
|
* Converts internal source features back to GeoJSON format.
|
|
298
303
|
*/
|
|
299
|
-
function convertToGeoJSON(source) {
|
|
304
|
+
function convertToGeoJSON(source, worldCoordinates = false) {
|
|
300
305
|
return {
|
|
301
306
|
type: "FeatureCollection",
|
|
302
|
-
features: source.map((feature) => featureToGeoJSON(feature))
|
|
307
|
+
features: source.map((feature) => featureToGeoJSON(feature, worldCoordinates))
|
|
303
308
|
};
|
|
304
309
|
}
|
|
305
310
|
/**
|
|
306
311
|
* Converts a single internal feature to GeoJSON format.
|
|
307
312
|
*/
|
|
308
|
-
function featureToGeoJSON(feature) {
|
|
313
|
+
function featureToGeoJSON(feature, worldCoordinates = false) {
|
|
309
314
|
const geojsonFeature = {
|
|
310
315
|
type: "Feature",
|
|
311
|
-
geometry: geometryToGeoJSON(feature),
|
|
316
|
+
geometry: geometryToGeoJSON(feature, worldCoordinates),
|
|
312
317
|
properties: feature.tags
|
|
313
318
|
};
|
|
314
319
|
if (feature.id != null) geojsonFeature.id = feature.id;
|
|
@@ -317,50 +322,52 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
317
322
|
/**
|
|
318
323
|
* Converts a single internal feature geometry to GeoJSON format.
|
|
319
324
|
*/
|
|
320
|
-
function geometryToGeoJSON(feature) {
|
|
325
|
+
function geometryToGeoJSON(feature, worldCoordinates) {
|
|
321
326
|
const { type, geometry } = feature;
|
|
322
327
|
switch (type) {
|
|
323
328
|
case "Point": return {
|
|
324
329
|
type,
|
|
325
|
-
coordinates: unprojectPoint(geometry[0], geometry[1])
|
|
330
|
+
coordinates: unprojectPoint(geometry[0], geometry[1], worldCoordinates)
|
|
326
331
|
};
|
|
327
332
|
case "MultiPoint": return {
|
|
328
333
|
type,
|
|
329
|
-
coordinates: unprojectPoints(geometry)
|
|
334
|
+
coordinates: unprojectPoints(geometry, worldCoordinates)
|
|
330
335
|
};
|
|
331
336
|
case "LineString": return {
|
|
332
337
|
type,
|
|
333
|
-
coordinates: unprojectPoints(geometry.points)
|
|
338
|
+
coordinates: unprojectPoints(geometry.points, worldCoordinates)
|
|
334
339
|
};
|
|
335
340
|
case "MultiLineString":
|
|
336
341
|
case "Polygon": return {
|
|
337
342
|
type,
|
|
338
|
-
coordinates: geometry.map((ring) => unprojectPoints(ring.points))
|
|
343
|
+
coordinates: geometry.map((ring) => unprojectPoints(ring.points, worldCoordinates))
|
|
339
344
|
};
|
|
340
345
|
case "MultiPolygon": return {
|
|
341
346
|
type,
|
|
342
|
-
coordinates: geometry.map((polygon) => polygon.map((ring) => unprojectPoints(ring.points)))
|
|
347
|
+
coordinates: geometry.map((polygon) => polygon.map((ring) => unprojectPoints(ring.points, worldCoordinates)))
|
|
343
348
|
};
|
|
344
349
|
}
|
|
345
350
|
}
|
|
346
|
-
function unprojectPoints(coords) {
|
|
351
|
+
function unprojectPoints(coords, worldCoordinates = false) {
|
|
347
352
|
const result = [];
|
|
348
|
-
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));
|
|
349
354
|
return result;
|
|
350
355
|
}
|
|
351
|
-
function unprojectPoint(x, y) {
|
|
352
|
-
return [unprojectX(x), unprojectY(y)];
|
|
356
|
+
function unprojectPoint(x, y, worldCoordinates) {
|
|
357
|
+
return [unprojectX(x, worldCoordinates), unprojectY(y, worldCoordinates)];
|
|
353
358
|
}
|
|
354
359
|
/**
|
|
355
|
-
* 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
|
|
356
361
|
*/
|
|
357
|
-
function unprojectX(x) {
|
|
362
|
+
function unprojectX(x, worldCoordinates = false) {
|
|
363
|
+
if (worldCoordinates) return x;
|
|
358
364
|
return (x - .5) * 360;
|
|
359
365
|
}
|
|
360
366
|
/**
|
|
361
|
-
* 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
|
|
362
368
|
*/
|
|
363
|
-
function unprojectY(y) {
|
|
369
|
+
function unprojectY(y, worldCoordinates = false) {
|
|
370
|
+
if (worldCoordinates) return y;
|
|
364
371
|
const y2 = (180 - y * 360) * Math.PI / 180;
|
|
365
372
|
return 360 * Math.atan(Math.exp(y2)) / Math.PI - 90;
|
|
366
373
|
}
|
|
@@ -488,12 +495,12 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
488
495
|
let exited = false;
|
|
489
496
|
if (trackMetrics) segLen = Math.sqrt(Math.pow(ax - bx, 2) + Math.pow(ay - by, 2));
|
|
490
497
|
if (a < start) {
|
|
491
|
-
if (b
|
|
498
|
+
if (b >= start) {
|
|
492
499
|
t = intersect(slice, ax, ay, bx, by, start);
|
|
493
500
|
if (trackMetrics) slice.start = len + segLen * t;
|
|
494
501
|
}
|
|
495
502
|
} else if (a > end) {
|
|
496
|
-
if (b
|
|
503
|
+
if (b <= end) {
|
|
497
504
|
t = intersect(slice, ax, ay, bx, by, end);
|
|
498
505
|
if (trackMetrics) slice.start = len + segLen * t;
|
|
499
506
|
}
|
|
@@ -554,6 +561,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
554
561
|
//#endregion
|
|
555
562
|
//#region src/wrap.ts
|
|
556
563
|
function wrap(features, options) {
|
|
564
|
+
if (options.worldCoordinates) return features;
|
|
557
565
|
const buffer = options.buffer / options.extent;
|
|
558
566
|
let merged = features;
|
|
559
567
|
const left = clip(features, 1, -1 - buffer, buffer, 0, -1, 2, options);
|
|
@@ -675,7 +683,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
675
683
|
* @param vtFeatures - the original features
|
|
676
684
|
* @param update - the update object to apply
|
|
677
685
|
* @param options - the options to use for the wrap method
|
|
678
|
-
* @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.
|
|
679
687
|
*/
|
|
680
688
|
function getUpdatedFeatures(vtFeatures, update, options) {
|
|
681
689
|
const changeGeometry = !!update.newGeometry;
|
|
@@ -691,7 +699,11 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
691
699
|
let features = convertToInternal({
|
|
692
700
|
type: "FeatureCollection",
|
|
693
701
|
features: [geojsonFeature]
|
|
694
|
-
},
|
|
702
|
+
}, {
|
|
703
|
+
...options,
|
|
704
|
+
promoteId: void 0,
|
|
705
|
+
generateId: false
|
|
706
|
+
});
|
|
695
707
|
features = wrap(features, options);
|
|
696
708
|
return features;
|
|
697
709
|
}
|
|
@@ -728,7 +740,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
728
740
|
return {
|
|
729
741
|
removeAll: diff.removeAll,
|
|
730
742
|
remove: new Set(diff.remove || []),
|
|
731
|
-
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])),
|
|
732
744
|
update: new Map(diff.update?.map((update) => [update.id, update]))
|
|
733
745
|
};
|
|
734
746
|
}
|
|
@@ -1033,52 +1045,101 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
1033
1045
|
reduce: null,
|
|
1034
1046
|
map: (props) => props
|
|
1035
1047
|
};
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
const
|
|
1040
|
-
|
|
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;
|
|
1041
1082
|
/**
|
|
1042
1083
|
* This class allow clustering of geojson points.
|
|
1043
1084
|
*/
|
|
1044
1085
|
var ClusterTileIndex = class {
|
|
1045
|
-
constructor(options) {
|
|
1046
|
-
this.options =
|
|
1086
|
+
constructor(options, worldCoordinates = false) {
|
|
1087
|
+
this.options = createOptions(options);
|
|
1088
|
+
this.worldCoordinates = worldCoordinates;
|
|
1047
1089
|
this.trees = new Array(this.options.maxZoom + 1);
|
|
1048
|
-
this.stride = this.options.reduce ?
|
|
1090
|
+
this.stride = this.options.reduce ? 5 : 4;
|
|
1049
1091
|
this.clusterProps = [];
|
|
1050
1092
|
this.points = [];
|
|
1051
1093
|
}
|
|
1052
1094
|
/**
|
|
1053
|
-
* Loads GeoJSON
|
|
1054
|
-
*
|
|
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.
|
|
1055
1098
|
*/
|
|
1056
1099
|
load(points) {
|
|
1057
1100
|
const features = [];
|
|
1058
1101
|
for (const point of points) {
|
|
1059
1102
|
if (!point.geometry) continue;
|
|
1060
|
-
const
|
|
1061
|
-
const [
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
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
|
+
}
|
|
1069
1115
|
}
|
|
1070
1116
|
this.createIndex(features);
|
|
1071
1117
|
}
|
|
1072
1118
|
/**
|
|
1073
1119
|
* @internal
|
|
1074
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.
|
|
1075
1122
|
* @param features - {@link GeoJSONVTInternalFeature} data source features to filter and cluster.
|
|
1076
1123
|
*/
|
|
1077
1124
|
initialize(features) {
|
|
1078
1125
|
const points = [];
|
|
1079
1126
|
for (const feature of features) {
|
|
1080
|
-
if (feature.type
|
|
1081
|
-
|
|
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
|
+
}
|
|
1082
1143
|
}
|
|
1083
1144
|
this.createIndex(points);
|
|
1084
1145
|
}
|
|
@@ -1088,41 +1149,59 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
1088
1149
|
* @param features
|
|
1089
1150
|
*/
|
|
1090
1151
|
updateIndex(features, _affected, options) {
|
|
1091
|
-
this.options =
|
|
1152
|
+
this.options = createOptions(options.clusterOptions);
|
|
1092
1153
|
this.initialize(features);
|
|
1093
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
|
+
*/
|
|
1094
1159
|
createIndex(points) {
|
|
1095
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 = [];
|
|
1096
1164
|
if (log) console.time("total time");
|
|
1097
1165
|
const timerId = `prepare ${points.length} points`;
|
|
1098
1166
|
if (log) console.time(timerId);
|
|
1099
1167
|
this.points = points;
|
|
1100
|
-
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;
|
|
1101
1173
|
for (let i = 0; i < points.length; i++) {
|
|
1102
1174
|
const p = points[i];
|
|
1103
|
-
if (!p?.geometry) continue;
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
data
|
|
1108
|
-
|
|
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++;
|
|
1109
1184
|
}
|
|
1110
|
-
let tree = this.trees[maxZoom + 1] = this.createTree(data);
|
|
1185
|
+
let tree = this.trees[maxZoom + 1] = this.createTree(coords, data, numItems);
|
|
1111
1186
|
if (log) console.timeEnd(timerId);
|
|
1187
|
+
const nextCoords = new Int32Array(numItems * 2);
|
|
1188
|
+
const nextData = new Int32Array(numItems * stride);
|
|
1112
1189
|
for (let z = maxZoom; z >= minZoom; z--) {
|
|
1113
1190
|
const now = Date.now();
|
|
1114
|
-
|
|
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;
|
|
1115
1194
|
if (log) console.log("z%d: %d clusters in %dms", z, tree.numItems, Date.now() - now);
|
|
1116
1195
|
}
|
|
1117
1196
|
if (log) console.timeEnd("total time");
|
|
1118
1197
|
}
|
|
1119
1198
|
/**
|
|
1120
1199
|
* Returns clusters and/or points within a bounding box at a given zoom level.
|
|
1121
|
-
* @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.
|
|
1122
1201
|
* @param zoom - Zoom level to query.
|
|
1123
1202
|
*/
|
|
1124
1203
|
getClusters(bbox, zoom) {
|
|
1125
|
-
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));
|
|
1126
1205
|
}
|
|
1127
1206
|
getClustersInternal(bbox, zoom) {
|
|
1128
1207
|
let minLng = ((bbox[0] + 180) % 360 + 360) % 360 - 180;
|
|
@@ -1147,13 +1226,25 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
1147
1226
|
], zoom);
|
|
1148
1227
|
return easternHem.concat(westernHem);
|
|
1149
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) {
|
|
1150
1240
|
const tree = this.trees[this.limitZoom(zoom)];
|
|
1151
|
-
const ids = tree.range(
|
|
1241
|
+
const ids = tree.range(encode(minX), encode(minY), encode(maxX), encode(maxY));
|
|
1242
|
+
const coords = tree.flatCoords;
|
|
1152
1243
|
const data = tree.flatData;
|
|
1153
1244
|
const clusters = [];
|
|
1154
1245
|
for (const id of ids) {
|
|
1155
1246
|
const k = this.stride * id;
|
|
1156
|
-
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]]);
|
|
1157
1248
|
}
|
|
1158
1249
|
return clusters;
|
|
1159
1250
|
}
|
|
@@ -1167,16 +1258,17 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
1167
1258
|
const clusterError = /* @__PURE__ */ new Error("No cluster with the specified id: " + clusterId);
|
|
1168
1259
|
const tree = this.trees[originZoom];
|
|
1169
1260
|
if (!tree) throw clusterError;
|
|
1261
|
+
const coords = tree.flatCoords;
|
|
1170
1262
|
const data = tree.flatData;
|
|
1171
|
-
if (originId
|
|
1172
|
-
const r = this.options.radius / (this.options.extent * Math.pow(2, originZoom - 1));
|
|
1173
|
-
const x =
|
|
1174
|
-
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];
|
|
1175
1267
|
const ids = tree.within(x, y, r);
|
|
1176
1268
|
const children = [];
|
|
1177
1269
|
for (const id of ids) {
|
|
1178
1270
|
const k = id * this.stride;
|
|
1179
|
-
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));
|
|
1180
1272
|
}
|
|
1181
1273
|
if (children.length === 0) throw clusterError;
|
|
1182
1274
|
return children;
|
|
@@ -1204,10 +1296,11 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
1204
1296
|
const tree = this.trees[this.limitZoom(z)];
|
|
1205
1297
|
if (!tree) return null;
|
|
1206
1298
|
const z2 = Math.pow(2, z);
|
|
1299
|
+
if (this.worldCoordinates && (x < 0 || x >= z2 || y < 0 || y >= z2)) return null;
|
|
1207
1300
|
const { extent, radius } = this.options;
|
|
1208
1301
|
const p = radius / extent;
|
|
1209
|
-
const top = (y - p) / z2;
|
|
1210
|
-
const bottom = (y + 1 + p) / z2;
|
|
1302
|
+
const top = encode((y - p) / z2);
|
|
1303
|
+
const bottom = encode((y + 1 + p) / z2);
|
|
1211
1304
|
const tile = {
|
|
1212
1305
|
transformed: true,
|
|
1213
1306
|
features: [],
|
|
@@ -1216,9 +1309,9 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
1216
1309
|
y,
|
|
1217
1310
|
z
|
|
1218
1311
|
};
|
|
1219
|
-
this.addTileFeatures(tree.range((x - p) / z2, top, (x + 1 + p) / z2, bottom), tree
|
|
1220
|
-
if (x === 0) this.addTileFeatures(tree.range(1 - p / z2, top, 1, bottom), tree
|
|
1221
|
-
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);
|
|
1222
1315
|
return tile;
|
|
1223
1316
|
}
|
|
1224
1317
|
/**
|
|
@@ -1241,15 +1334,27 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
1241
1334
|
}
|
|
1242
1335
|
return skipped;
|
|
1243
1336
|
}
|
|
1244
|
-
createTree(data) {
|
|
1245
|
-
const tree = new KDBush(
|
|
1246
|
-
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]);
|
|
1247
1340
|
tree.finish();
|
|
1341
|
+
tree.flatCoords = coords;
|
|
1248
1342
|
tree.flatData = data;
|
|
1249
1343
|
tree.data = null;
|
|
1250
1344
|
return tree;
|
|
1251
1345
|
}
|
|
1252
|
-
|
|
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;
|
|
1253
1358
|
for (const i of ids) {
|
|
1254
1359
|
const k = i * this.stride;
|
|
1255
1360
|
const isCluster = data[k + OFFSET_NUM] > 1;
|
|
@@ -1258,8 +1363,8 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
1258
1363
|
let py;
|
|
1259
1364
|
if (isCluster) {
|
|
1260
1365
|
tags = getClusterProperties(data, k, this.clusterProps);
|
|
1261
|
-
px =
|
|
1262
|
-
py =
|
|
1366
|
+
px = decode(coords[i * 2]);
|
|
1367
|
+
py = decode(coords[i * 2 + 1]);
|
|
1263
1368
|
} else {
|
|
1264
1369
|
const p = this.points[data[k + OFFSET_ID]];
|
|
1265
1370
|
tags = p.tags;
|
|
@@ -1280,22 +1385,30 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
1280
1385
|
limitZoom(z) {
|
|
1281
1386
|
return Math.max(this.options.minZoom, Math.min(Math.floor(+z), this.options.maxZoom + 1));
|
|
1282
1387
|
}
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
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;
|
|
1286
1397
|
const data = tree.flatData;
|
|
1287
|
-
const nextData = [];
|
|
1288
1398
|
const stride = this.stride;
|
|
1289
|
-
|
|
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;
|
|
1290
1403
|
if (data[i + OFFSET_ZOOM] <= zoom) continue;
|
|
1291
1404
|
data[i + OFFSET_ZOOM] = zoom;
|
|
1292
|
-
const x =
|
|
1293
|
-
const y =
|
|
1294
|
-
const
|
|
1405
|
+
const x = coords[index * 2];
|
|
1406
|
+
const y = coords[index * 2 + 1];
|
|
1407
|
+
const numNeighbors = tree.withinInto(x, y, r, neighborIds);
|
|
1295
1408
|
const numPointsOrigin = data[i + OFFSET_NUM];
|
|
1296
1409
|
let numPoints = numPointsOrigin;
|
|
1297
|
-
for (
|
|
1298
|
-
const k =
|
|
1410
|
+
for (let n = 0; n < numNeighbors; n++) {
|
|
1411
|
+
const k = neighborIds[n] * stride;
|
|
1299
1412
|
if (data[k + OFFSET_ZOOM] > zoom) numPoints += data[k + OFFSET_NUM];
|
|
1300
1413
|
}
|
|
1301
1414
|
if (numPoints > numPointsOrigin && numPoints >= minPoints) {
|
|
@@ -1303,14 +1416,15 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
1303
1416
|
let wy = y * numPointsOrigin;
|
|
1304
1417
|
let clusterProperties;
|
|
1305
1418
|
let clusterPropIndex = -1;
|
|
1306
|
-
const id = (
|
|
1307
|
-
for (
|
|
1308
|
-
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;
|
|
1309
1423
|
if (data[k + OFFSET_ZOOM] <= zoom) continue;
|
|
1310
1424
|
data[k + OFFSET_ZOOM] = zoom;
|
|
1311
1425
|
const numPoints2 = data[k + OFFSET_NUM];
|
|
1312
|
-
wx +=
|
|
1313
|
-
wy +=
|
|
1426
|
+
wx += coords[neighborIndex * 2] * numPoints2;
|
|
1427
|
+
wy += coords[neighborIndex * 2 + 1] * numPoints2;
|
|
1314
1428
|
data[k + OFFSET_PARENT] = id;
|
|
1315
1429
|
if (reduce) {
|
|
1316
1430
|
if (!clusterProperties) {
|
|
@@ -1322,19 +1436,33 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
1322
1436
|
}
|
|
1323
1437
|
}
|
|
1324
1438
|
data[i + OFFSET_PARENT] = id;
|
|
1325
|
-
|
|
1326
|
-
|
|
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++;
|
|
1327
1448
|
} else {
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
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;
|
|
1331
1456
|
if (data[k + OFFSET_ZOOM] <= zoom) continue;
|
|
1332
1457
|
data[k + OFFSET_ZOOM] = zoom;
|
|
1333
|
-
|
|
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++;
|
|
1334
1462
|
}
|
|
1335
1463
|
}
|
|
1336
1464
|
}
|
|
1337
|
-
return
|
|
1465
|
+
return nextNumItems;
|
|
1338
1466
|
}
|
|
1339
1467
|
getOriginId(clusterId) {
|
|
1340
1468
|
return clusterId - this.points.length >> 5;
|
|
@@ -1352,22 +1480,28 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
1352
1480
|
return clone && result === original ? Object.assign({}, result) : result;
|
|
1353
1481
|
}
|
|
1354
1482
|
};
|
|
1355
|
-
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) {
|
|
1356
1490
|
return {
|
|
1357
1491
|
id: data[i + OFFSET_ID],
|
|
1358
1492
|
type: "Point",
|
|
1359
1493
|
tags: getClusterProperties(data, i, clusterProps),
|
|
1360
|
-
geometry: [
|
|
1494
|
+
geometry: [decode(coords[index * 2]), decode(coords[index * 2 + 1])]
|
|
1361
1495
|
};
|
|
1362
1496
|
}
|
|
1363
|
-
function getClusterGeoJSON(data, i, clusterProps) {
|
|
1497
|
+
function getClusterGeoJSON(coords, index, data, i, clusterProps, worldCoordinates) {
|
|
1364
1498
|
return {
|
|
1365
1499
|
type: "Feature",
|
|
1366
1500
|
id: data[i + OFFSET_ID],
|
|
1367
1501
|
properties: getClusterProperties(data, i, clusterProps),
|
|
1368
1502
|
geometry: {
|
|
1369
1503
|
type: "Point",
|
|
1370
|
-
coordinates: [unprojectX(
|
|
1504
|
+
coordinates: [unprojectX(decode(coords[index * 2]), worldCoordinates), unprojectY(decode(coords[index * 2 + 1]), worldCoordinates)]
|
|
1371
1505
|
}
|
|
1372
1506
|
};
|
|
1373
1507
|
}
|
|
@@ -1383,6 +1517,9 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
1383
1517
|
point_count_abbreviated: abbrev
|
|
1384
1518
|
});
|
|
1385
1519
|
}
|
|
1520
|
+
function isInWorld(geometry) {
|
|
1521
|
+
return geometry[0] >= 0 && geometry[0] <= 1 && geometry[1] >= 0 && geometry[1] <= 1;
|
|
1522
|
+
}
|
|
1386
1523
|
|
|
1387
1524
|
//#endregion
|
|
1388
1525
|
//#region src/tile.ts
|
|
@@ -1594,7 +1731,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
1594
1731
|
this.total = 0;
|
|
1595
1732
|
}
|
|
1596
1733
|
initialize(features) {
|
|
1597
|
-
this.splitTile(features, 0, 0, 0);
|
|
1734
|
+
this.splitTile(this.clipToWorld(features), 0, 0, 0);
|
|
1598
1735
|
if (this.options.debug) {
|
|
1599
1736
|
if (features.length) console.log("features: %d, points: %d", this.tiles[0].numFeatures, this.tiles[0].numPoints);
|
|
1600
1737
|
console.timeEnd("generate tiles");
|
|
@@ -1614,8 +1751,9 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
1614
1751
|
0,
|
|
1615
1752
|
0
|
|
1616
1753
|
];
|
|
1617
|
-
const
|
|
1618
|
-
rootTile
|
|
1754
|
+
const rootSource = this.clipToWorld(source);
|
|
1755
|
+
const rootTile = createTile(rootSource, z, x, y, options);
|
|
1756
|
+
rootTile.source = rootSource;
|
|
1619
1757
|
const id = toID(z, x, y);
|
|
1620
1758
|
this.tiles[id] = rootTile;
|
|
1621
1759
|
this.tileCoords.push({
|
|
@@ -1646,6 +1784,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
1646
1784
|
getTile(z, x, y) {
|
|
1647
1785
|
const { extent, debug } = this.options;
|
|
1648
1786
|
const z2 = 1 << z;
|
|
1787
|
+
if (this.options.worldCoordinates && (x < 0 || x >= z2)) return null;
|
|
1649
1788
|
x = x + z2 & z2 - 1;
|
|
1650
1789
|
const id = toID(z, x, y);
|
|
1651
1790
|
if (this.tiles[id]) return transformTile(this.tiles[id], extent);
|
|
@@ -1671,6 +1810,17 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
1671
1810
|
return transformTile(this.tiles[id], extent);
|
|
1672
1811
|
}
|
|
1673
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
|
+
/**
|
|
1674
1824
|
* splits features from a parent tile to sub-tiles.
|
|
1675
1825
|
* z, x, and y are the coordinates of the parent tile
|
|
1676
1826
|
* cz, cx, and cy are the coordinates of the target tile
|
|
@@ -1824,7 +1974,8 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
1824
1974
|
updateable: false,
|
|
1825
1975
|
cluster: false,
|
|
1826
1976
|
clusterOptions: defaultClusterOptions,
|
|
1827
|
-
debug: 0
|
|
1977
|
+
debug: 0,
|
|
1978
|
+
worldCoordinates: false
|
|
1828
1979
|
};
|
|
1829
1980
|
/**
|
|
1830
1981
|
* Main class for creating and managing a vector tile index from GeoJSON data.
|
|
@@ -1847,7 +1998,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
1847
1998
|
this.initializeIndex(features, options);
|
|
1848
1999
|
}
|
|
1849
2000
|
initializeIndex(features, options) {
|
|
1850
|
-
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);
|
|
1851
2002
|
if (!features.length) return;
|
|
1852
2003
|
this.tileIndex.initialize(features);
|
|
1853
2004
|
}
|
|
@@ -1885,7 +2036,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
1885
2036
|
const removeIds = /* @__PURE__ */ new Set();
|
|
1886
2037
|
for (const feature of source) {
|
|
1887
2038
|
if (feature.id == void 0) continue;
|
|
1888
|
-
if (predicate(featureToGeoJSON(feature))) continue;
|
|
2039
|
+
if (predicate(featureToGeoJSON(feature, this.options.worldCoordinates))) continue;
|
|
1889
2040
|
affected.push(feature);
|
|
1890
2041
|
removeIds.add(feature.id);
|
|
1891
2042
|
}
|
|
@@ -1900,7 +2051,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
1900
2051
|
*/
|
|
1901
2052
|
getData() {
|
|
1902
2053
|
if (!this.options.updateable) throw new Error("to retrieve data the `updateable` option must be set to true");
|
|
1903
|
-
return convertToGeoJSON(this.source);
|
|
2054
|
+
return convertToGeoJSON(this.source, this.options.worldCoordinates);
|
|
1904
2055
|
}
|
|
1905
2056
|
/**
|
|
1906
2057
|
* Update supercluster options and regenerate the index.
|