bluegreencity-ki

Groundwater Criterion (groundwater)

Overview

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).

Implementation

Location

How It Works

  1. Discover CSV files: Scans the configured directory for Grundwasserstand_Flurabstand_*.csv files (one per year). New yearly files are picked up automatically.
  2. Load & aggregate: Reads all CSVs, builds a GeoDataFrame of monitoring wells (EPSG:25832), and computes four temporal aggregates per well: latest, median, max, standard deviation.
  3. Spatial interpolation: For each of the four values, interpolates from monitoring wells (~200 points) to all tree locations. Default method is Ordinary Kriging (PyKrige, configurable variogram model); IDW remains available via interpolation_method: "idw" for comparison runs.
  4. Grimm-Strele trend (optional): Loads the 20-year groundwater trend shapefile and assigns each tree the trend of the nearest monitoring well via gpd.sjoin_nearest().
  5. Risk scoring: Maps the selected groundwater level value (configurable: latest/median/max) to a risk score via np.interp() with configurable breakpoints. Applies the Grimm-Strele trend as an additive modifier.
  6. Output: Returns CriterionResult with risk score + 6 extras for every tree.

Spatial Join Strategy

Kriging Failure Fallback

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.

Scoring Logic

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

Output Extras

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_year and years_used_for_snapshot are pipeline-wide provenance columns (written by risk_scoring.py), not extras of this criterion — they appear next to these outputs in panel runs.

Year-aware Snapshot Filtering (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.

Configuration

Basic Configuration

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

Parameters

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

Dynamic Year Discovery

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:

Data Notes

Changelog