@nika-js/onlymap 0.6.1 → 0.6.4
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 +106 -1
- package/README.md +28 -15
- package/dist/{LercDecode.es-CJnypw8j.js → LercDecode.es-D5in29tf.js} +1 -1
- package/dist/{basemap-DrQ0-eyR.js → basemap-C0pFT3AO.js} +13 -3
- package/dist/basemap.d.ts +10 -1
- package/dist/clip-box-controller.d.ts +94 -0
- package/dist/clip-box.d.ts +109 -0
- package/dist/data-layer.d.ts +34 -4
- package/dist/draw-controller.d.ts +75 -1
- package/dist/draw.d.ts +22 -4
- package/dist/elements/om-map.d.ts +20 -0
- package/dist/elements/om-overlay.d.ts +12 -0
- package/dist/geodesy.d.ts +32 -3
- package/dist/{geoparquet-Dix4lTNy.js → geoparquet-By98JVB0.js} +1 -1
- package/dist/html-data.d.ts +2 -2
- package/dist/{index-BQMjW5w0.js → index-Bx9GFkrn.js} +1 -1
- package/dist/{index-CXPaeisL.js → index-CqC4sW_k.js} +1 -1
- package/dist/{index-1UgNlfGR.js → index-Cxo9mCw_.js} +30947 -27785
- package/dist/{index-B_1PPJgC.js → index-olfncHIq.js} +1 -1
- package/dist/{index-CAuT5j9Y.js → index-tnlYDALL.js} +2 -2
- package/dist/index.d.ts +8 -3
- package/dist/ir-snapshot.d.ts +3 -1
- package/dist/layers/bim-layer.d.ts +30 -7
- package/dist/layers/feature-mesh-layer.d.ts +1 -1
- package/dist/layers/popup-layer.d.ts +13 -0
- package/dist/legend-spec.d.ts +1 -1
- package/dist/{lerc-CbTjQ7uI.js → lerc-gKDDtc69.js} +2 -2
- package/dist/license.d.ts +25 -4
- package/dist/measure-controller.d.ts +482 -4
- package/dist/onlymap.standalone.js +53976 -50804
- package/dist/onlymapjs.js +42 -40
- package/dist/parse-manifest.d.ts +3 -0
- package/dist/programmatic.d.ts +34 -5
- package/dist/{raster-0b0nSHUh.js → raster-Cl5m3KsC.js} +2 -2
- package/dist/{raster-pipeline-D8siq7-4.js → raster-pipeline-hJGxIwYx.js} +1 -1
- package/dist/react/om-layer.d.ts +6 -1
- package/dist/react.js +167 -160
- package/dist/region-export-controller.d.ts +37 -0
- package/dist/region-export.d.ts +56 -0
- package/dist/runtime-core.d.ts +150 -0
- package/dist/selection.d.ts +11 -1
- package/dist/site-placement.d.ts +22 -0
- package/dist/snapping.d.ts +88 -0
- package/dist/terrain-heightfield.d.ts +53 -0
- package/dist/terrain-sample.d.ts +30 -0
- package/dist/terrain.d.ts +6 -1
- package/dist/units.d.ts +27 -0
- package/dist/version.d.ts +1 -1
- package/dist/volumetrics-run.d.ts +13 -0
- package/dist/volumetrics-worker.d.ts +1 -0
- package/dist/volumetrics.d.ts +201 -0
- package/dist/{zarr-DCYro_Vs.js → zarr-hRDavRGV.js} +2 -2
- package/docs/3d-assets.md +49 -1
- package/docs/live-data.md +26 -1
- package/docs/react.md +9 -1
- package/docs/testing.md +2 -0
- package/llms.txt +10 -5
- package/onlymapjs.html-data.json +161 -2
- package/package.json +4 -2
- package/skills/onlymapjs/SKILL.md +11 -1
- package/skills/onlymapjs/references/react.md +2 -2
- package/skills/onlymapjs/references/syntax.md +20 -8
- package/skills/onlymapjs/references/testing.md +6 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Region 3D export (spec: issue #34 §"Technical Implementation Steps" —
|
|
3
|
+
* region export; ported from a sibling project's proven implementation
|
|
4
|
+
* rather than rebuilt from scratch, per direct instruction). Draw a 2D
|
|
5
|
+
* footprint over loaded 3D Tiles content (BIM/`BIMLayer`, `Tile3DLayer`)
|
|
6
|
+
* and export exactly the triangles inside it as a portable GLB — or a
|
|
7
|
+
* b3dm-wrapped GLB for pipelines (Cesium, 3D Tiles tooling) that expect
|
|
8
|
+
* that container.
|
|
9
|
+
*
|
|
10
|
+
* Deliberately simpler than the source implementation in one respect: tile
|
|
11
|
+
* selection is a plain bbox prefilter (`tileGeoBounds` vs. the ring's own
|
|
12
|
+
* bbox) rather than the source's mesh-sampling/loose-footprint heuristics —
|
|
13
|
+
* those exist there to avoid walking every triangle of city-scale national
|
|
14
|
+
* tilesets before an exact clip; at this library's current single-tileset
|
|
15
|
+
* (BIM model) scale, bbox-prefilter-then-exact-clip is both simpler and
|
|
16
|
+
* already correct (a tile outside the ring exact-clips to zero triangles
|
|
17
|
+
* regardless), and the same geometric pipeline this module already needs
|
|
18
|
+
* is reused straightforwardly if the heuristic layer becomes worth adding.
|
|
19
|
+
*
|
|
20
|
+
* No textures: BIM/IFC materials are flat colors, not textured meshes, so
|
|
21
|
+
* the UV/image plumbing the source implementation carries is dead weight
|
|
22
|
+
* here — dropped rather than kept for a case this library doesn't have.
|
|
23
|
+
* Each triangle's own source color IS carried through, though — baked as
|
|
24
|
+
* per-vertex `COLOR_0` (see `resolvePrimitiveColor`/`buildGlb`), not left
|
|
25
|
+
* as an undifferentiated flat gray.
|
|
26
|
+
*
|
|
27
|
+
* PURE geometry/binary-writing core (this file) + a thin DOM-touching
|
|
28
|
+
* driver (`region-export-run.ts`) that resolves the live tileset registry
|
|
29
|
+
* and triggers the browser download — same split as terrain-sample.ts /
|
|
30
|
+
* terrain-heightfield.ts.
|
|
31
|
+
*/
|
|
32
|
+
export type LngLatRing = Array<[number, number]>;
|
|
33
|
+
/** Closed (first === last) + CCW — committed draw rings arrive closed, previews open; both normalize the same way. */
|
|
34
|
+
export declare function normalizeRing(ring: LngLatRing): LngLatRing;
|
|
35
|
+
export interface B3dmBatchTable {
|
|
36
|
+
/** batchLength: number of features encoded in the glb's `_BATCHID`; each property array below must have this length. */
|
|
37
|
+
batchLength: number;
|
|
38
|
+
/** Plain-JSON per-feature properties — entry i is feature i's value. Omit for a structurally-valid b3dm with no property data. */
|
|
39
|
+
properties?: Record<string, unknown[]>;
|
|
40
|
+
}
|
|
41
|
+
export interface RegionExportResult {
|
|
42
|
+
bytes: Uint8Array;
|
|
43
|
+
triangleCount: number;
|
|
44
|
+
format: "glb" | "b3dm";
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Clip every loaded tile's triangles across ALL given tilesets against
|
|
48
|
+
* `ring`, re-frame the clipped result to a local ENU frame at the ring's
|
|
49
|
+
* own centroid (portable — most viewers can't display ECEF-scale
|
|
50
|
+
* coordinates), and pack it as GLB or b3dm. `null` when the ring or the
|
|
51
|
+
* clip produces nothing (an empty region, or a ring drawn where nothing is
|
|
52
|
+
* loaded) — the caller reports that distinctly from a thrown error.
|
|
53
|
+
*/
|
|
54
|
+
export declare function computeRegionExport(ring: LngLatRing, tilesets: Map<string, unknown>, options?: {
|
|
55
|
+
format?: "glb" | "b3dm";
|
|
56
|
+
}): RegionExportResult | null;
|
package/dist/runtime-core.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import type { PickingInfo } from "@deck.gl/core";
|
|
1
2
|
import { type LightingIR } from "./scene-lighting";
|
|
2
3
|
import { type TerrainIR } from "./terrain";
|
|
4
|
+
import { type ClipBoxIR } from "./clip-box";
|
|
5
|
+
import { type SnapAgent, type SnapConfig } from "./snapping";
|
|
3
6
|
import type { ViewOrigin } from "./external-store";
|
|
4
7
|
import type { LayerIR } from "./ir";
|
|
5
8
|
import { type Selection } from "./selection";
|
|
@@ -60,6 +63,18 @@ export interface FeatureTableEntry {
|
|
|
60
63
|
* feature IDs are tile-local. Returns the (possibly new) entry.
|
|
61
64
|
*/
|
|
62
65
|
export declare function accumulateFeatureRows(tables: Map<string, FeatureTableEntry>, layerId: string, sourceKey: string, tileKey: string, rows: Record<string, unknown>[]): FeatureTableEntry;
|
|
66
|
+
/**
|
|
67
|
+
* The subset of deck.gl's MjolnirGestureEvent a gizmo drag-claim needs (spec:
|
|
68
|
+
* "Cut/fill volume measurement"). `stopPropagation()` sets the underlying
|
|
69
|
+
* event's `handled` flag, which `Controller.isPointInBounds` checks before
|
|
70
|
+
* starting a camera pan — calling it from `onGizmoDragStart` when the gizmo
|
|
71
|
+
* is the picked layer is what lets a LEFT-button drag on the gizmo win over
|
|
72
|
+
* the map's own drag-to-pan (deck dispatches its root onDragStart before the
|
|
73
|
+
* controller's own pan handling for the same gesture, so this is in time).
|
|
74
|
+
*/
|
|
75
|
+
export interface GizmoDragEvent {
|
|
76
|
+
stopPropagation(): void;
|
|
77
|
+
}
|
|
63
78
|
export interface RuntimeCoreCallbacks {
|
|
64
79
|
/**
|
|
65
80
|
* Fired whenever the view state changes (pan/zoom/pitch/bearing) — a
|
|
@@ -86,6 +101,17 @@ export interface RuntimeCoreCallbacks {
|
|
|
86
101
|
* re-render on every drag-move.
|
|
87
102
|
*/
|
|
88
103
|
onDragPick?: (selection: Selection) => void;
|
|
104
|
+
/**
|
|
105
|
+
* Raw deck.gl drag lifecycle (spec: "Cut/fill volume measurement"),
|
|
106
|
+
* forwarded verbatim alongside `onDragPick` — for a controller that owns a
|
|
107
|
+
* pickable internal layer (the volume tool's height gizmo) and needs the
|
|
108
|
+
* real `x`/`y`/`viewport`, not a resolved `Selection`. `onDragPick` stays
|
|
109
|
+
* the mechanism for declarative `on="drag"` behaviors; this is the
|
|
110
|
+
* mechanism for a controller driving its own layer via patchAnimatedProps.
|
|
111
|
+
*/
|
|
112
|
+
onGizmoDragStart?: (info: PickingInfo, event: GizmoDragEvent) => void;
|
|
113
|
+
onGizmoDrag?: (info: PickingInfo) => void;
|
|
114
|
+
onGizmoDragEnd?: (info: PickingInfo) => void;
|
|
89
115
|
/**
|
|
90
116
|
* Every click/hover's map coordinate (spec: "Manual Drawing"), fired
|
|
91
117
|
* ALONGSIDE onSelectionChange but INCLUDING empty-map events — which
|
|
@@ -94,6 +120,23 @@ export interface RuntimeCoreCallbacks {
|
|
|
94
120
|
* deck reports none (e.g. off-globe).
|
|
95
121
|
*/
|
|
96
122
|
onMapPoint?: (coordinate: [number, number] | null, kind: "click" | "hover") => void;
|
|
123
|
+
/**
|
|
124
|
+
* XY snapping (spec: issue #34 Part A) — fires ALONGSIDE onMapPoint on
|
|
125
|
+
* every click/hover, `null` whenever that point ISN'T a snap (no config,
|
|
126
|
+
* no candidate within tolerance, an opted-out/unsupported layer) — the
|
|
127
|
+
* snap-tip UI's entire "show only while actually snapped" contract reads
|
|
128
|
+
* off this being null vs. set, not off onMapPoint's own coordinate.
|
|
129
|
+
* Carries `position` itself (not just agent/layer) so a consumer never
|
|
130
|
+
* has to correlate this against a SEPARATE onMapPoint firing in the same
|
|
131
|
+
* tick to know where to anchor a tip — both already come from the one
|
|
132
|
+
* `resolveMapPoint` call.
|
|
133
|
+
*/
|
|
134
|
+
onSnapPoint?: (result: {
|
|
135
|
+
position: [number, number];
|
|
136
|
+
agent: SnapAgent;
|
|
137
|
+
layerId: string;
|
|
138
|
+
elevation?: number;
|
|
139
|
+
} | null) => void;
|
|
97
140
|
/**
|
|
98
141
|
* A `pick-features` layer decoded its EXT_structural_metadata property
|
|
99
142
|
* table — the whole table, indexed by feature ID. Fires once per layer, on
|
|
@@ -177,6 +220,10 @@ export declare class RuntimeCore {
|
|
|
177
220
|
private mode;
|
|
178
221
|
/** Layers reconciled while the basemap chunk is still loading — applied on adapter arrival. */
|
|
179
222
|
private pendingLayers?;
|
|
223
|
+
/** setDrawCapture's last value — composed with dragPanSuppressed by standaloneControllerOverrides. */
|
|
224
|
+
private drawCaptureActive;
|
|
225
|
+
/** setDragPan's last value, inverted — see standaloneControllerOverrides. */
|
|
226
|
+
private dragPanSuppressed;
|
|
180
227
|
/** Retained descriptors — what the per-frame channel re-applies against. */
|
|
181
228
|
private lastIRs;
|
|
182
229
|
/** layerId → effect-driven plain-prop patches (the per-frame channel). */
|
|
@@ -210,6 +257,29 @@ export declare class RuntimeCore {
|
|
|
210
257
|
*/
|
|
211
258
|
private terrain;
|
|
212
259
|
private terrainGeneration;
|
|
260
|
+
/**
|
|
261
|
+
* Clip box (spec: issue #34 §"Cutting / Clipping") — the retained box IR.
|
|
262
|
+
* Non-null patches every opted-in layer with `ClipBoxExtension` in
|
|
263
|
+
* buildLayers (see `applyClipBox`'s own doc comment for the default-
|
|
264
|
+
* applied, `clip="off"`-to-exclude posture). `clipBoxGeneration` bumps on
|
|
265
|
+
* every active-state flip, same reason and same fix as terrain's own
|
|
266
|
+
* generation counter: extension sets must be BIRTH-stable (verified
|
|
267
|
+
* empirically — a box present from the first render works, toggling one
|
|
268
|
+
* onto an already-mounted DataFilterExtension-carrying layer silently
|
|
269
|
+
* blanks it), so patched layers get `#c<generation>`-based deck ids.
|
|
270
|
+
*/
|
|
271
|
+
private clipBox;
|
|
272
|
+
private clipBoxGeneration;
|
|
273
|
+
/** XY snapping (spec: issue #34 Part A) — `null` is the free/common case (no pickingRadius change, resolveMapPoint short-circuits to the plain pick). */
|
|
274
|
+
private snapConfig;
|
|
275
|
+
/** See resolveMapPoint's memo comment — the last picked object's derived snap geometry. */
|
|
276
|
+
private snapGeometryMemo;
|
|
277
|
+
/** See pickForSnap's memo comment — the non-terrain layer-id list, keyed by the deck layer array's own identity. */
|
|
278
|
+
private snapLayerIdsMemo;
|
|
279
|
+
/** Spacebar-held suppression (spec: issue #34 Part A) — deliberately leaves `pickingRadius`/`snapConfig` untouched, only gates `resolveMapPoint`'s refinement step, since the underlying pick tolerance isn't what a user means by "hold to disable snapping momentarily." */
|
|
280
|
+
private snapSuppressed;
|
|
281
|
+
/** Per-BIMLayer lonLat/heading/scale, captured off `onGeoreference` (see `BimGeoreferenceInfo`) — `resolveMapPoint`'s only way to convert a picked EdgeRow's raw local vertices back to real `[lng, lat]` for snapping, since that georeference otherwise lives entirely inside BIMLayer's own async load state. */
|
|
282
|
+
private bimGeoreference;
|
|
213
283
|
/** Rendered deck layer id → authored manifest/controller id (terrain uses fresh renderer ids). */
|
|
214
284
|
private renderedLayerIds;
|
|
215
285
|
/** The decoded property table for a pick-features layer, or undefined before its first tile. */
|
|
@@ -300,6 +370,72 @@ export declare class RuntimeCore {
|
|
|
300
370
|
*/
|
|
301
371
|
setTerrain(ir: TerrainIR | null): void;
|
|
302
372
|
getTerrainInternal(): TerrainIR | null;
|
|
373
|
+
/** Clip box on/off/extent change (spec: issue #34) — no basemap-replacement dance like terrain's (a clip box never changes render mode), just a straight re-apply. Bumps the generation on an active-state flip (fresh deck ids — see the `clipBoxGeneration` field doc). */
|
|
374
|
+
setClipBox(ir: ClipBoxIR | null): void;
|
|
375
|
+
getClipBoxInternal(): ClipBoxIR | null;
|
|
376
|
+
/**
|
|
377
|
+
* XY snapping on/off + tolerance change (spec: issue #34 Part A). Unlike
|
|
378
|
+
* clip box/terrain, this never touches the layer list — only deck's
|
|
379
|
+
* `pickingRadius` (0 by default and never otherwise set in this
|
|
380
|
+
* codebase; without it a cursor has to land EXACTLY on rendered pixels
|
|
381
|
+
* to pick anything, which defeats snapping's own point of "near enough
|
|
382
|
+
* counts") and `resolveMapPoint`'s own read of `this.snapConfig` on the
|
|
383
|
+
* next pick — no `applyLayers()` needed.
|
|
384
|
+
*/
|
|
385
|
+
/** Spacebar held/released (spec: issue #34 Part A) — no-op when snapping isn't configured at all. */
|
|
386
|
+
setSnapSuppressed(suppressed: boolean): void;
|
|
387
|
+
/**
|
|
388
|
+
* Whether a snap resolver is actually configured — the ONLY condition under
|
|
389
|
+
* which om-map's window-level keydown listener may `preventDefault()` the
|
|
390
|
+
* spacebar. Without this gate, every page embedding an `<om-map>` lost
|
|
391
|
+
* space-to-scroll and space-to-activate-a-focused-button, snapping
|
|
392
|
+
* configured or not (`setSnapSuppressed` no-ops in that case, but
|
|
393
|
+
* `preventDefault` does not).
|
|
394
|
+
*/
|
|
395
|
+
hasSnapConfig(): boolean;
|
|
396
|
+
setSnapConfig(config: SnapConfig | null): void;
|
|
397
|
+
/**
|
|
398
|
+
* `coordOf` plus snap refinement — the two-stage resolver (see
|
|
399
|
+
* src/snapping.ts's own header comment): a pick (stage 1) tells us which
|
|
400
|
+
* feature + viewport are under the cursor; `resolveSnap` (stage 2)
|
|
401
|
+
* refines that to the nearest vertex/edge/midpoint. Falls back to the
|
|
402
|
+
* plain pick on no config, no layer/viewport (an empty-map pick), a
|
|
403
|
+
* `snap="off"` layer, or no candidate within tolerance — `resolveSnap`'s
|
|
404
|
+
* own `null` covers the last case AND "geometry shape this module
|
|
405
|
+
* doesn't understand" (e.g. a raw meter-offsets PathLayer row)
|
|
406
|
+
* identically, which is the right behavior either way: nothing to add,
|
|
407
|
+
* use the raw pick.
|
|
408
|
+
*
|
|
409
|
+
* Stage 1 does NOT simply reuse `info` (deck's own top-level pick that
|
|
410
|
+
* already drove `onMapPoint`/`onSelectionChange`) whenever terrain is
|
|
411
|
+
* active — confirmed live, terrain wins deck's pick-buffer resolution
|
|
412
|
+
* against ANY co-located pickable content, vector or BIM mesh alike,
|
|
413
|
+
* even where that content visibly, correctly renders on top of it (the
|
|
414
|
+
* SAME finding already documented on this session's z-tooltip work, one
|
|
415
|
+
* layer up: there it was worked around with an unscoped hover behavior;
|
|
416
|
+
* here, where snapping specifically needs to know WHICH non-terrain
|
|
417
|
+
* feature is under the cursor, that workaround doesn't apply). Instead,
|
|
418
|
+
* re-picks explicitly via `deck.pickObject`, `layerIds`-restricted to
|
|
419
|
+
* everything EXCEPT the terrain layer — deliberately narrow (standalone
|
|
420
|
+
* mode only, terrain's own required mode) rather than a general-purpose
|
|
421
|
+
* picking API this codebase otherwise avoids.
|
|
422
|
+
*/
|
|
423
|
+
private resolveMapPoint;
|
|
424
|
+
/**
|
|
425
|
+
* See `resolveMapPoint`'s own doc comment for why this re-picks rather than
|
|
426
|
+
* reusing deck's own top-level `info`. `radius` mirrors the configured
|
|
427
|
+
* tolerance — a vertex sitting outside the tolerance couldn't win anyway,
|
|
428
|
+
* and this keeps the explicit re-pick's own search window consistent with
|
|
429
|
+
* the resolver's.
|
|
430
|
+
*
|
|
431
|
+
* The `layerIds` list is memoized against the layer ARRAY's identity: this
|
|
432
|
+
* runs on every pointer move while snapping is on, and deck only hands out
|
|
433
|
+
* a new array when the layer set actually changes, so the filter+map runs
|
|
434
|
+
* per layer-set change instead of per mousemove. (The `pickObject` call
|
|
435
|
+
* itself is a synchronous GPU readback and remains the real cost here —
|
|
436
|
+
* this just stops adding avoidable per-move allocation on top of it.)
|
|
437
|
+
*/
|
|
438
|
+
private pickForSnap;
|
|
303
439
|
private drainSnapshotCaptures;
|
|
304
440
|
/** Settle queued snapshot promises when the renderer they were waiting on goes away (mode flip / destroy) — never leave a caller hanging. */
|
|
305
441
|
private cancelSnapshotCaptures;
|
|
@@ -377,6 +513,20 @@ export declare class RuntimeCore {
|
|
|
377
513
|
* fight drag-pan, so pan stays live.
|
|
378
514
|
*/
|
|
379
515
|
setDrawCapture(active: boolean): void;
|
|
516
|
+
/**
|
|
517
|
+
* Suspend/restore drag-to-pan while a gizmo is grabbed — otherwise a drag
|
|
518
|
+
* on the gizmo also pans the camera underneath it. Originally standalone-
|
|
519
|
+
* only (the volume tool's own height gizmo requires terrain, which always
|
|
520
|
+
* forces standalone mode — see setTerrain — so it never hit basemap mode
|
|
521
|
+
* in practice); the clip-box face-handle gizmo (issue #34) has no such
|
|
522
|
+
* precondition and runs fine with a plain basemap, which surfaced the gap
|
|
523
|
+
* for real (reported: dragging a handle panned the map underneath it and
|
|
524
|
+
* the two fighting over every mousemove read as the whole page hanging).
|
|
525
|
+
* `basemap.setInteractive` is the SAME mechanism setDrawCapture already
|
|
526
|
+
* uses for doubleClickZoom, just a different MapLibre gesture handler.
|
|
527
|
+
*/
|
|
528
|
+
setDragPan(active: boolean): void;
|
|
529
|
+
private standaloneControllerOverrides;
|
|
380
530
|
/**
|
|
381
531
|
* Used by the built-in `zoom-controls` widget's emitted zoom-in/zoom-out
|
|
382
532
|
* intents. Programmatic, so — unlike a user drag/scroll — standalone mode
|
package/dist/selection.d.ts
CHANGED
|
@@ -14,7 +14,17 @@ export interface Selection {
|
|
|
14
14
|
* onSelectionChange), so downstream consumers still always see an object.
|
|
15
15
|
*/
|
|
16
16
|
index: number;
|
|
17
|
-
|
|
17
|
+
/**
|
|
18
|
+
* `[lng, lat]`, or `[lng, lat, z]` when deck actually ran the depth-pick
|
|
19
|
+
* pass for this pick (some pickable layer in the scene has `pickable:
|
|
20
|
+
* '3d'` — see terrain.ts and BIMLayer's own hover picking). Without that
|
|
21
|
+
* pass deck's `info.coordinate` is the ray∩z=0-plane intersection, not a
|
|
22
|
+
* real surface point, so a 2-length coordinate here is not "z=0 at this
|
|
23
|
+
* point" — it's "no z was ever computed," and callers (the hover-tooltip
|
|
24
|
+
* z field, this session's own concrete use) must treat it as absent, not
|
|
25
|
+
* zero.
|
|
26
|
+
*/
|
|
27
|
+
coordinate: [number, number] | [number, number, number] | null;
|
|
18
28
|
pixel: [number, number];
|
|
19
29
|
type: "hover" | "click";
|
|
20
30
|
/**
|
package/dist/site-placement.d.ts
CHANGED
|
@@ -68,6 +68,28 @@ export declare function enuMatrix(lonDeg: number, latDeg: number, height?: numbe
|
|
|
68
68
|
* function rather than an inline `rotateZ`.
|
|
69
69
|
*/
|
|
70
70
|
export declare function localPlacementMatrix(p: SitePlacement): Matrix4;
|
|
71
|
+
/**
|
|
72
|
+
* Companion to `localPlacementMatrix` + `coordinateOrigin` (the outline
|
|
73
|
+
* overlay's own placement, see this file's header comment) — converts ONE
|
|
74
|
+
* RAW local vertex (an `EdgeRow.path` point, in the model's own pre-
|
|
75
|
+
* transform local frame — the exact same value `getPath` hands the
|
|
76
|
+
* PathLayer) to real `[lng, lat]`, the SAME two steps deck.gl itself
|
|
77
|
+
* applies to render that vertex on screen: `localPlacementMatrix`'s
|
|
78
|
+
* `scale * Rz(-heading)` first, THEN interpret the result as an east/north
|
|
79
|
+
* metre offset from `coordinateOrigin`. Needed for XY snapping (spec:
|
|
80
|
+
* issue #34 Part A) against the BIM edges overlay specifically: its picked
|
|
81
|
+
* rows carry this raw local form, not GeoJSON, so `snapping.ts`'s
|
|
82
|
+
* resolver — which only understands `[lng, lat]` — cannot search them
|
|
83
|
+
* directly without this.
|
|
84
|
+
*
|
|
85
|
+
* The metres-to-degrees step is the standard small-area linear
|
|
86
|
+
* approximation (matches `getDistanceScales` inside
|
|
87
|
+
* `@math.gl/web-mercator`, which is what ACTUALLY renders this exact
|
|
88
|
+
* vertex on screen today — deliberately consistent with that real
|
|
89
|
+
* projection rather than a more "correct" ellipsoidal model that would
|
|
90
|
+
* silently disagree with it).
|
|
91
|
+
*/
|
|
92
|
+
export declare function localOffsetToLngLat(local: readonly [number, number], origin: readonly [number, number], heading: number, scale: number): [number, number];
|
|
71
93
|
/**
|
|
72
94
|
* The `Tileset3D.modelMatrix` that moves a tileset anchored at `baked` onto `p`.
|
|
73
95
|
*
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* XY snapping (spec: issue #34 Part A). Two-stage, deliberately NOT a
|
|
3
|
+
* spatial index: deck.gl's own hover/click pick already tells us WHICH
|
|
4
|
+
* feature is under the cursor (stage 1 — free, runs every frame regardless
|
|
5
|
+
* of snapping) — this module refines that ONE feature's own geometry to
|
|
6
|
+
* the nearest vertex/edge/midpoint within a pixel tolerance (stage 2, CPU,
|
|
7
|
+
* only when snapping is on). OnlyMapJS never calls `pickObject` itself for
|
|
8
|
+
* this; the resolver runs on the SAME `PickingInfo` runtime-core.ts's
|
|
9
|
+
* existing `onHover`/`onClick` callbacks already receive every frame, at
|
|
10
|
+
* the exact point they currently discard everything but a bare
|
|
11
|
+
* `[lng, lat]` (see `coordOf`'s call sites).
|
|
12
|
+
*
|
|
13
|
+
* Agent vocabulary follows ArcGIS (the lingua franca every surveyor
|
|
14
|
+
* already knows): Vertex, Edge, Midpoint here; Endpoint/Intersection/
|
|
15
|
+
* Point-cloud/Snap-to-sketch are documented follow-ups (see the module's
|
|
16
|
+
* own README section on issue #34), Tangent skipped outright (CAD
|
|
17
|
+
* heritage, no geospatial use). "Closest wins" is NOT the tie-break when
|
|
18
|
+
* two agents both have a candidate within tolerance — Vertex beats
|
|
19
|
+
* Midpoint beats Edge (ArcGIS's own conflict order: a midpoint sitting
|
|
20
|
+
* near an edge should snap to the more specific point, not the general
|
|
21
|
+
* one), ties WITHIN one agent broken by distance.
|
|
22
|
+
*/
|
|
23
|
+
export type SnapAgent = "vertex" | "edge" | "midpoint";
|
|
24
|
+
export declare const SNAP_AGENTS: readonly SnapAgent[];
|
|
25
|
+
export declare const DEFAULT_SNAP_TOLERANCE_PX = 12;
|
|
26
|
+
export interface SnapConfig {
|
|
27
|
+
agents: ReadonlySet<SnapAgent>;
|
|
28
|
+
tolerancePx: number;
|
|
29
|
+
}
|
|
30
|
+
export interface SnapResult {
|
|
31
|
+
/** The snapped [lng, lat] — interpolated for edge/midpoint, exact for vertex. */
|
|
32
|
+
position: [number, number];
|
|
33
|
+
agent: SnapAgent;
|
|
34
|
+
/** The snapped point's elevation in meters, when the matched geometry carried one — for ANCHORING UI at the visible point (the snap tip); the position contract itself stays XY. */
|
|
35
|
+
elevation?: number;
|
|
36
|
+
}
|
|
37
|
+
/** Minimal viewport contract this module needs — matches deck.gl's real `Viewport.project`. */
|
|
38
|
+
export interface SnapViewport {
|
|
39
|
+
project(coordinate: number[]): number[];
|
|
40
|
+
}
|
|
41
|
+
interface GeoJsonGeometryLike {
|
|
42
|
+
type: string;
|
|
43
|
+
coordinates: unknown;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* `snap="vertex edge midpoint"` / `snap-tolerance="12"` on `<om-map>` →
|
|
47
|
+
* `SnapConfig`, or `null` when `snap` is absent/empty (the common case,
|
|
48
|
+
* must stay free — no pickingRadius change, no per-pick resolver call).
|
|
49
|
+
* Unrecognized tokens (a typo, or a future agent not implemented yet) are
|
|
50
|
+
* silently dropped rather than rejecting the whole attribute — validation.ts's
|
|
51
|
+
* `validateSnap` carries the loud version of that same check.
|
|
52
|
+
*/
|
|
53
|
+
export declare function parseSnapAttrs(getAttr: (name: string) => string | null): SnapConfig | null;
|
|
54
|
+
/**
|
|
55
|
+
* The geometry a picked object can be snapped against. GeoJSON-shaped
|
|
56
|
+
* objects (GeoJsonLayer, MVT, draw features) carry it directly — but MOST
|
|
57
|
+
* OnlyMapJS data is flat rows (CSV/JSON + `get-position` accessors), whose
|
|
58
|
+
* picked object is just the row, with no `.geometry` at all: snapping
|
|
59
|
+
* silently found nothing on them (reported: "doesn't detect the vertices in
|
|
60
|
+
* most of my data"). For those, the layer's own resolved accessors ARE the
|
|
61
|
+
* geometry — evaluate them against the picked row exactly the way the
|
|
62
|
+
* layer's shader binding did: `get-polygon` → Polygon, `get-path` →
|
|
63
|
+
* LineString, `get-source-position`+`get-target-position` → a two-point
|
|
64
|
+
* LineString, `get-position` → Point.
|
|
65
|
+
*
|
|
66
|
+
* Guarded to geographic layers: a layer in an offset/cartesian coordinate
|
|
67
|
+
* system (a BIM mesh sublayer, a local-frame point cloud) yields raw local
|
|
68
|
+
* meters from these accessors, not lng/lat — callers handle those through
|
|
69
|
+
* their own conversion branches (see resolveMapPoint's BIM-edges path).
|
|
70
|
+
*/
|
|
71
|
+
export declare function geometryFromPick(layerProps: Record<string, unknown>, object: unknown): GeoJsonGeometryLike | null;
|
|
72
|
+
/**
|
|
73
|
+
* Resolves a hover/click's cursor against ONE already-picked feature's
|
|
74
|
+
* geometry. `cursorPx`/`viewport` project every candidate to screen for
|
|
75
|
+
* pixel-accurate proximity; the winning MAP-space position is derived by
|
|
76
|
+
* interpolating the geometry's own [lng, lat] vertices with the SAME
|
|
77
|
+
* fraction found in screen space (an approximation for edge/midpoint on a
|
|
78
|
+
* long, steeply-projected segment, but exact for the vertex agent, and
|
|
79
|
+
* consistent with this codebase's own documented sphere-vs-ellipsoid,
|
|
80
|
+
* screen-space-drag-math tradeoffs elsewhere — e.g. geodesy.ts, the
|
|
81
|
+
* clip-box gizmo). Returns `null` when nothing is within tolerance, or the
|
|
82
|
+
* geometry type isn't one `extractRings` understands (e.g. a raw
|
|
83
|
+
* meter-offsets row, which this module does not attempt to project itself
|
|
84
|
+
* — see this file's own header comment on BIM/PathLayer content being a
|
|
85
|
+
* documented follow-up).
|
|
86
|
+
*/
|
|
87
|
+
export declare function resolveSnap(geometry: GeoJsonGeometryLike | null | undefined, cursorPx: readonly number[], viewport: SnapViewport, config: SnapConfig): SnapResult | null;
|
|
88
|
+
export {};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bulk terrain heightfield (spec item C2 / "Elevation profile" — the shared
|
|
3
|
+
* infrastructure both reference, "sampling shares C2's bulk heightfield, not
|
|
4
|
+
* per-point elevationAt()"). `terrain-sample.ts`'s `sampleTerrainElevation`
|
|
5
|
+
* is one fetch per point — fine for volume mode's handful of footprint
|
|
6
|
+
* vertices, but the wrong shape for a profile's tens-to-hundreds of samples
|
|
7
|
+
* along a line or the volumetrics grid's 10⁵–10⁶ cells: nearby points share
|
|
8
|
+
* DEM tiles, so fetching (and decoding) each tile once and reading every
|
|
9
|
+
* point out of it in memory is both fewer round-trips and what the per-cell
|
|
10
|
+
* volume integrator (volumetrics.ts) builds on.
|
|
11
|
+
*
|
|
12
|
+
* This module is the DOM-TOUCHING half (fetch + canvas decode); the pixel
|
|
13
|
+
* math — bilinear sampling with tile-seam handling — lives in
|
|
14
|
+
* volumetrics.ts's `sampleBilinear`, which is pure and shared with the
|
|
15
|
+
* volumetrics worker (decoded tiles postMessage across; canvases don't).
|
|
16
|
+
*/
|
|
17
|
+
import type { LngLat } from "./geodesy";
|
|
18
|
+
import type { TerrainIR } from "./terrain";
|
|
19
|
+
import { type HeightfieldTiles } from "./volumetrics";
|
|
20
|
+
/** A resolved, in-memory set of DEM tiles — `elevationAt` never touches the network. */
|
|
21
|
+
export interface TerrainHeightfield {
|
|
22
|
+
/** Ground elevation (meters) at `lngLat` — bilinear-interpolated, seam-correct — or `null` if no covering tile loaded/decoded. Ignores `terrain.exaggeration`, same as `sampleTerrainElevation`. */
|
|
23
|
+
elevationAt(lngLat: LngLat): number | null;
|
|
24
|
+
/** The raw decoded tile set — the transferable form volumetrics.ts (and its worker) consume directly. */
|
|
25
|
+
tiles: HeightfieldTiles;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Resolves every DEM tile `points` touches — INCLUDING the neighbor tiles a
|
|
29
|
+
* bilinear read at a tile-edge point spills into (the seam case: a point
|
|
30
|
+
* within half a pixel of a tile border reads up to 3 pixels from adjacent
|
|
31
|
+
* tiles; without pre-loading those, edge points would silently degrade to
|
|
32
|
+
* fewer-corner interpolation) — fetches + decodes each ONE TIME
|
|
33
|
+
* (deduplicated, in parallel), and returns a synchronous bilinear lookup. A
|
|
34
|
+
* tile that fails to load makes points inside it resolve to `null` (same
|
|
35
|
+
* "treat as no terrain" contract as `sampleTerrainElevation`) without
|
|
36
|
+
* affecting other tiles.
|
|
37
|
+
*/
|
|
38
|
+
export declare function loadHeightfield(points: LngLat[], terrain: TerrainIR): Promise<TerrainHeightfield>;
|
|
39
|
+
/**
|
|
40
|
+
* Resolves the DEM tile set COVERING a lng/lat bounding box — the
|
|
41
|
+
* volumetrics grid's shape of demand ("every cell inside the polygon's
|
|
42
|
+
* bbox"), which a point list can't express without enumerating 10⁶ cells.
|
|
43
|
+
* Starts at the provider's `maxZoom` and steps down until the cover fits
|
|
44
|
+
* `MAX_BOUNDS_TILES` — the returned `tiles.zoom` records what was actually
|
|
45
|
+
* used, and the integrator derives its GSD (and error model) from that, so
|
|
46
|
+
* a coarsened cover is REPORTED coarser, never silently pretended finer.
|
|
47
|
+
*/
|
|
48
|
+
export declare function loadHeightfieldForBounds(bounds: {
|
|
49
|
+
west: number;
|
|
50
|
+
south: number;
|
|
51
|
+
east: number;
|
|
52
|
+
north: number;
|
|
53
|
+
}, terrain: TerrainIR): Promise<TerrainHeightfield>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One-shot terrain elevation sampling (spec: "Cut/fill volume measurement").
|
|
3
|
+
* Kept separate from terrain.ts, whose header documents it as PURE and
|
|
4
|
+
* unit-testable without a GPU — this module is deliberately the DOM-touching
|
|
5
|
+
* half (fetch + canvas), for the one thing nothing else in this codebase
|
|
6
|
+
* needed before: "what is the real ground height at this lng/lat".
|
|
7
|
+
*
|
|
8
|
+
* Fetches the DEM tile as a blob (never an `<img src>`) so the canvas read
|
|
9
|
+
* below is never tainted regardless of the provider's CORS posture — and
|
|
10
|
+
* since deck.gl's own TerrainLayer already fetches this exact URL template
|
|
11
|
+
* cross-origin to RENDER the surface, a caller only ever reaches this with a
|
|
12
|
+
* URL that's already proven fetchable.
|
|
13
|
+
*/
|
|
14
|
+
import type { LngLat } from "./geodesy";
|
|
15
|
+
import type { TerrainIR } from "./terrain";
|
|
16
|
+
/** Slippy-map tile + fractional pixel covering `lngLat` at `zoom` (256px tiles). Exported for `terrain-heightfield.ts`'s bulk loader — same tile math, no reason to duplicate it. */
|
|
17
|
+
export declare function tilePixel(lngLat: LngLat, zoom: number): {
|
|
18
|
+
x: number;
|
|
19
|
+
y: number;
|
|
20
|
+
px: number;
|
|
21
|
+
py: number;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Ground elevation (meters) at `lngLat`, decoded from `terrain`'s DEM at its
|
|
25
|
+
* configured `maxZoom`. `null` on any fetch/decode failure — the caller
|
|
26
|
+
* treats that the same as "no terrain": flat ground at 0m, not a thrown error.
|
|
27
|
+
* Ignores `terrain.exaggeration` deliberately: that scales the RENDERED
|
|
28
|
+
* surface for legibility, volume math wants the true decoded height.
|
|
29
|
+
*/
|
|
30
|
+
export declare function sampleTerrainElevation(lngLat: LngLat, terrain: TerrainIR): Promise<number | null>;
|
package/dist/terrain.d.ts
CHANGED
|
@@ -99,7 +99,12 @@ export interface TerrainLayerPatch {
|
|
|
99
99
|
/**
|
|
100
100
|
* Per-layer terrain patches, PURE. `terrainActive` false → identity patches
|
|
101
101
|
* (stable ids, no props). Runtime-internal layers (trace temps, draw
|
|
102
|
-
* preview, tooltip plumbing)
|
|
102
|
+
* preview, tooltip plumbing) default OFF — they're screen-space/transient —
|
|
103
|
+
* but an internal layer that explicitly sets `terrain` (spec: "Cut/fill
|
|
104
|
+
* volume measurement": the shared draw-preview and measure's committed-
|
|
105
|
+
* geometry/label layers opt into "drape" so their 2D lng/lat vertices sit ON
|
|
106
|
+
* the terrain surface instead of at literal sea level, invisible below/
|
|
107
|
+
* behind a raised mesh) wins over that default, same as an authored layer.
|
|
103
108
|
*/
|
|
104
109
|
export declare function applyTerrain(irs: readonly LayerIR[], terrainActive: boolean, generation: number): Map<string, TerrainLayerPatch>;
|
|
105
110
|
/**
|
package/dist/units.d.ts
CHANGED
|
@@ -22,6 +22,33 @@ export declare function formatLength(meters: number, system?: UnitSystem): strin
|
|
|
22
22
|
* question 1 — nmi² is not a denomination users read).
|
|
23
23
|
*/
|
|
24
24
|
export declare function formatArea(m2: number, system?: UnitSystem): string;
|
|
25
|
+
/**
|
|
26
|
+
* Format a volume (m³, spec: "Cut/fill volume measurement"), with denomination
|
|
27
|
+
* crossover. Metric: m³, crossing to "×10³ m³" past 1000 m³ (the km² crossover
|
|
28
|
+
* shape). Imperial: cubic yards — the real-world earthworks unit, not ft³ — with
|
|
29
|
+
* the same ×10³ crossover. Nautical falls back to metric (formatArea's own
|
|
30
|
+
* documented fallback: nmi³ is not a denomination anyone reads).
|
|
31
|
+
*/
|
|
32
|
+
export declare function formatVolume(m3: number, system?: UnitSystem): string;
|
|
33
|
+
/**
|
|
34
|
+
* Format a mass (kg, spec item D — "tonnage"), with denomination crossover.
|
|
35
|
+
* Metric: tonnes ("t"), crossing to "×10³ t" past 1000 t (matching
|
|
36
|
+
* {@link formatVolume}'s own crossover shape). Imperial: US short tons
|
|
37
|
+
* ("ton", 2000 lb) — the earthworks-industry convention, not the UK long
|
|
38
|
+
* ton. Nautical falls back to metric (formatArea/formatVolume's own
|
|
39
|
+
* documented fallback).
|
|
40
|
+
*/
|
|
41
|
+
export declare function formatMass(kg: number, system?: UnitSystem): string;
|
|
42
|
+
/**
|
|
43
|
+
* Convert an author-provided density (spec item D — `density` attribute) to
|
|
44
|
+
* canonical kg/m³, interpreted per the ACTIVE unit system at the moment it's
|
|
45
|
+
* set: metric authors give t/m³ (tonnes per cubic meter — the earthworks
|
|
46
|
+
* convention, e.g. 1.6-1.8 for common soil), imperial authors give lb/yd³.
|
|
47
|
+
* Nautical falls back to the metric (t/m³) reading. Canonicalizing once here
|
|
48
|
+
* means a later unit-system TOGGLE (the widget's own units buttons) doesn't
|
|
49
|
+
* need to re-interpret an already-set density — kg/m³ is unit-system-agnostic.
|
|
50
|
+
*/
|
|
51
|
+
export declare function densityToKgM3(raw: number, system: UnitSystem): number;
|
|
25
52
|
/**
|
|
26
53
|
* The largest 1/2/3/5 ×10ⁿ value ≤ `value` (GL's log10 "nice number"). Used by
|
|
27
54
|
* the scale bar to snap its bar to a round distance. log10-based, so it is
|
package/dist/version.d.ts
CHANGED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Volumetrics runner — dispatches a `VolumetricsRequest` to the inline-blob
|
|
3
|
+
* Web Worker (volumetrics-worker.ts) and falls back to a synchronous
|
|
4
|
+
* main-thread `computeVolumetrics` call when worker construction isn't
|
|
5
|
+
* possible (no `Worker` global — headless test environments, exotic
|
|
6
|
+
* embeddings), exactly raster.ts's COG-decode posture: "slower, but never
|
|
7
|
+
* blank." One fresh worker per run, terminated on completion — integrations
|
|
8
|
+
* fire once per footprint commit (not per frame; drag frames re-sum the
|
|
9
|
+
* CACHED grid via `reintegrateCustomBase` without ever coming back here),
|
|
10
|
+
* so a persistent pool would be state without a workload.
|
|
11
|
+
*/
|
|
12
|
+
import { type VolumetricsRequest, type VolumetricsResult } from "./volumetrics";
|
|
13
|
+
export declare function runVolumetrics(req: VolumetricsRequest): Promise<VolumetricsResult>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|