A clean, object-oriented C++ simulation of a single-target 2D radar tracking system, built to demonstrate synthetic radar measurement generation, Gaussian sensor noise, and Kalman Filterโbased state estimation.
Ground truth trajectory (solid), noisy radar measurements (scattered dots), and the Kalman Filter's estimate (dashed) for a single maneuvering air target.
Tip
๐ข Radar screen convention: in this README, radar/tracking-related highlights use the classic phosphor radar green (#39FF14) โ the same color long associated with analog radar and sonar displays.
- Overview
- Why This Project Exists
- Architecture
- Class Design
- The Math: Kalman Filter
- Results
- Project Structure
- Building the Project
- Running the Simulation
- Visualizing the Results
- Configuration
- Roadmap (Future Versions)
- License
RadarTrackingSim simulates the core pipeline of a radar tracking system for a single air target moving in a 2D plane:
- ๐ฐ๏ธ A target moves through space with a (slightly curving) constant-velocity motion model.
- ๐ก A synthetic radar "observes" the target's true position and adds realistic Gaussian measurement noise.
- ๐งฎ A Kalman Filter consumes the noisy measurements and produces a smoothed, statistically optimal estimate of the target's position and velocity.
- ๐ The full run (true position, noisy measurement, filtered estimate) is exported to CSV and visualized with professional charts.
This is V1 of the project โ intentionally scoped to a single target and a linear, constant-velocity Kalman Filter, so that the fundamental building blocks are easy to read, test, and extend.
Radar tracking is a classic application of estimation theory. Real radars never see the "true" position of a target โ they see a noisy measurement corrupted by thermal noise, atmospheric effects, and quantization. The Kalman Filter is the textbook solution for recovering a clean state estimate from this noisy stream, and it remains the foundation of far more sophisticated tracking systems used in aviation, defense, and autonomous vehicles.
This project builds that pipeline from scratch, in modern C++, with:
- No hidden "black box" math โ the Kalman Filter's matrix algebra is implemented explicitly and commented step by step.
- A clean separation of concerns between the physical simulation (
Target), the sensor model (Radar), and the estimator (KalmanFilter). - A CMake build system so it compiles the same way on any platform with a C++17 compiler.
Note
This project is a learning-oriented reference implementation, not a certified/operational tracking system. It's meant to make the Kalman Filter's math tangible by actually running it end-to-end.
Each simulation step flows through four collaborating classes:
Targetadvances the true (ground-truth) position and velocity of the aircraft using its motion model.Radartakes that true position and returns a noisy measurement by adding zero-mean Gaussian noise to each axis.KalmanFilterpredicts the next state from its internal model, then corrects that prediction using the noisy radar measurement.Simulationorchestrates the loop, collects every step's data, and exports it to CSV for plotting.
| Class | Responsibility |
|---|---|
Vector2D |
Minimal 2D vector/point type used throughout the project (position, velocity). |
Target |
Owns the ground-truth kinematic state of the aircraft. Supports constant-velocity motion with an optional turn rate for a more realistic, curved flight path. |
Radar |
๐ข Synthetic sensor. Converts a true position into a noisy measurement using std::normal_distribution (Gaussian/white noise). |
KalmanFilter |
๐งฎ Implements a 4-state (x, y, vx, vy) linear Kalman Filter with explicit predict() and update() steps. All matrix operations (multiplication, transpose, 2ร2 inversion) are hand-written for full transparency โ no external linear algebra library is required. |
Simulation |
Orchestrates the per-step pipeline (Target โ Radar โ KalmanFilter), records results, and exports them to CSV. |
This separation means each class can be unit-tested, replaced, or extended independently โ for example, swapping in an Extended Kalman Filter later only touches the KalmanFilter class.
The filter tracks a 4-dimensional state vector:
X = [ x, y, vx, vy ]แต
Prediction step (uses the motion model to project the state forward by dt):
X_k = F ยท X_(k-1)
P_k = F ยท P_(k-1) ยท Fแต + Q
Update step (corrects the prediction using the new noisy measurement Z):
y = Z - H ยท X_k (innovation / residual)
S = H ยท P_k ยท Hแต + R (innovation covariance)
K = P_k ยท Hแต ยท Sโปยน (Kalman gain)
X_k = X_k + K ยท y (corrected state)
P_k = (I - K ยท H) ยท P_k (corrected covariance)
Where:
Fis the constant-velocity state transition matrix (position updated byvelocity ร dt).His the measurement matrix, selecting the position components (x, y) from the state.Qis the process noise covariance (models unpredictable target maneuvers).Ris the measurement noise covariance (matches the radar's known noise level).Pis the state covariance matrix (the filter's own uncertainty about its estimate).
All of this is implemented by hand in KalmanFilter.cpp using fixed-size arrays โ readable, dependency-free, and easy to step through in a debugger.
Running the default configuration (200 steps, dt = 0.5s, radar noise ฯ = 25m) produces the following error comparison:
Important
In this run, the Kalman Filter reduces the average position error by roughly 45โ50% compared to the raw, unfiltered radar measurements โ while also producing a visibly smoother trajectory (see the trajectory plot above).
Exact numbers vary slightly between runs unless a fixed random seed is used (V1 defaults to a fixed seed of 42 for reproducibility).
RadarTrackingSim/
โโโ CMakeLists.txt # CMake build configuration
โโโ include/ # Public headers (class interfaces)
โ โโโ Vector2D.h
โ โโโ Target.h
โ โโโ Radar.h
โ โโโ KalmanFilter.h
โ โโโ Simulation.h
โโโ src/ # Implementation files
โ โโโ Target.cpp
โ โโโ Radar.cpp
โ โโโ KalmanFilter.cpp
โ โโโ Simulation.cpp
โ โโโ main.cpp
โโโ scripts/
โ โโโ plot_results.py # Generates the trajectory & error charts (interactive + saved)
โ โโโ make_architecture_diagram.py # Regenerates assets/architecture.png
โ โโโ make_radar_banner.py # Regenerates the radar-green banner image
โโโ data/ # CSV output from the simulation (generated)
โโโ assets/ # Charts/images used in this README (generated)
โโโ README.md # This file (English)
โโโ README_TR.md # Turkish version
Requirements: a C++17-capable compiler (GCC โฅ 9, Clang โฅ 10, MSVC โฅ 2019) and CMake โฅ 3.15.
git clone <this-repo-url>
cd RadarTrackingSim
mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
make
cd ..This produces an executable at build/radar_tracking_sim. The cd .. at the end matters โ the next step assumes you're back in the project root.
From the project root (not from inside build/):
./build/radar_tracking_simThis runs the full simulation (target motion โ radar noise โ Kalman filtering) and writes the results to data/simulation_output.csv, relative to wherever you ran the command from โ which is why running it from the project root matters:
time, true_x, true_y, meas_x, meas_y, est_x, est_y
The plotting script requires Python 3 with matplotlib and numpy:
pip install matplotlib numpyThen, from the project root:
python3 scripts/plot_results.pyThis is the correct, working command. By default it:
- reads
data/simulation_output.csv(the file produced in the previous step), - (re)writes
assets/tracking_result.pngandassets/error_over_time.png, - and opens an interactive Matplotlib window (
plt.show()) if your machine has a display/GUI available, so you can pan, zoom, and inspect the trajectory live โ not just overwrite the saved images silently.
Useful variations:
# Headless machine / SSH session / CI โ skip the interactive window, just save the PNGs
python3 scripts/plot_results.py --no-show
# Point at a different CSV location or output folder
python3 scripts/plot_results.py --csv build/data/simulation_output.csv --outdir assetsNote
If you run the simulation from inside build/ instead of the project root (e.g. cd build && ./radar_tracking_sim), the CSV ends up at build/data/simulation_output.csv. In that case, run the plotting script with --csv build/data/simulation_output.csv as shown above.
All simulation parameters live in Simulation::Config (see main.cpp):
| Parameter | Description | Default |
|---|---|---|
dt |
Time step per simulation iteration (seconds) | 0.5 |
numSteps |
Total number of simulation steps | 200 |
initialPosition |
Target's starting position (m) | (0, 0) |
initialVelocity |
Target's starting velocity (m/s) | (40, 15) |
targetTurnRateRadPerSec |
Turn rate applied to the target's velocity vector (rad/s) | 0.02 |
radarNoiseStdDev |
Standard deviation of the radar's Gaussian measurement noise (m) | 25.0 |
kalmanProcessNoiseStd |
Assumed process noise standard deviation for the filter | 1.0 |
radarSeed |
Random seed for reproducible noise generation | 42 |
Feel free to tune these to see how the Kalman Filter behaves with noisier sensors, sharper turns, or different sampling rates.
This is explicitly a V1. Natural next steps for future iterations include:
- ๐ฏ Multi-target tracking with data association (e.g. Nearest Neighbor, JPDA).
- ๐ก Range/azimuth (polar) radar measurements instead of direct Cartesian noise, with an Extended Kalman Filter (EKF) or Unscented Kalman Filter (UKF).
- ๐ Maneuvering-target models (e.g. Interacting Multiple Model / IMM filters).
- ๐ฅ๏ธ Real-time visualization instead of post-run CSV plotting.
- โ Unit tests (GoogleTest) for each class.
This project is provided as an educational reference implementation. Feel free to use, modify, and extend it for learning or prototyping purposes.