@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.
Files changed (54) hide show
  1. package/CHANGELOG.md +15 -1
  2. package/README.md +18 -6
  3. package/THIRD-PARTY-LICENSES.md +27 -0
  4. package/dist/{LercDecode.es-fbo6h5rt.js → LercDecode.es-CJnypw8j.js} +1 -1
  5. package/dist/{basemap-CprSan9q.js → basemap-DrQ0-eyR.js} +1 -1
  6. package/dist/cityjson-D_V5GY8b.js +332 -0
  7. package/dist/crs-Ciu7Xs7a.js +108 -0
  8. package/dist/crs.d.ts +50 -0
  9. package/dist/ctx.d.ts +8 -1
  10. package/dist/elements/om-map.d.ts +26 -2
  11. package/dist/elements/om-overlay.d.ts +1 -1
  12. package/dist/elements/om-widget.d.ts +4 -0
  13. package/dist/feature-colors.d.ts +41 -0
  14. package/dist/{geoparquet-Bm8pfxyo.js → geoparquet-Dix4lTNy.js} +1 -1
  15. package/dist/html-data.d.ts +1 -1
  16. package/dist/ifc-Dc-TdSJr.js +877 -0
  17. package/dist/ifc.d.ts +406 -0
  18. package/dist/{index-Bz-rbmhj.js → index-1UgNlfGR.js} +20892 -18886
  19. package/dist/{index-GOwMjqm6.js → index-BQMjW5w0.js} +1 -1
  20. package/dist/{index-DSRIZR81.js → index-B_1PPJgC.js} +1 -1
  21. package/dist/{index-CCQttJzO.js → index-CAuT5j9Y.js} +2 -2
  22. package/dist/{index-UzmNhuS0.js → index-CXPaeisL.js} +1 -1
  23. package/dist/index.d.ts +8 -0
  24. package/dist/layer-registry.d.ts +14 -0
  25. package/dist/layers/bim-layer.d.ts +104 -0
  26. package/dist/layers/feature-mesh-layer.d.ts +288 -0
  27. package/dist/layers/gltf-scene-walk.d.ts +55 -0
  28. package/dist/{lerc-CuifOkoA.js → lerc-CbTjQ7uI.js} +2 -2
  29. package/dist/meshopt_simplifier-Co6uRDDA.js +370 -0
  30. package/dist/onlymap.standalone.js +57996 -54706
  31. package/dist/onlymapjs.js +69 -67
  32. package/dist/programmatic.d.ts +22 -2
  33. package/dist/{raster-dysYIQ_k.js → raster-0b0nSHUh.js} +2 -2
  34. package/dist/{raster-pipeline-ZdCOGfqV.js → raster-pipeline-D8siq7-4.js} +1 -1
  35. package/dist/react/om-overlay.d.ts +9 -0
  36. package/dist/react.js +144 -144
  37. package/dist/runtime-core.d.ts +110 -3
  38. package/dist/selection.d.ts +33 -0
  39. package/dist/site-placement.d.ts +85 -0
  40. package/dist/testing.d.ts +6 -2
  41. package/dist/tile3d-metadata.d.ts +185 -0
  42. package/dist/version.d.ts +1 -1
  43. package/dist/widget-registry.d.ts +11 -0
  44. package/dist/widgets/ifc-clash.d.ts +18 -0
  45. package/dist/widgets/ifc.d.ts +41 -0
  46. package/dist/{zarr-OewK7k2K.js → zarr-DCYro_Vs.js} +79 -66
  47. package/docs/testing.md +2 -2
  48. package/llms.txt +3 -3
  49. package/onlymapjs.html-data.json +152 -0
  50. package/package.json +3 -1
  51. package/skills/onlymapjs/SKILL.md +4 -2
  52. package/skills/onlymapjs/references/react.md +1 -1
  53. package/skills/onlymapjs/references/syntax.md +298 -11
  54. package/dist/cityjson-urQeujQv.js +0 -407
