mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 18:34:02 +00:00
From gekh's review of #768, each with a test that fails before: - The routes refuse cross-site requests (sameSiteGated, as the paid proxies do) and the tile route has a generous per-IP limit (600/min), so another page cannot burn the token's quota or trip the shared 429 hold. - At most six upstream fetches run at once; a queued request that is abandoned never fetches, nor does one abandoned during the disk read. - A body past the 48 MB cap aborts the upstream request as it streams. - Empty tiles are charged against the memory and disk budgets. - Each disk write uses its own temp file and a retrim is skipped while a write of that tile is pending, so concurrent hits cannot extend a tile's life or fail a rename. - Upstream refusals other than 401/403/429 answer 502. - The tile route's success path is tested end to end (200 with the image layer stripped and the right headers, 204 when empty, an abandoned response aborts upstream); the teardown race that made this file flaky is gone (240 parallel runs, no failure); the QA-constant tautologies are replaced by a check against the real provider registry; the boundary group lists the modules the provider actually imports. Co-Authored-By: Claude Opus 5.5 <[email protected]>
91 lines
4.0 KiB
JavaScript
91 lines
4.0 KiB
JavaScript
import path from 'node:path';
|
||
|
||
/** Mapillary vector tile root; the path template is /{layer}/2/{z}/{x}/{y}. */
|
||
export const MAPILLARY_TILE_HOST = 'https://tiles.mapillary.com/maps/vtp';
|
||
|
||
/** Public tile layer names this proxy exposes, mapped to Mapillary's ids. */
|
||
export const TILE_LAYERS = Object.freeze({
|
||
// Overview points (z0–5), sequences (z6–14) and image points (z14 only).
|
||
// The app asks for overview z0–5 and sequences z11–14 only (see
|
||
// src/layers/streetLevel/tileMath.js); region-sized z6–10 tiles are refused.
|
||
// The z14 `image` point layer is ~98% of a 10 MB tile and unused here:
|
||
// image positions come from the graph API per sequence. Dropped in transit.
|
||
coverage: Object.freeze({
|
||
upstream: 'mly1_public',
|
||
zoomRanges: Object.freeze([Object.freeze([0, 5]), Object.freeze([11, 14])]),
|
||
dropLayers: Object.freeze(['image']),
|
||
}),
|
||
});
|
||
|
||
/** Disk cache root, alongside the other providers' caches. */
|
||
export const MAPILLARY_CACHE_DIR = path.join(
|
||
process.cwd(),
|
||
'.gev-cache',
|
||
'mapillary',
|
||
);
|
||
export const TILE_DISK_DIR = path.join(MAPILLARY_CACHE_DIR, 'tiles');
|
||
|
||
/**
|
||
* Tiles change when new imagery is processed, which is far slower than the
|
||
* 15-minute upstream Cache-Control. A day keeps a session over one city from
|
||
* re-downloading its tiles on every camera move.
|
||
*/
|
||
export const TILE_DISK_TTL_MS = 24 * 60 * 60 * 1000;
|
||
|
||
/**
|
||
* Disk cache bound. A tile write starts a background sweep at most this often:
|
||
* it deletes files past TILE_DISK_TTL_MS, then the oldest files until the
|
||
* cache fits in TILE_DISK_MAX_BYTES (thousands of trimmed street-level tiles).
|
||
*/
|
||
export const TILE_DISK_SWEEP_INTERVAL_MS = 15 * 60 * 1000;
|
||
export const TILE_DISK_MAX_BYTES = 1024 * 1024 * 1024;
|
||
/**
|
||
* What each cached file costs against TILE_DISK_MAX_BYTES on top of its size:
|
||
* one filesystem block and its directory entry. An empty (no coverage) tile is
|
||
* 0 bytes but still a file, so without this ocean tiles would never count.
|
||
*/
|
||
export const TILE_DISK_FILE_OVERHEAD_BYTES = 4096;
|
||
|
||
/** A z14 image tile over a dense city is ~11 MB; anything past this is wrong. */
|
||
export const TILE_MAX_BYTES = 48 * 1024 * 1024;
|
||
|
||
/** In-memory tile cache budget (bytes) and upstream fetch timeout. */
|
||
export const TILE_MEMORY_BUDGET_BYTES = 96 * 1024 * 1024;
|
||
/**
|
||
* What each in-memory entry costs against the budget on top of its bytes (the
|
||
* key, the map entry and the Buffer object), so empty tiles count too.
|
||
*/
|
||
export const TILE_MEMORY_ENTRY_OVERHEAD_BYTES = 1024;
|
||
export const TILE_FETCH_TIMEOUT_MS = 60_000;
|
||
/**
|
||
* Upstream tile fetches running at once; further misses queue for a slot. A
|
||
* view asks for at most 25 tiles, so a few slots keep it quick while a burst
|
||
* of distinct misses cannot open thousands of buffered requests at once.
|
||
*/
|
||
export const TILE_UPSTREAM_CONCURRENCY = 6;
|
||
/**
|
||
* Tile requests per client IP per minute (cache hits included). The app asks
|
||
* only for tiles it has not loaded yet, at most 9 street + 16 overview tiles
|
||
* per view after a 320 ms camera debounce (src/layers/streetLevel/providers/
|
||
* mapillary/policy.js), and the browser keeps each answer for an hour. 600 is
|
||
* 24 entirely new views a minute, one every 2.5 s, which a person flying the
|
||
* camera does not reach; it still stops one page from draining the shared
|
||
* token into Mapillary rate limits that would hold every miss for everyone.
|
||
*/
|
||
export const TILE_ROUTE_MAX_PER_MIN = 600;
|
||
/**
|
||
* After Mapillary rejects the token (401/403), tile misses are answered from
|
||
* that verdict for this long instead of asking again on every camera move.
|
||
* A changed token is asked at once.
|
||
*/
|
||
export const TILE_KEY_REJECTED_HOLD_MS = 5 * 60 * 1000;
|
||
/** After a 429 without a usable Retry-After, hold tile misses this long. */
|
||
export const TILE_RATE_LIMIT_HOLD_MS = 60_000;
|
||
/** The longest Retry-After honoured, so a bad header cannot stall coverage. */
|
||
export const TILE_RATE_LIMIT_MAX_HOLD_MS = 10 * 60 * 1000;
|
||
|
||
/** The client token lives in the browser by design; the server adds it to tile URLs too. */
|
||
export function mapillaryToken() {
|
||
return String(process.env.MAPILLARY_CLIENT_TOKEN || '').trim();
|
||
}
|