Skip to content

Repository files navigation

ADS-B 3D

Release Contributors Issues

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. Runs on your desktop, your phone, and, with a headset, your coffee table.

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.

ADS-B 3D live view

Built with the community

This project is better because people showed up:

  • @tyzbit — requested VR support, then became the entire hardware QA department for it: two rounds of Quest 3 testing with annotated videos, the bug isolation that cracked the AR rendering freeze, the control-scheme design that became free-fly mode, the altitude-scale idea (#8) that became the vertical scale slider, and the VR/AR demo video below.
  • @ValkyrieUK — built the full-stack Docker integration test suite and CI workflow (#9), and caught a bug that silently broke retention on every fresh install.
  • @unLieb — scoped the localization architecture before a line was written (#10) and is the native-speaker reviewer for the German translation.
  • @rknobbe — asked the "can it render the mountains?" question (#7) that became 3D terrain.

Want your name here? Issues with reproduction steps, hardware testing, and translations count just as much as code.

What's new

  • AR diorama mode, matured. The scope clips to an open-top box you place on real furniture and resize from the wrist menu; panning slides the map beneath the fixed frame (yaw-only, so a tilted head never lifts the floor). New auto-orbit slowly circles the scope center or a followed aircraft for a proper desk-ornament view, with an optional follow-random-aircraft mode that re-targets the chase when the followed aircraft drops off the feed. A high-res basemap option fetches tiles one zoom level sharper — low-res tiles are the first thing a headset makes obvious.
  • v0.9.0: the hardening release. A full-project audit in one drop: security headers with a configurable CORS lockdown, per-client rate limiting behind reverse proxies, backend loops that survive bad upstream data and report honest healthchecks, a broad frontend performance pass, keyboard and screen-reader access for the aircraft list, and test coverage from one placeholder test to 750+ real ones with eslint, ruff, and pytest wired into CI.
  • v0.7.0: aircraft that look like aircraft. Every common silhouette now has a real 3D body built from its own tar1090 drawing: a lofted fuselage that follows the drawn width, engines with intake lips on the drawn pods, tail fins, a proper T-tail on the C-17, and rotor blades on helicopters at the angles the artist drew.

Full history in CHANGELOG.md and on the releases page.

What you get

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.

Aircraft list with filter pills Aircraft detail card

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.

3D airway-density heatmap

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.

Languages — English, German, and Spanish (translations are machine-drafted pending native review — corrections welcome). Auto follows your browser locale.

3D terrain (opt-in) — the basemap rises to real ground elevation, with range rings, markers, and aircraft ground icons draped over the hills and an above-ground-level readout in the detail card. Free elevation data, no API key. Pairs beautifully with the OpenTopoMap basemap. With terrain off, the flat map stands at the home field's elevation, so landing traffic meets the map instead of floating at field-elevation height above it. A companion altitude scale slider warps the vertical axis toward low-altitude detail (pattern traffic spreads out) or high-altitude detail (flight levels spread out) — terrain and aircraft stay consistent at any position.

VR / AR — immersive WebXR sessions with laser-pointer controllers, a wrist menu with full settings parity, comfort locomotion (scope or free-fly, snap or smooth turning), and AR placement of the scope onto real furniture. The diorama clip box turns AR into a desk ornament: airspace confined to a placeable, resizable open-top box, with follow mode, slow auto-orbit, and an optional random-aircraft chase. Hardware-tested on a Quest 3; see VR & AR below for the demo video. Side-by-side stereo (Cardboard) is still there for anything without WebXR.

High-res basemaps — an optional sharper-tile mode fetches the basemap one zoom level deeper (4× the tiles for the same coverage). Worth it on a headset or a 4K display; costs bandwidth accordingly.

Keyboard & screen readers — the aircraft list is a real listbox: arrow keys move, Enter/Space selects, and screen readers get proper roles, a focus-trapped ACARS dialog, and a throttled live aircraft count instead of per-second chatter.

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.

Prerequisites

  • Docker Engine 20+ and Docker Compose v2
  • An ADS-B feeder already publishing aircraft.json (tar1090, ultrafeeder, dump1090-fa, readsb-protobuf, …)

Quick start

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://ultrafeeder

docker 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.

VR & AR

Put the airspace on your desk. With a WebXR headset the scope becomes a walkable diorama: orbit a selected aircraft, free-fly through the traffic, change settings from a wrist menu, or enter AR and drop the map onto real furniture with a glance and a trigger pull. Diorama clipping keeps it desk-sized — the map pans beneath a fixed, resizable open-top box — and auto-orbit slowly circles the scope (or your followed aircraft) like a live model.

adsb3d-demo-clip.mp4

Demo recorded on a Quest 3 by @tyzbit, who hardware-tested every iteration of these features. The full-length demo is attached to the v0.6.0 release.

Reverse proxy

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.

Multi-feed

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                # feet MSL, like all altitudes here
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           # optional

Slot 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.

Environment variables

Core:

Variable Default Purpose
LATITUDE / LONGITUDE / ALTITUDE Receiver location; ALTITUDE in feet MSL (as are all FEEDN_ALT)
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)
ENABLE_TERRAIN true Deploy-level 3D-terrain kill switch (users still opt in per-browser)
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)
DNS_RESOLVER 127.0.0.11 nginx resolver address(es) for upstream lookups; override on runtimes without Docker's embedded DNS (e.g. Kubernetes CoreDNS)
MAP_ZOOM / MAP_GRID_SIZE 8 / 21 boot-time tile pre-cache around the station (zoom level / grid width); only active with the /tiles volume mounted

Multi-feed: FEEDN_NAME, FEEDN_LAT, FEEDN_LON, FEEDN_ALT, FEEDN_URL, FEEDN_COLOR, FEEDN_ACARS — see Multi-feed. (FEEDS_CONFIG accepts the raw JSON feed array directly and skips the FEEDN_* synthesis — see .env.example for the shape.)

Backend services (track-service / acars-service containers, not the main image — see the compose example):

Variable Default Purpose
FEEDER_URL http://ultrafeeder same feeder the frontend uses; both services poll it
FEEDER_POLL_SECONDS heuristic live-stream poll cadence override (defaults per feeder type)
COLLECTION_INTERVAL 5 seconds between DB track snapshots (track-service)
RETENTION_DAYS 90 TimescaleDB retention for tracks and ACARS messages
DB_HOST / DB_PORT / DB_NAME / DB_USER / DB_PASSWORD timescaledb-adsb / 5432 / adsb_tracks / adsb / — TimescaleDB connection; password has no default on purpose
ACARS_HOST / ACARS_PORT acarshub / 15550 acarshub JSON-over-TCP feed (acars-service)
STATION_ID adsb-3d station identifier stamped on stored ACARS messages

Reverse proxy: BASE_PATH overrides the auto-detected subpath (see Reverse proxy). TRUSTED_PROXY_CIDR (comma- separated CIDRs) makes rate limiting key on the real client IP behind a fronting proxy instead of one shared bucket. CORS_ALLOW_ORIGIN (default empty = no CORS headers) opts a specific origin — or * — back in if another site consumes the API directly.

Controls

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

Upgrading

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_HISTORICAL defaults to false (was true) — set it explicitly to true if you run track-service.
  • ENABLE_VOICE=true now requires VOICE_STREAM_HOST + VOICE_EVENTS_HOST — the container fails fast without them.
  • track-service / acars-service run as non-root (uid 10001) — host paths bind-mounted into them must be writable by that uid.

Full detail in CHANGELOG.md.

Architecture

                ┌──────── 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.sh renders the config (including per-feed proxy blocks) from env vars at start.

See CLAUDE.md for a deeper architecture orientation.

Development

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/

Credits

  • 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.

License

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.

About

Real-time and historical 3D visualization of ADS-B aircraft data with 7 visual themes, mini radar, and interactive controls

Topics

Resources

Stars

43 stars

Watchers

3 watching

Forks

Releases

Packages

Contributors

Languages