import { Marker } from "/vendor/maplibre-gl/maplibre-gl.mjs"; // Replaces MapLibre's own GeolocateControl (browser Geolocation API only) // with one that prefers this app's own GNSS receiver when it's turned on // (the sidebar "GNSS" icon, GnssControl) over a phone/laptop's separate // (and often less accurate, e.g. wifi-based) location service. GNSS itself // is only ever used here when explicitly enabled via that sidebar // control — not just whenever a receiver happens to have a fix — so this // doesn't do any polling of its own for that case; GnssControl already // does, and already keeps the map following it once enabled, the same way // this control's own browser-geolocation fallback follows below. // // Reuses MapLibre's own geolocate button styling (same classes/icon // GeolocateControl itself uses, maplibre-gl.css) rather than a new icon — // visually a drop-in replacement, just sourced differently. export class LocateControl { constructor(gnssControl) { this._gnssControl = gnssControl; this._active = false; this._marker = null; this._watchId = null; } onAdd(map) { this._map = map; this._button = document.createElement("button"); this._button.type = "button"; this._button.className = "maplibregl-ctrl-geolocate"; this._button.title = "Find my location"; this._button.setAttribute("aria-label", "Find my location"); const icon = document.createElement("span"); icon.className = "maplibregl-ctrl-icon"; icon.setAttribute("aria-hidden", "true"); this._button.appendChild(icon); this._button.addEventListener("click", () => this._click()); this._container = document.createElement("div"); this._container.className = "maplibregl-ctrl maplibregl-ctrl-group"; this._container.appendChild(this._button); return this._container; } onRemove() { this._stop(); this._container.remove(); } getDefaultPosition() { return "top-left"; } _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…"); } return; } if (this._active) { this._stop(); } else { this._start(); } } _start() { this._active = true; this._button.classList.add("maplibregl-ctrl-geolocate-active"); this._watchId = navigator.geolocation.watchPosition( (position) => this._setPosition(position.coords.longitude, position.coords.latitude), () => { window.alert("Could not determine your location."); this._stop(); }, { enableHighAccuracy: true }, ); } // Recenters on every fix, following the position the way GeolocateControl // itself does while tracking — flyTo (with its zoom swoop) only for the // first fix, since that's the one jumping from wherever the map already // was; easeTo for later updates, a plain pan/zoom-preserving follow // rather than repeating that same swoop on every update. // // MapLibre's own blue pulsing "user location" dot styling // (.maplibregl-user-location-dot, maplibre-gl.css — the exact look // GeolocateControl itself used here before), not GnssControl's green // .gnss-marker — reusing that green for a browser-estimated position too // looked like this control was somehow still using GNSS data even when // it very much wasn't; distinct colors for distinct sources. _setPosition(lon, lat) { if (!this._marker) { const el = document.createElement("div"); el.className = "maplibregl-user-location-dot"; this._marker = new Marker({ element: el }).setLngLat([lon, lat]).addTo(this._map); this._map.flyTo({ center: [lon, lat], zoom: Math.max(this._map.getZoom(), 15) }); } else { this._marker.setLngLat([lon, lat]); this._map.easeTo({ center: [lon, lat] }); } } _stop() { this._active = false; this._button.classList.remove("maplibregl-ctrl-geolocate-active"); if (this._watchId !== null) { navigator.geolocation.clearWatch(this._watchId); this._watchId = null; } if (this._marker) { this._marker.remove(); this._marker = null; } } }