@thatopen/services 0.16.1 → 0.18.0

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.
@@ -428,6 +428,11 @@ declare class _CDEManager extends OBC.Component implements OBC.Transitionable<CD
428
428
  private _permissions;
429
429
  private _projectDataFolderId;
430
430
  private _appFolderIds;
431
+ /** In-flight folder ensures. Two concurrent first calls (user config and
432
+ * app config load in parallel) used to each miss the cache and create a
433
+ * second folder of the same name; the promise is the lock. */
434
+ private _projectDataFolderPromise;
435
+ private _appFolderPromises;
431
436
  private _excludedFolderIds;
432
437
  private _projectMembers;
433
438
  get projectMembers(): {
@@ -471,8 +476,20 @@ declare class _CDEManager extends OBC.Component implements OBC.Transitionable<CD
471
476
  getSelectedFiles(): CDEFile[];
472
477
  getActivity(fileId: string): CDEFileActivity[];
473
478
  updateFilesMetadata(fileIds: string[], updates: Record<string, string>): Promise<void>;
479
+ /** The context this component cannot work without. Reading or writing
480
+ * project data with no context used to resolve with null / the empty
481
+ * string while persisting NOTHING - a call indistinguishable from
482
+ * success, which cost an integrator days (reported 2026-10-01). A
483
+ * missing context is a setup problem and must say so. */
484
+ private _requireCtx;
474
485
  readProjectData(app: string, filename: string): Promise<any>;
475
486
  writeProjectData(app: string, filename: string, data: unknown): Promise<string>;
487
+ /** Of several same-named folders, always the oldest. Duplicates exist in
488
+ * real projects (the backend had no unique index and this class had no
489
+ * lock), and `loadFolders` used to pick the LAST root match while the
490
+ * ensure path picked the FIRST: reads and writes could land in different
491
+ * folders of the same name. ObjectIds sort by creation time as strings. */
492
+ private static _oldest;
476
493
  private _ensureProjectDataFolder;
477
494
  private _ensureAppFolder;
478
495
  private _loadPermissions;
@@ -949,6 +966,1040 @@ export type FileList = InstanceType<typeof _FileList>;
949
966
  */
950
967
  export const FileList = { uuid: 'b0b5e2a2-0b3a-4a6b-8b1c-0b1c4a6b8b1c' } as typeof _FileList & { uuid: 'b0b5e2a2-0b3a-4a6b-8b1c-0b1c4a6b8b1c' };
951
968
 
969
+ /** A point-layer vertex whose Z gives the surface height at that place. */
970
+ export interface HeightNode {
971
+ lon: number;
972
+ lat: number;
973
+ z: number;
974
+ }
975
+ /**
976
+ * One orthometric height per vertex, in the same nesting as the geometry's
977
+ * coordinates: `number` for a Point, `number[]` for a LineString/MultiPoint/
978
+ * ring, `number[][]` for a MultiLineString/Polygon, and so on.
979
+ */
980
+ export type HeightTree = number | HeightTree[];
981
+ /**
982
+ * Which rule gave a line end its height (see `resolveLineEnds`): 1 = its own
983
+ * property, 2 = node surface height minus depth, 3 = copied from the other end
984
+ * (pipe drawn level), 4 = node surface height (no depth available).
985
+ */
986
+ export type EndRule = 1 | 2 | 3 | 4;
987
+ export interface FeatureHeights {
988
+ /**
989
+ * "placed": `heights` holds the per-vertex heights. "flat": draw this
990
+ * feature with the legacy flat behaviour. "skipped": it cannot be placed
991
+ * and must not be drawn.
992
+ */
993
+ status: "placed" | "flat" | "skipped";
994
+ heights?: HeightTree;
995
+ /** "attributes" lines only — the rule that resolved each end. */
996
+ startRule?: EndRule;
997
+ endRule?: EndRule;
998
+ }
999
+ export interface LayerHeights {
1000
+ /** True when the whole layer must be drawn with the legacy flat behaviour. */
1001
+ fallback: boolean;
1002
+ /** One entry per input feature, same order. Empty when `fallback`. */
1003
+ features: FeatureHeights[];
1004
+ /** Number of "skipped" features. */
1005
+ skipped: number;
1006
+ /** How many line ends each rule resolved ("attributes" layers). */
1007
+ ruleCounts: Record<EndRule, number>;
1008
+ }
1009
+ /** A value read from a property or Z is MISSING when it is not a finite number or is exactly 0. */
1010
+ export declare function isMissingHeight(value: unknown): boolean;
1011
+ /**
1012
+ * Horizontal distance in metres between two lon/lat pairs — equirectangular
1013
+ * approximation, plenty for the sub-metre comparisons and line-length weights
1014
+ * it is used for here.
1015
+ */
1016
+ export declare function horizontalDistance(lon1: number, lat1: number, lon2: number, lat2: number): number;
1017
+ /**
1018
+ * Spatial hash over nodes: cells are twice the snap distance wide, so any node
1019
+ * within the snap distance of a query lies in the 3×3 block of cells around
1020
+ * it — a lookup costs the same at 2,000 nodes as at 50,000.
1021
+ */
1022
+ export declare class NodeIndex {
1023
+ private readonly _snapDistance;
1024
+ private readonly _cells;
1025
+ private readonly _cellSize;
1026
+ private readonly _lonScale;
1027
+ constructor(nodes: HeightNode[], _snapDistance: number);
1028
+ private _cell;
1029
+ private _key;
1030
+ /** The nearest node within the snap distance of (lon, lat), or null. */
1031
+ nearest(lon: number, lat: number): HeightNode | null;
1032
+ }
1033
+ /**
1034
+ * Resolves the heights of a line's two ends (rules 1–4, see `EndRule`).
1035
+ * Both ends get rules 1–2 first, so rule 3 never copies a value that itself
1036
+ * came from rule 3/4. Returns null when an end ends up with no height at all.
1037
+ */
1038
+ export declare function resolveLineEnds(properties: GeoJSON.GeoJsonProperties, start: [number, number], end: [number, number], config: VectorLayerHeightConfig, index: NodeIndex | null): {
1039
+ start: number;
1040
+ end: number;
1041
+ startRule: EndRule;
1042
+ endRule: EndRule;
1043
+ } | null;
1044
+ /**
1045
+ * Resolves the orthometric height of every vertex of every feature in a
1046
+ * layer, following the rules documented on `VectorLayerHeightConfig` ("geometry"
1047
+ * and "attributes"). Emits at most ONE `console.warn` per call (the fallback,
1048
+ * or the skipped-feature summary), never one per vertex/feature.
1049
+ */
1050
+ export declare function resolveLayerHeights(features: GeoJSON.Feature[], config: VectorLayerHeightConfig, nodes?: HeightNode[], layerId?: string): LayerHeights;
1051
+
1052
+ /**
1053
+ * Pixels either side of a fat line's own stroke (`Line2`) that still count as
1054
+ * a hit — the strokes are only a couple of pixels wide, so without it a click
1055
+ * has to land on the exact pixel row of the line. three adds
1056
+ * `raycaster.params.Line2.threshold` to the material's full `linewidth` and
1057
+ * then tests against HALF of the sum, hence the doubling in
1058
+ * `pickVectorFeature()`.
1059
+ */
1060
+ export declare const VECTOR_PICK_TOLERANCE_PX = 6;
1061
+ /** What a pick found: which feature of which layer, and where along the ray. */
1062
+ export interface VectorPickHit {
1063
+ layerId: string;
1064
+ /** Index in the layer's `geojson.features`. */
1065
+ featureIndex: number;
1066
+ /** Distance from the ray origin (the camera, for a perspective camera). */
1067
+ distance: number;
1068
+ point: THREE.Vector3;
1069
+ }
1070
+ /**
1071
+ * Nearest vector-layer feature under a pointer, or null.
1072
+ *
1073
+ * `objects` are the roots `VectorLayers` built (each, or one of its
1074
+ * descendants, carries `userData.{layerId, featureIndex}`) — raycast directly
1075
+ * rather than through a scene, since they may live in the world's scene or in
1076
+ * the private deferred-mode one. `ndc` is the pointer in normalised device
1077
+ * coordinates; `size` is the renderer's pixel size, which fat lines need both
1078
+ * to set their `LineMaterial.resolution` and to turn
1079
+ * {@link VECTOR_PICK_TOLERANCE_PX} into world units.
1080
+ */
1081
+ export declare function pickVectorFeature(objects: THREE.Object3D[], camera: THREE.Camera, ndc: THREE.Vector2, size: THREE.Vector2): VectorPickHit | null;
1082
+
1083
+ /**
1084
+ * Per-layer style — `simplestyle-spec` property names, see
1085
+ * docs/adrs/vector-layer-style-property-naming.md for why. Layer-level only
1086
+ * (applies to every feature in the layer), not per-feature.
1087
+ */
1088
+ export interface VectorLayerStyle {
1089
+ stroke?: string;
1090
+ "stroke-width"?: number;
1091
+ fill?: string;
1092
+ "fill-opacity"?: number;
1093
+ }
1094
+ /** Per-layer placement options (not styling). */
1095
+ export interface VectorLayerOptions {
1096
+ /**
1097
+ * Height of every feature relative to the origin, in metres (negative =
1098
+ * below ground, e.g. buried utilities). Default 0.
1099
+ */
1100
+ elevation?: number;
1101
+ /**
1102
+ * Opt-in per-feature heights (see `VectorLayerHeightConfig`). When set and
1103
+ * resolvable, each vertex is placed at its own height + the project's geoid
1104
+ * undulation instead of flat at `elevation`. Absent = legacy flat drawing.
1105
+ */
1106
+ height?: VectorLayerHeightConfig;
1107
+ /** "attributes" heights only — surface nodes (a line end snaps to the nearest one for its surface height). */
1108
+ nodes?: HeightNode[];
1109
+ }
1110
+ /**
1111
+ * One feature's geometry in scene coordinates, for 2D consumers (the
1112
+ * minimap). `rings` holds a single vertex for points, the vertices for
1113
+ * lines, and outer ring + holes for polygons (multi-geometries are split
1114
+ * into one feature per part).
1115
+ */
1116
+ export interface VectorMapFeature {
1117
+ type: "point" | "line" | "polygon";
1118
+ rings: THREE.Vector3[][];
1119
+ /** Index in the layer's `geojson.features` — the same identity a 3D pick yields, shared by every part of a multi-geometry. */
1120
+ featureIndex: number;
1121
+ }
1122
+ /** A loaded layer as seen by 2D consumers. */
1123
+ export interface VectorMapLayer {
1124
+ id: string;
1125
+ style: {
1126
+ stroke: string;
1127
+ strokeWidth: number;
1128
+ fill?: string;
1129
+ fillOpacity: number;
1130
+ };
1131
+ features: VectorMapFeature[];
1132
+ }
1133
+ export declare const VECTOR_HOVER_COLOR = "#FFC107";
1134
+ export declare const VECTOR_SELECTION_COLOR = "#00E5FF";
1135
+ /**
1136
+ * Payload of `VectorLayers.onFeatureSelected`: the selected feature's identity
1137
+ * plus its attribute-table row. `properties` is the feature's own
1138
+ * `properties` object, untouched (keys keep whatever the source file has).
1139
+ */
1140
+ export interface VectorFeatureSelection {
1141
+ layerId: string;
1142
+ layerName: string;
1143
+ /** Index in the layer's `geojson.features`. */
1144
+ featureIndex: number;
1145
+ geometryType: string;
1146
+ properties: GeoJSON.GeoJsonProperties;
1147
+ }
1148
+ /**
1149
+ * Internal submodule of `GISManager` for vector GIS layers (shapefile/GeoJSON —
1150
+ * shapefiles are converted to GeoJSON at ingestion time, see docs/adrs). Kept as
1151
+ * a plain internal class rather than a separate `OBC.Component`: `GISManager`
1152
+ * already covers "everything GIS", not just the basemap, so this stays behind
1153
+ * `gisManager.vectorLayers` instead of its own public component with its own
1154
+ * `components.get(...)` entry point.
1155
+ *
1156
+ * Positions features using the same georeferencing math `ReorientationPlugin`
1157
+ * uses to align the 3D Tiles (`Ellipsoid.getObjectFrame()`, WGS84 lat/lon/
1158
+ * height/azimuth -> a rigid ECEF<->scene-local transform), computed directly
1159
+ * from `GISManager`'s own origin params rather than read off the tileset's
1160
+ * `matrixWorld` — see `geoToWorld()` for why: that transform is a pure
1161
+ * function of the 4 origin parameters (confirmed by reading
1162
+ * `ReorientationPlugin.transformLatLonHeightToOrigin`, which touches nothing
1163
+ * tileset-specific besides which `group`/`ellipsoid` object to write into),
1164
+ * so vector layers don't need to wait for a tileset to exist or load, and
1165
+ * stay correct even when `GISManager.snapToGround()` changes the origin's
1166
+ * height independently of any tileset event.
1167
+ */
1168
+ export declare class VectorLayers {
1169
+ world?: OBC.World;
1170
+ getOrigin?: () => {
1171
+ latitude: number;
1172
+ longitude: number;
1173
+ height: number;
1174
+ rotation: number;
1175
+ };
1176
+ getGeoidUndulation?: () => number;
1177
+ private static readonly _POINT_RADIUS;
1178
+ private _occlusionScene;
1179
+ /** Called once the deferred scene exists, so the owner registers its pass. */
1180
+ ensurePass?: () => void;
1181
+ /** Fires whenever layers are added, removed or rebuilt (e.g. origin change). */
1182
+ readonly onLayersChanged: OBC.Event<void>;
1183
+ /**
1184
+ * Fires with the newly selected feature, or null when the selection is
1185
+ * cleared. Not fired when the already-selected feature is selected again.
1186
+ */
1187
+ readonly onFeatureSelected: OBC.Event<VectorFeatureSelection | null>;
1188
+ getLayerName?: (id: string) => string | undefined;
1189
+ private _selected;
1190
+ private _hovered;
1191
+ private readonly _highlighted;
1192
+ private readonly _layers;
1193
+ private readonly _mapLayers;
1194
+ /** Loaded layers' geometry for 2D consumers (see `VectorMapFeature`). */
1195
+ get mapLayers(): VectorMapLayer[];
1196
+ private readonly _layerSources;
1197
+ get hasLayers(): boolean;
1198
+ /**
1199
+ * Draws every loaded layer into the renderer's current target, with fat
1200
+ * lines sized for a `width`×`height` pixel target (their width is in
1201
+ * pixels of it). Used by `GISManager`'s deferred pass; also works in
1202
+ * composer mode (objects drawn one by one). The minimap doesn't use this —
1203
+ * it draws `mapLayers` as live 2D vectors instead.
1204
+ */
1205
+ render(renderer: THREE.WebGLRenderer, camera: THREE.Camera, width: number, height: number): void;
1206
+ /**
1207
+ * @param id When given, tracks everything this call adds under that id so
1208
+ * `removeLayer(id)` can clean it up later — calling `addLayer` again with an
1209
+ * id already in use replaces it (removes the old one first), rather than
1210
+ * duplicating it. Without an id, nothing is tracked and there is nothing to
1211
+ * remove later (matches the previous, untracked behaviour).
1212
+ */
1213
+ addLayer(geojson: GeoJSON.FeatureCollection, style?: VectorLayerStyle, id?: string, options?: VectorLayerOptions): void;
1214
+ /** Removes and disposes everything the given `id`'s `addLayer()` call added. No-op if unknown/already removed. */
1215
+ removeLayer(id: string): void;
1216
+ private _dropLayer;
1217
+ /**
1218
+ * Nearest feature under a pointer (NDC), or null — see
1219
+ * `pickVectorFeature()`. Costs nothing when no layer is loaded.
1220
+ */
1221
+ pickFeature(ndc: THREE.Vector2): VectorPickHit | null;
1222
+ /** Selects (and highlights) one feature, replacing any previous selection. No event if it is already the selected one. */
1223
+ selectFeature(layerId: string, featureIndex: number): void;
1224
+ /** The current selection as the `onFeatureSelected` payload (null when nothing is selected) — for consumers created after the event fired. */
1225
+ get selection(): VectorFeatureSelection | null;
1226
+ /** Drops the selection and its highlight. No-op (no event) when nothing is selected. */
1227
+ clearSelection(): void;
1228
+ /** The currently selected / hovered feature (null = none) — read by the 2D map to draw the same states as 3D. */
1229
+ get selectedFeature(): {
1230
+ layerId: string;
1231
+ featureIndex: number;
1232
+ } | null;
1233
+ get hoveredFeature(): {
1234
+ layerId: string;
1235
+ featureIndex: number;
1236
+ } | null;
1237
+ /** Scene-space box of everything built for one feature (null if it isn't loaded), at its real resolved heights. */
1238
+ getFeatureBounds(layerId: string, featureIndex: number): THREE.Box3 | null;
1239
+ /** Hover-highlights one feature (null = none). Does not touch the selection and fires no event. */
1240
+ setHoveredFeature(hit: {
1241
+ layerId: string;
1242
+ featureIndex: number;
1243
+ } | null): void;
1244
+ /** Top-level objects built for one feature (empty if the layer/feature isn't loaded). */
1245
+ private _featureObjects;
1246
+ private _paint;
1247
+ isLayerLoaded(id: string): boolean;
1248
+ /**
1249
+ * Rebuilds every currently loaded layer's geometry from its cached
1250
+ * geojson/style, using the current `getOrigin()` — the fix for
1251
+ * loaded-layer-geometry-freezing-at-load-time: `addLayer()` only reads
1252
+ * `getOrigin()` once, at build time, so an origin change (gizmo drag,
1253
+ * lat/lon/height/rotation inputs, `snapToGround()`) otherwise never moves
1254
+ * already-loaded layers. Callers should debounce this — see
1255
+ * `GISManager.setPosition()` — since it rebuilds real geometry, not just
1256
+ * repositions existing objects.
1257
+ */
1258
+ reprojectAll(): void;
1259
+ /** Scene-space bounding box of everything `id`'s `addLayer()` call added — `null` if the id is unknown or nothing was actually added. */
1260
+ getLayerBounds(id: string): THREE.Box3 | null;
1261
+ private _disposeObject;
1262
+ private _resolveTargetScene;
1263
+ /**
1264
+ * WGS84 lat/lon/height (degrees, metres above the ellipsoid) -> scene-local
1265
+ * position, computed straight from `GISManager`'s origin params — the exact
1266
+ * same math `ReorientationPlugin.transformLatLonHeightToOrigin` runs against
1267
+ * `tiles.group.matrix` (`Ellipsoid.getObjectFrame()` for the origin's own
1268
+ * lat/lon/height/azimuth -> ECEF<->local transform, inverted), just without
1269
+ * needing a `TilesRenderer` to hold the intermediate result. No tileset
1270
+ * required, and always current — including immediately after
1271
+ * `GISManager.snapToGround()` changes the origin height, with nothing to
1272
+ * resync.
1273
+ */
1274
+ geoToWorld(lat: number, lon: number, height?: number): THREE.Vector3;
1275
+ /**
1276
+ * lon/lat (GeoJSON coordinate order) -> scene position, X/Z only — Y is the
1277
+ * layer's `elevation` (relative to the origin). This is the legacy flat
1278
+ * path, used by every layer that doesn't opt in to per-feature heights:
1279
+ * `geoToWorld`'s real Y depends on the gap between the origin's height and
1280
+ * each vertex's own height, which produced large, confusing vertical
1281
+ * offsets for raw file Z values — so for these layers per-vertex heights
1282
+ * are ignored and the whole layer sits flat at its `elevation`. Layers with
1283
+ * a `height` config go through `_toScenePosition()` instead.
1284
+ */
1285
+ private _toSceneVector;
1286
+ /**
1287
+ * One vertex's scene position: with a resolved orthometric `height`, the
1288
+ * full `geoToWorld()` result for `height + undulation` (all three
1289
+ * components, Y included); without one, the legacy flat `_toSceneVector()`.
1290
+ */
1291
+ private _toScenePosition;
1292
+ /** Renderer's current pixel size — `LineMaterial` needs it to size `linewidth` correctly. */
1293
+ private _resolution;
1294
+ private _buildFeatureObject;
1295
+ private _buildMapFeatures;
1296
+ private _buildPoint;
1297
+ private _buildLine;
1298
+ /**
1299
+ * Surface (`THREE.ShapeGeometry`, triangulated — handles non-convex rings)
1300
+ * when `fill` is set, plus the outline for every ring either way. Built in
1301
+ * local 2D (x, z) then rotated flat: `ShapeGeometry` lives in the XY plane,
1302
+ * and rotating +90° around X maps local (x, y, 0) -> scene (x, 0, y), which
1303
+ * lines up with `_toSceneVector`'s (x, 0, z) exactly; the layer's
1304
+ * `elevation` is re-applied afterwards as a plain vertical translation.
1305
+ * With per-vertex `heights` the outlines follow them, while the (planar)
1306
+ * surface sits at the average resolved height of the outer ring.
1307
+ */
1308
+ private _buildPolygonSurface;
1309
+ }
1310
+
1311
+ export interface GISManagerState {
1312
+ apiToken: string;
1313
+ assetId: string;
1314
+ latitude: number;
1315
+ longitude: number;
1316
+ height: number;
1317
+ rotation: number;
1318
+ tilesVisible: boolean;
1319
+ gizmoVisible: boolean;
1320
+ tilesOffsetEast: number;
1321
+ tilesOffsetNorth: number;
1322
+ tilesOffsetUp: number;
1323
+ /** See-through factor for the tiles (1 = opaque) — see `GISManager.setTilesOpacity`. */
1324
+ tilesOpacity: number;
1325
+ }
1326
+ /** One entry in the persisted vector-layer manifest — metadata only, not the geometry itself. */
1327
+ export interface GISVectorLayerConfig {
1328
+ id: string;
1329
+ name: string;
1330
+ fileId: string;
1331
+ /**
1332
+ * File format: `"shapefile"` (a zip with .shp/.dbf/.prj…) or `"geojson"`. When
1333
+ * missing or unrecognised the downloaded bytes decide (zip signature →
1334
+ * shapefile, leading `{` → GeoJSON).
1335
+ */
1336
+ type: string;
1337
+ style: VectorLayerStyle;
1338
+ /** Opt-in per-feature heights (see `VectorLayerHeightConfig`); absent = flat. */
1339
+ height?: VectorLayerHeightConfig;
1340
+ }
1341
+ /** How a layer's features get their height. Absent = legacy behaviour: the whole layer is drawn flat. */
1342
+ export interface VectorLayerHeightConfig {
1343
+ /**
1344
+ * "geometry": each vertex uses its own Z (third GeoJSON coordinate).
1345
+ * "attributes": lines get their start/end heights from feature properties.
1346
+ */
1347
+ source: "geometry" | "attributes";
1348
+ /** "attributes" only — property holding the height of a line's first vertex. */
1349
+ startProperty?: string;
1350
+ /** "attributes" only — property holding the height of a line's last vertex. */
1351
+ endProperty?: string;
1352
+ /** "attributes" only — property holding the depth below the surface at the first vertex. */
1353
+ startDepthProperty?: string;
1354
+ /** "attributes" only — property holding the depth below the surface at the last vertex. */
1355
+ endDepthProperty?: string;
1356
+ /** "attributes" only — ids of point layers (in this same manifest) whose Z gives the surface height at a line end. */
1357
+ nodeLayers?: string[];
1358
+ /** "attributes" only — max horizontal distance (m) between a line end and a node for them to count as the same place. Default 0.5. */
1359
+ nodeSnapDistance?: number;
1360
+ }
1361
+ /**
1362
+ * Shape of `{GISManager.uuid}.json`, the project-storage file
1363
+ * `GISManager.init()` reads. Origin lives in `ProjectManager`'s own record
1364
+ * instead (the platform's canonical project data) — this file is only the
1365
+ * vector-layer manifest.
1366
+ */
1367
+ export interface GISPersistedConfig {
1368
+ layers: GISVectorLayerConfig[];
1369
+ /** Small local East/North/Up fine-tune offset (metres) for Google 3D
1370
+ * Tiles — see `GISManager.setTilesOffset`'s own doc comment. Optional:
1371
+ * absent means "no offset saved yet", same as `{0,0,0}`. */
1372
+ tilesOffset?: {
1373
+ east: number;
1374
+ north: number;
1375
+ up: number;
1376
+ };
1377
+ }
1378
+
1379
+ /** Calls `visit` with every [lon, lat, …] vertex of a GeoJSON geometry (all supported types, collections included). */
1380
+ export declare function forEachVertex(geometry: GeoJSON.Geometry | null, visit: (coord: number[]) => void): void;
1381
+ /**
1382
+ * Horizontal (scene X/Z) bounding box of every vertex of every given feature
1383
+ * collection, each vertex taken through `project(lat, lon)` (the real
1384
+ * `geoToWorld`). Heights are irrelevant here and left at 0. Vertices with
1385
+ * non-finite coordinates are ignored; null when nothing usable is left.
1386
+ */
1387
+ export declare function layersBoundsXZ(collections: GeoJSON.FeatureCollection[], project: (lat: number, lon: number) => THREE.Vector3): THREE.Box3 | null;
1388
+ /** The square, in scene X/Z, that a minimap capture covers. */
1389
+ export interface MinimapExtent {
1390
+ centerX: number;
1391
+ centerZ: number;
1392
+ /** Half the side of the square, in metres. */
1393
+ half: number;
1394
+ }
1395
+ /**
1396
+ * Extra room around the content's extent, as a FRACTION of its half-size (not
1397
+ * metres), so the outermost features don't sit exactly on the image edge.
1398
+ */
1399
+ export declare const MINIMAP_EXTENT_MARGIN = 0.05;
1400
+ /**
1401
+ * The capture square for the vector layers' box (world space): centred on its
1402
+ * X/Z centre, with half the larger X/Z span as its half-size (the image stays
1403
+ * square), plus `margin`. With nothing usable — no box, an empty or non-finite
1404
+ * one, or content with no horizontal size at all — falls back to
1405
+ * `fallbackHalf` metres around the origin.
1406
+ */
1407
+ export declare function computeMinimapExtent(box: THREE.Box3 | null | undefined, fallbackHalf: number, margin?: number): MinimapExtent;
1408
+
1409
+ /**
1410
+ * What to do when the Highlighter's "select" style highlights something:
1411
+ * - "undo-bim": the highlight belongs to a click that a vector feature just
1412
+ * won (it fires inside that click's short window) — take the BIM highlight
1413
+ * back and keep the vector selection;
1414
+ * - "clear-vector": a genuine BIM selection (viewer, model tree, objects
1415
+ * panel, …) — the vector selection must go;
1416
+ * - "none": nothing to do (an empty highlight outside the window).
1417
+ */
1418
+ export type BimHighlightAction = "undo-bim" | "clear-vector" | "none";
1419
+ export declare function bimHighlightAction(insideVectorWinWindow: boolean, bimSelectionEmpty: boolean): BimHighlightAction;
1420
+ /** Whether a Highlighter selection map (`modelId → Set<localId>`) selects nothing. */
1421
+ export declare function isModelIdMapEmpty(map: Record<string, Set<number>> | null | undefined): boolean;
1422
+
1423
+ /**
1424
+ * What the minimap needs from `GISManager`, provided as a private hook object
1425
+ * (the minimap never reaches into `GISManager` itself).
1426
+ */
1427
+ export interface MinimapSource {
1428
+ /**
1429
+ * Gets the Google GIS tiles ready to be drawn from `camera` (registers it
1430
+ * with the streaming renderer and waits until the content it sees has
1431
+ * loaded).
1432
+ */
1433
+ prepare(camera: THREE.OrthographicCamera, resolution: number): Promise<void>;
1434
+ /** Draws the tiles into the renderer's CURRENT render target (a fresh, transparent one). */
1435
+ draw(renderer: THREE.WebGLRenderer, camera: THREE.OrthographicCamera): void;
1436
+ /** Undoes whatever `prepare` set up. Always called, even if `prepare` threw. */
1437
+ cleanup(camera: THREE.OrthographicCamera): void;
1438
+ /**
1439
+ * Live opacity (0..1) the map applies when drawing the captured image — read
1440
+ * on every redraw, so it can change without recapturing.
1441
+ */
1442
+ opacity(): number;
1443
+ /** Horizontal world box of ALL the project's vector layers (null when there is none). */
1444
+ getVectorLayersBox(): Promise<THREE.Box3 | null>;
1445
+ }
1446
+ /**
1447
+ * Result of a capture: the image plus what's needed to map world ↔ image.
1448
+ */
1449
+ export interface MinimapCapture {
1450
+ /** Top-down, north-up image of the GIS tiles (canvas pixels, sRGB, transparent elsewhere). */
1451
+ image: HTMLCanvasElement;
1452
+ /** Live opacity (0..1) to draw `image` with. */
1453
+ opacity: () => number;
1454
+ /** Side of the image, in pixels. */
1455
+ size: number;
1456
+ /** Copy of the camera the image was taken with, for world ↔ map conversion. */
1457
+ camera: THREE.OrthographicCamera;
1458
+ }
1459
+ /**
1460
+ * A marker drawn over the minimap (an HTML overlay on the panel's canvas,
1461
+ * following zoom and pan; hidden while there is no capture).
1462
+ */
1463
+ export interface MinimapMarker {
1464
+ id: string;
1465
+ /** Scene coordinates. Read on every redraw, so a marker follows if this changes. */
1466
+ position: THREE.Vector3;
1467
+ label?: string;
1468
+ /**
1469
+ * Builds the marker's element. A FACTORY, not an element: a panel can be
1470
+ * destroyed and recreated (the user switching layouts), and each one needs
1471
+ * its own DOM node. A marker with one owns its click behaviour; without it
1472
+ * the panel draws a default badge with the label and reports clicks through
1473
+ * `onMarkerClicked`.
1474
+ */
1475
+ createElement?: () => HTMLElement;
1476
+ }
1477
+ /** What changed in the marker list — the panel just reconciles against `markers`. */
1478
+ export interface MinimapMarkersChange {
1479
+ kind: "added" | "updated" | "removed" | "cleared";
1480
+ /** The marker's id (absent for "cleared"). */
1481
+ id?: string;
1482
+ }
1483
+ /**
1484
+ * Internal submodule of `GISManager` for the 2D minimap, same pattern as
1485
+ * `VectorLayers` (a plain internal class behind `gisManager.minimap`, not its
1486
+ * own component). Baked top-down image: renders the Google GIS tiles once,
1487
+ * from an orthographic camera looking straight down at the middle of the
1488
+ * project's vector layers, into an in-memory image (never persisted). The UI
1489
+ * (`<top-map-panel>`) draws live overlays (vector layers, camera arrow, view
1490
+ * cone) on top of that static image, game-minimap style.
1491
+ *
1492
+ * North-up: scene axes follow the codebase's +X=West, +Y=Up, +Z=North
1493
+ * convention (origin rotation fixed at 0), so the camera's "up" is +Z. The
1494
+ * world ↔ map conversions go through the capture camera's own projection, so
1495
+ * they stay correct without hardcoding that convention anywhere else.
1496
+ */
1497
+ export declare class Minimap {
1498
+ private readonly _source;
1499
+ world?: OBC.World;
1500
+ /**
1501
+ * Fallback only: half the side of the captured square, in metres, centred on
1502
+ * the origin — used when the project has no usable vector layers. Otherwise
1503
+ * the area is the box of all of them.
1504
+ */
1505
+ halfExtent: number;
1506
+ /**
1507
+ * Side of the captured image, in pixels (clamped to the GPU's max texture
1508
+ * size). Fixed, so the map gets coarser as the site gets larger: the image
1509
+ * covers the whole data-driven extent. Scaling the image with the extent was
1510
+ * tried and was too slow to capture. The intended improvement is to recapture
1511
+ * only the visible area when the user zooms in on the map, so sharpness no
1512
+ * longer depends on the size of the site.
1513
+ */
1514
+ resolution: number;
1515
+ readonly onCaptured: OBC.Event<MinimapCapture>;
1516
+ /** Fires once per `addMarker` / `updateMarker` / `removeMarker` / `clearMarkers` that changed something. */
1517
+ readonly onMarkersChanged: OBC.Event<MinimapMarkersChange>;
1518
+ /** Fires with the id when the panel's DEFAULT marker (one without `createElement`) is clicked. */
1519
+ readonly onMarkerClicked: OBC.Event<string>;
1520
+ private readonly _markers;
1521
+ private _capture;
1522
+ private _capturing;
1523
+ constructor(_source: MinimapSource);
1524
+ get capture(): MinimapCapture | null;
1525
+ get capturing(): boolean;
1526
+ /** Every marker, by id (read-only). */
1527
+ get markers(): ReadonlyMap<string, MinimapMarker>;
1528
+ getMarker(id: string): MinimapMarker | undefined;
1529
+ /** Adds a marker, or replaces the one with the same id. */
1530
+ addMarker(marker: MinimapMarker): void;
1531
+ /** Changes some fields of a marker. Returns false (and fires nothing) if there is no such marker. */
1532
+ updateMarker(id: string, partial: Partial<Omit<MinimapMarker, "id">>): boolean;
1533
+ /** Returns false (and fires nothing) if there is no such marker. */
1534
+ removeMarker(id: string): boolean;
1535
+ clearMarkers(): void;
1536
+ /**
1537
+ * The area the next capture covers: the box of all the project's vector
1538
+ * layers (waits for them to be read as data), or `halfExtent` around the
1539
+ * origin without any.
1540
+ */
1541
+ getExtent(): Promise<MinimapExtent>;
1542
+ /** Renders the GIS tiles into a fresh image. */
1543
+ captureMap(): Promise<MinimapCapture>;
1544
+ /** World point → normalized image coords (0..1, origin top-left), or null without a capture. */
1545
+ worldToMap(point: THREE.Vector3): {
1546
+ x: number;
1547
+ y: number;
1548
+ } | null;
1549
+ /** Normalized image coords → world point on the horizontal plane at `height`. */
1550
+ mapToWorld(x: number, y: number, height?: number): THREE.Vector3 | null;
1551
+ private _render;
1552
+ dispose(): void;
1553
+ }
1554
+
1555
+ /** The file formats a vector layer can come in (the manifest entry's `type`). */
1556
+ export type VectorLayerFormat = "shapefile" | "geojson";
1557
+ /**
1558
+ * Which parser a downloaded layer file needs. The manifest's `type` decides
1559
+ * when it is recognised; otherwise the bytes do: a zip signature (`PK\x03\x04`)
1560
+ * means a shapefile, a first non-whitespace `{` means GeoJSON. Anything else
1561
+ * throws, with a message meant for the user.
1562
+ */
1563
+ export declare function detectLayerFormat(type: string | undefined, bytes: Uint8Array): VectorLayerFormat;
1564
+ type CrsKind = {
1565
+ kind: "lonlat";
1566
+ } | {
1567
+ kind: "reproject";
1568
+ from: string;
1569
+ } | {
1570
+ kind: "unsupported";
1571
+ name: string;
1572
+ };
1573
+ /**
1574
+ * Reads the legacy GeoJSON `crs` member's name. Understands the CRS84 URN,
1575
+ * EPSG URNs (with or without a version), `EPSG:<code>` and the OpenGIS http
1576
+ * form. EPSG:4326 is taken as lon/lat as-is: GDAL/QGIS write lon/lat even when
1577
+ * they label it 4326. Reprojected: EPSG:3857 and the UTM families (326xx/327xx
1578
+ * WGS84, 258xx ETRS89, 269xx NAD83), their definition built from the code.
1579
+ */
1580
+ export declare function parseCrsName(name: string): CrsKind;
1581
+ /**
1582
+ * Parses GeoJSON text into a `FeatureCollection` in WGS84 lon/lat (`z` kept).
1583
+ * The coordinate system comes from the legacy `crs` member; without one the
1584
+ * coordinates must already be lon/lat, which is checked. Throws a clear error
1585
+ * for an unsupported CRS, or projected coordinates that don't say which CRS
1586
+ * they are in. The legacy `crs` member is dropped from the result.
1587
+ */
1588
+ export declare function parseGeoJSONLayer(text: string): Promise<GeoJSON.FeatureCollection>;
1589
+
1590
+ /**
1591
+ * Streams Cesium Ion 3D Tiles (Google Photorealistic 3D Tiles by default) co-located
1592
+ * with the BIM model.
1593
+ *
1594
+ * Rendering path depends on `world.renderer.postproduction.mode`:
1595
+ * - DEFERRED: the deferred G-buffer capture pass only draws objects whose material
1596
+ * carries the engine's "capture-emission" marker (set on Fragments/BIM surface
1597
+ * materials only) — every other object, however opaque, is hidden for the
1598
+ * duration of the capture (confirmed directly from the engine's own
1599
+ * `SurfaceCapturePass` docs). So tiles are added to a private off-scene
1600
+ * `THREE.Scene` and drawn via `addOcclusionPass` (direct mode — opaque,
1601
+ * self-occluding, same as `PointCloudLoader`), which runs AFTER the deferred
1602
+ * composite and bypasses the G-buffer requirement entirely.
1603
+ * - COMPOSER: the EffectComposer path renders the real scene graph directly, no
1604
+ * special material marker needed, so tiles are added straight to
1605
+ * `world.scene.three` like any other mesh.
1606
+ *
1607
+ * The Cesium Ion API token is kept in memory only (no persistence yet) — see
1608
+ * docs/adrs for the pending decision on where project geo-location should live.
1609
+ */
1610
+ declare class _GISManager extends OBC.Component {
1611
+ static readonly uuid: "d8f3a1c2-6b4e-4f9a-8c1d-2e7b5a9f3c60";
1612
+ enabled: boolean;
1613
+ readonly onStateChanged: OBC.Event<GISManagerState>;
1614
+ readonly onLoadError: OBC.Event<string>;
1615
+ readonly onLayersLoaded: OBC.Event<GISVectorLayerConfig[]>;
1616
+ readonly onVectorLayerError: OBC.Event<{
1617
+ id: string;
1618
+ message: string;
1619
+ }>;
1620
+ readonly onOffsetDirtyChange: OBC.Event<boolean>;
1621
+ readonly onOffsetSaveComplete: OBC.Event<boolean>;
1622
+ world?: OBC.World;
1623
+ vectorLayersConfig: GISVectorLayerConfig[];
1624
+ private _client?;
1625
+ private _projectId?;
1626
+ private _appDataStore?;
1627
+ private _offsetDirty;
1628
+ get offsetDirty(): boolean;
1629
+ readonly vectorLayers: VectorLayers;
1630
+ /**
1631
+ * Fires when a click selects a vector feature (payload: its layer, index and
1632
+ * attribute row) and with null when the selection is cleared — re-exposed
1633
+ * from `vectorLayers` for consumers (the future properties panel).
1634
+ */
1635
+ readonly onFeatureSelected: OBC.Event<VectorFeatureSelection | null>;
1636
+ /**
1637
+ * Fires on every `selectVectorFeature(..., { focus: true })`, including a
1638
+ * re-select of the already-selected feature (which fires no
1639
+ * `onFeatureSelected`) — carries the scene-space sphere the 3D camera frames,
1640
+ * so other views (the 2D map) can focus the same neighbourhood.
1641
+ */
1642
+ readonly onFocusRequested: OBC.Event<{
1643
+ layerId: string;
1644
+ featureIndex: number;
1645
+ sphere: THREE.Sphere;
1646
+ }>;
1647
+ private _apiToken;
1648
+ private _assetId;
1649
+ private _latitude;
1650
+ private _longitude;
1651
+ private _height;
1652
+ private _rotation;
1653
+ private _tilesVisible;
1654
+ private _gizmoVisible;
1655
+ private _tilesOffsetEast;
1656
+ private _tilesOffsetNorth;
1657
+ private _tilesOffsetUp;
1658
+ private _tiles;
1659
+ private _reorientation;
1660
+ private _draco;
1661
+ private _occlusionScene;
1662
+ private _usingOcclusionPass;
1663
+ private _locationControls;
1664
+ private _controlPoint;
1665
+ private _dragStartMatrix;
1666
+ private _tilesOpacity;
1667
+ private _fadeTarget;
1668
+ private _fadeQuad;
1669
+ private _passRegistered;
1670
+ private _ensureOcclusionPass;
1671
+ private readonly _occlusion;
1672
+ private _ensureFadeTarget;
1673
+ private _disposeFade;
1674
+ /** Freezes the reference frame for the upcoming gesture — see `_onControlPointMoved`. */
1675
+ private readonly _onDragStart;
1676
+ /**
1677
+ * Fires on every pointer move during the drag (live relocation). Always
1678
+ * converts the TOTAL offset since the drag began (that's what
1679
+ * `_controlPoint.position` holds throughout) through the FROZEN
1680
+ * start-of-gesture matrix, never the live `tiles.group.matrixWorld` —
1681
+ * otherwise every event would re-apply the same growing total against a frame
1682
+ * already shifted by all earlier events, compounding into runaway motion.
1683
+ *
1684
+ * Immediately resets `_controlPoint.position` back to (0,0,0) afterwards, so
1685
+ * the gizmo reads as a joystick fixed at the origin — only the terrain visibly
1686
+ * moves. This is safe precisely because of the point above: TransformControls
1687
+ * computes each event's position from its own frozen drag-start reference plus
1688
+ * the raw mouse offset, never incrementally from the object's last value, so
1689
+ * it doesn't notice or care that we reset it out from under it.
1690
+ */
1691
+ private readonly _onControlPointMoved;
1692
+ /** Gesture is over — nothing left to commit (every event already did), just clear the frozen reference. */
1693
+ private readonly _onDragEnd;
1694
+ private readonly _minimapSource;
1695
+ /** The 2D minimap (a baked top-down image of the GIS tiles over the project's vector layers) — see `Minimap`. */
1696
+ readonly minimap: Minimap;
1697
+ private readonly _onBeforeUpdate;
1698
+ private readonly _onOriginChange;
1699
+ private readonly _onCrsChange;
1700
+ private static readonly _CLICK_MAX_MOVE_PX;
1701
+ private static readonly _BIM_CLEAR_WINDOW_MS;
1702
+ private _pickDown?;
1703
+ private _clearBimUntil;
1704
+ private _bimClearHandler?;
1705
+ /**
1706
+ * Wires the click and hover handling on the viewer canvas. Window-level
1707
+ * CAPTURE listeners filtered to the canvas, so they run before the
1708
+ * Highlighter's own canvas listeners for the same event (it selects BIM
1709
+ * elements on mouse up). The canvas lives inside `top-viewer`'s shadow root,
1710
+ * where an event observed at `window` is retargeted to the shadow host — so
1711
+ * the filter compares `composedPath()[0]` (the real origin), never
1712
+ * `event.target`. The canvas itself is looked up per event rather than
1713
+ * cached: the world can resolve before its renderer exists.
1714
+ *
1715
+ * "Nearest wins" between a vector feature and a BIM element: on mouse DOWN,
1716
+ * only if the raycast hits a vector feature, the BIM element under the same
1717
+ * pixel is picked with `FastModelPickers` (`getItemAt` + `getPointAt`, the
1718
+ * same GPU pick the Highlighter selects with) and the two camera distances
1719
+ * are compared; the camera doesn't move during a click, so the answer is
1720
+ * ready by mouse up. On mouse up, if the vector feature won it is selected
1721
+ * and the BIM selection that same click makes is undone (the Highlighter
1722
+ * can't be told to skip a click — disabling it for one event leaves its
1723
+ * internal pointer state stuck — so its highlight is cleared the moment it
1724
+ * fires). Otherwise the vector selection is cleared and the Highlighter
1725
+ * proceeds as always. Gaussian-splat and Google 3D tiles are not considered.
1726
+ */
1727
+ private _setupFeaturePicking;
1728
+ private readonly _onHighlighterSetup;
1729
+ private get _pickCanvas();
1730
+ /** True when the event really originated on the viewer canvas (see `_setupFeaturePicking`). */
1731
+ private _isOnCanvas;
1732
+ private _hoverPoint?;
1733
+ private _hoverFrame;
1734
+ private readonly _onHoverMove;
1735
+ private readonly _runHover;
1736
+ private readonly _clearHover;
1737
+ /** Vector layers loaded and the viewer in plain select mode (no modal tool). */
1738
+ private _canPick;
1739
+ private _ensureBimClearHandler;
1740
+ private _ndc;
1741
+ private _isPickable;
1742
+ private readonly _onPickDown;
1743
+ private readonly _onPickUp;
1744
+ /**
1745
+ * Horizontal scene-space box of ALL the project's vector layers (every
1746
+ * manifest entry, displayed or not), for the minimap extent — the only thing
1747
+ * that defines it. Layers are read as data through the shared per-layer
1748
+ * cache (so nothing downloads twice and nothing is added to the scene), then
1749
+ * projected with the CURRENT origin on every call, so an origin change never
1750
+ * leaves a stale box. A layer that fails to load is skipped with one warning.
1751
+ */
1752
+ private _vectorLayersBox;
1753
+ constructor(components: OBC.Components);
1754
+ /**
1755
+ * Reads `{GISManager.uuid}.json` from project storage (same pattern as
1756
+ * `ClashesManager`/`TopicsManager`'s own `init(client)` — a plain method
1757
+ * call, not `@lit/context`).
1758
+ * Restores the origin (lat/lon/height/rotation) by composition from
1759
+ * `ProjectManager` — the platform's canonical project record — instead of
1760
+ * duplicating it into GIS's own persisted file, plus the vector-layer
1761
+ * manifest (metadata only — id/name/fileId/type/style, NOT the geometry
1762
+ * itself) from GIS's own `{GISManager.uuid}.json`. If that layers file
1763
+ * doesn't exist yet in the project, leaves the layer list empty and does
1764
+ * NOT create one — that only happens later, on an explicit save.
1765
+ *
1766
+ * Assumes `ProjectManager.init(client)` has already run by the time this is
1767
+ * called — the caller must chain `GISManager.init()` after
1768
+ * the `ProjectManager.init()` promise resolves. If `GISManager` can ever be
1769
+ * initialized in a different order/context, `ProjectManager.origin.get()`
1770
+ * below would just read stale/undefined data silently — revisit this if
1771
+ * that assumption stops holding (e.g. by awaiting `ProjectManager.init`
1772
+ * here directly, if `GISManager` can no longer rely on caller ordering).
1773
+ *
1774
+ * No-op (never throws) if there's no project context, no matching file, or
1775
+ * the read fails for any reason — the app should always be usable with
1776
+ * defaults, storage is a progressive enhancement here, not a hard
1777
+ * dependency.
1778
+ */
1779
+ init(client: PlatformClient): Promise<void>;
1780
+ /** Whether loading a vector layer also frames the camera on it. Off for now. */
1781
+ fitCameraOnLayerLoad: boolean;
1782
+ /**
1783
+ * Downloads the given layer's shapefile `.zip` from project storage
1784
+ * (`vectorLayersConfig` entry's `fileId`), parses it with `shpjs.parseZip`
1785
+ * (dynamic import — same heavy-dependency convention as the rest of this
1786
+ * component), and renders it via `vectorLayers.addLayer(geojson, style,
1787
+ * id)`. The single entry point for "the user checked this layer's box" —
1788
+ * callers don't need to touch storage/shpjs/`VectorLayers` directly, and
1789
+ * don't need their own try/catch: failures are reported via
1790
+ * `onVectorLayerError`, mirroring `enableTiles()`/`onLoadError`.
1791
+ */
1792
+ loadVectorLayer(id: string): Promise<void>;
1793
+ private readonly _layerGeoJSON;
1794
+ private _getLayerGeoJSON;
1795
+ /**
1796
+ * Surface nodes for a layer whose heights come from attributes: every
1797
+ * Point/MultiPoint coordinate with a finite Z in the layers named by
1798
+ * `height.nodeLayers`. Those layers are only read as data here — nothing is
1799
+ * added to the scene and the panel's checkboxes are untouched. An unknown id
1800
+ * is warned about and skipped.
1801
+ */
1802
+ private _loadHeightNodes;
1803
+ private _downloadAndParseLayer;
1804
+ /**
1805
+ * Real-world vector data can easily sit tens of km from the origin (a whole
1806
+ * shapefile's extent, not just small hand-built test shapes) —
1807
+ * far outside the default camera framing (~87 units from origin). Frames
1808
+ * the camera on the layer's actual bounding box right after it loads, same
1809
+ * `CameraControls.fitToBox()` the point-cloud loader already uses for the
1810
+ * same reason, so newly loaded data doesn't just silently look like nothing
1811
+ * rendered.
1812
+ */
1813
+ private _fitCameraToLayer;
1814
+ private static readonly _FOCUS_MIN_RADIUS;
1815
+ /**
1816
+ * The single entry point for selecting a vector feature — the 3D click and
1817
+ * the 2D map's click both come through here, so they share one selection
1818
+ * state, one `onFeatureSelected` event and one camera travel. With `focus`,
1819
+ * the camera flies to the feature: `CameraControls.fitToSphere()` (animated)
1820
+ * on the bounding sphere of what `VectorLayers` built for it, at its real
1821
+ * resolved heights; the viewing direction is kept, only the target and the
1822
+ * distance change. Selecting the already-selected feature fires no event but
1823
+ * still focuses. Clearing stays `vectorLayers.clearSelection()`.
1824
+ */
1825
+ selectVectorFeature(layerId: string, featureIndex: number, options?: {
1826
+ focus?: boolean;
1827
+ }): void;
1828
+ private _focusVectorFeature;
1829
+ /** Removes a layer loaded via `loadVectorLayer` — the "user unchecked the box" counterpart. */
1830
+ unloadVectorLayer(id: string): void;
1831
+ private _firstWorld;
1832
+ get apiToken(): string;
1833
+ set apiToken(value: string);
1834
+ get assetId(): string;
1835
+ set assetId(value: string);
1836
+ get latitude(): number;
1837
+ get longitude(): number;
1838
+ get height(): number;
1839
+ get rotation(): number;
1840
+ get tilesVisible(): boolean;
1841
+ get gizmoVisible(): boolean;
1842
+ get tilesOffsetEast(): number;
1843
+ get tilesOffsetNorth(): number;
1844
+ get tilesOffsetUp(): number;
1845
+ get tilesOpacity(): number;
1846
+ /**
1847
+ * See-through factor for the tiles (0..1, 1 = opaque), mirrored by the
1848
+ * minimap's terrain image. In-memory only. Applies in DEFERRED mode (the
1849
+ * viewer's default); the composer path keeps the tiles opaque.
1850
+ */
1851
+ setTilesOpacity(opacity: number): void;
1852
+ toggleGizmo(): void;
1853
+ /** Manual geo-location input (sliders) — no IFC georeferencing read yet, see docs/adrs. */
1854
+ setPosition(latitude: number, longitude: number, height?: number, rotation?: number): void;
1855
+ /**
1856
+ * Small local fine-tune offset (metres, East/North/Up) layered ON TOP of
1857
+ * the origin-derived tiles position — a manual calibration knob for Google
1858
+ * Photorealistic 3D Tiles' own (independently known) absolute-positioning
1859
+ * inaccuracy, NOT a change to `ProjectManager.origin`/real georeferencing.
1860
+ * Re-derives the tiles position from the CURRENT origin fields + this
1861
+ * offset on every call (via `_reorientation`), so repeated calls replace
1862
+ * rather than accumulate.
1863
+ *
1864
+ * NOT auto-saved — marks `offsetDirty`/fires `onOffsetDirtyChange` instead,
1865
+ * same pattern `ProjectManager.origin` uses, so a caller driving this
1866
+ * continuously (the fine-tune sliders) doesn't write to the network on
1867
+ * every tick. `saveTilesOffset()` persists explicitly.
1868
+ */
1869
+ setTilesOffset(east: number, north: number, up: number): void;
1870
+ /**
1871
+ * Persists the current tiles fine-tune offset to `{GISManager.uuid}.json`
1872
+ * (loose in `__project_data`, alongside `ProjectManager`'s own file — see
1873
+ * `_appDataStore`), preserving whatever `layers` are already in there.
1874
+ * `save()` itself is fire-and-forget/debounced — `offsetDirty` only clears
1875
+ * (and `onOffsetSaveComplete` fires) once `_appDataStore.onSaveComplete`
1876
+ * confirms the write actually succeeded (wired in `init()`), not
1877
+ * optimistically here.
1878
+ */
1879
+ saveTilesOffset(): void;
1880
+ private _applyTilesOffset;
1881
+ private _reprojectDebounce?;
1882
+ private _scheduleReprojectLayers;
1883
+ /**
1884
+ * Casts a ray straight down from high above the recentered origin against the
1885
+ * currently loaded tile geometry to find the real ground elevation at
1886
+ * (latitude, longitude), then re-applies the reorientation using that as
1887
+ * `height` so the visible terrain/rooftop lands at world (0,0,0) instead of
1888
+ * wherever `height=0` (the bare WGS84 ellipsoid, which can be hundreds of
1889
+ * meters away from the real surface at elevated locations) happened to put it.
1890
+ *
1891
+ * Relies on the ENU tangent-plane guarantee `transformLatLonHeightToOrigin`
1892
+ * gives at the origin: after recentering at some height, "up" from that lat/lon
1893
+ * is exactly world +Y, and world Y directly corresponds to metres of height — so
1894
+ * a hit at world Y = `dy` means the real surface is `dy` metres higher than the
1895
+ * height currently used, and re-running the transform with
1896
+ * `height + dy` should bring it to Y ≈ 0. Only accurate near the origin (a few
1897
+ * km), which is exactly the regime this is used in.
1898
+ *
1899
+ * Tries straight down first, then straight up (content can end up above OR below
1900
+ * the origin depending on how far off `height=0` was from the real surface).
1901
+ *
1902
+ * Uses `TilesRenderer.raycast()` — 3d-tiles-renderer's own documented raycasting
1903
+ * entry point — instead of a generic `THREE.Raycaster.intersectObject(tiles.group)`.
1904
+ * The library keeps its own tile-activity bookkeeping (`activeTiles`,
1905
+ * `tile.traversal.used`, per-tile bounding volumes) separate from the plain
1906
+ * Object3D tree, and only that entry point knows how to walk it; a generic
1907
+ * scene-graph raycast against `tiles.group` silently finds nothing.
1908
+ *
1909
+ * Tile traversal (`markUsedTiles` in the engine's own source) bails out
1910
+ * immediately — without even descending into children — for any tile whose
1911
+ * bounding volume isn't in the frustum of AT LEAST ONE registered camera:
1912
+ * `if (!tile.traversal.inFrustum) return;`. That branch is never visited, let
1913
+ * alone loaded or marked active, regardless of `displayActiveTiles` (which only
1914
+ * affects the final visibility toggle of tiles that already got marked active —
1915
+ * a narrower case). So if the real viewport camera isn't looking anywhere near
1916
+ * the target point, nothing here ever loads and the raycast finds nothing no
1917
+ * matter what.
1918
+ *
1919
+ * `TilesRenderer` supports multiple simultaneously-registered cameras
1920
+ * (`cameras: Camera[]`), and a tile counts as "in frustum" if ANY of them sees
1921
+ * it. So we register a throwaway camera positioned high above the origin,
1922
+ * looking straight down — wide FOV and far plane so its frustum (a cone
1923
+ * widening downward, centred on the target column) covers a generous height
1924
+ * range around the origin regardless of whether the real surface turns out to
1925
+ * be above or below it — force one `update()` traversal with it registered,
1926
+ * then remove it again. The real viewport camera's own registration and
1927
+ * behaviour are untouched throughout.
1928
+ *
1929
+ * Returns false if no tile geometry was found (nothing to snap to) — caller
1930
+ * should retry once more of the tileset streams in. The first call after
1931
+ * moving to a new area may land approximately rather than exactly, since the
1932
+ * newly-forced-into-frustum content is still streaming in asynchronously in
1933
+ * the background — calling it again shortly after tends to refine it further.
1934
+ */
1935
+ snapToGround(): boolean;
1936
+ /**
1937
+ * `CesiumIonAuth.fetch()`/`refreshToken()` (see `3d-tiles-renderer/core/plugins/auth/CesiumIonAuth.js`)
1938
+ * only ever throws `"CesiumIonAuthPlugin: Failed to load data with error code {status}"`
1939
+ * on a non-OK HTTP response — no further detail. Map the common statuses to
1940
+ * something a user can actually act on; fall back to the raw message otherwise.
1941
+ */
1942
+ private _toFriendlyLoadError;
1943
+ /**
1944
+ * Sets everything up and resolves only once the FIRST real outcome is known —
1945
+ * either actual content loaded ("load-tileset") or the initial fetch (auth
1946
+ * against the token/asset) failed ("load-error") — instead of resolving the
1947
+ * instant the TilesRenderer is constructed, before any network activity has
1948
+ * even started. `tilesVisible`/state only flips to enabled on success; on
1949
+ * failure everything set up here is torn down again and the promise rejects,
1950
+ * so the caller (the panel) never has to un-render a half-enabled state.
1951
+ */
1952
+ enableTiles(): Promise<void>;
1953
+ disableTiles(): void;
1954
+ /** Tears down everything `enableTiles()` set up, without touching `_tilesVisible`. */
1955
+ private _teardownTiles;
1956
+ dispose(): void;
1957
+ private _trigger;
1958
+ }
1959
+
1960
+ /**
1961
+ * Streams Cesium Ion 3D Tiles (Google Photorealistic 3D Tiles by default) co-located
1962
+ * with the BIM model.
1963
+ *
1964
+ * Rendering path depends on `world.renderer.postproduction.mode`:
1965
+ * - DEFERRED: the deferred G-buffer capture pass only draws objects whose material
1966
+ * carries the engine's "capture-emission" marker (set on Fragments/BIM surface
1967
+ * materials only) — every other object, however opaque, is hidden for the
1968
+ * duration of the capture (confirmed directly from the engine's own
1969
+ * `SurfaceCapturePass` docs). So tiles are added to a private off-scene
1970
+ * `THREE.Scene` and drawn via `addOcclusionPass` (direct mode — opaque,
1971
+ * self-occluding, same as `PointCloudLoader`), which runs AFTER the deferred
1972
+ * composite and bypasses the G-buffer requirement entirely.
1973
+ * - COMPOSER: the EffectComposer path renders the real scene graph directly, no
1974
+ * special material marker needed, so tiles are added straight to
1975
+ * `world.scene.three` like any other mesh.
1976
+ *
1977
+ * The Cesium Ion API token is kept in memory only (no persistence yet) — see
1978
+ * docs/adrs for the pending decision on where project geo-location should live.
1979
+ */
1980
+ export type GISManager = InstanceType<typeof _GISManager>;
1981
+ /**
1982
+ * Streams Cesium Ion 3D Tiles (Google Photorealistic 3D Tiles by default) co-located
1983
+ * with the BIM model.
1984
+ *
1985
+ * Rendering path depends on `world.renderer.postproduction.mode`:
1986
+ * - DEFERRED: the deferred G-buffer capture pass only draws objects whose material
1987
+ * carries the engine's "capture-emission" marker (set on Fragments/BIM surface
1988
+ * materials only) — every other object, however opaque, is hidden for the
1989
+ * duration of the capture (confirmed directly from the engine's own
1990
+ * `SurfaceCapturePass` docs). So tiles are added to a private off-scene
1991
+ * `THREE.Scene` and drawn via `addOcclusionPass` (direct mode — opaque,
1992
+ * self-occluding, same as `PointCloudLoader`), which runs AFTER the deferred
1993
+ * composite and bypasses the G-buffer requirement entirely.
1994
+ * - COMPOSER: the EffectComposer path renders the real scene graph directly, no
1995
+ * special material marker needed, so tiles are added straight to
1996
+ * `world.scene.three` like any other mesh.
1997
+ *
1998
+ * The Cesium Ion API token is kept in memory only (no persistence yet) — see
1999
+ * docs/adrs for the pending decision on where project geo-location should live.
2000
+ */
2001
+ export const GISManager = { uuid: 'd8f3a1c2-6b4e-4f9a-8c1d-2e7b5a9f3c60' } as typeof _GISManager & { uuid: 'd8f3a1c2-6b4e-4f9a-8c1d-2e7b5a9f3c60' };
2002
+
952
2003
  /** One element mutation inside a revit-flow commit. */
953
2004
  export interface RevitFlowChange {
954
2005
  /** create → element added, update → geometry/data changed, delete → removed. */