mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 10:53:59 +00:00
Merge upstream/main into feature/catalonia-cctv-cameras
Keeps PR #595 current with #730-#743. Clean merge, no conflicts. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
commit
b27e8a35c8
184 files changed
+23240
-252
No files matched your search
+170
-2
@@ -1,5 +1,42 @@
|
||||
# God's Eye View Current State
|
||||
|
||||
## Cyber HUD — September 23, 2026
|
||||
|
||||
Display > HUD > Layout includes Cyber, also available through the HUD voice
|
||||
action and shared visual state. An explicit first transition into Cyber selects
|
||||
FLIR with Ironbow 0.42; restored links and subsequent visual tuning remain
|
||||
authoritative. The skin uses shared red/slate panel treatments in map and cockpit,
|
||||
with compact 200px collapsed controls and wider expanded panels. Other HUD
|
||||
layouts retain their existing presentation.
|
||||
Chamfered panel expand/collapse controls use an inset keyboard-focus outline and
|
||||
red hover border in both map and cockpit, so clipping does not hide the highlight.
|
||||
|
||||
Cyber's map-side right rail opens one panel at a time while keeping collapsed
|
||||
launchers available. Display, CCTV and Context scroll their contents inside fixed
|
||||
headers and decorative frames. The narrow-screen rail remains scrollable to reach
|
||||
each panel. Radio retains the shared nested Context player and compact
|
||||
header disclosure, without relocating playback controls on theme changes.
|
||||
|
||||
Sonar has one contact-highlighting method. Native Cesium points, billboards and
|
||||
labels are treated in GPU draw commands without replacing their positions,
|
||||
identities or pick commands. Model and canvas/DOM overlays retain their own
|
||||
rendering paths. Sonar OFF stops the sweep while retaining Cyber's neutral
|
||||
contact treatment. Reduced motion disables the animated sweep; cockpit and
|
||||
hidden HUD states do not apply the map treatment. If the native shader is not
|
||||
supported, native contacts remain available and Display reports the limitation.
|
||||
No CSS scene-dimming fallback or effect selector is exposed.
|
||||
|
||||
The Sonar controls adjust rings, range, power, opacity and sector. Sweep opacity
|
||||
affects detection painting rather than label cohort admission. Theme exit and
|
||||
renderer teardown release the derived GPU resources and restore owned model
|
||||
colors without replacing newer layer-owned color changes.
|
||||
|
||||
The `set_cyber_sonar` voice action adjusts the same controls without switching
|
||||
themes or enabling omitted settings. State readback distinguishes configured
|
||||
settings from active map/contact effects. Sonar settings are session-only and
|
||||
return to their defaults on reload. Sliders accept integer values matching the
|
||||
action's ranges.
|
||||
|
||||
Wind appears in the Weather group before Utilities. The surface-weather prototype
|
||||
uses keyless NOAA GFS or ECMWF IFS forecasts on an approximately 1° display grid.
|
||||
It defaults to 10 m wind trails over the existing basemap. Speed shading is an explicit choice; earlier v2 links retain their original speed-shading meaning.
|
||||
@@ -1013,7 +1050,7 @@ Non-object or array-valued properties reject the response instead of being treat
|
||||
|
||||
Launch payloads with missing records now say PAYLOAD DATA UNAVAILABLE. Missing names use Unnamed payload; absent or invalid mass stays unknown instead of appearing as 0 KG.
|
||||
|
||||
Updated: September 15, 2026
|
||||
Updated: September 23, 2026
|
||||
|
||||
## Aircraft and vessel server modules
|
||||
|
||||
@@ -2691,8 +2728,11 @@ its criteria cannot be silently ignored.
|
||||
| Datacenters ▣ | OSM extract (bundled) | `src/data/localLayers.js` | — | static |
|
||||
| Dams ▰ | OpenInfraMap/OSM extract (bundled) | `src/data/localLayers.js` | — | static |
|
||||
| Submarine Cables ◠ | TeleGeography public map (bundled) | `src/data/telegeographySubmarineCables.js` | — | static |
|
||||
| FIRMS Active Fires ▲ | NASA FIRMS live (VIIRS ×3 NRT, trailing 24h) | `src/data/firmsHeatmap.js` | `/api/firms` (`FIRMS_MAP_KEY`) | 10 min (proxy TTL 30 min) |
|
||||
| FIRMS Active Fires ▲ | NASA FIRMS live (VIIRS ×3 NRT + MODIS NRT, trailing 24h) | `src/data/firmsHeatmap.js` | `/api/firms` (`FIRMS_MAP_KEY`) | 10 min (proxy TTL 30 min) |
|
||||
| Wind 🌬 | NOAA GFS 10 m wind (keyless, 0.25°→1° grid; animated particles) | `src/data/wind.js` | `/api/wind` | 1 h (forecast cycle) |
|
||||
| Fire Perimeters 🔥 | NIFC WFIGS current interagency perimeters (keyless, paged past the 2000-record cap); InciWeb catalog + incident page origin and update time checks for verified incident-page links | `src/layers/perimeters/` via `src/app/layers/perimeters.js` | `/api/fire-perimeters` + `/api/fire-perimeters/inciweb/*` | 5 min (server caches: catalog 1 h; publication 30 min) |
|
||||
|
||||
Fire Perimeters uses capped, timed server reads with stale-on-error caching and a per-client limit. Unchanged snapshots retain geometry; link checks cancel on disable or selection change, and the row legend shows reported containment.
|
||||
|
||||
Directions is a keyless front end to the routing the voice agent already
|
||||
uses. Its row chips are the whole interface: DRIVE / WALK / BIKE pick the
|
||||
@@ -4104,6 +4144,134 @@ Existing catalog validation, category filters, tuning and voice playback behavio
|
||||
remain unchanged. Audio connects directly to the broadcaster after an explicit
|
||||
play action; the source does not relay or record streams.
|
||||
|
||||
## Local RTL-SDR and Local ADS-B
|
||||
|
||||
The Radio panel holds a Local RTL-SDR card below internet radio. One browser
|
||||
WebUSB session (`src/sdr/controller.js`) drives a USB RTL-SDR in desktop Chrome
|
||||
or Edge on localhost or HTTPS; nothing opens until CONNECT. Demodulation and
|
||||
Mode S decoding run in a worker; FM audio plays through an audio worklet.
|
||||
Local FM and internet radio are one listening surface: starting local FM stops
|
||||
internet-radio playback, and starting internet radio stops local FM. ADS-B
|
||||
reception does not interrupt internet radio.
|
||||
|
||||
- **Modes.** FM (87.5–108 MHz, tune, seek, volume) or ADS-B at 1090 MHz.
|
||||
With a receiver open, selecting ADS-B enables the Local ADS-B layer;
|
||||
selecting FM disables it.
|
||||
Enabling the layer switches the receiver to ADS-B; disabling it leaves the
|
||||
receiver mode unchanged.
|
||||
- **Gain.** AUTO (tuner AGC) or a manual R820T step from 0.0 to 49.6 dB,
|
||||
stored per mode in `gev:sdr:gain:v1`. Defaults: ADS-B 28.0 dB, FM AUTO.
|
||||
Changes apply to the open receiver without reconnecting.
|
||||
- **Receiver stats (ADS-B).** CRC-valid messages per second, aircraft heard,
|
||||
aircraft with a fresh position and the IQ level.
|
||||
- **Devices.** An already-authorized receiver opens without the WebUSB
|
||||
picker. ADS-B prefers a device whose product name matches ADS-B or 1090 (the
|
||||
1090 MHz channel of a dual-channel board); FM avoids ADS-B/UAT channels.
|
||||
A device chosen in the picker is remembered per mode by vendor, product,
|
||||
product name and serial in `gev:sdr:device:v1`. CHANGE DEVICE reopens the
|
||||
picker. LOCATE uses browser location for receiver-relative CPR decoding.
|
||||
|
||||
The Local ADS-B layer (`local-adsb`, `src/layers/localAdsb/`) is off by default
|
||||
and registered as local-only: it never enters share links or stored layer
|
||||
state. Its aircraft get the public Flights treatment in magenta, alongside
|
||||
the public Flights layer. Public Flights renders about one poll interval
|
||||
(~30 s) behind for smooth interpolation while local aircraft are shown as
|
||||
heard, so a local marker usually leads its public twin; both markers are drawn
|
||||
on purpose:
|
||||
|
||||
- **Class.** Each aircraft is classified with `aircraftClass.js` from its
|
||||
ADS-B emitter category (dump1090 strings: `A1` light, `A3` large, `A7`
|
||||
rotorcraft; set A/B/C/D = type code 4/3/2/1) and, once known, its adsbdb
|
||||
ICAO type. Silhouette and per-class scale come from `aircraftIcons.js`
|
||||
(×0.8 on the ground), tinted magenta.
|
||||
- **3D.** The DISPLAY rail's 3D toggle and its proximity/all mode apply: the
|
||||
layer reads the Flights layer's `models3d` params and uses the same ceiling,
|
||||
caps, add/keep radii, visible-first eligibility
|
||||
(`src/data/modelEligibility.js`) and per-class GLB
|
||||
(`src/layers/flights/modelSpec.js`), tinted magenta through the same MIX
|
||||
colour blend Flights uses for military amber. Heights are barometric +
|
||||
geoid N, as Flights renders a contact without a geometric altitude; a
|
||||
grounded model rides the one-shot ground snap and belly offset.
|
||||
- **Motion.** No display delay. The marker is extrapolated forward from the
|
||||
newest fix with `motionModel.js` (arc extrapolation, path-derived course
|
||||
blended over the reported track, rate-limited slew); a new fix is absorbed
|
||||
by a correction that decays over 900 ms. Coasting continues while messages
|
||||
arrive and stops 10 s after the last one (60 s at most).
|
||||
- **Sanity filter.** For airborne positions the browser decoder prefers a
|
||||
fresh even/odd pair, then a frame decoded relative to the aircraft's own
|
||||
position (under 10 minutes old), then the receiver location. Surface
|
||||
positions are stricter: a single surface frame decodes only against the
|
||||
aircraft's own recent fix, and only when that fix is provably within 45 NM
|
||||
(it could not have gone farther since at its speed limit); the receiver
|
||||
location never decodes a lone surface frame and only picks the quadrant of
|
||||
a surface pair. Without an existing suitable fix for that aircraft
|
||||
(airborne or surface, provably within 45 NM), surface acquisition
|
||||
requires an even/odd pair. Every fix
|
||||
must be reachable from the last accepted one at 1.5 × reported ground
|
||||
speed + 50 kt (1,000 kt without a speed, 350 kt for A1/A7/B1/B4) over the
|
||||
elapsed time + 1 s, plus 500 m.
|
||||
Refused fixes are counted (`positionsRejected` in the SDR state; the layer
|
||||
applies the same check to merged feed records and reports the total as
|
||||
`rejectedPositions` in its stats). Three refusals in a row re-anchor.
|
||||
- **Trail.** A selected aircraft draws a magenta trail of the positions the
|
||||
receiver heard (up to 10 minutes / 600 fixes, dropped with the aircraft),
|
||||
with the tracked-flight trail look and a live head segment. No network
|
||||
backfill.
|
||||
- **Enrichment.** Only the selected aircraft (type + route) and aircraft
|
||||
about to render as models (type) are looked up, through the Flights
|
||||
source's cached adsbdb proxy with the same once-per-session, four-in-flight,
|
||||
200 ms drip rules.
|
||||
|
||||
A marker drops once its newest position is 60 s old; an aircraft is forgotten
|
||||
after 60 s without a message. Clicking a marker opens a readout card (ICAO,
|
||||
callsign, class and category, adsbdb operator/type and a plausible route when
|
||||
known, altitude, ground speed, track, vertical rate, position age, message
|
||||
count, "Heard by your receiver"); markers are not camera-followed. Positioned
|
||||
aircraft join detection boxes, labelled only with a decoded callsign.
|
||||
|
||||
Receivers publish one record shape from `src/sources/adsbRecords.js`
|
||||
(`icao`, `callsign`, `category`, `onGround`, `lat`, `lon`, `altitudeFt`,
|
||||
`groundSpeedKt`, `trackDeg`, `verticalRateFpm`, `lastPositionAt`,
|
||||
`lastMessageAt`, `messageCount`, `rssiDbfs`, `band`, `source`). `band` is `1090` or `978`; `source` is
|
||||
`webusb` (browser SDR) or `feed` (decoder feed).
|
||||
`normalizeDump1090Aircraft(json, nowMs, { band })` maps a dump1090/readsb/
|
||||
skyaware978 `aircraft.json` document into the same records. Voice
|
||||
`set_layer_visibility` accepts `local-adsb` ("local ADS-B", "my receiver",
|
||||
"my antenna").
|
||||
|
||||
**Decoder feeds.** The layer's second input is the server route
|
||||
`GET /api/local-receivers/aircraft`
|
||||
(`server/providers/local-receivers.js`), configured only by the
|
||||
`LOCAL_RECEIVER_FEEDS` environment value (`band=url`, comma-separated, e.g.
|
||||
`1090=http://localhost:8080/data/aircraft.json`). Every host must pass
|
||||
`parseTapAddress` (loopback, RFC1918, `localhost`, `*.local`), the scheme must
|
||||
be http(s) and the path must end in `aircraft.json`; an invalid entry is logged
|
||||
at startup, reported `invalid` and never fetched. The route reads all feeds in
|
||||
parallel (2 s timeout, redirects refused, 2 MB cap, single-flight with a 1 s
|
||||
cache) and returns `{ configured, generatedAt, feeds: [{ band, label, status,
|
||||
aircraft, ageMs }], records }`, where a feed is `live`, `stale` (its own `now`
|
||||
is over 10 s old), `unreachable` or `invalid`. Labels name the band only;
|
||||
addresses and upstream error text stay on the server. Unconfigured, it returns
|
||||
`{ configured: false, feeds: [], records: [] }` without fetching.
|
||||
|
||||
The browser session (`src/layers/localAdsb/feeds.js`) polls the route every
|
||||
second only while the layer is enabled and only while the route reports
|
||||
`configured`; the Local RTL-SDR card makes one probe request at startup to show
|
||||
its read-only "Decoder feeds: 1090 live · 978 live" line. Feed records are
|
||||
rebased to the browser clock and kept up to 60 s after their last message.
|
||||
`mergeLocalAdsbRecords` merges both inputs by ICAO, keeping the newest position
|
||||
(tie: newest message) and the bands/sources heard in the last 60 s; the card's
|
||||
receiver line names them ("Heard by your receiver · 1090 MHz + 978 MHz UAT ·
|
||||
browser SDR + decoder feed"). Aircraft heard only on 978 MHz UAT draw with a
|
||||
thin light-magenta ring. With feeds configured the row status reads "2 feeds
|
||||
live · 14 heard" when every feed is live. While any input is producing (the
|
||||
browser receiver streaming, or a feed live) the row stays nominal and names the
|
||||
other feeds as a trailing note ("3 heard · USB 5.8 msg/s · feed 1090 stale"),
|
||||
since a decoder stopped so the browser can take the dongle is not a fault. With
|
||||
nothing producing, every readable feed stale reads STALE and anything else is
|
||||
an error. Without feeds it keeps the WebUSB statuses. See
|
||||
`docs/LOCAL-RECEIVERS.md`.
|
||||
|
||||
## Bundled geography and submarine cable components
|
||||
|
||||
Submarine cables use separate source, geometry, rendering, interaction and
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
# Local receivers
|
||||
|
||||
The Local ADS-B layer draws aircraft heard by your own receivers. It has two
|
||||
inputs, and it can use both at once.
|
||||
|
||||
| Input | What it is | Bands |
|
||||
| ------------ | ----------------------------------------------------------------------------------------------------------------------------------- | -------------------- |
|
||||
| Browser SDR | A USB RTL-SDR opened by the browser through WebUSB (Radio panel, Local RTL-SDR card). Decoding runs in the page. | 1090 MHz |
|
||||
| Decoder feed | The `aircraft.json` served by a decoder you run: dump1090-fa, readsb or tar1090 (1090 MHz), or dump978-fa + skyaware978 (978 MHz). | 1090 MHz, 978 MHz UAT |
|
||||
|
||||
Aircraft from both inputs are merged by ICAO address. The newest position
|
||||
wins, and the click card names every band and source that heard the aircraft
|
||||
in the last 60 s. Aircraft heard only on 978 MHz UAT draw with a thin ring.
|
||||
|
||||
Local aircraft usually lead their public twins. The public Flights layer
|
||||
renders positions about one poll interval (~30 s) behind so it can interpolate
|
||||
smoothly between fixes, while local receiver aircraft are shown as heard. An
|
||||
aircraft in both layers therefore draws twice, the magenta local marker ahead
|
||||
of the public one; both markers are drawn on purpose.
|
||||
|
||||
## Which to use
|
||||
|
||||
- **One plain RTL-SDR dongle, desktop Chrome or Edge:** use the browser SDR.
|
||||
Nothing else to install.
|
||||
- **A dual-band board, a Raspberry Pi feeder, or a dongle already used by a
|
||||
decoder:** use a decoder feed. A USB dongle can be opened by only one program
|
||||
at a time, so a dongle that dump1090 or dump978 owns cannot also be opened by
|
||||
the browser.
|
||||
|
||||
## Configuring feeds
|
||||
|
||||
Feeds are set in the server environment only (`.env`), never in the browser:
|
||||
|
||||
```sh
|
||||
LOCAL_RECEIVER_FEEDS=1090=http://localhost:8080/data/aircraft.json,978=http://localhost:8978/data/aircraft.json
|
||||
```
|
||||
|
||||
Each entry is `band=url`, where `band` is `1090` or `978`. The URL must:
|
||||
|
||||
- use `http` or `https`;
|
||||
- point at loopback, a private (RFC1918) address, `localhost` or a `*.local`
|
||||
name. Public addresses, link-local addresses (including `169.254.169.254`),
|
||||
IPv6 and other host names are refused;
|
||||
- have a path ending in `aircraft.json`, with no credentials, query or
|
||||
fragment.
|
||||
|
||||
An entry that breaks a rule is logged when the server starts, shown as
|
||||
`invalid` and never fetched. Restart the server after changing the value.
|
||||
|
||||
The server reads every feed about once a second while the Local ADS-B layer is
|
||||
on. A feed is `live`, `stale` (its own `now` is more than 10 s old, usually a
|
||||
stopped decoder), `unreachable` or `invalid`. The Layers panel row and the
|
||||
Local RTL-SDR card show these states.
|
||||
|
||||
## Example: dump1090-fa and dump978-fa on macOS or Linux
|
||||
|
||||
Write each decoder's JSON to a directory, then serve the directory on
|
||||
localhost. Each command keeps running, so start each in its own terminal:
|
||||
|
||||
```sh
|
||||
# 1090 MHz
|
||||
dump1090-fa --device-index 0 --write-json /tmp/adsb1090
|
||||
python3 -m http.server 8080 --bind 127.0.0.1 --directory /tmp/adsb1090
|
||||
|
||||
# 978 MHz UAT: dump978-fa decodes, skyaware978 writes aircraft.json
|
||||
dump978-fa --sdr driver=rtlsdr,rtl=1 --raw-port 30978
|
||||
skyaware978 --connect localhost:30978 --json-dir /tmp/adsb978
|
||||
python3 -m http.server 8978 --bind 127.0.0.1 --directory /tmp/adsb978
|
||||
```
|
||||
|
||||
```sh
|
||||
LOCAL_RECEIVER_FEEDS=1090=http://localhost:8080/aircraft.json,978=http://localhost:8978/aircraft.json
|
||||
```
|
||||
|
||||
A board with two identical-serial channels, such as the Nooelec FlyCatcher,
|
||||
cannot be told apart by serial number. Select each channel by index:
|
||||
`--device-index <n>` for dump1090-fa and `--sdr driver=rtlsdr,rtl=<n>` for
|
||||
dump978-fa. `rtl_test` lists the indexes.
|
||||
|
||||
A tar1090 or readsb install on a Raspberry Pi already serves
|
||||
`http://<pi-address>/tar1090/data/aircraft.json`. Use the Pi's private address
|
||||
or its `.local` name.
|
||||
Reference in new issue
Block a user