@maplibre/geojson-vt 5.0.3 → 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 +22 -0
- package/dist/clip.d.ts.map +1 -0
- package/dist/clip.test.d.ts +2 -0
- package/dist/clip.test.d.ts.map +1 -0
- 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 +17 -0
- package/dist/convert.d.ts.map +1 -0
- 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 +241 -0
- package/dist/definitions.d.ts.map +1 -0
- package/dist/difference.d.ts +67 -0
- package/dist/difference.d.ts.map +1 -0
- package/dist/difference.test.d.ts +2 -0
- package/dist/difference.test.d.ts.map +1 -0
- package/dist/feature.d.ts +20 -0
- package/dist/feature.d.ts.map +1 -0
- 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 +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/simplify.d.ts +9 -0
- package/dist/simplify.d.ts.map +1 -0
- package/dist/simplify.test.d.ts +2 -0
- package/dist/simplify.test.d.ts.map +1 -0
- package/dist/tile-index.d.ts +51 -0
- package/dist/tile-index.d.ts.map +1 -0
- package/dist/tile.d.ts +12 -0
- package/dist/tile.d.ts.map +1 -0
- package/dist/transform.d.ts +10 -0
- package/dist/transform.d.ts.map +1 -0
- package/dist/wrap.d.ts +3 -0
- package/dist/wrap.d.ts.map +1 -0
- package/package.json +26 -12
- 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
package/src/convert.ts
CHANGED
|
@@ -1,131 +1,147 @@
|
|
|
1
1
|
|
|
2
2
|
import {simplify} from './simplify';
|
|
3
3
|
import {createFeature} from './feature';
|
|
4
|
-
import type {
|
|
4
|
+
import type {GeoJSONVTInternalFeature, GeoJSONVTOptions, StartEndSizeArray} from './definitions';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* converts GeoJSON
|
|
7
|
+
* converts GeoJSON to internal source features (an intermediate projected JSON vector format with simplification data)
|
|
8
8
|
* @param data
|
|
9
9
|
* @param options
|
|
10
10
|
* @returns
|
|
11
11
|
*/
|
|
12
|
-
export function
|
|
12
|
+
export function convertToInternal(data: GeoJSON.GeoJSON, options: GeoJSONVTOptions): GeoJSONVTInternalFeature[] {
|
|
13
13
|
const features: GeoJSONVTInternalFeature[] = [];
|
|
14
14
|
|
|
15
15
|
switch (data.type) {
|
|
16
16
|
case 'FeatureCollection':
|
|
17
17
|
for (let i = 0; i < data.features.length; i++) {
|
|
18
|
-
|
|
18
|
+
featureToInternal(features, data.features[i], options, i);
|
|
19
19
|
}
|
|
20
20
|
break;
|
|
21
21
|
case 'Feature':
|
|
22
|
-
|
|
22
|
+
featureToInternal(features, data, options);
|
|
23
23
|
break;
|
|
24
24
|
default:
|
|
25
|
-
|
|
25
|
+
featureToInternal(features, {type: "Feature" as const, geometry: data, properties: undefined}, options);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
return features;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
function
|
|
31
|
+
function featureToInternal(features: GeoJSONVTInternalFeature[], geojson: GeoJSON.Feature, options: GeoJSONVTOptions, index?: number) {
|
|
32
32
|
if (!geojson.geometry) return;
|
|
33
33
|
|
|
34
34
|
if (geojson.geometry.type === 'GeometryCollection') {
|
|
35
|
-
|
|
36
|
-
convertFeature(features, {
|
|
37
|
-
id: geojson.id,
|
|
38
|
-
type: 'Feature',
|
|
39
|
-
geometry: singleGeometry,
|
|
40
|
-
properties: geojson.properties
|
|
41
|
-
}, options, index);
|
|
42
|
-
}
|
|
35
|
+
convertGeometryCollection(features, geojson, geojson.geometry, options, index);
|
|
43
36
|
return;
|
|
44
37
|
}
|
|
45
38
|
|
|
46
39
|
const coords = geojson.geometry.coordinates;
|
|
47
40
|
if (!coords?.length) return;
|
|
48
41
|
|
|
42
|
+
const id = getFeatureId(geojson, options, index);
|
|
49
43
|
const tolerance = Math.pow(options.tolerance / ((1 << options.maxZoom) * options.extent), 2);
|
|
50
|
-
let id = geojson.id;
|
|
51
|
-
if (options.promoteId) {
|
|
52
|
-
id = geojson.properties?.[options.promoteId];
|
|
53
|
-
} else if (options.generateId) {
|
|
54
|
-
id = index || 0;
|
|
55
|
-
}
|
|
56
44
|
|
|
57
45
|
switch (geojson.geometry.type) {
|
|
58
|
-
case 'Point':
|
|
59
|
-
|
|
60
|
-
|
|
46
|
+
case 'Point':
|
|
47
|
+
convertPointFeature(features, id, geojson.geometry, geojson.properties);
|
|
48
|
+
return;
|
|
61
49
|
|
|
62
|
-
|
|
50
|
+
case 'MultiPoint':
|
|
51
|
+
convertMultiPointFeature(features, id, geojson.geometry, geojson.properties);
|
|
63
52
|
return;
|
|
64
|
-
}
|
|
65
53
|
|
|
66
|
-
case '
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
convertPoint(p, multiPointGeometry);
|
|
70
|
-
}
|
|
54
|
+
case 'LineString':
|
|
55
|
+
convertLineStringFeature(features, id, geojson.geometry, tolerance, geojson.properties);
|
|
56
|
+
return;
|
|
71
57
|
|
|
72
|
-
|
|
58
|
+
case 'MultiLineString':
|
|
59
|
+
convertMultiLineStringFeature(features, id, geojson.geometry, tolerance, options, geojson.properties);
|
|
73
60
|
return;
|
|
74
|
-
}
|
|
75
61
|
|
|
76
|
-
case '
|
|
77
|
-
|
|
78
|
-
|
|
62
|
+
case 'Polygon':
|
|
63
|
+
convertPolygonFeature(features, id, geojson.geometry, tolerance, geojson.properties);
|
|
64
|
+
return;
|
|
79
65
|
|
|
80
|
-
|
|
66
|
+
case 'MultiPolygon':
|
|
67
|
+
convertMultiPolygonFeature(features, id, geojson.geometry, tolerance, geojson.properties);
|
|
81
68
|
return;
|
|
82
|
-
}
|
|
83
69
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const lineGeometry: StartEndSizeArray = [];
|
|
89
|
-
convertLine(line, lineGeometry, tolerance, false);
|
|
90
|
-
features.push(createFeature(id, 'LineString', lineGeometry, geojson.properties));
|
|
91
|
-
}
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
70
|
+
default:
|
|
71
|
+
throw new Error('Input data is not a valid GeoJSON object.');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
94
74
|
|
|
95
|
-
|
|
96
|
-
|
|
75
|
+
function getFeatureId(geojson: GeoJSON.Feature, options: GeoJSONVTOptions, index?: number): number | string | undefined {
|
|
76
|
+
if (options.promoteId) {
|
|
77
|
+
return geojson.properties?.[options.promoteId];
|
|
78
|
+
}
|
|
79
|
+
if (options.generateId) {
|
|
80
|
+
return index || 0;
|
|
81
|
+
}
|
|
82
|
+
return geojson.id;
|
|
83
|
+
}
|
|
97
84
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
85
|
+
function convertGeometryCollection(features: GeoJSONVTInternalFeature[], geojson: GeoJSON.Feature, geometry: GeoJSON.GeometryCollection, options: GeoJSONVTOptions, index?: number) {
|
|
86
|
+
for (const geom of geometry.geometries) {
|
|
87
|
+
featureToInternal(features, {
|
|
88
|
+
id: geojson.id,
|
|
89
|
+
type: 'Feature',
|
|
90
|
+
geometry: geom,
|
|
91
|
+
properties: geojson.properties
|
|
92
|
+
}, options, index);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
101
95
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
96
|
+
function convertPointFeature(features: GeoJSONVTInternalFeature[], id: number | string | undefined, geom: GeoJSON.Point, properties: GeoJSON.GeoJsonProperties) {
|
|
97
|
+
const out: number[] = [];
|
|
98
|
+
out.push(projectX(geom.coordinates[0]), projectY(geom.coordinates[1]), 0);
|
|
99
|
+
features.push(createFeature(id, 'Point', out, properties));
|
|
100
|
+
}
|
|
105
101
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
102
|
+
function convertMultiPointFeature(features: GeoJSONVTInternalFeature[], id: number | string | undefined, geom: GeoJSON.MultiPoint, properties: GeoJSON.GeoJsonProperties) {
|
|
103
|
+
const out: number[] = [];
|
|
104
|
+
for (const coords of geom.coordinates) {
|
|
105
|
+
out.push(projectX(coords[0]), projectY(coords[1]), 0);
|
|
106
|
+
}
|
|
107
|
+
features.push(createFeature(id, 'MultiPoint', out, properties));
|
|
108
|
+
}
|
|
109
109
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
multiPolygonGeometry.push(newPolygon);
|
|
116
|
-
}
|
|
110
|
+
function convertLineStringFeature(features: GeoJSONVTInternalFeature[], id: number | string | undefined, geom: GeoJSON.LineString, tolerance: number, properties: GeoJSON.GeoJsonProperties) {
|
|
111
|
+
const out: StartEndSizeArray = [];
|
|
112
|
+
convertLine(geom.coordinates, out, tolerance, false);
|
|
113
|
+
features.push(createFeature(id, 'LineString', out, properties));
|
|
114
|
+
}
|
|
117
115
|
|
|
118
|
-
|
|
119
|
-
|
|
116
|
+
function convertMultiLineStringFeature(features: GeoJSONVTInternalFeature[], id: number | string | undefined, geom: GeoJSON.MultiLineString, tolerance: number, options: GeoJSONVTOptions, properties: GeoJSON.GeoJsonProperties) {
|
|
117
|
+
if (options.lineMetrics) {
|
|
118
|
+
// explode into linestrings to be able to track metrics
|
|
119
|
+
for (const line of geom.coordinates) {
|
|
120
|
+
const out: StartEndSizeArray = [];
|
|
121
|
+
convertLine(line, out, tolerance, false);
|
|
122
|
+
features.push(createFeature(id, 'LineString', out, properties));
|
|
120
123
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
+
} else {
|
|
125
|
+
const out: StartEndSizeArray[] = [];
|
|
126
|
+
convertLines(geom.coordinates, out, tolerance, false);
|
|
127
|
+
features.push(createFeature(id, 'MultiLineString', out, properties));
|
|
124
128
|
}
|
|
125
129
|
}
|
|
126
130
|
|
|
127
|
-
function
|
|
128
|
-
out
|
|
131
|
+
function convertPolygonFeature(features: GeoJSONVTInternalFeature[], id: number | string | undefined, geom: GeoJSON.Polygon, tolerance: number, properties: GeoJSON.GeoJsonProperties) {
|
|
132
|
+
const out: StartEndSizeArray[] = [];
|
|
133
|
+
convertLines(geom.coordinates, out, tolerance, true);
|
|
134
|
+
features.push(createFeature(id, 'Polygon', out, properties));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function convertMultiPolygonFeature(features: GeoJSONVTInternalFeature[], id: number | string | undefined, geom: GeoJSON.MultiPolygon, tolerance: number, properties: GeoJSON.GeoJsonProperties) {
|
|
138
|
+
const out: StartEndSizeArray[][] = [];
|
|
139
|
+
for (const polygon of geom.coordinates) {
|
|
140
|
+
const polygonOut: StartEndSizeArray[] = [];
|
|
141
|
+
convertLines(polygon, polygonOut, tolerance, true);
|
|
142
|
+
out.push(polygonOut);
|
|
143
|
+
}
|
|
144
|
+
features.push(createFeature(id, 'MultiPolygon', out, properties));
|
|
129
145
|
}
|
|
130
146
|
|
|
131
147
|
function convertLine(ring: GeoJSON.Position[], out: StartEndSizeArray, tolerance: number, isPolygon: boolean) {
|
|
@@ -167,11 +183,17 @@ function convertLines(rings: GeoJSON.Position[][], out: StartEndSizeArray[], tol
|
|
|
167
183
|
}
|
|
168
184
|
}
|
|
169
185
|
|
|
170
|
-
|
|
186
|
+
/**
|
|
187
|
+
* Convert longitude to spherical mercator in [0..1] range
|
|
188
|
+
*/
|
|
189
|
+
export function projectX(x: number) {
|
|
171
190
|
return x / 360 + 0.5;
|
|
172
191
|
}
|
|
173
192
|
|
|
174
|
-
|
|
193
|
+
/**
|
|
194
|
+
* Convert latitude to spherical mercator in [0..1] range
|
|
195
|
+
*/
|
|
196
|
+
export function projectY(y: number) {
|
|
175
197
|
const sin = Math.sin(y * Math.PI / 180);
|
|
176
198
|
const y2 = 0.5 - 0.25 * Math.log((1 + sin) / (1 - sin)) / Math.PI;
|
|
177
199
|
return y2 < 0 ? 0 : y2 > 1 ? 1 : y2;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
|
|
2
|
+
import {test, expect} from 'vitest';
|
|
3
|
+
import {featureToGeoJSON, unprojectX, unprojectY} from './deconvert';
|
|
4
|
+
import {projectX, projectY} from './convert';
|
|
5
|
+
import type {GeoJSONVTInternalFeature} from './definitions';
|
|
6
|
+
|
|
7
|
+
test('project/unproject roundtrip retains precision', () => {
|
|
8
|
+
const coords = [
|
|
9
|
+
{lng: 0, lat: 0},
|
|
10
|
+
{lng: 90, lat: 45.0564839289},
|
|
11
|
+
{lng: -90, lat: -45.0564839289},
|
|
12
|
+
{lng: 180, lat: 85.0511287798},
|
|
13
|
+
{lng: -180, lat: -85.0511287798},
|
|
14
|
+
{lng: 45.1234567895, lat: 23.5456789012},
|
|
15
|
+
{lng: -123.9876543210, lat: -67.8901234564},
|
|
16
|
+
{lng: 0.0000000001, lat: 0.0000000001},
|
|
17
|
+
{lng: 179.9999999999, lat: 85},
|
|
18
|
+
{lng: -179.9999999999, lat: -85}
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
for (const {lng, lat} of coords) {
|
|
22
|
+
expect(unprojectX(projectX(lng))).toBeCloseTo(lng, 10);
|
|
23
|
+
expect(unprojectY(projectY(lat))).toBeCloseTo(lat, 10);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('featureToGeoJSON: converts Point geometry', () => {
|
|
28
|
+
const feature: GeoJSONVTInternalFeature = {
|
|
29
|
+
type: 'Point',
|
|
30
|
+
id: 'point1',
|
|
31
|
+
geometry: [0.5, 0.5, 0],
|
|
32
|
+
tags: {name: 'Test Point'},
|
|
33
|
+
minX: 0.5, minY: 0.5, maxX: 0.5, maxY: 0.5
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const result = featureToGeoJSON(feature);
|
|
37
|
+
|
|
38
|
+
expect(result.type).toBe('Feature');
|
|
39
|
+
expect(result.geometry.type).toBe('Point');
|
|
40
|
+
expect((result.geometry as GeoJSON.Point).coordinates).toEqual([0, 0]);
|
|
41
|
+
expect(result.id).toBe('point1');
|
|
42
|
+
expect(result.properties).toEqual({name: 'Test Point'});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('featureToGeoJSON: converts MultiPoint geometry', () => {
|
|
46
|
+
const feature: GeoJSONVTInternalFeature = {
|
|
47
|
+
type: 'MultiPoint',
|
|
48
|
+
id: 'multipoint1',
|
|
49
|
+
geometry: [0.5, 0.5, 0, 0.525, 0.5, 0],
|
|
50
|
+
tags: {},
|
|
51
|
+
minX: 0.5, minY: 0.5, maxX: 0.525, maxY: 0.5
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const result = featureToGeoJSON(feature);
|
|
55
|
+
|
|
56
|
+
expect(result.geometry.type).toBe('MultiPoint');
|
|
57
|
+
expect((result.geometry as GeoJSON.MultiPoint).coordinates.length).toBe(2);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('featureToGeoJSON: converts LineString geometry', () => {
|
|
61
|
+
const feature: GeoJSONVTInternalFeature = {
|
|
62
|
+
type: 'LineString',
|
|
63
|
+
id: 'line1',
|
|
64
|
+
geometry: [0.5, 0.5, 0, 0.525, 0.5, 0, 0.525, 0.525, 0],
|
|
65
|
+
tags: {highway: 'primary'},
|
|
66
|
+
minX: 0.5, minY: 0.5, maxX: 0.525, maxY: 0.525
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const result = featureToGeoJSON(feature);
|
|
70
|
+
|
|
71
|
+
expect(result.geometry.type).toBe('LineString');
|
|
72
|
+
expect((result.geometry as GeoJSON.LineString).coordinates.length).toBe(3);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('featureToGeoJSON: converts MultiLineString geometry', () => {
|
|
76
|
+
const feature: GeoJSONVTInternalFeature = {
|
|
77
|
+
type: 'MultiLineString',
|
|
78
|
+
id: 'multiline1',
|
|
79
|
+
geometry: [
|
|
80
|
+
[0.5, 0.5, 0, 0.525, 0.5, 0],
|
|
81
|
+
[0.55, 0.55, 0, 0.575, 0.55, 0]
|
|
82
|
+
],
|
|
83
|
+
tags: {},
|
|
84
|
+
minX: 0.5, minY: 0.5, maxX: 0.575, maxY: 0.55
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const result = featureToGeoJSON(feature);
|
|
88
|
+
|
|
89
|
+
expect(result.geometry.type).toBe('MultiLineString');
|
|
90
|
+
expect((result.geometry as GeoJSON.MultiLineString).coordinates.length).toBe(2);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('featureToGeoJSON: converts Polygon geometry', () => {
|
|
94
|
+
const feature: GeoJSONVTInternalFeature = {
|
|
95
|
+
type: 'Polygon',
|
|
96
|
+
id: 'polygon1',
|
|
97
|
+
geometry: [
|
|
98
|
+
[0.5, 0.5, 0, 0.6, 0.5, 0, 0.6, 0.6, 0, 0.5, 0.6, 0, 0.5, 0.5, 0],
|
|
99
|
+
[0.52, 0.52, 0, 0.58, 0.52, 0, 0.58, 0.58, 0, 0.52, 0.58, 0, 0.52, 0.52, 0]
|
|
100
|
+
],
|
|
101
|
+
tags: {landuse: 'residential'},
|
|
102
|
+
minX: 0.5, minY: 0.5, maxX: 0.6, maxY: 0.6
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const result = featureToGeoJSON(feature);
|
|
106
|
+
|
|
107
|
+
expect(result.geometry.type).toBe('Polygon');
|
|
108
|
+
expect((result.geometry as GeoJSON.Polygon).coordinates.length).toBe(2);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test('featureToGeoJSON: converts MultiPolygon geometry', () => {
|
|
112
|
+
const feature: GeoJSONVTInternalFeature = {
|
|
113
|
+
type: 'MultiPolygon',
|
|
114
|
+
id: 'multipolygon1',
|
|
115
|
+
geometry: [
|
|
116
|
+
[[0.5, 0.5, 0, 0.52, 0.5, 0, 0.52, 0.52, 0, 0.5, 0.52, 0, 0.5, 0.5, 0]],
|
|
117
|
+
[[0.55, 0.55, 0, 0.57, 0.55, 0, 0.57, 0.57, 0, 0.55, 0.57, 0, 0.55, 0.55, 0]]
|
|
118
|
+
],
|
|
119
|
+
tags: {},
|
|
120
|
+
minX: 0.5, minY: 0.5, maxX: 0.57, maxY: 0.57
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const result = featureToGeoJSON(feature);
|
|
124
|
+
|
|
125
|
+
expect(result.geometry.type).toBe('MultiPolygon');
|
|
126
|
+
expect((result.geometry as GeoJSON.MultiPolygon).coordinates.length).toBe(2);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test('featureToGeoJSON: handles various id types', () => {
|
|
130
|
+
const noId: GeoJSONVTInternalFeature = {type: 'Point', geometry: [0.5, 0.5, 0], tags: {}, minX: 0.5, minY: 0.5, maxX: 0.5, maxY: 0.5};
|
|
131
|
+
const stringId: GeoJSONVTInternalFeature = {type: 'Point', id: 'string-id', geometry: [0.5, 0.5, 0], tags: {}, minX: 0.5, minY: 0.5, maxX: 0.5, maxY: 0.5};
|
|
132
|
+
const numberId: GeoJSONVTInternalFeature = {type: 'Point', id: 42, geometry: [0.5, 0.5, 0], tags: {}, minX: 0.5, minY: 0.5, maxX: 0.5, maxY: 0.5};
|
|
133
|
+
|
|
134
|
+
expect(featureToGeoJSON(noId).id).toBeUndefined();
|
|
135
|
+
expect(featureToGeoJSON(stringId).id).toBe('string-id');
|
|
136
|
+
expect(featureToGeoJSON(numberId).id).toBe(42);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test('featureToGeoJSON: correctly unprojects known coordinates', () => {
|
|
140
|
+
const feature: GeoJSONVTInternalFeature = {
|
|
141
|
+
type: 'MultiPoint',
|
|
142
|
+
geometry: [0.5, 0.5, 0, 0, 0.5, 0, 1, 0.5, 0],
|
|
143
|
+
tags: {},
|
|
144
|
+
minX: 0, minY: 0.5, maxX: 1, maxY: 0.5
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const result = featureToGeoJSON(feature);
|
|
148
|
+
const coords = (result.geometry as GeoJSON.MultiPoint).coordinates;
|
|
149
|
+
|
|
150
|
+
expect(coords[0]).toEqual([0, 0]);
|
|
151
|
+
expect(coords[1]).toEqual([-180, 0]);
|
|
152
|
+
expect(coords[2]).toEqual([180, 0]);
|
|
153
|
+
});
|
package/src/deconvert.ts
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type {GeoJSONVTInternalFeature} from './definitions';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Converts internal source features back to GeoJSON format.
|
|
5
|
+
*/
|
|
6
|
+
export function convertToGeoJSON(source: GeoJSONVTInternalFeature[]): GeoJSON.GeoJSON {
|
|
7
|
+
const geojson: GeoJSON.GeoJSON = {
|
|
8
|
+
type: 'FeatureCollection',
|
|
9
|
+
features: source.map(feature => featureToGeoJSON(feature))
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
return geojson;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Converts a single internal feature to GeoJSON format.
|
|
17
|
+
*/
|
|
18
|
+
export function featureToGeoJSON(feature: GeoJSONVTInternalFeature): GeoJSON.Feature {
|
|
19
|
+
const geojsonFeature: GeoJSON.Feature = {
|
|
20
|
+
type: 'Feature',
|
|
21
|
+
geometry: geometryToGeoJSON(feature),
|
|
22
|
+
properties: feature.tags
|
|
23
|
+
};
|
|
24
|
+
if (feature.id != null) {
|
|
25
|
+
geojsonFeature.id = feature.id;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return geojsonFeature;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Converts a single internal feature geometry to GeoJSON format.
|
|
33
|
+
*/
|
|
34
|
+
function geometryToGeoJSON(feature: GeoJSONVTInternalFeature): GeoJSON.Geometry {
|
|
35
|
+
const {type, geometry} = feature;
|
|
36
|
+
|
|
37
|
+
switch (type) {
|
|
38
|
+
case 'Point':
|
|
39
|
+
return {
|
|
40
|
+
type: type,
|
|
41
|
+
coordinates: unprojectPoint(geometry[0], geometry[1])
|
|
42
|
+
};
|
|
43
|
+
case 'MultiPoint':
|
|
44
|
+
case 'LineString':
|
|
45
|
+
return {
|
|
46
|
+
type: type,
|
|
47
|
+
coordinates: unprojectPoints(geometry)
|
|
48
|
+
};
|
|
49
|
+
case 'MultiLineString':
|
|
50
|
+
case 'Polygon':
|
|
51
|
+
return {
|
|
52
|
+
type: type,
|
|
53
|
+
coordinates: geometry.map(ring => unprojectPoints(ring))
|
|
54
|
+
};
|
|
55
|
+
case 'MultiPolygon':
|
|
56
|
+
return {
|
|
57
|
+
type: type,
|
|
58
|
+
coordinates: geometry.map(polygon =>
|
|
59
|
+
polygon.map(ring => unprojectPoints(ring))
|
|
60
|
+
)
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function unprojectPoints(coords: number[]): GeoJSON.Position[] {
|
|
66
|
+
const result: GeoJSON.Position[] = [];
|
|
67
|
+
|
|
68
|
+
for (let i = 0; i < coords.length; i += 3) {
|
|
69
|
+
result.push(unprojectPoint(coords[i], coords[i + 1]));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function unprojectPoint(x: number, y: number): GeoJSON.Position {
|
|
76
|
+
return [unprojectX(x), unprojectY(y)];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Convert spherical mercator in [0..1] range to longitude
|
|
81
|
+
*/
|
|
82
|
+
export function unprojectX(x: number): number {
|
|
83
|
+
return (x - 0.5) * 360;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Convert spherical mercator in [0..1] range to latitude
|
|
88
|
+
*/
|
|
89
|
+
export function unprojectY(y: number): number {
|
|
90
|
+
const y2 = (180 - y * 360) * Math.PI / 180;
|
|
91
|
+
return 360 * Math.atan(Math.exp(y2)) / Math.PI - 90;
|
|
92
|
+
}
|