# Bodemloket (soil information desk)

Where has the soil been surveyed, where is it contaminated and what is being done? The Bodemloket shows what provinces, environmental services and large municipalities supply about their sites: the stage of investigation, the verdict on the contamination and whether it was remediated. On 15 October 2026 it covered 241,945 sites.

- Publisher: Rijkswaterstaat
- Area: Netherlands · Finest detail: site (plot or polygon)
- Period: momentopname (geen datum per locatie) · Refreshed: monthly
- Access: Open · Licence: Niet vermeld (openbaar te raadplegen)
- Join keys: locatiecode bevoegd gezag, RD-coördinaten (EPSG:28992)
- Status: grijs · 241945 locaties met bodeminformatie
- URL: https://www.bodemloket.nl

## What can you do with it?
- **What lies beneath the new housing estate?.** Request all sites within 250 metres of an address. For each site you see the stage of investigation and the verdict, for example 'Potentieel Ernstig' (potentially serious) or 'voldoende gesaneerd' (sufficiently remediated).
- **How much work is left?.** Count per municipality or environmental service the sites where investigation or remediation is still needed (VERVOLG_WBB). On 15 October 2026, 41,143 sites were marked 'Uitvoeren historisch onderzoek' (historical investigation to do).
- **Mapping urgent sites.** Filter on verdicts containing 'spoed' or 'urgent' and lay them over the neighbourhood boundaries. Do people live on or next to them?
- **Which municipalities show nothing?.** The Bevoegd_gezag layer states per authority whether its data is in the Bodemloket or only on its own website. On 15 October 2026, 'only via own website' applied to 31 of the 41 authorities, including Utrecht, Groningen, Eindhoven and Nijmegen.

## How to process it
1. **Look at the layers and fields.** The data sits in a Rijkswaterstaat ArcGIS service with three layers: WBB_locaties (the sites), Beschikbaarheid_gegevens and Bevoegd_gezag. At most 2,000 features per request, output as JSON, GeoJSON or PBF.
   `https://gis.gdngeoservices.nl/standalone/rest/services/blk_gdn/lks_blk_rd_v1/MapServer?f=json`
2. **Sites around a point.** Give a point in RD coordinates and a radius. Around this point in Helmond (Brouwhuis-Oost neighbourhood) a 250 metre radius returned 29 sites.
   `https://gis.gdngeoservices.nl/standalone/rest/services/blk_gdn/lks_blk_rd_v1/MapServer/0/query?geometry=177060%2C386040&geometryType=esriGeometryPoint&inSR=28992&distance=250&units=esriSRUnit_Meter&spatialRel=esriSpatialRelIntersects&outFields=LOCATIECODE_BEVOEGD_GEZAG%2CVERVOLG_WBB%2CSTATUS_OORD&returnGeometry=false&f=json`
3. **Counting on the server.** With outStatistics and groupByFieldsForStatistics the service counts for you, for example per administrator (ADMINISTRATOR_NM). That way you don't have to fetch 241,945 sites.
   `https://gis.gdngeoservices.nl/standalone/rest/services/blk_gdn/lks_blk_rd_v1/MapServer/0/query?where=1%3D1&outStatistics=%5B%7B%22statisticType%22%3A%22count%22%2C%22onStatisticField%22%3A%22WBB_DOSSIER_DBK%22%2C%22outStatisticFieldName%22%3A%22n%22%7D%5D&groupByFieldsForStatistics=ADMINISTRATOR_NM&f=json`
4. **Clean up the verdicts.** STATUS_OORD is free text from different systems: 'Potentieel Ernstig', 'potentieel spoed', 'Urgent, start san voor 2015'. Build your own short classification before counting.

