Real-time 3D visualization of ADS-B aircraft, with historical playback, 3D airway-density heatmaps, and optional ACARS message decoding. One Docker image: it serves the viewer and reverse-proxies your existing ADS-B feeder.
Works with anything that publishes readsb's aircraft.json — tar1090,
ultrafeeder, dump1090-fa, readsb-protobuf, and so on.
Live demo → — explore a running instance in your browser.
Warning
Ran the older monolithic version? This is a ground-up rewrite and carries breaking changes — read Upgrading before you pull.
Live mode — per-second updates with a tar1090 altitude color palette
across cones, trails, ground icons, and labels. Click an aircraft for a
detail card: photo, filed route, airframe, and autopilot data when
broadcast. Filter pills (All / Air / Ground / Mil / Emerg) drive both
the list and the 3D scene; emergency squawks get a pulsing red ring.
Mobile-friendly — the sidebar collapses and settings open as a sheet.
Historical mode (needs track-service + TimescaleDB) — scrub a timestamp cursor across the last 1h / 24h / 7d at 1×–60× speed. The 3D airway-density overlay renders every flight path at its real altitude, so busy airways and approach corridors light up as bright bundles in the sky.
ACARS (needs acars-service) — per-aircraft datalink messages in the detail card with an OOOI flight-phase chip (taxi-out / airborne / taxi-in / at gate), a searchable full-page browser, and a 3D ping ring when a message lands for an aircraft on scope.
Multi-feed — point at several receivers and the status bar grows a feed picker. Switching is in-place — no page reload.
Themes — five built-in palettes (Midnight Glass, Daylight, Sectional
Chart, Phosphor CRT, High Contrast) pickable from the settings panel.
Auto follows your system light/dark preference. Switching is live; the
3D scene re-tints in place. Plays especially well with the FAA chart
basemaps below.
VR / AR (experimental) — "Enter VR" opens an immersive WebXR
session for any connected headset, with laser-pointer controllers, an
in-VR wrist menu for settings, and thumbstick locomotion. "Enter AR"
opens a passthrough session on devices that support immersive-ar.
Built without a real headset on hand, so issue reports are welcome.
Side-by-side stereo (Cardboard) is still there for anything without
WebXR.
FAA aeronautical charts (US only) — Sectional, Helicopter, IFR Low, IFR High, and a sectional + roads hybrid, served through the same tile proxy as the regular basemaps. The container auto-discovers the current FAA 56-day chart cycle at start, so the upstream URL stays valid through each rotation as long as you restart the container occasionally.
Voice scanner (needs a separate voice stack) — an optional VHF airband call feed, shown only on the local feed. See docs/VOICE.md.
- Docker Engine 20+ and Docker Compose v2
- An ADS-B feeder already publishing
aircraft.json(tar1090, ultrafeeder, dump1090-fa, readsb-protobuf, …)
services:
adsb-3d:
image: ghcr.io/hook-365/adsb-3d:latest
ports: ["8086:80"]
environment:
- LATITUDE=45.0000
- LONGITUDE=-90.0000
- ALTITUDE=1000
- LOCATION_NAME=My Station
- FEEDER_URL=http://ultrafeederdocker compose up -d, then open http://localhost:8086/ — your
aircraft should appear within a few seconds.
FEEDER_URL must be reachable from inside the container. A bare
service name like http://ultrafeeder only resolves if adsb-3d shares a
Docker network with your feeder. If it doesn't, use the feeder's host IP
and port — e.g. http://192.168.1.50:8080. If the page loads but stays
empty, this is almost always why: check docker logs adsb-3d (the
container reports unhealthy until it can reach the feeder).
For historical playback, ACARS, or the voice scanner, copy
.env.example to .env and start from docker-compose.example.yml
in the repo root. Both have track-service, ACARS, and TimescaleDB
ready to uncomment; the ENABLE_* flags below switch each on.
adsb-3d runs on a subdomain or a subpath (example.com/3d). The
entrypoint auto-detects the subpaths /3d, /adsb, and /adsb-3d when
your proxy passes the prefix through; set BASE_PATH if the proxy strips
the prefix or uses a different path. Worked configs for nginx, Traefik,
Caddy, Apache, and Nginx Proxy Manager are in
docs/REVERSE-PROXY.md.
Define each feed with flat FEEDN_* env vars — the entrypoint
synthesises the rest:
FEED1_NAME=Home Station
FEED1_LAT=45.0000
FEED1_LON=-90.0000
FEED1_ALT=1000
FEED1_ACARS=true # optional
FEED2_NAME=Remote Site
FEED2_URL=192.0.2.10:8086 # host:port of another adsb-3d instance
FEED2_LAT=43.0000
FEED2_LON=-89.0000
FEED2_COLOR=#ff8c4c # optionalSlot 1 is always local — FEED1_URL is ignored. Slots 2+ point at other
adsb-3d instances and the entrypoint wires up the nginx proxy blocks.
Parsing stops at the first missing FEEDN_NAME.
Core:
| Variable | Default | Purpose |
|---|---|---|
LATITUDE / LONGITUDE / ALTITUDE |
— | Receiver location |
LOCATION_NAME |
Home |
Display name |
FEEDER_URL |
http://ultrafeeder |
Anything publishing /data/aircraft.json — must be reachable from the container |
ENABLE_HISTORICAL |
false |
Historical playback UI (needs track-service) |
ENABLE_ACARS |
false |
ACARS panel (needs acars-service) |
ENABLE_VOICE |
false |
VHF voice scanner panel (see docs/VOICE.md) |
HIDE_TOWER |
false |
Hide the home tower marker |
TRACK_API_HOST |
track-service:8000 |
nginx upstream |
ACARS_API_HOST |
acars-service:8000 |
nginx upstream |
VOICE_EVENTS_HOST |
— | nginx upstream for /voice/calls + /voice/ws — what the frontend uses |
VOICE_STREAM_HOST |
— | required when voice is on; point at any reachable host:port (legacy Icecast block, not played by the frontend) |
Multi-feed: FEEDN_NAME, FEEDN_LAT, FEEDN_LON, FEEDN_ALT,
FEEDN_URL, FEEDN_COLOR, FEEDN_ACARS — see Multi-feed.
Reverse proxy: BASE_PATH overrides the auto-detected subpath
(see Reverse proxy).
Mouse: left-drag orbits the camera, scroll zooms, right-drag pans the view (moves the center point).
| Key | Action |
|---|---|
| Arrow keys | Pan the view across the map |
R |
Recenter camera + clear selection |
/ |
Focus the list search box |
Esc |
Close settings panel or ACARS browser |
The older version was a single ~14k-line vanilla-JS app; this is a TypeScript / Three.js rewrite. Most deployments keep working after a pull, but review these first:
ENABLE_HISTORICALdefaults tofalse(wastrue) — set it explicitly totrueif you runtrack-service.ENABLE_VOICE=truenow requiresVOICE_STREAM_HOST+VOICE_EVENTS_HOST— the container fails fast without them.track-service/acars-servicerun as non-root (uid10001) — host paths bind-mounted into them must be writable by that uid.
Full detail in CHANGELOG.md.
┌──────── browser ────────┐
│ Vite/TS Three.js app │
└───────────┬─────────────┘
│
nginx (port 80)
│
┌──────────┬──────────┼──────────┬──────────────┐
│ │ │ │ │
/data/... /api/... /ws/... /api/feeds/N/... /acars-api/
│ │ │ │ │
ultrafeeder track-service │ remote adsb-3d acars-service
│ instance (slot N)
▼
TimescaleDB
(aircraft_positions
+ aircraft_metadata
+ acars_messages)
frontend/— Vite + TypeScript + Three.js viewer, no framework.track-service/— FastAPI + asyncpg; a live WebSocket diff stream plus a TimescaleDB history collector.acars-service/— FastAPI bridge to an acarshub TCP feed.nginx/— reverse proxy + static host;entrypoint.shrenders the config (including per-feed proxy blocks) from env vars at start.
See CLAUDE.md for a deeper architecture orientation.
cd frontend
npm install
npm run dev # Vite dev server
npm run typecheck # tsc, strict
npm run test # Vitest
npm run build # → dist/npm run dev proxies backend routes to http://localhost:8080; override
with DEV_BACKEND. To run the full container against an existing backend:
docker compose -f docker-compose.dev.yml --project-directory . up --build -d
# → http://localhost:8186/- tar1090 (wiedehopf/tar1090, GPL v2+) — SVG aircraft shape catalog and the altitude → color palette.
- readsb / dump1090-fa — upstream Mode S/ADS-B decoder.
- planespotters.net — aircraft photographs in the detail panel.
- adsb.im — callsign → route resolution.
- OpenStreetMap, Carto, ESRI, OpenTopoMap — basemap tile providers.
- VFRMap — hosting for FAA Sectional / Helicopter / IFR Low / IFR High chart tiles, kept in sync with the FAA 56-day cycle. Free non-commercial service; please don't abuse it.
The full history lives in CHANGELOG.md.
- v0.4.0 (2026-05-28): Performance + UX push for high-density feeds. Virtualized aircraft list, click-to-extend trails with 24 h backfill, search filters the scene as well as the list, lazy-loaded feature modules, and a long list of reconciler / trail / bundle optimizations.
- v0.3.0 (2026-05-27): WebXR for real headsets (experimental), with controllers, an in-VR wrist menu, locomotion, and AR passthrough.
- v0.2.0 (2026-05-27): Five color themes (auto-follows system light/dark) and FAA aeronautical chart basemaps (US only).
- v0.1.1 (2026-05-22):
HIDE_TOWER=truenow hides the home marker on the map as well as the HUD coordinates. - v0.1.0 (2026-05-21): First public release of the TypeScript / Three.js rewrite. See the Upgrading section.
Source code is MIT (see LICENSE). Vendored tar1090 data
(frontend/src/aircraft/shapes-data.json) is GPL v2+ per upstream — if
you redistribute the built app, GPL governs that component.



