@mapvx/web-js 3.6.0-dev.1 → 4.0.0-dev.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. package/README.md +6 -1
  2. package/dist/cjs/index.js +2 -95
  3. package/dist/cjs/index.js.LICENSE.txt +1 -0
  4. package/dist/cjs/index.js.map +1 -1
  5. package/dist/es/controllers/routeController.d.ts.map +1 -1
  6. package/dist/es/controllers/routeController.js +25 -8
  7. package/dist/es/controllers/routeController.js.map +1 -1
  8. package/dist/es/logger/sentry.js +1 -1
  9. package/dist/es/map/liveMapFitTransform.d.ts +11 -5
  10. package/dist/es/map/liveMapFitTransform.d.ts.map +1 -1
  11. package/dist/es/map/liveMapFitTransform.js +28 -19
  12. package/dist/es/map/liveMapFitTransform.js.map +1 -1
  13. package/dist/es/map/map.d.ts.map +1 -1
  14. package/dist/es/map/map.js +3 -6
  15. package/dist/es/map/map.js.map +1 -1
  16. package/dist/umd/index.js +932 -126
  17. package/dist/umd/index.js.map +1 -1
  18. package/dist/umd/styles.css +1 -1
  19. package/dist/umd/styles.css.map +1 -1
  20. package/package.json +7 -4
  21. package/dist/cjs/assets/icons.js +0 -85
  22. package/dist/cjs/assets/icons.js.map +0 -1
  23. package/dist/cjs/config/draco.js +0 -15
  24. package/dist/cjs/config/draco.js.map +0 -1
  25. package/dist/cjs/config/sdkConfig.js +0 -3
  26. package/dist/cjs/config/sdkConfig.js.map +0 -1
  27. package/dist/cjs/controllers/markerLayoutController.js +0 -359
  28. package/dist/cjs/controllers/markerLayoutController.js.map +0 -1
  29. package/dist/cjs/controllers/routeController.js +0 -1549
  30. package/dist/cjs/controllers/routeController.js.map +0 -1
  31. package/dist/cjs/domain/models/_internal.js +0 -14
  32. package/dist/cjs/domain/models/_internal.js.map +0 -1
  33. package/dist/cjs/domain/models/_rtl.js +0 -10
  34. package/dist/cjs/domain/models/_rtl.js.map +0 -1
  35. package/dist/cjs/domain/models/animation.js +0 -64
  36. package/dist/cjs/domain/models/animation.js.map +0 -1
  37. package/dist/cjs/domain/models/banner.js +0 -24
  38. package/dist/cjs/domain/models/banner.js.map +0 -1
  39. package/dist/cjs/domain/models/campaign.js +0 -73
  40. package/dist/cjs/domain/models/campaign.js.map +0 -1
  41. package/dist/cjs/domain/models/categories.js +0 -37
  42. package/dist/cjs/domain/models/categories.js.map +0 -1
  43. package/dist/cjs/domain/models/circle.js +0 -253
  44. package/dist/cjs/domain/models/circle.js.map +0 -1
  45. package/dist/cjs/domain/models/cityFilterOption.js +0 -3
  46. package/dist/cjs/domain/models/cityFilterOption.js.map +0 -1
  47. package/dist/cjs/domain/models/configuration.js +0 -3
  48. package/dist/cjs/domain/models/configuration.js.map +0 -1
  49. package/dist/cjs/domain/models/cssCustomization.js +0 -3
  50. package/dist/cjs/domain/models/cssCustomization.js.map +0 -1
  51. package/dist/cjs/domain/models/fitOptions.js +0 -3
  52. package/dist/cjs/domain/models/fitOptions.js.map +0 -1
  53. package/dist/cjs/domain/models/innerFloors.js +0 -3
  54. package/dist/cjs/domain/models/innerFloors.js.map +0 -1
  55. package/dist/cjs/domain/models/institution.js +0 -45
  56. package/dist/cjs/domain/models/institution.js.map +0 -1
  57. package/dist/cjs/domain/models/latLng.js +0 -3
  58. package/dist/cjs/domain/models/latLng.js.map +0 -1
  59. package/dist/cjs/domain/models/location.js +0 -3
  60. package/dist/cjs/domain/models/location.js.map +0 -1
  61. package/dist/cjs/domain/models/loggeable.js +0 -52
  62. package/dist/cjs/domain/models/loggeable.js.map +0 -1
  63. package/dist/cjs/domain/models/mapCamera.js +0 -3
  64. package/dist/cjs/domain/models/mapCamera.js.map +0 -1
  65. package/dist/cjs/domain/models/mapConfig.js +0 -37
  66. package/dist/cjs/domain/models/mapConfig.js.map +0 -1
  67. package/dist/cjs/domain/models/marker.js +0 -502
  68. package/dist/cjs/domain/models/marker.js.map +0 -1
  69. package/dist/cjs/domain/models/multilanguage.js +0 -17
  70. package/dist/cjs/domain/models/multilanguage.js.map +0 -1
  71. package/dist/cjs/domain/models/openingHours.js +0 -3
  72. package/dist/cjs/domain/models/openingHours.js.map +0 -1
  73. package/dist/cjs/domain/models/place.js +0 -127
  74. package/dist/cjs/domain/models/place.js.map +0 -1
  75. package/dist/cjs/domain/models/platform.js +0 -3
  76. package/dist/cjs/domain/models/platform.js.map +0 -1
  77. package/dist/cjs/domain/models/popover.js +0 -3
  78. package/dist/cjs/domain/models/popover.js.map +0 -1
  79. package/dist/cjs/domain/models/product.js +0 -3
  80. package/dist/cjs/domain/models/product.js.map +0 -1
  81. package/dist/cjs/domain/models/property.js +0 -104
  82. package/dist/cjs/domain/models/property.js.map +0 -1
  83. package/dist/cjs/domain/models/route.js +0 -166
  84. package/dist/cjs/domain/models/route.js.map +0 -1
  85. package/dist/cjs/domain/models/routeConfiguration.js +0 -82
  86. package/dist/cjs/domain/models/routeConfiguration.js.map +0 -1
  87. package/dist/cjs/domain/models/routeInstance.js +0 -30
  88. package/dist/cjs/domain/models/routeInstance.js.map +0 -1
  89. package/dist/cjs/domain/models/routeStyle.js +0 -3
  90. package/dist/cjs/domain/models/routeStyle.js.map +0 -1
  91. package/dist/cjs/domain/models/scene3D.js +0 -3
  92. package/dist/cjs/domain/models/scene3D.js.map +0 -1
  93. package/dist/cjs/domain/models/transport.js +0 -22
  94. package/dist/cjs/domain/models/transport.js.map +0 -1
  95. package/dist/cjs/domain/ports/ICache.js +0 -3
  96. package/dist/cjs/domain/ports/ICache.js.map +0 -1
  97. package/dist/cjs/domain/ports/IHttpClient.js +0 -3
  98. package/dist/cjs/domain/ports/IHttpClient.js.map +0 -1
  99. package/dist/cjs/domain/ports/ILogger.js +0 -23
  100. package/dist/cjs/domain/ports/ILogger.js.map +0 -1
  101. package/dist/cjs/domain/ports/index.js +0 -10
  102. package/dist/cjs/domain/ports/index.js.map +0 -1
  103. package/dist/cjs/infrastructure/cache/CacheManager.js +0 -258
  104. package/dist/cjs/infrastructure/cache/CacheManager.js.map +0 -1
  105. package/dist/cjs/infrastructure/cache/LRUCache.js +0 -153
  106. package/dist/cjs/infrastructure/cache/LRUCache.js.map +0 -1
  107. package/dist/cjs/infrastructure/cache/PersistentCache.js +0 -168
  108. package/dist/cjs/infrastructure/cache/PersistentCache.js.map +0 -1
  109. package/dist/cjs/infrastructure/cache/cacheConfig.js +0 -70
  110. package/dist/cjs/infrastructure/cache/cacheConfig.js.map +0 -1
  111. package/dist/cjs/infrastructure/http/FetchHttpClient.js +0 -75
  112. package/dist/cjs/infrastructure/http/FetchHttpClient.js.map +0 -1
  113. package/dist/cjs/infrastructure/logging/MVXLogger.js +0 -80
  114. package/dist/cjs/infrastructure/logging/MVXLogger.js.map +0 -1
  115. package/dist/cjs/interfaces/cache.js +0 -3
  116. package/dist/cjs/interfaces/cache.js.map +0 -1
  117. package/dist/cjs/interfaces/healthResponse.js +0 -3
  118. package/dist/cjs/interfaces/healthResponse.js.map +0 -1
  119. package/dist/cjs/interfaces/institutionResponse.js +0 -3
  120. package/dist/cjs/interfaces/institutionResponse.js.map +0 -1
  121. package/dist/cjs/interfaces/metadata.js +0 -3
  122. package/dist/cjs/interfaces/metadata.js.map +0 -1
  123. package/dist/cjs/interfaces/placeResponse.js +0 -3
  124. package/dist/cjs/interfaces/placeResponse.js.map +0 -1
  125. package/dist/cjs/interfaces/routeCacheResponse.js +0 -3
  126. package/dist/cjs/interfaces/routeCacheResponse.js.map +0 -1
  127. package/dist/cjs/interfaces/routeResponse.js +0 -3
  128. package/dist/cjs/interfaces/routeResponse.js.map +0 -1
  129. package/dist/cjs/interfaces/styleResponse.js +0 -3
  130. package/dist/cjs/interfaces/styleResponse.js.map +0 -1
  131. package/dist/cjs/logger/logger.js +0 -78
  132. package/dist/cjs/logger/logger.js.map +0 -1
  133. package/dist/cjs/logger/sentry.js +0 -81
  134. package/dist/cjs/logger/sentry.js.map +0 -1
  135. package/dist/cjs/map/fitCamera.js +0 -124
  136. package/dist/cjs/map/fitCamera.js.map +0 -1
  137. package/dist/cjs/map/liveMapFitTransform.js +0 -180
  138. package/dist/cjs/map/liveMapFitTransform.js.map +0 -1
  139. package/dist/cjs/map/map.js +0 -2766
  140. package/dist/cjs/map/map.js.map +0 -1
  141. package/dist/cjs/map/mapInteractionOptions.js +0 -73
  142. package/dist/cjs/map/mapInteractionOptions.js.map +0 -1
  143. package/dist/cjs/map/scene3DController.js +0 -849
  144. package/dist/cjs/map/scene3DController.js.map +0 -1
  145. package/dist/cjs/repository/repository.js +0 -694
  146. package/dist/cjs/repository/repository.js.map +0 -1
  147. package/dist/cjs/repository/requester.js +0 -771
  148. package/dist/cjs/repository/requester.js.map +0 -1
  149. package/dist/cjs/sdk.js +0 -438
  150. package/dist/cjs/sdk.js.map +0 -1
  151. package/dist/cjs/utils/3d.js +0 -245
  152. package/dist/cjs/utils/3d.js.map +0 -1
  153. package/dist/cjs/utils/deckLayers.js +0 -153
  154. package/dist/cjs/utils/deckLayers.js.map +0 -1
  155. package/dist/cjs/utils/error.js +0 -19
  156. package/dist/cjs/utils/error.js.map +0 -1
  157. package/dist/cjs/utils/images.js +0 -25
  158. package/dist/cjs/utils/images.js.map +0 -1
  159. package/dist/cjs/utils/loadGlb.js +0 -190
  160. package/dist/cjs/utils/loadGlb.js.map +0 -1
  161. package/dist/cjs/utils/mapvxHostname.js +0 -21
  162. package/dist/cjs/utils/mapvxHostname.js.map +0 -1
  163. package/dist/cjs/utils/nav-functions.js +0 -150
  164. package/dist/cjs/utils/nav-functions.js.map +0 -1
  165. package/dist/cjs/utils/opening-hours-helper.js +0 -914
  166. package/dist/cjs/utils/opening-hours-helper.js.map +0 -1
  167. package/dist/cjs/utils/preconnect.js +0 -131
  168. package/dist/cjs/utils/preconnect.js.map +0 -1
  169. package/dist/cjs/utils/route-utils.js +0 -57
  170. package/dist/cjs/utils/route-utils.js.map +0 -1
  171. package/dist/cjs/utils/semaphore.js +0 -143
  172. package/dist/cjs/utils/semaphore.js.map +0 -1
  173. package/dist/cjs/utils/three.js +0 -113
  174. package/dist/cjs/utils/three.js.map +0 -1
  175. package/dist/cjs/utils/threeScene.js +0 -126
  176. package/dist/cjs/utils/threeScene.js.map +0 -1
  177. package/dist/cjs/utils/update-css.js +0 -76
  178. package/dist/cjs/utils/update-css.js.map +0 -1
  179. package/dist/cjs/utils/utils.js +0 -104
  180. package/dist/cjs/utils/utils.js.map +0 -1
