map-zero 0.1.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/CHANGELOG.md +13 -0
- package/LICENSE +21 -0
- package/README.md +220 -0
- package/docs/api.md +66 -0
- package/docs/architecture.md +87 -0
- package/docs/cartography.md +77 -0
- package/docs/cesium.md +107 -0
- package/docs/openlayers.md +98 -0
- package/docs/styles.md +103 -0
- package/package.json +51 -0
- package/packages/cesium/package.json +13 -0
- package/packages/cesium/src/index.js +405 -0
- package/packages/ol/package.json +14 -0
- package/packages/ol/src/index.js +1705 -0
- package/packages/ol/src/labels.js +977 -0
- package/src/3dtiles/b3dm.js +38 -0
- package/src/3dtiles/clipper-surfaces.js +317 -0
- package/src/3dtiles/export.js +768 -0
- package/src/3dtiles/extrude.js +301 -0
- package/src/3dtiles/flat.js +531 -0
- package/src/3dtiles/glb.js +178 -0
- package/src/3dtiles/gpkg-buildings.js +240 -0
- package/src/3dtiles/gpkg-features.js +157 -0
- package/src/3dtiles/tileset.js +75 -0
- package/src/build.js +134 -0
- package/src/cli.js +656 -0
- package/src/export-pmtiles.js +962 -0
- package/src/geometry-read.js +50 -0
- package/src/gpkg-read.js +460 -0
- package/src/gpkg.js +567 -0
- package/src/html.js +593 -0
- package/src/layers.js +357 -0
- package/src/manifest.js +29 -0
- package/src/mvt.js +2593 -0
- package/src/ol.js +5 -0
- package/src/osm.js +2110 -0
- package/src/pmtiles-worker.js +70 -0
- package/src/pmtiles.js +260 -0
- package/src/server.js +720 -0
- package/src/style-command.js +78 -0
- package/src/style-filters.js +76 -0
- package/src/style-presets.js +93 -0
- package/src/style-themes.js +235 -0
- package/src/style.js +13 -0
- package/src/tile-cache.js +59 -0
- package/src/utils.js +222 -0
- package/styles/presets/light.json +4655 -0
- package/styles/presets/monochrome.json +4655 -0
- package/styles/presets/neon-dark-3d.json +90 -0
- package/styles/presets/neon-dark.json +4690 -0
- package/styles/presets/tactical.json +4690 -0
- package/styles/themes/neon-dark.theme.json +20 -0
package/src/utils.js
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { basename, extname } from 'node:path';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Parse and validate a bbox string.
|
|
5
|
+
*
|
|
6
|
+
* @param {string} value minLon,minLat,maxLon,maxLat
|
|
7
|
+
* @returns {[number, number, number, number]}
|
|
8
|
+
*/
|
|
9
|
+
export function parseBbox(value) {
|
|
10
|
+
const parts = String(value).split(',').map((part) => Number(part.trim()));
|
|
11
|
+
|
|
12
|
+
if (parts.length !== 4 || parts.some((part) => !Number.isFinite(part))) {
|
|
13
|
+
throw new Error('bbox must use the format minLon,minLat,maxLon,maxLat');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const [minLon, minLat, maxLon, maxLat] = parts;
|
|
17
|
+
|
|
18
|
+
if (minLon < -180 || maxLon > 180 || minLat < -90 || maxLat > 90) {
|
|
19
|
+
throw new Error('bbox coordinates must be valid WGS84 lon/lat values');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (minLon >= maxLon || minLat >= maxLat) {
|
|
23
|
+
throw new Error('bbox is inverted; minLon/minLat must be smaller than maxLon/maxLat');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return /** @type {[number, number, number, number]} */ (parts);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Parse and validate a comma-separated layer list.
|
|
31
|
+
*
|
|
32
|
+
* @param {string} value
|
|
33
|
+
* @param {string[]} supportedLayers
|
|
34
|
+
* @param {Record<string, string>} [aliases]
|
|
35
|
+
* @returns {string[]}
|
|
36
|
+
*/
|
|
37
|
+
export function parseLayerList(value, supportedLayers, aliases = {}) {
|
|
38
|
+
const layers = String(value)
|
|
39
|
+
.split(',')
|
|
40
|
+
.map((layer) => layer.trim())
|
|
41
|
+
.map((layer) => aliases[layer] ?? layer)
|
|
42
|
+
.filter(Boolean);
|
|
43
|
+
|
|
44
|
+
if (layers.length === 0) {
|
|
45
|
+
throw new Error('at least one layer must be selected');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const supported = new Set(supportedLayers);
|
|
49
|
+
const invalid = layers.filter((layer) => !supported.has(layer));
|
|
50
|
+
|
|
51
|
+
if (invalid.length > 0) {
|
|
52
|
+
throw new Error(`unsupported layer(s): ${invalid.join(', ')}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return [...new Set(layers)];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Derive a package name from an output path.
|
|
60
|
+
*
|
|
61
|
+
* @param {string} outPath
|
|
62
|
+
* @returns {string}
|
|
63
|
+
*/
|
|
64
|
+
export function packageNameFromPath(outPath) {
|
|
65
|
+
const base = basename(outPath);
|
|
66
|
+
return extname(base) === '.mapzero' ? base.slice(0, -'.mapzero'.length) : base;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Check whether a coordinate is inside a bbox.
|
|
71
|
+
*
|
|
72
|
+
* @param {[number, number]} coordinate
|
|
73
|
+
* @param {[number, number, number, number]} bbox
|
|
74
|
+
* @returns {boolean}
|
|
75
|
+
*/
|
|
76
|
+
export function pointInBbox(coordinate, bbox) {
|
|
77
|
+
const [lon, lat] = coordinate;
|
|
78
|
+
const [minLon, minLat, maxLon, maxLat] = bbox;
|
|
79
|
+
return lon >= minLon && lon <= maxLon && lat >= minLat && lat <= maxLat;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Check whether two bboxes intersect.
|
|
84
|
+
*
|
|
85
|
+
* @param {[number, number, number, number]} a
|
|
86
|
+
* @param {[number, number, number, number]} b
|
|
87
|
+
* @returns {boolean}
|
|
88
|
+
*/
|
|
89
|
+
export function bboxIntersects(a, b) {
|
|
90
|
+
return a[0] <= b[2] && a[2] >= b[0] && a[1] <= b[3] && a[3] >= b[1];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Calculate a bbox for a GeoJSON-like geometry.
|
|
95
|
+
*
|
|
96
|
+
* @param {{ type: string, coordinates: unknown }} geometry
|
|
97
|
+
* @returns {[number, number, number, number] | null}
|
|
98
|
+
*/
|
|
99
|
+
export function geometryBbox(geometry) {
|
|
100
|
+
/** @type {[number, number, number, number] | null} */
|
|
101
|
+
let bbox = null;
|
|
102
|
+
|
|
103
|
+
walkCoordinates(geometry.coordinates, (coordinate) => {
|
|
104
|
+
const [lon, lat] = coordinate;
|
|
105
|
+
if (!Number.isFinite(lon) || !Number.isFinite(lat)) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (bbox === null) {
|
|
110
|
+
bbox = [lon, lat, lon, lat];
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
bbox[0] = Math.min(bbox[0], lon);
|
|
115
|
+
bbox[1] = Math.min(bbox[1], lat);
|
|
116
|
+
bbox[2] = Math.max(bbox[2], lon);
|
|
117
|
+
bbox[3] = Math.max(bbox[3], lat);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
return bbox;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Remove consecutive duplicate coordinates from a line or ring.
|
|
125
|
+
*
|
|
126
|
+
* @param {Array<[number, number]>} coordinates
|
|
127
|
+
* @returns {Array<[number, number]>}
|
|
128
|
+
*/
|
|
129
|
+
export function dedupeCoordinates(coordinates) {
|
|
130
|
+
/** @type {Array<[number, number]>} */
|
|
131
|
+
const deduped = [];
|
|
132
|
+
|
|
133
|
+
for (const coordinate of coordinates) {
|
|
134
|
+
const previous = deduped.at(-1);
|
|
135
|
+
if (!previous || previous[0] !== coordinate[0] || previous[1] !== coordinate[1]) {
|
|
136
|
+
deduped.push(coordinate);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return deduped;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Close a polygon ring if needed.
|
|
145
|
+
*
|
|
146
|
+
* @param {Array<[number, number]>} coordinates
|
|
147
|
+
* @returns {Array<[number, number]>}
|
|
148
|
+
*/
|
|
149
|
+
export function closeRing(coordinates) {
|
|
150
|
+
const ring = dedupeCoordinates(coordinates);
|
|
151
|
+
const first = ring[0];
|
|
152
|
+
const last = ring.at(-1);
|
|
153
|
+
|
|
154
|
+
if (!first || !last) {
|
|
155
|
+
return ring;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (first[0] !== last[0] || first[1] !== last[1]) {
|
|
159
|
+
ring.push([first[0], first[1]]);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return ring;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Check whether a point is inside a polygon ring.
|
|
167
|
+
*
|
|
168
|
+
* @param {[number, number]} point
|
|
169
|
+
* @param {Array<[number, number]>} ring
|
|
170
|
+
* @returns {boolean}
|
|
171
|
+
*/
|
|
172
|
+
export function pointInRing(point, ring) {
|
|
173
|
+
const [x, y] = point;
|
|
174
|
+
let inside = false;
|
|
175
|
+
|
|
176
|
+
for (let i = 0, j = ring.length - 1; i < ring.length; j = i, i += 1) {
|
|
177
|
+
const [xi, yi] = ring[i];
|
|
178
|
+
const [xj, yj] = ring[j];
|
|
179
|
+
const intersects = yi > y !== yj > y && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi;
|
|
180
|
+
|
|
181
|
+
if (intersects) {
|
|
182
|
+
inside = !inside;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return inside;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Safely quote a SQLite identifier.
|
|
191
|
+
*
|
|
192
|
+
* @param {string} identifier
|
|
193
|
+
* @returns {string}
|
|
194
|
+
*/
|
|
195
|
+
export function quoteIdentifier(identifier) {
|
|
196
|
+
return `"${String(identifier).replaceAll('"', '""')}"`;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Visit all [lon, lat] coordinates in a GeoJSON-like coordinate tree.
|
|
201
|
+
*
|
|
202
|
+
* @param {unknown} value
|
|
203
|
+
* @param {(coordinate: [number, number]) => void} visitor
|
|
204
|
+
*/
|
|
205
|
+
function walkCoordinates(value, visitor) {
|
|
206
|
+
if (!Array.isArray(value)) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (
|
|
211
|
+
value.length >= 2 &&
|
|
212
|
+
typeof value[0] === 'number' &&
|
|
213
|
+
typeof value[1] === 'number'
|
|
214
|
+
) {
|
|
215
|
+
visitor(/** @type {[number, number]} */ (value));
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
for (const child of value) {
|
|
220
|
+
walkCoordinates(child, visitor);
|
|
221
|
+
}
|
|
222
|
+
}
|