All projects

Science, multi-target tracking, radar simulation

Radar Track Lab

  • Public ADS-B truth
  • 20 Monte Carlo runs per condition

A multi-target tracker for a simulated airport surveillance radar, evaluated against real aircraft trajectories. Public ADS-B records of a busy half hour near Amsterdam Schiphol are the ground truth; a virtual rotating radar turns them into noisy detections with misses and clutter, and an extended Kalman filter with Hungarian assignment is compared with a nearest-neighbour baseline that receives exactly the same detections.

Code on GitHub: hyh4867/radar-track-lab ↗

Simulated radar scope (PPI) at 12:57:06 UTC, scan 151: range rings to 100 km, the sweep with fading detections, and confirmed tracks with anonymous numbers, speeds in knots, velocity vectors and predicted paths.
The demo's radar scope in the default condition (Pd 0.9, 5 clutter per scan, σθ 0.15°) · anonymous track numbers · simulated detections over real ADS-B trajectories.
Six panels of ID switches per 30 minutes against detection probability, for clutter 0, 5 and 20 per scan and bearing noise 0.15° and 0.30°. The baseline falls from about 830–900 at Pd 0.7 to 200–320 at Pd 0.97; the tracker stays near 25–60 throughout.
Noise sweep: ID switches per 30 minutes against detection probability · 20 runs per point, band = 95% t-interval of the mean.
Average NEES per track update step for cases A (Pd 1, no clutter) and B (Pd 0.9, clutter 5), with the 95% χ² interval as a band; most steps sit inside, with a spike near steps 37–38 in A and an off-scale first step in B.
Filter consistency on synthetic truth · average NEES per update step against its 95% χ² interval · 50 runs × 10 targets.

The question

How much does a filter-and-assignment tracker improve on simply joining each detection to the nearest earlier one when the targets are real aircraft in busy terminal traffic, and where does it still go wrong?

Setup

Sensor: a virtual 2-D rotating airport surveillance radar at the Schiphol reference point, covering 100 km, with textbook values: one rotation every 4.8 s (12.5 rpm), range noise σr 50 m, bearing noise σθ 0.15°, detection probability Pd 0.9, and 5 clutter detections per scan on average. These are generic values at the level of public textbooks, not the specifications of any real military system.

Ground truth: adsb.lol ADS-B records for 6 November 2026, 12:45–13:15 UTC (30 minutes, 375 scans), the busiest part of that day. After keeping only airborne ADS-B positions with a position-accuracy category (NACp) of 8 or better, 149 aircraft appear in the window, about 50 per scan (50.4 on average, at most 58).

Each detection is timed when the beam passes the target's bearing, and the true position at that instant is interpolated from the neighbouring ADS-B points. A leave-one-out test puts that interpolation error at about 6–7 m RMS, an order of magnitude below the radar noise.

Aircraft flagged as military, LADD or PIA are dropped at the first step, and no registration, flight number or ICAO address is kept; aircraft get plain numbers. There is no threat evaluation or classification of any kind.

What I built

The tracker is an extended Kalman filter with a nearly-constant-velocity model and polar (range, bearing) measurements. Each track–detection pair is predicted to that detection's own time, gated with a χ² test, and assigned with the Hungarian algorithm (global nearest neighbour). Tracks are confirmed after 3 detections in their first 4 scans and deleted after 5 missed scans.

The process-noise level (q = 25 m²/s³) was chosen from ten candidates on a separate 15-minute tuning window (10:00–10:15 UTC) only, never on the evaluation window.

The baseline has no filter: a track's position is its last detection, and it is joined greedily to the nearest detection within 2 km. Track management is the same as the tracker's, so the differences come from filtering, prediction and gating alone.

Tracker against baseline

