This application provides a web API to query current-year WorldPop population data by country.
The dataset currently referenced is an alpha version (R2025A) product and may change over the coming year as improvements are made.
Bondarenko M., Priyatikanto R., Tejedor-Garavito N., Zhang W., McKeen T., Cunningham A., Woods T., Hilton J., Cihan D., Nosatiuk B., Brinkhoff T., Tatem A., Sorichetta A.. Constrained estimates of 2015-2030 total number of people per grid square at a resolution of 3 arc (approximately 100m at the equator) R2025A version v1. Global Demographic Data Project - Funded by The Bill and Melinda Gates Foundation (INV-045237). WorldPop - School of Geography and Environmental Science, University of Southampton. DOI:10.5258/SOTON/WP00839
Access the interactive Swagger UI at /docs.
The API uses bearer token authentication.
- Admin users manage the system.
- Regular users can view their bearer token on the user management page.
- Use the
Authorizebutton in/docsto enter your bearer token.
GET /api/pop- Query parameters:
iso3,lat,lon - Returns:
{"pop": 12345}
- Query parameters:
GET /api/pop-radius- Query parameters:
iso3,lat,lon,radius radiusis in metres and defaults to a permitted range of1to100000- Returns:
{"pop": 12345}
- Query parameters:
POST /api/pop-shape- Query parameters:
iso3 - Form field:
geojson_filecontaining a GeoJSON document - Default file size limit: 5 MB
- Default vertex limit: 10,000
- Returns:
{"pop": 12345}
- Query parameters:
Operational limits are defined on Settings in app/config.py. They can also be
overridden with environment variables of the same name.
| Setting | Default | Purpose |
|---|---|---|
POP_RADIUS_MIN_METERS |
1 | Minimum population-radius query size |
POP_RADIUS_MAX_METERS |
100,000 | Maximum population-radius query size |
GEOJSON_MAX_SIZE_BYTES |
5,242,880 | Maximum uploaded GeoJSON size |
GEOJSON_MAX_VERTICES |
10,000 | Maximum vertices in uploaded GeoJSON |
TILE_DOWNLOAD_TIMEOUT_SECONDS |
120 | WorldPop tile download timeout |
TILE_CACHE_EXPIRY_DAYS |
365 | Cached tile lifetime |
ACCESS_TOKEN_EXPIRE_MINUTES |
10,080 | Login access-token lifetime |
The application always loads and queries the single tile for the requested ISO3 country code.
- Radius and GeoJSON queries are clipped to that tile.
- Coverage outside the country tile is ignored.
- Tile URL configuration lives in
app/config.py:dataset = "Global_2015_2030"release = "R2025A"version = "v1"year = 2025
Population queries are implemented against a single cached GeoTIFF per country code in app/services/worldpop.py.
GET /api/popperforms a single-point raster sample withrasterio.sample(...).GET /api/pop-radiusbuilds a circular geometry around the supplied centre point, then converts the geometry bounds into a raster window withrasterio.windows.from_bounds(...).POST /api/pop-shapeparses the uploaded GeoJSON file, extracts one or more geometries, and uses the same raster-window path.- Both radius and GeoJSON queries read only the raster window that overlaps the supplied geometry; the full TIFF is not loaded into memory unless the requested window spans the whole tile.
- The selected window is then masked with
rasterio.features.geometry_mask(...)so only pixels inside the supplied geometry contribute to the sum. - Point and Radius queries require the supplied centre point to fall inside the cached country tile. If the centre is outside the tile bounds,
/api/pop-radiusreturns422with a detail message. - If the requested GeoJSON geometry does not overlap the tile, the API returns a
422result for GeoJSON intersections with the detailgeojson is not inside the bounds of country {iso3}. - GeoJSON queries may extend beyond the tile bounds. In that case the request proceeds and only the portion overlapping the tile contributes to the population sum.
Key packages used in the query pipeline:
rasterio: TIFF access, windowed reads, masking, and transformsshapely: geometry construction, bounds extraction, and geometry operationspyproj: coordinate transformation for the radius buffernumpy: masked-array handling and summationhttpx: tile download on cache missSQLAlchemy: cached-tile lookup and persistence
The server caches one tile per country code:
- Identification: requested ISO3 code
- Download: on demand
- Storage:
/app/data/tiles - Expiration: cached tiles expire after
TILE_CACHE_EXPIRY_DAYSand are removed from disk and SQLite
The existing user management UI remains available at /manage-users.
Create an admin user:
docker exec -it wpopapi-app python manage_users.py create admin@example.com YourSecretPasswordRemove a user:
docker exec -it wpopapi-app python manage_users.py remove admin@example.com.
├── app/
│ ├── main.py
│ ├── config.py
│ ├── database.py
│ ├── dependencies.py
│ ├── models/
│ ├── routers/
│ ├── services/
│ └── templates/
├── data_table_setup.sql
├── docker-compose.yml
├── Dockerfile
├── manage_users.py
└── requirements.txt
- The service only loads the country tile for the requested ISO3 code.
- Multi-tile coverage is not supported.