Skip to content

Population and areas

Each H3 cell at resolution 9 has a population count and the administrative areas it belongs to. Where a country publishes small-area census data, the cell also has demographics. This page explains where the numbers come from and how to query them per cell, per area and per administrative division.

Sources

The Data API lists two population sources in GET /v1/catalog.

  • WorldPop. The population count on every cell comes from WorldPop, 2020, on a 100 m grid. We add up the grid squares in each resolution 9 cell. Every inhabited cell in every country has a count. The pop_source field on each cell reads worldpop_2020.
  • Kontur Population. A second population dataset, 2023, on H3 cells at resolution 8. We use it to choose where we sample places for the accuracy comparison. It is not returned by any endpoint on this page. See Accuracy.

Demographics come from each country's official small-area census and small-area income data. A cell's rates are the population-weighted share of the source areas that overlap it. A cell with no census coverage has a population count and a null demographics block. We never write a zero in place of a missing value.

Census coverage differs by country. Within Great Britain it comes from three publishers: ONS Census 2021 for England and Wales, NRS Census 2022 for Scotland, and NISRA Census 2021 for Northern Ireland. Read census_source on each cell rather than assuming one. Scotland and Northern Ireland publish no small-area income series, so their cells carry a demographics block with null income figures. A null means the publisher has no figure for that cell. It never means zero.

Query population

GET /v1/h3/population returns one row per cell. Send exactly one of these.

  • bbox=south,west,north,east returns every cell whose centre is inside the box. Add min_population=1 to drop empty cells. We recommend this for a viewport heatmap.
  • cells=<csv> returns the cells you list. A single id is a list of one. A cell with no data is left out of the result. There is no 404. The ids can be resolution 6 to 9, mixed, with the case, overlap and area rules of Total an area. A coarser cell comes back as one row: its counts are added up from the resolution 9 cells inside it, its rates are population-weighted, and its breadcrumb keeps only the areas every one of those cells sits in. resolution is null when the ids mix resolutions.
  • division=<id> returns every resolution 9 cell inside an administrative area. The id comes from GET /v1/divisions. See Administrative areas.

bash

curl -H "Authorization: Bearer $POI_API_KEY" \
  "$POI_BASE/v1/h3/population?country=GB&bbox=51.50,-0.15,51.55,-0.10&min_population=1"

bash

curl -H "Authorization: Bearer $POI_API_KEY" \
  "$POI_BASE/v1/h3/population?country=GB&division=R51800"

The second request returns the 52 cells of the City of London. Each cell carries population, pop_source, a breadcrumb of the areas it belongs to, from country down to district, and a demographics block where the census covers it.

Every response has the same shape: country, resolution, count, limit, truncated, generated_at and cells. limit defaults to 100 and the maximum is 10,000. Add fields=slim to cut each row to h3 and population.

Full parameter and response shapes are in the API reference.

Total an area

POST /v1/h3/population/aggregate returns one summary for an area instead of one row per cell. Population, household and resident counts are added up. Every rate is the population-weighted mean. Use it for a catchment: a travel time, a drawn shape or a division all reduce to a set of cells.

Send exactly one area: cells, a list of up to 100,000 ids, or division_id. The ids can be resolution 6 to 9, mixed in one list, so the output of h3.compact_cells goes in as it is. A coarser cell counts everything in the resolution 9 cells inside it, up to 1,000,000 of those per request, and no cell may lie inside another in the list. We recommend division_id for anything the size of a county. The API resolves the division's cells for you in one call, instead of paging through GET /v1/divisions/{division_id}/cells. Add include_per_cell=true to also get one AggregatePerCell row per cell, at the resolution you sent it.

bash

curl -X POST -H "Authorization: Bearer $POI_API_KEY" \
  -H 'content-type: application/json' \
  "$POI_BASE/v1/h3/population/aggregate?country=GB" \
  -d '{"cells": ["89195da4a93ffff", "89195da4a97ffff"]}'

bash

