mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 10:53:59 +00:00
fix(weather): wait out metered RainViewer tiles instead of caching holes
This commit is contained in:
1 parent
d496dd9f33
commit
9f3cdf8203
2 files changed
+53
-1
No files matched your search
@@ -10,6 +10,19 @@ export const RAINVIEWER_BOUNDS = Object.freeze({
|
||||
north: MERCATOR_LATITUDE,
|
||||
});
|
||||
|
||||
const wait = (ms, signal) =>
|
||||
new Promise((resolve, reject) => {
|
||||
const timer = setTimeout(resolve, ms);
|
||||
signal.addEventListener(
|
||||
'abort',
|
||||
() => {
|
||||
clearTimeout(timer);
|
||||
reject(signal.reason);
|
||||
},
|
||||
{ once: true },
|
||||
);
|
||||
});
|
||||
|
||||
/** Clamp a non-wrapping geographic window to the Mercator domain. */
|
||||
export function mercatorBbox(bbox = RAINVIEWER_BOUNDS) {
|
||||
const box = { ...bbox };
|
||||
@@ -92,6 +105,7 @@ export async function composeMercatorImage({
|
||||
signal = new AbortController().signal,
|
||||
maxTiles = 24,
|
||||
concurrency = 6,
|
||||
sleep = wait,
|
||||
}) {
|
||||
signal.throwIfAborted();
|
||||
if (
|
||||
@@ -131,7 +145,21 @@ export async function composeMercatorImage({
|
||||
.replace('{z}', z)
|
||||
.replace('{x}', x)
|
||||
.replace('{y}', y);
|
||||
const response = await fetchImpl(url, { signal });
|
||||
let response = await fetchImpl(url, { signal });
|
||||
// The proxy meters RainViewer; wait out a 429 rather than baking a hole
|
||||
// into a frame that is then cached.
|
||||
for (let retry = 0; response.status === 429 && retry < 3; retry++) {
|
||||
await response.body?.cancel();
|
||||
const seconds = Number(response.headers.get('retry-after'));
|
||||
await sleep(
|
||||
Math.min(
|
||||
15,
|
||||
Number.isFinite(seconds) && seconds > 0 ? seconds : 2,
|
||||
) * 1000,
|
||||
signal,
|
||||
);
|
||||
response = await fetchImpl(url, { signal });
|
||||
}
|
||||
if (!response.ok) throw new Error('Weather tile unavailable');
|
||||
const bytes = await readResponseBytesCapped(response, 1024 * 1024);
|
||||
signal.throwIfAborted();
|
||||
|
||||
@@ -150,6 +150,7 @@ test('mosaic limits concurrency, tolerates partial failure, releases decoded ima
|
||||
composeMercatorImage({
|
||||
...options,
|
||||
fetchImpl: async () => new Response('', { status: 429 }),
|
||||
sleep: async () => {},
|
||||
}),
|
||||
/tiles unavailable/,
|
||||
);
|
||||
@@ -218,3 +219,26 @@ test('whole and detail RainViewer images use composition and strict source metad
|
||||
validateWeatherSnapshot({ ...snapshot, product: 'radar' }, 'radar'),
|
||||
);
|
||||
});
|
||||
|
||||
test('a metered tile waits out Retry-After instead of leaving a hole', async () => {
|
||||
const seen = new Map();
|
||||
const waits = [];
|
||||
const result = await composeMercatorImage({
|
||||
...options,
|
||||
bbox: { west: -180, south: -85.0511, east: 180, north: 85.0511 },
|
||||
size: { width: 512, height: 256 },
|
||||
maxTiles: 1,
|
||||
fetchImpl: async (url) => {
|
||||
const count = (seen.get(url) ?? 0) + 1;
|
||||
seen.set(url, count);
|
||||
return count === 1
|
||||
? new Response('', { status: 429, headers: { 'Retry-After': '3' } })
|
||||
: new Response(new Uint8Array([1]));
|
||||
},
|
||||
sleep: async (ms) => {
|
||||
waits.push(ms);
|
||||
},
|
||||
});
|
||||
assert.deepEqual(waits, [3000]);
|
||||
assert.equal(result.texture.draws[0][0].draws.length, 1);
|
||||
});
|
||||
Reference in new issue
Block a user