refactor: add configurable Nominatim adapter and portable source helpers

This commit is contained in:
Sameh Khamis committed 2026-09-14 22:08:16 -07:00
1 parent 2b1092bf89
commit 66eb6d6a7c
14 files changed
+646 -152

No files matched your search

+58 -35
View File
@@ -1,3 +1,4 @@
import { createNominatimProvider } from './nominatim.js';
import { createGeospatialServices } from './geospatial.js';
import { createHttpGeospatialProvider } from './http.js';
import { createPlaceSearch } from './placeSearch.js';
@@ -22,54 +23,76 @@ export function createDefaultPlaceSearch({
endpoints = {},
providers = {},
presets = null,
geocoding = null,
} = {}) {
if (geocoding && geocoding.provider !== 'nominatim')
throw new TypeError('Unsupported geocoding provider');
const selected = geocoding
? createNominatimProvider({ ...geocoding, fetchImpl })
: null;
if (selected && !selected.geocode)
throw new TypeError('Nominatim searchEndpoint is required');
const forward = createPlaceSearch({
signal,
providers: providers.geocode || [
createCoordinateGeocoder(),
...(presets ? [createPresetGeocoder({ presets })] : []),
createGoogleGeocoder({
request(query, { bias, signal }) {
const key = resolveApiKey?.();
if (!key) return null;
const url = new URL(
endpoints.geocode ||
'https://maps.googleapis.com/maps/api/geocode/json',
);
url.searchParams.set('address', query);
url.searchParams.set('key', key);
if (bias) url.searchParams.set('bounds', bias);
return fetchImpl(url.toString(), { signal });
},
}),
createPhotonGeocoder({ fetchImpl, endpoint: endpoints.photon }),
// Last resort: the local Nominatim route, which answers with no key when
// neither of the two above did. It speaks the same result shape, so it
// rides the existing Google adapter rather than needing its own.
createGoogleGeocoder({
request(query, { bias, signal }) {
const params = new URLSearchParams({ q: query });
if (bias) params.set('bounds', bias);
return fetchImpl(
`${endpoints.nominatim || '/api/geocode'}?${params}`,
{
signal,
},
);
},
}),
...(selected
? [selected]
: [
createGoogleGeocoder({
request(query, { bias, signal }) {
const key = resolveApiKey?.();
if (!key) return null;
const url = new URL(
endpoints.geocode ||
'https://maps.googleapis.com/maps/api/geocode/json',
);
url.searchParams.set('address', query);
url.searchParams.set('key', key);
if (bias) url.searchParams.set('bounds', bias);
return fetchImpl(url.toString(), { signal });
},
}),
createPhotonGeocoder({ fetchImpl, endpoint: endpoints.photon }),
// Last resort: the local Nominatim route, which answers with no key when
// neither of the two above did. It speaks the same result shape, so it
// rides the existing Google adapter rather than needing its own.
createGoogleGeocoder({
request(query, { bias, signal }) {
const params = new URLSearchParams({ q: query });
if (bias) params.set('bounds', bias);
return fetchImpl(
`${endpoints.nominatim || '/api/geocode'}?${params}`,
{
signal,
},
);
},
}),
]),
],
});
const operations = createHttpGeospatialProvider({
fetchImpl,
resolveApiKey,
endpoints,
});
return {
...forward,
...createGeospatialServices({
signal,
providers: {
...createHttpGeospatialProvider({
fetchImpl,
resolveApiKey,
endpoints,
}),
...operations,
...(selected
? {
reverseGeocode: selected.reverseGeocode,
attribution: {
...operations.attribution,
...selected.attribution,
},
}
: {}),
...providers,
},
}),