@maplibre/geojson-vt 6.2.0 → 6.3.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.
@@ -134,6 +134,51 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
134
134
  }
135
135
  }
136
136
 
137
+ //#endregion
138
+ //#region src/world-coordinate-helper.ts
139
+ /**
140
+ * Longitude and latitude, which spherical mercator puts in the world square.
141
+ */
142
+ var MercatorWorldCoordinateHelper = class {
143
+ projectX(lng) {
144
+ return lng / 360 + .5;
145
+ }
146
+ projectY(lat) {
147
+ const sin = Math.sin(lat * Math.PI / 180);
148
+ const y = .5 - .25 * Math.log((1 + sin) / (1 - sin)) / Math.PI;
149
+ return y < 0 ? 0 : y > 1 ? 1 : y;
150
+ }
151
+ unprojectX(x) {
152
+ return (x - .5) * 360;
153
+ }
154
+ unprojectY(y) {
155
+ const y2 = (180 - y * 360) * Math.PI / 180;
156
+ return 360 * Math.atan(Math.exp(y2)) / Math.PI - 90;
157
+ }
158
+ };
159
+ /**
160
+ * Positions in the unit square already, see `coordinates` in {@link GeoJSONVTOptions}.
161
+ */
162
+ var IdentityWorldCoordinateHelper = class {
163
+ projectX(x) {
164
+ return x;
165
+ }
166
+ projectY(y) {
167
+ return y;
168
+ }
169
+ unprojectX(x) {
170
+ return x;
171
+ }
172
+ unprojectY(y) {
173
+ return y;
174
+ }
175
+ };
176
+ const mercatorWorldCoordinateHelper = new MercatorWorldCoordinateHelper();
177
+ const identityWorldCoordinateHelper = new IdentityWorldCoordinateHelper();
178
+ function getWorldCoordinateHelper(coordinates) {
179
+ return coordinates === "unitSquare" ? identityWorldCoordinateHelper : mercatorWorldCoordinateHelper;
180
+ }
181
+
137
182
  //#endregion
138
183
  //#region src/convert.ts
139
184
  const MAX_GEOMETRY_COLLECTION_DEPTH = 1024;
@@ -141,25 +186,38 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
141
186
  * converts GeoJSON to internal source features (an intermediate projected JSON vector format with simplification data)
142
187
  * @param data
143
188
  * @param options
189
+ * @param unlocated - the features without coordinates, by id: a feature with an id that converts to nothing is set in it, and one that converts to something is deleted from it
144
190
  * @returns
145
191
  */
146
- function convertToInternal(data, options) {
192
+ function convertToInternal(data, options, unlocated) {
147
193
  const features = [];
148
194
  switch (data.type) {
149
195
  case "FeatureCollection":
150
- for (let i = 0; i < data.features.length; i++) featureToInternal(features, data.features[i], options, i);
196
+ for (let i = 0; i < data.features.length; i++) convertFeature(features, data.features[i], options, unlocated, i);
151
197
  break;
152
198
  case "Feature":
153
- featureToInternal(features, data, options);
199
+ convertFeature(features, data, options, unlocated);
154
200
  break;
155
- default: featureToInternal(features, {
201
+ default: convertFeature(features, {
156
202
  type: "Feature",
157
203
  geometry: data,
158
204
  properties: void 0
159
- }, options);
205
+ }, options, unlocated);
160
206
  }
161
207
  return features;
162
208
  }
209
+ function convertFeature(features, geojson, options, unlocated, index) {
210
+ const count = features.length;
211
+ featureToInternal(features, geojson, options, index);
212
+ if (!unlocated) return;
213
+ const id = getFeatureId(geojson, options, index);
214
+ if (id == null) return;
215
+ if (features.length > count) unlocated.delete(id);
216
+ else unlocated.set(id, {
217
+ ...geojson,
218
+ id
219
+ });
220
+ }
163
221
  function featureToInternal(features, geojson, options, index, depth = 0) {
164
222
  if (!geojson.geometry) return;
165
223
  if (depth > MAX_GEOMETRY_COLLECTION_DEPTH) throw new Error("GeometryCollection nesting exceeds supported depth: 1024");
@@ -170,25 +228,25 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
170
228
  if (!geojson.geometry.coordinates?.length) return;
171
229
  const id = getFeatureId(geojson, options, index);
172
230
  const tolerance = Math.pow(options.tolerance / ((1 << options.maxZoom) * options.extent), 2);
173
- const worldCoordinates = options.worldCoordinates === true;
231
+ const worldCoordinateHelper = getWorldCoordinateHelper(options.coordinates);
174
232
  switch (geojson.geometry.type) {
175
233
  case "Point":
176
- convertPointFeature(features, id, geojson.geometry, worldCoordinates, geojson.properties);
234
+ convertPointFeature(features, id, geojson.geometry, worldCoordinateHelper, geojson.properties);
177
235
  return;
178
236
  case "MultiPoint":
179
- convertMultiPointFeature(features, id, geojson.geometry, worldCoordinates, geojson.properties);
237
+ convertMultiPointFeature(features, id, geojson.geometry, worldCoordinateHelper, geojson.properties);
180
238
  return;
181
239
  case "LineString":
182
- convertLineStringFeature(features, id, geojson.geometry, tolerance, worldCoordinates, geojson.properties);
240
+ convertLineStringFeature(features, id, geojson.geometry, tolerance, worldCoordinateHelper, geojson.properties);
183
241
  return;
184
242
  case "MultiLineString":
185
- convertMultiLineStringFeature(features, id, geojson.geometry, tolerance, worldCoordinates, options, geojson.properties);
243
+ convertMultiLineStringFeature(features, id, geojson.geometry, tolerance, worldCoordinateHelper, options, geojson.properties);
186
244
  return;
187
245
  case "Polygon":
188
- convertPolygonFeature(features, id, geojson.geometry, tolerance, worldCoordinates, geojson.properties);
246
+ convertPolygonFeature(features, id, geojson.geometry, tolerance, worldCoordinateHelper, geojson.properties);
189
247
  return;
190
248
  case "MultiPolygon":
191
- convertMultiPolygonFeature(features, id, geojson.geometry, tolerance, worldCoordinates, geojson.properties);
249
+ convertMultiPolygonFeature(features, id, geojson.geometry, tolerance, worldCoordinateHelper, geojson.properties);
192
250
  return;
193
251
  default: throw new Error("Input data is not a valid GeoJSON object.");
194
252
  }
@@ -206,55 +264,55 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
206
264
  properties: geojson.properties
207
265
  }, options, index, depth);