Pd 0.9, 5 clutter detections per scan, σr 50 m, σθ 0.15°, one rotation every 4.8 s; 149 aircraft over 30 minutes. Mean of 20 Monte Carlo runs ± 95% t-interval of the mean; the first 60 s are excluded. GOSPA is an all-target summed metric: one number per scan that adds up localisation error, missed targets and false tracks over every target (cut-off 2 km), so it appears here only as a summary, with its three parts.
MeasureEKF + Hungarian trackerNearest-neighbour baseline
Position RMSE at evaluation time174.7 ± 1.2 m528.1 ± 1.4 m
Position RMSE right after an update125.8 ± 0.5 m172.8 ± 0.6 m
ID switches (30 min)24.9 ± 2.1459.2 ± 7.8
Track breaks (30 min)3.6 ± 0.7414.2 ± 7.9
Missed targets per scan0.82 ± 0.024.02 ± 0.07
False tracks per scan1.08 ± 0.014.91 ± 0.07
Track initiation delay, mean13.8 ± 0.2 s14.1 ± 0.3 s
Mean GOSPA (all-target summed metric)2,236 ± 10 m5,508 ± 24 m
GOSPA, localisation part1,229 ± 9 m3,592 ± 8 m
GOSPA, missed-target part1,282 ± 12 m2,833 ± 23 m
GOSPA, false-track part1,468 ± 7 m3,134 ± 21 m

Dense clutter

Pd 0.9, σθ 0.15°, 20 runs per row, mean ± 95% t-interval. Clutter is spread uniformly in range and bearing, so its density falls with distance from the radar; averaged over the 31,413 km² coverage it is 1.6×10⁻⁴ (5 per scan), 6.4×10⁻³ (200) and 3.2×10⁻² (1,000) per km² per scan.
Clutter per scan, methodPosition RMSEID switches (30 min)False tracks per scanMissed targets per scan
5, tracker174.7 ± 1.2 m24.9 ± 2.11.08 ± 0.010.82 ± 0.02
5, baseline528.1 ± 1.4 m459.2 ± 7.84.91 ± 0.074.02 ± 0.07
200, tracker184.6 ± 1.8 m43.2 ± 3.012.23 ± 0.200.93 ± 0.02
200, baseline545.2 ± 1.3 m692.8 ± 10.362.36 ± 0.524.35 ± 0.07
1,000, tracker230.1 ± 2.9 m344.6 ± 17.0555.22 ± 2.310.91 ± 0.03
1,000, baseline576.5 ± 1.6 m2,156.4 ± 15.11,016.35 ± 2.113.43 ± 0.04

Filter consistency (NEES)

Synthetic truth that moves exactly as the model assumes (nearly constant velocity, q = 25): 50 runs × 10 targets × 50 scans. ANEES is the average normalised estimation error squared over the 4-D state; a consistent filter averages 4.
CaseSamplesANEES95% interval for that many samplesUpdate steps inside their 95% interval
A: Pd 1, no clutter24,2694.16[3.96, 4.04]93.9% (46 / 49)
B: Pd 0.9, 5 clutter per scan21,7224.19[3.96, 4.04]91.5%

Evaluation

Each scan is scored when it ends: confirmed tracks are predicted to that instant (the baseline keeps its last detection) and compared with the ADS-B truth at the same instant. The first 60 s are left out.

Position RMSE is taken over track–aircraft pairs closer than 2 km. The after-update RMSE uses tracks right after a detection updates them, which removes the prediction gap between scans. An ID switch is a change of track number on the same aircraft; a track break is a lost pairing while the aircraft is still in the truth.

Each condition runs 20 times with fixed seeds: the truth stays the same while noise, misses and clutter change, and the tracker and the baseline receive the same detections. ± is the 95% t-interval of the mean.

Noise sweep: detection probability 0.7, 0.8, 0.9 and 0.97 × clutter 0, 5 and 20 per scan × bearing noise 0.15° and 0.30°, so 24 conditions, 20 runs each, for both methods. An extra run raised clutter to 200 and 1,000 per scan at Pd 0.9 and σθ 0.15°.

Filter consistency is checked with NEES on synthetic truth that follows the model exactly. Real trajectories do not follow a constant-velocity model, so NEES on the ADS-B truth is reported but not used as a consistency test.

Key findings

On the same detections, the tracker cut position RMSE from 528 m to 175 m and ID switches from 459 to about 25 in 30 minutes; track breaks fell from 414 to 3.6.

Most of the baseline's 528 m comes from standing still at its last detection between scans. Right after an update the gap is smaller: 173 m against 126 m, 27% lower.

