@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.
@@ -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 (last - index > 3) simplify(coords, index, last, sqTolerance);
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) if (isPolygon) size += (x0 * y - x * y0) / 2;
252
- else size += Math.sqrt(Math.pow(x - x0, 2) + Math.pow(y - y0, 2));
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 > start) {
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 < end) {
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
- type: "Feature",
682
- id: vtFeature.id,
683
- geometry: update.newGeometry,
684
- properties: changeProps ? applyPropertyUpdates(vtFeature.tags, update) : vtFeature.tags
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, (VERSION << 4) + arrayTypeIndex]);
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
- const OFFSET_ZOOM = 2;
1029
- const OFFSET_ID = 3;
1030
- const OFFSET_PARENT = 4;
1031
- const OFFSET_NUM = 5;
1032
- const OFFSET_PROP = 6;
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 = Object.assign(Object.create(defaultClusterOptions), 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 ? 7 : 6;
1090
+ this.stride = this.options.reduce ? 5 : 4;
1041
1091
  this.clusterProps = [];
1042
1092
  this.points = [];
1043
1093
  }
1044
1094
  /**
1045
- * Loads GeoJSON point features and builds the internal clustering index.
1046
- * @param points - GeoJSON point features to cluster.
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 [lng, lat] = point.geometry.coordinates;
1053
- const [x, y] = [projectX(lng), projectY(lat)];
1054
- const feature = {
1055
- id: point.id,
1056
- type: "Point",
1057
- geometry: [x, y],
1058
- tags: point.properties
1059
- };
1060
- features.push(feature);
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 !== "Point") continue;
1073
- points.push(feature);
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 = Object.assign(Object.create(defaultClusterOptions), options.clusterOptions);
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 data = [];
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
- let [x, y] = p.geometry;
1097
- x = Math.fround(x);
1098
- y = Math.fround(y);
1099
- data.push(x, y, Infinity, i, -1, 1);
1100
- if (this.options.reduce) data.push(0);
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
- tree = this.trees[z] = this.createTree(this.cluster(tree, z));
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(projectX(minLng), projectY(maxLat), projectX(maxLng), projectY(minLat));
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 * this.stride >= data.length) throw clusterError;
1164
- const r = this.options.radius / (this.options.extent * Math.pow(2, originZoom - 1));
1165
- const x = data[originId * this.stride];
1166
- const y = data[originId * this.stride + 1];
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.flatData, x, y, z2, tile);
1212
- if (x === 0) this.addTileFeatures(tree.range(1 - p / z2, top, 1, bottom), tree.flatData, z2, y, z2, tile);
1213
- if (x === z2 - 1) this.addTileFeatures(tree.range(0, top, p / z2, bottom), tree.flatData, -1, y, z2, tile);
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) if (skipped + props.point_count <= offset) skipped += props.point_count;
1228
- else skipped = this.appendLeaves(result, props.cluster_id, limit, offset, skipped);
1229
- else if (skipped < offset) skipped++;
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(data.length / this.stride | 0, this.options.nodeSize, Float32Array);
1237
- for (let i = 0; i < data.length; i += this.stride) tree.add(data[i], data[i + 1]);
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
- addTileFeatures(ids, data, x, y, z2, tile) {
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 = data[k];
1253
- py = data[k + 1];
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
- cluster(tree, zoom) {
1275
- const { radius, extent, reduce, minPoints } = this.options;
1276
- const r = radius / (extent * Math.pow(2, zoom));
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
- for (let i = 0; i < data.length; i += stride) {
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 = data[i];
1284
- const y = data[i + 1];
1285
- const neighborIds = tree.within(data[i], data[i + 1], r);
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 (const neighborId of neighborIds) {
1289
- const k = neighborId * stride;
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 = ((i / stride | 0) << 5) + (zoom + 1) + this.points.length;
1298
- for (const neighborId of neighborIds) {
1299
- const k = neighborId * stride;
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 += data[k] * numPoints2;
1304
- wy += data[k + 1] * numPoints2;
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
- nextData.push(wx / numPoints, wy / numPoints, Infinity, id, -1, numPoints);
1317
- if (reduce) nextData.push(clusterPropIndex);
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
- for (let j = 0; j < stride; j++) nextData.push(data[i + j]);
1320
- if (numPoints > 1) for (const neighborId of neighborIds) {
1321
- const k = neighborId * stride;
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
- for (let j = 0; j < stride; j++) nextData.push(data[k + j]);
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 nextData;
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 getClusterFeature(data, i, clusterProps) {
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: [data[i], data[i + 1]]
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(data[i]), unprojectY(data[i + 1])]
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 rootTile = createTile(source, z, x, y, options);
1609
- rootTile.source = source;
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
- features = clip(clip(features, pow2, x - buffer, x + 1 + buffer, 0, -1, 2, options) || [], pow2, y - buffer, y + 1 + buffer, 1, -1, 2, options);
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
  }