Method

How The Century of Africa was made

The story is four chapters of maps. This is the paperwork behind them: every source and what it actually measures, the judgment calls and why they went the way they did, the things that were tried and thrown away, and how the modelled numbers hold up against independent data.

1. The question, and why four chapters

The UN expects Africa's population to roughly triple this century while every other region levels off or falls. The interesting thing about that number is not its size, it is how little of it is negotiable: it is mostly arithmetic on an age pyramid that already exists. The piece argues one thing, at four scales, because a claim that only works at one scale is not really a finding. The arithmetic is locked. The systems are not.

The order is deliberate. Each chapter is a smaller frame than the last, and the last one is the only place the reader can check the story against something you could walk on. The argument narrows the same way: a number nobody can move, then the places it lands, then the systems that have to meet it, then one city where you can measure whether they did.

2. Every source, and what it measures

UN World Population Prospects 2024

Country population by year, medium variant, plus the low and high variants drawn as the band on the regional chart. This is an estimate built from national statistics, censuses and vital registration, projected forward under fertility and mortality assumptions. It is a central path, not a prediction, and the overtaking years the story quotes move if you change the variant.

UN World Urbanization Prospects 2025, DEGURBA cities

Agglomeration population 1975 to 2050. What it measures is a city defined by a consistent settlement rule rather than by whatever each country happens to call a municipality, which is why the figures sometimes differ from a national statistics office. The 2100 city figures are not the UN's: they come from Hoornweg and Pope (2017), sit well above any UN city horizon, and are drawn hollow everywhere they appear.

JRC Global Human Settlement Layer, R2023A

Two grids, both at 3 arcseconds, both tile R10_C20. GHS-BUILT-S gives the share of each cell covered by building surface, derived from the Landsat and Sentinel archives. GHS-POP distributes census counts across those cells. The important thing about both is that they are model output: nobody measured Kinshasa's rooftops in 1975, an algorithm inferred them from imagery. The 2030 epoch is the JRC's own projection, not an observation.

Copernicus DEM GLO-30

Ground elevation at 30 metres, from radar. Slope is derived from it here, not supplied by it. Licensed free and open, with the attribution notice the licence requires carried in the story footer.

OpenStreetMap

The river, the roads and streets, the commune boundaries, the real alignments of built railways, the Matadi corridor, and the road network the gravity model is tested against. OSM measures what mappers have mapped. In Kinshasa that is good and getting better, and it is a present-day snapshot with no history, which matters for one figure and is flagged where it does.

Harmonized DMSP and VIIRS nighttime lights, 2020

Li, Zhou, Zhao and Zhao's harmonized series, which stitches the old DMSP sensor to the newer VIIRS one so the two are comparable. It measures light leaving the ground at night. It is a proxy for electrification and activity, not a measurement of either.

World Bank Open Data

Access to electricity and to basic drinking water, as a share of population. The snapshot map is the most recent reported year per country, so it mixes observations from different years, and its "people without" totals multiply that share by the UN's 2025 population.

The chart is a different cut of the same indicators: the full series from 2000 to 2022, counting only the countries reporting in a given year and pairing each year's share against UN population for that same year. Counting a consistent set per year is what stops a country reporting late from putting a step in either curve.

Natural Earth

Country outlines and the existing rail and road layer drawn in chapter three. It is a cartographic basemap at 1:10 million, generalised for drawing. That generalisation is fine for a backdrop and, as section 4 records, not fine for measurement.

3. The decisions

A 20 per cent built threshold

GHS-BUILT-S is continuous: every cell has a built fraction between zero and one. Drawing it that way produces a soft grey smear with no edge, and the whole point of chapter four is the edge, because the reader is being shown where the city stopped in each decade. Twenty per cent is the point where the footprint reads as a city rather than as a haze, and it is applied identically to every epoch so the comparison between decades is consistent even though the absolute area depends on the cut.

Block-averaging the 30 metre DEM

The elevation model is roughly three times finer than the built-up grid. Deriving slope at 30 metres and then asking which 92 metre built cells are steep mixes two resolutions and produces detail that is not really there. The DEM is averaged down to the built grid first, so slope and built-up are answering at the same scale. This also took the polygonize step from over half an hour to about ninety seconds, and cut the layer from 1.33 MB to 0.31 MB, which is a real consideration on a page that ships its data.

Classed colour, not a gradient

The chapter 1 maps were first drawn with linear colour ramps stretched across a global range, and the result was a continent of one colour. Half of Africa's median ages fall between 15 and 22, which was a single segment of that ramp, so Niger at 15.6 and Botswana at 26 came out the same orange. The map looked like data and carried none.

They are now classed rather than interpolated, on Fisher-Jenks breaks computed from the African values themselves, with extra classes added above where the rest of the world needs somewhere to sit. Flat classes are easier to compare than a gradient, and the key can name what each one means instead of showing a bar with two numbers on it. 17_class_breaks.py recomputes the breaks and reports how many countries land in each class, so the choice is checkable rather than a matter of taste.

