Merge branch 'main' into fix/cctv-tool-routing

This commit is contained in:
Sameh Khamis committed 2026-10-20 10:05:39 -07:00
commit 13b474d302
11 files changed
+754 -36

No files matched your search

+3
View File
@@ -0,0 +1,3 @@
- Require a geocoded containment anchor for ambiguous analyst region names,
preserve boundary provenance and cancellation safety, and retain exact-name
precedence for natural outlines (manjunath22466, #1235).
+9
View File
@@ -1,5 +1,14 @@
# God's Eye View Current State # God's Eye View Current State
Analyst region queries resolve unique Natural Earth names offline without
geocoding, so Gold Coast and Ivory Coast name the West African natural regions.
Duplicate names require a geocoded containment anchor; when several entries for
the same name contain that anchor, the lookup stays unresolved rather than
selecting the largest entry. Natural outlines try exact names before aliases and
broader suffix variants. Follow-ups retain boundary provenance, and cancelled
lookups preserve the previous analyst cohort and boundary source. Navigation
search keeps a successful geocode, with no search or view-based region recovery.
Shared distance checks use `src/geoDistance.js` with `{ lat, lon }` points and Shared distance checks use `src/geoDistance.js` with `{ lat, lon }` points and
the IUGG mean spherical Earth radius, 6371.0088 km. `greatCircleKm` is the IUGG mean spherical Earth radius, 6371.0088 km. `greatCircleKm` is
canonical; `greatCircleMeters` is its exact metre conversion. Missing, canonical; `greatCircleMeters` is its exact metre conversion. Missing,
+5 -3
View File
@@ -1745,8 +1745,9 @@ async function runBehaviorLayer() {
'behavior: analyst follow-up re-filters the remembered set', 'behavior: analyst follow-up re-filters the remembered set',
`count=${r?.count} followUp=${r?.coverage?.followUp}`, `count=${r?.count} followUp=${r?.coverage?.followUp}`,
); );
// A marine region resolves from the bundled Natural Earth pack in the page, // Require the bundled boundary source and the existing whole-query limit.
// without the slower geocode fallback. // This elapsed time includes record acquisition and filtering; the region
// resolver has its own separate fallback budget.
const gulfStarted = Date.now(); const gulfStarted = Date.now();
r = await run('analyst_query', { r = await run('analyst_query', {
layers: ['flights'], layers: ['flights'],
@@ -1757,9 +1758,10 @@ async function runBehaviorLayer() {
report( report(
r?.ok === true && r?.ok === true &&
String(r?.coverage?.scope || '').includes('Gulf of Mexico') && String(r?.coverage?.scope || '').includes('Gulf of Mexico') &&
r?.coverage?.scopeSource === 'natural-earth' &&
gulfMs < 3000, gulfMs < 3000,
'behavior: analyst resolves the Gulf of Mexico from the bundled pack', 'behavior: analyst resolves the Gulf of Mexico from the bundled pack',
`scope=${r?.coverage?.scope} ms=${gulfMs} err=${r?.error || ''}`, `scope=${r?.coverage?.scope} source=${r?.coverage?.scopeSource} ms=${gulfMs} err=${r?.error || ''}`,
); );
// (5) zoom_to_globe is ABSOLUTE full-earth (>12,000 km band) // (5) zoom_to_globe is ABSOLUTE full-earth (>12,000 km band)
+87
View File
@@ -120,6 +120,93 @@ test('Georgia resolves from camera geography without a geocoder', async () => {
assert.deepEqual(geocodes, []); assert.deepEqual(geocodes, []);
}); });
test('Gold Coast in Australia cannot acquire its West African namesake outline', async () => {
for (const types of [['locality', 'political'], ['natural_feature']]) {
const { resolve, geocodes } = harness({
lat: -28.0167,
lng: 153.4,
label: 'Gold Coast, Australia',
name: 'Gold Coast',
types,
});
const resolved = await resolve('Gold Coast', viewerAt(-28.0167, 153.4));
assert.equal(resolved?.source, 'geocode');
assert.equal(resolved.lat, -28.0167);
assert.equal(resolved.lon, 153.4);
assert.equal(resolved.naturalRegion, undefined);
const outline = await resolved.resolveOutline();
assert.equal(outline?.naturalRegion, undefined);
assert.deepEqual(geocodes, ['Gold Coast']);
}
});
test('natural annotation outlines require a geocoded anchor', async () => {
for (const viewer of [AUSTIN, {}, viewerAt(46.5, 10, 248_000)]) {
const miss = harness();
assert.equal(await miss.resolve('the Alps', viewer), null);
assert.ok(miss.geocodes.includes('the Alps'));
}
const anchored = harness({
lat: 46.5,
lng: 10,
label: 'Alps',
name: 'Alps',
types: ['natural_feature'],
});
const resolved = await anchored.resolve('the Alps', {});
assert.equal(resolved.source, 'geocode');
const outline = await resolved.resolveOutline();
assert.equal(outline.naturalRegion, 'Alps');
assert.ok(outline.ring.length >= 8);
});
test('overlapping natural candidates continue through geocoding without choosing a ring', async () => {
const { resolve, geocodes } = harness({
lat: 45.6336,
lng: -74.86493,
label: 'Canadian Shield',
name: 'Canadian Shield',
types: ['natural_feature'],
});
const resolved = await resolve(
'Canadian Shield',
viewerAt(45.6336, -74.86493),
);
assert.equal(resolved?.source, 'geocode');
assert.equal((await resolved.resolveOutline())?.naturalRegion, undefined);
assert.deepEqual(geocodes, ['Canadian Shield']);
});
test('geocoded natural outlines respect district and around intent', async () => {
for (const extra of [
{ entityKind: 'district' },
{ intent: 'around_the_thing' },
]) {
const { resolve, geocodes } = harness({
lat: 46.5,
lng: 10,
label: 'Alps',
name: 'Alps',
types:
extra.entityKind === 'district' ? ['locality'] : ['natural_feature'],
});
const resolved = await resolve('the Alps', viewerAt(46.5, 10), extra);
assert.ok(geocodes.includes('the Alps'));
assert.equal((await resolved.resolveOutline())?.naturalRegion, undefined);
}
});
test('cancellation while a bundled admin lookup is pending cannot publish an annotation', async () => {
const controller = new AbortController();
const { resolve, geocodes } = harness();
const pending = resolve('the Alps', viewerAt(46.5, 10), {
signal: controller.signal,
});
controller.abort();
await assert.rejects(pending, { name: 'AbortError' });
assert.deepEqual(geocodes, []);
});
test('country asks and geocoded country scopes use the bundled outline', async () => { test('country asks and geocoded country scopes use the bundled outline', async () => {
const direct = harness(); const direct = harness();
for (const name of ['Switzerland', 'Iran', 'France', 'Japan']) { for (const name of ['Switzerland', 'Iran', 'France', 'Japan']) {
+106
View File
@@ -44,10 +44,115 @@ test('region ring: Natural Earth names resolve without a geocoder', async () =>
for (const name of ['Gulf of Mexico', 'the Alps']) { for (const name of ['Gulf of Mexico', 'the Alps']) {
const region = await resolveRegionRingForQuery(name); const region = await resolveRegionRingForQuery(name);
assert.ok(region?.ring?.length >= 3, `${name} resolves to a ring`); assert.ok(region?.ring?.length >= 3, `${name} resolves to a ring`);
assert.equal(region.source, 'natural-earth');
assert.equal(region.error, undefined); assert.equal(region.error, undefined);
} }
}); });
test('region ring: duplicate natural names need a containing geocoded anchor', async () => {
const { resolveRegionRingForQuery } = createAnnotationResolver({
featureSource: featureSource(),
});
const miss = geocoderFor(null);
assert.equal(
await resolveRegionRingForQuery('Cordillera Oriental', undefined, miss),
null,
);
assert.deepEqual(miss.calls, ['Cordillera Oriental']);
const inside = geocoderFor({
lat: 5,
lng: -73,
label: 'Cordillera Oriental',
name: 'Cordillera Oriental',
types: ['natural_feature'],
});
const region = await resolveRegionRingForQuery(
'Cordillera Oriental',
undefined,
inside,
);
assert.equal(region?.source, 'natural-earth');
assert.equal(region.name, 'Cordillera Oriental');
assert.ok(region.ring.length >= 8);
assert.deepEqual(inside.calls, ['Cordillera Oriental']);
});
test('region ring: outside and overlapping geocoded natural anchors stay unresolved', async () => {
const { resolveRegionRingForQuery } = createAnnotationResolver({
featureSource: featureSource(),
});
for (const [name, lat, lng] of [
['Cordillera Oriental', 30.27, -97.74],
['Canadian Shield', 45.6336, -74.86493],
]) {
const geocoder = geocoderFor({
lat,
lng,
label: name,
name,
types: ['natural_feature'],
});
assert.equal(
await resolveRegionRingForQuery(name, undefined, geocoder),
null,
);
assert.deepEqual(geocoder.calls, [name]);
}
});
test('region ring: geocoded administrative scope wins over duplicate natural geography', async () => {
const lookups = [];
const { resolveRegionRingForQuery } = createAnnotationResolver({
featureSource: featureSource({
getAdministrativeAreas: async (point) => {
lookups.push(point);
return [];
},
}),
});
const geo = geocoderFor({
lat: 5,
lng: -73,
label: 'Cordillera Oriental',
name: 'Cordillera Oriental',
types: ['locality'],
});
const resolved = await resolveRegionRingForQuery(
'Cordillera Oriental',
undefined,
geo,
);
assert.equal(
resolved,
null,
'an unavailable city boundary is not a natural-region outline',
);
assert.deepEqual(lookups, [{ lat: 5, lon: -73 }]);
assert.deepEqual(geo.calls, ['Cordillera Oriental']);
});
test('region ring: cancellation during bundled lookup and geocode never returns a region', async () => {
const { resolveRegionRingForQuery } = createAnnotationResolver({
featureSource: featureSource(),
});
const controller = new AbortController();
const pending = resolveRegionRingForQuery('the Alps', controller.signal);
controller.abort();
await assert.rejects(pending, { name: 'AbortError' });
const later = new AbortController();
await assert.rejects(
resolveRegionRingForQuery('Cordillera Oriental', later.signal, {
async geocode() {
later.abort();
return { place: null };
},
}),
{ name: 'AbortError' },
);
});
test('region ring: a slow admin-boundary lookup returns region-timeout within the budget', async () => { test('region ring: a slow admin-boundary lookup returns region-timeout within the budget', async () => {
let adminSignal; let adminSignal;
let release; let release;
@@ -119,6 +224,7 @@ test('region ring: bundled states and counties resolve without a geocoder', asyn
]) { ]) {
const region = await resolveRegionRingForQuery(query, undefined, geocoder); const region = await resolveRegionRingForQuery(query, undefined, geocoder);
assert.equal(region?.name, name, query); assert.equal(region?.name, name, query);
assert.equal(region.source, 'bundled-admin');
assert.ok(region.ring.length >= 8, `${query} has a real ring`); assert.ok(region.ring.length >= 8, `${query} has a real ring`);
} }
assert.deepEqual(geocoder.calls, [], 'no geocoding'); assert.deepEqual(geocoder.calls, [], 'no geocoding');
+40 -8
View File
@@ -444,6 +444,7 @@ export function createAnnotationResolver({
* AFTER the anchor mark is drawn and upgrade the mark in place. * AFTER the anchor mark is drawn and upgrade the mark in place.
*/ */
const resolveOutline = async () => { const resolveOutline = async () => {
signal?.throwIfAborted();
let scope = baseScope; let scope = baseScope;
const isAdmin = const isAdmin =
scope === 'country' || scope === 'country' ||
@@ -469,6 +470,7 @@ export function createAnnotationResolver({
const ne = await lookupNaturalRegionOutline(target, lat, lon).catch( const ne = await lookupNaturalRegionOutline(target, lat, lon).catch(
() => null, () => null,
); );
signal?.throwIfAborted();
if (ne) { if (ne) {
registerDynamicCredit(viewer, NATURAL_EARTH_CREDIT); registerDynamicCredit(viewer, NATURAL_EARTH_CREDIT);
const neCentroid = ringCentroid(ne.ring); const neCentroid = ringCentroid(ne.ring);
@@ -2004,8 +2006,8 @@ export function createAnnotationResolver({
/** /**
* Region ring for ANALYST queries ("how many flights over Texas / the Alps") — * Region ring for ANALYST queries ("how many flights over Texas / the Alps") —
* a name-only entry point that reuses this module's boundary machinery * a name-only entry point that reuses this module's boundary machinery
* without the annotation pipeline. Natural Earth pack first (offline, * without the annotation pipeline. Unique Natural Earth names resolve first
* instant; largest-area match is correct for a global name-only ask), then * (offline); duplicate names require a geocoded containment anchor, then
* geocode + admin boundary for states/countries/counties (Tier A disk-cached * geocode + admin boundary for states/countries/counties (Tier A disk-cached
* Overpass, so repeat asks are instant). Returns null when the name doesn't * Overpass, so repeat asks are instant). Returns null when the name doesn't
* resolve to a region-like boundary — the analyst engine reports that * resolve to a region-like boundary — the analyst engine reports that
@@ -2020,7 +2022,7 @@ export function createAnnotationResolver({
* @param {AbortSignal} [signal] * @param {AbortSignal} [signal]
* @param {object} [placeSearch] * @param {object} [placeSearch]
* @param {{budgetMs?: number}} [options] `Infinity` waits for the lookup. * @param {{budgetMs?: number}} [options] `Infinity` waits for the lookup.
* @returns {Promise<{name:string, ring:Array<[number,number]>}|{name:string, ring:null, error:'region-timeout'}|null>} * @returns {Promise<{name:string, ring:Array<[number,number]>, source:string}|{name:string, ring:null, error:'region-timeout'}|null>}
*/ */
async function resolveRegionRingForQuery( async function resolveRegionRingForQuery(
name, name,
@@ -2037,16 +2039,24 @@ export function createAnnotationResolver({
const q = String(name || '').trim(); const q = String(name || '').trim();
if (!q) return null; if (!q) return null;
const ne = await findNaturalRegion(q).catch(() => null); const ne = await findNaturalRegion(q).catch(() => null);
if (ne?.polygons?.length) { signal?.throwIfAborted();
if (ne?.candidates === 1 && ne.polygons?.length) {
// Largest ring carries the query scope; multi-ring regions (Andes) keep // Largest ring carries the query scope; multi-ring regions (Andes) keep
// their main cordillera — good enough for containment counting. // their main cordillera — good enough for containment counting.
const ring = [...ne.polygons].sort((a, b) => b.length - a.length)[0]; const ring = [...ne.polygons].sort((a, b) => b.length - a.length)[0];
if (ring?.length >= 3) return { name: ne.name, ring }; if (ring?.length >= 3)
return { name: ne.name, ring, source: 'natural-earth' };
} }
// Bundled states/provinces/counties by name (offline); the main part only, // Bundled states/provinces/counties by name (offline); the main part only,
// like the Natural Earth rung above. // like the Natural Earth rung above.
const admin = await findAdminArea(q).catch(() => null); const admin = await findAdminArea(q).catch(() => null);
if (admin) return { name: admin.name, ring: [...admin.ring] }; signal?.throwIfAborted();
if (admin)
return {
name: admin.name,
ring: [...admin.ring],
source: 'bundled-admin',
};
const lookup = resolveAdminRegionRing(q, signal, placeSearch); const lookup = resolveAdminRegionRing(q, signal, placeSearch);
if (!Number.isFinite(budgetMs)) return lookup; if (!Number.isFinite(budgetMs)) return lookup;
let timer; let timer;
@@ -2067,8 +2077,26 @@ export function createAnnotationResolver({
const geo = await geocodePlace(q, null, signal, placeSearch).catch( const geo = await geocodePlace(q, null, signal, placeSearch).catch(
() => null, () => null,
); );
signal?.throwIfAborted();
if (!geo) return null; if (!geo) return null;
const scope = scopeFromTypes(geo.types); const scope = scopeFromTypes(geo.types);
if (
scope === 'auto' ||
(scope === 'compound' && geo.types?.includes('natural_feature'))
) {
const natural = await lookupNaturalRegionOutline(
q,
geo.lat,
geo.lon,
).catch(() => null);
signal?.throwIfAborted();
if (natural)
return {
name: natural.name,
ring: natural.ring,
source: 'natural-earth',
};
}
if (!['country', 'state', 'county', 'city'].includes(scope)) return null; if (!['country', 'state', 'county', 'city'].includes(scope)) return null;
if (scope === 'state' || scope === 'county' || scope === 'country') { if (scope === 'state' || scope === 'county' || scope === 'country') {
const admin = await findAdminAreaAt( const admin = await findAdminAreaAt(
@@ -2077,12 +2105,16 @@ export function createAnnotationResolver({
geo.lon, geo.lon,
scope, scope,
).catch(() => null); ).catch(() => null);
if (admin) return { name: q, ring: [...admin.ring] }; signal?.throwIfAborted();
if (admin)
return { name: q, ring: [...admin.ring], source: 'bundled-admin' };
} }
const fp = await fetchAdminArea(geo.lat, geo.lon, q, scope, signal).catch( const fp = await fetchAdminArea(geo.lat, geo.lon, q, scope, signal).catch(
() => null, () => null,
); );
if (fp?.ring?.length >= 3) return { name: q, ring: fp.ring }; signal?.throwIfAborted();
if (fp?.ring?.length >= 3)
return { name: q, ring: fp.ring, source: 'remote-boundary' };
return null; return null;
} }
+14 -2
View File
@@ -881,6 +881,7 @@ export function createAnalystEngine(providers) {
followUp && !spec.scope ? lastResult.distanceOf || null : null; followUp && !spec.scope ? lastResult.distanceOf || null : null;
let resolvedScope = null; let resolvedScope = null;
let scopeNote = rememberedScope?.coverage || 'anywhere'; let scopeNote = rememberedScope?.coverage || 'anywhere';
let scopeSource = rememberedScope?.source || null;
// Human phrasing for the same scope, so every spoken count can name what it // Human phrasing for the same scope, so every spoken count can name what it
// measured ("8 in view", "about 30 within 250 km of Austin") instead of // measured ("8 in view", "about 30 within 250 km of Austin") instead of
// arriving as a bare number that contradicts the panel. // arriving as a bare number that contradicts the panel.
@@ -890,8 +891,16 @@ export function createAnalystEngine(providers) {
// further when a new scope is given. // further when a new scope is given.
const scope = spec.scope || { kind: followUp ? 'anywhere' : 'view' }; const scope = spec.scope || { kind: followUp ? 'anywhere' : 'view' };
if (scope.kind === 'region') { if (scope.kind === 'region') {
const region = await providers.resolveRegionRing(scope.name, signal); let region;
if (!isCurrent()) return cancelled(); try {
region = await providers.resolveRegionRing(scope.name, signal);
} catch (error) {
// Cancellation belongs to the query owner, not an error's name: a
// resource-local AbortError from a current lookup still propagates.
if (signal?.aborted || !isCurrent()) return cancelled();
throw error;
}
if (signal?.aborted || !isCurrent()) return cancelled();
if (region?.error === 'region-timeout') { if (region?.error === 'region-timeout') {
return { return {
ok: false, ok: false,
@@ -910,6 +919,7 @@ export function createAnalystEngine(providers) {
} }
resolvedScope = region; resolvedScope = region;
scopeNote = `region:${region.name}`; scopeNote = `region:${region.name}`;
scopeSource = region.source || null;
scopeLabel = `over ${region.name}`; scopeLabel = `over ${region.name}`;
scopeDetail = scopeLabel; scopeDetail = scopeLabel;
} else if (scope.kind === 'radius') { } else if (scope.kind === 'radius') {
@@ -1049,6 +1059,7 @@ export function createAnalystEngine(providers) {
layersQueried, layersQueried,
records: totals, records: totals,
scope: scopeNote, scope: scopeNote,
...(scopeSource ? { scopeSource } : {}),
...(rememberedScope?.note ? { note: rememberedScope.note } : {}), ...(rememberedScope?.note ? { note: rememberedScope.note } : {}),
...(queriedSnapshots.length ...(queriedSnapshots.length
? { feedProvenance: feedProvenanceEnvelope(queriedSnapshots) } ? { feedProvenance: feedProvenanceEnvelope(queriedSnapshots) }
@@ -1081,6 +1092,7 @@ export function createAnalystEngine(providers) {
label: memoryOverride?.scopeLabel || result.scopeLabel, label: memoryOverride?.scopeLabel || result.scopeLabel,
detail: memoryOverride?.scopeDetail || result.display.scope, detail: memoryOverride?.scopeDetail || result.display.scope,
coverage: committedCoverage.scope, coverage: committedCoverage.scope,
source: committedCoverage.scopeSource || null,
note: committedCoverage.note || null, note: committedCoverage.note || null,
}, },
}; };
+303
View File
@@ -1,6 +1,8 @@
import { test } from 'node:test'; import { test } from 'node:test';
import assert from 'node:assert/strict'; import assert from 'node:assert/strict';
import { createAnalystEngine, applyScope, haversineKm } from './analystEngine.js'; import { createAnalystEngine, applyScope, haversineKm } from './analystEngine.js';
import { createAnnotationResolver } from '../annotations/resolver.js';
import { FEATURE_SOURCE_METHODS } from '../sources/featureSource.js';
// Stub world: a square "Texland" region, flights + ships + fires around it. // Stub world: a square "Texland" region, flights + ships + fires around it.
const TEXLAND = { name: 'Texland', ring: [[-100, 28], [-94, 28], [-94, 33], [-100, 33]] }; const TEXLAND = { name: 'Texland', ring: [[-100, 28], [-94, 28], [-94, 33], [-100, 33]] };
@@ -131,6 +133,175 @@ test('analyst: count flights over a region', async () => {
assert.ok(r.coverage.scope.includes('Texland')); assert.ok(r.coverage.scope.includes('Texland'));
}); });
test('analyst: region coverage identifies the source of the boundary used for counting', async () => {
for (const source of ['natural-earth', 'bundled-admin', 'remote-boundary']) {
const engine = createAnalystEngine({
getRecords: () => FLIGHTS,
resolveRegionRing: async () => ({ ...TEXLAND, source }),
});
const result = await engine.query({
layers: ['flights'],
scope: { kind: 'region', name: 'Texland' },
});
assert.equal(result.ok, true);
assert.equal(result.count, 3, 'the boundary excludes the Oregon flight');
assert.equal(result.coverage.scope, 'region:Texland');
assert.equal(result.coverage.scopeSource, source);
}
});
test('analyst: filter-only follow-ups retain the region source without resolving again', async () => {
const lookups = [];
const engine = createAnalystEngine({
getRecords: () => FLIGHTS,
resolveRegionRing: async (name) => {
lookups.push(name);
return { ...TEXLAND, source: 'natural-earth' };
},
});
const first = await engine.query({
layers: ['flights'],
scope: { kind: 'region', name: 'Texland' },
});
const airborne = await engine.query({
followUp: true,
filters: [{ field: 'onGround', op: 'eq', value: false }],
});
const military = await engine.query({
followUp: true,
filters: [{ field: 'military', op: 'eq', value: true }],
});
assert.equal(first.count, 3);
assert.equal(airborne.count, 2);
assert.deepEqual(military.items.map((item) => item.id), ['RCH01']);
for (const result of [first, airborne, military]) {
assert.equal(result.coverage.scope, 'region:Texland');
assert.equal(result.scopeLabel, 'over Texland');
assert.equal(result.coverage.scopeSource, 'natural-earth');
}
assert.deepEqual(lookups, ['Texland']);
});
test('analyst: an explicit replacement region replaces retained provenance', async () => {
const engine = createAnalystEngine({
getRecords: () => FLIGHTS,
resolveRegionRing: async (name) => name === 'Texland'
? { ...TEXLAND, source: 'natural-earth' }
: {
name,
ring: [[-98, 30.5], [-96, 30.5], [-96, 31.5], [-98, 31.5]],
source: 'remote-boundary',
},
});
await engine.query({
layers: ['flights'],
scope: { kind: 'region', name: 'Texland' },
});
const replacement = await engine.query({
followUp: true,
scope: { kind: 'region', name: 'Northern Texland' },
});
const followUp = await engine.query({ followUp: true });
for (const result of [replacement, followUp]) {
assert.deepEqual(result.items.map((item) => item.id), ['RCH01']);
assert.equal(result.coverage.scope, 'region:Northern Texland');
assert.equal(result.coverage.scopeSource, 'remote-boundary');
}
});
test('analyst: an explicit scope without provenance clears the retained region source', async () => {
for (const scope of [
{ kind: 'anywhere' },
{ kind: 'view' },
{ kind: 'radius', km: 150 },
{ kind: 'region', name: 'Uncredited Texland' },
]) {
const engine = createAnalystEngine({
getRecords: () => FLIGHTS,
resolveRegionRing: async (name) => ({
...TEXLAND,
name,
...(name === 'Texland' ? { source: 'natural-earth' } : {}),
}),
getViewContext: () => ({ lat: 30.27, lon: -97.74, viewRadiusKm: 150 }),
});
const first = await engine.query({
layers: ['flights'],
scope: { kind: 'region', name: 'Texland' },
});
const replacement = await engine.query({ followUp: true, scope });
const followUp = await engine.query({ followUp: true });
assert.equal(first.coverage.scopeSource, 'natural-earth');
for (const result of [replacement, followUp]) {
assert.equal(result.ok, true, scope.kind);
assert.equal(result.count, 3, scope.kind);
assert.equal(Object.hasOwn(result.coverage, 'scopeSource'), false, scope.kind);
assert.notEqual(result.coverage.scope, first.coverage.scope, scope.kind);
}
}
});
for (const [label, committedScope, scopeSource] of [
['replacement boundary source', 'region:Presented Texland', 'remote-boundary'],
['window without a boundary source', 'window:presented', null],
]) {
test(`analyst: pre-commit displayed cohort retains ${label} and its feed provenance`, async () => {
const committedSnapshot = {
id: 'flights', enabled: true, feedState: 'stale',
source: 'displayed-cohort-feed', lastUpdate: 1_800_000_000_000,
};
let reads = 0;
let lookups = 0;
const engine = createAnalystEngine({
getRecords: () => { reads++; return FLIGHTS; },
getLayerSnapshot: () => ({
id: 'flights', enabled: true, feedState: 'nominal', source: 'current-feed',
}),
resolveRegionRing: async () => {
lookups++;
return { ...TEXLAND, source: 'natural-earth' };
},
});
await engine.query(
{ layers: ['flights'], scope: { kind: 'region', name: 'Texland' } },
{
beforeCommit: async (result) => {
assert.equal(result.count, 3);
assert.equal(result.coverage.scopeSource, 'natural-earth');
const { scopeSource: _originalSource, ...coverage } = result.coverage;
return {
matched: [{ key: 'flights', rows: [FLIGHTS[1]] }],
scopeLabel: 'the presented cohort',
scopeDetail: 'the presented cohort window',
coverage: {
...coverage,
scope: committedScope,
...(scopeSource ? { scopeSource } : {}),
feedProvenance: { layers: [committedSnapshot] },
},
};
},
},
);
const followUp = await engine.query({ followUp: true });
assert.equal(followUp.count, 1);
assert.deepEqual(followUp.items.map((item) => item.id), ['RCH01']);
assert.equal(followUp.scopeLabel, 'the presented cohort');
assert.equal(followUp.display.scope, 'the presented cohort window');
assert.equal(followUp.coverage.scope, committedScope);
if (scopeSource) assert.equal(followUp.coverage.scopeSource, scopeSource);
else assert.equal(Object.hasOwn(followUp.coverage, 'scopeSource'), false);
assert.equal(followUp.coverage.feedProvenance.overall, 'stale');
assert.deepEqual(followUp.coverage.feedProvenance.layers, [committedSnapshot]);
assert.equal(reads, 1, 'displayed cohort is not replaced by another feed read');
assert.equal(lookups, 1, 'follow-up retains the committed scope without resolving again');
});
}
test('analyst: attribute filter — above 40,000 ft (~12,192 m)', async () => { test('analyst: attribute filter — above 40,000 ft (~12,192 m)', async () => {
const r = await makeEngine().query({ const r = await makeEngine().query({
layers: ['flights'], scope: { kind: 'anywhere' }, layers: ['flights'], scope: { kind: 'anywhere' },
@@ -317,6 +488,138 @@ test('analyst: region lookup receives and obeys the owning action signal', async
assert.equal(result.cancelled, true); assert.equal(result.cancelled, true);
}); });
for (const [label, reason, trackAuthority] of [
['default abort reason', undefined, false],
['custom error reason', new Error('operator stopped'), false],
['primitive reason and revoked authority', 'operator stopped', true],
]) {
test(`analyst: real region resolver cancellation preserves memory (${label})`, async () => {
const started = Promise.withResolvers();
const geocode = Promise.withResolvers();
const controller = new AbortController();
const lookups = [];
const { resolveRegionRingForQuery } = createAnnotationResolver({
featureSource: Object.fromEntries(FEATURE_SOURCE_METHODS.map((method) => [
method,
async () => { throw new Error('offline region query reached feature source'); },
])),
});
const placeSearch = {
geocode(name, { signal }) {
lookups.push(name);
assert.equal(signal, controller.signal);
started.resolve();
return geocode.promise;
},
};
const engine = createAnalystEngine({
getRecords: () => FLIGHTS,
resolveRegionRing: (name, signal) => resolveRegionRingForQuery(
name, signal, placeSearch, { budgetMs: Infinity },
),
});
const first = await engine.query({
layers: ['flights'], scope: { kind: 'region', name: 'Texas' },
});
assert.equal(first.ok, true);
assert.equal(first.coverage.scopeSource, 'bundled-admin');
assert.deepEqual(first.items.map((item) => item.id), ['SWA1', 'RCH01', 'GND1']);
let commits = 0;
const pending = engine.query(
{ layers: ['flights'], scope: { kind: 'region', name: 'Cordillera Oriental' } },
{
signal: controller.signal,
...(trackAuthority ? { isCurrent: () => !controller.signal.aborted } : {}),
beforeCommit: async () => { commits++; },
},
);
await started.promise;
controller.abort(reason);
// Let an actual late geocode result cross the resolver's post-await check.
geocode.resolve({ place: {
lat: 5, lng: -73, label: 'Cordillera Oriental',
name: 'Cordillera Oriental', types: ['natural_feature'],
} });
const result = await pending;
assert.equal(result.ok, false);
assert.equal(result.code, 'CANCELLED');
assert.equal(result.cancelled, true);
assert.equal(commits, 0);
const followUp = await engine.query({ followUp: true });
assert.deepEqual(followUp.items.map((item) => item.id), first.items.map((item) => item.id));
assert.equal(followUp.coverage.scope, first.coverage.scope);
assert.equal(followUp.coverage.scopeSource, 'bundled-admin');
assert.equal(followUp.scopeLabel, first.scopeLabel);
assert.deepEqual(lookups, ['Cordillera Oriental'], 'follow-up does not resolve again');
});
}
test('analyst: a rejected superseded region lookup returns cancellation and retains its prior cohort', async () => {
const lookup = Promise.withResolvers();
let current = true;
const engine = createAnalystEngine({
getRecords: () => FLIGHTS,
resolveRegionRing: (name) => name === 'Texland'
? { ...TEXLAND, source: 'natural-earth' }
: lookup.promise,
});
const first = await engine.query({ layers: ['flights'], scope: { kind: 'region', name: 'Texland' } });
const pending = engine.query(
{ layers: ['flights'], scope: { kind: 'region', name: 'Replacement' } },
{ isCurrent: () => current },
);
current = false;
lookup.reject(new Error('late lookup failed'));
assert.equal((await pending).code, 'CANCELLED');
const followUp = await engine.query({ followUp: true });
assert.deepEqual(followUp.items.map((item) => item.id), first.items.map((item) => item.id));
assert.equal(followUp.coverage.scopeSource, 'natural-earth');
assert.equal(followUp.coverage.scope, first.coverage.scope);
});
test('analyst: an aborted owner cannot commit a region provider that returns a late success', async () => {
const controller = new AbortController();
const lookup = Promise.withResolvers();
const engine = createAnalystEngine({
getRecords: () => FLIGHTS,
resolveRegionRing: () => lookup.promise,
});
const pending = engine.query(
{ layers: ['flights'], scope: { kind: 'region', name: 'Texland' } },
{ signal: controller.signal },
);
controller.abort();
lookup.resolve({ ...TEXLAND, source: 'natural-earth' });
assert.equal((await pending).code, 'CANCELLED');
assert.equal(engine.hasMemory(), false);
});
for (const error of [new Error('live lookup failed'), new DOMException('resource stopped', 'AbortError')]) {
test(`analyst: live region ${error.name} propagates without changing memory`, async () => {
const controller = new AbortController();
const engine = createAnalystEngine({
getRecords: () => FLIGHTS,
resolveRegionRing: async (name) => {
if (name === 'Texland') return { ...TEXLAND, source: 'natural-earth' };
throw error;
},
});
const first = await engine.query({ layers: ['flights'], scope: { kind: 'region', name: 'Texland' } });
await assert.rejects(
engine.query(
{ layers: ['flights'], scope: { kind: 'region', name: 'Replacement' } },
{ signal: controller.signal },
),
(thrown) => thrown === error,
);
assert.equal(controller.signal.aborted, false);
const followUp = await engine.query({ followUp: true });
assert.deepEqual(followUp.items.map((item) => item.id), first.items.map((item) => item.id));
assert.equal(followUp.coverage.scope, first.coverage.scope);
assert.equal(followUp.coverage.scopeSource, 'natural-earth');
});
}
test('analyst: route fields queryable from cached enrichment only', async () => { test('analyst: route fields queryable from cached enrichment only', async () => {
const r = await makeEngine().query({ const r = await makeEngine().query({
layers: ['flights'], scope: { kind: 'anywhere' }, layers: ['flights'], scope: { kind: 'anywhere' },
+28 -21
View File
@@ -189,7 +189,7 @@ const loadIndex = createRetryableLoader(async () => {
return index; return index;
}); });
function toResult(entry) { function toResult(entry, candidates = 1) {
return { return {
name: entry.name, name: entry.name,
featurecla: entry.featurecla, featurecla: entry.featurecla,
@@ -197,6 +197,7 @@ function toResult(entry) {
polygons: entry.polygons, polygons: entry.polygons,
bboxDiagonalKm: entry.bboxDiagonalKm, bboxDiagonalKm: entry.bboxDiagonalKm,
areaKm2: entry.areaKm2, areaKm2: entry.areaKm2,
candidates,
}; };
} }
@@ -209,7 +210,9 @@ function toResult(entry) {
* @param {string} query e.g. "the Alps", "Rockies", "Gulf of Mexico" * @param {string} query e.g. "the Alps", "Rockies", "Gulf of Mexico"
* @returns {Promise<{name:string, featurecla:string, kind:'natural'|'marine', * @returns {Promise<{name:string, featurecla:string, kind:'natural'|'marine',
* polygons:Array<Array<[number,number]>>, bboxDiagonalKm:number, * polygons:Array<Array<[number,number]>>, bboxDiagonalKm:number,
* areaKm2:number}|null>} largest-area match, or null when nothing matches. * areaKm2:number, candidates:number}|null>} largest-area match plus the
* number of same-name entries, or null when nothing matches. Uniqueness in
* this pack alone does not establish the user's intended place.
*/ */
export async function findNaturalRegion(query) { export async function findNaturalRegion(query) {
const norm = normalizeName(query); const norm = normalizeName(query);
@@ -223,7 +226,7 @@ export async function findNaturalRegion(query) {
for (const key of candidates) { for (const key of candidates) {
if (!key) continue; if (!key) continue;
const list = index.get(key); const list = index.get(key);
if (list && list.length) return toResult(list[0]); if (list && list.length) return toResult(list[0], list.length);
} }
return null; return null;
} }
@@ -270,9 +273,10 @@ export function pointInRing(ring, lat, lon) {
/** /**
* Resolve a natural-region OUTLINE ring for the annotation resolver's first * Resolve a natural-region OUTLINE ring for the annotation resolver's first
* rung. Stricter than `findNaturalRegion`: walks ALL entries sharing the * rung. Stricter than `findNaturalRegion`: walks ALL entries sharing the
* matched name (duplicate names included — the US and Spanish "Sierra * matched name (duplicate names included), trying exact names, aliases and
* Nevada" both exist upstream) and returns the single ring that CONTAINS the * broader suffix variants in that order. Returns a ring only when exactly one
* geocoded anchor. Containment is simultaneously the disambiguator and the * entry under the matched name CONTAINS the anchor. Containment is the
* disambiguator and the
* wrong-place guard: when no ring contains the anchor this returns null and * wrong-place guard: when no ring contains the anchor this returns null and
* the resolver's normal ladder continues unchanged. * the resolver's normal ladder continues unchanged.
* *
@@ -291,23 +295,26 @@ export async function lookupNaturalRegionOutline(query, lat, lon) {
ALIASES[norm], ALIASES[norm],
...suffixVariants(ALIASES[norm] || norm), ...suffixVariants(ALIASES[norm] || norm),
]; ];
const seen = new Set(); for (const key of new Set(candidates.filter(Boolean))) {
for (const key of candidates) { const containing = [];
if (!key || seen.has(key)) continue;
seen.add(key);
for (const entry of index.get(key) || []) { for (const entry of index.get(key) || []) {
for (const ring of entry.polygons) { const ring = entry.polygons.find((candidate) =>
if (pointInRing(ring, lat, lon)) { pointInRing(candidate, lat, lon),
return { );
name: entry.name, if (ring) containing.push({ entry, ring });
kind: entry.kind,
featurecla: entry.featurecla,
ring,
areaKm2: entry.areaKm2,
};
}
}
} }
// Specific names win over aliases and broader rewrites. Ambiguity belongs
// to this name, not the union of every lower-priority candidate name.
if (containing.length > 1) return null;
if (containing.length === 0) continue;
const [{ entry, ring }] = containing;
return {
name: entry.name,
kind: entry.kind,
featurecla: entry.featurecla,
ring,
areaKm2: entry.areaKm2,
};
} }
return null; return null;
} }
+41 -2
View File
@@ -33,7 +33,10 @@ test('marquee ranges resolve with sane areas (owner acceptance: Alps + Rockies)'
}); });
test('aliases and articles: "the Alps", "Rockies", "Sahara Desert", "Himalaya"', async () => { test('aliases and articles: "the Alps", "Rockies", "Sahara Desert", "Himalaya"', async () => {
assert.equal((await findNaturalRegion('the Alps'))?.name, 'Alps'); const alps = await findNaturalRegion('the Alps');
assert.equal(alps?.name, 'Alps');
assert.equal(alps?.candidates, 1);
assert.equal((await findNaturalRegion('Alps mountains'))?.name, 'Alps');
assert.equal((await findNaturalRegion('Rockies'))?.name, 'Rocky Mountains'); assert.equal((await findNaturalRegion('Rockies'))?.name, 'Rocky Mountains');
assert.equal((await findNaturalRegion('the Rockies'))?.name, 'Rocky Mountains'); assert.equal((await findNaturalRegion('the Rockies'))?.name, 'Rocky Mountains');
assert.equal((await findNaturalRegion('Sahara Desert'))?.name, 'Sahara'); assert.equal((await findNaturalRegion('Sahara Desert'))?.name, 'Sahara');
@@ -41,6 +44,11 @@ test('aliases and articles: "the Alps", "Rockies", "Sahara Desert", "Himalaya"',
assert.equal((await findNaturalRegion(' THE ALPS '))?.name, 'Alps'); assert.equal((await findNaturalRegion(' THE ALPS '))?.name, 'Alps');
}); });
test('duplicate natural-region names expose ambiguity instead of claiming uniqueness', async () => {
assert.ok((await findNaturalRegion('Cordillera Oriental'))?.candidates > 1);
assert.ok((await findNaturalRegion('Canadian Shield'))?.candidates > 1);
});
test('major deserts/ranges resolve with sane areas', async () => { test('major deserts/ranges resolve with sane areas', async () => {
const sahara = await findNaturalRegion('Sahara'); const sahara = await findNaturalRegion('Sahara');
assert.ok(sahara && sahara.areaKm2 > 8_000_000 && sahara.areaKm2 < 12_000_000, assert.ok(sahara && sahara.areaKm2 > 8_000_000 && sahara.areaKm2 < 12_000_000,
@@ -122,24 +130,55 @@ test('listRegions() enumerates both kinds for diagnostics', async () => {
}); });
// ── lookupNaturalRegionOutline (resolver first-rung contract) ────── // ── lookupNaturalRegionOutline (resolver first-rung contract) ──────
test('outline lookup: Alps ring via containment anchor', async () => { test('outline lookup: containing exact names win over broader suffix variants', async () => {
const r = await lookupNaturalRegionOutline('the Alps', 46.5, 10.0); const r = await lookupNaturalRegionOutline('the Alps', 46.5, 10.0);
assert.ok(r, 'Alps must resolve with an inside anchor'); assert.ok(r, 'Alps must resolve with an inside anchor');
assert.equal(r.name, 'Alps'); assert.equal(r.name, 'Alps');
assert.ok(r.ring.length >= 8, 'range-scale ring'); assert.ok(r.ring.length >= 8, 'range-scale ring');
assert.ok(r.areaKm2 > 100000 && r.areaKm2 < 400000, `sane Alps area, got ${r.areaKm2}`); assert.ok(r.areaKm2 > 100000 && r.areaKm2 < 400000, `sane Alps area, got ${r.areaKm2}`);
const range = await lookupNaturalRegionOutline('Alaska Range', 63.14, -148.71);
assert.equal(range?.name, 'Alaska Range');
assert.ok(pointInRing(range.ring, 63.14, -148.71));
assert.equal((await lookupNaturalRegionOutline('Alaska', 63.14, -148.71))?.name, 'Alaska');
}); });
test('outline lookup: duplicate names disambiguate by anchor containment', async () => { test('outline lookup: duplicate names disambiguate by anchor containment', async () => {
const us = await lookupNaturalRegionOutline('Sierra Nevada', 37.2, -119.0); const us = await lookupNaturalRegionOutline('Sierra Nevada', 37.2, -119.0);
assert.ok(us, 'US anchor must match a Sierra Nevada'); assert.ok(us, 'US anchor must match a Sierra Nevada');
assert.ok(pointInRing(us.ring, 37.2, -119.0), 'returned ring contains the US anchor'); assert.ok(pointInRing(us.ring, 37.2, -119.0), 'returned ring contains the US anchor');
const colombia = await lookupNaturalRegionOutline('Cordillera Oriental', 5, -73);
assert.ok(colombia, 'a single containing candidate resolves a duplicate name');
assert.ok(pointInRing(colombia.ring, 5, -73));
});
test('outline lookup: overlapping duplicate candidates remain unresolved', async () => {
assert.equal(await lookupNaturalRegionOutline('Canadian Shield', 45.6336, -74.86493), null);
}); });
test('outline lookup: anchor outside every ring → null (wrong-place guard)', async () => { test('outline lookup: anchor outside every ring → null (wrong-place guard)', async () => {
assert.equal(await lookupNaturalRegionOutline('the Alps', 30.26, -97.77), null); assert.equal(await lookupNaturalRegionOutline('the Alps', 30.26, -97.77), null);
}); });
test('outline lookup: a unique physical-region name does not override an outside contemporary place', async () => {
for (const [name, lat, lon] of [
['Gold Coast', -28, 153.4],
['Santa Cruz', 37, -122],
['Isabela', 17, 122],
['India', 28, 95], // Northeast India lies outside the physical peninsula.
['Korea', 33.5, 126.5], // Jeju lies outside the physical peninsula.
]) {
assert.equal((await findNaturalRegion(name))?.candidates, 1, `${name} has a unique physical namesake`);
assert.equal(await lookupNaturalRegionOutline(name, lat, lon), null, `${name} requires containment`);
}
});
test('outline lookup: aliases retain containment checks', async () => {
const alps = await lookupNaturalRegionOutline('Alps mountains', 46.5, 10);
assert.equal(alps?.name, 'Alps');
assert.ok(pointInRing(alps.ring, 46.5, 10));
assert.equal(await lookupNaturalRegionOutline('Alps mountains', -28, 153.4), null);
});
test('outline lookup: non-region names never match', async () => { test('outline lookup: non-region names never match', async () => {
assert.equal(await lookupNaturalRegionOutline('Zilker Park', 30.26, -97.77), null); assert.equal(await lookupNaturalRegionOutline('Zilker Park', 30.26, -97.77), null);
assert.equal(await lookupNaturalRegionOutline('Texas', 31.0, -99.0), null); assert.equal(await lookupNaturalRegionOutline('Texas', 31.0, -99.0), null);
+118
View File
@@ -152,6 +152,124 @@ test('regionFramingPlan: E-W elongated region looks along the east axis', () =>
assert.equal(plan.headingDeg, 90); assert.equal(plan.headingDeg, 90);
}); });
// Compatibility guard for inherited geocode authority, not a newly fixed bug.
test('search retains main geocode authority for Gold Coast, Australia', async () => {
const viewer = stubViewer();
const result = await runSearch(viewer, {}, {
query: 'Gold Coast',
result: {
formatted_address: 'Gold Coast, Queensland, Australia',
types: ['locality', 'political'],
geometry: {
location: { lat: -28, lng: 153.4 },
viewport: boxOf(-28.2, 153.2, -27.8, 153.6),
},
},
});
assert.equal(result.label, 'Gold Coast, Queensland, Australia');
assert.equal(result.navigationMode, 'city-overview');
assert.equal(viewer.flights.length, 1);
const flown = flownRectangleDegrees(viewer);
assert.ok(Math.abs((flown.south + flown.north) / 2 + 28) < 0.000001);
assert.ok(Math.abs((flown.west + flown.east) / 2 - 153.4) < 0.000001);
});
function alpsViewer() {
const viewer = stubViewer();
Object.assign(viewer.camera.positionCartographic, {
latitude: Cesium.Math.toRadians(46.5),
longitude: Cesium.Math.toRadians(10),
height: 248_000,
});
return viewer;
}
test('a successful nearby recovery resolves after a geocoder miss', async () => {
const viewer = alpsViewer();
const result = await searchAndFlyTo(viewer, 'the Alps', {
placeSearch: { async geocode() { return { place: null, answered: true }; } },
recoverNearView: async () => ({
lat: 46.6,
lon: 10.1,
label: 'The Alps local landmark',
types: ['locality', 'political'],
viewport: { low: { latitude: 46.5, longitude: 10 }, high: { latitude: 46.7, longitude: 10.2 } },
}),
});
assert.equal(result.label, 'The Alps local landmark');
assert.equal(result.navigationMode, 'city-overview');
assert.equal(viewer.flights.length, 1);
});
test('a geocoder miss returns null even over a namesake region', async () => {
for (const answered of [true, false]) {
const viewer = alpsViewer();
const result = await searchAndFlyTo(viewer, 'the Alps', {
placeSearch: { async geocode() { return { place: null, answered }; } },
recoverNearView: async () => null,
});
assert.equal(result, null);
assert.equal(viewer.flights.length, 0);
}
});
test('a geocoder rejection propagates without nearby recovery or a flight', async () => {
const viewer = alpsViewer();
const failure = new Error('geocoder failed');
let recoveries = 0;
await assert.rejects(searchAndFlyTo(viewer, 'the Alps', {
placeSearch: { async geocode() { throw failure; } },
recoverNearView: async () => { recoveries += 1; return null; },
}), (error) => error === failure);
assert.equal(recoveries, 0);
assert.equal(viewer.flights.length, 0);
});
test('search cancellation before or during geocoding cannot issue a flight', async () => {
for (const preAborted of [true, false]) {
const viewer = alpsViewer();
const controller = new AbortController();
if (preAborted) controller.abort();
let geocodes = 0;
let recoveries = 0;
await assert.rejects(searchAndFlyTo(viewer, 'the Alps', {
signal: controller.signal,
placeSearch: {
async geocode(_query, { signal }) {
geocodes += 1;
assert.equal(signal, controller.signal);
controller.abort();
return { place: null, answered: true };
},
},
recoverNearView: async () => { recoveries += 1; return null; },
}), { name: 'AbortError' });
assert.equal(geocodes, preAborted ? 0 : 1);
assert.equal(recoveries, 0);
assert.equal(viewer.flights.length, 0);
}
});
test('cancellation during nearby recovery cannot issue a flight', async () => {
const viewer = alpsViewer();
const controller = new AbortController();
let authorityChecks = 0;
await assert.rejects(searchAndFlyTo(viewer, 'the Alps', {
signal: controller.signal,
placeSearch: { async geocode() { return { place: null, answered: true }; } },
recoverNearView: async () => {
controller.abort();
return {
lat: 46.6, lon: 10.1, label: 'Alpine landmark',
types: ['locality'],
};
},
beforeFly: () => { authorityChecks += 1; return true; },
}), { name: 'AbortError' });
assert.equal(authorityChecks, 0);
assert.equal(viewer.flights.length, 0);
});
test('regionFramingPlan: park- and lake-scale viewports keep full framing', () => { test('regionFramingPlan: park- and lake-scale viewports keep full framing', () => {
// Zilker Park (~3 km). // Zilker Park (~3 km).
assert.equal(regionFramingPlan({ assert.equal(regionFramingPlan({