Terrain-aware radar coverage analysis for siting a Monopulse Secondary Surveillance Radar (MSSR) near Nice Côte d'Azur Airport. The tool computes line-of-sight visibility over real elevation data at eight flight levels, weights it by the airspace aircraft can actually fly, and exports the result to Google Earth.
📄 Read the full technical report (PDF) — physical modelling, algorithms, constraint pipeline, and the two proposed sites.
Developed during the Data & Modeling Week at CentraleSupélec (January 2026), in collaboration with Thales, in response to a public tender from the DRAC (French Civil Aviation Authority).
Given terrain elevation data, a candidate radar site and a list of flight levels, the tool answers three questions:
- Where can the radar see? Ray tracing over the terrain, corrected for Earth curvature and atmospheric refraction.
- Where can aircraft actually be? A flyability model combining an approach glide slope near the airport with a terrain clearance margin en route.
- Where does coverage actually matter? The intersection of the two — useful visibility — which is what distinguishes a good site from one that merely covers empty sky.
| Step | Approach |
|---|---|
| Coordinates | Local ENU plane centred on the radar; error ≈ (d/R)² — under 0.03% at 100 km |
| Earth curvature | Targets and terrain dropped by d²/(2R_eff) |
| Refraction | 4/3 effective Earth radius (R_eff ≈ 8495 km, ITU-R P.453), so rays stay straight |
| Ray tracing | Parametric ray radar → target, bilinear terrain interpolation along the path |
| Sampling | Distance-proportional (50 samples/km) to match the ~90 m terrain resolution |
| Performance | Numba JIT with prange parallel loops — full 8-level run drops from tens of minutes to a couple of minutes over 2.3 M grid points |
The terrain interpolator is a custom Numba-compatible bilinear routine, written because SciPy's
RegularGridInterpolator cannot be called from inside a JIT-compiled parallel loop.
radar-coverage-analysis-tool/
├── radar_visibility/ # Line-of-sight engine
│ ├── config.py # Radar site, flight levels, Earth model, sampling
│ ├── terrain.py # Terrain loading, ENU projection, JIT interpolator
│ ├── visibility.py # Ray-tracing visibility computation (Numba, parallel)
│ ├── export.py # PNG overlays, KML/KMZ, NPZ output
│ └── main.py # Entry point
│
├── flyability_map/ # Flyable-airspace model
│ ├── config.py # Airport position, glide slope, cruise margin
│ ├── flyability.py # Approach and cruise minimum-altitude surfaces
│ └── main.py # Entry point
│
├── useful_visibility/ # Visibility ∩ flyability
│ ├── combine.py # Mask combination and coverage statistics
│ ├── export.py # Maps and Google Earth export
│ └── main.py # Entry point
│
├── webapp/ # Interactive Streamlit application
│ ├── app.py # UI: site selection, parameters, 3D views
│ ├── compute.py # Standalone computation backend
│ └── config_manager.py # Run configuration
│
├── data/
│ └── Nice_Terrain_Data.npz # Elevation grid, 1296 × 1785 points (~90 m)
│
└── docs/
└── Radar_Coverage_Analysis_Report.pdf
Requires Python 3.9+.
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txtstreamlit run webapp/app.pyLoad the bundled terrain, place a radar site, tune the parameters and inspect coverage interactively.
Run the modules in order — each one consumes the previous module's output:
python radar_visibility/main.py
python flyability_map/main.py
python useful_visibility/main.pyEverything tunable lives in the config.py of each module.
radar_visibility/config.py
RADAR_LAT,RADAR_LON,RADAR_HEIGHT— candidate site and mast heightFLIGHT_LEVELS— target altitudes in metres (FL5 … FL400)USE_EARTH_CURVATURE— toggle the 4/3 R correctionSAMPLES_PER_KM,MIN_SAMPLES,MAX_SAMPLES— ray sampling density
flyability_map/config.py
AIRPORT_LAT,AIRPORT_LON— runway referenceAPPROACH_SLOPE,APPROACH_MAX_ALTITUDE— glide-slope corridorCRUISE_MARGIN— terrain clearance above FL100
Each module writes to its own output/ folder:
vis_overlay_FL<alt>.png— per-flight-level coverage overlaysradar_visibility.kmz— layered Google Earth file with the radar markervisibility_data.npz— raw masks, reused by the downstream modulesuseful_visibility.png/.kmz— combined coverage maps
Two compliant sites were proposed, representing complementary trade-offs:
| Site A — most accessible | Site B — maximum coverage | |
|---|---|---|
| Coordinates | 43.6500°N, 7.1020°E | 43.7469°N, 7.1081°E |
| Elevation | 209 m | 895 m |
| Road / power access | 16 m / 100 m | 495 m / 495 m |
| Visibility at FL300+ | ~100% | 100% |
| Score | 9.4 / 10 | 9.5 / 10 |
Site A minimises civil-works risk and favours terminal approach surveillance; Site B maximises en-route coverage from a higher, less masked position. Full statistics per flight level are in the report.
- The bundled terrain dataset covers 43.12°–44.20°N, 6.46°–7.95°E.
- A custom
.npzcan be used instead, provided it containster,latandlonarrays. - First run is slower: Numba compiles the kernels before the first computation.
Data & Modeling Week project, CentraleSupélec — Paris-Saclay University.
Augustin Frenk · Charles Ferragu · Zakary Boublil · Julien Frantz · Amaury Cagnol