@nika-js/onlymap 0.6.0 → 0.6.2
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 +63 -1
- package/README.md +39 -14
- package/THIRD-PARTY-LICENSES.md +27 -0
- package/dist/{LercDecode.es-fbo6h5rt.js → LercDecode.es-BOJJr6Gx.js} +1 -1
- package/dist/{basemap-CprSan9q.js → basemap-BofXgXxy.js} +13 -3
- package/dist/basemap.d.ts +10 -1
- package/dist/cityjson-D_V5GY8b.js +332 -0
- package/dist/clip-box-controller.d.ts +94 -0
- package/dist/clip-box.d.ts +109 -0
- package/dist/crs-Ciu7Xs7a.js +108 -0
- package/dist/crs.d.ts +50 -0
- package/dist/ctx.d.ts +8 -1
- package/dist/data-layer.d.ts +11 -3
- package/dist/draw-controller.d.ts +75 -1
- package/dist/draw.d.ts +22 -4
- package/dist/elements/om-map.d.ts +45 -2
- package/dist/elements/om-overlay.d.ts +13 -1
- package/dist/elements/om-widget.d.ts +4 -0
- package/dist/feature-colors.d.ts +41 -0
- package/dist/geodesy.d.ts +29 -3
- package/dist/{geoparquet-Bm8pfxyo.js → geoparquet-DapHATA0.js} +1 -1
- package/dist/html-data.d.ts +2 -2
- package/dist/ifc-Dc-TdSJr.js +877 -0
- package/dist/ifc.d.ts +406 -0
- package/dist/{index-Bz-rbmhj.js → index-7B-6Cbzu.js} +31619 -26565
- package/dist/{index-DSRIZR81.js → index-BF9iO8Tq.js} +1 -1
- package/dist/{index-GOwMjqm6.js → index-BmX6IId3.js} +1 -1
- package/dist/{index-UzmNhuS0.js → index-SPJVn_n_.js} +1 -1
- package/dist/{index-CCQttJzO.js → index-xmJjZxQJ.js} +2 -2
- package/dist/index.d.ts +13 -2
- package/dist/ir-snapshot.d.ts +3 -1
- package/dist/layer-registry.d.ts +14 -0
- package/dist/layers/bim-layer.d.ts +127 -0
- package/dist/layers/feature-mesh-layer.d.ts +288 -0
- package/dist/layers/gltf-scene-walk.d.ts +55 -0
- package/dist/layers/popup-layer.d.ts +13 -0
- package/dist/legend-spec.d.ts +1 -1
- package/dist/{lerc-CuifOkoA.js → lerc-C6k7EzSN.js} +2 -2
- package/dist/license.d.ts +12 -5
- package/dist/measure-controller.d.ts +458 -4
- package/dist/meshopt_simplifier-Co6uRDDA.js +370 -0
- package/dist/onlymap.standalone.js +57077 -50729
- package/dist/onlymapjs.js +72 -69
- package/dist/parse-manifest.d.ts +3 -0
- package/dist/programmatic.d.ts +39 -5
- package/dist/{raster-dysYIQ_k.js → raster-Cuzhibe2.js} +2 -2
- package/dist/{raster-pipeline-ZdCOGfqV.js → raster-pipeline-DLrnJK8y.js} +1 -1
- package/dist/react/om-layer.d.ts +6 -1
- package/dist/react/om-overlay.d.ts +9 -0
- package/dist/react.js +176 -169
- package/dist/region-export-controller.d.ts +37 -0
- package/dist/region-export.d.ts +56 -0
- package/dist/runtime-core.d.ts +260 -3
- package/dist/selection.d.ts +44 -1
- package/dist/site-placement.d.ts +107 -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/testing.d.ts +6 -2
- package/dist/tile3d-metadata.d.ts +185 -0
- 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/widget-registry.d.ts +11 -0
- package/dist/widgets/ifc-clash.d.ts +18 -0
- package/dist/widgets/ifc.d.ts +41 -0
- package/dist/{zarr-OewK7k2K.js → zarr-CUzEX8fB.js} +79 -66
- package/docs/3d-assets.md +48 -0
- package/docs/live-data.md +9 -1
- package/docs/testing.md +4 -2
- package/llms.txt +10 -5
- package/onlymapjs.html-data.json +313 -2
- package/package.json +6 -2
- package/skills/onlymapjs/SKILL.md +14 -2
- package/skills/onlymapjs/references/react.md +2 -2
- package/skills/onlymapjs/references/syntax.md +314 -15
- package/dist/cityjson-urQeujQv.js +0 -407
|
@@ -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,10 +1,30 @@
|
|
|
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";
|
|
6
9
|
import type { ValidationEntry } from "./validation";
|
|
7
10
|
import type { MapViewport } from "./basemap";
|
|
11
|
+
/**
|
|
12
|
+
* The tile-level EXT_structural_metadata property-table index, resolved
|
|
13
|
+
* independently of `FeatureMeshLayer`'s own copy of this same computation.
|
|
14
|
+
*
|
|
15
|
+
* `tile.content.featureIdPropertyTableIndex` (which `FeatureMeshLayer`
|
|
16
|
+
* eventually sets, in its own `renderLayers()`) is NOT readable here: deck.gl's
|
|
17
|
+
* `Tile3DLayer._onTileLoad` fires the `onTileLoad` prop callback (what feeds
|
|
18
|
+
* this hook) BEFORE it ever constructs `FeatureMeshLayer` for that tile —
|
|
19
|
+
* confirmed against `@deck.gl/geo-layers`'s own `_onTileLoad`/`_getSubLayer`
|
|
20
|
+
* ordering. Reading that field here always saw `undefined`, silently falling
|
|
21
|
+
* back to property table 0 regardless of which table a tile's feature IDs
|
|
22
|
+
* actually reference — invisible only because this repo's own IFC converter
|
|
23
|
+
* never emits more than one table. Recomputing from the tile's raw glTF with
|
|
24
|
+
* the same pure, already-tested helpers `FeatureMeshLayer` itself uses avoids
|
|
25
|
+
* depending on that ordering entirely.
|
|
26
|
+
*/
|
|
27
|
+
export declare function resolveTilePropertyTableIndex(gltf: unknown, featureIdProperty: string): number;
|
|
8
28
|
/** Mapbox presets/protocol URLs aren't implemented — kept maplibre-free so validation/runtime can check without loading the chunk. */
|
|
9
29
|
export declare function isMapboxBasemap(raw: string): boolean;
|
|
10
30
|
export interface InitialView {
|
|
@@ -25,6 +45,36 @@ export interface CameraOptions {
|
|
|
25
45
|
/** Arc the move (zoom-out-and-in) instead of easing directly — MapLibre `flyTo` / deck `FlyToInterpolator`. */
|
|
26
46
|
curve?: boolean;
|
|
27
47
|
}
|
|
48
|
+
export { resolveFeatureColor } from "./feature-colors";
|
|
49
|
+
/** One layer's accumulated (per-tile) property tables — see RuntimeCore.featureTables. */
|
|
50
|
+
export interface FeatureTableEntry {
|
|
51
|
+
source: string;
|
|
52
|
+
tiles: Map<string, Record<string, unknown>[]>;
|
|
53
|
+
rows: Record<string, unknown>[];
|
|
54
|
+
rowsVersion: number;
|
|
55
|
+
mappingKey?: string;
|
|
56
|
+
mapping?: import("./feature-colors").FeatureColorMapping | null;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The accumulator, extracted for direct testing: resets on a SOURCE change
|
|
60
|
+
* (the loader widget swapping models under one layer id), REPLACES rows for a
|
|
61
|
+
* re-seen tile key (deck evicts + refetches tiles as new objects — identity
|
|
62
|
+
* dedup accumulated duplicates), and keeps per-tile row arrays because
|
|
63
|
+
* feature IDs are tile-local. Returns the (possibly new) entry.
|
|
64
|
+
*/
|
|
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
|
+
}
|
|
28
78
|
export interface RuntimeCoreCallbacks {
|
|
29
79
|
/**
|
|
30
80
|
* Fired whenever the view state changes (pan/zoom/pitch/bearing) — a
|
|
@@ -34,7 +84,14 @@ export interface RuntimeCoreCallbacks {
|
|
|
34
84
|
* convention, the echo-loop half of two-way store binding.
|
|
35
85
|
*/
|
|
36
86
|
onViewportChange?: (origin: ViewOrigin) => void;
|
|
37
|
-
|
|
87
|
+
/**
|
|
88
|
+
* `pickType` is the pointer event that produced this pick — REQUIRED even
|
|
89
|
+
* when `selection` is null (an empty pick), because toSelection discards
|
|
90
|
+
* empty picks and with them their type. Overlays scoped with
|
|
91
|
+
* `selection-type` need it: a click on empty space dismisses a
|
|
92
|
+
* click-anchored popup, a hover over empty space must not.
|
|
93
|
+
*/
|
|
94
|
+
onSelectionChange?: (selection: Selection | null, pickType: "hover" | "click") => void;
|
|
38
95
|
/** Fired once the map (deck.gl standalone, or the basemap) has finished its async init (spec: Behavior Engine "load" event). */
|
|
39
96
|
onLoad?: () => void;
|
|
40
97
|
/**
|
|
@@ -44,6 +101,17 @@ export interface RuntimeCoreCallbacks {
|
|
|
44
101
|
* re-render on every drag-move.
|
|
45
102
|
*/
|
|
46
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;
|
|
47
115
|
/**
|
|
48
116
|
* Every click/hover's map coordinate (spec: "Manual Drawing"), fired
|
|
49
117
|
* ALONGSIDE onSelectionChange but INCLUDING empty-map events — which
|
|
@@ -52,6 +120,31 @@ export interface RuntimeCoreCallbacks {
|
|
|
52
120
|
* deck reports none (e.g. off-globe).
|
|
53
121
|
*/
|
|
54
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;
|
|
140
|
+
/**
|
|
141
|
+
* A `pick-features` layer decoded its EXT_structural_metadata property
|
|
142
|
+
* table — the whole table, indexed by feature ID. Fires once per layer, on
|
|
143
|
+
* the first tile that carries one, because every tile in a tileset shares
|
|
144
|
+
* the same schema. This is what lets a legend enumerate categories without
|
|
145
|
+
* a hand-built sidecar next to the tileset.
|
|
146
|
+
*/
|
|
147
|
+
onFeatureTable?: (layerId: string, rows: Record<string, unknown>[]) => void;
|
|
55
148
|
/**
|
|
56
149
|
* A tileset-bearing layer (Tile3DLayer) finished loading its root tileset
|
|
57
150
|
* — the consumer hook for tools that must reach the LIVE deck tileset
|
|
@@ -60,6 +153,30 @@ export interface RuntimeCoreCallbacks {
|
|
|
60
153
|
* @loaders.gl/tiles type dependency in the core).
|
|
61
154
|
*/
|
|
62
155
|
onTilesetLoad?: (layerId: string, tileset: unknown) => void;
|
|
156
|
+
/**
|
|
157
|
+
* A `carriesGeoreference` layer (BIMLayer) finished loading its source file
|
|
158
|
+
* and read whatever georeferencing it declares — `hasFullMapConversion`
|
|
159
|
+
* is true only when the file both resolved a real position
|
|
160
|
+
* (`IfcMapConversion` into a recognized CRS) AND declared
|
|
161
|
+
* `OrthogonalHeight`, the two facts absolute elevation needs to mean
|
|
162
|
+
* anything. Informational for consumers: the library never auto-applies
|
|
163
|
+
* terrain from it (what-you-write-is-what-you-see) — validation instead
|
|
164
|
+
* requires the map to author `terrain` explicitly for these layers.
|
|
165
|
+
*
|
|
166
|
+
* `approximatePlacement` is a narrower, separate fact: true whenever the
|
|
167
|
+
* file's position did NOT come from a real `IfcMapConversion` (IfcSite
|
|
168
|
+
* lat/lon, or no georeference at all) — that source has no rotation data
|
|
169
|
+
* at all, and is very often an authoring tool's default location rather
|
|
170
|
+
* than a survey. A plain boolean (not the raw `originSource` string) to
|
|
171
|
+
* keep this file decoupled from IFC-specific types, same reasoning as
|
|
172
|
+
* `hasFullMapConversion` already being precomputed rather than passed as
|
|
173
|
+
* raw parts. The front-end layer surfaces this as a validation warning.
|
|
174
|
+
*/
|
|
175
|
+
onGeoreference?: (layerId: string, info: {
|
|
176
|
+
orthogonalHeight?: number;
|
|
177
|
+
hasFullMapConversion: boolean;
|
|
178
|
+
approximatePlacement: boolean;
|
|
179
|
+
}) => void;
|
|
63
180
|
/**
|
|
64
181
|
* Runtime error boundary (spec: "Runtime error boundary") — deck.gl-level
|
|
65
182
|
* failures (a crashing accessor, an incompatible prop) formatted into the
|
|
@@ -103,6 +220,10 @@ export declare class RuntimeCore {
|
|
|
103
220
|
private mode;
|
|
104
221
|
/** Layers reconciled while the basemap chunk is still loading — applied on adapter arrival. */
|
|
105
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;
|
|
106
227
|
/** Retained descriptors — what the per-frame channel re-applies against. */
|
|
107
228
|
private lastIRs;
|
|
108
229
|
/** layerId → effect-driven plain-prop patches (the per-frame channel). */
|
|
@@ -136,8 +257,65 @@ export declare class RuntimeCore {
|
|
|
136
257
|
*/
|
|
137
258
|
private terrain;
|
|
138
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;
|
|
139
283
|
/** Rendered deck layer id → authored manifest/controller id (terrain uses fresh renderer ids). */
|
|
140
284
|
private renderedLayerIds;
|
|
285
|
+
/** The decoded property table for a pick-features layer, or undefined before its first tile. */
|
|
286
|
+
getFeatureTable(layerId: string): Record<string, unknown>[] | undefined;
|
|
287
|
+
/**
|
|
288
|
+
* Decoded property tables per `pick-features` layer, keyed by layer id.
|
|
289
|
+
* Held here rather than in the element because LAYER props derive from them
|
|
290
|
+
* too (`feature-color-by`), not only widgets. `rows` accumulates across
|
|
291
|
+
* every tile that has loaded so far (see `featureTableHook`) — for a
|
|
292
|
+
* grid-tiled model this means the table is only as complete as whatever
|
|
293
|
+
* has actually streamed in, same as any real tiled 3D-Tiles viewer.
|
|
294
|
+
*/
|
|
295
|
+
/**
|
|
296
|
+
* Per-layer accumulated property tables (spec: BIM feature picking). Rows
|
|
297
|
+
* are kept PER TILE — feature IDs are tile-local, so a flat concatenation
|
|
298
|
+
* can never be indexed by feature ID (the multi-tile styling bug). `rows`
|
|
299
|
+
* is the derived aggregate for legends/`ctx.features()`/global domains;
|
|
300
|
+
* `tiles` is keyed by a STABLE content key so an evicted-and-refetched tile
|
|
301
|
+
* REPLACES its rows instead of duplicating them, and `source` is the layer's
|
|
302
|
+
* source URL string (not the per-parse rows-array identity, which reset the
|
|
303
|
+
* whole accumulator on every reconcile mid-stream). `mapping` memoizes the
|
|
304
|
+
* derived colour mapping so animation frames reuse one identity — the
|
|
305
|
+
* style-texture-rebuild-per-rAF fix.
|
|
306
|
+
*/
|
|
307
|
+
private featureTables;
|
|
308
|
+
/** Fallback identity for tiles that expose no content URL — a stable per-object key. */
|
|
309
|
+
private tileKeyFallback;
|
|
310
|
+
private tileKeyCounter;
|
|
311
|
+
/**
|
|
312
|
+
* The memoized global colour mapping for a layer — rebuilt only when the
|
|
313
|
+
* accumulated table grows or a colour prop changes, so `applyLayers` calls
|
|
314
|
+
* from the per-frame channel (patchAnimatedProps runs one per rAF during
|
|
315
|
+
* story animations) hand every tile the SAME mapping identity and no GPU
|
|
316
|
+
* style texture is ever rebuilt for an unrelated animation.
|
|
317
|
+
*/
|
|
318
|
+
private featureColorMappingFor;
|
|
141
319
|
/**
|
|
142
320
|
* The basemap attribute suppressed while terrain is active — a flat
|
|
143
321
|
* MapLibre canvas at sea level visibly desyncs from a raised surface
|
|
@@ -192,6 +370,72 @@ export declare class RuntimeCore {
|
|
|
192
370
|
*/
|
|
193
371
|
setTerrain(ir: TerrainIR | null): void;
|
|
194
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;
|
|
195
439
|
private drainSnapshotCaptures;
|
|
196
440
|
/** Settle queued snapshot promises when the renderer they were waiting on goes away (mode flip / destroy) — never leave a caller hanging. */
|
|
197
441
|
private cancelSnapshotCaptures;
|
|
@@ -259,7 +503,7 @@ export declare class RuntimeCore {
|
|
|
259
503
|
* behavior dispatch, and the overlay flush are same-path by construction.
|
|
260
504
|
* Available in every mode (a browser-level test may use it too).
|
|
261
505
|
*/
|
|
262
|
-
injectPick(selection: Selection | null): void;
|
|
506
|
+
injectPick(selection: Selection | null, pickType?: "hover" | "click"): void;
|
|
263
507
|
getViewState(): Readonly<ViewState>;
|
|
264
508
|
/**
|
|
265
509
|
* Sketch capture (spec: "Manual Drawing", D4) — while a draw tool is active,
|
|
@@ -269,6 +513,20 @@ export declare class RuntimeCore {
|
|
|
269
513
|
* fight drag-pan, so pan stays live.
|
|
270
514
|
*/
|
|
271
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;
|
|
272
530
|
/**
|
|
273
531
|
* Used by the built-in `zoom-controls` widget's emitted zoom-in/zoom-out
|
|
274
532
|
* intents. Programmatic, so — unlike a user drag/scroll — standalone mode
|
|
@@ -305,4 +563,3 @@ export declare class RuntimeCore {
|
|
|
305
563
|
flyToBounds(bounds: [[number, number], [number, number]], padding?: number, opts?: CameraOptions): void;
|
|
306
564
|
destroy(): void;
|
|
307
565
|
}
|
|
308
|
-
export {};
|
package/dist/selection.d.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Converts a deck.gl PickingInfo into the documented, deck.gl-independent
|
|
4
4
|
* ctx.selection shape (HU2: ctx never leaks deck.gl internals).
|
|
5
5
|
*/
|
|
6
|
+
import { type Tile3DPickMetadata } from "./tile3d-metadata";
|
|
6
7
|
export interface Selection {
|
|
7
8
|
layerId: string;
|
|
8
9
|
object: unknown;
|
|
@@ -13,9 +14,44 @@ export interface Selection {
|
|
|
13
14
|
* onSelectionChange), so downstream consumers still always see an object.
|
|
14
15
|
*/
|
|
15
16
|
index: number;
|
|
16
|
-
|
|
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;
|
|
17
28
|
pixel: [number, number];
|
|
18
29
|
type: "hover" | "click";
|
|
30
|
+
/**
|
|
31
|
+
* BIM feature-picking epic. Undefined for every pick except a
|
|
32
|
+
* Tile3DLayer pick that resolved SOME metadata; `class`/`properties`
|
|
33
|
+
* follow the same "ctx never leaks deck.gl internals" normalization as
|
|
34
|
+
* the rest of this shape, `rawMetadata` is the deliberate escape hatch.
|
|
35
|
+
* Two sources, most-specific-wins:
|
|
36
|
+
* - Phase 1 (tile granularity, always active): 3D-Tiles-native
|
|
37
|
+
* tileset/group/tile/content entities, merged with a single-feature
|
|
38
|
+
* glTF EXT_structural_metadata property table when the content
|
|
39
|
+
* resolves to exactly one feature. See src/tile3d-metadata.ts.
|
|
40
|
+
* - Phase 2 (per-feature, only when `pick-features` resolved a specific
|
|
41
|
+
* vertex-level feature): overrides Phase 1's properties/class/guid
|
|
42
|
+
* with the picked feature's own row from the same property table,
|
|
43
|
+
* still layered on top of Phase 1's broader tile-level context. See
|
|
44
|
+
* src/layers/feature-mesh-layer.ts.
|
|
45
|
+
*/
|
|
46
|
+
properties?: Record<string, unknown>;
|
|
47
|
+
class?: string | null;
|
|
48
|
+
/** Best-effort identity (IFC GlobalId / gml:id / guid), from either Phase 1's tile-granularity merge or Phase 2's per-feature row — see tile3d-metadata.ts's findGuidLike. Undefined when nothing matched. */
|
|
49
|
+
guid?: string | null;
|
|
50
|
+
/** The picked vertex-level feature ID — only set by Phase 2's per-vertex picking; undefined for a Phase 1 tile-granularity pick. TILE-LOCAL: each tile's property table indexes 0..n-1, so the same number exists in every tile of a grid-tiled model — use `featureKey` to identify an element across tiles/models. */
|
|
51
|
+
featureId?: number;
|
|
52
|
+
/** Stable cross-tile identity for a Phase 2 pick: the element's own guid when the table carries one (IFC GlobalId / gml:id), else `<tile content key>#<featureId>`. Undefined when no per-feature pick resolved. */
|
|
53
|
+
featureKey?: string;
|
|
54
|
+
rawMetadata?: Tile3DPickMetadata["rawMetadata"];
|
|
19
55
|
}
|
|
20
56
|
interface PickingInfoLike {
|
|
21
57
|
layer?: {
|
|
@@ -26,6 +62,13 @@ interface PickingInfoLike {
|
|
|
26
62
|
coordinate?: number[];
|
|
27
63
|
x: number;
|
|
28
64
|
y: number;
|
|
65
|
+
/** Tile3DLayer's own getPickingInfo always sets this (undefined on a miss) — see tile3d-metadata.ts. */
|
|
66
|
+
sourceTile?: unknown;
|
|
67
|
+
/** Set by src/layers/feature-mesh-layer.ts's SinglePrimitiveFeatureLayer.getPickingInfo when pick-features resolved a specific vertex-level feature — takes precedence over the tile-granularity fallback below. */
|
|
68
|
+
featureId?: number;
|
|
69
|
+
properties?: Record<string, unknown>;
|
|
70
|
+
class?: string | null;
|
|
71
|
+
guid?: string | null;
|
|
29
72
|
}
|
|
30
73
|
export declare function toSelection(info: PickingInfoLike, type: "hover" | "click", resolveLayerId?: (renderedId: string) => string): Selection | null;
|
|
31
74
|
export {};
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Declarative georeferencing — `site-origin` / `site-heading` / `site-scale`.
|
|
3
|
+
*
|
|
4
|
+
* An IFC export tells you where it thinks it is, and it is very often wrong:
|
|
5
|
+
* authoring tools ship a default project location (the Medical-Dental Clinic
|
|
6
|
+
* sample carries Revit's Boston default; the Duplex carries a Chicago city
|
|
7
|
+
* centre point), `IfcMapConversion` is absent from most IFC2x3 files, and
|
|
8
|
+
* `TrueNorth` is routinely left unset. Baking that into the tileset at
|
|
9
|
+
* conversion time means every correction is a reconversion.
|
|
10
|
+
*
|
|
11
|
+
* So placement moves to the layer, where it is an attribute like any other —
|
|
12
|
+
* inspectable in the markup, settable by the IFC loader from whatever it
|
|
13
|
+
* managed to extract, and undoable/story-steppable for free.
|
|
14
|
+
*
|
|
15
|
+
* Two transports, because the two layers that make up an IFC model are
|
|
16
|
+
* anchored differently:
|
|
17
|
+
*
|
|
18
|
+
* - The MESH is a 3D Tiles tileset whose root already carries an
|
|
19
|
+
* east/north/up frame. deck's `Tile3DLayer` forwards `loadOptions.tileset`
|
|
20
|
+
* into the `Tileset3D` constructor, and `Tileset3D` PRE-multiplies its
|
|
21
|
+
* `modelMatrix` onto the root transform, so relocating means cancelling
|
|
22
|
+
* the baked frame first: `target · Rz · S · baked⁻¹`. That needs the baked
|
|
23
|
+
* frame, hence `tilesetBakedFrame` below.
|
|
24
|
+
* - The OUTLINES are a `PathLayer` of local east/north/up metres, which
|
|
25
|
+
* `METER_OFFSETS` places directly from `coordinateOrigin` — no baked frame
|
|
26
|
+
* to cancel, and rotation/scale ride a plain `modelMatrix` uniform, so
|
|
27
|
+
* they are free to change per frame.
|
|
28
|
+
*/
|
|
29
|
+
import { Matrix4 } from "@math.gl/core";
|
|
30
|
+
export interface SitePlacement {
|
|
31
|
+
/** Absent when the author gave only a heading/scale — rotate in place. */
|
|
32
|
+
origin: [number, number, number] | null;
|
|
33
|
+
/** Degrees CLOCKWISE from true north, matching how a survey bearing reads. */
|
|
34
|
+
heading: number;
|
|
35
|
+
scale: number;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Reads the three placement props off a resolved layer IR.
|
|
39
|
+
*
|
|
40
|
+
* Returns null when none is authored, which is the common case and must stay
|
|
41
|
+
* free: no prefetch, no matrix, no change to how the layer loads.
|
|
42
|
+
*/
|
|
43
|
+
export declare function parseSitePlacement(props: Record<string, unknown>): SitePlacement | null;
|
|
44
|
+
/**
|
|
45
|
+
* Short, DOT-FREE token for "has the placement changed?" — appended to a
|
|
46
|
+
* tileset URL to force deck to reload (see the cache-bust in runtime-core).
|
|
47
|
+
*
|
|
48
|
+
* Hashed rather than spelled out because loaders.gl types a tile by
|
|
49
|
+
* `url.split('?')[0].split('.').pop()`, and a coordinate spelled into the
|
|
50
|
+
* fragment (`#site=-71.059776,…`) hands that sniffer `059776` instead of
|
|
51
|
+
* `json`. Tilesets that force `isTileset` would survive it; ones relying on
|
|
52
|
+
* auto-detection would not, and the failure is the silent kind where nothing
|
|
53
|
+
* renders. Base-36 of a 32-bit FNV-1a has no dots by construction.
|
|
54
|
+
*/
|
|
55
|
+
export declare function sitePlacementToken(p: SitePlacement): string;
|
|
56
|
+
/**
|
|
57
|
+
* Local east/north/up frame at a geodetic point, as the column-major matrix
|
|
58
|
+
* 3D Tiles calls `root.transform`.
|
|
59
|
+
*/
|
|
60
|
+
export declare function enuMatrix(lonDeg: number, latDeg: number, height?: number): Matrix4;
|
|
61
|
+
/**
|
|
62
|
+
* Rotation + uniform scale about the model's own origin, in its local
|
|
63
|
+
* east/north/up frame.
|
|
64
|
+
*
|
|
65
|
+
* Heading is clockwise from north; ENU is right-handed with +Z up, where a
|
|
66
|
+
* POSITIVE rotation about Z turns north toward west. So a clockwise bearing is
|
|
67
|
+
* a negative rotation — the sign here is the whole reason this is a named
|
|
68
|
+
* function rather than an inline `rotateZ`.
|
|
69
|
+
*/
|
|
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];
|
|
93
|
+
/**
|
|
94
|
+
* The `Tileset3D.modelMatrix` that moves a tileset anchored at `baked` onto `p`.
|
|
95
|
+
*
|
|
96
|
+
* `target · Rz · S · anchor⁻¹`: the inverse undoes the frame the converter
|
|
97
|
+
* wrote, then the model is re-planted. With no `site-origin` the target IS the
|
|
98
|
+
* anchor, so the inverse cancels it exactly and only the rotation and scale
|
|
99
|
+
* survive — `site-heading` on its own spins the model where it stands.
|
|
100
|
+
*
|
|
101
|
+
* Both the pivot and the outline overlay's `coordinateOrigin` are the same
|
|
102
|
+
* ground-level site point, so mesh and outlines rotate together.
|
|
103
|
+
*/
|
|
104
|
+
export declare function tilesetPlacementMatrix(baked: Matrix4, p: SitePlacement): Matrix4;
|
|
105
|
+
export declare function tilesetBakedFrame(url: string, onReady: () => void): Matrix4 | null | undefined;
|
|
106
|
+
/** Test seam — the cache is process-wide and would leak between cases. */
|
|
107
|
+
export declare function resetSitePlacementCache(): void;
|