@mapvx/web-js 3.4.0-dev.2 → 3.5.0-dev.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/domain/models/campaign.js +73 -0
- package/dist/cjs/domain/models/campaign.js.map +1 -0
- package/dist/cjs/index.js +4 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/logger/sentry.js +1 -1
- package/dist/cjs/map/map.js +380 -14
- package/dist/cjs/map/map.js.map +1 -1
- package/dist/cjs/map/scene3DController.js +36 -0
- package/dist/cjs/map/scene3DController.js.map +1 -1
- package/dist/cjs/repository/repository.js +65 -4
- package/dist/cjs/repository/repository.js.map +1 -1
- package/dist/cjs/repository/requester.js +81 -9
- package/dist/cjs/repository/requester.js.map +1 -1
- package/dist/cjs/sdk.js +12 -0
- package/dist/cjs/sdk.js.map +1 -1
- package/dist/es/config/sdkConfig.d.ts +15 -0
- package/dist/es/config/sdkConfig.d.ts.map +1 -1
- package/dist/es/domain/models/campaign.d.ts +116 -0
- package/dist/es/domain/models/campaign.d.ts.map +1 -0
- package/dist/es/domain/models/campaign.js +69 -0
- package/dist/es/domain/models/campaign.js.map +1 -0
- package/dist/es/domain/models/mapConfig.d.ts +6 -0
- package/dist/es/domain/models/mapConfig.d.ts.map +1 -1
- package/dist/es/index.d.ts +2 -0
- package/dist/es/index.d.ts.map +1 -1
- package/dist/es/index.js +2 -0
- package/dist/es/index.js.map +1 -1
- package/dist/es/logger/sentry.js +1 -1
- package/dist/es/map/map.d.ts +145 -0
- package/dist/es/map/map.d.ts.map +1 -1
- package/dist/es/map/map.js +380 -14
- package/dist/es/map/map.js.map +1 -1
- package/dist/es/map/scene3DController.d.ts +20 -0
- package/dist/es/map/scene3DController.d.ts.map +1 -1
- package/dist/es/map/scene3DController.js +36 -0
- package/dist/es/map/scene3DController.js.map +1 -1
- package/dist/es/repository/repository.d.ts +16 -1
- package/dist/es/repository/repository.d.ts.map +1 -1
- package/dist/es/repository/repository.js +65 -4
- package/dist/es/repository/repository.js.map +1 -1
- package/dist/es/repository/requester.d.ts +30 -1
- package/dist/es/repository/requester.d.ts.map +1 -1
- package/dist/es/repository/requester.js +81 -9
- package/dist/es/repository/requester.js.map +1 -1
- package/dist/es/sdk.d.ts +11 -1
- package/dist/es/sdk.d.ts.map +1 -1
- package/dist/es/sdk.js +12 -0
- package/dist/es/sdk.js.map +1 -1
- package/dist/umd/index.js +1688 -838
- package/dist/umd/index.js.map +1 -1
- package/package.json +1 -1
package/dist/es/map/map.js
CHANGED
|
@@ -27,6 +27,13 @@ const MAX_TILE_RETRIES = 5;
|
|
|
27
27
|
* often instead of once per timer tick.
|
|
28
28
|
*/
|
|
29
29
|
const TILE_RETRY_BASE_DELAY_MS = 1000;
|
|
30
|
+
/**
|
|
31
|
+
* Safety deadline for {@link InternalMapVXMap.setStyle}'s `onStyleReady` callback. The
|
|
32
|
+
* callback normally fires on the map's `idle` event, but `idle` may never arrive (the
|
|
33
|
+
* style request failed, the swap never settles). Callers hide the map behind a loading
|
|
34
|
+
* overlay until the callback arrives, so it must be released no matter what.
|
|
35
|
+
*/
|
|
36
|
+
const STYLE_READY_TIMEOUT_MS = 4000;
|
|
30
37
|
/** Fill/line layer ids for one circle render-order bucket. */
|
|
31
38
|
function circleLayerIdsFor(order) {
|
|
32
39
|
const suffix = CIRCLE_LAYER_SUFFIXES[order];
|
|
@@ -171,7 +178,7 @@ export class InternalMapVXMap extends Loggeable {
|
|
|
171
178
|
* @returns A new instance of MapVXMap.
|
|
172
179
|
*/
|
|
173
180
|
constructor(mapConfig, container, token) {
|
|
174
|
-
var _a, _b, _c;
|
|
181
|
+
var _a, _b, _c, _d;
|
|
175
182
|
super();
|
|
176
183
|
/**
|
|
177
184
|
* Monotonic counter for {@link addLiveRoute} calls. Refreshes overlap (each one
|
|
@@ -185,6 +192,34 @@ export class InternalMapVXMap extends Loggeable {
|
|
|
185
192
|
this.subPlaces = [];
|
|
186
193
|
this.currentFloor = "";
|
|
187
194
|
this.baseFilters = {};
|
|
195
|
+
/**
|
|
196
|
+
* Layer ids whose declared (base) filter has already been resolved — either captured
|
|
197
|
+
* into {@link baseFilters} or confirmed absent.
|
|
198
|
+
*
|
|
199
|
+
* `baseFilters[id] === undefined` cannot carry that information on its own: it means
|
|
200
|
+
* both "not looked at yet" and "looked at, the layer declares no filter". Without the
|
|
201
|
+
* distinction, a later pass re-reads the layer's LIVE filter — which by then is the
|
|
202
|
+
* one this class wrote — and stores our own floor/public-place filter as if it were
|
|
203
|
+
* the style's, so the layer keeps filtering by a stale floor forever.
|
|
204
|
+
*
|
|
205
|
+
* An id lands here the moment its declared filter is read from the style spec, or the
|
|
206
|
+
* moment this class writes a filter to that layer, whichever happens first. Layers
|
|
207
|
+
* added asynchronously after a `setStyle` swap are in neither situation, so their
|
|
208
|
+
* genuinely pristine live filter is still captured on first touch.
|
|
209
|
+
*/
|
|
210
|
+
this.capturedBaseFilters = new Set();
|
|
211
|
+
/**
|
|
212
|
+
* Monotonic id of the most recently STARTED style swap. Every {@link setStyle} call
|
|
213
|
+
* captures its own value and checks it again in each async continuation, so a
|
|
214
|
+
* superseded swap can neither touch the shared bookkeeping (`styleReadyTimer`,
|
|
215
|
+
* `pendingStyleSwapListener`) nor apply its obsolete style to the live map.
|
|
216
|
+
*
|
|
217
|
+
* A per-call `readied` flag is not enough: the timer and the listener slot are
|
|
218
|
+
* single-valued instance state, so two overlapping swaps otherwise clobber each
|
|
219
|
+
* other's — one caller's overlay is released by the other's failure, or an
|
|
220
|
+
* abandoned listener re-seeds filters from layer ids the current style lacks.
|
|
221
|
+
*/
|
|
222
|
+
this.styleSwapGeneration = 0;
|
|
188
223
|
this.markers = [];
|
|
189
224
|
this.circles = [];
|
|
190
225
|
this.enableHover = false;
|
|
@@ -206,15 +241,18 @@ export class InternalMapVXMap extends Loggeable {
|
|
|
206
241
|
routeApiUrl: mapConfig.routeApiUrl,
|
|
207
242
|
healthApiUrl: mapConfig.healthApiUrl,
|
|
208
243
|
legacyApiUrl: mapConfig.legacyApiUrl,
|
|
244
|
+
portalApiUrl: mapConfig.portalApiUrl,
|
|
209
245
|
});
|
|
210
246
|
this.token = token;
|
|
247
|
+
this.authToken = mapConfig.authToken;
|
|
248
|
+
this.currentLang = (_c = mapConfig.lang) !== null && _c !== void 0 ? _c : "en";
|
|
211
249
|
this.parentPlaceId = mapConfig.parentPlaceId;
|
|
212
250
|
this.routeController = new RouteController(this);
|
|
213
251
|
this.userLocationMarker = undefined;
|
|
214
252
|
this.watchPositionID = undefined;
|
|
215
253
|
this.onFloorChange = mapConfig.onFloorChange;
|
|
216
254
|
this.onParentPlaceChange = mapConfig.onParentPlaceChange;
|
|
217
|
-
this.mode = (
|
|
255
|
+
this.mode = (_d = mapConfig.mode) !== null && _d !== void 0 ? _d : "2D";
|
|
218
256
|
this.scene3DConfig = mapConfig.scene3D;
|
|
219
257
|
this.initialCenter = mapConfig.center;
|
|
220
258
|
this.tileCacheConfig = (() => {
|
|
@@ -397,6 +435,17 @@ export class InternalMapVXMap extends Loggeable {
|
|
|
397
435
|
style.layers
|
|
398
436
|
.filter((layer) => layer.id.startsWith("indoor-"))
|
|
399
437
|
.forEach((layer) => {
|
|
438
|
+
// Seed from the STYLE SPEC — the only pristine source — and do it BEFORE the
|
|
439
|
+
// setFilter below overwrites the live filter. Reading it back afterwards would
|
|
440
|
+
// return our own `publicPlaceFilter` and store it as the layer's declared one,
|
|
441
|
+
// turning `public_place` from an OR into an AND and hiding every private
|
|
442
|
+
// feature on the selected floor. Every indoor layer is covered, not just
|
|
443
|
+
// fill/line/symbol: a `circle` layer is discriminated by its filter just the
|
|
444
|
+
// same, and dropping it makes the layer repaint every feature.
|
|
445
|
+
// `filter` is absent from the union member for `background` layers, hence the
|
|
446
|
+
// structural read rather than `layer.filter`.
|
|
447
|
+
const declared = layer.filter;
|
|
448
|
+
this.captureDeclaredBaseFilter(layer.id, declared);
|
|
400
449
|
const baseFilter = this.baseFilters[layer.id];
|
|
401
450
|
if (baseFilter != null) {
|
|
402
451
|
const finalFilter = ["all", baseFilter, publicPlaceFilter];
|
|
@@ -405,20 +454,29 @@ export class InternalMapVXMap extends Loggeable {
|
|
|
405
454
|
else {
|
|
406
455
|
this.map.setFilter(layer.id, publicPlaceFilter);
|
|
407
456
|
}
|
|
408
|
-
switch (layer.type) {
|
|
409
|
-
case "line":
|
|
410
|
-
case "symbol":
|
|
411
|
-
case "fill-extrusion":
|
|
412
|
-
case "fill":
|
|
413
|
-
if (layer.filter != null) {
|
|
414
|
-
this.baseFilters[layer.id] = layer.filter;
|
|
415
|
-
}
|
|
416
|
-
break;
|
|
417
|
-
default:
|
|
418
|
-
break;
|
|
419
|
-
}
|
|
420
457
|
});
|
|
421
458
|
}
|
|
459
|
+
/**
|
|
460
|
+
* Records a layer's declared (base) filter the first time the layer is seen, from an
|
|
461
|
+
* explicit value (a style-spec `filter`) when one is available, otherwise by reading
|
|
462
|
+
* the live filter — which is only pristine for layers this class has never written to.
|
|
463
|
+
*
|
|
464
|
+
* Marking happens even when no filter exists, so the layer is never revisited: on a
|
|
465
|
+
* second visit its live filter is one we produced, and storing that as the "declared"
|
|
466
|
+
* filter is what freezes a layer to a stale floor.
|
|
467
|
+
*
|
|
468
|
+
* @param layerId Id of the layer whose base filter should be resolved.
|
|
469
|
+
* @param declared Declared filter straight from the style spec, when the caller has it.
|
|
470
|
+
*/
|
|
471
|
+
captureDeclaredBaseFilter(layerId, declared) {
|
|
472
|
+
if (this.capturedBaseFilters.has(layerId))
|
|
473
|
+
return;
|
|
474
|
+
this.capturedBaseFilters.add(layerId);
|
|
475
|
+
const baseFilter = declared !== null && declared !== void 0 ? declared : this.safeGetFilter(layerId);
|
|
476
|
+
if (baseFilter != null) {
|
|
477
|
+
this.baseFilters[layerId] = baseFilter;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
422
480
|
setLayersForLanguage(lang) {
|
|
423
481
|
var _a, _b;
|
|
424
482
|
// outdoor layers
|
|
@@ -500,6 +558,14 @@ export class InternalMapVXMap extends Loggeable {
|
|
|
500
558
|
var _a;
|
|
501
559
|
this.circles = [];
|
|
502
560
|
this.markerLayoutController.destroy();
|
|
561
|
+
// Drop any in-flight `setStyle` bookkeeping while the map still exists: the
|
|
562
|
+
// listener has to be detached before `map.remove()`, and the safety timer must not
|
|
563
|
+
// outlive the map and call `onStyleReady` for a map that is already gone.
|
|
564
|
+
this.clearPendingStyleSwapListener();
|
|
565
|
+
if (this.styleReadyTimer) {
|
|
566
|
+
clearTimeout(this.styleReadyTimer);
|
|
567
|
+
this.styleReadyTimer = undefined;
|
|
568
|
+
}
|
|
503
569
|
// Dispose the 3D scene's GPU resources before the map (and its shared WebGL context) go away —
|
|
504
570
|
// MapLibre's own teardown never calls the custom GLB layer's `onRemove`.
|
|
505
571
|
(_a = this.scene3D) === null || _a === void 0 ? void 0 : _a.dispose();
|
|
@@ -574,6 +640,13 @@ export class InternalMapVXMap extends Loggeable {
|
|
|
574
640
|
const useServiceWorkerCaching = this.tileCacheConfig.enabled && this.tileCacheConfig.persistToServiceWorker;
|
|
575
641
|
const finalStyle = useServiceWorkerCaching ? this.transformStyleForCaching(style) : style;
|
|
576
642
|
this.map.setStyle(finalStyle);
|
|
643
|
+
// The new place's style declares its own filters for the same `indoor-*` layer
|
|
644
|
+
// ids. Without this reset every id is still marked captured from the previous
|
|
645
|
+
// place, so `captureDeclaredBaseFilter` early-returns for all of them and the
|
|
646
|
+
// previous place's expressions keep filtering the new style forever. (The
|
|
647
|
+
// `switch (layer.type)` block that used to re-read them on every style load was
|
|
648
|
+
// removed when capture became once-per-id, which is what made this necessary.)
|
|
649
|
+
this.resetBaseFilters();
|
|
577
650
|
this.whenStyleUpdates(finalStyle);
|
|
578
651
|
onStyleReady === null || onStyleReady === void 0 ? void 0 : onStyleReady();
|
|
579
652
|
})
|
|
@@ -613,6 +686,7 @@ export class InternalMapVXMap extends Loggeable {
|
|
|
613
686
|
}
|
|
614
687
|
setLang(lang) {
|
|
615
688
|
this.repository.setLang(lang);
|
|
689
|
+
this.currentLang = lang;
|
|
616
690
|
if (rtlLanguages.includes(lang)) {
|
|
617
691
|
this.setRTLSupport();
|
|
618
692
|
}
|
|
@@ -630,6 +704,201 @@ export class InternalMapVXMap extends Loggeable {
|
|
|
630
704
|
setParentPlace(place, updateStyle, onStyleReady) {
|
|
631
705
|
this.changeParentPlaceTo(place, updateStyle, onStyleReady);
|
|
632
706
|
}
|
|
707
|
+
setStyle(styleRef, styleTheme, onStyleReady) {
|
|
708
|
+
// Every swap gets a generation. All shared bookkeeping below is touched only while
|
|
709
|
+
// this swap is still the current one, so two overlapping calls cannot release each
|
|
710
|
+
// other's overlay or apply an obsolete style. `readied` stays per-call because each
|
|
711
|
+
// caller's callback must still fire exactly once.
|
|
712
|
+
const generation = ++this.styleSwapGeneration;
|
|
713
|
+
const isCurrent = () => this.styleSwapGeneration === generation;
|
|
714
|
+
let readied = false;
|
|
715
|
+
const reportReady = (error) => {
|
|
716
|
+
if (readied)
|
|
717
|
+
return;
|
|
718
|
+
readied = true;
|
|
719
|
+
// Only the current swap owns the shared timer; a superseded swap clearing it here
|
|
720
|
+
// would leave the live swap with no safety net (and its caller hidden forever).
|
|
721
|
+
if (isCurrent() && this.styleReadyTimer) {
|
|
722
|
+
clearTimeout(this.styleReadyTimer);
|
|
723
|
+
this.styleReadyTimer = undefined;
|
|
724
|
+
}
|
|
725
|
+
onStyleReady === null || onStyleReady === void 0 ? void 0 : onStyleReady(error);
|
|
726
|
+
};
|
|
727
|
+
// `onStyleReady` must fire on EVERY path — success, a rejected style request, or a
|
|
728
|
+
// swap that never settles — because callers keep the map hidden behind a loading
|
|
729
|
+
// overlay until it arrives. The safety timer is therefore armed before the request
|
|
730
|
+
// instead of inside `.then()`.
|
|
731
|
+
if (this.styleReadyTimer)
|
|
732
|
+
clearTimeout(this.styleReadyTimer);
|
|
733
|
+
this.styleReadyTimer = setTimeout(reportReady, STYLE_READY_TIMEOUT_MS);
|
|
734
|
+
// A previous swap may still hold a `styledata` listener whose style never reached the
|
|
735
|
+
// loaded state. Drop it before starting a new one: it captured an obsolete style and
|
|
736
|
+
// would otherwise fire on this swap, wiping `baseFilters` and calling `setFilter`
|
|
737
|
+
// with layer ids the new style does not have.
|
|
738
|
+
this.clearPendingStyleSwapListener();
|
|
739
|
+
// `this.map` is created asynchronously (see `onMapStyleLoaded`), so a `setStyle` call
|
|
740
|
+
// made right after `createMap` can land before it exists. Fail loudly instead of
|
|
741
|
+
// letting `this.map.setStyle` throw into `.catch`, which reported READY and left the
|
|
742
|
+
// caller revealing the previous style as if the swap had succeeded.
|
|
743
|
+
if (!this.map) {
|
|
744
|
+
const error = new Error("setStyle called before the map finished initializing; wait for the map's load event");
|
|
745
|
+
this.logError(error, { functionName: "setStyle" });
|
|
746
|
+
reportReady(error);
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
// When a user bearer token is present, the style is resolved through the
|
|
750
|
+
// bearer-authenticated endpoint, which authorizes by the user's client access
|
|
751
|
+
// rather than the api key's scopes — so a client's branded style can be
|
|
752
|
+
// previewed/applied without granting that place to a shared api key.
|
|
753
|
+
// Api-key consumers keep the `/api/sdk/v2/mapStyleJson` path.
|
|
754
|
+
const stylePromise = this.authToken
|
|
755
|
+
? this.repository.fetchAndParseMapStyleByBearer(this.parentPlaceId, this.authToken, styleRef, styleTheme)
|
|
756
|
+
: this.repository.fetchAndParseMapStyle(this.parentPlaceId, styleRef, styleTheme);
|
|
757
|
+
stylePromise
|
|
758
|
+
.then((style) => {
|
|
759
|
+
// A newer swap started while this style was in flight. Applying this one now
|
|
760
|
+
// would fight the newer style layer by layer, so drop it and let the newer swap
|
|
761
|
+
// own the map and its caller's callback.
|
|
762
|
+
if (!isCurrent())
|
|
763
|
+
return;
|
|
764
|
+
const useServiceWorkerCaching = this.tileCacheConfig.enabled && this.tileCacheConfig.persistToServiceWorker;
|
|
765
|
+
const finalStyle = useServiceWorkerCaching ? this.transformStyleForCaching(style) : style;
|
|
766
|
+
// Full (non-diff) swap so the previewed style completely replaces the base —
|
|
767
|
+
// a diff would keep old base layers and reappear on the next floor change.
|
|
768
|
+
this.map.setStyle(finalStyle, { diff: false });
|
|
769
|
+
// Drop the previous style's base filters SYNCHRONOUSLY, right where the layers
|
|
770
|
+
// they describe stop existing. They are keyed by layer id and persist across
|
|
771
|
+
// swaps, so any code reading them before the apply pass below (the `idle`
|
|
772
|
+
// handler's `filterByFloorKey`, a floor change racing the swap) would AND the
|
|
773
|
+
// previous style's discriminator into the new style's layers — the stale-filter
|
|
774
|
+
// washout this reset exists to prevent. Gating it on `isStyleLoaded` left exactly
|
|
775
|
+
// that window open. Fresh declared filters are re-captured lazily in
|
|
776
|
+
// `applyLayerFilters` as each indoor layer is first touched.
|
|
777
|
+
this.resetBaseFilters();
|
|
778
|
+
// Re-apply floor filters, route/circle layers and markers ONLY once the new
|
|
779
|
+
// style's sources/layers have finished loading; running them mid-reload throws
|
|
780
|
+
// (`getSource` on an undefined style) and the floor filter would not stick.
|
|
781
|
+
const applyOnceLoaded = () => {
|
|
782
|
+
// Superseded: detach and leave the map to the newer swap. Without this the
|
|
783
|
+
// listener stays attached (the slot it was tracked in has been overwritten)
|
|
784
|
+
// and re-seeds filters from layer ids the current style does not have, which
|
|
785
|
+
// makes maplibre throw and aborts the newer swap's own listener.
|
|
786
|
+
if (!isCurrent()) {
|
|
787
|
+
this.map.off("styledata", applyOnceLoaded);
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
790
|
+
if (!this.hasLoadedStyle())
|
|
791
|
+
return;
|
|
792
|
+
this.map.off("styledata", applyOnceLoaded);
|
|
793
|
+
if (this.pendingStyleSwapListener === applyOnceLoaded) {
|
|
794
|
+
this.pendingStyleSwapListener = undefined;
|
|
795
|
+
}
|
|
796
|
+
this.whenStyleUpdates(finalStyle);
|
|
797
|
+
// A non-diff swap replaces every layer, so anything applied ON TOP of the
|
|
798
|
+
// style — label language, caller-requested place highlights, the 3D scene's
|
|
799
|
+
// GLB layer and hidden-layer overrides — is gone and has to be replayed.
|
|
800
|
+
// `whenStyleUpdates` covers only floor filters, routes and circles.
|
|
801
|
+
this.restoreStyleOverlays();
|
|
802
|
+
this.logEvent("setStyle");
|
|
803
|
+
};
|
|
804
|
+
this.pendingStyleSwapListener = applyOnceLoaded;
|
|
805
|
+
this.map.on("styledata", applyOnceLoaded);
|
|
806
|
+
applyOnceLoaded();
|
|
807
|
+
// Re-apply the floor filter and report ready once the map is idle. This is
|
|
808
|
+
// intentionally OUTSIDE applyOnceLoaded's `isStyleLoaded` gate: the indoor
|
|
809
|
+
// polygon layers settle a beat after the swap (so the floor filter in
|
|
810
|
+
// whenStyleUpdates misses them and the venue paints every floor at once), and
|
|
811
|
+
// `idle` only fires once the style is loaded and its first tiles rendered — so
|
|
812
|
+
// filterByFloorKey is safe here and does not depend on the sometimes-flaky
|
|
813
|
+
// isStyleLoaded check. onStyleReady lets callers reveal the map only once it is
|
|
814
|
+
// settled; the timer armed above just clears a hanging overlay if idle never fires.
|
|
815
|
+
this.map.once("idle", () => {
|
|
816
|
+
// `once` cannot be cancelled, so a superseded swap's handler still fires —
|
|
817
|
+
// filtering here keeps it from re-filtering the newer style's floors and from
|
|
818
|
+
// reporting the newer swap's caller ready ahead of time.
|
|
819
|
+
if (!isCurrent())
|
|
820
|
+
return;
|
|
821
|
+
this.filterByFloorKey(this.currentFloor);
|
|
822
|
+
reportReady();
|
|
823
|
+
});
|
|
824
|
+
})
|
|
825
|
+
.catch((error) => {
|
|
826
|
+
// A superseded swap must not tear down the listener the CURRENT swap installed.
|
|
827
|
+
if (isCurrent()) {
|
|
828
|
+
this.clearPendingStyleSwapListener();
|
|
829
|
+
}
|
|
830
|
+
this.logError(error, { functionName: "setStyle" });
|
|
831
|
+
// Release the caller immediately instead of making it wait out the safety timer:
|
|
832
|
+
// the style will never arrive, so the overlay has nothing to wait for. The error
|
|
833
|
+
// is handed to the callback so a caller can tell a failed swap from an applied
|
|
834
|
+
// one — the previous style is still on screen either way.
|
|
835
|
+
reportReady(error instanceof Error ? error : new Error(String(error)));
|
|
836
|
+
});
|
|
837
|
+
}
|
|
838
|
+
/**
|
|
839
|
+
* Forgets every captured base filter so the next style's declared filters are read
|
|
840
|
+
* fresh from that style instead of inherited from the previous one.
|
|
841
|
+
*
|
|
842
|
+
* Both halves must move together: `baseFilters` holds the expressions and
|
|
843
|
+
* `capturedBaseFilters` records which layer ids have already been resolved, so
|
|
844
|
+
* clearing only the first would leave every id marked "already captured" and the new
|
|
845
|
+
* style's filters would never be read at all.
|
|
846
|
+
*/
|
|
847
|
+
resetBaseFilters() {
|
|
848
|
+
this.baseFilters = {};
|
|
849
|
+
this.capturedBaseFilters.clear();
|
|
850
|
+
}
|
|
851
|
+
/**
|
|
852
|
+
* Replays everything that lives ON TOP of the map style after a full (non-diff) swap
|
|
853
|
+
* has deleted it: label language expressions, caller-requested place highlights and
|
|
854
|
+
* the 3D scene's GLB layer / hidden-layer overrides.
|
|
855
|
+
*
|
|
856
|
+
* These are deliberately not part of `whenStyleUpdates`, which restores only
|
|
857
|
+
* style-derived state. These are decisions the SDK's callers made through `setLang`,
|
|
858
|
+
* `setPlacesAsSelected`, `addBorderToPlaces` and the 3D config, and nothing else would
|
|
859
|
+
* ever restore them.
|
|
860
|
+
*/
|
|
861
|
+
restoreStyleOverlays() {
|
|
862
|
+
// Each step is isolated: the style itself has already been applied by the time we get
|
|
863
|
+
// here, so one failing restore must neither skip the others nor bubble out into
|
|
864
|
+
// `setStyle`'s `.catch`, which would report the successful swap to `onStyleReady` as
|
|
865
|
+
// a failure. `setPlacesAsSelected` / `addBorderToPlaces` throw by contract.
|
|
866
|
+
const restore = (what, step) => {
|
|
867
|
+
try {
|
|
868
|
+
step();
|
|
869
|
+
}
|
|
870
|
+
catch (error) {
|
|
871
|
+
this.logError(error, { functionName: "restoreStyleOverlays", step: what });
|
|
872
|
+
}
|
|
873
|
+
};
|
|
874
|
+
restore("language", () => this.setLayersForLanguage(this.currentLang));
|
|
875
|
+
if (this.coloredPlacesRequest) {
|
|
876
|
+
const { placeIds, color } = this.coloredPlacesRequest;
|
|
877
|
+
restore("coloredPlaces", () => this.setPlacesAsSelected(placeIds, color));
|
|
878
|
+
}
|
|
879
|
+
if (this.borderedPlacesRequest) {
|
|
880
|
+
const { placeIds, color, width } = this.borderedPlacesRequest;
|
|
881
|
+
restore("borderedPlaces", () => this.addBorderToPlaces(placeIds, color, width));
|
|
882
|
+
}
|
|
883
|
+
// The swap removed the GLB custom layer, and MapLibre called its `onRemove`, which
|
|
884
|
+
// disposes the whole three.js scene. Nothing else re-adds it, so without this every
|
|
885
|
+
// 3D model disappears permanently and `hideLayers` stops being applied.
|
|
886
|
+
restore("scene3D", () => { var _a; return (_a = this.scene3D) === null || _a === void 0 ? void 0 : _a.reattachToStyle(); });
|
|
887
|
+
}
|
|
888
|
+
/**
|
|
889
|
+
* Removes the `styledata` listener left by an in-flight {@link setStyle} swap, if any.
|
|
890
|
+
* Called before starting a new swap and when one fails, so a listener holding an
|
|
891
|
+
* obsolete style can never fire against a different style's layers.
|
|
892
|
+
*/
|
|
893
|
+
clearPendingStyleSwapListener() {
|
|
894
|
+
var _a;
|
|
895
|
+
if (!this.pendingStyleSwapListener)
|
|
896
|
+
return;
|
|
897
|
+
// `this.map?` — a listener can only have been installed while the map existed, but
|
|
898
|
+
// this also runs from `destroyMap` and from `setStyle` before the map is created.
|
|
899
|
+
(_a = this.map) === null || _a === void 0 ? void 0 : _a.off("styledata", this.pendingStyleSwapListener);
|
|
900
|
+
this.pendingStyleSwapListener = undefined;
|
|
901
|
+
}
|
|
633
902
|
/**
|
|
634
903
|
* Removes current parent place and updates style to default
|
|
635
904
|
*/
|
|
@@ -1146,6 +1415,15 @@ export class InternalMapVXMap extends Loggeable {
|
|
|
1146
1415
|
});
|
|
1147
1416
|
const baseFloorId = (_c = this.innerFloors.find((floor) => floor.key === floorId)) === null || _c === void 0 ? void 0 : _c.baseFloor;
|
|
1148
1417
|
const indoorLayers = layers.filter((layer) => layer.id.startsWith("indoor-"));
|
|
1418
|
+
// Resolve each indoor layer's declared (base) filter before we start folding floor
|
|
1419
|
+
// filters into it — and before cloning it into a `base-indoor-*` layer, which copies
|
|
1420
|
+
// the base filter across. Layers added asynchronously after a `setStyle` swap are
|
|
1421
|
+
// missing when `setBaseFilters` runs, so this is where their still-pristine filter
|
|
1422
|
+
// is captured; without it the layer loses its own discrimination and repaints every
|
|
1423
|
+
// feature (the washed-out / blue-outline look after changing floors). Layers already
|
|
1424
|
+
// resolved are skipped, so a filter this class wrote is never mistaken for a
|
|
1425
|
+
// declared one.
|
|
1426
|
+
indoorLayers.forEach((layer) => this.captureDeclaredBaseFilter(layer.id));
|
|
1149
1427
|
if (baseFloorId != undefined) {
|
|
1150
1428
|
const firstLayerId = (_d = indoorLayers[0]) === null || _d === void 0 ? void 0 : _d.id;
|
|
1151
1429
|
indoorLayers
|
|
@@ -1162,8 +1440,33 @@ export class InternalMapVXMap extends Loggeable {
|
|
|
1162
1440
|
this.applyLayerFilters(layer, floorId);
|
|
1163
1441
|
});
|
|
1164
1442
|
}
|
|
1443
|
+
/**
|
|
1444
|
+
* Reads a layer's current filter, tolerating the layer having been removed or the
|
|
1445
|
+
* style being mid-swap (maplibre `getFilter` throws in those cases). Returns
|
|
1446
|
+
* `undefined` instead of throwing so base-filter capture never aborts floor filtering.
|
|
1447
|
+
*/
|
|
1448
|
+
safeGetFilter(layerId) {
|
|
1449
|
+
try {
|
|
1450
|
+
return this.map.getFilter(layerId);
|
|
1451
|
+
}
|
|
1452
|
+
catch (_a) {
|
|
1453
|
+
return undefined;
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1165
1456
|
applyLayerFilters(layer, floorId) {
|
|
1166
1457
|
var _a;
|
|
1458
|
+
// Resolve the layer's declared (base) filter the first time we touch it. Indoor
|
|
1459
|
+
// layers such as `indoor-polygon-room` share one source and are told apart only by
|
|
1460
|
+
// their own filter (e.g. `["in","room",["get","tags"]]`); we must fold that into the
|
|
1461
|
+
// floor filter or the layer repaints every feature. Some of these layers are added
|
|
1462
|
+
// asynchronously after a `setStyle` swap, so they are absent when `setBaseFilters`
|
|
1463
|
+
// runs — capturing here, while their live filter is still pristine, is what keeps a
|
|
1464
|
+
// floor change from wiping the style (the washed-out, blue-outline look).
|
|
1465
|
+
// `base-indoor-*` clones inherit their base filter from the layer they were cloned
|
|
1466
|
+
// from, so they are never read back.
|
|
1467
|
+
if (!layer.id.startsWith("base-indoor-")) {
|
|
1468
|
+
this.captureDeclaredBaseFilter(layer.id);
|
|
1469
|
+
}
|
|
1167
1470
|
let baseFilter = this.baseFilters[layer.id];
|
|
1168
1471
|
const floorIdString = floorId !== null && floorId !== void 0 ? floorId : "";
|
|
1169
1472
|
const floorFilter = [
|
|
@@ -1196,6 +1499,10 @@ export class InternalMapVXMap extends Loggeable {
|
|
|
1196
1499
|
(layer.type == "fill" || layer.type === "fill-extrusion")) {
|
|
1197
1500
|
customFilters.push(["!", ["in", this.hoveredId, ["get", "ref"]]]);
|
|
1198
1501
|
}
|
|
1502
|
+
// From here on this layer's live filter is ours, never the style's declared one.
|
|
1503
|
+
// Marking it keeps any later pass from reading it back as a base filter — that is
|
|
1504
|
+
// what used to AND the previous floor into the next one and blank the layer out.
|
|
1505
|
+
this.capturedBaseFilters.add(layer.id);
|
|
1199
1506
|
if (baseFilter != null) {
|
|
1200
1507
|
const finalFilter = ["all", baseFilter, customFilters];
|
|
1201
1508
|
this.map.setFilter(layer.id, finalFilter);
|
|
@@ -1622,6 +1929,7 @@ export class InternalMapVXMap extends Loggeable {
|
|
|
1622
1929
|
}
|
|
1623
1930
|
setPlacesAsSelected(placeIds, color) {
|
|
1624
1931
|
try {
|
|
1932
|
+
this.coloredPlacesRequest = { placeIds: [...placeIds], color };
|
|
1625
1933
|
if (!this.hasLayer("indoor-colored-places")) {
|
|
1626
1934
|
this.createColoredPlacesLayer();
|
|
1627
1935
|
}
|
|
@@ -1633,6 +1941,9 @@ export class InternalMapVXMap extends Loggeable {
|
|
|
1633
1941
|
const placeFilter = ["any", ...coloredPlacesFilters];
|
|
1634
1942
|
const condition = ["all", placeFilter, basicFilter];
|
|
1635
1943
|
this.map.setFilter("indoor-colored-places", condition);
|
|
1944
|
+
// Caller-defined base filter: mark it resolved so no later capture can
|
|
1945
|
+
// overwrite this customization with the layer's style-declared filter.
|
|
1946
|
+
this.capturedBaseFilters.add("indoor-colored-places");
|
|
1636
1947
|
this.baseFilters["indoor-colored-places"] = placeFilter;
|
|
1637
1948
|
if (color !== undefined) {
|
|
1638
1949
|
const layer = this.map.getLayer("indoor-colored-places");
|
|
@@ -1716,8 +2027,12 @@ export class InternalMapVXMap extends Loggeable {
|
|
|
1716
2027
|
return layer !== undefined;
|
|
1717
2028
|
}
|
|
1718
2029
|
clearColoredPlaces() {
|
|
2030
|
+
this.coloredPlacesRequest = undefined;
|
|
1719
2031
|
if (this.hasLayer("indoor-colored-places")) {
|
|
1720
2032
|
const condition = ["==", ["get", "ref"], "unselected"];
|
|
2033
|
+
// Caller-defined base filter: mark it resolved so no later capture can
|
|
2034
|
+
// overwrite this customization with the layer's style-declared filter.
|
|
2035
|
+
this.capturedBaseFilters.add("indoor-colored-places");
|
|
1721
2036
|
this.baseFilters["indoor-colored-places"] = condition;
|
|
1722
2037
|
this.map.setFilter("indoor-colored-places", condition);
|
|
1723
2038
|
}
|
|
@@ -1912,6 +2227,7 @@ export class InternalMapVXMap extends Loggeable {
|
|
|
1912
2227
|
}
|
|
1913
2228
|
addBorderToPlaces(placeIds, color, width) {
|
|
1914
2229
|
try {
|
|
2230
|
+
this.borderedPlacesRequest = { placeIds: [...placeIds], color, width };
|
|
1915
2231
|
if (!this.hasLayer("indoor-bordered-places")) {
|
|
1916
2232
|
this.createBorderedPlacesLayer();
|
|
1917
2233
|
}
|
|
@@ -1923,6 +2239,9 @@ export class InternalMapVXMap extends Loggeable {
|
|
|
1923
2239
|
const placeFilter = ["any", ...coloredPlacesFilters];
|
|
1924
2240
|
const condition = ["all", placeFilter, basicFilter];
|
|
1925
2241
|
this.map.setFilter("indoor-bordered-places", condition);
|
|
2242
|
+
// Caller-defined base filter: mark it resolved so no later capture can
|
|
2243
|
+
// overwrite this customization with the layer's style-declared filter.
|
|
2244
|
+
this.capturedBaseFilters.add("indoor-bordered-places");
|
|
1926
2245
|
this.baseFilters["indoor-bordered-places"] = placeFilter;
|
|
1927
2246
|
if (color !== undefined && width !== undefined) {
|
|
1928
2247
|
this.map.setPaintProperty("indoor-bordered-places", "line-color", color);
|
|
@@ -1948,8 +2267,12 @@ export class InternalMapVXMap extends Loggeable {
|
|
|
1948
2267
|
this.map.addLayer(layer);
|
|
1949
2268
|
}
|
|
1950
2269
|
clearBorderedPlaces() {
|
|
2270
|
+
this.borderedPlacesRequest = undefined;
|
|
1951
2271
|
if (this.hasLayer("indoor-bordered-places")) {
|
|
1952
2272
|
const condition = ["==", ["get", "ref"], "unselected"];
|
|
2273
|
+
// Caller-defined base filter: mark it resolved so no later capture can
|
|
2274
|
+
// overwrite this customization with the layer's style-declared filter.
|
|
2275
|
+
this.capturedBaseFilters.add("indoor-bordered-places");
|
|
1953
2276
|
this.baseFilters["indoor-bordered-places"] = condition;
|
|
1954
2277
|
this.map.setFilter("indoor-bordered-places", condition);
|
|
1955
2278
|
}
|
|
@@ -1994,6 +2317,38 @@ export class InternalMapVXMap extends Loggeable {
|
|
|
1994
2317
|
this.failedTiles.delete(e.tile);
|
|
1995
2318
|
}
|
|
1996
2319
|
}
|
|
2320
|
+
/**
|
|
2321
|
+
* True when a style object is attached to the map. During a full `setStyle` swap the
|
|
2322
|
+
* map is momentarily style-less and any `getSource`/`getFilter` call throws
|
|
2323
|
+
* ("Cannot read properties of undefined"), so call sites that only need the style to
|
|
2324
|
+
* exist should gate on this rather than on `isStyleLoaded()`.
|
|
2325
|
+
*/
|
|
2326
|
+
hasStyle() {
|
|
2327
|
+
var _a;
|
|
2328
|
+
// `map.getStyle()` is the PUBLIC probe: it returns the serialized style, or throws
|
|
2329
|
+
// while the map is momentarily style-less mid-swap. Reading maplibre's internal
|
|
2330
|
+
// `style` field instead (it is absent from the public typings, so it needed a double
|
|
2331
|
+
// cast) would fail closed and silently forever the moment a maplibre upgrade renames
|
|
2332
|
+
// or privatizes it — disabling both the tile-retry loop and `setStyle`'s apply pass
|
|
2333
|
+
// with no throw and no log. `safeGetFilter` already uses this try/catch shape.
|
|
2334
|
+
try {
|
|
2335
|
+
return ((_a = this.map) === null || _a === void 0 ? void 0 : _a.getStyle()) != null;
|
|
2336
|
+
}
|
|
2337
|
+
catch (_b) {
|
|
2338
|
+
return false;
|
|
2339
|
+
}
|
|
2340
|
+
}
|
|
2341
|
+
/**
|
|
2342
|
+
* True when the map has a style object attached and fully loaded. Unlike
|
|
2343
|
+
* `map.isStyleLoaded()`, this does not emit maplibre's "There is no style
|
|
2344
|
+
* added to the map" warning when the style is momentarily absent (e.g. during
|
|
2345
|
+
* a `setStyle` swap), because it checks for the style object first.
|
|
2346
|
+
*/
|
|
2347
|
+
hasLoadedStyle() {
|
|
2348
|
+
if (!this.hasStyle())
|
|
2349
|
+
return false;
|
|
2350
|
+
return this.map.isStyleLoaded() === true;
|
|
2351
|
+
}
|
|
1997
2352
|
subscribeToFailedTiles() {
|
|
1998
2353
|
if (this.retryTimer)
|
|
1999
2354
|
return;
|
|
@@ -2003,6 +2358,17 @@ export class InternalMapVXMap extends Loggeable {
|
|
|
2003
2358
|
this.boundOnWebGLContextLost = this.onWebGLContextLost.bind(this);
|
|
2004
2359
|
this.boundOnWebGLContextRestored = this.onWebGLContextRestored.bind(this);
|
|
2005
2360
|
this.retryTimer = setInterval(() => {
|
|
2361
|
+
// During a full style swap (`setStyle` with `diff: false`) the map can
|
|
2362
|
+
// momentarily have no style object attached. Calling `getSource` then
|
|
2363
|
+
// throws ("Cannot read properties of undefined (reading 'getSource')").
|
|
2364
|
+
// Only the style's PRESENCE is checked here, never `isStyleLoaded()`:
|
|
2365
|
+
// that is false while any source still has tiles in flight, which is
|
|
2366
|
+
// exactly when tiles fail — gating on it would skip every tick during a
|
|
2367
|
+
// bad-network burst. The queue is left untouched so the tiles queued
|
|
2368
|
+
// mid-swap are still retried on the next tick.
|
|
2369
|
+
if (!this.hasStyle()) {
|
|
2370
|
+
return;
|
|
2371
|
+
}
|
|
2006
2372
|
if (!this.map.getSource("indoorequal")) {
|
|
2007
2373
|
this.failedTiles.clear();
|
|
2008
2374
|
return;
|