package/dist/ifc.d.ts ADDED
@@ -0,0 +1,406 @@
1
+ /**
2
+ * In-browser IFC loader — parses an `.ifc` with web-ifc (WASM) and produces,
3
+ * IN MEMORY, exactly the artifacts the offline converter writes to disk: a
4
+ * glTF binary carrying per-element feature IDs, a 3D Tiles tileset pointing at
5
+ * it, a property table, and a crease/boundary outline overlay.
6
+ *
7
+ * A LAZY chunk, on the same terms as `cityjson.ts`: nothing here is reachable
8
+ * from the main bundle except through `loadIfc`'s dynamic import in
9
+ * `index.ts`, and web-ifc's ~2 MB WASM is fetched only when a page actually
10
+ * loads an IFC.
11
+ *
12
+ * This is a LOADER, not a converter — nothing is written out. Everything comes
13
+ * back as blob URLs, so `pick-features`, `feature-styles`, the declarative
14
+ * isolate/hide/ghost attributes and a PathLayer outline overlay all work
15
+ * unchanged: they cannot tell an uploaded model from a prepared tileset.
16
+ *
17
+ * Why blob URLs rather than feeding geometry to a layer directly: the whole
18
+ * 3D-Tiles path — tile traversal, `FeatureMeshLayer`'s per-primitive fan-out,
19
+ * picking, the style textures — already consumes a tileset URL. Handing it one
20
+ * reuses every bit of that instead of growing a second rendering path.
21
+ *
22
+ * web-ifc is MPL-2.0 and is NOT a package dependency. It is fetched from a CDN
23
+ * by default; `configureIfc({wasmPath})` points it at a self-hosted copy.
24
+ */
25
+ /** Point the loader at a self-hosted web-ifc build instead of the default CDN. */
26
+ export declare function configureIfc(options: {
27
+ wasmPath?: string;
28
+ }): void;
29
+ /** One row of the property table — one IFC element. */
30
+ export interface IfcFeature {
31
+ ifcClass: string;
32
+ name: string;
33
+ material: string;
34
+ container: string;
35
+ /**
36
+ * The element's full spatial ancestry, outermost first, joined by
37
+ * `SPATIAL_SEPARATOR` — e.g. `Default site / Building / 00 groundfloor`.
38
+ *
39
+ * `container` is only the element's IMMEDIATE spatial parent, which is a
40
+ * flat list, not a hierarchy. This is what a model tree navigates. Empty
41
+ * when the model has no spatial decomposition to walk.
42
+ */
43
+ spatialPath: string;
44
+ /** `IfcDoorType > Single-Flush 0915x2134` — the element's type object. */
45
+ typePath: string;
46
+ /** The system, zone or group the element is assigned to, including parent systems. */
47
+ systemPath: string;
48
+ /** `CCS > Ss > Ss_25_10_30` — the classification reference chain, walked via `ReferencedSource`. */
49
+ classificationPath: string;
50
+ netVolume: number;
51
+ }
52
+ /**
53
+ * Separator inside `spatialPath`. U+001F (UNIT SEPARATOR) exists for exactly
54
+ * this and cannot occur in an IFC label, so splitting is unambiguous — a
55
+ * readable delimiter like " / " would break on any storey actually named
56
+ * "Level 1 / Mezzanine".
57
+ */
58
+ export declare const SPATIAL_SEPARATOR = "\u001F";
59
+ export interface IfcModel {
60
+ /** Blob URL of a 3D Tiles tileset — assign to a `Tile3DLayer`'s `tileset`. */
61
+ tilesetUrl: string;
62
+ /** Blob URL of PathLayer rows tracing crease/boundary edges, or null when disabled. */
63
+ edgesUrl: string | null;
64
+ /** The property table, indexed BY feature ID — the same rows picking resolves. */
65
+ features: IfcFeature[];
66
+ /**
67
+ * The `loadOptions` a `Tile3DLayer` needs for this model — assign alongside
68
+ * `tilesetUrl`.
69
+ *
70
+ * `isTileset: true` is REQUIRED, not a tuning knob. Tiles3DLoader decides
71
+ * tileset-vs-tile with `context.url.indexOf('.json') !== -1`, and
72
+ * `Response.url` strips the fragment, so a blob URL never satisfies it and
73
+ * the tileset JSON gets parsed as a binary tile
74
+ * ("3DTileLoader: unknown type {\"as"). Forcing it is safe: loaders.gl
75
+ * overrides the flag per tile from the tile's own type
76
+ * (`isTileset: this.type === 'json'`), so tile CONTENT still parses normally.
77
+ */
78
+ loadOptions: Record<string, unknown>;
79
+ /**
80
+ * Where the model says it is: its own IfcSite position when it declares one.
81
+ *
82
+ * Feed this to the layer's `site-origin` rather than baking it in — see
83
+ * `readGeoreference` for why a declared position is so often an authoring
84
+ * tool's default rather than a survey.
85
+ */
86
+ lonLat: [number, number];
87
+ /** True when `lonLat` came from the model rather than the fallback. */
88
+ georeferenced: boolean;
89
+ /**
90
+ * The file's declared `IfcMapConversion.OrthogonalHeight`, RAW — undefined
91
+ * when the file never states one (the common case; distinct from a
92
+ * declared 0). No geoid/vertical-datum conversion is applied: absolute
93
+ * elevation only means something once the layer sits on a basemap that
94
+ * also has real elevation (`terrain`), and no correction turns a flat
95
+ * Z=0 basemap into one — feed this to `site-origin`'s elevation component
96
+ * only when terrain is active.
97
+ */
98
+ orthogonalHeight?: number;
99
+ /** Bearing for the layer's `site-heading`, in degrees clockwise from true north. */
100
+ heading: number;
101
+ /** Multiplier for the layer's `site-scale`; 1 unless IfcMapConversion says otherwise. */
102
+ scale: number;
103
+ /** Whether `heading` was read from the file or merely assumed — never conflate the two. */
104
+ headingSource: HeadingSource;
105
+ /** Which of the file's georeferencing routes supplied `lonLat` — a survey-grade transform, a declared site, or neither. */
106
+ originSource: OriginSource;
107
+ stats: {
108
+ elements: number;
109
+ primitives: number;
110
+ polylines: number;
111
+ bytes: number;
112
+ };
113
+ /** Milliseconds per phase — for finding where a slow load actually goes. */
114
+ timings: Record<string, number>;
115
+ /** Tile-space bounds (Z-up metres) and the offset applied — for diagnosing placement. */
116
+ bounds: {
117
+ lo: [number, number, number];
118
+ hi: [number, number, number];
119
+ centre: [number, number, number];
120
+ };
121
+ /**
122
+ * The model-space origin subtracted from every vertex, in the FILE's own
123
+ * coordinates. Feed it back as `LoadIfcOptions.origin` when loading another
124
+ * discipline model of the same building so the two share a frame.
125
+ */
126
+ origin: [number, number, number];
127
+ /** Frees the blob URLs. Call when swapping models — they are not garbage collected. */
128
+ revoke(): void;
129
+ }
130
+ export interface LoadIfcOptions {
131
+ onProgress?(message: string): void;
132
+ /** Used when the model declares no position of its own. */
133
+ fallbackLonLat?: [number, number];
134
+ /** Trace crease/boundary outlines (default true). Expensive on very large models. */
135
+ edges?: boolean;
136
+ /** Minimum angle between adjacent faces for a crease edge, in degrees. */
137
+ creaseDegrees?: number;
138
+ /**
139
+ * Shared model-space origin, in the file's own coordinates (tile axes:
140
+ * east, north, up). Pass the `origin` a previous `loadIfc` returned to keep
141
+ * two discipline models CO-REGISTERED — without it each model is centred on
142
+ * its own bounding box and they drift apart by the difference, which is
143
+ * precisely what makes a clash pass return nothing.
144
+ */
145
+ origin?: [number, number, number];
146
+ }
147
+ /** Minimal shape of the bits of web-ifc's API this module touches. */
148
+ interface IfcApiLike {
149
+ SetWasmPath(path: string, absolute?: boolean): void;
150
+ Init(): Promise<void>;
151
+ OpenModel(data: Uint8Array, settings?: Record<string, unknown>): number;
152
+ CloseModel(modelID: number): void;
153
+ GetLine(modelID: number, expressID: number, flatten?: boolean): Record<string, unknown>;
154
+ GetLineType(modelID: number, expressID: number): number;
155
+ GetNameFromTypeCode(type: number): string;
156
+ GetLineIDsWithType(modelID: number, type: number): {
157
+ size(): number;
158
+ get(i: number): number;
159
+ };
160
+ LoadAllGeometry(modelID: number): {
161
+ size(): number;
162
+ get(i: number): StreamedMesh;
163
+ };
164
+ GetGeometry(modelID: number, id: number): IfcGeometryLike;
165
+ GetVertexArray(ptr: number, size: number): Float32Array;
166
+ GetIndexArray(ptr: number, size: number): Uint32Array;
167
+ }
168
+ interface StreamedMesh {
169
+ expressID: number;
170
+ geometries: {
171
+ size(): number;
172
+ get(i: number): {
173
+ geometryExpressID: number;
174
+ color: {
175
+ x: number;
176
+ y: number;
177
+ z: number;
178
+ w: number;
179
+ };
180
+ flatTransformation: ArrayLike<number>;
181
+ };
182
+ };
183
+ }
184
+ interface IfcGeometryLike {
185
+ GetVertexData(): number;
186
+ GetVertexDataSize(): number;
187
+ GetIndexData(): number;
188
+ GetIndexDataSize(): number;
189
+ delete(): void;
190
+ }
191
+ /** The pinned hash a URL must satisfy, or null when it is not the default CDN's (self-hosted → deliberately unverified, see `PINNED_SHA256`). */
192
+ export declare function pinnedWebIfcSha256(url: string): string | null;
193
+ /**
194
+ * `IfcCompoundPlaneAngleMeasure` -> decimal degrees.
195
+ *
196
+ * IFC2x3 predates `IfcMapConversion`, so a site's position lives on
197
+ * `IfcSite.RefLatitude`/`RefLongitude` as (degrees, minutes, seconds,
198
+ * millionths-of-a-second). The sign rides on the leading non-zero component
199
+ * and applies to the whole measure — reading only the degrees puts a western
200
+ * longitude in the wrong place by several arc-minutes.
201
+ */
202
+ export declare function compoundAngleToDegrees(parts: unknown): number | null;
203
+ export type HeadingSource = "map-conversion" | "true-north" | "assumed";
204
+ /**
205
+ * The affine an `IfcMapConversion` (or its IFC2x3 / 4.3 equivalents) defines,
206
+ * kept so it can be applied to a REAL MODEL POINT rather than collapsed into a
207
+ * single coordinate.
208
+ *
209
+ * This is the difference between georeferencing a model and merely putting it
210
+ * near the right place. The conversion anchors the model's ORIGIN — (0,0,0) in
211
+ * its own coordinates — but a tileset is recentred on the geometry, and those
212
+ * are not the same point. MiniBIM-3.1's origin sits 32.7 m from its own
213
+ * centre, so un-projecting the eastings alone and dropping the model there
214
+ * lands it 32.7 m out with a perfectly plausible-looking result.
215
+ */
216
+ export interface MapConversionAffine {
217
+ /** EPSG code of the target projected CRS, or null when it could not be read. */
218
+ code: number | null;
219
+ eastings: number;
220
+ northings: number;
221
+ orthogonalHeight: number;
222
+ /** Model +X in the map grid, as (cos, sin). */
223
+ xAxisAbscissa: number;
224
+ xAxisOrdinate: number;
225
+ scale: number;
226
+ }
227
+ /**
228
+ * Model coordinates -> projected map coordinates.
229
+ *
230
+ * The spec's affine, applied in full: rotate by the grid axis, scale, then
231
+ * offset by the eastings/northings. Straight out of IfcMapConversion's own
232
+ * definition rather than an approximation of it.
233
+ */
234
+ export declare function mapConversionApply(a: MapConversionAffine, x: number, y: number): [number, number];
235
+ /**
236
+ * Assembles the affine a parsed `IfcMapConversion` line defines, from its
237
+ * already-unwrapped numeric attributes. Pulled out from `readGeoreference` so
238
+ * the one rule that matters is independently testable without booting web-ifc.
239
+ *
240
+ * `scale` is fixed at 1 — deliberately NOT the file's own `Scale` attribute.
241
+ * Per buildingSMART's definition, `IfcMapConversion.Scale` exists to convert
242
+ * a file's OWN declared length unit into the target CRS's when they differ
243
+ * ("used when the length unit... [is] not identical with the length unit
244
+ * established for this project... e.g. to convert feet into metres") — but
245
+ * web-ifc already returns every vertex normalised to metres regardless of
246
+ * what the file declares, so that conversion has already happened before any
247
+ * coordinate reaches this function. Taking the file's Scale at face value
248
+ * here — or worse, as a render-time `site-scale` — would apply it a second
249
+ * time: `geographic-referencing-utm.ifc` declares millimetres with
250
+ * Scale=1000 (mm -> m), and doing so rendered the whole model 1000x too
251
+ * large, a building-sized object turned into a kilometre-wide monument.
252
+ */
253
+ export declare function buildMapConversionAffine(params: {
254
+ epsg: number | null;
255
+ eastings: number;
256
+ northings: number;
257
+ orthogonalHeight: number;
258
+ xAxisAbscissa: number;
259
+ xAxisOrdinate: number;
260
+ }): MapConversionAffine;
261
+ /**
262
+ * `OrthogonalHeight` corrected for the tileset's own recentring — the
263
+ * vertical counterpart of un-projecting `mapConversionApply` AT `centre`
264
+ * rather than at the file's raw origin (see that comment at the call site
265
+ * below). The tileset's baked origin sits at the model's LOWEST vertex (or,
266
+ * federated, whatever point the first model shared), not at the file's own
267
+ * local Z=0 — which is what `OrthogonalHeight` is declared relative to.
268
+ * `recentredLocalHeight` is `centre[2]`: the model's ORIGINAL, pre-recentre
269
+ * local height at the point that becomes the tile origin. The vertex now
270
+ * AT that origin was at that local height before recentring, so its
271
+ * absolute elevation is `orthogonalHeight + recentredLocalHeight`, not
272
+ * `orthogonalHeight` alone — exactly as the 32.7 m horizontal precedent
273
+ * this mirrors (see `applying an IfcMapConversion` tests below) needed
274
+ * `centre[0]`/`centre[1]` folded into the un-projection, not the model's
275
+ * raw origin.
276
+ */
277
+ export declare function correctOrthogonalHeightForRecentring(orthogonalHeight: number, recentredLocalHeight: number): number;
278
+ /** Where `lonLat` came from — a survey-grade transform, a declared site, or nothing at all. */
279
+ export type OriginSource = "map-conversion" | "ifc-site" | "fallback";
280
+ /**
281
+ * UTM easting/northing -> lon/lat on WGS84 (the standard inverse series).
282
+ *
283
+ * Needed because `IfcMapConversion` states position in a PROJECTED CRS, so a
284
+ * properly georeferenced export — the case that actually matters on real
285
+ * projects — cannot be placed without un-projecting it. UTM covers the
286
+ * overwhelming majority of what `IfcProjectedCRS` names in practice
287
+ * (EPSG:326xx north / 327xx south); anything else is declined rather than
288
+ * approximated, because a wrong projection lands the model in a different
289
+ * country while looking perfectly plausible.
290
+ *
291
+ * Verified against a known control point (Sydney Opera House, zone 56S) to
292
+ * within 0.1 m.
293
+ */
294
+ export declare function utmToLonLat(easting: number, northing: number, zone: number, south: boolean): [number, number];
295
+ /** `EPSG:32633` -> `{zone: 33, south: false}`. Anything that is not a WGS84 UTM zone returns null. */
296
+ export declare function parseUtmCrs(name: string): {
297
+ zone: number;
298
+ south: boolean;
299
+ } | null;
300
+ /**
301
+ * Grid convergence at a point in a UTM zone: the bearing of GRID north
302
+ * clockwise from TRUE north, in degrees — γ = atan(tan(λ−λ₀)·sinφ), the
303
+ * standard spherical approximation (well under the ~1e-4° heading rounding in
304
+ * `readGeoreference` anywhere inside a zone's extent).
305
+ *
306
+ * The analytic counterpart of crs.ts's numerically-derived `gridConvergence`,
307
+ * with the SAME sign convention (that function unprojects a step of grid
308
+ * north and takes its true bearing; verified against it in ifc.test.ts). It
309
+ * exists for the same reason too: a model aligned to a UTM GRID is not
310
+ * aligned to true north, and `site-heading` is a true bearing. UTM zones are
311
+ * resolved analytically precisely so ANY zone works without proj4 or a
312
+ * bundled def — so the correction has to be analytic as well, or an
313
+ * EPSG:326xx/327xx file sits skewed (up to ~3° near a zone edge at high
314
+ * latitude) while an EPSG:25832 file, whose convergence rides the proj4
315
+ * branch, does not.
316
+ */
317
+ export declare function utmGridConvergence(lonDeg: number, latDeg: number, zone: number): number;
318
+ /**
319
+ * Un-projects an `IfcMapConversion` affine AT a real model point — the
320
+ * tileset's own anchor, `centre` — rather than at the raw origin the
321
+ * conversion names. Pulled out of `loadIfc` so the routing rule is
322
+ * independently testable without booting web-ifc.
323
+ *
324
+ * The routing mirrors `readGeoreference` exactly, and that symmetry IS the
325
+ * fix: WGS84 UTM codes go through the analytic `utmToLonLat` (any of the 120
326
+ * zones, either hemisphere, no proj4), everything else through the bundled
327
+ * CRS table. An earlier version sent every code to `projectedToLonLat`,
328
+ * which returns null for the UTM zones outside the bundled defs (e.g.
329
+ * EPSG:32656) — so exactly the files whose position had been resolved
330
+ * analytically had their re-anchor silently no-op, keeping the raw-origin
331
+ * position and reintroducing the ~32.7 m offset class this correction exists
332
+ * to remove.
333
+ *
334
+ * Returns null when neither route can un-project the code; the caller keeps
335
+ * its current position but must stop claiming survey-grade placement for it.
336
+ */
337
+ export declare function reanchorMapConversion(affine: MapConversionAffine, centreEast: number, centreNorth: number): Promise<[number, number] | null>;
338
+ /**
339
+ * Where the model says it is, which way it says north is, and at what scale.
340
+ *
341
+ * Three independent facts from three different places, every one optional:
342
+ *
343
+ * - POSITION — `IfcSite.RefLatitude`/`RefLongitude`. Read it, but do not trust
344
+ * it: an authoring tool's DEFAULT project location is indistinguishable from
345
+ * a surveyed one. Both prepared samples in this repo carry defaults (the
346
+ * clinic sits on Revit's Boston, the duplex on a Chicago city centre point),
347
+ * so the models land on occupied downtown blocks at an arbitrary rotation.
348
+ * That is the whole reason `site-origin` exists as an override.
349
+ * - HEADING — `IfcMapConversion.XAxisAbscissa`/`XAxisOrdinate` on a properly
350
+ * georeferenced export, otherwise `IfcGeometricRepresentationContext.
351
+ * TrueNorth`. Most IFC2x3 files carry neither, and then project north is
352
+ * ASSUMED to be true north. `headingSource` says which of the three
353
+ * happened, because "read from the file" and "assumed because the file was
354
+ * silent" should never look the same to a caller.
355
+ * - SCALE — `IfcMapConversion.Scale`.
356
+ *
357
+ * `IfcMapConversion.Eastings`/`Northings` are deliberately NOT used for
358
+ * position. They are in a projected CRS identified only by name on
359
+ * `IfcProjectedCRS`, and guessing a projection for it would put the model
360
+ * somewhere confidently wrong instead of admittedly approximate.
361
+ *
362
+ * Exported for tests: `api` is structurally typed, so a hand-rolled fake
363
+ * exercises the reading rules (convergence, unit handling, precedence)
364
+ * without booting web-ifc's WASM.
365
+ */
366
+ export declare function readGeoreference(api: IfcApiLike, modelID: number, fallbackLonLat: [number, number]): Promise<{
367
+ lonLat: [number, number];
368
+ georeferenced: boolean;
369
+ heading: number;
370
+ scale: number;
371
+ headingSource: HeadingSource;
372
+ originSource: OriginSource;
373
+ affine: MapConversionAffine | null;
374
+ /** True only when the file itself states `OrthogonalHeight` — absent defaults to 0 the same as a declared 0 would, so this is the one signal that tells the two apart. */
375
+ hasOrthogonalHeight: boolean;
376
+ }>;
377
+ /**
378
+ * Below this element count, tiling overhead isn't worth it: the whole model
379
+ * keeps today's plain single-tile treatment, verbatim (see `useGrid` in
380
+ * `loadIfc`). Existing small fixtures (the clinic, duplex, bridge) stay on
381
+ * this path and produce byte-identical output to before spatial tiling
382
+ * existed at all — the clinic alone has 2,626 elements, so this has to clear
383
+ * that with real headroom, not just be "a small number". See
384
+ * `ifc.test.ts`'s "spatial tiling threshold" describe block, which asserts
385
+ * this against the clinic's real committed element count directly — change
386
+ * this constant without re-checking that and it is exactly the mistake that
387
+ * test exists to catch.
388
+ */
389
+ export declare const GRID_TILING_THRESHOLD = 5000;
390
+ /**
391
+ * A feature's cell, keyed off its FIRST placed geometry's own placement
392
+ * origin (the matrix translation column) rather than a true centroid — cheap
393
+ * (no vertex scan needed before bucketing starts) and adequate: it is only a
394
+ * bucketing heuristic, not a visual clip, since each cell's own 3D-Tiles
395
+ * bounding box is always computed from the vertices actually inside it.
396
+ */
397
+ export declare function gridCellKey(east: number, north: number): string;
398
+ /**
399
+ * Parses IFC bytes into a renderable 3D Tiles model held entirely in memory.
400
+ *
401
+ * Assign `tilesetUrl` to a `Tile3DLayer`'s `tileset` and `edgesUrl` to a
402
+ * companion `PathLayer`'s `data`; `features` is the same property table
403
+ * picking resolves, for building legends and category lists.
404
+ */
405
+ export declare function loadIfc(source: ArrayBuffer | Uint8Array | Blob, options?: LoadIfcOptions): Promise<IfcModel>;
406
+ export {};