mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 10:24:02 +00:00
Supersedes #420 by @GladiatorrX9, ported onto the current component layout with fixes from review. Verified before merge: format, package boundaries, unit suite, build, and the browser tracking suite; a browser harness for the change where one applies; adversarial review applied. Co-authored-by: Cyber Knight <[email protected]>
149 lines
6.0 KiB
JavaScript
149 lines
6.0 KiB
JavaScript
import path from 'node:path';
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Overpass API proxy constants and cache state
|
||
// ---------------------------------------------------------------------------
|
||
/**
|
||
* User-Agent sent to every Overpass mirror.
|
||
*
|
||
* The OSM API usage policy asks for a "Valid User-Agent identifying application
|
||
* and version"; a generic proxy label is not one. A mirror is free to refuse a
|
||
* client it cannot identify, and `src/overpassProxy.test.mjs` pins what that
|
||
* costs: a refusal is never data, so the query falls through to whatever
|
||
* mirrors are left. Keep this honest and stable — if it is ever refused, the
|
||
* answer is less query volume, not a new name.
|
||
*/
|
||
const OVERPASS_USER_AGENT =
|
||
'gods-eye-view/0.1 (+https://github.com/bilawalsidhu/gods-eye-view)';
|
||
|
||
/** Ordered list of Overpass API mirrors; tried sequentially on failure/rate-limit. */
|
||
const OVERPASS_UPSTREAMS = [
|
||
'https://overpass-api.de/api/interpreter',
|
||
'https://overpass.kumi.systems/api/interpreter',
|
||
'https://lz4.overpass-api.de/api/interpreter',
|
||
// Community full-planet instance (privateforge nonprofit) — added 2026-07-30
|
||
// when all three mirrors above refused this IP (likely a dev-traffic rate
|
||
// ban; refused connections fail in ms, so healthy mirrors above still win).
|
||
// Verified: planet coverage (Texas query), CORS *, ~5-20 s cold latency.
|
||
'https://overpass.private.coffee/api/interpreter',
|
||
];
|
||
|
||
/**
|
||
* TTL for FRESH cached Overpass responses (ms). Road geometry is static for
|
||
* months — the original 45 s TTL forced a public-mirror round-trip on nearly
|
||
* every viewport revisit and left nothing to serve when the mirrors 502
|
||
* (field-test 2026-07-17: all three mirrors down during US morning peak =
|
||
* "traffic takes forever to load"). 24 h in memory; the disk layer below
|
||
* keeps 7 days and also survives dev-server restarts.
|
||
*/
|
||
const OVERPASS_CACHE_MS = 86_400_000;
|
||
|
||
/** Disk-cache TTL for Overpass responses (ms) — 7 days. */
|
||
const OVERPASS_DISK_TTL_MS = 7 * 86_400_000;
|
||
|
||
/**
|
||
* Disk-cache TTL for BOUNDARY-class queries (is_in / admin-relation pivots) — 30
|
||
* days. Admin boundaries change ≈never, and their pivots are the most expensive
|
||
* queries the app issues (multi-MB coastline geometry, 10–25 s on public mirrors —
|
||
* field test 2026-07-23: outline latency + the Sicily miss). Keeping them a month
|
||
* means each boundary is fetched roughly once per machine, ever.
|
||
*/
|
||
const OVERPASS_BOUNDARY_DISK_TTL_MS = 30 * 86_400_000;
|
||
|
||
/** Disk-cache directory for Overpass responses. */
|
||
const OVERPASS_DISK_DIR = path.join(process.cwd(), '.gev-cache', 'overpass');
|
||
|
||
/** Per-upstream fetch timeout (ms). */
|
||
const OVERPASS_TIMEOUT_MS = 22000;
|
||
|
||
/** Max entries in the Overpass response cache (LRU-like, oldest evicted first). */
|
||
const OVERPASS_CACHE_MAX_ENTRIES = 120;
|
||
|
||
// --- Abuse guards shared by the Overpass + route proxies --------------------
|
||
/** Max accepted POST body for the Overpass proxy (Overpass QL queries are tiny). */
|
||
const OVERPASS_MAX_BODY_BYTES = 24 * 1024;
|
||
|
||
// 24 KB
|
||
/**
|
||
* Hard cap on a single Overpass upstream response we will buffer into memory.
|
||
* 32 MB (was 12 MB): a dense island/state admin boundary at full `out geom`
|
||
* fidelity — Sicilia's Mediterranean coastline — can exceed 12 MB, and clipping
|
||
* it read as a permanent "transient" failure (field test 2026-07-23, Sicily
|
||
* never traced). The buffered payload is SIMPLIFIED server-side before it is
|
||
* cached or sent (simplifyOverpassPayloadBody), so the raised cap does not
|
||
* raise what clients receive or what the disk stores.
|
||
*/
|
||
const OVERPASS_MAX_RESPONSE_BYTES = 32 * 1024 * 1024;
|
||
|
||
// 32 MB
|
||
/** Only payloads at least this large go through geometry simplification. */
|
||
const OVERPASS_SIMPLIFY_MIN_BYTES = 1_500_000;
|
||
|
||
/** Only per-element geometry arrays with at least this many points are simplified. */
|
||
const OVERPASS_SIMPLIFY_MIN_POINTS = 1200;
|
||
|
||
/**
|
||
* Douglas-Peucker tolerance (degrees, ≈44 m of latitude). Region/state boundary
|
||
* rings are drawn at regional camera scale and the client simplifies again for
|
||
* draw, so ~44 m fidelity is invisible; building footprints never reach the
|
||
* point threshold above and pass through untouched.
|
||
*/
|
||
const OVERPASS_SIMPLIFY_TOLERANCE_DEG = 0.0004;
|
||
|
||
/** Max concurrent in-flight upstream Overpass fetches across all distinct queries. */
|
||
const OVERPASS_MAX_CONCURRENT = 6;
|
||
|
||
/** Server-side timeout ceiling (seconds) we allow inside an Overpass QL query. */
|
||
const OVERPASS_MAX_QL_TIMEOUT = 30;
|
||
|
||
/** Max `around:` radius (m) — every app caller uses <= 1800 m. */
|
||
const OVERPASS_MAX_AROUND_M = 50000;
|
||
|
||
/** Max bbox span (degrees) — app bboxes are small viewport tiles. */
|
||
const OVERPASS_MAX_BBOX_DEG = 12;
|
||
|
||
/**
|
||
* Every Overpass element-type specifier, including the combined shortcuts
|
||
* (nwr/nw/nr/wr) and `rel`. Shared by the selector + area-element-deny regexes so
|
||
* they can't drift (a missing shortcut like `wr` was an area-scan bypass).
|
||
*/
|
||
const OVERPASS_ELEMENT_TYPES = 'node|way|relation|nwr|nw|nr|wr|rel';
|
||
|
||
/** Element-selector (incl. `area`) whose statements must be individually bounded. */
|
||
const OVERPASS_SELECTOR_RE = new RegExp(
|
||
`\\b(?:${OVERPASS_ELEMENT_TYPES}|area)\\b`,
|
||
);
|
||
|
||
/** An element selector bounded BY an area — the country-scan abuse shape. */
|
||
const OVERPASS_AREA_ELEMENT_RE = new RegExp(
|
||
`\\b(?:${OVERPASS_ELEMENT_TYPES})\\s*\\(\\s*area\\b`,
|
||
'i',
|
||
);
|
||
|
||
/** A single bbox 4-tuple `(s,w,n,e)` (non-global so it does not advance lastIndex). */
|
||
const OVERPASS_BBOX_RE =
|
||
/\(\s*-?\d+(?:\.\d+)?\s*,\s*-?\d+(?:\.\d+)?\s*,\s*-?\d+(?:\.\d+)?\s*,\s*-?\d+(?:\.\d+)?\s*\)/;
|
||
|
||
export {
|
||
OVERPASS_BOUNDARY_DISK_TTL_MS,
|
||
OVERPASS_DISK_TTL_MS,
|
||
OVERPASS_DISK_DIR,
|
||
OVERPASS_CACHE_MS,
|
||
OVERPASS_CACHE_MAX_ENTRIES,
|
||
OVERPASS_MAX_BODY_BYTES,
|
||
OVERPASS_MAX_CONCURRENT,
|
||
OVERPASS_MAX_AROUND_M,
|
||
OVERPASS_MAX_BBOX_DEG,
|
||
OVERPASS_AREA_ELEMENT_RE,
|
||
OVERPASS_SELECTOR_RE,
|
||
OVERPASS_BBOX_RE,
|
||
OVERPASS_MAX_QL_TIMEOUT,
|
||
OVERPASS_SIMPLIFY_MIN_BYTES,
|
||
OVERPASS_SIMPLIFY_MIN_POINTS,
|
||
OVERPASS_SIMPLIFY_TOLERANCE_DEG,
|
||
OVERPASS_MAX_RESPONSE_BYTES,
|
||
OVERPASS_UPSTREAMS,
|
||
OVERPASS_USER_AGENT,
|
||
OVERPASS_TIMEOUT_MS,
|
||
};
|