@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.
- package/README.md +3 -13
- package/dist/clip.d.ts +20 -1
- package/dist/clip.d.ts.map +1 -1
- package/dist/cluster-tile-index.d.ts +76 -0
- package/dist/cluster-tile-index.d.ts.map +1 -0
- package/dist/cluster-tile-index.test.d.ts +2 -0
- package/dist/cluster-tile-index.test.d.ts.map +1 -0
- package/dist/convert.d.ts +10 -2
- package/dist/convert.d.ts.map +1 -1
- package/dist/deconvert.d.ts +19 -0
- package/dist/deconvert.d.ts.map +1 -0
- package/dist/deconvert.test.d.ts +2 -0
- package/dist/deconvert.test.d.ts.map +1 -0
- package/dist/definitions.d.ts +176 -20
- package/dist/definitions.d.ts.map +1 -1
- package/dist/feature.d.ts +11 -3
- package/dist/feature.d.ts.map +1 -1
- package/dist/geojson-to-tile.d.ts +35 -0
- package/dist/geojson-to-tile.d.ts.map +1 -0
- package/dist/geojson-vt-dev.js +1582 -478
- package/dist/geojson-vt.js +1 -1
- package/dist/geojson-vt.mjs +1250 -473
- package/dist/geojson-vt.mjs.map +1 -1
- package/dist/geojsonvt.d.ts +76 -0
- package/dist/geojsonvt.d.ts.map +1 -0
- package/dist/geojsonvt.test.d.ts +2 -0
- package/dist/geojsonvt.test.d.ts.map +1 -0
- package/dist/index.d.ts +8 -62
- package/dist/index.d.ts.map +1 -1
- package/dist/tile-index.d.ts +51 -0
- package/dist/tile-index.d.ts.map +1 -0
- package/dist/tile.d.ts +1 -29
- package/dist/tile.d.ts.map +1 -1
- package/dist/transform.d.ts +1 -18
- package/dist/transform.d.ts.map +1 -1
- package/package.json +18 -10
- package/src/clip.ts +119 -81
- package/src/cluster-tile-index.test.ts +205 -0
- package/src/cluster-tile-index.ts +513 -0
- package/src/convert.ts +97 -75
- package/src/deconvert.test.ts +153 -0
- package/src/deconvert.ts +92 -0
- package/src/definitions.ts +196 -18
- package/src/difference.ts +3 -3
- package/src/feature.ts +11 -4
- package/src/geojson-to-tile.ts +58 -0
- package/src/geojsonvt.test.ts +39 -0
- package/src/geojsonvt.ts +209 -0
- package/src/index.ts +27 -378
- package/src/tile-index.ts +310 -0
- package/src/tile.ts +92 -103
- package/src/transform.ts +41 -39
- 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
|
-
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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 "./
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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,
|
|
11
|
-
const right = clip(features, 1, 1 - buffer, 2 + buffer,
|
|
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,
|
|
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
|