Growth keeps a hard boundary at one times, because shrinking and growing are different kinds of country rather than two ends of a scale, and the top age classes exist so that Japan at 49.8 still reads as a different world from Tunisia at 32.9. One population scale serves 2025, 2050 and 2100 rather than three fitted ones, so the reader watches countries climb between steps: four African countries are in the top class by 2100 and none are today.

Two slope classes, not five

Steep and steepest, at 10 and 15 degrees. Five classes would look more analytical and would be false precision against a 92 metre surface. Two classes is what the data can carry.

Rings measured from Gombe

The expansion analysis measures distance outward from 15.313E, 4.305S, the old river front. Kinshasa did not grow from a geometric centroid, it grew away from the river, and a centroid would have put the origin in ground that was empty in 1975. Using the historical core means the rings answer the question actually being asked: how far from the original city did each decade build?

Cumulative epoch masks, drawn newest first

Each growth ring is the full built footprint at that epoch, not just the increment. Drawing increments leaves gaps wherever the model revised a cell between epochs, and the rings stop nesting. Cumulative masks nest by construction; painting them newest first means each older, smaller footprint lands on top, and the reader sees clean vintage bands.

Why the Tokyo and New York circles came out

Chapter two originally drew Western cities as comparison circles. On a frame that has to hold the whole continent they sat at the edge of the viewport, small, and far from anything to compare against. They made the comparison harder rather than easier. The comparison is now made in the copy, as numbers, where a reader can actually hold both figures at once.

Why the 2100 circles are hollow

Every other circle on that map is a UN series. The 2100 figures are one academic team's projection, from a different method, well above the UN's own city horizons. They are worth showing and they are not the same kind of object, so they are drawn as rings rather than discs, and labelled.

Why the whole thing is GeoJSON with no backend

About 4 MB of geometry, committed, split into a critical wave and a deferred one. No tile server, no API, nothing to keep running or pay for. It loads from any static host and it will still work in five years, which is a longer guarantee than most map stacks offer.

4. Tried and rejected

The cumulative slope statistic

The slope chapter began with a hypothesis: as Kinshasa ran out of flat land it would be forced uphill. The first statistic computed for it was the share of the whole built footprint on steep ground, epoch by epoch. It moved from 20.7 per cent to 25.5 per cent across fifty years, which says almost nothing, because each epoch is dominated by the ground already built in the one before. Recomputing it per increment, on only the cells each decade added, produced the actual result: the 1975 city sits at a median 2 degrees, and every later decade came in steeper, the 1990 expansion at double the old city's slope. The copy was then rewritten to follow the number rather than the hypothesis. Both statistics were correct; only one was informative.

Natural Earth for the corridor coverage test

The obvious way to test the gravity model was against the road layer already loaded on the page. It was tried on a single link first. Natural Earth's 1:10 million network reports Lagos to Onitsha as 22 per cent served; OpenStreetMap reports 75 per cent. The generalised basemap would have produced a confidently wrong finding and there would have been nothing in the output to reveal it. The test uses OSM.

A slope layer derived to the camera's bounding box

The terrain derivation was first run over exactly the area the map shows. The layer then drew a hard rectangle across the map wherever the data simply stopped. The derivation window is now wider than the camera, which costs nothing and looks like terrain instead of like a raster.

Five decimal places of GeoJSON

Coordinates are written at three decimals, roughly 110 metres, which is finer than any layer on the page resolves. The extra digits were bytes spent to encode precision that was never in the source.

5. Validation

Everything in chapters one to three is projection or model output, and chapter four is model output too. So two independent checks were run, both as scripts in the pipeline, both writing their results to committed JSON that the page reads from.

GHS-POP against the UN, inside real boundaries

The JRC infers population from satellite imagery. The UN estimates the same cities from national statistics. Two methods with almost nothing in common, so where they agree that is evidence, and where they disagree that is worth naming.

14_validate.py sums the GHS-POP grid inside Kinshasa's 3,516 square kilometres of OpenStreetMap commune boundaries, which is close to the footprint the UN's city figure describes, and compares the two.

Kinshasa, GHS-POP summed inside commune boundaries against UN WUP 2025
YearGHS-POPUN WUPRatio
19751.17M0.98M1.19
202513.24M10.90M1.21

The grid runs about a fifth above the UN in both years. That is the useful part: the offset is stable across fifty years, so the growth the story describes is not an artefact of the grid drifting. A source that agreed in 1975 and diverged by 2025 would have undermined the whole chapter. This one does not, and the story now says on the page that its Kinshasa totals carry about that much slack.

Brazzaville was intended as a second case and could not be run: OSM has no admin_level 7 boundaries north of the Pool, so there was nothing to sum inside. It is reported here rather than quietly dropped.

The same script re-derives the density figure the story quotes. Inside the communes, the 99th percentile cell holds about 133,000 people per square kilometre and the peak is roughly 298,000, with 7,249 cells at or above 60,000. The story's "over 60,000 per square kilometre" is a conservative reading of its own data.

Share against absolute number

