The Netherlands is one of the best-mapped countries in the world, and almost all of it is open. The hard part is knowing which source has what, and how to get it into your code without clicking through a portal. This page covers the four sources that will get you through most of the #30DayMapChallenge, with code that downloads directly.
The code on this page is shown but not run when the book is built, because it depends on outside services. Every request was tested in October 2026.
Which source for what¶
| You want | Go to | Licence |
|---|---|---|
| Amenities, shops, roads, paths, trees, anything people map | OpenStreetMap | ODbL 1.0, must credit |
| Building footprints with construction year (BAG) | PDOK | CC0 |
| Population, income, age, housing per neighbourhood | CBS Wijken en buurten, via PDOK | CC BY 4.0 |
| Elevation, at 0.5 m resolution (AHN) | PDOK | CC0 |
| Topographic base map layers (BRT, TOP10NL) | PDOK | CC BY 4.0 |
| Municipal data: trees, lamp posts, parking, events | Eindhoven open data | mostly CC0 or CC BY, per dataset |
Write every source and its licence in your map’s README, and put the credit on the map itself.
OpenStreetMap¶
OpenStreetMap (OSM) is a map of the world made by volunteers. Everything in it is a feature
with tags: key=value pairs such as amenity=cafe, highway=cycleway or natural=tree. To
find the tag for what you want, search the OSM wiki
or click on a similar object at https://
Overpass Turbo: look before you download¶
Overpass Turbo runs a query in your browser and shows the result on
a map, so you can check you are getting what you think before writing any code. The Wizard
button turns plain text like amenity=cafe in Eindhoven into a query. Export saves the result
as GeoJSON.
This is the query used to make data/eindhoven_cafes.geojson:
[out:json][timeout:60];
area["boundary"="administrative"]["admin_level"="8"]["name"="Eindhoven"]["ref:gemeentecode"="0772"]->.gem;
(
node["amenity"="cafe"](area.gem);
way["amenity"="cafe"](area.gem);
);
out center tags;Matching on the municipality code (0772) as well as the name avoids picking up anything else
called Eindhoven.
From code¶
# pip install osmnx
import osmnx as ox
trees = ox.features_from_place("Eindhoven, Netherlands", tags={"natural": "tree"})
parks = ox.features_from_place("Eindhoven, Netherlands", tags={"leisure": "park"})
# a street network as a graph, for routing and Day 24 (Network)
G = ox.graph_from_place("Eindhoven, Netherlands", network_type="bike")
nodes, edges = ox.graph_to_gdfs(G)
trees = trees.to_crs(28992)features_from_place returns a GeoDataFrame with mixed geometry types (points, lines and
polygons) and one column per tag. Filter on trees.geom_type before plotting.
# install.packages("osmdata")
library(osmdata)
library(sf)
trees <- opq("Eindhoven, Netherlands") |>
add_osm_feature(key = "natural", value = "tree") |>
osmdata_sf()
trees_pts <- st_transform(trees$osm_points, 28992)osmdata_sf() returns a list with osm_points, osm_lines, osm_polygons and the multi
versions. Pick the one that matches what you asked for.
PDOK: Dutch government geodata¶
PDOK publishes the national base registrations as web services that you
can read straight into GeoPandas or sf. The modern ones are OGC API Features: a URL that
returns GeoJSON, with filters as query parameters. Browse what is available in the
PDOK dataset list.
CBS neighbourhoods with statistics¶
CBS publishes the boundaries of every municipality (gemeente), district (wijk) and neighbourhood (buurt), with more than a hundred statistics attached: population, households, age groups, income, housing, distance to the nearest school or park. It is the best source for choropleth maps of the Netherlands.
import geopandas as gpd
url = ("https://api.pdok.nl/cbs/wijken-en-buurten-2024/ogc/v1/collections/buurten/items"
"?f=json&limit=1000&gemeentenaam=Eindhoven")
buurten = gpd.read_file(url).to_crs(28992)
len(buurten) # 116 neighbourhoods
buurten[["buurtnaam", "aantal_inwoners", "bevolkingsdichtheid_inwoners_per_km2"]]library(sf)
url <- paste0("https://api.pdok.nl/cbs/wijken-en-buurten-2024/ogc/v1/collections/buurten/items",
"?f=json&limit=1000&gemeentenaam=Eindhoven")
buurten <- st_read(url) |> st_transform(28992)Swap buurten for wijken or gemeenten, and the year in the URL for another edition
(2023, 2024 and 2025 are available). Any column can be used as a filter in the same way as
gemeentenaam.
BAG buildings¶
The BAG (Basisregistratie Adressen en Gebouwen) has every building (pand) in the country,
with its footprint, construction year (bouwjaar) and status. Colouring buildings by
construction year is a classic map, and a good one for Day 3, Polygons, or Day 6, Vintage.
bbox = "5.470,51.435,5.490,51.448" # lon/lat: min x, min y, max x, max y
url = ("https://api.pdok.nl/kadaster/bag/ogc/v2/collections/pand/items"
f"?f=json&limit=1000&bbox={bbox}")
panden = gpd.read_file(url).to_crs(28992)One request returns at most 1000 features. For a larger area, follow the next link in the
response, or download the whole BAG as a file from the
BAG dataset page
(several GB for the country).
AHN elevation¶
The AHN (Actueel Hoogtebestand Nederland) is a laser-scanned elevation model of the whole
country at 0.5 m resolution. dtm_05m is the ground surface; dsm_05m includes buildings and
trees. It is served as a WCS (a service for rasters), and you ask for a box in RD coordinates:
import requests
url = ("https://service.pdok.nl/rws/ahn/wcs/v1_0?service=WCS&version=2.0.1&request=GetCoverage"
"&coverageId=dtm_05m&subset=x(161000,161500)&subset=y(383500,384000)&format=image/tiff")
with open("ahn_dtm.tif", "wb") as f:
f.write(requests.get(url, timeout=60).content)That is a 500 × 500 m tile of central Eindhoven, 1000 × 1000 pixels, about 1.5 MB. Keep requests small: a whole city at 0.5 m is gigabytes. See Grids, rasters and web maps for how to plot it.
Eindhoven open data¶
The municipality publishes its own datasets at https://
bomen = gpd.read_file("https://data.eindhoven.nl/api/explore/v2.1/catalog/datasets/"
"bomen/exports/geojson").to_crs(28992)For another dataset, replace bomen with the identifier in that dataset’s URL. This is a large
download (well over a hundred thousand trees), so save it locally after the first time. Check the
licence on each dataset page; they differ.
Habits that save time¶
Download once, then read from disk. Save what you downloaded (
gdf.to_file("data/trees.gpkg")) and read the local copy while you work on the styling. Services are slow, rate-limited, and occasionally down on the day you need them.Record where it came from next to the file: source, URL or query, download date, licence.
data/README.mdis a worked example. Your future self, and your supervisor, will ask.Keep the repository small. Commit data under a few MB; for anything bigger, commit the script that downloads it.
Check the date of the data, not only the download. CBS figures for 2024 may describe 1 January 2024; OSM changes every minute.