mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 10:53:59 +00:00
fix(terrain): keep known heights when a terrain request fails
A request with any point the proxy cannot answer gets 502, and the page used to put every point of that chunk on its geoid, including the ones the proxy had real (cached or stale) heights for. During an upstream 429 pause that drops cached ground under every chunk that also holds a new point. The 502 body now carries `partial`: the request's real heights in order, null for points without one, left out when there are none. The terrain request service attaches it to the error it throws, and the page caches those heights and uses its geoid only for the rest. `get_terrain_height` still reports the failure as before. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
3a6f27620f
commit
19b4a7a7e0
7 files changed
+149
-14
No files matched your search
@@ -150,12 +150,25 @@ export function createApplicationRequestServices({
|
||||
const query = points
|
||||
.map(({ lat, lon }) => `${lon.toFixed(5)},${lat.toFixed(5)}`)
|
||||
.join(';');
|
||||
return requireOk(
|
||||
await request(`${urls.terrain}?points=${encodeURIComponent(query)}`, {
|
||||
signal,
|
||||
}),
|
||||
'Terrain heights',
|
||||
)?.results;
|
||||
const response = await request(
|
||||
`${urls.terrain}?points=${encodeURIComponent(query)}`,
|
||||
{ signal },
|
||||
);
|
||||
// A failed request still carries the real heights the server had, so
|
||||
// the caller can keep them; null marks a point without one.
|
||||
const partial = response.data?.partial;
|
||||
if (
|
||||
!response.ok &&
|
||||
Array.isArray(partial) &&
|
||||
partial.length === points.length
|
||||
) {
|
||||
const error = new Error(
|
||||
`Terrain heights unavailable (${response.status})`,
|
||||
);
|
||||
error.partial = partial;
|
||||
throw error;
|
||||
}
|
||||
return requireOk(response, 'Terrain heights')?.results;
|
||||
},
|
||||
},
|
||||
regional: {
|
||||
|
||||
@@ -94,3 +94,20 @@ test('a failed probe is retried after its backoff, not on every query', async ()
|
||||
'GET /api/overpass/status',
|
||||
]);
|
||||
});
|
||||
|
||||
test('a failed terrain request carries the real heights the server had', async () => {
|
||||
const partial = [{ ellipsoid: 412.5 }, null];
|
||||
const services = createApplicationRequestServices({
|
||||
fetchImpl: async () => Response.json({ error: 'x', partial }, { status: 502 }),
|
||||
});
|
||||
const points = [{ lat: 30, lon: -97 }, { lat: 31, lon: -97 }];
|
||||
await assert.rejects(services.terrain.getHeights(points), (error) => {
|
||||
assert.match(error.message, /Terrain heights unavailable \(502\)/);
|
||||
assert.deepEqual(error.partial, partial);
|
||||
return true;
|
||||
});
|
||||
await assert.rejects(services.terrain.getHeights(points.slice(0, 1)), (error) => {
|
||||
assert.equal(error.partial, undefined, 'a list of the wrong length is not attached');
|
||||
return true;
|
||||
});
|
||||
});
|
||||
@@ -226,7 +226,8 @@ export function createTerrainHeights({
|
||||
* - Remaining (deduplicated) coordinates are sent to `/api/terrain/heights`
|
||||
* in sequential chunks of <=64 points.
|
||||
* - If a chunk request fails (network error, non-ok HTTP, malformed body),
|
||||
* every point in THAT chunk falls back to geoid math
|
||||
* every point in THAT chunk without a real height in the failure's
|
||||
* `partial` falls back to geoid math
|
||||
* (`sourceOrthometricM + geoidHeight` or `geoidHeight` alone) rather than
|
||||
* failing the whole batch — a transient proxy outage in the middle of a
|
||||
* large batch shouldn't blank out entities whose chunk happened to land
|
||||
@@ -314,15 +315,34 @@ export function createTerrainHeights({
|
||||
cacheStore(item.key, entry);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
} catch (error) {
|
||||
requestSignal?.throwIfAborted();
|
||||
// Proxy down (or cold cache had nothing to serve-stale) — fall back to
|
||||
// geoid math for every point in this chunk. `ensureGeoidReady()` is
|
||||
// awaited lazily, only on the fallback path, so the common (proxy
|
||||
// healthy) case never pays for the geoid grid's dynamic import.
|
||||
// geoid math for the points in this chunk. A failure that still
|
||||
// carries real heights (the proxy's `partial`, e.g. cached points
|
||||
// during an upstream 429 pause) keeps them; only the rest use the
|
||||
// geoid. `ensureGeoidReady()` is awaited lazily, only on the fallback
|
||||
// path, so the common (proxy healthy) case never pays for the geoid
|
||||
// grid's dynamic import.
|
||||
const partial =
|
||||
Array.isArray(error?.partial) && error.partial.length === chunk.length
|
||||
? error.partial
|
||||
: [];
|
||||
const fallback = [];
|
||||
chunk.forEach((item, index) => {
|
||||
const ellipsoid = partial[index]?.ellipsoid;
|
||||
if (!Number.isFinite(ellipsoid)) {
|
||||
fallback.push(item);
|
||||
return;
|
||||
}
|
||||
const entry = { ellipsoid, source: 'reearth' };
|
||||
resolvedThisCall.set(item.key, entry);
|
||||
cacheStore(item.key, entry);
|
||||
});
|
||||
if (fallback.length === 0) continue;
|
||||
await ensureGeoidReady();
|
||||
requestSignal?.throwIfAborted();
|
||||
for (const item of chunk) {
|
||||
for (const item of fallback) {
|
||||
const ellipsoid = geoidFallback(
|
||||
item.lat,
|
||||
item.lon,
|
||||
|
||||
Reference in new issue
Block a user