@maplibre/geojson-vt 6.0.5 → 6.1.1

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.
@@ -5,21 +5,6 @@ export declare const defaultOptions: GeoJSONVTOptions;
5
5
  * Main class for creating and managing a vector tile index from GeoJSON data.
6
6
  */
7
7
  export declare class GeoJSONVT {
8
- /**
9
- * @internal
10
- * This is for the tests
11
- */
12
- get tiles(): any;
13
- /**
14
- * @internal
15
- * This is for the tests
16
- */
17
- get stats(): any;
18
- /**
19
- * @internal
20
- * This is for the tests
21
- */
22
- get total(): any;
23
8
  private options;
24
9
  private source?;
25
10
  private tileIndex;
@@ -1 +1 @@
1
- {"version":3,"file":"geojsonvt.d.ts","sourceRoot":"","sources":["../src/geojsonvt.ts"],"names":[],"mappings":"AAGA,OAAO,EAAkB,KAAK,mBAAmB,EAAC,MAAM,cAAc,CAAC;AAGvE,OAAO,KAAK,EAAC,qBAAqB,EAAgD,gBAAgB,EAAE,aAAa,EAAE,mBAAmB,EAAC,MAAM,eAAe,CAAC;AAE7J,eAAO,MAAM,cAAc,EAAE,gBAc5B,CAAC;AAEF;;GAEG;AACH,qBAAa,SAAS;IAElB;;;OAGG;IACH,IAAW,KAAK,QAGf;IACD;;;OAGG;IACH,IAAW,KAAK,QAGf;IACA;;;MAGE;IACH,IAAW,KAAK,QAGf;IAED,OAAO,CAAC,OAAO,CAAmB;IAElC,OAAO,CAAC,MAAM,CAAC,CAA6B;IAC5C,OAAO,CAAC,SAAS,CAAqB;gBAE1B,IAAI,EAAE,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,gBAAgB;IA8B7D,OAAO,CAAC,eAAe;IAMvB;;;;;;OAMG;IACH,OAAO,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,aAAa,GAAG,IAAI;IAUzF;;;OAGG;IACH,UAAU,CAAC,IAAI,EAAE,mBAAmB,EAAE,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,KAAK,OAAO;IAqBpF;;OAEG;IACH,OAAO,CAAC,YAAY;IAcpB;;OAEG;IACH,OAAO,IAAI,OAAO,CAAC,OAAO;IAK1B;;;;OAIG;IACH,oBAAoB,CAAC,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,mBAAmB;IAa1E;;;;OAIG;IACH,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAIzD;;;;OAIG;IACH,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,qBAAqB,EAAE,GAAG,IAAI;IAIrE;;;;;;OAMG;IACH,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,GAAG,IAAI;CAG9G"}
1
+ {"version":3,"file":"geojsonvt.d.ts","sourceRoot":"","sources":["../src/geojsonvt.ts"],"names":[],"mappings":"AAGA,OAAO,EAAkB,KAAK,mBAAmB,EAAC,MAAM,cAAc,CAAC;AAGvE,OAAO,KAAK,EAAC,qBAAqB,EAAgD,gBAAgB,EAAE,aAAa,EAAE,mBAAmB,EAAC,MAAM,eAAe,CAAC;AAE7J,eAAO,MAAM,cAAc,EAAE,gBAc5B,CAAC;AAEF;;GAEG;AACH,qBAAa,SAAS;IAClB,OAAO,CAAC,OAAO,CAAmB;IAElC,OAAO,CAAC,MAAM,CAAC,CAA6B;IAC5C,OAAO,CAAC,SAAS,CAAqB;gBAE1B,IAAI,EAAE,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,gBAAgB;IA8B7D,OAAO,CAAC,eAAe;IAMvB;;;;;;OAMG;IACH,OAAO,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,aAAa,GAAG,IAAI;IAUzF;;;OAGG;IACH,UAAU,CAAC,IAAI,EAAE,mBAAmB,EAAE,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,KAAK,OAAO;IAqBpF;;OAEG;IACH,OAAO,CAAC,YAAY;IAcpB;;OAEG;IACH,OAAO,IAAI,OAAO,CAAC,OAAO;IAK1B;;;;OAIG;IACH,oBAAoB,CAAC,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,mBAAmB;IAa1E;;;;OAIG;IACH,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAIzD;;;;OAIG;IACH,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,qBAAqB,EAAE,GAAG,IAAI;IAIrE;;;;;;OAMG;IACH,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,GAAG,IAAI;CAG9G"}
@@ -2,6 +2,7 @@ import type { GeoJSONVTInternalFeature, GeoJSONVTOptions, ClusterOrPointFeature,
2
2
  export declare class TileIndex implements GeoJSONVTTileIndex {
3
3
  private options;
4
4
  private tileCoords;
5
+ private total;
5
6
  /** @internal */
6
7
  tiles: {
7
8
  [key: string]: GeoJSONVTInternalTile;
@@ -10,8 +11,6 @@ export declare class TileIndex implements GeoJSONVTTileIndex {
10
11
  stats: {
11
12
  [key: string]: number;
12
13
  };
13
- /** @internal */
14
- total: number;
15
14
  constructor(options: GeoJSONVTOptions);
16
15
  initialize(features: GeoJSONVTInternalFeature[]): void;
17
16
  /** {@inheritdoc} */
@@ -1 +1 @@
1
- {"version":3,"file":"tile-index.d.ts","sourceRoot":"","sources":["../src/tile-index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,wBAAwB,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEjK,qBAAa,SAAU,YAAW,kBAAkB;IAWpC,OAAO,CAAC,OAAO;IAT3B,OAAO,CAAC,UAAU,CAAkD;IAEpE,gBAAgB;IACT,KAAK,EAAE;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,qBAAqB,CAAA;KAAC,CAAC;IACrD,gBAAgB;IACT,KAAK,EAAE;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;KAAC,CAAM;IAC3C,gBAAgB;IACT,KAAK,EAAE,MAAM,CAAK;gBAEL,OAAO,EAAE,gBAAgB;IAO7C,UAAU,CAAC,QAAQ,EAAE,wBAAwB,EAAE,GAAG,IAAI;IAWtD,oBAAoB;IACpB,WAAW,CAAC,MAAM,EAAE,wBAAwB,EAAE,EAAE,QAAQ,EAAE,wBAAwB,EAAE,EAAE,OAAO,EAAE,gBAAgB,GAAG,IAAI;IA2BtH,oBAAoB;IAEpB,uBAAuB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAI1D,oBAAoB;IAEpB,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,qBAAqB,EAAE,GAAG,IAAI;IAI/D,oBAAoB;IAEpB,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,GAAG,IAAI;IAIzG,oBAAoB;IACpB,OAAO,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI;IAwC9D;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,SAAS;IA4FjB;;;;OAIG;IACH,OAAO,CAAC,eAAe;CAuE1B"}
1
+ {"version":3,"file":"tile-index.d.ts","sourceRoot":"","sources":["../src/tile-index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,wBAAwB,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEjK,qBAAa,SAAU,YAAW,kBAAkB;IAWpC,OAAO,CAAC,OAAO;IAT3B,OAAO,CAAC,UAAU,CAAkD;IACpE,OAAO,CAAC,KAAK,CAAa;IAE1B,gBAAgB;IACT,KAAK,EAAE;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,qBAAqB,CAAA;KAAC,CAAC;IACrD,gBAAgB;IACT,KAAK,EAAE;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;KAAC,CAAM;gBAGvB,OAAO,EAAE,gBAAgB;IAO7C,UAAU,CAAC,QAAQ,EAAE,wBAAwB,EAAE,GAAG,IAAI;IAWtD,oBAAoB;IACpB,WAAW,CAAC,MAAM,EAAE,wBAAwB,EAAE,EAAE,QAAQ,EAAE,wBAAwB,EAAE,EAAE,OAAO,EAAE,gBAAgB,GAAG,IAAI;IA2BtH,oBAAoB;IAEpB,uBAAuB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAI1D,oBAAoB;IAEpB,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,qBAAqB,EAAE,GAAG,IAAI;IAI/D,oBAAoB;IAEpB,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,GAAG,IAAI;IAIzG,oBAAoB;IACpB,OAAO,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI;IAwC9D;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,SAAS;IA4FjB;;;;OAIG;IACH,OAAO,CAAC,eAAe;CAuE1B"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=tile-index.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tile-index.test.d.ts","sourceRoot":"","sources":["../src/tile-index.test.ts"],"names":[],"mappings":""}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maplibre/geojson-vt",
3
- "version": "6.0.5",
3
+ "version": "6.1.1",
4
4
  "description": "Slice GeoJSON data into vector tiles efficiently",
5
5
  "type": "module",
6
6
  "exports": {
@@ -25,30 +25,26 @@
25
25
  "unpkg": "dist/geojson-vt.js",
26
26
  "devDependencies": {
27
27
  "@eslint/js": "^10.0.1",
28
- "@rollup/plugin-node-resolve": "^16.0.3",
29
- "@rollup/plugin-terser": "^1.0.0",
30
- "@rollup/plugin-typescript": "^12.3.0",
31
28
  "@types/benchmark": "^2.1.5",
32
29
  "@types/geojson": "^7946.0.16",
33
- "@types/node": "^25.5.2",
34
- "@vitest/coverage-v8": "^4.1.2",
30
+ "@types/node": "^26.0.1",
31
+ "@vitest/coverage-v8": "^4.1.8",
35
32
  "benchmark": "^2.1.4",
36
- "eslint": "^10.2.0",
37
- "rollup": "^4.60.1",
38
- "tslib": "^2.8.1",
39
- "tsx": "^4.21.0",
40
- "typedoc": "^0.28.18",
41
- "typescript": "^6.0.2",
42
- "typescript-eslint": "^8.58.0",
43
- "vitest": "^4.0.17"
33
+ "eslint": "^10.6.0",
34
+ "rolldown": "^1.1.3",
35
+ "tsx": "^4.22.4",
36
+ "typedoc": "^0.28.19",
37
+ "typescript": "^6.0.3",
38
+ "typescript-eslint": "^8.62.1",
39
+ "vitest": "^4.1.9"
44
40
  },
45
41
  "license": "ISC",
46
42
  "scripts": {
47
43
  "lint": "eslint",
48
44
  "test": "vitest",
49
45
  "coverage": "vitest run --coverage",
50
- "build": "rollup -c && tsc -p tsconfig.declaration.json",
51
- "watch": "rollup -cw",
46
+ "build": "rolldown -c && tsc -p tsconfig.declaration.json",
47
+ "watch": "rolldown -cw",
52
48
  "start": "npm run watch",
53
49
  "bench": "tsx bench/benchmark.ts && tsx --expose-gc bench/memory-bench.ts",
54
50
  "docs": "typedoc && mkdir -p docs/debug && cp -r debug/* docs/debug && find docs/debug -name '*.html' -exec sed -i 's|../dist/geojson-vt-dev.js|https://unpkg.com/@maplibre/geojson-vt@latest/dist/geojson-vt.js|g' {} +",
@@ -63,6 +59,6 @@
63
59
  "url": "https://github.com/maplibre/geojson-vt"
64
60
  },
65
61
  "dependencies": {
66
- "kdbush": "^4.0.2"
62
+ "kdbush": "^4.1.0"
67
63
  }
68
64
  }
@@ -0,0 +1,40 @@
1
+ import {test, expect, describe} from 'vitest';
2
+ import {convertToInternal} from './convert';
3
+ import {defaultOptions} from './geojsonvt';
4
+
5
+ /**
6
+ * Wraps a geometry in `depth` nested GeometryCollections.
7
+ */
8
+ function nestGeometryCollections(depth: number, inner: GeoJSON.Geometry): GeoJSON.Geometry {
9
+ let geometry = inner;
10
+ for (let i = 0; i < depth; i++) {
11
+ geometry = {type: 'GeometryCollection', geometries: [geometry]};
12
+ }
13
+ return geometry;
14
+ }
15
+
16
+ const point: GeoJSON.Point = {type: 'Point', coordinates: [0, 0]};
17
+
18
+ function feature(geometry: GeoJSON.Geometry): GeoJSON.Feature {
19
+ return {type: 'Feature', geometry, properties: null};
20
+ }
21
+ describe('convertToInternal', () => {
22
+ test('converts a geometry nested in GeometryCollections', () => {
23
+ const features = convertToInternal(feature(nestGeometryCollections(3, point)), defaultOptions);
24
+
25
+ expect(features).toHaveLength(1);
26
+ expect(features[0].type).toBe('Point');
27
+ });
28
+
29
+ test('allows nesting up to the supported depth', () => {
30
+ const features = convertToInternal(feature(nestGeometryCollections(1024, point)), defaultOptions);
31
+
32
+ expect(features).toHaveLength(1);
33
+ expect(features[0].type).toBe('Point');
34
+ });
35
+
36
+ test('throws when GeometryCollection nesting exceeds the supported depth', () => {
37
+ expect(() => convertToInternal(feature(nestGeometryCollections(1025, point)), defaultOptions))
38
+ .toThrow('GeometryCollection nesting exceeds supported depth: 1024');
39
+ });
40
+ })
package/src/convert.ts CHANGED
@@ -3,6 +3,8 @@ import {simplify} from './simplify';
3
3
  import {createFeature, optimizeLineMemory} from './feature';
4
4
  import type {GeoJSONVTInternalFeature, GeoJSONVTOptions, SliceArray} from './definitions';
5
5
 
6
+ const MAX_GEOMETRY_COLLECTION_DEPTH = 1024;
7
+
6
8
  /**
7
9
  * converts GeoJSON to internal source features (an intermediate projected JSON vector format with simplification data)
8
10
  * @param data
@@ -28,11 +30,15 @@ export function convertToInternal(data: GeoJSON.GeoJSON, options: GeoJSONVTOptio
28
30
  return features;
29
31
  }
30
32
 
31
- function featureToInternal(features: GeoJSONVTInternalFeature[], geojson: GeoJSON.Feature, options: GeoJSONVTOptions, index?: number) {
33
+ function featureToInternal(features: GeoJSONVTInternalFeature[], geojson: GeoJSON.Feature, options: GeoJSONVTOptions, index?: number, depth = 0) {
32
34
  if (!geojson.geometry) return;
33
35
 
36
+ if (depth > MAX_GEOMETRY_COLLECTION_DEPTH) {
37
+ throw new Error('GeometryCollection nesting exceeds supported depth: ' + MAX_GEOMETRY_COLLECTION_DEPTH);
38
+ }
39
+
34
40
  if (geojson.geometry.type === 'GeometryCollection') {
35
- convertGeometryCollection(features, geojson, geojson.geometry, options, index);
41
+ convertGeometryCollection(features, geojson, geojson.geometry, options, index, depth + 1);
36
42
  return;
37
43
  }
38
44
 
@@ -82,14 +88,14 @@ function getFeatureId(geojson: GeoJSON.Feature, options: GeoJSONVTOptions, index
82
88
  return geojson.id;
83
89
  }
84
90
 
85
- function convertGeometryCollection(features: GeoJSONVTInternalFeature[], geojson: GeoJSON.Feature, geometry: GeoJSON.GeometryCollection, options: GeoJSONVTOptions, index?: number) {
91
+ function convertGeometryCollection(features: GeoJSONVTInternalFeature[], geojson: GeoJSON.Feature, geometry: GeoJSON.GeometryCollection, options: GeoJSONVTOptions, index?: number, depth = 0) {
86
92
  for (const geom of geometry.geometries) {
87
93
  featureToInternal(features, {
88
94
  id: geojson.id,
89
95
  type: 'Feature',
90
96
  geometry: geom,
91
97
  properties: geojson.properties
92
- }, options, index);
98
+ }, options, index, depth);
93
99
  }
94
100
  }
95
101
 
package/src/difference.ts CHANGED
@@ -95,7 +95,7 @@ export function applySourceDiff(source: GeoJSONVTInternalFeature[], dataDiff: Ge
95
95
  }
96
96
 
97
97
  if (removeFeatures.length) {
98
- affected.push(...removeFeatures);
98
+ affected = affected.concat(removeFeatures);
99
99
  const removeIds = new Set(removeFeatures.map(f => f.id));
100
100
  source = source.filter(f => !removeIds.has(f.id));
101
101
  }
@@ -103,33 +103,31 @@ export function applySourceDiff(source: GeoJSONVTInternalFeature[], dataDiff: Ge
103
103
  if (diff.add.size) {
104
104
  let addFeatures = convertToInternal({type: 'FeatureCollection', features: Array.from(diff.add.values())}, options);
105
105
  addFeatures = wrap(addFeatures, options);
106
- affected.push(...addFeatures);
107
- source.push(...addFeatures);
106
+ affected = affected.concat(addFeatures);
107
+ source = source.concat(addFeatures);
108
108
  }
109
109
  }
110
110
 
111
111
  if (diff.update.size) {
112
112
  // Features can be duplicated across the antimeridian (wrap) in a single tile, so must update all instances with the same id
113
- for (const [id, update] of diff.update) {
114
- const oldFeatures = [];
115
- const keepFeatures = [];
116
-
117
- for (const feature of source) {
118
- if (feature.id === id) {
119
- oldFeatures.push(feature);
120
- } else {
121
- keepFeatures.push(feature);
122
- }
113
+ const oldFeaturesMap = new Map<string | number, GeoJSONVTInternalFeature[]>();
114
+ let keepFeatures: GeoJSONVTInternalFeature[] = [];
115
+ for (const feature of source) {
116
+ if (diff.update.has(feature.id)) {
117
+ oldFeaturesMap.set(feature.id, [...(oldFeaturesMap.get(feature.id) || []), feature]);
118
+ } else {
119
+ keepFeatures.push(feature);
123
120
  }
124
- if (!oldFeatures.length) continue;
125
-
121
+ }
122
+ for (const [id, update] of diff.update) {
123
+ const oldFeatures = oldFeaturesMap.get(id);
124
+ if (!oldFeatures || oldFeatures.length === 0) continue;
126
125
  const updatedFeatures = getUpdatedFeatures(oldFeatures, update, options);
127
- if (!updatedFeatures.length) continue;
128
126
 
129
- affected.push(...oldFeatures, ...updatedFeatures);
130
- keepFeatures.push(...updatedFeatures);
131
- source = keepFeatures;
127
+ affected = affected.concat(oldFeatures, updatedFeatures);
128
+ keepFeatures = keepFeatures.concat(updatedFeatures);
132
129
  }
130
+ source = keepFeatures;
133
131
  }
134
132
 
135
133
  return {affected, source};
@@ -174,7 +172,7 @@ function getUpdatedFeatures(vtFeatures: GeoJSONVTInternalFeature[], update: GeoJ
174
172
  return updated;
175
173
  }
176
174
 
177
- return [];
175
+ return vtFeatures;
178
176
  }
179
177
 
180
178
  /**
package/src/geojsonvt.ts CHANGED
@@ -26,32 +26,6 @@ export const defaultOptions: GeoJSONVTOptions = {
26
26
  * Main class for creating and managing a vector tile index from GeoJSON data.
27
27
  */
28
28
  export class GeoJSONVT {
29
-
30
- /**
31
- * @internal
32
- * This is for the tests
33
- */
34
- public get tiles() {
35
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
36
- return (this.tileIndex as any)?.tiles ?? {};
37
- }
38
- /**
39
- * @internal
40
- * This is for the tests
41
- */
42
- public get stats() {
43
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
44
- return (this.tileIndex as any).stats;
45
- }
46
- /**
47
- * @internal
48
- * This is for the tests
49
- */
50
- public get total() {
51
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
52
- return (this.tileIndex as any).total;
53
- }
54
-
55
29
  private options: GeoJSONVTOptions;
56
30
 
57
31
  private source?: GeoJSONVTInternalFeature[];
@@ -0,0 +1,270 @@
1
+ import {describe, expect, test} from "vitest";
2
+ import {TileIndex} from "./tile-index";
3
+ import {convertToInternal} from "./convert";
4
+ import {applySourceDiff} from "./difference";
5
+ import {wrap} from "./wrap";
6
+ import {defaultOptions} from "./geojsonvt";
7
+
8
+ function toID(z: number, x: number, y: number): number {
9
+ return (((1 << z) * y + x) * 32) + z;
10
+ }
11
+
12
+ describe('TileIndex', () => {
13
+ test('updateData: invalidates empty tiles', () => {
14
+ const initialData = {
15
+ type: 'FeatureCollection' as const,
16
+ features: [
17
+ {
18
+ type: 'Feature' as const,
19
+ id: 'nw-only',
20
+ geometry: {
21
+ type: 'Point' as const,
22
+ coordinates: [-90, 45] // top left quadrant only
23
+ },
24
+ properties: {}
25
+ }
26
+ ]
27
+ };
28
+ const options = {
29
+ ...defaultOptions,
30
+ updateable: true,
31
+ indexMaxZoom: 1,
32
+ indexMaxPoints: 0,
33
+ debug: 2
34
+ };
35
+ const index = new TileIndex(options);
36
+ let sourceFeatures = convertToInternal(initialData, options);
37
+ sourceFeatures = wrap(sourceFeatures, options);
38
+ index.initialize(sourceFeatures);
39
+ expect(index.stats.z1).toBe(4);
40
+
41
+ const globalFeature = {
42
+ type: 'Feature' as const,
43
+ id: 'global',
44
+ geometry: {
45
+ type: 'LineString' as const,
46
+ coordinates: [[-180, -85], [180, 85]] // spans whole world
47
+ },
48
+ properties: {}
49
+ };
50
+ const {source, affected} = applySourceDiff(sourceFeatures, {add: [globalFeature]}, options);
51
+ index.updateIndex(source, affected, options);
52
+ expect(index.stats.z1).toBe(0);
53
+ });
54
+
55
+ test('updateData: invalidates tiles when feature is within the buffer edge', () => {
56
+ const initialData = {
57
+ type: 'FeatureCollection' as const,
58
+ features: [{
59
+ type: 'Feature' as const,
60
+ id: 'feature1',
61
+ geometry: {
62
+ type: 'Point' as const,
63
+ coordinates: [-45, 45] // inside tile 1-0-0
64
+ },
65
+ properties: {}
66
+ }]
67
+ };
68
+
69
+ const options = {
70
+ ...defaultOptions,
71
+ updateable: true,
72
+ indexMaxZoom: 1,
73
+ indexMaxPoints: 0
74
+ };
75
+ const index = new TileIndex(options);
76
+ let sourceFeatures = convertToInternal(initialData, options);
77
+ sourceFeatures = wrap(sourceFeatures, options);
78
+ index.initialize(sourceFeatures);
79
+
80
+ const tileId = toID(1, 0, 0);
81
+ index.getTile(1, 0, 0);
82
+ expect(index.tiles[tileId]).toBeTruthy();
83
+
84
+ const featureWithinBuffer = {
85
+ type: 'Feature' as const,
86
+ id: 'buffer-feature',
87
+ geometry: {
88
+ type: 'Point' as const,
89
+ coordinates: [2, 0] // feature within tile buffer edge
90
+ },
91
+ properties: {}
92
+ };
93
+ const {source, affected} = applySourceDiff(sourceFeatures, {add: [featureWithinBuffer]}, options);
94
+ index.updateIndex(source, affected, options);
95
+ expect(index.tiles[tileId]).toBeUndefined();
96
+ });
97
+
98
+ test('updateData: invalidates tiles at deeper zoom', () => {
99
+ const initialData = {
100
+ type: 'FeatureCollection' as const,
101
+ features: [{
102
+ type: 'Feature' as const,
103
+ id: 'feature1',
104
+ geometry: {
105
+ type: 'Polygon' as const,
106
+ coordinates: [[
107
+ [0, 0], [5, 0], [5, 5], [0, 5], [0, 0]
108
+ ]]
109
+ },
110
+ properties: {name: 'Original'}
111
+ }]
112
+ };
113
+
114
+ const options = {
115
+ ...defaultOptions,
116
+ updateable: true,
117
+ indexMaxZoom: 5,
118
+ indexMaxPoints: 0
119
+ };
120
+ const index = new TileIndex(options);
121
+ let sourceFeatures = convertToInternal(initialData, options);
122
+ sourceFeatures = wrap(sourceFeatures, options);
123
+ index.initialize(sourceFeatures);
124
+
125
+ const tileId = toID(5, 16, 16);
126
+
127
+ const tileBefore = index.tiles[tileId];
128
+ expect(tileBefore).toBeTruthy();
129
+ expect(tileBefore.numFeatures).toBe(1);
130
+
131
+ const updatedFeature = {
132
+ type: 'Feature' as const,
133
+ id: 'feature1',
134
+ geometry: {
135
+ type: 'Polygon' as const,
136
+ coordinates: [[
137
+ [0, 0], [10, 0], [10, 10], [0, 10], [0, 0]
138
+ ]]
139
+ },
140
+ properties: {name: 'Updated'}
141
+ };
142
+
143
+ const {source, affected} = applySourceDiff(sourceFeatures, {add: [updatedFeature]}, options);
144
+ index.updateIndex(source, affected, options);
145
+
146
+ const tileAfter = index.tiles[tileId];
147
+ expect(tileAfter).toBeUndefined();
148
+
149
+ const tileRegenerated = index.getTile(5, 16, 16);
150
+ expect(tileRegenerated).toBeTruthy();
151
+ expect(tileRegenerated.features[0].tags.name).toBe('Updated');
152
+ });
153
+
154
+ test('updateData: does not invalidate unaffected tiles', () => {
155
+ const initialData = {
156
+ type: 'FeatureCollection' as const,
157
+ features: [
158
+ {
159
+ type: 'Feature' as const,
160
+ id: 'northwest',
161
+ geometry: {
162
+ type: 'Point' as const,
163
+ coordinates: [-90, 45] // NW quadrant only
164
+ },
165
+ properties: {}
166
+ },
167
+ {
168
+ type: 'Feature' as const,
169
+ id: 'southeast',
170
+ geometry: {
171
+ type: 'Point' as const,
172
+ coordinates: [90, -45] // SE quadrant only
173
+ },
174
+ properties: {}
175
+ }
176
+ ]
177
+ };
178
+
179
+ const options = {
180
+ ...defaultOptions,
181
+ updateable: true,
182
+ indexMaxZoom: 2,
183
+ indexMaxPoints: 0
184
+ };
185
+ const index = new TileIndex(options);
186
+ let sourceFeatures = convertToInternal(initialData, options);
187
+ sourceFeatures = wrap(sourceFeatures, options);
188
+ index.initialize(sourceFeatures);
189
+
190
+ const nwTileId = toID(1, 0, 0);
191
+ const seTileId = toID(1, 1, 1);
192
+
193
+ const nwTileBefore = index.tiles[nwTileId];
194
+ const seTileBefore = index.tiles[seTileId];
195
+
196
+ expect(nwTileBefore).toBeTruthy();
197
+ expect(seTileBefore).toBeTruthy();
198
+
199
+ const updatedFeature = {
200
+ type: 'Feature' as const,
201
+ id: 'northwest',
202
+ geometry: {
203
+ type: 'Point' as const,
204
+ coordinates: [-85, 40] // NW different coordinate
205
+ },
206
+ properties: {}
207
+ };
208
+
209
+
210
+ const {source, affected} = applySourceDiff(sourceFeatures, {add: [updatedFeature]}, options);
211
+ index.updateIndex(source, affected, options);
212
+
213
+ const nwTileAfter = index.tiles[nwTileId];
214
+ expect(nwTileAfter).toBeUndefined();
215
+
216
+ const seTileAfter = index.tiles[seTileId];
217
+ expect(seTileAfter).toBe(seTileBefore);
218
+ });
219
+ });
220
+
221
+ describe('Multiworld test', () => {
222
+ const leftPoint = {
223
+ type: 'Feature' as const,
224
+ properties: {},
225
+ geometry: {
226
+ coordinates: [-540, 0],
227
+ type: 'Point' as const
228
+ }
229
+ };
230
+
231
+ const rightPoint = {
232
+ type: 'Feature' as const,
233
+ properties: {},
234
+ geometry: {
235
+ coordinates: [540, 0],
236
+ type: 'Point' as const
237
+ }
238
+ };
239
+
240
+ test('handle point only in the rightside world', () => {
241
+ const vt = new TileIndex(defaultOptions);
242
+ let sourceFeatures = convertToInternal(rightPoint, defaultOptions);
243
+ sourceFeatures = wrap(sourceFeatures, defaultOptions);
244
+ vt.initialize(sourceFeatures);
245
+ expect(vt.tiles[0].features[0].geometry[0]).toBe(1);
246
+ expect(vt.tiles[0].features[0].geometry[1]).toBe(.5);
247
+ });
248
+
249
+ test('handle point only in the leftside world', () => {
250
+ const vt = new TileIndex(defaultOptions);
251
+ let sourceFeatures = convertToInternal(leftPoint, defaultOptions);
252
+ sourceFeatures = wrap(sourceFeatures, defaultOptions);
253
+ vt.initialize(sourceFeatures);
254
+ expect(vt.tiles[0].features[0].geometry[0]).toBe(0);
255
+ expect(vt.tiles[0].features[0].geometry[1]).toBe(.5);
256
+ });
257
+
258
+ test('handle points in the leftside world and the rightside world', () => {
259
+ const vt = new TileIndex(defaultOptions);
260
+ let sourceFeatures = convertToInternal({type: 'FeatureCollection', features: [leftPoint, rightPoint]}, defaultOptions);
261
+ sourceFeatures = wrap(sourceFeatures, defaultOptions);
262
+ vt.initialize(sourceFeatures);
263
+
264
+ expect(vt.tiles[0].features[0].geometry[0]).toBe(0);
265
+ expect(vt.tiles[0].features[0].geometry[1]).toBe(.5);
266
+
267
+ expect(vt.tiles[0].features[1].geometry[0]).toBe(1);
268
+ expect(vt.tiles[0].features[1].geometry[1]).toBe(.5);
269
+ });
270
+ })
package/src/tile-index.ts CHANGED
@@ -6,13 +6,13 @@ import type { GeoJSONVTInternalFeature, GeoJSONVTOptions, ClusterOrPointFeature,
6
6
  export class TileIndex implements GeoJSONVTTileIndex {
7
7
 
8
8
  private tileCoords: {z: number, x: number, y: number, id: number}[];
9
+ private total: number = 0;
9
10
 
10
11
  /** @internal */
11
12
  public tiles: {[key: string]: GeoJSONVTInternalTile};
12
13
  /** @internal */
13
14
  public stats: {[key: string]: number} = {};
14
- /** @internal */
15
- public total: number = 0;
15
+
16
16
 
17
17
  constructor(private options: GeoJSONVTOptions) {
18
18
  this.tiles = {};
@@ -54,7 +54,7 @@ export class TileIndex implements GeoJSONVTTileIndex {
54
54
  this.tileCoords.push({z, x, y, id});
55
55
 
56
56
  if (options.debug) {
57
- const key = `z${ z}`;
57
+ const key = `z${z}`;
58
58
  this.stats[key] = (this.stats[key] || 0) + 1;
59
59
  this.total++;
60
60
  }
@@ -164,7 +164,7 @@ export class TileIndex implements GeoJSONVTTileIndex {
164
164
  z, x, y, tile.numFeatures, tile.numPoints, tile.numSimplified);
165
165
  console.timeEnd('creation');
166
166
  }
167
- const key = `z${ z}`;
167
+ const key = `z${z}`;
168
168
  this.stats[key] = (this.stats[key] || 0) + 1;
169
169
  this.total++;
170
170
  }
@@ -289,7 +289,7 @@ export class TileIndex implements GeoJSONVTTileIndex {
289
289
  console.log('invalidate tile z%d-%d-%d (features: %d, points: %d, simplified: %d)',
290
290
  tile.z, tile.x, tile.y, tile.numFeatures, tile.numPoints, tile.numSimplified);
291
291
  }
292
- const key = `z${ tile.z}`;
292
+ const key = `z${tile.z}`;
293
293
  this.stats[key] = (this.stats[key] || 0) - 1;
294
294
  this.total--;
295
295
  }