@maplibre/geojson-vt 5.0.4 → 6.0.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.
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 +1586 -478
  21. package/dist/geojson-vt.js +1 -1
  22. package/dist/geojson-vt.mjs +1252 -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 +9 -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 +3 -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 +30 -378
  50. package/src/tile-index.ts +310 -0
  51. package/src/tile.ts +94 -104
  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,7 @@
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
-
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
- }
3
+ export const GEOJSONVT_CLIP_START = 'geojsonvt_clip_start';
4
+ export const GEOJSONVT_CLIP_END = 'geojsonvt_clip_end';
33
5
 
34
6
  /**
35
7
  * Creates a tile object from the given features
@@ -44,19 +16,19 @@ export function createTile(features: GeoJSONVTInternalFeature[], z: number, tx:
44
16
  const tolerance = z === options.maxZoom ? 0 : options.tolerance / ((1 << z) * options.extent);
45
17
 
46
18
  const tile = {
19
+ transformed: false,
47
20
  features: [] as GeoJSONVTInternalTileFeature[],
48
- numPoints: 0,
49
- numSimplified: 0,
50
- numFeatures: features.length,
51
- source: null as GeoJSONVTInternalFeature[] | null,
21
+ source: null as GeoJSONVTInternalFeature[],
52
22
  x: tx,
53
23
  y: ty,
54
- z,
55
- transformed: false,
24
+ z: z,
56
25
  minX: 2,
57
26
  minY: 1,
58
27
  maxX: -1,
59
- maxY: 0
28
+ maxY: 0,
29
+ numPoints: 0,
30
+ numSimplified: 0,
31
+ numFeatures: features.length
60
32
  };
61
33
 
62
34
  for (const feature of features) {
@@ -72,81 +44,99 @@ function addFeature(tile: GeoJSONVTInternalTile, feature: GeoJSONVTInternalFeatu
72
44
  tile.maxX = Math.max(tile.maxX, feature.maxX);
73
45
  tile.maxY = Math.max(tile.maxY, feature.maxY);
74
46
 
75
- let tags = feature.tags || null;
76
-
77
- let tileFeature: GeoJSONVTInternalTileFeature;
78
-
79
47
  switch (feature.type) {
80
48
  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
- }
49
+ case 'MultiPoint':
50
+ addPointsTileFeature(tile, feature);
51
+ return;
52
+ case 'LineString':
53
+ addLineTileFeautre(tile, feature, tolerance, options);
54
+ return;
114
55
  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
- }
56
+ case 'Polygon':
57
+ addLinesTileFeature(tile, feature, tolerance);
58
+ return;
59
+ case 'MultiPolygon':
60
+ addMultiPolygonTileFeature(tile, feature, tolerance);
61
+ return;
62
+ }
63
+ }
64
+
65
+ function addPointsTileFeature(tile: GeoJSONVTInternalTile, feature: GeoJSONVTInternalPointFeature | GeoJSONVTInternalMultiPointFeature) {
66
+ const geometry: number[] = [];
67
+ for (let i = 0; i < feature.geometry.length; i += 3) {
68
+ geometry.push(feature.geometry[i] , feature.geometry[i + 1]);
69
+ tile.numPoints++;
70
+ tile.numSimplified++;
71
+ }
72
+ if (!geometry.length) return;
73
+ const tileFeature: GeoJSONVTInternalTileFeature = {
74
+ type: 1,
75
+ tags: feature.tags || null,
76
+ geometry: geometry
77
+ };
78
+ if (feature.id !== null) {
79
+ tileFeature.id = feature.id;
144
80
  }
81
+ tile.features.push(tileFeature);
82
+ }
145
83
 
84
+ function addLineTileFeautre(tile: GeoJSONVTInternalTile, feature: GeoJSONVTInternalLineStringFeature, tolerance: number, options: GeoJSONVTOptions) {
85
+ const geometry: number[][] = [];
86
+ addLine(geometry, feature.geometry, tile, tolerance, false, false);
87
+ if (!geometry.length) return;
88
+ let tags = feature.tags || null;
89
+ if (options.lineMetrics) {
90
+ tags = {};
91
+ for (const key in feature.tags) tags[key] = feature.tags[key];
92
+ tags[GEOJSONVT_CLIP_START] = feature.geometry.start / feature.geometry.size;
93
+ tags[GEOJSONVT_CLIP_END] = feature.geometry.end / feature.geometry.size;
94
+ }
95
+ const tileFeature: GeoJSONVTInternalTileFeature = {
96
+ type: 2,
97
+ tags: tags,
98
+ geometry: geometry
99
+ }
146
100
  if (feature.id !== null) {
147
101
  tileFeature.id = feature.id;
148
102
  }
103
+ tile.features.push(tileFeature);
104
+ }
149
105
 
106
+ function addLinesTileFeature(tile: GeoJSONVTInternalTile, feature: GeoJSONVTInternalPolygonFeature | GeoJSONVTInternalMultiLineStringFeature, tolerance: number) {
107
+ const geometry: number[][] = [];
108
+ for (let i = 0; i < feature.geometry.length; i++) {
109
+ addLine(geometry, feature.geometry[i], tile, tolerance, feature.type === 'Polygon', i === 0);
110
+ }
111
+ if (!geometry.length) return;
112
+ const tileFeature: GeoJSONVTInternalTileFeature = {
113
+ type: feature.type === 'Polygon' ? 3 : 2,
114
+ tags: feature.tags || null,
115
+ geometry: geometry
116
+ }
117
+ if (feature.id !== null) {
118
+ tileFeature.id = feature.id;
119
+ }
120
+ tile.features.push(tileFeature);
121
+ }
122
+
123
+ function addMultiPolygonTileFeature(tile: GeoJSONVTInternalTile, feature: GeoJSONVTInternalMultiPolygonFeature, tolerance: number) {
124
+ const geometry: number[][] = [];
125
+ for (let k = 0; k < feature.geometry.length; k++) {
126
+ const polygon = feature.geometry[k];
127
+ for (let i = 0; i < polygon.length; i++) {
128
+ addLine(geometry, polygon[i], tile, tolerance, true, i === 0);
129
+ }
130
+ }
131
+ if (!geometry.length) return;
132
+ const tileFeature: GeoJSONVTInternalTileFeature = {
133
+ type: 3,
134
+ tags: feature.tags || null,
135
+ geometry: geometry
136
+ }
137
+ if (feature.id !== null) {
138
+ tileFeature.id = feature.id;
139
+ }
150
140
  tile.features.push(tileFeature);
151
141
  }
152
142
 
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