mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 10:53:59 +00:00
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:
1 parent
3b6b3f1007
commit
f16fc2db0d
7 files changed
+496
-8
No files matched your search
+24
-8
@@ -1,5 +1,6 @@
|
||||
import * as Cesium from 'cesium';
|
||||
import { viewportBias, placesNearViewRecovery } from './annotations/annotationResolver.js';
|
||||
import { geocodeKeyless } from './keylessGeocoder.js';
|
||||
|
||||
/**
|
||||
* Points of Interest per city.
|
||||
@@ -348,7 +349,6 @@ export const CANCELLED_SEARCH = Object.freeze({ cancelled: true });
|
||||
*/
|
||||
export async function searchAndFlyTo(viewer, query, options = {}) {
|
||||
const apiKey = window.__GOOGLE_MAPS_API_KEY__ || import.meta.env.GOOGLE_MAPS_API_KEY;
|
||||
if (!apiKey) throw new Error('No Google Maps API key available for geocoding');
|
||||
|
||||
const beforeFly = typeof options.beforeFly === 'function' ? options.beforeFly : null;
|
||||
const mayFly = () => beforeFly === null || beforeFly() !== false;
|
||||
@@ -356,13 +356,17 @@ export async function searchAndFlyTo(viewer, query, options = {}) {
|
||||
// Viewport-biased geocode — the same bias annotationResolver's geocodePlace uses:
|
||||
// "Sixth Street" spoken over Austin must prefer the Sixth Street on screen, not a
|
||||
// same-named road in another city (or the wrong end of town — the W 6th vs E 6th bug).
|
||||
let url = `https://maps.googleapis.com/maps/api/geocode/json?address=${encodeURIComponent(query)}&key=${apiKey}`;
|
||||
const bias = viewportBias(viewer);
|
||||
if (bias) url += `&bounds=${bias}`;
|
||||
const response = await fetch(url);
|
||||
const data = await response.json();
|
||||
let result = null;
|
||||
|
||||
if (apiKey) {
|
||||
let url = `https://maps.googleapis.com/maps/api/geocode/json?address=${encodeURIComponent(query)}&key=${apiKey}`;
|
||||
if (bias) url += `&bounds=${bias}`;
|
||||
const response = await fetch(url);
|
||||
const data = await response.json();
|
||||
result = (data.status === 'OK' && data.results?.length) ? data.results[0] : null;
|
||||
}
|
||||
|
||||
const result = (data.status === 'OK' && data.results?.length) ? data.results[0] : null;
|
||||
let lat = result?.geometry.location.lat;
|
||||
let lng = result?.geometry.location.lng;
|
||||
let label = result ? result.formatted_address : null;
|
||||
@@ -372,7 +376,10 @@ export async function searchAndFlyTo(viewer, query, options = {}) {
|
||||
// Places-near-view recovery (annotationResolver's twin): a missed geocode, or one
|
||||
// that landed implausibly far from the view centre, snaps back to a view-biased
|
||||
// Places hit within the trust bound — "the Capitol" means the one on screen.
|
||||
const recovered = await placesNearViewRecovery(viewer, query, result ? { lat, lon: lng } : null);
|
||||
// Places is a Google service, so it is only worth asking when Google answered.
|
||||
const recovered = apiKey
|
||||
? await placesNearViewRecovery(viewer, query, result ? { lat, lon: lng } : null)
|
||||
: null;
|
||||
if (recovered) {
|
||||
lat = recovered.lat;
|
||||
lng = recovered.lon;
|
||||
@@ -380,7 +387,16 @@ export async function searchAndFlyTo(viewer, query, options = {}) {
|
||||
types = recovered.types || [];
|
||||
viewport = placesViewportToBounds(recovered.viewport) || viewport;
|
||||
} else if (!result) {
|
||||
return null;
|
||||
// Keyless fallback. Reached when no key is configured, and also when a key
|
||||
// exists but Google declined — an unenabled Geocoding API answers HTTP 200
|
||||
// with REQUEST_DENIED, so the miss above is what detects it, not an error.
|
||||
const keyless = await geocodeKeyless(query, { bias });
|
||||
if (!keyless) return null;
|
||||
lat = keyless.lat;
|
||||
lng = keyless.lng;
|
||||
label = keyless.label || query;
|
||||
types = keyless.types;
|
||||
viewport = keyless.viewport;
|
||||
}
|
||||
|
||||
const requestedRange = finitePositive(options.range);
|
||||
|
||||
Reference in new issue
Block a user