Technical repository for an open, reproducible pipeline that turns OpenStreetMap into a dataset of sports facilities across the African continent, then maps and analyzes it.
📄 Preprint: Construire les données manquantes à l'échelle continentale : une méthode reproductible à partir d'OpenStreetMap — Benoît Prieur (2026), Zenodo. https://doi.org/10.5281/zenodo.20738614
At continental scale, few domains have an open, homogeneous and comparable dataset. There is none for African sports infrastructure, so this repo builds one from OSM (imperfect, non-representative, but open and re-runnable) and publishes the analysis as a static site.
- 🌍 Site: https://thepriben.github.io/sports-facilities-africa-osm-2026/
- 📄 Preprint: https://doi.org/10.5281/zenodo.20738614
- 🗓️ Data snapshot: Geofabrik Africa extract; see
SNAPSHOT_DATEinscripts/common.py
Dataset at a glance (snapshot 2026-06-15): 123,936 geolocated sports sites · 194 distinct sport tags · 61,679 football pitches. Figures are regenerated by the pipeline (see
data/sport_counts.csvanddocs/data/sport_counts.json).
Geofabrik africa-latest.osm.pbf
│ osmium tags-filter (leisure=pitch | leisure=stadium | building=stadium)
▼
filtered.osm.pbf
│ osmium export -f geojsonseq → shapely (geometry → WKT)
▼
data/sports_facilities.csv (geometry WKT, sport) ← raw, kept intact
│ explode ";" + normalize tags (scripts/common.py)
├─► data/sport_counts.csv (sport, count)
├─► docs/data/sport_counts.json (top N + totals, for the site)
└─► docs/maps/*.html (Folium maps)
The first stage (download + osmium) is encapsulated in
scripts/refresh_from_geofabrik.py. Because the
continental extract is ~8 GB, the script downloads it to a temp folder, filters it,
and deletes the .pbf afterwards (use --keep-pbf to keep it).
.
├── data/
│ ├── sports_facilities.csv # raw extract: columns (geometry WKT, sport)
│ └── sport_counts.csv # cleaned per-sport counts (generated)
├── scripts/
│ ├── common.py # paths, SNAPSHOT_DATE, sport normalization, extract_point()
│ ├── refresh_from_geofabrik.py # OSM → data/sports_facilities.csv
│ ├── build_counts.py # counts CSV + docs/data/sport_counts.json
│ └── make_map.py # Folium maps: heatmap | sport | british
├── docs/ # GitHub Pages site (served from /docs)
│ ├── index.html
│ ├── assets/{css,js,img}/
│ ├── data/sport_counts.json
│ ├── maps/*.html
│ └── .nojekyll
├── preprint/ # French article (source .md + figures); published on Zenodo (DOI above)
├── images/ # static figures
├── requirements.txt
└── LICENSE
data/sports_facilities.csv contains one row per OSM feature carrying a sport=* tag:
| column | description |
|---|---|
geometry |
WKT (POINT, LINESTRING, POLYGON, …) in WGS84 |
sport |
raw OSM sport value (may be multi-valued: a;b) |
Multi-valued tags are split on ;; values are lowercased, trimmed, and a small alias
map (SPORT_ALIASES in scripts/common.py) fixes frequent typos before counting.
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt # pandas, folium, shapely
# requires the `osmium` CLI (osmium-tool) on PATH
# 1. Rebuild the dataset from OpenStreetMap (downloads ~8 GB, then discards it)
python scripts/refresh_from_geofabrik.py
# 2. Counts + JSON for the site
python scripts/build_counts.py
# 3. Maps (written to docs/maps/)
python scripts/make_map.py heatmap
python scripts/make_map.py sport --sport netball --color "#1d3557" --name netball
python scripts/make_map.py britishrefresh_from_geofabrik.py always pulls africa-latest. To pin a specific date or to
build a time series, pass --url with a dated Geofabrik extract, bump SNAPSHOT_DATE
in scripts/common.py, re-run steps 2–3, and commit the regenerated data/ and
docs/. Comparing snapshots is the whole point: it turns a single measurement into a
trend.
GitHub Pages, source = main branch, /docs folder. The site is fully static
(vanilla HTML/CSS/JS + Chart.js via CDN + embedded Folium maps); no build step.
- Code: MIT, see
LICENSE. - Data: derived from OpenStreetMap, © OpenStreetMap contributors, under the Open Database License (ODbL).