How to Build a Live Flight Map (Real-Time Aircraft Positions via API)
Build a real-time flight map that plots live aircraft and animates them smoothly — the endpoints, the render loop, and the dead-reckoning trick, with code.
A live flight map is one of those features that looks like magic and turns out to be surprisingly tractable: a scatter of aircraft over a map, each one gliding along its heading, updating as the world moves. Whether you're building an operations dashboard, an arrivals board, or a "what's flying overhead" widget, the recipe is the same. This guide walks through it end to end, with the one trick that makes it feel smooth.
The two queries you need
A live map is driven by just two endpoints:
/airspace— every aircraft in an area right now. Query a bounding box for the current viewport, a radius around a point, everything near an airport, or everything inbound to one./track— the live position of a single flight, when a user clicks one.
Grab the aircraft inside the map's current view in one call:
# every aircraft in the visible box curl "https://api.flightnerve.com/airspace/KEY?bbox=34,-10,60,20" → [ { "callsign":"BAW117","latitude":51.3,"longitude":-0.2, "altitude":9000,"groundSpeed":260,"heading":78 }, … ]
Drawing them
Render on a <canvas> overlay, not with one DOM marker per plane — a busy sky is thousands of aircraft. For each one, project its lat/lon to screen pixels, translate there, rotate by heading, and draw a small triangle. That's the whole renderer.
The trick: dead-reckoning
If you only redraw when new data arrives, planes teleport every refresh. The fix is to fetch occasionally but animate every frame: between refreshes, advance each aircraft along its own heading at its own ground speed. Over a few seconds a jet flies a straight line, so a simple extrapolation looks perfectly real:
// each animation frame, dt = seconds since last frame const degPerSec = plane.speedKt * 1.852 / 3600 / 111; // kt → deg/s const r = plane.heading * Math.PI/180; plane.lat += Math.cos(r) * degPerSec * dt; plane.lon += Math.sin(r) * degPerSec * dt / Math.cos(plane.lat*Math.PI/180);
Refetch /airspace every ~60 seconds and snap positions back to truth. Between fetches, dead-reckoning carries the motion. This is exactly how the map on our own homepage works.
Polling, cost & coverage
- Poll on move, throttled. Refetch when the viewport changes or on a ~60s timer — not every frame.
- Coverage is ADS-B. Excellent over land and busy airspace, with gaps over remote oceans — good to note in your UI.
- Freshness. Each aircraft carries a last-seen timestamp; fade or drop very stale ones.
Bonus: a live arrivals board
The same endpoint powers an arrivals board in one call — every airborne aircraft routed to an airport, with distance and a rough ETA:
curl "https://api.flightnerve.com/airspace/KEY?inbound=JFK" → { "aircraft":[ { "callsign":"AAL1185","distanceKm":9.6,"etaMinutes":2 }, … ] }
From snapshot to motion: interpolating between updates
A live map feels alive only if the aircraft move between data refreshes. Since position updates arrive every so often rather than every frame, the trick is dead reckoning: between updates, advance each aircraft along its last known heading at its last known ground speed, then snap it to the true position when a fresh update lands. Done well, planes glide smoothly instead of teleporting.
# advance a marker between updates (per animation frame)
dt = (now - lastFrame) / 1000
distKm = groundSpeedKt * 1.852 * (dt / 3600)
lat += Math.cos(headingRad) * distKm / 111
lon += Math.sin(headingRad) * distKm / (111 * Math.cos(latRad))Rendering thousands of aircraft smoothly
A busy sky is thousands of moving objects. DOM markers buckle at that scale, so render to a single <canvas> and redraw each frame, it comfortably handles tens of thousands of points. Colour aircraft by altitude for instant depth, size them subtly by phase, and cull anything outside the current viewport before you draw. Batch your data refresh (one call for the whole visible area) rather than one call per aircraft.
Filtering the sky: show only what matters
Raw "everything airborne" is rarely the product. Give users meaningful slices: aircraft near a point, inside a bounding box, around a specific airport, or inbound to an airport with a live ETA, an instant arrivals board on a map. Server-side filtering keeps the payload small and the map fast.
# everything inbound to JFK, with live ETA curl "https://api.flightnerve.com/airspace/KEY?inbound=JFK"
Keeping it honest: freshness and coverage
A live map is only as trustworthy as its freshness. Stamp every aircraft with the age of its last fix and fade or drop markers that go stale, so you never show an aircraft frozen in place as if it were live. Coverage is strongest over busy, populated airspace; be transparent about it rather than implying a plane is missing when it is simply out of range.
Frequently asked questions
How do I make aircraft move between updates?
Dead-reckon between refreshes: advance each marker along its heading at its ground speed every frame, then correct to the true position when a new update arrives.
Canvas or DOM markers?
Canvas. A single canvas redrawn per frame scales to tens of thousands of aircraft; individual DOM markers do not.
How do I avoid downloading the whole sky?
Filter server-side, by point and radius, bounding box, airport, or inbound-to-airport, so you only fetch the aircraft actually in view.
How do I show an arrivals board on the map?
Query the airspace endpoint for aircraft inbound to an airport; each comes with a live ETA you can list alongside the map.
Getting started
FlightNerve puts all of this behind a single API key: flight status, live position, airports, routes and live airspace, in one clean JSON shape. It is free to start, no card required. Create a key, make your first call in a minute, and browse the documentation or the airlines and airports we track on the Airlines and Airports pages.
