mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-06 03:14:00 +00:00
fix(cctv): harden the WSDOT catalog fetch and describe its real direction codes
The catalog fetch now refuses redirects, cancels rejected bodies and reads through the shared capped reader before the windows-1252 decode. The feed sends cardinal codes plus B (both), O and null, not BW or diagonals; tests, comments and docs now say so, with unchanged id-hash fallbacks. Documents the 8-1,200 per-pack range, drops the unverified low-volume compliance claim for facts and a link to WSDOT's terms, and stops claiming the per-pack defaults fit under the 4,000 cap: it is filled round-robin.
This commit is contained in:
1 parent
24f14a77a3
commit
76bfd74a78
7 files changed
+148
-29
No files matched your search
@@ -8,8 +8,8 @@ export const DEFAULT_AUSTIN_MAX_SOURCES = 250;
|
||||
* Catalog-wide safety ceiling on served cameras. Each pack already caps
|
||||
* itself (nearest-to-anchor first); this bound only matters when the packs
|
||||
* together exceed it, and it is then filled round-robin across packs (see
|
||||
* cap.js) so no region is silently dropped. Sized above the sum of the
|
||||
* default per-pack caps so a default install never trims.
|
||||
* cap.js) so no region is silently dropped. The default per-pack caps
|
||||
* together can exceed it, so a default install may be thinned evenly here.
|
||||
*/
|
||||
export const DEFAULT_CCTV_MAX_SOURCES = 4000;
|
||||
/** Hard upper bound for CCTV_MAX_SOURCES; also sizes the health map. */
|
||||
@@ -249,9 +249,9 @@ export const VEGVESEN_IMAGE_ORIGIN =
|
||||
* per-camera manifest path is registered. */
|
||||
export const VEGVESEN_VIDEO_URL = (cameraId) =>
|
||||
`https://kamera.vegvesen.no/public/${cameraId}/manifest.m3u8`;
|
||||
/** Above the ~850 working cameras so a default install keeps them all, while
|
||||
* staying close to them: the per-pack defaults are meant to sum to no more
|
||||
* than DEFAULT_CCTV_MAX_SOURCES. */
|
||||
/** Above the ~850 working cameras so the pack keeps them all on its own. The
|
||||
* catalog-wide DEFAULT_CCTV_MAX_SOURCES cap still applies across packs and is
|
||||
* filled round-robin, so a full default install may thin this pack. */
|
||||
export const DEFAULT_VEGVESEN_MAX_SOURCES = 900;
|
||||
/** Hard ceiling on the catalog body. The whole country is ~800 KB. */
|
||||
export const VEGVESEN_MAX_CATALOG_BYTES = 8 * 1024 * 1024;
|
||||
@@ -308,8 +308,8 @@ export const DELDOT_ANCHORS = [
|
||||
|
||||
/** WSDOT Travel Center cameras (Washington State): keyless Esri JSON on the
|
||||
* official open-data host — ~1,700 point features in EPSG:3857 (Web Mercator),
|
||||
* ETag/Last-Modified served. WSDOT's metadata marks the feed "low volume" use,
|
||||
* which the 15-minute catalog cache respects. */
|
||||
* ETag/Last-Modified served. WSDOT's metadata marks the feed "low volume" use;
|
||||
* the catalog is fetched once per CCTV_SOURCE_CACHE_MS refresh. */
|
||||
export const WSDOT_CAMERAS_URL =
|
||||
'https://data.wsdot.wa.gov/travelcenter/Cameras.json';
|
||||
/** Only frames on WSDOT's own image host are registered; the catalog also
|
||||
@@ -317,6 +317,10 @@ export const WSDOT_CAMERAS_URL =
|
||||
* whose own terms apply. */
|
||||
export const WSDOT_IMAGE_ORIGIN = 'https://images.wsdot.wa.gov/';
|
||||
export const DEFAULT_WSDOT_MAX_SOURCES = 250;
|
||||
/** Hard ceiling on the WSDOT catalog body. The live layer is ~400 KB; this
|
||||
* only exists so an upstream that streams an unbounded body cannot be
|
||||
* buffered without limit. */
|
||||
export const WSDOT_MAX_CATALOG_BYTES = 4 * 1024 * 1024;
|
||||
/** Prioritization anchors: downtown Seattle and Spokane. */
|
||||
export const WSDOT_ANCHORS = [
|
||||
{ lat: 47.6062, lon: -122.3321 }, // Seattle
|
||||
|
||||
@@ -63,6 +63,7 @@ import {
|
||||
WSDOT_IMAGE_ORIGIN,
|
||||
DEFAULT_WSDOT_MAX_SOURCES,
|
||||
WSDOT_ANCHORS,
|
||||
WSDOT_MAX_CATALOG_BYTES,
|
||||
DEFAULT_VEGVESEN_CCTV_URL,
|
||||
VEGVESEN_IMAGE_ORIGIN,
|
||||
VEGVESEN_VIDEO_URL,
|
||||
@@ -100,7 +101,10 @@ import {
|
||||
prioritizeSources,
|
||||
} from './normalize.js';
|
||||
import { directionToHeading } from '../../../src/data/directionText.js';
|
||||
import { readResponseJsonCapped } from '../common/http.js';
|
||||
import {
|
||||
readResponseBytesCapped,
|
||||
readResponseJsonCapped,
|
||||
} from '../common/http.js';
|
||||
/**
|
||||
* Fetch and parse Austin traffic camera records from the city Open Data portal.
|
||||
*
|
||||
@@ -1721,10 +1725,10 @@ export async function loadDelDOTSourcesFromOpenData() {
|
||||
}
|
||||
}
|
||||
|
||||
/** WSDOT `CompassDirection` codes as headings in degrees, per the layer's own
|
||||
* metadata (N/E/S/W, NE/NW/SE/SW, BW = Bothways). `BW` and anything else
|
||||
* (`O`, null, unknown codes) carry no single facing and fall through to the
|
||||
* id hash. */
|
||||
/** WSDOT `CompassDirection` codes as headings in degrees. The live feed sends
|
||||
* single cardinals (N/E/S/W), `B` (both directions — most rows), `O`, and
|
||||
* null; the diagonals are mapped in case they appear. `B`, `O`, null, and any
|
||||
* other code carry no single facing and fall through to the id hash. */
|
||||
const WSDOT_COMPASS_HEADINGS = Object.freeze({
|
||||
N: 0,
|
||||
NE: 45,
|
||||
@@ -1770,9 +1774,9 @@ function isLikelyWashingtonCoordinate(lat, lon) {
|
||||
* finite in-state coordinates, and a frame on WSDOT's own image host are
|
||||
* kept — the catalog's ~70 partner cameras (Oregon DOT, City of Seattle,
|
||||
* lodges, airports) are skipped rather than proxied under someone else's
|
||||
* terms. A single-facing `CompassDirection` (cardinal or diagonal) gives a
|
||||
* high-confidence heading; `BW` (Bothways) and `O`/null fall back to the id
|
||||
* hash.
|
||||
* terms. A single-facing `CompassDirection` (a cardinal) gives a
|
||||
* high-confidence heading; `B` (both directions), `O`, and null fall back to
|
||||
* the id hash.
|
||||
* The feed carries Windows-1252 bytes (en-dashes in titles) with no charset
|
||||
* declaration, so the body is decoded explicitly before parsing.
|
||||
*
|
||||
@@ -1782,14 +1786,31 @@ export async function loadWsdotSourcesFromOpenData() {
|
||||
try {
|
||||
const resp = await fetch(WSDOT_CAMERAS_URL, {
|
||||
headers: { Accept: 'application/json' },
|
||||
redirect: 'manual',
|
||||
signal: AbortSignal.timeout(CCTV_SOURCE_FETCH_TIMEOUT_MS),
|
||||
});
|
||||
const discard = async () => {
|
||||
try {
|
||||
await resp.body?.cancel();
|
||||
} catch {
|
||||
/* no-op */
|
||||
}
|
||||
return [];
|
||||
};
|
||||
if (resp.status >= 300 && resp.status < 400) {
|
||||
console.warn(
|
||||
'[CCTV] WSDOT catalog redirected; redirects are not followed',
|
||||
);
|
||||
return discard();
|
||||
}
|
||||
if (!resp.ok) {
|
||||
console.warn('[CCTV] WSDOT camera download failed:', resp.status);
|
||||
return [];
|
||||
return discard();
|
||||
}
|
||||
const payload = JSON.parse(
|
||||
new TextDecoder('windows-1252').decode(await resp.arrayBuffer()),
|
||||
new TextDecoder('windows-1252').decode(
|
||||
await readResponseBytesCapped(resp, WSDOT_MAX_CATALOG_BYTES),
|
||||
),
|
||||
);
|
||||
const rows = Array.isArray(payload?.features) ? payload.features : [];
|
||||
|
||||
|
||||
Reference in new issue
Block a user