@mapvx/web-js 2.0.2 → 3.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.
Files changed (66) hide show
  1. package/dist/cjs/controllers/routeController.js +26 -4
  2. package/dist/cjs/controllers/routeController.js.map +1 -1
  3. package/dist/cjs/index.js.map +1 -1
  4. package/dist/cjs/infrastructure/cache/CacheManager.js.map +1 -1
  5. package/dist/cjs/logger/logger.js +2 -3
  6. package/dist/cjs/logger/logger.js.map +1 -1
  7. package/dist/cjs/logger/sentry.js +1 -1
  8. package/dist/cjs/map/map.js +123 -49
  9. package/dist/cjs/map/map.js.map +1 -1
  10. package/dist/cjs/map/scene3DController.js +93 -8
  11. package/dist/cjs/map/scene3DController.js.map +1 -1
  12. package/dist/cjs/repository/repository.js +21 -21
  13. package/dist/cjs/repository/repository.js.map +1 -1
  14. package/dist/cjs/sdk.js +21 -21
  15. package/dist/cjs/sdk.js.map +1 -1
  16. package/dist/cjs/utils/3d.js +34 -0
  17. package/dist/cjs/utils/3d.js.map +1 -1
  18. package/dist/cjs/utils/opening-hours-helper.js +22 -19
  19. package/dist/cjs/utils/opening-hours-helper.js.map +1 -1
  20. package/dist/cjs/utils/semaphore.js.map +1 -1
  21. package/dist/es/controllers/routeController.d.ts +8 -0
  22. package/dist/es/controllers/routeController.d.ts.map +1 -1
  23. package/dist/es/controllers/routeController.js +26 -4
  24. package/dist/es/controllers/routeController.js.map +1 -1
  25. package/dist/es/domain/models/route.d.ts +1 -1
  26. package/dist/es/domain/models/route.d.ts.map +1 -1
  27. package/dist/es/domain/models/scene3D.d.ts +34 -0
  28. package/dist/es/domain/models/scene3D.d.ts.map +1 -1
  29. package/dist/es/index.d.ts +1 -1
  30. package/dist/es/index.d.ts.map +1 -1
  31. package/dist/es/index.js.map +1 -1
  32. package/dist/es/infrastructure/cache/CacheManager.d.ts.map +1 -1
  33. package/dist/es/infrastructure/cache/CacheManager.js.map +1 -1
  34. package/dist/es/logger/logger.d.ts.map +1 -1
  35. package/dist/es/logger/logger.js +2 -3
  36. package/dist/es/logger/logger.js.map +1 -1
  37. package/dist/es/logger/sentry.js +1 -1
  38. package/dist/es/map/map.d.ts +24 -5
  39. package/dist/es/map/map.d.ts.map +1 -1
  40. package/dist/es/map/map.js +123 -49
  41. package/dist/es/map/map.js.map +1 -1
  42. package/dist/es/map/scene3DController.d.ts +31 -4
  43. package/dist/es/map/scene3DController.d.ts.map +1 -1
  44. package/dist/es/map/scene3DController.js +94 -9
  45. package/dist/es/map/scene3DController.js.map +1 -1
  46. package/dist/es/repository/repository.js +21 -21
  47. package/dist/es/repository/repository.js.map +1 -1
  48. package/dist/es/sdk.d.ts +26 -1
  49. package/dist/es/sdk.d.ts.map +1 -1
  50. package/dist/es/sdk.js +21 -21
  51. package/dist/es/sdk.js.map +1 -1
  52. package/dist/es/utils/3d.d.ts +32 -1
  53. package/dist/es/utils/3d.d.ts.map +1 -1
  54. package/dist/es/utils/3d.js +31 -0
  55. package/dist/es/utils/3d.js.map +1 -1
  56. package/dist/es/utils/opening-hours-helper.d.ts.map +1 -1
  57. package/dist/es/utils/opening-hours-helper.js +22 -19
  58. package/dist/es/utils/opening-hours-helper.js.map +1 -1
  59. package/dist/es/utils/semaphore.d.ts +1 -1
  60. package/dist/es/utils/semaphore.d.ts.map +1 -1
  61. package/dist/es/utils/semaphore.js.map +1 -1
  62. package/dist/umd/index.js +598 -199
  63. package/dist/umd/index.js.map +1 -1
  64. package/dist/umd/styles.css +0 -12
  65. package/dist/umd/styles.css.map +1 -1
  66. package/package.json +1 -1
