Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
"xy_coordinate_resolution": 0.01,
"bbox": [ 107080.87, 521548.54, 107453.90, 521643.63 ],
"features": [
{ "type": "Feature", "properties": { "name": "drain_dwk", "elevation": 0.73, "conductance_per_meter": 1.0, "conductance_per_squared_meter": null, "bgt-identificatie": "", "mover_lake_name": "", "comment": "wordt met pomp bemalen" }, "bbox": [ 107080.87, 521548.54, 107253.51, 521590.54 ], "geometry": { "type": "LineString", "coordinates": [ [ 107080.87, 521548.54 ], [ 107253.51, 521590.54 ] ] } },
{ "type": "Feature", "properties": { "name": "drain_pomgebouw_ZUID", "elevation": -0.62, "conductance_per_meter": 1.0, "conductance_per_squared_meter": null, "bgt-identificatie": "", "mover_lake_name": "", "comment": "via put uitloop op -0.5mNAP" }, "bbox": [ 107341.82, 521617.28, 107406.43, 521639.18 ], "geometry": { "type": "LineString", "coordinates": [ [ 107342.3, 521639.18 ], [ 107341.82, 521617.28 ], [ 107394.21, 521617.91 ], [ 107406.43, 521627.28 ] ] } },
{ "type": "Feature", "properties": { "name": "drain_pomgebouw_NRD", "elevation": -0.8, "conductance_per_meter": 1.0, "conductance_per_squared_meter": null, "bgt-identificatie": "", "mover_lake_name": "", "comment": "via put uitloop op -0.5mNAP" }, "bbox": [ 107347.06, 521635.69, 107407.07, 521643.63 ], "geometry": { "type": "LineString", "coordinates": [ [ 107347.06, 521643.15 ], [ 107398.49, 521643.63 ], [ 107407.07, 521635.69 ] ] } },
{ "type": "Feature", "properties": { "name": "drain_dienstgebouw", "elevation": -0.844, "conductance_per_meter": 1.0, "conductance_per_squared_meter": null, "bgt-identificatie": "", "mover_lake_name": "", "comment": "uitloop onder water op -0.5mNAP" }, "bbox": [ 107422.94, 521619.66, 107453.9, 521636.01 ], "geometry": { "type": "LineString", "coordinates": [ [ 107453.9, 521636.01 ], [ 107453.74, 521620.29 ], [ 107436.12, 521619.66 ], [ 107422.94, 521629.18 ] ] } },
{ "type": "Feature", "properties": { "name": "drain_dwk", "elevation": 0.73, "conductance_per_meter": 1.0, "conductance_per_squared_meter": null, "bgt-identificatie": "", "mover_lake_name": "vijver 1, 2 en 3", "comment": "wordt met pomp bemalen" }, "bbox": [ 107080.87, 521548.54, 107253.51, 521590.54 ], "geometry": { "type": "LineString", "coordinates": [ [ 107080.87, 521548.54 ], [ 107253.51, 521590.54 ] ] } },
{ "type": "Feature", "properties": { "name": "drain_pomgebouw_ZUID", "elevation": -0.5, "conductance_per_meter": 1.0, "conductance_per_squared_meter": null, "bgt-identificatie": "", "mover_lake_name": "vijver 4", "comment": "via put uitloop op -0.5mNAP" }, "bbox": [ 107341.82, 521617.28, 107406.43, 521639.18 ], "geometry": { "type": "LineString", "coordinates": [ [ 107342.3, 521639.18 ], [ 107341.82, 521617.28 ], [ 107394.21, 521617.91 ], [ 107406.43, 521627.28 ] ] } },
{ "type": "Feature", "properties": { "name": "drain_pomgebouw_NRD", "elevation": -0.5, "conductance_per_meter": 1.0, "conductance_per_squared_meter": null, "bgt-identificatie": "", "mover_lake_name": "vijver 4", "comment": "via put uitloop op -0.5mNAP" }, "bbox": [ 107347.06, 521635.69, 107407.07, 521643.63 ], "geometry": { "type": "LineString", "coordinates": [ [ 107347.06, 521643.15 ], [ 107398.49, 521643.63 ], [ 107407.07, 521635.69 ] ] } },
{ "type": "Feature", "properties": { "name": "drain_dienstgebouw", "elevation": -0.5, "conductance_per_meter": 1.0, "conductance_per_squared_meter": null, "bgt-identificatie": "", "mover_lake_name": "vijver 4", "comment": "uitloop onder water op -0.5mNAP" }, "bbox": [ 107422.94, 521619.66, 107453.9, 521636.01 ], "geometry": { "type": "LineString", "coordinates": [ [ 107453.9, 521636.01 ], [ 107453.74, 521620.29 ], [ 107436.12, 521619.66 ], [ 107422.94, 521629.18 ] ] } },
{ "type": "Feature", "properties": { "name": "bassin", "elevation": -0.5, "conductance_per_meter": null, "conductance_per_squared_meter": 1.0, "bgt-identificatie": "", "mover_lake_name": "vijver 4", "comment": "" }, "bbox": [ 107403.66, 521624.72, 107425.88, 521641.05 ], "geometry": { "type": "Polygon", "coordinates": [ [ [ 107403.66, 521638.85 ], [ 107409.53, 521640.66 ], [ 107425.54, 521641.05 ], [ 107425.88, 521627.06 ], [ 107423.49, 521624.98 ], [ 107412.31, 521624.72 ], [ 107404.06, 521625.46 ], [ 107403.66, 521638.85 ] ] ] } }
]
}
13 changes: 11 additions & 2 deletions src/nhflodata/data/mockup/drains_pwn/v1.0.0/drains_pwn.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,24 @@
gdf_dir = get_abs_data_path("drains_en_damwand_bergen")
gdf = gpd.read_file(gdf_dir / "drains_en_damwand_bergen.geojson")
gdf = gdf.loc[gdf.naam.str.startswith("drain")]
gdf["elevation"] = gdf[["diepte_van", "diepte_tot"]].min(axis=1)
gdf = gdf.rename(columns={"naam": "name", "opmerking": "comment"})

