// Map used by /rover-locations/map — plots every "good" rover-location // as a green circle marker with a popup linking to the detail page. import { updateGridOverlay } from "./maidenhead_grid" interface RoverPoint { id: string lat: number lon: number grid: string notes: string | null } interface RoverLocationsMapHook extends ViewHook { map: L.Map gridLayer: L.LayerGroup gridVisible: boolean mounted(this: RoverLocationsMapHook): void } function escapeHtml(str: string): string { return str.replace(/[&<>"']/g, ch => { switch (ch) { case "&": return "&" case "<": return "<" case ">": return ">" case "\"": return """ case "'": return "'" default: return ch } }) } export const RoverLocationsMap = { mounted(this: RoverLocationsMapHook) { const points: RoverPoint[] = JSON.parse(this.el.dataset.points || "[]") const defaultCenter: [number, number] = [32.897, -97.038] const osm = L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", { maxZoom: 19, attribution: "© OpenStreetMap contributors" }) const satellite = L.tileLayer( "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}", { maxZoom: 21, maxNativeZoom: 19 } ) const map = L.map(this.el, { zoomControl: true, scrollWheelZoom: true, attributionControl: true, layers: [osm] }).setView(defaultCenter, 6) this.map = map L.control.layers( { "OSM": osm, "Satellite": satellite }, {}, { position: "topright", collapsed: false } ).addTo(map) // Maidenhead grid overlay + toggle. this.gridLayer = L.layerGroup().addTo(map) this.gridVisible = true updateGridOverlay(map, this.gridLayer) map.on("moveend", () => { if (this.gridVisible) updateGridOverlay(map, this.gridLayer) }) const GridToggle = L.Control.extend({ options: { position: "topright" as const }, onAdd: () => { const btn = L.DomUtil.create("button", "leaflet-bar leaflet-control") btn.innerHTML = "Grid" btn.title = "Toggle Maidenhead grid" btn.style.cssText = "background:#fff;padding:4px 8px;font-size:12px;cursor:pointer;font-weight:600;" L.DomEvent.disableClickPropagation(btn) btn.onclick = () => { this.gridVisible = !this.gridVisible if (this.gridVisible) { this.gridLayer.addTo(map) updateGridOverlay(map, this.gridLayer) btn.style.opacity = "1" } else { this.gridLayer.remove() btn.style.opacity = "0.5" } } return btn } }) new GridToggle().addTo(map) const bounds: [number, number][] = [] for (const p of points) { if (!Number.isFinite(p.lat) || !Number.isFinite(p.lon)) continue const popupHtml = `