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
@@ -0,0 +1,301 @@
1
+ import earcut from 'earcut';
2
+
3
+ const WGS84_A = 6378137.0;
4
+ const WGS84_F = 1 / 298.257223563;
5
+ const WGS84_E2 = WGS84_F * (2 - WGS84_F);
6
+
7
+ /**
8
+ * @typedef {{ coordinates: Array<[number, number]>, height: number }} Footprint
9
+ * @typedef {{
10
+ * positions: Float32Array,
11
+ * normals: Float32Array,
12
+ * indices: Uint16Array | Uint32Array,
13
+ * min: [number, number, number],
14
+ * max: [number, number, number],
15
+ * bbox: [number, number, number, number],
16
+ * maxHeight: number,
17
+ * featureCount: number
18
+ * }} ExtrudedMesh
19
+ */
20
+
21
+ /**
22
+ * Build one merged mesh from building footprints.
23
+ *
24
+ * @param {Footprint[]} footprints
25
+ * @param {{ baseHeight?: number }} [options]
26
+ * @returns {ExtrudedMesh | null}
27
+ */
28
+ export function buildMergedExtrudedPolygonMesh(footprints, options = {}) {
29
+ const baseHeight = options.baseHeight ?? 0;
30
+ const positions = [];
31
+ const normals = [];
32
+ const indices = [];
33
+ const bboxes = [];
34
+ let maxHeight = baseHeight;
35
+ let featureCount = 0;
36
+
37
+ for (const footprint of footprints) {
38
+ const mesh = buildExtrudedPolygonMesh(footprint.coordinates, baseHeight, footprint.height);
39
+ if (!mesh) {
40
+ continue;
41
+ }
42
+
43
+ const vertexOffset = positions.length / 3;
44
+ positions.push(...mesh.positions);
45
+ normals.push(...mesh.normals);
46
+ for (const index of mesh.indices) {
47
+ indices.push(index + vertexOffset);
48
+ }
49
+ bboxes.push(mesh.bbox);
50
+ maxHeight = Math.max(maxHeight, footprint.height);
51
+ featureCount++;
52
+ }
53
+
54
+ if (featureCount === 0) {
55
+ return null;
56
+ }
57
+
58
+ const positionArray = new Float32Array(positions);
59
+ const normalArray = new Float32Array(normals);
60
+ const indexArray = positionArray.length / 3 > 65535
61
+ ? new Uint32Array(indices)
62
+ : new Uint16Array(indices);
63
+ const bounds = minMaxVec3(positionArray);
64
+
65
+ return {
66
+ positions: positionArray,
67
+ normals: normalArray,
68
+ indices: indexArray,
69
+ min: bounds.min,
70
+ max: bounds.max,
71
+ bbox: mergeBboxes(bboxes),
72
+ maxHeight,
73
+ featureCount
74
+ };
75
+ }
76
+
77
+ /**
78
+ * @param {Array<[number, number]>} ring
79
+ * @param {number} baseHeight
80
+ * @param {number} topHeight
81
+ * @returns {{ positions: number[], normals: number[], indices: number[], bbox: [number, number, number, number] } | null}
82
+ */
83
+ export function buildExtrudedPolygonMesh(ring, baseHeight, topHeight) {
84
+ const clean = cleanRing(ring);
85
+ if (clean.length < 3) {
86
+ return null;
87
+ }
88
+
89
+ const centroid = polygonCentroid(clean);
90
+ const up = normalize(wgs84SurfaceNormal(centroid[0], centroid[1]));
91
+ const bottom = clean.map(([lon, lat]) => wgs84ToEcef(lon, lat, baseHeight));
92
+ const top = clean.map(([lon, lat]) => wgs84ToEcef(lon, lat, topHeight));
93
+ const positions = [];
94
+ const normals = [];
95
+ const indices = [];
96
+
97
+ const addTriangle = (a, b, c, normal) => {
98
+ const n = normal ?? triangleNormal(a, b, c);
99
+ const index = positions.length / 3;
100
+ positions.push(...a, ...b, ...c);
101
+ normals.push(...n, ...n, ...n);
102
+ indices.push(index, index + 1, index + 2);
103
+ };
104
+
105
+ const projected = projectRing(clean, centroid);
106
+ const roofTriangles = earcut(projected.flat, null, 2);
107
+ if (roofTriangles.length === 0) {
108
+ return null;
109
+ }
110
+
111
+ for (let i = 0; i < roofTriangles.length; i += 3) {
112
+ addTriangle(top[roofTriangles[i]], top[roofTriangles[i + 1]], top[roofTriangles[i + 2]], up);
113
+ }
114
+
115
+ const down = [-up[0], -up[1], -up[2]];
116
+ for (let i = 0; i < roofTriangles.length; i += 3) {
117
+ addTriangle(bottom[roofTriangles[i]], bottom[roofTriangles[i + 2]], bottom[roofTriangles[i + 1]], down);
118
+ }
119
+
120
+ for (let i = 0; i < clean.length; i++) {
121
+ const next = (i + 1) % clean.length;
122
+ const b0 = bottom[i];
123
+ const b1 = bottom[next];
124
+ const t0 = top[i];
125
+ const t1 = top[next];
126
+ const wallNormal = triangleNormal(b0, b1, t1);
127
+ addTriangle(b0, b1, t1, wallNormal);
128
+ addTriangle(b0, t1, t0, wallNormal);
129
+ }
130
+
131
+ return {
132
+ positions,
133
+ normals,
134
+ indices,
135
+ bbox: polygonBbox(clean)
136
+ };
137
+ }
138
+
139
+ /**
140
+ * @param {Array<[number, number]>} ring
141
+ * @returns {Array<[number, number]>}
142
+ */
143
+ export function cleanRing(ring) {
144
+ const out = [];
145
+ for (const point of ring) {
146
+ const lon = Number(point?.[0]);
147
+ const lat = Number(point?.[1]);
148
+ if (Number.isFinite(lon) && Number.isFinite(lat)) {
149
+ out.push([lon, lat]);
150
+ }
151
+ }
152
+
153
+ if (out.length > 1) {
154
+ const first = out[0];
155
+ const last = out[out.length - 1];
156
+ if (first[0] === last[0] && first[1] === last[1]) {
157
+ out.pop();
158
+ }
159
+ }
160
+
161
+ return out;
162
+ }
163
+
164
+ /**
165
+ * @param {Array<[number, number]>} points
166
+ * @param {[number, number]} centroid
167
+ * @returns {{ flat: number[] }}
168
+ */
169
+ function projectRing(points, centroid) {
170
+ const meanLat = centroid[1] * Math.PI / 180;
171
+ const metersPerLon = Math.max(1, 111320 * Math.cos(meanLat));
172
+ const metersPerLat = 110540;
173
+ const flat = [];
174
+ for (const [lon, lat] of points) {
175
+ flat.push((lon - centroid[0]) * metersPerLon, (lat - centroid[1]) * metersPerLat);
176
+ }
177
+ return { flat };
178
+ }
179
+
180
+ /**
181
+ * @param {Array<[number, number]>} points
182
+ * @returns {[number, number]}
183
+ */
184
+ function polygonCentroid(points) {
185
+ let lon = 0;
186
+ let lat = 0;
187
+ for (const point of points) {
188
+ lon += point[0];
189
+ lat += point[1];
190
+ }
191
+ return [lon / points.length, lat / points.length];
192
+ }
193
+
194
+ /**
195
+ * @param {Array<[number, number]>} points
196
+ * @returns {[number, number, number, number]}
197
+ */
198
+ function polygonBbox(points) {
199
+ let minLon = Infinity;
200
+ let minLat = Infinity;
201
+ let maxLon = -Infinity;
202
+ let maxLat = -Infinity;
203
+ for (const [lon, lat] of points) {
204
+ minLon = Math.min(minLon, lon);
205
+ minLat = Math.min(minLat, lat);
206
+ maxLon = Math.max(maxLon, lon);
207
+ maxLat = Math.max(maxLat, lat);
208
+ }
209
+ const pad = 0.00002;
210
+ return [minLon - pad, minLat - pad, maxLon + pad, maxLat + pad];
211
+ }
212
+
213
+ /**
214
+ * @param {Array<[number, number, number, number]>} bboxes
215
+ * @returns {[number, number, number, number]}
216
+ */
217
+ function mergeBboxes(bboxes) {
218
+ let minLon = Infinity;
219
+ let minLat = Infinity;
220
+ let maxLon = -Infinity;
221
+ let maxLat = -Infinity;
222
+ for (const bbox of bboxes) {
223
+ minLon = Math.min(minLon, bbox[0]);
224
+ minLat = Math.min(minLat, bbox[1]);
225
+ maxLon = Math.max(maxLon, bbox[2]);
226
+ maxLat = Math.max(maxLat, bbox[3]);
227
+ }
228
+ return [minLon, minLat, maxLon, maxLat];
229
+ }
230
+
231
+ /**
232
+ * @param {Float32Array} positions
233
+ * @returns {{ min: [number, number, number], max: [number, number, number] }}
234
+ */
235
+ function minMaxVec3(positions) {
236
+ const min = [Infinity, Infinity, Infinity];
237
+ const max = [-Infinity, -Infinity, -Infinity];
238
+ for (let i = 0; i < positions.length; i += 3) {
239
+ min[0] = Math.min(min[0], positions[i]);
240
+ min[1] = Math.min(min[1], positions[i + 1]);
241
+ min[2] = Math.min(min[2], positions[i + 2]);
242
+ max[0] = Math.max(max[0], positions[i]);
243
+ max[1] = Math.max(max[1], positions[i + 1]);
244
+ max[2] = Math.max(max[2], positions[i + 2]);
245
+ }
246
+ return { min, max };
247
+ }
248
+
249
+ function triangleNormal(a, b, c) {
250
+ return normalize(cross(subtract(b, a), subtract(c, a)));
251
+ }
252
+
253
+ function subtract(a, b) {
254
+ return [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
255
+ }
256
+
257
+ function cross(a, b) {
258
+ return [
259
+ a[1] * b[2] - a[2] * b[1],
260
+ a[2] * b[0] - a[0] * b[2],
261
+ a[0] * b[1] - a[1] * b[0]
262
+ ];
263
+ }
264
+
265
+ function normalize(vector) {
266
+ const length = Math.hypot(vector[0], vector[1], vector[2]);
267
+ if (!Number.isFinite(length) || length === 0) {
268
+ return [0, 0, 1];
269
+ }
270
+ return [vector[0] / length, vector[1] / length, vector[2] / length];
271
+ }
272
+
273
+ export function wgs84SurfaceNormal(lonDeg, latDeg) {
274
+ const lonRad = degToRad(lonDeg);
275
+ const latRad = degToRad(latDeg);
276
+ const cosLat = Math.cos(latRad);
277
+ return [
278
+ cosLat * Math.cos(lonRad),
279
+ cosLat * Math.sin(lonRad),
280
+ Math.sin(latRad)
281
+ ];
282
+ }
283
+
284
+ export function wgs84ToEcef(lonDeg, latDeg, h) {
285
+ const lonRad = degToRad(lonDeg);
286
+ const latRad = degToRad(latDeg);
287
+ const sinLat = Math.sin(latRad);
288
+ const cosLat = Math.cos(latRad);
289
+ const sinLon = Math.sin(lonRad);
290
+ const cosLon = Math.cos(lonRad);
291
+ const n = WGS84_A / Math.sqrt(1 - WGS84_E2 * sinLat * sinLat);
292
+ return [
293
+ (n + h) * cosLat * cosLon,
294
+ (n + h) * cosLat * sinLon,
295
+ (n * (1 - WGS84_E2) + h) * sinLat
296
+ ];
297
+ }
298
+
299
+ function degToRad(value) {
300
+ return value * Math.PI / 180;
301
+ }