@mapvx/web-js 3.6.0-dev.1 → 4.0.0-dev.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -1
- package/dist/cjs/index.js +2 -95
- package/dist/cjs/index.js.LICENSE.txt +1 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/es/controllers/routeController.d.ts.map +1 -1
- package/dist/es/controllers/routeController.js +25 -8
- package/dist/es/controllers/routeController.js.map +1 -1
- package/dist/es/logger/sentry.js +1 -1
- package/dist/es/map/liveMapFitTransform.d.ts +11 -5
- package/dist/es/map/liveMapFitTransform.d.ts.map +1 -1
- package/dist/es/map/liveMapFitTransform.js +28 -19
- package/dist/es/map/liveMapFitTransform.js.map +1 -1
- package/dist/es/map/map.d.ts.map +1 -1
- package/dist/es/map/map.js +3 -6
- package/dist/es/map/map.js.map +1 -1
- package/dist/umd/index.js +932 -126
- package/dist/umd/index.js.map +1 -1
- package/dist/umd/styles.css +1 -1
- package/dist/umd/styles.css.map +1 -1
- package/package.json +7 -4
- package/dist/cjs/assets/icons.js +0 -85
- package/dist/cjs/assets/icons.js.map +0 -1
- package/dist/cjs/config/draco.js +0 -15
- package/dist/cjs/config/draco.js.map +0 -1
- package/dist/cjs/config/sdkConfig.js +0 -3
- package/dist/cjs/config/sdkConfig.js.map +0 -1
- package/dist/cjs/controllers/markerLayoutController.js +0 -359
- package/dist/cjs/controllers/markerLayoutController.js.map +0 -1
- package/dist/cjs/controllers/routeController.js +0 -1549
- package/dist/cjs/controllers/routeController.js.map +0 -1
- package/dist/cjs/domain/models/_internal.js +0 -14
- package/dist/cjs/domain/models/_internal.js.map +0 -1
- package/dist/cjs/domain/models/_rtl.js +0 -10
- package/dist/cjs/domain/models/_rtl.js.map +0 -1
- package/dist/cjs/domain/models/animation.js +0 -64
- package/dist/cjs/domain/models/animation.js.map +0 -1
- package/dist/cjs/domain/models/banner.js +0 -24
- package/dist/cjs/domain/models/banner.js.map +0 -1
- package/dist/cjs/domain/models/campaign.js +0 -73
- package/dist/cjs/domain/models/campaign.js.map +0 -1
- package/dist/cjs/domain/models/categories.js +0 -37
- package/dist/cjs/domain/models/categories.js.map +0 -1
- package/dist/cjs/domain/models/circle.js +0 -253
- package/dist/cjs/domain/models/circle.js.map +0 -1
- package/dist/cjs/domain/models/cityFilterOption.js +0 -3
- package/dist/cjs/domain/models/cityFilterOption.js.map +0 -1
- package/dist/cjs/domain/models/configuration.js +0 -3
- package/dist/cjs/domain/models/configuration.js.map +0 -1
- package/dist/cjs/domain/models/cssCustomization.js +0 -3
- package/dist/cjs/domain/models/cssCustomization.js.map +0 -1
- package/dist/cjs/domain/models/fitOptions.js +0 -3
- package/dist/cjs/domain/models/fitOptions.js.map +0 -1
- package/dist/cjs/domain/models/innerFloors.js +0 -3
- package/dist/cjs/domain/models/innerFloors.js.map +0 -1
- package/dist/cjs/domain/models/institution.js +0 -45
- package/dist/cjs/domain/models/institution.js.map +0 -1
- package/dist/cjs/domain/models/latLng.js +0 -3
- package/dist/cjs/domain/models/latLng.js.map +0 -1
- package/dist/cjs/domain/models/location.js +0 -3
- package/dist/cjs/domain/models/location.js.map +0 -1
- package/dist/cjs/domain/models/loggeable.js +0 -52
- package/dist/cjs/domain/models/loggeable.js.map +0 -1
- package/dist/cjs/domain/models/mapCamera.js +0 -3
- package/dist/cjs/domain/models/mapCamera.js.map +0 -1
- package/dist/cjs/domain/models/mapConfig.js +0 -37
- package/dist/cjs/domain/models/mapConfig.js.map +0 -1
- package/dist/cjs/domain/models/marker.js +0 -502
- package/dist/cjs/domain/models/marker.js.map +0 -1
- package/dist/cjs/domain/models/multilanguage.js +0 -17
- package/dist/cjs/domain/models/multilanguage.js.map +0 -1
- package/dist/cjs/domain/models/openingHours.js +0 -3
- package/dist/cjs/domain/models/openingHours.js.map +0 -1
- package/dist/cjs/domain/models/place.js +0 -127
- package/dist/cjs/domain/models/place.js.map +0 -1
- package/dist/cjs/domain/models/platform.js +0 -3
- package/dist/cjs/domain/models/platform.js.map +0 -1
- package/dist/cjs/domain/models/popover.js +0 -3
- package/dist/cjs/domain/models/popover.js.map +0 -1
- package/dist/cjs/domain/models/product.js +0 -3
- package/dist/cjs/domain/models/product.js.map +0 -1
- package/dist/cjs/domain/models/property.js +0 -104
- package/dist/cjs/domain/models/property.js.map +0 -1
- package/dist/cjs/domain/models/route.js +0 -166
- package/dist/cjs/domain/models/route.js.map +0 -1
- package/dist/cjs/domain/models/routeConfiguration.js +0 -82
- package/dist/cjs/domain/models/routeConfiguration.js.map +0 -1
- package/dist/cjs/domain/models/routeInstance.js +0 -30
- package/dist/cjs/domain/models/routeInstance.js.map +0 -1
- package/dist/cjs/domain/models/routeStyle.js +0 -3
- package/dist/cjs/domain/models/routeStyle.js.map +0 -1
- package/dist/cjs/domain/models/scene3D.js +0 -3
- package/dist/cjs/domain/models/scene3D.js.map +0 -1
- package/dist/cjs/domain/models/transport.js +0 -22
- package/dist/cjs/domain/models/transport.js.map +0 -1
- package/dist/cjs/domain/ports/ICache.js +0 -3
- package/dist/cjs/domain/ports/ICache.js.map +0 -1
- package/dist/cjs/domain/ports/IHttpClient.js +0 -3
- package/dist/cjs/domain/ports/IHttpClient.js.map +0 -1
- package/dist/cjs/domain/ports/ILogger.js +0 -23
- package/dist/cjs/domain/ports/ILogger.js.map +0 -1
- package/dist/cjs/domain/ports/index.js +0 -10
- package/dist/cjs/domain/ports/index.js.map +0 -1
- package/dist/cjs/infrastructure/cache/CacheManager.js +0 -258
- package/dist/cjs/infrastructure/cache/CacheManager.js.map +0 -1
- package/dist/cjs/infrastructure/cache/LRUCache.js +0 -153
- package/dist/cjs/infrastructure/cache/LRUCache.js.map +0 -1
- package/dist/cjs/infrastructure/cache/PersistentCache.js +0 -168
- package/dist/cjs/infrastructure/cache/PersistentCache.js.map +0 -1
- package/dist/cjs/infrastructure/cache/cacheConfig.js +0 -70
- package/dist/cjs/infrastructure/cache/cacheConfig.js.map +0 -1
- package/dist/cjs/infrastructure/http/FetchHttpClient.js +0 -75
- package/dist/cjs/infrastructure/http/FetchHttpClient.js.map +0 -1
- package/dist/cjs/infrastructure/logging/MVXLogger.js +0 -80
- package/dist/cjs/infrastructure/logging/MVXLogger.js.map +0 -1
- package/dist/cjs/interfaces/cache.js +0 -3
- package/dist/cjs/interfaces/cache.js.map +0 -1
- package/dist/cjs/interfaces/healthResponse.js +0 -3
- package/dist/cjs/interfaces/healthResponse.js.map +0 -1
- package/dist/cjs/interfaces/institutionResponse.js +0 -3
- package/dist/cjs/interfaces/institutionResponse.js.map +0 -1
- package/dist/cjs/interfaces/metadata.js +0 -3
- package/dist/cjs/interfaces/metadata.js.map +0 -1
- package/dist/cjs/interfaces/placeResponse.js +0 -3
- package/dist/cjs/interfaces/placeResponse.js.map +0 -1
- package/dist/cjs/interfaces/routeCacheResponse.js +0 -3
- package/dist/cjs/interfaces/routeCacheResponse.js.map +0 -1
- package/dist/cjs/interfaces/routeResponse.js +0 -3
- package/dist/cjs/interfaces/routeResponse.js.map +0 -1
- package/dist/cjs/interfaces/styleResponse.js +0 -3
- package/dist/cjs/interfaces/styleResponse.js.map +0 -1
- package/dist/cjs/logger/logger.js +0 -78
- package/dist/cjs/logger/logger.js.map +0 -1
- package/dist/cjs/logger/sentry.js +0 -81
- package/dist/cjs/logger/sentry.js.map +0 -1
- package/dist/cjs/map/fitCamera.js +0 -124
- package/dist/cjs/map/fitCamera.js.map +0 -1
- package/dist/cjs/map/liveMapFitTransform.js +0 -180
- package/dist/cjs/map/liveMapFitTransform.js.map +0 -1
- package/dist/cjs/map/map.js +0 -2766
- package/dist/cjs/map/map.js.map +0 -1
- package/dist/cjs/map/mapInteractionOptions.js +0 -73
- package/dist/cjs/map/mapInteractionOptions.js.map +0 -1
- package/dist/cjs/map/scene3DController.js +0 -849
- package/dist/cjs/map/scene3DController.js.map +0 -1
- package/dist/cjs/repository/repository.js +0 -694
- package/dist/cjs/repository/repository.js.map +0 -1
- package/dist/cjs/repository/requester.js +0 -771
- package/dist/cjs/repository/requester.js.map +0 -1
- package/dist/cjs/sdk.js +0 -438
- package/dist/cjs/sdk.js.map +0 -1
- package/dist/cjs/utils/3d.js +0 -245
- package/dist/cjs/utils/3d.js.map +0 -1
- package/dist/cjs/utils/deckLayers.js +0 -153
- package/dist/cjs/utils/deckLayers.js.map +0 -1
- package/dist/cjs/utils/error.js +0 -19
- package/dist/cjs/utils/error.js.map +0 -1
- package/dist/cjs/utils/images.js +0 -25
- package/dist/cjs/utils/images.js.map +0 -1
- package/dist/cjs/utils/loadGlb.js +0 -190
- package/dist/cjs/utils/loadGlb.js.map +0 -1
- package/dist/cjs/utils/mapvxHostname.js +0 -21
- package/dist/cjs/utils/mapvxHostname.js.map +0 -1
- package/dist/cjs/utils/nav-functions.js +0 -150
- package/dist/cjs/utils/nav-functions.js.map +0 -1
- package/dist/cjs/utils/opening-hours-helper.js +0 -914
- package/dist/cjs/utils/opening-hours-helper.js.map +0 -1
- package/dist/cjs/utils/preconnect.js +0 -131
- package/dist/cjs/utils/preconnect.js.map +0 -1
- package/dist/cjs/utils/route-utils.js +0 -57
- package/dist/cjs/utils/route-utils.js.map +0 -1
- package/dist/cjs/utils/semaphore.js +0 -143
- package/dist/cjs/utils/semaphore.js.map +0 -1
- package/dist/cjs/utils/three.js +0 -113
- package/dist/cjs/utils/three.js.map +0 -1
- package/dist/cjs/utils/threeScene.js +0 -126
- package/dist/cjs/utils/threeScene.js.map +0 -1
- package/dist/cjs/utils/update-css.js +0 -76
- package/dist/cjs/utils/update-css.js.map +0 -1
- package/dist/cjs/utils/utils.js +0 -104
- package/dist/cjs/utils/utils.js.map +0 -1
package/dist/cjs/map/map.js
DELETED
|
@@ -1,2766 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.InternalMapVXMap = void 0;
|
|
37
|
-
const maplibre_gl_1 = __importStar(require("maplibre-gl"));
|
|
38
|
-
const semaphore_1 = require("../utils/semaphore");
|
|
39
|
-
/** Shared GeoJSON source holding every circle drawn through the circle API. */
|
|
40
|
-
const CIRCLE_SOURCE_ID = "mapvx-circles";
|
|
41
|
-
/** Fill layer rendering the translucent interior of the circles. */
|
|
42
|
-
const CIRCLE_FILL_LAYER_ID = "mapvx-circles-fill";
|
|
43
|
-
/** Line layer rendering the circle outlines. */
|
|
44
|
-
const CIRCLE_LINE_LAYER_ID = "mapvx-circles-line";
|
|
45
|
-
/**
|
|
46
|
-
* Layer-id suffix per circle render order. The default placement keeps the
|
|
47
|
-
* unsuffixed ids so existing consumers referencing them keep working.
|
|
48
|
-
*/
|
|
49
|
-
const CIRCLE_LAYER_SUFFIXES = {
|
|
50
|
-
aboveBasemap: "",
|
|
51
|
-
belowLabels: "-below-labels",
|
|
52
|
-
top: "-top",
|
|
53
|
-
};
|
|
54
|
-
/**
|
|
55
|
-
* How many times a failed vector tile is reloaded before it is dropped from the retry
|
|
56
|
-
* queue. Bounded so a permanently unavailable tile stops being requested, while a tile
|
|
57
|
-
* that failed on a transient network error still gets several chances.
|
|
58
|
-
*/
|
|
59
|
-
const MAX_TILE_RETRIES = 5;
|
|
60
|
-
/**
|
|
61
|
-
* Base delay for the retry backoff. Attempt N waits `BASE * 2^(N-1)` before it is
|
|
62
|
-
* eligible again — 1s, 2s, 4s, 8s — so a tile that keeps failing is retried ever less
|
|
63
|
-
* often instead of once per timer tick.
|
|
64
|
-
*/
|
|
65
|
-
const TILE_RETRY_BASE_DELAY_MS = 1000;
|
|
66
|
-
/**
|
|
67
|
-
* Safety deadline for {@link InternalMapVXMap.setStyle}'s `onStyleReady` callback. The
|
|
68
|
-
* callback normally fires on the map's `idle` event, but `idle` may never arrive (the
|
|
69
|
-
* style request failed, the swap never settles). Callers hide the map behind a loading
|
|
70
|
-
* overlay until the callback arrives, so it must be released no matter what.
|
|
71
|
-
*/
|
|
72
|
-
const STYLE_READY_TIMEOUT_MS = 4000;
|
|
73
|
-
/**
|
|
74
|
-
* Frames a floor change waits before it will believe the indoor tiles are
|
|
75
|
-
* already rebuilt (see `updateMarkersWhenFloorSettles`). Genuinely a frame
|
|
76
|
-
* count: the reload is dispatched on the map's next render, so this is how
|
|
77
|
-
* many renders it gets to be observed in.
|
|
78
|
-
*/
|
|
79
|
-
const FLOOR_SETTLE_MIN_FRAMES = 3;
|
|
80
|
-
/**
|
|
81
|
-
* Longest a floor change waits for the new floor's tiles before switching the
|
|
82
|
-
* HTML markers anyway, so a stalled source never hides markers for good.
|
|
83
|
-
*
|
|
84
|
-
* A deadline rather than a frame count. The panels this matters on run at
|
|
85
|
-
* 20-30 fps, where a 60-frame cap is two to three seconds — and the wait also
|
|
86
|
-
* gates `addMarker`, so overshooting it is not merely a late marker switch.
|
|
87
|
-
*/
|
|
88
|
-
const FLOOR_SETTLE_MAX_MS = 1000;
|
|
89
|
-
/** Fill/line layer ids for one circle render-order bucket. */
|
|
90
|
-
function circleLayerIdsFor(order) {
|
|
91
|
-
const suffix = CIRCLE_LAYER_SUFFIXES[order];
|
|
92
|
-
return { fill: CIRCLE_FILL_LAYER_ID + suffix, line: CIRCLE_LINE_LAYER_ID + suffix };
|
|
93
|
-
}
|
|
94
|
-
/** True for the layer ids owned by the circle API, regardless of bucket. */
|
|
95
|
-
function isCircleLayerId(id) {
|
|
96
|
-
return id.startsWith(CIRCLE_FILL_LAYER_ID) || id.startsWith(CIRCLE_LINE_LAYER_ID);
|
|
97
|
-
}
|
|
98
|
-
// Flag to track if cached-tile protocol has been registered
|
|
99
|
-
let cachedTileProtocolRegistered = false;
|
|
100
|
-
/**
|
|
101
|
-
* Deep clone helper that works in all environments.
|
|
102
|
-
* Uses structuredClone if available, otherwise falls back to JSON serialization.
|
|
103
|
-
* @param obj The object to clone
|
|
104
|
-
* @returns A deep clone of the object
|
|
105
|
-
*/
|
|
106
|
-
function deepClone(obj) {
|
|
107
|
-
if (obj === undefined || obj === null) {
|
|
108
|
-
return obj;
|
|
109
|
-
}
|
|
110
|
-
if (typeof structuredClone === "function") {
|
|
111
|
-
return structuredClone(obj);
|
|
112
|
-
}
|
|
113
|
-
return JSON.parse(JSON.stringify(obj));
|
|
114
|
-
}
|
|
115
|
-
/**
|
|
116
|
-
* Register a custom protocol for cached tiles that routes requests through the main thread.
|
|
117
|
-
* This allows the service worker to intercept and cache tile requests.
|
|
118
|
-
*
|
|
119
|
-
* Wraps fetches in a {@link Semaphore} so tile CDN / WAFs are not hit with
|
|
120
|
-
* unbounded parallel requests (same host as the service worker cache). The
|
|
121
|
-
* semaphore is created once with the limit from the first map's config; later
|
|
122
|
-
* calls are no-ops because the protocol can only be registered once globally
|
|
123
|
-
* on MapLibre.
|
|
124
|
-
*
|
|
125
|
-
* @param maxConcurrentFetches - Maximum number of in-flight tile fetches.
|
|
126
|
-
*/
|
|
127
|
-
function registerCachedTileProtocol(maxConcurrentFetches) {
|
|
128
|
-
if (cachedTileProtocolRegistered)
|
|
129
|
-
return;
|
|
130
|
-
const semaphore = new semaphore_1.Semaphore(maxConcurrentFetches);
|
|
131
|
-
maplibre_gl_1.default.addProtocol("cached-tile", (params, abortController) => {
|
|
132
|
-
const url = params.url.replace("cached-tile://", "https://");
|
|
133
|
-
// Pass the abort signal to acquire() so a request cancelled while still
|
|
134
|
-
// queued (e.g. during rapid zoom) drops out of the FIFO queue instead of
|
|
135
|
-
// waiting for a slot just to bail. release() runs only inside this chain,
|
|
136
|
-
// i.e. only after a slot was actually granted.
|
|
137
|
-
return semaphore.acquire(abortController.signal).then(() => fetch(url, { signal: abortController.signal })
|
|
138
|
-
.then((response) => {
|
|
139
|
-
if (!response.ok) {
|
|
140
|
-
throw new Error(`HTTP error! status: ${response.status}`);
|
|
141
|
-
}
|
|
142
|
-
return response.arrayBuffer();
|
|
143
|
-
})
|
|
144
|
-
.then((data) => ({ data }))
|
|
145
|
-
.finally(() => {
|
|
146
|
-
semaphore.release();
|
|
147
|
-
}));
|
|
148
|
-
});
|
|
149
|
-
cachedTileProtocolRegistered = true;
|
|
150
|
-
}
|
|
151
|
-
/**
|
|
152
|
-
* Converts a PaddingValue to pixels based on the container and viewport dimensions.
|
|
153
|
-
* @param value The padding value (number or string with unit)
|
|
154
|
-
* @param dimension The reference dimension in pixels (container width/height)
|
|
155
|
-
* @returns The padding value in pixels
|
|
156
|
-
*/
|
|
157
|
-
function parsePaddingValue(value, dimension) {
|
|
158
|
-
if (value === undefined) {
|
|
159
|
-
return 0;
|
|
160
|
-
}
|
|
161
|
-
if (typeof value === "number") {
|
|
162
|
-
return value;
|
|
163
|
-
}
|
|
164
|
-
const match = value.match(/^(-?\d*\.?\d+)(px|%|vh|vw)?$/);
|
|
165
|
-
if (!match) {
|
|
166
|
-
return 0;
|
|
167
|
-
}
|
|
168
|
-
const numericValue = Number.parseFloat(match[1]);
|
|
169
|
-
const unit = match[2] || "px";
|
|
170
|
-
switch (unit) {
|
|
171
|
-
case "px":
|
|
172
|
-
return numericValue;
|
|
173
|
-
case "%":
|
|
174
|
-
return (numericValue / 100) * dimension;
|
|
175
|
-
case "vh":
|
|
176
|
-
return (numericValue / 100) * window.innerHeight;
|
|
177
|
-
case "vw":
|
|
178
|
-
return (numericValue / 100) * window.innerWidth;
|
|
179
|
-
default:
|
|
180
|
-
return numericValue;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
/**
|
|
184
|
-
* Converts PaddingOptions with mixed units to MapLibre's pixel-based padding format.
|
|
185
|
-
* @param padding The padding options with optional fields and mixed units
|
|
186
|
-
* @param containerWidth The map container width in pixels
|
|
187
|
-
* @param containerHeight The map container height in pixels
|
|
188
|
-
* @returns Padding object with all values in pixels
|
|
189
|
-
*/
|
|
190
|
-
function convertPaddingToPixels(padding, containerWidth, containerHeight) {
|
|
191
|
-
if (!padding) {
|
|
192
|
-
return { top: 0, bottom: 0, left: 0, right: 0 };
|
|
193
|
-
}
|
|
194
|
-
return {
|
|
195
|
-
top: parsePaddingValue(padding.top, containerHeight),
|
|
196
|
-
bottom: parsePaddingValue(padding.bottom, containerHeight),
|
|
197
|
-
left: parsePaddingValue(padding.left, containerWidth),
|
|
198
|
-
right: parsePaddingValue(padding.right, containerWidth),
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
const icons_1 = require("../assets/icons");
|
|
202
|
-
const markerLayoutController_1 = require("../controllers/markerLayoutController");
|
|
203
|
-
const routeController_1 = require("../controllers/routeController");
|
|
204
|
-
const _rtl_1 = require("../domain/models/_rtl");
|
|
205
|
-
const animation_1 = require("../domain/models/animation");
|
|
206
|
-
const circle_1 = require("../domain/models/circle");
|
|
207
|
-
const loggeable_1 = require("../domain/models/loggeable");
|
|
208
|
-
const mapConfig_1 = require("../domain/models/mapConfig");
|
|
209
|
-
const marker_1 = require("../domain/models/marker");
|
|
210
|
-
const route_1 = require("../domain/models/route");
|
|
211
|
-
const routeConfiguration_1 = require("../domain/models/routeConfiguration");
|
|
212
|
-
const repository_1 = require("../repository/repository");
|
|
213
|
-
const route_utils_1 = require("../utils/route-utils");
|
|
214
|
-
const utils_1 = require("../utils/utils");
|
|
215
|
-
const scene3DController_1 = require("./scene3DController");
|
|
216
|
-
const fitCamera_1 = require("./fitCamera");
|
|
217
|
-
const liveMapFitTransform_1 = require("./liveMapFitTransform");
|
|
218
|
-
const mapInteractionOptions_1 = require("./mapInteractionOptions");
|
|
219
|
-
/**
|
|
220
|
-
* Class to interact with the map.
|
|
221
|
-
* @category Map
|
|
222
|
-
* @hidden
|
|
223
|
-
*/
|
|
224
|
-
class InternalMapVXMap extends loggeable_1.Loggeable {
|
|
225
|
-
/** Whether a floor change is still rebuilding, so markers wait for it. */
|
|
226
|
-
get floorSettlePending() {
|
|
227
|
-
return this.floorSettleDeadline != null && this.now() < this.floorSettleDeadline;
|
|
228
|
-
}
|
|
229
|
-
/** Wall clock for the settle deadline; `Date.now` where `performance` is absent. */
|
|
230
|
-
now() {
|
|
231
|
-
return typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
232
|
-
}
|
|
233
|
-
/**
|
|
234
|
-
* Creates a new map instance with the parameters specified in mapConfig and assigns it to the provided HTML container.
|
|
235
|
-
* @param mapConfig Configuration parameters for the map.
|
|
236
|
-
* @param container Container where the assignment of the created map is required.
|
|
237
|
-
* @param parentPlace Parent place required for map floor mapping, along with the display of subplaces.
|
|
238
|
-
* @param token The token provided for the creation and fetching of map information.
|
|
239
|
-
* @returns A new instance of MapVXMap.
|
|
240
|
-
*/
|
|
241
|
-
constructor(mapConfig, container, token) {
|
|
242
|
-
var _a, _b, _c, _d;
|
|
243
|
-
super();
|
|
244
|
-
/**
|
|
245
|
-
* Monotonic counter for {@link addLiveRoute} calls. Refreshes overlap (each one
|
|
246
|
-
* awaits a fetch), so only the call holding the latest ticket may touch the
|
|
247
|
-
* map — an older response resolving last is discarded instead of clobbering
|
|
248
|
-
* the newer route.
|
|
249
|
-
*/
|
|
250
|
-
this.liveRouteTicket = 0;
|
|
251
|
-
this.potentialParentPlaces = [];
|
|
252
|
-
this.innerFloors = [];
|
|
253
|
-
this.subPlaces = [];
|
|
254
|
-
this.currentFloor = "";
|
|
255
|
-
this.baseFilters = {};
|
|
256
|
-
/**
|
|
257
|
-
* Layer ids whose declared (base) filter has already been resolved — either captured
|
|
258
|
-
* into {@link baseFilters} or confirmed absent.
|
|
259
|
-
*
|
|
260
|
-
* `baseFilters[id] === undefined` cannot carry that information on its own: it means
|
|
261
|
-
* both "not looked at yet" and "looked at, the layer declares no filter". Without the
|
|
262
|
-
* distinction, a later pass re-reads the layer's LIVE filter — which by then is the
|
|
263
|
-
* one this class wrote — and stores our own floor/public-place filter as if it were
|
|
264
|
-
* the style's, so the layer keeps filtering by a stale floor forever.
|
|
265
|
-
*
|
|
266
|
-
* An id lands here the moment its declared filter is read from the style spec, or the
|
|
267
|
-
* moment this class writes a filter to that layer, whichever happens first. Layers
|
|
268
|
-
* added asynchronously after a `setStyle` swap are in neither situation, so their
|
|
269
|
-
* genuinely pristine live filter is still captured on first touch.
|
|
270
|
-
*/
|
|
271
|
-
this.capturedBaseFilters = new Set();
|
|
272
|
-
/**
|
|
273
|
-
* Monotonic id of the most recently STARTED style swap. Every {@link setStyle} call
|
|
274
|
-
* captures its own value and checks it again in each async continuation, so a
|
|
275
|
-
* superseded swap can neither touch the shared bookkeeping (`styleReadyTimer`,
|
|
276
|
-
* `pendingStyleSwapListener`) nor apply its obsolete style to the live map.
|
|
277
|
-
*
|
|
278
|
-
* A per-call `readied` flag is not enough: the timer and the listener slot are
|
|
279
|
-
* single-valued instance state, so two overlapping swaps otherwise clobber each
|
|
280
|
-
* other's — one caller's overlay is released by the other's failure, or an
|
|
281
|
-
* abandoned listener re-seeds filters from layer ids the current style lacks.
|
|
282
|
-
*/
|
|
283
|
-
this.styleSwapGeneration = 0;
|
|
284
|
-
this.markers = [];
|
|
285
|
-
this.circles = [];
|
|
286
|
-
this.enableHover = false;
|
|
287
|
-
this.hoveredId = "unselected";
|
|
288
|
-
this.failedTiles = new Set();
|
|
289
|
-
/**
|
|
290
|
-
* Retry bookkeeping per failed tile. A WeakMap so it can never outlive the tiles
|
|
291
|
-
* themselves — entries disappear with the tile objects, which is why none of the
|
|
292
|
-
* `failedTiles.clear()` paths have to reset it.
|
|
293
|
-
*/
|
|
294
|
-
this.tileRetryState = new WeakMap();
|
|
295
|
-
this.geoLocation = navigator.geolocation;
|
|
296
|
-
/** Floor-change settle bookkeeping (see `updateMarkersWhenFloorSettles`). */
|
|
297
|
-
this.floorSettleToken = 0;
|
|
298
|
-
/**
|
|
299
|
-
* When the floor-change settle stops holding markers back, or `null` when no
|
|
300
|
-
* change is in flight.
|
|
301
|
-
*
|
|
302
|
-
* A deadline rather than a flag, because `addMarker` reads it: the poll that
|
|
303
|
-
* would clear a flag runs on `requestAnimationFrame`, which does not fire in
|
|
304
|
-
* a backgrounded tab and does not run at all for a map torn down through
|
|
305
|
-
* MapLibre's own `remove()` rather than `destroyMap`. A flag left set there
|
|
306
|
-
* makes every later `addMarker` skip the call that attaches the marker, for
|
|
307
|
-
* good; a deadline expires on its own.
|
|
308
|
-
*/
|
|
309
|
-
this.floorSettleDeadline = null;
|
|
310
|
-
/** Owns the deck.gl overlay + GLB scene; created on style load only when 3D is active. */
|
|
311
|
-
this.scene3D = undefined;
|
|
312
|
-
if (_rtl_1.rtlLanguages.includes((_a = mapConfig.lang) !== null && _a !== void 0 ? _a : "")) {
|
|
313
|
-
this.setRTLSupport();
|
|
314
|
-
}
|
|
315
|
-
this.repository = new repository_1.Repository(token, (_b = mapConfig.lang) !== null && _b !== void 0 ? _b : "en", mapConfig.apiUrl, undefined, mapConfig.mapvxRequestContext, {
|
|
316
|
-
routeApiUrl: mapConfig.routeApiUrl,
|
|
317
|
-
healthApiUrl: mapConfig.healthApiUrl,
|
|
318
|
-
legacyApiUrl: mapConfig.legacyApiUrl,
|
|
319
|
-
portalApiUrl: mapConfig.portalApiUrl,
|
|
320
|
-
});
|
|
321
|
-
this.token = token;
|
|
322
|
-
this.authToken = mapConfig.authToken;
|
|
323
|
-
this.currentLang = (_c = mapConfig.lang) !== null && _c !== void 0 ? _c : "en";
|
|
324
|
-
this.parentPlaceId = mapConfig.parentPlaceId;
|
|
325
|
-
this.routeController = new routeController_1.RouteController(this);
|
|
326
|
-
this.userLocationMarker = undefined;
|
|
327
|
-
this.watchPositionID = undefined;
|
|
328
|
-
this.onFloorChange = mapConfig.onFloorChange;
|
|
329
|
-
this.onParentPlaceChange = mapConfig.onParentPlaceChange;
|
|
330
|
-
this.mode = (_d = mapConfig.mode) !== null && _d !== void 0 ? _d : "2D";
|
|
331
|
-
this.styleRef = mapConfig.styleRef;
|
|
332
|
-
this.styleTheme = mapConfig.styleTheme;
|
|
333
|
-
this.scene3DConfig = mapConfig.scene3D;
|
|
334
|
-
this.initialCenter = mapConfig.center;
|
|
335
|
-
this.tileCacheConfig = (() => {
|
|
336
|
-
const merged = Object.assign(Object.assign({}, mapConfig_1.DEFAULT_TILE_CACHE_CONFIG), mapConfig.tileCache);
|
|
337
|
-
merged.maxTiles = Math.min(merged.maxTiles, mapConfig_1.MAPLIBRE_MAX_TILE_CACHE_HARD_CAP);
|
|
338
|
-
return merged;
|
|
339
|
-
})();
|
|
340
|
-
this.markerLayoutConfig = Object.assign(Object.assign({}, mapConfig_1.DEFAULT_MARKER_LAYOUT_CONFIG), mapConfig.markerLayout);
|
|
341
|
-
this.markerLayoutController = new markerLayoutController_1.MarkerLayoutController(this, this.markerLayoutConfig);
|
|
342
|
-
// `container` is the very element handed to MapLibre below, and every
|
|
343
|
-
// marker is mounted inside it, so publishing the gap/width variables here
|
|
344
|
-
// covers markers added before the map's "load" event fires.
|
|
345
|
-
this.markerLayoutController.applyLayoutVariables(container);
|
|
346
|
-
if (mapConfig.parentPlaceId != null) {
|
|
347
|
-
this.initialPlaceDetailSetUp(mapConfig.parentPlaceId, mapConfig.authToken);
|
|
348
|
-
}
|
|
349
|
-
if (mapConfig.otherPotentialParentPlacesIds &&
|
|
350
|
-
mapConfig.otherPotentialParentPlacesIds.length > 0) {
|
|
351
|
-
this.loadOtherPotentialParentPlaces(mapConfig.otherPotentialParentPlacesIds);
|
|
352
|
-
}
|
|
353
|
-
this.enableHover = mapConfig.enableHover || false;
|
|
354
|
-
this.initialMapStyleSetUp(mapConfig, container);
|
|
355
|
-
}
|
|
356
|
-
setRTLSupport() {
|
|
357
|
-
(0, maplibre_gl_1.setRTLTextPlugin)("https://unpkg.com/@mapbox/mapbox-gl-rtl-text@0.3.0/dist/mapbox-gl-rtl-text.js", false).catch(() => {
|
|
358
|
-
// RTL support may fail in some browsers - this is expected
|
|
359
|
-
});
|
|
360
|
-
}
|
|
361
|
-
/**
|
|
362
|
-
* Transform style sources to use cached-tile:// protocol for tile URLs.
|
|
363
|
-
* This enables the service worker to intercept and cache tile requests.
|
|
364
|
-
* @param style The original style specification
|
|
365
|
-
* @returns A new style with transformed tile URLs
|
|
366
|
-
*/
|
|
367
|
-
transformStyleForCaching(style) {
|
|
368
|
-
if (!style) {
|
|
369
|
-
return style;
|
|
370
|
-
}
|
|
371
|
-
const transformedStyle = deepClone(style);
|
|
372
|
-
if (transformedStyle === null || transformedStyle === void 0 ? void 0 : transformedStyle.sources) {
|
|
373
|
-
for (const sourceKey of Object.keys(transformedStyle.sources)) {
|
|
374
|
-
const source = transformedStyle.sources[sourceKey];
|
|
375
|
-
// Transform vector and raster tile sources
|
|
376
|
-
// Only transform source.tiles (actual tile URLs), NOT source.url (TileJSON metadata URL)
|
|
377
|
-
// source.url points to a TileJSON file that returns JSON, not binary tile data
|
|
378
|
-
if (source.tiles && Array.isArray(source.tiles)) {
|
|
379
|
-
source.tiles = source.tiles.map((tileUrl) => {
|
|
380
|
-
if (tileUrl.startsWith("https://")) {
|
|
381
|
-
return tileUrl.replace("https://", "cached-tile://");
|
|
382
|
-
}
|
|
383
|
-
return tileUrl;
|
|
384
|
-
});
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
return transformedStyle;
|
|
389
|
-
}
|
|
390
|
-
initialPlaceDetailSetUp(parentPlaceId, withBearerToken) {
|
|
391
|
-
if (withBearerToken) {
|
|
392
|
-
this.repository
|
|
393
|
-
.fetchAndParseAnyPlaceDetail({ id: parentPlaceId }, withBearerToken)
|
|
394
|
-
.then((place) => {
|
|
395
|
-
this.changeParentPlaceTo(place);
|
|
396
|
-
this.potentialParentPlaces.push(place);
|
|
397
|
-
})
|
|
398
|
-
.catch(console.error);
|
|
399
|
-
return;
|
|
400
|
-
}
|
|
401
|
-
this.repository
|
|
402
|
-
.fetchAndParsePlaceDetail({ id: parentPlaceId })
|
|
403
|
-
.then((place) => {
|
|
404
|
-
this.changeParentPlaceTo(place);
|
|
405
|
-
this.potentialParentPlaces.push(place);
|
|
406
|
-
})
|
|
407
|
-
.catch(console.error);
|
|
408
|
-
}
|
|
409
|
-
loadOtherPotentialParentPlaces(ids) {
|
|
410
|
-
Promise.all(ids.map((id) => this.repository.fetchAndParsePlaceDetail({ id })))
|
|
411
|
-
.then((places) => {
|
|
412
|
-
this.potentialParentPlaces.push(...places);
|
|
413
|
-
})
|
|
414
|
-
.catch(console.error);
|
|
415
|
-
}
|
|
416
|
-
subPlacesLoad(parentPlaceId) {
|
|
417
|
-
this.repository
|
|
418
|
-
.fetchAndParseSubPlaces(parentPlaceId)
|
|
419
|
-
.then((subPlaces) => {
|
|
420
|
-
this.subPlaces = subPlaces;
|
|
421
|
-
})
|
|
422
|
-
.catch(console.error);
|
|
423
|
-
}
|
|
424
|
-
initialMapStyleSetUp(mapConfig, container) {
|
|
425
|
-
if (mapConfig.institutionId && mapConfig.authToken) {
|
|
426
|
-
this.repository
|
|
427
|
-
.fetchAndParseAnyMapStyle(mapConfig.institutionId, mapConfig.authToken)
|
|
428
|
-
.then((style) => {
|
|
429
|
-
return this.onMapStyleLoaded(mapConfig, container, style);
|
|
430
|
-
})
|
|
431
|
-
.catch(console.error);
|
|
432
|
-
return;
|
|
433
|
-
}
|
|
434
|
-
// Through the same helper as `setStyle` and `changeParentPlaceTo`. This PR
|
|
435
|
-
// forwards `styleRef`/`styleTheme` to the initial load too, and a ref scoped
|
|
436
|
-
// to the user's client rather than to the api key resolves only over bearer
|
|
437
|
-
// — fetched over the api-key path it 4xx'd, and since this is the call that
|
|
438
|
-
// creates the map, the map was never created at all.
|
|
439
|
-
this.fetchVenueStyle(mapConfig.parentPlaceId, mapConfig.styleRef, mapConfig.styleTheme)
|
|
440
|
-
.then((style) => {
|
|
441
|
-
return this.onMapStyleLoaded(mapConfig, container, style);
|
|
442
|
-
})
|
|
443
|
-
.catch((error) => {
|
|
444
|
-
this.logError(error, { functionName: "initialMapStyleSetUp" });
|
|
445
|
-
});
|
|
446
|
-
}
|
|
447
|
-
onMapStyleLoaded(mapConfig, container, style) {
|
|
448
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
449
|
-
// Determine if service worker caching should be enabled
|
|
450
|
-
const useServiceWorkerCaching = this.tileCacheConfig.enabled && this.tileCacheConfig.persistToServiceWorker;
|
|
451
|
-
if (useServiceWorkerCaching) {
|
|
452
|
-
registerCachedTileProtocol(this.tileCacheConfig.maxConcurrentTileFetches);
|
|
453
|
-
}
|
|
454
|
-
// Transform tile URLs only if service worker caching is enabled
|
|
455
|
-
const finalStyle = useServiceWorkerCaching ? this.transformStyleForCaching(style) : style;
|
|
456
|
-
const mapOptions = Object.assign({ container, style: finalStyle, center: mapConfig.center, zoom: mapConfig.zoom, pitch: (_a = mapConfig.pitch) !== null && _a !== void 0 ? _a : 0, attributionControl: false, maplibreLogo: false, bearingSnap: (_b = mapConfig.bearingSnap) !== null && _b !== void 0 ? _b : 0, cancelPendingTileRequestsWhileZooming: true,
|
|
457
|
-
// Use configured maxTiles for MapLibre's memory cache
|
|
458
|
-
maxTileCacheSize: this.tileCacheConfig.maxTiles }, (0, mapInteractionOptions_1.buildInteractionOptions)(mapConfig));
|
|
459
|
-
if (mapConfig.maxZoom)
|
|
460
|
-
mapOptions.maxZoom = mapConfig.maxZoom;
|
|
461
|
-
if (mapConfig.minZoom)
|
|
462
|
-
mapOptions.minZoom = mapConfig.minZoom;
|
|
463
|
-
// Reduced internal resolution on request (fill-rate relief for low-end GPUs);
|
|
464
|
-
// MapLibre keeps devicePixelRatio when the option is left out.
|
|
465
|
-
if (mapConfig.pixelRatio !== undefined)
|
|
466
|
-
mapOptions.pixelRatio = mapConfig.pixelRatio;
|
|
467
|
-
// `0` disables the post-gesture fade tail entirely; keep MapLibre's default otherwise.
|
|
468
|
-
if (mapConfig.fadeDuration !== undefined)
|
|
469
|
-
mapOptions.fadeDuration = mapConfig.fadeDuration;
|
|
470
|
-
if (mapConfig.maxBounds && mapConfig.maxBounds.length > 1) {
|
|
471
|
-
const boundingBox = (0, utils_1.getBoundingBox)(mapConfig.maxBounds);
|
|
472
|
-
mapOptions.maxBounds = new maplibre_gl_1.LngLatBounds([boundingBox[0].lng, boundingBox[0].lat], [boundingBox[1].lng, boundingBox[1].lat]);
|
|
473
|
-
}
|
|
474
|
-
this.map = new maplibre_gl_1.Map(mapOptions);
|
|
475
|
-
// Create the 3D controller only when the map opts into 3D. All heavy WebGL / deck.gl work is
|
|
476
|
-
// owned by it; in 2D mode `this.scene3D` stays undefined and every hook below is a no-op.
|
|
477
|
-
if (this.is3DSceneActive() && this.scene3DConfig) {
|
|
478
|
-
this.scene3D = new scene3DController_1.Scene3DController(this.map, this.scene3DConfig, this.initialCenter, mapConfig.onScene3DModelsProgress);
|
|
479
|
-
}
|
|
480
|
-
// When rotation is disabled we still want pinch-to-zoom to work, so the
|
|
481
|
-
// two-finger rotation is turned off here instead of via the constructor.
|
|
482
|
-
if ((0, mapInteractionOptions_1.shouldDisableTouchRotation)(mapConfig)) {
|
|
483
|
-
(_d = (_c = this.map.touchZoomRotate) === null || _c === void 0 ? void 0 : _c.disableRotation) === null || _d === void 0 ? void 0 : _d.call(_c);
|
|
484
|
-
}
|
|
485
|
-
this.map.addControl(new maplibre_gl_1.NavigationControl({
|
|
486
|
-
showCompass: (_e = mapConfig.showCompass) !== null && _e !== void 0 ? _e : true,
|
|
487
|
-
showZoom: (_f = mapConfig.showZoom) !== null && _f !== void 0 ? _f : true,
|
|
488
|
-
}), (_g = mapConfig.navigationPosition) !== null && _g !== void 0 ? _g : "top-right");
|
|
489
|
-
this.map.on("load", () => {
|
|
490
|
-
var _a, _b;
|
|
491
|
-
// 3D libraries load asynchronously; the rest of the load flow does not wait on them.
|
|
492
|
-
void ((_a = this.scene3D) === null || _a === void 0 ? void 0 : _a.onMapLoad());
|
|
493
|
-
this.whenStyleUpdates(style);
|
|
494
|
-
if (mapConfig.lang)
|
|
495
|
-
this.setLayersForLanguage(mapConfig.lang);
|
|
496
|
-
(_b = mapConfig.onMapReady) === null || _b === void 0 ? void 0 : _b.call(mapConfig);
|
|
497
|
-
this.onHover();
|
|
498
|
-
this.subscribeToFailedTiles();
|
|
499
|
-
this.markerLayoutController.attach();
|
|
500
|
-
});
|
|
501
|
-
// Self-healing for circles: a full style reload wipes custom sources and
|
|
502
|
-
// layers, and not every restyle path goes through whenStyleUpdates.
|
|
503
|
-
this.map.on("styledata", () => {
|
|
504
|
-
if (this.circles.length > 0)
|
|
505
|
-
this.ensureCircleLayers();
|
|
506
|
-
});
|
|
507
|
-
this.map.on("zoom", () => {
|
|
508
|
-
var _a;
|
|
509
|
-
(_a = this.scene3D) === null || _a === void 0 ? void 0 : _a.onZoom();
|
|
510
|
-
});
|
|
511
|
-
this.map.on("zoomend", () => {
|
|
512
|
-
var _a;
|
|
513
|
-
(_a = mapConfig.onZoomEnd) === null || _a === void 0 ? void 0 : _a.call(mapConfig, this.getZoomLevel());
|
|
514
|
-
});
|
|
515
|
-
this.map.on("rotate", () => {
|
|
516
|
-
var _a;
|
|
517
|
-
(_a = mapConfig.onRotate) === null || _a === void 0 ? void 0 : _a.call(mapConfig, this.map.getBearing());
|
|
518
|
-
});
|
|
519
|
-
this.defaultClickListener();
|
|
520
|
-
}
|
|
521
|
-
setBaseFilters(style) {
|
|
522
|
-
if (!style)
|
|
523
|
-
return;
|
|
524
|
-
const publicPlaceFilter = ["==", ["get", "public_place"], true];
|
|
525
|
-
style.layers
|
|
526
|
-
.filter((layer) => layer.id.startsWith("indoor-"))
|
|
527
|
-
.forEach((layer) => {
|
|
528
|
-
// Seed from the STYLE SPEC — the only pristine source — and do it BEFORE the
|
|
529
|
-
// setFilter below overwrites the live filter. Reading it back afterwards would
|
|
530
|
-
// return our own `publicPlaceFilter` and store it as the layer's declared one,
|
|
531
|
-
// turning `public_place` from an OR into an AND and hiding every private
|
|
532
|
-
// feature on the selected floor. Every indoor layer is covered, not just
|
|
533
|
-
// fill/line/symbol: a `circle` layer is discriminated by its filter just the
|
|
534
|
-
// same, and dropping it makes the layer repaint every feature.
|
|
535
|
-
// `filter` is absent from the union member for `background` layers, hence the
|
|
536
|
-
// structural read rather than `layer.filter`.
|
|
537
|
-
const declared = layer.filter;
|
|
538
|
-
this.captureDeclaredBaseFilter(layer.id, declared);
|
|
539
|
-
const baseFilter = this.baseFilters[layer.id];
|
|
540
|
-
if (baseFilter != null) {
|
|
541
|
-
const finalFilter = ["all", baseFilter, publicPlaceFilter];
|
|
542
|
-
this.map.setFilter(layer.id, finalFilter);
|
|
543
|
-
}
|
|
544
|
-
else {
|
|
545
|
-
this.map.setFilter(layer.id, publicPlaceFilter);
|
|
546
|
-
}
|
|
547
|
-
});
|
|
548
|
-
}
|
|
549
|
-
/**
|
|
550
|
-
* Records a layer's declared (base) filter the first time the layer is seen, from an
|
|
551
|
-
* explicit value (a style-spec `filter`) when one is available, otherwise by reading
|
|
552
|
-
* the live filter — which is only pristine for layers this class has never written to.
|
|
553
|
-
*
|
|
554
|
-
* Marking happens even when no filter exists, so the layer is never revisited: on a
|
|
555
|
-
* second visit its live filter is one we produced, and storing that as the "declared"
|
|
556
|
-
* filter is what freezes a layer to a stale floor.
|
|
557
|
-
*
|
|
558
|
-
* @param layerId Id of the layer whose base filter should be resolved.
|
|
559
|
-
* @param declared Declared filter straight from the style spec, when the caller has it.
|
|
560
|
-
*/
|
|
561
|
-
captureDeclaredBaseFilter(layerId, declared) {
|
|
562
|
-
if (this.capturedBaseFilters.has(layerId))
|
|
563
|
-
return;
|
|
564
|
-
this.capturedBaseFilters.add(layerId);
|
|
565
|
-
const baseFilter = declared !== null && declared !== void 0 ? declared : this.safeGetFilter(layerId);
|
|
566
|
-
if (baseFilter != null) {
|
|
567
|
-
this.baseFilters[layerId] = baseFilter;
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
setLayersForLanguage(lang) {
|
|
571
|
-
var _a, _b;
|
|
572
|
-
// outdoor layers
|
|
573
|
-
const layersName = [
|
|
574
|
-
"aerodrome_label",
|
|
575
|
-
"mountain_peak",
|
|
576
|
-
"park",
|
|
577
|
-
"place",
|
|
578
|
-
"poi",
|
|
579
|
-
"transportation_name",
|
|
580
|
-
"water_name",
|
|
581
|
-
"waterway",
|
|
582
|
-
];
|
|
583
|
-
const layers = (_b = (_a = this.map.getStyle()) === null || _a === void 0 ? void 0 : _a.layers) !== null && _b !== void 0 ? _b : [];
|
|
584
|
-
const layersIds = layers
|
|
585
|
-
.filter((layer) => {
|
|
586
|
-
var _a;
|
|
587
|
-
return layer.type === "symbol" &&
|
|
588
|
-
layer.source === "openmaptiles" &&
|
|
589
|
-
layersName.includes((_a = layer["source-layer"]) !== null && _a !== void 0 ? _a : "");
|
|
590
|
-
})
|
|
591
|
-
.map((layer) => {
|
|
592
|
-
return layer.id;
|
|
593
|
-
});
|
|
594
|
-
layersIds.forEach((id) => {
|
|
595
|
-
try {
|
|
596
|
-
const property = this.map.getLayoutProperty(id, "symbol-placement");
|
|
597
|
-
this.map.setLayoutProperty(id, "text-field", [
|
|
598
|
-
"case",
|
|
599
|
-
["has", `name:${lang}`],
|
|
600
|
-
["get", `name:${lang}`],
|
|
601
|
-
[
|
|
602
|
-
"format",
|
|
603
|
-
["get", "name:latin"],
|
|
604
|
-
property === "line" ? " " : "\n",
|
|
605
|
-
["get", "name:nonlatin"],
|
|
606
|
-
],
|
|
607
|
-
]);
|
|
608
|
-
}
|
|
609
|
-
catch (error) {
|
|
610
|
-
this.debugLog("Layer language update failed (layer may not exist)", error);
|
|
611
|
-
}
|
|
612
|
-
});
|
|
613
|
-
// indoor layers
|
|
614
|
-
const indoorLayerIds = layers
|
|
615
|
-
.filter((layer) => {
|
|
616
|
-
var _a;
|
|
617
|
-
return layer.type === "symbol" &&
|
|
618
|
-
layer.source === "indoorequal" &&
|
|
619
|
-
((_a = layer.layout) === null || _a === void 0 ? void 0 : _a["text-field"]) !== undefined;
|
|
620
|
-
})
|
|
621
|
-
.map((layer) => {
|
|
622
|
-
return layer.id;
|
|
623
|
-
});
|
|
624
|
-
indoorLayerIds.forEach((id) => {
|
|
625
|
-
try {
|
|
626
|
-
this.map.setLayoutProperty(id, "text-field", [
|
|
627
|
-
"case",
|
|
628
|
-
["has", `name:${lang}`],
|
|
629
|
-
["get", `name:${lang}`],
|
|
630
|
-
["get", "name"],
|
|
631
|
-
]);
|
|
632
|
-
}
|
|
633
|
-
catch (error) {
|
|
634
|
-
this.debugLog("Layer language update failed (layer may not exist)", error);
|
|
635
|
-
}
|
|
636
|
-
});
|
|
637
|
-
}
|
|
638
|
-
/**
|
|
639
|
-
* Add values to identify the client.
|
|
640
|
-
* @param segmentation The pair values to use as segmentation.
|
|
641
|
-
* @returns The segmentation with the added values.
|
|
642
|
-
*/
|
|
643
|
-
prepareSegmentation(segmentation) {
|
|
644
|
-
var _a;
|
|
645
|
-
return Object.assign(Object.assign({}, segmentation), { token: this.token, parentPlaceId: (_a = this.parentPlaceId) !== null && _a !== void 0 ? _a : "None" });
|
|
646
|
-
}
|
|
647
|
-
destroyMap() {
|
|
648
|
-
var _a;
|
|
649
|
-
this.circles = [];
|
|
650
|
-
this.markerLayoutController.destroy();
|
|
651
|
-
// Drop any in-flight `setStyle` bookkeeping while the map still exists: the
|
|
652
|
-
// listener has to be detached before `map.remove()`, and the safety timer must not
|
|
653
|
-
// outlive the map and call `onStyleReady` for a map that is already gone.
|
|
654
|
-
this.clearPendingStyleSwapListener();
|
|
655
|
-
if (this.styleReadyTimer) {
|
|
656
|
-
clearTimeout(this.styleReadyTimer);
|
|
657
|
-
this.styleReadyTimer = undefined;
|
|
658
|
-
}
|
|
659
|
-
// Drop any floor-settle poll in flight: bumping the token makes the next
|
|
660
|
-
// `requestAnimationFrame` callback return instead of re-queueing itself and
|
|
661
|
-
// re-attaching markers to a map that has already been removed.
|
|
662
|
-
this.floorSettleToken++;
|
|
663
|
-
this.floorSettleDeadline = null;
|
|
664
|
-
// Dispose the 3D scene's GPU resources before the map (and its shared WebGL context) go away —
|
|
665
|
-
// MapLibre's own teardown never calls the custom GLB layer's `onRemove`.
|
|
666
|
-
(_a = this.scene3D) === null || _a === void 0 ? void 0 : _a.dispose();
|
|
667
|
-
this.map.remove();
|
|
668
|
-
this.unsubscribeFromFailedTiles();
|
|
669
|
-
}
|
|
670
|
-
clearTileCache() {
|
|
671
|
-
try {
|
|
672
|
-
// Clear the failed tiles tracking set
|
|
673
|
-
this.failedTiles.clear();
|
|
674
|
-
// Force MapLibre to reload all visible tiles by triggering a repaint
|
|
675
|
-
// Note: MapLibre doesn't expose a direct cache clear method, so we
|
|
676
|
-
// trigger a style refresh which causes tiles to be re-requested
|
|
677
|
-
if (this.map) {
|
|
678
|
-
this.map.triggerRepaint();
|
|
679
|
-
}
|
|
680
|
-
this.logEvent("clearTileCache");
|
|
681
|
-
}
|
|
682
|
-
catch (error) {
|
|
683
|
-
this.debugLog("Tile cache clear failed (cache may not exist)", error);
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
/**
|
|
687
|
-
* Whether 3D scene features (deck.gl overlay, declarative GLB custom layer, etc.) are active.
|
|
688
|
-
* Requires {@link MapConfig.mode} `"3D"` and {@link Scene3DConfig.enabled} not `false`.
|
|
689
|
-
* The heavy WebGL / deck.gl work lives in {@link Scene3DController}; this only decides whether one
|
|
690
|
-
* is created and wired up. When `scene3D` is omitted, 3D features remain available.
|
|
691
|
-
*/
|
|
692
|
-
is3DSceneActive() {
|
|
693
|
-
var _a;
|
|
694
|
-
return this.mode === "3D" && ((_a = this.scene3DConfig) === null || _a === void 0 ? void 0 : _a.enabled) !== false;
|
|
695
|
-
}
|
|
696
|
-
getCurrentFloor() {
|
|
697
|
-
var _a;
|
|
698
|
-
return (_a = this.currentFloor) !== null && _a !== void 0 ? _a : "";
|
|
699
|
-
}
|
|
700
|
-
/**
|
|
701
|
-
* Sets the route path used for GLB route-transparency collision, so 3D models a route passes
|
|
702
|
-
* through fade and reveal the floor plan underneath. Called by {@link RouteController} whenever
|
|
703
|
-
* the set of drawn routes changes. Points are tagged with the floor their segment is drawn on;
|
|
704
|
-
* only points on the displayed floor participate. Pass an empty array to clear.
|
|
705
|
-
*
|
|
706
|
-
* No-op unless the 3D scene is active.
|
|
707
|
-
*/
|
|
708
|
-
setRouteCollisionPath(points) {
|
|
709
|
-
var _a;
|
|
710
|
-
if (!this.is3DSceneActive()) {
|
|
711
|
-
return;
|
|
712
|
-
}
|
|
713
|
-
(_a = this.scene3D) === null || _a === void 0 ? void 0 : _a.setRouteCollisionPath(points);
|
|
714
|
-
}
|
|
715
|
-
/**
|
|
716
|
-
* Performs changes when parent place is updated
|
|
717
|
-
* @param place new parent place
|
|
718
|
-
* @param updateStyle defines if new parent place style must be set
|
|
719
|
-
*/
|
|
720
|
-
changeParentPlaceTo(place, updateStyle = false, onStyleReady) {
|
|
721
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
722
|
-
this.parentPlace = place;
|
|
723
|
-
this.parentPlaceId = place === null || place === void 0 ? void 0 : place.mapvxId;
|
|
724
|
-
this.innerFloors = (_b = (_a = place === null || place === void 0 ? void 0 : place.innerFloors) === null || _a === void 0 ? void 0 : _a.sort((a, b) => a.index - b.index)) !== null && _b !== void 0 ? _b : [];
|
|
725
|
-
this.currentFloor =
|
|
726
|
-
(_f = (_d = (_c = this.innerFloors.find((floor) => floor.defaultFloor)) === null || _c === void 0 ? void 0 : _c.key) !== null && _d !== void 0 ? _d : (_e = this.innerFloors[0]) === null || _e === void 0 ? void 0 : _e.key) !== null && _f !== void 0 ? _f : "";
|
|
727
|
-
if (place) {
|
|
728
|
-
this.subPlacesLoad(place.mapvxId);
|
|
729
|
-
}
|
|
730
|
-
(_g = this.scene3D) === null || _g === void 0 ? void 0 : _g.onFloorChange(this.currentFloor);
|
|
731
|
-
if (updateStyle) {
|
|
732
|
-
this.fetchVenueStyle(place === null || place === void 0 ? void 0 : place.mapvxId, this.styleRef, this.styleTheme)
|
|
733
|
-
.then((style) => {
|
|
734
|
-
const useServiceWorkerCaching = this.tileCacheConfig.enabled && this.tileCacheConfig.persistToServiceWorker;
|
|
735
|
-
const finalStyle = useServiceWorkerCaching ? this.transformStyleForCaching(style) : style;
|
|
736
|
-
this.map.setStyle(finalStyle);
|
|
737
|
-
// The new place's style declares its own filters for the same `indoor-*` layer
|
|
738
|
-
// ids. Without this reset every id is still marked captured from the previous
|
|
739
|
-
// place, so `captureDeclaredBaseFilter` early-returns for all of them and the
|
|
740
|
-
// previous place's expressions keep filtering the new style forever. (The
|
|
741
|
-
// `switch (layer.type)` block that used to re-read them on every style load was
|
|
742
|
-
// removed when capture became once-per-id, which is what made this necessary.)
|
|
743
|
-
this.resetBaseFilters();
|
|
744
|
-
this.whenStyleUpdates(finalStyle);
|
|
745
|
-
onStyleReady === null || onStyleReady === void 0 ? void 0 : onStyleReady();
|
|
746
|
-
})
|
|
747
|
-
.catch((error) => {
|
|
748
|
-
this.logError(error, { functionName: "changeParentPlaceTo" });
|
|
749
|
-
// Release the caller either way. `onStyleReady` is what a consumer
|
|
750
|
-
// hides its loading overlay on, so swallowing the failure here left
|
|
751
|
-
// the overlay up for good rather than merely unstyled.
|
|
752
|
-
onStyleReady === null || onStyleReady === void 0 ? void 0 : onStyleReady();
|
|
753
|
-
});
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
/**
|
|
757
|
-
* Fetch the venue style over whichever endpoint this map is authorized on.
|
|
758
|
-
*
|
|
759
|
-
* A bearer token authorizes by the user's client access rather than the api
|
|
760
|
-
* key's scopes, so a client's branded style resolves without granting that
|
|
761
|
-
* place to a shared api key; api-key consumers keep the
|
|
762
|
-
* `/api/sdk/v2/mapStyleJson` path.
|
|
763
|
-
*
|
|
764
|
-
* Both callers go through here so they cannot disagree. They used to: once
|
|
765
|
-
* `setStyle` had stored a `styleRef` it resolved over bearer, the next
|
|
766
|
-
* parent-place change refetched that same ref over the api-key path, whose
|
|
767
|
-
* key does not scope it — a non-ok response, a rejected promise, and a style
|
|
768
|
-
* that was never applied, with `onStyleReady` left unfired under a caller's
|
|
769
|
-
* loading overlay.
|
|
770
|
-
*/
|
|
771
|
-
fetchVenueStyle(placeId, styleRef, styleTheme) {
|
|
772
|
-
return this.authToken
|
|
773
|
-
? this.repository.fetchAndParseMapStyleByBearer(placeId, this.authToken, styleRef, styleTheme)
|
|
774
|
-
: this.repository.fetchAndParseMapStyle(placeId, styleRef, styleTheme);
|
|
775
|
-
}
|
|
776
|
-
/**
|
|
777
|
-
* Update the parent place and the current floor.
|
|
778
|
-
* Does not update layers, markers, etc.
|
|
779
|
-
* @param parentPlaceId
|
|
780
|
-
* @param floorId
|
|
781
|
-
* @throws A error if the parent place is not one of the allowed places to show.
|
|
782
|
-
*/
|
|
783
|
-
updatePlaceAndFloor(parentPlaceId, floorId) {
|
|
784
|
-
var _a, _b, _c, _d, _e, _f;
|
|
785
|
-
const place = this.potentialParentPlaces.find((place) => place.mapvxId === parentPlaceId);
|
|
786
|
-
this.parentPlace = place;
|
|
787
|
-
this.parentPlaceId = place === null || place === void 0 ? void 0 : place.mapvxId;
|
|
788
|
-
this.innerFloors = (_a = place === null || place === void 0 ? void 0 : place.innerFloors.sort((a, b) => a.index - b.index)) !== null && _a !== void 0 ? _a : [];
|
|
789
|
-
this.currentFloor =
|
|
790
|
-
(_e = (_c = floorId !== null && floorId !== void 0 ? floorId : (_b = this.innerFloors.find((floor) => floor.defaultFloor)) === null || _b === void 0 ? void 0 : _b.key) !== null && _c !== void 0 ? _c : (_d = this.innerFloors[0]) === null || _d === void 0 ? void 0 : _d.key) !== null && _e !== void 0 ? _e : "";
|
|
791
|
-
(_f = this.scene3D) === null || _f === void 0 ? void 0 : _f.onFloorChange(this.currentFloor);
|
|
792
|
-
}
|
|
793
|
-
updateParentPlaceAndFloor(parentPlaceId, floorId, options) {
|
|
794
|
-
var _a, _b, _c, _d;
|
|
795
|
-
try {
|
|
796
|
-
this.updatePlaceAndFloor(parentPlaceId, floorId);
|
|
797
|
-
this.filterByFloorKey(this.currentFloor);
|
|
798
|
-
(_a = this.onParentPlaceChange) === null || _a === void 0 ? void 0 : _a.call(this, parentPlaceId);
|
|
799
|
-
(_b = this.onFloorChange) === null || _b === void 0 ? void 0 : _b.call(this, (_c = this.currentFloor) !== null && _c !== void 0 ? _c : "");
|
|
800
|
-
this.logEvent("updateParentPlaceAndFloor");
|
|
801
|
-
(_d = options === null || options === void 0 ? void 0 : options.onComplete) === null || _d === void 0 ? void 0 : _d.call(options);
|
|
802
|
-
}
|
|
803
|
-
catch (error) {
|
|
804
|
-
this.debugLog("Parent place/floor update failed", error);
|
|
805
|
-
}
|
|
806
|
-
}
|
|
807
|
-
setLang(lang) {
|
|
808
|
-
this.repository.setLang(lang);
|
|
809
|
-
this.currentLang = lang;
|
|
810
|
-
if (_rtl_1.rtlLanguages.includes(lang)) {
|
|
811
|
-
this.setRTLSupport();
|
|
812
|
-
}
|
|
813
|
-
// setLayersForLanguage reads this.map.getStyle()?.layers, which is empty
|
|
814
|
-
// until the style finishes loading. If setLang is called right after
|
|
815
|
-
// createMap (before the "load" event), apply it once the style is ready
|
|
816
|
-
// so the language change is not silently dropped.
|
|
817
|
-
if (this.map.isStyleLoaded()) {
|
|
818
|
-
this.setLayersForLanguage(lang);
|
|
819
|
-
}
|
|
820
|
-
else {
|
|
821
|
-
this.map.once("load", () => this.setLayersForLanguage(lang));
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
-
setParentPlace(place, updateStyle, onStyleReady) {
|
|
825
|
-
this.changeParentPlaceTo(place, updateStyle, onStyleReady);
|
|
826
|
-
}
|
|
827
|
-
setStyle(styleRef, styleTheme, onStyleReady) {
|
|
828
|
-
// Every swap gets a generation. All shared bookkeeping below is touched only while
|
|
829
|
-
// this swap is still the current one, so two overlapping calls cannot release each
|
|
830
|
-
// other's overlay or apply an obsolete style. `readied` stays per-call because each
|
|
831
|
-
// caller's callback must still fire exactly once.
|
|
832
|
-
const generation = ++this.styleSwapGeneration;
|
|
833
|
-
const isCurrent = () => this.styleSwapGeneration === generation;
|
|
834
|
-
let readied = false;
|
|
835
|
-
const reportReady = (error) => {
|
|
836
|
-
if (readied)
|
|
837
|
-
return;
|
|
838
|
-
readied = true;
|
|
839
|
-
// Only the current swap owns the shared timer; a superseded swap clearing it here
|
|
840
|
-
// would leave the live swap with no safety net (and its caller hidden forever).
|
|
841
|
-
if (isCurrent() && this.styleReadyTimer) {
|
|
842
|
-
clearTimeout(this.styleReadyTimer);
|
|
843
|
-
this.styleReadyTimer = undefined;
|
|
844
|
-
}
|
|
845
|
-
onStyleReady === null || onStyleReady === void 0 ? void 0 : onStyleReady(error);
|
|
846
|
-
};
|
|
847
|
-
// `onStyleReady` must fire on EVERY path — success, a rejected style request, or a
|
|
848
|
-
// swap that never settles — because callers keep the map hidden behind a loading
|
|
849
|
-
// overlay until it arrives. The safety timer is therefore armed before the request
|
|
850
|
-
// instead of inside `.then()`.
|
|
851
|
-
if (this.styleReadyTimer)
|
|
852
|
-
clearTimeout(this.styleReadyTimer);
|
|
853
|
-
this.styleReadyTimer = setTimeout(reportReady, STYLE_READY_TIMEOUT_MS);
|
|
854
|
-
// A previous swap may still hold a `styledata` listener whose style never reached the
|
|
855
|
-
// loaded state. Drop it before starting a new one: it captured an obsolete style and
|
|
856
|
-
// would otherwise fire on this swap, wiping `baseFilters` and calling `setFilter`
|
|
857
|
-
// with layer ids the new style does not have.
|
|
858
|
-
this.clearPendingStyleSwapListener();
|
|
859
|
-
// `this.map` is created asynchronously (see `onMapStyleLoaded`), so a `setStyle` call
|
|
860
|
-
// made right after `createMap` can land before it exists. Fail loudly instead of
|
|
861
|
-
// letting `this.map.setStyle` throw into `.catch`, which reported READY and left the
|
|
862
|
-
// caller revealing the previous style as if the swap had succeeded.
|
|
863
|
-
if (!this.map) {
|
|
864
|
-
const error = new Error("setStyle called before the map finished initializing; wait for the map's load event");
|
|
865
|
-
this.logError(error, { functionName: "setStyle" });
|
|
866
|
-
reportReady(error);
|
|
867
|
-
return;
|
|
868
|
-
}
|
|
869
|
-
const stylePromise = this.fetchVenueStyle(this.parentPlaceId, styleRef, styleTheme);
|
|
870
|
-
stylePromise
|
|
871
|
-
.then((style) => {
|
|
872
|
-
// A newer swap started while this style was in flight. Applying this one now
|
|
873
|
-
// would fight the newer style layer by layer, so drop it and let the newer swap
|
|
874
|
-
// own the map and its caller's callback.
|
|
875
|
-
if (!isCurrent())
|
|
876
|
-
return;
|
|
877
|
-
// Remember what is actually on screen: `changeParentPlaceTo` refetches the
|
|
878
|
-
// style with these, so without this an explicitly applied style would be
|
|
879
|
-
// silently reverted to the constructor's on the next parent-place change.
|
|
880
|
-
// After the generation check: a superseded swap never reached the map, so it
|
|
881
|
-
// must not claim to be what is on it.
|
|
882
|
-
this.styleRef = styleRef;
|
|
883
|
-
this.styleTheme = styleTheme;
|
|
884
|
-
const useServiceWorkerCaching = this.tileCacheConfig.enabled && this.tileCacheConfig.persistToServiceWorker;
|
|
885
|
-
const finalStyle = useServiceWorkerCaching ? this.transformStyleForCaching(style) : style;
|
|
886
|
-
// Full (non-diff) swap so the previewed style completely replaces the base —
|
|
887
|
-
// a diff would keep old base layers and reappear on the next floor change.
|
|
888
|
-
this.map.setStyle(finalStyle, { diff: false });
|
|
889
|
-
// Drop the previous style's base filters SYNCHRONOUSLY, right where the layers
|
|
890
|
-
// they describe stop existing. They are keyed by layer id and persist across
|
|
891
|
-
// swaps, so any code reading them before the apply pass below (the `idle`
|
|
892
|
-
// handler's `filterByFloorKey`, a floor change racing the swap) would AND the
|
|
893
|
-
// previous style's discriminator into the new style's layers — the stale-filter
|
|
894
|
-
// washout this reset exists to prevent. Gating it on `isStyleLoaded` left exactly
|
|
895
|
-
// that window open. Fresh declared filters are re-captured lazily in
|
|
896
|
-
// `applyLayerFilters` as each indoor layer is first touched.
|
|
897
|
-
this.resetBaseFilters();
|
|
898
|
-
// Re-apply floor filters, route/circle layers and markers ONLY once the new
|
|
899
|
-
// style's sources/layers have finished loading; running them mid-reload throws
|
|
900
|
-
// (`getSource` on an undefined style) and the floor filter would not stick.
|
|
901
|
-
const applyOnceLoaded = () => {
|
|
902
|
-
// Superseded: detach and leave the map to the newer swap. Without this the
|
|
903
|
-
// listener stays attached (the slot it was tracked in has been overwritten)
|
|
904
|
-
// and re-seeds filters from layer ids the current style does not have, which
|
|
905
|
-
// makes maplibre throw and aborts the newer swap's own listener.
|
|
906
|
-
if (!isCurrent()) {
|
|
907
|
-
this.map.off("styledata", applyOnceLoaded);
|
|
908
|
-
return;
|
|
909
|
-
}
|
|
910
|
-
if (!this.hasLoadedStyle())
|
|
911
|
-
return;
|
|
912
|
-
this.map.off("styledata", applyOnceLoaded);
|
|
913
|
-
if (this.pendingStyleSwapListener === applyOnceLoaded) {
|
|
914
|
-
this.pendingStyleSwapListener = undefined;
|
|
915
|
-
}
|
|
916
|
-
this.whenStyleUpdates(finalStyle);
|
|
917
|
-
// A non-diff swap replaces every layer, so anything applied ON TOP of the
|
|
918
|
-
// style — label language, caller-requested place highlights, the 3D scene's
|
|
919
|
-
// GLB layer and hidden-layer overrides — is gone and has to be replayed.
|
|
920
|
-
// `whenStyleUpdates` covers only floor filters, routes and circles.
|
|
921
|
-
this.restoreStyleOverlays();
|
|
922
|
-
this.logEvent("setStyle");
|
|
923
|
-
};
|
|
924
|
-
this.pendingStyleSwapListener = applyOnceLoaded;
|
|
925
|
-
this.map.on("styledata", applyOnceLoaded);
|
|
926
|
-
applyOnceLoaded();
|
|
927
|
-
// Re-apply the floor filter and report ready once the map is idle. This is
|
|
928
|
-
// intentionally OUTSIDE applyOnceLoaded's `isStyleLoaded` gate: the indoor
|
|
929
|
-
// polygon layers settle a beat after the swap (so the floor filter in
|
|
930
|
-
// whenStyleUpdates misses them and the venue paints every floor at once), and
|
|
931
|
-
// `idle` only fires once the style is loaded and its first tiles rendered — so
|
|
932
|
-
// filterByFloorKey is safe here and does not depend on the sometimes-flaky
|
|
933
|
-
// isStyleLoaded check. onStyleReady lets callers reveal the map only once it is
|
|
934
|
-
// settled; the timer armed above just clears a hanging overlay if idle never fires.
|
|
935
|
-
this.map.once("idle", () => {
|
|
936
|
-
// `once` cannot be cancelled, so a superseded swap's handler still fires —
|
|
937
|
-
// filtering here keeps it from re-filtering the newer style's floors and from
|
|
938
|
-
// reporting the newer swap's caller ready ahead of time.
|
|
939
|
-
if (!isCurrent())
|
|
940
|
-
return;
|
|
941
|
-
this.filterByFloorKey(this.currentFloor);
|
|
942
|
-
reportReady();
|
|
943
|
-
});
|
|
944
|
-
})
|
|
945
|
-
.catch((error) => {
|
|
946
|
-
// A superseded swap must not tear down the listener the CURRENT swap installed.
|
|
947
|
-
if (isCurrent()) {
|
|
948
|
-
this.clearPendingStyleSwapListener();
|
|
949
|
-
}
|
|
950
|
-
this.logError(error, { functionName: "setStyle" });
|
|
951
|
-
// Release the caller immediately instead of making it wait out the safety timer:
|
|
952
|
-
// the style will never arrive, so the overlay has nothing to wait for. The error
|
|
953
|
-
// is handed to the callback so a caller can tell a failed swap from an applied
|
|
954
|
-
// one — the previous style is still on screen either way.
|
|
955
|
-
reportReady(error instanceof Error ? error : new Error(String(error)));
|
|
956
|
-
});
|
|
957
|
-
}
|
|
958
|
-
/**
|
|
959
|
-
* Forgets every captured base filter so the next style's declared filters are read
|
|
960
|
-
* fresh from that style instead of inherited from the previous one.
|
|
961
|
-
*
|
|
962
|
-
* Both halves must move together: `baseFilters` holds the expressions and
|
|
963
|
-
* `capturedBaseFilters` records which layer ids have already been resolved, so
|
|
964
|
-
* clearing only the first would leave every id marked "already captured" and the new
|
|
965
|
-
* style's filters would never be read at all.
|
|
966
|
-
*/
|
|
967
|
-
resetBaseFilters() {
|
|
968
|
-
this.baseFilters = {};
|
|
969
|
-
this.capturedBaseFilters.clear();
|
|
970
|
-
}
|
|
971
|
-
/**
|
|
972
|
-
* Replays everything that lives ON TOP of the map style after a full (non-diff) swap
|
|
973
|
-
* has deleted it: label language expressions, caller-requested place highlights and
|
|
974
|
-
* the 3D scene's GLB layer / hidden-layer overrides.
|
|
975
|
-
*
|
|
976
|
-
* These are deliberately not part of `whenStyleUpdates`, which restores only
|
|
977
|
-
* style-derived state. These are decisions the SDK's callers made through `setLang`,
|
|
978
|
-
* `setPlacesAsSelected`, `addBorderToPlaces` and the 3D config, and nothing else would
|
|
979
|
-
* ever restore them.
|
|
980
|
-
*/
|
|
981
|
-
restoreStyleOverlays() {
|
|
982
|
-
// Each step is isolated: the style itself has already been applied by the time we get
|
|
983
|
-
// here, so one failing restore must neither skip the others nor bubble out into
|
|
984
|
-
// `setStyle`'s `.catch`, which would report the successful swap to `onStyleReady` as
|
|
985
|
-
// a failure. `setPlacesAsSelected` / `addBorderToPlaces` throw by contract.
|
|
986
|
-
const restore = (what, step) => {
|
|
987
|
-
try {
|
|
988
|
-
step();
|
|
989
|
-
}
|
|
990
|
-
catch (error) {
|
|
991
|
-
this.logError(error, { functionName: "restoreStyleOverlays", step: what });
|
|
992
|
-
}
|
|
993
|
-
};
|
|
994
|
-
restore("language", () => this.setLayersForLanguage(this.currentLang));
|
|
995
|
-
if (this.coloredPlacesRequest) {
|
|
996
|
-
const { placeIds, color } = this.coloredPlacesRequest;
|
|
997
|
-
restore("coloredPlaces", () => this.setPlacesAsSelected(placeIds, color));
|
|
998
|
-
}
|
|
999
|
-
if (this.borderedPlacesRequest) {
|
|
1000
|
-
const { placeIds, color, width } = this.borderedPlacesRequest;
|
|
1001
|
-
restore("borderedPlaces", () => this.addBorderToPlaces(placeIds, color, width));
|
|
1002
|
-
}
|
|
1003
|
-
// The swap removed the GLB custom layer, and MapLibre called its `onRemove`, which
|
|
1004
|
-
// disposes the whole three.js scene. Nothing else re-adds it, so without this every
|
|
1005
|
-
// 3D model disappears permanently and `hideLayers` stops being applied.
|
|
1006
|
-
restore("scene3D", () => { var _a; return (_a = this.scene3D) === null || _a === void 0 ? void 0 : _a.reattachToStyle(); });
|
|
1007
|
-
}
|
|
1008
|
-
/**
|
|
1009
|
-
* Removes the `styledata` listener left by an in-flight {@link setStyle} swap, if any.
|
|
1010
|
-
* Called before starting a new swap and when one fails, so a listener holding an
|
|
1011
|
-
* obsolete style can never fire against a different style's layers.
|
|
1012
|
-
*/
|
|
1013
|
-
clearPendingStyleSwapListener() {
|
|
1014
|
-
var _a;
|
|
1015
|
-
if (!this.pendingStyleSwapListener)
|
|
1016
|
-
return;
|
|
1017
|
-
// `this.map?` — a listener can only have been installed while the map existed, but
|
|
1018
|
-
// this also runs from `destroyMap` and from `setStyle` before the map is created.
|
|
1019
|
-
(_a = this.map) === null || _a === void 0 ? void 0 : _a.off("styledata", this.pendingStyleSwapListener);
|
|
1020
|
-
this.pendingStyleSwapListener = undefined;
|
|
1021
|
-
}
|
|
1022
|
-
/**
|
|
1023
|
-
* Removes current parent place and updates style to default
|
|
1024
|
-
*/
|
|
1025
|
-
removeParentPlace() {
|
|
1026
|
-
this.changeParentPlaceTo(undefined, true);
|
|
1027
|
-
}
|
|
1028
|
-
/**
|
|
1029
|
-
* Applies filters and changes to layers according to given style.
|
|
1030
|
-
* Expected to be called every time a new style is loaded
|
|
1031
|
-
* @param newStyle
|
|
1032
|
-
*/
|
|
1033
|
-
whenStyleUpdates(newStyle) {
|
|
1034
|
-
var _a;
|
|
1035
|
-
if (newStyle) {
|
|
1036
|
-
this.setBaseFilters(newStyle);
|
|
1037
|
-
}
|
|
1038
|
-
this.routeController.addSourcesAndLayers();
|
|
1039
|
-
this.refreshCircles();
|
|
1040
|
-
this.filterByFloorKey(this.currentFloor);
|
|
1041
|
-
(_a = this.scene3D) === null || _a === void 0 ? void 0 : _a.refreshOverlay(this.currentFloor);
|
|
1042
|
-
}
|
|
1043
|
-
/**
|
|
1044
|
-
* Internal accessor for every marker currently on the map, used by
|
|
1045
|
-
* {@link MarkerLayoutController} to compute collision avoidance. Not part
|
|
1046
|
-
* of the public `MapVXMap` interface.
|
|
1047
|
-
*
|
|
1048
|
-
* The user-location marker and the route controller's markers live outside
|
|
1049
|
-
* `this.markers`, but they still occupy screen space — leaving them out
|
|
1050
|
-
* would let labels be placed straight on top of them. They carry no label
|
|
1051
|
-
* of their own, so they only ever act as obstacles.
|
|
1052
|
-
* @internal
|
|
1053
|
-
*/
|
|
1054
|
-
getMarkers() {
|
|
1055
|
-
const unmanaged = this.routeController.getRouteMarkers();
|
|
1056
|
-
if (this.userLocationMarker)
|
|
1057
|
-
unmanaged.push(this.userLocationMarker);
|
|
1058
|
-
return unmanaged.length > 0 ? [...this.markers, ...unmanaged] : this.markers;
|
|
1059
|
-
}
|
|
1060
|
-
addMarker(marker) {
|
|
1061
|
-
var _a;
|
|
1062
|
-
try {
|
|
1063
|
-
// Check if a marker with the same ID already exists and remove it
|
|
1064
|
-
if (marker.id) {
|
|
1065
|
-
const existingMarkerIndex = this.markers.findIndex((m) => m.id === marker.id);
|
|
1066
|
-
if (existingMarkerIndex !== -1) {
|
|
1067
|
-
const existingMarker = this.markers[existingMarkerIndex];
|
|
1068
|
-
existingMarker.remove();
|
|
1069
|
-
this.markers.splice(existingMarkerIndex, 1);
|
|
1070
|
-
this.logEvent("removeMarker", {
|
|
1071
|
-
markerId: marker.id,
|
|
1072
|
-
reason: "duplicate",
|
|
1073
|
-
});
|
|
1074
|
-
}
|
|
1075
|
-
}
|
|
1076
|
-
const markerCreated = new marker_1.MarkerAttribute(Object.assign(Object.assign({}, marker), { maxLabelWidthPx: (_a = marker.maxLabelWidthPx) !== null && _a !== void 0 ? _a : this.markerLayoutConfig.maxLabelWidthPx }));
|
|
1077
|
-
// While a floor change is still being rebuilt, the marker is shown by that
|
|
1078
|
-
// change's settle step, together with the new floor.
|
|
1079
|
-
if (!this.floorSettlePending) {
|
|
1080
|
-
markerCreated.changeFloor(this.currentFloor, this.map, this.isOutdoorFloor(this.currentFloor));
|
|
1081
|
-
}
|
|
1082
|
-
this.markers.push(markerCreated);
|
|
1083
|
-
this.markerLayoutController.scheduleRelayout();
|
|
1084
|
-
this.logEvent("addMarker");
|
|
1085
|
-
return markerCreated.id;
|
|
1086
|
-
}
|
|
1087
|
-
catch (error) {
|
|
1088
|
-
throw new Error("Failed to add marker", { cause: error });
|
|
1089
|
-
}
|
|
1090
|
-
}
|
|
1091
|
-
updateMarker(markerConfig) {
|
|
1092
|
-
var _a;
|
|
1093
|
-
try {
|
|
1094
|
-
const marker = this.markers.find((e) => e.id === markerConfig.id);
|
|
1095
|
-
if (marker !== undefined) {
|
|
1096
|
-
marker.updateIcon(markerConfig);
|
|
1097
|
-
this.markerLayoutController.invalidateMarker(marker);
|
|
1098
|
-
this.markerLayoutController.scheduleRelayout();
|
|
1099
|
-
}
|
|
1100
|
-
this.logEvent("updateMarker");
|
|
1101
|
-
return (_a = marker === null || marker === void 0 ? void 0 : marker.id) !== null && _a !== void 0 ? _a : "";
|
|
1102
|
-
}
|
|
1103
|
-
catch (error) {
|
|
1104
|
-
throw new Error("Failed to update marker", { cause: error });
|
|
1105
|
-
}
|
|
1106
|
-
}
|
|
1107
|
-
updateMarkerPosition(markerId, newPosition) {
|
|
1108
|
-
try {
|
|
1109
|
-
const marker = this.markers.find((e) => e.id === markerId);
|
|
1110
|
-
if (marker !== undefined) {
|
|
1111
|
-
marker.setLngLat(newPosition);
|
|
1112
|
-
this.markerLayoutController.scheduleRelayout();
|
|
1113
|
-
}
|
|
1114
|
-
this.logEvent("updateMarkerPosition");
|
|
1115
|
-
}
|
|
1116
|
-
catch (error) {
|
|
1117
|
-
throw new Error("Failed to update marker position", { cause: error });
|
|
1118
|
-
}
|
|
1119
|
-
}
|
|
1120
|
-
showMarker(markerId) {
|
|
1121
|
-
try {
|
|
1122
|
-
const marker = this.markers.find((e) => e.id === markerId);
|
|
1123
|
-
if (marker !== null && this.map) {
|
|
1124
|
-
marker === null || marker === void 0 ? void 0 : marker.show(this.map);
|
|
1125
|
-
this.markerLayoutController.scheduleRelayout();
|
|
1126
|
-
}
|
|
1127
|
-
this.logEvent("showMarker");
|
|
1128
|
-
}
|
|
1129
|
-
catch (error) {
|
|
1130
|
-
this.debugLog("Show marker failed (marker may not exist)", error);
|
|
1131
|
-
}
|
|
1132
|
-
}
|
|
1133
|
-
hideMarker(markerId) {
|
|
1134
|
-
try {
|
|
1135
|
-
const marker = this.markers.find((e) => e.id === markerId);
|
|
1136
|
-
if (marker !== null) {
|
|
1137
|
-
marker === null || marker === void 0 ? void 0 : marker.hide();
|
|
1138
|
-
this.markerLayoutController.scheduleRelayout();
|
|
1139
|
-
}
|
|
1140
|
-
this.logEvent("hideMarker");
|
|
1141
|
-
}
|
|
1142
|
-
catch (error) {
|
|
1143
|
-
this.debugLog("Hide marker failed (marker may not exist)", error);
|
|
1144
|
-
}
|
|
1145
|
-
}
|
|
1146
|
-
removeMarker(markerId) {
|
|
1147
|
-
try {
|
|
1148
|
-
const marker = this.markers.find((e) => e.id === markerId);
|
|
1149
|
-
if (marker !== null) {
|
|
1150
|
-
marker === null || marker === void 0 ? void 0 : marker.remove();
|
|
1151
|
-
this.markers = this.markers.filter((e) => e.id !== markerId);
|
|
1152
|
-
this.markerLayoutController.scheduleRelayout();
|
|
1153
|
-
}
|
|
1154
|
-
this.logEvent("removeMarker");
|
|
1155
|
-
}
|
|
1156
|
-
catch (error) {
|
|
1157
|
-
throw new Error("Failed to remove marker", { cause: error });
|
|
1158
|
-
}
|
|
1159
|
-
}
|
|
1160
|
-
removeAllMarkers() {
|
|
1161
|
-
try {
|
|
1162
|
-
this.markers.forEach((marker) => {
|
|
1163
|
-
marker.remove();
|
|
1164
|
-
});
|
|
1165
|
-
this.markers = [];
|
|
1166
|
-
this.markerLayoutController.scheduleRelayout();
|
|
1167
|
-
this.logEvent("removeAllMarkers");
|
|
1168
|
-
}
|
|
1169
|
-
catch (error) {
|
|
1170
|
-
throw new Error("Failed to remove all markers", { cause: error });
|
|
1171
|
-
}
|
|
1172
|
-
}
|
|
1173
|
-
addCircle(circle) {
|
|
1174
|
-
try {
|
|
1175
|
-
// Check if a circle with the same ID already exists and replace it
|
|
1176
|
-
if (circle.id) {
|
|
1177
|
-
this.circles = this.circles.filter((c) => c.id !== circle.id);
|
|
1178
|
-
}
|
|
1179
|
-
// resolveCircleConfig validates radiusMeters and coordinates
|
|
1180
|
-
const record = (0, circle_1.resolveCircleConfig)(circle);
|
|
1181
|
-
this.circles.push(record);
|
|
1182
|
-
this.refreshCircles();
|
|
1183
|
-
this.logEvent("addCircle");
|
|
1184
|
-
return record.id;
|
|
1185
|
-
}
|
|
1186
|
-
catch (error) {
|
|
1187
|
-
throw new Error(`Failed to add circle: ${error instanceof Error ? error.message : String(error)}`);
|
|
1188
|
-
}
|
|
1189
|
-
}
|
|
1190
|
-
updateCircle(circleConfig) {
|
|
1191
|
-
try {
|
|
1192
|
-
const index = this.circles.findIndex((c) => c.id === circleConfig.id);
|
|
1193
|
-
if (index === -1)
|
|
1194
|
-
return null;
|
|
1195
|
-
// resolveCircleConfig validates radiusMeters and coordinates
|
|
1196
|
-
this.circles[index] = (0, circle_1.resolveCircleConfig)(circleConfig, this.circles[index].hidden);
|
|
1197
|
-
this.refreshCircles();
|
|
1198
|
-
this.logEvent("updateCircle");
|
|
1199
|
-
return this.circles[index].id;
|
|
1200
|
-
}
|
|
1201
|
-
catch (error) {
|
|
1202
|
-
throw new Error(`Failed to update circle: ${error instanceof Error ? error.message : String(error)}`);
|
|
1203
|
-
}
|
|
1204
|
-
}
|
|
1205
|
-
getCircle(circleId) {
|
|
1206
|
-
const circle = this.circles.find((c) => c.id === circleId);
|
|
1207
|
-
return circle ? (0, circle_1.cloneCircleRecord)(circle) : undefined;
|
|
1208
|
-
}
|
|
1209
|
-
getCircles() {
|
|
1210
|
-
return this.circles.map(circle_1.cloneCircleRecord);
|
|
1211
|
-
}
|
|
1212
|
-
hasCircle(circleId) {
|
|
1213
|
-
return this.circles.some((c) => c.id === circleId);
|
|
1214
|
-
}
|
|
1215
|
-
updateCirclePosition(circleId, center, radiusMeters) {
|
|
1216
|
-
try {
|
|
1217
|
-
const circle = this.circles.find((c) => c.id === circleId);
|
|
1218
|
-
if (circle === undefined)
|
|
1219
|
-
return false;
|
|
1220
|
-
// Validate inputs before modifying
|
|
1221
|
-
if (!center) {
|
|
1222
|
-
throw new Error("Circle center is required");
|
|
1223
|
-
}
|
|
1224
|
-
const { lat, lng } = center;
|
|
1225
|
-
if (!Number.isFinite(lat) || lat < -90 || lat > 90) {
|
|
1226
|
-
throw new Error(`Invalid latitude: ${lat}. Must be between -90 and 90.`);
|
|
1227
|
-
}
|
|
1228
|
-
if (!Number.isFinite(lng) || lng < -180 || lng > 180) {
|
|
1229
|
-
throw new Error(`Invalid longitude: ${lng}. Must be between -180 and 180.`);
|
|
1230
|
-
}
|
|
1231
|
-
if (radiusMeters !== undefined) {
|
|
1232
|
-
if (!Number.isFinite(radiusMeters) || radiusMeters <= 0) {
|
|
1233
|
-
throw new Error(`Invalid radiusMeters: ${radiusMeters}. Must be a positive finite number.`);
|
|
1234
|
-
}
|
|
1235
|
-
}
|
|
1236
|
-
circle.coordinate = { lat: center.lat, lng: center.lng };
|
|
1237
|
-
if (radiusMeters !== undefined) {
|
|
1238
|
-
circle.radiusMeters = radiusMeters;
|
|
1239
|
-
}
|
|
1240
|
-
this.refreshCircles();
|
|
1241
|
-
this.logEvent("updateCirclePosition");
|
|
1242
|
-
return true;
|
|
1243
|
-
}
|
|
1244
|
-
catch (error) {
|
|
1245
|
-
throw new Error(`Failed to update circle position: ${error instanceof Error ? error.message : String(error)}`);
|
|
1246
|
-
}
|
|
1247
|
-
}
|
|
1248
|
-
updateCircleStyle(circleId, style) {
|
|
1249
|
-
try {
|
|
1250
|
-
const circle = this.circles.find((c) => c.id === circleId);
|
|
1251
|
-
if (circle === undefined)
|
|
1252
|
-
return false;
|
|
1253
|
-
// Update only style properties, clamp opacities
|
|
1254
|
-
if (style.fillColor !== undefined)
|
|
1255
|
-
circle.fillColor = style.fillColor;
|
|
1256
|
-
if (style.fillOpacity !== undefined)
|
|
1257
|
-
circle.fillOpacity = Math.max(0, Math.min(1, style.fillOpacity));
|
|
1258
|
-
if (style.strokeColor !== undefined)
|
|
1259
|
-
circle.strokeColor = style.strokeColor;
|
|
1260
|
-
if (style.strokeWidth !== undefined)
|
|
1261
|
-
circle.strokeWidth = style.strokeWidth;
|
|
1262
|
-
if (style.strokeOpacity !== undefined)
|
|
1263
|
-
circle.strokeOpacity = Math.max(0, Math.min(1, style.strokeOpacity));
|
|
1264
|
-
this.refreshCircles();
|
|
1265
|
-
this.logEvent("updateCircleStyle");
|
|
1266
|
-
return true;
|
|
1267
|
-
}
|
|
1268
|
-
catch (error) {
|
|
1269
|
-
throw new Error(`Failed to update circle style: ${error instanceof Error ? error.message : String(error)}`);
|
|
1270
|
-
}
|
|
1271
|
-
}
|
|
1272
|
-
removeCircle(circleId) {
|
|
1273
|
-
try {
|
|
1274
|
-
this.circles = this.circles.filter((c) => c.id !== circleId);
|
|
1275
|
-
this.refreshCircles();
|
|
1276
|
-
this.logEvent("removeCircle");
|
|
1277
|
-
}
|
|
1278
|
-
catch (error) {
|
|
1279
|
-
throw new Error("Failed to remove circle", { cause: error });
|
|
1280
|
-
}
|
|
1281
|
-
}
|
|
1282
|
-
removeAllCircles() {
|
|
1283
|
-
try {
|
|
1284
|
-
this.circles = [];
|
|
1285
|
-
this.refreshCircles();
|
|
1286
|
-
this.logEvent("removeAllCircles");
|
|
1287
|
-
}
|
|
1288
|
-
catch (error) {
|
|
1289
|
-
throw new Error("Failed to remove all circles", { cause: error });
|
|
1290
|
-
}
|
|
1291
|
-
}
|
|
1292
|
-
showCircle(circleId) {
|
|
1293
|
-
try {
|
|
1294
|
-
const circle = this.circles.find((c) => c.id === circleId);
|
|
1295
|
-
if (circle === undefined) {
|
|
1296
|
-
return false;
|
|
1297
|
-
}
|
|
1298
|
-
circle.hidden = false;
|
|
1299
|
-
this.refreshCircles();
|
|
1300
|
-
this.logEvent("showCircle");
|
|
1301
|
-
return true;
|
|
1302
|
-
}
|
|
1303
|
-
catch (error) {
|
|
1304
|
-
this.debugLog("Show circle failed", error);
|
|
1305
|
-
return false;
|
|
1306
|
-
}
|
|
1307
|
-
}
|
|
1308
|
-
hideCircle(circleId) {
|
|
1309
|
-
try {
|
|
1310
|
-
const circle = this.circles.find((c) => c.id === circleId);
|
|
1311
|
-
if (circle === undefined) {
|
|
1312
|
-
return false;
|
|
1313
|
-
}
|
|
1314
|
-
circle.hidden = true;
|
|
1315
|
-
this.refreshCircles();
|
|
1316
|
-
this.logEvent("hideCircle");
|
|
1317
|
-
return true;
|
|
1318
|
-
}
|
|
1319
|
-
catch (error) {
|
|
1320
|
-
this.debugLog("Hide circle failed", error);
|
|
1321
|
-
return false;
|
|
1322
|
-
}
|
|
1323
|
-
}
|
|
1324
|
-
/**
|
|
1325
|
-
* Builds the GeoJSON FeatureCollection for every currently visible circle.
|
|
1326
|
-
* Visibility mirrors marker semantics: a circle with a floor is shown only
|
|
1327
|
-
* while that floor is displayed, and a circle without a floor is shown only
|
|
1328
|
-
* in outdoor contexts. Hidden circles are always omitted.
|
|
1329
|
-
*/
|
|
1330
|
-
circleFeatureCollection() {
|
|
1331
|
-
var _a;
|
|
1332
|
-
const floorId = (_a = this.currentFloor) !== null && _a !== void 0 ? _a : "";
|
|
1333
|
-
const isOutdoor = this.isOutdoorFloor(floorId);
|
|
1334
|
-
const features = this.circles
|
|
1335
|
-
.filter((circle) => {
|
|
1336
|
-
var _a;
|
|
1337
|
-
if (circle.hidden)
|
|
1338
|
-
return false;
|
|
1339
|
-
const circleFloor = (_a = circle.floorId) !== null && _a !== void 0 ? _a : "";
|
|
1340
|
-
return circleFloor === floorId || (isOutdoor && circleFloor === "");
|
|
1341
|
-
})
|
|
1342
|
-
.map((circle) => ({
|
|
1343
|
-
type: "Feature",
|
|
1344
|
-
properties: {
|
|
1345
|
-
id: circle.id,
|
|
1346
|
-
fillColor: circle.fillColor,
|
|
1347
|
-
fillOpacity: circle.fillOpacity,
|
|
1348
|
-
strokeColor: circle.strokeColor,
|
|
1349
|
-
strokeWidth: circle.strokeWidth,
|
|
1350
|
-
strokeOpacity: circle.strokeOpacity,
|
|
1351
|
-
renderOrder: circle.renderOrder,
|
|
1352
|
-
},
|
|
1353
|
-
geometry: {
|
|
1354
|
-
type: "Polygon",
|
|
1355
|
-
coordinates: [
|
|
1356
|
-
(0, circle_1.circleRing)(circle.coordinate.lng, circle.coordinate.lat, circle.radiusMeters),
|
|
1357
|
-
],
|
|
1358
|
-
},
|
|
1359
|
-
}));
|
|
1360
|
-
return { type: "FeatureCollection", features };
|
|
1361
|
-
}
|
|
1362
|
-
/**
|
|
1363
|
-
* Idempotently adds the shared circle source and its fill/line layers.
|
|
1364
|
-
* Layers are inserted below the first symbol layer so place labels and
|
|
1365
|
-
* markers stay readable above the translucent fill. Safe to call at any
|
|
1366
|
-
* time: a style that is still loading simply rejects the calls, and the
|
|
1367
|
-
* styledata listener retries once the style is ready.
|
|
1368
|
-
*/
|
|
1369
|
-
/**
|
|
1370
|
-
* Resolves the layer id to insert circle layers before, for one render
|
|
1371
|
-
* order. `undefined` means "append on top".
|
|
1372
|
-
*
|
|
1373
|
-
* `aboveBasemap` finds the topmost non-symbol layer of the style and
|
|
1374
|
-
* inserts before the first symbol layer that follows it. On indoor styles
|
|
1375
|
-
* the floor-plate and building polygons are ordered after the first symbol
|
|
1376
|
-
* layer, so anchoring on the topmost geometry layer — instead of the first
|
|
1377
|
-
* symbol layer — guarantees circles are never occluded by basemap fills
|
|
1378
|
-
* while still rendering below the labels that follow them.
|
|
1379
|
-
*/
|
|
1380
|
-
circleBeforeIdFor(order) {
|
|
1381
|
-
var _a, _b, _c, _d;
|
|
1382
|
-
if (order === "top")
|
|
1383
|
-
return undefined;
|
|
1384
|
-
const layers = ((_b = (_a = this.map.getStyle()) === null || _a === void 0 ? void 0 : _a.layers) !== null && _b !== void 0 ? _b : []).filter((layer) => !isCircleLayerId(layer.id));
|
|
1385
|
-
if (order === "belowLabels") {
|
|
1386
|
-
return (_c = layers.find((layer) => layer.type === "symbol")) === null || _c === void 0 ? void 0 : _c.id;
|
|
1387
|
-
}
|
|
1388
|
-
// aboveBasemap
|
|
1389
|
-
let lastNonSymbolIndex = -1;
|
|
1390
|
-
layers.forEach((layer, index) => {
|
|
1391
|
-
if (layer.type !== "symbol")
|
|
1392
|
-
lastNonSymbolIndex = index;
|
|
1393
|
-
});
|
|
1394
|
-
return (_d = layers.slice(lastNonSymbolIndex + 1).find((layer) => layer.type === "symbol")) === null || _d === void 0 ? void 0 : _d.id;
|
|
1395
|
-
}
|
|
1396
|
-
/**
|
|
1397
|
-
* Idempotently ensures the shared circle source and one fill/line layer
|
|
1398
|
-
* pair per render order in use, at the placement that order requires.
|
|
1399
|
-
* Placement is recomputed and re-asserted (via moveLayer) on every call,
|
|
1400
|
-
* so circles regain their correct z-order after any style reload or
|
|
1401
|
-
* floor/parent-place change. Safe to call at any time: a style that is
|
|
1402
|
-
* still loading simply rejects the calls, and the styledata listener
|
|
1403
|
-
* retries once the style is ready.
|
|
1404
|
-
*/
|
|
1405
|
-
ensureCircleLayers() {
|
|
1406
|
-
if (!this.map)
|
|
1407
|
-
return;
|
|
1408
|
-
try {
|
|
1409
|
-
if (!this.map.getSource(CIRCLE_SOURCE_ID)) {
|
|
1410
|
-
this.map.addSource(CIRCLE_SOURCE_ID, {
|
|
1411
|
-
type: "geojson",
|
|
1412
|
-
data: this.circleFeatureCollection(),
|
|
1413
|
-
});
|
|
1414
|
-
}
|
|
1415
|
-
const ordersInUse = new Set(this.circles.map((c) => c.renderOrder));
|
|
1416
|
-
// Always keep the default bucket alive so an empty map still renders
|
|
1417
|
-
// newly added circles without a layer rebuild
|
|
1418
|
-
ordersInUse.add("aboveBasemap");
|
|
1419
|
-
// Process buckets lowest-first (CIRCLE_RENDER_ORDERS is canonical):
|
|
1420
|
-
// when two buckets resolve to the same anchor, each addLayer/moveLayer
|
|
1421
|
-
// lands immediately before it, so a bucket processed later paints above
|
|
1422
|
-
// the ones processed earlier. Canonical order keeps
|
|
1423
|
-
// belowLabels < aboveBasemap < top regardless of circle insertion order.
|
|
1424
|
-
for (const order of circle_1.CIRCLE_RENDER_ORDERS) {
|
|
1425
|
-
if (!ordersInUse.has(order))
|
|
1426
|
-
continue;
|
|
1427
|
-
const ids = circleLayerIdsFor(order);
|
|
1428
|
-
const beforeId = this.circleBeforeIdFor(order);
|
|
1429
|
-
const orderFilter = ["==", ["get", "renderOrder"], order];
|
|
1430
|
-
if (!this.map.getLayer(ids.fill)) {
|
|
1431
|
-
const fillLayer = {
|
|
1432
|
-
id: ids.fill,
|
|
1433
|
-
type: "fill",
|
|
1434
|
-
source: CIRCLE_SOURCE_ID,
|
|
1435
|
-
filter: orderFilter,
|
|
1436
|
-
paint: {
|
|
1437
|
-
"fill-color": ["get", "fillColor"],
|
|
1438
|
-
"fill-opacity": ["get", "fillOpacity"],
|
|
1439
|
-
},
|
|
1440
|
-
};
|
|
1441
|
-
this.map.addLayer(fillLayer, beforeId);
|
|
1442
|
-
}
|
|
1443
|
-
else {
|
|
1444
|
-
this.map.moveLayer(ids.fill, beforeId);
|
|
1445
|
-
}
|
|
1446
|
-
if (!this.map.getLayer(ids.line)) {
|
|
1447
|
-
const lineLayer = {
|
|
1448
|
-
id: ids.line,
|
|
1449
|
-
type: "line",
|
|
1450
|
-
source: CIRCLE_SOURCE_ID,
|
|
1451
|
-
filter: orderFilter,
|
|
1452
|
-
paint: {
|
|
1453
|
-
"line-color": ["get", "strokeColor"],
|
|
1454
|
-
"line-width": ["get", "strokeWidth"],
|
|
1455
|
-
"line-opacity": ["get", "strokeOpacity"],
|
|
1456
|
-
},
|
|
1457
|
-
};
|
|
1458
|
-
this.map.addLayer(lineLayer, beforeId);
|
|
1459
|
-
}
|
|
1460
|
-
else {
|
|
1461
|
-
this.map.moveLayer(ids.line, beforeId);
|
|
1462
|
-
}
|
|
1463
|
-
}
|
|
1464
|
-
}
|
|
1465
|
-
catch (error) {
|
|
1466
|
-
this.debugLog("Layers not added: style not loaded yet, the styledata listener will re-add them", error);
|
|
1467
|
-
}
|
|
1468
|
-
}
|
|
1469
|
-
/**
|
|
1470
|
-
* Re-renders all circles: ensures the source and layers exist, then pushes
|
|
1471
|
-
* the current FeatureCollection. Called after every mutation of the circle
|
|
1472
|
-
* list, on floor changes, and when the map style reloads.
|
|
1473
|
-
*/
|
|
1474
|
-
refreshCircles() {
|
|
1475
|
-
if (!this.map)
|
|
1476
|
-
return;
|
|
1477
|
-
if (this.circles.length === 0 && !this.map.getSource(CIRCLE_SOURCE_ID))
|
|
1478
|
-
return;
|
|
1479
|
-
this.ensureCircleLayers();
|
|
1480
|
-
try {
|
|
1481
|
-
const source = this.map.getSource(CIRCLE_SOURCE_ID);
|
|
1482
|
-
source === null || source === void 0 ? void 0 : source.setData(this.circleFeatureCollection());
|
|
1483
|
-
}
|
|
1484
|
-
catch (error) {
|
|
1485
|
-
this.debugLog("Source not found (a new style may still be loading)", error);
|
|
1486
|
-
}
|
|
1487
|
-
}
|
|
1488
|
-
/**
|
|
1489
|
-
* Use it to change the current layer
|
|
1490
|
-
* @param floorKey floor id
|
|
1491
|
-
*/
|
|
1492
|
-
filterByFloorKey(floorKey) {
|
|
1493
|
-
this.currentFloor = floorKey;
|
|
1494
|
-
const floorKeyString = floorKey !== null && floorKey !== void 0 ? floorKey : "";
|
|
1495
|
-
if (this.innerFloors.every((floor) => floor.key !== floorKey) && floorKey !== "") {
|
|
1496
|
-
this.logEvent("invalidFloorKey", { floorKey: floorKeyString });
|
|
1497
|
-
}
|
|
1498
|
-
// One style read for the whole floor change. `getStyle()` serializes every
|
|
1499
|
-
// source and layer of the venue style, and the second read is the expensive
|
|
1500
|
-
// one: `updateFiltersTo` writes filters and layout properties in between,
|
|
1501
|
-
// and each write nulls maplibre's `_serializedLayers` cache, so anything
|
|
1502
|
-
// that reads the style afterwards rebuilds it from scratch.
|
|
1503
|
-
const layers = this.safeStyleLayers();
|
|
1504
|
-
this.updateFiltersTo(floorKeyString, layers);
|
|
1505
|
-
this.updateMarkersWhenFloorSettles(floorKeyString, layers);
|
|
1506
|
-
this.refreshCircles();
|
|
1507
|
-
this.routeController.updateRouteLayers(floorKeyString);
|
|
1508
|
-
this.routeController.updateRouteMarkerVisibility(floorKeyString);
|
|
1509
|
-
}
|
|
1510
|
-
/**
|
|
1511
|
-
* Show/hide the HTML markers for `floorId` once the indoor tiles have been
|
|
1512
|
-
* rebuilt with the new floor filters.
|
|
1513
|
-
*
|
|
1514
|
-
* A floor change is a `setFilter` on every indoor layer: MapLibre rebuilds the
|
|
1515
|
-
* tile buckets in its worker, so the new floor appears a few frames later
|
|
1516
|
-
* (hundreds of ms on a low-end panel) while a marker is a DOM element that
|
|
1517
|
-
* would move instantly. Waiting for the source to report loaded makes floor
|
|
1518
|
-
* polygons and markers switch in the same frame. The wait is bounded, is
|
|
1519
|
-
* dropped when a newer floor change arrives, and is skipped outside a
|
|
1520
|
-
* browser (no `requestAnimationFrame`).
|
|
1521
|
-
*/
|
|
1522
|
-
updateMarkersWhenFloorSettles(floorId, styleLayers) {
|
|
1523
|
-
const token = ++this.floorSettleToken;
|
|
1524
|
-
// Resolved rather than probed with `hasStyle()`, which is another full
|
|
1525
|
-
// `getStyle()` — and by this point the most expensive kind, because
|
|
1526
|
-
// `updateFiltersTo` has just invalidated the serialized-layer cache.
|
|
1527
|
-
const layers = styleLayers === undefined ? this.safeStyleLayers() : styleLayers;
|
|
1528
|
-
if (typeof requestAnimationFrame !== "function" || layers === null) {
|
|
1529
|
-
// No poll will run for this floor change, so nothing would ever clear the
|
|
1530
|
-
// flag later: leaving it set makes every subsequent `addMarker` skip
|
|
1531
|
-
// `changeFloor` and the marker is never attached to the map.
|
|
1532
|
-
this.floorSettleDeadline = null;
|
|
1533
|
-
this.updateMarkersTo(floorId);
|
|
1534
|
-
return;
|
|
1535
|
-
}
|
|
1536
|
-
const sourceIds = this.floorSettleSourceIds(floorId, layers);
|
|
1537
|
-
const deadline = this.now() + FLOOR_SETTLE_MAX_MS;
|
|
1538
|
-
this.floorSettleDeadline = deadline;
|
|
1539
|
-
let sawReloading = false;
|
|
1540
|
-
let frames = 0;
|
|
1541
|
-
const poll = () => {
|
|
1542
|
-
if (token !== this.floorSettleToken)
|
|
1543
|
-
return;
|
|
1544
|
-
frames++;
|
|
1545
|
-
const loaded = sourceIds.every((id) => this.isSourceSettled(id));
|
|
1546
|
-
if (!loaded)
|
|
1547
|
-
sawReloading = true;
|
|
1548
|
-
// The reload is dispatched on the map's next render, which may run after
|
|
1549
|
-
// this callback within the same frame: give it a couple of frames to be
|
|
1550
|
-
// observed before concluding there was nothing to reload.
|
|
1551
|
-
const settled = loaded && (sawReloading || frames >= FLOOR_SETTLE_MIN_FRAMES);
|
|
1552
|
-
if (settled || this.now() >= deadline) {
|
|
1553
|
-
this.floorSettleDeadline = null;
|
|
1554
|
-
this.updateMarkersTo(floorId);
|
|
1555
|
-
return;
|
|
1556
|
-
}
|
|
1557
|
-
requestAnimationFrame(poll);
|
|
1558
|
-
};
|
|
1559
|
-
requestAnimationFrame(poll);
|
|
1560
|
-
}
|
|
1561
|
-
/**
|
|
1562
|
-
* Sources whose tiles a switch to `floorId` actually reloads, and which the
|
|
1563
|
-
* marker settle therefore has to wait on.
|
|
1564
|
-
*
|
|
1565
|
-
* With a vector basemap that is the source the `indoor-*` layers draw from,
|
|
1566
|
-
* because the floor change is a `setFilter` over those layers. An offline
|
|
1567
|
-
* bundle's raster basemap switches floors by making `raster-floor-<key>`
|
|
1568
|
-
* visible instead, and those tiles only begin loading at that moment — while
|
|
1569
|
-
* its `indoor-*` layers carry labels and hover, are already loaded, and would
|
|
1570
|
-
* report settled at once, letting the markers switch a floor before its image
|
|
1571
|
-
* had painted. That is the configuration this wait exists for, so both are
|
|
1572
|
-
* waited on.
|
|
1573
|
-
*/
|
|
1574
|
-
floorSettleSourceIds(floorId, layers) {
|
|
1575
|
-
const ids = new Set();
|
|
1576
|
-
const rasterFloor = layers.find((layer) => layer.id === `raster-floor-${floorId}`);
|
|
1577
|
-
if (rasterFloor && "source" in rasterFloor && typeof rasterFloor.source === "string") {
|
|
1578
|
-
ids.add(rasterFloor.source);
|
|
1579
|
-
}
|
|
1580
|
-
ids.add(this.indoorSourceId(layers));
|
|
1581
|
-
return [...ids];
|
|
1582
|
-
}
|
|
1583
|
-
/** The vector source the `indoor-*` layers draw from (`indoorequal` by default). */
|
|
1584
|
-
indoorSourceId(layers) {
|
|
1585
|
-
for (const layer of layers) {
|
|
1586
|
-
if (layer.id.startsWith("indoor-") && "source" in layer && typeof layer.source === "string") {
|
|
1587
|
-
return layer.source;
|
|
1588
|
-
}
|
|
1589
|
-
}
|
|
1590
|
-
return "indoorequal";
|
|
1591
|
-
}
|
|
1592
|
-
isSourceSettled(sourceId) {
|
|
1593
|
-
try {
|
|
1594
|
-
if (!this.map.getSource(sourceId))
|
|
1595
|
-
return true;
|
|
1596
|
-
return this.map.isSourceLoaded(sourceId) !== false;
|
|
1597
|
-
}
|
|
1598
|
-
catch (_a) {
|
|
1599
|
-
return true;
|
|
1600
|
-
}
|
|
1601
|
-
}
|
|
1602
|
-
getContainer() {
|
|
1603
|
-
return this.map.getContainer();
|
|
1604
|
-
}
|
|
1605
|
-
resize() {
|
|
1606
|
-
try {
|
|
1607
|
-
if (this.map) {
|
|
1608
|
-
this.map.resize();
|
|
1609
|
-
this.logEvent("resize");
|
|
1610
|
-
}
|
|
1611
|
-
}
|
|
1612
|
-
catch (error) {
|
|
1613
|
-
this.debugLog("Resize failed (map/container may not be ready)", error);
|
|
1614
|
-
}
|
|
1615
|
-
}
|
|
1616
|
-
updateMarkersTo(floorId) {
|
|
1617
|
-
const isOutdoor = this.isOutdoorFloor(floorId);
|
|
1618
|
-
this.markers.forEach((e) => {
|
|
1619
|
-
e.changeFloor(floorId, this.map, isOutdoor);
|
|
1620
|
-
});
|
|
1621
|
-
this.markerLayoutController.scheduleRelayout();
|
|
1622
|
-
}
|
|
1623
|
-
updateFiltersTo(floorId, styleLayers) {
|
|
1624
|
-
var _a, _b, _c;
|
|
1625
|
-
if (!this.map)
|
|
1626
|
-
return;
|
|
1627
|
-
// `null` means the caller looked and there was no style; only `undefined`
|
|
1628
|
-
// means it did not look.
|
|
1629
|
-
const layers = (_a = (styleLayers === undefined ? this.safeStyleLayers() : styleLayers)) !== null && _a !== void 0 ? _a : [];
|
|
1630
|
-
// Raster basemap (offline bundles baked with one raster archive per floor):
|
|
1631
|
-
// a floor is switched by showing its `raster-floor-<key>` layer and hiding
|
|
1632
|
-
// the others. The remaining `indoor-*` layers (labels, hover, highlights)
|
|
1633
|
-
// are still vector and keep going through the filter logic below.
|
|
1634
|
-
const rasterFloorPrefix = "raster-floor-";
|
|
1635
|
-
// Only switch the raster basemap when there is a baked layer to switch to.
|
|
1636
|
-
// Hiding every raster floor leaves the basemap blank, and no id can match in
|
|
1637
|
-
// two cases: no floor at all (an outdoor place, or a venue with no floors,
|
|
1638
|
-
// where `currentFloor` is `""`), and a floor the bundle has no raster
|
|
1639
|
-
// archive for — a venue where only some floors are baked, or a floor added
|
|
1640
|
-
// after the bundle was built. Keeping the current one visible is the safe
|
|
1641
|
-
// fallback for both.
|
|
1642
|
-
const rasterFloorLayerIds = layers
|
|
1643
|
-
.filter((l) => l.id.startsWith(rasterFloorPrefix))
|
|
1644
|
-
.map((l) => l.id);
|
|
1645
|
-
const targetRasterFloor = rasterFloorPrefix + floorId;
|
|
1646
|
-
if (floorId && rasterFloorLayerIds.includes(targetRasterFloor)) {
|
|
1647
|
-
rasterFloorLayerIds.forEach((id) => {
|
|
1648
|
-
this.map.setLayoutProperty(id, "visibility", id === targetRasterFloor ? "visible" : "none");
|
|
1649
|
-
});
|
|
1650
|
-
}
|
|
1651
|
-
layers
|
|
1652
|
-
.filter((l) => l.id.startsWith("base-indoor-"))
|
|
1653
|
-
.forEach((l) => {
|
|
1654
|
-
this.map.removeLayer(l.id);
|
|
1655
|
-
});
|
|
1656
|
-
const baseFloorId = (_b = this.innerFloors.find((floor) => floor.key === floorId)) === null || _b === void 0 ? void 0 : _b.baseFloor;
|
|
1657
|
-
const indoorLayers = layers.filter((layer) => layer.id.startsWith("indoor-"));
|
|
1658
|
-
// Resolve each indoor layer's declared (base) filter before we start folding floor
|
|
1659
|
-
// filters into it — and before cloning it into a `base-indoor-*` layer, which copies
|
|
1660
|
-
// the base filter across. Layers added asynchronously after a `setStyle` swap are
|
|
1661
|
-
// missing when `setBaseFilters` runs, so this is where their still-pristine filter
|
|
1662
|
-
// is captured; without it the layer loses its own discrimination and repaints every
|
|
1663
|
-
// feature (the washed-out / blue-outline look after changing floors). Layers already
|
|
1664
|
-
// resolved are skipped, so a filter this class wrote is never mistaken for a
|
|
1665
|
-
// declared one.
|
|
1666
|
-
indoorLayers.forEach((layer) => this.captureDeclaredBaseFilter(layer.id));
|
|
1667
|
-
if (baseFloorId != undefined) {
|
|
1668
|
-
const firstLayerId = (_c = indoorLayers[0]) === null || _c === void 0 ? void 0 : _c.id;
|
|
1669
|
-
indoorLayers
|
|
1670
|
-
.filter((layer) => layer.type !== "fill-extrusion")
|
|
1671
|
-
.forEach((layer) => {
|
|
1672
|
-
const baseLayer = deepClone(layer);
|
|
1673
|
-
baseLayer.id = "base-" + layer.id;
|
|
1674
|
-
this.baseFilters[baseLayer.id] = this.baseFilters[layer.id];
|
|
1675
|
-
this.map.addLayer(baseLayer, firstLayerId);
|
|
1676
|
-
this.applyLayerFilters(baseLayer, baseFloorId);
|
|
1677
|
-
});
|
|
1678
|
-
}
|
|
1679
|
-
indoorLayers.forEach((layer) => {
|
|
1680
|
-
this.applyLayerFilters(layer, floorId);
|
|
1681
|
-
});
|
|
1682
|
-
}
|
|
1683
|
-
/**
|
|
1684
|
-
* Reads a layer's current filter, tolerating the layer having been removed or the
|
|
1685
|
-
* style being mid-swap (maplibre `getFilter` throws in those cases). Returns
|
|
1686
|
-
* `undefined` instead of throwing so base-filter capture never aborts floor filtering.
|
|
1687
|
-
*/
|
|
1688
|
-
safeGetFilter(layerId) {
|
|
1689
|
-
try {
|
|
1690
|
-
return this.map.getFilter(layerId);
|
|
1691
|
-
}
|
|
1692
|
-
catch (_a) {
|
|
1693
|
-
return undefined;
|
|
1694
|
-
}
|
|
1695
|
-
}
|
|
1696
|
-
applyLayerFilters(layer, floorId) {
|
|
1697
|
-
var _a;
|
|
1698
|
-
// Resolve the layer's declared (base) filter the first time we touch it. Indoor
|
|
1699
|
-
// layers such as `indoor-polygon-room` share one source and are told apart only by
|
|
1700
|
-
// their own filter (e.g. `["in","room",["get","tags"]]`); we must fold that into the
|
|
1701
|
-
// floor filter or the layer repaints every feature. Some of these layers are added
|
|
1702
|
-
// asynchronously after a `setStyle` swap, so they are absent when `setBaseFilters`
|
|
1703
|
-
// runs — capturing here, while their live filter is still pristine, is what keeps a
|
|
1704
|
-
// floor change from wiping the style (the washed-out, blue-outline look).
|
|
1705
|
-
// `base-indoor-*` clones inherit their base filter from the layer they were cloned
|
|
1706
|
-
// from, so they are never read back.
|
|
1707
|
-
if (!layer.id.startsWith("base-indoor-")) {
|
|
1708
|
-
this.captureDeclaredBaseFilter(layer.id);
|
|
1709
|
-
}
|
|
1710
|
-
let baseFilter = this.baseFilters[layer.id];
|
|
1711
|
-
const floorIdString = floorId !== null && floorId !== void 0 ? floorId : "";
|
|
1712
|
-
const floorFilter = [
|
|
1713
|
-
"any",
|
|
1714
|
-
["in", floorIdString, ["get", "floor_key"]],
|
|
1715
|
-
];
|
|
1716
|
-
const publicPlaceFilter = ["==", ["get", "public_place"], true];
|
|
1717
|
-
const subPlaceFilter = [
|
|
1718
|
-
"!",
|
|
1719
|
-
["in", (_a = this.parentPlaceId) !== null && _a !== void 0 ? _a : "noRef", ["get", "is_subplace"]],
|
|
1720
|
-
];
|
|
1721
|
-
const showInFloorFilter = [
|
|
1722
|
-
"in",
|
|
1723
|
-
floorIdString,
|
|
1724
|
-
["get", "show_in_floor"],
|
|
1725
|
-
];
|
|
1726
|
-
let basicFilters = publicPlaceFilter;
|
|
1727
|
-
if (this.parentPlaceId != null) {
|
|
1728
|
-
basicFilters = ["any", floorFilter, publicPlaceFilter];
|
|
1729
|
-
}
|
|
1730
|
-
const customFilters = [
|
|
1731
|
-
"all",
|
|
1732
|
-
basicFilters,
|
|
1733
|
-
["any", subPlaceFilter, showInFloorFilter],
|
|
1734
|
-
];
|
|
1735
|
-
if (layer.id.includes("hover")) {
|
|
1736
|
-
baseFilter = ["in", this.hoveredId, ["get", "ref"]];
|
|
1737
|
-
}
|
|
1738
|
-
else if (!layer.id.includes("colored") &&
|
|
1739
|
-
(layer.type == "fill" || layer.type === "fill-extrusion")) {
|
|
1740
|
-
customFilters.push(["!", ["in", this.hoveredId, ["get", "ref"]]]);
|
|
1741
|
-
}
|
|
1742
|
-
// From here on this layer's live filter is ours, never the style's declared one.
|
|
1743
|
-
// Marking it keeps any later pass from reading it back as a base filter — that is
|
|
1744
|
-
// what used to AND the previous floor into the next one and blank the layer out.
|
|
1745
|
-
this.capturedBaseFilters.add(layer.id);
|
|
1746
|
-
if (baseFilter != null) {
|
|
1747
|
-
const finalFilter = ["all", baseFilter, customFilters];
|
|
1748
|
-
this.map.setFilter(layer.id, finalFilter);
|
|
1749
|
-
}
|
|
1750
|
-
else {
|
|
1751
|
-
this.map.setFilter(layer.id, customFilters);
|
|
1752
|
-
}
|
|
1753
|
-
}
|
|
1754
|
-
/**
|
|
1755
|
-
* Floors of the parent place
|
|
1756
|
-
* @returns Inner floors of the parent place
|
|
1757
|
-
*/
|
|
1758
|
-
getInnerFloors() {
|
|
1759
|
-
return this.innerFloors;
|
|
1760
|
-
}
|
|
1761
|
-
/**
|
|
1762
|
-
* Whether `floorId` shows the venue's outdoors, and so whether entities that
|
|
1763
|
-
* carry no floor of their own belong on screen with it.
|
|
1764
|
-
*
|
|
1765
|
-
* A map without a parent place is outdoors throughout; inside one, only a
|
|
1766
|
-
* floor reachable from GPS is. Markers, circles and the route marker all hang
|
|
1767
|
-
* their visibility on this, so they answer it the same way.
|
|
1768
|
-
*/
|
|
1769
|
-
isOutdoorFloor(floorId) {
|
|
1770
|
-
var _a, _b;
|
|
1771
|
-
return (!this.parentPlace ||
|
|
1772
|
-
((_b = (_a = this.innerFloors.find((floor) => floor.key === floorId)) === null || _a === void 0 ? void 0 : _a.reachableFromGPS) !== null && _b !== void 0 ? _b : false));
|
|
1773
|
-
}
|
|
1774
|
-
updateCamera(newCamera, callback) {
|
|
1775
|
-
void this.map.once("moveend", callback);
|
|
1776
|
-
this.map.easeTo(Object.assign({}, newCamera));
|
|
1777
|
-
}
|
|
1778
|
-
setMaxZoom(zoomLvl, options) {
|
|
1779
|
-
var _a;
|
|
1780
|
-
this.map.setMaxZoom(zoomLvl);
|
|
1781
|
-
(_a = options === null || options === void 0 ? void 0 : options.onComplete) === null || _a === void 0 ? void 0 : _a.call(options);
|
|
1782
|
-
}
|
|
1783
|
-
setMinZoom(zoomLvl, options) {
|
|
1784
|
-
var _a;
|
|
1785
|
-
this.map.setMinZoom(zoomLvl);
|
|
1786
|
-
(_a = options === null || options === void 0 ? void 0 : options.onComplete) === null || _a === void 0 ? void 0 : _a.call(options);
|
|
1787
|
-
}
|
|
1788
|
-
setBearing(degrees, options) {
|
|
1789
|
-
var _a;
|
|
1790
|
-
if (!Number.isFinite(degrees))
|
|
1791
|
-
return;
|
|
1792
|
-
if (options === null || options === void 0 ? void 0 : options.animate) {
|
|
1793
|
-
if (options.onComplete)
|
|
1794
|
-
void this.map.once("moveend", options.onComplete);
|
|
1795
|
-
this.map.rotateTo(degrees, { duration: 600 });
|
|
1796
|
-
return;
|
|
1797
|
-
}
|
|
1798
|
-
this.map.setBearing(degrees);
|
|
1799
|
-
(_a = options === null || options === void 0 ? void 0 : options.onComplete) === null || _a === void 0 ? void 0 : _a.call(options);
|
|
1800
|
-
}
|
|
1801
|
-
setRotationEnabled(enabled) {
|
|
1802
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
1803
|
-
if (enabled) {
|
|
1804
|
-
(_b = (_a = this.map.dragRotate) === null || _a === void 0 ? void 0 : _a.enable) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
1805
|
-
(_d = (_c = this.map.touchZoomRotate) === null || _c === void 0 ? void 0 : _c.enableRotation) === null || _d === void 0 ? void 0 : _d.call(_c);
|
|
1806
|
-
(_f = (_e = this.map.keyboard) === null || _e === void 0 ? void 0 : _e.enable) === null || _f === void 0 ? void 0 : _f.call(_e);
|
|
1807
|
-
}
|
|
1808
|
-
else {
|
|
1809
|
-
(_h = (_g = this.map.dragRotate) === null || _g === void 0 ? void 0 : _g.disable) === null || _h === void 0 ? void 0 : _h.call(_g);
|
|
1810
|
-
(_k = (_j = this.map.touchZoomRotate) === null || _j === void 0 ? void 0 : _j.disableRotation) === null || _k === void 0 ? void 0 : _k.call(_j);
|
|
1811
|
-
}
|
|
1812
|
-
}
|
|
1813
|
-
setPanEnabled(enabled) {
|
|
1814
|
-
var _a, _b, _c, _d;
|
|
1815
|
-
if (enabled) {
|
|
1816
|
-
(_b = (_a = this.map.dragPan) === null || _a === void 0 ? void 0 : _a.enable) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
1817
|
-
}
|
|
1818
|
-
else {
|
|
1819
|
-
(_d = (_c = this.map.dragPan) === null || _c === void 0 ? void 0 : _c.disable) === null || _d === void 0 ? void 0 : _d.call(_c);
|
|
1820
|
-
}
|
|
1821
|
-
}
|
|
1822
|
-
setScrollZoomEnabled(enabled) {
|
|
1823
|
-
var _a, _b, _c, _d;
|
|
1824
|
-
if (enabled) {
|
|
1825
|
-
(_b = (_a = this.map.scrollZoom) === null || _a === void 0 ? void 0 : _a.enable) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
1826
|
-
}
|
|
1827
|
-
else {
|
|
1828
|
-
(_d = (_c = this.map.scrollZoom) === null || _c === void 0 ? void 0 : _c.disable) === null || _d === void 0 ? void 0 : _d.call(_c);
|
|
1829
|
-
}
|
|
1830
|
-
}
|
|
1831
|
-
isInsideBounds(point) {
|
|
1832
|
-
const mapBounds = this.map.getMaxBounds();
|
|
1833
|
-
if (mapBounds != null) {
|
|
1834
|
-
return mapBounds.contains(point);
|
|
1835
|
-
}
|
|
1836
|
-
return true;
|
|
1837
|
-
}
|
|
1838
|
-
setMaxBounds(coordinates, options) {
|
|
1839
|
-
var _a;
|
|
1840
|
-
if (coordinates.length > 1) {
|
|
1841
|
-
const boundingBox = (0, utils_1.getBoundingBox)(coordinates);
|
|
1842
|
-
this.map.setMaxBounds(new maplibre_gl_1.LngLatBounds([boundingBox[0].lng, boundingBox[0].lat], [boundingBox[1].lng, boundingBox[1].lat]));
|
|
1843
|
-
}
|
|
1844
|
-
(_a = options === null || options === void 0 ? void 0 : options.onComplete) === null || _a === void 0 ? void 0 : _a.call(options);
|
|
1845
|
-
}
|
|
1846
|
-
fitCoordinates(coordinates, options) {
|
|
1847
|
-
var _a, _b;
|
|
1848
|
-
if (coordinates.length === 0) {
|
|
1849
|
-
return;
|
|
1850
|
-
}
|
|
1851
|
-
const onComplete = options === null || options === void 0 ? void 0 : options.onComplete;
|
|
1852
|
-
if (coordinates.length === 1) {
|
|
1853
|
-
const [coord] = coordinates;
|
|
1854
|
-
if (onComplete) {
|
|
1855
|
-
void this.map.once("moveend", onComplete);
|
|
1856
|
-
}
|
|
1857
|
-
this.map.setCenter({
|
|
1858
|
-
lat: coord.lat,
|
|
1859
|
-
lng: coord.lng,
|
|
1860
|
-
});
|
|
1861
|
-
}
|
|
1862
|
-
else {
|
|
1863
|
-
let minLat;
|
|
1864
|
-
let minLng;
|
|
1865
|
-
let maxLat;
|
|
1866
|
-
let maxLng;
|
|
1867
|
-
for (const coord of coordinates) {
|
|
1868
|
-
const { lat, lng } = coord;
|
|
1869
|
-
minLat = minLat === undefined ? lat : Math.min(minLat, lat);
|
|
1870
|
-
minLng = minLng === undefined ? lng : Math.min(minLng, lng);
|
|
1871
|
-
maxLat = maxLat === undefined ? lat : Math.max(maxLat, lat);
|
|
1872
|
-
maxLng = maxLng === undefined ? lng : Math.max(maxLng, lng);
|
|
1873
|
-
}
|
|
1874
|
-
const bounds = [
|
|
1875
|
-
minLng !== null && minLng !== void 0 ? minLng : 0,
|
|
1876
|
-
minLat !== null && minLat !== void 0 ? minLat : 0,
|
|
1877
|
-
maxLng !== null && maxLng !== void 0 ? maxLng : 0,
|
|
1878
|
-
maxLat !== null && maxLat !== void 0 ? maxLat : 0,
|
|
1879
|
-
];
|
|
1880
|
-
// Get container dimensions for percentage-based padding calculations
|
|
1881
|
-
const container = this.map.getContainer();
|
|
1882
|
-
const containerWidth = container.clientWidth;
|
|
1883
|
-
const containerHeight = container.clientHeight;
|
|
1884
|
-
// Convert padding values with units to pixels
|
|
1885
|
-
const padding = convertPaddingToPixels(options === null || options === void 0 ? void 0 : options.padding, containerWidth, containerHeight);
|
|
1886
|
-
const fitOptions = Object.assign(Object.assign({}, options), { padding });
|
|
1887
|
-
const bearing = this.map.getBearing();
|
|
1888
|
-
const pitch = this.map.getPitch();
|
|
1889
|
-
(_a = fitOptions.bearing) !== null && _a !== void 0 ? _a : (fitOptions.bearing = bearing);
|
|
1890
|
-
(_b = fitOptions.pitch) !== null && _b !== void 0 ? _b : (fitOptions.pitch = pitch);
|
|
1891
|
-
if (onComplete) {
|
|
1892
|
-
void this.map.once("moveend", onComplete);
|
|
1893
|
-
}
|
|
1894
|
-
this.map.fitBounds(bounds, fitOptions);
|
|
1895
|
-
}
|
|
1896
|
-
}
|
|
1897
|
-
easeToCoordinates(coordinates, options) {
|
|
1898
|
-
var _a, _b, _c;
|
|
1899
|
-
if (coordinates.length === 0) {
|
|
1900
|
-
return;
|
|
1901
|
-
}
|
|
1902
|
-
const onComplete = options === null || options === void 0 ? void 0 : options.onComplete;
|
|
1903
|
-
// Get container dimensions for percentage-based padding calculations
|
|
1904
|
-
const container = this.map.getContainer();
|
|
1905
|
-
const containerWidth = container.clientWidth;
|
|
1906
|
-
const containerHeight = container.clientHeight;
|
|
1907
|
-
// Convert padding values with units to pixels
|
|
1908
|
-
const padding = convertPaddingToPixels(options === null || options === void 0 ? void 0 : options.padding, containerWidth, containerHeight);
|
|
1909
|
-
if (coordinates.length === 1) {
|
|
1910
|
-
const [coord] = coordinates;
|
|
1911
|
-
if (onComplete) {
|
|
1912
|
-
void this.map.once("moveend", onComplete);
|
|
1913
|
-
}
|
|
1914
|
-
this.map.easeTo(Object.assign(Object.assign({}, options), { center: { lat: coord.lat, lng: coord.lng }, padding }));
|
|
1915
|
-
return;
|
|
1916
|
-
}
|
|
1917
|
-
const bearing = (_a = options === null || options === void 0 ? void 0 : options.bearing) !== null && _a !== void 0 ? _a : this.map.getBearing();
|
|
1918
|
-
const pitch = (_b = options === null || options === void 0 ? void 0 : options.pitch) !== null && _b !== void 0 ? _b : this.map.getPitch();
|
|
1919
|
-
const cameraParams = {
|
|
1920
|
-
bearing,
|
|
1921
|
-
padding,
|
|
1922
|
-
containerWidth,
|
|
1923
|
-
containerHeight,
|
|
1924
|
-
maxZoom: (_c = options === null || options === void 0 ? void 0 : options.maxZoom) !== null && _c !== void 0 ? _c : this.map.getMaxZoom(),
|
|
1925
|
-
minZoom: this.map.getMinZoom(),
|
|
1926
|
-
};
|
|
1927
|
-
// Orthographic oriented fit (exact for pitch 0). maplibre's fitBounds only
|
|
1928
|
-
// fits a north-up LngLatBounds, which wastes viewport space for diagonal
|
|
1929
|
-
// geometries under a bearing.
|
|
1930
|
-
let camera = (0, fitCamera_1.computeOrientedCamera)(coordinates, cameraParams);
|
|
1931
|
-
// With a tilted camera the orthographic zoom is off due to perspective, so
|
|
1932
|
-
// refine it against maplibre's real projection on a cloned transform.
|
|
1933
|
-
if (pitch > 0) {
|
|
1934
|
-
// Simulated on the live map through its public API rather than on `map.transform.clone()`:
|
|
1935
|
-
// MapLibre v6 removes the internal transform and exposes no replacement. The whole loop is
|
|
1936
|
-
// synchronous and ends in `restore()`, so no intermediate frame is ever painted.
|
|
1937
|
-
// See DEVELOPER_DOCS/MAPLIBRE_V6_MIGRATION.md.
|
|
1938
|
-
const fitTransform = new liveMapFitTransform_1.LiveMapFitTransform(this.map);
|
|
1939
|
-
try {
|
|
1940
|
-
camera = (0, fitCamera_1.refineCameraForPitch)(fitTransform, coordinates, camera, Object.assign(Object.assign({}, cameraParams), { pitch }));
|
|
1941
|
-
}
|
|
1942
|
-
finally {
|
|
1943
|
-
fitTransform.restore();
|
|
1944
|
-
}
|
|
1945
|
-
}
|
|
1946
|
-
if (onComplete) {
|
|
1947
|
-
void this.map.once("moveend", onComplete);
|
|
1948
|
-
}
|
|
1949
|
-
this.map.easeTo(Object.assign(Object.assign({}, options), { center: camera.center, zoom: camera.zoom, bearing,
|
|
1950
|
-
pitch,
|
|
1951
|
-
padding }));
|
|
1952
|
-
}
|
|
1953
|
-
fitRouteByPlace(route, placeId, options) {
|
|
1954
|
-
// Filter steps that belong to the specified place
|
|
1955
|
-
const stepsInPlace = route.steps.filter((step) => step.startInsidePlace === placeId || step.endInsidePlace === placeId);
|
|
1956
|
-
if (stepsInPlace.length === 0) {
|
|
1957
|
-
return;
|
|
1958
|
-
}
|
|
1959
|
-
// Extract all coordinates from the filtered steps
|
|
1960
|
-
const coords = stepsInPlace.flatMap((step) => (0, route_utils_1.extractStepCoordinates)(step));
|
|
1961
|
-
if (coords.length > 0) {
|
|
1962
|
-
this.fitCoordinates(coords, options);
|
|
1963
|
-
}
|
|
1964
|
-
}
|
|
1965
|
-
getZoomLevel() {
|
|
1966
|
-
return this.map.getZoom();
|
|
1967
|
-
}
|
|
1968
|
-
updateFloor(floorId, options) {
|
|
1969
|
-
var _a, _b, _c;
|
|
1970
|
-
try {
|
|
1971
|
-
this.filterByFloorKey(floorId);
|
|
1972
|
-
(_a = this.scene3D) === null || _a === void 0 ? void 0 : _a.onFloorChange(this.currentFloor);
|
|
1973
|
-
(_b = this.onFloorChange) === null || _b === void 0 ? void 0 : _b.call(this, floorId);
|
|
1974
|
-
this.logEvent("updateFloor", { floorId });
|
|
1975
|
-
(_c = options === null || options === void 0 ? void 0 : options.onComplete) === null || _c === void 0 ? void 0 : _c.call(options);
|
|
1976
|
-
}
|
|
1977
|
-
catch (error) {
|
|
1978
|
-
this.debugLog("Floor update failed", error);
|
|
1979
|
-
}
|
|
1980
|
-
}
|
|
1981
|
-
updatePlaceAndFloorForRouting(placeId, floorId) {
|
|
1982
|
-
var _a;
|
|
1983
|
-
try {
|
|
1984
|
-
this.updatePlaceAndFloor(placeId, floorId);
|
|
1985
|
-
const layers = this.safeStyleLayers();
|
|
1986
|
-
this.updateFiltersTo(floorId, layers);
|
|
1987
|
-
this.updateMarkersWhenFloorSettles(floorId, layers);
|
|
1988
|
-
(_a = this.onFloorChange) === null || _a === void 0 ? void 0 : _a.call(this, floorId);
|
|
1989
|
-
this.logEvent("updatePlaceAndFloorForRouting", { floorId });
|
|
1990
|
-
}
|
|
1991
|
-
catch (error) {
|
|
1992
|
-
this.debugLog("Place/floor update for routing failed", error);
|
|
1993
|
-
}
|
|
1994
|
-
}
|
|
1995
|
-
async addRoute(config, drawConfig = {}) {
|
|
1996
|
-
try {
|
|
1997
|
-
this.logEvent("addRoute");
|
|
1998
|
-
const fetchRouteConfig = new routeConfiguration_1.InternalGetRouteConfiguration(config);
|
|
1999
|
-
const drawRouteConfig = new routeConfiguration_1.InternalDrawRouteConfiguration(drawConfig);
|
|
2000
|
-
const route = await this.repository.fetchAndParseRoute(fetchRouteConfig);
|
|
2001
|
-
this.routeController.addRoute(route, drawRouteConfig);
|
|
2002
|
-
return route;
|
|
2003
|
-
}
|
|
2004
|
-
catch (error) {
|
|
2005
|
-
throw new Error("Error: Failed to add route", { cause: error });
|
|
2006
|
-
}
|
|
2007
|
-
}
|
|
2008
|
-
async addLiveRoute(config, drawConfig = {}) {
|
|
2009
|
-
try {
|
|
2010
|
-
this.logEvent("addLiveRoute");
|
|
2011
|
-
const fetchRouteConfig = new routeConfiguration_1.InternalGetRouteConfiguration(config);
|
|
2012
|
-
const drawRouteConfig = new routeConfiguration_1.InternalDrawRouteConfiguration(drawConfig);
|
|
2013
|
-
// Take a ticket BEFORE awaiting: refreshes overlap, and an older response
|
|
2014
|
-
// resolving after a newer one must not clobber it (stale route drawn,
|
|
2015
|
-
// liveRouteId pointing at the wrong instance).
|
|
2016
|
-
const ticket = ++this.liveRouteTicket;
|
|
2017
|
-
const route = await this.repository.fetchAndParseLiveRoute(fetchRouteConfig);
|
|
2018
|
-
if (ticket !== this.liveRouteTicket) {
|
|
2019
|
-
// Superseded while in flight — the map shows (or will show) the newer
|
|
2020
|
-
// route. Return the fetched data without drawing it.
|
|
2021
|
-
return route;
|
|
2022
|
-
}
|
|
2023
|
-
// Replace the previous live route so repeated refreshes don't stack paths
|
|
2024
|
-
// on the map. Removal happens AFTER the fetch, so a failed refresh leaves
|
|
2025
|
-
// the current route on screen. Targeted by id, so routes drawn with
|
|
2026
|
-
// addRoute are untouched.
|
|
2027
|
-
if (this.liveRouteId !== undefined && this.liveRouteId !== route.id) {
|
|
2028
|
-
this.routeController.removeRoute(this.liveRouteId);
|
|
2029
|
-
}
|
|
2030
|
-
// Clear any instance already using this id (a same-id refresh, or the same
|
|
2031
|
-
// route previously drawn via addRoute) — the controller derives its
|
|
2032
|
-
// source/layer ids from the route id, so re-adding without removing would
|
|
2033
|
-
// throw "Source already exists".
|
|
2034
|
-
if (this.routeController.getRouteIds().includes(route.id)) {
|
|
2035
|
-
this.routeController.removeRoute(route.id);
|
|
2036
|
-
}
|
|
2037
|
-
this.routeController.addRoute(route, drawRouteConfig);
|
|
2038
|
-
this.liveRouteId = route.id;
|
|
2039
|
-
return route;
|
|
2040
|
-
}
|
|
2041
|
-
catch (error) {
|
|
2042
|
-
const reason = error instanceof Error ? error.message : String(error);
|
|
2043
|
-
throw new Error(`Error: Failed to add route: ${reason}`, { cause: error });
|
|
2044
|
-
}
|
|
2045
|
-
}
|
|
2046
|
-
updateRouteProgress(routeId, position, behindStyle = { type: "Solid", color: "#276EF1" }) {
|
|
2047
|
-
try {
|
|
2048
|
-
const behindConfig = new routeConfiguration_1.InternalDrawRouteConfiguration({
|
|
2049
|
-
routeStyle: behindStyle,
|
|
2050
|
-
});
|
|
2051
|
-
this.routeController.updateRouteProgress(routeId, position, behindConfig);
|
|
2052
|
-
}
|
|
2053
|
-
catch (error) {
|
|
2054
|
-
this.debugLog("Route update failed (route may not exist)", error);
|
|
2055
|
-
}
|
|
2056
|
-
}
|
|
2057
|
-
removeRoute(routeId) {
|
|
2058
|
-
try {
|
|
2059
|
-
this.logEvent("removeRoute");
|
|
2060
|
-
this.routeController.removeRoute(routeId);
|
|
2061
|
-
}
|
|
2062
|
-
catch (error) {
|
|
2063
|
-
this.debugLog("Remove route failed (no route may exist)", error);
|
|
2064
|
-
}
|
|
2065
|
-
this.syncLiveRouteId();
|
|
2066
|
-
}
|
|
2067
|
-
removeRouteById(routeId) {
|
|
2068
|
-
try {
|
|
2069
|
-
this.logEvent("removeRouteById");
|
|
2070
|
-
this.routeController.removeRoute(routeId);
|
|
2071
|
-
}
|
|
2072
|
-
catch (error) {
|
|
2073
|
-
this.debugLog("Remove route failed (route may not exist)", error);
|
|
2074
|
-
}
|
|
2075
|
-
this.syncLiveRouteId();
|
|
2076
|
-
}
|
|
2077
|
-
removeAllRoutes() {
|
|
2078
|
-
try {
|
|
2079
|
-
this.logEvent("removeAllRoutes");
|
|
2080
|
-
this.routeController.removeAllRoutes();
|
|
2081
|
-
}
|
|
2082
|
-
catch (error) {
|
|
2083
|
-
this.debugLog("Remove all routes failed", error);
|
|
2084
|
-
}
|
|
2085
|
-
this.syncLiveRouteId();
|
|
2086
|
-
}
|
|
2087
|
-
/**
|
|
2088
|
-
* Drop {@link liveRouteId} when the instance it points at is no longer on the
|
|
2089
|
-
* map (removed via removeRoute/removeRouteById/removeAllRoutes — including the
|
|
2090
|
-
* no-arg removeRoute, which removes the last-added route). Keeps the live
|
|
2091
|
-
* bookkeeping in sync so a later {@link addLiveRoute} doesn't act on a
|
|
2092
|
-
* dangling id.
|
|
2093
|
-
*/
|
|
2094
|
-
syncLiveRouteId() {
|
|
2095
|
-
if (this.liveRouteId !== undefined &&
|
|
2096
|
-
!this.routeController.getRouteIds().includes(this.liveRouteId)) {
|
|
2097
|
-
this.liveRouteId = undefined;
|
|
2098
|
-
}
|
|
2099
|
-
}
|
|
2100
|
-
getActiveRouteIds() {
|
|
2101
|
-
return this.routeController.getRouteIds();
|
|
2102
|
-
}
|
|
2103
|
-
addPopOver(popOverConfig) {
|
|
2104
|
-
const placeId = popOverConfig.placeId;
|
|
2105
|
-
if (placeId !== undefined) {
|
|
2106
|
-
const pointedPlace = this.subPlaces.find((subplace) => {
|
|
2107
|
-
return subplace.clientId === placeId || subplace.mapvxId === placeId;
|
|
2108
|
-
});
|
|
2109
|
-
if (pointedPlace !== undefined) {
|
|
2110
|
-
new maplibre_gl_1.default.Popup()
|
|
2111
|
-
.setLngLat(pointedPlace.position)
|
|
2112
|
-
.setText(pointedPlace.title)
|
|
2113
|
-
.addTo(this.map);
|
|
2114
|
-
}
|
|
2115
|
-
}
|
|
2116
|
-
return "";
|
|
2117
|
-
}
|
|
2118
|
-
removePopOver(id) {
|
|
2119
|
-
throw Error("Not implemented");
|
|
2120
|
-
}
|
|
2121
|
-
startClickListener(callback) {
|
|
2122
|
-
this.clickCallback = callback;
|
|
2123
|
-
}
|
|
2124
|
-
stopClickListener() {
|
|
2125
|
-
this.clickCallback = undefined;
|
|
2126
|
-
}
|
|
2127
|
-
defaultClickListener() {
|
|
2128
|
-
this.map.on("click", async (e) => {
|
|
2129
|
-
var _a, _b;
|
|
2130
|
-
const features = this.map.queryRenderedFeatures(e.point);
|
|
2131
|
-
// Filter room features by current floor
|
|
2132
|
-
const roomFeatures = features.filter((feature) => { var _a; return feature.properties.class === "room" && ((_a = feature.properties) === null || _a === void 0 ? void 0 : _a.floor_key) === this.currentFloor; });
|
|
2133
|
-
const touchedFeature = roomFeatures[0];
|
|
2134
|
-
const ref = touchedFeature === null || touchedFeature === void 0 ? void 0 : touchedFeature.properties.ref;
|
|
2135
|
-
if (ref != null && typeof ref === "string" && ref.length > 0) {
|
|
2136
|
-
console.log("touchedFeature", ref);
|
|
2137
|
-
// First, search in current subPlaces
|
|
2138
|
-
let clickedPlace = this.subPlaces.find((subPlace) => subPlace.mapvxId === ref);
|
|
2139
|
-
// If not found, search in subplaces of potential parent places
|
|
2140
|
-
if (clickedPlace === undefined) {
|
|
2141
|
-
for (const potentialParentPlace of this.potentialParentPlaces) {
|
|
2142
|
-
const cachedSubPlaces = this.repository.getCachedSubPlaces(potentialParentPlace.mapvxId);
|
|
2143
|
-
if (cachedSubPlaces) {
|
|
2144
|
-
clickedPlace = cachedSubPlaces.find((subPlace) => subPlace.mapvxId === ref);
|
|
2145
|
-
if (clickedPlace !== undefined) {
|
|
2146
|
-
break;
|
|
2147
|
-
}
|
|
2148
|
-
}
|
|
2149
|
-
}
|
|
2150
|
-
}
|
|
2151
|
-
// As last resort, search in general cache
|
|
2152
|
-
if (clickedPlace === undefined) {
|
|
2153
|
-
const cachedPlaces = this.repository.getCachedPlaces([ref]);
|
|
2154
|
-
if (cachedPlaces.length === 0) {
|
|
2155
|
-
console.log("fetching place detail from server", ref);
|
|
2156
|
-
clickedPlace = await this.repository.fetchAndParsePlaceDetail({ id: ref });
|
|
2157
|
-
}
|
|
2158
|
-
else {
|
|
2159
|
-
console.log("using cached place detail", ref);
|
|
2160
|
-
clickedPlace = cachedPlaces === null || cachedPlaces === void 0 ? void 0 : cachedPlaces[0];
|
|
2161
|
-
}
|
|
2162
|
-
}
|
|
2163
|
-
console.log("clickedPlace", clickedPlace === null || clickedPlace === void 0 ? void 0 : clickedPlace.title);
|
|
2164
|
-
const clickedPlaceId = (_b = (_a = clickedPlace === null || clickedPlace === void 0 ? void 0 : clickedPlace.mapvxId) !== null && _a !== void 0 ? _a : clickedPlace === null || clickedPlace === void 0 ? void 0 : clickedPlace.clientId) !== null && _b !== void 0 ? _b : "";
|
|
2165
|
-
if (clickedPlace !== undefined && this.clickCallback) {
|
|
2166
|
-
this.clickCallback(clickedPlaceId);
|
|
2167
|
-
}
|
|
2168
|
-
}
|
|
2169
|
-
});
|
|
2170
|
-
}
|
|
2171
|
-
onHover() {
|
|
2172
|
-
if (!this.enableHover)
|
|
2173
|
-
return;
|
|
2174
|
-
this.map.on("mousemove", "hoverable", (e) => {
|
|
2175
|
-
var _a;
|
|
2176
|
-
const feature = (_a = e.features) === null || _a === void 0 ? void 0 : _a.find((feature) => { var _a, _b; return ((_a = feature.properties.ref) === null || _a === void 0 ? void 0 : _a.length) > 0 && ((_b = feature.properties) === null || _b === void 0 ? void 0 : _b.floor_key) === this.currentFloor; });
|
|
2177
|
-
const ref = feature === null || feature === void 0 ? void 0 : feature.properties.ref;
|
|
2178
|
-
if (ref != null && typeof ref === "string" && ref.length > 0) {
|
|
2179
|
-
this.hoveredId = ref;
|
|
2180
|
-
this.updateFiltersTo(this.currentFloor);
|
|
2181
|
-
}
|
|
2182
|
-
else if (ref === undefined && this.hoveredId !== "unselected") {
|
|
2183
|
-
this.hoveredId = "unselected";
|
|
2184
|
-
this.updateFiltersTo(this.currentFloor);
|
|
2185
|
-
}
|
|
2186
|
-
});
|
|
2187
|
-
// When the mouse leaves the state-fill layer, update the feature state of the
|
|
2188
|
-
// previously hovered feature.
|
|
2189
|
-
this.map.on("mouseleave", "hoverable", () => {
|
|
2190
|
-
this.hoveredId = "unselected";
|
|
2191
|
-
this.updateFiltersTo(this.currentFloor);
|
|
2192
|
-
});
|
|
2193
|
-
}
|
|
2194
|
-
setPlacesAsSelected(placeIds, color) {
|
|
2195
|
-
try {
|
|
2196
|
-
this.coloredPlacesRequest = { placeIds: [...placeIds], color };
|
|
2197
|
-
if (!this.hasLayer("indoor-colored-places")) {
|
|
2198
|
-
this.createColoredPlacesLayer();
|
|
2199
|
-
}
|
|
2200
|
-
const basicFilter = this.createBasicFilters();
|
|
2201
|
-
const coloredPlacesFilters = placeIds.map((id) => {
|
|
2202
|
-
const filter = ["==", ["get", "ref"], id];
|
|
2203
|
-
return filter;
|
|
2204
|
-
});
|
|
2205
|
-
const placeFilter = ["any", ...coloredPlacesFilters];
|
|
2206
|
-
const condition = ["all", placeFilter, basicFilter];
|
|
2207
|
-
this.map.setFilter("indoor-colored-places", condition);
|
|
2208
|
-
// Caller-defined base filter: mark it resolved so no later capture can
|
|
2209
|
-
// overwrite this customization with the layer's style-declared filter.
|
|
2210
|
-
this.capturedBaseFilters.add("indoor-colored-places");
|
|
2211
|
-
this.baseFilters["indoor-colored-places"] = placeFilter;
|
|
2212
|
-
if (color !== undefined) {
|
|
2213
|
-
const layer = this.map.getLayer("indoor-colored-places");
|
|
2214
|
-
switch (layer === null || layer === void 0 ? void 0 : layer.type) {
|
|
2215
|
-
case "fill":
|
|
2216
|
-
this.map.setPaintProperty("indoor-colored-places", "fill-color", color);
|
|
2217
|
-
break;
|
|
2218
|
-
case "fill-extrusion":
|
|
2219
|
-
this.map.setPaintProperty("indoor-colored-places", "fill-extrusion-color", color);
|
|
2220
|
-
break;
|
|
2221
|
-
default:
|
|
2222
|
-
break;
|
|
2223
|
-
}
|
|
2224
|
-
}
|
|
2225
|
-
}
|
|
2226
|
-
catch (error) {
|
|
2227
|
-
this.debugLog("Place selection failed (layer may not exist)", error);
|
|
2228
|
-
}
|
|
2229
|
-
}
|
|
2230
|
-
is3DStyle() {
|
|
2231
|
-
const layers = this.map.getStyle().layers;
|
|
2232
|
-
return layers.some((layer) => layer.type === "fill-extrusion");
|
|
2233
|
-
}
|
|
2234
|
-
createColoredPlacesLayer() {
|
|
2235
|
-
if (this.is3DStyle()) {
|
|
2236
|
-
const layer = {
|
|
2237
|
-
id: "indoor-colored-places",
|
|
2238
|
-
type: "fill-extrusion",
|
|
2239
|
-
source: "indoorequal",
|
|
2240
|
-
"source-layer": "area",
|
|
2241
|
-
filter: ["all", ["has", "ref"], ["==", ["get", "ref"], "unselected"]],
|
|
2242
|
-
paint: {
|
|
2243
|
-
"fill-extrusion-height": 2.5,
|
|
2244
|
-
"fill-extrusion-color": circle_1.MAPVX_BRAND_COLOR,
|
|
2245
|
-
"fill-extrusion-opacity": 0.8,
|
|
2246
|
-
},
|
|
2247
|
-
};
|
|
2248
|
-
this.map.addLayer(layer);
|
|
2249
|
-
}
|
|
2250
|
-
else {
|
|
2251
|
-
const layer = {
|
|
2252
|
-
id: "indoor-colored-places",
|
|
2253
|
-
type: "fill",
|
|
2254
|
-
source: "indoorequal",
|
|
2255
|
-
"source-layer": "area",
|
|
2256
|
-
filter: ["all", ["has", "ref"], ["==", ["get", "ref"], "unselected"]],
|
|
2257
|
-
paint: {
|
|
2258
|
-
"fill-color": circle_1.MAPVX_BRAND_COLOR,
|
|
2259
|
-
},
|
|
2260
|
-
};
|
|
2261
|
-
this.map.addLayer(layer);
|
|
2262
|
-
}
|
|
2263
|
-
}
|
|
2264
|
-
createBasicFilters() {
|
|
2265
|
-
var _a, _b;
|
|
2266
|
-
const publicPlaceFilter = ["==", ["get", "public_place"], true];
|
|
2267
|
-
const subPlaceFilter = [
|
|
2268
|
-
"!",
|
|
2269
|
-
["in", (_a = this.parentPlaceId) !== null && _a !== void 0 ? _a : "noRef", ["get", "is_subplace"]],
|
|
2270
|
-
];
|
|
2271
|
-
const floorId = (_b = this.currentFloor) !== null && _b !== void 0 ? _b : "noRef";
|
|
2272
|
-
const floorFilter = ["in", floorId, ["get", "floor_key"]];
|
|
2273
|
-
const showInFloorFilter = [
|
|
2274
|
-
"in",
|
|
2275
|
-
floorId,
|
|
2276
|
-
["get", "show_in_floor"],
|
|
2277
|
-
];
|
|
2278
|
-
let basicFilters = publicPlaceFilter;
|
|
2279
|
-
if (this.parentPlaceId != null) {
|
|
2280
|
-
basicFilters = ["any", floorFilter, publicPlaceFilter];
|
|
2281
|
-
}
|
|
2282
|
-
const customFilters = [
|
|
2283
|
-
"all",
|
|
2284
|
-
basicFilters,
|
|
2285
|
-
["any", subPlaceFilter, showInFloorFilter],
|
|
2286
|
-
];
|
|
2287
|
-
return customFilters;
|
|
2288
|
-
}
|
|
2289
|
-
hasLayer(layerId) {
|
|
2290
|
-
const layer = this.map.getLayer(layerId);
|
|
2291
|
-
return layer !== undefined;
|
|
2292
|
-
}
|
|
2293
|
-
clearColoredPlaces() {
|
|
2294
|
-
this.coloredPlacesRequest = undefined;
|
|
2295
|
-
if (this.hasLayer("indoor-colored-places")) {
|
|
2296
|
-
const condition = ["==", ["get", "ref"], "unselected"];
|
|
2297
|
-
// Caller-defined base filter: mark it resolved so no later capture can
|
|
2298
|
-
// overwrite this customization with the layer's style-declared filter.
|
|
2299
|
-
this.capturedBaseFilters.add("indoor-colored-places");
|
|
2300
|
-
this.baseFilters["indoor-colored-places"] = condition;
|
|
2301
|
-
this.map.setFilter("indoor-colored-places", condition);
|
|
2302
|
-
}
|
|
2303
|
-
}
|
|
2304
|
-
async startAnimateRoute(routeConfig, drawConfig = {}, animationConfig = {}) {
|
|
2305
|
-
try {
|
|
2306
|
-
let route;
|
|
2307
|
-
if (routeConfig instanceof route_1.MVXRoute) {
|
|
2308
|
-
route = routeConfig;
|
|
2309
|
-
}
|
|
2310
|
-
else {
|
|
2311
|
-
const fetchRouteConfig = new routeConfiguration_1.InternalGetRouteConfiguration(routeConfig);
|
|
2312
|
-
route = await this.repository.fetchAndParseRoute(fetchRouteConfig);
|
|
2313
|
-
}
|
|
2314
|
-
const drawRouteConfig = new animation_1.InternalAnimationDrawingConfig(drawConfig);
|
|
2315
|
-
const animationRouteConfig = new animation_1.InternalAnimationConfig(animationConfig);
|
|
2316
|
-
this.routeController.startAnimating(route, drawRouteConfig, animationRouteConfig);
|
|
2317
|
-
return route;
|
|
2318
|
-
}
|
|
2319
|
-
catch (error) {
|
|
2320
|
-
throw new Error("Error: Failed to start animating route", { cause: error });
|
|
2321
|
-
}
|
|
2322
|
-
}
|
|
2323
|
-
async startAnimateRouteV2(routeConfig, drawConfig = {}, animationConfig = {}) {
|
|
2324
|
-
try {
|
|
2325
|
-
let route;
|
|
2326
|
-
if (routeConfig instanceof route_1.MVXRoute) {
|
|
2327
|
-
route = routeConfig;
|
|
2328
|
-
}
|
|
2329
|
-
else {
|
|
2330
|
-
const fetchRouteConfig = new routeConfiguration_1.InternalGetRouteConfiguration(routeConfig);
|
|
2331
|
-
route = await this.repository.fetchAndParseRouteV2(fetchRouteConfig);
|
|
2332
|
-
}
|
|
2333
|
-
const drawRouteConfig = new animation_1.InternalAnimationDrawingConfig(drawConfig);
|
|
2334
|
-
const animationRouteConfig = new animation_1.InternalAnimationConfig(animationConfig);
|
|
2335
|
-
this.routeController.startAnimating(route, drawRouteConfig, animationRouteConfig);
|
|
2336
|
-
return route;
|
|
2337
|
-
}
|
|
2338
|
-
catch (error) {
|
|
2339
|
-
throw new Error("Error: Failed to start animating route", { cause: error });
|
|
2340
|
-
}
|
|
2341
|
-
}
|
|
2342
|
-
pauseAnimateRoute(routeId) {
|
|
2343
|
-
try {
|
|
2344
|
-
this.routeController.pauseAnimation(routeId);
|
|
2345
|
-
this.logEvent("pauseAnimateRoute");
|
|
2346
|
-
}
|
|
2347
|
-
catch (error) {
|
|
2348
|
-
this.debugLog("Pause animation failed (no animation may be running)", error);
|
|
2349
|
-
}
|
|
2350
|
-
}
|
|
2351
|
-
resumeAnimateRoute(routeId) {
|
|
2352
|
-
try {
|
|
2353
|
-
this.routeController.resumeAnimation(routeId);
|
|
2354
|
-
this.logEvent("resumeAnimateRoute");
|
|
2355
|
-
}
|
|
2356
|
-
catch (error) {
|
|
2357
|
-
this.debugLog("Resume animation failed (no animation may be paused)", error);
|
|
2358
|
-
}
|
|
2359
|
-
}
|
|
2360
|
-
restartAnimateRoute(routeId) {
|
|
2361
|
-
try {
|
|
2362
|
-
this.routeController.restartAnimation(routeId);
|
|
2363
|
-
this.logEvent("restartAnimateRoute");
|
|
2364
|
-
}
|
|
2365
|
-
catch (error) {
|
|
2366
|
-
this.debugLog("Restart animation failed (no animation may exist)", error);
|
|
2367
|
-
}
|
|
2368
|
-
}
|
|
2369
|
-
stopAnimateRoute() {
|
|
2370
|
-
this.logWarning(new Error("Not implemented"), {
|
|
2371
|
-
functionName: "stopAnimateRoute",
|
|
2372
|
-
});
|
|
2373
|
-
}
|
|
2374
|
-
async addStepAnimation(routeConfig, drawConfig = {}, animationConfig = {}) {
|
|
2375
|
-
try {
|
|
2376
|
-
let route;
|
|
2377
|
-
if (routeConfig instanceof route_1.MVXRoute) {
|
|
2378
|
-
route = routeConfig;
|
|
2379
|
-
}
|
|
2380
|
-
else {
|
|
2381
|
-
const fetchRouteConfig = new routeConfiguration_1.InternalGetRouteConfiguration(routeConfig);
|
|
2382
|
-
route = await this.repository.fetchAndParseRoute(fetchRouteConfig);
|
|
2383
|
-
}
|
|
2384
|
-
const drawRouteConfig = new animation_1.InternalAnimationDrawingConfig(drawConfig);
|
|
2385
|
-
const animationRouteConfig = new animation_1.InternalAnimationConfig(animationConfig);
|
|
2386
|
-
this.routeController.addStepAnimation(route, drawRouteConfig, animationRouteConfig);
|
|
2387
|
-
this.logEvent("addStepAnimation");
|
|
2388
|
-
return route;
|
|
2389
|
-
}
|
|
2390
|
-
catch (error) {
|
|
2391
|
-
throw new Error("Error: Failed to add step animation", { cause: error });
|
|
2392
|
-
}
|
|
2393
|
-
}
|
|
2394
|
-
async addStepAnimationV2(routeConfig, drawConfig = {}, animationConfig = {}) {
|
|
2395
|
-
try {
|
|
2396
|
-
let route;
|
|
2397
|
-
if (routeConfig instanceof route_1.MVXRoute) {
|
|
2398
|
-
route = routeConfig;
|
|
2399
|
-
}
|
|
2400
|
-
else {
|
|
2401
|
-
const fetchRouteConfig = new routeConfiguration_1.InternalGetRouteConfiguration(routeConfig);
|
|
2402
|
-
route = await this.repository.fetchAndParseRouteV2(fetchRouteConfig);
|
|
2403
|
-
}
|
|
2404
|
-
const drawRouteConfig = new animation_1.InternalAnimationDrawingConfig(drawConfig);
|
|
2405
|
-
const animationRouteConfig = new animation_1.InternalAnimationConfig(animationConfig);
|
|
2406
|
-
this.routeController.addStepAnimation(route, drawRouteConfig, animationRouteConfig);
|
|
2407
|
-
this.logEvent("addStepAnimationV2");
|
|
2408
|
-
return route;
|
|
2409
|
-
}
|
|
2410
|
-
catch (error) {
|
|
2411
|
-
throw new Error("Error: Failed to add step animation", { cause: error });
|
|
2412
|
-
}
|
|
2413
|
-
}
|
|
2414
|
-
nextStepAnimation(routeId) {
|
|
2415
|
-
try {
|
|
2416
|
-
this.routeController.nextStepAnimation(routeId);
|
|
2417
|
-
this.logEvent("nextStepAnimation");
|
|
2418
|
-
}
|
|
2419
|
-
catch (error) {
|
|
2420
|
-
throw new Error("Error: Failed to animate step", { cause: error });
|
|
2421
|
-
}
|
|
2422
|
-
}
|
|
2423
|
-
previousStepAnimation(routeId) {
|
|
2424
|
-
try {
|
|
2425
|
-
this.routeController.previousStepAnimation(routeId);
|
|
2426
|
-
this.logEvent("previousStepAnimation");
|
|
2427
|
-
}
|
|
2428
|
-
catch (error) {
|
|
2429
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2430
|
-
throw new Error("Error: Failed to prepare to animate previous step, " + errorMessage);
|
|
2431
|
-
}
|
|
2432
|
-
}
|
|
2433
|
-
centerOnCurrentStepAnimation(routeId) {
|
|
2434
|
-
try {
|
|
2435
|
-
this.routeController.centerOnCurrentStep(routeId);
|
|
2436
|
-
this.logEvent("centerOnCurrentStepAnimation");
|
|
2437
|
-
}
|
|
2438
|
-
catch (error) {
|
|
2439
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2440
|
-
throw new Error("Error: Failed to center on current step animation, " + errorMessage);
|
|
2441
|
-
}
|
|
2442
|
-
}
|
|
2443
|
-
addUserLocationTracking(enableHighAccuracy, geoLocation, icon) {
|
|
2444
|
-
const initialCoordinate = {
|
|
2445
|
-
lat: 0.0,
|
|
2446
|
-
lng: 0.0,
|
|
2447
|
-
};
|
|
2448
|
-
let markerIcon;
|
|
2449
|
-
if (icon != null) {
|
|
2450
|
-
markerIcon = icon;
|
|
2451
|
-
}
|
|
2452
|
-
else {
|
|
2453
|
-
const image = new Image(40, 40);
|
|
2454
|
-
image.src = icons_1.userLocationDataUrl;
|
|
2455
|
-
markerIcon = image;
|
|
2456
|
-
}
|
|
2457
|
-
const markerConfig = {
|
|
2458
|
-
coordinate: initialCoordinate,
|
|
2459
|
-
icon: markerIcon,
|
|
2460
|
-
text: "",
|
|
2461
|
-
};
|
|
2462
|
-
const marker = new marker_1.MarkerAttribute(markerConfig);
|
|
2463
|
-
marker.addToMap(this.map);
|
|
2464
|
-
this.userLocationMarker = marker;
|
|
2465
|
-
this.markerLayoutController.scheduleRelayout();
|
|
2466
|
-
const updateMarker = (position) => {
|
|
2467
|
-
marker.setLngLat([position.coords.longitude, position.coords.latitude]);
|
|
2468
|
-
this.markerLayoutController.scheduleRelayout();
|
|
2469
|
-
};
|
|
2470
|
-
this.geoLocation = geoLocation !== null && geoLocation !== void 0 ? geoLocation : navigator.geolocation;
|
|
2471
|
-
this.watchPositionID = this.geoLocation.watchPosition(updateMarker, (error) => {
|
|
2472
|
-
console.error("Error getting user location:", error);
|
|
2473
|
-
}, {
|
|
2474
|
-
enableHighAccuracy: enableHighAccuracy,
|
|
2475
|
-
});
|
|
2476
|
-
}
|
|
2477
|
-
removeUserLocationTracking() {
|
|
2478
|
-
if (this.userLocationMarker !== undefined) {
|
|
2479
|
-
this.userLocationMarker.remove();
|
|
2480
|
-
this.userLocationMarker = undefined;
|
|
2481
|
-
this.markerLayoutController.scheduleRelayout();
|
|
2482
|
-
// Check if watchPosition ID is valid and stop tracking
|
|
2483
|
-
if (this.watchPositionID) {
|
|
2484
|
-
this.geoLocation.clearWatch(this.watchPositionID);
|
|
2485
|
-
this.watchPositionID = undefined;
|
|
2486
|
-
}
|
|
2487
|
-
}
|
|
2488
|
-
}
|
|
2489
|
-
getBearing() {
|
|
2490
|
-
return this.map.getBearing();
|
|
2491
|
-
}
|
|
2492
|
-
addBorderToPlaces(placeIds, color, width) {
|
|
2493
|
-
try {
|
|
2494
|
-
this.borderedPlacesRequest = { placeIds: [...placeIds], color, width };
|
|
2495
|
-
if (!this.hasLayer("indoor-bordered-places")) {
|
|
2496
|
-
this.createBorderedPlacesLayer();
|
|
2497
|
-
}
|
|
2498
|
-
const basicFilter = this.createBasicFilters();
|
|
2499
|
-
const coloredPlacesFilters = placeIds.map((id) => {
|
|
2500
|
-
const filter = ["==", ["get", "ref"], id];
|
|
2501
|
-
return filter;
|
|
2502
|
-
});
|
|
2503
|
-
const placeFilter = ["any", ...coloredPlacesFilters];
|
|
2504
|
-
const condition = ["all", placeFilter, basicFilter];
|
|
2505
|
-
this.map.setFilter("indoor-bordered-places", condition);
|
|
2506
|
-
// Caller-defined base filter: mark it resolved so no later capture can
|
|
2507
|
-
// overwrite this customization with the layer's style-declared filter.
|
|
2508
|
-
this.capturedBaseFilters.add("indoor-bordered-places");
|
|
2509
|
-
this.baseFilters["indoor-bordered-places"] = placeFilter;
|
|
2510
|
-
if (color !== undefined && width !== undefined) {
|
|
2511
|
-
this.map.setPaintProperty("indoor-bordered-places", "line-color", color);
|
|
2512
|
-
this.map.setPaintProperty("indoor-bordered-places", "line-width", width);
|
|
2513
|
-
}
|
|
2514
|
-
}
|
|
2515
|
-
catch (error) {
|
|
2516
|
-
throw new Error("Failed to add border to places", { cause: error });
|
|
2517
|
-
}
|
|
2518
|
-
}
|
|
2519
|
-
createBorderedPlacesLayer() {
|
|
2520
|
-
const layer = {
|
|
2521
|
-
id: "indoor-bordered-places",
|
|
2522
|
-
type: "line",
|
|
2523
|
-
source: "indoorequal",
|
|
2524
|
-
"source-layer": "area",
|
|
2525
|
-
filter: ["all", ["has", "ref"], ["==", ["get", "ref"], "unselected"]],
|
|
2526
|
-
paint: {
|
|
2527
|
-
"line-color": circle_1.MAPVX_BRAND_COLOR,
|
|
2528
|
-
"line-width": 4,
|
|
2529
|
-
},
|
|
2530
|
-
};
|
|
2531
|
-
this.map.addLayer(layer);
|
|
2532
|
-
}
|
|
2533
|
-
clearBorderedPlaces() {
|
|
2534
|
-
this.borderedPlacesRequest = undefined;
|
|
2535
|
-
if (this.hasLayer("indoor-bordered-places")) {
|
|
2536
|
-
const condition = ["==", ["get", "ref"], "unselected"];
|
|
2537
|
-
// Caller-defined base filter: mark it resolved so no later capture can
|
|
2538
|
-
// overwrite this customization with the layer's style-declared filter.
|
|
2539
|
-
this.capturedBaseFilters.add("indoor-bordered-places");
|
|
2540
|
-
this.baseFilters["indoor-bordered-places"] = condition;
|
|
2541
|
-
this.map.setFilter("indoor-bordered-places", condition);
|
|
2542
|
-
}
|
|
2543
|
-
}
|
|
2544
|
-
onWebGLContextLost(e) {
|
|
2545
|
-
var _a, _b;
|
|
2546
|
-
e.originalEvent.preventDefault();
|
|
2547
|
-
console.warn("Contexto WebGL perdido 😢");
|
|
2548
|
-
// Intentar restaurar el contexto automáticamente
|
|
2549
|
-
const canvas = (_a = this === null || this === void 0 ? void 0 : this.map) === null || _a === void 0 ? void 0 : _a.getCanvas();
|
|
2550
|
-
const gl = (canvas === null || canvas === void 0 ? void 0 : canvas.getContext("webgl")) || (canvas === null || canvas === void 0 ? void 0 : canvas.getContext("webgl2"));
|
|
2551
|
-
if (gl && gl.isContextLost()) {
|
|
2552
|
-
canvas.addEventListener("webglcontextrestored", () => {
|
|
2553
|
-
console.debug("Contexto WebGL restaurado, forzando re-render");
|
|
2554
|
-
this.map.triggerRepaint(); // forzar repaint
|
|
2555
|
-
this.failedTiles.clear();
|
|
2556
|
-
}, { once: true });
|
|
2557
|
-
const ext = gl.getExtension("WEBGL_lose_context");
|
|
2558
|
-
(_b = ext === null || ext === void 0 ? void 0 : ext.restoreContext) === null || _b === void 0 ? void 0 : _b.call(ext); // si aplica
|
|
2559
|
-
}
|
|
2560
|
-
}
|
|
2561
|
-
onWebGLContextRestored() {
|
|
2562
|
-
console.debug("Evento webglcontextrestored recibido, haciendo map.redraw()");
|
|
2563
|
-
this.map.redraw(); // fuerza un redraw síncrono [oai_citation:0‡maplibre.org](https://www.maplibre.org/maplibre-gl-js/docs/API/classes/Map/?utm_source=chatgpt.com)
|
|
2564
|
-
this.failedTiles.clear();
|
|
2565
|
-
}
|
|
2566
|
-
onSourceData(e) {
|
|
2567
|
-
var _a;
|
|
2568
|
-
// Only track tiles that actually failed to load (not aborted or never loaded)
|
|
2569
|
-
if (e.source.type === "vector" && e.sourceId === "indoorequal" && e.tile && !e.isSourceLoaded) {
|
|
2570
|
-
// Check if tile has an error state before adding to retry queue
|
|
2571
|
-
// Access state property safely as it may not always be available
|
|
2572
|
-
const tileState = (_a = e.tile) === null || _a === void 0 ? void 0 : _a.state;
|
|
2573
|
-
if (tileState === "errored" || tileState === "expired") {
|
|
2574
|
-
this.failedTiles.add(e.tile);
|
|
2575
|
-
}
|
|
2576
|
-
}
|
|
2577
|
-
}
|
|
2578
|
-
onSourceDataAbort(e) {
|
|
2579
|
-
// Remove aborted tiles from retry queue as they shouldn't be retried
|
|
2580
|
-
if (e.source.type === "vector" && e.sourceId === "indoorequal" && e.tile) {
|
|
2581
|
-
this.failedTiles.delete(e.tile);
|
|
2582
|
-
}
|
|
2583
|
-
}
|
|
2584
|
-
/**
|
|
2585
|
-
* True when a style object is attached to the map. During a full `setStyle` swap the
|
|
2586
|
-
* map is momentarily style-less and any `getSource`/`getFilter` call throws
|
|
2587
|
-
* ("Cannot read properties of undefined"), so call sites that only need the style to
|
|
2588
|
-
* exist should gate on this rather than on `isStyleLoaded()`.
|
|
2589
|
-
*/
|
|
2590
|
-
/**
|
|
2591
|
-
* This style's layers, or `null` when the map has no style right now.
|
|
2592
|
-
*
|
|
2593
|
-
* The one place the style is read for a floor change, so callers can pass the
|
|
2594
|
-
* result on instead of each probing again — `getStyle()` serializes every
|
|
2595
|
-
* source and layer, and `Style._updateLayer` drops the cache behind it on
|
|
2596
|
-
* every filter or layout write.
|
|
2597
|
-
*/
|
|
2598
|
-
safeStyleLayers() {
|
|
2599
|
-
var _a, _b, _c;
|
|
2600
|
-
try {
|
|
2601
|
-
return (_c = (_b = (_a = this.map) === null || _a === void 0 ? void 0 : _a.getStyle()) === null || _b === void 0 ? void 0 : _b.layers) !== null && _c !== void 0 ? _c : null;
|
|
2602
|
-
}
|
|
2603
|
-
catch (_d) {
|
|
2604
|
-
return null;
|
|
2605
|
-
}
|
|
2606
|
-
}
|
|
2607
|
-
hasStyle() {
|
|
2608
|
-
var _a;
|
|
2609
|
-
// `map.getStyle()` is the PUBLIC probe: it returns the serialized style, or throws
|
|
2610
|
-
// while the map is momentarily style-less mid-swap. Reading maplibre's internal
|
|
2611
|
-
// `style` field instead (it is absent from the public typings, so it needed a double
|
|
2612
|
-
// cast) would fail closed and silently forever the moment a maplibre upgrade renames
|
|
2613
|
-
// or privatizes it — disabling both the tile-retry loop and `setStyle`'s apply pass
|
|
2614
|
-
// with no throw and no log. `safeGetFilter` already uses this try/catch shape.
|
|
2615
|
-
try {
|
|
2616
|
-
return ((_a = this.map) === null || _a === void 0 ? void 0 : _a.getStyle()) != null;
|
|
2617
|
-
}
|
|
2618
|
-
catch (_b) {
|
|
2619
|
-
return false;
|
|
2620
|
-
}
|
|
2621
|
-
}
|
|
2622
|
-
/**
|
|
2623
|
-
* True when the map has a style object attached and fully loaded. Unlike
|
|
2624
|
-
* `map.isStyleLoaded()`, this does not emit maplibre's "There is no style
|
|
2625
|
-
* added to the map" warning when the style is momentarily absent (e.g. during
|
|
2626
|
-
* a `setStyle` swap), because it checks for the style object first.
|
|
2627
|
-
*/
|
|
2628
|
-
hasLoadedStyle() {
|
|
2629
|
-
if (!this.hasStyle())
|
|
2630
|
-
return false;
|
|
2631
|
-
return this.map.isStyleLoaded() === true;
|
|
2632
|
-
}
|
|
2633
|
-
subscribeToFailedTiles() {
|
|
2634
|
-
if (this.retryTimer)
|
|
2635
|
-
return;
|
|
2636
|
-
// Store bound functions for proper cleanup
|
|
2637
|
-
this.boundOnSourceData = this.onSourceData.bind(this);
|
|
2638
|
-
this.boundOnSourceDataAbort = this.onSourceDataAbort.bind(this);
|
|
2639
|
-
this.boundOnWebGLContextLost = this.onWebGLContextLost.bind(this);
|
|
2640
|
-
this.boundOnWebGLContextRestored = this.onWebGLContextRestored.bind(this);
|
|
2641
|
-
this.retryTimer = setInterval(() => {
|
|
2642
|
-
// During a full style swap (`setStyle` with `diff: false`) the map can
|
|
2643
|
-
// momentarily have no style object attached. Calling `getSource` then
|
|
2644
|
-
// throws ("Cannot read properties of undefined (reading 'getSource')").
|
|
2645
|
-
// Only the style's PRESENCE is checked here, never `isStyleLoaded()`:
|
|
2646
|
-
// that is false while any source still has tiles in flight, which is
|
|
2647
|
-
// exactly when tiles fail — gating on it would skip every tick during a
|
|
2648
|
-
// bad-network burst. The queue is left untouched so the tiles queued
|
|
2649
|
-
// mid-swap are still retried on the next tick.
|
|
2650
|
-
if (!this.hasStyle()) {
|
|
2651
|
-
return;
|
|
2652
|
-
}
|
|
2653
|
-
if (!this.map.getSource("indoorequal")) {
|
|
2654
|
-
this.failedTiles.clear();
|
|
2655
|
-
return;
|
|
2656
|
-
}
|
|
2657
|
-
const source = this.map.getSource("indoorequal");
|
|
2658
|
-
if (!source || source.type !== "vector") {
|
|
2659
|
-
this.failedTiles.clear();
|
|
2660
|
-
return;
|
|
2661
|
-
}
|
|
2662
|
-
// Process tiles and remove invalid ones
|
|
2663
|
-
const tilesToRetry = [];
|
|
2664
|
-
const tilesToRemove = [];
|
|
2665
|
-
this.failedTiles.forEach((tile) => {
|
|
2666
|
-
// Check if tile is still valid before attempting reload
|
|
2667
|
-
if (!tile || tile.state === "unloaded" || tile.state === "loading") {
|
|
2668
|
-
tilesToRemove.push(tile);
|
|
2669
|
-
return;
|
|
2670
|
-
}
|
|
2671
|
-
// Only retry tiles that are in an error state
|
|
2672
|
-
if (tile.state === "errored" || tile.state === "expired") {
|
|
2673
|
-
tilesToRetry.push(tile);
|
|
2674
|
-
}
|
|
2675
|
-
else {
|
|
2676
|
-
// Tile is in a valid state, remove from retry queue
|
|
2677
|
-
tilesToRemove.push(tile);
|
|
2678
|
-
}
|
|
2679
|
-
});
|
|
2680
|
-
// Remove invalid tiles from the set
|
|
2681
|
-
tilesToRemove.forEach((tile) => this.failedTiles.delete(tile));
|
|
2682
|
-
// Attempt to reload valid failed tiles
|
|
2683
|
-
const now = Date.now();
|
|
2684
|
-
tilesToRetry.forEach((tile) => {
|
|
2685
|
-
var _a;
|
|
2686
|
-
const retry = (_a = this.tileRetryState.get(tile)) !== null && _a !== void 0 ? _a : {
|
|
2687
|
-
attempts: 0,
|
|
2688
|
-
nextAttemptAt: 0,
|
|
2689
|
-
pending: false,
|
|
2690
|
-
};
|
|
2691
|
-
// Skip tiles with a reload still in flight or still inside their backoff window.
|
|
2692
|
-
if (retry.pending || now < retry.nextAttemptAt)
|
|
2693
|
-
return;
|
|
2694
|
-
const onFailure = (error) => {
|
|
2695
|
-
const attempts = retry.attempts + 1;
|
|
2696
|
-
if (attempts >= MAX_TILE_RETRIES) {
|
|
2697
|
-
this.debugLog(`Tile reload failed ${attempts} times, dropping it from the retry queue`, error);
|
|
2698
|
-
this.failedTiles.delete(tile);
|
|
2699
|
-
this.tileRetryState.delete(tile);
|
|
2700
|
-
return;
|
|
2701
|
-
}
|
|
2702
|
-
// Stay in the queue and try again once the backoff has elapsed.
|
|
2703
|
-
const backoffMs = TILE_RETRY_BASE_DELAY_MS * 2 ** (attempts - 1);
|
|
2704
|
-
this.debugLog(`Tile reload failed (attempt ${attempts}/${MAX_TILE_RETRIES}), retrying in ${backoffMs}ms`, error);
|
|
2705
|
-
this.tileRetryState.set(tile, {
|
|
2706
|
-
attempts,
|
|
2707
|
-
nextAttemptAt: Date.now() + backoffMs,
|
|
2708
|
-
pending: false,
|
|
2709
|
-
});
|
|
2710
|
-
};
|
|
2711
|
-
try {
|
|
2712
|
-
// Verify tile still exists and is in a retryable state
|
|
2713
|
-
if (tile && (tile.state === "errored" || tile.state === "expired")) {
|
|
2714
|
-
this.tileRetryState.set(tile, Object.assign(Object.assign({}, retry), { pending: true }));
|
|
2715
|
-
// loadTile is async, so a rejection escapes this try/catch and has to be handled
|
|
2716
|
-
// on the promise itself - otherwise it surfaces as an unhandled rejection and the
|
|
2717
|
-
// tile is retried on every tick regardless of how often it has already failed.
|
|
2718
|
-
source
|
|
2719
|
-
.loadTile(tile)
|
|
2720
|
-
.then(() => {
|
|
2721
|
-
// Loaded: forget the attempt history so a later failure starts from zero. The
|
|
2722
|
-
// tile leaves the queue through the tilesToRemove branch on the next tick,
|
|
2723
|
-
// once its state is no longer errored/expired.
|
|
2724
|
-
this.tileRetryState.delete(tile);
|
|
2725
|
-
})
|
|
2726
|
-
.catch(onFailure);
|
|
2727
|
-
}
|
|
2728
|
-
}
|
|
2729
|
-
catch (error) {
|
|
2730
|
-
onFailure(error);
|
|
2731
|
-
}
|
|
2732
|
-
});
|
|
2733
|
-
}, 1000);
|
|
2734
|
-
this.map.on("sourcedata", this.boundOnSourceData);
|
|
2735
|
-
this.map.on("sourcedataabort", this.boundOnSourceDataAbort);
|
|
2736
|
-
// Escucha el evento de contexto perdido
|
|
2737
|
-
this.map.on("webglcontextlost", this.boundOnWebGLContextLost);
|
|
2738
|
-
// Adicional: escucha evento restauración
|
|
2739
|
-
this.map.on("webglcontextrestored", this.boundOnWebGLContextRestored);
|
|
2740
|
-
}
|
|
2741
|
-
unsubscribeFromFailedTiles() {
|
|
2742
|
-
if (this.retryTimer) {
|
|
2743
|
-
clearInterval(this.retryTimer);
|
|
2744
|
-
this.retryTimer = undefined;
|
|
2745
|
-
}
|
|
2746
|
-
if (this.boundOnSourceData) {
|
|
2747
|
-
this.map.off("sourcedata", this.boundOnSourceData);
|
|
2748
|
-
this.boundOnSourceData = undefined;
|
|
2749
|
-
}
|
|
2750
|
-
if (this.boundOnSourceDataAbort) {
|
|
2751
|
-
this.map.off("sourcedataabort", this.boundOnSourceDataAbort);
|
|
2752
|
-
this.boundOnSourceDataAbort = undefined;
|
|
2753
|
-
}
|
|
2754
|
-
if (this.boundOnWebGLContextLost) {
|
|
2755
|
-
this.map.off("webglcontextlost", this.boundOnWebGLContextLost);
|
|
2756
|
-
this.boundOnWebGLContextLost = undefined;
|
|
2757
|
-
}
|
|
2758
|
-
if (this.boundOnWebGLContextRestored) {
|
|
2759
|
-
this.map.off("webglcontextrestored", this.boundOnWebGLContextRestored);
|
|
2760
|
-
this.boundOnWebGLContextRestored = undefined;
|
|
2761
|
-
}
|
|
2762
|
-
this.failedTiles.clear();
|
|
2763
|
-
}
|
|
2764
|
-
}
|
|
2765
|
-
exports.InternalMapVXMap = InternalMapVXMap;
|
|
2766
|
-
//# sourceMappingURL=map.js.map
|