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