Groundwater Level Dossier (GLD)

import matplotlib.pyplot as plt
import pandas as pd
import brodata

Download a GroundwaterLevelDossier with GroundwaterLevelDossier.from_bro_id(bro_id). The method returns an object containing the data from the BRO XML file.

gld = brodata.gld.GroundwaterLevelDossier.from_bro_id("GLD000000012893")
gld
GLD000000012893 contains 280 duplicates (of 14230). Keeping only first values (sorted for importance).
GroundwaterLevelDossier(broId='GLD000000012893')

Observation data is available in the observation attribute as a pandas DataFrame.

df = gld.observation
df
value qualifier status observation_type
time
2020-09-14 01:00:00 0.790 goedgekeurd voorlopig reguliereMeting
2020-09-14 02:00:00 0.790 goedgekeurd voorlopig reguliereMeting
2020-09-14 03:00:00 0.788 goedgekeurd voorlopig reguliereMeting
2020-09-14 04:00:03 0.787 goedgekeurd voorlopig reguliereMeting
2020-09-14 05:00:03 0.792 goedgekeurd voorlopig reguliereMeting
... ... ... ... ...
2026-03-10 11:02:18 0.832 goedgekeurd voorlopig reguliereMeting
2026-03-29 01:04:09 0.862 goedgekeurd voorlopig reguliereMeting
2026-04-28 04:06:42 0.815 goedgekeurd voorlopig reguliereMeting
2026-04-30 15:02:44 0.822 goedgekeurd voorlopig reguliereMeting
2026-05-03 22:03:00 0.859 goedgekeurd voorlopig reguliereMeting

13950 rows × 4 columns

Create a time-series plot that uses different colors for each metadata combination. The helper function plot_series below implements this.

def plot_series(df):
    f, ax = plt.subplots(figsize=(10, 8))
    columns = ["qualifier", "status", "observation_type"]
    for qualifier, status, observation_type in df[columns].drop_duplicates().values:
        mask = df["qualifier"] == qualifier
        if pd.isna(status):
            mask = mask & pd.isna(df["status"])
        else:
            mask = mask & (df["status"] == status)
        mask = mask & (df["observation_type"] == observation_type)
        if status is None:
            label = f"{observation_type} {qualifier}"
        else:
            label = f"{observation_type} {status} {qualifier}"
        if mask.sum() > 100:
            linestyle = "-"
            marker = None
        else:
            linestyle = "none"
            marker = "o"
        df.loc[mask, "value"].plot(label=label, linestyle=linestyle, marker=marker)
    ax.legend()
    return f, ax


plot_series(df);
../_images/1bc8232ee991fa076094e0478cdce7d44177d180b8df752ce6a7f9bb60b5d7b5.png

We can look at all other contents of this GroundwaterLevelDossier by using the to_dict() method.

gld_data = gld.to_dict()
gld_data.pop("observation")
gld_data
{'broId': 'GLD000000012893',
 'corrected': 'nee',
 'deliveryAccountableParty': '62251686',
 'deregistered': 'nee',
 'groundwaterMonitoringNet': 'GMN000000000163',
 'groundwaterMonitoringWell': 'GMW000000036287',
 'id': 'BRO_0197',
 'latestAdditionTime': '2026-05-11T07:02:32+02:00',
 'objectRegistrationTime': '2022-03-02T16:45:28+01:00',
 'qualityRegime': 'IMBRO',
 'registrationStatus': 'aangevuld',
 'reregistered': 'nee',
 'researchFirstDate': '2020-09-14',
 'researchLastDate': '2026-05-03',
 'tubeNumber': 1,
 'underReview': 'nee'}

Multiple objects

All measurements of one tube

Download all GroundwaterLevelDossiers for a particular tube (piezometer) using brodata.gmw.get_tube_observations.

df = brodata.gmw.get_tube_observations("GMW000000017757", 1)
GMW000000017757_1 contains 172848 duplicates (of 260149). Keeping only first values (sorted for importance).

Plot the resulting pandas DataFrame again using the plot_series function defined above.

