@maplibre/geojson-vt 5.0.4 → 6.0.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.
Files changed (53) hide show
  1. package/README.md +3 -13
  2. package/dist/clip.d.ts +20 -1
  3. package/dist/clip.d.ts.map +1 -1
  4. package/dist/cluster-tile-index.d.ts +76 -0
  5. package/dist/cluster-tile-index.d.ts.map +1 -0
  6. package/dist/cluster-tile-index.test.d.ts +2 -0
  7. package/dist/cluster-tile-index.test.d.ts.map +1 -0
  8. package/dist/convert.d.ts +10 -2
  9. package/dist/convert.d.ts.map +1 -1
  10. package/dist/deconvert.d.ts +19 -0
  11. package/dist/deconvert.d.ts.map +1 -0
  12. package/dist/deconvert.test.d.ts +2 -0
  13. package/dist/deconvert.test.d.ts.map +1 -0
  14. package/dist/definitions.d.ts +176 -20
  15. package/dist/definitions.d.ts.map +1 -1
  16. package/dist/feature.d.ts +11 -3
  17. package/dist/feature.d.ts.map +1 -1
  18. package/dist/geojson-to-tile.d.ts +35 -0
  19. package/dist/geojson-to-tile.d.ts.map +1 -0
  20. package/dist/geojson-vt-dev.js +1582 -478
  21. package/dist/geojson-vt.js +1 -1
  22. package/dist/geojson-vt.mjs +1250 -473
  23. package/dist/geojson-vt.mjs.map +1 -1
  24. package/dist/geojsonvt.d.ts +76 -0
  25. package/dist/geojsonvt.d.ts.map +1 -0
  26. package/dist/geojsonvt.test.d.ts +2 -0
  27. package/dist/geojsonvt.test.d.ts.map +1 -0
  28. package/dist/index.d.ts +8 -62
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/tile-index.d.ts +51 -0
  31. package/dist/tile-index.d.ts.map +1 -0
  32. package/dist/tile.d.ts +1 -29
  33. package/dist/tile.d.ts.map +1 -1
  34. package/dist/transform.d.ts +1 -18
  35. package/dist/transform.d.ts.map +1 -1
  36. package/package.json +18 -10
  37. package/src/clip.ts +119 -81
  38. package/src/cluster-tile-index.test.ts +205 -0
  39. package/src/cluster-tile-index.ts +513 -0
  40. package/src/convert.ts +97 -75
  41. package/src/deconvert.test.ts +153 -0
  42. package/src/deconvert.ts +92 -0
  43. package/src/definitions.ts +196 -18
  44. package/src/difference.ts +3 -3
  45. package/src/feature.ts +11 -4
  46. package/src/geojson-to-tile.ts +58 -0
  47. package/src/geojsonvt.test.ts +39 -0
  48. package/src/geojsonvt.ts +209 -0
  49. package/src/index.ts +27 -378
  50. package/src/tile-index.ts +310 -0
  51. package/src/tile.ts +92 -103
  52. package/src/transform.ts +41 -39
  53. package/src/wrap.ts +4 -4
