Skip to content

Commit 4a9110d

Browse files
committed
Update a2 GEF example for new bedrock version
1 parent bd0ecdb commit 4a9110d

21 files changed

+105
-109
lines changed

examples/a2-maastricht-gef/a2_maastricht_gef.py renamed to examples/a2_maastricht_gef/a2_maastricht_gef_to_brgi_geodb.py

Lines changed: 105 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,22 @@
11
# /// script
22
# requires-python = ">=3.12"
33
# dependencies = [
4-
# "bedrock-ge==0.2.3",
5-
# "chardet==5.2.0",
6-
# "folium==0.19.5",
7-
# "geopandas==1.0.1",
8-
# "mapclassify==2.8.1",
4+
# "bedrock-ge==0.3.1",
5+
# "folium==0.20.0",
6+
# "geopandas==1.1.0",
7+
# "mapclassify==2.9.0",
98
# "marimo",
109
# "matplotlib==3.10.1",
11-
# "pandas==2.2.3",
1210
# "pyproj==3.7.1",
1311
# "requests==2.32.3",
14-
# "shapely==2.1.0",
12+
# "shapely==2.1.1",
1513
# "pygef"==0.11.1"
1614
# ]
1715
# ///
1816

1917
import marimo
2018

21-
__generated_with = "0.13.11"
19+
__generated_with = "0.14.7"
2220
app = marimo.App(width="medium")
2321

2422