## Interesting combinations
- How many homes stand on a seriously contaminated site? → [pdok-bag](https://www.opendataindex.nl/en/bron/pdok-bag.md) via locatievlak (RD). Intersect the BAG residential units with the site polygons and count per verdict. Both are in RD.
- Which neighbourhoods have the most open cases? → [pdok-wijkenbuurten](https://www.opendataindex.nl/en/bron/pdok-wijkenbuurten.md) via locatievlak (RD). Count per neighbourhood the sites where VERVOLG_WBB still names an action. Note that big cities are missing.

## Similar sources
- [DINOloket (TNO)](https://www.dinoloket.nl): The deeper subsurface (boreholes, groundwater, geology), not contamination per plot.
- [Atlas Leefomgeving](https://www.atlasleefomgeving.nl): Shows soil quality next to air, noise and greenery, for residents. Less suitable for calculations.
- [data-overheid](https://www.opendataindex.nl/en/bron/data-overheid.md): Some municipalities publish their own soil database there, such as The Hague (Historisch bodembestand).

## What to watch out for
- **Big cities are missing.** Amsterdam and Rotterdam had zero sites in the service on 15 October 2026, and 31 of the 41 authorities show their data only on their own website. An empty map therefore does not mean the soil is clean.
- **No date.** Sites have no date of investigation or last change. How often an authority supplies data varies and is not stated in the service.
- **Many empty verdicts.** For 99,633 of the 241,945 sites STATUS_OORD is empty. Count them separately and say so.
- **Licence not stated.** Site and service state no open licence, only a disclaimer. For large-scale reuse, check what is allowed and always credit the source.
- **Map outages.** Around 20 and 21 August 2026 the map, and then the soil quality map layers, did not work for a while (news items on the site). Build in a retry.

## API (ArcGIS REST)

- Base: https://gis.gdngeoservices.nl/standalone/rest/services/blk_gdn
- Access: No key or token: the directory and all calls below worked anonymously. (tested)
- Formats: json, geojson, pbf, png (export, WMS)
- Size: 241945 locations in layer 0 WBB_locaties (returnCountOnly, 16 Oct 2026) (tested)
- Filtering: SQL-like where clause on the layer's fields, plus an optional spatial filter (geometry). Group and count with outStatistics.; =, <>, >, >=, <, <=, LIKE, IN, IS NULL, BETWEEN, AND, OR, NOT

### Endpoints
- `GET ?f=json`: Folder blk_gdn: one service, lks_blk_rd_v1 (MapServer, ArcGIS 11.5).
- `GET /lks_blk_rd_v1/MapServer?f=json`: The service: layer 0 WBB_locaties, 1 Beschikbaarheid_gegevens (31), 2 Bevoegd_gezag (41); maxRecordCount 2,000; WMSServer extension.
- `GET /lks_blk_rd_v1/MapServer/{laag}?f=json`: Fields and capabilities of one layer.
- `GET /lks_blk_rd_v1/MapServer/0/query`: Query soil locations: at a point with a radius, per competent authority, per status; count and group.
- `GET /lks_blk_rd_v1/MapServer/export?bbox=120000,480000,125000,485000&bboxSR=28992&size=400,400&layers=show:0&format=png&f=json`: Map image of a rectangle (link to a PNG; with f=image the image itself).
- `GET /lks_blk_rd_v1/MapServer/legend?f=json`: Legend per layer.
- `GET https://gis.gdngeoservices.nl/standalone/services/blk_gdn/lks_blk_rd_v1/MapServer/WMSServer?request=GetCapabilities&service=WMS`: WMS 1.3.0 of the same service (path without /rest/).

### Parameters
- `where`: Condition in SQL-92 style: 1=1 for everything, text in single quotes (GEMEENTE='Zwolle'), LIKE with %, AND/OR, dates as DATE 'YYYY-MM-DD'. Required when there is no other filter. (tested)
- `outFields`: Fields in the response, comma-separated, or * for all fields. (tested)
- `returnGeometry`: false = attributes only (much smaller response). (tested)
- `outSR`: Projection of the geometry in the response; 4326 = degrees (WGS84). Default is the layer's projection, here RD (28992). (tested)
- `f`: Format: json (Esri JSON), geojson, pbf (protobuf), pjson (indented) or html (browse in the browser). (tested)
- `resultOffset`: Pagination: number of features to skip (from 0). Use together with orderByFields for a stable order. (tested)
- `resultRecordCount`: Maximum number of features in this response (capped at the layer's maxRecordCount). (tested)
- `orderByFields`: Sort order, e.g. 'OBJECTID' or 'n DESC' (also on a statistics field). (tested)
- `returnCountOnly`: true = only the number of features matching where: {"count": …}. (tested)
- `outStatistics`: Server-side statistics: list of {statisticType, onStatisticField, outStatisticFieldName}; statisticType count, sum, min, max, avg, stddev or var. URL-encode it. (tested)
- `groupByFieldsForStatistics`: Fields to group by with outStatistics (like GROUP BY). (tested)
- `returnDistinctValues`: true with outFields and returnGeometry=false: the unique values of those fields. (tested)
- `geometry`: Spatial filter: a point 'x,y' or envelope 'xmin,ymin,xmax,ymax', with geometryType (esriGeometryPoint, esriGeometryEnvelope), inSR (input projection), spatialRel (esriSpatialRelIntersects) and optionally distance + units (esriSRUnit_Meter) for a radius. (tested)

### Fields of the example layer (9)
`WBB_DOSSIER_DBK` OID, `SIKB_ID` String, `LOCATIECODE_BEVOEGD_GEZAG` String, `BIS_LOCCODE` String, `TYPE_CD` String, `VERVOLG_WBB` String, `STATUS_OORD` String, `ADMINISTRATOR_NM` String, `NEN3610_ID_SOIL_LOCATION` String

### Limits and pitfalls
- At most 2.000 features per response (the layer's maxRecordCount; varies per layer, see /{layer}?f=json). If there are more, the response has exceededTransferLimit: true: fetch the rest with resultOffset and resultRecordCount. (tested)
- No key and no documented rate limit. Take it easy (a few requests per second) and prefer statistics or counts over fetching all features. (experience)
- The outStatistics field comes back in upper case: if you ask for outStatisticFieldName 'n', the response calls it 'N'. Sorting with orderByFields=n DESC still works. (tested)
- The object key field is WBB_DOSSIER_DBK, not OBJECTID: orderByFields=OBJECTID returns 'Invalid or missing input parameters'. (tested)
- Errors come back with HTTP 200 and a JSON object {"error": {"code": 400, …}} ('Failed to execute query.' for an unknown field in where). Always check for an error field, not just the status code. (tested)
- 22,869 locations have no VERVOLG_WBB and no TYPE_CD. Amsterdam and Rotterdam supply nothing; 31 of the 41 competent authorities (layer 1) only show their data on their own website. (tested)
- No date field and no editingInfo: the API does not say when a location was updated. The total fluctuates (15 Oct 241,945, earlier on 16 Oct 242,206, in the evening 241,945 again). (tested)
- Layer 0 has polygons in RD (EPSG:28992). A query at a point with a radius: geometry=x,y&geometryType=esriGeometryPoint&inSR=28992&distance=250&units=esriSRUnit_Meter. (tested)

### Example
```
https://gis.gdngeoservices.nl/standalone/rest/services/blk_gdn/lks_blk_rd_v1/MapServer/0/query?where=1%3D1&outStatistics=%5B%7B%22statisticType%22%3A%22count%22%2C%22onStatisticField%22%3A%22WBB_DOSSIER_DBK%22%2C%22outStatisticFieldName%22%3A%22n%22%7D%5D&groupByFieldsForStatistics=VERVOLG_WBB&orderByFields=n%20DESC&f=json
```

## How to fetch it

### curl
```bash
curl "https://gis.gdngeoservices.nl/standalone/rest/services/blk_gdn/lks_blk_rd_v1/MapServer/0/query?geometry=177060%2C386040&geometryType=esriGeometryPoint&inSR=28992&distance=250&units=esriSRUnit_Meter&spatialRel=esriSpatialRelIntersects&outFields=LOCATIECODE_BEVOEGD_GEZAG%2CVERVOLG_WBB%2CSTATUS_OORD&returnGeometry=false&f=json"
```

### python
```python
import requests

# Locaties binnen 250 meter van een punt (RD-coördinaten)
r = requests.get(
    "https://gis.gdngeoservices.nl/standalone/rest/services/blk_gdn/lks_blk_rd_v1/MapServer/0/query",
    params={"geometry": "177060,386040", "geometryType": "esriGeometryPoint", "inSR": 28992,
            "distance": 250, "units": "esriSRUnit_Meter", "spatialRel": "esriSpatialRelIntersects",
            "outFields": "LOCATIECODE_BEVOEGD_GEZAG,VERVOLG_WBB,STATUS_OORD",
            "returnGeometry": "false", "f": "json"})
for f in r.json()["features"]:
    a = f["attributes"]
    print(a["LOCATIECODE_BEVOEGD_GEZAG"], a["VERVOLG_WBB"], a["STATUS_OORD"], sep=" | ")
```


## Examples with real figures (2)

For each example the finding, the reference date and the call that fetches exactly these figures.

- **Bodemloket sites by supplying authority: most Omgevingsdienst Haaglanden (34,157 sites), then Omgevingsdienst West-Holland and Provincie Limburg** (oktober 2026). Table WBB_locaties. [Example](https://www.opendataindex.nl/en/voorbeeld/bodemloket-beheerders)
  - `https://gis.gdngeoservices.nl/standalone/rest/services/blk_gdn/lks_blk_rd_v1/MapServer/0/query?where=1%3D1&outStatistics=%5B%7B%22statisticType%22%3A%22count%22%2C%22onStatisticField%22%3A%22WBB_DOSSIER_DBK%22%2C%22outStatisticFieldName%22%3A%22n%22%7D%5D&groupByFieldsForStatistics=ADMINISTRATOR_NM&f=json`
  - `https://gis.gdngeoservices.nl/standalone/rest/services/blk_gdn/lks_blk_rd_v1/MapServer/1/query?where=1%3D1&outFields=AUTHORITY_NM&returnGeometry=false&f=json`
- **Bodemloket sites by next step: most sufficiently investigated (140,558 sites), then historical survey to do and exploratory survey to do** (oktober 2026). Table WBB_locaties. [Example](https://www.opendataindex.nl/en/voorbeeld/bodemloket-vervolgstap)
  - `https://gis.gdngeoservices.nl/standalone/rest/services/blk_gdn/lks_blk_rd_v1/MapServer/0/query?where=1%3D1&outStatistics=%5B%7B%22statisticType%22%3A%22count%22%2C%22onStatisticField%22%3A%22WBB_DOSSIER_DBK%22%2C%22outStatisticFieldName%22%3A%22n%22%7D%5D&groupByFieldsForStatistics=VERVOLG_WBB&f=json`