plot_series(df);
../_images/d28f27120208b8a178775871f5a06396f03b59b17efd0877a5b4c18913baa0f6.png

All measurements of one well

Download all measurements of a Groundwater Monitoring Well using brodata.gmw.get_observations(groundwaterMonitoringWell_id). The result is a GeoDataFrame where each row contains an observation DataFrame for a monitoring tube and a list of Groundwater Level Dossier BRO IDs.

gdf = brodata.gmw.get_observations("GMW000000017757")
gdf = gdf.set_index(["groundwaterMonitoringWell", "tubeNumber"]).sort_index()
GLD000000018830 contains 1656 duplicates (of 2693). Keeping only first values (sorted for importance).
GLD000000018832 contains 171002 duplicates (of 257456). Keeping only first values (sorted for importance).
GLD000000018833 contains 1646 duplicates (of 2691). Keeping only first values (sorted for importance).
GLD000000018834 contains 207055 duplicates (of 293499). Keeping only first values (sorted for importance).
GLD000000018856 contains 1630 duplicates (of 2692). Keeping only first values (sorted for importance).
GLD000000018835 contains 183001 duplicates (of 269466). Keeping only first values (sorted for importance).
GLD000000018836 contains 653 duplicates (of 1333). Keeping only first values (sorted for importance).
GLD000000018838 contains 1651 duplicates (of 2692). Keeping only first values (sorted for importance).
GLD000000018839 contains 156467 duplicates (of 243546). Keeping only first values (sorted for importance).
GLD000000018846 contains 1608 duplicates (of 2669). Keeping only first values (sorted for importance).
GLD000000018840 contains 169469 duplicates (of 255479). Keeping only first values (sorted for importance).
GLD000000018841 contains 1611 duplicates (of 2673). Keeping only first values (sorted for importance).
GLD000000018842 contains 248756 duplicates (of 335183). Keeping only first values (sorted for importance).
gdf
broId corrected deliveryAccountableParty deregistered id latestAdditionTime latestCorrectionTime objectRegistrationTime observation qualityRegime registrationStatus reregistered researchFirstDate researchLastDate underReview
groundwaterMonitoringWell tubeNumber
GMW000000017757 1 GLD000000018830 ja 17278718 nee BRO_0018 2025-11-11T12:18:27+01:00 2026-04-08T11:53:44+02:00 2022-07-14T04:17:41+02:00 value    qualifier              st... IMBRO/A aangevuld nee 1983-01-25 2011-04-06 nee
1 GLD000000018832 nee 17278718 nee BRO_1864 2026-05-12T20:29:55+02:00 NaN 2022-07-14T04:36:52+02:00 value    qualifier       ... IMBRO aangevuld nee 2010-08-18 2026-05-11 nee
2 GLD000000018833 ja 17278718 nee BRO_0018 2025-11-11T15:15:16+01:00 2026-05-08T11:27:10+02:00 2022-07-14T04:37:25+02:00 value    qualifier              st... IMBRO/A aangevuld nee 1983-01-25 2011-04-12 nee
2 GLD000000018834 ja 17278718 nee BRO_1868 2026-05-12T23:39:30+02:00 2026-05-08T11:27:59+02:00 2022-07-14T04:58:55+02:00 value    qualifier       ... IMBRO aangevuld nee 2010-08-18 2026-05-12 nee
3 GLD000000018856 ja 17278718 nee BRO_0018 2025-11-11T14:40:23+01:00 2026-04-08T11:56:15+02:00 2022-07-14T06:59:31+02:00 value    qualifier              st... IMBRO/A aangevuld nee 1983-01-25 2011-04-30 nee
3 GLD000000018835 nee 17278718 nee BRO_1838 2026-05-12T22:59:30+02:00 NaN 2022-07-14T04:59:30+02:00 value    qualifier       ... IMBRO aangevuld nee 2010-08-18 2026-05-12 nee
4 GLD000000018836 ja 17278718 nee BRO_0018 2025-11-11T12:33:52+01:00 2026-04-08T11:56:36+02:00 2022-07-14T05:00:06+02:00 value    qualifier              st... IMBRO/A aangevuld nee 1983-01-25 2007-03-14 nee
4 GLD000000018837 nee 17278718 nee BRO_0004 2025-07-29T09:29:15+02:00 NaN 2022-07-14T05:01:25+02:00 value    qualifier status... IMBRO aangevuld nee 2016-09-21 2016-09-22 nee
5 GLD000000018838 ja 17278718 nee BRO_0018 2025-11-11T10:52:39+01:00 2026-04-08T11:57:53+02:00 2022-07-14T05:21:44+02:00 value    qualifier              st... IMBRO/A aangevuld nee 1983-01-25 2011-04-12 nee
5 GLD000000018839 ja 17278718 nee BRO_1830 2026-05-12T20:53:49+02:00 2026-04-25T20:04:19+02:00 2022-07-14T05:22:25+02:00 value    qualifier     st... IMBRO aangevuld nee 1983-01-28 2026-05-12 nee
6 GLD000000018846 ja 17278718 nee BRO_0018 2025-11-04T23:08:30+01:00 2026-04-08T11:59:10+02:00 2022-07-14T06:00:35+02:00 value    qualifier     status obse... IMBRO/A aangevuld nee 1983-01-25 2011-05-12 nee
6 GLD000000018840 nee 17278718 nee BRO_1862 2026-05-12T21:20:50+02:00 NaN 2022-07-14T05:26:08+02:00 value    qualifier       ... IMBRO aangevuld nee 2010-08-18 2026-05-12 nee
7 GLD000000018841 ja 17278718 nee BRO_0018 2025-11-07T21:53:19+01:00 2026-04-12T08:05:09+02:00 2022-07-14T05:27:05+02:00 value    qualifier              st... IMBRO/A aangevuld nee 1983-01-25 2011-05-09 nee
7 GLD000000018842 ja 17278718 nee BRO_1836 2026-05-12T22:56:30+02:00 2026-04-12T08:06:45+02:00 2022-07-14T05:39:17+02:00 value    qualifier     st... IMBRO aangevuld nee 2010-08-19 2026-05-12 nee
f, ax = plt.subplots(figsize=(10, 8))
for index in gdf.index.unique():
    observations = [x for x in gdf.loc[[index], "observation"] if not x.empty]
    df = pd.concat(observations).sort_index()
    for qualifier in df["qualifier"].unique():
        if pd.isna(qualifier):
            continue
        mask = df["qualifier"] == qualifier
        label = f"{index[0]}_{index[1]} {qualifier}"
        if mask.sum() > 100:
            linestyle = "-"
            marker = None
        else:
            linestyle = "none"
            marker = "o"
        df.loc[mask, "value"].plot(label=label, linestyle=linestyle, marker=marker)
