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 9781 duplicates (of 36460). 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-06-16 05:00:00 0.929 goedgekeurd voorlopig reguliereMeting
2026-06-16 06:00:00 0.925 goedgekeurd voorlopig reguliereMeting
2026-06-16 07:00:00 0.926 goedgekeurd voorlopig reguliereMeting
2026-06-24 14:00:50 0.925 goedgekeurd voorlopig reguliereMeting
2026-06-30 05:01:16 0.950 goedgekeurd voorlopig reguliereMeting

26679 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/a8c273683ed5d8541fdc235a3baa1d997fe19e54d5ba0ca13c238107f2ebebce.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_0267',
 'latestAdditionTime': '2026-07-08T07:02:30+02:00',
 'objectRegistrationTime': '2022-03-02T16:45:28+01:00',
 'qualityRegime': 'IMBRO',
 'registrationStatus': 'aangevuld',
 'reregistered': 'nee',
 'researchFirstDate': '2020-09-14',
 'researchLastDate': '2026-06-30',
 '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 185508 duplicates (of 274166). Keeping only first values (sorted for importance).

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

plot_series(df);
../_images/fbd8ba3ff78031e5706a57a9005cd9106432757935dfa9b834a922e49825dcc2.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 183662 duplicates (of 271473). Keeping only first values (sorted for importance).
GLD000000018833 contains 1646 duplicates (of 2691). Keeping only first values (sorted for importance).
GLD000000018834 contains 219197 duplicates (of 306986). Keeping only first values (sorted for importance).
GLD000000018856 contains 1630 duplicates (of 2692). Keeping only first values (sorted for importance).
GLD000000018835 contains 203701 duplicates (of 291490). 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 169151 duplicates (of 257575). Keeping only first values (sorted for importance).
GLD000000018846 contains 1608 duplicates (of 2669). Keeping only first values (sorted for importance).
GLD000000018840 contains 184735 duplicates (of 272102). Keeping only first values (sorted for importance).
GLD000000018841 contains 1611 duplicates (of 2673). Keeping only first values (sorted for importance).
GLD000000018842 contains 260924 duplicates (of 348696). Keeping only first values (sorted for importance).
gdf
corrected deliveryAccountableParty deregistered id latestAdditionTime latestCorrectionTime objectRegistrationTime observation qualityRegime registrationStatus reregistered researchFirstDate researchLastDate underReview
groundwaterMonitoringWell tubeNumber
GMW000000017757 1 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 nee 17278718 nee BRO_1914 2026-07-08T00:48:55+02:00 NaN 2022-07-14T04:36:52+02:00 value    qualifier     st... IMBRO aangevuld nee 2010-08-18 2026-07-07 nee
2 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 ja 17278718 nee BRO_1916 2026-07-07T22:25:28+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-07-07 nee
3 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 ja 17278718 nee BRO_1886 2026-07-07T19:43:36+02:00 2026-05-16T20:21:14+02:00 2022-07-14T04:59:30+02:00 value    qualifier       ... IMBRO aangevuld nee 2010-08-18 2026-07-06 nee
4 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 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 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 ja 17278718 nee BRO_1880 2026-07-08T00:25:33+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-07-07 nee
6 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              st... IMBRO/A aangevuld nee 1983-01-25 2011-05-12 nee
6 nee 17278718 nee BRO_1912 2026-07-07T23:19:55+02:00 NaN 2022-07-14T05:26:08+02:00 value    qualifier       ... IMBRO aangevuld nee 2010-08-18 2026-07-07 nee
7 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     status obse... IMBRO/A aangevuld nee 1983-01-25 2011-05-09 nee
7 ja 17278718 nee BRO_1884 2026-07-07T23:49:34+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-07-07 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/8f6abde2f6b3c024d0fe89a250c129cd9900fa67a646d6dfcb5d7bb028639cf9.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)
GLD000000012892 contains 20206 duplicates (of 60849). Keeping only first values (sorted for importance).
GLD000000012893 contains 9781 duplicates (of 36460). Keeping only first values (sorted for importance).
gdf_gm.T
gmw_bro_id GMW000000036287 GMW000000036328 GMW000000088164
tube_number 1 1 1
geometry POINT (118300.36532391477 439767.46430370974) POINT (118342.32107156981 439964.2012157428) POINT (118206.55880049393 439792.29601374094)
gm_gmw_monitoringtube_pk 36386 36427 101157
gm_gmw.href https://api.pdok.nl/tno/bro-grondwatermonitori... https://api.pdok.nl/tno/bro-grondwatermonitori... https://api.pdok.nl/tno/bro-grondwatermonitori...
gm_gmw_fk 27656 27697 75834
tube_status gebruiksklaar gebruiksklaar gebruiksklaar
tube_in_use ja ja ja
tube_top_diameter 63 63 40
screen_top_position 0.142 0.795 0.86
screen_bottom_position -0.858 -0.205 -0.14
electrode_top_position None None None
electrode_bottom_position None None None
observation value    qualifier     st... value    qualifier     st... value    qualifier     st...
groundwaterLevelDossier [GLD000000012893] [GLD000000012892] [GLD000000153600]
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-06-16 05:00:00 0.929 goedgekeurd voorlopig reguliereMeting
2026-06-16 06:00:00 0.925 goedgekeurd voorlopig reguliereMeting
2026-06-16 07:00:00 0.926 goedgekeurd voorlopig reguliereMeting
2026-06-24 14:00:50 0.925 goedgekeurd voorlopig reguliereMeting
2026-06-30 05:01:16 0.950 goedgekeurd voorlopig reguliereMeting

