@mappedin/dynamic-focus 6.0.1-beta.61 → 6.0.1-beta.62

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.
@@ -1,1175 +0,0 @@
1
- var __defProp = Object.defineProperty;
2
- var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
-
4
- // <define:process>
5
- var define_process_default = { env: { npm_package_version: "6.0.1-beta.61" } };
6
-
7
- // src/dynamic-focus.ts
8
- import { Floor as Floor3, getMultiFloorState as getMultiFloorState2 } from "@mappedin/mappedin-js";
9
-
10
- // ../packages/common/array-utils.ts
11
- function arraysEqual(arr1, arr2) {
12
- if (arr1 == null || arr2 == null) {
13
- return arr1 === arr2;
14
- }
15
- if (arr1.length !== arr2.length) {
16
- return false;
17
- }
18
- for (let i = 0; i < arr1.length; i++) {
19
- if (arr1[i] !== arr2[i]) {
20
- return false;
21
- }
22
- }
23
- return true;
24
- }
25
- __name(arraysEqual, "arraysEqual");
26
-
27
- // ../packages/common/pubsub.ts
28
- var PubSub = class {
29
- static {
30
- __name(this, "PubSub");
31
- }
32
- /**
33
- * @private
34
- * @internal
35
- */
36
- _subscribers = {};
37
- /**
38
- * @private
39
- * @internal
40
- */
41
- _destroyed = false;
42
- /**
43
- * @private
44
- * @internal
45
- */
46
- publish(eventName, data) {
47
- if (!this._subscribers || !this._subscribers[eventName] || this._destroyed) {
48
- return;
49
- }
50
- this._subscribers[eventName].forEach(function(fn) {
51
- if (typeof fn !== "function") {
52
- return;
53
- }
54
- fn(data);
55
- });
56
- }
57
- /**
58
- * Subscribe a function to an event.
59
- *
60
- * @param eventName An event name which, when fired, will call the provided
61
- * function.
62
- * @param fn A callback that gets called when the corresponding event is fired. The
63
- * callback will get passed an argument with a type that's one of event payloads.
64
- * @example
65
- * // Subscribe to the 'click' event
66
- * const handler = (event) => {
67
- * const { coordinate } = event;
68
- * const { latitude, longitude } = coordinate;
69
- * console.log(`Map was clicked at ${latitude}, ${longitude}`);
70
- * };
71
- * map.on('click', handler);
72
- */
73
- on(eventName, fn) {
74
- if (!this._subscribers || this._destroyed) {
75
- this._subscribers = {};
76
- }
77
- this._subscribers[eventName] = this._subscribers[eventName] || [];
78
- this._subscribers[eventName].push(fn);
79
- }
80
- /**
81
- * Unsubscribe a function previously subscribed with {@link on}
82
- *
83
- * @param eventName An event name to which the provided function was previously
84
- * subscribed.
85
- * @param fn A function that was previously passed to {@link on}. The function must
86
- * have the same reference as the function that was subscribed.
87
- * @example
88
- * // Unsubscribe from the 'click' event
89
- * const handler = (event) => {
90
- * console.log('Map was clicked', event);
91
- * };
92
- * map.off('click', handler);
93
- */
94
- off(eventName, fn) {
95
- if (!this._subscribers || this._subscribers[eventName] == null || this._destroyed) {
96
- return;
97
- }
98
- const itemIdx = this._subscribers[eventName].indexOf(fn);
99
- if (itemIdx !== -1) {
100
- this._subscribers[eventName].splice(itemIdx, 1);
101
- }
102
- }
103
- /**
104
- * @private
105
- * @internal
106
- */
107
- destroy() {
108
- this._destroyed = true;
109
- this._subscribers = {};
110
- }
111
- };
112
-
113
- // src/building.ts
114
- import { Facade, Floor, FloorStack } from "@mappedin/mappedin-js";
115
- var Building = class {
116
- static {
117
- __name(this, "Building");
118
- }
119
- __type = "building";
120
- #floorStack;
121
- #floorsById = /* @__PURE__ */ new Map();
122
- #floorsByElevation = /* @__PURE__ */ new Map();
123
- #floorsByElevationSorted = [];
124
- #mapView;
125
- #animationInProgress;
126
- defaultFloor;
127
- activeFloor;
128
- excluded = false;
129
- floorsAltitudesMap = /* @__PURE__ */ new Map();
130
- aboveGroundFloors = [];
131
- constructor(floorStack, mapView) {
132
- this.#floorStack = floorStack;
133
- this.#floorsById = new Map(floorStack.floors.map((floor) => [floor.id, floor]));
134
- this.#floorsByElevation = new Map(floorStack.floors.map((floor) => [floor.elevation, floor]));
135
- this.#floorsByElevationSorted = Array.from(this.#floorsByElevation.values()).sort(
136
- (a, b) => a.elevation - b.elevation
137
- );
138
- this.defaultFloor = floorStack.defaultFloor;
139
- this.activeFloor = this.defaultFloor;
140
- this.#mapView = mapView;
141
- this.aboveGroundFloors = this.#floorsByElevationSorted.filter((floor) => floor.elevation >= 0);
142
- const sortedSpaces = floorStack.facade?.spaces.map((space) => this.#mapView.getState(space)).sort((a, b) => a.altitude - b.altitude);
143
- if (sortedSpaces && sortedSpaces.length === this.aboveGroundFloors.length) {
144
- for (const idx in this.aboveGroundFloors) {
145
- const floor = this.aboveGroundFloors[idx];
146
- const space = sortedSpaces[idx];
147
- this.floorsAltitudesMap.set(floor.elevation, {
148
- altitude: space.altitude,
149
- effectiveHeight: space.height
150
- });
151
- }
152
- }
153
- }
154
- get id() {
155
- return this.#floorStack.id;
156
- }
157
- get name() {
158
- return this.#floorStack.name;
159
- }
160
- get floors() {
161
- return this.#floorStack.floors;
162
- }
163
- get floorStack() {
164
- return this.#floorStack;
165
- }
166
- get facade() {
167
- return this.#floorStack.facade;
168
- }
169
- get isCurrentFloorStack() {
170
- return this.id === this.#mapView.currentFloorStack.id;
171
- }
172
- get isIndoor() {
173
- const state = this.#mapView.getState(this.facade);
174
- return this.isCurrentFloorStack || state?.visible === false && state?.opacity === 0;
175
- }
176
- get canSetFloor() {
177
- return this.isIndoor || !this.excluded;
178
- }
179
- #maxElevation;
180
- get maxElevation() {
181
- if (this.#maxElevation == null) {
182
- this.#maxElevation = Math.max(...this.#floorsByElevation.keys());
183
- }
184
- return this.#maxElevation;
185
- }
186
- #minElevation;
187
- get minElevation() {
188
- if (this.#minElevation == null) {
189
- this.#minElevation = Math.min(...this.#floorsByElevation.keys());
190
- }
191
- return this.#minElevation;
192
- }
193
- has(f) {
194
- if (Floor.is(f)) {
195
- return this.#floorsById.has(f.id);
196
- }
197
- if (FloorStack.is(f)) {
198
- return f.id === this.#floorStack.id;
199
- }
200
- if (Facade.is(f)) {
201
- return f.id === this.facade.id;
202
- }
203
- return false;
204
- }
205
- getFloorByElevation(elevation) {
206
- return this.#floorsByElevation.get(elevation);
207
- }
208
- getNearestFloorByElevation(targetElevation) {
209
- const exactMatch = this.getFloorByElevation(targetElevation);
210
- if (exactMatch) {
211
- return exactMatch;
212
- }
213
- if (targetElevation >= 0) {
214
- if (targetElevation > this.maxElevation) {
215
- return this.#floorsByElevationSorted[this.#floorsByElevationSorted.length - 1];
216
- }
217
- for (let i = this.#floorsByElevationSorted.length - 1; i >= 0; i--) {
218
- const floor = this.#floorsByElevationSorted[i];
219
- if (floor.elevation <= targetElevation) {
220
- return floor;
221
- }
222
- }
223
- } else {
224
- if (targetElevation < this.minElevation) {
225
- return this.#floorsByElevationSorted[0];
226
- }
227
- for (const floor of this.#floorsByElevationSorted) {
228
- if (floor.elevation >= targetElevation) {
229
- return floor;
230
- }
231
- }
232
- }
233
- return void 0;
234
- }
235
- getHighestFloor() {
236
- return this.#floorsByElevationSorted[this.#floorsByElevationSorted.length - 1];
237
- }
238
- cancelAnimation() {
239
- if (this.#animationInProgress) {
240
- this.#animationInProgress.animation.cancel();
241
- this.#animationInProgress = void 0;
242
- }
243
- }
244
- async animateFacade(newState, options = { duration: 150 }) {
245
- const currentState = this.#animationInProgress?.state || this.#mapView.getState(this.facade);
246
- if (!currentState || !newState || currentState?.opacity === newState.opacity && currentState?.visible === newState.visible) {
247
- return { result: "completed" };
248
- }
249
- this.cancelAnimation();
250
- const { opacity, visible } = newState;
251
- this.#mapView.updateState(this.facade, {
252
- visible: true
253
- });
254
- if (opacity !== void 0) {
255
- this.#animationInProgress = {
256
- animation: this.#mapView.animateState(
257
- this.facade,
258
- {
259
- opacity
260
- },
261
- {
262
- duration: options.duration
263
- }
264
- ),
265
- state: newState
266
- };
267
- const result = await this.#animationInProgress.animation;
268
- this.#animationInProgress = void 0;
269
- if (result.result === "cancelled") {
270
- return result;
271
- }
272
- }
273
- if (visible === false) {
274
- this.#mapView.updateState(this.facade, {
275
- visible
276
- });
277
- }
278
- return { result: "completed" };
279
- }
280
- destroy() {
281
- this.cancelAnimation();
282
- this.#floorStack.floors.forEach((floor) => {
283
- this.#mapView.updateState(floor, {
284
- visible: floor.id === this.#mapView.currentFloor.id
285
- });
286
- });
287
- if (!this.has(this.#mapView.currentFloor)) {
288
- this.#mapView.updateState(this.facade, {
289
- visible: true,
290
- opacity: 1
291
- });
292
- }
293
- }
294
- };
295
-
296
- // src/dynamic-focus-scene.ts
297
- function getZoomState(zoomLevel, indoorThreshold, outdoorThreshold) {
298
- if (zoomLevel >= indoorThreshold) {
299
- return "in-range";
300
- }
301
- if (zoomLevel <= outdoorThreshold) {
302
- return "out-of-range";
303
- }
304
- return "transition";
305
- }
306
- __name(getZoomState, "getZoomState");
307
- function getFloorToShow(target, mode, elevation) {
308
- if (target.__type === "outdoors" && "floor" in target) {
309
- return target.floor;
310
- }
311
- const building = target;
312
- if (building.excluded) {
313
- return building.activeFloor;
314
- }
315
- switch (mode) {
316
- case "lock-elevation":
317
- return building.getFloorByElevation(elevation);
318
- case "nearest-elevation":
319
- return building.getNearestFloorByElevation(elevation);
320
- default:
321
- break;
322
- }
323
- return building.isIndoor ? building.activeFloor : building.defaultFloor;
324
- }
325
- __name(getFloorToShow, "getFloorToShow");
326
- function getFacadeState(facade, inFocus) {
327
- return {
328
- facade,
329
- state: {
330
- type: "facade",
331
- visible: !inFocus,
332
- opacity: inFocus ? 0 : 1
333
- }
334
- };
335
- }
336
- __name(getFacadeState, "getFacadeState");
337
- function getSingleBuildingState(floorStack, currentFloor, inFocus) {
338
- return {
339
- outdoorOpacity: 1,
340
- floorStates: floorStack.floors.map((f) => {
341
- const visible = f.id === currentFloor.id && inFocus;
342
- return {
343
- floor: f,
344
- state: {
345
- type: "floor",
346
- visible,
347
- geometry: {
348
- visible
349
- },
350
- labels: {
351
- enabled: visible
352
- },
353
- markers: {
354
- enabled: visible
355
- },
356
- footprint: {
357
- visible: false
358
- },
359
- occlusion: {
360
- enabled: false
361
- }
362
- }
363
- };
364
- })
365
- };
366
- }
367
- __name(getSingleBuildingState, "getSingleBuildingState");
368
- function getOutdoorOpacity(floorStackStates, mode) {
369
- if (!mode.includes("lock-elevation")) {
370
- return 1;
371
- }
372
- for (const state of floorStackStates) {
373
- if (state.outdoorOpacity >= 0 && state.outdoorOpacity <= 1) {
374
- return state.outdoorOpacity;
375
- }
376
- }
377
- return 1;
378
- }
379
- __name(getOutdoorOpacity, "getOutdoorOpacity");
380
- function getSetFloorTargetFromZoomState(buildings, outdoors, zoomState) {
381
- switch (zoomState) {
382
- case "in-range":
383
- return buildings[0]?.canSetFloor ? buildings[0] : void 0;
384
- case "out-of-range":
385
- return outdoors;
386
- case "transition":
387
- default:
388
- return void 0;
389
- }
390
- }
391
- __name(getSetFloorTargetFromZoomState, "getSetFloorTargetFromZoomState");
392
- function getBuildingStates(buildings, inViewSet, currentFloor, zoomState, mode, elevation, floorIdsInNavigation, floorStackIdsInNavigation, multiFloorView, getMultiFloorState3, dynamicBuildingInteriors) {
393
- const buildingStates = /* @__PURE__ */ new Map();
394
- for (const building of buildings) {
395
- const inCameraView = inViewSet.has(building.id);
396
- const showIndoor = shouldShowIndoor(
397
- building,
398
- currentFloor,
399
- zoomState === "in-range",
400
- inCameraView,
401
- mode,
402
- elevation,
403
- floorStackIdsInNavigation.includes(building.id),
404
- dynamicBuildingInteriors
405
- );
406
- const floorToShow = currentFloor.floorStack.id === building.id ? building.activeFloor : getFloorToShow(building, mode, elevation);
407
- const floorStackState = multiFloorView.enabled ? getMultiFloorState3(
408
- building.floors,
409
- floorToShow || building.activeFloor,
410
- multiFloorView?.floorGap,
411
- floorIdsInNavigation,
412
- building.floorsAltitudesMap
413
- ) : getSingleBuildingState(building.floorStack, floorToShow || building.activeFloor, showIndoor);
414
- const facadeState = getFacadeState(building.facade, showIndoor);
415
- buildingStates.set(building.id, {
416
- building,
417
- showIndoor,
418
- floorStackState,
419
- facadeState,
420
- inFocus: inCameraView,
421
- floorToShow
422
- });
423
- }
424
- return buildingStates;
425
- }
426
- __name(getBuildingStates, "getBuildingStates");
427
- function getBuildingAnimationType(building, showIndoor) {
428
- if (building.excluded) {
429
- return "none";
430
- }
431
- if (showIndoor) {
432
- return "indoor";
433
- }
434
- return "outdoor";
435
- }
436
- __name(getBuildingAnimationType, "getBuildingAnimationType");
437
- function shouldShowIndoor(building, currentFloor, inRange, inFocus, mode, elevation, inNavigation, dynamicBuildingInteriors) {
438
- if (building.id === currentFloor.floorStack.id) {
439
- return true;
440
- }
441
- if (building.excluded) {
442
- return false;
443
- }
444
- if (inNavigation === true) {
445
- return true;
446
- }
447
- if (!dynamicBuildingInteriors) {
448
- return currentFloor.floorStack.type !== "Outdoor";
449
- }
450
- if (!inRange) {
451
- return false;
452
- }
453
- if (!inFocus) {
454
- return false;
455
- }
456
- switch (mode) {
457
- case "lock-elevation":
458
- return building.getFloorByElevation(elevation) != null;
459
- default:
460
- break;
461
- }
462
- return true;
463
- }
464
- __name(shouldShowIndoor, "shouldShowIndoor");
465
- function shouldDeferSetFloor(trackedCameraElevation, currentCameraElevation, userInteracting) {
466
- return trackedCameraElevation !== currentCameraElevation && !userInteracting;
467
- }
468
- __name(shouldDeferSetFloor, "shouldDeferSetFloor");
469
-
470
- // src/types.ts
471
- var DYNAMIC_FOCUS_MODES = ["default-floor", "lock-elevation", "nearest-elevation"];
472
-
473
- // src/validation.ts
474
- import { Floor as Floor2, FloorStack as FloorStack2 } from "@mappedin/mappedin-js";
475
-
476
- // ../packages/common/Mappedin.Logger.ts
477
- var MI_ERROR_LABEL = "[MappedinJS]";
478
- function createLogger(name = "", { prefix = MI_ERROR_LABEL } = {}) {
479
- const label = `${prefix}${name ? `-${name}` : ""}`;
480
- const rnDebug = /* @__PURE__ */ __name((type, args) => {
481
- if (typeof window !== "undefined" && window.rnDebug) {
482
- const processed = args.map((arg) => {
483
- if (arg instanceof Error && arg.stack) {
484
- return `${arg.message}
485
- ${arg.stack}`;
486
- }
487
- return arg;
488
- });
489
- window.rnDebug(`${name} ${type}: ${processed.join(" ")}`);
490
- }
491
- }, "rnDebug");
492
- return {
493
- logState: define_process_default.env.NODE_ENV === "test" ? 3 /* SILENT */ : 0 /* LOG */,
494
- log(...args) {
495
- if (this.logState <= 0 /* LOG */) {
496
- console.log(label, ...args);
497
- rnDebug("log", args);
498
- }
499
- },
500
- warn(...args) {
501
- if (this.logState <= 1 /* WARN */) {
502
- console.warn(label, ...args);
503
- rnDebug("warn", args);
504
- }
505
- },
506
- error(...args) {
507
- if (this.logState <= 2 /* ERROR */) {
508
- console.error(label, ...args);
509
- rnDebug("error", args);
510
- }
511
- },
512
- // It's a bit tricky to prepend [MappedinJs] to assert and time because of how the output is structured in the console, so it is left out for simplicity
513
- assert(...args) {
514
- console.assert(...args);
515
- },
516
- time(label2) {
517
- console.time(label2);
518
- },
519
- timeEnd(label2) {
520
- console.timeEnd(label2);
521
- },
522
- setLevel(level) {
523
- if (0 /* LOG */ <= level && level <= 3 /* SILENT */) {
524
- this.logState = level;
525
- }
526
- }
527
- };
528
- }
529
- __name(createLogger, "createLogger");
530
- var Logger = createLogger();
531
- var Mappedin_Logger_default = Logger;
532
-
533
- // ../packages/common/math-utils.ts
534
- function clampWithWarning(x, lower, upper, warning) {
535
- if (x < lower || x > upper) {
536
- Mappedin_Logger_default.warn(warning);
537
- }
538
- return Math.min(upper, Math.max(lower, x));
539
- }
540
- __name(clampWithWarning, "clampWithWarning");
541
-
542
- // src/validation.ts
543
- function validateFloorForStack(floor, floorStack) {
544
- if (!Floor2.is(floor) || floor?.floorStack == null || !FloorStack2.is(floorStack)) {
545
- return false;
546
- }
547
- if (floor.floorStack.id !== floorStack.id) {
548
- Mappedin_Logger_default.warn(`Floor (${floor.id}) does not belong to floor stack (${floorStack.id}).`);
549
- return false;
550
- }
551
- return true;
552
- }
553
- __name(validateFloorForStack, "validateFloorForStack");
554
- function validateZoomThreshold(value, min, max, name) {
555
- return clampWithWarning(value, min, max, `${name} must be between ${min} and ${max}.`);
556
- }
557
- __name(validateZoomThreshold, "validateZoomThreshold");
558
-
559
- // src/logger.ts
560
- var Logger2 = createLogger("", { prefix: "[DynamicFocus]" });
561
-
562
- // src/outdoors.ts
563
- var Outdoors = class {
564
- static {
565
- __name(this, "Outdoors");
566
- }
567
- __type = "outdoors";
568
- #floorStack;
569
- #mapView;
570
- constructor(floorStack, mapView) {
571
- this.#floorStack = floorStack;
572
- this.#mapView = mapView;
573
- }
574
- get id() {
575
- return this.#floorStack.id;
576
- }
577
- get floorStack() {
578
- return this.#floorStack;
579
- }
580
- get floor() {
581
- return this.#floorStack.defaultFloor;
582
- }
583
- matchesFloorStack(floorStack) {
584
- return floorStack === this.#floorStack || floorStack === this.#floorStack.id;
585
- }
586
- #setVisible(visible) {
587
- for (const floor of this.#floorStack.floors) {
588
- this.#mapView.updateState(floor, {
589
- visible
590
- });
591
- }
592
- }
593
- show() {
594
- this.#setVisible(true);
595
- }
596
- hide() {
597
- this.#setVisible(false);
598
- }
599
- destroy() {
600
- if (!this.matchesFloorStack(this.#mapView.currentFloorStack)) {
601
- this.hide();
602
- }
603
- }
604
- };
605
- function getOutdoorFloorStack(floorStacks, buildingsSet) {
606
- const outdoorFloorStack = floorStacks.find((floorStack) => floorStack.type?.toLowerCase() === "outdoor");
607
- if (outdoorFloorStack) {
608
- return outdoorFloorStack;
609
- }
610
- const likelyOutdoorFloorStack = floorStacks.find(
611
- (floorStack) => floorStack.facade == null && !buildingsSet.has(floorStack.id)
612
- );
613
- if (likelyOutdoorFloorStack) {
614
- return likelyOutdoorFloorStack;
615
- }
616
- Logger2.warn("No good candidate for the outdoor floor stack was found. Using the first floor stack.");
617
- return floorStacks[0];
618
- }
619
- __name(getOutdoorFloorStack, "getOutdoorFloorStack");
620
-
621
- // src/constants.ts
622
- import { getMultiFloorState } from "@mappedin/mappedin-js";
623
- var DEFAULT_STATE = {
624
- indoorZoomThreshold: 18,
625
- outdoorZoomThreshold: 17,
626
- setFloorOnFocus: true,
627
- autoFocus: false,
628
- indoorAnimationOptions: {
629
- duration: 150
630
- },
631
- outdoorAnimationOptions: {
632
- duration: 150
633
- },
634
- autoAdjustFacadeHeights: false,
635
- mode: "default-floor",
636
- preloadFloors: true,
637
- customMultiFloorVisibilityState: getMultiFloorState,
638
- dynamicBuildingInteriors: true
639
- };
640
-
641
- // src/dynamic-focus.ts
642
- var DynamicFocus = class {
643
- static {
644
- __name(this, "DynamicFocus");
645
- }
646
- #pubsub;
647
- #mapView;
648
- #mapData;
649
- #state = DEFAULT_STATE;
650
- /**
651
- * The buildings that are currently in view, sorted by the order they are in the facades-in-view-change event.
652
- * While these will be within the camera view, these may not be in focus or showIndoor depending on the mode.
653
- */
654
- #sortedBuildingsInView = [];
655
- /**
656
- * The buildings that are currently in view, as a set of building ids.
657
- */
658
- #buildingIdsInViewSet = /* @__PURE__ */ new Set();
659
- #buildings = /* @__PURE__ */ new Map();
660
- #outdoors;
661
- #userInteracting = false;
662
- #pendingFacadeUpdate = false;
663
- #floorElevation = 0;
664
- #cameraElevation = 0;
665
- /**
666
- * Tracks the current zoom state based on camera zoom level:
667
- * - 'in-range': Camera is zoomed in enough to show indoor details (>= indoorZoomThreshold)
668
- * - 'out-of-range': Camera is zoomed out to show outdoor context (<= outdoorZoomThreshold)
669
- * - 'transition': Camera is between the two thresholds (no floor changes occur)
670
- */
671
- #zoomState = "transition";
672
- #viewState = "outdoor";
673
- /**
674
- * The facades that are actually focused and shown (filtered by showIndoor logic)
675
- */
676
- #facadesInFocus = [];
677
- #enabled = false;
678
- /**
679
- * @internal
680
- * Used to await the current scene update before starting a new one, or when the scene is updated asynchronously by an event.
681
- */
682
- sceneUpdateQueue = Promise.resolve();
683
- /**
684
- * Creates a new instance of the Dynamic Focus controller.
685
- *
686
- * @param mapView - The {@link MapView} to attach Dynamic Focus to.
687
- * @param options - Options for configuring Dynamic Focus.
688
- *
689
- * @example
690
- * ```ts
691
- * const mapView = show3dMap(...);
692
- * const df = new DynamicFocus(mapView);
693
- * df.enable({ autoFocus: true });
694
- *
695
- * // pause the listener
696
- * df.updateState({ autoFocus: false });
697
- *
698
- * // manually trigger a focus
699
- * df.focus();
700
- * ```
701
- */
702
- constructor(mapView) {
703
- this.#pubsub = new PubSub();
704
- this.#mapView = mapView;
705
- Logger2.setLevel(Mappedin_Logger_default.logState);
706
- this.#mapData = this.#mapView.getMapData();
707
- this.#floorElevation = this.#mapView.currentFloor.elevation;
708
- this.#cameraElevation = this.#mapView.Camera.elevation;
709
- for (const facade of this.#mapData.getByType("facade")) {
710
- this.#buildings.set(facade.floorStack.id, new Building(facade.floorStack, this.#mapView));
711
- }
712
- this.#outdoors = new Outdoors(
713
- getOutdoorFloorStack(this.#mapData.getByType("floor-stack"), this.#buildings),
714
- this.#mapView
715
- );
716
- this.#mapView.on("floor-change-start", this.#handleFloorChangeStart);
717
- this.#mapView.on("camera-change", this.#handleCameraChange);
718
- this.#mapView.on("facades-in-view-change", this.#handleFacadesInViewChange);
719
- this.#mapView.on("user-interaction-start", this.#handleUserInteractionStart);
720
- this.#mapView.on("user-interaction-end", this.#handleUserInteractionEnd);
721
- }
722
- /**
723
- * Enables Dynamic Focus with the given options.
724
- * @param options - The options to enable Dynamic Focus with.
725
- */
726
- enable(options) {
727
- if (this.#enabled) {
728
- Logger2.warn("enable() called on an already enabled Dynamic Focus instance.");
729
- return;
730
- }
731
- this.#enabled = true;
732
- this.#mapView.manualFloorVisibility = true;
733
- this.#outdoors.show();
734
- this.updateState({ ...this.#state, ...options });
735
- this.#handleCameraChange({
736
- zoomLevel: this.#mapView.Camera.zoomLevel,
737
- center: this.#mapView.Camera.center,
738
- bearing: this.#mapView.Camera.bearing,
739
- pitch: this.#mapView.Camera.pitch
740
- });
741
- this.#mapView.Camera.updateFacadesInView();
742
- }
743
- /**
744
- * Disables Dynamic Focus and returns the MapView to it's previous state.
745
- */
746
- disable() {
747
- if (!this.#enabled) {
748
- Logger2.warn("disable() called on an already disabled Dynamic Focus instance.");
749
- return;
750
- }
751
- this.#enabled = false;
752
- this.#mapView.manualFloorVisibility = false;
753
- }
754
- /**
755
- * Returns true if Dynamic Focus is enabled.
756
- */
757
- get isEnabled() {
758
- return this.#enabled;
759
- }
760
- /**
761
- * Returns true if the current view state is indoor.
762
- */
763
- get isIndoor() {
764
- return this.#viewState === "indoor";
765
- }
766
- /**
767
- * Returns true if the current view state is outdoor.
768
- */
769
- get isOutdoor() {
770
- return this.#viewState === "outdoor";
771
- }
772
- /**
773
- * Sets the view state to indoor, regardless of the current zoom level.
774
- */
775
- setIndoor() {
776
- this.#viewState = "indoor";
777
- this.#zoomState = "in-range";
778
- this.#handleViewStateChange();
779
- }
780
- /**
781
- * Sets the view state to outdoor, regardless of the current zoom level.
782
- */
783
- setOutdoor() {
784
- this.#viewState = "outdoor";
785
- this.#zoomState = "out-of-range";
786
- this.#handleViewStateChange();
787
- }
788
- #handleViewStateChange() {
789
- if (this.#state.autoFocus) {
790
- if (this.#userInteracting) {
791
- this.focus();
792
- } else {
793
- this.#pendingFacadeUpdate = true;
794
- }
795
- }
796
- this.#pubsub.publish("state-change");
797
- }
798
- #preloadFloors(mode) {
799
- this.#mapView.preloadFloors(
800
- this.#mapData.getByType("facade").map((facade) => getFloorToShow(this.#buildings.get(facade.floorStack.id), mode, this.#floorElevation)).filter((f) => f != null && Floor3.is(f))
801
- );
802
- }
803
- /**
804
- * Returns the facades that are currently in focus.
805
- */
806
- get focusedFacades() {
807
- return [...this.#facadesInFocus];
808
- }
809
- /**
810
- * Subscribe to a Dynamic Focus event.
811
- */
812
- on = /* @__PURE__ */ __name((eventName, fn) => {
813
- this.#pubsub.on(eventName, fn);
814
- }, "on");
815
- /**
816
- * Unsubscribe from a Dynamic Focus event.
817
- */
818
- off = /* @__PURE__ */ __name((eventName, fn) => {
819
- this.#pubsub.off(eventName, fn);
820
- }, "off");
821
- /**
822
- * Returns the current state of the Dynamic Focus controller.
823
- */
824
- getState() {
825
- return { ...this.#state };
826
- }
827
- /**
828
- * Updates the state of the Dynamic Focus controller.
829
- * @param state - The state to update.
830
- */
831
- updateState(state) {
832
- let shouldReapplyBuildingStates = false;
833
- if (state.indoorZoomThreshold != null) {
834
- state.indoorZoomThreshold = validateZoomThreshold(
835
- state.indoorZoomThreshold,
836
- state?.outdoorZoomThreshold ?? this.#state.outdoorZoomThreshold,
837
- this.#mapView.Camera.maxZoomLevel,
838
- "indoorZoomThreshold"
839
- );
840
- shouldReapplyBuildingStates = true;
841
- }
842
- if (state.outdoorZoomThreshold != null) {
843
- state.outdoorZoomThreshold = validateZoomThreshold(
844
- state.outdoorZoomThreshold,
845
- this.#mapView.Camera.minZoomLevel,
846
- state?.indoorZoomThreshold ?? this.#state.indoorZoomThreshold,
847
- "outdoorZoomThreshold"
848
- );
849
- shouldReapplyBuildingStates = true;
850
- }
851
- if (state.mode != null) {
852
- state.mode = DYNAMIC_FOCUS_MODES.includes(state.mode) ? state.mode : "default-floor";
853
- }
854
- if (state.autoAdjustFacadeHeights != null) {
855
- this.#state.autoAdjustFacadeHeights = state.autoAdjustFacadeHeights;
856
- }
857
- if (state.dynamicBuildingInteriors != null && state.dynamicBuildingInteriors !== this.#state.dynamicBuildingInteriors) {
858
- shouldReapplyBuildingStates = true;
859
- }
860
- this.#state = Object.assign(
861
- {},
862
- this.#state,
863
- Object.fromEntries(Object.entries(state).filter(([, value]) => value != null))
864
- );
865
- if (state.mode != null && state.preloadFloors) {
866
- this.#preloadFloors(state.mode);
867
- }
868
- if (shouldReapplyBuildingStates) {
869
- this.#applyBuildingStates();
870
- }
871
- }
872
- /**
873
- * Destroys the Dynamic Focus instance and unsubscribes all MapView event listeners.
874
- */
875
- destroy() {
876
- this.disable();
877
- this.#mapView.off("facades-in-view-change", this.#handleFacadesInViewChange);
878
- this.#mapView.off("floor-change-start", this.#handleFloorChangeStart);
879
- this.#mapView.off("camera-change", this.#handleCameraChange);
880
- this.#mapView.off("user-interaction-start", this.#handleUserInteractionStart);
881
- this.#mapView.off("user-interaction-end", this.#handleUserInteractionEnd);
882
- this.#buildings.forEach((building) => building.destroy());
883
- this.#outdoors.destroy();
884
- this.#pubsub.destroy();
885
- }
886
- /**
887
- * Perform a manual visual update of the focused facades, and optionally set the floor.
888
- * @param setFloor - Whether to set the floor. This will default to the current state of setFloorOnFocus.
889
- */
890
- async focus(setFloor = this.#state.setFloorOnFocus) {
891
- if (setFloor) {
892
- if (shouldDeferSetFloor(this.#cameraElevation, this.#mapView.Camera.elevation, this.#userInteracting)) {
893
- this.#cameraElevation = this.#mapView.Camera.elevation;
894
- this.#pendingFacadeUpdate = true;
895
- } else {
896
- const target = getSetFloorTargetFromZoomState(this.#sortedBuildingsInView, this.#outdoors, this.#zoomState);
897
- const floor = target ? getFloorToShow(target, this.#state.mode, this.#floorElevation) : void 0;
898
- if (floor && floor.id !== this.#mapView.currentFloor.id) {
899
- this.#mapView.setFloor(floor, { context: "dynamic-focus" });
900
- }
901
- }
902
- }
903
- await this.#applyBuildingStates();
904
- this.#pubsub.publish("focus", { facades: this.focusedFacades });
905
- }
906
- /**
907
- * Preloads the initial floors for each building to improve performance when rendering the building for the first time. See {@link DynamicFocusState.preloadFloors}.
908
- */
909
- preloadFloors() {
910
- this.#preloadFloors(this.#state.mode);
911
- }
912
- /**
913
- * Sets the default floor for a floor stack. This is the floor that will be shown when focusing on a facade if there is no currently active floor in the stack.
914
- * See {@link resetDefaultFloorForStack} to reset the default floor.
915
- * @param floorStack - The floor stack to set the default floor for.
916
- * @param floor - The floor to set as the default floor.
917
- */
918
- setDefaultFloorForStack(floorStack, floor) {
919
- if (!validateFloorForStack(floor, floorStack)) {
920
- return;
921
- }
922
- const building = this.#buildings.get(floorStack.id);
923
- if (building) {
924
- building.defaultFloor = floor;
925
- }
926
- }
927
- /**
928
- * Resets the default floor for a floor stack to it's initial value.
929
- * @param floorStack - The floor stack to reset the default floor for.
930
- */
931
- resetDefaultFloorForStack(floorStack) {
932
- const building = this.#buildings.get(floorStack.id);
933
- if (building) {
934
- building.defaultFloor = floorStack.defaultFloor;
935
- }
936
- }
937
- /**
938
- * Returns the current default floor for a floor stack.
939
- * @param floorStack - The floor stack to get the default floor for.
940
- * @returns The current default floor for the floor stack.
941
- */
942
- getDefaultFloorForStack(floorStack) {
943
- return this.#buildings.get(floorStack.id)?.defaultFloor || floorStack.defaultFloor || floorStack.floors[0];
944
- }
945
- /**
946
- * Sets the current floor for a floor stack. If the floor stack is currently focused, this floor will become visible.
947
- * @param floorStack - The floor stack to set the current floor for.
948
- */
949
- setCurrentFloorForStack(floorStack, floor) {
950
- if (!validateFloorForStack(floor, floorStack)) {
951
- return;
952
- }
953
- const building = this.#buildings.get(floorStack.id);
954
- if (building) {
955
- building.activeFloor = floor;
956
- this.#applyBuildingStates();
957
- }
958
- }
959
- /**
960
- * Excludes a floor stack from visibility changes.
961
- * @param excluded - The floor stack or stacks to exclude.
962
- */
963
- exclude(excluded) {
964
- const excludedFloorStacks = Array.isArray(excluded) ? excluded : [excluded];
965
- for (const floorStack of excludedFloorStacks) {
966
- const building = this.#buildings.get(floorStack.id);
967
- if (building) {
968
- building.excluded = true;
969
- }
970
- }
971
- }
972
- /**
973
- * Includes a floor stack in visibility changes.
974
- * @param included - The floor stack or stacks to include.
975
- */
976
- include(included) {
977
- const includedFloorStacks = Array.isArray(included) ? included : [included];
978
- for (const floorStack of includedFloorStacks) {
979
- const building = this.#buildings.get(floorStack.id);
980
- if (building) {
981
- building.excluded = false;
982
- }
983
- }
984
- }
985
- /**
986
- * Returns the current floor for a floor stack.
987
- * @param floorStack - The floor stack to get the current floor for.
988
- * @returns The current floor for the floor stack.
989
- */
990
- getCurrentFloorForStack(floorStack) {
991
- return this.#buildings.get(floorStack.id)?.activeFloor || this.getDefaultFloorForStack(floorStack);
992
- }
993
- /**
994
- * Handles management of focused facades but doesn't trigger view updates unless enabled.
995
- * @see {@link focus} to manually trigger a view update.
996
- */
997
- #handleFacadesInViewChange = /* @__PURE__ */ __name((event) => {
998
- if (!this.isEnabled) {
999
- return;
1000
- }
1001
- const { facades } = event;
1002
- if (arraysEqual(facades, this.focusedFacades) && this.#viewState === "transition" && !this.#pendingFacadeUpdate) {
1003
- return;
1004
- }
1005
- this.#pendingFacadeUpdate = false;
1006
- this.#sortedBuildingsInView = [];
1007
- this.#buildingIdsInViewSet.clear();
1008
- if (facades.length > 0) {
1009
- for (const facade of facades) {
1010
- const building = this.#buildings.get(facade.floorStack.id);
1011
- if (building) {
1012
- this.#buildingIdsInViewSet.add(building.id);
1013
- this.#sortedBuildingsInView.push(building);
1014
- }
1015
- }
1016
- }
1017
- if (this.#state.autoFocus) {
1018
- this.focus();
1019
- }
1020
- }, "#handleFacadesInViewChange");
1021
- /**
1022
- * Handles floor and facade visibility when the floor changes.
1023
- */
1024
- #handleFloorChangeStart = /* @__PURE__ */ __name(async (event) => {
1025
- if (!this.isEnabled) {
1026
- return;
1027
- }
1028
- const { floor: newFloor } = event;
1029
- const building = this.#buildings.get(newFloor.floorStack.id);
1030
- if (building) {
1031
- building.activeFloor = newFloor;
1032
- }
1033
- if (building?.excluded === false && !this.#outdoors.matchesFloorStack(newFloor.floorStack)) {
1034
- this.#floorElevation = newFloor.elevation;
1035
- }
1036
- if (this.#mapView.manualFloorVisibility === true) {
1037
- if (event.reason !== "dynamic-focus") {
1038
- await this.#applyBuildingStates(newFloor);
1039
- this.#pubsub.publish("focus", { facades: this.focusedFacades });
1040
- }
1041
- const altitude = building?.floorsAltitudesMap.get(newFloor.elevation)?.altitude ?? 0;
1042
- if (this.#mapView.options.multiFloorView != null && this.#mapView.options.multiFloorView?.enabled && this.#mapView.options.multiFloorView?.updateCameraElevationOnFloorChange && this.#mapView.Camera.elevation !== altitude) {
1043
- this.#mapView.Camera.animateElevation(altitude, {
1044
- duration: 750,
1045
- easing: "ease-in-out"
1046
- });
1047
- }
1048
- }
1049
- }, "#handleFloorChangeStart");
1050
- #handleUserInteractionStart = /* @__PURE__ */ __name(() => {
1051
- this.#userInteracting = true;
1052
- }, "#handleUserInteractionStart");
1053
- #handleUserInteractionEnd = /* @__PURE__ */ __name(() => {
1054
- this.#userInteracting = false;
1055
- }, "#handleUserInteractionEnd");
1056
- /**
1057
- * Handles camera moving in and out of zoom range and fires a focus event if the camera is in range.
1058
- */
1059
- #handleCameraChange = /* @__PURE__ */ __name((event) => {
1060
- if (!this.isEnabled) {
1061
- return;
1062
- }
1063
- const { zoomLevel } = event;
1064
- const newZoomState = getZoomState(zoomLevel, this.#state.indoorZoomThreshold, this.#state.outdoorZoomThreshold);
1065
- if (this.#zoomState !== newZoomState) {
1066
- this.#zoomState = newZoomState;
1067
- if (newZoomState === "in-range") {
1068
- this.setIndoor();
1069
- } else if (newZoomState === "out-of-range") {
1070
- this.setOutdoor();
1071
- }
1072
- }
1073
- }, "#handleCameraChange");
1074
- #updateFloorVisibility(floorStates, shouldShow, inFocus) {
1075
- floorStates.forEach((floorState) => {
1076
- if (floorState.floor) {
1077
- if (shouldShow) {
1078
- const state = inFocus !== void 0 ? {
1079
- ...floorState.state,
1080
- labels: {
1081
- ...floorState.state.labels,
1082
- enabled: floorState.state.labels.enabled && inFocus
1083
- },
1084
- markers: {
1085
- ...floorState.state.markers,
1086
- enabled: floorState.state.markers.enabled && inFocus
1087
- },
1088
- occlusion: {
1089
- ...floorState.state.occlusion,
1090
- // We don't want this floor to occlude if it's not in focus
1091
- // Allows us to show a label above this floor while it's not active
1092
- enabled: floorState.state.occlusion.enabled && inFocus
1093
- }
1094
- } : floorState.state;
1095
- this.#mapView.updateState(floorState.floor, state);
1096
- } else {
1097
- this.#mapView.updateState(floorState.floor, { visible: false });
1098
- }
1099
- }
1100
- });
1101
- }
1102
- async #animateIndoorSequence(building, floorStackState, facadeState, inFocus) {
1103
- this.#updateFloorVisibility(floorStackState.floorStates, true, inFocus);
1104
- return building.animateFacade(facadeState.state, this.#state.indoorAnimationOptions);
1105
- }
1106
- async #animateOutdoorSequence(building, floorStackState, facadeState, floorStackIdsInNavigation) {
1107
- const result = await building.animateFacade(facadeState.state, this.#state.outdoorAnimationOptions);
1108
- if (result.result === "completed") {
1109
- this.#updateFloorVisibility(
1110
- floorStackState.floorStates,
1111
- shouldShowIndoor(
1112
- building,
1113
- this.#mapView.currentFloor,
1114
- this.#zoomState === "in-range",
1115
- this.#buildingIdsInViewSet.has(building.id),
1116
- this.#state.mode,
1117
- this.#floorElevation,
1118
- floorStackIdsInNavigation.includes(building.floorStack.id),
1119
- this.#state.dynamicBuildingInteriors
1120
- )
1121
- );
1122
- }
1123
- return result;
1124
- }
1125
- #applyBuildingStates = /* @__PURE__ */ __name(async (newFloor) => {
1126
- if (this.#mapView.manualFloorVisibility !== true || !this.isEnabled) {
1127
- return;
1128
- }
1129
- const currentFloor = newFloor || this.#mapView.currentFloor;
1130
- const multiFloorView = this.#mapView.options.multiFloorView;
1131
- const floorIdsInNavigation = this.#mapView.Navigation?.floors?.map((f) => f.id) || [];
1132
- const floorStackIdsInNavigation = this.#mapView.Navigation?.floorStacks.map((fs) => fs.id) || [];
1133
- const updatePromise = new Promise(async (resolve) => {
1134
- await this.sceneUpdateQueue;
1135
- const buildingStates = getBuildingStates(
1136
- Array.from(this.#buildings.values()),
1137
- this.#buildingIdsInViewSet,
1138
- currentFloor,
1139
- this.#zoomState,
1140
- this.#state.mode,
1141
- this.#floorElevation,
1142
- floorIdsInNavigation,
1143
- floorStackIdsInNavigation,
1144
- multiFloorView,
1145
- this.#state.customMultiFloorVisibilityState ?? getMultiFloorState2,
1146
- this.#state.dynamicBuildingInteriors
1147
- );
1148
- const floorStackStates = [];
1149
- await Promise.all(
1150
- Array.from(buildingStates.values()).map(async (buildingState) => {
1151
- const { building, showIndoor, floorStackState, facadeState, inFocus } = buildingState;
1152
- floorStackStates.push(floorStackState);
1153
- const animation = getBuildingAnimationType(building, showIndoor);
1154
- if (animation === "indoor") {
1155
- return this.#animateIndoorSequence(building, floorStackState, facadeState, inFocus);
1156
- } else if (animation === "outdoor") {
1157
- return this.#animateOutdoorSequence(building, floorStackState, facadeState, floorStackIdsInNavigation);
1158
- }
1159
- })
1160
- );
1161
- this.#mapView.Outdoor.setOpacity(getOutdoorOpacity(floorStackStates, this.#state.mode));
1162
- this.#facadesInFocus = this.#sortedBuildingsInView.filter((building) => buildingStates.get(building.id)?.showIndoor === true).map((building) => building.facade);
1163
- this.#pubsub.publish("focus", { facades: this.#facadesInFocus });
1164
- resolve();
1165
- });
1166
- this.sceneUpdateQueue = updatePromise;
1167
- return updatePromise;
1168
- }, "#applyBuildingStates");
1169
- };
1170
-
1171
- export {
1172
- __name,
1173
- DynamicFocus
1174
- };
1175
- //# sourceMappingURL=chunk-RSECYADN.js.map