@maplibre/geojson-vt 6.1.2 → 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.
- package/README.md +8 -0
- package/dist/cluster-tile-index.d.ts +43 -5
- package/dist/cluster-tile-index.d.ts.map +1 -1
- package/dist/convert.d.ts +2 -9
- package/dist/convert.d.ts.map +1 -1
- package/dist/deconvert.d.ts +4 -11
- package/dist/deconvert.d.ts.map +1 -1
- package/dist/definitions.d.ts +47 -7
- package/dist/definitions.d.ts.map +1 -1
- package/dist/difference.d.ts +2 -1
- package/dist/difference.d.ts.map +1 -1
- package/dist/geojson-vt-dev.js +462 -181
- package/dist/geojson-vt.js +1 -1
- package/dist/geojson-vt.mjs +459 -180
- package/dist/geojson-vt.mjs.map +1 -1
- package/dist/geojsonvt.d.ts +8 -4
- package/dist/geojsonvt.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/tile-index.d.ts +7 -0
- package/dist/tile-index.d.ts.map +1 -1
- package/dist/world-coordinate-helper.d.ts +13 -0
- package/dist/world-coordinate-helper.d.ts.map +1 -0
- 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 +123 -0
- package/src/cluster-tile-index.ts +270 -99
- package/src/convert.ts +49 -44
- package/src/deconvert.test.ts +4 -4
- package/src/deconvert.ts +15 -29
- package/src/definitions.ts +48 -7
- package/src/difference.test.ts +71 -0
- package/src/difference.ts +50 -21
- package/src/geojson-to-tile.ts +2 -2
- package/src/geojsonvt.test.ts +387 -1
- package/src/geojsonvt.ts +25 -10
- package/src/index.ts +2 -1
- package/src/tile-index.ts +19 -3
- package/src/world-coordinate-helper.ts +63 -0
- package/src/wrap.ts +2 -0
package/dist/geojson-vt.mjs
CHANGED
|
@@ -127,31 +127,88 @@ function calcLineBBox(feature, geom) {
|
|
|
127
127
|
}
|
|
128
128
|
}
|
|
129
129
|
//#endregion
|
|
130
|
+
//#region src/world-coordinate-helper.ts
|
|
131
|
+
/**
|
|
132
|
+
* Longitude and latitude, which spherical mercator puts in the world square.
|
|
133
|
+
*/
|
|
134
|
+
var MercatorWorldCoordinateHelper = class {
|
|
135
|
+
projectX(lng) {
|
|
136
|
+
return lng / 360 + .5;
|
|
137
|
+
}
|
|
138
|
+
projectY(lat) {
|
|
139
|
+
const sin = Math.sin(lat * Math.PI / 180);
|
|
140
|
+
const y = .5 - .25 * Math.log((1 + sin) / (1 - sin)) / Math.PI;
|
|
141
|
+
return y < 0 ? 0 : y > 1 ? 1 : y;
|
|
142
|
+
}
|
|
143
|
+
unprojectX(x) {
|
|
144
|
+
return (x - .5) * 360;
|
|
145
|
+
}
|
|
146
|
+
unprojectY(y) {
|
|
147
|
+
const y2 = (180 - y * 360) * Math.PI / 180;
|
|
148
|
+
return 360 * Math.atan(Math.exp(y2)) / Math.PI - 90;
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
/**
|
|
152
|
+
* Positions in the unit square already, see `coordinates` in {@link GeoJSONVTOptions}.
|
|
153
|
+
*/
|
|
154
|
+
var IdentityWorldCoordinateHelper = class {
|
|
155
|
+
projectX(x) {
|
|
156
|
+
return x;
|
|
157
|
+
}
|
|
158
|
+
projectY(y) {
|
|
159
|
+
return y;
|
|
160
|
+
}
|
|
161
|
+
unprojectX(x) {
|
|
162
|
+
return x;
|
|
163
|
+
}
|
|
164
|
+
unprojectY(y) {
|
|
165
|
+
return y;
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
const mercatorWorldCoordinateHelper = new MercatorWorldCoordinateHelper();
|
|
169
|
+
const identityWorldCoordinateHelper = new IdentityWorldCoordinateHelper();
|
|
170
|
+
function getWorldCoordinateHelper(coordinates) {
|
|
171
|
+
return coordinates === "unitSquare" ? identityWorldCoordinateHelper : mercatorWorldCoordinateHelper;
|
|
172
|
+
}
|
|
173
|
+
//#endregion
|
|
130
174
|
//#region src/convert.ts
|
|
131
175
|
const MAX_GEOMETRY_COLLECTION_DEPTH = 1024;
|
|
132
176
|
/**
|
|
133
177
|
* converts GeoJSON to internal source features (an intermediate projected JSON vector format with simplification data)
|
|
134
178
|
* @param data
|
|
135
179
|
* @param options
|
|
180
|
+
* @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
|
|
136
181
|
* @returns
|
|
137
182
|
*/
|
|
138
|
-
function convertToInternal(data, options) {
|
|
183
|
+
function convertToInternal(data, options, unlocated) {
|
|
139
184
|
const features = [];
|
|
140
185
|
switch (data.type) {
|
|
141
186
|
case "FeatureCollection":
|
|
142
|
-
for (let i = 0; i < data.features.length; i++)
|
|
187
|
+
for (let i = 0; i < data.features.length; i++) convertFeature(features, data.features[i], options, unlocated, i);
|
|
143
188
|
break;
|
|
144
189
|
case "Feature":
|
|
145
|
-
|
|
190
|
+
convertFeature(features, data, options, unlocated);
|
|
146
191
|
break;
|
|
147
|
-
default:
|
|
192
|
+
default: convertFeature(features, {
|
|
148
193
|
type: "Feature",
|
|
149
194
|
geometry: data,
|
|
150
195
|
properties: void 0
|
|
151
|
-
}, options);
|
|
196
|
+
}, options, unlocated);
|
|
152
197
|
}
|
|
153
198
|
return features;
|
|
154
199
|
}
|
|
200
|
+
function convertFeature(features, geojson, options, unlocated, index) {
|
|
201
|
+
const count = features.length;
|
|
202
|
+
featureToInternal(features, geojson, options, index);
|
|
203
|
+
if (!unlocated) return;
|
|
204
|
+
const id = getFeatureId(geojson, options, index);
|
|
205
|
+
if (id == null) return;
|
|
206
|
+
if (features.length > count) unlocated.delete(id);
|
|
207
|
+
else unlocated.set(id, {
|
|
208
|
+
...geojson,
|
|
209
|
+
id
|
|
210
|
+
});
|
|
211
|
+
}
|
|
155
212
|
function featureToInternal(features, geojson, options, index, depth = 0) {
|
|
156
213
|
if (!geojson.geometry) return;
|
|
157
214
|
if (depth > MAX_GEOMETRY_COLLECTION_DEPTH) throw new Error("GeometryCollection nesting exceeds supported depth: 1024");
|
|
@@ -162,24 +219,25 @@ function featureToInternal(features, geojson, options, index, depth = 0) {
|
|
|
162
219
|
if (!geojson.geometry.coordinates?.length) return;
|
|
163
220
|
const id = getFeatureId(geojson, options, index);
|
|
164
221
|
const tolerance = Math.pow(options.tolerance / ((1 << options.maxZoom) * options.extent), 2);
|
|
222
|
+
const worldCoordinateHelper = getWorldCoordinateHelper(options.coordinates);
|
|
165
223
|
switch (geojson.geometry.type) {
|
|
166
224
|
case "Point":
|
|
167
|
-
convertPointFeature(features, id, geojson.geometry, geojson.properties);
|
|
225
|
+
convertPointFeature(features, id, geojson.geometry, worldCoordinateHelper, geojson.properties);
|
|
168
226
|
return;
|
|
169
227
|
case "MultiPoint":
|
|
170
|
-
convertMultiPointFeature(features, id, geojson.geometry, geojson.properties);
|
|
228
|
+
convertMultiPointFeature(features, id, geojson.geometry, worldCoordinateHelper, geojson.properties);
|
|
171
229
|
return;
|
|
172
230
|
case "LineString":
|
|
173
|
-
convertLineStringFeature(features, id, geojson.geometry, tolerance, geojson.properties);
|
|
231
|
+
convertLineStringFeature(features, id, geojson.geometry, tolerance, worldCoordinateHelper, geojson.properties);
|
|
174
232
|
return;
|
|
175
233
|
case "MultiLineString":
|
|
176
|
-
convertMultiLineStringFeature(features, id, geojson.geometry, tolerance, options, geojson.properties);
|
|
234
|
+
convertMultiLineStringFeature(features, id, geojson.geometry, tolerance, worldCoordinateHelper, options, geojson.properties);
|
|
177
235
|
return;
|
|
178
236
|
case "Polygon":
|
|
179
|
-
convertPolygonFeature(features, id, geojson.geometry, tolerance, geojson.properties);
|
|
237
|
+
convertPolygonFeature(features, id, geojson.geometry, tolerance, worldCoordinateHelper, geojson.properties);
|
|
180
238
|
return;
|
|
181
239
|
case "MultiPolygon":
|
|
182
|
-
convertMultiPolygonFeature(features, id, geojson.geometry, tolerance, geojson.properties);
|
|
240
|
+
convertMultiPolygonFeature(features, id, geojson.geometry, tolerance, worldCoordinateHelper, geojson.properties);
|
|
183
241
|
return;
|
|
184
242
|
default: throw new Error("Input data is not a valid GeoJSON object.");
|
|
185
243
|
}
|
|
@@ -197,53 +255,55 @@ function convertGeometryCollection(features, geojson, geometry, options, index,
|
|
|
197
255
|
properties: geojson.properties
|
|
198
256
|
}, options, index, depth);
|
|
199
257
|
}
|
|
200
|
-
function convertPointFeature(features, id, geom, properties) {
|
|
258
|
+
function convertPointFeature(features, id, geom, worldCoordinateHelper, properties) {
|
|
201
259
|
const out = [];
|
|
202
|
-
out.push(projectX(geom.coordinates[0]), projectY(geom.coordinates[1]), 0);
|
|
260
|
+
out.push(worldCoordinateHelper.projectX(geom.coordinates[0]), worldCoordinateHelper.projectY(geom.coordinates[1]), 0);
|
|
203
261
|
features.push(createFeature(id, "Point", out, properties));
|
|
204
262
|
}
|
|
205
|
-
function convertMultiPointFeature(features, id, geom, properties) {
|
|
263
|
+
function convertMultiPointFeature(features, id, geom, worldCoordinateHelper, properties) {
|
|
206
264
|
const out = [];
|
|
207
|
-
for (const coords of geom.coordinates) out.push(projectX(coords[0]), projectY(coords[1]), 0);
|
|
265
|
+
for (const coords of geom.coordinates) out.push(worldCoordinateHelper.projectX(coords[0]), worldCoordinateHelper.projectY(coords[1]), 0);
|
|
208
266
|
features.push(createFeature(id, "MultiPoint", out, properties));
|
|
209
267
|
}
|
|
210
|
-
function convertLineStringFeature(features, id, geom, tolerance, properties) {
|
|
268
|
+
function convertLineStringFeature(features, id, geom, tolerance, worldCoordinateHelper, properties) {
|
|
211
269
|
const out = { points: [] };
|
|
212
|
-
convertLine(geom.coordinates, out, tolerance, false);
|
|
270
|
+
convertLine(geom.coordinates, out, tolerance, false, worldCoordinateHelper);
|
|
213
271
|
features.push(createFeature(id, "LineString", out, properties));
|
|
214
272
|
}
|
|
215
|
-
function convertMultiLineStringFeature(features, id, geom, tolerance, options, properties) {
|
|
273
|
+
function convertMultiLineStringFeature(features, id, geom, tolerance, worldCoordinateHelper, options, properties) {
|
|
216
274
|
if (options.lineMetrics) for (const line of geom.coordinates) {
|
|
217
275
|
const out = { points: [] };
|
|
218
|
-
convertLine(line, out, tolerance, false);
|
|
276
|
+
convertLine(line, out, tolerance, false, worldCoordinateHelper);
|
|
219
277
|
features.push(createFeature(id, "LineString", out, properties));
|
|
220
278
|
}
|
|
221
279
|
else {
|
|
222
280
|
const out = [];
|
|
223
|
-
convertLines(geom.coordinates, out, tolerance, false);
|
|
281
|
+
convertLines(geom.coordinates, out, tolerance, false, worldCoordinateHelper);
|
|
224
282
|
features.push(createFeature(id, "MultiLineString", out, properties));
|
|
225
283
|
}
|
|
226
284
|
}
|
|
227
|
-
function convertPolygonFeature(features, id, geom, tolerance, properties) {
|
|
285
|
+
function convertPolygonFeature(features, id, geom, tolerance, worldCoordinateHelper, properties) {
|
|
228
286
|
const out = [];
|
|
229
|
-
convertLines(geom.coordinates, out, tolerance, true);
|
|
287
|
+
convertLines(geom.coordinates, out, tolerance, true, worldCoordinateHelper);
|
|
230
288
|
features.push(createFeature(id, "Polygon", out, properties));
|
|
231
289
|
}
|
|
232
|
-
function convertMultiPolygonFeature(features, id, geom, tolerance, properties) {
|
|
290
|
+
function convertMultiPolygonFeature(features, id, geom, tolerance, worldCoordinateHelper, properties) {
|
|
233
291
|
const out = [];
|
|
234
292
|
for (const polygon of geom.coordinates) {
|
|
235
293
|
const polygonOut = [];
|
|
236
|
-
convertLines(polygon, polygonOut, tolerance, true);
|
|
294
|
+
convertLines(polygon, polygonOut, tolerance, true, worldCoordinateHelper);
|
|
295
|
+
if (!polygonOut.length) continue;
|
|
237
296
|
out.push(polygonOut);
|
|
238
297
|
}
|
|
298
|
+
if (!out.length) return;
|
|
239
299
|
features.push(createFeature(id, "MultiPolygon", out, properties));
|
|
240
300
|
}
|
|
241
|
-
function convertLine(ring, out, tolerance, isPolygon) {
|
|
301
|
+
function convertLine(ring, out, tolerance, isPolygon, worldCoordinateHelper) {
|
|
242
302
|
let x0, y0;
|
|
243
303
|
let size = 0;
|
|
244
304
|
for (let j = 0; j < ring.length; j++) {
|
|
245
|
-
const x = projectX(ring[j][0]);
|
|
246
|
-
const y = projectY(ring[j][1]);
|
|
305
|
+
const x = worldCoordinateHelper.projectX(ring[j][0]);
|
|
306
|
+
const y = worldCoordinateHelper.projectY(ring[j][1]);
|
|
247
307
|
out.points.push(x, y, 0);
|
|
248
308
|
if (j > 0) {
|
|
249
309
|
if (isPolygon) size += (x0 * y - x * y0) / 2;
|
|
@@ -261,45 +321,31 @@ function convertLine(ring, out, tolerance, isPolygon) {
|
|
|
261
321
|
out.start = 0;
|
|
262
322
|
out.end = out.size;
|
|
263
323
|
}
|
|
264
|
-
function convertLines(rings, out, tolerance, isPolygon) {
|
|
324
|
+
function convertLines(rings, out, tolerance, isPolygon, worldCoordinateHelper) {
|
|
265
325
|
for (let i = 0; i < rings.length; i++) {
|
|
266
326
|
const geom = { points: [] };
|
|
267
|
-
convertLine(rings[i], geom, tolerance, isPolygon);
|
|
327
|
+
convertLine(rings[i], geom, tolerance, isPolygon, worldCoordinateHelper);
|
|
268
328
|
out.push(geom);
|
|
269
329
|
}
|
|
270
330
|
}
|
|
271
|
-
/**
|
|
272
|
-
* Convert longitude to spherical mercator in [0..1] range
|
|
273
|
-
*/
|
|
274
|
-
function projectX(x) {
|
|
275
|
-
return x / 360 + .5;
|
|
276
|
-
}
|
|
277
|
-
/**
|
|
278
|
-
* Convert latitude to spherical mercator in [0..1] range
|
|
279
|
-
*/
|
|
280
|
-
function projectY(y) {
|
|
281
|
-
const sin = Math.sin(y * Math.PI / 180);
|
|
282
|
-
const y2 = .5 - .25 * Math.log((1 + sin) / (1 - sin)) / Math.PI;
|
|
283
|
-
return y2 < 0 ? 0 : y2 > 1 ? 1 : y2;
|
|
284
|
-
}
|
|
285
331
|
//#endregion
|
|
286
332
|
//#region src/deconvert.ts
|
|
287
333
|
/**
|
|
288
334
|
* Converts internal source features back to GeoJSON format.
|
|
289
335
|
*/
|
|
290
|
-
function convertToGeoJSON(source) {
|
|
336
|
+
function convertToGeoJSON(source, worldCoordinateHelper = mercatorWorldCoordinateHelper) {
|
|
291
337
|
return {
|
|
292
338
|
type: "FeatureCollection",
|
|
293
|
-
features: source.map((feature) => featureToGeoJSON(feature))
|
|
339
|
+
features: source.map((feature) => featureToGeoJSON(feature, worldCoordinateHelper))
|
|
294
340
|
};
|
|
295
341
|
}
|
|
296
342
|
/**
|
|
297
343
|
* Converts a single internal feature to GeoJSON format.
|
|
298
344
|
*/
|
|
299
|
-
function featureToGeoJSON(feature) {
|
|
345
|
+
function featureToGeoJSON(feature, worldCoordinateHelper = mercatorWorldCoordinateHelper) {
|
|
300
346
|
const geojsonFeature = {
|
|
301
347
|
type: "Feature",
|
|
302
|
-
geometry: geometryToGeoJSON(feature),
|
|
348
|
+
geometry: geometryToGeoJSON(feature, worldCoordinateHelper),
|
|
303
349
|
properties: feature.tags
|
|
304
350
|
};
|
|
305
351
|
if (feature.id != null) geojsonFeature.id = feature.id;
|
|
@@ -308,52 +354,39 @@ function featureToGeoJSON(feature) {
|
|
|
308
354
|
/**
|
|
309
355
|
* Converts a single internal feature geometry to GeoJSON format.
|
|
310
356
|
*/
|
|
311
|
-
function geometryToGeoJSON(feature) {
|
|
357
|
+
function geometryToGeoJSON(feature, worldCoordinateHelper) {
|
|
312
358
|
const { type, geometry } = feature;
|
|
313
359
|
switch (type) {
|
|
314
360
|
case "Point": return {
|
|
315
361
|
type,
|
|
316
|
-
coordinates: unprojectPoint(geometry[0], geometry[1])
|
|
362
|
+
coordinates: unprojectPoint(geometry[0], geometry[1], worldCoordinateHelper)
|
|
317
363
|
};
|
|
318
364
|
case "MultiPoint": return {
|
|
319
365
|
type,
|
|
320
|
-
coordinates: unprojectPoints(geometry)
|
|
366
|
+
coordinates: unprojectPoints(geometry, worldCoordinateHelper)
|
|
321
367
|
};
|
|
322
368
|
case "LineString": return {
|
|
323
369
|
type,
|
|
324
|
-
coordinates: unprojectPoints(geometry.points)
|
|
370
|
+
coordinates: unprojectPoints(geometry.points, worldCoordinateHelper)
|
|
325
371
|
};
|
|
326
372
|
case "MultiLineString":
|
|
327
373
|
case "Polygon": return {
|
|
328
374
|
type,
|
|
329
|
-
coordinates: geometry.map((ring) => unprojectPoints(ring.points))
|
|
375
|
+
coordinates: geometry.map((ring) => unprojectPoints(ring.points, worldCoordinateHelper))
|
|
330
376
|
};
|
|
331
377
|
case "MultiPolygon": return {
|
|
332
378
|
type,
|
|
333
|
-
coordinates: geometry.map((polygon) => polygon.map((ring) => unprojectPoints(ring.points)))
|
|
379
|
+
coordinates: geometry.map((polygon) => polygon.map((ring) => unprojectPoints(ring.points, worldCoordinateHelper)))
|
|
334
380
|
};
|
|
335
381
|
}
|
|
336
382
|
}
|
|
337
|
-
function unprojectPoints(coords) {
|
|
383
|
+
function unprojectPoints(coords, worldCoordinateHelper = mercatorWorldCoordinateHelper) {
|
|
338
384
|
const result = [];
|
|
339
|
-
for (let i = 0; i < coords.length; i += 3) result.push(unprojectPoint(coords[i], coords[i + 1]));
|
|
385
|
+
for (let i = 0; i < coords.length; i += 3) result.push(unprojectPoint(coords[i], coords[i + 1], worldCoordinateHelper));
|
|
340
386
|
return result;
|
|
341
387
|
}
|
|
342
|
-
function unprojectPoint(x, y) {
|
|
343
|
-
return [unprojectX(x), unprojectY(y)];
|
|
344
|
-
}
|
|
345
|
-
/**
|
|
346
|
-
* Convert spherical mercator in [0..1] range to longitude
|
|
347
|
-
*/
|
|
348
|
-
function unprojectX(x) {
|
|
349
|
-
return (x - .5) * 360;
|
|
350
|
-
}
|
|
351
|
-
/**
|
|
352
|
-
* Convert spherical mercator in [0..1] range to latitude
|
|
353
|
-
*/
|
|
354
|
-
function unprojectY(y) {
|
|
355
|
-
const y2 = (180 - y * 360) * Math.PI / 180;
|
|
356
|
-
return 360 * Math.atan(Math.exp(y2)) / Math.PI - 90;
|
|
388
|
+
function unprojectPoint(x, y, worldCoordinateHelper) {
|
|
389
|
+
return [worldCoordinateHelper.unprojectX(x), worldCoordinateHelper.unprojectY(y)];
|
|
357
390
|
}
|
|
358
391
|
//#endregion
|
|
359
392
|
//#region src/clip.ts
|
|
@@ -478,12 +511,12 @@ function clipLine(geom, newGeom, start, end, axis, isPolygon, trackMetrics) {
|
|
|
478
511
|
let exited = false;
|
|
479
512
|
if (trackMetrics) segLen = Math.sqrt(Math.pow(ax - bx, 2) + Math.pow(ay - by, 2));
|
|
480
513
|
if (a < start) {
|
|
481
|
-
if (b
|
|
514
|
+
if (b >= start) {
|
|
482
515
|
t = intersect(slice, ax, ay, bx, by, start);
|
|
483
516
|
if (trackMetrics) slice.start = len + segLen * t;
|
|
484
517
|
}
|
|
485
518
|
} else if (a > end) {
|
|
486
|
-
if (b
|
|
519
|
+
if (b <= end) {
|
|
487
520
|
t = intersect(slice, ax, ay, bx, by, end);
|
|
488
521
|
if (trackMetrics) slice.start = len + segLen * t;
|
|
489
522
|
}
|
|
@@ -543,6 +576,7 @@ function intersectY(out, ax, ay, bx, by, y) {
|
|
|
543
576
|
//#endregion
|
|
544
577
|
//#region src/wrap.ts
|
|
545
578
|
function wrap(features, options) {
|
|
579
|
+
if (!options.wrap) return features;
|
|
546
580
|
const buffer = options.buffer / options.extent;
|
|
547
581
|
let merged = features;
|
|
548
582
|
const left = clip(features, 1, -1 - buffer, buffer, 0, -1, 2, options);
|
|
@@ -612,16 +646,19 @@ function shiftLineCoords(line, offset) {
|
|
|
612
646
|
* @param source
|
|
613
647
|
* @param dataDiff
|
|
614
648
|
* @param options
|
|
649
|
+
* @param unlocated - the features without coordinates, by id, so that an update can give them some
|
|
615
650
|
* @returns
|
|
616
651
|
*/
|
|
617
|
-
function applySourceDiff(source, dataDiff, options) {
|
|
652
|
+
function applySourceDiff(source, dataDiff, options, unlocated) {
|
|
618
653
|
const diff = diffToHashed(dataDiff, options);
|
|
619
654
|
let affected = [];
|
|
620
655
|
if (diff.removeAll) {
|
|
621
656
|
affected = source;
|
|
622
657
|
source = [];
|
|
658
|
+
unlocated?.clear();
|
|
623
659
|
}
|
|
624
660
|
if (diff.remove.size || diff.add.size) {
|
|
661
|
+
for (const id of diff.remove) unlocated?.delete(id);
|
|
625
662
|
const removeFeatures = [];
|
|
626
663
|
for (const feature of source) if (diff.remove.has(feature.id) || diff.add.has(feature.id)) removeFeatures.push(feature);
|
|
627
664
|
if (removeFeatures.length) {
|
|
@@ -633,7 +670,7 @@ function applySourceDiff(source, dataDiff, options) {
|
|
|
633
670
|
let addFeatures = convertToInternal({
|
|
634
671
|
type: "FeatureCollection",
|
|
635
672
|
features: Array.from(diff.add.values())
|
|
636
|
-
}, options);
|
|
673
|
+
}, options, unlocated);
|
|
637
674
|
addFeatures = wrap(addFeatures, options);
|
|
638
675
|
affected = affected.concat(addFeatures);
|
|
639
676
|
source = source.concat(addFeatures);
|
|
@@ -646,8 +683,14 @@ function applySourceDiff(source, dataDiff, options) {
|
|
|
646
683
|
else keepFeatures.push(feature);
|
|
647
684
|
for (const [id, update] of diff.update) {
|
|
648
685
|
const oldFeatures = oldFeaturesMap.get(id);
|
|
649
|
-
if (!oldFeatures || oldFeatures.length === 0)
|
|
650
|
-
|
|
686
|
+
if (!oldFeatures || oldFeatures.length === 0) {
|
|
687
|
+
if (!unlocated?.has(id)) continue;
|
|
688
|
+
const locatedFeatures = getUpdatedUnlocatedFeatures(id, update, options, unlocated);
|
|
689
|
+
affected = affected.concat(locatedFeatures);
|
|
690
|
+
keepFeatures = keepFeatures.concat(locatedFeatures);
|
|
691
|
+
continue;
|
|
692
|
+
}
|
|
693
|
+
const updatedFeatures = getUpdatedFeatures(oldFeatures, update, options, unlocated);
|
|
651
694
|
affected = affected.concat(oldFeatures, updatedFeatures);
|
|
652
695
|
keepFeatures = keepFeatures.concat(updatedFeatures);
|
|
653
696
|
}
|
|
@@ -663,24 +706,16 @@ function applySourceDiff(source, dataDiff, options) {
|
|
|
663
706
|
* @param vtFeatures - the original features
|
|
664
707
|
* @param update - the update object to apply
|
|
665
708
|
* @param options - the options to use for the wrap method
|
|
666
|
-
* @
|
|
709
|
+
* @param unlocated - the features without coordinates, by id, which a new geometry without coordinates adds the feature to
|
|
710
|
+
* @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.
|
|
667
711
|
*/
|
|
668
|
-
function getUpdatedFeatures(vtFeatures, update, options) {
|
|
712
|
+
function getUpdatedFeatures(vtFeatures, update, options, unlocated) {
|
|
669
713
|
const changeGeometry = !!update.newGeometry;
|
|
670
|
-
const changeProps = update
|
|
714
|
+
const changeProps = hasPropertyChanges(update);
|
|
671
715
|
if (changeGeometry) {
|
|
672
716
|
const vtFeature = vtFeatures[0];
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
features: [{
|
|
676
|
-
type: "Feature",
|
|
677
|
-
id: vtFeature.id,
|
|
678
|
-
geometry: update.newGeometry,
|
|
679
|
-
properties: changeProps ? applyPropertyUpdates(vtFeature.tags, update) : vtFeature.tags
|
|
680
|
-
}]
|
|
681
|
-
}, options);
|
|
682
|
-
features = wrap(features, options);
|
|
683
|
-
return features;
|
|
717
|
+
const properties = changeProps ? applyPropertyUpdates(vtFeature.tags, update) : vtFeature.tags;
|
|
718
|
+
return convertNewGeometry(vtFeature.id, update.newGeometry, properties, options, unlocated);
|
|
684
719
|
}
|
|
685
720
|
if (changeProps) {
|
|
686
721
|
const updated = [];
|
|
@@ -694,6 +729,43 @@ function getUpdatedFeatures(vtFeatures, update, options) {
|
|
|
694
729
|
return vtFeatures;
|
|
695
730
|
}
|
|
696
731
|
/**
|
|
732
|
+
* Gets the features an update gives a feature without coordinates: none, unless its new geometry has some.
|
|
733
|
+
* Its property changes apply either way.
|
|
734
|
+
*/
|
|
735
|
+
function getUpdatedUnlocatedFeatures(id, update, options, unlocated) {
|
|
736
|
+
const feature = unlocated.get(id);
|
|
737
|
+
const properties = hasPropertyChanges(update) ? applyPropertyUpdates(feature.properties, update) : feature.properties;
|
|
738
|
+
if (!update.newGeometry) {
|
|
739
|
+
unlocated.set(id, {
|
|
740
|
+
...feature,
|
|
741
|
+
properties
|
|
742
|
+
});
|
|
743
|
+
return [];
|
|
744
|
+
}
|
|
745
|
+
return convertNewGeometry(id, update.newGeometry, properties, options, unlocated);
|
|
746
|
+
}
|
|
747
|
+
/**
|
|
748
|
+
* Converts the new geometry of a feature to internal format, keeping the id the feature already has
|
|
749
|
+
*/
|
|
750
|
+
function convertNewGeometry(id, geometry, properties, options, unlocated) {
|
|
751
|
+
return wrap(convertToInternal({
|
|
752
|
+
type: "FeatureCollection",
|
|
753
|
+
features: [{
|
|
754
|
+
type: "Feature",
|
|
755
|
+
id,
|
|
756
|
+
geometry,
|
|
757
|
+
properties
|
|
758
|
+
}]
|
|
759
|
+
}, {
|
|
760
|
+
...options,
|
|
761
|
+
promoteId: void 0,
|
|
762
|
+
generateId: false
|
|
763
|
+
}, unlocated), options);
|
|
764
|
+
}
|
|
765
|
+
function hasPropertyChanges(update) {
|
|
766
|
+
return update.removeAllProperties || update.removeProperties?.length > 0 || update.addOrUpdateProperties?.length > 0;
|
|
767
|
+
}
|
|
768
|
+
/**
|
|
697
769
|
* helper to apply property updates from a diff update object to a properties object
|
|
698
770
|
*/
|
|
699
771
|
function applyPropertyUpdates(tags, update) {
|
|
@@ -715,7 +787,7 @@ function diffToHashed(diff, options) {
|
|
|
715
787
|
return {
|
|
716
788
|
removeAll: diff.removeAll,
|
|
717
789
|
remove: new Set(diff.remove || []),
|
|
718
|
-
add: new Map(diff.add?.map((feature) => [options.promoteId ? feature.properties[options.promoteId] : feature.id, feature])),
|
|
790
|
+
add: new Map(diff.add?.map((feature) => [options.promoteId ? feature.properties?.[options.promoteId] : feature.id, feature])),
|
|
719
791
|
update: new Map(diff.update?.map((update) => [update.id, update]))
|
|
720
792
|
};
|
|
721
793
|
}
|
|
@@ -1016,54 +1088,105 @@ const defaultClusterOptions = {
|
|
|
1016
1088
|
log: false,
|
|
1017
1089
|
generateId: false,
|
|
1018
1090
|
reduce: null,
|
|
1019
|
-
map: (props) => props
|
|
1091
|
+
map: (props) => props,
|
|
1092
|
+
coordinates: "lngLat",
|
|
1093
|
+
wrap: true
|
|
1020
1094
|
};
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
const
|
|
1025
|
-
|
|
1095
|
+
/**
|
|
1096
|
+
* The highest zoom level to cluster on. A cluster id keeps its zoom level in five bits.
|
|
1097
|
+
*/
|
|
1098
|
+
const MAX_ZOOM = 30;
|
|
1099
|
+
/**
|
|
1100
|
+
* Projected coordinates, which are 0 to 1 inside the world, are stored as integers with this many steps per world,
|
|
1101
|
+
* so that they take half the memory of doubles. One step is ~4 cm at the equator.
|
|
1102
|
+
*/
|
|
1103
|
+
const SCALE = 1073741824;
|
|
1104
|
+
/**
|
|
1105
|
+
* The largest value a 32-bit integer holds.
|
|
1106
|
+
*/
|
|
1107
|
+
const MAX_INTEGER = 2147483647;
|
|
1108
|
+
/**
|
|
1109
|
+
* A projected coordinate in steps from the center of the world, see `SCALE`.
|
|
1110
|
+
*/
|
|
1111
|
+
function encode(coord) {
|
|
1112
|
+
return (coord - .5) * SCALE;
|
|
1113
|
+
}
|
|
1114
|
+
function decode(value) {
|
|
1115
|
+
return value / SCALE + .5;
|
|
1116
|
+
}
|
|
1117
|
+
/**
|
|
1118
|
+
* Whether a projected coordinate fits in a 32-bit integer. One that is more than two worlds from the center of the
|
|
1119
|
+
* world does not, and neither does one that is not a number.
|
|
1120
|
+
*/
|
|
1121
|
+
function fitsInteger(coord) {
|
|
1122
|
+
return Math.abs(encode(coord)) < MAX_INTEGER;
|
|
1123
|
+
}
|
|
1124
|
+
const OFFSET_ZOOM = 0;
|
|
1125
|
+
const OFFSET_ID = 1;
|
|
1126
|
+
const OFFSET_PARENT = 2;
|
|
1127
|
+
const OFFSET_NUM = 3;
|
|
1128
|
+
const OFFSET_PROP = 4;
|
|
1026
1129
|
/**
|
|
1027
1130
|
* This class allow clustering of geojson points.
|
|
1028
1131
|
*/
|
|
1029
1132
|
var ClusterTileIndex = class {
|
|
1030
1133
|
constructor(options) {
|
|
1031
|
-
this.options =
|
|
1134
|
+
this.options = createOptions(options);
|
|
1135
|
+
this.worldCoordinateHelper = getWorldCoordinateHelper(this.options.coordinates);
|
|
1032
1136
|
this.trees = new Array(this.options.maxZoom + 1);
|
|
1033
|
-
this.stride = this.options.reduce ?
|
|
1137
|
+
this.stride = this.options.reduce ? 5 : 4;
|
|
1034
1138
|
this.clusterProps = [];
|
|
1035
1139
|
this.points = [];
|
|
1036
1140
|
}
|
|
1037
1141
|
/**
|
|
1038
|
-
* Loads GeoJSON
|
|
1039
|
-
*
|
|
1142
|
+
* Loads GeoJSON Point and MultiPoint features and builds the internal clustering index.
|
|
1143
|
+
* Each position of a MultiPoint is clustered as a point of its own, with the id and properties of its feature.
|
|
1144
|
+
* @param points - GeoJSON Point and MultiPoint features to cluster.
|
|
1040
1145
|
*/
|
|
1041
1146
|
load(points) {
|
|
1042
1147
|
const features = [];
|
|
1043
1148
|
for (const point of points) {
|
|
1044
1149
|
if (!point.geometry) continue;
|
|
1045
|
-
const
|
|
1046
|
-
const [
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1150
|
+
const positions = point.geometry.type === "MultiPoint" ? point.geometry.coordinates : [point.geometry.coordinates];
|
|
1151
|
+
for (const [lng, lat] of positions) {
|
|
1152
|
+
const [x, y] = [this.worldCoordinateHelper.projectX(lng), this.worldCoordinateHelper.projectY(lat)];
|
|
1153
|
+
if (!this.options.wrap && !isInWorld([x, y])) continue;
|
|
1154
|
+
const feature = {
|
|
1155
|
+
id: point.id,
|
|
1156
|
+
type: "Point",
|
|
1157
|
+
geometry: [x, y],
|
|
1158
|
+
tags: point.properties
|
|
1159
|
+
};
|
|
1160
|
+
features.push(feature);
|
|
1161
|
+
}
|
|
1054
1162
|
}
|
|
1055
1163
|
this.createIndex(features);
|
|
1056
1164
|
}
|
|
1057
1165
|
/**
|
|
1058
1166
|
* @internal
|
|
1059
1167
|
* Loads internal GeoJSONVT point features from a data source and builds the clustering index.
|
|
1168
|
+
* Each position of a MultiPoint feature becomes a point of its own, with the id and properties of its feature.
|
|
1060
1169
|
* @param features - {@link GeoJSONVTInternalFeature} data source features to filter and cluster.
|
|
1061
1170
|
*/
|
|
1062
1171
|
initialize(features) {
|
|
1063
1172
|
const points = [];
|
|
1064
1173
|
for (const feature of features) {
|
|
1065
|
-
if (feature.type
|
|
1066
|
-
|
|
1174
|
+
if (feature.type === "Point") {
|
|
1175
|
+
if (!this.options.wrap && !isInWorld(feature.geometry)) continue;
|
|
1176
|
+
points.push(feature);
|
|
1177
|
+
continue;
|
|
1178
|
+
}
|
|
1179
|
+
if (feature.type !== "MultiPoint") continue;
|
|
1180
|
+
for (let i = 0; i < feature.geometry.length; i += 3) {
|
|
1181
|
+
const geometry = [feature.geometry[i], feature.geometry[i + 1]];
|
|
1182
|
+
if (!this.options.wrap && !isInWorld(geometry)) continue;
|
|
1183
|
+
points.push({
|
|
1184
|
+
id: feature.id,
|
|
1185
|
+
type: "Point",
|
|
1186
|
+
geometry,
|
|
1187
|
+
tags: feature.tags
|
|
1188
|
+
});
|
|
1189
|
+
}
|
|
1067
1190
|
}
|
|
1068
1191
|
this.createIndex(points);
|
|
1069
1192
|
}
|
|
@@ -1073,41 +1196,113 @@ var ClusterTileIndex = class {
|
|
|
1073
1196
|
* @param features
|
|
1074
1197
|
*/
|
|
1075
1198
|
updateIndex(features, _affected, options) {
|
|
1076
|
-
this.options =
|
|
1199
|
+
this.options = createOptions({
|
|
1200
|
+
...options.clusterOptions,
|
|
1201
|
+
coordinates: this.options.coordinates,
|
|
1202
|
+
wrap: this.options.wrap
|
|
1203
|
+
});
|
|
1077
1204
|
this.initialize(features);
|
|
1078
1205
|
}
|
|
1206
|
+
/**
|
|
1207
|
+
* Builds a tree per zoom level from the points. A point whose position does not fit in an integer is left out,
|
|
1208
|
+
* see `fitsInteger`.
|
|
1209
|
+
*/
|
|
1079
1210
|
createIndex(points) {
|
|
1080
1211
|
const { log, minZoom, maxZoom } = this.options;
|
|
1212
|
+
this.trees = new Array(maxZoom + 1);
|
|
1213
|
+
this.stride = this.options.reduce ? 5 : 4;
|
|
1214
|
+
this.clusterProps = [];
|
|
1081
1215
|
if (log) console.time("total time");
|
|
1082
1216
|
const timerId = `prepare ${points.length} points`;
|
|
1083
1217
|
if (log) console.time(timerId);
|
|
1084
1218
|
this.points = points;
|
|
1085
|
-
const
|
|
1219
|
+
const stride = this.stride;
|
|
1220
|
+
const notProcessed = maxZoom + 1;
|
|
1221
|
+
const coords = new Int32Array(points.length * 2);
|
|
1222
|
+
const data = new Int32Array(points.length * stride);
|
|
1223
|
+
let numItems = 0;
|
|
1086
1224
|
for (let i = 0; i < points.length; i++) {
|
|
1087
1225
|
const p = points[i];
|
|
1088
|
-
if (!p?.geometry) continue;
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
data
|
|
1093
|
-
|
|
1226
|
+
if (!p?.geometry || !fitsInteger(p.geometry[0]) || !fitsInteger(p.geometry[1])) continue;
|
|
1227
|
+
coords[numItems * 2] = Math.round(encode(p.geometry[0]));
|
|
1228
|
+
coords[numItems * 2 + 1] = Math.round(encode(p.geometry[1]));
|
|
1229
|
+
const k = numItems * stride;
|
|
1230
|
+
data[k + OFFSET_ZOOM] = notProcessed;
|
|
1231
|
+
data[k + OFFSET_ID] = i;
|
|
1232
|
+
data[k + OFFSET_PARENT] = -1;
|
|
1233
|
+
data[k + OFFSET_NUM] = 1;
|
|
1234
|
+
numItems++;
|
|
1094
1235
|
}
|
|
1095
|
-
let tree = this.trees[maxZoom + 1] = this.createTree(data);
|
|
1236
|
+
let tree = this.trees[maxZoom + 1] = this.createTree(coords, data, numItems);
|
|
1096
1237
|
if (log) console.timeEnd(timerId);
|
|
1238
|
+
const nextCoords = new Int32Array(numItems * 2);
|
|
1239
|
+
const nextData = new Int32Array(numItems * stride);
|
|
1097
1240
|
for (let z = maxZoom; z >= minZoom; z--) {
|
|
1098
1241
|
const now = Date.now();
|
|
1099
|
-
|
|
1242
|
+
const nextNumItems = this.cluster(tree, z, nextCoords, nextData);
|
|
1243
|
+
if (!this.isSameAsTree(tree, nextData, nextNumItems)) tree = this.createTree(nextCoords.slice(0, nextNumItems * 2), nextData.slice(0, nextNumItems * stride), nextNumItems);
|
|
1244
|
+
this.trees[z] = tree;
|
|
1100
1245
|
if (log) console.log("z%d: %d clusters in %dms", z, tree.numItems, Date.now() - now);
|
|
1101
1246
|
}
|
|
1102
1247
|
if (log) console.timeEnd("total time");
|
|
1103
1248
|
}
|
|
1104
1249
|
/**
|
|
1105
1250
|
* Returns clusters and/or points within a bounding box at a given zoom level.
|
|
1106
|
-
* @param bbox - Bounding box in `[westLng, southLat, eastLng, northLat]` order
|
|
1251
|
+
* @param bbox - Bounding box in `[westLng, southLat, eastLng, northLat]` order, or `[minX, minY, maxX, maxY]` in the unit square with `coordinates: 'unitSquare'`.
|
|
1252
|
+
* In a world that wraps, the box can reach across the antimeridian. In one that doesn't, only its part inside the world counts.
|
|
1107
1253
|
* @param zoom - Zoom level to query.
|
|
1108
1254
|
*/
|
|
1109
1255
|
getClusters(bbox, zoom) {
|
|
1110
|
-
return this.getClustersInternal(bbox, zoom).map((f) => featureToGeoJSON(f));
|
|
1256
|
+
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));
|
|
1257
|
+
}
|
|
1258
|
+
/**
|
|
1259
|
+
* The clusters and points in the part of a box inside a world that ends at its edges.
|
|
1260
|
+
*/
|
|
1261
|
+
getClustersInWorld(bbox, zoom) {
|
|
1262
|
+
const lngLat = this.options.coordinates !== "unitSquare";
|
|
1263
|
+
const y1 = this.worldCoordinateHelper.projectY(lngLat ? Math.max(-90, Math.min(90, bbox[1])) : bbox[1]);
|
|
1264
|
+
const y2 = this.worldCoordinateHelper.projectY(lngLat ? Math.max(-90, Math.min(90, bbox[3])) : bbox[3]);
|
|
1265
|
+
const minX = Math.max(0, this.worldCoordinateHelper.projectX(bbox[0]));
|
|
1266
|
+
const maxX = Math.min(1, this.worldCoordinateHelper.projectX(bbox[2]));
|
|
1267
|
+
if (minX > maxX) return [];
|
|
1268
|
+
return this.getClustersInRange([
|
|
1269
|
+
minX,
|
|
1270
|
+
Math.min(y1, y2),
|
|
1271
|
+
maxX,
|
|
1272
|
+
Math.max(y1, y2)
|
|
1273
|
+
], zoom);
|
|
1274
|
+
}
|
|
1275
|
+
/**
|
|
1276
|
+
* The clusters and points in a box of a unit square that wraps, where the box can reach across its left or right edge.
|
|
1277
|
+
*/
|
|
1278
|
+
getClustersAcrossEdge([minX, minY, maxX, maxY], zoom) {
|
|
1279
|
+
if (maxX < minX) maxX += 1;
|
|
1280
|
+
if (maxX - minX >= 1) return this.getClustersInRange([
|
|
1281
|
+
0,
|
|
1282
|
+
minY,
|
|
1283
|
+
1,
|
|
1284
|
+
maxY
|
|
1285
|
+
], zoom);
|
|
1286
|
+
const shift = Math.floor(minX);
|
|
1287
|
+
const west = minX - shift;
|
|
1288
|
+
const east = maxX - shift;
|
|
1289
|
+
if (east <= 1) return this.getClustersInRange([
|
|
1290
|
+
west,
|
|
1291
|
+
minY,
|
|
1292
|
+
east,
|
|
1293
|
+
maxY
|
|
1294
|
+
], zoom);
|
|
1295
|
+
return this.getClustersInRange([
|
|
1296
|
+
west,
|
|
1297
|
+
minY,
|
|
1298
|
+
1,
|
|
1299
|
+
maxY
|
|
1300
|
+
], zoom).concat(this.getClustersInRange([
|
|
1301
|
+
0,
|
|
1302
|
+
minY,
|
|
1303
|
+
east - 1,
|
|
1304
|
+
maxY
|
|
1305
|
+
], zoom));
|
|
1111
1306
|
}
|
|
1112
1307
|
getClustersInternal(bbox, zoom) {
|
|
1113
1308
|
let minLng = ((bbox[0] + 180) % 360 + 360) % 360 - 180;
|
|
@@ -1132,13 +1327,25 @@ var ClusterTileIndex = class {
|
|
|
1132
1327
|
], zoom);
|
|
1133
1328
|
return easternHem.concat(westernHem);
|
|
1134
1329
|
}
|
|
1330
|
+
return this.getClustersInRange([
|
|
1331
|
+
this.worldCoordinateHelper.projectX(minLng),
|
|
1332
|
+
this.worldCoordinateHelper.projectY(maxLat),
|
|
1333
|
+
this.worldCoordinateHelper.projectX(maxLng),
|
|
1334
|
+
this.worldCoordinateHelper.projectY(minLat)
|
|
1335
|
+
], zoom);
|
|
1336
|
+
}
|
|
1337
|
+
/**
|
|
1338
|
+
* The clusters and points in a box of the internal coordinates, `[minX, minY, maxX, maxY]`.
|
|
1339
|
+
*/
|
|
1340
|
+
getClustersInRange([minX, minY, maxX, maxY], zoom) {
|
|
1135
1341
|
const tree = this.trees[this.limitZoom(zoom)];
|
|
1136
|
-
const ids = tree.range(
|
|
1342
|
+
const ids = tree.range(encode(minX), encode(minY), encode(maxX), encode(maxY));
|
|
1343
|
+
const coords = tree.flatCoords;
|
|
1137
1344
|
const data = tree.flatData;
|
|
1138
1345
|
const clusters = [];
|
|
1139
1346
|
for (const id of ids) {
|
|
1140
1347
|
const k = this.stride * id;
|
|
1141
|
-
clusters.push(data[k + OFFSET_NUM] > 1 ? getClusterFeature(data, k, this.clusterProps) : this.points[data[k + OFFSET_ID]]);
|
|
1348
|
+
clusters.push(data[k + OFFSET_NUM] > 1 ? getClusterFeature(coords, id, data, k, this.clusterProps) : this.points[data[k + OFFSET_ID]]);
|
|
1142
1349
|
}
|
|
1143
1350
|
return clusters;
|
|
1144
1351
|
}
|
|
@@ -1152,16 +1359,17 @@ var ClusterTileIndex = class {
|
|
|
1152
1359
|
const clusterError = /* @__PURE__ */ new Error("No cluster with the specified id: " + clusterId);
|
|
1153
1360
|
const tree = this.trees[originZoom];
|
|
1154
1361
|
if (!tree) throw clusterError;
|
|
1362
|
+
const coords = tree.flatCoords;
|
|
1155
1363
|
const data = tree.flatData;
|
|
1156
|
-
if (originId
|
|
1157
|
-
const r = this.options.radius / (this.options.extent * Math.pow(2, originZoom - 1));
|
|
1158
|
-
const x =
|
|
1159
|
-
const y =
|
|
1364
|
+
if (originId >= tree.numItems) throw clusterError;
|
|
1365
|
+
const r = this.options.radius / (this.options.extent * Math.pow(2, originZoom - 1)) * SCALE;
|
|
1366
|
+
const x = coords[originId * 2];
|
|
1367
|
+
const y = coords[originId * 2 + 1];
|
|
1160
1368
|
const ids = tree.within(x, y, r);
|
|
1161
1369
|
const children = [];
|
|
1162
1370
|
for (const id of ids) {
|
|
1163
1371
|
const k = id * this.stride;
|
|
1164
|
-
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]]));
|
|
1372
|
+
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));
|
|
1165
1373
|
}
|
|
1166
1374
|
if (children.length === 0) throw clusterError;
|
|
1167
1375
|
return children;
|
|
@@ -1189,10 +1397,11 @@ var ClusterTileIndex = class {
|
|
|
1189
1397
|
const tree = this.trees[this.limitZoom(z)];
|
|
1190
1398
|
if (!tree) return null;
|
|
1191
1399
|
const z2 = Math.pow(2, z);
|
|
1400
|
+
if (!this.options.wrap && (x < 0 || x >= z2 || y < 0 || y >= z2)) return null;
|
|
1192
1401
|
const { extent, radius } = this.options;
|
|
1193
1402
|
const p = radius / extent;
|
|
1194
|
-
const top = (y - p) / z2;
|
|
1195
|
-
const bottom = (y + 1 + p) / z2;
|
|
1403
|
+
const top = encode((y - p) / z2);
|
|
1404
|
+
const bottom = encode((y + 1 + p) / z2);
|
|
1196
1405
|
const tile = {
|
|
1197
1406
|
transformed: true,
|
|
1198
1407
|
features: [],
|
|
@@ -1201,9 +1410,9 @@ var ClusterTileIndex = class {
|
|
|
1201
1410
|
y,
|
|
1202
1411
|
z
|
|
1203
1412
|
};
|
|
1204
|
-
this.addTileFeatures(tree.range((x - p) / z2, top, (x + 1 + p) / z2, bottom), tree
|
|
1205
|
-
if (x === 0) this.addTileFeatures(tree.range(1 - p / z2, top, 1, bottom), tree
|
|
1206
|
-
if (x === z2 - 1) this.addTileFeatures(tree.range(0, top, p / z2, bottom), tree
|
|
1413
|
+
this.addTileFeatures(tree.range(encode((x - p) / z2), top, encode((x + 1 + p) / z2), bottom), tree, x, y, z2, tile);
|
|
1414
|
+
if (this.options.wrap && x === 0) this.addTileFeatures(tree.range(encode(1 - p / z2), top, encode(1), bottom), tree, z2, y, z2, tile);
|
|
1415
|
+
if (this.options.wrap && x === z2 - 1) this.addTileFeatures(tree.range(encode(0), top, encode(p / z2), bottom), tree, -1, y, z2, tile);
|
|
1207
1416
|
return tile;
|
|
1208
1417
|
}
|
|
1209
1418
|
/**
|
|
@@ -1226,15 +1435,27 @@ var ClusterTileIndex = class {
|
|
|
1226
1435
|
}
|
|
1227
1436
|
return skipped;
|
|
1228
1437
|
}
|
|
1229
|
-
createTree(data) {
|
|
1230
|
-
const tree = new KDBush(
|
|
1231
|
-
for (let i = 0; i <
|
|
1438
|
+
createTree(coords, data, numItems) {
|
|
1439
|
+
const tree = new KDBush(numItems, this.options.nodeSize, Int32Array);
|
|
1440
|
+
for (let i = 0; i < numItems; i++) tree.add(coords[i * 2], coords[i * 2 + 1]);
|
|
1232
1441
|
tree.finish();
|
|
1442
|
+
tree.flatCoords = coords;
|
|
1233
1443
|
tree.flatData = data;
|
|
1234
1444
|
tree.data = null;
|
|
1235
1445
|
return tree;
|
|
1236
1446
|
}
|
|
1237
|
-
|
|
1447
|
+
/**
|
|
1448
|
+
* Whether clustering a tree left all its points and clusters as they were, in the same order.
|
|
1449
|
+
*/
|
|
1450
|
+
isSameAsTree(tree, nextData, nextNumItems) {
|
|
1451
|
+
if (nextNumItems !== tree.numItems) return false;
|
|
1452
|
+
const data = tree.flatData;
|
|
1453
|
+
for (let k = OFFSET_ID; k < nextNumItems * this.stride; k += this.stride) if (nextData[k] !== data[k]) return false;
|
|
1454
|
+
return true;
|
|
1455
|
+
}
|
|
1456
|
+
addTileFeatures(ids, tree, x, y, z2, tile) {
|
|
1457
|
+
const coords = tree.flatCoords;
|
|
1458
|
+
const data = tree.flatData;
|
|
1238
1459
|
for (const i of ids) {
|
|
1239
1460
|
const k = i * this.stride;
|
|
1240
1461
|
const isCluster = data[k + OFFSET_NUM] > 1;
|
|
@@ -1243,8 +1464,8 @@ var ClusterTileIndex = class {
|
|
|
1243
1464
|
let py;
|
|
1244
1465
|
if (isCluster) {
|
|
1245
1466
|
tags = getClusterProperties(data, k, this.clusterProps);
|
|
1246
|
-
px =
|
|
1247
|
-
py =
|
|
1467
|
+
px = decode(coords[i * 2]);
|
|
1468
|
+
py = decode(coords[i * 2 + 1]);
|
|
1248
1469
|
} else {
|
|
1249
1470
|
const p = this.points[data[k + OFFSET_ID]];
|
|
1250
1471
|
tags = p.tags;
|
|
@@ -1265,22 +1486,30 @@ var ClusterTileIndex = class {
|
|
|
1265
1486
|
limitZoom(z) {
|
|
1266
1487
|
return Math.max(this.options.minZoom, Math.min(Math.floor(+z), this.options.maxZoom + 1));
|
|
1267
1488
|
}
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1489
|
+
/**
|
|
1490
|
+
* Clusters the points of a tree on a zoom level, writing the result to `nextCoords` and `nextData`.
|
|
1491
|
+
* @returns the number of points and clusters written
|
|
1492
|
+
*/
|
|
1493
|
+
cluster(tree, zoom, nextCoords, nextData) {
|
|
1494
|
+
const { radius, extent, reduce, minPoints, maxZoom } = this.options;
|
|
1495
|
+
const r = radius / (extent * Math.pow(2, zoom)) * SCALE;
|
|
1496
|
+
const notProcessed = maxZoom + 1;
|
|
1497
|
+
const coords = tree.flatCoords;
|
|
1271
1498
|
const data = tree.flatData;
|
|
1272
|
-
const nextData = [];
|
|
1273
1499
|
const stride = this.stride;
|
|
1274
|
-
|
|
1500
|
+
const neighborIds = new Uint32Array(tree.numItems);
|
|
1501
|
+
let nextNumItems = 0;
|
|
1502
|
+
for (let index = 0; index < tree.numItems; index++) {
|
|
1503
|
+
const i = index * stride;
|
|
1275
1504
|
if (data[i + OFFSET_ZOOM] <= zoom) continue;
|
|
1276
1505
|
data[i + OFFSET_ZOOM] = zoom;
|
|
1277
|
-
const x =
|
|
1278
|
-
const y =
|
|
1279
|
-
const
|
|
1506
|
+
const x = coords[index * 2];
|
|
1507
|
+
const y = coords[index * 2 + 1];
|
|
1508
|
+
const numNeighbors = tree.withinInto(x, y, r, neighborIds);
|
|
1280
1509
|
const numPointsOrigin = data[i + OFFSET_NUM];
|
|
1281
1510
|
let numPoints = numPointsOrigin;
|
|
1282
|
-
for (
|
|
1283
|
-
const k =
|
|
1511
|
+
for (let n = 0; n < numNeighbors; n++) {
|
|
1512
|
+
const k = neighborIds[n] * stride;
|
|
1284
1513
|
if (data[k + OFFSET_ZOOM] > zoom) numPoints += data[k + OFFSET_NUM];
|
|
1285
1514
|
}
|
|
1286
1515
|
if (numPoints > numPointsOrigin && numPoints >= minPoints) {
|
|
@@ -1288,14 +1517,15 @@ var ClusterTileIndex = class {
|
|
|
1288
1517
|
let wy = y * numPointsOrigin;
|
|
1289
1518
|
let clusterProperties;
|
|
1290
1519
|
let clusterPropIndex = -1;
|
|
1291
|
-
const id = (
|
|
1292
|
-
for (
|
|
1293
|
-
const
|
|
1520
|
+
const id = (index << 5) + (zoom + 1) + this.points.length;
|
|
1521
|
+
for (let n = 0; n < numNeighbors; n++) {
|
|
1522
|
+
const neighborIndex = neighborIds[n];
|
|
1523
|
+
const k = neighborIndex * stride;
|
|
1294
1524
|
if (data[k + OFFSET_ZOOM] <= zoom) continue;
|
|
1295
1525
|
data[k + OFFSET_ZOOM] = zoom;
|
|
1296
1526
|
const numPoints2 = data[k + OFFSET_NUM];
|
|
1297
|
-
wx +=
|
|
1298
|
-
wy +=
|
|
1527
|
+
wx += coords[neighborIndex * 2] * numPoints2;
|
|
1528
|
+
wy += coords[neighborIndex * 2 + 1] * numPoints2;
|
|
1299
1529
|
data[k + OFFSET_PARENT] = id;
|
|
1300
1530
|
if (reduce) {
|
|
1301
1531
|
if (!clusterProperties) {
|
|
@@ -1307,19 +1537,33 @@ var ClusterTileIndex = class {
|
|
|
1307
1537
|
}
|
|
1308
1538
|
}
|
|
1309
1539
|
data[i + OFFSET_PARENT] = id;
|
|
1310
|
-
|
|
1311
|
-
|
|
1540
|
+
nextCoords[nextNumItems * 2] = Math.round(wx / numPoints);
|
|
1541
|
+
nextCoords[nextNumItems * 2 + 1] = Math.round(wy / numPoints);
|
|
1542
|
+
const next = nextNumItems * stride;
|
|
1543
|
+
nextData[next + OFFSET_ZOOM] = notProcessed;
|
|
1544
|
+
nextData[next + OFFSET_ID] = id;
|
|
1545
|
+
nextData[next + OFFSET_PARENT] = -1;
|
|
1546
|
+
nextData[next + OFFSET_NUM] = numPoints;
|
|
1547
|
+
if (reduce) nextData[next + OFFSET_PROP] = clusterPropIndex;
|
|
1548
|
+
nextNumItems++;
|
|
1312
1549
|
} else {
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1550
|
+
nextCoords[nextNumItems * 2] = x;
|
|
1551
|
+
nextCoords[nextNumItems * 2 + 1] = y;
|
|
1552
|
+
for (let j = 0; j < stride; j++) nextData[nextNumItems * stride + j] = data[i + j];
|
|
1553
|
+
nextNumItems++;
|
|
1554
|
+
if (numPoints > 1) for (let n = 0; n < numNeighbors; n++) {
|
|
1555
|
+
const neighborIndex = neighborIds[n];
|
|
1556
|
+
const k = neighborIndex * stride;
|
|
1316
1557
|
if (data[k + OFFSET_ZOOM] <= zoom) continue;
|
|
1317
1558
|
data[k + OFFSET_ZOOM] = zoom;
|
|
1318
|
-
|
|
1559
|
+
nextCoords[nextNumItems * 2] = coords[neighborIndex * 2];
|
|
1560
|
+
nextCoords[nextNumItems * 2 + 1] = coords[neighborIndex * 2 + 1];
|
|
1561
|
+
for (let j = 0; j < stride; j++) nextData[nextNumItems * stride + j] = data[k + j];
|
|
1562
|
+
nextNumItems++;
|
|
1319
1563
|
}
|
|
1320
1564
|
}
|
|
1321
1565
|
}
|
|
1322
|
-
return
|
|
1566
|
+
return nextNumItems;
|
|
1323
1567
|
}
|
|
1324
1568
|
getOriginId(clusterId) {
|
|
1325
1569
|
return clusterId - this.points.length >> 5;
|
|
@@ -1337,22 +1581,28 @@ var ClusterTileIndex = class {
|
|
|
1337
1581
|
return clone && result === original ? Object.assign({}, result) : result;
|
|
1338
1582
|
}
|
|
1339
1583
|
};
|
|
1340
|
-
function
|
|
1584
|
+
function createOptions(options) {
|
|
1585
|
+
const result = Object.assign(Object.create(defaultClusterOptions), options);
|
|
1586
|
+
result.minZoom = Math.min(result.minZoom, MAX_ZOOM);
|
|
1587
|
+
result.maxZoom = Math.min(result.maxZoom, MAX_ZOOM);
|
|
1588
|
+
return result;
|
|
1589
|
+
}
|
|
1590
|
+
function getClusterFeature(coords, index, data, i, clusterProps) {
|
|
1341
1591
|
return {
|
|
1342
1592
|
id: data[i + OFFSET_ID],
|
|
1343
1593
|
type: "Point",
|
|
1344
1594
|
tags: getClusterProperties(data, i, clusterProps),
|
|
1345
|
-
geometry: [
|
|
1595
|
+
geometry: [decode(coords[index * 2]), decode(coords[index * 2 + 1])]
|
|
1346
1596
|
};
|
|
1347
1597
|
}
|
|
1348
|
-
function getClusterGeoJSON(data, i, clusterProps) {
|
|
1598
|
+
function getClusterGeoJSON(coords, index, data, i, clusterProps, worldCoordinateHelper) {
|
|
1349
1599
|
return {
|
|
1350
1600
|
type: "Feature",
|
|
1351
1601
|
id: data[i + OFFSET_ID],
|
|
1352
1602
|
properties: getClusterProperties(data, i, clusterProps),
|
|
1353
1603
|
geometry: {
|
|
1354
1604
|
type: "Point",
|
|
1355
|
-
coordinates: [unprojectX(
|
|
1605
|
+
coordinates: [worldCoordinateHelper.unprojectX(decode(coords[index * 2])), worldCoordinateHelper.unprojectY(decode(coords[index * 2 + 1]))]
|
|
1356
1606
|
}
|
|
1357
1607
|
};
|
|
1358
1608
|
}
|
|
@@ -1368,6 +1618,9 @@ function getClusterProperties(data, i, clusterProps) {
|
|
|
1368
1618
|
point_count_abbreviated: abbrev
|
|
1369
1619
|
});
|
|
1370
1620
|
}
|
|
1621
|
+
function isInWorld(geometry) {
|
|
1622
|
+
return geometry[0] >= 0 && geometry[0] <= 1 && geometry[1] >= 0 && geometry[1] <= 1;
|
|
1623
|
+
}
|
|
1371
1624
|
//#endregion
|
|
1372
1625
|
//#region src/tile.ts
|
|
1373
1626
|
const GEOJSONVT_CLIP_START = "geojsonvt_clip_start";
|
|
@@ -1576,7 +1829,7 @@ var TileIndex = class {
|
|
|
1576
1829
|
this.total = 0;
|
|
1577
1830
|
}
|
|
1578
1831
|
initialize(features) {
|
|
1579
|
-
this.splitTile(features, 0, 0, 0);
|
|
1832
|
+
this.splitTile(this.clipToWorld(features), 0, 0, 0);
|
|
1580
1833
|
if (this.options.debug) {
|
|
1581
1834
|
if (features.length) console.log("features: %d, points: %d", this.tiles[0].numFeatures, this.tiles[0].numPoints);
|
|
1582
1835
|
console.timeEnd("generate tiles");
|
|
@@ -1596,8 +1849,9 @@ var TileIndex = class {
|
|
|
1596
1849
|
0,
|
|
1597
1850
|
0
|
|
1598
1851
|
];
|
|
1599
|
-
const
|
|
1600
|
-
rootTile
|
|
1852
|
+
const rootSource = this.clipToWorld(source);
|
|
1853
|
+
const rootTile = createTile(rootSource, z, x, y, options);
|
|
1854
|
+
rootTile.source = rootSource;
|
|
1601
1855
|
const id = toID(z, x, y);
|
|
1602
1856
|
this.tiles[id] = rootTile;
|
|
1603
1857
|
this.tileCoords.push({
|
|
@@ -1628,6 +1882,7 @@ var TileIndex = class {
|
|
|
1628
1882
|
getTile(z, x, y) {
|
|
1629
1883
|
const { extent, debug } = this.options;
|
|
1630
1884
|
const z2 = 1 << z;
|
|
1885
|
+
if (!this.options.wrap && (x < 0 || x >= z2)) return null;
|
|
1631
1886
|
x = x + z2 & z2 - 1;
|
|
1632
1887
|
const id = toID(z, x, y);
|
|
1633
1888
|
if (this.tiles[id]) return transformTile(this.tiles[id], extent);
|
|
@@ -1653,6 +1908,18 @@ var TileIndex = class {
|
|
|
1653
1908
|
return transformTile(this.tiles[id], extent);
|
|
1654
1909
|
}
|
|
1655
1910
|
/**
|
|
1911
|
+
* The features the top tile starts from. A world that wraps keeps them all left and right, where wrapping already
|
|
1912
|
+
* put them inside, and one that doesn't only what lies inside the world square and its buffer. Positions in the
|
|
1913
|
+
* unit square are also cut above and below in a world that wraps, where lng/lat is clamped already. The index keeps
|
|
1914
|
+
* the rest, so an update can still move a feature in from outside.
|
|
1915
|
+
*/
|
|
1916
|
+
clipToWorld(features) {
|
|
1917
|
+
const { wrap, coordinates } = this.options;
|
|
1918
|
+
if (wrap && coordinates !== "unitSquare") return features;
|
|
1919
|
+
const buffer = this.options.buffer / this.options.extent;
|
|
1920
|
+
return clip(wrap ? features : clip(features, 1, -buffer, 1 + buffer, 0, -Infinity, Infinity, this.options) || [], 1, -buffer, 1 + buffer, 1, -Infinity, Infinity, this.options) || [];
|
|
1921
|
+
}
|
|
1922
|
+
/**
|
|
1656
1923
|
* splits features from a parent tile to sub-tiles.
|
|
1657
1924
|
* z, x, and y are the coordinates of the parent tile
|
|
1658
1925
|
* cz, cx, and cy are the coordinates of the target tile
|
|
@@ -1805,7 +2072,9 @@ const defaultOptions = {
|
|
|
1805
2072
|
updateable: false,
|
|
1806
2073
|
cluster: false,
|
|
1807
2074
|
clusterOptions: defaultClusterOptions,
|
|
1808
|
-
debug: 0
|
|
2075
|
+
debug: 0,
|
|
2076
|
+
coordinates: "lngLat",
|
|
2077
|
+
wrap: true
|
|
1809
2078
|
};
|
|
1810
2079
|
/**
|
|
1811
2080
|
* Main class for creating and managing a vector tile index from GeoJSON data.
|
|
@@ -1813,11 +2082,13 @@ const defaultOptions = {
|
|
|
1813
2082
|
var GeoJSONVT = class {
|
|
1814
2083
|
constructor(data, options) {
|
|
1815
2084
|
options = this.options = Object.assign({}, defaultOptions, options);
|
|
2085
|
+
this.worldCoordinateHelper = getWorldCoordinateHelper(options.coordinates);
|
|
1816
2086
|
const debug = options.debug;
|
|
1817
2087
|
if (debug) console.time("preprocess data");
|
|
1818
2088
|
if (options.maxZoom < 0 || options.maxZoom > 24) throw new Error("maxZoom should be in the 0-24 range");
|
|
1819
2089
|
if (options.promoteId && options.generateId) throw new Error("promoteId and generateId cannot be used together.");
|
|
1820
|
-
|
|
2090
|
+
if (options.updateable) this.unlocated = /* @__PURE__ */ new Map();
|
|
2091
|
+
let features = convertToInternal(data, options, this.unlocated);
|
|
1821
2092
|
if (debug) {
|
|
1822
2093
|
console.timeEnd("preprocess data");
|
|
1823
2094
|
console.log("index: maxZoom: %d, maxPoints: %d", options.indexMaxZoom, options.indexMaxPoints);
|
|
@@ -1828,7 +2099,11 @@ var GeoJSONVT = class {
|
|
|
1828
2099
|
this.initializeIndex(features, options);
|
|
1829
2100
|
}
|
|
1830
2101
|
initializeIndex(features, options) {
|
|
1831
|
-
this.tileIndex = options.cluster ? new ClusterTileIndex(
|
|
2102
|
+
this.tileIndex = options.cluster ? new ClusterTileIndex({
|
|
2103
|
+
...options.clusterOptions,
|
|
2104
|
+
coordinates: options.coordinates,
|
|
2105
|
+
wrap: options.wrap
|
|
2106
|
+
}) : new TileIndex(options);
|
|
1832
2107
|
if (!features.length) return;
|
|
1833
2108
|
this.tileIndex.initialize(features);
|
|
1834
2109
|
}
|
|
@@ -1853,24 +2128,27 @@ var GeoJSONVT = class {
|
|
|
1853
2128
|
updateData(diff, filter) {
|
|
1854
2129
|
const options = this.options;
|
|
1855
2130
|
if (!options.updateable) throw new Error("to update tile geojson `updateable` option must be set to true");
|
|
1856
|
-
let { affected, source } = applySourceDiff(this.source, diff, options);
|
|
2131
|
+
let { affected, source } = applySourceDiff(this.source, diff, options, this.unlocated);
|
|
1857
2132
|
if (filter) ({affected, source} = this.filterUpdate(source, affected, filter));
|
|
1858
2133
|
if (!affected.length) return;
|
|
1859
2134
|
this.source = source;
|
|
1860
2135
|
this.tileIndex.updateIndex(source, affected, options);
|
|
1861
2136
|
}
|
|
1862
2137
|
/**
|
|
1863
|
-
* Filter an update using a predicate function
|
|
2138
|
+
* Filter an update using a predicate function, the features an update emptied included, so that one the filter
|
|
2139
|
+
* rejects can't come back with a later update. A feature without a geometry at all isn't passed to the predicate.
|
|
2140
|
+
* Returns the affected and updated source features.
|
|
1864
2141
|
*/
|
|
1865
2142
|
filterUpdate(source, affected, predicate) {
|
|
1866
2143
|
const removeIds = /* @__PURE__ */ new Set();
|
|
1867
2144
|
for (const feature of source) {
|
|
1868
2145
|
if (feature.id == void 0) continue;
|
|
1869
|
-
if (predicate(featureToGeoJSON(feature))) continue;
|
|
2146
|
+
if (predicate(featureToGeoJSON(feature, this.worldCoordinateHelper))) continue;
|
|
1870
2147
|
affected.push(feature);
|
|
1871
2148
|
removeIds.add(feature.id);
|
|
1872
2149
|
}
|
|
1873
2150
|
source = source.filter((feature) => !removeIds.has(feature.id));
|
|
2151
|
+
for (const [id, feature] of this.unlocated ?? []) if (feature.geometry && !predicate(feature)) this.unlocated.delete(id);
|
|
1874
2152
|
return {
|
|
1875
2153
|
affected,
|
|
1876
2154
|
source
|
|
@@ -1881,12 +2159,12 @@ var GeoJSONVT = class {
|
|
|
1881
2159
|
*/
|
|
1882
2160
|
getData() {
|
|
1883
2161
|
if (!this.options.updateable) throw new Error("to retrieve data the `updateable` option must be set to true");
|
|
1884
|
-
return convertToGeoJSON(this.source);
|
|
2162
|
+
return convertToGeoJSON(this.source, this.worldCoordinateHelper);
|
|
1885
2163
|
}
|
|
1886
2164
|
/**
|
|
1887
2165
|
* Update supercluster options and regenerate the index.
|
|
1888
2166
|
* @param cluster - whether to enable clustering
|
|
1889
|
-
* @param clusterOptions - {@link SuperclusterOptions}
|
|
2167
|
+
* @param clusterOptions - {@link SuperclusterOptions} without `coordinates` and `wrap`, which come from the index options
|
|
1890
2168
|
*/
|
|
1891
2169
|
updateClusterOptions(cluster, clusterOptions) {
|
|
1892
2170
|
const wasCluster = this.options.cluster;
|
|
@@ -1962,9 +2240,10 @@ var GeoJSONVT = class {
|
|
|
1962
2240
|
function geoJSONToTile(data, z, x, y, options = {}) {
|
|
1963
2241
|
options = {
|
|
1964
2242
|
...defaultOptions,
|
|
2243
|
+
wrap: false,
|
|
1965
2244
|
...options
|
|
1966
2245
|
};
|
|
1967
|
-
const { wrap: shouldWrap
|
|
2246
|
+
const { wrap: shouldWrap, clip: shouldClip = false } = options;
|
|
1968
2247
|
let features = convertToInternal(data, options);
|
|
1969
2248
|
if (shouldWrap) features = wrap(features, options);
|
|
1970
2249
|
if (shouldClip || options.lineMetrics) {
|