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
|
@@ -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
|
+
}
|