208
266
  }
209
- function convertPointFeature(features, id, geom, worldCoordinates, properties) {
267
+ function convertPointFeature(features, id, geom, worldCoordinateHelper, properties) {
210
268
  const out = [];
211
- out.push(projectX(geom.coordinates[0], worldCoordinates), projectY(geom.coordinates[1], worldCoordinates), 0);
269
+ out.push(worldCoordinateHelper.projectX(geom.coordinates[0]), worldCoordinateHelper.projectY(geom.coordinates[1]), 0);
212
270
  features.push(createFeature(id, "Point", out, properties));
213
271
  }
214
- function convertMultiPointFeature(features, id, geom, worldCoordinates, properties) {
272
+ function convertMultiPointFeature(features, id, geom, worldCoordinateHelper, properties) {
215
273
  const out = [];
216
- for (const coords of geom.coordinates) out.push(projectX(coords[0], worldCoordinates), projectY(coords[1], worldCoordinates), 0);
274
+ for (const coords of geom.coordinates) out.push(worldCoordinateHelper.projectX(coords[0]), worldCoordinateHelper.projectY(coords[1]), 0);
217
275
  features.push(createFeature(id, "MultiPoint", out, properties));
218
276
  }
219
- function convertLineStringFeature(features, id, geom, tolerance, worldCoordinates, properties) {
277
+ function convertLineStringFeature(features, id, geom, tolerance, worldCoordinateHelper, properties) {
220
278
  const out = { points: [] };
221
- convertLine(geom.coordinates, out, tolerance, false, worldCoordinates);
279
+ convertLine(geom.coordinates, out, tolerance, false, worldCoordinateHelper);
222
280
  features.push(createFeature(id, "LineString", out, properties));
223
281
  }
224
- function convertMultiLineStringFeature(features, id, geom, tolerance, worldCoordinates, options, properties) {
282
+ function convertMultiLineStringFeature(features, id, geom, tolerance, worldCoordinateHelper, options, properties) {
225
283
  if (options.lineMetrics) for (const line of geom.coordinates) {
226
284
  const out = { points: [] };
227
- convertLine(line, out, tolerance, false, worldCoordinates);
285
+ convertLine(line, out, tolerance, false, worldCoordinateHelper);
228
286
  features.push(createFeature(id, "LineString", out, properties));
229
287
  }
230
288
  else {
231
289
  const out = [];
232
- convertLines(geom.coordinates, out, tolerance, false, worldCoordinates);
290
+ convertLines(geom.coordinates, out, tolerance, false, worldCoordinateHelper);
233
291
  features.push(createFeature(id, "MultiLineString", out, properties));
234
292
  }
235
293
  }
236
- function convertPolygonFeature(features, id, geom, tolerance, worldCoordinates, properties) {
294
+ function convertPolygonFeature(features, id, geom, tolerance, worldCoordinateHelper, properties) {
237
295
  const out = [];
238
- convertLines(geom.coordinates, out, tolerance, true, worldCoordinates);
296
+ convertLines(geom.coordinates, out, tolerance, true, worldCoordinateHelper);
239
297
  features.push(createFeature(id, "Polygon", out, properties));
240
298
  }
241
- function convertMultiPolygonFeature(features, id, geom, tolerance, worldCoordinates, properties) {
299
+ function convertMultiPolygonFeature(features, id, geom, tolerance, worldCoordinateHelper, properties) {
242
300
  const out = [];
243
301
  for (const polygon of geom.coordinates) {
244
302
  const polygonOut = [];
245
- convertLines(polygon, polygonOut, tolerance, true, worldCoordinates);
303
+ convertLines(polygon, polygonOut, tolerance, true, worldCoordinateHelper);
246
304
  if (!polygonOut.length) continue;
247
305
  out.push(polygonOut);
248
306
  }
249
307
  if (!out.length) return;
250
308
  features.push(createFeature(id, "MultiPolygon", out, properties));
251
309
  }
252
- function convertLine(ring, out, tolerance, isPolygon, worldCoordinates) {
310
+ function convertLine(ring, out, tolerance, isPolygon, worldCoordinateHelper) {
253
311
  let x0, y0;
254
312
  let size = 0;
255
313
  for (let j = 0; j < ring.length; j++) {
256
- const x = projectX(ring[j][0], worldCoordinates);
257
- const y = projectY(ring[j][1], worldCoordinates);
314
+ const x = worldCoordinateHelper.projectX(ring[j][0]);
315
+ const y = worldCoordinateHelper.projectY(ring[j][1]);
258
316
  out.points.push(x, y, 0);
259
317
  if (j > 0) {
260
318
  if (isPolygon) size += (x0 * y - x * y0) / 2;
@@ -272,48 +330,32 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
272
330
  out.start = 0;
273
331
  out.end = out.size;
274
332
  }
275
- function convertLines(rings, out, tolerance, isPolygon, worldCoordinates) {
333
+ function convertLines(rings, out, tolerance, isPolygon, worldCoordinateHelper) {
276
334
  for (let i = 0; i < rings.length; i++) {
277
335
  const geom = { points: [] };
278
- convertLine(rings[i], geom, tolerance, isPolygon, worldCoordinates);
336
+ convertLine(rings[i], geom, tolerance, isPolygon, worldCoordinateHelper);
279
337
  out.push(geom);
280
338
  }
281
339
  }
282
- /**
283
- * Convert longitude to spherical mercator in [0..1] range, or keep a world coordinate as it is
284
- */
285
- function projectX(x, worldCoordinates = false) {
286
- if (worldCoordinates) return x;
287
- return x / 360 + .5;
288
- }
289
- /**
290
- * Convert latitude to spherical mercator in [0..1] range, or keep a world coordinate as it is
291
- */
292
- function projectY(y, worldCoordinates = false) {
293
- if (worldCoordinates) return y;
294
- const sin = Math.sin(y * Math.PI / 180);
295
- const y2 = .5 - .25 * Math.log((1 + sin) / (1 - sin)) / Math.PI;
296
- return y2 < 0 ? 0 : y2 > 1 ? 1 : y2;
297
- }
298
340
 
299
341
  //#endregion
300
342
  //#region src/deconvert.ts
301
343
  /**
302
344
  * Converts internal source features back to GeoJSON format.
303
345
  */
304
- function convertToGeoJSON(source, worldCoordinates = false) {
346
+ function convertToGeoJSON(source, worldCoordinateHelper = mercatorWorldCoordinateHelper) {
305
347
  return {
306
348
  type: "FeatureCollection",
307
- features: source.map((feature) => featureToGeoJSON(feature, worldCoordinates))
349
+ features: source.map((feature) => featureToGeoJSON(feature, worldCoordinateHelper))
308
350
  };
309
351
  }
310
352
  /**
311
353
  * Converts a single internal feature to GeoJSON format.
312
354
  */
313
- function featureToGeoJSON(feature, worldCoordinates = false) {
355
+ function featureToGeoJSON(feature, worldCoordinateHelper = mercatorWorldCoordinateHelper) {
314
356
  const geojsonFeature = {
315
357
  type: "Feature",
316
- geometry: geometryToGeoJSON(feature, worldCoordinates),
358
+ geometry: geometryToGeoJSON(feature, worldCoordinateHelper),
317
359
  properties: feature.tags
318
360
  };
319
361
  if (feature.id != null) geojsonFeature.id = feature.id;
@@ -322,54 +364,39 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
322
364
  /**
323
365
  * Converts a single internal feature geometry to GeoJSON format.
324
366
  */
325
- function geometryToGeoJSON(feature, worldCoordinates) {
367
+ function geometryToGeoJSON(feature, worldCoordinateHelper) {
326
368
  const { type, geometry } = feature;
327
369
  switch (type) {
328
370
  case "Point": return {
329
371
  type,
330
- coordinates: unprojectPoint(geometry[0], geometry[1], worldCoordinates)
372
+ coordinates: unprojectPoint(geometry[0], geometry[1], worldCoordinateHelper)
331
373
  };
332
374
  case "MultiPoint": return {
333
375
  type,
334
- coordinates: unprojectPoints(geometry, worldCoordinates)
376
+ coordinates: unprojectPoints(geometry, worldCoordinateHelper)
335
377
  };
336
378
  case "LineString": return {
337
379
  type,
338
- coordinates: unprojectPoints(geometry.points, worldCoordinates)
380
+ coordinates: unprojectPoints(geometry.points, worldCoordinateHelper)
339
381
  };
340
382
  case "MultiLineString":
341
383
  case "Polygon": return {
342
384
  type,
343
- coordinates: geometry.map((ring) => unprojectPoints(ring.points, worldCoordinates))
385
+ coordinates: geometry.map((ring) => unprojectPoints(ring.points, worldCoordinateHelper))
344
386
  };
345
387
  case "MultiPolygon": return {
346
388
  type,
347
- coordinates: geometry.map((polygon) => polygon.map((ring) => unprojectPoints(ring.points, worldCoordinates)))
389
+ coordinates: geometry.map((polygon) => polygon.map((ring) => unprojectPoints(ring.points, worldCoordinateHelper)))
348
390
  };
349
391
  }
350
392
  }
351
- function unprojectPoints(coords, worldCoordinates = false) {
393
+ function unprojectPoints(coords, worldCoordinateHelper = mercatorWorldCoordinateHelper) {
352
394
  const result = [];
353
- for (let i = 0; i < coords.length; i += 3) result.push(unprojectPoint(coords[i], coords[i + 1], worldCoordinates));
395
+ for (let i = 0; i < coords.length; i += 3) result.push(unprojectPoint(coords[i], coords[i + 1], worldCoordinateHelper));
354
396
  return result;
355
397
  }
356
- function unprojectPoint(x, y, worldCoordinates) {
357
- return [unprojectX(x, worldCoordinates), unprojectY(y, worldCoordinates)];
358
- }
359
- /**
360
- * Convert spherical mercator in [0..1] range to longitude, or keep a world coordinate as it is
361
- */
362
- function unprojectX(x, worldCoordinates = false) {
363
- if (worldCoordinates) return x;
364
- return (x - .5) * 360;
365
- }
366
- /**
367
- * Convert spherical mercator in [0..1] range to latitude, or keep a world coordinate as it is
368
- */
369
- function unprojectY(y, worldCoordinates = false) {
370
- if (worldCoordinates) return y;
371
- const y2 = (180 - y * 360) * Math.PI / 180;
372
- return 360 * Math.atan(Math.exp(y2)) / Math.PI - 90;
398
+ function unprojectPoint(x, y, worldCoordinateHelper) {
399
+ return [worldCoordinateHelper.unprojectX(x), worldCoordinateHelper.unprojectY(y)];
373
400
  }
374
401
 
375
402
  //#endregion
@@ -561,7 +588,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
561
588
  //#endregion
562
589
  //#region src/wrap.ts
563
590
  function wrap(features, options) {
564
- if (options.worldCoordinates) return features;
591
+ if (!options.wrap) return features;
565
592
  const buffer = options.buffer / options.extent;
566
593
  let merged = features;
567
594
  const left = clip(features, 1, -1 - buffer, buffer, 0, -1, 2, options);
@@ -632,16 +659,19 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
632
659
  * @param source
633
660
  * @param dataDiff
634
661
  * @param options
662
+ * @param unlocated - the features without coordinates, by id, so that an update can give them some
635
663
  * @returns
636
664
  */
637
- function applySourceDiff(source, dataDiff, options) {
665
+ function applySourceDiff(source, dataDiff, options, unlocated) {
638
666
  const diff = diffToHashed(dataDiff, options);
639
667
  let affected = [];
640
668
  if (diff.removeAll) {
641
669
  affected = source;
642
670
  source = [];
671
+ unlocated?.clear();
643
672
  }
644
673
  if (diff.remove.size || diff.add.size) {
674
+ for (const id of diff.remove) unlocated?.delete(id);
645
675
  const removeFeatures = [];
646
676
  for (const feature of source) if (diff.remove.has(feature.id) || diff.add.has(feature.id)) removeFeatures.push(feature);
647
677
  if (removeFeatures.length) {
@@ -653,7 +683,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
653
683
  let addFeatures = convertToInternal({
654
684
  type: "FeatureCollection",
655
685
  features: Array.from(diff.add.values())
656
- }, options);
686
+ }, options, unlocated);
657
687
  addFeatures = wrap(addFeatures, options);
658
688
  affected = affected.concat(addFeatures);
659
689
  source = source.concat(addFeatures);
@@ -666,8 +696,14 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
666
696
  else keepFeatures.push(feature);
667
697
  for (const [id, update] of diff.update) {
668
698
  const oldFeatures = oldFeaturesMap.get(id);
669
- if (!oldFeatures || oldFeatures.length === 0) continue;
670
- const updatedFeatures = getUpdatedFeatures(oldFeatures, update, options);
699
+ if (!oldFeatures || oldFeatures.length === 0) {
700
+ if (!unlocated?.has(id)) continue;
701
+ const locatedFeatures = getUpdatedUnlocatedFeatures(id, update, options, unlocated);
702
+ affected = affected.concat(locatedFeatures);
703
+ keepFeatures = keepFeatures.concat(locatedFeatures);
704
+ continue;
705
+ }
706
+ const updatedFeatures = getUpdatedFeatures(oldFeatures, update, options, unlocated);
671
707
  affected = affected.concat(oldFeatures, updatedFeatures);
672
708
  keepFeatures = keepFeatures.concat(updatedFeatures);
673
709
  }
@@ -683,29 +719,16 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
683
719
  * @param vtFeatures - the original features
684
720
  * @param update - the update object to apply
685
721
  * @param options - the options to use for the wrap method
722
+ * @param unlocated - the features without coordinates, by id, which a new geometry without coordinates adds the feature to
686
723
  * @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.
687
724
  */
688
- function getUpdatedFeatures(vtFeatures, update, options) {
725
+ function getUpdatedFeatures(vtFeatures, update, options, unlocated) {
689
726
  const changeGeometry = !!update.newGeometry;
690
- const changeProps = update.removeAllProperties || update.removeProperties?.length > 0 || update.addOrUpdateProperties?.length > 0;
727
+ const changeProps = hasPropertyChanges(update);
691
728
  if (changeGeometry) {
692
729
  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
- };
699
- let features = convertToInternal({
700
- type: "FeatureCollection",
701
- features: [geojsonFeature]
702
- }, {
703
- ...options,
704
- promoteId: void 0,
705
- generateId: false
706
- });
707
- features = wrap(features, options);
708
- return features;
730
+ const properties = changeProps ? applyPropertyUpdates(vtFeature.tags, update) : vtFeature.tags;
731
+ return convertNewGeometry(vtFeature.id, update.newGeometry, properties, options, unlocated);
709
732
  }
710
733
  if (changeProps) {
711
734
  const updated = [];
@@ -719,6 +742,44 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
719
742
  return vtFeatures;
720
743
  }
721
744
  /**
745
+ * Gets the features an update gives a feature without coordinates: none, unless its new geometry has some.
746
+ * Its property changes apply either way.
747
+ */
748
+ function getUpdatedUnlocatedFeatures(id, update, options, unlocated) {
749
+ const feature = unlocated.get(id);
750
+ const properties = hasPropertyChanges(update) ? applyPropertyUpdates(feature.properties, update) : feature.properties;
751
+ if (!update.newGeometry) {
752
+ unlocated.set(id, {
753
+ ...feature,
754
+ properties
755
+ });
756
+ return [];
757
+ }
758
+ return convertNewGeometry(id, update.newGeometry, properties, options, unlocated);
759
+ }
760
+ /**
761
+ * Converts the new geometry of a feature to internal format, keeping the id the feature already has
762
+ */
763
+ function convertNewGeometry(id, geometry, properties, options, unlocated) {
764
+ const features = convertToInternal({
765
+ type: "FeatureCollection",
766
+ features: [{
767
+ type: "Feature",
768
+ id,
769
+ geometry,
770
+ properties
771
+ }]
772
+ }, {
773
+ ...options,
774
+ promoteId: void 0,
775
+ generateId: false
776
+ }, unlocated);
777
+ return wrap(features, options);
778
+ }
779
+ function hasPropertyChanges(update) {
780
+ return update.removeAllProperties || update.removeProperties?.length > 0 || update.addOrUpdateProperties?.length > 0;
781
+ }
782
+ /**
722
783
  * helper to apply property updates from a diff update object to a properties object
723
784
  */
724
785
  function applyPropertyUpdates(tags, update) {
@@ -1043,7 +1104,9 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
1043
1104
  log: false,
1044
1105
  generateId: false,
1045
1106
  reduce: null,
1046
- map: (props) => props
1107
+ map: (props) => props,
1108
+ coordinates: "lngLat",
1109
+ wrap: true
1047
1110
  };
1048
1111
  /**
1049
1112
  * The highest zoom level to cluster on. A cluster id keeps its zoom level in five bits.
@@ -1083,9 +1146,9 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
1083
1146
  * This class allow clustering of geojson points.
1084
1147
  */
1085
1148
  var ClusterTileIndex = class {
1086
- constructor(options, worldCoordinates = false) {
1149
+ constructor(options) {
1087
1150
  this.options = createOptions(options);
1088
- this.worldCoordinates = worldCoordinates;
1151
+ this.worldCoordinateHelper = getWorldCoordinateHelper(this.options.coordinates);
1089
1152
  this.trees = new Array(this.options.maxZoom + 1);
1090
1153
  this.stride = this.options.reduce ? 5 : 4;
1091
1154
  this.clusterProps = [];
@@ -1102,8 +1165,8 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
1102
1165
  if (!point.geometry) continue;
1103
1166
  const positions = point.geometry.type === "MultiPoint" ? point.geometry.coordinates : [point.geometry.coordinates];
1104
1167
  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;
1168
+ const [x, y] = [this.worldCoordinateHelper.projectX(lng), this.worldCoordinateHelper.projectY(lat)];
1169
+ if (!this.options.wrap && !isInWorld([x, y])) continue;
1107
1170
  const feature = {
1108
1171
  id: point.id,
1109
1172
  type: "Point",
@@ -1125,14 +1188,14 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
1125
1188
  const points = [];
1126
1189
  for (const feature of features) {
1127
1190
  if (feature.type === "Point") {
1128
- if (this.worldCoordinates && !isInWorld(feature.geometry)) continue;
1191
+ if (!this.options.wrap && !isInWorld(feature.geometry)) continue;
1129
1192
  points.push(feature);
1130
1193
  continue;
1131
1194
  }
1132
1195
  if (feature.type !== "MultiPoint") continue;
1133
1196
  for (let i = 0; i < feature.geometry.length; i += 3) {
1134
1197
  const geometry = [feature.geometry[i], feature.geometry[i + 1]];
1135
- if (this.worldCoordinates && !isInWorld(geometry)) continue;
1198
+ if (!this.options.wrap && !isInWorld(geometry)) continue;
1136
1199
  points.push({
1137
1200
  id: feature.id,
1138
1201
  type: "Point",
@@ -1149,7 +1212,11 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
1149
1212
  * @param features
1150
1213
  */
1151
1214
  updateIndex(features, _affected, options) {
1152
- this.options = createOptions(options.clusterOptions);
1215
+ this.options = createOptions({
1216
+ ...options.clusterOptions,
1217
+ coordinates: this.options.coordinates,
1218
+ wrap: this.options.wrap
1219
+ });
1153
1220
  this.initialize(features);
1154
1221
  }
1155
1222
  /**
@@ -1197,11 +1264,61 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
1197
1264
  }
1198
1265
  /**
1199
1266
  * Returns clusters and/or points within a bounding box at a given zoom level.
1200
- * @param bbox - Bounding box in `[westLng, southLat, eastLng, northLat]` order, or `[minX, minY, maxX, maxY]` in world coordinates.
1267
+ * @param bbox - Bounding box in `[westLng, southLat, eastLng, northLat]` order, or `[minX, minY, maxX, maxY]` in the unit square with `coordinates: 'unitSquare'`.
1268
+ * In a world that wraps, the box can reach across the antimeridian. In one that doesn't, only its part inside the world counts.
1201
1269
  * @param zoom - Zoom level to query.
1202
1270
  */
1203
1271
  getClusters(bbox, zoom) {
1204
- return (this.worldCoordinates ? this.getClustersInRange(bbox, zoom) : this.getClustersInternal(bbox, zoom)).map((f) => featureToGeoJSON(f, this.worldCoordinates));
1272
+ return (!this.options.wrap ? this.getClustersInWorld(bbox, zoom) : this.options.coordinates === "unitSquare" ? this.getClustersAcrossEdge(bbox, zoom) : this.getClustersInternal(bbox, zoom)).map((f) => featureToGeoJSON(f, this.worldCoordinateHelper));
1273
+ }
1274
+ /**
1275
+ * The clusters and points in the part of a box inside a world that ends at its edges.
1276
+ */
1277
+ getClustersInWorld(bbox, zoom) {
1278
+ const lngLat = this.options.coordinates !== "unitSquare";
1279
+ const y1 = this.worldCoordinateHelper.projectY(lngLat ? Math.max(-90, Math.min(90, bbox[1])) : bbox[1]);
1280
+ const y2 = this.worldCoordinateHelper.projectY(lngLat ? Math.max(-90, Math.min(90, bbox[3])) : bbox[3]);
1281
+ const minX = Math.max(0, this.worldCoordinateHelper.projectX(bbox[0]));
1282
+ const maxX = Math.min(1, this.worldCoordinateHelper.projectX(bbox[2]));
1283
+ if (minX > maxX) return [];
1284
+ return this.getClustersInRange([
1285
+ minX,
1286
+ Math.min(y1, y2),
1287
+ maxX,
1288
+ Math.max(y1, y2)
1289
+ ], zoom);
1290
+ }
1291
+ /**
1292
+ * The clusters and points in a box of a unit square that wraps, where the box can reach across its left or right edge.
1293
+ */
1294
+ getClustersAcrossEdge([minX, minY, maxX, maxY], zoom) {
1295
+ if (maxX < minX) maxX += 1;
1296
+ if (maxX - minX >= 1) return this.getClustersInRange([
1297
+ 0,
1298
+ minY,
1299
+ 1,
1300
+ maxY
1301
+ ], zoom);
1302
+ const shift = Math.floor(minX);
1303
+ const west = minX - shift;
1304
+ const east = maxX - shift;
1305
+ if (east <= 1) return this.getClustersInRange([
1306
+ west,
1307
+ minY,
1308
+ east,
1309
+ maxY
1310
+ ], zoom);
1311
+ return this.getClustersInRange([
1312
+ west,
1313
+ minY,
1314
+ 1,
1315
+ maxY
1316
+ ], zoom).concat(this.getClustersInRange([
1317
+ 0,
1318
+ minY,
1319
+ east - 1,
1320
+ maxY
1321
+ ], zoom));
1205
1322
  }
1206
1323
  getClustersInternal(bbox, zoom) {
1207
1324
  let minLng = ((bbox[0] + 180) % 360 + 360) % 360 - 180;
@@ -1227,10 +1344,10 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
1227
1344
  return easternHem.concat(westernHem);
1228
1345
  }
1229
1346
  return this.getClustersInRange([
1230
- projectX(minLng),
1231
- projectY(maxLat),
1232
- projectX(maxLng),
1233
- projectY(minLat)
1347
+ this.worldCoordinateHelper.projectX(minLng),
1348
+ this.worldCoordinateHelper.projectY(maxLat),
1349
+ this.worldCoordinateHelper.projectX(maxLng),
1350
+ this.worldCoordinateHelper.projectY(minLat)
1234
1351
  ], zoom);
1235
1352
  }
1236
1353
  /**
@@ -1268,7 +1385,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
1268
1385
  const children = [];
1269
1386
  for (const id of ids) {
1270
1387
  const k = id * this.stride;
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));
1388
+ if (data[k + OFFSET_PARENT] === clusterId) children.push(data[k + OFFSET_NUM] > 1 ? getClusterGeoJSON(coords, id, data, k, this.clusterProps, this.worldCoordinateHelper) : featureToGeoJSON(this.points[data[k + OFFSET_ID]], this.worldCoordinateHelper));
1272
1389
  }
1273
1390
  if (children.length === 0) throw clusterError;
1274
1391
  return children;
@@ -1296,7 +1413,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
1296
1413
  const tree = this.trees[this.limitZoom(z)];
1297
1414
  if (!tree) return null;
1298
1415
  const z2 = Math.pow(2, z);
1299
- if (this.worldCoordinates && (x < 0 || x >= z2 || y < 0 || y >= z2)) return null;
1416
+ if (!this.options.wrap && (x < 0 || x >= z2 || y < 0 || y >= z2)) return null;
1300
1417
  const { extent, radius } = this.options;
1301
1418
  const p = radius / extent;
1302
1419
  const top = encode((y - p) / z2);
@@ -1310,8 +1427,8 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
1310
1427
  z
1311
1428
  };
1312
1429
  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);
1430
+ if (this.options.wrap && x === 0) this.addTileFeatures(tree.range(encode(1 - p / z2), top, encode(1), bottom), tree, z2, y, z2, tile);
1431
+ if (this.options.wrap && x === z2 - 1) this.addTileFeatures(tree.range(encode(0), top, encode(p / z2), bottom), tree, -1, y, z2, tile);
1315
1432
  return tile;
1316
1433
  }
1317
1434
  /**
@@ -1494,14 +1611,14 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
1494
1611
  geometry: [decode(coords[index * 2]), decode(coords[index * 2 + 1])]
1495
1612
  };
1496
1613
  }
1497
- function getClusterGeoJSON(coords, index, data, i, clusterProps, worldCoordinates) {
1614
+ function getClusterGeoJSON(coords, index, data, i, clusterProps, worldCoordinateHelper) {
1498
1615
  return {
1499
1616
  type: "Feature",
1500
1617
  id: data[i + OFFSET_ID],
1501
1618
  properties: getClusterProperties(data, i, clusterProps),
1502
1619
  geometry: {
1503
1620
  type: "Point",
1504
- coordinates: [unprojectX(decode(coords[index * 2]), worldCoordinates), unprojectY(decode(coords[index * 2 + 1]), worldCoordinates)]
1621
+ coordinates: [worldCoordinateHelper.unprojectX(decode(coords[index * 2])), worldCoordinateHelper.unprojectY(decode(coords[index * 2 + 1]))]
1505
1622
  }
1506
1623
  };
1507
1624
  }
@@ -1784,7 +1901,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
1784
1901
  getTile(z, x, y) {
1785
1902
  const { extent, debug } = this.options;
1786
1903
  const z2 = 1 << z;
1787
- if (this.options.worldCoordinates && (x < 0 || x >= z2)) return null;
1904
+ if (!this.options.wrap && (x < 0 || x >= z2)) return null;
1788
1905
  x = x + z2 & z2 - 1;
1789
1906
  const id = toID(z, x, y);
1790
1907
  if (this.tiles[id]) return transformTile(this.tiles[id], extent);
@@ -1810,14 +1927,16 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
1810
1927
  return transformTile(this.tiles[id], extent);
1811
1928
  }
1812
1929
  /**
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.
1930
+ * The features the top tile starts from. A world that wraps keeps them all left and right, where wrapping already
1931
+ * put them inside, and one that doesn't only what lies inside the world square and its buffer. Positions in the
1932
+ * unit square are also cut above and below in a world that wraps, where lng/lat is clamped already. The index keeps
1933
+ * the rest, so an update can still move a feature in from outside.
1816
1934
  */
1817
1935
  clipToWorld(features) {
1818
- if (!this.options.worldCoordinates) return features;
1936
+ const { wrap, coordinates } = this.options;
1937
+ if (wrap && coordinates !== "unitSquare") return features;
1819
1938
  const buffer = this.options.buffer / this.options.extent;
1820
- const inX = clip(features, 1, -buffer, 1 + buffer, 0, -Infinity, Infinity, this.options) || [];
1939
+ const inX = wrap ? features : clip(features, 1, -buffer, 1 + buffer, 0, -Infinity, Infinity, this.options) || [];
1821
1940
  return clip(inX, 1, -buffer, 1 + buffer, 1, -Infinity, Infinity, this.options) || [];
1822
1941
  }
1823
1942
  /**
@@ -1975,7 +2094,8 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
1975
2094
  cluster: false,
1976
2095
  clusterOptions: defaultClusterOptions,
1977
2096
  debug: 0,
1978
- worldCoordinates: false
2097
+ coordinates: "lngLat",
2098
+ wrap: true
1979
2099
  };
1980
2100
  /**
1981
2101
  * Main class for creating and managing a vector tile index from GeoJSON data.
@@ -1983,11 +2103,13 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
1983
2103
  var GeoJSONVT = class {
1984
2104
  constructor(data, options) {
1985
2105
  options = this.options = Object.assign({}, defaultOptions, options);
2106
+ this.worldCoordinateHelper = getWorldCoordinateHelper(options.coordinates);
1986
2107
  const debug = options.debug;
1987
2108
  if (debug) console.time("preprocess data");
1988
2109
  if (options.maxZoom < 0 || options.maxZoom > 24) throw new Error("maxZoom should be in the 0-24 range");
1989
2110
  if (options.promoteId && options.generateId) throw new Error("promoteId and generateId cannot be used together.");
1990
- let features = convertToInternal(data, options);
2111
+ if (options.updateable) this.unlocated = /* @__PURE__ */ new Map();
2112
+ let features = convertToInternal(data, options, this.unlocated);
1991
2113
  if (debug) {
1992
2114
  console.timeEnd("preprocess data");
1993
2115
  console.log("index: maxZoom: %d, maxPoints: %d", options.indexMaxZoom, options.indexMaxPoints);
@@ -1998,7 +2120,11 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
1998
2120
  this.initializeIndex(features, options);
1999
2121
  }
2000
2122
  initializeIndex(features, options) {
2001
- this.tileIndex = options.cluster ? new ClusterTileIndex(options.clusterOptions, options.worldCoordinates) : new TileIndex(options);
2123
+ this.tileIndex = options.cluster ? new ClusterTileIndex({
2124
+ ...options.clusterOptions,
2125
+ coordinates: options.coordinates,
2126
+ wrap: options.wrap
2127
+ }) : new TileIndex(options);
2002
2128
  if (!features.length) return;
2003
2129
  this.tileIndex.initialize(features);
2004
2130
  }
@@ -2023,24 +2149,27 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2023
2149
  updateData(diff, filter) {
2024
2150
  const options = this.options;
2025
2151
  if (!options.updateable) throw new Error("to update tile geojson `updateable` option must be set to true");
2026
- let { affected, source } = applySourceDiff(this.source, diff, options);
2152
+ let { affected, source } = applySourceDiff(this.source, diff, options, this.unlocated);
2027
2153
  if (filter) ({affected, source} = this.filterUpdate(source, affected, filter));
2028
2154
  if (!affected.length) return;
2029
2155
  this.source = source;
2030
2156
  this.tileIndex.updateIndex(source, affected, options);
2031
2157
  }
2032
2158
  /**
2033
- * Filter an update using a predicate function. Returns the affected and updated source features.
2159
+ * Filter an update using a predicate function, the features an update emptied included, so that one the filter
2160
+ * rejects can't come back with a later update. A feature without a geometry at all isn't passed to the predicate.
2161
+ * Returns the affected and updated source features.
2034
2162
  */
2035
2163
  filterUpdate(source, affected, predicate) {
2036
2164
  const removeIds = /* @__PURE__ */ new Set();
2037
2165
  for (const feature of source) {
2038
2166
  if (feature.id == void 0) continue;
2039
- if (predicate(featureToGeoJSON(feature, this.options.worldCoordinates))) continue;
2167
+ if (predicate(featureToGeoJSON(feature, this.worldCoordinateHelper))) continue;
2040
2168
  affected.push(feature);
2041
2169
  removeIds.add(feature.id);
2042
2170
  }
2043
2171
  source = source.filter((feature) => !removeIds.has(feature.id));
2172
+ for (const [id, feature] of this.unlocated ?? []) if (feature.geometry && !predicate(feature)) this.unlocated.delete(id);
2044
2173
  return {
2045
2174
  affected,
2046
2175
  source
@@ -2051,12 +2180,12 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2051
2180
  */
2052
2181
  getData() {
2053
2182
  if (!this.options.updateable) throw new Error("to retrieve data the `updateable` option must be set to true");
2054
- return convertToGeoJSON(this.source, this.options.worldCoordinates);
2183
+ return convertToGeoJSON(this.source, this.worldCoordinateHelper);
2055
2184
  }
2056
2185
  /**
2057
2186
  * Update supercluster options and regenerate the index.
2058
2187
  * @param cluster - whether to enable clustering
2059
- * @param clusterOptions - {@link SuperclusterOptions}
2188
+ * @param clusterOptions - {@link SuperclusterOptions} without `coordinates` and `wrap`, which come from the index options
2060
2189
  */
2061
2190
  updateClusterOptions(cluster, clusterOptions) {
2062
2191
  const wasCluster = this.options.cluster;
@@ -2133,9 +2262,10 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2133
2262
  function geoJSONToTile(data, z, x, y, options = {}) {
2134
2263
  options = {
2135
2264
  ...defaultOptions,
2265
+ wrap: false,
2136
2266
  ...options
2137
2267
  };
2138
- const { wrap: shouldWrap = false, clip: shouldClip = false } = options;
2268
+ const { wrap: shouldWrap, clip: shouldClip = false } = options;
2139
2269
  let features = convertToInternal(data, options);
2140
2270
  if (shouldWrap) features = wrap(features, options);
2141
2271
  if (shouldClip || options.lineMetrics) {