@mapvx/web-js 1.2.3 → 1.3.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.
Files changed (42) hide show
  1. package/LICENSE.md +2 -2
  2. package/README.md +9 -10
  3. package/dist/cjs/assets/icons.js +8 -6
  4. package/dist/cjs/assets/icons.js.map +1 -1
  5. package/dist/cjs/assets/route_animation_icon.svg +15 -0
  6. package/dist/cjs/assets/user-dot-icon.svg +3 -0
  7. package/dist/cjs/domain/models/animation.js +2 -2
  8. package/dist/cjs/logger/logger.js +1 -1
  9. package/dist/cjs/logger/rollbar.js +1 -1
  10. package/dist/cjs/map/map.js +490 -5
  11. package/dist/cjs/map/map.js.map +1 -1
  12. package/dist/cjs/sdk.js +1 -0
  13. package/dist/cjs/sdk.js.map +1 -1
  14. package/dist/cjs/utils/3d.js +12 -0
  15. package/dist/cjs/utils/3d.js.map +1 -0
  16. package/dist/es/assets/icons.d.ts +4 -4
  17. package/dist/es/assets/icons.d.ts.map +1 -1
  18. package/dist/es/assets/icons.js +8 -6
  19. package/dist/es/assets/icons.js.map +1 -1
  20. package/dist/es/assets/route_animation_icon.svg +15 -0
  21. package/dist/es/assets/user-dot-icon.svg +3 -0
  22. package/dist/es/domain/models/animation.d.ts +3 -3
  23. package/dist/es/domain/models/animation.js +2 -2
  24. package/dist/es/domain/models/mapConfig.d.ts +11 -0
  25. package/dist/es/domain/models/mapConfig.d.ts.map +1 -1
  26. package/dist/es/interfaces/routeCacheResponse.d.ts.map +1 -1
  27. package/dist/es/logger/logger.js +1 -1
  28. package/dist/es/logger/rollbar.js +1 -1
  29. package/dist/es/map/map.d.ts +27 -1
  30. package/dist/es/map/map.d.ts.map +1 -1
  31. package/dist/es/map/map.js +491 -6
  32. package/dist/es/map/map.js.map +1 -1
  33. package/dist/es/sdk.d.ts.map +1 -1
  34. package/dist/es/sdk.js +1 -0
  35. package/dist/es/sdk.js.map +1 -1
  36. package/dist/es/utils/3d.d.ts +2 -0
  37. package/dist/es/utils/3d.d.ts.map +1 -0
  38. package/dist/es/utils/3d.js +8 -0
  39. package/dist/es/utils/3d.js.map +1 -0
  40. package/dist/umd/index.js +153836 -1958
  41. package/dist/umd/index.js.map +1 -1
  42. package/package.json +62 -62
@@ -1,4 +1,8 @@
1
- import maplibregl, { LngLatBounds, Map, NavigationControl, setRTLTextPlugin, } from "maplibre-gl";
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";
2
6
  import { Semaphore } from "../utils/semaphore";
3
7
  /** Shared GeoJSON source holding every circle drawn through the circle API. */
4
8
  const CIRCLE_SOURCE_ID = "mapvx-circles";
@@ -127,19 +131,20 @@ function convertPaddingToPixels(padding, containerWidth, containerHeight) {
127
131
  right: parsePaddingValue(padding.right, containerWidth),
128
132
  };
129
133
  }
134
+ import { MapboxOverlay } from "@deck.gl/mapbox";
130
135
  import { userLocationDataUrl } from "../assets/icons";
131
136
  import { RouteController } from "../controllers/routeController";
132
137
  import { rtlLanguages } from "../domain/models/_rtl";
133
138
  import { InternalAnimationConfig, InternalAnimationDrawingConfig, } from "../domain/models/animation";
139
+ import { CIRCLE_RENDER_ORDERS, circleRing, cloneCircleRecord, MAPVX_BRAND_COLOR, resolveCircleConfig, } from "../domain/models/circle";
134
140
  import { Loggeable } from "../domain/models/loggeable";
