@maplibre/geojson-vt 5.0.0 → 5.0.2

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/feature.ts ADDED
@@ -0,0 +1,64 @@
1
+ import type { GeoJSONVTFeature, GeometryType, GeometryTypeMap } from "./definitions";
2
+
3
+ export type SupportedGeometries = GeoJSON.Point | GeoJSON.MultiPoint | GeoJSON.LineString | GeoJSON.MultiLineString | GeoJSON.Polygon | GeoJSON.MultiPolygon;
4
+
5
+ /**
6
+ *
7
+ * @param id - the feature's ID
8
+ * @param type - the feature's type
9
+ * @param geom - the feature's geometry
10
+ * @param tags - the feature's properties
11
+ * @returns the created feature
12
+ */
13
+ export function createFeature<T extends GeometryType>(id: number | string | undefined, type: T, geom: GeometryTypeMap[T], tags: GeoJSON.GeoJsonProperties): GeoJSONVTFeature {
14
+ // This is mostly for TypeScript type narrowing
15
+ const data = { type, geom } as { [K in GeometryType]: { type: K, geom: GeometryTypeMap[K] } }[GeometryType];
16
+
17
+ const feature = {
18
+ id: id == null ? null : id,
19
+ type: data.type,
20
+ geometry: data.geom,
21
+ tags,
22
+ minX: Infinity,
23
+ minY: Infinity,
24
+ maxX: -Infinity,
25
+ maxY: -Infinity
26
+ } as GeoJSONVTFeature;
27
+
28
+ switch (data.type) {
29
+ case 'Point':
30
+ case 'MultiPoint':
31
+ case 'LineString':
32
+ calcLineBBox(feature, data.geom);
33
+ break;
34
+
35
+ case 'Polygon':
36
+ // the outer ring (ie [0]) contains all inner rings
37
+ calcLineBBox(feature, data.geom[0]);
38
+ break;
39
+
40
+ case 'MultiLineString':
41
+ for (const line of data.geom) {
42
+ calcLineBBox(feature, line);
43
+ }
44
+ break;
45
+
46
+ case 'MultiPolygon':
47
+ for (const polygon of data.geom) {
48
+ // the outer ring (ie [0]) contains all inner rings
49
+ calcLineBBox(feature, polygon[0]);
50
+ }
51
+ break;
52
+ }
53
+
54
+ return feature;
55
+ }
56
+
57
+ function calcLineBBox(feature: GeoJSONVTFeature, geom: number[]) {
58
+ for (let i = 0; i < geom.length; i += 3) {
59
+ feature.minX = Math.min(feature.minX, geom[i]);
60
+ feature.minY = Math.min(feature.minY, geom[i + 1]);
61
+ feature.maxX = Math.max(feature.maxX, geom[i]);
62
+ feature.maxY = Math.max(feature.maxY, geom[i + 1]);
63
+ }
64
+ }
@@ -1,28 +1,42 @@
1
1
 