@@ -28,18 +26,23 @@ def _(mo):
2826
"""
2927
# GEF Data for A2 Tunnel Maastricht
3028
31-
This notebook demonstrates how to
29+
This notebook demonstrates how to
3230
33-
1. Read in Ground Investigation (GI) data from [GEF files]() using [pygef](https://cemsbv.github.io/pygef/)
34-
1. Use `bedrock-ge` to convert that data into a standardized GI database using `bedrock-ge`
31+
1. Read in Ground Investigation (GI) data from [GEF files]() using the [pygef](https://cemsbv.github.io/pygef/) library.
32+
1. Use `bedrock-ge` to convert that data into a standardized GI database.
3533
1. Transform the GI data into 3D spatial features with proper coordinates and geometry ([OGC Simple Feature](https://en.wikipedia.org/wiki/Simple_Features))
36-
1. Explore and analyze the GI data using interactive filtering with Pandas DataFrames and interactive visualization on a map with GeoPandas.
37-
1. Export the processed GI database to a GeoPackage file for use in other software, like QGIS.
34+
1. Explore and analyze the GI data using interactive filtering with Pandas DataFrames and interactive visualization on a map using GeoPandas.
35+
1. Export the processed GI database to a GeoPackage file for use in GIS software.
3836
3937
<details>
4038
<summary>What are GEF files?</summary>
4139
<p>
42-
<abbr>Geotechnical Exchange Format (GEF)</abbr> is a standardized, text-based format designed to facilitate the reliable exchange and archiving of geotechnical investigation data, particularly CPT results, across different organizations and software platforms. GEF can also be used for other types of soil tests and borehole data. It is widely used in the Netherlands in ground investigration.
40+
<abbr>Geotechnical Exchange Format (GEF)</abbr> is a standardized,
41+
text-based format designed to facilitate the reliable exchange and archiving
42+
of geotechnical investigation data, particularly CPT results, across
43+
different organizations and software platforms. GEF can also be used for
44+
other types of soil tests and borehole data. It is widely used in the
45+
Netherlands in ground investigation.
4346
</p>
4447
</details>
4548
@@ -52,15 +55,15 @@ def _(mo):
5255
5356
## Context
5457
55-
The Koning Willem-Alexander Tunnel is a double-deck tunnel for motorized traffic in the Dutch city of Maastricht. The tunnel has a length of 2.5 kilometers (lower tunnel tubes) and 2.3 kilometers (upper tunnel tubes).
58+
The Koning Willem-Alexander Tunnel is a double-deck tunnel for motorized traffic in the city Maastricht, the Netherlands. The tunnel has a length of 2.5 kilometers (lower tunnel tubes) and 2.3 kilometers (upper tunnel tubes).
5659
5760
The tunnel has moved the old A2 highway underground. This highway previously formed a barrier for the city and slowed traffic.
5861
5962
### Geology
6063
61-
The uppermost layer consists of topsoil, clay, and loam, with a thickness of about 2 to 4 meters. These soft Holocene deposits are attributed to the Boxtel Formation, laid down by the Meuse River, as the tunnel is situated in a former river arm.
64+
The uppermost layer consists of topsoil, clay, and loam, with a thickness of about 2 to 4 meters. These soft Holocene deposits are attributed to the Boxtel Formation, laid down by the Meuse River. The tunnel is situated in a former river arm.
6265
63-
Beneath the surface layer lies an approximately 8-meter-thick gravel deposit. This gravel acts as a significant aquifer and was a key factor in the groundwater management strategies required for the tunnel construction.
66+
Beneath the surface layer lies an approximately 8-m thick gravel deposit. This gravel acts as a significant aquifer and was a key factor in the groundwater management strategies required for the tunnel construction.
6467
6568
Below the gravel lies a fissured limestone layer belonging to the Maastricht Formation (mergel). This layer is a very weak, porous, sandy, shallow marine limestone, often weathered, and includes chalk and calcarenite components.
6669
@@ -81,7 +84,7 @@ def _(mo):
8184
8285
## Ground Investigation Data
8386
84-
The GI data was downloaded from [Dinoloket](https://www.dinoloket.nl/ondergrondgegevens), a platform where you can view and request data and models from TNO and BRO about the subsurface of the Netherlands.
87+
The GI data was downloaded from [Dinoloket](https://www.dinoloket.nl/ondergrondgegevens), a platform where you can view and request data and models from the Dutch Geological Survey and Basisregistratie Ondergrond about the subsurface of the Netherlands.
8588
"""
8689
)
8790
return
@@ -133,13 +136,13 @@ def _(mo):
133136
r"""
134137
## Converting multiple GEF files to a relational database
135138
136-
Rather than dealing with a folder of files, we would like to combine all files into a single database with spatial information. This is where Bedrock comes in.
139+
Rather than dealing with a folder of files in a format that very few software can handle, we would like to combine all of these files into a single database with spatial information. This is where `bedrock-ge` comes in.
137140
138141
### Relational Databases
139142
140143
A [relational database](https://observablehq.com/blog/databases-101-basics-data-analysts#what-are-relational-databases) is a database with multiple tables that are linked to each other with relations. This type of database is ideal for storing GI data, given its [hierarchical structure](https://bedrock.engineer/docs/#hierarchical-nature-of-gi-data).
141144
142-
In Python it's convenient to represent a relational database as a dictionary of DataFrame's.
145+
In Python it's convenient to represent a relational database as a dictionary of DataFrames.
143146
144147
### Coordinated Reference System (CRS)
145148
@@ -150,18 +153,12 @@ def _(mo):
150153

151154

152155
@app.cell
153-
def _(boreholes):
156+
def _(CRS, boreholes):
154157
code = {bore.delivered_location.srs_name for bore in boreholes}.pop()
155158
orig_epsg_code = code.split("EPSG::")[-1]
156-
orig_crs = f"EPSG:{orig_epsg_code}"
159+
orig_crs = CRS(f"EPSG:{orig_epsg_code}")
157160
orig_crs
158-
return
159-
160-
161-
@app.cell
162-
def _():
163-
crs = "EPSG:7415"
164-
return (crs,)
161+
return (orig_crs,)
165162

166163

167164
@app.cell(hide_code=True)
@@ -170,16 +167,22 @@ def _(mo):
170167
r"""
171168
The data is in EPSG:28992, which is the [Rijksdriehoekscoördinaten (NL)](https://nl.wikipedia.org/wiki/Rijksdriehoeksco%C3%B6rdinaten) system, also called "Amersfoort / RD New". This reference system does not include elevation.
172169
173-
To represent GI data spatially in 3D geometry we need a CRS with elevation. That's why we will use
174-
EPSG:7415 Amersfoort / RD New + NAP height.
170+
To represent GI data spatially in 3D geometry we need a CRS **with elevation**. That's why we will use
171+
EPSG:5709 NAP height as the vertical CS.
175172
"""
176173
)
177174
return
178175

179176

180177
@app.cell
181-
def _():
182-
wgs = "EPSG:4326"
178+
def _(CRS):
179+
vertical_crs = CRS("EPSG:5709")
180+
return (vertical_crs,)
181+
182+
183+
@app.cell
184+
def _(CRS):
185+
wgs = CRS("EPSG:4326")
183186
return
184187

185188

@@ -190,33 +193,15 @@ def _():
190193

191194

192195
@app.cell
193-
def _(CRS, crs, pd, project_uid):
196+
def _(orig_crs, pd, project_uid, vertical_crs):
194197
project = pd.DataFrame({
195198
"project_uid": [project_uid], # primary key
196-
"crs_wkt": CRS(crs).to_wkt()
199+
"horizontal_crs_wkt": orig_crs.to_wkt(),
200+
"vertical_crs_wkt": vertical_crs.to_wkt(),
197201
})
198202
return (project,)
199203

200204

201-
@app.cell
202-
def _(insitu_geo, locations, project):
203-
brgi_db = {"Project": project, "Location": locations.drop(columns=["data"]), "InSitu_GEOL": insitu_geo }
204-
return (brgi_db,)
205-
206-
207-
@app.function
208-
def process_data(bore):
209-
df = bore.data.to_pandas().dropna(axis=1, how='all').rename(columns=
210-
{
211-
'upperBoundary': 'depth_to_top',
212-
'lowerBoundary': 'depth_to_base',
213-
'upperBoundaryOffset': 'elevation_at_top',
214-
'lowerBoundaryOffset': 'elevation_at_base'
215-
})
216-
217-
return df
218-
219-
220205
@app.cell(hide_code=True)
221206
def _(mo):
222207
mo.md(
@@ -250,78 +235,79 @@ def _(boreholes, pd, project_uid):
250235
return (locations_df,)
251236

252237

253-
@app.cell
254-
def _(calculate_location_gis_geometry, crs, locations_df):
255-
locations = calculate_location_gis_geometry(locations_df, crs=crs)
256-
return (locations,)
257-
258-
259-
@app.cell(hide_code=True)
260-
def _(mo):
261-
mo.md(
262-
r"""
263-
## Displaying the GI locations on a map
264-
265-
Rather than multiple tables (DataFrames) and soil profiles, we would like see an overview of what this ground investigation covers. It's **spatial** data after all, let's view it in a spatial context.
266-
267-
### Web Mapping Caveats
268-
269-
Web-mapping tools are rarely capable of handling geometry in non-WGS84 coordinates. Additionally, vertical lines are not visible when looking at a map from straight above. That's why use `create_lon_lat_height_table` to create points in the WGS84 CRS so we can view the locations of the boreholes.
270-
"""
271-
)
272-
return
273-
274-
275-
@app.cell
276-
def _(create_lon_lat_height_table, crs, locations):
277-
create_lon_lat_height_table(locations, crs).explore(marker_kwds={"radius":5})
278-
return
279-
280-
281238
@app.cell
282239
def _(mo):
283240
mo.md(r"""Here we create a DataFrame for the In-Situ data of all locations. To relate the in-situ data to locations and the project, we add foreign keys.""")
284241
return
285242

286243

287244
@app.cell
288-
def _(locations, pd):
245+
def _(locations_df, pd):
289246
insitu = pd.DataFrame([
290247
{
291248
**layer,
292249
"location_uid": location["location_uid"], # foreignkey
293250
"project_uid": location["project_uid"], # foreignkey
294251
}
295252
# Outer loop: iterate through each location
296-
for location in locations.to_dict('records')
253+
for location in locations_df.to_dict('records')
297254
# Inner loop: iterate through each layer in the location's data dataframe
298255
for layer in location["data"].to_dict('records')
299256
])
257+
insitu
300258
return (insitu,)
301259

302260

303261
@app.cell
304-
def _(insitu):
305-
insitu
306-
return
262+
def _(BedrockGIDatabase, insitu, locations_df, project):
263+
brgi_db = BedrockGIDatabase(
264+
Project=project,
265+
Location=locations_df.drop(columns=["data"]),
266+
InSituTests={"interpretation":insitu},
267+
)
268+
brgi_db
269+
return (brgi_db,)
270+
271+
272+
@app.cell
273+
def _(brgi_db, create_brgi_geodb):
274+
brgi_geodb = create_brgi_geodb(brgi_db)
275+
return (brgi_geodb,)
276+
277+
278+
@app.function
279+
def process_data(bore):
280+
df = bore.data.to_pandas().dropna(axis=1, how='all').rename(columns=
281+
{
282+
'upperBoundary': 'depth_to_top',
283+
'lowerBoundary': 'depth_to_base',
284+
'upperBoundaryOffset': 'elevation_at_top',
285+
'lowerBoundaryOffset': 'elevation_at_base'
286+
})
287+
288+
return df
307289

308290

309291
@app.cell(hide_code=True)
310292
def _(mo):
311-
mo.md(r"""In-situ data is also spatial data. It has a location, a depth and a height. We can also turn it into spatial data using Bedrock's `calculate_in_situ_gis_geometry`""")
312-
return
293+
mo.md(
294+
r"""
295+
## Displaying the GI locations on a map
313296
297+
Rather than multiple tables (DataFrames) and soil profiles, we would like see an overview of what this ground investigation covers. It's **spatial** data after all, let's view it in a spatial context.
314298
315-
@app.cell
316-
def _(calculate_in_situ_gis_geometry, crs, insitu, locations):
317-
insitu_geo = calculate_in_situ_gis_geometry(insitu, locations, crs)
318-
insitu_geo.index
319-
return (insitu_geo,)
299+
`create_brgi_geodb` creates a `LonLatHeight` table which contains the GI locations at ground level in WGS84 - World Geodetic System 1984 - EPSG:4326 coordinates (Longitude, Latitude, Ellipsoidal Height).
300+
301+
302+
The reason for creating the `LonLatHeight` table is that vertical lines in projected Coordinate Reference Systems (CRS) are often not rendered nicely by default in all web-mapping software. Vertical lines are often not visible when looking at a map from above, and not all web-mapping software is capable of handling geometry in non-WGS84, i.e. (Lon, Lat) coordinates.
303+
"""
304+
)
305+
return
320306

321307

322308
@app.cell
323-
def _(insitu_geo):
324-
insitu_geo
309+
def _(brgi_geodb):
310+
brgi_geodb.LonLatHeight.explore(marker_kwds={"radius":5})
325311
return
326312

327313

@@ -331,7 +317,7 @@ def _(mo):
331317
r"""
332318
## Saving the GI geospatial database as a GeoPackage (.gpkg)
333319
334-
Finally, we'll write it to an actual geospatial database file, a GeoPackage, so we can share our GI data with others, for example, to reuse it in other computational notebooks, create dashboards, access the GI data in QGIS or ArcGIS, and more...
320+
Finally, we'll write it to an actual geospatial database file, a [GeoPackage](https://www.geopackage.org/), so we can share our GI data with others, for example, to reuse it in other computational notebooks, create dashboards, access the GI data in QGIS or ArcGIS, and more...
335321
336322
A GeoPackage is an <abbr title="Open Geospatial Consortium">OGC-standardized</abbr> extension of SQLite (a relational database in a single file, .sqlite or .db) that allows you to store any type of GIS data (both raster as well as vector data) in a single file that has the .gpkg extension. Therefore, many (open-source) GIS software packages support GeoPackage!
337323
"""
@@ -340,14 +326,14 @@ def _(mo):
340326

341327

342328
@app.cell
343-
def _(brgi_db, check_brgi_database):
344-
check_brgi_database(brgi_db)
329+
def _(brgi_db, check_brgi_geodb):
330+
check_brgi_geodb(brgi_db)
345331
return
346332

347333

348334
@app.cell
349-
def _(brgi_db, write_gi_db_to_gpkg):
350-
write_gi_db_to_gpkg(brgi_db, gpkg_path="./output/A2_Maastricht.gpkg")
335+
def _(Path, brgi_db, write_brgi_db_to_file):
336+
write_brgi_db_to_file(brgi_db, path=Path("./output/A2_Maastricht.gpkg"), driver="GPKG")
351337
return
352338

353339

@@ -364,23 +350,33 @@ def _():
364350
import folium
365351
import mapclassify
366352
from shapely.geometry import Point, LineString
367-
from bedrock_ge.gi.gis_geometry import calculate_in_situ_gis_geometry, calculate_gis_geometry, calculate_location_gis_geometry, create_lon_lat_height_table
368-
from bedrock_ge.gi.write import write_gi_db_to_gpkg
369-
from bedrock_ge.gi.validate import check_brgi_database
353+
354+
from bedrock_ge.gi.schemas import BedrockGIDatabase
355+
from bedrock_ge.gi.db_operations import merge_dbs
356+
from bedrock_ge.gi.geospatial import create_brgi_geodb
357+
from bedrock_ge.gi.io_utils import geodf_to_df
358+
from bedrock_ge.gi.validate import check_brgi_geodb
359+
from bedrock_ge.gi.mapper import map_to_brgi_db
360+
from bedrock_ge.gi.write import write_brgi_db_to_file
370361
from pyproj import CRS
371362
from typing import Dict, Tuple, Union
372363
return (
364+
BedrockGIDatabase,
373365
CRS,
374366
Path,
375-
calculate_in_situ_gis_geometry,
376-
calculate_location_gis_geometry,
377-
check_brgi_database,
378-
create_lon_lat_height_table,
367+
check_brgi_geodb,
368+
create_brgi_geodb,
379369
mo,
380370
pd,
381371
pygef,
382-
write_gi_db_to_gpkg,
372+
write_brgi_db_to_file,
383373
)
384374

375+
376+
@app.cell
377+
def _():
378+
return
379+
380+
385381
if __name__ == "__main__":
386382
app.run()
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

0 commit comments

Comments
 (0)