Ticket 094: SORA Ground Risk Class (iGRC)¶
Status¶
Implemented.
Goal¶
Compute the intrinsic Ground Risk Class (iGRC) for a mission by overlaying the route against a population-density grid and the aircraft's characteristic dimension, following the EASA SORA methodology. Output a per-leg and whole-mission iGRC so operators can see, before flight, which ground-risk class their operation falls into.
Why This Is High Impact¶
SORA (Specific Operations Risk Assessment) is the regulatory framework that governs BVLOS approvals in the EU, UK, and a growing number of other jurisdictions. The first number every SORA submission needs is the intrinsic Ground Risk Class — a table lookup derived from the aircraft's characteristic dimension and the population density beneath the flight path.
bvlos-sim already models the route, terrain, wind, energy, and geofences. The one thing it cannot answer is the question a regulator asks first: "what is the ground risk if this aircraft comes down here?" Adding iGRC computation moves bvlos-sim from an engineering feasibility tool into a regulatory pre-assessment tool — by far the most compelling reason a professional BVLOS operator would adopt it.
Background: the SORA iGRC table¶
The intrinsic GRC is a lookup on two axes:
- Aircraft characteristic dimension: ≤1 m, ≤3 m, ≤8 m, >8 m. This bands the maximum kinetic energy at impact.
- Population density beneath the operational volume: controlled ground area, <5 ppl/km², <50, <500, <5 000, <50 000, >50 000 (gatherings).
The cell value is the iGRC. Higher dimension and higher population both raise the class. Values above 7 are reported but annotated as outside the standard specific-category envelope.
Scope¶
New population-density grid asset¶
A new asset type, parallel to the terrain grid, supplying population density per grid cell in people per km²:
# population_grid.v1
origin_lat: 47.04
origin_lon: 8.29
step_lat_deg: 0.001
step_lon_deg: 0.001
density_ppl_km2:
- [12.0, 12.0, 340.0, ...] # one row per latitude step, south to north
- ...
Referenced from the mission:
New vehicle field¶
# schemas/vehicle.py — VehicleProfile
characteristic_dimension_m: float | None # max span/diameter; required for iGRC
When omitted, iGRC is not computed and a POPULATION_DENSITY_DIMENSION_MISSING
advisory warning is emitted (consistent with other provider-dependent features).
Computation¶
- For each route leg, sample the population-density grid at the leg's sampled midpoints (reusing the sub-segment sampling already used for wind).
- Take the maximum density encountered along the operational volume of each leg (a buffer around the route is configurable; default uses the leg path only).
- Map (characteristic_dimension_m, max_density) → iGRC via the SORA table.
- The mission iGRC is the maximum iGRC across all legs.
Output integration¶
- New
--format ground-riskforestimate: a Markdown table of per-leg iGRC, population density, and the governing waypoint, plus the mission-level iGRC. --format checklistgains a Ground risk class row showing the mission iGRC.--format jsonresult envelope gains aground_riskblock:--format geojsonroute legs gain anigrcproperty so the route can be colour-coded by ground risk in QGIS / Google Earth.
Fetch script¶
A new scripts/fetch_population.py that pulls population density from an open
gridded source (e.g. GHSL / WorldPop / GPW) for a bounding box and writes a
population_grid.v1 YAML. Consistent with the existing fetch-script pattern;
not part of core CI.
New schema and enum additions¶
| File | Change |
|---|---|
schemas/vehicle.py |
Add characteristic_dimension_m |
schemas/mission.py |
Add assets.population_grid_file |
estimator/core/enums.py |
Add POPULATION_DENSITY_DIMENSION_MISSING warning |
estimator/core/results.py |
Add GroundRiskEstimate + GroundRiskLegEstimate |
estimator/environment/population.py |
New population grid provider |
estimator/execution/ground_risk.py |
New iGRC computation |
adapters/assets/population_grid.py |
New loader |
adapters/ground_risk_markdown.py |
New --format ground-risk renderer |
scripts/fetch_population.py |
New fetch script |
tests/test_ground_risk.py |
New unit + integration tests |
docs/USAGE.md |
New ## Ground Risk (SORA iGRC) section |
Non-goals¶
- This ticket computes the intrinsic GRC only. Mitigations (M1/M2/M3) that lower the final GRC are out of scope and belong in a later ticket.
- Air Risk Class and SAIL determination are Ticket 095.
- The population grid is offline; live population data fetch is best-effort in the fetch script only, never in core estimation.
Composition¶
- The population grid loads exactly like the terrain grid (Ticket 032 pattern),
through
assets.population_grid_file, resolved relative to the mission file. - iGRC computation reuses the sub-segment sampling already implemented for wind.
- Output flows through the existing envelope/markdown/geojson surfaces.
Acceptance criteria¶
- A mission over a 12 ppl/km² area with a 1 m aircraft returns the correct low iGRC; the same route over a 5 000 ppl/km² cell returns a higher iGRC, matching the implemented SORA-style table.
- A vehicle with no
characteristic_dimension_memitsPOPULATION_DENSITY_DIMENSION_MISSINGand omits theground_riskblock. estimate --format ground-riskproduces a per-leg iGRC table and a mission-level iGRC.estimate --format geojsonincludes anigrcproperty on each route leg.- The SORA lookup table is unit-tested at every cell boundary.
- A mission with no
population_grid_fileis unaffected (backward compatible).