Boreholes
This notebook shows how to plot borehole data along with a groundwater measurement well.
Two different packages are used:
brodata, to obtain and visualise borehole data from the BRO databasegeostto get Bodemkundig booronderzoek from the BRO database
[1]:
import brodata
import contextily as ctx
import geopandas as gpd
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from shapely.geometry import LineString, Point
import hydropandas as hpd
[2]:
# reading data from a groundwatermonitoring well (see notebook 01)
gw_bro = hpd.GroundwaterObs.from_bro("GMW000000041261", 1)
Borehole (Brodata)
[3]:
# select extent with a buffer around gw_bro
p = Point(gw_bro.x, gw_bro.y)
extent = np.array(p.buffer(1000).bounds).astype(int)[[0, 2, 1, 3]]
# get bro ids of borehole data
gdf = brodata.bhr.get_characteristics(extent=extent)
[4]:
# plot borehole data
f, axes = plt.subplots(ncols=2, figsize=(16, 8))
gdf["broid"] = gdf.index.str[-7:]
gdf["x"] = gdf.geometry.x
gdf = gdf.sort_values("x")
gdf["geometry"] = gdf.geometry
line = LineString(gdf["deliveredLocation"].values)
distance_along_line = pd.Series(
[line.project(point) for point in gdf["geometry"]], gdf.index
)
# plot values
xticks = []
xticklabels = []
for bro_id in gdf.index:
bhrgt = brodata.bhr.GeotechnicalBoreholeResearch.from_bro_id(bro_id)
for x, bl in enumerate(bhrgt.descriptiveBoreholeLog):
brodata.plot.bro_lithology_advanced(
bl["layer"],
x=distance_along_line[bro_id],
z=bhrgt.offset,
width=80,
bro_id=bro_id,
ax=axes[0],
)
xticks.append(distance_along_line[bro_id])
xticklabels.append(bro_id[-7:])
axes[0].set_xlim(min(distance_along_line) - 80, max(distance_along_line) + 80)
axes[0].set_xticks(xticks)
axes[0].set_xticklabels(xticklabels, rotation=45, ha="right")
axes[0].set_ylim(3, gdf["offset"].astype(float).max() + 0.5)
axes[0].set_ylabel("z (m t.o.v. NAP)")
# plot map
# plot well
axes[1].scatter(gw_bro.x, gw_bro.y, color="black")
axes[1].annotate(gw_bro.name, (gw_bro.x + 25, gw_bro.y + 10))
# plot boreholes
legend_labels = gdf["broid"].tolist()
gdf.plot("broid", ax=axes[1], legend=True)
# plot line
gpd.GeoDataFrame(geometry=[line]).plot(ax=axes[1])
# settings
axes[1].set_xlim(extent[:2])
axes[1].set_ylim(extent[2:])
# basemap
ctx.add_basemap(axes[1], crs=28992)
Tag meanHighestGroundwaterLevel not supported in GeotechnicalBoreholeResearch BHR000000348833
Tag meanLowestGroundwaterLevel not supported in GeotechnicalBoreholeResearch BHR000000348833
Tag meanHighestGroundwaterLevel not supported in GeotechnicalBoreholeResearch BHR000000348833
Tag meanLowestGroundwaterLevel not supported in GeotechnicalBoreholeResearch BHR000000348833
Tag determinationProcedure not supported in GeotechnicalBoreholeResearch BHR000000348833
Tag determinationMethod not supported in GeotechnicalBoreholeResearch BHR000000348833
Tag fractionDistribution not supported in GeotechnicalBoreholeResearch BHR000000348833
Tag dispersionMethod not supported in GeotechnicalBoreholeResearch BHR000000348833
Tag removedMaterial not supported in GeotechnicalBoreholeResearch BHR000000348833
Tag equivalentMassDeterminationMethod not supported in GeotechnicalBoreholeResearch BHR000000348833
Tag equivalentMass not supported in GeotechnicalBoreholeResearch BHR000000348833
Tag basicParticleSizeDistribution not supported in GeotechnicalBoreholeResearch BHR000000348833
SoilName sterkZandigSilt not supported (found at broId BHR000000348833). Please add sterkZandigSilt to lithology_properties.
Tag meanHighestGroundwaterLevel not supported in GeotechnicalBoreholeResearch BHR000000348807
Tag meanLowestGroundwaterLevel not supported in GeotechnicalBoreholeResearch BHR000000348807
Tag meanHighestGroundwaterLevel not supported in GeotechnicalBoreholeResearch BHR000000348866
Tag meanLowestGroundwaterLevel not supported in GeotechnicalBoreholeResearch BHR000000348866
Tag meanHighestGroundwaterLevel not supported in GeotechnicalBoreholeResearch BHR000000348629
Tag meanLowestGroundwaterLevel not supported in GeotechnicalBoreholeResearch BHR000000348629
Tag meanHighestGroundwaterLevel not supported in GeotechnicalBoreholeResearch BHR000000348741
Tag meanLowestGroundwaterLevel not supported in GeotechnicalBoreholeResearch BHR000000348741
Tag meanHighestGroundwaterLevel not supported in GeotechnicalBoreholeResearch BHR000000348867
Tag meanLowestGroundwaterLevel not supported in GeotechnicalBoreholeResearch BHR000000348867
Tag meanHighestGroundwaterLevel not supported in GeotechnicalBoreholeResearch BHR000000348683
Tag meanLowestGroundwaterLevel not supported in GeotechnicalBoreholeResearch BHR000000348683
Tag meanHighestGroundwaterLevel not supported in GeotechnicalBoreholeResearch BHR000000348675
Tag meanLowestGroundwaterLevel not supported in GeotechnicalBoreholeResearch BHR000000348675
Tag meanHighestGroundwaterLevel not supported in GeotechnicalBoreholeResearch BHR000000348869
Tag meanLowestGroundwaterLevel not supported in GeotechnicalBoreholeResearch BHR000000348869
Tag meanHighestGroundwaterLevel not supported in GeotechnicalBoreholeResearch BHR000000348801
Tag meanLowestGroundwaterLevel not supported in GeotechnicalBoreholeResearch BHR000000348801
Tag meanHighestGroundwaterLevel not supported in GeotechnicalBoreholeResearch BHR000000348684
Tag meanLowestGroundwaterLevel not supported in GeotechnicalBoreholeResearch BHR000000348684
Bodemkundig booronderzoek (geost)
The geost package can only be pip installed for python version 3.12 or higher. Install it using:
pip install geost
[5]:
!pip install geost
Collecting geost
Downloading geost-0.3.0-py3-none-any.whl.metadata (16 kB)
Requirement already satisfied: geopandas in /home/docs/checkouts/readthedocs.org/user_builds/hydropandas/envs/latest/lib/python3.12/site-packages (from geost) (1.1.3)
Requirement already satisfied: lxml in /home/docs/checkouts/readthedocs.org/user_builds/hydropandas/envs/latest/lib/python3.12/site-packages (from geost) (6.1.1)
Requirement already satisfied: numpy in /home/docs/checkouts/readthedocs.org/user_builds/hydropandas/envs/latest/lib/python3.12/site-packages (from geost) (2.4.6)
Requirement already satisfied: openpyxl in /home/docs/checkouts/readthedocs.org/user_builds/hydropandas/envs/latest/lib/python3.12/site-packages (from geost) (3.1.5)
Requirement already satisfied: pandas in /home/docs/checkouts/readthedocs.org/user_builds/hydropandas/envs/latest/lib/python3.12/site-packages (from geost) (2.3.3)
Collecting pandera (from geost)
Downloading pandera-0.31.1-py3-none-any.whl.metadata (10 kB)
Requirement already satisfied: pooch in /home/docs/checkouts/readthedocs.org/user_builds/hydropandas/envs/latest/lib/python3.12/site-packages (from geost) (1.9.0)
Requirement already satisfied: pyarrow in /home/docs/checkouts/readthedocs.org/user_builds/hydropandas/envs/latest/lib/python3.12/site-packages (from geost) (24.0.0)
Requirement already satisfied: pyproj in /home/docs/checkouts/readthedocs.org/user_builds/hydropandas/envs/latest/lib/python3.12/site-packages (from geost) (3.7.2)
Collecting pyvista (from geost)
Downloading pyvista-0.48.4-py3-none-any.whl.metadata (13 kB)
Collecting rioxarray (from geost)
Downloading rioxarray-0.22.0-py3-none-any.whl.metadata (5.4 kB)
Requirement already satisfied: shapely in /home/docs/checkouts/readthedocs.org/user_builds/hydropandas/envs/latest/lib/python3.12/site-packages (from geost) (2.1.2)
Collecting xlrd (from geost)
Downloading xlrd-2.0.2-py2.py3-none-any.whl.metadata (3.5 kB)
Requirement already satisfied: pyogrio>=0.7.2 in /home/docs/checkouts/readthedocs.org/user_builds/hydropandas/envs/latest/lib/python3.12/site-packages (from geopandas->geost) (0.12.1)
Requirement already satisfied: packaging in /home/docs/checkouts/readthedocs.org/user_builds/hydropandas/envs/latest/lib/python3.12/site-packages (from geopandas->geost) (26.2)
Requirement already satisfied: python-dateutil>=2.8.2 in /home/docs/checkouts/readthedocs.org/user_builds/hydropandas/envs/latest/lib/python3.12/site-packages (from pandas->geost) (2.9.0.post0)
Requirement already satisfied: pytz>=2020.1 in /home/docs/checkouts/readthedocs.org/user_builds/hydropandas/envs/latest/lib/python3.12/site-packages (from pandas->geost) (2026.2)
Requirement already satisfied: tzdata>=2022.7 in /home/docs/checkouts/readthedocs.org/user_builds/hydropandas/envs/latest/lib/python3.12/site-packages (from pandas->geost) (2026.2)
Requirement already satisfied: certifi in /home/docs/checkouts/readthedocs.org/user_builds/hydropandas/envs/latest/lib/python3.12/site-packages (from pyogrio>=0.7.2->geopandas->geost) (2026.5.20)
Requirement already satisfied: six>=1.5 in /home/docs/checkouts/readthedocs.org/user_builds/hydropandas/envs/latest/lib/python3.12/site-packages (from python-dateutil>=2.8.2->pandas->geost) (1.17.0)
Requirement already satisfied: et-xmlfile in /home/docs/checkouts/readthedocs.org/user_builds/hydropandas/envs/latest/lib/python3.12/site-packages (from openpyxl->geost) (2.0.0)
Requirement already satisfied: pydantic in /home/docs/checkouts/readthedocs.org/user_builds/hydropandas/envs/latest/lib/python3.12/site-packages (from pandera->geost) (2.13.4)
Collecting typeguard (from pandera->geost)
Downloading typeguard-4.5.2-py3-none-any.whl.metadata (3.8 kB)
Requirement already satisfied: typing_extensions in /home/docs/checkouts/readthedocs.org/user_builds/hydropandas/envs/latest/lib/python3.12/site-packages (from pandera->geost) (4.15.0)
Collecting typing_inspect>=0.6.0 (from pandera->geost)
Downloading typing_inspect-0.9.0-py3-none-any.whl.metadata (1.5 kB)
Collecting mypy-extensions>=0.3.0 (from typing_inspect>=0.6.0->pandera->geost)
Downloading mypy_extensions-1.1.0-py3-none-any.whl.metadata (1.1 kB)
Requirement already satisfied: platformdirs>=2.5.0 in /home/docs/checkouts/readthedocs.org/user_builds/hydropandas/envs/latest/lib/python3.12/site-packages (from pooch->geost) (4.10.0)
Requirement already satisfied: requests>=2.19.0 in /home/docs/checkouts/readthedocs.org/user_builds/hydropandas/envs/latest/lib/python3.12/site-packages (from pooch->geost) (2.34.2)
Requirement already satisfied: charset_normalizer<4,>=2 in /home/docs/checkouts/readthedocs.org/user_builds/hydropandas/envs/latest/lib/python3.12/site-packages (from requests>=2.19.0->pooch->geost) (3.4.7)
Requirement already satisfied: idna<4,>=2.5 in /home/docs/checkouts/readthedocs.org/user_builds/hydropandas/envs/latest/lib/python3.12/site-packages (from requests>=2.19.0->pooch->geost) (3.18)
Requirement already satisfied: urllib3<3,>=1.26 in /home/docs/checkouts/readthedocs.org/user_builds/hydropandas/envs/latest/lib/python3.12/site-packages (from requests>=2.19.0->pooch->geost) (2.7.0)
Requirement already satisfied: annotated-types>=0.6.0 in /home/docs/checkouts/readthedocs.org/user_builds/hydropandas/envs/latest/lib/python3.12/site-packages (from pydantic->pandera->geost) (0.7.0)
Requirement already satisfied: pydantic-core==2.46.4 in /home/docs/checkouts/readthedocs.org/user_builds/hydropandas/envs/latest/lib/python3.12/site-packages (from pydantic->pandera->geost) (2.46.4)
Requirement already satisfied: typing-inspection>=0.4.2 in /home/docs/checkouts/readthedocs.org/user_builds/hydropandas/envs/latest/lib/python3.12/site-packages (from pydantic->pandera->geost) (0.4.2)
Collecting cyclopts>=4.0.0 (from pyvista->geost)
Downloading cyclopts-4.17.0-py3-none-any.whl.metadata (12 kB)
Requirement already satisfied: matplotlib>=3.0.1 in /home/docs/checkouts/readthedocs.org/user_builds/hydropandas/envs/latest/lib/python3.12/site-packages (from pyvista->geost) (3.10.9)
Requirement already satisfied: pillow in /home/docs/checkouts/readthedocs.org/user_builds/hydropandas/envs/latest/lib/python3.12/site-packages (from pyvista->geost) (12.2.0)
Collecting scooby>=0.5.1 (from pyvista->geost)
Downloading scooby-0.11.2-py3-none-any.whl.metadata (16 kB)
Collecting vtk!=9.4.0 (from pyvista->geost)
Downloading vtk-9.6.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.metadata (5.6 kB)
Requirement already satisfied: attrs>=23.1.0 in /home/docs/checkouts/readthedocs.org/user_builds/hydropandas/envs/latest/lib/python3.12/site-packages (from cyclopts>=4.0.0->pyvista->geost) (26.1.0)
Collecting docstring-parser<4.0,>=0.15 (from cyclopts>=4.0.0->pyvista->geost)
Downloading docstring_parser-0.18.0-py3-none-any.whl.metadata (3.5 kB)
Collecting rich-rst<3.0.0,>=1.3.1 (from cyclopts>=4.0.0->pyvista->geost)
Downloading rich_rst-2.0.1-py3-none-any.whl.metadata (4.6 kB)
Requirement already satisfied: rich>=13.6.0 in /home/docs/checkouts/readthedocs.org/user_builds/hydropandas/envs/latest/lib/python3.12/site-packages (from cyclopts>=4.0.0->pyvista->geost) (15.0.0)
Requirement already satisfied: pygments>=2.0.0 in /home/docs/checkouts/readthedocs.org/user_builds/hydropandas/envs/latest/lib/python3.12/site-packages (from rich-rst<3.0.0,>=1.3.1->cyclopts>=4.0.0->pyvista->geost) (2.20.0)
Requirement already satisfied: contourpy>=1.0.1 in /home/docs/checkouts/readthedocs.org/user_builds/hydropandas/envs/latest/lib/python3.12/site-packages (from matplotlib>=3.0.1->pyvista->geost) (1.3.3)
Requirement already satisfied: cycler>=0.10 in /home/docs/checkouts/readthedocs.org/user_builds/hydropandas/envs/latest/lib/python3.12/site-packages (from matplotlib>=3.0.1->pyvista->geost) (0.12.1)
Requirement already satisfied: fonttools>=4.22.0 in /home/docs/checkouts/readthedocs.org/user_builds/hydropandas/envs/latest/lib/python3.12/site-packages (from matplotlib>=3.0.1->pyvista->geost) (4.63.0)
Requirement already satisfied: kiwisolver>=1.3.1 in /home/docs/checkouts/readthedocs.org/user_builds/hydropandas/envs/latest/lib/python3.12/site-packages (from matplotlib>=3.0.1->pyvista->geost) (1.5.0)
Requirement already satisfied: pyparsing>=3 in /home/docs/checkouts/readthedocs.org/user_builds/hydropandas/envs/latest/lib/python3.12/site-packages (from matplotlib>=3.0.1->pyvista->geost) (3.3.2)
Requirement already satisfied: markdown-it-py>=2.2.0 in /home/docs/checkouts/readthedocs.org/user_builds/hydropandas/envs/latest/lib/python3.12/site-packages (from rich>=13.6.0->cyclopts>=4.0.0->pyvista->geost) (4.2.0)
Requirement already satisfied: mdurl~=0.1 in /home/docs/checkouts/readthedocs.org/user_builds/hydropandas/envs/latest/lib/python3.12/site-packages (from markdown-it-py>=2.2.0->rich>=13.6.0->cyclopts>=4.0.0->pyvista->geost) (0.1.2)
Requirement already satisfied: rasterio>=1.4.3 in /home/docs/checkouts/readthedocs.org/user_builds/hydropandas/envs/latest/lib/python3.12/site-packages (from rioxarray->geost) (1.5.0)
Requirement already satisfied: xarray>=2026.2 in /home/docs/checkouts/readthedocs.org/user_builds/hydropandas/envs/latest/lib/python3.12/site-packages (from rioxarray->geost) (2026.4.0)
Requirement already satisfied: affine in /home/docs/checkouts/readthedocs.org/user_builds/hydropandas/envs/latest/lib/python3.12/site-packages (from rasterio>=1.4.3->rioxarray->geost) (2.4.0)
Requirement already satisfied: click!=8.2.*,>=4.0 in /home/docs/checkouts/readthedocs.org/user_builds/hydropandas/envs/latest/lib/python3.12/site-packages (from rasterio>=1.4.3->rioxarray->geost) (8.4.1)
Requirement already satisfied: cligj>=0.5 in /home/docs/checkouts/readthedocs.org/user_builds/hydropandas/envs/latest/lib/python3.12/site-packages (from rasterio>=1.4.3->rioxarray->geost) (0.7.2)
Downloading geost-0.3.0-py3-none-any.whl (88 kB)
Downloading pandera-0.31.1-py3-none-any.whl (386 kB)
Downloading typing_inspect-0.9.0-py3-none-any.whl (8.8 kB)
Downloading mypy_extensions-1.1.0-py3-none-any.whl (5.0 kB)
Downloading pyvista-0.48.4-py3-none-any.whl (2.6 MB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2.6/2.6 MB 18.2 MB/s 0:00:00
Downloading vtk-9.6.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (146.0 MB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 146.0/146.0 MB 141.6 MB/s 0:00:01
Downloading cyclopts-4.17.0-py3-none-any.whl (219 kB)
Downloading docstring_parser-0.18.0-py3-none-any.whl (22 kB)
Downloading rich_rst-2.0.1-py3-none-any.whl (272 kB)
Downloading scooby-0.11.2-py3-none-any.whl (20 kB)
Downloading rioxarray-0.22.0-py3-none-any.whl (72 kB)
Downloading typeguard-4.5.2-py3-none-any.whl (36 kB)
Downloading xlrd-2.0.2-py2.py3-none-any.whl (96 kB)
Installing collected packages: xlrd, typeguard, scooby, mypy-extensions, docstring-parser, typing_inspect, vtk, rich-rst, pandera, rioxarray, cyclopts, pyvista, geost
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 13/13 [geost]
Successfully installed cyclopts-4.17.0 docstring-parser-0.18.0 geost-0.3.0 mypy-extensions-1.1.0 pandera-0.31.1 pyvista-0.48.4 rich-rst-2.0.1 rioxarray-0.22.0 scooby-0.11.2 typeguard-4.5.2 typing_inspect-0.9.0 vtk-9.6.2 xlrd-2.0.2
[6]:
import geost
/home/docs/checkouts/readthedocs.org/user_builds/hydropandas/envs/latest/lib/python3.12/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
from .autonotebook import tqdm as notebook_tqdm
[7]:
# get Bodemkundig booronderzoek (BHR-GT)
boreholes = geost.bro_api_read("BHR-GT", bbox=[int(i) for i in p.buffer(1000).bounds])
[8]:
# add colors based on column 'standard_name'
# specify colors
colors = brodata.plot.lithology_colors.copy()
colors_rgb = {}
for soil, color in colors.items():
colors_rgb[soil] = tuple((np.array(color) * 255).astype(int))
colors_rgb["sterkZandigSilt"] = (243, 225, 100)
colors_rgb["siltigZandMetGrind"] = (220, 200, 80)
colors_rgb["siltigZand"] = (243, 225, 150)
colors_rgb["zwakGrindigZand"] = (231, 210, 70)
col = "geotechnicalSoilName" # "standardSoilName"
boreholes.data["color"] = boreholes.data[col].map(colors_rgb)
# plot borehole data
f, axes = plt.subplots(figsize=(16, 8), ncols=2)
labels = []
for i, (xy, df) in enumerate(boreholes.data.groupby("x")):
df["top"] = df["surface"] - df["top"]
df["bottom"] = df["surface"] - df["bottom"]
for _, row in df.iterrows():
color = [c / 255 for c in row["color"]] + [1.0]
if row[col] in labels:
axes[0].fill_between(
[i - 0.25, i + 0.25],
[row["top"], row["top"]],
[row["bottom"], row["bottom"]],
color=color,
)
else:
axes[0].fill_between(
[i - 0.25, i + 0.25],
[row["top"], row["top"]],
[row["bottom"], row["bottom"]],
color=color,
label=row[col],
)
labels.append(row[col])
axes[0].set_xticks(np.arange(0, i + 1), boreholes.data.sort_values("x")["nr"].unique())
# Rotate and align the xtick labels
for label in axes[0].get_xticklabels():
label.set_rotation(45)
label.set_ha("right")
if (boreholes.header.vertical_datum == "NAP").all():
axes[0].set_ylabel("m NAP")
axes[0].set_xlim(-0.5, i + 1.5)
axes[0].legend()
axes[0].set_title(f"Bodemkundig booronderzoek close to {gw_bro.name}")
# plot map
# plot well
axes[1].scatter(gw_bro.x, gw_bro.y, label=gw_bro.name, color="black")
# plot boreholes
for i, dfnr in boreholes.data.groupby(["x", "y"]):
axes[1].scatter(*i, label=dfnr["nr"].unique()[0])
# settings
axes[1].set_xlim(extent[:2])
axes[1].set_ylim(extent[2:])
axes[1].legend()
# basemap
ctx.add_basemap(axes[1], crs=28992)
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
Cell In[8], line 19
15
16 # plot borehole data
17 f, axes = plt.subplots(figsize=(16, 8), ncols=2)
18 labels = []
---> 19 for i, (xy, df) in enumerate(boreholes.data.groupby("x")):
20 df["top"] = df["surface"] - df["top"]
21 df["bottom"] = df["surface"] - df["bottom"]
22 for _, row in df.iterrows():
AttributeError: 'LayeredData' object has no attribute 'groupby'