@mapvx/web-js 1.2.1 → 1.3.0-dev.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/cjs/controllers/routeController.js +19 -19
- package/dist/cjs/controllers/routeController.js.map +1 -1
- package/dist/cjs/domain/models/categories.js +23 -10
- package/dist/cjs/domain/models/categories.js.map +1 -1
- package/dist/cjs/domain/models/mapConfig.js +10 -1
- package/dist/cjs/domain/models/mapConfig.js.map +1 -1
- package/dist/cjs/domain/models/marker.js +86 -80
- package/dist/cjs/domain/models/marker.js.map +1 -1
- package/dist/cjs/domain/models/routeConfiguration.js +3 -1
- package/dist/cjs/domain/models/routeConfiguration.js.map +1 -1
- package/dist/cjs/index.js +17 -12
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/logger/logger.js +13 -8
- package/dist/cjs/logger/logger.js.map +1 -1
- package/dist/cjs/logger/rollbar.js +11 -6
- package/dist/cjs/logger/rollbar.js.map +1 -1
- package/dist/cjs/map/map.js +549 -28
- package/dist/cjs/map/map.js.map +1 -1
- package/dist/cjs/map/mapInteractionOptions.js +56 -0
- package/dist/cjs/map/mapInteractionOptions.js.map +1 -0
- package/dist/cjs/repository/repository.js +25 -26
- package/dist/cjs/repository/repository.js.map +1 -1
- package/dist/cjs/repository/requester.js +71 -91
- package/dist/cjs/repository/requester.js.map +1 -1
- package/dist/cjs/sdk.js +18 -0
- package/dist/cjs/sdk.js.map +1 -1
- package/dist/cjs/utils/3d.js +12 -0
- package/dist/cjs/utils/3d.js.map +1 -0
- package/dist/cjs/utils/semaphore.js +143 -0
- package/dist/cjs/utils/semaphore.js.map +1 -0
- package/dist/es/controllers/routeController.d.ts.map +1 -1
- package/dist/es/controllers/routeController.js +19 -19
- package/dist/es/controllers/routeController.js.map +1 -1
- package/dist/es/domain/models/categories.d.ts +34 -10
- package/dist/es/domain/models/categories.d.ts.map +1 -1
- package/dist/es/domain/models/categories.js +21 -9
- package/dist/es/domain/models/categories.js.map +1 -1
- package/dist/es/domain/models/configuration.d.ts +8 -0
- package/dist/es/domain/models/configuration.d.ts.map +1 -1
- package/dist/es/domain/models/mapConfig.d.ts +129 -3
- package/dist/es/domain/models/mapConfig.d.ts.map +1 -1
- package/dist/es/domain/models/mapConfig.js +9 -0
- package/dist/es/domain/models/mapConfig.js.map +1 -1
- package/dist/es/domain/models/marker.d.ts +8 -0
- package/dist/es/domain/models/marker.d.ts.map +1 -1
- package/dist/es/domain/models/marker.js +86 -80
- package/dist/es/domain/models/marker.js.map +1 -1
- package/dist/es/domain/models/routeConfiguration.d.ts +47 -0
- package/dist/es/domain/models/routeConfiguration.d.ts.map +1 -1
- package/dist/es/domain/models/routeConfiguration.js +3 -1
- package/dist/es/domain/models/routeConfiguration.js.map +1 -1
- package/dist/es/index.d.ts +13 -12
- package/dist/es/index.d.ts.map +1 -1
- package/dist/es/index.js +8 -6
- package/dist/es/index.js.map +1 -1
- package/dist/es/logger/logger.d.ts.map +1 -1
- package/dist/es/logger/logger.js +13 -8
- package/dist/es/logger/logger.js.map +1 -1
- package/dist/es/logger/rollbar.d.ts.map +1 -1
- package/dist/es/logger/rollbar.js +11 -6
- package/dist/es/logger/rollbar.js.map +1 -1
- package/dist/es/map/map.d.ts +106 -1
- package/dist/es/map/map.d.ts.map +1 -1
- package/dist/es/map/map.js +551 -30
- package/dist/es/map/map.js.map +1 -1
- package/dist/es/map/mapInteractionOptions.d.ts +37 -0
- package/dist/es/map/mapInteractionOptions.d.ts.map +1 -0
- package/dist/es/map/mapInteractionOptions.js +51 -0
- package/dist/es/map/mapInteractionOptions.js.map +1 -0
- package/dist/es/repository/repository.d.ts +0 -1
- package/dist/es/repository/repository.d.ts.map +1 -1
- package/dist/es/repository/repository.js +25 -26
- package/dist/es/repository/repository.js.map +1 -1
- package/dist/es/repository/requester.d.ts +12 -2
- package/dist/es/repository/requester.d.ts.map +1 -1
- package/dist/es/repository/requester.js +71 -91
- package/dist/es/repository/requester.js.map +1 -1
- package/dist/es/sdk.d.ts +2 -0
- package/dist/es/sdk.d.ts.map +1 -1
- package/dist/es/sdk.js +18 -0
- package/dist/es/sdk.js.map +1 -1
- package/dist/es/utils/3d.d.ts +2 -0
- package/dist/es/utils/3d.d.ts.map +1 -0
- package/dist/es/utils/3d.js +8 -0
- package/dist/es/utils/3d.js.map +1 -0
- package/dist/es/utils/semaphore.d.ts +70 -0
- package/dist/es/utils/semaphore.d.ts.map +1 -0
- package/dist/es/utils/semaphore.js +139 -0
- package/dist/es/utils/semaphore.js.map +1 -0
- package/dist/umd/index.js +154429 -2148
- package/dist/umd/index.js.map +1 -1
- package/dist/umd/styles.css +32 -14
- package/dist/umd/styles.css.map +1 -1
- package/package.json +19 -5
package/dist/es/map/map.js
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { MVTLayer } from "@deck.gl/geo-layers";
|
|
2
|
+
import { IconLayer, PathLayer, TextLayer } from "@deck.gl/layers";
|
|
3
|
+
import maplibregl, { LngLatBounds, Map, MercatorCoordinate, NavigationControl, setRTLTextPlugin, } from "maplibre-gl";
|
|
4
|
+
import * as THREE from "three";
|
|
5
|
+
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
|
|
6
|
+
import { Semaphore } from "../utils/semaphore";
|
|
2
7
|
/** Shared GeoJSON source holding every circle drawn through the circle API. */
|
|
3
8
|
const CIRCLE_SOURCE_ID = "mapvx-circles";
|
|
4
9
|
/** Fill layer rendering the translucent interior of the circles. */
|
|
@@ -43,21 +48,36 @@ function deepClone(obj) {
|
|
|
43
48
|
/**
|
|
44
49
|
* Register a custom protocol for cached tiles that routes requests through the main thread.
|
|
45
50
|
* This allows the service worker to intercept and cache tile requests.
|
|
51
|
+
*
|
|
52
|
+
* Wraps fetches in a {@link Semaphore} so tile CDN / WAFs are not hit with
|
|
53
|
+
* unbounded parallel requests (same host as the service worker cache). The
|
|
54
|
+
* semaphore is created once with the limit from the first map's config; later
|
|
55
|
+
* calls are no-ops because the protocol can only be registered once globally
|
|
56
|
+
* on MapLibre.
|
|
57
|
+
*
|
|
58
|
+
* @param maxConcurrentFetches - Maximum number of in-flight tile fetches.
|
|
46
59
|
*/
|
|
47
|
-
function registerCachedTileProtocol() {
|
|
60
|
+
function registerCachedTileProtocol(maxConcurrentFetches) {
|
|
48
61
|
if (cachedTileProtocolRegistered)
|
|
49
62
|
return;
|
|
63
|
+
const semaphore = new Semaphore(maxConcurrentFetches);
|
|
50
64
|
maplibregl.addProtocol("cached-tile", (params, abortController) => {
|
|
51
|
-
// Convert cached-tile:// URL back to https://
|
|
52
65
|
const url = params.url.replace("cached-tile://", "https://");
|
|
53
|
-
|
|
66
|
+
// Pass the abort signal to acquire() so a request cancelled while still
|
|
67
|
+
// queued (e.g. during rapid zoom) drops out of the FIFO queue instead of
|
|
68
|
+
// waiting for a slot just to bail. release() runs only inside this chain,
|
|
69
|
+
// i.e. only after a slot was actually granted.
|
|
70
|
+
return semaphore.acquire(abortController.signal).then(() => fetch(url, { signal: abortController.signal })
|
|
54
71
|
.then((response) => {
|
|
55
72
|
if (!response.ok) {
|
|
56
73
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
57
74
|
}
|
|
58
75
|
return response.arrayBuffer();
|
|
59
76
|
})
|
|
60
|
-
.then((data) => ({ data }))
|
|
77
|
+
.then((data) => ({ data }))
|
|
78
|
+
.finally(() => {
|
|
79
|
+
semaphore.release();
|
|
80
|
+
}));
|
|
61
81
|
});
|
|
62
82
|
cachedTileProtocolRegistered = true;
|
|
63
83
|
}
|
|
@@ -111,19 +131,21 @@ function convertPaddingToPixels(padding, containerWidth, containerHeight) {
|
|
|
111
131
|
right: parsePaddingValue(padding.right, containerWidth),
|
|
112
132
|
};
|
|
113
133
|
}
|
|
134
|
+
import { MapboxOverlay } from "@deck.gl/mapbox";
|
|
114
135
|
import { userLocationDataUrl } from "../assets/icons";
|
|
115
136
|
import { RouteController } from "../controllers/routeController";
|
|
116
137
|
import { rtlLanguages } from "../domain/models/_rtl";
|
|
117
138
|
import { InternalAnimationConfig, InternalAnimationDrawingConfig, } from "../domain/models/animation";
|
|
139
|
+
import { CIRCLE_RENDER_ORDERS, circleRing, cloneCircleRecord, MAPVX_BRAND_COLOR, resolveCircleConfig, } from "../domain/models/circle";
|
|
118
140
|
import { Loggeable } from "../domain/models/loggeable";
|
|
119
|
-
import { DEFAULT_TILE_CACHE_CONFIG, } from "../domain/models/mapConfig";
|
|
120
|
-
import { circleRing, cloneCircleRecord, MAPVX_BRAND_COLOR, resolveCircleConfig, } from "../domain/models/circle";
|
|
141
|
+
import { DEFAULT_TILE_CACHE_CONFIG, MAPLIBRE_MAX_TILE_CACHE_HARD_CAP, } from "../domain/models/mapConfig";
|
|
121
142
|
import { MarkerAttribute } from "../domain/models/marker";
|
|
122
143
|
import { MVXRoute } from "../domain/models/route";
|
|
123
144
|
import { InternalDrawRouteConfiguration, InternalGetRouteConfiguration, } from "../domain/models/routeConfiguration";
|
|
124
145
|
import { Repository } from "../repository/repository";
|
|
125
|
-
import { getBoundingBox } from "../utils/utils";
|
|
126
146
|
import { extractStepCoordinates } from "../utils/route-utils";
|
|
147
|
+
import { getBoundingBox } from "../utils/utils";
|
|
148
|
+
import { buildInteractionOptions, shouldDisableTouchRotation } from "./mapInteractionOptions";
|
|
127
149
|
/**
|
|
128
150
|
* Class to interact with the map.
|
|
129
151
|
* @category Map
|
|
@@ -139,7 +161,7 @@ export class InternalMapVXMap extends Loggeable {
|
|
|
139
161
|
* @returns A new instance of MapVXMap.
|
|
140
162
|
*/
|
|
141
163
|
constructor(mapConfig, container, token) {
|
|
142
|
-
var _a, _b;
|
|
164
|
+
var _a, _b, _c;
|
|
143
165
|
super();
|
|
144
166
|
this.potentialParentPlaces = [];
|
|
145
167
|
this.innerFloors = [];
|
|
@@ -152,6 +174,22 @@ export class InternalMapVXMap extends Loggeable {
|
|
|
152
174
|
this.hoveredId = "unselected";
|
|
153
175
|
this.failedTiles = new Set();
|
|
154
176
|
this.geoLocation = navigator.geolocation;
|
|
177
|
+
// 3d related variables
|
|
178
|
+
this.mode = "2D";
|
|
179
|
+
this.deckOverlay = undefined;
|
|
180
|
+
this.escalatorScene = undefined;
|
|
181
|
+
this.escalatorModelTemplate = undefined;
|
|
182
|
+
this.escalatorCenter = undefined;
|
|
183
|
+
this.escalatorCache = {};
|
|
184
|
+
this.ESCALATOR_MODEL_URL = "https://mapvx-glb-assets.s3.us-east-1.amazonaws.com/shared/escalator.glb";
|
|
185
|
+
/**
|
|
186
|
+
* Number of lights to use for the escalators
|
|
187
|
+
*
|
|
188
|
+
*/
|
|
189
|
+
this.ESCALATOR_LIGHTS = 3;
|
|
190
|
+
this.SPRITE_URL = "https://lazarillo.app/internal/maps/vector-style/cenco-cl-pe-co-ar-3D/sprites_cencosud";
|
|
191
|
+
this.spriteIconMapping = {};
|
|
192
|
+
this.spriteAtlasImage = new Image();
|
|
155
193
|
if (rtlLanguages.includes((_a = mapConfig.lang) !== null && _a !== void 0 ? _a : "")) {
|
|
156
194
|
this.setRTLSupport();
|
|
157
195
|
}
|
|
@@ -163,8 +201,16 @@ export class InternalMapVXMap extends Loggeable {
|
|
|
163
201
|
this.watchPositionID = undefined;
|
|
164
202
|
this.onFloorChange = mapConfig.onFloorChange;
|
|
165
203
|
this.onParentPlaceChange = mapConfig.onParentPlaceChange;
|
|
166
|
-
|
|
167
|
-
this.
|
|
204
|
+
this.mode = (_c = mapConfig.mode) !== null && _c !== void 0 ? _c : "2D";
|
|
205
|
+
this.initialCenter = mapConfig.center;
|
|
206
|
+
this.tileCacheConfig = (() => {
|
|
207
|
+
const merged = Object.assign(Object.assign({}, DEFAULT_TILE_CACHE_CONFIG), mapConfig.tileCache);
|
|
208
|
+
merged.maxTiles = Math.min(merged.maxTiles, MAPLIBRE_MAX_TILE_CACHE_HARD_CAP);
|
|
209
|
+
return merged;
|
|
210
|
+
})();
|
|
211
|
+
if (this.mode === "3D") {
|
|
212
|
+
this.loadSpriteAtlas().catch(console.error);
|
|
213
|
+
}
|
|
168
214
|
if (mapConfig.parentPlaceId != null) {
|
|
169
215
|
this.initialPlaceDetailSetUp(mapConfig.parentPlaceId, mapConfig.authToken);
|
|
170
216
|
}
|
|
@@ -261,28 +307,17 @@ export class InternalMapVXMap extends Loggeable {
|
|
|
261
307
|
.catch(console.error);
|
|
262
308
|
}
|
|
263
309
|
onMapStyleLoaded(mapConfig, container, style) {
|
|
264
|
-
var _a, _b, _c;
|
|
310
|
+
var _a, _b, _c, _d, _e;
|
|
265
311
|
// Determine if service worker caching should be enabled
|
|
266
312
|
const useServiceWorkerCaching = this.tileCacheConfig.enabled && this.tileCacheConfig.persistToServiceWorker;
|
|
267
|
-
// Register cached-tile protocol only if service worker caching is enabled
|
|
268
313
|
if (useServiceWorkerCaching) {
|
|
269
|
-
registerCachedTileProtocol();
|
|
314
|
+
registerCachedTileProtocol(this.tileCacheConfig.maxConcurrentTileFetches);
|
|
270
315
|
}
|
|
271
316
|
// Transform tile URLs only if service worker caching is enabled
|
|
272
317
|
const finalStyle = useServiceWorkerCaching ? this.transformStyleForCaching(style) : style;
|
|
273
|
-
const mapOptions = {
|
|
274
|
-
container,
|
|
275
|
-
style: finalStyle,
|
|
276
|
-
center: mapConfig.center,
|
|
277
|
-
zoom: mapConfig.zoom,
|
|
278
|
-
pitch: (_a = mapConfig.pitch) !== null && _a !== void 0 ? _a : 0,
|
|
279
|
-
attributionControl: false,
|
|
280
|
-
maplibreLogo: false,
|
|
281
|
-
bearingSnap: (_b = mapConfig.bearingSnap) !== null && _b !== void 0 ? _b : 0,
|
|
282
|
-
cancelPendingTileRequestsWhileZooming: false,
|
|
318
|
+
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,
|
|
283
319
|
// Use configured maxTiles for MapLibre's memory cache
|
|
284
|
-
maxTileCacheSize: this.tileCacheConfig.maxTiles,
|
|
285
|
-
};
|
|
320
|
+
maxTileCacheSize: this.tileCacheConfig.maxTiles }, buildInteractionOptions(mapConfig));
|
|
286
321
|
if (mapConfig.maxZoom)
|
|
287
322
|
mapOptions.maxZoom = mapConfig.maxZoom;
|
|
288
323
|
if (mapConfig.minZoom)
|
|
@@ -292,12 +327,41 @@ export class InternalMapVXMap extends Loggeable {
|
|
|
292
327
|
mapOptions.maxBounds = new LngLatBounds([boundingBox[0].lng, boundingBox[0].lat], [boundingBox[1].lng, boundingBox[1].lat]);
|
|
293
328
|
}
|
|
294
329
|
this.map = new Map(mapOptions);
|
|
330
|
+
// When rotation is disabled we still want pinch-to-zoom to work, so the
|
|
331
|
+
// two-finger rotation is turned off here instead of via the constructor.
|
|
332
|
+
if (shouldDisableTouchRotation(mapConfig)) {
|
|
333
|
+
(_d = (_c = this.map.touchZoomRotate) === null || _c === void 0 ? void 0 : _c.disableRotation) === null || _d === void 0 ? void 0 : _d.call(_c);
|
|
334
|
+
}
|
|
295
335
|
this.map.addControl(new NavigationControl({
|
|
296
336
|
showCompass: mapConfig.showCompass !== undefined ? mapConfig.showCompass : true,
|
|
297
337
|
showZoom: mapConfig.showZoom !== undefined ? mapConfig.showZoom : true,
|
|
298
|
-
}), (
|
|
338
|
+
}), (_e = mapConfig.navigationPosition) !== null && _e !== void 0 ? _e : "top-right");
|
|
299
339
|
this.map.on("load", () => {
|
|
300
340
|
var _a;
|
|
341
|
+
if (this.mode === "3D") {
|
|
342
|
+
this.deckOverlay = new MapboxOverlay({ layers: [] });
|
|
343
|
+
this.map.addControl(this.deckOverlay);
|
|
344
|
+
this.hideIndoorSymbolLayers();
|
|
345
|
+
// Hide MapLibre icon layers — replaced by deck.gl IconLayer
|
|
346
|
+
this.map.setLayoutProperty("indoor-poi-logo", "visibility", "none");
|
|
347
|
+
const style = this.map.getStyle();
|
|
348
|
+
for (const layer of style.layers) {
|
|
349
|
+
if (layer.id.startsWith("indoor-logo-tiendas-ancla")) {
|
|
350
|
+
this.map.setLayoutProperty(layer.id, "visibility", "none");
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
// Add 3D escalator layer and hide 2D transportation line
|
|
354
|
+
this.map.addLayer(this.createEscalatorLayer());
|
|
355
|
+
this.map.setPaintProperty("indoor-transportation", "line-opacity", 0);
|
|
356
|
+
// Capture escalator positions when new tiles arrive (stops once cached)
|
|
357
|
+
this.map.on("sourcedata", (e) => {
|
|
358
|
+
if (e.sourceId === "indoorequal" &&
|
|
359
|
+
this.currentFloor &&
|
|
360
|
+
!this.escalatorCache[this.currentFloor]) {
|
|
361
|
+
this.placeEscalators();
|
|
362
|
+
}
|
|
363
|
+
});
|
|
364
|
+
}
|
|
301
365
|
this.whenStyleUpdates(style);
|
|
302
366
|
if (mapConfig.lang)
|
|
303
367
|
this.setLayersForLanguage(mapConfig.lang);
|
|
@@ -320,7 +384,6 @@ export class InternalMapVXMap extends Loggeable {
|
|
|
320
384
|
(_a = mapConfig.onRotate) === null || _a === void 0 ? void 0 : _a.call(mapConfig, this.map.getBearing());
|
|
321
385
|
});
|
|
322
386
|
this.defaultClickListener();
|
|
323
|
-
return this.map;
|
|
324
387
|
}
|
|
325
388
|
setBaseFilters(style) {
|
|
326
389
|
if (!style)
|
|
@@ -449,6 +512,386 @@ export class InternalMapVXMap extends Loggeable {
|
|
|
449
512
|
// Tile cache clear may fail if cache doesn't exist
|
|
450
513
|
}
|
|
451
514
|
}
|
|
515
|
+
// Start 3D related methods ------------------------------------------------------------
|
|
516
|
+
hideIndoorSymbolLayers() {
|
|
517
|
+
var _a, _b;
|
|
518
|
+
const style = this.map.getStyle();
|
|
519
|
+
for (const layer of style.layers) {
|
|
520
|
+
if (layer.type === "symbol" && layer.id.startsWith("indoor-")) {
|
|
521
|
+
const hasText = !!((_a = layer.layout) === null || _a === void 0 ? void 0 : _a["text-field"]);
|
|
522
|
+
const hasIcon = !!((_b = layer.layout) === null || _b === void 0 ? void 0 : _b["icon-image"]);
|
|
523
|
+
// Hide text-only layers (replaced by deck.gl)
|
|
524
|
+
if (hasText && !hasIcon) {
|
|
525
|
+
this.map.setLayoutProperty(layer.id, "visibility", "none");
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
createEscalatorLayer() {
|
|
531
|
+
const refMercator = MercatorCoordinate.fromLngLat(this.initialCenter, 0);
|
|
532
|
+
const meterScale = refMercator.meterInMercatorCoordinateUnits();
|
|
533
|
+
const modelTransform = new THREE.Matrix4()
|
|
534
|
+
.makeTranslation(refMercator.x, refMercator.y, refMercator.z)
|
|
535
|
+
.scale(new THREE.Vector3(meterScale, -meterScale, meterScale))
|
|
536
|
+
.multiply(new THREE.Matrix4().makeRotationX(Math.PI / 2));
|
|
537
|
+
this.escalatorScene = new THREE.Scene();
|
|
538
|
+
const dirLight = new THREE.DirectionalLight(0xffffff, 2.0);
|
|
539
|
+
dirLight.position.set(50, 100, 80);
|
|
540
|
+
this.escalatorScene.add(dirLight);
|
|
541
|
+
const dirLight2 = new THREE.DirectionalLight(0xffffff, 1.5);
|
|
542
|
+
dirLight2.position.set(-50, 80, -60);
|
|
543
|
+
this.escalatorScene.add(dirLight2);
|
|
544
|
+
this.escalatorScene.add(new THREE.AmbientLight(0xffffff, 1.5));
|
|
545
|
+
new GLTFLoader().load(this.ESCALATOR_MODEL_URL, (gltf) => {
|
|
546
|
+
this.escalatorModelTemplate = gltf.scene;
|
|
547
|
+
const box = new THREE.Box3().setFromObject(this.escalatorModelTemplate);
|
|
548
|
+
this.escalatorCenter = box.getCenter(new THREE.Vector3());
|
|
549
|
+
this.escalatorCenter.y = box.min.y; // align bottom to ground
|
|
550
|
+
this.placeEscalators();
|
|
551
|
+
});
|
|
552
|
+
let renderer = undefined;
|
|
553
|
+
let camera = undefined;
|
|
554
|
+
const tmpMatrix = new THREE.Matrix4();
|
|
555
|
+
return {
|
|
556
|
+
id: "3d-escalators",
|
|
557
|
+
type: "custom",
|
|
558
|
+
renderingMode: "3d",
|
|
559
|
+
onAdd(_map, gl) {
|
|
560
|
+
camera = new THREE.Camera();
|
|
561
|
+
renderer = new THREE.WebGLRenderer({
|
|
562
|
+
canvas: _map.getCanvas(),
|
|
563
|
+
context: gl,
|
|
564
|
+
antialias: true,
|
|
565
|
+
});
|
|
566
|
+
renderer.autoClear = false;
|
|
567
|
+
},
|
|
568
|
+
render: (gl, args) => {
|
|
569
|
+
var _a, _b;
|
|
570
|
+
if (!camera ||
|
|
571
|
+
!renderer ||
|
|
572
|
+
!this.escalatorModelTemplate ||
|
|
573
|
+
!this.escalatorScene ||
|
|
574
|
+
this.escalatorScene.children.length <= this.ESCALATOR_LIGHTS)
|
|
575
|
+
return;
|
|
576
|
+
const matrixData = (_b = (_a = args === null || args === void 0 ? void 0 : args.defaultProjectionData) === null || _a === void 0 ? void 0 : _a.mainMatrix) !== null && _b !== void 0 ? _b : args;
|
|
577
|
+
tmpMatrix.fromArray(matrixData).multiply(modelTransform);
|
|
578
|
+
camera.projectionMatrix.copy(tmpMatrix);
|
|
579
|
+
renderer.resetState();
|
|
580
|
+
renderer.render(this.escalatorScene, camera);
|
|
581
|
+
},
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
placeEscalators() {
|
|
585
|
+
if (!this.escalatorModelTemplate ||
|
|
586
|
+
!this.escalatorScene ||
|
|
587
|
+
!this.currentFloor ||
|
|
588
|
+
!this.escalatorCenter)
|
|
589
|
+
return;
|
|
590
|
+
// Try to capture positions if not cached yet
|
|
591
|
+
this.captureEscalatorPositions();
|
|
592
|
+
const positions = this.escalatorCache[this.currentFloor];
|
|
593
|
+
if (!positions)
|
|
594
|
+
return;
|
|
595
|
+
// Clear scene models (keep lights)
|
|
596
|
+
while (this.escalatorScene.children.length > this.ESCALATOR_LIGHTS) {
|
|
597
|
+
const lastChild = this.escalatorScene.children.at(-1);
|
|
598
|
+
if (lastChild) {
|
|
599
|
+
this.escalatorScene.remove(lastChild);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
const refMercator = MercatorCoordinate.fromLngLat(this.initialCenter, 0);
|
|
603
|
+
const meterScale = refMercator.meterInMercatorCoordinateUnits();
|
|
604
|
+
for (const pos of positions) {
|
|
605
|
+
const target = maplibregl.MercatorCoordinate.fromLngLat([pos.lng, pos.lat], 0);
|
|
606
|
+
const sx = (target.x - refMercator.x) / meterScale;
|
|
607
|
+
const sz = (target.y - refMercator.y) / meterScale;
|
|
608
|
+
const pivot = new THREE.Group();
|
|
609
|
+
pivot.position.set(sx, 0, sz);
|
|
610
|
+
pivot.rotation.y = Math.PI / 2 - pos.bearing;
|
|
611
|
+
pivot.scale.set(0.3, 0.3, 0.3);
|
|
612
|
+
const model = this.escalatorModelTemplate.clone();
|
|
613
|
+
model.position.set(-this.escalatorCenter.x, -this.escalatorCenter.y - 3, -this.escalatorCenter.z);
|
|
614
|
+
pivot.add(model);
|
|
615
|
+
this.escalatorScene.add(pivot);
|
|
616
|
+
}
|
|
617
|
+
this.map.triggerRepaint();
|
|
618
|
+
}
|
|
619
|
+
captureEscalatorPositions() {
|
|
620
|
+
if (this.currentFloor && this.escalatorCache[this.currentFloor])
|
|
621
|
+
return;
|
|
622
|
+
// queryRenderedFeatures uses exact rendered coordinates (not tile-approximated)
|
|
623
|
+
const features = this.map
|
|
624
|
+
.queryRenderedFeatures(undefined, {
|
|
625
|
+
layers: ["indoor-transportation"],
|
|
626
|
+
})
|
|
627
|
+
.filter((f) => { var _a; return ((_a = f.properties) === null || _a === void 0 ? void 0 : _a.floor_key) === this.currentFloor; });
|
|
628
|
+
if (!features.length)
|
|
629
|
+
return;
|
|
630
|
+
// Collect all midpoints first
|
|
631
|
+
const allMids = [];
|
|
632
|
+
for (const f of features) {
|
|
633
|
+
const coords = f.geometry.coordinates;
|
|
634
|
+
if (!coords || coords.length < 2)
|
|
635
|
+
continue;
|
|
636
|
+
const start = coords[0];
|
|
637
|
+
const end = coords[coords.length - 1];
|
|
638
|
+
const midLng = (start[0] + end[0]) / 2;
|
|
639
|
+
const midLat = (start[1] + end[1]) / 2;
|
|
640
|
+
const bearing = Math.atan2(end[0] - start[0], end[1] - start[1]);
|
|
641
|
+
allMids.push({ lng: midLng, lat: midLat, bearing });
|
|
642
|
+
}
|
|
643
|
+
// Merge points within ~15m of each other into one
|
|
644
|
+
const MERGE_DEG = 0.00015; // ~15m
|
|
645
|
+
const positions = [];
|
|
646
|
+
const used = new Set();
|
|
647
|
+
for (let i = 0; i < allMids.length; i++) {
|
|
648
|
+
if (used.has(i))
|
|
649
|
+
continue;
|
|
650
|
+
used.add(i);
|
|
651
|
+
const group = [allMids[i]];
|
|
652
|
+
for (let j = i + 1; j < allMids.length; j++) {
|
|
653
|
+
if (used.has(j))
|
|
654
|
+
continue;
|
|
655
|
+
const dLng = allMids[i].lng - allMids[j].lng;
|
|
656
|
+
const dLat = allMids[i].lat - allMids[j].lat;
|
|
657
|
+
if (Math.abs(dLng) < MERGE_DEG && Math.abs(dLat) < MERGE_DEG) {
|
|
658
|
+
used.add(j);
|
|
659
|
+
group.push(allMids[j]);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
// Use average position and first bearing
|
|
663
|
+
const avgLng = group.reduce((s, p) => s + p.lng, 0) / group.length;
|
|
664
|
+
const avgLat = group.reduce((s, p) => s + p.lat, 0) / group.length;
|
|
665
|
+
positions.push({ lng: avgLng, lat: avgLat, bearing: group[0].bearing });
|
|
666
|
+
}
|
|
667
|
+
if (positions.length && this.currentFloor) {
|
|
668
|
+
this.escalatorCache[this.currentFloor] = positions;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
async loadSpriteAtlas() {
|
|
672
|
+
const [json, img] = await Promise.all([
|
|
673
|
+
fetch(`${this.SPRITE_URL}.json`).then((r) => r.json()),
|
|
674
|
+
new Promise((resolve, reject) => {
|
|
675
|
+
const image = new Image();
|
|
676
|
+
image.crossOrigin = "anonymous";
|
|
677
|
+
image.onload = () => resolve(image);
|
|
678
|
+
image.onerror = reject;
|
|
679
|
+
image.src = `${this.SPRITE_URL}.png`;
|
|
680
|
+
}),
|
|
681
|
+
]);
|
|
682
|
+
this.spriteIconMapping = json;
|
|
683
|
+
this.spriteAtlasImage = img;
|
|
684
|
+
}
|
|
685
|
+
makeMVTLayer(floorKey) {
|
|
686
|
+
return new MVTLayer({
|
|
687
|
+
id: "indoor-lines-elevated",
|
|
688
|
+
data: "https://tiles.mapvx.com/",
|
|
689
|
+
binary: false,
|
|
690
|
+
renderSubLayers: (props) => {
|
|
691
|
+
var _a;
|
|
692
|
+
const roomData = (props.data || []);
|
|
693
|
+
const roomFeatures = roomData.filter((f) => {
|
|
694
|
+
var _a, _b, _c;
|
|
695
|
+
return ((_a = f.properties) === null || _a === void 0 ? void 0 : _a.class) === "room" &&
|
|
696
|
+
((_b = f.properties) === null || _b === void 0 ? void 0 : _b.subclass) !== "empty" &&
|
|
697
|
+
((_c = f.properties) === null || _c === void 0 ? void 0 : _c.floor_key) === floorKey;
|
|
698
|
+
});
|
|
699
|
+
if (!roomFeatures.length)
|
|
700
|
+
return null;
|
|
701
|
+
const paths = [];
|
|
702
|
+
for (const f of roomFeatures) {
|
|
703
|
+
const z = ((_a = f.properties) === null || _a === void 0 ? void 0 : _a.height) ? Number(f.properties.height) : 5;
|
|
704
|
+
const geom = f.geometry;
|
|
705
|
+
const polygons = geom.type === "Polygon"
|
|
706
|
+
? [geom.coordinates]
|
|
707
|
+
: geom.type === "MultiPolygon"
|
|
708
|
+
? geom.coordinates
|
|
709
|
+
: [];
|
|
710
|
+
for (const rings of polygons) {
|
|
711
|
+
for (const ring of rings) {
|
|
712
|
+
paths.push({ path: ring.map(([lon, lat]) => [lon, lat, z]) });
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
return new PathLayer(Object.assign(Object.assign({}, props), { id: `${props.id}-paths`, data: paths, getPath: (d) => d.path, getColor: [203, 203, 203], getWidth: 1.5, widthUnits: "pixels", pickable: false }));
|
|
717
|
+
},
|
|
718
|
+
updateTriggers: {
|
|
719
|
+
renderSubLayers: [floorKey],
|
|
720
|
+
},
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
makeLabelLayer(floorKey, useSdf = true) {
|
|
724
|
+
return new MVTLayer({
|
|
725
|
+
id: "indoor-labels",
|
|
726
|
+
data: "https://tiles.mapvx.com/",
|
|
727
|
+
binary: false,
|
|
728
|
+
minZoom: 19.5,
|
|
729
|
+
maxZoom: 24,
|
|
730
|
+
renderSubLayers: (props) => {
|
|
731
|
+
// Use area_name and poi Point features — unique per tile, no duplicates
|
|
732
|
+
// If logo-{name} image exists in sprite, icon has priority over text label
|
|
733
|
+
const labelData = (props.data || []);
|
|
734
|
+
const labelFeatures = labelData.filter((f) => {
|
|
735
|
+
var _a, _b, _c, _d, _e, _f;
|
|
736
|
+
if (((_a = f.properties) === null || _a === void 0 ? void 0 : _a.layerName) !== "area_name" && ((_b = f.properties) === null || _b === void 0 ? void 0 : _b.layerName) !== "poi")
|
|
737
|
+
return false;
|
|
738
|
+
if (((_c = f.geometry) === null || _c === void 0 ? void 0 : _c.type) !== "Point")
|
|
739
|
+
return false;
|
|
740
|
+
const name = ((_d = f.properties) === null || _d === void 0 ? void 0 : _d["name:latin"]) || ((_e = f.properties) === null || _e === void 0 ? void 0 : _e.name);
|
|
741
|
+
if (!name)
|
|
742
|
+
return false;
|
|
743
|
+
if (((_f = f.properties) === null || _f === void 0 ? void 0 : _f.floor_key) && f.properties.floor_key !== floorKey)
|
|
744
|
+
return false;
|
|
745
|
+
if (this.map.hasImage("logo-" + name))
|
|
746
|
+
return false; // icon has priority
|
|
747
|
+
return true;
|
|
748
|
+
});
|
|
749
|
+
if (!labelFeatures.length)
|
|
750
|
+
return null;
|
|
751
|
+
const textData = labelFeatures.map((f) => {
|
|
752
|
+
var _a, _b, _c;
|
|
753
|
+
return ({
|
|
754
|
+
position: [
|
|
755
|
+
...f.geometry.coordinates.slice(0, 2),
|
|
756
|
+
((_a = f.properties) === null || _a === void 0 ? void 0 : _a.height) ? Number(f.properties.height) : 5,
|
|
757
|
+
],
|
|
758
|
+
text: ((_b = f.properties) === null || _b === void 0 ? void 0 : _b["name:latin"]) || ((_c = f.properties) === null || _c === void 0 ? void 0 : _c.name) || "",
|
|
759
|
+
});
|
|
760
|
+
});
|
|
761
|
+
return new TextLayer(Object.assign(Object.assign({}, props), { id: `${props.id}-labels`, data: textData, getPosition: (d) => d.position, getText: (d) => d.text, getSize: 16, getColor: [145, 150, 155, 255], outlineColor: useSdf ? [255, 255, 255, 255] : [0, 0, 0, 0], outlineWidth: useSdf ? 4 : 0, fontSettings: { sdf: useSdf }, fontFamily: "Open Sans, sans-serif", fontWeight: 600, billboard: true, pickable: false, characterSet: "auto", wordBreak: "break-word", maxWidth: 150 }));
|
|
762
|
+
},
|
|
763
|
+
updateTriggers: {
|
|
764
|
+
renderSubLayers: [floorKey],
|
|
765
|
+
},
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
makePoiIconLayer(floorKey) {
|
|
769
|
+
if (!this.spriteIconMapping || !this.spriteAtlasImage)
|
|
770
|
+
return null;
|
|
771
|
+
return new MVTLayer({
|
|
772
|
+
id: "indoor-poi-icons",
|
|
773
|
+
data: "https://tiles.mapvx.com/",
|
|
774
|
+
binary: false,
|
|
775
|
+
minZoom: 17,
|
|
776
|
+
maxZoom: 24,
|
|
777
|
+
renderSubLayers: (props) => {
|
|
778
|
+
const poiData = (props.data || []);
|
|
779
|
+
const poiFeatures = poiData.filter((f) => {
|
|
780
|
+
var _a, _b, _c;
|
|
781
|
+
if (((_a = f.geometry) === null || _a === void 0 ? void 0 : _a.type) !== "Point")
|
|
782
|
+
return false;
|
|
783
|
+
if (((_b = f.properties) === null || _b === void 0 ? void 0 : _b.floor_key) && f.properties.floor_key !== floorKey)
|
|
784
|
+
return false;
|
|
785
|
+
const sub = (_c = f.properties) === null || _c === void 0 ? void 0 : _c.subclass;
|
|
786
|
+
return sub === "elevator" || sub === "toilets";
|
|
787
|
+
});
|
|
788
|
+
if (!poiFeatures.length)
|
|
789
|
+
return null;
|
|
790
|
+
const iconData = poiFeatures.map((f) => {
|
|
791
|
+
var _a, _b, _c;
|
|
792
|
+
const tags = ((_a = f.properties) === null || _a === void 0 ? void 0 : _a.tags) || "";
|
|
793
|
+
let icon;
|
|
794
|
+
if (tags.includes("wheelchair"))
|
|
795
|
+
icon = "accessible_toilets";
|
|
796
|
+
else if (tags.includes("changing_table"))
|
|
797
|
+
icon = "changing_table";
|
|
798
|
+
else
|
|
799
|
+
icon = ((_b = f.properties) === null || _b === void 0 ? void 0 : _b.subclass) || "";
|
|
800
|
+
return {
|
|
801
|
+
position: [
|
|
802
|
+
...f.geometry.coordinates.slice(0, 2),
|
|
803
|
+
((_c = f.properties) === null || _c === void 0 ? void 0 : _c.height) ? Number(f.properties.height) : 5,
|
|
804
|
+
],
|
|
805
|
+
icon,
|
|
806
|
+
};
|
|
807
|
+
});
|
|
808
|
+
return new IconLayer(Object.assign(Object.assign({}, props), { id: `${props.id}-poi-icons`, data: iconData, iconAtlas: this.spriteAtlasImage, iconMapping: this.spriteIconMapping, getIcon: (d) => d.icon, getPosition: (d) => d.position, getSize: 2, sizeUnits: "meters", sizeScale: 1, billboard: true, pickable: false }));
|
|
809
|
+
},
|
|
810
|
+
updateTriggers: {
|
|
811
|
+
renderSubLayers: [floorKey],
|
|
812
|
+
},
|
|
813
|
+
});
|
|
814
|
+
}
|
|
815
|
+
makeStoreLogoLayer(floorKey) {
|
|
816
|
+
if (!this.spriteIconMapping || !this.spriteAtlasImage)
|
|
817
|
+
return null;
|
|
818
|
+
return new MVTLayer({
|
|
819
|
+
id: "indoor-store-logos",
|
|
820
|
+
data: "https://tiles.mapvx.com/",
|
|
821
|
+
binary: false,
|
|
822
|
+
minZoom: 13,
|
|
823
|
+
maxZoom: 24,
|
|
824
|
+
renderSubLayers: (props) => {
|
|
825
|
+
const propsData = (props.data || []);
|
|
826
|
+
const logoFeatures = propsData.filter((f) => {
|
|
827
|
+
var _a, _b, _c;
|
|
828
|
+
if (((_a = f.geometry) === null || _a === void 0 ? void 0 : _a.type) !== "Point")
|
|
829
|
+
return false;
|
|
830
|
+
if (((_b = f.properties) === null || _b === void 0 ? void 0 : _b.floor_key) && f.properties.floor_key !== floorKey)
|
|
831
|
+
return false;
|
|
832
|
+
const name = (_c = f.properties) === null || _c === void 0 ? void 0 : _c.name;
|
|
833
|
+
if (!name)
|
|
834
|
+
return false;
|
|
835
|
+
return !!this.spriteIconMapping["logo-" + name];
|
|
836
|
+
});
|
|
837
|
+
if (!logoFeatures.length)
|
|
838
|
+
return null;
|
|
839
|
+
const logoData = logoFeatures.map((f) => {
|
|
840
|
+
var _a, _b, _c;
|
|
841
|
+
const name = (_a = f.properties) === null || _a === void 0 ? void 0 : _a.name;
|
|
842
|
+
const icon = "logo-" + name;
|
|
843
|
+
const rotation = ((_b = f.properties) === null || _b === void 0 ? void 0 : _b.rotation_icon) ? Number(f.properties.rotation_icon) : 0;
|
|
844
|
+
return {
|
|
845
|
+
position: [
|
|
846
|
+
...f.geometry.coordinates.slice(0, 2),
|
|
847
|
+
((_c = f.properties) === null || _c === void 0 ? void 0 : _c.height) ? Number(f.properties.height) : 5,
|
|
848
|
+
],
|
|
849
|
+
icon,
|
|
850
|
+
rotation,
|
|
851
|
+
};
|
|
852
|
+
});
|
|
853
|
+
return new IconLayer(Object.assign(Object.assign({}, props), { id: `${props.id}-store-logos`, data: logoData, iconAtlas: this.spriteAtlasImage, iconMapping: this.spriteIconMapping, getIcon: (d) => d.icon, getPosition: (d) => d.position, getAngle: (d) => -d.rotation, getSize: (d) => {
|
|
854
|
+
const LOGO_SCALE = {
|
|
855
|
+
"logo-Maxi-K": 0.4,
|
|
856
|
+
"logo-Ripley": 2,
|
|
857
|
+
"logo-Falabella": 2,
|
|
858
|
+
"logo-CasaIdeas": 2,
|
|
859
|
+
"logo-Sky Costanera": 0.6,
|
|
860
|
+
"logo-Easy": 1.4,
|
|
861
|
+
"logo-Jumbo": 2,
|
|
862
|
+
"logo-Decathlon": 1.4,
|
|
863
|
+
};
|
|
864
|
+
const m = this.spriteIconMapping[d.icon];
|
|
865
|
+
if (!m)
|
|
866
|
+
return 10;
|
|
867
|
+
const pr = m.pixelRatio;
|
|
868
|
+
const w = m.width / pr;
|
|
869
|
+
const h = m.height / pr;
|
|
870
|
+
const base = 10 * (h / Math.max(w, h));
|
|
871
|
+
return base * (LOGO_SCALE[d.icon] || 1);
|
|
872
|
+
}, sizeUnits: "meters", sizeScale: 1, billboard: false, pickable: false }));
|
|
873
|
+
},
|
|
874
|
+
updateTriggers: {
|
|
875
|
+
renderSubLayers: [floorKey],
|
|
876
|
+
},
|
|
877
|
+
});
|
|
878
|
+
}
|
|
879
|
+
updateDeckOverlay() {
|
|
880
|
+
if (!this.deckOverlay || !this.currentFloor)
|
|
881
|
+
return;
|
|
882
|
+
const layers = [
|
|
883
|
+
this.makeMVTLayer(this.currentFloor),
|
|
884
|
+
this.makeLabelLayer(this.currentFloor, false),
|
|
885
|
+
];
|
|
886
|
+
const poiLayer = this.makePoiIconLayer(this.currentFloor);
|
|
887
|
+
if (poiLayer)
|
|
888
|
+
layers.push(poiLayer);
|
|
889
|
+
const storeLayer = this.makeStoreLogoLayer(this.currentFloor);
|
|
890
|
+
if (storeLayer)
|
|
891
|
+
layers.push(storeLayer);
|
|
892
|
+
this.deckOverlay.setProps({ layers });
|
|
893
|
+
}
|
|
894
|
+
// End 3D related methods ------------------------------------------------------------
|
|
452
895
|
getCurrentFloor() {
|
|
453
896
|
var _a;
|
|
454
897
|
return (_a = this.currentFloor) !== null && _a !== void 0 ? _a : "";
|
|
@@ -468,6 +911,10 @@ export class InternalMapVXMap extends Loggeable {
|
|
|
468
911
|
if (place) {
|
|
469
912
|
this.subPlacesLoad(place.mapvxId);
|
|
470
913
|
}
|
|
914
|
+
if (this.mode === "3D") {
|
|
915
|
+
this.updateDeckOverlay();
|
|
916
|
+
this.placeEscalators();
|
|
917
|
+
}
|
|
471
918
|
if (updateStyle) {
|
|
472
919
|
this.repository
|
|
473
920
|
.fetchAndParseMapStyle(place === null || place === void 0 ? void 0 : place.mapvxId)
|
|
@@ -496,6 +943,10 @@ export class InternalMapVXMap extends Loggeable {
|
|
|
496
943
|
this.innerFloors = (_a = place === null || place === void 0 ? void 0 : place.innerFloors.sort((a, b) => a.index - b.index)) !== null && _a !== void 0 ? _a : [];
|
|
497
944
|
this.currentFloor =
|
|
498
945
|
(_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 : "";
|
|
946
|
+
if (this.mode === "3D") {
|
|
947
|
+
this.updateDeckOverlay();
|
|
948
|
+
this.placeEscalators();
|
|
949
|
+
}
|
|
499
950
|
}
|
|
500
951
|
updateParentPlaceAndFloor(parentPlaceId, floorId, options) {
|
|
501
952
|
var _a, _b, _c, _d;
|
|
@@ -513,6 +964,19 @@ export class InternalMapVXMap extends Loggeable {
|
|
|
513
964
|
}
|
|
514
965
|
setLang(lang) {
|
|
515
966
|
this.repository.setLang(lang);
|
|
967
|
+
if (rtlLanguages.includes(lang)) {
|
|
968
|
+
this.setRTLSupport();
|
|
969
|
+
}
|
|
970
|
+
// setLayersForLanguage reads this.map.getStyle()?.layers, which is empty
|
|
971
|
+
// until the style finishes loading. If setLang is called right after
|
|
972
|
+
// createMap (before the "load" event), apply it once the style is ready
|
|
973
|
+
// so the language change is not silently dropped.
|
|
974
|
+
if (this.map.isStyleLoaded()) {
|
|
975
|
+
this.setLayersForLanguage(lang);
|
|
976
|
+
}
|
|
977
|
+
else {
|
|
978
|
+
this.map.once("load", () => this.setLayersForLanguage(lang));
|
|
979
|
+
}
|
|
516
980
|
}
|
|
517
981
|
setParentPlace(place, updateStyle, onStyleReady) {
|
|
518
982
|
this.changeParentPlaceTo(place, updateStyle, onStyleReady);
|
|
@@ -535,6 +999,9 @@ export class InternalMapVXMap extends Loggeable {
|
|
|
535
999
|
this.routeController.addSourcesAndLayers();
|
|
536
1000
|
this.refreshCircles();
|
|
537
1001
|
this.filterByFloorKey(this.currentFloor);
|
|
1002
|
+
if (this.mode === "3D") {
|
|
1003
|
+
this.updateDeckOverlay();
|
|
1004
|
+
}
|
|
538
1005
|
}
|
|
539
1006
|
addMarker(marker) {
|
|
540
1007
|
var _a, _b;
|
|
@@ -884,7 +1351,14 @@ export class InternalMapVXMap extends Loggeable {
|
|
|
884
1351
|
// Always keep the default bucket alive so an empty map still renders
|
|
885
1352
|
// newly added circles without a layer rebuild
|
|
886
1353
|
ordersInUse.add("aboveBasemap");
|
|
887
|
-
|
|
1354
|
+
// Process buckets lowest-first (CIRCLE_RENDER_ORDERS is canonical):
|
|
1355
|
+
// when two buckets resolve to the same anchor, each addLayer/moveLayer
|
|
1356
|
+
// lands immediately before it, so a bucket processed later paints above
|
|
1357
|
+
// the ones processed earlier. Canonical order keeps
|
|
1358
|
+
// belowLabels < aboveBasemap < top regardless of circle insertion order.
|
|
1359
|
+
for (const order of CIRCLE_RENDER_ORDERS) {
|
|
1360
|
+
if (!ordersInUse.has(order))
|
|
1361
|
+
continue;
|
|
888
1362
|
const ids = circleLayerIdsFor(order);
|
|
889
1363
|
const beforeId = this.circleBeforeIdFor(order);
|
|
890
1364
|
const orderFilter = ["==", ["get", "renderOrder"], order];
|
|
@@ -956,6 +1430,10 @@ export class InternalMapVXMap extends Loggeable {
|
|
|
956
1430
|
if (this.innerFloors.every((floor) => floor.key !== floorKey) && floorKey !== "") {
|
|
957
1431
|
this.logEvent("invalidFloorKey", { floorKey: floorKeyString });
|
|
958
1432
|
}
|
|
1433
|
+
if (this.mode === "3D") {
|
|
1434
|
+
this.updateDeckOverlay();
|
|
1435
|
+
this.placeEscalators();
|
|
1436
|
+
}
|
|
959
1437
|
this.updateFiltersTo(floorKeyString);
|
|
960
1438
|
this.updateMarkersTo(floorKeyString);
|
|
961
1439
|
this.refreshCircles();
|
|
@@ -1064,6 +1542,49 @@ export class InternalMapVXMap extends Loggeable {
|
|
|
1064
1542
|
this.map.setMinZoom(zoomLvl);
|
|
1065
1543
|
(_a = options === null || options === void 0 ? void 0 : options.onComplete) === null || _a === void 0 ? void 0 : _a.call(options);
|
|
1066
1544
|
}
|
|
1545
|
+
setBearing(degrees, options) {
|
|
1546
|
+
var _a;
|
|
1547
|
+
if (!Number.isFinite(degrees))
|
|
1548
|
+
return;
|
|
1549
|
+
if (options === null || options === void 0 ? void 0 : options.animate) {
|
|
1550
|
+
if (options.onComplete)
|
|
1551
|
+
void this.map.once("moveend", options.onComplete);
|
|
1552
|
+
this.map.rotateTo(degrees, { duration: 600 });
|
|
1553
|
+
return;
|
|
1554
|
+
}
|
|
1555
|
+
this.map.setBearing(degrees);
|
|
1556
|
+
(_a = options === null || options === void 0 ? void 0 : options.onComplete) === null || _a === void 0 ? void 0 : _a.call(options);
|
|
1557
|
+
}
|
|
1558
|
+
setRotationEnabled(enabled) {
|
|
1559
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
1560
|
+
if (enabled) {
|
|
1561
|
+
(_b = (_a = this.map.dragRotate) === null || _a === void 0 ? void 0 : _a.enable) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
1562
|
+
(_d = (_c = this.map.touchZoomRotate) === null || _c === void 0 ? void 0 : _c.enableRotation) === null || _d === void 0 ? void 0 : _d.call(_c);
|
|
1563
|
+
(_f = (_e = this.map.keyboard) === null || _e === void 0 ? void 0 : _e.enable) === null || _f === void 0 ? void 0 : _f.call(_e);
|
|
1564
|
+
}
|
|
1565
|
+
else {
|
|
1566
|
+
(_h = (_g = this.map.dragRotate) === null || _g === void 0 ? void 0 : _g.disable) === null || _h === void 0 ? void 0 : _h.call(_g);
|
|
1567
|
+
(_k = (_j = this.map.touchZoomRotate) === null || _j === void 0 ? void 0 : _j.disableRotation) === null || _k === void 0 ? void 0 : _k.call(_j);
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
setPanEnabled(enabled) {
|
|
1571
|
+
var _a, _b, _c, _d;
|
|
1572
|
+
if (enabled) {
|
|
1573
|
+
(_b = (_a = this.map.dragPan) === null || _a === void 0 ? void 0 : _a.enable) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
1574
|
+
}
|
|
1575
|
+
else {
|
|
1576
|
+
(_d = (_c = this.map.dragPan) === null || _c === void 0 ? void 0 : _c.disable) === null || _d === void 0 ? void 0 : _d.call(_c);
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
setScrollZoomEnabled(enabled) {
|
|
1580
|
+
var _a, _b, _c, _d;
|
|
1581
|
+
if (enabled) {
|
|
1582
|
+
(_b = (_a = this.map.scrollZoom) === null || _a === void 0 ? void 0 : _a.enable) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
1583
|
+
}
|
|
1584
|
+
else {
|
|
1585
|
+
(_d = (_c = this.map.scrollZoom) === null || _c === void 0 ? void 0 : _c.disable) === null || _d === void 0 ? void 0 : _d.call(_c);
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1067
1588
|
isInsideBounds(point) {
|
|
1068
1589
|
const mapBounds = this.map.getMaxBounds();
|
|
1069
1590
|
if (mapBounds != null) {
|
|
@@ -1232,7 +1753,7 @@ export class InternalMapVXMap extends Loggeable {
|
|
|
1232
1753
|
if (pointedPlace !== undefined) {
|
|
1233
1754
|
new maplibregl.Popup()
|
|
1234
1755
|
.setLngLat(pointedPlace.position)
|
|
1235
|
-
.
|
|
1756
|
+
.setText(pointedPlace.title)
|
|
1236
1757
|
.addTo(this.map);
|
|
1237
1758
|
}
|
|
1238
1759
|
}
|