@nika-js/onlymap 0.6.0 → 0.6.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 +15 -1
- package/README.md +18 -6
- package/THIRD-PARTY-LICENSES.md +27 -0
- package/dist/{LercDecode.es-fbo6h5rt.js → LercDecode.es-CJnypw8j.js} +1 -1
- package/dist/{basemap-CprSan9q.js → basemap-DrQ0-eyR.js} +1 -1
- package/dist/cityjson-D_V5GY8b.js +332 -0
- package/dist/crs-Ciu7Xs7a.js +108 -0
- package/dist/crs.d.ts +50 -0
- package/dist/ctx.d.ts +8 -1
- package/dist/elements/om-map.d.ts +26 -2
- package/dist/elements/om-overlay.d.ts +1 -1
- package/dist/elements/om-widget.d.ts +4 -0
- package/dist/feature-colors.d.ts +41 -0
- package/dist/{geoparquet-Bm8pfxyo.js → geoparquet-Dix4lTNy.js} +1 -1
- package/dist/html-data.d.ts +1 -1
- package/dist/ifc-Dc-TdSJr.js +877 -0
- package/dist/ifc.d.ts +406 -0
- package/dist/{index-Bz-rbmhj.js → index-1UgNlfGR.js} +20892 -18886
- package/dist/{index-GOwMjqm6.js → index-BQMjW5w0.js} +1 -1
- package/dist/{index-DSRIZR81.js → index-B_1PPJgC.js} +1 -1
- package/dist/{index-CCQttJzO.js → index-CAuT5j9Y.js} +2 -2
- package/dist/{index-UzmNhuS0.js → index-CXPaeisL.js} +1 -1
- package/dist/index.d.ts +8 -0
- package/dist/layer-registry.d.ts +14 -0
- package/dist/layers/bim-layer.d.ts +104 -0
- package/dist/layers/feature-mesh-layer.d.ts +288 -0
- package/dist/layers/gltf-scene-walk.d.ts +55 -0
- package/dist/{lerc-CuifOkoA.js → lerc-CbTjQ7uI.js} +2 -2
- package/dist/meshopt_simplifier-Co6uRDDA.js +370 -0
- package/dist/onlymap.standalone.js +57996 -54706
- package/dist/onlymapjs.js +69 -67
- package/dist/programmatic.d.ts +22 -2
- package/dist/{raster-dysYIQ_k.js → raster-0b0nSHUh.js} +2 -2
- package/dist/{raster-pipeline-ZdCOGfqV.js → raster-pipeline-D8siq7-4.js} +1 -1
- package/dist/react/om-overlay.d.ts +9 -0
- package/dist/react.js +144 -144
- package/dist/runtime-core.d.ts +110 -3
- package/dist/selection.d.ts +33 -0
- package/dist/site-placement.d.ts +85 -0
- package/dist/testing.d.ts +6 -2
- package/dist/tile3d-metadata.d.ts +185 -0
- package/dist/version.d.ts +1 -1
- 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-DCYro_Vs.js} +79 -66
- package/docs/testing.md +2 -2
- package/llms.txt +3 -3
- package/onlymapjs.html-data.json +152 -0
- package/package.json +3 -1
- package/skills/onlymapjs/SKILL.md +4 -2
- package/skills/onlymapjs/references/react.md +1 -1
- package/skills/onlymapjs/references/syntax.md +298 -11
- package/dist/cityjson-urQeujQv.js +0 -407
package/dist/runtime-core.d.ts
CHANGED
|
@@ -5,6 +5,23 @@ import type { LayerIR } from "./ir";
|
|
|
5
5
|
import { type Selection } from "./selection";
|
|
6
6
|
import type { ValidationEntry } from "./validation";
|
|
7
7
|
import type { MapViewport } from "./basemap";
|
|
8
|
+
/**
|
|
9
|
+
* The tile-level EXT_structural_metadata property-table index, resolved
|
|
10
|
+
* independently of `FeatureMeshLayer`'s own copy of this same computation.
|
|
11
|
+
*
|
|
12
|
+
* `tile.content.featureIdPropertyTableIndex` (which `FeatureMeshLayer`
|
|
13
|
+
* eventually sets, in its own `renderLayers()`) is NOT readable here: deck.gl's
|
|
14
|
+
* `Tile3DLayer._onTileLoad` fires the `onTileLoad` prop callback (what feeds
|
|
15
|
+
* this hook) BEFORE it ever constructs `FeatureMeshLayer` for that tile —
|
|
16
|
+
* confirmed against `@deck.gl/geo-layers`'s own `_onTileLoad`/`_getSubLayer`
|
|
17
|
+
* ordering. Reading that field here always saw `undefined`, silently falling
|
|
18
|
+
* back to property table 0 regardless of which table a tile's feature IDs
|
|
19
|
+
* actually reference — invisible only because this repo's own IFC converter
|
|
20
|
+
* never emits more than one table. Recomputing from the tile's raw glTF with
|
|
21
|
+
* the same pure, already-tested helpers `FeatureMeshLayer` itself uses avoids
|
|
22
|
+
* depending on that ordering entirely.
|
|
23
|
+
*/
|
|
24
|
+
export declare function resolveTilePropertyTableIndex(gltf: unknown, featureIdProperty: string): number;
|
|
8
25
|
/** Mapbox presets/protocol URLs aren't implemented — kept maplibre-free so validation/runtime can check without loading the chunk. */
|
|
9
26
|
export declare function isMapboxBasemap(raw: string): boolean;
|
|
10
27
|
export interface InitialView {
|
|
@@ -25,6 +42,24 @@ export interface CameraOptions {
|
|
|
25
42
|
/** Arc the move (zoom-out-and-in) instead of easing directly — MapLibre `flyTo` / deck `FlyToInterpolator`. */
|
|
26
43
|
curve?: boolean;
|
|
27
44
|
}
|
|
45
|
+
export { resolveFeatureColor } from "./feature-colors";
|
|
46
|
+
/** One layer's accumulated (per-tile) property tables — see RuntimeCore.featureTables. */
|
|
47
|
+
export interface FeatureTableEntry {
|
|
48
|
+
source: string;
|
|
49
|
+
tiles: Map<string, Record<string, unknown>[]>;
|
|
50
|
+
rows: Record<string, unknown>[];
|
|
51
|
+
rowsVersion: number;
|
|
52
|
+
mappingKey?: string;
|
|
53
|
+
mapping?: import("./feature-colors").FeatureColorMapping | null;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* The accumulator, extracted for direct testing: resets on a SOURCE change
|
|
57
|
+
* (the loader widget swapping models under one layer id), REPLACES rows for a
|
|
58
|
+
* re-seen tile key (deck evicts + refetches tiles as new objects — identity
|
|
59
|
+
* dedup accumulated duplicates), and keeps per-tile row arrays because
|
|
60
|
+
* feature IDs are tile-local. Returns the (possibly new) entry.
|
|
61
|
+
*/
|
|
62
|
+
export declare function accumulateFeatureRows(tables: Map<string, FeatureTableEntry>, layerId: string, sourceKey: string, tileKey: string, rows: Record<string, unknown>[]): FeatureTableEntry;
|
|
28
63
|
export interface RuntimeCoreCallbacks {
|
|
29
64
|
/**
|
|
30
65
|
* Fired whenever the view state changes (pan/zoom/pitch/bearing) — a
|
|
@@ -34,7 +69,14 @@ export interface RuntimeCoreCallbacks {
|
|
|
34
69
|
* convention, the echo-loop half of two-way store binding.
|
|
35
70
|
*/
|
|
36
71
|
onViewportChange?: (origin: ViewOrigin) => void;
|
|
37
|
-
|
|
72
|
+
/**
|
|
73
|
+
* `pickType` is the pointer event that produced this pick — REQUIRED even
|
|
74
|
+
* when `selection` is null (an empty pick), because toSelection discards
|
|
75
|
+
* empty picks and with them their type. Overlays scoped with
|
|
76
|
+
* `selection-type` need it: a click on empty space dismisses a
|
|
77
|
+
* click-anchored popup, a hover over empty space must not.
|
|
78
|
+
*/
|
|
79
|
+
onSelectionChange?: (selection: Selection | null, pickType: "hover" | "click") => void;
|
|
38
80
|
/** Fired once the map (deck.gl standalone, or the basemap) has finished its async init (spec: Behavior Engine "load" event). */
|
|
39
81
|
onLoad?: () => void;
|
|
40
82
|
/**
|
|
@@ -52,6 +94,14 @@ export interface RuntimeCoreCallbacks {
|
|
|
52
94
|
* deck reports none (e.g. off-globe).
|
|
53
95
|
*/
|
|
54
96
|
onMapPoint?: (coordinate: [number, number] | null, kind: "click" | "hover") => void;
|
|
97
|
+
/**
|
|
98
|
+
* A `pick-features` layer decoded its EXT_structural_metadata property
|
|
99
|
+
* table — the whole table, indexed by feature ID. Fires once per layer, on
|
|
100
|
+
* the first tile that carries one, because every tile in a tileset shares
|
|
101
|
+
* the same schema. This is what lets a legend enumerate categories without
|
|
102
|
+
* a hand-built sidecar next to the tileset.
|
|
103
|
+
*/
|
|
104
|
+
onFeatureTable?: (layerId: string, rows: Record<string, unknown>[]) => void;
|
|
55
105
|
/**
|
|
56
106
|
* A tileset-bearing layer (Tile3DLayer) finished loading its root tileset
|
|
57
107
|
* — the consumer hook for tools that must reach the LIVE deck tileset
|
|
@@ -60,6 +110,30 @@ export interface RuntimeCoreCallbacks {
|
|
|
60
110
|
* @loaders.gl/tiles type dependency in the core).
|
|
61
111
|
*/
|
|
62
112
|
onTilesetLoad?: (layerId: string, tileset: unknown) => void;
|
|
113
|
+
/**
|
|
114
|
+
* A `carriesGeoreference` layer (BIMLayer) finished loading its source file
|
|
115
|
+
* and read whatever georeferencing it declares — `hasFullMapConversion`
|
|
116
|
+
* is true only when the file both resolved a real position
|
|
117
|
+
* (`IfcMapConversion` into a recognized CRS) AND declared
|
|
118
|
+
* `OrthogonalHeight`, the two facts absolute elevation needs to mean
|
|
119
|
+
* anything. Informational for consumers: the library never auto-applies
|
|
120
|
+
* terrain from it (what-you-write-is-what-you-see) — validation instead
|
|
121
|
+
* requires the map to author `terrain` explicitly for these layers.
|
|
122
|
+
*
|
|
123
|
+
* `approximatePlacement` is a narrower, separate fact: true whenever the
|
|
124
|
+
* file's position did NOT come from a real `IfcMapConversion` (IfcSite
|
|
125
|
+
* lat/lon, or no georeference at all) — that source has no rotation data
|
|
126
|
+
* at all, and is very often an authoring tool's default location rather
|
|
127
|
+
* than a survey. A plain boolean (not the raw `originSource` string) to
|
|
128
|
+
* keep this file decoupled from IFC-specific types, same reasoning as
|
|
129
|
+
* `hasFullMapConversion` already being precomputed rather than passed as
|
|
130
|
+
* raw parts. The front-end layer surfaces this as a validation warning.
|
|
131
|
+
*/
|
|
132
|
+
onGeoreference?: (layerId: string, info: {
|
|
133
|
+
orthogonalHeight?: number;
|
|
134
|
+
hasFullMapConversion: boolean;
|
|
135
|
+
approximatePlacement: boolean;
|
|
136
|
+
}) => void;
|
|
63
137
|
/**
|
|
64
138
|
* Runtime error boundary (spec: "Runtime error boundary") — deck.gl-level
|
|
65
139
|
* failures (a crashing accessor, an incompatible prop) formatted into the
|
|
@@ -138,6 +212,40 @@ export declare class RuntimeCore {
|
|
|
138
212
|
private terrainGeneration;
|
|
139
213
|
/** Rendered deck layer id → authored manifest/controller id (terrain uses fresh renderer ids). */
|
|
140
214
|
private renderedLayerIds;
|
|
215
|
+
/** The decoded property table for a pick-features layer, or undefined before its first tile. */
|
|
216
|
+
getFeatureTable(layerId: string): Record<string, unknown>[] | undefined;
|
|
217
|
+
/**
|
|
218
|
+
* Decoded property tables per `pick-features` layer, keyed by layer id.
|
|
219
|
+
* Held here rather than in the element because LAYER props derive from them
|
|
220
|
+
* too (`feature-color-by`), not only widgets. `rows` accumulates across
|
|
221
|
+
* every tile that has loaded so far (see `featureTableHook`) — for a
|
|
222
|
+
* grid-tiled model this means the table is only as complete as whatever
|
|
223
|
+
* has actually streamed in, same as any real tiled 3D-Tiles viewer.
|
|
224
|
+
*/
|
|
225
|
+
/**
|
|
226
|
+
* Per-layer accumulated property tables (spec: BIM feature picking). Rows
|
|
227
|
+
* are kept PER TILE — feature IDs are tile-local, so a flat concatenation
|
|
228
|
+
* can never be indexed by feature ID (the multi-tile styling bug). `rows`
|
|
229
|
+
* is the derived aggregate for legends/`ctx.features()`/global domains;
|
|
230
|
+
* `tiles` is keyed by a STABLE content key so an evicted-and-refetched tile
|
|
231
|
+
* REPLACES its rows instead of duplicating them, and `source` is the layer's
|
|
232
|
+
* source URL string (not the per-parse rows-array identity, which reset the
|
|
233
|
+
* whole accumulator on every reconcile mid-stream). `mapping` memoizes the
|
|
234
|
+
* derived colour mapping so animation frames reuse one identity — the
|
|
235
|
+
* style-texture-rebuild-per-rAF fix.
|
|
236
|
+
*/
|
|
237
|
+
private featureTables;
|
|
238
|
+
/** Fallback identity for tiles that expose no content URL — a stable per-object key. */
|
|
239
|
+
private tileKeyFallback;
|
|
240
|
+
private tileKeyCounter;
|
|
241
|
+
/**
|
|
242
|
+
* The memoized global colour mapping for a layer — rebuilt only when the
|
|
243
|
+
* accumulated table grows or a colour prop changes, so `applyLayers` calls
|
|
244
|
+
* from the per-frame channel (patchAnimatedProps runs one per rAF during
|
|
245
|
+
* story animations) hand every tile the SAME mapping identity and no GPU
|
|
246
|
+
* style texture is ever rebuilt for an unrelated animation.
|
|
247
|
+
*/
|
|
248
|
+
private featureColorMappingFor;
|
|
141
249
|
/**
|
|
142
250
|
* The basemap attribute suppressed while terrain is active — a flat
|
|
143
251
|
* MapLibre canvas at sea level visibly desyncs from a raised surface
|
|
@@ -259,7 +367,7 @@ export declare class RuntimeCore {
|
|
|
259
367
|
* behavior dispatch, and the overlay flush are same-path by construction.
|
|
260
368
|
* Available in every mode (a browser-level test may use it too).
|
|
261
369
|
*/
|
|
262
|
-
injectPick(selection: Selection | null): void;
|
|
370
|
+
injectPick(selection: Selection | null, pickType?: "hover" | "click"): void;
|
|
263
371
|
getViewState(): Readonly<ViewState>;
|
|
264
372
|
/**
|
|
265
373
|
* Sketch capture (spec: "Manual Drawing", D4) — while a draw tool is active,
|
|
@@ -305,4 +413,3 @@ export declare class RuntimeCore {
|
|
|
305
413
|
flyToBounds(bounds: [[number, number], [number, number]], padding?: number, opts?: CameraOptions): void;
|
|
306
414
|
destroy(): void;
|
|
307
415
|
}
|
|
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;
|
|
@@ -16,6 +17,31 @@ export interface Selection {
|
|
|
16
17
|
coordinate: [number, number] | null;
|
|
17
18
|
pixel: [number, number];
|
|
18
19
|
type: "hover" | "click";
|
|
20
|
+
/**
|
|
21
|
+
* BIM feature-picking epic. Undefined for every pick except a
|
|
22
|
+
* Tile3DLayer pick that resolved SOME metadata; `class`/`properties`
|
|
23
|
+
* follow the same "ctx never leaks deck.gl internals" normalization as
|
|
24
|
+
* the rest of this shape, `rawMetadata` is the deliberate escape hatch.
|
|
25
|
+
* Two sources, most-specific-wins:
|
|
26
|
+
* - Phase 1 (tile granularity, always active): 3D-Tiles-native
|
|
27
|
+
* tileset/group/tile/content entities, merged with a single-feature
|
|
28
|
+
* glTF EXT_structural_metadata property table when the content
|
|
29
|
+
* resolves to exactly one feature. See src/tile3d-metadata.ts.
|
|
30
|
+
* - Phase 2 (per-feature, only when `pick-features` resolved a specific
|
|
31
|
+
* vertex-level feature): overrides Phase 1's properties/class/guid
|
|
32
|
+
* with the picked feature's own row from the same property table,
|
|
33
|
+
* still layered on top of Phase 1's broader tile-level context. See
|
|
34
|
+
* src/layers/feature-mesh-layer.ts.
|
|
35
|
+
*/
|
|
36
|
+
properties?: Record<string, unknown>;
|
|
37
|
+
class?: string | null;
|
|
38
|
+
/** 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. */
|
|
39
|
+
guid?: string | null;
|
|
40
|
+
/** 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. */
|
|
41
|
+
featureId?: number;
|
|
42
|
+
/** 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. */
|
|
43
|
+
featureKey?: string;
|
|
44
|
+
rawMetadata?: Tile3DPickMetadata["rawMetadata"];
|
|
19
45
|
}
|
|
20
46
|
interface PickingInfoLike {
|
|
21
47
|
layer?: {
|
|
@@ -26,6 +52,13 @@ interface PickingInfoLike {
|
|
|
26
52
|
coordinate?: number[];
|
|
27
53
|
x: number;
|
|
28
54
|
y: number;
|
|
55
|
+
/** Tile3DLayer's own getPickingInfo always sets this (undefined on a miss) — see tile3d-metadata.ts. */
|
|
56
|
+
sourceTile?: unknown;
|
|
57
|
+
/** 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. */
|
|
58
|
+
featureId?: number;
|
|
59
|
+
properties?: Record<string, unknown>;
|
|
60
|
+
class?: string | null;
|
|
61
|
+
guid?: string | null;
|
|
29
62
|
}
|
|
30
63
|
export declare function toSelection(info: PickingInfoLike, type: "hover" | "click", resolveLayerId?: (renderedId: string) => string): Selection | null;
|
|
31
64
|
export {};
|
|
@@ -0,0 +1,85 @@
|
|
|
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
|
+
* The `Tileset3D.modelMatrix` that moves a tileset anchored at `baked` onto `p`.
|
|
73
|
+
*
|
|
74
|
+
* `target · Rz · S · anchor⁻¹`: the inverse undoes the frame the converter
|
|
75
|
+
* wrote, then the model is re-planted. With no `site-origin` the target IS the
|
|
76
|
+
* anchor, so the inverse cancels it exactly and only the rotation and scale
|
|
77
|
+
* survive — `site-heading` on its own spins the model where it stands.
|
|
78
|
+
*
|
|
79
|
+
* Both the pivot and the outline overlay's `coordinateOrigin` are the same
|
|
80
|
+
* ground-level site point, so mesh and outlines rotate together.
|
|
81
|
+
*/
|
|
82
|
+
export declare function tilesetPlacementMatrix(baked: Matrix4, p: SitePlacement): Matrix4;
|
|
83
|
+
export declare function tilesetBakedFrame(url: string, onReady: () => void): Matrix4 | null | undefined;
|
|
84
|
+
/** Test seam — the cache is process-wide and would leak between cases. */
|
|
85
|
+
export declare function resetSitePlacementCache(): void;
|
package/dist/testing.d.ts
CHANGED
|
@@ -49,8 +49,12 @@ export interface TestHarness {
|
|
|
49
49
|
* selection path a real pick takes. Settles before resolving.
|
|
50
50
|
*/
|
|
51
51
|
pick(opts: PickOptions): Promise<void>;
|
|
52
|
-
/**
|
|
53
|
-
|
|
52
|
+
/**
|
|
53
|
+
* Ends the current selection with an empty pick. Default kind "hover" — a
|
|
54
|
+
* hover-off, the path that auto-hides tooltips. Pass "click" for a click on
|
|
55
|
+
* empty space, which is what dismisses a `selection-type="click"` popup.
|
|
56
|
+
*/
|
|
57
|
+
clearSelection(kind?: "hover" | "click"): Promise<void>;
|
|
54
58
|
/**
|
|
55
59
|
* Feeds a map coordinate through the same path a real deck click/hover
|
|
56
60
|
* takes — drives the draw controller and fires the `om-map-point` event.
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BIM feature-picking epic, Phase 1 (design pending — see
|
|
3
|
+
* agent-map-library-architecture.md once the design section lands).
|
|
4
|
+
*
|
|
5
|
+
* Enriches a Tile3DLayer pick with whatever metadata is CHEAPLY available at
|
|
6
|
+
* tile granularity — no shader work, no new manifest attribute, no per-tile
|
|
7
|
+
* caching, no new deck.gl prop injection. Three independent sources merge,
|
|
8
|
+
* most-content-specific winning when more than one is present:
|
|
9
|
+
*
|
|
10
|
+
* 1. glTF `EXT_mesh_features` + `EXT_structural_metadata`, already fully
|
|
11
|
+
* decoded by loaders.gl's GLTFLoader by the time a tile is even
|
|
12
|
+
* renderable (`Tile3D.content.gltf`, post `postProcessGLTF` — verified
|
|
13
|
+
* against node_modules/@loaders.gl/{gltf,3d-tiles} source: primitive
|
|
14
|
+
* `.extensions` and the top-level `EXT_structural_metadata` survive the
|
|
15
|
+
* postprocess step via object spread, so the same decode this repo's
|
|
16
|
+
* Phase-0 spike proved against a real fixture applies here unchanged).
|
|
17
|
+
* Only used when the content resolves to exactly ONE feature — Phase 1
|
|
18
|
+
* has no per-vertex resolution (that's Phase 2's `pick-features` shader
|
|
19
|
+
* work), so a multi-feature content would be misattributing properties
|
|
20
|
+
* to whichever vertex the coarse tile-level pick happened to return.
|
|
21
|
+
* 2. **Legacy 3D Tiles 1.0 batch tables** — b3dm's older per-feature
|
|
22
|
+
* mechanism (a JSON `batchTableJson` of `{propertyName: [oneValuePerBatch]}`
|
|
23
|
+
* arrays, keyed by a `_BATCHID` vertex attribute never surfaced by this
|
|
24
|
+
* layer since Phase 1 doesn't do per-vertex work). Verified directly
|
|
25
|
+
* against real production data (NYC DoITT building footprints and Japan's
|
|
26
|
+
* PLATEAU CityGML export, both streamed live via Cesium ion): real-world
|
|
27
|
+
* 3D Tiles datasets overwhelmingly still ship this older mechanism, not
|
|
28
|
+
* 1.1's `EXT_structural_metadata` — this is the metadata source that
|
|
29
|
+
* actually matters for content people can get their hands on today, not
|
|
30
|
+
* just synthetic samples. loaders.gl's b3dm parser
|
|
31
|
+
* (`parse-3d-tile-tables.js`) sets `tile.featureTableJson.BATCH_LENGTH`
|
|
32
|
+
* and `tile.batchTableJson` directly on the SAME parsed-content object
|
|
33
|
+
* `.gltf` lives on (confirmed by reading the parser itself — no
|
|
34
|
+
* intermediate class wraps it). Same single-feature-only restriction as
|
|
35
|
+
* the glTF path, same reasoning: `BATCH_LENGTH === 1` is unambiguous,
|
|
36
|
+
* `BATCH_LENGTH > 1` needs per-vertex `_BATCHID` resolution this layer
|
|
37
|
+
* doesn't attempt.
|
|
38
|
+
* 3. 3D Tiles 1.1's OWN (non-glTF) metadata mechanism — tileset/group/tile/
|
|
39
|
+
* content-level entities declared directly in tileset.json as plain
|
|
40
|
+
* inline JSON (no binary property-table decode needed at this
|
|
41
|
+
* granularity, unlike the glTF one — verified against a real tileset.json,
|
|
42
|
+
* CesiumGS/3d-tiles-samples 1.1/MetadataGranularities). loaders.gl's
|
|
43
|
+
* `Tile3D` retains the raw tile node verbatim on `.header`, and
|
|
44
|
+
* `Tileset3D` retains the raw parsed tileset.json on `.tileset`, so this
|
|
45
|
+
* reads entirely off already-retained data.
|
|
46
|
+
*/
|
|
47
|
+
interface MetadataEntity {
|
|
48
|
+
class?: string;
|
|
49
|
+
properties?: Record<string, unknown>;
|
|
50
|
+
}
|
|
51
|
+
interface GltfPropertyTable {
|
|
52
|
+
class?: string;
|
|
53
|
+
count: number;
|
|
54
|
+
/** Decoded column data. NOT numeric-only — STRING properties decode to `string[]`, which is what most BIM classification fields are. */
|
|
55
|
+
properties?: Record<string, {
|
|
56
|
+
data?: ArrayLike<unknown>;
|
|
57
|
+
}>;
|
|
58
|
+
}
|
|
59
|
+
export interface GltfExtStructuralMetadata {
|
|
60
|
+
schema?: {
|
|
61
|
+
classes?: Record<string, {
|
|
62
|
+
name?: string;
|
|
63
|
+
}>;
|
|
64
|
+
};
|
|
65
|
+
propertyTables?: GltfPropertyTable[];
|
|
66
|
+
}
|
|
67
|
+
interface GltfMeshFeaturesId {
|
|
68
|
+
attribute?: number;
|
|
69
|
+
/** Present instead of `attribute` for "feature ID by texture coordinates" sets — see resolvePrimitiveFeatureIdSet. */
|
|
70
|
+
texture?: {
|
|
71
|
+
index?: number;
|
|
72
|
+
texCoord?: number;
|
|
73
|
+
channels?: number[];
|
|
74
|
+
};
|
|
75
|
+
propertyTable?: number;
|
|
76
|
+
data?: ArrayLike<number>;
|
|
77
|
+
}
|
|
78
|
+
export interface GltfPrimitiveLike {
|
|
79
|
+
extensions?: {
|
|
80
|
+
EXT_mesh_features?: {
|
|
81
|
+
featureIds?: GltfMeshFeaturesId[];
|
|
82
|
+
};
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Finds the `EXT_mesh_features` feature-ID set selected by a `_FEATURE_ID_N`
|
|
87
|
+
* name (the manifest's `feature-id-property`, default `_FEATURE_ID_0`) on one
|
|
88
|
+
* glTF primitive. Returns null when the primitive carries no usable feature
|
|
89
|
+
* IDs (a plain, non-BIM mesh — the common case until real IFC-derived content
|
|
90
|
+
* is loaded).
|
|
91
|
+
*
|
|
92
|
+
* `EXT_mesh_features` defines THREE ways to carry feature IDs, and N selects
|
|
93
|
+
* across all of them:
|
|
94
|
+
*
|
|
95
|
+
* - **By vertex** (`attribute: N` -> the `_FEATURE_ID_N` vertex attribute).
|
|
96
|
+
* Matched by attribute number first, so an explicitly numbered set always
|
|
97
|
+
* wins over its position in the array.
|
|
98
|
+
* - **By texture** (`texture: {...}`, no `attribute`) — how photogrammetry
|
|
99
|
+
* classification datasets carry per-element IDs, since a photogrammetry
|
|
100
|
+
* mesh is one triangle soup with the classification painted on. These sets
|
|
101
|
+
* have no attribute number to match, so N falls back to selecting the Nth
|
|
102
|
+
* entry of the `featureIds` array — the same positional convention the
|
|
103
|
+
* extension's own `featureId_N` shader naming uses.
|
|
104
|
+
* - **By index** (neither field; the vertex's own index is its feature ID).
|
|
105
|
+
* loaders.gl leaves `data` empty for these, so they resolve to null here.
|
|
106
|
+
*
|
|
107
|
+
* CAVEAT for texture-backed sets: loaders.gl decodes them by sampling the
|
|
108
|
+
* feature-ID texture once PER VERTEX at that vertex's UV
|
|
109
|
+
* (`getPrimitiveTextureData`), not per fragment. The resulting IDs are
|
|
110
|
+
* therefore only as precise as the mesh is dense — a classification boundary
|
|
111
|
+
* that runs through the middle of a triangle is lost, and that triangle takes
|
|
112
|
+
* whichever ID its vertices sampled. Good enough to pick an element, not
|
|
113
|
+
* equivalent to a per-fragment lookup.
|
|
114
|
+
*
|
|
115
|
+
* Note both texture and index sets require the glTF to be loaded with
|
|
116
|
+
* `loadBuffers` (and, for textures, `loadImages`); without them loaders.gl
|
|
117
|
+
* leaves `data` undefined/empty and this returns null.
|
|
118
|
+
*/
|
|
119
|
+
export declare function resolvePrimitiveFeatureIdSet(primitive: GltfPrimitiveLike | undefined, featureIdProperty: string): {
|
|
120
|
+
data: ArrayLike<number>;
|
|
121
|
+
propertyTable?: number;
|
|
122
|
+
texture?: GltfMeshFeaturesId["texture"];
|
|
123
|
+
nullFeatureId?: number;
|
|
124
|
+
} | null;
|
|
125
|
+
/**
|
|
126
|
+
* Reads ONE property of a decoded property table as a column — one entry per
|
|
127
|
+
* feature ID, indexed by feature ID.
|
|
128
|
+
*
|
|
129
|
+
* This is the row-major counterpart to `resolveGltfFeatureRow`: the declarative
|
|
130
|
+
* `hide-features`/`ghost-features`/`isolate-features` attributes need to test
|
|
131
|
+
* a single field across EVERY feature at once, and building a full properties
|
|
132
|
+
* object per row just to read one key would allocate the whole table to answer
|
|
133
|
+
* a one-column question. Returns null when the table or property is absent.
|
|
134
|
+
*/
|
|
135
|
+
export declare function readPropertyColumn(structuralMetadata: GltfExtStructuralMetadata | undefined, propertyTableIndex: number | undefined, property: string): unknown[] | null;
|
|
136
|
+
/**
|
|
137
|
+
* The WHOLE property table as row objects, indexed by feature ID.
|
|
138
|
+
*
|
|
139
|
+
* Picking resolves one row at a time, which is right for a hover but useless
|
|
140
|
+
* for anything that needs the set: a legend listing every IfcClass, a category
|
|
141
|
+
* palette, a model tree. Those previously had to fetch a hand-built sidecar
|
|
142
|
+
* next to the tileset. This reads the same table the GLB already carries.
|
|
143
|
+
*/
|
|
144
|
+
export declare function extractPropertyTableRows(structuralMetadata: GltfExtStructuralMetadata | undefined, propertyTableIndex: number | undefined): Record<string, unknown>[] | null;
|
|
145
|
+
/**
|
|
146
|
+
* Resolves one feature's properties out of a specific decoded property
|
|
147
|
+
* table, by row index — the general case (Phase 2: `rowIndex` is the
|
|
148
|
+
* per-vertex feature ID; Phase 1's single-feature path below is just this
|
|
149
|
+
* with `rowIndex = 0` on a `count === 1` table). Returns null for an
|
|
150
|
+
* out-of-range table/row (defensive — a malformed or partially-loaded tile
|
|
151
|
+
* should degrade to "no properties," not throw during picking).
|
|
152
|
+
*/
|
|
153
|
+
export declare function resolveGltfFeatureRow(structuralMetadata: GltfExtStructuralMetadata | undefined, propertyTableIndex: number | undefined, rowIndex: number): {
|
|
154
|
+
properties: Record<string, unknown>;
|
|
155
|
+
class: string | null;
|
|
156
|
+
} | null;
|
|
157
|
+
export interface Tile3DPickMetadata {
|
|
158
|
+
/** Merged properties, most-specific source wins: glTF content / legacy batch table > 3D-Tiles content > tile > group > tileset. Empty object if nothing resolved. */
|
|
159
|
+
properties: Record<string, unknown>;
|
|
160
|
+
/** The class name of the most specific contributing source, or null if nothing resolved. */
|
|
161
|
+
class: string | null;
|
|
162
|
+
/** Best-effort identity (IFC GlobalId / gml:id / guid), or null — see findGuidLike. */
|
|
163
|
+
guid: string | null;
|
|
164
|
+
/** Unmerged, per-source view — the escape hatch. Deck.gl/loaders.gl shapes leak here deliberately, unlike `properties`/`class`. */
|
|
165
|
+
rawMetadata: {
|
|
166
|
+
tileset?: MetadataEntity;
|
|
167
|
+
group?: MetadataEntity;
|
|
168
|
+
tile?: MetadataEntity;
|
|
169
|
+
content?: MetadataEntity;
|
|
170
|
+
gltfProperties?: Record<string, unknown>;
|
|
171
|
+
gltfClass?: string;
|
|
172
|
+
batchTableProperties?: Record<string, unknown>;
|
|
173
|
+
batchTableClass?: string;
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Resolves whatever tile-granularity BIM metadata is available for a
|
|
178
|
+
* Tile3DLayer pick. `sourceTile` is `PickingInfo.sourceTile` (Tile3DLayer's
|
|
179
|
+
* own `getPickingInfo` always sets it, undefined for a miss) — callers
|
|
180
|
+
* should only invoke this once they know the pick landed on a
|
|
181
|
+
* `carriesTileset` layer. Returns null when nothing resolved (a tileset
|
|
182
|
+
* with no metadata at all — the common case today, since this is new).
|
|
183
|
+
*/
|
|
184
|
+
export declare function resolveTile3DPickMetadata(sourceTile: unknown): Tile3DPickMetadata | null;
|
|
185
|
+
export {};
|
package/dist/version.d.ts
CHANGED
|
@@ -36,6 +36,17 @@ export interface WidgetImpl {
|
|
|
36
36
|
*/
|
|
37
37
|
neverHides?: boolean;
|
|
38
38
|
render(ctx: RuntimeContext, host: WidgetHost): void | Promise<void>;
|
|
39
|
+
/**
|
|
40
|
+
* Optional teardown, called by <om-widget> when the element is REMOVED for
|
|
41
|
+
* real — an author delete, or an SPA unmounting the whole <om-map> — never
|
|
42
|
+
* on the transient disconnect of a slot/fold/cluster reparent (om-widget
|
|
43
|
+
* distinguishes the two; see its disconnectedCallback). For releasing
|
|
44
|
+
* resources the document would otherwise pin past the widget's life: blob
|
|
45
|
+
* URLs, external listeners, large cached tables held in `host.state`.
|
|
46
|
+
* May fire again after a re-connect if the widget is removed a second
|
|
47
|
+
* time, so implementations must tolerate repeat calls.
|
|
48
|
+
*/
|
|
49
|
+
destroy?(host: WidgetHost): void;
|
|
39
50
|
}
|
|
40
51
|
/** Clusterable (spec: "Widget Layout Manager") — the type is compact AND the author didn't opt out. One predicate for the cluster pass AND the validation lint. */
|
|
41
52
|
export declare function isClusterableWidget(el: Element): boolean;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
interface ClashItem {
|
|
2
|
+
id: number;
|
|
3
|
+
ifcClass: string;
|
|
4
|
+
name: string;
|
|
5
|
+
box: number[];
|
|
6
|
+
}
|
|
7
|
+
/** Rows to clash candidates, dropping ignored classes and anything with no real box. */
|
|
8
|
+
export declare function clashCandidates(rows: Record<string, unknown>[]): ClashItem[];
|
|
9
|
+
/**
|
|
10
|
+
* Axis-aligned overlap, with a tolerance that SHRINKS both boxes.
|
|
11
|
+
*
|
|
12
|
+
* The strict comparison matters: two slabs meeting exactly face to face share a
|
|
13
|
+
* plane, and reporting that would mean every floor clashes with every wall
|
|
14
|
+
* standing on it. `tolerance` then demands a real interpenetration of that
|
|
15
|
+
* depth before a pair counts, which is how modelling noise gets tuned out.
|
|
16
|
+
*/
|
|
17
|
+
export declare function boxesOverlap(a: number[], b: number[], tolerance: number): boolean;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export declare function ensureStyles(root: ShadowRoot): void;
|
|
2
|
+
/** Every `<om-layer pick-features>` on a map — the BIM widgets' shared universe of pickable models. */
|
|
3
|
+
export declare function pickFeatureLayers(map: Element | null | undefined): Element[];
|
|
4
|
+
interface TreeNode {
|
|
5
|
+
/** Full prefix identifying this node — also its key in the visibility state. */
|
|
6
|
+
key: string;
|
|
7
|
+
label: string;
|
|
8
|
+
count: number;
|
|
9
|
+
/** Every concrete `spatialPath` value at or below this node. */
|
|
10
|
+
values: string[];
|
|
11
|
+
children: Map<string, TreeNode>;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Distinct spatial paths -> a nested tree, with counts aggregated upward.
|
|
15
|
+
*
|
|
16
|
+
* Depth is NOT uniform in real models: the bridge sample mixes 1-level and
|
|
17
|
+
* 5-level paths in one file, and a model can leave elements with no spatial
|
|
18
|
+
* container at all (700 of the clinic's 2,626). Both are represented rather
|
|
19
|
+
* than dropped — an element that vanishes from the tree is an element nobody
|
|
20
|
+
* can find again.
|
|
21
|
+
*/
|
|
22
|
+
export declare function buildSpatialTree(counts: Map<string, number>): TreeNode;
|
|
23
|
+
/**
|
|
24
|
+
* Writes an attribute only when it would actually change.
|
|
25
|
+
*
|
|
26
|
+
* These widgets re-render on the `layers`/`features` tokens and their writes
|
|
27
|
+
* mutate layers, so an unconditional write schedules the MutationObserver →
|
|
28
|
+
* reconcile → full parseManifest pass that produced it — on EVERY render,
|
|
29
|
+
* even when nothing changed. `setAttribute` notifies observers even when the
|
|
30
|
+
* value is identical, so equality has to be checked here rather than relied
|
|
31
|
+
* on.
|
|
32
|
+
*
|
|
33
|
+
* It also matters for size: an isolate list or a serialized feature-styles
|
|
34
|
+
* table spans every element and runs to tens of kilobytes, and re-parsing
|
|
35
|
+
* that on a loop would be felt.
|
|
36
|
+
*
|
|
37
|
+
* Shared with ifc-clash (imported from here — the dependency already flows
|
|
38
|
+
* that way for ensureStyles/pickFeatureLayers).
|
|
39
|
+
*/
|
|
40
|
+
export declare function setIfChanged(element: Element | null, name: string, value: string | null): void;
|
|
41
|
+
export {};
|