The story claims a problem of rate, and a snapshot cannot support a claim about rate. So the services indicators were rebuilt as series.

Both say the same thing independently. Electricity access across Africa went from 37.7% to 58.5% between 2000 and 2022 while the number of people without it rose from 510 million to 600 million. Water access went from 52.8% to 70.1% while the number without rose from 386 million to 429 million. Two indicators, separately collected, both showing real and large progress losing to the population it is chasing.

This is the difference between a piece that asserts a gap and one that measures whether the gap is closing. It is also why the copy credits the building rather than dismissing it, and why the map carries a layer for electrification gained since 2000: Kenya went from 15% to 76% and Rwanda from 6% to 51% in the same window, which is the evidence that the number is a choice and not a fate.

Drawing the corridors on land

A gravity model pairs cities. It has no opinion about what lies between them, so drawn as straight great circles, twelve of the thirty-six links ran across open water: Luanda to Lagos was 98.5 per cent Atlantic. Five of the curated Trans-African Highway lines did the same thing along the West African coast.

That was not only an ugly map. The coverage test below samples points along each link and asks whether a road passes nearby, so along an ocean line it was measuring the sea.

18_land_routes.py reroutes any line crossing water along a land-constrained shortest path: a 0.2 degree grid over Africa, cells kept where the Natural Earth land polygons reach, eight-way search with great-circle edge costs, simplified afterwards at 0.08 degrees so the grid staircase smooths without cutting corners across bays. The worst remaining overlap with water is 3.1 per cent, which is a coastal route hugging the shore at 22 kilometre resolution.

The scores are still calculated on straight-line distance, which is the textbook formulation and the thing the model is actually claiming. What the routing adds is the length of the trip: Lagos to Luanda is 2,046 km apart and 2,421 km by road around the Gulf, a fifth further and across five borders. Every link carries that ratio, and the corridor popups show it.

The routes are schematic. They know nothing of terrain, borders, gradient or existing pavement, and they are not engineering alignments. They promise only that a reader tracing one with a finger stays on the continent.

The gravity model against the road network

Chapter three runs population times population over distance squared across the thirty largest African cities of 2050. That is a textbook toy, and calling it a sketch is honest but is not a defence. So it was tested. 15_corridor_coverage.py samples 40 points along each of the 36 modelled links and asks whether a mapped motorway, trunk or primary road passes within about 24 kilometres. It runs after the routing above, so it measures the road beside the line that is actually drawn.

A model whose strongest output is a corridor that has already been built is not drawing noise, and that is what licenses reading the rest of it. Which makes the low scores the finding rather than the failure: Dar es Salaam to Kampala at 42 per cent, Luanda to Kinshasa at 48, Cairo to Khartoum at 48, Addis Ababa to Khartoum and Kinshasa to Yaoundé at 52, Dar es Salaam to Nairobi at 62. They are interior and cross-border links, not coastal ones.

These figures replace an earlier set that was wrong. Measured along the old straight-line geometry, Luanda to Lagos came back 8 per cent served, which the story quoted as its sharpest example of unmet demand. It was an artefact of sampling the Atlantic. Along the coastal road the same link is 88 per cent served, and the honest thing about it is the detour, not the absence. The threshold moved too: the query fetched roads within 0.22 degrees while the test counted them at 0.25, so roads in that band satisfied the test but were never retrieved. Both numbers are 0.22 now.

The test measures whether a route exists, not whether it is paved, open, safe or big enough. Every Overpass response is cached under the pipeline's raw directory, keyed on the query itself so that moving a corridor cannot be answered with the roads around the line it used to follow.

One city, or a pattern?

The sharpest number in the story is Kinshasa's street supply falling from about nine metres per resident to one. On its own that is a single case, and the fair objection is that Kinshasa is exceptional: a capital that grew through state collapse and two wars.

16_city_streets.py runs the identical measurement over nine cities, chosen to span the range rather than to flatter the argument. Cairo and Accra are in it precisely because they were the most likely to contradict it. Every city with a UN 1975 baseline has less street per resident today than it had then. Kinshasa comes last at 1.18 metres against a median of 1.82, so it is the sharp end of a real pattern rather than a typical case, and Cairo fell least, which is what a state that builds looks like.

Two things make that result worth trusting. Kinshasa's figure here is derived from a different window than the chapter's own, and both land on 1.18. And the check caught a real fault on the way: Addis Ababa first came back at 0.12 metres per resident, an order of magnitude below everything else, because its measurement window ran off the edge of its GHSL tile. Asking for a window past a tile edge does not fail. It returns the rows that exist while the transform still describes the window requested, so the footprint silently shifts by the missing margin. The script now mosaics every tile a window touches and refuses to run on partial coverage.

The numbers on the page are asserted, not typed

Every figure quoted in the story is checked against the shipped JSON and GeoJSON by a test script before it goes in the HTML, so the copy cannot drift away from the data during an edit. Layout, contrast, reduced-motion behaviour and small-viewport rendering are checked by an audit script driving a headless browser at several viewport sizes.

6. Known limits

Back to The Century of Africa