feat(voice): add reusable geometry actions

- turn named places and mapped landmarks into reusable bounded areas
- count loaded records inside resolved, drawn and annotated geometry
- find recent imagery for an area, the view or the pointer snapshot
- keep osm place search behind an operator-configured overpass endpoint
- preserve cancellation, ownership and follow-up memory across every action
- cover geometry, provider lifecycle, cache isolation and voice routing
This commit is contained in:
manjunath22466 committed 2026-10-03 10:23:38 +05:30
1 parent 01979bba1e
commit b234d7b427
84 files changed
+14262 -1681

No files matched your search

+2
View File
@@ -124,6 +124,8 @@ export function createNominatimOutlineClient({
const outline = {
polygons,
name: body.outline.name || null,
adminArea: body.outline.adminArea || null,
adminLevel: body.outline.adminLevel || null,
class: body.outline.class || null,
type: body.outline.type || null,
osm: body.outline.osm || null,
+20 -3
View File
@@ -29,14 +29,31 @@ test('an accepted outline comes back with its polygons and is remembered', async
const { calls, lookup } = client(() =>
reply(200, {
status: 'OK',
outline: { name: 'Paris', class: 'boundary', type: 'administrative', polygons: [[ring]] },
outline: {
name: 'Bagmati Province',
adminArea: 'Bagmati Province',
adminLevel: 'admin1',
class: 'boundary',
type: 'administrative',
polygons: [[ring]],
},
}),
);
const ask = { query: 'Paris', kind: 'city', lat: 48.85, lon: 2.35 };
const ask = {
query: 'Bagmati Province',
kind: 'admin',
lat: 27.72,
lon: 85.32,
};
const outline = await lookup(ask);
assert.deepEqual(outline.polygons, [[ring]]);
assert.equal(outline.class, 'boundary');
assert.match(calls[0], /^\/api\/geocode\/outline\?q=Paris&kind=city&lat=48\.850&lon=2\.350$/);
assert.equal(outline.adminArea, 'Bagmati Province');
assert.equal(outline.adminLevel, 'admin1');
assert.match(
calls[0],
/^\/api\/geocode\/outline\?q=Bagmati\+Province&kind=admin&lat=27\.720&lon=85\.320$/,
);
await lookup(ask);
assert.equal(calls.length, 1, 'one request per ask in a session');
});
+54
View File
@@ -0,0 +1,54 @@
/**
* Browser client for `/api/osm/features`: one curated preset inside a box,
* as a count or as features. The server writes and bounds the Overpass
* query and answers only when an operator configured Overpass; this only
* names the preset, box, mode and limit.
* @module sources/osmFeatures
*/
/**
* @param {{fetchImpl?: typeof fetch, endpoint?: string}} [options]
* @returns {(request: {preset: string, bbox: number[], mode?: 'count'|'features', limit?: number}, options?: {signal?: AbortSignal}) => Promise<object>}
*/
export function createOsmFeatureSource({
fetchImpl = (...args) => globalThis.fetch(...args),
endpoint = '/api/osm/features',
} = {}) {
return async function searchOsmFeatures(
{ preset, bbox, mode = 'features', limit },
{ signal } = {},
) {
const params = new URLSearchParams({
preset,
bbox: bbox.map((v) => Number(v).toFixed(5)).join(','),
mode,
});
if (Number.isFinite(limit)) params.set('limit', String(limit));
let response;
try {
response = await fetchImpl(`${endpoint}?${params}`, { signal });
} catch (error) {
if (signal?.aborted) throw error;
return {
ok: false,
status: 0,
error: 'OpenStreetMap search is unreachable.',
};
}
let body = null;
try {
body = await response.json();
} catch {
body = null;
}
if (!response.ok)
return {
ok: false,
status: response.status,
code: body?.code || null,
error:
body?.error || `OpenStreetMap search failed (${response.status}).`,
};
return { ok: true, ...body };
};
}
+10
View File
@@ -281,6 +281,10 @@ test('shared decoded tiles are isolated from consumer mutation', async () => {
secondResult.tiles[0][0].name = 'second mutation';
secondResult.tiles[0][0].coordinates[0][0] = 1;
const secondCachedResult = await second.fetchBounds(box, { zoom: 14, tiles });
assert.equal(secondCachedResult.tiles[0][0].name, 'Congress Avenue');
assert.equal(secondCachedResult.tiles[0][0].coordinates[0][0], -97);
const thirdResult = await third.fetchBounds(box, { zoom: 14, tiles });
assert.equal(thirdResult.tiles[0][0].name, 'Congress Avenue');
assert.equal(thirdResult.tiles[0][0].coordinates[0][0], -97);
@@ -334,6 +338,12 @@ test('one source isolates in-flight and completed cache results from consumer mu
const cachedResult = await source.fetchBounds(box, { zoom: 14, tiles });
assert.equal(cachedResult.tiles[0][0].name, 'Congress Avenue');
assert.equal(cachedResult.tiles[0][0].coordinates[0][0], -97);
cachedResult.tiles[0][0].name = 'cached mutation';
cachedResult.tiles[0][0].coordinates[0][0] = 2;
const revisitedResult = await source.fetchBounds(box, { zoom: 14, tiles });
assert.equal(revisitedResult.tiles[0][0].name, 'Congress Avenue');
assert.equal(revisitedResult.tiles[0][0].coordinates[0][0], -97);
assert.equal(requests, 1);
assert.equal(decodes, 1);
});