S3: Health Facility Accessibility — Clermont-Ferrand
Advanced CLI + Map
Capabilities: filter isochrone classify_by_ring
Use Case
A health planner analyzes healthcare coverage in Clermont-Ferrand with four concentric walking isochrones computed on the real BD TOPO road network — 500 m (~5 min), 750 m (~7.5 min), 1 km (~10 min), 1.5 km (~15 min) from every categorie == 'Santé' POI. The four rings are emitted in one multi-source Dijkstra pass (cost_budgets parameter, metric CRS EPSG:2154), then classify_by_ring tags every building with the smallest ring that contains it. Palette: green (inside the 500 m zone, served) → yellow (500-750 m, +2.5 min) → orange (750 m-1 km, +5 min) → red (1-1.5 km, +10 min) → dark red (beyond 1.5 km).
IGN BD TOPO V3 + OSM Data
| Layer | Content | Features | Source |
|---|---|---|---|
routes | Road segments (type, lanes, width, speed) | 2,272 | data.geopf.fr — BDTOPO_V3:troncon_de_route |
equipements | OSM healthcare POIs (Overpass) — exhaustive: pharmacies, GPs and specialists, hospitals, clinics, dentists, labs, physios, nursing homes, social care, veterinaries... | 223 | overpass-api.de — `amenity~hospital |
batiments | Buildings to assign | ~77,000 | data.geopf.fr — BDTOPO_V3:batiment |
Why OSM instead of BD TOPO equipements?
BD TOPO V3 only lists 47 "Santé" establishments in the Clermont bbox — hospitals, clinics, nursing homes, thermal baths. Individual GPs, pharmacies, dentists, labs are absent. OSM surfaces 223 health POIs — Overpass cache generated by scripts/fetch_health_pois_osm.py and committed to examples/datasets/clermont_ferrand_health_osm.geojson.
python examples/prepare_playground_data.py --city clermont-ferrand
gispulse info examples/datasets/clermont_ferrand_bdtopo.gpkgPipeline (3 steps)
equipements (223 OSM POIs) ──► filter categorie == 'Santé'
│ → 223 health POIs (pharmacies, GPs, labs, nursing homes, hospitals...)
│
▼
isochrone cost_budgets=[500, 750, 1000, 1500]
│ Multi-source Dijkstra, EPSG:2154, ONE pass → 4 rings
│ → GeoDataFrame of 4 polygons { cost_budget, geometry }
│
batiments ─────────────────► classify_by_ring ref_layers=[isochrone_rings]
│ → access_ring (500|750|1000|1500|99999)
│ → access_class (1..5)
│ → access_color (green → dark red)
▼
ALL buildings mapped by the ring that contains themRules
{
"version": 2,
"name": "health_accessibility",
"ref_layers": {
"routes": "routes",
"batiments": "batiments"
},
"steps": [
{
"id": "filter_sante",
"type": "capability",
"capability": "filter",
"params": { "expression": "categorie == 'Santé'" }
},
{
"id": "isochrone_rings",
"type": "capability",
"capability": "isochrone",
"params": {
"ref_layer": "routes",
"cost_budgets": [500, 750, 1000, 1500],
"crs_meters": "EPSG:2154",
"edge_buffer_m": 200,
"dissolve": true
},
"input": "filter_sante"
},
{
"id": "classify_by_ring",
"type": "capability",
"capability": "classify_by_ring",
"params": {
"ref_layers": ["isochrone_rings"],
"ring_field": "cost_budget",
"class_col": "access_class",
"color_col": "access_color",
"value_col": "access_ring",
"palette": ["#1a9850", "#fee08b", "#fdae61", "#f46d43", "#a50026"],
"use_centroid": true,
"ring_simplify_tolerance": 10.0
},
"input": "batiments"
}
]
}Multi-budget mode (cost_budgets)
A single Dijkstra pass with cutoff = max(cost_budgets), then per-budget filtering of reachable nodes to buffer+dissolve each. N-ring cost ≈ single-ring cost. Every ring is a filled zone (not a hollow annulus) — classify_by_ring picks the innermost ring that contains each feature. The playground assigns one colour per budget (green → red) and reverses draw order so the smallest ring sits on top.
Perf — use_centroid + ring_simplify_tolerance
On ~50 000 buildings × 4 BD TOPO isochrones the strict polygon-vs-polygon sjoin takes > 100 s: every 1.5 km ring is a unary-union polygon with tens of thousands of vertices, and intersects pays the boundary cost against each building footprint. Two cumulative knobs:
use_centroid: true— switches to awithinquery on each building's centroid. Footprints are tiny (~10-30 m on a side), so class assignment is preserved except for the rare buildings that genuinely straddle a ring boundary.ring_simplify_tolerance: 10.0— simplifies the rings to 10 m before the join, dropping most vertices without visibly shifting boundaries at city scale.
Typical effect: 138 s → ~3 s on the S3 scenario without changing the displayed classes.
Metric CRS — why EPSG:2154
Source data is in EPSG:4326 (degrees). A cost_budget: 500 without reprojection would be interpreted in degrees (~55 km). The crs_meters: "EPSG:2154" parameter reprojects the network to Lambert-93 for the routing pass, so budgets are expressed in real meters; the result is then reprojected back to the source CRS.
Download
Execution
gispulse run examples/datasets/clermont_ferrand_bdtopo.gpkg \
--layer equipements \
--rules playground/scenario-3-rules.json \
-o output/health_coverage.gpkg
gispulse serve output/health_coverage.gpkgExpected result
Output schema
| Column | Type | Description |
|---|---|---|
access_ring | float | cost_budget value of the innermost ring that contains the building, or 99999 outside every ring |
access_class | int | Ring index 1-5 (1 = inside the 500 m zone, 5 = beyond 1.5 km) |
access_color | string | Hex from the RdYlGn-reversed palette (green → dark red) |
Interactive playground
Live 3-step pipeline (requires the demo backend).
Step by step:
filter_sante(orange) — keeps features wherecategorie == 'Santé'(hospitals, clinics, pharmacies, practices...).isochrone_rings(purple → blue gradient) — multi-source Dijkstra overroutesin metric CRS EPSG:2154, one pass for the 4 budgets[500, 750, 1000, 1500]. Output: 4 stacked dissolved polygons, one per budget.classify_by_ring(multi-colour) — for every building, picks the smallestcost_budgetamong intersecting rings, maps it to a class index 1..5 (5 = outside every ring), and writes the palette colour.
Interactions:
- Popup on health-facility markers (category, nature).
- Isochrone polygons follow the real road network (not a circular buffer).
- Click the map to add a new source: the isochrones are recomputed live.
Try it live
GET/capabilitiesGET/datasetsGoing further
- S4: Road Network + Urban Setback — 50 m buffer and DML trigger in Clermont-Ferrand
- Network capabilities — isochrone, shortest_path, connectivity_check
- Cross-layer references —
--ref-sourcesyntax