mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 18:34:02 +00:00
fix(data): load bundled region packs in the browser; bound region lookups (#743)
The Natural Earth and neighborhood packs were loaded with a JSON-attributed
import(). The Vite dev server answers that request with a JavaScript module,
which the browser rejects, so every analyst region scope failed and fell
through to a geocode lookup that could take 12-28 s.
- src/data/bundledJson.js fetches the pack from a new URL(..., import.meta.url)
asset in the browser; under Node the caller's attributed import runs.
- resolveRegionRingForQuery caps the geocode + admin-boundary rung at 3 s and
returns { error: 'region-timeout' }; the lookup keeps running and fills the
cache. The analyst query reports it as code 'region-timeout'. The Nepal
locator opts out of the budget.
- Unit tests for the loader, the timeout and the analyst error; the behavior
harness checks the Gulf of Mexico scope and retries Texas on region-timeout.
Co-authored-by: Bilawal Sidhu <[email protected]>
This commit is contained in:
1 parent
513dc87a0e
commit
ce671ce500
15 files changed
+263
-28
No files matched your search
@@ -0,0 +1,86 @@
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { FEATURE_SOURCE_METHODS } from '../sources/featureSource.js';
|
||||
import { createAnnotationResolver } from './resolver.js';
|
||||
|
||||
function featureSource(overrides = {}) {
|
||||
const source = {};
|
||||
for (const method of FEATURE_SOURCE_METHODS)
|
||||
source[method] = async () => null;
|
||||
return Object.assign(source, overrides);
|
||||
}
|
||||
|
||||
const texasGeocode = {
|
||||
async geocode() {
|
||||
return {
|
||||
place: {
|
||||
lat: 31.0,
|
||||
lng: -99.9,
|
||||
label: 'Texas, USA',
|
||||
name: 'Texas',
|
||||
types: ['administrative_area_level_1', 'political'],
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
test('region ring: Natural Earth names resolve without a geocoder', async () => {
|
||||
const { resolveRegionRingForQuery } = createAnnotationResolver({
|
||||
featureSource: featureSource(),
|
||||
});
|
||||
for (const name of ['Gulf of Mexico', 'the Alps']) {
|
||||
const region = await resolveRegionRingForQuery(name);
|
||||
assert.ok(region?.ring?.length >= 3, `${name} resolves to a ring`);
|
||||
assert.equal(region.error, undefined);
|
||||
}
|
||||
});
|
||||
|
||||
test('region ring: a slow admin-boundary lookup returns region-timeout within the budget', async () => {
|
||||
let adminSignal;
|
||||
let release;
|
||||
const { resolveRegionRingForQuery } = createAnnotationResolver({
|
||||
featureSource: featureSource({
|
||||
getAdministrativeAreas(_point, { signal }) {
|
||||
adminSignal = signal;
|
||||
return new Promise((resolve) => {
|
||||
release = resolve;
|
||||
});
|
||||
},
|
||||
}),
|
||||
});
|
||||
const started = Date.now();
|
||||
const region = await resolveRegionRingForQuery(
|
||||
'Texas',
|
||||
undefined,
|
||||
texasGeocode,
|
||||
{ budgetMs: 50 },
|
||||
);
|
||||
assert.deepEqual(region, {
|
||||
name: 'Texas',
|
||||
ring: null,
|
||||
error: 'region-timeout',
|
||||
});
|
||||
assert.ok(
|
||||
Date.now() - started < 1000,
|
||||
'returns at the budget, not the lookup',
|
||||
);
|
||||
// The lookup is left running so it can fill the boundary cache.
|
||||
assert.equal(adminSignal?.aborted ?? false, false);
|
||||
release(null);
|
||||
});
|
||||
|
||||
test('region ring: an unbounded budget waits for the lookup', async () => {
|
||||
const { resolveRegionRingForQuery } = createAnnotationResolver({
|
||||
featureSource: featureSource({
|
||||
getAdministrativeAreas: () =>
|
||||
new Promise((resolve) => setTimeout(() => resolve(null), 30)),
|
||||
}),
|
||||
});
|
||||
const region = await resolveRegionRingForQuery(
|
||||
'Texas',
|
||||
undefined,
|
||||
texasGeocode,
|
||||
{ budgetMs: Infinity },
|
||||
);
|
||||
assert.equal(region, null);
|
||||
});
|
||||
@@ -25,6 +25,9 @@ import {
|
||||
import { unavailablePlaceSearch } from '../search/placeSearch.js';
|
||||
import { isPickedWorldPosition } from '../data/scenePick.js';
|
||||
|
||||
/** Ms the analyst region lookup waits on geocode + admin boundary. */
|
||||
export const REGION_FALLBACK_BUDGET_MS = 3_000;
|
||||
|
||||
/** Own annotation lookup caches and ranking of supplied feature candidates. */
|
||||
export function createAnnotationResolver({
|
||||
boundarySource,
|
||||
@@ -1877,14 +1880,22 @@ export function createAnnotationResolver({
|
||||
* resolve to a region-like boundary — the analyst engine reports that
|
||||
* honestly rather than silently scoping to nothing.
|
||||
*
|
||||
* The geocode + admin-boundary rung is capped at `budgetMs`. Past it the
|
||||
* call returns `{name, ring: null, error: 'region-timeout'}` so a voice
|
||||
* answer is not held for tens of seconds; the lookup keeps running and
|
||||
* fills the boundary cache, so asking again shortly is fast.
|
||||
*
|
||||
* @param {string} name e.g. "Texas", "the Alps", "France", "Gulf of Mexico"
|
||||
* @param {AbortSignal} [signal]
|
||||
* @returns {Promise<{name:string, ring:Array<[number,number]>}|null>}
|
||||
* @param {object} [placeSearch]
|
||||
* @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>}
|
||||
*/
|
||||
async function resolveRegionRingForQuery(
|
||||
name,
|
||||
signal,
|
||||
placeSearch = unavailablePlaceSearch,
|
||||
{ budgetMs = REGION_FALLBACK_BUDGET_MS } = {},
|
||||
) {
|
||||
signal = lifetime
|
||||
? signal
|
||||
@@ -1901,6 +1912,23 @@ export function createAnnotationResolver({
|
||||
const ring = [...ne.polygons].sort((a, b) => b.length - a.length)[0];
|
||||
if (ring?.length >= 3) return { name: ne.name, ring };
|
||||
}
|
||||
const lookup = resolveAdminRegionRing(q, signal, placeSearch);
|
||||
if (!Number.isFinite(budgetMs)) return lookup;
|
||||
let timer;
|
||||
const timeout = new Promise((resolve) => {
|
||||
timer = setTimeout(
|
||||
() => resolve({ name: q, ring: null, error: 'region-timeout' }),
|
||||
budgetMs,
|
||||
);
|
||||
});
|
||||
try {
|
||||
return await Promise.race([lookup, timeout]);
|
||||
} finally {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
}
|
||||
|
||||
async function resolveAdminRegionRing(q, signal, placeSearch) {
|
||||
const geo = await geocodePlace(q, null, signal, placeSearch).catch(
|
||||
() => null,
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user