4D flight trajectory prediction (lat, lon, altitude, time) using real-world ADS-B data from the OpenSky Network.
Temporal Transformer trained on 25,000+ real flights over the Paris terminal area (TMA).
Air traffic management systems need to predict where an aircraft will be in the next few minutes - for conflict detection, runway sequencing, and fuel optimization. This project builds a real ML system for that task, trained on actual ADS-B transponder data rather than a packaged benchmark dataset.
The Transformer is compared against a kinematic baseline (constant velocity + heading + vertical rate - what a radar operator would extrapolate by hand):
| Horizon | Kinematic Baseline | Transformer | Gain |
|---|---|---|---|
| +1 min | 0.80 km (median) | 1.14 km | - |
| +2 min | 1.60 km | 1.93 km | - |
| +3 min | 2.93 km | 2.69 km | −8% |
| +6 min | 12.42 km | 4.84 km | −61% |
Evaluated on 39,289 test windows from 3,835 held-out flights. Haversine error (great-circle distance), median.
ADE / FDE @ 5 min horizon (baseline): ADE = 5.38 km, FDE = 12.60 km
The model underperforms the baseline at short horizons (+1/+2 min), where constant-velocity kinematics is hard to beat. It gains a significant advantage at longer horizons (+6 min), where aircraft maneuvers, phase transitions, and traffic patterns diverge from naive extrapolation.
- Source: OpenSky Network REST API (ADS-B state vectors)
- Area: Paris TMA bounding box - lat [48.0, 49.5], lon [1.0, 3.5]
- Collection: 7 days (June 2026), polling every 30s, ~938k raw state vectors
- After preprocessing: 25,571 clean flights, median duration 13.5 min
| Stage | Count |
|---|---|
| Raw state vectors | 937,923 |
| Flight segments detected | 32,648 |
| After duration filter | 25,698 |
| After resample + quality filter | 25,571 |
Input sequence (10 × 30s = 5 min context)
12 features per step:
Δlat, Δlon, Δaltitude ← displacement deltas
velocity, vertical_rate ← kinematic state
sin(heading), cos(heading) ← circular heading encoding
Δheading, velocity_roc ← rate of change
phase (climb/cruise/descent) ← flight phase embedding
baro_altitude, elapsed_s_norm ← absolute context
│
▼
Linear projection → d_model=256
│
▼
Learned positional encoding
│
▼
6 × Transformer encoder layers
(8 heads, FFN dim=1024, GELU, Pre-LN, dropout=0.3)
│
▼
[CLS] token pooling
│
▼
4 × independent prediction heads
→ [Δlat, Δlon, Δalt] at +1/+2/+3/+6 min
4.9M parameters - trained on a T4 GPU (Google Colab), ~82 epochs, early stopping.
Why a Transformer over LSTM?
Self-attention lets every timestep attend to every other directly - no vanishing gradient over long sequences. Empirically outperforms LSTM on trajectory prediction tasks at horizons > 3 min.
OpenSky API (OAuth2)
│
▼
scripts/run_collector.py ← ADS-B polling, parquet output, budget management
│
▼
src/data/preprocess.py ← flight segmentation, resample(30s), interpolation
│
▼
src/data/dataset.py ← sliding windows, feature engineering, normalisation
│
▼
src/models/transformer.py ← TrajectoryTransformer
│
▼
scripts/train.py ← AdamW + cosine LR + warmup + early stopping
│
▼
src/eval/baseline.py ← kinematic baseline + haversine metrics
│
▼
src/serving/api.py ← FastAPI inference endpoint
│
▼
docs/demo.html ← live map demo (Leaflet)
- Python 3.11+
- Docker
- OpenSky Network account (register here)
git clone https://github.com/JeremyMaille/flight-trajectory-prediction.git
cd flight-trajectory-prediction
python3 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt
cp .env.example .env
# Fill in your OpenSky OAuth2 credentialsexport $(grep -v '^#' .env | xargs)
python scripts/run_collector.py
# Collects ADS-B state vectors every 30s into data/raw/
# Budget: 3500 API credits/day (resets at midnight UTC)python src/data/preprocess.py
# Segments flights, resamples to 30s grid, engineers features
# Output: data/processed/flights/*.parquetpython src/eval/baseline.py
# Prints kinematic baseline metrics (haversine error, ADE/FDE)# Open notebooks/train_colab_v2.ipynb in Colab
# Runtime → Change runtime type → T4 GPU
# Runtime → Run AllOr locally:
python scripts/train.py
mlflow ui # monitor at http://localhost:5000docker compose up --build
# API available at http://localhost:8000
# Open docs/demo.html in your browserGET /health → model status
GET /flights → list available flights
GET /demo/{flight_id} → fetch a real flight for the demo
POST /predict → predict future positions
Example prediction request:
curl -X POST http://localhost:8000/predict \
-H "Content-Type: application/json" \
-d '{
"points": [
{"ts": "2026-06-20T10:00:00Z", "latitude": 48.85, "longitude": 2.35,
"baro_altitude": 10000, "velocity": 230, "true_track": 90, "vertical_rate": 0},
... // 10 points minimum, ~30s apart
]
}'Response:
{
"status": "ok",
"predictions": [
{"horizon_label": "+1 min", "pred_latitude": 48.853, "pred_longitude": 2.578, "pred_altitude": 9998},
{"horizon_label": "+2 min", "pred_latitude": 48.856, "pred_longitude": 2.806, "pred_altitude": 9995},
{"horizon_label": "+3 min", "pred_latitude": 48.860, "pred_longitude": 3.034, "pred_altitude": 9990},
{"horizon_label": "+6 min", "pred_latitude": 48.871, "pred_longitude": 3.718, "pred_altitude": 9975}
]
}- Short horizons: The kinematic baseline outperforms the model at +1/+2 min. On very short horizons, physics-based extrapolation is hard to beat without a much larger dataset.
- Data volume: 25k flights over 7 days is a limited training set. Performance would improve significantly with historical data via the OpenSky Trino interface (access requested).
- Geographic scope: Trained exclusively on the Paris TMA - the model may not generalise to other airspaces without retraining.
- No intent encoding: The model has no access to flight plan data, which would substantially improve longer-horizon predictions.
| Component | Technology |
|---|---|
| Data collection | OpenSky REST API (OAuth2), Python |
| Data processing | pandas, pyarrow, resample/interpolate |
| ML framework | PyTorch 2.x |
| Model | Temporal Transformer (custom) |
| Experiment tracking | MLflow |
| Serving | FastAPI + Uvicorn |
| Containerisation | Docker + docker-compose |
| Demo | Leaflet.js |
| Training environment | Google Colab (T4 GPU) |
flight-trajectory-prediction/
├── data/
│ ├── raw/ # ADS-B parquet files (gitignored)
│ └── processed/flights/ # per-flight parquet (gitignored)
├── docs/
│ └── demo.html # live map demo
├── models/
│ ├── best_model.pt # trained checkpoint (gitignored)
│ └── norm_stats.json # normalisation statistics
├── notebooks/
│ └── train_colab_v2.ipynb # Colab training notebook (Run All)
├── scripts/
│ ├── run_collector.py # ADS-B data collector
│ └── train.py # training entry point
├── src/
│ ├── data/
│ │ ├── preprocess.py # flight segmentation + resampling
│ │ └── dataset.py # PyTorch Dataset + feature engineering
│ ├── eval/
│ │ └── baseline.py # kinematic baseline + metrics
│ ├── models/
│ │ └── transformer.py # TrajectoryTransformer architecture
│ └── serving/
│ └── api.py # FastAPI inference service
├── Dockerfile
├── docker-compose.yml
├── requirements.txt
└── .env.example
Jérémy Maille - Ingénieur IA & Machine Learning
CESI École d'Ingénieurs (Bac+5)
GitHub · LinkedIn