2
- import convert from './convert.js'; // GeoJSON conversion and preprocessing
3
- import clip from './clip.js'; // stripe clipping algorithm
4
- import wrap from './wrap.js'; // date line processing
5
- import transform from './transform.js'; // coordinate transformation
6
- import createTile from './tile.js'; // final simplified tile generation
7
- import {applySourceDiff} from './difference.js'; // diff utilities
8
-
9
- const defaultOptions = {
10
- maxZoom: 14, // max zoom to preserve detail on
11
- indexMaxZoom: 5, // max zoom in the tile index
12
- indexMaxPoints: 100000, // max number of points per tile in the tile index
13
- tolerance: 3, // simplification tolerance (higher means simpler)
14
- extent: 4096, // tile extent
15
- buffer: 64, // tile buffer on each side
16
- lineMetrics: false, // whether to calculate line metrics
17
- promoteId: null, // name of a feature property to be promoted to feature.id
18
- generateId: false, // whether to generate feature ids. Cannot be used with promoteId
19
- updateable: false, // whether geojson can be updated (with caveat of a stored simplified copy)
20
- debug: 0 // logging level (0, 1 or 2)
2
+ import {convert} from './convert';
3
+ import {clip} from './clip';
4
+ import {wrap} from './wrap';
5
+ import {transformTile, type GeoJSONVTTransformedTile} from './transform';
6
+ import {createTile, type GeoJSONVTTile, type GeoJSONVTTileFeature} from './tile';
7
+ import {applySourceDiff, type GeoJSONVTFeatureDiff, type GeoJSONVTSourceDiff} from './difference';
8
+ import type { GeoJSONVTFeature, GeoJSONVTOptions, GeometryType, GeometryTypeMap, PartialGeoJSONVTFeature, StartEndSizeArray } from './definitions';
9
+
10
+ const defaultOptions: GeoJSONVTOptions = {
11
+ maxZoom: 14,
12
+ indexMaxZoom: 5,
13
+ indexMaxPoints: 100000,
14
+ tolerance: 3,
15
+ extent: 4096,
16
+ buffer: 64,
17
+ lineMetrics: false,
18
+ promoteId: null,
19
+ generateId: false,
20
+ updateable: false,
21
+ debug: 0
21
22
  };
22
23
 
24
+ /**
25
+ * Main class for creating and managing a vector tile index from GeoJSON data.
26
+ */
23
27
  class GeoJSONVT {
24
- constructor(data, options) {
25
- options = this.options = extend(Object.create(defaultOptions), options);
28
+ private options: GeoJSONVTOptions;
29
+ /** @internal */
30
+ public tiles: {[key: string]: GeoJSONVTTile};
31
+ private tileCoords: {z: number, x: number, y: number, id: number}[];
32
+ /** @internal */
33
+ public stats: {[key: string]: number} = {};
34
+ /** @internal */
35
+ public total: number = 0;
36
+ private source?: GeoJSONVTFeature[];
37
+
38
+ constructor(data: GeoJSON.GeoJSON, options: GeoJSONVTOptions) {
39
+ options = this.options = Object.assign({}, defaultOptions, options);
26
40
 
27
41
  const debug = options.debug;
28
42
 
@@ -50,7 +64,9 @@ class GeoJSONVT {
50
64
  features = wrap(features, options);
51
65
 
52
66
  // start slicing from the top tile down
53
- if (features.length) this.splitTile(features, 0, 0, 0);
67
+ if (features.length) {
68
+ this.splitTile(features, 0, 0, 0);
69
+ }
54
70
 
55
71
  // for updateable indexes, store a copy of the original simplified features
56
72
  if (options.updateable) {
@@ -64,13 +80,23 @@ class GeoJSONVT {
64
80
  }
65
81
  }
66
82
 
67
- // splits features from a parent tile to sub-tiles.
68
- // z, x, and y are the coordinates of the parent tile
69
- // cz, cx, and cy are the coordinates of the target tile
70
- //
71
- // If no target tile is specified, splitting stops when we reach the maximum
72
- // zoom or the number of points is low as specified in the options.
73
- splitTile(features, z, x, y, cz, cx, cy) {
83
+ /**
84
+ * splits features from a parent tile to sub-tiles.
85
+ * z, x, and y are the coordinates of the parent tile
86
+ * cz, cx, and cy are the coordinates of the target tile
87
+ *
88
+ * If no target tile is specified, splitting stops when we reach the maximum
89
+ * zoom or the number of points is low as specified in the options.
90
+ * @internal
91
+ * @param features - features to split
92
+ * @param z - tile zoom level
93
+ * @param x - tile x coordinate
94
+ * @param y - tile y coordinate
95
+ * @param cz - target tile zoom level
96
+ * @param cx - target tile x coordinate
97
+ * @param cy - target tile y coordinate
98
+ */
99
+ splitTile(features: GeoJSONVTFeature[], z: number, x: number, y: number, cz?: number, cx?: number, cy?: number) {
74
100
 
75
101
  const stack = [features, z, x, y];
76
102
  const options = this.options;
@@ -78,10 +104,10 @@ class GeoJSONVT {
78
104
 
79
105
  // avoid recursion by using a processing queue
80
106
  while (stack.length) {
81
- y = stack.pop();
82
- x = stack.pop();
83
- z = stack.pop();
84
- features = stack.pop();
107
+ y = stack.pop() as number;
108
+ x = stack.pop() as number;
109
+ z = stack.pop() as number;
110
+ features = stack.pop() as GeoJSONVTFeature[];
85
111
 
86
112
  const z2 = 1 << z;
87
113
  const id = toID(z, x, y);
@@ -125,7 +151,7 @@ class GeoJSONVT {
125
151
  // if we slice further down, no need to keep source geometry
126
152
  tile.source = null;
127
153
 
128
- if (features.length === 0) continue;
154
+ if (!features.length) continue;
129
155
 
130
156
  if (debug > 1) console.time('clipping');
131
157
 
@@ -162,7 +188,14 @@ class GeoJSONVT {
162
188
  }
163
189
  }
164
190
 
165
- getTile(z, x, y) {
191
+ /**
192
+ * Given z, x, and y tile coordinates, returns the corresponding tile with geometries in tile coordinates, much like MVT data is stored.
193
+ * @param z - tile zoom level
194
+ * @param x - tile x coordinate
195
+ * @param y - tile y coordinate
196
+ * @returns the transformed tile or null if not found
197
+ */
198
+ getTile(z: number | string, x: number | string, y: number | string): GeoJSONVTTransformedTile | null {
166
199
  z = +z;
167
200
  x = +x;
168
201
  y = +y;
@@ -176,7 +209,9 @@ class GeoJSONVT {
176
209
  x = (x + z2) & (z2 - 1); // wrap tile x coordinate
177
210
 
178
211
  const id = toID(z, x, y);
179
- if (this.tiles[id]) return transform(this.tiles[id], extent);
212
+ if (this.tiles[id]) {
213
+ return transformTile(this.tiles[id], extent);
214
+ }
180
215
 
181
216
  if (debug > 1) console.log('drilling down to z%d-%d-%d', z, x, y);
182
217
 
@@ -192,7 +227,7 @@ class GeoJSONVT {
192
227
  parent = this.tiles[toID(z0, x0, y0)];
193
228
  }
194
229
 
195
- if (!parent || !parent.source) return null;
230
+ if (!parent?.source) return null;
196
231
 
197
232
  // if we found a parent tile containing the original geometry, we can drill down from it
198
233
  if (debug > 1) {
@@ -202,11 +237,17 @@ class GeoJSONVT {
202
237
  this.splitTile(parent.source, z0, x0, y0, z, x, y);
203
238
  if (debug > 1) console.timeEnd('drilling down');
204
239
 
205
- return this.tiles[id] ? transform(this.tiles[id], extent) : null;
240
+ if (!this.tiles[id]) return null;
241
+
242
+ return transformTile(this.tiles[id], extent);
206
243
  }
207
244
 
208
- // invalidates (removes) tiles affected by the provided features
209
- invalidateTiles(features) {
245
+ /**
246
+ * Invalidates (removes) tiles affected by the provided features
247
+ * @internal
248
+ * @param features
249
+ */
250
+ invalidateTiles(features: GeoJSONVTFeature[]) {
210
251
  const options = this.options;
211
252
  const {debug} = options;
212
253
 
@@ -272,13 +313,17 @@ class GeoJSONVT {
272
313
  }
273
314
 
274
315
  // remove tile coords that are no longer in the index
275
- if (removedLookup.size) this.tileCoords = this.tileCoords.filter(c => !removedLookup.has(c.id));
316
+ if (removedLookup.size) {
317
+ this.tileCoords = this.tileCoords.filter(c => !removedLookup.has(c.id));
318
+ }
276
319
  }
277
320
 
278
- // updates the tile index by adding and/or removing geojson features
279
- // invalidates tiles that are affected by the update for regeneration on next getTile call
280
- // diff is an object with properties specified in difference.js
281
- updateData(diff) {
321
+ /**
322
+ * Updates the tile index by adding and/or removing geojson features
323
+ * invalidates tiles that are affected by the update for regeneration on next getTile call.
324
+ * @param diff - the source diff object
325
+ */
326
+ updateData(diff: GeoJSONVTSourceDiff) {
282
327
  const options = this.options;
283
328
  const debug = options.debug;
284
329
 
@@ -320,15 +365,25 @@ class GeoJSONVT {
320
365
  }
321
366
  }
322
367
 
323
- function toID(z, x, y) {
368
+ function toID(z: number, x: number, y: number): number {
324
369
  return (((1 << z) * y + x) * 32) + z;
325
370
  }
326
371
 
327
- function extend(dest, src) {
328
- for (const i in src) dest[i] = src[i];
329
- return dest;
330
- }
331
-
332
- export default function geojsonvt(data, options) {
372
+ export default function geojsonvt(data: GeoJSON.GeoJSON, options?: GeoJSONVTOptions) {
333
373
  return new GeoJSONVT(data, options);
334
374
  }
375
+
376
+ export type {
377
+ GeoJSONVTFeature,
378
+ GeoJSONVTOptions,
379
+ GeoJSONVTTile,
380
+ GeoJSONVTTileFeature,
381
+ GeometryType,
382
+ PartialGeoJSONVTFeature,
383
+ GeoJSONVT,
384
+ GeometryTypeMap,
385
+ StartEndSizeArray,
386
+ GeoJSONVTTransformedTile,
387
+ GeoJSONVTSourceDiff,
388
+ GeoJSONVTFeatureDiff
389
+ };
@@ -0,0 +1,73 @@
1
+
2
+ import {test, expect} from 'vitest';
3
+ import {simplify} from './simplify';
4
+
5
+ const points = [
6
+ [0.22455,0.25015],[0.22691,0.24419],[0.23331,0.24145],[0.23498,0.23606],
7
+ [0.24421,0.23276],[0.26259,0.21531],[0.26776,0.21381],[0.27357,0.20184],
8
+ [0.27312,0.19216],[0.27762,0.18903],[0.28036,0.18141],[0.28651,0.17774],
9
+ [0.29241,0.15937],[0.29691,0.15564],[0.31495,0.15137],[0.31975,0.14516],
10
+ [0.33033,0.13757],[0.34148,0.13996],[0.36998,0.13789],[0.38739,0.14251],
11
+ [0.39128,0.13939],[0.40952,0.14114],[0.41482,0.13975],[0.42772,0.12730],
12
+ [0.43960,0.11974],[0.47493,0.10787],[0.48651,0.10675],[0.48920,0.10945],
13
+ [0.49379,0.10863],[0.50474,0.11966],[0.51296,0.12235],[0.51863,0.12089],
14
+ [0.52409,0.12688],[0.52957,0.12786],[0.53421,0.14093],[0.53927,0.14724],
15
+ [0.56769,0.14891],[0.57525,0.15726],[0.58062,0.15815],[0.60153,0.15685],
16
+ [0.61774,0.15986],[0.62200,0.16704],[0.62955,0.19460],[0.63890,0.19561],
17
+ [0.64126,0.20081],[0.65177,0.20456],[0.67155,0.22255],[0.68368,0.21745],
18
+ [0.69525,0.21915],[0.70064,0.21798],[0.70312,0.21436],[0.71226,0.21587],
19
+ [0.72149,0.21281],[0.72781,0.21336],[0.72998,0.20873],[0.73532,0.20820],
20
+ [0.73994,0.20477],[0.76998,0.20842],[0.77960,0.21687],[0.78420,0.21816],
21
+ [0.80024,0.21462],[0.81053,0.21973],[0.81719,0.22682],[0.82077,0.23617],
22
+ [0.82723,0.23616],[0.82989,0.23989],[0.85100,0.24894],[0.85988,0.25549],
23
+ [0.86521,0.26853],[0.85795,0.28030],[0.86548,0.29145],[0.86681,0.29866],
24
+ [0.86468,0.30271],[0.86779,0.30617],[0.85987,0.31137],[0.86008,0.31435],
25
+ [0.85829,0.31494],[0.85810,0.32760],[0.85454,0.33540],[0.86092,0.34300],
26
+ [0.85643,0.35015],[0.85142,0.35296],[0.84984,0.35959],[0.85456,0.36553],
27
+ [0.84974,0.37038],[0.84409,0.37189],[0.84475,0.38044],[0.84152,0.38367],
28
+ [0.83957,0.39040],[0.84559,0.39905],[0.84840,0.40755],[0.84371,0.41130],
29
+ [0.84409,0.41988],[0.83951,0.43276],[0.84133,0.44104],[0.84762,0.44922],
30
+ [0.84716,0.45844],[0.85138,0.46279],[0.85397,0.47115],[0.86636,0.48077]
31
+ ];
32
+
33
+ const simplified = [
34
+ [0.22455,0.25015],[0.26776,0.21381],[0.29691,0.15564],[0.33033,0.13757],
35
+ [0.40952,0.14114],[0.4396,0.11974],[0.48651,0.10675],[0.52957,0.12786],
36
+ [0.53927,0.14724],[0.56769,0.14891],[0.61774,0.15986],[0.62955,0.1946],
37
+ [0.67155,0.22255],[0.72781,0.21336],[0.73994,0.20477],[0.76998,0.20842],
38
+ [0.7842,0.21816],[0.80024,0.21462],[0.82077,0.23617],[0.85988,0.25549],
39
+ [0.86521,0.26853],[0.85795,0.2803],[0.86779,0.30617],[0.85829,0.31494],
40
+ [0.85454,0.3354],[0.86092,0.343],[0.84984,0.35959],[0.85456,0.36553],
41
+ [0.84409,0.37189],[0.83957,0.3904],[0.8484,0.40755],[0.83951,0.43276],
42
+ [0.85397,0.47115],[0.86636,0.48077]
43
+ ];
44
+
45
+ test('simplifies points correctly with the given tolerance', () => {
46
+ const coords = [];
47
+ for (let i = 0; i < points.length; i++) {
48
+ coords.push(points[i][0], points[i][1], 0);
49
+ }
50
+
51
+ coords[2] = 1;
52
+ coords[coords.length - 1] = 1;
53
+ simplify(coords, 0, coords.length - 3, 0.001 * 0.001);
54
+
55
+ const result = [];
56
+ for (let i = 0; i < coords.length; i += 3) {
57
+ if (coords[i + 2] > 0.005 * 0.005) {
58
+ result.push([coords[i], coords[i + 1]]);
59
+ }
60
+ }
61
+ expect(result).toEqual(simplified);
62
+ });
63
+
64
+ test('does not throw max call stack error on bad long input', () => {
65
+ const coords: number[] = [];
66
+ for (let i = 0; i < 1400; i++) {
67
+ coords.push(0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0);
68
+ }
69
+
70
+ expect(() => {
71
+ simplify(coords, 0, coords.length, 2e-15);
72
+ }).not.toThrow();
73
+ });
@@ -1,7 +1,11 @@
1
-
2
- // calculate simplification data using optimized Douglas-Peucker algorithm
3
-
4
- export default function simplify(coords, first, last, sqTolerance) {
1
+ /**
2
+ * calculate simplification data using optimized Douglas-Peucker algorithm
3
+ * @param coords - flat array of coordinates
4
+ * @param first - index of the first coordinate in the segment
5
+ * @param last - index of the last coordinate in the segment
6
+ * @param sqTolerance - square tolerance value
7
+ */
8
+ export function simplify(coords: number[], first: number, last: number, sqTolerance: number) {
5
9
  let maxSqDist = sqTolerance;
6
10
  const mid = first + ((last - first) >> 1);
7
11
  let minPosToMid = last - first;
@@ -18,8 +22,10 @@ export default function simplify(coords, first, last, sqTolerance) {
18
22
  if (d > maxSqDist) {
19
23
  index = i;
20
24
  maxSqDist = d;
25
+ continue;
26
+ }
21
27
 
22
- } else if (d === maxSqDist) {
28
+ if (d === maxSqDist) {
23
29
  // a workaround to ensure we choose a pivot close to the middle of the list,
24
30
  // reducing recursion depth, for certain degenerate inputs
25
31
  // https://github.com/mapbox/geojson-vt/issues/104
@@ -38,14 +44,21 @@ export default function simplify(coords, first, last, sqTolerance) {
38
44
  }
39
45
  }
40
46
 
41
- // square distance from a point to a segment
42
- function getSqSegDist(px, py, x, y, bx, by) {
43
-
47
+ /**
48
+ * Claculates the square distance from a point to a segment
49
+ * @param px - x coordinate of the point
50
+ * @param py - y coordinate of the point
51
+ * @param x - x coordinate of the first segment endpoint
52
+ * @param y - y coordinate of the first segment endpoint
53
+ * @param bx - x coordinate of the second segment endpoint
54
+ * @param by - y coordinate of the second segment endpoint
55
+ * @returns square distance from a point to a segment
56
+ */
57
+ function getSqSegDist(px: number, py: number, x: number, y: number, bx: number, by: number): number {
44
58
  let dx = bx - x;
45
59
  let dy = by - y;
46
60
 
47
61
  if (dx !== 0 || dy !== 0) {
48
-
49
62
  const t = ((px - x) * dx + (py - y) * dy) / (dx * dx + dy * dy);
50
63
 
51
64
  if (t > 1) {
package/src/tile.ts ADDED
@@ -0,0 +1,166 @@
1
+ import type { GeoJSONVTFeature, GeoJSONVTOptions, StartEndSizeArray } from "./definitions";
2
+
3
+ export type GeoJSONVTTileFeature = {
4
+ id? : number | string | undefined;
5
+ type: 1 | 2 | 3;
6
+ tags: GeoJSON.GeoJsonProperties | null;
7
+ geometry: number[] | number[][];
8
+ }
9
+
10
+ export type GeoJSONVTTile = {
11
+ features: GeoJSONVTTileFeature[];
12
+ numPoints: number;
13
+ numSimplified: number;
14
+ numFeatures: number;
15
+ x: number;
16
+ y: number;
17
+ z: number;
18
+ transformed: boolean;
19
+ minX: number;
20
+ minY: number;
21
+ maxX: number;
22
+ maxY: number;
23
+ source: GeoJSONVTFeature[] | null;
24
+ }
25
+
26
+ /**
27
+ * Creates a tile object from the given features
28
+ * @param features - the features to include in the tile
29
+ * @param z
30
+ * @param tx
31
+ * @param ty
32
+ * @param options - the options object
33
+ * @returns the created tile
34
+ */
35
+ export function createTile(features: GeoJSONVTFeature[], z: number, tx: number, ty: number, options: GeoJSONVTOptions): GeoJSONVTTile {
36
+ const tolerance = z === options.maxZoom ? 0 : options.tolerance / ((1 << z) * options.extent);
37
+
38
+ const tile = {
39
+ features: [] as GeoJSONVTTileFeature[],
40
+ numPoints: 0,
41
+ numSimplified: 0,
42
+ numFeatures: features.length,
43
+ source: null as GeoJSONVTFeature[] | null,
44
+ x: tx,
45
+ y: ty,
46
+ z,
47
+ transformed: false,
48
+ minX: 2,
49
+ minY: 1,
50
+ maxX: -1,
51
+ maxY: 0
52
+ };
53
+
54
+ for (const feature of features) {
55
+ addFeature(tile, feature, tolerance, options);
56
+ }
57
+
58
+ return tile;
59
+ }
60
+
61
+ function addFeature(tile: GeoJSONVTTile, feature: GeoJSONVTFeature, tolerance: number, options: GeoJSONVTOptions) {
62
+ const simplified: number[] | number[][] = [];
63
+
64
+ tile.minX = Math.min(tile.minX, feature.minX);
65
+ tile.minY = Math.min(tile.minY, feature.minY);
66
+ tile.maxX = Math.max(tile.maxX, feature.maxX);
67
+ tile.maxY = Math.max(tile.maxY, feature.maxY);
68
+
69
+ switch (feature.type) {
70
+ case 'Point':
71
+ case 'MultiPoint':
72
+ for (let i = 0; i < feature.geometry.length; i += 3) {
73
+ (simplified as number[]).push(feature.geometry[i] , feature.geometry[i + 1]);
74
+ tile.numPoints++;
75
+ tile.numSimplified++;
76
+ }
77
+ break;
78
+
79
+ case 'LineString':
80
+ addLine(simplified as number[][], feature.geometry, tile, tolerance, false, false);
81
+ break;
82
+
83
+ case 'MultiLineString':
84
+ case 'Polygon':
85
+ for (let i = 0; i < feature.geometry.length; i++) {
86
+ addLine(simplified as number[][], feature.geometry[i], tile, tolerance, feature.type === 'Polygon', i === 0);
87
+ }
88
+ break;
89
+
90
+ case 'MultiPolygon':
91
+ for (let k = 0; k < feature.geometry.length; k++) {
92
+ const polygon = feature.geometry[k];
93
+ for (let i = 0; i < polygon.length; i++) {
94
+ addLine(simplified as number[][], polygon[i], tile, tolerance, true, i === 0);
95
+ }
96
+ }
97
+ break;
98
+ }
99
+ if (!simplified.length) return;
100
+
101
+ let tags = feature.tags || null;
102
+
103
+ if (feature.type === 'LineString' && options.lineMetrics) {
104
+ tags = {};
105
+ for (const key in feature.tags) tags[key] = feature.tags[key];
106
+ // HM TODO: replace with geojsonvt
107
+ tags['mapbox_clip_start'] = feature.geometry.start / feature.geometry.size;
108
+ tags['mapbox_clip_end'] = feature.geometry.end / feature.geometry.size;
109
+ }
110
+
111
+ const tileFeature: GeoJSONVTTileFeature = {
112
+ geometry: simplified,
113
+ type: feature.type === 'Polygon' || feature.type === 'MultiPolygon' ? 3 :
114
+ (feature.type === 'LineString' || feature.type === 'MultiLineString' ? 2 : 1),
115
+ tags
116
+ };
117
+
118
+ if (feature.id !== null) {
119
+ tileFeature.id = feature.id;
120
+ }
121
+
122
+ tile.features.push(tileFeature);
123
+ }
124
+
125
+ function addLine(result: number[][], geom: StartEndSizeArray, tile: GeoJSONVTTile, tolerance: number, isPolygon: boolean, isOuter: boolean) {
126
+ const sqTolerance = tolerance * tolerance;
127
+
128
+ if (tolerance > 0 && (geom.size < (isPolygon ? sqTolerance : tolerance))) {
129
+ tile.numPoints += geom.length / 3;
130
+ return;
131
+ }
132
+
133
+ const ring = [];
134
+
135
+ for (let i = 0; i < geom.length; i += 3) {
136
+ if (tolerance === 0 || geom[i + 2] > sqTolerance) {
137
+ tile.numSimplified++;
138
+ ring.push(geom[i], geom[i + 1]);
139
+ }
140
+ tile.numPoints++;
141
+ }
142
+
143
+ if (isPolygon) rewind(ring, isOuter);
144
+
145
+ result.push(ring);
146
+ }
147
+
148
+ function rewind(ring: number[], clockwise: boolean) {
149
+ let area = 0;
150
+
151
+ for (let i = 0, len = ring.length, j = len - 2; i < len; j = i, i += 2) {
152
+ area += (ring[i] - ring[j]) * (ring[i + 1] + ring[j + 1]);
153
+ }
154
+
155
+ if (area > 0 !== clockwise) return;
156
+
157
+ for (let i = 0, len = ring.length; i < len / 2; i += 2) {
158
+ const x = ring[i];
159
+ const y = ring[i + 1];
160
+
161
+ ring[i] = ring[len - 2 - i];
162
+ ring[i + 1] = ring[len - 1 - i];
163
+ ring[len - 2 - i] = x;
164
+ ring[len - 1 - i] = y;
165
+ }
166
+ }
@@ -0,0 +1,55 @@
1
+ import type { GeoJSONVTTile } from "./tile";
2
+
3
+ export type GeoJSONVTTransformedTile = GeoJSONVTTile & {
4
+ transformed: true;
5
+ geometry: [number, number][] | [number, number][][];
6
+ }
7
+
8
+ /**
9
+ * Transforms the coordinates of each feature in the given tile from
10
+ * mercator-projected space into (extent x extent) tile space.
11
+ * @param tile - the tile to transform, this gets modified in place
12
+ * @param extent - the tile extent (usually 4096)
13
+ * @returns the transformed tile
14
+ */
15
+ export function transformTile(tile: GeoJSONVTTile, extent: number): GeoJSONVTTransformedTile {
16
+ if (tile.transformed) {
17
+ return tile as GeoJSONVTTransformedTile;
18
+ }
19
+
20
+ const z2 = 1 << tile.z;
21
+ const tx = tile.x;
22
+ const ty = tile.y;
23
+
24
+ for (const feature of tile.features) {
25
+ const geom = feature.geometry;
26
+ const type = feature.type;
27
+
28
+ feature.geometry = [];
29
+
30
+ if (type === 1) {
31
+ for (let j = 0; j < geom.length; j += 2) {
32
+ (feature.geometry as [number, number][]).push(transformPoint(geom[j] as number, geom[j + 1] as number, extent, z2, tx, ty));
33
+ }
34
+ continue;
35
+ }
36
+
37
+ for (const singleGeom of geom as number[][]) {
38
+ const ring: [number, number][] = [];
39
+ for (let k = 0; k < singleGeom.length; k += 2) {
40
+ ring.push(transformPoint(singleGeom[k], singleGeom[k + 1], extent, z2, tx, ty));
41
+ }
42
+ (feature.geometry as unknown as [number, number][][]).push(ring);
43
+ }
44
+ }
45
+ tile.transformed = true;
46
+
47
+ return tile as GeoJSONVTTransformedTile;
48
+ }
49
+
50
+ function transformPoint(x: number, y: number, extent: number, z2: number, tx: number, ty: number): [number, number] {
51
+ return [
52
+ Math.round(extent * (x * z2 - tx)),
53
+ Math.round(extent * (y * z2 - ty))
54
+ ];
55
+ }