# Drain stage = the controlling drainage (outlet/weir) level, following the Bergen model
# (04v2pwnbergenmodel). This is the DRN elevation, NOT the pipe invert min(diepte_van, diepte_tot).
# The pump-building drains discharge via an outlet ("uitloop") at -0.5 m NAP (see the 'comment'
# field), while drain_dwk is pumped and discharges at +0.73 m NAP.
gdf["elevation"] = -0.5
gdf.loc[gdf["name"] == "drain_dwk", "elevation"] = 0.73

# Add conductances
# m3/day per meter head difference per meter drain length
gdf["conductance_per_meter"] = 1.0 # Default conductance
gdf["conductance_per_squared_meter"] = np.nan # Default conductance for open water
gdf["bgt-identificatie"] = "" # Placeholder for BGT identification
gdf["mover_lake_name"] = "" # Placeholder for mover lake boundary name, to move drained water to.
# Route drained water to a lake via the MVR (mover) package, following the Bergen model:
# drain_dwk -> "vijver 1, 2 en 3"; the pump-building drains -> "vijver 4".
gdf["mover_lake_name"] = "vijver 4"
gdf.loc[gdf["name"] == "drain_dwk", "mover_lake_name"] = "vijver 1, 2 en 3"
gdf = gdf[columns_to_keep]

# %% Add open water drainage
Expand Down
11 changes: 11 additions & 0 deletions src/nhflodata/data/mockup/hfb_pwn/v1.0.0/hfb_pwn.geojson
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"type": "FeatureCollection",
"name": "hfb_pwn",
"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:EPSG::28992" } },
"xy_coordinate_resolution": 0.01,
"bbox": [ 107034.61, 521536.91, 107511.14, 521642.41 ],
"features": [
{ "type": "Feature", "properties": { "name": "damwand_pompgebouw", "hydchr": 0.01, "depth": 5.0, "elevation": null, "comment": "" }, "bbox": [ 107280.34, 521589.56, 107511.14, 521619.63 ], "geometry": { "type": "LineString", "coordinates": [ [ 107280.34, 521589.56 ], [ 107511.14, 521619.63 ] ] } },
{ "type": "Feature", "properties": { "name": "damwand_dwk", "hydchr": 0.01, "depth": 5.0, "elevation": null, "comment": "tot in veenlaag (0mNAP) OF SDL dieper (-5mNAP)" }, "bbox": [ 107034.61, 521536.91, 107280.34, 521642.41 ], "geometry": { "type": "LineString", "coordinates": [ [ 107034.61, 521642.41 ], [ 107058.75, 521592.47 ], [ 107070.99, 521536.91 ], [ 107280.34, 521589.56 ] ] } }
]
}
64 changes: 64 additions & 0 deletions src/nhflodata/data/mockup/hfb_pwn/v1.0.0/hfb_pwn.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
from pathlib import Path

