import { SEARCH_ICON } from "/js/icons.js"; import { escapeHtml } from "/js/gpx.js"; const NOMINATIM_API = "https://nominatim.openstreetmap.org/search"; // "lat, lng" / "lat lng" decimal degrees — the same plain-text shape the // topbar's own coordinate readout copies to the clipboard (map.js). Tried // before hitting the geocoder, so pasting a coordinate pair jumps straight // there with no network round-trip. const COORD_PATTERN = /^\s*(-?\d+(?:\.\d+)?)\s*[, ]\s*(-?\d+(?:\.\d+)?)\s*$/; // A place-name/coordinate search box, matching NPS's own Park Tiles viewer. // Online only, like this app's other non-basemap online features (Radar, // the Wikipedia layer) — no offline geocoding index to fall back to. // Nominatim: OpenStreetMap's own geocoder, free, no API key, matching the // online basemap's own OSM-derived style already used here. export class SearchControl { constructor() { this._results = []; } onAdd(map) { this._map = map; this._container = document.createElement("div"); this._container.className = "maplibregl-ctrl maplibregl-ctrl-group search-ctrl"; this._container.innerHTML = ` `; this._button = this._container.querySelector(".search-ctrl__button"); this._panel = this._container.querySelector(".search-panel"); this._input = this._container.querySelector(".search-panel__input"); this._resultsEl = this._container.querySelector(".search-panel__results"); this._button.addEventListener("click", () => this._toggle()); this._container.querySelector(".search-panel__form").addEventListener("submit", (event) => { event.preventDefault(); this._search(); }); return this._container; } onRemove() { this._container.remove(); } getDefaultPosition() { return "top-left"; } _toggle() { const opening = this._panel.hidden; this._panel.hidden = !opening; this._button.classList.toggle("active", opening); if (opening) this._input.focus(); } async _search() { const query = this._input.value.trim(); if (!query) return; const coords = query.match(COORD_PATTERN); if (coords) { this._resultsEl.innerHTML = ""; this._jumpTo(Number(coords[1]), Number(coords[2])); return; } this._resultsEl.innerHTML = `
  • Searching…
  • `; let places; try { const url = new URL(NOMINATIM_API); url.searchParams.set("q", query); url.searchParams.set("format", "jsonv2"); url.searchParams.set("limit", "5"); places = await (await fetch(url)).json(); } catch { this._resultsEl.innerHTML = `
  • Search unavailable — check your connection.
  • `; return; } this._renderResults(places); } _renderResults(places) { this._results = places; if (places.length === 0) { this._resultsEl.innerHTML = `
  • No results
  • `; return; } this._resultsEl.innerHTML = places .map((place, i) => `
  • ${escapeHtml(place.display_name)}
  • `) .join(""); this._resultsEl.querySelectorAll(".search-panel__result").forEach((el) => { el.addEventListener("click", () => { const place = this._results[Number(el.dataset.index)]; this._jumpTo(Number(place.lat), Number(place.lon)); }); }); } _jumpTo(lat, lng) { this._map.flyTo({ center: [lng, lat], zoom: Math.max(this._map.getZoom(), 14) }); this._panel.hidden = true; this._button.classList.remove("active"); } }