plt.legend();
../_images/96b299440683044bad014bb475935a7c6c911d016d7ae4bad1a7a20297b99973.png

All measurements within an extent (option 1: brodata.gm.get_data_in_extent)

Download groundwater level data within a bounding box using brodata.gm.get_data_in_extent.

This method returns a GeoDataFrame containing metadata for monitoring tubes. Measurements are in the observation column (a DataFrame per tube), and the BRO IDs of the groundwater level dossiers are provided as a list in the groundwaterLevelDossier column.

extent = [118200, 118400, 439700, 440000]
gdf_gm = brodata.gm.get_data_in_extent(extent)
GLD000000012893 contains 280 duplicates (of 14230). Keeping only first values (sorted for importance).
GLD000000012892 contains 987 duplicates (of 34104). Keeping only first values (sorted for importance).
gdf_gm.T
gmw_bro_id GMW000000036287 GMW000000036328
tube_number 1 1
geometry POINT (118300.36532391477 439767.46430370974) POINT (118342.32107156981 439964.2012157428)
gm_gmw_monitoringtube_pk 36386 36427
gm_gmw.href https://api.pdok.nl/tno/bro-grondwatermonitori... https://api.pdok.nl/tno/bro-grondwatermonitori...
gm_gmw_fk 27656 27697
tube_status gebruiksklaar gebruiksklaar
tube_in_use ja ja
tube_top_diameter 63 63
screen_top_position 0.142 0.795
screen_bottom_position -0.858 -0.205
electrode_top_position None None
electrode_bottom_position None None
observation value    qualifier     st... value    qualifier     st...
groundwaterLevelDossier [GLD000000012893] [GLD000000012892]
gdf_gm['observation'].iloc[0]
value qualifier status observation_type
time
2020-09-14 01:00:00 0.790 goedgekeurd voorlopig reguliereMeting
2020-09-14 02:00:00 0.790 goedgekeurd voorlopig reguliereMeting
2020-09-14 03:00:00 0.788 goedgekeurd voorlopig reguliereMeting
2020-09-14 04:00:03 0.787 goedgekeurd voorlopig reguliereMeting
2020-09-14 05:00:03 0.792 goedgekeurd voorlopig reguliereMeting
... ... ... ... ...
2026-03-10 11:02:18 0.832 goedgekeurd voorlopig reguliereMeting
2026-03-29 01:04:09 0.862 goedgekeurd voorlopig reguliereMeting
2026-04-28 04:06:42 0.815 goedgekeurd voorlopig reguliereMeting
2026-04-30 15:02:44 0.822 goedgekeurd voorlopig reguliereMeting
2026-05-03 22:03:00 0.859 goedgekeurd voorlopig reguliereMeting