In the noise sweep the tracker's ID switches stayed at 22–26 per 30 minutes for Pd 0.9 and above, rose to 30–34 at Pd 0.8 and 59–64 at Pd 0.7. The baseline made 197–903 across the same conditions.

With sparse clutter (up to 200 false alarms per scan, 6.4×10⁻³ per km²) the tracker holds: position RMSE rises by only 10 m. At 1,000 per scan (3.2×10⁻² per km²) it breaks down, confirming 555 false tracks per scan, because clutter points fill the wide initiation gates.

With the model's own motion, the average NEES was 4.16 (Pd 1) and 4.19 (Pd 0.9 with clutter), slightly above the 95% interval [3.96, 4.04]; 46 of 49 update steps were inside their own interval. The largest values came from two samples 1.0 and 1.4 km from the radar, where the linearised polar measurement breaks down; beyond 5 km the average was 4.07.

On the real ADS-B truth the average NEES was 2.85 (median 2.00): the filter is more cautious than it needs to be on straight legs, because q was tuned to keep up with turns.

Where the errors come from

Of the tracker's 24.9 ID switches per run, 7.0 followed an ADS-B gap longer than 20 s, when the aircraft disappears from both the truth and the simulation; 4.6 happened with another aircraft within 3 km, where crossing traffic at different altitudes looks merged in 2-D; and 13.3 were tracks lost while the aircraft stayed present. That last group is probably the constant-velocity model falling behind in turns, but I did not check the cases one by one.

Of 1.08 false tracks per scan, 1.00 were tracks coasting for up to 5 scans after their aircraft left the truth (landed, left coverage or an ADS-B gap). No track was confirmed from clutter in this condition.

Of 0.82 missed targets per scan, 0.75 were aircraft that had just appeared and were not yet confirmed by the 3-of-4 rule, which takes about 3 scans (14 s). Only 0.08 had been tracked before and were lost.

My role and the agent workflow

I set the scope and the rules: a civil airport radar only, public data only, no threat evaluation, a baseline that receives the same detections, tuning on a separate window, and every number taken from the result files.

AI agents implemented the simulator, tracker, evaluation and demo in bounded steps on my instructions. I reviewed their output and keep responsibility for the claims on this page.

Limits

Flat 2-D geometry: altitude and slant range are ignored, so aircraft at different altitudes that cross in the plane cannot be told apart, and there is no overhead blind cone, vertical coverage or low-altitude shadowing from the Earth's curvature.

The truth contains only aircraft that broadcast ADS-B. 54 aircraft seen only by multilateration in this window, probably small aircraft, are left out of the simulation, as are aircraft on the ground.

ADS-B interpolation error (about 6–7 m RMS) and the ADS-B position error itself remain in the truth. Gaps longer than 20 s make aircraft vanish and reappear, which causes some of the ID switches and false tracks.

The radar values are generic textbook and standards-level values, not any particular equipment: constant Pd, uniformly spread clutter, Gaussian noise, no bias, no merging of close targets and no multipath.

A single constant-velocity model lags in turns. An interacting multiple model (IMM) was not tried.

Dense clutter defeats the track initiation: probabilistic association (JPDA/MHT) or clutter maps would be the next step; they were not tried here.

One 30-minute window at one airport. The process noise was tuned on another 15-minute window of the same day.

Evidence and reproducibility

Every number on this page comes from the project's results file (results/RESULTS.md), which a script generates from the run files; it also lists all 24 sweep conditions and the ground-truth filtering steps. The figures are produced by the project's plotting script from the same runs.

Data: adsb.lol globe_history_2026, records of 6 November 2026. Contains information from adsb.lol, made available under the ODbL 1.0; trajectory-derived files are under the ODbL 1.0 as well. The radar values cite the ASR-11 rotation rate and EUROCONTROL surveillance requirements as reported by ICAO.

adsb.lol globe_history_2026 (ODbL 1.0) ↗

ICAO APAC SURICG/6 IP/04 (2021), Practical approach to assess the performance of the surveillance systems ↗

ASR-11 (Wikipedia): 12.5 rpm antenna rotation ↗

Back to all projects