Compare commits

...
5 changed files with 75 additions and 3 deletions

No files matched your search

+3
View File
@@ -0,0 +1,3 @@
- MCP tools resolve place names through the keyless Photon geocoder when the
server has no geocoder configured, instead of answering "No place matched"
for every name (asturksever, #1603).
+2 -1
View File
@@ -5081,7 +5081,8 @@
"src/layers/alpr/records.js",
"src/layers/alpr/tileRecords.js",
"src/sources/infrastructureData.js",
"src/sources/eventPacks.js"
"src/sources/eventPacks.js",
"src/keylessGeocoder.js"
],
"external": ["@mapbox/vector-tile", "pbf"]
},
+19 -1
View File
@@ -24,6 +24,7 @@ export function createGeocodePlaceService({
cacheMs = 10 * 60_000,
timeoutMs = 15_000,
now = () => Date.now(),
keylessGeocoder = null,
} = {}) {
// Recent answers by name, so tools that combine others resolve a place to
// the same point once instead of once per section. A pending lookup is
@@ -33,8 +34,17 @@ export function createGeocodePlaceService({
async function lookup(name, signal) {
const query = new URLSearchParams({ q: name });
const response = await fetchImpl(`/api/geocode?${query}`, { signal });
const payload = await response.json().catch(() => null);
// No server geocoder (no Google key, no Nominatim): ask the keyless one
// the search box uses, rather than reporting every name as unmatched.
if (
keylessGeocoder &&
(payload?.configured === false || payload?.code === 'NOMINATIM_DISABLED')
) {
const { place } = await keylessGeocoder.geocode(name, { signal });
return place ? placeFromKeylessResult(place) : null;
}
if (!response.ok) throw new Error(`Geocode HTTP ${response.status}`);
const payload = await response.json();
return placeFromGeocodeResult(payload?.results?.[0]);
}
function start(key, name) {
@@ -100,6 +110,14 @@ export function createGeocodePlaceService({
}
/** Convert one geocode result to a place with bounds, or null. */
/** A keyless (Photon) answer in the same shape as a geocode result's. */
function placeFromKeylessResult({ lat, lng, label, name, viewport }) {
return placeFromGeocodeResult({
formatted_address: label || name || null,
geometry: { location: { lat, lng }, ...(viewport ? { viewport } : {}) },
});
}
export function placeFromGeocodeResult(result) {
const location = result?.geometry?.location;
if (!Number.isFinite(location?.lat) || !Number.isFinite(location?.lng))
+46
View File
@@ -93,3 +93,49 @@ test('a shared lookup outlives one cancelled caller, and times out on its own',
await places.resolve('Slow').catch(() => {});
assert.equal(signals.length, 3, 'a timed-out lookup is not cached');
});
test('without a server geocoder, names resolve through the keyless geocoder', async () => {
const asked = [];
const places = createGeocodePlaceService({
fetchImpl: async () =>
Response.json(
{
error: 'Place search is not configured',
code: 'NOMINATIM_DISABLED',
retryable: false,
},
{ status: 503 },
),
keylessGeocoder: {
geocode: async (name) => {
asked.push(name);
return name === 'Paris'
? {
place: {
lat: 48.8566,
lng: 2.3522,
name: 'Paris',
label: 'Paris, France',
viewport: {
southwest: { lat: 48.81, lng: 2.22 },
northeast: { lat: 48.9, lng: 2.47 },
},
},
answered: true,
}
: { place: null, answered: true };
},
},
});
const paris = await places.resolve('Paris');
assert.equal(paris.name, 'Paris, France');
assert.deepEqual(paris.point, { lat: 48.8566, lon: 2.3522 });
assert.deepEqual(paris.bounds, {
west: 2.22,
south: 48.81,
east: 2.47,
north: 48.9,
});
assert.equal(await places.resolve('Nowhere-at-all'), null);
assert.deepEqual(asked, ['Paris', 'Nowhere-at-all']);
});
+5 -1
View File
@@ -35,6 +35,7 @@ import {
createVesselSource,
createFlightSource,
} from '../sources/live/standalone.js';
import { createPhotonGeocoder } from '../keylessGeocoder.js';
import {
createGeocodePlaceService,
createPlaceSearchService,
@@ -86,7 +87,10 @@ export function createToolServices({ fetchImpl, appUrl, panelKey }) {
fetchImpl,
tileFetchImpl: fetchImpl,
}),
places: createGeocodePlaceService({ fetchImpl }),
places: createGeocodePlaceService({
fetchImpl,
keylessGeocoder: createPhotonGeocoder({ fetchImpl }),
}),
};
}