Live ADS-B radar, bin days and the awkward business of drawing an aeroplane in SVG.
2026-06-16
I have a monitor turned on its side next to my main one, and for months it did nothing but hold a terminal I never looked at. So I built something for it: a vertical dashboard of cards that pulls from about fifteen sources, none of which need an API key, and renders them in a browser I never close.
The card I keep looking at is a live flight tracker. It pulls ADS-B data for everything within 25 nautical miles and draws it as an actual radar scope, with range rings, a sweep and breadcrumb trails behind each aircraft, over dark basemap tiles.
Two details took longer than the rest of it combined. The first was making aircraft look like aircraft: fixed-wing traffic draws as a plane silhouette, rotary as a helicopter with crossed rotor blades, both nose-up so the existing track rotation points them correctly. The category flag that is supposed to tell you which is which is frequently absent, and it is absent on exactly the traffic I care about: the police and air ambulance units. So detection falls back to the ICAO type code and then the description text.
The second was the basemap. I wanted the town names brighter without washing out the coastlines, which is impossible on a combined tile layer. The fix was to fetch terrain and labels as two separate layers into their own SVG groups, so the place names can be lifted independently of everything underneath them.
The council's bin day API returns one date per container, the next one. That reads fine until you notice the weekly bin only ever appears against its own next collection, and never on the fortnightly bin's week, even though it obviously goes out then too. The endpoint also describes the cadence in a text field, so anything described as "every week" gets projected across every collection day that shares its weekday.
Every source here is best effort: community ADS-B receivers, council endpoints, news feeds. The cache holds the last good value for each and serves it when a refresh fails, so a flaky upstream leaves real if slightly old data on screen instead of an error. That sounds obvious and I did not build it that way first; I built it after watching three cards blank simultaneously because one receiver dropped out.
Zero framework on the server, and one runtime dependency in total, the grid library doing drag and resize. There is a fuller write-up on the Side Dashboard project page.