@maplibre/geojson-vt 5.0.1 → 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/dist/geojson-vt-dev.js +511 -534
- package/dist/geojson-vt.js +1 -1
- package/dist/geojson-vt.mjs +1155 -0
- package/dist/geojson-vt.mjs.map +1 -0
- package/package.json +19 -7
- package/src/clip.test.ts +79 -0
- package/src/clip.ts +232 -0
- package/src/convert.ts +178 -0
- package/src/definitions.ts +86 -0
- package/src/difference.test.ts +270 -0
- package/src/{difference.js → difference.ts} +87 -49
- package/src/feature.ts +64 -0
- package/src/{index.js → index.ts} +107 -52
- package/src/simplify.test.ts +73 -0
- package/src/{simplify.js → simplify.ts} +22 -9
- package/src/tile.ts +166 -0
- package/src/transform.ts +55 -0
- package/src/wrap.ts +81 -0
- package/src/clip.js +0 -200
- package/src/convert.js +0 -139
- package/src/feature.js +0 -43
- package/src/tile.js +0 -123
- package/src/transform.js +0 -41
- package/src/wrap.js +0 -68
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
|
|
3
|
-
import clip from './clip
|
|
4
|
-
import wrap from './wrap
|
|
5
|
-
import
|
|
6
|
-
import createTile from './tile
|
|
7
|
-
import {applySourceDiff} from './difference
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
25
|
-
|
|
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)
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
|
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
|
-
|
|
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])
|
|
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
|
|
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
|
-
|
|
240
|
+
if (!this.tiles[id]) return null;
|
|
241
|
+
|
|
242
|
+
return transformTile(this.tiles[id], extent);
|
|
206
243
|
}
|
|
207
244
|
|
|
208
|
-
|
|
209
|
-
|
|
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)
|
|
316
|
+
if (removedLookup.size) {
|
|
317
|
+
this.tileCoords = this.tileCoords.filter(c => !removedLookup.has(c.id));
|
|
318
|
+
}
|
|
276
319
|
}
|
|
277
320
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
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
|
|
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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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
|
-
|
|
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
|
-
|
|
42
|
-
|
|
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
|
+
}
|
package/src/transform.ts
ADDED
|
@@ -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
|
+
}
|