@@ -1,1549 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.RouteController = void 0;
4
- const marker_1 = require("../domain/models/marker");
5
- const routeInstance_1 = require("../domain/models/routeInstance");
6
- const nav_functions_1 = require("../utils/nav-functions");
7
- const utils_1 = require("../utils/utils");
8
- /**
9
- * Default padding values for auto-fit by place feature.
10
- * Used when padding is not explicitly provided in the animation configuration.
11
- */
12
- const DEFAULT_AUTO_FIT_PADDING = {
13
- top: 150,
14
- right: 150,
15
- bottom: 150,
16
- left: 150,
17
- };
18
- /**
19
- * Longest a step waits for its line to be uploaded before animating anyway, so
20
- * a stalled worker never freezes the route.
21
- *
22
- * A deadline rather than a frame count. `animateStep` is handed a `timestamp`
23
- * already, and a frame count would stretch to three seconds or more on the
24
- * 20-30 fps panels this wait exists for — the devices where a frozen marker at
25
- * every step boundary is most visible.
26
- */
27
- const MAX_SOURCE_WAIT_MS = 1500;
28
- /** Fully transparent: the part of a step line the reveal has not reached yet. */
29
- const TRANSPARENT_COLOR = "rgba(0, 0, 0, 0)";
30
- /**
31
- * Width of the reveal cut-off, as a fraction of the step line. One texel of
32
- * maplibre's 256-sample gradient ramp, i.e. the narrowest edge the ramp can
33
- * actually resolve.
34
- */
35
- const REVEAL_EDGE = 1 / 256;
36
- /**
37
- * Longest gap between two frames, in milliseconds, that still counts as
38
- * animation time.
39
- *
40
- * The frame loop charges the marker for however much wall clock has elapsed,
41
- * which is what makes a step take `stepTime` at any frame rate. A gap larger
42
- * than any frame rate can explain is not slow rendering, though — it is time
43
- * the animation did not run at all. `requestAnimationFrame` does not fire in a
44
- * backgrounded tab, so the first frame after the page comes back carries the
45
- * entire absence, and charging it advanced the draw by the whole gap: one
46
- * frame 10 s late at 50 m/s consumed exactly a hundred vertices in a single
47
- * `setData`, with the camera following the line as it jumped.
48
- *
49
- * Such a gap is therefore not charged (see `animateStep`). This is deliberately
50
- * NOT a clamp on how far one frame may advance: clamping would cap the catch-up
51
- * and hand back the frame-rate independence this loop exists to provide, and it
52
- * would still be wrong — the marker would sprint to catch up through time
53
- * nobody watched. Skipping the gap instead is what a pause does, which is what
54
- * an absence is.
55
- *
56
- * 500 ms is two frames per second. Real rendering does not sustain anything
57
- * near that low, so the threshold only ever separates absence from rendering,
58
- * and never shortens the catch-up of a genuinely slow frame.
59
- */
60
- const MAX_FRAME_GAP_MS = 500;
61
- /**
62
- * Represents the controller for routes on the map.
63
- * Supports multiple simultaneous routes with independent customizations,
64
- * animations, and lifecycle management.
65
- * @group Routes
66
- */
67
- class RouteController {
68
- constructor(map) {
69
- this.routes = new Map();
70
- this.mapvxMap = map;
71
- }
72
- get map() {
73
- return this.mapvxMap.map;
74
- }
75
- /**
76
- * Resolve a route instance by ID, or fall back to the last added route.
77
- */
78
- resolveInstance(routeId) {
79
- const id = routeId !== null && routeId !== void 0 ? routeId : this.lastAddedRouteId;
80
- return id ? this.routes.get(id) : undefined;
81
- }
82
- /**
83
- * Add MapLibre sources and layers for a single route instance.
84
- *
85
- * All three sources set `lineMetrics`: maplibre computes `line-progress` only
86
- * when a source asks it to. The CURRENT layer needs it to reveal the step
87
- * line as the marker advances, and `preparePaintForLayer` emits a
88
- * `line-progress` gradient for a GradientStyle on any of the three — without
89
- * it, a consumer setting a gradient route style silently got no gradient.
90
- */
91
- addSourcesAndLayersForRoute(instance) {
92
- this.map.addSource(instance.aheadId, {
93
- type: "geojson",
94
- lineMetrics: true,
95
- data: instance.aheadSourceData,
96
- });
97
- this.map.addLayer({
98
- id: instance.aheadId,
99
- type: "line",
100
- source: instance.aheadId,
101
- layout: {
102
- "line-join": "round",
103
- "line-cap": "round",
104
- },
105
- });
106
- this.map.addSource(instance.behindId, {
107
- type: "geojson",
108
- lineMetrics: true,
109
- data: instance.behindSourceData,
110
- });
111
- this.map.addLayer({
112
- id: instance.behindId,
113
- type: "line",
114
- source: instance.behindId,
115
- layout: {
116
- "line-join": "round",
117
- "line-cap": "round",
118
- },
119
- });
120
- this.map.addSource(instance.currentId, {
121
- type: "geojson",
122
- lineMetrics: true,
123
- data: instance.currentSourceData,
124
- });
125
- this.map.addLayer({
126
- id: instance.currentId,
127
- type: "line",
128
- source: instance.currentId,
129
- layout: {
130
- "line-join": "round",
131
- "line-cap": "round",
132
- },
133
- });
134
- }
135
- /**
136
- * Remove MapLibre sources and layers for a single route instance.
137
- */
138
- removeSourcesAndLayersForRoute(instance) {
139
- const ids = [instance.aheadId, instance.behindId, instance.currentId];
140
- for (const id of ids) {
141
- try {
142
- this.map.removeLayer(id);
143
- }
144
- catch (_a) {
145
- // Layer may not exist
146
- }
147
- try {
148
- this.map.removeSource(id);
149
- }
150
- catch (_b) {
151
- // Source may not exist
152
- }
153
- }
154
- }
155
- /**
156
- * Re-add sources and layers for all active routes.
157
- * Should be called when the map style is reloaded.
158
- */
159
- addSourcesAndLayers() {
160
- for (const instance of this.routes.values()) {
161
- this.addSourcesAndLayersForRoute(instance);
162
- this.restoreLayerPaint(instance);
163
- }
164
- this.syncRouteCollision();
165
- }
166
- /**
167
- * Re-apply the paint a style reload dropped.
168
- *
169
- * {@link addSourcesAndLayersForRoute} re-adds the layers with no paint at all, so
170
- * without this the route keeps its geometry but loses its colors — and the CURRENT
171
- * layer loses its `line-gradient`, which is the only thing hiding the part of the
172
- * step the marker has not reached yet: the whole step would be drawn at once.
173
- */
174
- restoreLayerPaint(instance) {
175
- var _a;
176
- for (const [layerId, drawConfig] of Object.entries(instance.layerDrawConfigs)) {
177
- this.setLayerPaint(instance, layerId, drawConfig);
178
- }
179
- // `?? 0`: a step whose geometry was loaded while the style was mid-swap
180
- // never got a reveal recorded, because `applyReveal` bails when there is no
181
- // layer to paint. Leaving the gradient off entirely then draws the whole
182
- // step at once in the traveled color, and the `awaitingSources` hold, the
183
- // rotation phase, the floor-change wait and a pause can each postpone the
184
- // frame that would correct it — indefinitely, in the case of a pause.
185
- // Revealing nothing is the safe default.
186
- const reveal = (_a = instance.revealFraction) !== null && _a !== void 0 ? _a : 0;
187
- // Force the update: `applyReveal` skips a fraction it believes is already on
188
- // the map, and the fresh layer has no gradient at all.
189
- instance.revealFraction = undefined;
190
- this.applyReveal(instance, reveal);
191
- }
192
- /**
193
- * Recompute the GLB route-transparency collision path from all active routes and push it to the
194
- * map. Every polyline point of every step is tagged with the floor its segment is drawn on (same
195
- * derivation as the route line's `floor_key`), so a 3D building only fades when a route is visibly
196
- * crossing it on the displayed floor. The map ignores this unless the 3D scene is active, so it is
197
- * safe to call unconditionally.
198
- */
199
- syncRouteCollision() {
200
- const points = [];
201
- for (const instance of this.routes.values()) {
202
- for (const step of instance.route.steps) {
203
- const floorKey = this.getFloorKeyForStep(step);
204
- for (const coord of step.coordinates) {
205
- points.push({ lng: coord.lng, lat: coord.lat, floorKey });
206
- }
207
- }
208
- }
209
- this.mapvxMap.setRouteCollisionPath(points);
210
- }
211
- /**
212
- * Add a route to the map.
213
- * @param route The route to add.
214
- * @param drawConfig The draw configuration for the route.
215
- */
216
- addRoute(route, drawConfig) {
217
- const instance = (0, routeInstance_1.createRouteInstance)(route);
218
- this.routes.set(instance.id, instance);
219
- this.lastAddedRouteId = instance.id;
220
- this.addSourcesAndLayersForRoute(instance);
221
- this.setSourceData(instance, instance.aheadId, route.steps);
222
- this.setLayerPaint(instance, instance.aheadId, drawConfig);
223
- this.updateLayerFilter(instance.aheadId);
224
- this.syncRouteCollision();
225
- }
226
- /**
227
- * Update the visual progress of a route based on an external user position.
228
- * Splits the route into behind (already traveled) and ahead (remaining) sections
229
- * at the point on the route closest to the given position.
230
- *
231
- * @param routeId The ID of the route to update.
232
- * @param position The current user position (will be projected onto the route).
233
- * @param behindDrawConfig The draw configuration for the behind (traveled) portion.
234
- */
235
- updateRouteProgress(routeId, position, behindDrawConfig) {
236
- const instance = this.routes.get(routeId);
237
- if (!instance)
238
- return;
239
- const route = instance.route;
240
- const steps = route.steps;
241
- // Find the closest point on the route to the position
242
- let bestDist = Infinity;
243
- let bestStepIndex = 0;
244
- let bestCoordIndex = 0;
245
- let bestFraction = 0;
246
- for (let s = 0; s < steps.length; s++) {
247
- const coords = steps[s].coordinates;
248
- for (let c = 0; c < coords.length - 1; c++) {
249
- const a = coords[c];
250
- const b = coords[c + 1];
251
- const { fraction, dist } = this.projectPointToSegment(position, a, b);
252
- if (dist < bestDist) {
253
- bestDist = dist;
254
- bestStepIndex = s;
255
- bestCoordIndex = c;
256
- bestFraction = fraction;
257
- }
258
- }
259
- }
260
- // Build behind features: all complete steps before the best step,
261
- // plus the partial segment of the current step up to the projected point
262
- const behindFeatures = [];
263
- for (let s = 0; s < bestStepIndex; s++) {
264
- behindFeatures.push({
265
- type: "Feature",
266
- properties: { floor_key: this.getFloorKeyForStep(steps[s]) },
267
- geometry: {
268
- type: "LineString",
269
- coordinates: steps[s].geoJsonCoordinates,
270
- },
271
- });
272
- }
273
- // Partial behind feature for the current step (up to projected point)
274
- const currentStep = steps[bestStepIndex];
275
- const currentCoords = currentStep.coordinates;
276
- const partialBehindCoords = [];
277
- for (let c = 0; c <= bestCoordIndex; c++) {
278
- partialBehindCoords.push([currentCoords[c].lng, currentCoords[c].lat]);
279
- }
280
- // Add the projected point
281
- const a = currentCoords[bestCoordIndex];
282
- const b = currentCoords[Math.min(bestCoordIndex + 1, currentCoords.length - 1)];
283
- const projLat = a.lat + bestFraction * (b.lat - a.lat);
284
- const projLng = a.lng + bestFraction * (b.lng - a.lng);
285
- partialBehindCoords.push([projLng, projLat]);
286
- if (partialBehindCoords.length >= 2) {
287
- behindFeatures.push({
288
- type: "Feature",
289
- properties: { floor_key: this.getFloorKeyForStep(currentStep) },
290
- geometry: { type: "LineString", coordinates: partialBehindCoords },
291
- });
292
- }
293
- // Build ahead features: partial remainder of current step + all remaining steps
294
- const aheadFeatures = [];
295
- const partialAheadCoords = [[projLng, projLat]];
296
- for (let c = bestCoordIndex + 1; c < currentCoords.length; c++) {
297
- partialAheadCoords.push([currentCoords[c].lng, currentCoords[c].lat]);
298
- }
299
- if (partialAheadCoords.length >= 2) {
300
- aheadFeatures.push({
301
- type: "Feature",
302
- properties: { floor_key: this.getFloorKeyForStep(currentStep) },
303
- geometry: { type: "LineString", coordinates: partialAheadCoords },
304
- });
305
- }
306
- for (let s = bestStepIndex + 1; s < steps.length; s++) {
307
- aheadFeatures.push({
308
- type: "Feature",
309
- properties: { floor_key: this.getFloorKeyForStep(steps[s]) },
310
- geometry: {
311
- type: "LineString",
312
- coordinates: steps[s].geoJsonCoordinates,
313
- },
314
- });
315
- }
316
- // Update sources
317
- instance.behindSourceData.features = behindFeatures;
318
- const behindSource = this.map.getSource(instance.behindId);
319
- behindSource === null || behindSource === void 0 ? void 0 : behindSource.setData(instance.behindSourceData);
320
- instance.aheadSourceData.features = aheadFeatures;
321
- const aheadSource = this.map.getSource(instance.aheadId);
322
- aheadSource === null || aheadSource === void 0 ? void 0 : aheadSource.setData(instance.aheadSourceData);
323
- // Ensure paint is set on the behind layer
324
- this.setLayerPaint(instance, instance.behindId, behindDrawConfig);
325
- this.updateLayerFilter(instance.behindId);
326
- this.updateLayerFilter(instance.aheadId);
327
- }
328
- /**
329
- * Project a point onto a line segment and return the clamped fraction and squared distance.
330
- */
331
- projectPointToSegment(p, a, b) {
332
- const dx = b.lng - a.lng;
333
- const dy = b.lat - a.lat;
334
- const lenSq = dx * dx + dy * dy;
335
- if (lenSq === 0) {
336
- const ddx = p.lng - a.lng;
337
- const ddy = p.lat - a.lat;
338
- return { fraction: 0, dist: ddx * ddx + ddy * ddy };
339
- }
340
- let t = ((p.lng - a.lng) * dx + (p.lat - a.lat) * dy) / lenSq;
341
- t = Math.max(0, Math.min(1, t));
342
- const projLng = a.lng + t * dx;
343
- const projLat = a.lat + t * dy;
344
- const ddx = p.lng - projLng;
345
- const ddy = p.lat - projLat;
346
- return { fraction: t, dist: ddx * ddx + ddy * ddy };
347
- }
348
- setSourceData(instance, id, steps) {
349
- const features = this.createFeaturesFrom(steps);
350
- const source = this.map.getSource(id);
351
- if (id === instance.aheadId) {
352
- instance.aheadSourceData.features = features;
353
- source === null || source === void 0 ? void 0 : source.setData(instance.aheadSourceData);
354
- }
355
- else if (id === instance.behindId) {
356
- instance.behindSourceData.features = features;
357
- source === null || source === void 0 ? void 0 : source.setData(instance.behindSourceData);
358
- }
359
- }
360
- /**
361
- * Upload the whole polyline of `step` as the CURRENT feature and reset its
362
- * reveal to zero. The line is then drawn progressively by moving a
363
- * `line-progress` cut-off (see {@link applyReveal}) instead of re-uploading a
364
- * growing geometry every frame: a paint change is applied synchronously, while
365
- * a `setData` goes through the worker and lands several frames after the DOM
366
- * marker already moved — on a slow device the marker visibly ran ahead of
367
- * its line.
368
- */
369
- loadStepGeometry(instance, step) {
370
- var _a;
371
- instance.currentSourceData.properties = {
372
- floor_key: this.getFloorKeyForStep(step),
373
- };
374
- const coordinates = (_a = step === null || step === void 0 ? void 0 : step.coordinates) !== null && _a !== void 0 ? _a : [];
375
- // A LineString needs at least two positions; anything shorter draws nothing.
376
- instance.currentSourceData.geometry.coordinates =
377
- coordinates.length >= 2 ? coordinates.map((c) => [c.lng, c.lat]) : [];
378
- const cumulative = [0];
379
- for (let i = 1; i < coordinates.length; i++) {
380
- cumulative.push(cumulative[i - 1] + nav_functions_1.NavFunctions.distance(coordinates[i - 1], coordinates[i]));
381
- }
382
- instance.stepMetrics = { cumulative, total: cumulative[cumulative.length - 1] };
383
- instance.revealFraction = undefined;
384
- this.applyReveal(instance, 0);
385
- const source = this.map.getSource(instance.currentId);
386
- source === null || source === void 0 ? void 0 : source.setData(instance.currentSourceData);
387
- }
388
- /**
389
- * Fraction of the step line up to `coordinatesIndex` plus `extraMeters` along
390
- * the following segment.
391
- */
392
- fractionAlongStep(instance, coordinatesIndex, extraMeters) {
393
- var _a;
394
- const metrics = instance.stepMetrics;
395
- if (!metrics || metrics.total <= 0)
396
- return 1;
397
- const index = Math.min(coordinatesIndex, metrics.cumulative.length - 1);
398
- return (((_a = metrics.cumulative[index]) !== null && _a !== void 0 ? _a : 0) + extraMeters) / metrics.total;
399
- }
400
- /**
401
- * Show the first `fraction` of the CURRENT step line: a `line-gradient` step on
402
- * `line-progress` that turns transparent past the cut-off. MapLibre treats it
403
- * as a plain paint update (a 256px ramp re-rendered on the next draw), so the
404
- * line head and the marker move in the same frame.
405
- *
406
- * The paint is set with `validate: false`: this runs on every animation frame and
407
- * the expression is built here, not taken from a consumer, so re-parsing and
408
- * re-validating it 60 times a second is pure overhead on a totem-class device.
409
- */
410
- applyReveal(instance, fraction) {
411
- const color = instance.currentLineColor;
412
- if (color === undefined)
413
- return;
414
- const clamped = Math.max(0, Math.min(1, fraction));
415
- if (instance.revealFraction === clamped)
416
- return;
417
- // A full style swap removes the route's sources and layers and re-adds them a
418
- // few frames later. Painting a layer that is not there fires a MapLibre error on
419
- // every animation frame; `revealFraction` is deliberately left untouched so
420
- // `restoreLayerPaint` re-applies this reveal once the layers are back.
421
- if (!this.hasSource(instance.currentId))
422
- return;
423
- instance.revealFraction = clamped;
424
- this.map.setPaintProperty(instance.currentId, "line-gradient", this.revealGradient(color, clamped), { validate: false });
425
- }
426
- /**
427
- * The `line-gradient` that shows the first `fraction` of the step line and
428
- * nothing after it.
429
- *
430
- * Deliberately built on `interpolate` rather than the `step` this reads like.
431
- * `LineStyleLayer._handleSpecialPaintPropertyUpdate` sets `stepInterpolant`
432
- * for any `step` root, and `stepInterpolant` swaps the gradient ramp from a
433
- * fixed 256 texels to
434
- * `clamp(nextPowerOfTwo(lineLength * 1024 * potentialOverzoom), 256, maxTextureSize)`
435
- * — 4096 to 16384 in practice. Every `setPaintProperty` here bumps
436
- * `gradientVersion`, so that ramp would be re-rendered and re-uploaded once
437
- * per visible tile per frame, which is the opposite of what this reveal is
438
- * for. An `interpolate` root keeps the ramp at 256 texels; the cost is a
439
- * one-texel soft edge at the cut-off instead of a hard one.
440
- */
441
- revealGradient(color, fraction) {
442
- if (fraction <= 0)
443
- return TRANSPARENT_COLOR;
444
- // `line-progress` reaches exactly 1 at the last vertex, so a fully revealed
445
- // line is just the line's own color, with no cut-off to express.
446
- if (fraction >= 1)
447
- return color;
448
- return [
449
- "interpolate",
450
- ["linear"],
451
- ["line-progress"],
452
- fraction,
453
- color,
454
- // `interpolate` clamps to its domain, so one texel past the cut-off is
455
- // enough to make everything beyond it transparent.
456
- Math.min(fraction + REVEAL_EDGE, 1),
457
- TRANSPARENT_COLOR,
458
- ];
459
- }
460
- /**
461
- * Whether the route's three sources have finished uploading their latest
462
- * data, i.e. what is on screen matches what was last set. Maps without
463
- * `isSourceLoaded` (test doubles) count as ready.
464
- */
465
- stepSourcesReady(instance) {
466
- try {
467
- // CURRENT alone. It carries the step being drawn, which is what has to be
468
- // on screen for the line, the floor and the marker to change together.
469
- // AHEAD and BEHIND hold the whole route and are rewritten in full at every
470
- // boundary, so waiting on them made the freeze at each step scale with the
471
- // route's total length rather than the step's — and on a long route that
472
- // is the difference between imperceptible and hitting the deadline.
473
- if (!this.map.getSource(instance.currentId))
474
- return false;
475
- return this.map.isSourceLoaded(instance.currentId) !== false;
476
- }
477
- catch (_a) {
478
- // The map is briefly style-less mid-swap and `getSource` throws there. This runs
479
- // inside a `requestAnimationFrame` callback, where an exception would kill the
480
- // animation loop for good, so treat it as "not ready yet" instead.
481
- return false;
482
- }
483
- }
484
- /** Whether `sourceId` is currently on the map (false while a style swap is in flight). */
485
- hasSource(sourceId) {
486
- try {
487
- return this.map.getSource(sourceId) != null;
488
- }
489
- catch (_a) {
490
- return false;
491
- }
492
- }
493
- /**
494
- * Apply a floor switch that was deferred until the next step's line was
495
- * uploaded, then put the marker where that step starts. Floor, line and
496
- * marker change in the same frame.
497
- */
498
- applyPendingFloor(instance) {
499
- var _a;
500
- const pending = (_a = instance.conditions) === null || _a === void 0 ? void 0 : _a.pendingFloor;
501
- if (!pending || !instance.conditions)
502
- return;
503
- instance.conditions.pendingFloor = undefined;
504
- this.updatePlaceFloorForRouting(instance, pending.placeId, pending.floorKey);
505
- this.setMarkerLngLat(instance, pending.location);
506
- }
507
- setLayerPaint(instance, layerId, drawConfig) {
508
- instance.layerDrawConfigs[layerId] = drawConfig;
509
- const paintConfig = this.preparePaintForLayer(drawConfig);
510
- for (const property in paintConfig) {
511
- this.map.setPaintProperty(layerId, property, paintConfig[property]);
512
- }
513
- }
514
- /**
515
- * Set the paint the step line is drawn with, resolving its color once.
516
- *
517
- * See {@link RouteInstance.currentLineColor}: resolving a gradient allocates
518
- * and can throw, and the reveal would otherwise redo it on every frame.
519
- */
520
- setCurrentDrawConfig(instance, drawConfig) {
521
- instance.currentDrawConfig = drawConfig;
522
- instance.currentLineColor = this.lineColorExpression(drawConfig);
523
- }
524
- /** The color of a route line: a solid color or a `line-progress` gradient ramp. */
525
- lineColorExpression(drawConfig) {
526
- if ("color" in drawConfig.routeStyle) {
527
- // Check for propriety unique to SolidStyle
528
- return drawConfig.routeStyle.color;
529
- }
530
- // Is GradienteStyle
531
- const { colors, positions } = drawConfig.routeStyle;
532
- const positionsToUse = positions !== null && positions !== void 0 ? positions : (0, utils_1.createPositionsForStops)(colors.length);
533
- if (positionsToUse.length !== colors.length) {
534
- throw new Error("Error: position array has different length than colors array");
535
- }
536
- const stops = (0, utils_1.createStops)(positionsToUse, colors);
537
- return ["interpolate", ["linear"], ["line-progress"], ...stops];
538
- }
539
- preparePaintForLayer(drawConfig) {
540
- const color = this.lineColorExpression(drawConfig);
541
- if (typeof color === "string") {
542
- return {
543
- "line-color": color,
544
- "line-gradient": null, // null to unset the existing value
545
- "line-width": drawConfig.polylineWidth,
546
- };
547
- }
548
- return {
549
- "line-color": null,
550
- "line-gradient": color,
551
- "line-width": drawConfig.polylineWidth,
552
- };
553
- }
554
- /**
555
- * Update the filter of the layer referenced with a ID.
556
- *
557
- * @see {@link https://maplibre.org/maplibre-gl-js/docs/API/classes/Map/#setfilter}
558
- * @param layerId The ID for the layer.
559
- */
560
- updateLayerFilter(layerId, floorId) {
561
- const newFloorId = floorId !== null && floorId !== void 0 ? floorId : this.mapvxMap.getCurrentFloor();
562
- if (newFloorId === undefined)
563
- return;
564
- // `getLayer` is a map lookup; `getStyle()` serialises every source and
565
- // layer in the style just to answer the same question. This runs three
566
- // times per route per floor change, and each preceding `setFilter`
567
- // invalidates maplibre's serialised-layer cache, so the later calls used to
568
- // re-serialise from scratch.
569
- if (this.map.getLayer(layerId) != null) {
570
- this.map.setFilter(layerId, ["==", ["get", "floor_key"], newFloorId]);
571
- }
572
- }
573
- /**
574
- * Determines the correct floor_key for a route step.
575
- * Outdoor steps (without startInsideFloor) should use the outdoor floor key,
576
- * not the current floor which might be indoor.
577
- * Elevator steps are always drawn on startInsideFloor; floor is updated to endInsideFloor
578
- * only when the step completes (see prepareForNextStep / animateStep).
579
- * @param step The route step to determine the floor_key for
580
- * @returns The floor_key string to use for the step
581
- */
582
- getFloorKeyForStep(step) {
583
- var _a, _b, _c, _d, _e, _f, _g;
584
- if (!step) {
585
- return this.mapvxMap.getCurrentFloor();
586
- }
587
- // If step has startInsideFloor, use it (unless it's a non-elevator floor change step)
588
- if (((_b = (_a = step.startInsideFloor) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0) > 0) {
589
- // If step changes floors (different start and end) and is not an elevator step,
590
- // use endInsideFloor so the step is drawn on the destination floor.
591
- // Elevator steps are drawn on startInsideFloor; floor updates on step completion.
592
- if (((_d = (_c = step.endInsideFloor) === null || _c === void 0 ? void 0 : _c.length) !== null && _d !== void 0 ? _d : 0) > 0 &&
593
- step.startInsideFloor !== step.endInsideFloor &&
594
- !step.elevatorStep) {
595
- return step.endInsideFloor;
596
- }
597
- return step.startInsideFloor;
598
- }
599
- // If step has endInsideFloor but no startInsideFloor (entering building), use endInsideFloor
600
- if (((_f = (_e = step.endInsideFloor) === null || _e === void 0 ? void 0 : _e.length) !== null && _f !== void 0 ? _f : 0) > 0) {
601
- return step.endInsideFloor;
602
- }
603
- // If step is outdoor (no startInsideFloor and no endInsideFloor), use outdoor floor key
604
- const innerFloors = this.mapvxMap.getInnerFloors();
605
- const outdoorFloor = innerFloors.find((floor) => floor.reachableFromGPS === true);
606
- return (_g = outdoorFloor === null || outdoorFloor === void 0 ? void 0 : outdoorFloor.key) !== null && _g !== void 0 ? _g : "";
607
- }
608
- createFeaturesFrom(steps) {
609
- const features = steps.map((step) => {
610
- return {
611
- type: "Feature",
612
- properties: { floor_key: this.getFloorKeyForStep(step) },
613
- geometry: {
614
- type: "LineString",
615
- coordinates: step.geoJsonCoordinates,
616
- },
617
- };
618
- });
619
- return features;
620
- }
621
- /**
622
- * Update the layers for all active routes.
623
- * @param floorId The floor ID to update the layers for.
624
- */
625
- updateRouteLayers(floorId) {
626
- for (const instance of this.routes.values()) {
627
- this.updateLayerFilter(instance.aheadId, floorId);
628
- this.updateLayerFilter(instance.behindId, floorId);
629
- this.updateLayerFilter(instance.currentId, floorId);
630
- }
631
- }
632
- /**
633
- * Every active route's marker. Used by `MarkerLayoutController` so marker
634
- * labels are never placed on top of a route's own marker.
635
- * @internal
636
- */
637
- getRouteMarkers() {
638
- const markers = [];
639
- for (const instance of this.routes.values()) {
640
- if (instance.routeMarker)
641
- markers.push(instance.routeMarker);
642
- }
643
- return markers;
644
- }
645
- /**
646
- * Update the visibility of route markers for all active routes.
647
- * @param floorId The floor ID to update the visibility for.
648
- */
649
- updateRouteMarkerVisibility(floorId) {
650
- var _a, _b, _c, _d;
651
- // An outdoor step gives its marker an empty floorId, which never equals a
652
- // real floor key — so on a venue with buildings the route arrow used to be
653
- // hidden the moment the map filtered by one, and only reappeared because
654
- // the animation re-attached the marker on its next frame. Outdoors is an
655
- // allowance markers and circles already make; the route arrow makes it too.
656
- const isOutdoor = this.mapvxMap.isOutdoorFloor(floorId);
657
- for (const instance of this.routes.values()) {
658
- const markerFloorId = (_b = (_a = instance.routeMarker) === null || _a === void 0 ? void 0 : _a.floorId) !== null && _b !== void 0 ? _b : "";
659
- markerFloorId === floorId || (isOutdoor && markerFloorId === "")
660
- ? (_c = instance.routeMarker) === null || _c === void 0 ? void 0 : _c.show(this.map)
661
- : (_d = instance.routeMarker) === null || _d === void 0 ? void 0 : _d.hide();
662
- }
663
- }
664
- updatePlaceFloorForRouting(instance, placeId, floorKey) {
665
- var _a;
666
- if (placeId && floorKey) {
667
- this.mapvxMap.updatePlaceAndFloorForRouting(placeId, floorKey);
668
- this.updateRouteLayers(floorKey);
669
- (_a = instance.routeMarker) === null || _a === void 0 ? void 0 : _a.setFloor(floorKey);
670
- this.updateRouteMarkerVisibility(floorKey);
671
- }
672
- }
673
- /**
674
- * Start the animation of a route.
675
- * @param route The route to animate.
676
- * @param drawConfig The draw configuration for the route.
677
- * @param animationConfig The animation configuration for the route.
678
- */
679
- startAnimating(route, drawConfig, animationConfig) {
680
- var _a, _b, _c;
681
- const instance = (0, routeInstance_1.createRouteInstance)(route);
682
- this.routes.set(instance.id, instance);
683
- this.lastAddedRouteId = instance.id;
684
- instance.animationStatus = "STARTED";
685
- instance.route = route;
686
- const firstStep = route.steps[0];
687
- this.addSourcesAndLayersForRoute(instance);
688
- this.updatePlaceFloorForRouting(instance, firstStep.startInsidePlace, firstStep.startInsideFloor);
689
- this.setSourceData(instance, instance.aheadId, route.steps);
690
- this.setSourceData(instance, instance.behindId, []);
691
- this.setLayerPaint(instance, instance.aheadId, drawConfig.aheadConfig);
692
- this.setLayerPaint(instance, instance.behindId, drawConfig.behindConfig);
693
- this.setLayerPaint(instance, instance.currentId, drawConfig.behindConfig);
694
- this.setCurrentDrawConfig(instance, drawConfig.behindConfig);
695
- this.loadStepGeometry(instance, firstStep);
696
- instance.animationConfig = animationConfig;
697
- instance.routeMarker = new marker_1.MarkerAttribute({
698
- coordinate: firstStep.startLocation,
699
- floorId: firstStep.startInsideFloor,
700
- icon: drawConfig.routeIcon,
701
- rotationAlignment: "map",
702
- });
703
- instance.routeMarker.addToMap(this.map);
704
- // Auto-fit by place if enabled
705
- if (((_a = animationConfig.autoFitByPlace) === null || _a === void 0 ? void 0 : _a.enabled) && route.steps.length > 0) {
706
- if (firstStep.startInsidePlace) {
707
- instance.currentPlaceId = firstStep.startInsidePlace;
708
- const fitOptions = {
709
- padding: (_b = animationConfig.autoFitByPlace.padding) !== null && _b !== void 0 ? _b : DEFAULT_AUTO_FIT_PADDING,
710
- };
711
- this.mapvxMap.fitRouteByPlace(route, firstStep.startInsidePlace, fitOptions);
712
- }
713
- }
714
- // The conditions go in before the consumer hears about the start, for the same reason
715
- // `restartAnimation` orders it that way. `animationStatus` is already "STARTED" here, so a
716
- // consumer that pauses from inside this callback — showing a modal per step, say — passed
717
- // `internalPause`'s status guard and then died on its `instance.conditions == null` check:
718
- // the pause was dropped in silence, the loop kept running, and because the status stayed
719
- // "STARTED" the matching `resumeAnimation()` bailed out too.
720
- instance.conditions = {
721
- index: 0,
722
- coordinatesIndex: 0,
723
- iconRotation: 0,
724
- rotatingIcon: true,
725
- startTime: undefined,
726
- progress: 0,
727
- resetTime: false,
728
- waitStartTime: undefined,
729
- awaitingSources: true,
730
- animationId: requestAnimationFrame((time) => this.animateStep(time, instance)),
731
- onFinish: (time) => this.prepareForNextStep(time, instance),
732
- };
733
- (_c = animationConfig.callBack) === null || _c === void 0 ? void 0 : _c.call(animationConfig, { isFinished: false, step: firstStep });
734
- this.syncRouteCollision();
735
- }
736
- /**
737
- * Tear one route instance down.
738
- *
739
- * `applyPendingFloorChange` decides what happens to a floor switch this
740
- * instance had decided on but not yet shown. Removing a single route mid-walk
741
- * applies it: the consumer may be removing the route from the step callback,
742
- * and dropping the switch would leave the map on the floor the route has
743
- * already left. Clearing every route does not — a floor switch is a visible
744
- * camera and filter change plus an `onFloorChange` to the consumer, and
745
- * `removeAllRoutes` is what teardown calls, where moving the map is
746
- * a side effect nobody asked for and a consumer that re-enters from
747
- * `onFloorChange` re-enters teardown.
748
- */
749
- clearRouteInstance(instance, applyPendingFloorChange = true) {
750
- var _a;
751
- if (applyPendingFloorChange) {
752
- this.applyPendingFloor(instance);
753
- }
754
- else if (instance.conditions) {
755
- instance.conditions.pendingFloor = undefined;
756
- }
757
- (_a = instance.routeMarker) === null || _a === void 0 ? void 0 : _a.remove();
758
- instance.routeMarker = undefined;
759
- this.setSourceData(instance, instance.aheadId, []);
760
- this.setSourceData(instance, instance.behindId, []);
761
- instance.currentSourceData.geometry.coordinates = [];
762
- const source = this.map.getSource(instance.currentId);
763
- source === null || source === void 0 ? void 0 : source.setData(instance.currentSourceData);
764
- if ((instance.animationStatus === "STARTED" || instance.stepAnimationStatus === "ANIMATING") &&
765
- instance.conditions != null) {
766
- cancelAnimationFrame(instance.conditions.animationId);
767
- }
768
- instance.animationStatus = "NO_ROUTE";
769
- instance.stepAnimationStatus = "NO_ROUTE";
770
- instance.conditions = undefined;
771
- instance.animationConfig = undefined;
772
- instance.currentPlaceId = undefined;
773
- }
774
- /**
775
- * Remove a route from the map.
776
- * @param routeId The ID of the route to remove. If not provided, removes the last added route.
777
- */
778
- removeRoute(routeId) {
779
- const instance = this.resolveInstance(routeId);
780
- if (!instance)
781
- return;
782
- this.clearRouteInstance(instance);
783
- this.removeSourcesAndLayersForRoute(instance);
784
- this.routes.delete(instance.id);
785
- if (this.lastAddedRouteId === instance.id) {
786
- const remaining = [...this.routes.keys()];
787
- this.lastAddedRouteId = remaining.length > 0 ? remaining[remaining.length - 1] : undefined;
788
- }
789
- this.syncRouteCollision();
790
- }
791
- /**
792
- * Remove all routes from the map.
793
- */
794
- removeAllRoutes() {
795
- for (const instance of this.routes.values()) {
796
- this.clearRouteInstance(instance, false);
797
- this.removeSourcesAndLayersForRoute(instance);
798
- }
799
- this.routes.clear();
800
- this.lastAddedRouteId = undefined;
801
- this.syncRouteCollision();
802
- }
803
- /**
804
- * Get the IDs of all active routes on the map.
805
- * @returns Array of route IDs.
806
- */
807
- getRouteIds() {
808
- return [...this.routes.keys()];
809
- }
810
- internalPause(instance) {
811
- if (instance.animationStatus != "STARTED")
812
- return;
813
- if (instance.conditions == null)
814
- return;
815
- cancelAnimationFrame(instance.conditions.animationId);
816
- instance.animationStatus = "PAUSED";
817
- }
818
- /**
819
- * Pause the animation of a route.
820
- * @param routeId The ID of the route. If not provided, pauses the last added route.
821
- */
822
- pauseAnimation(routeId) {
823
- const instance = this.resolveInstance(routeId);
824
- if (!instance)
825
- return;
826
- this.internalPause(instance);
827
- }
828
- internalResume(instance) {
829
- if (instance.animationStatus != "PAUSED")
830
- return;
831
- if (instance.conditions == null)
832
- return;
833
- instance.conditions.resetTime = true;
834
- instance.conditions.animationId = requestAnimationFrame((time) => this.animateStep(time, instance));
835
- instance.animationStatus = "STARTED";
836
- }
837
- /**
838
- * Resume the animation of a route.
839
- * @param routeId The ID of the route. If not provided, resumes the last added route.
840
- */
841
- resumeAnimation(routeId) {
842
- const instance = this.resolveInstance(routeId);
843
- if (!instance)
844
- return;
845
- this.internalResume(instance);
846
- }
847
- /**
848
- * Restart the animation of a route.
849
- * @param routeId The ID of the route. If not provided, restarts the last added route.
850
- */
851
- restartAnimation(routeId) {
852
- var _a, _b, _c, _d, _e;
853
- const instance = this.resolveInstance(routeId);
854
- if (!instance) {
855
- throw new Error("Does not exist animated route to restart");
856
- }
857
- if (instance.animationStatus === "NO_ROUTE") {
858
- throw new Error("Does not exist animated route to restart");
859
- }
860
- if (instance.conditions == null ||
861
- instance.animationConfig == null ||
862
- instance.route == null ||
863
- instance.routeMarker == null) {
864
- throw new Error("Error: conditions and animationConfig should not be null at this point");
865
- }
866
- if (instance.animationStatus === "STARTED") {
867
- cancelAnimationFrame(instance.conditions.animationId);
868
- }
869
- instance.animationStatus = "STARTED";
870
- const firstStep = instance.route.steps[0];
871
- this.updatePlaceFloorForRouting(instance, firstStep.startInsidePlace, firstStep.startInsideFloor);
872
- this.setSourceData(instance, instance.aheadId, instance.route.steps);
873
- this.setSourceData(instance, instance.behindId, []);
874
- this.loadStepGeometry(instance, firstStep);
875
- (_a = this.setMarkerRotation(instance, 0)) === null || _a === void 0 ? void 0 : _a.setLngLat(firstStep.startLocation).addTo(this.map);
876
- // Auto-fit by place if enabled
877
- if (((_b = instance.animationConfig.autoFitByPlace) === null || _b === void 0 ? void 0 : _b.enabled) && instance.route.steps.length > 0) {
878
- if (firstStep.startInsidePlace) {
879
- instance.currentPlaceId = firstStep.startInsidePlace;
880
- const fitOptions = {
881
- padding: (_c = instance.animationConfig.autoFitByPlace.padding) !== null && _c !== void 0 ? _c : DEFAULT_AUTO_FIT_PADDING,
882
- };
883
- this.mapvxMap.fitRouteByPlace(instance.route, firstStep.startInsidePlace, fitOptions);
884
- }
885
- }
886
- // The new conditions go in before the consumer hears about the restart.
887
- // Invoking the callback first left `instance.conditions` pointing at the
888
- // previous run, so a consumer that pauses from inside it — showing a modal
889
- // per step, say — cancelled a frame id that had already fired, marked the
890
- // instance PAUSED, and then had a live loop installed underneath it. The
891
- // next resume passed the "not paused" guard and started a second loop on
892
- // the same instance; each schedules its own successor, so the doubling was
893
- // permanent.
894
- instance.conditions = {
895
- index: 0,
896
- coordinatesIndex: 0,
897
- iconRotation: 0,
898
- rotatingIcon: true,
899
- startTime: undefined,
900
- progress: 0,
901
- resetTime: false,
902
- waitStartTime: undefined,
903
- awaitingSources: true,
904
- animationId: requestAnimationFrame((time) => this.animateStep(time, instance)),
905
- onFinish: (time) => this.prepareForNextStep(time, instance),
906
- };
907
- (_e = (_d = instance.animationConfig).callBack) === null || _e === void 0 ? void 0 : _e.call(_d, { isFinished: false, step: firstStep });
908
- }
909
- /**
910
- * Stop the animation of a route.
911
- * @param routeId The ID of the route. If not provided, stops the last added route.
912
- */
913
- stopAnimation(routeId) {
914
- const instance = this.resolveInstance(routeId);
915
- if (!instance)
916
- return;
917
- if (instance.animationStatus == "FINISHED") {
918
- return;
919
- }
920
- // todo hacer cosas para detener animación
921
- instance.animationStatus = "FINISHED";
922
- }
923
- /**
924
- * Add a step animation to a route.
925
- * @param route The route to animate.
926
- * @param drawConfig The draw configuration for the route.
927
- * @param animationConfig The animation configuration for the route.
928
- */
929
- addStepAnimation(route, drawConfig, animationConfig) {
930
- var _a, _b, _c;
931
- const instance = (0, routeInstance_1.createRouteInstance)(route);
932
- this.routes.set(instance.id, instance);
933
- this.lastAddedRouteId = instance.id;
934
- instance.stepAnimationStatus = "WAITING";
935
- instance.route = route;
936
- const firstStep = route.steps[0];
937
- this.addSourcesAndLayersForRoute(instance);
938
- this.updatePlaceFloorForRouting(instance, firstStep.startInsidePlace, firstStep.startInsideFloor);
939
- this.setSourceData(instance, instance.aheadId, route.steps);
940
- this.setSourceData(instance, instance.behindId, []);
941
- this.setLayerPaint(instance, instance.aheadId, drawConfig.aheadConfig);
942
- this.setLayerPaint(instance, instance.behindId, drawConfig.behindConfig);
943
- this.setLayerPaint(instance, instance.currentId, drawConfig.behindConfig);
944
- this.setCurrentDrawConfig(instance, drawConfig.behindConfig);
945
- this.loadStepGeometry(instance, firstStep);
946
- instance.animationConfig = animationConfig;
947
- instance.routeMarker = new marker_1.MarkerAttribute({
948
- coordinate: firstStep.startLocation,
949
- floorId: firstStep.startInsideFloor,
950
- icon: drawConfig.routeIcon,
951
- rotationAlignment: "map",
952
- });
953
- instance.routeMarker.addToMap(this.map);
954
- this.cameraFollow(instance, firstStep.startLocation);
955
- // Auto-fit by place if enabled
956
- if (((_a = animationConfig.autoFitByPlace) === null || _a === void 0 ? void 0 : _a.enabled) && route.steps.length > 0) {
957
- if (firstStep.startInsidePlace) {
958
- instance.currentPlaceId = firstStep.startInsidePlace;
959
- const fitOptions = {
960
- padding: (_b = animationConfig.autoFitByPlace.padding) !== null && _b !== void 0 ? _b : DEFAULT_AUTO_FIT_PADDING,
961
- };
962
- this.mapvxMap.fitRouteByPlace(route, firstStep.startInsidePlace, fitOptions);
963
- }
964
- }
965
- // The conditions go in before the consumer hears about the route, as they
966
- // do in `startAnimating`. Invoking the callback first left `conditions`
967
- // undefined, so a consumer advancing the route from it — the step-by-step
968
- // integration draws its first step this way — hit `nextStepAnimation`'s
969
- // null check and got an exception instead of a step.
970
- instance.conditions = {
971
- index: 0,
972
- coordinatesIndex: 0,
973
- iconRotation: 0,
974
- rotatingIcon: true,
975
- startTime: undefined,
976
- progress: 0,
977
- resetTime: false,
978
- waitStartTime: undefined,
979
- animationId: 0,
980
- isStepByStep: true,
981
- onFinish: () => {
982
- var _a, _b, _c, _d;
983
- if (instance.conditions == null ||
984
- instance.animationConfig == null ||
985
- instance.route == null) {
986
- throw new Error("Error: conditions, animationConfig or route should not be null at this point");
987
- }
988
- let { index } = instance.conditions;
989
- index++;
990
- const behindSteps = instance.route.steps.slice(0, index);
991
- const aheadSteps = instance.route.steps.slice(index);
992
- const step = instance.route.steps[index];
993
- this.setSourceData(instance, instance.behindId, behindSteps);
994
- this.setSourceData(instance, instance.aheadId, aheadSteps);
995
- this.loadStepGeometry(instance, step);
996
- instance.conditions.index = index;
997
- // The status settles before either callback, as everywhere else: no frame
998
- // is live once `animateStep` hands over to `onFinish`, so "WAITING" is
999
- // already true here. Writing it afterwards overwrote the "ANIMATING" of a
1000
- // consumer that chained the next step from this callback, and the
1001
- // following `nextStepAnimation` then skipped its cancel and installed a
1002
- // second self-sustaining loop on the same instance.
1003
- instance.stepAnimationStatus = "WAITING";
1004
- if (index < route.steps.length) {
1005
- const nextStep = route.steps[index];
1006
- // Auto-fit by place if enabled and place changed
1007
- if (((_a = animationConfig.autoFitByPlace) === null || _a === void 0 ? void 0 : _a.enabled) && nextStep && instance.route != null) {
1008
- const stepPlaceId = nextStep.startInsidePlace || nextStep.endInsidePlace;
1009
- if (stepPlaceId && stepPlaceId !== instance.currentPlaceId) {
1010
- instance.currentPlaceId = stepPlaceId;
1011
- const fitOptions = {
1012
- padding: (_b = animationConfig.autoFitByPlace.padding) !== null && _b !== void 0 ? _b : DEFAULT_AUTO_FIT_PADDING,
1013
- };
1014
- this.mapvxMap.fitRouteByPlace(instance.route, stepPlaceId, fitOptions);
1015
- }
1016
- }
1017
- (_c = animationConfig.callBack) === null || _c === void 0 ? void 0 : _c.call(animationConfig, { isFinished: false, step: nextStep });
1018
- }
1019
- else {
1020
- (_d = animationConfig.callBack) === null || _d === void 0 ? void 0 : _d.call(animationConfig, { isFinished: true, step: null });
1021
- }
1022
- },
1023
- };
1024
- (_c = animationConfig.callBack) === null || _c === void 0 ? void 0 : _c.call(animationConfig, { isFinished: false, step: firstStep });
1025
- this.syncRouteCollision();
1026
- }
1027
- /**
1028
- * Animate the next step of a route.
1029
- * @param routeId The ID of the route. If not provided, uses the last added route.
1030
- */
1031
- nextStepAnimation(routeId) {
1032
- const instance = this.resolveInstance(routeId);
1033
- if (!instance) {
1034
- throw new Error("Error: Does not exist step animation");
1035
- }
1036
- if (instance.stepAnimationStatus === "NO_ROUTE") {
1037
- throw new Error("Error: Does not exist step animation");
1038
- }
1039
- if (instance.conditions == null || instance.animationConfig == null || instance.route == null) {
1040
- throw new Error("Error: conditions, animationConfig or route should not be null at this point");
1041
- }
1042
- if (instance.stepAnimationStatus === "ANIMATING") {
1043
- cancelAnimationFrame(instance.conditions.animationId);
1044
- }
1045
- instance.stepAnimationStatus = "ANIMATING";
1046
- const { index } = instance.conditions;
1047
- if (index < instance.route.steps.length) {
1048
- instance.conditions.coordinatesIndex = 0;
1049
- instance.conditions.startTime = undefined;
1050
- instance.conditions.progress = 0;
1051
- instance.conditions.rotatingIcon = true;
1052
- instance.conditions.waitStartTime = undefined;
1053
- instance.conditions.animationId = requestAnimationFrame((time) => this.animateStep(time, instance));
1054
- }
1055
- else {
1056
- throw new Error("Error: Does not exist next step to animate");
1057
- }
1058
- }
1059
- /**
1060
- * Go to the previous step of a route.
1061
- * @param routeId The ID of the route. If not provided, uses the last added route.
1062
- */
1063
- previousStepAnimation(routeId) {
1064
- var _a, _b, _c, _d;
1065
- const instance = this.resolveInstance(routeId);
1066
- if (!instance) {
1067
- throw new Error("Error: Does not exist step animation");
1068
- }
1069
- if (instance.stepAnimationStatus === "NO_ROUTE") {
1070
- throw new Error("Error: Does not exist step animation");
1071
- }
1072
- if (instance.conditions == null || instance.animationConfig == null || instance.route == null) {
1073
- throw new Error("Error: conditions, animationConfig or route should not be null at this point");
1074
- }
1075
- if (instance.stepAnimationStatus === "ANIMATING") {
1076
- cancelAnimationFrame(instance.conditions.animationId);
1077
- }
1078
- instance.stepAnimationStatus = "WAITING";
1079
- let { index } = instance.conditions;
1080
- if (index > 0) {
1081
- index--;
1082
- const behindSteps = instance.route.steps.slice(0, index);
1083
- const aheadSteps = instance.route.steps.slice(index);
1084
- const step = instance.route.steps[index];
1085
- this.setSourceData(instance, instance.behindId, behindSteps);
1086
- this.setSourceData(instance, instance.aheadId, aheadSteps);
1087
- this.loadStepGeometry(instance, step);
1088
- if (step.startInsideFloor != step.endInsideFloor && step.startInsideFloor) {
1089
- this.updatePlaceFloorForRouting(instance, step.startInsidePlace, step.startInsideFloor);
1090
- }
1091
- instance.conditions.index = index;
1092
- instance.conditions.coordinatesIndex = 0;
1093
- instance.conditions.startTime = undefined;
1094
- instance.conditions.progress = 0;
1095
- instance.conditions.rotatingIcon = true;
1096
- instance.conditions.waitStartTime = undefined;
1097
- this.setMarkerLngLat(instance, step.startLocation);
1098
- const previous = behindSteps[behindSteps.length - 1];
1099
- if (previous != null) {
1100
- const coordinates = previous.coordinates.slice(-2);
1101
- const angle = nav_functions_1.NavFunctions.bearingInDegBetween(coordinates[0], coordinates[1]);
1102
- this.setMarkerRotation(instance, angle);
1103
- }
1104
- else {
1105
- this.setMarkerRotation(instance, 0);
1106
- }
1107
- // Auto-fit by place if enabled and place changed
1108
- if (((_a = instance.animationConfig.autoFitByPlace) === null || _a === void 0 ? void 0 : _a.enabled) && step && instance.route != null) {
1109
- const stepPlaceId = step.startInsidePlace || step.endInsidePlace;
1110
- if (stepPlaceId && stepPlaceId !== instance.currentPlaceId) {
1111
- instance.currentPlaceId = stepPlaceId;
1112
- const fitOptions = {
1113
- padding: (_b = instance.animationConfig.autoFitByPlace.padding) !== null && _b !== void 0 ? _b : DEFAULT_AUTO_FIT_PADDING,
1114
- };
1115
- this.mapvxMap.fitRouteByPlace(instance.route, stepPlaceId, fitOptions);
1116
- }
1117
- }
1118
- (_d = (_c = instance.animationConfig).callBack) === null || _d === void 0 ? void 0 : _d.call(_c, {
1119
- isFinished: false,
1120
- step,
1121
- });
1122
- }
1123
- else {
1124
- throw new Error("previous step doesn't exists to animate");
1125
- }
1126
- }
1127
- /**
1128
- * Center on the current step of a route.
1129
- * @param routeId The ID of the route. If not provided, uses the last added route.
1130
- */
1131
- centerOnCurrentStep(routeId) {
1132
- var _a;
1133
- const instance = this.resolveInstance(routeId);
1134
- if (!instance) {
1135
- throw new Error("Error: Does not exist step animation");
1136
- }
1137
- if (instance.stepAnimationStatus === "NO_ROUTE") {
1138
- throw new Error("Error: Does not exist step animation");
1139
- }
1140
- if (instance.conditions == null || instance.animationConfig == null || instance.route == null) {
1141
- throw new Error("Error: conditions, animationConfig or route should not be null at this point");
1142
- }
1143
- const currentMarkerPosition = (_a = instance.routeMarker) === null || _a === void 0 ? void 0 : _a.getLngLat();
1144
- if (currentMarkerPosition) {
1145
- this.map.panTo(currentMarkerPosition);
1146
- }
1147
- else {
1148
- throw new Error("Error: marker has no position set");
1149
- }
1150
- }
1151
- prepareForNextStep(timestamp, instance) {
1152
- var _a, _b, _c, _d, _e, _f;
1153
- if (instance.conditions == null || instance.animationConfig == null || instance.route == null) {
1154
- throw new Error("Error: conditions, animationConfig or route should not be null at this point");
1155
- }
1156
- const { index } = instance.conditions;
1157
- const behindSteps = instance.route.steps.slice(0, index + 1);
1158
- const aheadSteps = instance.route.steps.slice(index + 1);
1159
- this.setSourceData(instance, instance.behindId, behindSteps);
1160
- this.setSourceData(instance, instance.aheadId, aheadSteps);
1161
- if (index + 1 < instance.route.steps.length) {
1162
- const step = instance.route.steps[index + 1];
1163
- this.loadStepGeometry(instance, step);
1164
- instance.conditions.startTime = timestamp;
1165
- instance.conditions.index++;
1166
- instance.conditions.coordinatesIndex = 0;
1167
- instance.conditions.progress = 0;
1168
- instance.conditions.rotatingIcon = true;
1169
- // The step starts once its line (and the updated behind/ahead lines) are
1170
- // on screen; a floor switch decided by the previous step, or implied by a
1171
- // step that starts on another floor, is applied at that same moment.
1172
- instance.conditions.awaitingSources = true;
1173
- instance.conditions.awaitDeadline = undefined;
1174
- // Compared against the floor the PREVIOUS step ended on, not the map's current
1175
- // floor: the route decides its own floor changes, so a user who browsed to
1176
- // another floor mid-animation is not yanked back at every step boundary.
1177
- const previousStep = instance.route.steps[index];
1178
- // Against the previous step's *effective* floor, not its raw
1179
- // `endInsideFloor`. Venue data routinely carries `startInsideFloor` with
1180
- // no `endInsideFloor` — `getFloorKeyForStep` exists to resolve exactly
1181
- // that shape — and comparing against the raw field made every boundary
1182
- // on such a route look like a floor change: the map moved, the consumer
1183
- // got an `onFloorChange`, a settle poll ran and the step callback was
1184
- // deferred a frame, at each step. A user who had browsed to another floor
1185
- // was yanked back, which is what the deferral above is meant to avoid.
1186
- const previousFloor = this.getFloorKeyForStep(previousStep);
1187
- if (!instance.conditions.pendingFloor &&
1188
- step.startInsideFloor &&
1189
- step.startInsideFloor !== previousFloor) {
1190
- instance.conditions.pendingFloor = {
1191
- placeId: step.startInsidePlace,
1192
- floorKey: step.startInsideFloor,
1193
- location: step.startLocation,
1194
- };
1195
- }
1196
- if (!(0, nav_functions_1.compareLatLng)(instance.route.steps[index].endLocation, step.startLocation))
1197
- instance.conditions.nextFloorFirstStepIndex = index + 2;
1198
- instance.conditions.animationId = requestAnimationFrame((time) => this.animateStep(time, instance));
1199
- // Auto-fit by place if enabled and place changed
1200
- if (((_a = instance.animationConfig.autoFitByPlace) === null || _a === void 0 ? void 0 : _a.enabled) && step && instance.route != null) {
1201
- const stepPlaceId = step.startInsidePlace || step.endInsidePlace;
1202
- if (stepPlaceId && stepPlaceId !== instance.currentPlaceId) {
1203
- instance.currentPlaceId = stepPlaceId;
1204
- const fitOptions = {
1205
- padding: (_b = instance.animationConfig.autoFitByPlace.padding) !== null && _b !== void 0 ? _b : DEFAULT_AUTO_FIT_PADDING,
1206
- };
1207
- this.mapvxMap.fitRouteByPlace(instance.route, stepPlaceId, fitOptions);
1208
- }
1209
- }
1210
- if (instance.conditions.pendingFloor) {
1211
- // The switch lands a few frames later, in `animateStep`. Firing the callback
1212
- // now would report the step while the map is still on the previous floor —
1213
- // and a consumer that stops the route from it would drop the switch entirely.
1214
- instance.conditions.pendingStepCallback = step;
1215
- }
1216
- else {
1217
- (_d = (_c = instance.animationConfig).callBack) === null || _d === void 0 ? void 0 : _d.call(_c, { isFinished: false, step });
1218
- }
1219
- }
1220
- else {
1221
- // finishes animation
1222
- this.applyPendingFloor(instance);
1223
- // That reached `onFloorChange`, and a consumer removing the route from it
1224
- // has already had `clearRouteInstance` null `animationConfig` and settle
1225
- // the status at "NO_ROUTE". Both lines below write through it — the
1226
- // `?.` is on `callBack`, not on `animationConfig` — so without this the
1227
- // branch threw a TypeError out of the rAF callback, and overwrote a
1228
- // finished instance's status with "FINISHED". `animateStep`'s release
1229
- // path takes the same guard after the same call.
1230
- if (instance.conditions == null || instance.animationConfig == null)
1231
- return;
1232
- // The status settles before the consumer hears that the route finished,
1233
- // for the same reason `startAnimating` and `restartAnimation` order it
1234
- // that way. A consumer that relaunches from this callback — looping a
1235
- // kiosk demo is the ordinary case — got its fresh "STARTED" overwritten
1236
- // by the "FINISHED" written afterwards, and was left with a live loop the
1237
- // controller believed was over: `pauseAnimation` and `stopAnimation` both
1238
- // bailed on the status guard, and the next `restartAnimation` skipped its
1239
- // cancel and installed a second self-sustaining loop.
1240
- instance.animationStatus = "FINISHED";
1241
- (_f = (_e = instance.animationConfig).callBack) === null || _f === void 0 ? void 0 : _f.call(_e, { isFinished: true, step: null });
1242
- }
1243
- }
1244
- animateStep(timestamp, instance) {
1245
- var _a, _b, _c, _d;
1246
- if (instance.conditions == null || instance.animationConfig == null || instance.route == null) {
1247
- throw new Error("Error: conditions and animationConfig should not be null at this point");
1248
- }
1249
- let { startTime, resetTime, progress, waitStartTime } = instance.conditions;
1250
- const { coordinatesIndex, iconRotation, index, onFinish, rotatingIcon } = instance.conditions;
1251
- const { stepTime, minimumSpeed, iconRotationTime, changeFloorTime } = instance.animationConfig;
1252
- // How long this frame took to arrive. Recorded before any early return, so
1253
- // the next frame measures from the last one delivered rather than from the
1254
- // last one that drew.
1255
- const previousFrameTime = instance.conditions.lastFrameTime;
1256
- instance.conditions.lastFrameTime = timestamp;
1257
- if (startTime == null) {
1258
- instance.conditions.startTime = timestamp;
1259
- instance.conditions.animationId = requestAnimationFrame((time) => this.animateStep(time, instance));
1260
- return;
1261
- }
1262
- if (instance.conditions.awaitingSources) {
1263
- let deadline = (_a = instance.conditions.awaitDeadline) !== null && _a !== void 0 ? _a : timestamp + MAX_SOURCE_WAIT_MS;
1264
- // The deadline is wall clock, and this block returns before the frame-gap
1265
- // branch below can ever run, so it needs that branch's shift applied here
1266
- // — the same one `waitStartTime` gets, for the same reason. A backgrounded
1267
- // tab or a pause longer than the deadline would otherwise release the step
1268
- // on the first frame back without once asking whether its line is on
1269
- // screen, which is the desync the wait exists to prevent.
1270
- if (previousFrameTime != null && timestamp - previousFrameTime > MAX_FRAME_GAP_MS) {
1271
- deadline += timestamp - previousFrameTime;
1272
- }
1273
- instance.conditions.awaitDeadline = deadline;
1274
- if (!this.stepSourcesReady(instance) && timestamp < deadline) {
1275
- // Hold the clock: the step starts when its line can be seen.
1276
- instance.conditions.startTime = timestamp;
1277
- instance.conditions.animationId = requestAnimationFrame((time) => this.animateStep(time, instance));
1278
- return;
1279
- }
1280
- instance.conditions.awaitingSources = false;
1281
- instance.conditions.awaitDeadline = undefined;
1282
- instance.conditions.startTime = timestamp;
1283
- const heldStep = instance.conditions.pendingStepCallback;
1284
- instance.conditions.pendingStepCallback = undefined;
1285
- // The frame that releases the wait draws nothing: it queues its successor
1286
- // and hands control to the consumer, and that successor does the drawing.
1287
- //
1288
- // The queue has to come first. Both calls below reach consumer code —
1289
- // `applyPendingFloor` through the `onFloorChange` handler, and the held
1290
- // step callback directly — and a `pauseAnimation()` from either one
1291
- // cancels whatever id sits in `conditions.animationId`. While that id was
1292
- // the frame already executing, cancelling it did nothing: the loop kept
1293
- // running under a `PAUSED` status, and the matching `resumeAnimation()`
1294
- // passed its status guard and installed a second concurrent rAF chain on
1295
- // the same instance. Queueing first gives that pause a pending frame to
1296
- // cancel, and returning afterwards means this frame does not queue a
1297
- // replacement for the one it just cancelled.
1298
- instance.conditions.animationId = requestAnimationFrame((time) => this.animateStep(time, instance));
1299
- this.applyPendingFloor(instance);
1300
- // `onFloorChange` may have removed the route; everything below reads
1301
- // `conditions`.
1302
- if (instance.conditions == null || instance.animationConfig == null)
1303
- return;
1304
- if (heldStep) {
1305
- (_c = (_b = instance.animationConfig).callBack) === null || _c === void 0 ? void 0 : _c.call(_b, { isFinished: false, step: heldStep });
1306
- }
1307
- return;
1308
- }
1309
- if (resetTime) {
1310
- startTime = timestamp - progress;
1311
- instance.conditions.startTime = startTime;
1312
- resetTime = false;
1313
- instance.conditions.resetTime = resetTime;
1314
- }
1315
- else if (previousFrameTime != null && timestamp - previousFrameTime > MAX_FRAME_GAP_MS) {
1316
- // Absence, not slow rendering: see MAX_FRAME_GAP_MS. Shift the clock
1317
- // forward by the gap instead of charging it, exactly as resuming from a
1318
- // pause does above, so this frame redraws where the last one left off
1319
- // and the next resumes from there.
1320
- const gap = timestamp - previousFrameTime;
1321
- // What the previous frame had charged. Equal to `progress` whenever the
1322
- // two are in step, but the rotation branches above set `startTime` to
1323
- // the frame timestamp without resetting `progress`, so reading the
1324
- // stored value would carry a whole rotation's worth of time into the
1325
- // first frame back. Recomputed instead, which is 0 in exactly that case.
1326
- progress = previousFrameTime - startTime;
1327
- instance.conditions.progress = progress;
1328
- startTime = timestamp - progress;
1329
- instance.conditions.startTime = startTime;
1330
- // The floor-change wait runs on the raw timestamp rather than on
1331
- // `progress`, so it needs the same shift or an absence would satisfy it
1332
- // outright.
1333
- if (waitStartTime != null) {
1334
- waitStartTime += gap;
1335
- instance.conditions.waitStartTime = waitStartTime;
1336
- }
1337
- }
1338
- else {
1339
- progress = timestamp - startTime;
1340
- instance.conditions.progress = progress;
1341
- }
1342
- // Not const: the segment loop below rewinds it as it charges the clock for
1343
- // each segment it consumes within the frame.
1344
- let progressInSeconds = progress / 1000;
1345
- const step = instance.route.steps[index];
1346
- if (step.coordinates.length <= 0) {
1347
- onFinish === null || onFinish === void 0 ? void 0 : onFinish(timestamp);
1348
- return;
1349
- }
1350
- const start = step.coordinates[coordinatesIndex];
1351
- const end = step.coordinates[coordinatesIndex + 1];
1352
- if (start != null && end != null) {
1353
- if (rotatingIcon) {
1354
- const angle = nav_functions_1.NavFunctions.bearingInDegBetween(start, end);
1355
- if (iconRotationTime === 0 ||
1356
- instance.conditions.nextFloorFirstStepIndex === index ||
1357
- Math.abs(angle - instance.conditions.iconRotation) < 15) {
1358
- instance.conditions.nextFloorFirstStepIndex = undefined;
1359
- this.setMarkerRotation(instance, angle);
1360
- instance.conditions.startTime = timestamp;
1361
- instance.conditions.rotatingIcon = false;
1362
- instance.conditions.iconRotation = angle;
1363
- instance.conditions.animationId = requestAnimationFrame((time) => this.animateStep(time, instance));
1364
- return;
1365
- }
1366
- else if (progressInSeconds < iconRotationTime) {
1367
- const intermediateAngle = nav_functions_1.NavFunctions.intermediateAngleBetween(iconRotation, angle, progressInSeconds / iconRotationTime);
1368
- this.setMarkerRotation(instance, intermediateAngle);
1369
- instance.conditions.animationId = requestAnimationFrame((time) => this.animateStep(time, instance));
1370
- return;
1371
- }
1372
- else {
1373
- this.setMarkerRotation(instance, angle);
1374
- instance.conditions.startTime = timestamp;
1375
- instance.conditions.rotatingIcon = false;
1376
- instance.conditions.iconRotation = angle;
1377
- instance.conditions.animationId = requestAnimationFrame((time) => this.animateStep(time, instance));
1378
- return;
1379
- }
1380
- }
1381
- // There has to be a floor to switch *to*. An absent `startInsideFloor` is
1382
- // meaningful — that is a step entering the building from outdoors — but an
1383
- // absent `endInsideFloor` is not: venue data routinely omits it on a step
1384
- // that does not change floor, and reading that as "different" made every
1385
- // such step stall for `changeFloorTime` and then queue a switch whose
1386
- // `floorKey` was `undefined`. `updatePlaceFloorForRouting` drops that
1387
- // silently, but not before it has occupied the `pendingFloor` slot the
1388
- // step boundary needed for a real one.
1389
- if (step.endInsideFloor && step.startInsideFloor !== step.endInsideFloor) {
1390
- if (waitStartTime == null) {
1391
- instance.conditions.waitStartTime = timestamp;
1392
- instance.conditions.animationId = requestAnimationFrame((time) => this.animateStep(time, instance));
1393
- return;
1394
- }
1395
- if (timestamp - waitStartTime < changeFloorTime * 1000) {
1396
- instance.conditions.animationId = requestAnimationFrame((time) => this.animateStep(time, instance));
1397
- return;
1398
- }
1399
- else {
1400
- instance.conditions.waitStartTime = undefined;
1401
- if (instance.conditions.isStepByStep) {
1402
- // Step-by-step mode is user-driven: switch right away, as before.
1403
- this.updatePlaceFloorForRouting(instance, step.endInsidePlace, step.endInsideFloor);
1404
- this.setMarkerLngLat(instance, step.endLocation);
1405
- }
1406
- else {
1407
- // Defer the switch until the next step's line is uploaded, so floor,
1408
- // line and marker change together (see applyPendingFloor).
1409
- instance.conditions.pendingFloor = {
1410
- placeId: step.endInsidePlace,
1411
- floorKey: step.endInsideFloor,
1412
- location: step.endLocation,
1413
- };
1414
- }
1415
- onFinish === null || onFinish === void 0 ? void 0 : onFinish(timestamp);
1416
- return;
1417
- }
1418
- }
1419
- const speed = Math.max(step.distance / stepTime, minimumSpeed !== null && minimumSpeed !== void 0 ? minimumSpeed : 0);
1420
- // Consume every whole segment the elapsed time pays for inside this one
1421
- // frame, carrying the sub-segment remainder into the next.
1422
- //
1423
- // Advancing a single vertex per frame — and restarting the clock at each
1424
- // one, which threw the overshoot away — made a step's duration depend on
1425
- // the frame rate: it cost `stepTime` plus up to one frame per vertex,
1426
- // and once `speed * frameInterval` exceeded a segment's length it
1427
- // ignored `stepTime` altogether and collapsed to `vertexCount / fps`.
1428
- // Encoded polylines quantise near 1.1 m, so that regime was the common
1429
- // one, not the corner case.
1430
- //
1431
- // The step's whole polyline is already uploaded (see `loadStepGeometry`),
1432
- // so a frame commits nothing: it only moves the reveal cut-off, which is
1433
- // the same distance that moves the marker.
1434
- let segmentStart = start;
1435
- let segmentEnd = end;
1436
- let segmentDistance = nav_functions_1.NavFunctions.distance(segmentStart, segmentEnd);
1437
- while (speed * progressInSeconds > segmentDistance) {
1438
- if (instance.conditions.coordinatesIndex + 2 >= step.coordinates.length) {
1439
- // `segmentEnd` is the step's last vertex: reveal the line in full.
1440
- this.applyReveal(instance, this.fractionAlongStep(instance, instance.conditions.coordinatesIndex + 1, 0));
1441
- onFinish === null || onFinish === void 0 ? void 0 : onFinish(timestamp);
1442
- return;
1443
- }
1444
- // Charge the clock exactly what this segment cost instead of
1445
- // restarting it, so no time is lost at a vertex.
1446
- const segmentMs = (segmentDistance / speed) * 1000;
1447
- startTime += segmentMs;
1448
- progress -= segmentMs;
1449
- instance.conditions.startTime = startTime;
1450
- instance.conditions.progress = progress;
1451
- progressInSeconds = progress / 1000;
1452
- instance.conditions.coordinatesIndex++;
1453
- const next = step.coordinates[instance.conditions.coordinatesIndex + 1];
1454
- if (!(0, nav_functions_1.compareLatLng)(segmentEnd, next)) {
1455
- if (instance.conditions.isStepByStep) {
1456
- // Step-by-step mode animates the turn itself, on the rotation
1457
- // branch of the next frame. Stop consuming segments and let it.
1458
- //
1459
- // The clock restarts here because the rotation branch measures its
1460
- // own progress from `startTime`, and every other transition into it
1461
- // resets too (see `prepareForNextStep` and the floor-change hand-off).
1462
- // Carrying this frame's leftover sub-segment time over instead
1463
- // started the turn already part-way through — or skipped it outright
1464
- // once the remainder exceeded `iconRotationTime`. Before the segment
1465
- // loop existed the per-vertex path reset it as well.
1466
- instance.conditions.rotatingIcon = true;
1467
- instance.conditions.startTime = timestamp;
1468
- instance.conditions.progress = 0;
1469
- // The marker stops on the vertex it turns about, so the line does too.
1470
- this.applyReveal(instance, this.fractionAlongStep(instance, instance.conditions.coordinatesIndex, 0));
1471
- instance.conditions.animationId = requestAnimationFrame((time) => this.animateStep(time, instance));
1472
- return;
1473
- }
1474
- const nextAngle = nav_functions_1.NavFunctions.bearingInDegBetween(segmentEnd, next);
1475
- instance.conditions.iconRotation = nextAngle;
1476
- (_d = instance.routeMarker) === null || _d === void 0 ? void 0 : _d.setRotation(nextAngle);
1477
- }
1478
- segmentStart = segmentEnd;
1479
- segmentEnd = next;
1480
- segmentDistance = nav_functions_1.NavFunctions.distance(segmentStart, segmentEnd);
1481
- }
1482
- const traveled = Math.min(speed * progressInSeconds, segmentDistance);
1483
- const point = segmentDistance === 0
1484
- ? segmentEnd
1485
- : nav_functions_1.NavFunctions.intermediatePointBetween(segmentStart, segmentEnd, traveled / segmentDistance);
1486
- // The same distance drives the reveal cut-off and the marker, and the
1487
- // reveal is a synchronous paint update rather than a `setData` that lands
1488
- // several frames later, so the two cannot drift apart.
1489
- this.applyReveal(instance, this.fractionAlongStep(instance, instance.conditions.coordinatesIndex, traveled));
1490
- this.setMarkerLngLat(instance, point);
1491
- // Only when it has genuinely left the DOM. maplibre's `Marker.addTo`
1492
- // opens with `remove()`, so re-attaching an attached marker tears down
1493
- // and rebuilds 13 map listeners and detaches and re-appends the element.
1494
- // Unconditionally, that ran every frame — and quietly undid
1495
- // `updateRouteMarkerVisibility` hiding the arrow, which is the bug it was
1496
- // really covering for.
1497
- //
1498
- // `isConnected` alone cannot express that: `hide()` detaches the element
1499
- // through maplibre's `Marker.remove()`, so a marker hidden by a floor
1500
- // change reads as disconnected and was re-attached on the very next
1501
- // frame — the arrow reappeared on the wrong floor. Hidden-ness is an
1502
- // intent, so it is read from the flag `hide()`/`show()` maintain, and
1503
- // `isConnected` is left to answer only what it can: whether a marker
1504
- // nobody hid has fallen out of the DOM (a step transition removing it).
1505
- if (instance.routeMarker != null &&
1506
- !instance.routeMarker.hiddenOnPurpose &&
1507
- !instance.routeMarker.getElement().isConnected) {
1508
- instance.routeMarker.addTo(this.map);
1509
- }
1510
- instance.conditions.animationId = requestAnimationFrame((time) => this.animateStep(time, instance));
1511
- return;
1512
- }
1513
- else {
1514
- onFinish === null || onFinish === void 0 ? void 0 : onFinish(timestamp);
1515
- return;
1516
- }
1517
- }
1518
- setMarkerLngLat(instance, point) {
1519
- var _a, _b;
1520
- if ((_a = instance.conditions) === null || _a === void 0 ? void 0 : _a.isStepByStep) {
1521
- this.cameraFollow(instance, point);
1522
- }
1523
- (_b = instance.routeMarker) === null || _b === void 0 ? void 0 : _b.setLngLat(point);
1524
- }
1525
- setMarkerRotation(instance, angle) {
1526
- var _a, _b, _c, _d, _e;
1527
- if (((_a = instance.conditions) === null || _a === void 0 ? void 0 : _a.isStepByStep) && !((_b = instance.animationConfig) === null || _b === void 0 ? void 0 : _b.keepFixedBearing)) {
1528
- this.mapvxMap.updateCamera({
1529
- duration: (_c = instance.animationConfig) === null || _c === void 0 ? void 0 : _c.iconRotationTime,
1530
- animate: true,
1531
- bearing: angle,
1532
- center: (_d = instance.routeMarker) === null || _d === void 0 ? void 0 : _d.getLngLat(),
1533
- });
1534
- }
1535
- return (_e = instance.routeMarker) === null || _e === void 0 ? void 0 : _e.setRotation(angle);
1536
- }
1537
- setMarkerFloor(floorId, routeId) {
1538
- var _a;
1539
- const instance = this.resolveInstance(routeId);
1540
- (_a = instance === null || instance === void 0 ? void 0 : instance.routeMarker) === null || _a === void 0 ? void 0 : _a.setFloor(floorId);
1541
- }
1542
- cameraFollow(instance, point) {
1543
- var _a, _b, _c, _d;
1544
- const keepFixedBearing = (_b = (_a = instance.animationConfig) === null || _a === void 0 ? void 0 : _a.keepFixedBearing) !== null && _b !== void 0 ? _b : false;
1545
- this.mapvxMap.updateCamera(Object.assign({ center: point, animate: true, duration: (_c = instance.animationConfig) === null || _c === void 0 ? void 0 : _c.iconRotationTime }, (keepFixedBearing ? {} : { bearing: (_d = instance.routeMarker) === null || _d === void 0 ? void 0 : _d.getRotation() })));
1546
- }
1547
- }
1548
- exports.RouteController = RouteController;
1549
- //# sourceMappingURL=routeController.js.map