135
141
  import { DEFAULT_TILE_CACHE_CONFIG, MAPLIBRE_MAX_TILE_CACHE_HARD_CAP, } from "../domain/models/mapConfig";
136
- import { CIRCLE_RENDER_ORDERS, circleRing, cloneCircleRecord, MAPVX_BRAND_COLOR, resolveCircleConfig, } from "../domain/models/circle";
137
142
  import { MarkerAttribute } from "../domain/models/marker";
138
143
  import { MVXRoute } from "../domain/models/route";
139
144
  import { InternalDrawRouteConfiguration, InternalGetRouteConfiguration, } from "../domain/models/routeConfiguration";
140
145
  import { Repository } from "../repository/repository";
141
- import { getBoundingBox } from "../utils/utils";
142
146
  import { extractStepCoordinates } from "../utils/route-utils";
147
+ import { getBoundingBox } from "../utils/utils";
143
148
  import { buildInteractionOptions, shouldDisableTouchRotation } from "./mapInteractionOptions";
144
149
  /**
145
150
  * Class to interact with the map.
@@ -156,7 +161,7 @@ export class InternalMapVXMap extends Loggeable {
156
161
  * @returns A new instance of MapVXMap.
157
162
  */
158
163
  constructor(mapConfig, container, token) {
159
- var _a, _b;
164
+ var _a, _b, _c;
160
165
  super();
161
166
  this.potentialParentPlaces = [];
162
167
  this.innerFloors = [];
@@ -169,6 +174,22 @@ export class InternalMapVXMap extends Loggeable {
169
174
  this.hoveredId = "unselected";
170
175
  this.failedTiles = new Set();
171
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();
172
193
  if (rtlLanguages.includes((_a = mapConfig.lang) !== null && _a !== void 0 ? _a : "")) {
173
194
  this.setRTLSupport();
174
195
  }
@@ -180,11 +201,16 @@ export class InternalMapVXMap extends Loggeable {
180
201
  this.watchPositionID = undefined;
181
202
  this.onFloorChange = mapConfig.onFloorChange;
182
203
  this.onParentPlaceChange = mapConfig.onParentPlaceChange;
204
+ this.mode = (_c = mapConfig.mode) !== null && _c !== void 0 ? _c : "2D";
205
+ this.initialCenter = mapConfig.center;
183
206
  this.tileCacheConfig = (() => {
184
207
  const merged = Object.assign(Object.assign({}, DEFAULT_TILE_CACHE_CONFIG), mapConfig.tileCache);
185
208
  merged.maxTiles = Math.min(merged.maxTiles, MAPLIBRE_MAX_TILE_CACHE_HARD_CAP);
186
209
  return merged;
187
210
  })();
211
+ if (this.mode === "3D") {
212
+ this.loadSpriteAtlas().catch(console.error);
213
+ }
188
214
  if (mapConfig.parentPlaceId != null) {
189
215
  this.initialPlaceDetailSetUp(mapConfig.parentPlaceId, mapConfig.authToken);
190
216
  }
@@ -312,6 +338,30 @@ export class InternalMapVXMap extends Loggeable {
312
338
  }), (_e = mapConfig.navigationPosition) !== null && _e !== void 0 ? _e : "top-right");
313
339
  this.map.on("load", () => {
314
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
+ }
315
365
  this.whenStyleUpdates(style);
316
366
  if (mapConfig.lang)
317
367
  this.setLayersForLanguage(mapConfig.lang);
@@ -334,7 +384,6 @@ export class InternalMapVXMap extends Loggeable {
334
384
  (_a = mapConfig.onRotate) === null || _a === void 0 ? void 0 : _a.call(mapConfig, this.map.getBearing());
335
385
  });
336
386
  this.defaultClickListener();
337
- return this.map;
338
387
  }