13950 rows × 4 columns

Plot the GeoDataFrame on a map using its plot method.

f, ax = plt.subplots()
ax.axis("scaled")
ax.axis(extent)
gdf_gm.plot(ax=ax);
../_images/6e64045cff54a80e8b628b6ddfae935303c2e5d36cc6da5a50924e26b8a313a0.png

All measurements within an extent (option 2: brodata.gmw.get_data_in_extent)

The alternative method brodata.gmw.get_data_in_extent first downloads well characteristics, then each GroundwaterMonitoringWell to retrieve tube metadata, and uses the BRO relations API to obtain the relevant Groundwater Level Dossiers (GLD).

Both methods place measurements in the observation column and list the corresponding GLD BRO IDs in groundwaterLevelDossier. The two methods differ in how tube metadata is retrieved.

Because brodata.gmw.get_data_in_extent requires multiple BRO requests (characteristics, GroundwaterMonitoringWell, relations, GroundwaterLevelDossier), the BRO developed a PDOK web service that replaces the first three requests and is faster and less error-prone. For most use cases, brodata.gm.get_data_in_extent (option 1) is the preferred method because it is simpler and returns the key information (tube horizontal and vertical positions).

extent = [118200, 118400, 439700, 440000]
gdf_gmw = brodata.gmw.get_data_in_extent(
    extent=extent, kind="gld", combine=True, as_csv=True
)
GLD000000012893 contains 280 duplicates (of 14230). Keeping only first values (sorted for importance).
GLD000000012892 contains 987 duplicates (of 34104). Keeping only first values (sorted for importance).
gdf_gmw.T
groundwaterMonitoringWell GMW000000036287 GMW000000036328
tubeNumber 1 1
constructionStandard STOWAgwst STOWAgwst
coordinateTransformation RDNAPTRANS2008 RDNAPTRANS2008
corrected nee nee
deliveredLocation POINT (118300.381 439767.501) POINT (118342.335 439964.237)
deliveryAccountableParty 62251686 62251686
deliveryContext publiekeTaak publiekeTaak
deregistered nee nee
groundLevelPosition 1.932 2.215
groundLevelPositioningMethod RTKGPS0tot4cm RTKGPS0tot4cm
groundLevelStable ja ja
horizontalPositioningMethod RTKGPS0tot2cm RTKGPS0tot2cm
id BRO_0003 BRO_0003
initialFunction stand stand
localVerticalReferencePoint NAP NAP
numberOfMonitoringTubes 1 1
objectRegistrationTime 2020-09-15T10:17:39+02:00 2020-09-15T10:18:09+02:00
offset 0.0 0.0
owner 62251686 62251686
qualityRegime IMBRO IMBRO
registrationStatus geregistreerd geregistreerd
removed nee nee
reregistered nee nee
standardizedLocation POINT (4.85343324 51.94514556) POINT (4.85402244 51.94691658)
underReview nee nee
verticalDatum NAP NAP
wellCode GMW38B125170 GMW38B125211
wellConstructionDate 2020-08-06 00:00:00 2020-08-18 00:00:00
wellHeadProtector potNietWaterdicht potNietWaterdicht
withPrehistory nee nee
tubeType standaardbuis standaardbuis
artesianWellCapPresent nee nee
sedimentSumpPresent nee nee
numberOfGeoOhmCables 0 0
tubeTopDiameter 63.0 63.0
variableDiameter nee nee
tubeStatus gebruiksklaar gebruiksklaar
tubeTopPosition 1.802 2.065
tubeTopPositioningMethod RTKGPS0tot4cm RTKGPS0tot4cm
tubePartInserted nee nee
tubeInUse ja ja
tubePackingMaterial bentoniet bentoniet
tubeMaterial peHighDensity peHighDensity
glue geen geen
screenLength 1.0 1.0
sockMaterial nylon nylon
screenTopPosition 0.142 0.795
screenBottomPosition -0.858 -0.205
plainTubePartLength 1.66 1.27
observation value    qualifier     st... value    qualifier     st...
groundwaterLevelDossier [GLD000000012893] [GLD000000012892]
f, ax = plt.subplots()
ax.axis("scaled")
ax.axis(extent)
gdf_gmw.plot(ax=ax);
../_images/6e64045cff54a80e8b628b6ddfae935303c2e5d36cc6da5a50924e26b8a313a0.png

