From d9879bad60ac89451175c69db5ea57738a783dfe Mon Sep 17 00:00:00 2001 From: Roman Priesol Date: Tue, 8 Sep 2026 08:50:43 +0200 Subject: [PATCH] Switch to GNSS immediately if it turns on mid-track 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. --- docs/architecture.adoc | 15 ++++++++++++++ public/js/gnss.js | 11 ++++++++++ public/js/locate.js | 47 +++++++++++++++++++++++++++++++++--------- 3 files changed, 63 insertions(+), 10 deletions(-) diff --git a/docs/architecture.adoc b/docs/architecture.adoc index d636e3d..f591418 100644 --- a/docs/architecture.adoc +++ b/docs/architecture.adoc @@ -261,6 +261,21 @@ as "the GNSS simulator, leaking somehow"). Uses MapLibre's own blue pulsing `.maplibregl-user-location-dot` (`maplibre-gl.css`) instead — the same look GeolocateControl itself had here before, and visually 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) diff --git a/public/js/gnss.js b/public/js/gnss.js index 186fd14..ce1aa65 100644 --- a/public/js/gnss.js +++ b/public/js/gnss.js @@ -15,6 +15,16 @@ export class GnssControl { this._marker = null; this._timer = null; 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) { @@ -46,6 +56,7 @@ export class GnssControl { this._removeMarker(); this._status.textContent = ""; } + this._enabledListeners.forEach((callback) => callback(enabled)); } _stopPolling() { diff --git a/public/js/locate.js b/public/js/locate.js index 87fd68e..5927746 100644 --- a/public/js/locate.js +++ b/public/js/locate.js @@ -37,6 +37,25 @@ export class LocateControl { this._container = document.createElement("div"); this._container.className = "maplibregl-ctrl maplibregl-ctrl-group"; 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; } @@ -51,16 +70,7 @@ export class LocateControl { _click() { if (this._gnssControl.isEnabled()) { - // 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 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…"); - } + this._jumpToGnss({ alertIfNoFix: true }); 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() { this._active = true; this._button.classList.add("maplibregl-ctrl-geolocate-active");