@@ -0,0 +1,310 @@
1
+ import { AxisType, clip } from "./clip";
2
+ import { createTile } from "./tile";
3
+ import { transformTile } from "./transform";
4
+ import type { GeoJSONVTInternalFeature, GeoJSONVTOptions, ClusterOrPointFeature, GeoJSONVTTileIndex, GeoJSONVTInternalTile, GeoJSONVTTile } from "./definitions";
5
+
6
+ export class TileIndex implements GeoJSONVTTileIndex {
7
+
8
+ private tileCoords: {z: number, x: number, y: number, id: number}[];
9
+
10
+ /** @internal */
11
+ public tiles: {[key: string]: GeoJSONVTInternalTile};
12
+ /** @internal */
13
+ public stats: {[key: string]: number} = {};
14
+ /** @internal */
15
+ public total: number = 0;
16
+
17
+ constructor(private options: GeoJSONVTOptions) {
18
+ this.tiles = {};
19
+ this.tileCoords = [];
20
+ this.stats = {};
21
+ this.total = 0;
22
+ }
23
+
24
+ initialize(features: GeoJSONVTInternalFeature[]): void {
25
+ // start slicing from the top tile down
26
+ this.splitTile(features, 0, 0, 0);
27
+
28
+ if (this.options.debug) {
29
+ if (features.length) console.log('features: %d, points: %d', this.tiles[0].numFeatures, this.tiles[0].numPoints);
30
+ console.timeEnd('generate tiles');
31
+ console.log('tiles generated:', this.total, JSON.stringify(this.stats));
32
+ }
33
+ }
34
+
35
+ /** {@inheritdoc} */
36
+ updateIndex(source: GeoJSONVTInternalFeature[], affected: GeoJSONVTInternalFeature[], options: GeoJSONVTOptions): void {
37
+ if (options.debug > 1) {
38
+ console.log('invalidating tiles');
39
+ console.time('invalidating');
40
+ }
41
+
42
+ this.invalidateTiles(affected);
43
+
44
+ if (options.debug > 1) console.timeEnd('invalidating');
45
+
46
+ // re-generate root tile with updated feature set
47
+ const [z, x, y] = [0, 0, 0];
48
+ const rootTile = createTile(source, z, x, y, options);
49
+ rootTile.source = source;
50
+
51
+ // update tile index with new root tile - ready for getTile calls
52
+ const id = toID(z, x, y);
53
+ this.tiles[id] = rootTile;
54
+ this.tileCoords.push({z, x, y, id});
55
+
56
+ if (options.debug) {
57
+ const key = `z${ z}`;
58
+ this.stats[key] = (this.stats[key] || 0) + 1;
59
+ this.total++;
60
+ }
61
+ }
62
+
63
+ /** {@inheritdoc} */
64
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
65
+ getClusterExpansionZoom(_clusterId: number): number | null {
66
+ return null;
67
+ }
68
+
69
+ /** {@inheritdoc} */
70
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
71
+ getChildren(_clusterId: number): ClusterOrPointFeature[] | null {
72
+ return null;
73
+ }
74
+
75
+ /** {@inheritdoc} */
76
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
77
+ getLeaves(_clusterId: number, _limit?: number, _offset?: number): GeoJSON.Feature<GeoJSON.Point>[] | null {
78
+ return null;
79
+ }
80
+
81
+ /** {@inheritdoc} */
82
+ getTile(z: number, x: number, y: number): GeoJSONVTTile | null {
83
+ const {extent, debug} = this.options;
84
+
85
+ const z2 = 1 << z;
86
+ x = (x + z2) & (z2 - 1); // wrap tile x coordinate
87
+
88
+ const id = toID(z, x, y);
89
+ if (this.tiles[id]) {
90
+ return transformTile(this.tiles[id], extent);
91
+ }
92
+
93
+ if (debug > 1) console.log('drilling down to z%d-%d-%d', z, x, y);
94
+
95
+ let z0 = z;
96
+ let x0 = x;
97
+ let y0 = y;
98
+ let parent;
99
+
100
+ while (!parent && z0 > 0) {
101
+ z0--;
102
+ x0 = x0 >> 1;
103
+ y0 = y0 >> 1;
104
+ parent = this.tiles[toID(z0, x0, y0)];
105
+ }
106
+
107
+ if (!parent?.source) return null;
108
+
109
+ // if we found a parent tile containing the original geometry, we can drill down from it
110
+ if (debug > 1) {
111
+ console.log('found parent tile z%d-%d-%d', z0, x0, y0);
112
+ console.time('drilling down');
113
+ }
114
+ this.splitTile(parent.source, z0, x0, y0, z, x, y);
115
+ if (debug > 1) console.timeEnd('drilling down');
116
+
117
+ if (!this.tiles[id]) return null;
118
+
119
+ return transformTile(this.tiles[id], extent);
120
+ }
121
+
122
+ /**
123
+ * splits features from a parent tile to sub-tiles.
124
+ * z, x, and y are the coordinates of the parent tile
125
+ * cz, cx, and cy are the coordinates of the target tile
126
+ *
127
+ * If no target tile is specified, splitting stops when we reach the maximum
128
+ * zoom or the number of points is low as specified in the options.
129
+ * @internal
130
+ * @param features - features to split
131
+ * @param z - tile zoom level
132
+ * @param x - tile x coordinate
133
+ * @param y - tile y coordinate
134
+ * @param cz - target tile zoom level
135
+ * @param cx - target tile x coordinate
136
+ * @param cy - target tile y coordinate
137
+ */
138
+ private splitTile(features: GeoJSONVTInternalFeature[], z: number, x: number, y: number, cz?: number, cx?: number, cy?: number) {
139
+
140
+ const stack = [features, z, x, y];
141
+ const options = this.options;
142
+ const debug = options.debug;
143
+
144
+ // avoid recursion by using a processing queue
145
+ while (stack.length) {
146
+ y = stack.pop() as number;
147
+ x = stack.pop() as number;
148
+ z = stack.pop() as number;
149
+ features = stack.pop() as GeoJSONVTInternalFeature[];
150
+
151
+ const z2 = 1 << z;
152
+ const id = toID(z, x, y);
153
+ let tile = this.tiles[id];
154
+
155
+ if (!tile) {
156
+ if (debug > 1) console.time('creation');
157
+
158
+ tile = this.tiles[id] = createTile(features, z, x, y, options);
159
+ this.tileCoords.push({z, x, y, id});
160
+
161
+ if (debug) {
162
+ if (debug > 1) {
163
+ console.log('tile z%d-%d-%d (features: %d, points: %d, simplified: %d)',
164
+ z, x, y, tile.numFeatures, tile.numPoints, tile.numSimplified);
165
+ console.timeEnd('creation');
166
+ }
167
+ const key = `z${ z}`;
168
+ this.stats[key] = (this.stats[key] || 0) + 1;
169
+ this.total++;
170
+ }
171
+ }
172
+
173
+ // save reference to original geometry in tile so that we can drill down later if we stop now
174
+ tile.source = features;
175
+
176
+ // if it's the first-pass tiling
177
+ if (cz == null) {
178
+ // stop tiling if we reached max zoom, or if the tile is too simple
179
+ if (z === options.indexMaxZoom || tile.numPoints <= options.indexMaxPoints) continue;
180
+ // if a drilldown to a specific tile
181
+ } else if (z === options.maxZoom || z === cz) {
182
+ // stop tiling if we reached base zoom or our target tile zoom
183
+ continue;
184
+ } else if (cz != null) {
185
+ // stop tiling if it's not an ancestor of the target tile
186
+ const zoomSteps = cz - z;
187
+ if (x !== cx >> zoomSteps || y !== cy >> zoomSteps) continue;
188
+ }
189
+
190
+ // if we slice further down, no need to keep source geometry
191
+ tile.source = null;
192
+
193
+ if (!features.length) continue;
194
+
195
+ if (debug > 1) console.time('clipping');
196
+
197
+ // values we'll use for clipping
198
+ const k1 = 0.5 * options.buffer / options.extent;
199
+ const k2 = 0.5 - k1;
200
+ const k3 = 0.5 + k1;
201
+ const k4 = 1 + k1;
202
+
203
+ let tl = null;
204
+ let bl = null;
205
+ let tr = null;
206
+ let br = null;
207
+
208
+ const left = clip(features, z2, x - k1, x + k3, AxisType.X, tile.minX, tile.maxX, options);
209
+ const right = clip(features, z2, x + k2, x + k4, AxisType.X, tile.minX, tile.maxX, options);
210
+
211
+ if (left) {
212
+ tl = clip(left, z2, y - k1, y + k3, AxisType.Y, tile.minY, tile.maxY, options);
213
+ bl = clip(left, z2, y + k2, y + k4, AxisType.Y, tile.minY, tile.maxY, options);
214
+ }
215
+
216
+ if (right) {
217
+ tr = clip(right, z2, y - k1, y + k3, AxisType.Y, tile.minY, tile.maxY, options);
218
+ br = clip(right, z2, y + k2, y + k4, AxisType.Y, tile.minY, tile.maxY, options);
219
+ }
220
+
221
+ if (debug > 1) console.timeEnd('clipping');
222
+
223
+ stack.push(tl || [], z + 1, x * 2, y * 2);
224
+ stack.push(bl || [], z + 1, x * 2, y * 2 + 1);
225
+ stack.push(tr || [], z + 1, x * 2 + 1, y * 2);
226
+ stack.push(br || [], z + 1, x * 2 + 1, y * 2 + 1);
227
+ }
228
+ }
229
+
230
+ /**
231
+ * Invalidates (removes) tiles affected by the provided features
232
+ * @internal
233
+ * @param features
234
+ */
235
+ private invalidateTiles(features: GeoJSONVTInternalFeature[]) {
236
+ if (!features.length) return;
237
+ const options = this.options;
238
+ const {debug} = options;
239
+
240
+ // calculate bounding box of all features for trivial reject
241
+ let minX = Infinity;
242
+ let maxX = -Infinity;
243
+ let minY = Infinity;
244
+ let maxY = -Infinity;
245
+
246
+ for (const feature of features) {
247
+ minX = Math.min(minX, feature.minX);
248
+ maxX = Math.max(maxX, feature.maxX);
249
+ minY = Math.min(minY, feature.minY);
250
+ maxY = Math.max(maxY, feature.maxY);
251
+ }
252
+
253
+ // tile buffer clipping value - not halved as in splitTile above because checking against tile's own extent
254
+ const k1 = options.buffer / options.extent;
255
+
256
+ // track removed tile ids for o(1) lookup
257
+ const removedLookup = new Set();
258
+
259
+ // iterate through existing tiles and remove ones that are affected by features
260
+ for (const id in this.tiles) {
261
+ const tile = this.tiles[id];
262
+
263
+ // calculate tile bounds including buffer
264
+ const z2 = 1 << tile.z;
265
+ const tileMinX = (tile.x - k1) / z2;
266
+ const tileMaxX = (tile.x + 1 + k1) / z2;
267
+ const tileMinY = (tile.y - k1) / z2;
268
+ const tileMaxY = (tile.y + 1 + k1) / z2;
269
+
270
+ // trivial reject if feature bounds don't intersect tile
271
+ if (maxX < tileMinX || minX >= tileMaxX ||
272
+ maxY < tileMinY || minY >= tileMaxY) {
273
+ continue;
274
+ }
275
+
276
+ // check if any feature intersects with the tile
277
+ let intersects = false;
278
+ for (const feature of features) {
279
+ if (feature.maxX >= tileMinX && feature.minX < tileMaxX &&
280
+ feature.maxY >= tileMinY && feature.minY < tileMaxY) {
281
+ intersects = true;
282
+ break;
283
+ }
284
+ }
285
+ if (!intersects) continue;
286
+
287
+ if (debug) {
288
+ if (debug > 1) {
289
+ console.log('invalidate tile z%d-%d-%d (features: %d, points: %d, simplified: %d)',
290
+ tile.z, tile.x, tile.y, tile.numFeatures, tile.numPoints, tile.numSimplified);
291
+ }
292
+ const key = `z${ tile.z}`;
293
+ this.stats[key] = (this.stats[key] || 0) - 1;
294
+ this.total--;
295
+ }
296
+
297
+ delete this.tiles[id];
298
+ removedLookup.add(id);
299
+ }
300
+
301
+ // remove tile coords that are no longer in the index
302
+ if (removedLookup.size) {
303
+ this.tileCoords = this.tileCoords.filter(c => !removedLookup.has(c.id));
304
+ }
305
+ }
306
+ }
307
+
308
+ function toID(z: number, x: number, y: number): number {
309
+ return (((1 << z) * y + x) * 32) + z;
310
+ }
package/src/tile.ts CHANGED
@@ -1,35 +1,6 @@
1
- import type { GeoJSONVTInternalFeature, GeoJSONVTOptions, StartEndSizeArray } from "./definitions";
1
+ import type { GeoJSONVTInternalFeature, GeoJSONVTInternalLineStringFeature, GeoJSONVTInternalMultiLineStringFeature, GeoJSONVTInternalMultiPointFeature, GeoJSONVTInternalMultiPolygonFeature, GeoJSONVTInternalPointFeature, GeoJSONVTInternalPolygonFeature, GeoJSONVTInternalTile, GeoJSONVTInternalTileFeature, GeoJSONVTOptions, StartEndSizeArray } from "./definitions";
2
2
 