Observations summary

Use brodata.gld.get_observations_summary to download a summary of the observations within a Groundwater Level Dossier.

brodata.gld.get_observations_summary("GLD000000012893")
startDate endDate observationType observationProcessId observationStatus definitionProcessExists
observationId
_b2c67e6a-023e-40f2-b35c-9c084e64fde3 2021-06-01 2021-09-01 reguliereMeting _a70fde89-e249-46e2-b53e-20d2261f9c52 voorlopig ja
_c1567684-9a7b-443e-8b58-4b2c9ab7d80c 2021-03-01 2021-06-01 reguliereMeting _de73efa3-540e-4248-ab69-65dbc403e3bd voorlopig ja
_c02cf139-8f3e-41c1-abbd-95e56cc1f3fa 2021-01-01 2021-03-01 reguliereMeting _d207e131-f660-481c-90a9-d2e970863b7c voorlopig ja
_b5bffdc6-45ea-4ada-b8e7-d9a0814d4efe 2020-09-14 2021-01-01 reguliereMeting _cbf4d0d5-e32c-41bb-8baa-04b4e4dc8795 voorlopig ja
_0bd66762-5821-40d2-9ff0-4bb74edbb695 2022-01-02 2022-02-26 reguliereMeting _ad212724-16f0-4855-becf-a7ee8e4b422c voorlopig ja
... ... ... ... ... ... ...
_bb746633-1bf6-4c16-82ea-877866e98660 2026-03-10 2026-03-10 reguliereMeting _a97fa004-f60e-4dab-9ddd-ee51bf75f0ef voorlopig ja
_47aa3f9f-403a-40ea-b02e-216bffbb6ca8 2026-03-29 2026-03-29 reguliereMeting _b3bf7a69-f59e-4e6b-8d91-bc94df7b4b06 voorlopig ja
_2f574266-62f3-4089-96f2-ef679dfaf60f 2026-04-28 2026-04-28 reguliereMeting _0f5a8083-44ec-452e-a369-018a370d2836 voorlopig ja
_1e9b25f2-3c50-4919-a3c0-0d65fac5ed04 2026-04-30 2026-04-30 reguliereMeting _e024d736-6625-4519-a9f1-cb1ff7fe4b4b voorlopig ja
_234e3d38-9e51-4ecd-95a0-ef47cb2a9dd5 2026-05-03 2026-05-03 reguliereMeting _dc05982c-acee-4add-9bde-41b33ed4a977 voorlopig ja

97 rows × 6 columns