curl -X POST -H "Authorization: Bearer $POI_API_KEY" \
  -H 'content-type: application/json' \
  "$POI_BASE/v1/h3/population/aggregate?country=GB" \
  -d '{"division_id": "R51800"}'

The summary carries matched_cells and cells_with_census, so you can see how much of the area the census covers. Each rate has its denominator in rate_denominators.

Public transport coverage

GET /v1/h3/transit-coverage returns the cells within a 15 minute walk of a public transport station. Each cell carries its population, its station and line counts and the modes that serve it. Send a bbox, cells or division, as for population.

bash

curl -H "Authorization: Bearer $POI_API_KEY" \
  "$POI_BASE/v1/h3/transit-coverage?country=GB&bbox=51.48,-0.20,51.56,-0.05&res=9"

By default only covered cells are returned. Add include_uncovered=true to get the gap as well. Populated cells that no station reaches come back with covered: false and their uncovered_pop. At res=6 or res=7, each parent cell carries the summed uncovered population beneath it. Use this for a country or region view.

limit defaults to 500. With include_uncovered=true it defaults to the maximum of 10,000. At res=7 a box around the whole of Great Britain holds more populated parent cells than that, so the response has truncated: true. Narrow the box, or use res=6, which fits in one page.

bash

curl -H "Authorization: Bearer $POI_API_KEY" \
  "$POI_BASE/v1/h3/transit-coverage?country=GB&bbox=50.0,-6.0,58.7,1.8&res=7&include_uncovered=true"

GET /v1/h3/transit-coverage/divisions returns the share of each division's population within a 15 minute walk of a station. Set admin_level to choose the tier. The default sort is best covered first.

bash

curl -H "Authorization: Bearer $POI_API_KEY" \
  "$POI_BASE/v1/h3/transit-coverage/divisions?country=GB&admin_level=6&limit=3"

The stations come from the public transport dataset. Full parameter and response shapes are in the API reference.

Administrative areas

A division is an administrative area from OpenStreetMap, such as a nation, a county or a district. Divisions form a tree. Every cell's breadcrumb lists the divisions it belongs to, from the country down.

EndpointWhat to use it for
GET /v1/divisionsThe divisions in a country. Filter by admin_level. Sort by population, cells or name.
GET /v1/divisions/{division_id}One division, with its parent, its ancestors, its children and its population.
GET /v1/divisions/{division_id}/childrenThe divisions one tier down.
GET /v1/divisions/{division_id}/cellsThe cells holding data, at res 9, 7 or 6, paged. ?compact=true: the whole area in one go.

admin_level is the OpenStreetMap level, and its meaning differs by country. In Great Britain, level 4 is the four nations and level 6 is counties and council areas.

bash

curl -H "Authorization: Bearer $POI_API_KEY" "$POI_BASE/v1/divisions?country=GB&admin_level=4"

Pass a division's cells to any endpoint that takes cells: population, public transport coverage, places or green space. For a large division, ask for res=7 or res=6, or send division_id to an aggregate endpoint instead.

bash

DIVISION=R51800
curl -H "Authorization: Bearer $POI_API_KEY" \
  "$POI_BASE/v1/divisions/$DIVISION/cells?country=GB&res=7"

To go the other way, from cells to their divisions, use POST /v1/h3/divisions. Send cells at resolution 6 to 10 and an optional admin_level. The default is the finest level any of the cell's indexed resolution 9 cells has, so every division listed for a cell sits at that one level. A cell coarser than resolution 9 can cross a boundary, so each row lists every division it touches with the share of its indexed resolution 9 cells inside, and division names the largest. A cell with no indexed resolution 9 cell, such as empty countryside, comes back with division: null.

bash

curl -X POST -H "Authorization: Bearer $POI_API_KEY" -H "Content-Type: application/json" \
  "$POI_BASE/v1/h3/divisions?country=GB" \
  -d '{"cells": ["89195da4a93ffff", "87195da4affffff"], "admin_level": 8}'

You can browse the tree on a map in the playground's Areas tool. Pick a division and it fills its cells. Full parameter and response shapes are in the API reference.