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