3
- export type GeoJSONVTInternalTileFeaturePoint = {
4
- id? : number | string | undefined;
5
- type: 1;
6
- tags: GeoJSON.GeoJsonProperties | null;
7
- geometry: number[];
8
- }
9
3
 
10
- export type GeoJSONVTInternalTileFeaturNonPoint = {
11
- id? : number | string | undefined;
12
- type: 2 | 3;
13
- tags: GeoJSON.GeoJsonProperties | null;
14
- geometry: number[][];
15
- }
16
- export type GeoJSONVTInternalTileFeature = GeoJSONVTInternalTileFeaturePoint | GeoJSONVTInternalTileFeaturNonPoint;
17
-
18
- export type GeoJSONVTInternalTile = {
19
- features: GeoJSONVTInternalTileFeature[];
20
- numPoints: number;
21
- numSimplified: number;
22
- numFeatures: number;
23
- x: number;
24
- y: number;
25
- z: number;
26
- transformed: boolean;
27
- minX: number;
28
- minY: number;
29
- maxX: number;
30
- maxY: number;
31
- source: GeoJSONVTInternalFeature[] | null;
32
- }
33
4
 
34
5
  /**
35
6
  * Creates a tile object from the given features
@@ -44,19 +15,19 @@ export function createTile(features: GeoJSONVTInternalFeature[], z: number, tx:
44
15
  const tolerance = z === options.maxZoom ? 0 : options.tolerance / ((1 << z) * options.extent);
45
16
 
46
17
  const tile = {
18
+ transformed: false,
47
19
  features: [] as GeoJSONVTInternalTileFeature[],
48
- numPoints: 0,
49
- numSimplified: 0,
50
- numFeatures: features.length,
51
- source: null as GeoJSONVTInternalFeature[] | null,
20
+ source: null as GeoJSONVTInternalFeature[],
52
21
  x: tx,
53
22
  y: ty,
54
- z,
55
- transformed: false,
23
+ z: z,
56
24
  minX: 2,
57
25
  minY: 1,
58
26
  maxX: -1,
59
- maxY: 0
27
+ maxY: 0,
28
+ numPoints: 0,
29
+ numSimplified: 0,
30
+ numFeatures: features.length
60
31
  };
61
32
 
62
33
  for (const feature of features) {
@@ -72,81 +43,99 @@ function addFeature(tile: GeoJSONVTInternalTile, feature: GeoJSONVTInternalFeatu
72
43
  tile.maxX = Math.max(tile.maxX, feature.maxX);
73
44
  tile.maxY = Math.max(tile.maxY, feature.maxY);
74
45
 
75
- let tags = feature.tags || null;
76
-
77
- let tileFeature: GeoJSONVTInternalTileFeature;
78
-
79
46
  switch (feature.type) {
80
47
  case 'Point':
81
- case 'MultiPoint': {
82
- const geometry: number[] = [];
83
- for (let i = 0; i < feature.geometry.length; i += 3) {
84
- geometry.push(feature.geometry[i] , feature.geometry[i + 1]);
85
- tile.numPoints++;
86
- tile.numSimplified++;
87
- }
88
- if (!geometry.length) return;
89
- tileFeature = {
90
- type: 1,
91
- tags: tags,
92
- geometry: geometry
93
- }
94
- break;
95
- }
96
- case 'LineString': {
97
- const geometry: number[][] = [];
98
- addLine(geometry, feature.geometry, tile, tolerance, false, false);
99
- if (!geometry.length) return;
100
- if (options.lineMetrics) {
101
- tags = {};
102
- for (const key in feature.tags) tags[key] = feature.tags[key];
103
- // HM TODO: replace with geojsonvt
104
- tags['mapbox_clip_start'] = feature.geometry.start / feature.geometry.size;
105
- tags['mapbox_clip_end'] = feature.geometry.end / feature.geometry.size;
106
- }
107
- tileFeature = {
108
- type: 2,
109
- tags: tags,
110
- geometry: geometry
111
- }
112
- break;
113
- }
48
+ case 'MultiPoint':
49
+ addPointsTileFeature(tile, feature);
50
+ return;
51
+ case 'LineString':
52
+ addLineTileFeautre(tile, feature, tolerance, options);
53
+ return;
114
54
  case 'MultiLineString':
115
- case 'Polygon': {
116
- const geometry: number[][] = [];
117
- for (let i = 0; i < feature.geometry.length; i++) {
118
- addLine(geometry, feature.geometry[i], tile, tolerance, feature.type === 'Polygon', i === 0);
119
- }
120
- if (!geometry.length) return;
121
- tileFeature = {
122
- type: feature.type === 'Polygon' ? 3 : 2,
123
- tags: tags,
124
- geometry: geometry
125
- }
126
- break;
127
- }
128
- case 'MultiPolygon': {
129
- const geometry: number[][] = [];
130
- for (let k = 0; k < feature.geometry.length; k++) {
131
- const polygon = feature.geometry[k];
132
- for (let i = 0; i < polygon.length; i++) {
133
- addLine(geometry, polygon[i], tile, tolerance, true, i === 0);
134
- }
135
- }
136
- if (!geometry.length) return;
137
- tileFeature = {
138
- type: 3,
139
- tags: tags,
140
- geometry: geometry
141
- }
142
- break;
143
- }
55
+ case 'Polygon':
56
+ addLinesTileFeature(tile, feature, tolerance);
57
+ return;
58
+ case 'MultiPolygon':
59
+ addMultiPolygonTileFeature(tile, feature, tolerance);
60
+ return;
144
61
  }
62
+ }
145
63
 
64
+ function addPointsTileFeature(tile: GeoJSONVTInternalTile, feature: GeoJSONVTInternalPointFeature | GeoJSONVTInternalMultiPointFeature) {
65
+ const geometry: number[] = [];
66
+ for (let i = 0; i < feature.geometry.length; i += 3) {
67
+ geometry.push(feature.geometry[i] , feature.geometry[i + 1]);
68
+ tile.numPoints++;
69
+ tile.numSimplified++;
70
+ }
71
+ if (!geometry.length) return;
72
+ const tileFeature: GeoJSONVTInternalTileFeature = {
73
+ type: 1,
74
+ tags: feature.tags || null,
75
+ geometry: geometry
76
+ };
146
77
  if (feature.id !== null) {
147
78
  tileFeature.id = feature.id;
148
79
  }
80
+ tile.features.push(tileFeature);
81
+ }
149
82
 
83
+ function addLineTileFeautre(tile: GeoJSONVTInternalTile, feature: GeoJSONVTInternalLineStringFeature, tolerance: number, options: GeoJSONVTOptions) {
84
+ const geometry: number[][] = [];
85
+ addLine(geometry, feature.geometry, tile, tolerance, false, false);
86
+ if (!geometry.length) return;
87
+ let tags = feature.tags || null;
88
+ if (options.lineMetrics) {
89
+ tags = {};
90
+ for (const key in feature.tags) tags[key] = feature.tags[key];
91
+ tags['geojsonvt_clip_start'] = feature.geometry.start / feature.geometry.size;
92
+ tags['geojsonvt_clip_end'] = feature.geometry.end / feature.geometry.size;
93
+ }
94
+ const tileFeature: GeoJSONVTInternalTileFeature = {
95
+ type: 2,
96
+ tags: tags,
97
+ geometry: geometry
98
+ }
99
+ if (feature.id !== null) {
100
+ tileFeature.id = feature.id;
101
+ }
102
+ tile.features.push(tileFeature);
103
+ }
104
+
105
+ function addLinesTileFeature(tile: GeoJSONVTInternalTile, feature: GeoJSONVTInternalPolygonFeature | GeoJSONVTInternalMultiLineStringFeature, tolerance: number) {
106
+ const geometry: number[][] = [];
107
+ for (let i = 0; i < feature.geometry.length; i++) {
108
+ addLine(geometry, feature.geometry[i], tile, tolerance, feature.type === 'Polygon', i === 0);
109
+ }
110
+ if (!geometry.length) return;
111
+ const tileFeature: GeoJSONVTInternalTileFeature = {
112
+ type: feature.type === 'Polygon' ? 3 : 2,
113
+ tags: feature.tags || null,
114
+ geometry: geometry
115
+ }
116
+ if (feature.id !== null) {
117
+ tileFeature.id = feature.id;
118
+ }
119
+ tile.features.push(tileFeature);
120
+ }
121
+
122
+ function addMultiPolygonTileFeature(tile: GeoJSONVTInternalTile, feature: GeoJSONVTInternalMultiPolygonFeature, tolerance: number) {
123
+ const geometry: number[][] = [];
124
+ for (let k = 0; k < feature.geometry.length; k++) {
125
+ const polygon = feature.geometry[k];
126
+ for (let i = 0; i < polygon.length; i++) {
127
+ addLine(geometry, polygon[i], tile, tolerance, true, i === 0);
128
+ }
129
+ }
130
+ if (!geometry.length) return;
131
+ const tileFeature: GeoJSONVTInternalTileFeature = {
132
+ type: 3,
133
+ tags: feature.tags || null,
134
+ geometry: geometry
135
+ }
136
+ if (feature.id !== null) {
137
+ tileFeature.id = feature.id;
138
+ }
150
139
  tile.features.push(tileFeature);
151
140
  }
152
141
 
package/src/transform.ts CHANGED
@@ -1,25 +1,4 @@
1
- import type { GeoJSONVTInternalTile } from "./tile";
2
-
3
- export type GeoJSONVTFeaturePoint = {
4
- id? : number | string | undefined;
5
- type: 1;
6
- tags: GeoJSON.GeoJsonProperties | null;
7
- geometry: [number, number][]
8
- }
9
-
10
- export type GeoJSONVTFeatureNonPoint = {
11
- id? : number | string | undefined;
12
- type: 2 | 3;
13
- tags: GeoJSON.GeoJsonProperties | null;
14
- geometry: [number, number][][]
15
- }
16
-
17
- export type GeoJSONVTFeature = GeoJSONVTFeaturePoint | GeoJSONVTFeatureNonPoint;
18
-
19
- export type GeoJSONVTTile = GeoJSONVTInternalTile & {
20
- transformed: true;
21
- features: GeoJSONVTFeature[]
22
- }
1
+ import type { GeoJSONVTFeatureNonPoint, GeoJSONVTFeaturePoint, GeoJSONVTInternalTile, GeoJSONVTInternalTileFeatureNonPoint, GeoJSONVTInternalTileFeaturePoint, GeoJSONVTTile } from "./definitions";
23
2
 
24
3
  /**
25
4
  * Transforms the coordinates of each feature in the given tile from
@@ -39,29 +18,52 @@ export function transformTile(tile: GeoJSONVTInternalTile, extent: number): GeoJ
39
18
 
40
19
  for (const feature of tile.features) {
41
20
  if (feature.type === 1) {
42
- const pointGeometry: [number, number][] = []
43
- for (let j = 0; j < feature.geometry.length; j += 2) {
44
- pointGeometry.push(transformPoint(feature.geometry[j], feature.geometry[j + 1], extent, z2, tx, ty));
45
- }
46
- (feature as unknown as GeoJSONVTFeaturePoint).geometry = pointGeometry;
47
- continue;
48
- }
49
-
50
- const geometry: [number, number][][] = [];
51
- for (const singleGeom of feature.geometry) {
52
- const ring: [number, number][] = [];
53
- for (let k = 0; k < singleGeom.length; k += 2) {
54
- ring.push(transformPoint(singleGeom[k], singleGeom[k + 1], extent, z2, tx, ty));
55
- }
56
- geometry.push(ring);
57
- }
58
- (feature as unknown as GeoJSONVTFeatureNonPoint).geometry = geometry;
21
+ transformPointFeature(feature, extent, z2, tx, ty);
22
+ } else {
23
+ transformNonPointFeature(feature, extent, z2, tx, ty);
24
+ }
59
25
  }
60
26
  tile.transformed = true;
61
27
 
62
28
  return tile as GeoJSONVTTile;
63
29
  }
64
30
 
31
+ /**
32
+ * Transforms a single point feature from mercator-projected space into (extent x extent) tile space.
33
+ */
34
+ function transformPointFeature(feature: GeoJSONVTInternalTileFeaturePoint, extent: number, z2: number, tx: number, ty: number): GeoJSONVTFeaturePoint {
35
+ const transformed = feature as unknown as GeoJSONVTFeaturePoint;
36
+
37
+ const geometry = feature.geometry;
38
+ const point: GeoJSONVTFeaturePoint["geometry"] = [];
39
+ for (let i = 0; i < geometry.length; i += 2) {
40
+ point.push(transformPoint(geometry[i], geometry[i + 1], extent, z2, tx, ty));
41
+ }
42
+ transformed.geometry = point;
43
+
44
+ return transformed;
45
+ }
46
+
47
+ /**
48
+ * Transforms a single non-point feature from mercator-projected space into (extent x extent) tile space.
49
+ */
50
+ function transformNonPointFeature(feature: GeoJSONVTInternalTileFeatureNonPoint, extent: number, z2: number, tx: number, ty: number): GeoJSONVTFeatureNonPoint {
51
+ const transformed = feature as unknown as GeoJSONVTFeatureNonPoint;
52
+
53
+ const geometry = feature.geometry;
54
+ const nonPoint: GeoJSONVTFeatureNonPoint["geometry"] = [];
55
+ for (const geom of geometry) {
56
+ const ring: GeoJSONVTFeaturePoint["geometry"] = [];
57
+ for (let i = 0; i < geom.length; i += 2) {
58
+ ring.push(transformPoint(geom[i], geom[i + 1], extent, z2, tx, ty));
59
+ }
60
+ nonPoint.push(ring);
61
+ }
62
+ transformed.geometry = nonPoint;
63
+
64
+ return transformed;
65
+ }
66
+
65
67
  function transformPoint(x: number, y: number, extent: number, z2: number, tx: number, ty: number): [number, number] {
66
68
  return [
67
69
  Math.round(extent * (x * z2 - tx)),
package/src/wrap.ts CHANGED
@@ -1,5 +1,5 @@
1
1
 
2
- import {clip} from './clip';
2
+ import {AxisType, clip} from './clip';
3
3
  import type { GeoJSONVTInternalFeature, GeoJSONVTOptions, StartEndSizeArray } from './definitions';
4
4
  import {createFeature} from './feature';
5
5
 
@@ -7,12 +7,12 @@ export function wrap(features: GeoJSONVTInternalFeature[], options: GeoJSONVTOpt
7
7
  const buffer = options.buffer / options.extent;
8
8
  let merged = features;
9
9
 
10
- const left = clip(features, 1, -1 - buffer, buffer, 0, -1, 2, options); // left world copy
11
- const right = clip(features, 1, 1 - buffer, 2 + buffer, 0, -1, 2, options); // right world copy
10
+ const left = clip(features, 1, -1 - buffer, buffer, AxisType.X, -1, 2, options); // left world copy
11
+ const right = clip(features, 1, 1 - buffer, 2 + buffer, AxisType.X, -1, 2, options); // right world copy
12
12
 
13
13
  if (!left && !right) return merged;
14
14
 
15
- merged = clip(features, 1, -buffer, 1 + buffer, 0, -1, 2, options) || []; // center world copy
15
+ merged = clip(features, 1, -buffer, 1 + buffer, AxisType.X, -1, 2, options) || []; // center world copy
16
16
 
17
17
  if (left) merged = shiftFeatureCoords(left, 1).concat(merged); // merge left into center
18
18
  if (right) merged = merged.concat(shiftFeatureCoords(right, -1)); // merge right into center