Objects as csv

The XML representations of GroundwaterLevelDossier objects can become large and may be relatively slow to parse. To improve performance, the data is also available in CSV format. You can retrieve the data as CSV using the method brodata.gld.get_objects_as_csv(). This method returns a pandas.DataFrame equivalent to the GroundwaterLevelDossier.observation attribute.

You can set the parameter as_csv=True when calling brodata.gm.get_data_in_extent() or brodata.gmw.get_data_in_extent() (described in the previous sections). When enabled, these methods download and process the data using the CSV endpoint instead of the XML files.

df = brodata.gld.get_objects_as_csv("GLD000000012893")
df
GLD000000012893 contains 280 duplicates (of 14230). Keeping only first values (sorted for importance).
value qualifier status observation_type
time
2020-09-14 01:00:00 0.790 goedgekeurd voorlopig reguliereMeting
2020-09-14 02:00:00 0.790 goedgekeurd voorlopig reguliereMeting
2020-09-14 03:00:00 0.788 goedgekeurd voorlopig reguliereMeting
2020-09-14 04:00:03 0.787 goedgekeurd voorlopig reguliereMeting
2020-09-14 05:00:03 0.792 goedgekeurd voorlopig reguliereMeting
... ... ... ... ...
2026-03-10 11:02:18 0.832 goedgekeurd voorlopig reguliereMeting
2026-03-29 01:04:09 0.862 goedgekeurd voorlopig reguliereMeting
2026-04-28 04:06:42 0.815 goedgekeurd voorlopig reguliereMeting
2026-04-30 15:02:44 0.822 goedgekeurd voorlopig reguliereMeting
2026-05-03 22:03:00 0.859 goedgekeurd voorlopig reguliereMeting

13950 rows × 4 columns

plot_series(df);
../_images/1bc8232ee991fa076094e0478cdce7d44177d180b8df752ce6a7f9bb60b5d7b5.png

Series as csv

An alternative is the method brodata.gld.get_series_as_csv. This method retrieves a table with measurements for different observation types (regulier_voorlopig, regulier_beoordeeld, controle en onbekend) as columns, and is intended for applications such as the graphical visualization of groundwater levels.

brodata.gld.get_series_as_csv() returns a different data structure for the observations in the pandas.DataFrame compared to brodata.gld.get_objects_as_csv() or GroundwaterLevelDossier.observation. For this reason, its use is generally not recommended.

df = brodata.gld.get_series_as_csv("GLD000000012893")
df
Voorlopige Waarde [m] Voorlopige Opmerking Beoordeelde Waarde [m] Beoordeelde Opmerking Controle Waarde [m] Controle Opmerking Onbekend Waarde [m] Onbekend Opmerking
Tijdstip
2020-09-14 00:00:00 0.790 NaN NaN NaN NaN NaN NaN NaN
2020-09-14 01:00:00 0.790 NaN NaN NaN NaN NaN NaN NaN
2020-09-14 02:00:00 0.788 NaN NaN NaN NaN NaN NaN NaN
2020-09-14 03:00:03 0.787 NaN NaN NaN NaN NaN NaN NaN
2020-09-14 04:00:03 0.792 NaN NaN NaN NaN NaN NaN NaN
... ... ... ... ... ... ... ... ...
2026-03-10 10:02:18 0.832 NaN NaN NaN NaN NaN NaN NaN
2026-03-29 00:04:09 0.862 NaN NaN NaN NaN NaN NaN NaN
2026-04-28 03:06:42 0.815 NaN NaN NaN NaN NaN NaN NaN
2026-04-30 14:02:44 0.822 NaN NaN NaN NaN NaN NaN NaN
2026-05-03 21:03:00 0.859 NaN NaN NaN NaN NaN NaN NaN

13950 rows × 8 columns

df[["Voorlopige Waarde [m]", "Beoordeelde Waarde [m]", "Controle Waarde [m]", "Onbekend Waarde [m]"]].plot(figsize=(10, 8));
../_images/dd0a98d799f808e9d69844b5eb1082b1663296b4640fa523e8513584436a29dc.png