feat(search): fall back to keyless Photon geocoding

Place search was Google-only: searchAndFlyTo threw without a key, and with a
key whose Geocoding API is not enabled it returned nothing. Both cases leave
the LOCATION box and the fly_to_location voice tool unable to resolve a name,
even though every other layer in the app works keyless.

src/keylessGeocoder.js adapts Photon (komoot, over OpenStreetMap) into the
shape the Google path already produces — lat/lng, label, Google-style types,
{southwest,northeast} bounds — so geocodeNavigationMode, regionFramingPlan and
the region-swath cap keep running on unchanged inputs. The translation lives at
the boundary and nowhere else; Google remains primary and its branch is
unchanged when it answers.

Detection is the empty result, not an exception: an unenabled Geocoding API
answers HTTP 200 with REQUEST_DENIED. Places near-view recovery is now
attempted only when a key exists, since Places is a Google service too.

Three translations are load-bearing, and each was found by querying the live
service rather than by reasoning about OSM tagging:

- Google's `bounds` PREFERS results inside the box; Photon's `bbox` FILTERS to
  it. Sent as a bbox, searching "Ho Guom, Ha Noi" while looking at Austin
  returns features: []. Sent as Photon's lat/lon proximity bias it finds the
  lake, and "Sixth Street" still resolves to Austin's over Austin and to
  England's over London.
- Photon's `extent` is [west, north, east, south]. Read as [w,s,e,n] it yields
  an inverted but finite box that frames the camera at nothing.
- A specific OSM key=value rule must beat the coarse `type`. Photon tags a lake
  `water=lake` with `type: 'other'` — unmapped, that framed Hoan Kiem Lake as a
  precise place and put the camera 26 m over the water — and tags a town square
  `type: 'locality'`, which framed Times Square as if it were a city.

searchAndFlyTo no longer throws when no key is present. Both callers already
read a null return as not-found (ui.js renders its miss branch, gevActions
reports ok: false), so the removed throw has no consumer.

Verified end to end through the real search box, with a key Google refuses:
one denied Google geocode, one Photon request, camera from Austin
(30.2700, -97.7400) to 21.0288, 105.8524 — Hoan Kiem Lake to within metres —
and "Ha Noi" framed at province scale. npm test 2604/2604; npm run build
clean; npm run test:track 97/3, the same three failures unmodified main
produces on this machine.
This commit is contained in:
daikaginza committed 2026-09-05 09:46:04 +07:00
1 parent 3b6b3f1007
commit f16fc2db0d
7 files changed
+496 -8

No files matched your search

+1
View File
@@ -27,6 +27,7 @@ How to read this:
| **OpenStreetMap (Overpass API)** | Road geometry for traffic | ODbL 1.0 | "© OpenStreetMap contributors" |
| **TomTom Traffic API** (flow vector tiles) | Live congestion coloring for the traffic layer (optional, BYOK) | [TomTom for Developers terms](https://developer.tomtom.com) (proprietary, your own key; free tier currently 200K tile requests/month — see [current pricing](https://docs.tomtom.com/pricing/)) | "Traffic flow data © TomTom" — registered when live mode activates |
| **OpenStreetMap (Overpass API)** | Viewport-bounded mapped installation context for Global Context | ODbL 1.0 | "© OpenStreetMap contributors" (incomplete mapped context) |
| **Photon** (komoot) | Keyless place search — the fallback when no Google Maps key is configured, or when Google declines the Geocoding request | Service: free public instance, [fair use](https://photon.komoot.io) (no bulk/heavy use); underlying data: **ODbL 1.0** | "Photon (komoot)" + "© OpenStreetMap contributors" |
| **OpenStreetMap (Nominatim)** | Reverse-geocoded place label in the cockpit Local Info page | ODbL 1.0 + Nominatim usage policy | "© OpenStreetMap contributors" |
| **Open-Meteo** | Current weather in the cockpit Local Info page and cockpit-local dynamic atmospheric effects | [CC BY 4.0 data licence and adjacent-link attribution requirement](https://open-meteo.com/en/licence) | Linked "Weather data by Open-Meteo.com" beside the displayed local data |
| **Google News RSS** | Primary locality-matched headlines in the cockpit Regional News page | [Google News Terms of Service](https://www.google.com/intl/en_us/terms_google_news.html) restrict use to personal, noncommercial use; linked articles remain third-party publisher content and retain publisher terms | "Google News RSS" plus each article's linked publisher/domain |