339
388
  setBaseFilters(style) {
340
389
  if (!style)
@@ -463,6 +512,415 @@ export class InternalMapVXMap extends Loggeable {
463
512
  // Tile cache clear may fail if cache doesn't exist
464
513
  }
465
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
+ // Guard against an invalid initial center before it reaches fromLngLat.
603
+ if (!this.initialCenter ||
604
+ !Number.isFinite(this.initialCenter.lng) ||
605
+ !Number.isFinite(this.initialCenter.lat))
606
+ return;
607
+ const refMercator = MercatorCoordinate.fromLngLat(this.initialCenter, 0);
608
+ const meterScale = refMercator.meterInMercatorCoordinateUnits();
609
+ for (const pos of positions) {
610
+ // Defense in depth: never feed a non-finite coordinate to fromLngLat.
611
+ if (!Number.isFinite(pos.lng) || !Number.isFinite(pos.lat))
612
+ continue;
613
+ const target = maplibregl.MercatorCoordinate.fromLngLat([pos.lng, pos.lat], 0);
614
+ const sx = (target.x - refMercator.x) / meterScale;
615
+ const sz = (target.y - refMercator.y) / meterScale;
616
+ const pivot = new THREE.Group();
617
+ pivot.position.set(sx, 0, sz);
618
+ pivot.rotation.y = Math.PI / 2 - pos.bearing;
619
+ pivot.scale.set(0.3, 0.3, 0.3);
620
+ const model = this.escalatorModelTemplate.clone();
621
+ model.position.set(-this.escalatorCenter.x, -this.escalatorCenter.y - 3, -this.escalatorCenter.z);
622
+ pivot.add(model);
623
+ this.escalatorScene.add(pivot);
624
+ }
625
+ this.map.triggerRepaint();
626
+ }
627
+ captureEscalatorPositions() {
628
+ if (this.currentFloor && this.escalatorCache[this.currentFloor])
629
+ return;
630
+ // queryRenderedFeatures uses exact rendered coordinates (not tile-approximated)
631
+ const features = this.map
632
+ .queryRenderedFeatures(undefined, {
633
+ layers: ["indoor-transportation"],
634
+ })
635
+ .filter((f) => { var _a; return ((_a = f.properties) === null || _a === void 0 ? void 0 : _a.floor_key) === this.currentFloor; });
636
+ if (!features.length)
637
+ return;
638
+ // Collect all midpoints first
639
+ const allMids = [];
640
+ for (const f of features) {
641
+ const geom = f.geometry;
642
+ // queryRenderedFeatures may return a LineString or, when a line is split
643
+ // across tile boundaries, a MultiLineString. The previous code assumed
644
+ // LineString and read coords[0]/coords[n-1] as [lng,lat], producing
645
+ // NaN for MultiLineString (coordinates is number[][][]). Normalize both
646
+ // shapes to a list of line parts (Position[][]).
647
+ let lineParts;
648
+ if (geom.type === "LineString") {
649
+ lineParts = [geom.coordinates];
650
+ }
651
+ else if (geom.type === "MultiLineString") {
652
+ lineParts = geom.coordinates;
653
+ }
654
+ else {
655
+ continue;
656
+ }
657
+ for (const coords of lineParts) {
658
+ if (!coords || coords.length < 2)
659
+ continue;
660
+ const start = coords[0];
661
+ const end = coords[coords.length - 1];
662
+ const midLng = (start[0] + end[0]) / 2;
663
+ const midLat = (start[1] + end[1]) / 2;
664
+ const bearing = Math.atan2(end[0] - start[0], end[1] - start[1]);
665
+ // Skip any non-finite result so a bad feature can never poison the
666
+ // cache (and later crash MercatorCoordinate.fromLngLat).
667
+ if (!Number.isFinite(midLng) || !Number.isFinite(midLat) || !Number.isFinite(bearing))
668
+ continue;
669
+ allMids.push({ lng: midLng, lat: midLat, bearing });
670
+ }
671
+ }
672
+ // Merge points within ~15m of each other into one
673
+ const MERGE_DEG = 0.00015; // ~15m
674
+ const positions = [];
675
+ const used = new Set();
676
+ for (let i = 0; i < allMids.length; i++) {
677
+ if (used.has(i))
678
+ continue;
679
+ used.add(i);
680
+ const group = [allMids[i]];
681
+ for (let j = i + 1; j < allMids.length; j++) {
682
+ if (used.has(j))
683
+ continue;
684
+ const dLng = allMids[i].lng - allMids[j].lng;
685
+ const dLat = allMids[i].lat - allMids[j].lat;
686
+ if (Math.abs(dLng) < MERGE_DEG && Math.abs(dLat) < MERGE_DEG) {
687
+ used.add(j);
688
+ group.push(allMids[j]);
689
+ }
690
+ }
691
+ // Use average position and first bearing
692
+ const avgLng = group.reduce((s, p) => s + p.lng, 0) / group.length;
693
+ const avgLat = group.reduce((s, p) => s + p.lat, 0) / group.length;
694
+ positions.push({ lng: avgLng, lat: avgLat, bearing: group[0].bearing });
695
+ }
696
+ if (positions.length && this.currentFloor) {
697
+ this.escalatorCache[this.currentFloor] = positions;
698
+ }
699
+ }
700
+ async loadSpriteAtlas() {
701
+ const [json, img] = await Promise.all([
702
+ fetch(`${this.SPRITE_URL}.json`).then((r) => r.json()),
703
+ new Promise((resolve, reject) => {
704
+ const image = new Image();
705
+ image.crossOrigin = "anonymous";
706
+ image.onload = () => resolve(image);
707
+ image.onerror = reject;
708
+ image.src = `${this.SPRITE_URL}.png`;
709
+ }),
710
+ ]);
711
+ this.spriteIconMapping = json;
712
+ this.spriteAtlasImage = img;
713
+ }
714
+ makeMVTLayer(floorKey) {
715
+ return new MVTLayer({
716
+ id: "indoor-lines-elevated",
717
+ data: "https://tiles.mapvx.com/",
718
+ binary: false,
719
+ renderSubLayers: (props) => {
720
+ var _a;
721
+ const roomData = (props.data || []);
722
+ const roomFeatures = roomData.filter((f) => {
723
+ var _a, _b, _c;
724
+ return ((_a = f.properties) === null || _a === void 0 ? void 0 : _a.class) === "room" &&
725
+ ((_b = f.properties) === null || _b === void 0 ? void 0 : _b.subclass) !== "empty" &&
726
+ ((_c = f.properties) === null || _c === void 0 ? void 0 : _c.floor_key) === floorKey;
727
+ });
728
+ if (!roomFeatures.length)
729
+ return null;
730
+ const paths = [];
731
+ for (const f of roomFeatures) {
732
+ const z = ((_a = f.properties) === null || _a === void 0 ? void 0 : _a.height) ? Number(f.properties.height) : 5;
733
+ const geom = f.geometry;
734
+ const polygons = geom.type === "Polygon"
735
+ ? [geom.coordinates]
736
+ : geom.type === "MultiPolygon"
737
+ ? geom.coordinates
738
+ : [];
739
+ for (const rings of polygons) {
740
+ for (const ring of rings) {
741
+ paths.push({ path: ring.map(([lon, lat]) => [lon, lat, z]) });
742
+ }
743
+ }
744
+ }
745
+ 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 }));
746
+ },
747
+ updateTriggers: {
748
+ renderSubLayers: [floorKey],
749
+ },
750
+ });
751
+ }
752
+ makeLabelLayer(floorKey, useSdf = true) {
753
+ return new MVTLayer({
754
+ id: "indoor-labels",
755
+ data: "https://tiles.mapvx.com/",
756
+ binary: false,
757
+ minZoom: 19.5,
758
+ maxZoom: 24,
759
+ renderSubLayers: (props) => {
760
+ // Use area_name and poi Point features — unique per tile, no duplicates
761
+ // If logo-{name} image exists in sprite, icon has priority over text label
762
+ const labelData = (props.data || []);
763
+ const labelFeatures = labelData.filter((f) => {
764
+ var _a, _b, _c, _d, _e, _f;
765
+ 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")
766
+ return false;
767
+ if (((_c = f.geometry) === null || _c === void 0 ? void 0 : _c.type) !== "Point")
768
+ return false;
769
+ 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);
770
+ if (!name)
771
+ return false;
772
+ if (((_f = f.properties) === null || _f === void 0 ? void 0 : _f.floor_key) && f.properties.floor_key !== floorKey)
773
+ return false;
774
+ if (this.map.hasImage("logo-" + name))
775
+ return false; // icon has priority
776
+ return true;
777
+ });
778
+ if (!labelFeatures.length)
779
+ return null;
780
+ const textData = labelFeatures.map((f) => {
781
+ var _a, _b, _c;
782
+ return ({
783
+ position: [
784
+ ...f.geometry.coordinates.slice(0, 2),
785
+ ((_a = f.properties) === null || _a === void 0 ? void 0 : _a.height) ? Number(f.properties.height) : 5,
786
+ ],
787
+ text: ((_b = f.properties) === null || _b === void 0 ? void 0 : _b["name:latin"]) || ((_c = f.properties) === null || _c === void 0 ? void 0 : _c.name) || "",
788
+ });
789
+ });
790
+ 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 }));
791
+ },
792
+ updateTriggers: {
793
+ renderSubLayers: [floorKey],
794
+ },
795
+ });
796
+ }
797
+ makePoiIconLayer(floorKey) {
798
+ if (!this.spriteIconMapping || !this.spriteAtlasImage)
799
+ return null;
800
+ return new MVTLayer({
801
+ id: "indoor-poi-icons",
802
+ data: "https://tiles.mapvx.com/",
803
+ binary: false,
804
+ minZoom: 17,
805
+ maxZoom: 24,
806
+ renderSubLayers: (props) => {
807
+ const poiData = (props.data || []);
808
+ const poiFeatures = poiData.filter((f) => {
809
+ var _a, _b, _c;
810
+ if (((_a = f.geometry) === null || _a === void 0 ? void 0 : _a.type) !== "Point")
811
+ return false;
812
+ if (((_b = f.properties) === null || _b === void 0 ? void 0 : _b.floor_key) && f.properties.floor_key !== floorKey)
813
+ return false;
814
+ const sub = (_c = f.properties) === null || _c === void 0 ? void 0 : _c.subclass;
815
+ return sub === "elevator" || sub === "toilets";
816
+ });
817
+ if (!poiFeatures.length)
818
+ return null;
819
+ const iconData = poiFeatures.map((f) => {
820
+ var _a, _b, _c;
821
+ const tags = ((_a = f.properties) === null || _a === void 0 ? void 0 : _a.tags) || "";
822
+ let icon;
823
+ if (tags.includes("wheelchair"))
824
+ icon = "accessible_toilets";
825
+ else if (tags.includes("changing_table"))
826
+ icon = "changing_table";
827
+ else
828
+ icon = ((_b = f.properties) === null || _b === void 0 ? void 0 : _b.subclass) || "";
829
+ return {
830
+ position: [
831
+ ...f.geometry.coordinates.slice(0, 2),
832
+ ((_c = f.properties) === null || _c === void 0 ? void 0 : _c.height) ? Number(f.properties.height) : 5,
833
+ ],
834
+ icon,
835
+ };
836
+ });
837
+ 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 }));
838
+ },
839
+ updateTriggers: {
840
+ renderSubLayers: [floorKey],
841
+ },
842
+ });
843
+ }
844
+ makeStoreLogoLayer(floorKey) {
845
+ if (!this.spriteIconMapping || !this.spriteAtlasImage)
846
+ return null;
847
+ return new MVTLayer({
848
+ id: "indoor-store-logos",
849
+ data: "https://tiles.mapvx.com/",
850
+ binary: false,
851
+ minZoom: 13,
852
+ maxZoom: 24,
853
+ renderSubLayers: (props) => {
854
+ const propsData = (props.data || []);
855
+ const logoFeatures = propsData.filter((f) => {
856
+ var _a, _b, _c;
857
+ if (((_a = f.geometry) === null || _a === void 0 ? void 0 : _a.type) !== "Point")
858
+ return false;
859
+ if (((_b = f.properties) === null || _b === void 0 ? void 0 : _b.floor_key) && f.properties.floor_key !== floorKey)
860
+ return false;
861
+ const name = (_c = f.properties) === null || _c === void 0 ? void 0 : _c.name;
862
+ if (!name)
863
+ return false;
864
+ return !!this.spriteIconMapping["logo-" + name];
865
+ });
866
+ if (!logoFeatures.length)
867
+ return null;
868
+ const logoData = logoFeatures.map((f) => {
869
+ var _a, _b, _c;
870
+ const name = (_a = f.properties) === null || _a === void 0 ? void 0 : _a.name;
871
+ const icon = "logo-" + name;
872
+ const rotation = ((_b = f.properties) === null || _b === void 0 ? void 0 : _b.rotation_icon) ? Number(f.properties.rotation_icon) : 0;
873
+ return {
874
+ position: [
875
+ ...f.geometry.coordinates.slice(0, 2),
876
+ ((_c = f.properties) === null || _c === void 0 ? void 0 : _c.height) ? Number(f.properties.height) : 5,
877
+ ],
878
+ icon,
879
+ rotation,
880
+ };
881
+ });
882
+ 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) => {
883
+ const LOGO_SCALE = {
884
+ "logo-Maxi-K": 0.4,
885
+ "logo-Ripley": 2,
886
+ "logo-Falabella": 2,
887
+ "logo-CasaIdeas": 2,
888
+ "logo-Sky Costanera": 0.6,
889
+ "logo-Easy": 1.4,
890
+ "logo-Jumbo": 2,
891
+ "logo-Decathlon": 1.4,
892
+ };
893
+ const m = this.spriteIconMapping[d.icon];
894
+ if (!m)
895
+ return 10;
896
+ const pr = m.pixelRatio;
897
+ const w = m.width / pr;
898
+ const h = m.height / pr;
899
+ const base = 10 * (h / Math.max(w, h));
900
+ return base * (LOGO_SCALE[d.icon] || 1);
901
+ }, sizeUnits: "meters", sizeScale: 1, billboard: false, pickable: false }));
902
+ },
903
+ updateTriggers: {
904
+ renderSubLayers: [floorKey],
905
+ },
906
+ });
907
+ }
908
+ updateDeckOverlay() {
909
+ if (!this.deckOverlay || !this.currentFloor)
910
+ return;
911
+ const layers = [
912
+ this.makeMVTLayer(this.currentFloor),
913
+ this.makeLabelLayer(this.currentFloor, false),
914
+ ];
915
+ const poiLayer = this.makePoiIconLayer(this.currentFloor);
916
+ if (poiLayer)
917
+ layers.push(poiLayer);
918
+ const storeLayer = this.makeStoreLogoLayer(this.currentFloor);
919
+ if (storeLayer)
920
+ layers.push(storeLayer);
921
+ this.deckOverlay.setProps({ layers });
922
+ }
923
+ // End 3D related methods ------------------------------------------------------------
466
924
  getCurrentFloor() {
467
925
  var _a;
468
926
  return (_a = this.currentFloor) !== null && _a !== void 0 ? _a : "";
@@ -482,6 +940,10 @@ export class InternalMapVXMap extends Loggeable {
482
940
  if (place) {
483
941
  this.subPlacesLoad(place.mapvxId);
484
942
  }
943
+ if (this.mode === "3D") {
944
+ this.updateDeckOverlay();
945
+ this.placeEscalators();
946
+ }
485
947
  if (updateStyle) {
486
948
  this.repository
487
949
  .fetchAndParseMapStyle(place === null || place === void 0 ? void 0 : place.mapvxId)
@@ -510,6 +972,10 @@ export class InternalMapVXMap extends Loggeable {
510
972
  this.innerFloors = (_a = place === null || place === void 0 ? void 0 : place.innerFloors.sort((a, b) => a.index - b.index)) !== null && _a !== void 0 ? _a : [];
511
973
  this.currentFloor =
512
974
  (_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 : "";
975
+ if (this.mode === "3D") {
976
+ this.updateDeckOverlay();
977
+ this.placeEscalators();
978
+ }
513
979
  }
514
980
  updateParentPlaceAndFloor(parentPlaceId, floorId, options) {
515
981
  var _a, _b, _c, _d;
@@ -562,6 +1028,9 @@ export class InternalMapVXMap extends Loggeable {
562
1028
  this.routeController.addSourcesAndLayers();
563
1029
  this.refreshCircles();
564
1030
  this.filterByFloorKey(this.currentFloor);
1031
+ if (this.mode === "3D") {
1032
+ this.updateDeckOverlay();
1033
+ }
565
1034
  }
566
1035
  addMarker(marker) {
567
1036
  var _a, _b;
@@ -990,11 +1459,26 @@ export class InternalMapVXMap extends Loggeable {
990
1459
  if (this.innerFloors.every((floor) => floor.key !== floorKey) && floorKey !== "") {
991
1460
  this.logEvent("invalidFloorKey", { floorKey: floorKeyString });
992
1461
  }
1462
+ // Critical base-map updates run first and must never be blocked by an
1463
+ // optional 3D decoration failing. A throw in placeEscalators used to abort
1464
+ // this method before updateFiltersTo, leaving the base polygons unfiltered
1465
+ // (showing the wrong floor) while the caller's empty catch hid the error.
993
1466
  this.updateFiltersTo(floorKeyString);
994
1467
  this.updateMarkersTo(floorKeyString);
995
1468
  this.refreshCircles();
996
1469
  this.routeController.updateRouteLayers(floorKeyString);
997
1470
  this.routeController.updateRouteMarkerVisibility(floorKeyString);
1471
+ // 3D decorations are best-effort: isolate so any failure here can never
1472
+ // break base-map rendering.
1473
+ if (this.mode === "3D") {
1474
+ try {
1475
+ this.updateDeckOverlay();
1476
+ this.placeEscalators();
1477
+ }
1478
+ catch (error) {
1479
+ this.logError(error, "filterByFloorKey: 3D layer update failed");
1480
+ }
1481
+ }
998
1482
  }
999
1483
  getContainer() {
1000
1484
  return this.map.getContainer();
@@ -1259,7 +1743,7 @@ export class InternalMapVXMap extends Loggeable {
1259
1743
  throw new Error("Error: Failed to add route");
1260
1744
  }
1261
1745
  }
1262
- updateRouteProgress(routeId, position, behindStyle = { type: "Solid", color: "#276EF1" }) {
1746
+ updateRouteProgress(routeId, position, behindStyle = { type: "Solid", color: "#757575" }) {
1263
1747
  try {
1264
1748
  const behindConfig = new InternalDrawRouteConfiguration({
1265
1749
  routeStyle: behindStyle,
@@ -1315,6 +1799,7 @@ export class InternalMapVXMap extends Loggeable {
1315
1799
  }
1316
1800
  return "";
1317
1801
  }
1802
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1318
1803
  removePopOver(id) {
1319
1804
  throw Error("Not implemented");
1320
1805
  }