@maplibre/geojson-vt 6.1.1 → 6.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/geojsonvt.ts CHANGED
@@ -19,7 +19,8 @@ export const defaultOptions: GeoJSONVTOptions = {
19
19
  updateable: false,
20
20
  cluster: false,
21
21
  clusterOptions: defaultClusterOptions,
22
- debug: 0
22
+ debug: 0,
23
+ worldCoordinates: false
23
24
  };
24
25
 
25
26
  /**
@@ -62,7 +63,7 @@ export class GeoJSONVT {
62
63
  }
63
64
 
64
65
  private initializeIndex(features: GeoJSONVTInternalFeature[], options: GeoJSONVTOptions) {
65
- this.tileIndex = options.cluster ? new ClusterTileIndex(options.clusterOptions) : new TileIndex(options);
66
+ this.tileIndex = options.cluster ? new ClusterTileIndex(options.clusterOptions, options.worldCoordinates) : new TileIndex(options);
66
67
  if (!features.length) return;
67
68
  this.tileIndex.initialize(features);
68
69
  }
@@ -117,7 +118,7 @@ export class GeoJSONVT {
117
118
 
118
119
  for (const feature of source) {
119
120
  if (feature.id == undefined) continue;
120
- if (predicate(featureToGeoJSON(feature))) continue;
121
+ if (predicate(featureToGeoJSON(feature, this.options.worldCoordinates))) continue;
121
122
  affected.push(feature);
122
123
  removeIds.add(feature.id);
123
124
  }
@@ -131,7 +132,7 @@ export class GeoJSONVT {
131
132
  */
132
133
  getData(): GeoJSON.GeoJSON {
133
134
  if (!this.options.updateable) throw new Error('to retrieve data the `updateable` option must be set to true');
134
- return convertToGeoJSON(this.source);
135
+ return convertToGeoJSON(this.source, this.options.worldCoordinates);
135
136
  }
136
137
 
137
138
  /**
@@ -71,3 +71,21 @@ test('does not throw max call stack error on bad long input', () => {
71
71
  simplify(coords, 0, coords.length, 2e-15);
72
72
  }).not.toThrow();
73
73
  });
74
+
75
+ test('does not throw max call stack error when splitting off one vertex at a time due to zig-zag', () => {
76
+ // the old recursive implementation overflows at ~5000 vertices; the work is quadratic, so keep it small
77
+ const n = 6000;
78
+ const coords: number[] = [];
79
+ for (let i = 0; i < n; i++) {
80
+ coords.push(i / n, 0.5 + (i % 2 ? 1 : -1) * (1 + i * 1e-4) * 1e-3, 0);
81
+ }
82
+
83
+ expect(() => {
84
+ simplify(coords, 0, coords.length - 3, 1e-12);
85
+ }).not.toThrow();
86
+ let kept = 0;
87
+ for (let i = 5; i < coords.length - 3; i += 3) {
88
+ if (coords[i] > 0) kept++;
89
+ }
90
+ expect(kept).toBe(n - 2);
91
+ });
package/src/simplify.ts CHANGED
@@ -6,6 +6,16 @@
6
6
  * @param sqTolerance - square tolerance value
7
7
  */
8
8
  export function simplify(coords: number[], first: number, last: number, sqTolerance: number) {
9
+ const stack = [first, last];
10
+
11
+ while (stack.length) {
12
+ last = stack.pop();
13
+ first = stack.pop();
14
+ simplifySegment(coords, first, last, sqTolerance, stack);
15
+ }
16
+ }
17
+
18
+ function simplifySegment(coords: number[], first: number, last: number, sqTolerance: number, stack: number[]) {
9
19
  let maxSqDist = sqTolerance;
10
20
  const mid = first + ((last - first) >> 1);
11
21
  let minPosToMid = last - first;
@@ -38,9 +48,9 @@ export function simplify(coords: number[], first: number, last: number, sqTolera
38
48
  }
39
49
 
40
50
  if (maxSqDist > sqTolerance) {
41
- if (index - first > 3) simplify(coords, first, index, sqTolerance);
42
51
  coords[index + 2] = maxSqDist;
43
- if (last - index > 3) simplify(coords, index, last, sqTolerance);
52
+ if (index - first > 3) stack.push(first, index);
53
+ if (last - index > 3) stack.push(index, last);
44
54
  }
45
55
  }