26679 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/e725d48b5ce1a2278fd816bc4c0ee0f7a14da764f02b44fc554221db927c8917.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 9781 duplicates (of 36460). Keeping only first values (sorted for importance).
GLD000000012892 contains 20206 duplicates (of 60849). Keeping only first values (sorted for importance).
gdf_gmw.T
groundwaterMonitoringWell GMW000000036287 GMW000000036328 GMW000000088164
tubeNumber 1 1 1
constructionStandard STOWAgwst STOWAgwst NEN5104
coordinateTransformation RDNAPTRANS2008 RDNAPTRANS2008 RDNAPTRANS2018
corrected nee nee nee
deliveredLocation POINT (118300.381 439767.501) POINT (118342.335 439964.237) POINT (118206.574 439792.333)
deliveryAccountableParty 62251686 62251686 62251686
deliveryContext publiekeTaak publiekeTaak publiekeTaak
deregistered nee nee nee
groundLevelPosition 1.932 2.215 2.62
groundLevelPositioningMethod RTKGPS0tot4cm RTKGPS0tot4cm RTKGPS0tot4cm
groundLevelStable ja ja ja
horizontalPositioningMethod RTKGPS0tot2cm RTKGPS0tot2cm RTKGPS0tot2cm
id BRO_0003 BRO_0003 BRO_0003
initialFunction stand stand kwaliteit
localVerticalReferencePoint NAP NAP NAP
numberOfMonitoringTubes 1 1 1
objectRegistrationTime 2020-09-15T10:17:39+02:00 2020-09-15T10:18:09+02:00 2025-01-14T11:34:29+01:00
offset 0.0 0.0 0.0
owner 62251686 62251686 62251686
qualityRegime IMBRO IMBRO IMBRO
registrationStatus geregistreerd geregistreerd geregistreerd
removed nee nee nee
reregistered nee nee nee
standardizedLocation POINT (4.85343324 51.94514556) POINT (4.85402244 51.94691658) POINT (4.85206629 51.945362545)
underReview nee nee nee
verticalDatum NAP NAP NAP
wellCode GMW38B125170 GMW38B125211 GMW38B147347
wellConstructionDate NaT NaT NaT
wellHeadProtector potNietWaterdicht potNietWaterdicht potNietWaterdicht
withPrehistory nee nee nee
tubeType standaardbuis standaardbuis standaardbuis
artesianWellCapPresent nee nee nee
sedimentSumpPresent nee nee nee
numberOfGeoOhmCables 0 0 0
tubeTopDiameter 63.0 63.0 40.0
variableDiameter nee nee nee
tubeStatus gebruiksklaar gebruiksklaar gebruiksklaar
tubeTopPosition 1.802 2.065 2.5
tubeTopPositioningMethod RTKGPS0tot4cm RTKGPS0tot4cm RTKGPS0tot4cm
tubePartInserted nee nee nee
tubeInUse ja ja ja
tubePackingMaterial bentoniet bentoniet bentoniet
tubeMaterial peHighDensity peHighDensity pvc
glue geen geen geen
screenLength 1.0 1.0 1.0
sockMaterial nylon nylon nylon
screenTopPosition 0.142 0.795 0.86
screenBottomPosition -0.858 -0.205 -0.14
plainTubePartLength 1.66 1.27 1.64
observation value    qualifier     st... value    qualifier     st... value    qualifier     st...
groundwaterLevelDossier [GLD000000012893] [GLD000000012892] [GLD000000153600]
f, ax = plt.subplots()
ax.axis("scaled")
ax.axis(extent)
gdf_gmw.plot(ax=ax);
../_images/e725d48b5ce1a2278fd816bc4c0ee0f7a14da764f02b44fc554221db927c8917.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
... ... ... ... ... ... ...
_0421f266-586c-4c65-9e9e-099bcecf1871 2023-06-01 2023-07-01 reguliereMeting _e5fc63d2-02c5-4011-a219-ea5f662be0b3 voorlopig ja
_1f6bcbed-a4c9-4551-b377-18bf38e8dfb6 2023-07-01 2023-07-21 reguliereMeting _51fc6399-d3cd-400d-a29b-291b28ba930e voorlopig ja
_0fc0319f-59f9-438a-b18e-a58ea0bc24fc 2023-08-09 2023-08-29 reguliereMeting _1dd761d0-6d24-4fec-8548-1c9ca4afda48 voorlopig ja
_f66b7f05-f86b-46d6-a0a1-c933cdc02607 2026-06-24 2026-06-24 reguliereMeting _37c14390-7c4d-4c98-9523-7167707bc347 voorlopig ja
_a969ead0-5888-4f9f-83d1-12339056f5f3 2026-06-30 2026-06-30 reguliereMeting _9e16720b-8e5a-41a8-8b99-30867f744916 voorlopig ja

132 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 9781 duplicates (of 36460). 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-06-16 05:00:00 0.929 goedgekeurd voorlopig reguliereMeting
2026-06-16 06:00:00 0.925 goedgekeurd voorlopig reguliereMeting
2026-06-16 07:00:00 0.926 goedgekeurd voorlopig reguliereMeting
2026-06-24 14:00:50 0.925 goedgekeurd voorlopig reguliereMeting
2026-06-30 05:01:16 0.950 goedgekeurd voorlopig reguliereMeting

26679 rows × 4 columns

plot_series(df);
../_images/2c6c8adb7a2b663d1188c01ab961acdf755b5a2c108553dafddf74c60fb44459.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-06-16 04:00:00 0.929 NaN NaN NaN NaN NaN NaN NaN
2026-06-16 05:00:00 0.925 NaN NaN NaN NaN NaN NaN NaN
2026-06-16 06:00:00 0.926 NaN NaN NaN NaN NaN NaN NaN
2026-06-24 13:00:50 0.925 NaN NaN NaN NaN NaN NaN NaN
2026-06-30 04:01:16 0.950 NaN NaN NaN NaN NaN NaN NaN

26679 rows × 8 columns

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