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.
Files changed (52) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/LICENSE +21 -0
  3. package/README.md +220 -0
  4. package/docs/api.md +66 -0
  5. package/docs/architecture.md +87 -0
  6. package/docs/cartography.md +77 -0
  7. package/docs/cesium.md +107 -0
  8. package/docs/openlayers.md +98 -0
  9. package/docs/styles.md +103 -0
  10. package/package.json +51 -0
  11. package/packages/cesium/package.json +13 -0
  12. package/packages/cesium/src/index.js +405 -0
  13. package/packages/ol/package.json +14 -0
  14. package/packages/ol/src/index.js +1705 -0
  15. package/packages/ol/src/labels.js +977 -0
  16. package/src/3dtiles/b3dm.js +38 -0
  17. package/src/3dtiles/clipper-surfaces.js +317 -0
  18. package/src/3dtiles/export.js +768 -0
  19. package/src/3dtiles/extrude.js +301 -0
  20. package/src/3dtiles/flat.js +531 -0
  21. package/src/3dtiles/glb.js +178 -0
  22. package/src/3dtiles/gpkg-buildings.js +240 -0
  23. package/src/3dtiles/gpkg-features.js +157 -0
  24. package/src/3dtiles/tileset.js +75 -0
  25. package/src/build.js +134 -0
  26. package/src/cli.js +656 -0
  27. package/src/export-pmtiles.js +962 -0
  28. package/src/geometry-read.js +50 -0
  29. package/src/gpkg-read.js +460 -0
  30. package/src/gpkg.js +567 -0
  31. package/src/html.js +593 -0
  32. package/src/layers.js +357 -0
  33. package/src/manifest.js +29 -0
  34. package/src/mvt.js +2593 -0
  35. package/src/ol.js +5 -0
  36. package/src/osm.js +2110 -0
  37. package/src/pmtiles-worker.js +70 -0
  38. package/src/pmtiles.js +260 -0
  39. package/src/server.js +720 -0
  40. package/src/style-command.js +78 -0
  41. package/src/style-filters.js +76 -0
  42. package/src/style-presets.js +93 -0
  43. package/src/style-themes.js +235 -0
  44. package/src/style.js +13 -0
  45. package/src/tile-cache.js +59 -0
  46. package/src/utils.js +222 -0
  47. package/styles/presets/light.json +4655 -0
  48. package/styles/presets/monochrome.json +4655 -0
  49. package/styles/presets/neon-dark-3d.json +90 -0
  50. package/styles/presets/neon-dark.json +4690 -0
  51. package/styles/presets/tactical.json +4690 -0
  52. 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
+ }