46
56
 
package/src/tile-index.ts CHANGED
@@ -23,7 +23,7 @@ export class TileIndex implements GeoJSONVTTileIndex {
23
23
 
24
24
  initialize(features: GeoJSONVTInternalFeature[]): void {
25
25
  // start slicing from the top tile down
26
- this.splitTile(features, 0, 0, 0);
26
+ this.splitTile(this.clipToWorld(features), 0, 0, 0);
27
27
 
28
28
  if (this.options.debug) {
29
29
  if (features.length) console.log('features: %d, points: %d', this.tiles[0].numFeatures, this.tiles[0].numPoints);
@@ -45,8 +45,9 @@ export class TileIndex implements GeoJSONVTTileIndex {
45
45
 
46
46
  // re-generate root tile with updated feature set
47
47
  const [z, x, y] = [0, 0, 0];
48
- const rootTile = createTile(source, z, x, y, options);
49
- rootTile.source = source;
48
+ const rootSource = this.clipToWorld(source);
49
+ const rootTile = createTile(rootSource, z, x, y, options);
50
+ rootTile.source = rootSource;
50
51
 
51
52
  // update tile index with new root tile - ready for getTile calls
52
53
  const id = toID(z, x, y);
@@ -83,6 +84,7 @@ export class TileIndex implements GeoJSONVTTileIndex {
83
84
  const {extent, debug} = this.options;
84
85
 
85
86
  const z2 = 1 << z;
87
+ if (this.options.worldCoordinates && (x < 0 || x >= z2)) return null; // a row outside the world has no tile either
86
88
  x = (x + z2) & (z2 - 1); // wrap tile x coordinate
87
89
 
88
90
  const id = toID(z, x, y);
@@ -119,6 +121,18 @@ export class TileIndex implements GeoJSONVTTileIndex {
119
121
  return transformTile(this.tiles[id], extent);
120
122
  }
121
123
 
124
+ /**
125
+ * The features the top tile starts from: all of them in a wrapping world, where wrapping already put them inside,
126
+ * and only what lies inside the world square and its buffer with `worldCoordinates`. The index keeps the rest, so
127
+ * an update can still move a feature in from outside.
128
+ */
129
+ private clipToWorld(features: GeoJSONVTInternalFeature[]): GeoJSONVTInternalFeature[] {
130
+ if (!this.options.worldCoordinates) return features;
131
+ const buffer = this.options.buffer / this.options.extent;
132
+ const inX = clip(features, 1, -buffer, 1 + buffer, AxisType.X, -Infinity, Infinity, this.options) || [];
133
+ return clip(inX, 1, -buffer, 1 + buffer, AxisType.Y, -Infinity, Infinity, this.options) || [];
134
+ }
135
+
122
136
  /**
123
137
  * splits features from a parent tile to sub-tiles.
124
138
  * z, x, and y are the coordinates of the parent tile
package/src/wrap.ts CHANGED
@@ -4,6 +4,8 @@ import type { GeoJSONVTInternalFeature, GeoJSONVTOptions, SliceArray, SliceFixed
4
4
  import {createFeature, optimizeLineMemory} from './feature';
5
5
 
6
6
  export function wrap(features: GeoJSONVTInternalFeature[], options: GeoJSONVTOptions): GeoJSONVTInternalFeature[] {
7
+ if (options.worldCoordinates) return features;
8
+
7
9
  const buffer = options.buffer / options.extent;
8
10
  let merged = features;
9
11