Switch to GNSS immediately if it turns on mid-track
CI / check (push) Successful in 47s
CI / deploy (push) Successful in 18s

If Locate was already tracking via the browser-geolocation fallback
(GNSS was off when clicked) and GNSS then gets turned on via the
sidebar, it kept using the browser position until the button was
clicked again -- the browser's own (often less accurate) position had
no business still being used once the app's own receiver had a fix.

GnssControl#onEnabledChange (a small internal callback list) notifies
LocateControl the moment the sidebar toggle changes; if it's actively
tracking via the browser at that point, it stops that and jumps to
GNSS's position instead.

Caught a real bug while verifying this: the automatic handoff reused
the same jump-to-GNSS helper a deliberate click uses, which alerts if
GNSS has no fix yet -- but GNSS only just turned on and almost
certainly hasn't gotten its first poll response back yet at the exact
moment the handoff fires, so this popped a blocking window.alert()
(which halts the whole page until dismissed) on every single enable.
A test hung on exactly this before I noticed. Fixed with an
alertIfNoFix option, true only for the deliberate-click path.

Verified end to end (simulating the "already tracking" state directly
since headless geolocation isn't reliable enough to trust as the
trigger): toggling GNSS mid-track removes the blue browser marker,
shows the green GNSS one once its poll lands, and no dialog appears.
This commit is contained in:
randy committed 2026-09-08 08:50:43 +02:00
1 parent f0ac8ac5d4
commit d9879bad60
3 files changed
+63 -10

No files matched your search

+15
View File
@@ -261,6 +261,21 @@ as "the GNSS simulator, leaking somehow"). Uses MapLibre's own blue
pulsing `.maplibregl-user-location-dot` (`maplibre-gl.css`) instead — pulsing `.maplibregl-user-location-dot` (`maplibre-gl.css`) instead —
the same look GeolocateControl itself had here before, and visually the same look GeolocateControl itself had here before, and visually
distinct from GNSS's green on purpose. distinct from GNSS's green on purpose.
+
GNSS turning on *while* the browser fallback is already mid-track
switches over immediately rather than waiting for another click —
`GnssControl#onEnabledChange` (a small internal callback list, not a
full pub/sub system — nothing else needs one) notifies `LocateControl`
the moment the sidebar toggle changes; if it was actively tracking via
the browser at that point, it stops that and jumps to GNSS's position
instead. That jump doesn't alert if GNSS has no fix yet (a
`{ alertIfNoFix }` option on the shared jump-to-GNSS helper, `true`
only for a deliberate click) — `GnssControl` only just turned on and
almost certainly hasn't gotten its first poll response back yet at
the exact moment this fires, so alerting here would pop a blocking
dialog (window.alert() halts the whole page until dismissed) on every
single enable; its own next poll shows/recenters once it actually has
something, same as it would either way.
=== Debug inspector (Alt+click) === Debug inspector (Alt+click)
+11
View File
@@ -15,6 +15,16 @@ export class GnssControl {
this._marker = null; this._marker = null;
this._timer = null; this._timer = null;
this._enabled = false; this._enabled = false;
this._enabledListeners = [];
}
// Read by LocateControl: if it's mid-tracking on the browser-geolocation
// fallback when GNSS turns on, GNSS should take over there and then, not
// only the next time the button happens to get clicked — the browser's
// own (often less accurate) position has no business still being used
// once the app's own receiver has a fix.
onEnabledChange(callback) {
this._enabledListeners.push(callback);
} }
mount(sidebar) { mount(sidebar) {
@@ -46,6 +56,7 @@ export class GnssControl {
this._removeMarker(); this._removeMarker();
this._status.textContent = ""; this._status.textContent = "";
} }
this._enabledListeners.forEach((callback) => callback(enabled));
} }
_stopPolling() { _stopPolling() {
+37 -10
View File
@@ -37,6 +37,25 @@ export class LocateControl {
this._container = document.createElement("div"); this._container = document.createElement("div");
this._container.className = "maplibregl-ctrl maplibregl-ctrl-group"; this._container.className = "maplibregl-ctrl maplibregl-ctrl-group";
this._container.appendChild(this._button); this._container.appendChild(this._button);
// GNSS turning on mid-track (this control's own browser-geolocation
// fallback, not GnssControl's — that one manages its own tracking)
// should switch over immediately, not just the next time this button
// gets clicked; the browser's own position has no business still
// being used once the app's own receiver has a fix. No alertIfNoFix
// here (unlike a deliberate click, below) — GnssControl only just
// turned on and almost certainly hasn't gotten its first fix back yet
// (its own poll is async, still in flight at the moment this fires),
// so this would otherwise pop a blocking "waiting for a fix" dialog on
// every single enable; GnssControl's own next poll will show/recenter
// once it actually has something, same as it would either way.
this._gnssControl.onEnabledChange((enabled) => {
if (enabled && this._active) {
this._stop();
this._jumpToGnss();
}
});
return this._container; return this._container;
} }
@@ -51,16 +70,7 @@ export class LocateControl {
_click() { _click() {
if (this._gnssControl.isEnabled()) { if (this._gnssControl.isEnabled()) {
// GNSS already follows the map on its own once enabled (GnssControl) this._jumpToGnss({ alertIfNoFix: true });
// and has no separate "off" state to toggle from here — just jump to
// its current fix now, for the same instant feedback clicking this
// button has always given.
const position = this._gnssControl.getPosition();
if (position) {
this._map.flyTo({ center: position, zoom: Math.max(this._map.getZoom(), 15) });
} else {
window.alert("Waiting for a GNSS fix…");
}
return; return;
} }
@@ -71,6 +81,23 @@ export class LocateControl {
} }
} }
// GNSS already follows the map on its own once enabled (GnssControl) and
// has no separate "off" state to toggle from here — just jump to its
// current fix, for the same instant feedback clicking this button has
// always given. alertIfNoFix is opt-in (a deliberate click wants to know
// why nothing happened; the automatic mid-track handoff above doesn't —
// see its own comment) since a JS alert() blocks the whole page until
// dismissed, not something to pop up on a path that isn't a direct user
// action.
_jumpToGnss({ alertIfNoFix = false } = {}) {
const position = this._gnssControl.getPosition();
if (position) {
this._map.flyTo({ center: position, zoom: Math.max(this._map.getZoom(), 15) });
} else if (alertIfNoFix) {
window.alert("Waiting for a GNSS fix…");
}
}
_start() { _start() {
this._active = true; this._active = true;
this._button.classList.add("maplibregl-ctrl-geolocate-active"); this._button.classList.add("maplibregl-ctrl-geolocate-active");