map-zero 0.1.0 → 0.2.1
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 +14 -0
- package/README.md +31 -2
- package/docs/cesium.md +24 -9
- package/package.json +4 -3
- package/packages/cesium/package.json +6 -3
- package/packages/cesium/src/imagery-worker.js +604 -0
- package/packages/cesium/src/imagery.js +434 -0
- package/packages/cesium/src/index.js +199 -35
- package/packages/ol/package.json +1 -1
- package/packages/ol/src/index.js +349 -16
- package/src/3dtiles/b3dm.js +18 -2
- package/src/3dtiles/clipper-surfaces.js +121 -20
- package/src/3dtiles/export.js +298 -25
- package/src/3dtiles/extrude.js +78 -23
- package/src/3dtiles/flat.js +8 -20
- package/src/3dtiles/glb.js +78 -27
- package/src/3dtiles/gpkg-features.js +4 -4
- package/src/3dtiles/precision.js +47 -0
- package/src/cli.js +100 -2
- package/src/export-pmtiles.js +17 -16
- package/src/from-bbox.js +335 -0
- package/src/gpkg-read.js +15 -2
- package/src/html.js +28 -17
- package/src/manifest.js +1 -8
- package/src/mvt.js +35 -3
- package/src/package.js +343 -0
- package/src/server.js +38 -10
- package/src/style-command.js +1 -1
- package/src/style-filters.js +2 -3
- package/styles/presets/neon-dark-3d.json +0 -90
|
@@ -7,6 +7,14 @@ import {
|
|
|
7
7
|
} from 'js-angusj-clipper';
|
|
8
8
|
|
|
9
9
|
import { wgs84SurfaceNormal, wgs84ToEcef } from './extrude.js';
|
|
10
|
+
import { localizeEcefPositions } from './precision.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Builds flat elevated cartographic surfaces from linework by offsetting lines
|
|
14
|
+
* with Clipper, dissolving joins/caps, and triangulating the resulting polygons.
|
|
15
|
+
* This path is used for roads, railways, boundaries, and aviation linework where
|
|
16
|
+
* hand-built ribbons produced visible join artifacts.
|
|
17
|
+
*/
|
|
10
18
|
|
|
11
19
|
const DEFAULT_SCALE = 100;
|
|
12
20
|
const DEFAULT_ARC_TOLERANCE_METERS = 0.25;
|
|
@@ -92,6 +100,11 @@ export async function buildClipperLineSurfaceMesh(lines, options) {
|
|
|
92
100
|
return buildSurfaceMeshFromClipperPolygons(polygons, projection, scale, options.height ?? 1);
|
|
93
101
|
}
|
|
94
102
|
|
|
103
|
+
/**
|
|
104
|
+
* Lazily load the Clipper WASM/ASM implementation once per process.
|
|
105
|
+
*
|
|
106
|
+
* @returns {Promise<any>}
|
|
107
|
+
*/
|
|
95
108
|
function getClipper() {
|
|
96
109
|
clipperPromise ??= loadNativeClipperLibInstanceAsync(
|
|
97
110
|
NativeClipperLibRequestedFormat.WasmWithAsmJsFallback
|
|
@@ -99,6 +112,12 @@ function getClipper() {
|
|
|
99
112
|
return clipperPromise;
|
|
100
113
|
}
|
|
101
114
|
|
|
115
|
+
/**
|
|
116
|
+
* Flatten a Clipper PolyTree into polygon rings, preserving first-level holes.
|
|
117
|
+
*
|
|
118
|
+
* @param {any} polyTree
|
|
119
|
+
* @returns {Array<Array<Array<{ x: number, y: number }>>>}
|
|
120
|
+
*/
|
|
102
121
|
function collectPolyTreePolygons(polyTree) {
|
|
103
122
|
const polygons = [];
|
|
104
123
|
for (const child of polyTree.childs) {
|
|
@@ -107,6 +126,12 @@ function collectPolyTreePolygons(polyTree) {
|
|
|
107
126
|
return polygons;
|
|
108
127
|
}
|
|
109
128
|
|
|
129
|
+
/**
|
|
130
|
+
* Recursively collect closed non-hole nodes and their hole contours.
|
|
131
|
+
*
|
|
132
|
+
* @param {any} node
|
|
133
|
+
* @param {Array<Array<Array<{ x: number, y: number }>>>} polygons
|
|
134
|
+
*/
|
|
110
135
|
function collectNode(node, polygons) {
|
|
111
136
|
if (node.isOpen) return;
|
|
112
137
|
if (!node.isHole) {
|
|
@@ -124,6 +149,15 @@ function collectNode(node, polygons) {
|
|
|
124
149
|
}
|
|
125
150
|
}
|
|
126
151
|
|
|
152
|
+
/**
|
|
153
|
+
* Convert integer Clipper polygon rings back into localized ECEF mesh data.
|
|
154
|
+
*
|
|
155
|
+
* @param {Array<Array<Array<{ x: number, y: number }>>>} polygons
|
|
156
|
+
* @param {{ origin: [number, number], metersPerLon: number, metersPerLat: number }} projection
|
|
157
|
+
* @param {number} scale
|
|
158
|
+
* @param {number} height
|
|
159
|
+
* @returns {import('./extrude.js').ExtrudedMesh | null}
|
|
160
|
+
*/
|
|
127
161
|
function buildSurfaceMeshFromClipperPolygons(polygons, projection, scale, height) {
|
|
128
162
|
const positions = [];
|
|
129
163
|
const normals = [];
|
|
@@ -170,24 +204,32 @@ function buildSurfaceMeshFromClipperPolygons(polygons, projection, scale, height
|
|
|
170
204
|
}
|
|
171
205
|
|
|
172
206
|
if (positions.length === 0 || featureCount === 0) return null;
|
|
173
|
-
const
|
|
207
|
+
const localized = localizeEcefPositions(positions);
|
|
174
208
|
const normalArray = new Float32Array(normals);
|
|
175
|
-
const indexArray =
|
|
209
|
+
const indexArray = localized.positions.length / 3 > 65535
|
|
176
210
|
? new Uint32Array(indices)
|
|
177
211
|
: new Uint16Array(indices);
|
|
178
|
-
const bounds = minMaxVec3(positionArray);
|
|
179
212
|
return {
|
|
180
|
-
positions:
|
|
213
|
+
positions: localized.positions,
|
|
181
214
|
normals: normalArray,
|
|
182
215
|
indices: indexArray,
|
|
183
|
-
min:
|
|
184
|
-
max:
|
|
216
|
+
min: localized.min,
|
|
217
|
+
max: localized.max,
|
|
218
|
+
rtcCenter: localized.rtcCenter,
|
|
185
219
|
bbox: mergeBboxes(bboxes),
|
|
186
220
|
maxHeight: height,
|
|
187
221
|
featureCount
|
|
188
222
|
};
|
|
189
223
|
}
|
|
190
224
|
|
|
225
|
+
/**
|
|
226
|
+
* Convert a Clipper path to local meter coordinates and remove duplicate close
|
|
227
|
+
* points before triangulation.
|
|
228
|
+
*
|
|
229
|
+
* @param {Array<{ x: number, y: number }>} path
|
|
230
|
+
* @param {number} scale
|
|
231
|
+
* @returns {Array<[number, number]>}
|
|
232
|
+
*/
|
|
191
233
|
function cleanPathRing(path, scale) {
|
|
192
234
|
const ring = [];
|
|
193
235
|
for (const point of path) {
|
|
@@ -203,12 +245,25 @@ function cleanPathRing(path, scale) {
|
|
|
203
245
|
return ring;
|
|
204
246
|
}
|
|
205
247
|
|
|
248
|
+
/**
|
|
249
|
+
* Normalize a lon/lat line to finite coordinate pairs.
|
|
250
|
+
*
|
|
251
|
+
* @param {Array<[number, number]>} line
|
|
252
|
+
* @returns {Array<[number, number]>}
|
|
253
|
+
*/
|
|
206
254
|
function cleanLine(line) {
|
|
207
255
|
return line
|
|
208
256
|
.map((point) => [Number(point?.[0]), Number(point?.[1])])
|
|
209
257
|
.filter(([lon, lat]) => Number.isFinite(lon) && Number.isFinite(lat));
|
|
210
258
|
}
|
|
211
259
|
|
|
260
|
+
/**
|
|
261
|
+
* Drop adjacent projected points that are closer than minDistance meters.
|
|
262
|
+
*
|
|
263
|
+
* @param {Array<[number, number]>} points
|
|
264
|
+
* @param {number} minDistance
|
|
265
|
+
* @returns {Array<[number, number]>}
|
|
266
|
+
*/
|
|
212
267
|
function removeRedundantLocalPoints(points, minDistance) {
|
|
213
268
|
const out = [];
|
|
214
269
|
for (const point of points) {
|
|
@@ -220,6 +275,12 @@ function removeRedundantLocalPoints(points, minDistance) {
|
|
|
220
275
|
return out;
|
|
221
276
|
}
|
|
222
277
|
|
|
278
|
+
/**
|
|
279
|
+
* Create a local lon/lat <-> meter projection centered on the data extent.
|
|
280
|
+
*
|
|
281
|
+
* @param {Array<[number, number]>} points
|
|
282
|
+
* @returns {{ origin: [number, number], metersPerLon: number, metersPerLat: number }}
|
|
283
|
+
*/
|
|
223
284
|
function createLocalProjection(points) {
|
|
224
285
|
const origin = polygonCentroid(points);
|
|
225
286
|
const meanLat = origin[1] * Math.PI / 180;
|
|
@@ -230,6 +291,13 @@ function createLocalProjection(points) {
|
|
|
230
291
|
};
|
|
231
292
|
}
|
|
232
293
|
|
|
294
|
+
/**
|
|
295
|
+
* Project one lon/lat point into local meters.
|
|
296
|
+
*
|
|
297
|
+
* @param {[number, number]} point
|
|
298
|
+
* @param {{ origin: [number, number], metersPerLon: number, metersPerLat: number }} projection
|
|
299
|
+
* @returns {[number, number]}
|
|
300
|
+
*/
|
|
233
301
|
function projectPoint(point, projection) {
|
|
234
302
|
return [
|
|
235
303
|
(point[0] - projection.origin[0]) * projection.metersPerLon,
|
|
@@ -237,6 +305,13 @@ function projectPoint(point, projection) {
|
|
|
237
305
|
];
|
|
238
306
|
}
|
|
239
307
|
|
|
308
|
+
/**
|
|
309
|
+
* Convert local meters back to lon/lat.
|
|
310
|
+
*
|
|
311
|
+
* @param {[number, number]} point
|
|
312
|
+
* @param {{ origin: [number, number], metersPerLon: number, metersPerLat: number }} projection
|
|
313
|
+
* @returns {[number, number]}
|
|
314
|
+
*/
|
|
240
315
|
function unprojectPoint(point, projection) {
|
|
241
316
|
return [
|
|
242
317
|
projection.origin[0] + point[0] / projection.metersPerLon,
|
|
@@ -244,6 +319,13 @@ function unprojectPoint(point, projection) {
|
|
|
244
319
|
];
|
|
245
320
|
}
|
|
246
321
|
|
|
322
|
+
/**
|
|
323
|
+
* Convert projected meter coordinates to Clipper integer coordinates.
|
|
324
|
+
*
|
|
325
|
+
* @param {[number, number]} point
|
|
326
|
+
* @param {number} scale
|
|
327
|
+
* @returns {{ x: number, y: number }}
|
|
328
|
+
*/
|
|
247
329
|
function toIntPoint(point, scale) {
|
|
248
330
|
return {
|
|
249
331
|
x: Math.round(point[0] * scale),
|
|
@@ -251,6 +333,12 @@ function toIntPoint(point, scale) {
|
|
|
251
333
|
};
|
|
252
334
|
}
|
|
253
335
|
|
|
336
|
+
/**
|
|
337
|
+
* Return a simple arithmetic lon/lat centroid for small local geometries.
|
|
338
|
+
*
|
|
339
|
+
* @param {Array<[number, number]>} points
|
|
340
|
+
* @returns {[number, number]}
|
|
341
|
+
*/
|
|
254
342
|
function polygonCentroid(points) {
|
|
255
343
|
let lon = 0;
|
|
256
344
|
let lat = 0;
|
|
@@ -261,6 +349,12 @@ function polygonCentroid(points) {
|
|
|
261
349
|
return [lon / points.length, lat / points.length];
|
|
262
350
|
}
|
|
263
351
|
|
|
352
|
+
/**
|
|
353
|
+
* Compute a lon/lat bbox for a set of line or polygon vertices.
|
|
354
|
+
*
|
|
355
|
+
* @param {Array<[number, number]>} points
|
|
356
|
+
* @returns {[number, number, number, number]}
|
|
357
|
+
*/
|
|
264
358
|
function lineBbox(points) {
|
|
265
359
|
let minLon = Infinity;
|
|
266
360
|
let minLat = Infinity;
|
|
@@ -275,6 +369,12 @@ function lineBbox(points) {
|
|
|
275
369
|
return [minLon, minLat, maxLon, maxLat];
|
|
276
370
|
}
|
|
277
371
|
|
|
372
|
+
/**
|
|
373
|
+
* Merge multiple lon/lat bounding boxes.
|
|
374
|
+
*
|
|
375
|
+
* @param {Array<[number, number, number, number]>} bboxes
|
|
376
|
+
* @returns {[number, number, number, number]}
|
|
377
|
+
*/
|
|
278
378
|
function mergeBboxes(bboxes) {
|
|
279
379
|
let minLon = Infinity;
|
|
280
380
|
let minLat = Infinity;
|
|
@@ -289,28 +389,29 @@ function mergeBboxes(bboxes) {
|
|
|
289
389
|
return [minLon, minLat, maxLon, maxLat];
|
|
290
390
|
}
|
|
291
391
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
min[1] = Math.min(min[1], positions[i + 1]);
|
|
298
|
-
min[2] = Math.min(min[2], positions[i + 2]);
|
|
299
|
-
max[0] = Math.max(max[0], positions[i]);
|
|
300
|
-
max[1] = Math.max(max[1], positions[i + 1]);
|
|
301
|
-
max[2] = Math.max(max[2], positions[i + 2]);
|
|
302
|
-
}
|
|
303
|
-
return { min, max };
|
|
304
|
-
}
|
|
305
|
-
|
|
392
|
+
/**
|
|
393
|
+
* @param {[number, number]} a
|
|
394
|
+
* @param {[number, number]} b
|
|
395
|
+
* @returns {boolean}
|
|
396
|
+
*/
|
|
306
397
|
function samePoint(a, b) {
|
|
307
398
|
return Math.abs(a[0] - b[0]) < 1e-9 && Math.abs(a[1] - b[1]) < 1e-9;
|
|
308
399
|
}
|
|
309
400
|
|
|
401
|
+
/**
|
|
402
|
+
* @param {[number, number]} a
|
|
403
|
+
* @param {[number, number]} b
|
|
404
|
+
* @returns {number}
|
|
405
|
+
*/
|
|
310
406
|
function distance2(a, b) {
|
|
311
407
|
return Math.hypot(a[0] - b[0], a[1] - b[1]);
|
|
312
408
|
}
|
|
313
409
|
|
|
410
|
+
/**
|
|
411
|
+
* @param {unknown} value
|
|
412
|
+
* @param {number} fallback
|
|
413
|
+
* @returns {number}
|
|
414
|
+
*/
|
|
314
415
|
function positiveNumber(value, fallback) {
|
|
315
416
|
const number = Number(value);
|
|
316
417
|
return Number.isFinite(number) && number > 0 ? number : fallback;
|