RRaagul S.
ProjectsBackendFrontendSDROpen Source

Building FlightLog-SDR: a 3D flight tracker for a radio dongle

September 19, 2026

A cheap RTL-SDR dongle and a stub of an antenna can pick up the position, altitude and speed of every airliner within a couple of hundred kilometres. Aircraft broadcast this on 1090 MHz (ADS-B), and anyone can listen. Existing tools such as dump1090 decode it well, but they mostly show a flat map of what is flying right now. I wanted more: a log I own, a proper 3D view, and the ability to ask questions about the past. So I built FlightLog-SDR, and it's open source.

What I wanted

  • Log every flight my receiver hears, locally, with no accounts or API keys.
  • See aircraft in 3D at their true altitude, with sensible attitude.
  • Click a flight and see whether it has flown past before, and overlay flights from different days.
  • Get alerted for emergency squawks and for aircraft I care about.
  • Leave it running on a wall as a black-and-glow "projector" display.

The shape of it

The pipeline is short. dump1090 decodes the radio signal and serves the SBS-1 text stream on port 30003. A FastAPI backend reads that stream, tracks each aircraft, splits the data into flights and writes everything to SQLite. A Next.js app draws it on a CesiumJS globe and talks to the backend over REST and a WebSocket.

RTL-SDR → dump1090 → SBS-1 → FastAPI + SQLite → Next.js + CesiumJS

Things that turned out to be interesting

Smooth motion from sparse data

A receiver may hear an aircraft's position once or twice a second, sometimes much less. Drawing the raw updates makes planes jump. In Cesium I feed each aircraft a sampled position property and stamp samples slightly in the future, so the globe interpolates between them and the clock resyncs if it drifts. Trails and range rings use callback properties so they update every frame without rebuilding geometry.

Attitude that ADS-B doesn't give you

ADS-B carries no pitch or roll. I estimate them: heading is the ground track, pitch comes from vertical rate against ground speed, and roll from how fast the track is turning. It is an estimate and labelled as one in the app, but it makes the 3D models bank into turns and climb and descend believably.

Deciding what a "flight" is

Aircraft appear and disappear as they pass in and out of range. A new flight starts when the callsign changes or the signal has been silent for twenty minutes. That one rule made "has this flown before?" and overlaying different days work, because everything is a clean flight with a start, an end and a track.

Optional enrichment, with a privacy default

Raw ADS-B gives you a hex code and a callsign. To show airline, aircraft type, registration and route, the backend can look things up online and cache the answers. It is off by default, because it means sending the callsigns and addresses you receive to a third-party service. Public lookup APIs also return surprises, such as a string where you expect an object, so every response gets defensive parsing.

Signal strength and gain

Signal strength (RSSI) is only in dump1090's web feed, not in the SBS stream, so the backend polls both. With that data I could add a coverage plot and heatmap of what the antenna actually reaches. I also added a gain assistant. It can edit dump1090's config, restart it, check the feed came back and roll back if it didn't, because changing the tuner gain is the single biggest lever on how many aircraft you hear.

Projector mode

This started as "make the map black" and turned into the part I like most. The map shows only country and state outlines, airports, the receiver with range rings and glowing flight lines. Each aircraft carries a tag that follows it around the screen like a tooltip. The tags are plain HTML positioned every frame from the 3D scene. They are hidden when the aircraft is behind the Earth, laid out so they don't overlap, and flip at the screen edges. Getting them to look stuck to the plane rather than floating near it took the most tuning.

Lessons

  • Make the data model right first. Once flights and positions were clean, alerts, history, stats and replay were mostly queries.
  • Test without hardware. I wrote a fake SBS feed and made the test suite fully hermetic, with no radio, no network and no dependence on my own settings. Development became far faster than waiting for planes.
  • Default to private. Anything that leaves the machine is opt-in and documented.
  • Don't overpromise. Attitude is estimated and signal is relative to my setup. The README says so.

Try it

You need an RTL-SDR dongle, a running dump1090 or readsb, Python and Node. If you don't have a dongle yet, the repo includes a fake traffic generator so you can see everything working. It's licensed under Apache-2.0, so use it, change it, and keep the credit.

Source on GitHub