From 7ccb2214f972dda61402819a1554525956db216f Mon Sep 17 00:00:00 2001 From: type-two Date: Wed, 29 Jul 2026 13:57:26 +1000 Subject: [PATCH] =?UTF-8?q?feat:=20Wave=206=20OSINT4ALL=20sweep=20?= =?UTF-8?q?=E2=80=94=20TfL=20JamCams,=20InciWeb=20incidents,=20Radio=20Gar?= =?UTF-8?q?den?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three new layers from the OSINT4ALL directory triage: - jamcams (registry): ~800 live London traffic cameras via TfL open API (CORS-open, keyless). Click a cam dot -> live snapshot in the InfoBox, 5-min cache-busted refresh. New collapsed 'Regional - London' category. - inciweb (registry): named US wildfire incidents from InciWeb RSS. New spec.parse hook in the geojson-layer factory lets XML feeds supply their own parser; coordinates regex'd out of DMS text in . - radio (bespoke): ~12k live radio stations from Radio Garden pinned to their cities (PointPrimitiveCollection). Click -> station list + live audio player (audio streams direct; JSON via new allowlisted serve.py proxy/rg/ with 12h/1h caches, SSRF-tested). Keeps playing while you pan. Deploy note: prod nginx needs a location for /godsigh/proxy/rg/ before the radio layer works live (mirror of serve.py RG_PATH_RE allowlist). Co-Authored-By: Claude Fable 5 --- css/style.css | 41 ++++++++++ js/layers/geojson-layer.js | 5 +- js/layers/radiogarden.js | 135 +++++++++++++++++++++++++++++++++ js/main.js | 1 + js/registry.js | 86 +++++++++++++++++++++ js/solarsystem.js | 149 +++++++++++++++++++++++++++++++++++-- js/ssdata.js | 35 +++++++++ js/ui.js | 7 +- serve.py | 36 +++++++++ 9 files changed, 483 insertions(+), 12 deletions(-) create mode 100644 js/layers/radiogarden.js diff --git a/css/style.css b/css/style.css index cb3bd1e..1860ee0 100644 --- a/css/style.css +++ b/css/style.css @@ -254,6 +254,47 @@ html, body { /* Keep Cesium's info box on-theme without fighting it */ .cesium-infoBox { font-family: ui-monospace, monospace; } +/* ---- Radio Garden player ---- */ +#radio-player { + position: absolute; + bottom: 140px; + left: 14px; + width: 250px; + max-height: 320px; + display: flex; + flex-direction: column; + z-index: 11; + padding: 12px; + background: var(--panel-solid); + border: 1px solid var(--border); + border-left: 2px solid #7de3a0; + border-radius: 10px; + backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(8px); + font: 11px ui-monospace, Menlo, monospace; + color: var(--text); + box-shadow: 0 8px 40px rgba(0, 0, 0, 0.5); +} +#radio-player[hidden] { display: none; } +#radio-player .rp-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 8px; } +#radio-player .rp-title { font-weight: 700; color: #fff; font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +#radio-player .rp-close { cursor: pointer; color: var(--dim); font-size: 15px; flex: none; } +#radio-player .rp-close:hover { color: var(--text); } +#radio-player .rp-list { overflow-y: auto; display: flex; flex-direction: column; gap: 2px; } +#radio-player .rp-row { display: flex; align-items: center; gap: 8px; padding: 3px 2px; border-radius: 5px; } +#radio-player .rp-row:hover { background: rgba(255, 255, 255, 0.04); } +#radio-player .rp-row span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +#radio-player .rp-play { + flex: none; width: 22px; height: 22px; padding: 0; + font: 10px ui-monospace, monospace; color: #7de3a0; + background: rgba(125, 227, 160, 0.1); + border: 1px solid rgba(125, 227, 160, 0.35); + border-radius: 5px; cursor: pointer; +} +#radio-player .rp-play:hover { background: rgba(125, 227, 160, 0.25); } +#radio-player .rp-hint { color: var(--dim); padding: 4px 2px; } +#radio-player .rp-foot { margin-top: 8px; padding-top: 6px; border-top: 1px solid var(--border); color: var(--dim); font-size: 9px; } + /* ---- Solar System mode ---- */ #ss-btn { position: absolute; diff --git a/js/layers/geojson-layer.js b/js/layers/geojson-layer.js index 840b1c3..1923f63 100644 --- a/js/layers/geojson-layer.js +++ b/js/layers/geojson-layer.js @@ -131,7 +131,10 @@ export function createGeoJsonLayer(ctx, spec) { } let json; try { - json = await res.json(); // never gate on Content-Type (EONET lies rss+xml) + // spec.parse lets non-JSON feeds (e.g. InciWeb RSS/XML) supply their own + // text → features parser; default path never gates on Content-Type + // (EONET lies rss+xml but is JSON). + json = spec.parse ? spec.parse(await res.text()) : await res.json(); } catch { ui.setStatus(spec.id, 'bad response — retrying', 'err'); return; diff --git a/js/layers/radiogarden.js b/js/layers/radiogarden.js new file mode 100644 index 0000000..c5ca8f3 --- /dev/null +++ b/js/layers/radiogarden.js @@ -0,0 +1,135 @@ +// Radio Garden layer (Wave 6) — ~8,000 live radio stations pinned to their real +// cities. Click a dot → pick a station → it streams live through a small player. +// Place/channel JSON comes via the same-origin proxy (proxy/rg/…, no CORS +// upstream); the AUDIO itself streams straight from radio.garden —