import geopandas as gpd
import numpy as np
from nhflotools.geoconverter.utils import round_bounds, validate_crs, validate_geometry

from nhflodata import get_abs_data_path

# Properties are those consumed by nlmod.gwf.hfb.get_hfb_spd(ds, linestrings, hydchr,
# depth=None, elevation=None), which places a horizontal flow barrier on the cell faces
# crossed by the line and spans the model layers vertically:
# - hydchr: hydraulic characteristic of the barrier [1/day]; 1/100 == resistance of 100 days.
# - depth: depth of the barrier below groundlevel [m, positive]; the top is at groundlevel.
# - elevation: alternative to depth: absolute elevation of the barrier BOTTOM [m NAP].
# Use either depth or elevation (not both); here depth is populated and elevation left null.
columns_to_keep = [
"name",
"hydchr",
"depth",
"elevation",
"comment",
"geometry",
]

# %% Create horizontal flow barrier (sheet pile wall / "damwand") input
# The damwand features live in the same source dataset as the drains.
gdf_dir = get_abs_data_path("drains_en_damwand_bergen")
gdf = gpd.read_file(gdf_dir / "drains_en_damwand_bergen.geojson")
gdf = gdf.loc[gdf.naam.str.startswith("damwand")]
gdf = gdf.rename(columns={"naam": "name", "opmerking": "comment"})
gdf["comment"] = gdf["comment"].fillna("")

# diepte_van/diepte_tot are DEPTHS below groundlevel (negative = downward), so the barrier is
# defined by its construction depth, not by an absolute elevation. `depth` is the deepest extent
# below groundlevel as a POSITIVE number of metres -- exactly what nlmod.gwf.hfb.get_hfb_spd
# expects: the barrier top sits at groundlevel and extends `depth` metres straight down.
# `elevation` (absolute m NAP) is therefore left null; get_hfb_spd requires exactly one of the two.
#
# Deliberate modelling choice: representing the wall by construction depth (not an absolute tie-in)
# means that where the ground surface is high (dunes; AHN up to ~7.5 m NAP along this alignment)
# the wall bottom stays above the veen/SDL aquitard (0/-5 m NAP) and provides little cutoff there.
# This is intended -- the damwand is its physical 5 m of sheet pile, not forced down to the aquitard.
gdf["depth"] = -gdf[["diepte_van", "diepte_tot"]].min(axis=1)
gdf["elevation"] = np.nan

# Hydraulic characteristic [1/day]. Bergen model (04v2pwnbergenmodel) used 1/100,
# i.e. a resistance of 100 days for a unit gradient.
gdf["hydchr"] = 1.0 / 100.0

gdf = gdf[columns_to_keep]

# Checks
validate_crs(gdf)
for idx, geom in enumerate(gdf.geometry):
is_valid, error = validate_geometry(geom)
if not is_valid:
msg = f"Geometry at index {idx} is invalid: {error}"
raise ValueError(msg)

out_geojson = Path(__file__).parent / "hfb_pwn.geojson"
gdf[columns_to_keep].to_file(out_geojson, driver="GeoJSON", write_bbox=True, coordinate_precision=2)

