feat(outlines): restore voice outlines without public Overpass

use bundled place boundaries and shared OpenFreeMap tiles before a guarded Nominatim fallback

preserve multipart shapes, holes and cancellation,
and keep pending annotations visible while ground sampling finishes across frames

keep non-Latin name distinctions when selecting provider results,
and retain an outline anchor only when it lies in filled geometry
This commit is contained in:
manjunath committed 2026-10-27 13:24:02 +00:00
commit d4434aa6d7
1489 files changed
+196442 -12353

No files matched your search

+130 -60
View File
@@ -1,6 +1,7 @@
import path from 'node:path';
import { makeRateLimiter, clientKey } from '../common/rate-limit.js';
import { coalesceProxyRequest } from '../common/http.js';
import { admitSameSite } from '../common/same-site.js';
import { defaultSourceRoot } from '../common/source-root.js';
import { naturalRegionAtPoint } from '../../../src/data/naturalEarthRegions.js';
import {
nominatimToGeocodeResult,
@@ -30,8 +31,8 @@ const OUTLINE_RESULT_LIMIT = 3;
/** Largest upstream answer read for an outline (polygons are simplified upstream). */
const OUTLINE_MAX_RESPONSE_BYTES = 4 * 1024 * 1024;
/** Bump when the cached payload shape changes, so old entries are never read. */
const CACHE_SCHEMA = 'v2';
/** Bump when cached answers change shape or selection authority. */
const CACHE_SCHEMA = 'v3';
/**
* The query text actually sent upstream: Unicode-composed, whitespace
@@ -57,9 +58,73 @@ function malformed() {
});
}
function abandoned() {
return Object.assign(new Error('Place search was abandoned'), {
code: 'NOMINATIM_ABANDONED',
});
}
/**
* Share one upstream call while keeping each caller's cancellation independent.
* The shared controller is aborted only after the last subscriber leaves.
*/
function subscribeSharedRequest(inFlight, key, signal, create) {
if (signal?.aborted) return Promise.reject(abandoned());
let flight = inFlight.get(key);
if (!flight) {
flight = {
controller: new AbortController(),
users: 0,
settled: false,
promise: null,
};
const owned = flight;
owned.promise = Promise.resolve()
.then(() => create(owned.controller.signal))
.finally(() => {
owned.settled = true;
if (inFlight.get(key) === owned) inFlight.delete(key);
});
inFlight.set(key, owned);
}
flight.users += 1;
return new Promise((resolve, reject) => {
let left = false;
const leave = () => {
if (left) return;
left = true;
signal?.removeEventListener('abort', onAbort);
flight.users -= 1;
if (
flight.users === 0 &&
!flight.settled &&
inFlight.get(key) === flight
) {
inFlight.delete(key);
flight.controller.abort();
}
};
const onAbort = () => {
leave();
reject(abandoned());
};
signal?.addEventListener('abort', onAbort, { once: true });
flight.promise.then(
(value) => {
leave();
resolve(value);
},
(error) => {
leave();
reject(error);
},
);
});
}
/** Where the dev server keeps Nominatim answers and the daily count. */
export const NOMINATIM_CACHE_DIR = path.join(
process.cwd(),
defaultSourceRoot,
'.gev-cache',
'nominatim',
);
@@ -76,6 +141,9 @@ export const fetchRegionalPlace = createRegionalPlaceProvider();
* moves reuse one cache entry. The viewbox is a soft preference upstream.
*/
function coarseBounds(bounds) {
// Validate the original hint before snapping or clamping can disguise a
// wrapped, inverted or out-of-range box as an unrelated valid viewbox.
if (!nominatimViewboxFromBounds(bounds)) return null;
const match = String(bounds || '')
.trim()
.split('|')
@@ -122,27 +190,33 @@ export function createNominatimSearchProvider({
const cacheKey = requestKey(gateNow, 'search', params);
const cached = await cache.get(cacheKey);
if (cached) return { ...cached.payload, cached: true };
const { promise } = coalesceProxyRequest(inFlight, cacheKey, async () => {
const rows = await gateNow.requestJson(
(endpoint) => `${endpoint}?${params}`,
{ signal },
);
// Only a well-formed empty list is "no such place"; anything else is a
// failure and is never cached.
if (!Array.isArray(rows)) throw malformed();
const result = nominatimToGeocodeResult(rows[0] ?? null);
if (rows.length && !result) throw malformed();
const payload = result
? { status: 'OK', results: [result] }
: { status: 'ZERO_RESULTS', results: [] };
await cache.set(
cacheKey,
payload,
result ? NOMINATIM_CACHE_TTL_MS.place : NOMINATIM_CACHE_TTL_MS.notFound,
);
return payload;
});
return await promise;
return await subscribeSharedRequest(
inFlight,
cacheKey,
signal,
async (sharedSignal) => {
const rows = await gateNow.requestJson(
(endpoint) => `${endpoint}?${params}`,
{ signal: sharedSignal },
);
// Only a well-formed empty list is "no such place"; anything else is a
// failure and is never cached.
if (!Array.isArray(rows)) throw malformed();
const result = nominatimToGeocodeResult(rows[0] ?? null);
if (rows.length && !result) throw malformed();
const payload = result
? { status: 'OK', results: [result] }
: { status: 'ZERO_RESULTS', results: [] };
await cache.set(
cacheKey,
payload,
result
? NOMINATIM_CACHE_TTL_MS.place
: NOMINATIM_CACHE_TTL_MS.notFound,
);
return payload;
},
);
};
}
@@ -178,41 +252,38 @@ export function createNominatimOutlineProvider({
const cacheKey = requestKey(gateNow, `outline-${kind}`, params);
const cached = await cache.get(cacheKey);
if (cached) return { ...cached.payload, cached: true };
const { promise } = coalesceProxyRequest(inFlight, cacheKey, async () => {
const rows = await gateNow.requestJson(
(endpoint) => `${endpoint}?${params}`,
{ signal, maxBytes: OUTLINE_MAX_RESPONSE_BYTES },
);
if (!Array.isArray(rows)) throw malformed();
const { outline, skipped } = selectOutlineResult(rows, {
kind,
query: sent,
});
const payload = outline
? { status: 'OK', outline }
: { status: 'ZERO_RESULTS', outline: null, skipped };
await cache.set(
cacheKey,
payload,
outline
? NOMINATIM_CACHE_TTL_MS.outline
: NOMINATIM_CACHE_TTL_MS.notFound,
);
return payload;
});
return await promise;
return await subscribeSharedRequest(
inFlight,
cacheKey,
signal,
async (sharedSignal) => {
const rows = await gateNow.requestJson(
(endpoint) => `${endpoint}?${params}`,
{ signal: sharedSignal, maxBytes: OUTLINE_MAX_RESPONSE_BYTES },
);
if (!Array.isArray(rows)) throw malformed();
const { outline, skipped } = selectOutlineResult(rows, {
kind,
query: sent,
});
const payload = outline
? { status: 'OK', outline }
: { status: 'ZERO_RESULTS', outline: null, skipped };
await cache.set(
cacheKey,
payload,
outline
? NOMINATIM_CACHE_TTL_MS.outline
: NOMINATIM_CACHE_TTL_MS.notFound,
);
return payload;
},
);
};
}
export const fetchNominatimSearch = createNominatimSearchProvider();
/** A browser on another site may not spend this install's allowance. */
function crossSite(req) {
return (
String(req.headers?.['sec-fetch-site'] || '').toLowerCase() === 'cross-site'
);
}
function sendJson(res, status, body, headers = {}) {
if (res.writableEnded) return;
res.writeHead(status, {
@@ -312,14 +383,11 @@ export function geocodeProxy({
/** Common front door: method, same-site, rate limit, abandonment signal. */
function admit(req, res, limiter) {
if (admitSameSite(req, res)) return null;
if (req.method !== 'GET') {
sendJson(res, 405, { error: 'Method Not Allowed' });
return null;
}
if (crossSite(req)) {
sendJson(res, 403, { error: 'Cross-site requests are not accepted' });
return null;
}
if (!limiter(clientKey(req))) {
sendJson(
res,
@@ -394,7 +462,9 @@ export function geocodeProxy({
res,
200,
{ status: payload.status, results: payload.results },
{ 'Cache-Control': payload.cached ? 'public, max-age=60' : 'no-store' },
{
'Cache-Control': payload.cached ? 'private, max-age=60' : 'no-store',
},
);
} catch (error) {
sendFailure(res, error, 'Place search');