groundwater)Evaluates tree drought risk based on the depth to groundwater (Flurabstand) — how far below the surface the groundwater table sits at each tree location. Trees growing in areas with deep groundwater have limited root access to capillary water, increasing their drought stress.
Data source: Landeshauptstadt Hannover (LHH), Fachbereich Umwelt und Stadtgrün, Sachgebiet Boden- und Grundwasserschutz (December 2025).
bluegreencity_ki/criteria/groundwater.pytests/test_groundwater.pyGrundwasserstand_Flurabstand_*.csv files (one per year). New yearly files are picked up automatically.interpolation_method: "idw" for comparison runs.gpd.sjoin_nearest().np.interp() with configurable breakpoints. Applies the Grimm-Strele trend as an additive modifier.CriterionResult with risk score + 6 extras for every tree.min_neighbors wells within max_distance_m are not kriged and receive the neutral score instead.interpolation_method: "idw". Weights nearby wells by inverse distance with configurable power.PyKrige can fail while fitting a variogram when an aggregate has no spatial
variation. This occurs, for example, in a historical snapshot with only one
available groundwater year: groundwater_level_std is then 0 at every well,
and SciPy rejects the identical optimization bounds. If PyKrige raises an
ArithmeticError or ValueError, the criterion logs a warning and falls back
to IDW for that aggregate only. The fallback uses the configured idw_power,
max_distance_m, and min_neighbors, preserving the same coverage mask and
neutral-score behavior. Other aggregates continue to use Kriging.
Depth to groundwater (m) → Risk Score = np.interp(depth, breakpoints_x, breakpoints_y)
+ Grimm-Strele trend adjustment (additive, ±0.1 max)
Missing data (no wells within max_distance) → Risk Score = 0.5 (neutral)
Default Breakpoints:
| Depth to GW (m) | Risk Score | Interpretation |
|---|---|---|
| 0 | 0.1 | Water at surface — minimal risk |
| 1 | 0.2 | Very shallow — low risk |
| 2 | 0.4 | Shallow — moderate risk |
| 4 | 0.6 | Moderate depth — elevated risk |
| 6 | 0.8 | Deep — high risk |
| 10+ | 0.95 | Very deep — essentially unreachable |
Grimm-Strele Trend Adjustments:
| Trend | Adjustment |
|---|---|
| stark steigend | −0.1 |
| steigend | −0.05 |
| gleich bleibend | 0.0 |
| fallend | +0.05 |
| stark fallend | +0.1 |
All six extras are always computed and written to the tree output, regardless of which value is used for scoring:
| Column | Type | Description |
|---|---|---|
groundwater_level_latest |
float | Depth to GW from most recent year (m) |
groundwater_level_median |
float | Median depth across all available years (m) |
groundwater_level_max |
float | Maximum depth (driest conditions) across all years (m) |
groundwater_level_std |
float | Standard deviation across years (temporal variability) |
groundwater_trend |
str | Grimm-Strele 20-year trend category |
groundwater_trend_adjustment |
float | Numeric adjustment applied to score |
feature_reference_yearandyears_used_for_snapshotare pipeline-wide provenance columns (written byrisk_scoring.py), not extras of this criterion — they appear next to these outputs in panel runs.
feature_reference_year)When project.feature_reference_year is set, the groundwater criterion filters all
well measurements to those with year ≤ feature_reference_year before computing
temporal aggregates. This prevents data leakage in historical panel snapshots:
feature_reference_year = 2020
→ only well readings from 2020 and earlier are used
→ "latest" = most recent reading ≤ 2020
→ "median" / "max" = aggregated over all years ≤ 2020
This ensures that panel scores for 2020 trees do not inadvertently benefit from groundwater data collected in 2021–2024.
Add to config.yaml:
inputs:
groundwater:
type: "directory"
path: "LHH Grundwasserdaten/Grundwasserstände_Flurabstände/"
file_pattern: "Grundwasserstand_Flurabstand_*.csv"
grimm_strele_path: "LHH Grundwasserdaten/Grimm_Strele_Trend/Trendauswertung_Grimm_Strele_20Jahre.shp"
well_id_field: "ID_Messstelle"
x_field: "Rechtswert"
y_field: "Hochwert"
flurabstand_field: "Mittlerer_Flurabstand_m"
year_field: "Jahr"
criteria:
- name: "groundwater"
weight: 0.5 # example; current calibrated weight in config/config.yaml
params:
scoring_source: "median"
interpolation_method: "kriging"
variogram_model: "exponential"
idw_power: 2
max_distance_m: 5000
min_neighbors: 3
breakpoints:
0.0: 0.1
1.0: 0.2
2.0: 0.4
4.0: 0.6
6.0: 0.8
10.0: 0.95
trend_enabled: false # live config: trend adjustment currently inactive (code default: true)
trend_adjustment:
"stark steigend": -0.1
"steigend": -0.05
"gleich bleibend": 0.0
"fallend": 0.05
"stark fallend": 0.1
| Parameter | Type | Default | Description |
|---|---|---|---|
scoring_source |
str | "median" |
Which temporal aggregate to use for scoring: "latest", "median", or "max" |
interpolation_method |
str | "kriging" |
Spatial interpolation method: "kriging" (Ordinary Kriging) or "idw" |
variogram_model |
str | "exponential" |
Kriging only: PyKrige variogram model (exponential, spherical, gaussian, linear, power) |
idw_power |
float | 2.0 | IDW only: exponent (higher = more local influence) |
max_distance_m |
float | 5000 | Max interpolation distance (m); beyond → neutral score |
min_neighbors |
int | 3 | Minimum wells within range for valid interpolation |
breakpoints |
dict | REQUIRED | Depth (m) → risk score mapping (linearly interpolated) |
trend_enabled |
bool | true |
Whether to apply Grimm-Strele trend adjustment |
trend_adjustment |
dict | see above | Trend category → additive score modifier |
CSV files are discovered automatically via the file_pattern glob. When new yearly data (e.g., 2026, 2027) is added to the directory, it is automatically included in the next pipeline run:
latest always uses max(years)median, max, std always aggregate across all discovered yearsmeta output documents which years were used