bounds = dict(zip(["minx", "miny", "maxx", "maxy"], gdf.total_bounds, strict=False))
print("extent:", round_bounds(bounds, rounding_interval=1000.0).values()) # noqa: T201
21 changes: 21 additions & 0 deletions src/nhflodata/data/repository.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,27 @@ data:
changelog:
previous_version: 0.0.0
log: Initial version of drains of PWN. Uses mostly data from drains_en_damwand_bergen v1.0.0.
hfb_pwn:
- version_nhflo: 1.0.0
owner: PWN
publication_date: "2026-07-01"
version_owner: 1.0.0
description_short: Horizontal flow barriers (sheet pile walls) of PWN
description_long: Horizontal flow barriers (sheet pile walls / damwanden) of PWN, near pompstation Bergen
contact: bas.des.tombe@pwn.nl
timezone: Europe/Amsterdam
extent:
- 107000.0
- 521000.0
- 108000.0
- 522000.0
paths:
local: hfb_pwn/v1.0.0
nhflo_server: /data/hfb_pwn/v1.0.0
mockup: mockup/hfb_pwn/v1.0.0
changelog:
previous_version: 0.0.0
log: Initial version of horizontal flow barriers of PWN. Uses mostly data from drains_en_damwand_bergen v1.0.0.
rivers_pwn:
- version_nhflo: 1.0.0
owner: PWN
Expand Down
2 changes: 1 addition & 1 deletion src/nhflodata/get_paths.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
import re
from datetime import datetime
from pathlib import Path
from zoneinfo import ZoneInfo

import yaml
from yaml.loader import SafeLoader
from zoneinfo import ZoneInfo

logger = logging.getLogger(__name__)

Expand Down
3 changes: 2 additions & 1 deletion tests/schema_repository.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ dataset_version:
timezone: str(required=True)

# Spatial extent [minx, miny, maxx, maxy]
extent: list(num(), length=4, required=True)
# Note: yamale ignores `length=` on list(); use min/max to enforce exactly 4 elements.
extent: list(num(), min=4, max=4, required=True)

# Path information
paths: include('paths', required=True)
Expand Down
49 changes: 49 additions & 0 deletions tests/test_geojson_content.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
"""Content contract for the committed GeoJSON artifacts of the drain/HFB datasets.

The registry test (test_repository_yaml) and the folder test (test_mockup_folder_structure)
verify metadata and folder structure but never open the GeoJSON files. This lightweight,
dependency-free (stdlib json) check verifies that the committed artifacts consumers rely on are
structurally sound: the expected file exists, has non-empty features, declares a CRS, and every
feature carries the property columns its model consumer reads. It deliberately does NOT assert
specific data values, which are allowed to change for a data repository.
"""

import json

import pytest

from nhflodata.get_paths import get_abs_data_path

# Required property columns per dataset, matching what the model consumers read.
REQUIRED_COLUMNS = {
"drains_pwn": {
"name",
"elevation",
"conductance_per_meter",
"conductance_per_squared_meter",
"bgt-identificatie",
"mover_lake_name",
"comment",
},
"hfb_pwn": {"name", "hydchr", "elevation", "depth", "comment"},
}


@pytest.mark.parametrize("name", sorted(REQUIRED_COLUMNS))
def test_geojson_content_contract(name):
"""Committed <name>.geojson has non-empty features, a CRS, and all required columns."""
path = get_abs_data_path(name=name, version="latest", location="mockup")
geojson_file = path / f"{name}.geojson"
assert geojson_file.exists(), f"{geojson_file} is missing"

with open(geojson_file, encoding="utf-8") as f:
gj = json.load(f)

features = gj.get("features", [])
assert features, f"{name}.geojson has no features"
assert gj.get("crs"), f"{name}.geojson has no CRS"

required = REQUIRED_COLUMNS[name]
for i, feat in enumerate(features):
missing = required - set(feat.get("properties", {}))
assert not missing, f"{name}.geojson feature {i} is missing columns: {sorted(missing)}"
4 changes: 3 additions & 1 deletion tests/test_mockup_folder_structure.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ def test_mockup_folder_structure():
location="mockup",
)
)
if not path.endswith(version["paths"]["mockup"]):
# Normalize separators so the comparison also holds on Windows, where
# str(Path(...)) uses backslashes while the mockup path literal uses "/".
if not os.path.normpath(path).endswith(os.path.normpath(version["paths"]["mockup"])):
pytest.fail(f"Path in repository.yaml does not match the expected path: {version['paths']['mockup']}")

assert os.path.exists(path), f"Path {path} does not exist"
Expand Down
Loading