@@ -15,6 +15,18 @@ const CIRCLE_LAYER_SUFFIXES = {
15
15
  belowLabels: "-below-labels",
16
16
  top: "-top",
17
17
  };
18
+ /**
19
+ * How many times a failed vector tile is reloaded before it is dropped from the retry
20
+ * queue. Bounded so a permanently unavailable tile stops being requested, while a tile
21
+ * that failed on a transient network error still gets several chances.
22
+ */
23
+ const MAX_TILE_RETRIES = 5;
24
+ /**
25
+ * Base delay for the retry backoff. Attempt N waits `BASE * 2^(N-1)` before it is
26
+ * eligible again — 1s, 2s, 4s, 8s — so a tile that keeps failing is retried ever less
27
+ * often instead of once per timer tick.
28
+ */
29
+ const TILE_RETRY_BASE_DELAY_MS = 1000;
18
30
  /** Fill/line layer ids for one circle render-order bucket. */
19
31
  function circleLayerIdsFor(order) {
20
32
  const suffix = CIRCLE_LAYER_SUFFIXES[order];
@@ -94,7 +106,7 @@ function parsePaddingValue(value, dimension) {
94
106
  if (!match) {
95
107
  return 0;
96
108
  }
97
- const numericValue = parseFloat(match[1]);
109
+ const numericValue = Number.parseFloat(match[1]);
98
110
  const unit = match[2] || "px";
99
111
  switch (unit) {
100
112
  case "px":
@@ -177,6 +189,12 @@ export class InternalMapVXMap extends Loggeable {
177
189
  this.enableHover = false;
178
190
  this.hoveredId = "unselected";
179
191
  this.failedTiles = new Set();
192
+ /**
193
+ * Retry bookkeeping per failed tile. A WeakMap so it can never outlive the tiles
194
+ * themselves — entries disappear with the tile objects, which is why none of the
195
+ * `failedTiles.clear()` paths have to reset it.
196
+ */
197
+ this.tileRetryState = new WeakMap();
180
198
  this.geoLocation = navigator.geolocation;
181
199
  /** Owns the deck.gl overlay + GLB scene; created on style load only when 3D is active. */
182
200
  this.scene3D = undefined;
@@ -295,7 +313,7 @@ export class InternalMapVXMap extends Loggeable {
295
313
  .catch(console.error);
296
314
  }
297
315
  onMapStyleLoaded(mapConfig, container, style) {
298
- var _a, _b, _c, _d, _e;
316
+ var _a, _b, _c, _d, _e, _f, _g;
299
317
  // Determine if service worker caching should be enabled
300
318
  const useServiceWorkerCaching = this.tileCacheConfig.enabled && this.tileCacheConfig.persistToServiceWorker;
301
319
  if (useServiceWorkerCaching) {
@@ -326,9 +344,9 @@ export class InternalMapVXMap extends Loggeable {
326
344
  (_d = (_c = this.map.touchZoomRotate) === null || _c === void 0 ? void 0 : _c.disableRotation) === null || _d === void 0 ? void 0 : _d.call(_c);
327
345
  }
328
346
  this.map.addControl(new NavigationControl({
329
- showCompass: mapConfig.showCompass !== undefined ? mapConfig.showCompass : true,
330
- showZoom: mapConfig.showZoom !== undefined ? mapConfig.showZoom : true,
331
- }), (_e = mapConfig.navigationPosition) !== null && _e !== void 0 ? _e : "top-right");
347
+ showCompass: (_e = mapConfig.showCompass) !== null && _e !== void 0 ? _e : true,
348
+ showZoom: (_f = mapConfig.showZoom) !== null && _f !== void 0 ? _f : true,
349
+ }), (_g = mapConfig.navigationPosition) !== null && _g !== void 0 ? _g : "top-right");
332
350
  this.map.on("load", () => {
333
351
  var _a, _b;
334
352
  // 3D libraries load asynchronously; the rest of the load flow does not wait on them.
@@ -429,7 +447,7 @@ export class InternalMapVXMap extends Loggeable {
429
447
  ]);
430
448
  }
431
449
  catch (error) {
432
- // Layer language update may fail if layer doesn't exist
450
+ this.debugLog("Layer language update failed (layer may not exist)", error);
433
451
  }
434
452
  });
435
453
  // indoor layers
@@ -453,7 +471,7 @@ export class InternalMapVXMap extends Loggeable {
453
471
  ]);
454
472
  }
455
473
  catch (error) {
456
- // Layer language update may fail if layer doesn't exist
474
+ this.debugLog("Layer language update failed (layer may not exist)", error);
457
475
  }
458
476
  });
459
477
  }
@@ -484,7 +502,7 @@ export class InternalMapVXMap extends Loggeable {
484
502
  this.logEvent("clearTileCache");
485
503
  }
486
504
  catch (error) {
487
- // Tile cache clear may fail if cache doesn't exist
505
+ this.debugLog("Tile cache clear failed (cache may not exist)", error);
488
506
  }
489
507
  }
490
508
  /**
@@ -501,6 +519,21 @@ export class InternalMapVXMap extends Loggeable {
501
519
  var _a;
502
520
  return (_a = this.currentFloor) !== null && _a !== void 0 ? _a : "";
503
521
  }
522
+ /**
523
+ * Sets the route path used for GLB route-transparency collision, so 3D models a route passes
524
+ * through fade and reveal the floor plan underneath. Called by {@link RouteController} whenever
525
+ * the set of drawn routes changes. Points are tagged with the floor their segment is drawn on;
526
+ * only points on the displayed floor participate. Pass an empty array to clear.
527
+ *
528
+ * No-op unless the 3D scene is active.
529
+ */
530
+ setRouteCollisionPath(points) {
531
+ var _a;
532
+ if (!this.is3DSceneActive()) {
533
+ return;
534
+ }
535
+ (_a = this.scene3D) === null || _a === void 0 ? void 0 : _a.setRouteCollisionPath(points);
536
+ }
504
537
  /**
505
538
  * Performs changes when parent place is updated
506
539
  * @param place new parent place
@@ -558,7 +591,7 @@ export class InternalMapVXMap extends Loggeable {
558
591
  (_d = options === null || options === void 0 ? void 0 : options.onComplete) === null || _d === void 0 ? void 0 : _d.call(options);
559
592
  }
560
593
  catch (error) {
561
- // Parent place/floor update may fail
594
+ this.debugLog("Parent place/floor update failed", error);
562
595
  }
563
596
  }
564
597
  setLang(lang) {
@@ -626,7 +659,7 @@ export class InternalMapVXMap extends Loggeable {
626
659
  return markerCreated.id;
627
660
  }
628
661
  catch (error) {
629
- throw new Error("Failed to add marker");
662
+ throw new Error("Failed to add marker", { cause: error });
630
663
  }
631
664
  }
632
665
  updateMarker(markerConfig) {
@@ -640,7 +673,7 @@ export class InternalMapVXMap extends Loggeable {
640
673
  return (_a = marker === null || marker === void 0 ? void 0 : marker.id) !== null && _a !== void 0 ? _a : "";
641
674
  }
642
675
  catch (error) {
643
- throw new Error("Failed to update marker");
676
+ throw new Error("Failed to update marker", { cause: error });
644
677
  }
645
678
  }
646
679
  updateMarkerPosition(markerId, newPosition) {
@@ -652,7 +685,7 @@ export class InternalMapVXMap extends Loggeable {
652
685
  this.logEvent("updateMarkerPosition");
653
686
  }
654
687
  catch (error) {
655
- throw new Error("Failed to update marker position");
688
+ throw new Error("Failed to update marker position", { cause: error });
656
689
  }
657
690
  }
658
691
  showMarker(markerId) {
@@ -664,7 +697,7 @@ export class InternalMapVXMap extends Loggeable {
664
697
  this.logEvent("showMarker");
665
698
  }
666
699
  catch (error) {
667
- // Show marker may fail if marker doesn't exist
700
+ this.debugLog("Show marker failed (marker may not exist)", error);
668
701
  }
669
702
  }
670
703
  hideMarker(markerId) {
@@ -676,7 +709,7 @@ export class InternalMapVXMap extends Loggeable {
676
709
  this.logEvent("hideMarker");
677
710
  }
678
711
  catch (error) {
679
- // Hide marker may fail if marker doesn't exist
712
+ this.debugLog("Hide marker failed (marker may not exist)", error);
680
713
  }
681
714
  }
682
715
  removeMarker(markerId) {
@@ -689,7 +722,7 @@ export class InternalMapVXMap extends Loggeable {
689
722
  this.logEvent("removeMarker");
690
723
  }
691
724
  catch (error) {
692
- throw new Error("Failed to remove marker");
725
+ throw new Error("Failed to remove marker", { cause: error });
693
726
  }
694
727
  }
695
728
  removeAllMarkers() {
@@ -701,7 +734,7 @@ export class InternalMapVXMap extends Loggeable {
701
734
  this.logEvent("removeAllMarkers");
702
735
  }
703
736
  catch (error) {
704
- throw new Error("Failed to remove all markers");
737
+ throw new Error("Failed to remove all markers", { cause: error });
705
738
  }
706
739
  }
707
740
  addCircle(circle) {
@@ -810,7 +843,7 @@ export class InternalMapVXMap extends Loggeable {
810
843
  this.logEvent("removeCircle");
811
844
  }
812
845
  catch (error) {
813
- throw new Error("Failed to remove circle");
846
+ throw new Error("Failed to remove circle", { cause: error });
814
847
  }
815
848
  }
816
849
  removeAllCircles() {
@@ -820,7 +853,7 @@ export class InternalMapVXMap extends Loggeable {
820
853
  this.logEvent("removeAllCircles");
821
854
  }
822
855
  catch (error) {
823
- throw new Error("Failed to remove all circles");
856
+ throw new Error("Failed to remove all circles", { cause: error });
824
857
  }
825
858
  }
826
859
  showCircle(circleId) {
@@ -835,6 +868,7 @@ export class InternalMapVXMap extends Loggeable {
835
868
  return true;
836
869
  }
837
870
  catch (error) {
871
+ this.debugLog("Show circle failed", error);
838
872
  return false;
839
873
  }
840
874
  }
@@ -850,6 +884,7 @@ export class InternalMapVXMap extends Loggeable {
850
884
  return true;
851
885
  }
852
886
  catch (error) {
887
+ this.debugLog("Hide circle failed", error);
853
888
  return false;
854
889
  }
855
890
  }
@@ -996,7 +1031,7 @@ export class InternalMapVXMap extends Loggeable {
996
1031
  }
997
1032
  }
998
1033
  catch (error) {
999
- // Style may not be loaded yet; the styledata listener re-adds the layers
1034
+ this.debugLog("Layers not added: style not loaded yet, the styledata listener will re-add them", error);
1000
1035
  }
1001
1036
  }
1002
1037
  /**
@@ -1015,7 +1050,7 @@ export class InternalMapVXMap extends Loggeable {
1015
1050
  source === null || source === void 0 ? void 0 : source.setData(this.circleFeatureCollection());
1016
1051
  }
1017
1052
  catch (error) {
1018
- // Source may not exist while a new style is loading
1053
+ this.debugLog("Source not found (a new style may still be loading)", error);
1019
1054
  }
1020
1055
  }
1021
1056
  /**
@@ -1045,7 +1080,7 @@ export class InternalMapVXMap extends Loggeable {
1045
1080
  }
1046
1081
  }
1047
1082
  catch (error) {
1048
- // Resize may fail if the map/container is not ready
1083
+ this.debugLog("Resize failed (map/container may not be ready)", error);
1049
1084
  }
1050
1085
  }
1051
1086
  updateMarkersTo(floorId) {
@@ -1206,6 +1241,7 @@ export class InternalMapVXMap extends Loggeable {
1206
1241
  (_a = options === null || options === void 0 ? void 0 : options.onComplete) === null || _a === void 0 ? void 0 : _a.call(options);
1207
1242
  }
1208
1243
  fitCoordinates(coordinates, options) {
1244
+ var _a, _b;
1209
1245
  if (coordinates.length === 0) {
1210
1246
  return;
1211
1247
  }
@@ -1247,8 +1283,8 @@ export class InternalMapVXMap extends Loggeable {
1247
1283
  const fitOptions = Object.assign(Object.assign({}, options), { padding });
1248
1284
  const bearing = this.map.getBearing();
1249
1285
  const pitch = this.map.getPitch();
1250
- fitOptions.bearing = fitOptions.bearing !== undefined ? fitOptions.bearing : bearing;
1251
- fitOptions.pitch = fitOptions.pitch !== undefined ? fitOptions.pitch : pitch;
1286
+ (_a = fitOptions.bearing) !== null && _a !== void 0 ? _a : (fitOptions.bearing = bearing);
1287
+ (_b = fitOptions.pitch) !== null && _b !== void 0 ? _b : (fitOptions.pitch = pitch);
1252
1288
  if (onComplete) {
1253
1289
  void this.map.once("moveend", onComplete);
1254
1290
  }
@@ -1256,7 +1292,7 @@ export class InternalMapVXMap extends Loggeable {
1256
1292
  }
1257
1293
  }
1258
1294
  easeToCoordinates(coordinates, options) {
1259
- var _a;
1295
+ var _a, _b, _c;
1260
1296
  if (coordinates.length === 0) {
1261
1297
  return;
1262
1298
  }
@@ -1275,14 +1311,14 @@ export class InternalMapVXMap extends Loggeable {
1275
1311
  this.map.easeTo(Object.assign(Object.assign({}, options), { center: { lat: coord.lat, lng: coord.lng }, padding }));
1276
1312
  return;
1277
1313
  }
1278
- const bearing = (options === null || options === void 0 ? void 0 : options.bearing) !== undefined ? options.bearing : this.map.getBearing();
1279
- const pitch = (options === null || options === void 0 ? void 0 : options.pitch) !== undefined ? options.pitch : this.map.getPitch();
1314
+ const bearing = (_a = options === null || options === void 0 ? void 0 : options.bearing) !== null && _a !== void 0 ? _a : this.map.getBearing();
1315
+ const pitch = (_b = options === null || options === void 0 ? void 0 : options.pitch) !== null && _b !== void 0 ? _b : this.map.getPitch();
1280
1316
  const cameraParams = {
1281
1317
  bearing,
1282
1318
  padding,
1283
1319
  containerWidth,
1284
1320
  containerHeight,
1285
- maxZoom: (_a = options === null || options === void 0 ? void 0 : options.maxZoom) !== null && _a !== void 0 ? _a : this.map.getMaxZoom(),
1321
+ maxZoom: (_c = options === null || options === void 0 ? void 0 : options.maxZoom) !== null && _c !== void 0 ? _c : this.map.getMaxZoom(),
1286
1322
  minZoom: this.map.getMinZoom(),
1287
1323
  };
1288
1324
  // Orthographic oriented fit (exact for pitch 0). maplibre's fitBounds only
@@ -1326,7 +1362,7 @@ export class InternalMapVXMap extends Loggeable {
1326
1362
  (_c = options === null || options === void 0 ? void 0 : options.onComplete) === null || _c === void 0 ? void 0 : _c.call(options);
1327
1363
  }
1328
1364
  catch (error) {
1329
- // Floor update may fail
1365
+ this.debugLog("Floor update failed", error);
1330
1366
  }
1331
1367
  }
1332
1368
  updatePlaceAndFloorForRouting(placeId, floorId) {
@@ -1339,7 +1375,7 @@ export class InternalMapVXMap extends Loggeable {
1339
1375
  this.logEvent("updatePlaceAndFloorForRouting", { floorId });
1340
1376
  }
1341
1377
  catch (error) {
1342
- // Place/floor update for routing may fail
1378
+ this.debugLog("Place/floor update for routing failed", error);
1343
1379
  }
1344
1380
  }
1345
1381
  async addRoute(config, drawConfig = {}) {
@@ -1352,7 +1388,7 @@ export class InternalMapVXMap extends Loggeable {
1352
1388
  return route;
1353
1389
  }
1354
1390
  catch (error) {
1355
- throw new Error("Error: Failed to add route");
1391
+ throw new Error("Error: Failed to add route", { cause: error });
1356
1392
  }
1357
1393
  }
1358
1394
  async addLiveRoute(config, drawConfig = {}) {
@@ -1389,8 +1425,8 @@ export class InternalMapVXMap extends Loggeable {
1389
1425
  return route;
1390
1426
  }
1391
1427
  catch (error) {
1392
- const cause = error instanceof Error ? error.message : String(error);
1393
- throw new Error(`Error: Failed to add route: ${cause}`);
1428
+ const reason = error instanceof Error ? error.message : String(error);
1429
+ throw new Error(`Error: Failed to add route: ${reason}`, { cause: error });
1394
1430
  }
1395
1431
  }
1396
1432
  updateRouteProgress(routeId, position, behindStyle = { type: "Solid", color: "#276EF1" }) {
@@ -1401,7 +1437,7 @@ export class InternalMapVXMap extends Loggeable {
1401
1437
  this.routeController.updateRouteProgress(routeId, position, behindConfig);
1402
1438
  }
1403
1439
  catch (error) {
1404
- // Update may fail if route doesn't exist
1440
+ this.debugLog("Route update failed (route may not exist)", error);
1405
1441
  }
1406
1442
  }
1407
1443
  removeRoute(routeId) {
@@ -1410,7 +1446,7 @@ export class InternalMapVXMap extends Loggeable {
1410
1446
  this.routeController.removeRoute(routeId);
1411
1447
  }
1412
1448
  catch (error) {
1413
- // Remove route may fail if no route exists
1449
+ this.debugLog("Remove route failed (no route may exist)", error);
1414
1450
  }
1415
1451
  this.syncLiveRouteId();
1416
1452
  }
@@ -1420,7 +1456,7 @@ export class InternalMapVXMap extends Loggeable {
1420
1456
  this.routeController.removeRoute(routeId);
1421
1457
  }
1422
1458
  catch (error) {
1423
- // Remove route may fail if route doesn't exist
1459
+ this.debugLog("Remove route failed (route may not exist)", error);
1424
1460
  }
1425
1461
  this.syncLiveRouteId();
1426
1462
  }
@@ -1430,7 +1466,7 @@ export class InternalMapVXMap extends Loggeable {
1430
1466
  this.routeController.removeAllRoutes();
1431
1467
  }
1432
1468
  catch (error) {
1433
- // Remove all routes may fail
1469
+ this.debugLog("Remove all routes failed", error);
1434
1470
  }
1435
1471
  this.syncLiveRouteId();
1436
1472
  }
@@ -1570,7 +1606,7 @@ export class InternalMapVXMap extends Loggeable {
1570
1606
  }
1571
1607
  }
1572
1608
  catch (error) {
1573
- // Place selection may fail if layer doesn't exist
1609
+ this.debugLog("Place selection failed (layer may not exist)", error);
1574
1610
  }
1575
1611
  }
1576
1612
  is3DStyle() {
@@ -1659,7 +1695,7 @@ export class InternalMapVXMap extends Loggeable {
1659
1695
  return route;
1660
1696
  }
1661
1697
  catch (error) {
1662
- throw new Error("Error: Failed to start animating route");
1698
+ throw new Error("Error: Failed to start animating route", { cause: error });
1663
1699
  }
1664
1700
  }
1665
1701
  async startAnimateRouteV2(routeConfig, drawConfig = {}, animationConfig = {}) {
@@ -1678,7 +1714,7 @@ export class InternalMapVXMap extends Loggeable {
1678
1714
  return route;
1679
1715
  }
1680
1716
  catch (error) {
1681
- throw new Error("Error: Failed to start animating route");
1717
+ throw new Error("Error: Failed to start animating route", { cause: error });
1682
1718
  }
1683
1719
  }
1684
1720
  pauseAnimateRoute(routeId) {
@@ -1687,7 +1723,7 @@ export class InternalMapVXMap extends Loggeable {
1687
1723
  this.logEvent("pauseAnimateRoute");
1688
1724
  }
1689
1725
  catch (error) {
1690
- // Pause animation may fail if no animation is running
1726
+ this.debugLog("Pause animation failed (no animation may be running)", error);
1691
1727
  }
1692
1728
  }
1693
1729
  resumeAnimateRoute(routeId) {
@@ -1696,7 +1732,7 @@ export class InternalMapVXMap extends Loggeable {
1696
1732
  this.logEvent("resumeAnimateRoute");
1697
1733
  }
1698
1734
  catch (error) {
1699
- // Resume animation may fail if no animation is paused
1735
+ this.debugLog("Resume animation failed (no animation may be paused)", error);
1700
1736
  }
1701
1737
  }
1702
1738
  restartAnimateRoute(routeId) {
@@ -1705,7 +1741,7 @@ export class InternalMapVXMap extends Loggeable {
1705
1741
  this.logEvent("restartAnimateRoute");
1706
1742
  }
1707
1743
  catch (error) {
1708
- // Restart animation may fail if no animation exists
1744
+ this.debugLog("Restart animation failed (no animation may exist)", error);
1709
1745
  }
1710
1746
  }
1711
1747
  stopAnimateRoute() {
@@ -1730,7 +1766,7 @@ export class InternalMapVXMap extends Loggeable {
1730
1766
  return route;
1731
1767
  }
1732
1768
  catch (error) {
1733
- throw new Error("Error: Failed to add step animation");
1769
+ throw new Error("Error: Failed to add step animation", { cause: error });
1734
1770
  }
1735
1771
  }
1736
1772
  async addStepAnimationV2(routeConfig, drawConfig = {}, animationConfig = {}) {
@@ -1750,7 +1786,7 @@ export class InternalMapVXMap extends Loggeable {
1750
1786
  return route;
1751
1787
  }
1752
1788
  catch (error) {
1753
- throw new Error("Error: Failed to add step animation");
1789
+ throw new Error("Error: Failed to add step animation", { cause: error });
1754
1790
  }
1755
1791
  }
1756
1792
  nextStepAnimation(routeId) {
@@ -1759,7 +1795,7 @@ export class InternalMapVXMap extends Loggeable {
1759
1795
  this.logEvent("nextStepAnimation");
1760
1796
  }
1761
1797
  catch (error) {
1762
- throw new Error("Error: Failed to animate step");
1798
+ throw new Error("Error: Failed to animate step", { cause: error });
1763
1799
  }
1764
1800
  }
1765
1801
  previousStepAnimation(routeId) {
@@ -1848,7 +1884,7 @@ export class InternalMapVXMap extends Loggeable {
1848
1884
  }
1849
1885
  }
1850
1886
  catch (error) {
1851
- throw new Error("Failed to add border to places");
1887
+ throw new Error("Failed to add border to places", { cause: error });
1852
1888
  }
1853
1889
  }
1854
1890
  createBorderedPlacesLayer() {
@@ -1951,16 +1987,54 @@ export class InternalMapVXMap extends Loggeable {
1951
1987
  // Remove invalid tiles from the set
1952
1988
  tilesToRemove.forEach((tile) => this.failedTiles.delete(tile));
1953
1989
  // Attempt to reload valid failed tiles
1990
+ const now = Date.now();
1954
1991
  tilesToRetry.forEach((tile) => {
1992
+ var _a;
1993
+ const retry = (_a = this.tileRetryState.get(tile)) !== null && _a !== void 0 ? _a : {
1994
+ attempts: 0,
1995
+ nextAttemptAt: 0,
1996
+ pending: false,
1997
+ };
1998
+ // Skip tiles with a reload still in flight or still inside their backoff window.
1999
+ if (retry.pending || now < retry.nextAttemptAt)
2000
+ return;
2001
+ const onFailure = (error) => {
2002
+ const attempts = retry.attempts + 1;
2003
+ if (attempts >= MAX_TILE_RETRIES) {
2004
+ this.debugLog(`Tile reload failed ${attempts} times, dropping it from the retry queue`, error);
2005
+ this.failedTiles.delete(tile);
2006
+ this.tileRetryState.delete(tile);
2007
+ return;
2008
+ }
2009
+ // Stay in the queue and try again once the backoff has elapsed.
2010
+ const backoffMs = TILE_RETRY_BASE_DELAY_MS * 2 ** (attempts - 1);
2011
+ this.debugLog(`Tile reload failed (attempt ${attempts}/${MAX_TILE_RETRIES}), retrying in ${backoffMs}ms`, error);
2012
+ this.tileRetryState.set(tile, {
2013
+ attempts,
2014
+ nextAttemptAt: Date.now() + backoffMs,
2015
+ pending: false,
2016
+ });
2017
+ };
1955
2018
  try {
1956
2019
  // Verify tile still exists and is in a retryable state
1957
2020
  if (tile && (tile.state === "errored" || tile.state === "expired")) {
1958
- source.loadTile(tile);
2021
+ this.tileRetryState.set(tile, Object.assign(Object.assign({}, retry), { pending: true }));
2022
+ // loadTile is async, so a rejection escapes this try/catch and has to be handled
2023
+ // on the promise itself - otherwise it surfaces as an unhandled rejection and the
2024
+ // tile is retried on every tick regardless of how often it has already failed.
2025
+ source
2026
+ .loadTile(tile)
2027
+ .then(() => {
2028
+ // Loaded: forget the attempt history so a later failure starts from zero. The
2029
+ // tile leaves the queue through the tilesToRemove branch on the next tick,
2030
+ // once its state is no longer errored/expired.
2031
+ this.tileRetryState.delete(tile);
2032
+ })
2033
+ .catch(onFailure);
1959
2034
  }
1960
2035
  }
1961
2036
  catch (error) {
1962
- // If reload fails, remove tile from retry queue - this is expected
1963
- this.failedTiles.delete(tile);
2037
+ onFailure(error);
1964
2038
  }
1965
2039
  });
1966
2040
  }, 1000);