bluegreencity-ki

Land Use Criterion

Overview

The land_use criterion evaluates drought risk based on land use classification at tree locations. Different land use types have different impacts on tree water availability and stress levels, ranging from high-risk sealed surfaces to low-risk vegetation areas.

Where the LHH planting-site dataset (the “Baumumfeld” polygons from the Grünflächenkataster) covers a tree, its surface type overrides the raster-based value and — if mapped via planting_site_surface_type_scores — the risk score (issue #194). Trees outside its currently partial coverage keep the raster method; land_use_score_source records the score method per tree.

Implementation

Location

How It Works

  1. Raster Sampling: Samples the land use raster at each tree location using PostGIS raster SQL functions for PostGIS rasters or rasterio index + array access for local GeoTIFF files (note: rasterio.sample() is deliberately not used — it crashes on Windows)
  2. Code Mapping: Maps integer codes from the raster to risk scores and descriptive labels using a configurable mapping table
  3. Risk Scoring: Direct lookup of pre-configured risk scores (0-1 scale)
  4. Planting-site override (issue #194): Trees are spatially joined (within) onto the planting-site polygons (inputs.planting_sites in config). Covered trees receive the polygon surface type as planting_site_surface_type; when that type appears in planting_site_surface_type_scores (case-insensitive), the mapped score replaces the raster score. Unmapped surface types keep the raster score and are logged as a warning. The dataset is read fresh on every run and never cached because it currently covers only part of the city and will be replaced by a full-coverage export.

Scoring Logic

Land use code 7 → Risk Score = 0.9 (high risk)
Land use code 25 → Risk Score = 0.3 (low risk)
Unknown code → Risk Score = 0.5 (neutral)
NoData value → Risk Score = 0.5 (neutral)

Configuration

Land Use Mapping

The criterion uses a configurable mapping table that defines how land use codes map to risk scores and labels:

land_use:
  mapping:
    # Hohes Risiko (high risk - sealed surfaces, buildings)
    7: {risk_score: 0.9, label: "Gleis", description: "Rail tracks"}
    20: {risk_score: 0.9, label: "Gebäude", description: "Buildings"}

    # Geringes Risiko (low risk - vegetation, water)
    9: {risk_score: 0.1, label: "Freiland/Rasen", description: "Open land/grass"}
    14: {risk_score: 0.1, label: "Wasser", description: "Water"}

    # ... other codes ...

Risk Score Interpretation:

Criterion Configuration

criteria:
  - name: "land_use"
    weight: 0.5  # example; current calibrated weight in config/config.yaml
    params:
      nodata_value: 28  # NoData code for the raster dataset
      # Planting-site surface type → risk score (case-insensitive matching)
      planting_site_surface_type_scores:
        "Baumbestandsflächen": 0.1
        "Wiese": 0.2
        "Gebrauchsrasen": 0.3
        "Wassergebundene Flächen": 0.5
        "Pflasterflächen": 0.8
        "Asphaltflächen": 0.9
        # ... see config/config.yaml for the full 47-category mapping

The planting-site input itself is configured under inputs. Its source file retains the LHH delivery name Baumumfeld_zusammengefasst.gpkg:

inputs:
  planting_sites:
    type: "file"
    path: "Baumumfeld/Baumumfeld_zusammengefasst.gpkg"  # relative to raster_base_dir
    layer: "Baumumfeld_zusammengefasst"
    surface_type_field: "kategorie"   # read inside inputs.planting_sites

Parameters

Parameter Type Default Description
nodata_value int 28 The NoData value used in the raster (maps to neutral score)
planting_site_surface_type_scores dict — Planting-site surface type → risk score; unmapped types keep the raster score (warning logged)

Data Requirements

Raster Format

Data Quality Considerations

  1. Spatial Resolution: Should match or exceed tree spacing density (10m for Hannover dataset)
  2. Temporal Coverage: Should represent current conditions
  3. Code Consistency: All codes in raster should be documented in mapping table

Output Fields

The criterion produces the following output fields:

  1. land_use_risk - Normalized risk score (0-1)
    • High values: Trees in high-risk areas (sealed surfaces, buildings)
    • Low values: Trees in low-risk areas (vegetation, water)
  2. land_use_value - Raw land use code from raster
    • Integer value directly from the raster dataset
  3. land_use_label - Descriptive label
    • Human-readable description of the land use type
  4. planting_site_surface_type - Planting-site surface type (issue #194)
    • Source field kategorie (e.g. “Gebrauchsrasen”, “Pflasterflächen”); NaN outside coverage
    • Used as ML feature in the AutoGluon training dataset
  5. planting_site_area_m2 - Area of the matched planting-site polygon in m²
    • NaN outside coverage; used as ML feature
  6. land_use_score_source - Scoring method per tree: "raster" or "planting_site"

Default Mapping for Hannover 2022 Dataset

The default mapping is based on the Stadt Hannover Klimaanalyse 2022 dataset with 10m resolution:

Code Label Description Risk Score Risk Level
7 Gleis Rail tracks 0.9 High
20 Gebäude Buildings 0.9 High
22 Ebenerdig versiegelt Impervious surface (level) 0.9 High
24 Baum über Versiegelung Tree over sealed surface 0.9 High
26 Baum über Rasen naturferner Boden Tree over grass on non-natural soil 0.7 Medium-High
23 Rasen naturferner Boden Grass on non-natural soil (e.g., sports field) 0.5 Medium
25 Baum über Rasen Tree over grass 0.3 Low
9 Freiland/Rasen Open land/grass 0.1 Low
14 Wasser Water 0.1 Low
28 Tagebau (NODATA) NoData value 0.5 Neutral

Integration with Other Criteria

The land use criterion complements other risk factors:

Validation & Testing

Unit Tests

Run tests with:

conda activate bgcki_wesemann
python -m pytest tests/test_land_use.py -v

Tests cover:

Visual Validation

After running the pipeline, validate results by:

  1. Map overlay: Compare land use raster with risk scores
  2. Distribution analysis: Plot land use codes vs risk scores
  3. Spatial patterns: Check for expected urban/rural patterns
  4. Outlier detection: Identify anomalous scores

Example Validation Code

import geopandas as gpd
import matplotlib.pyplot as plt

# Load results
gdf = gpd.read_file("./data/output/<study_area>/trees_at_risk_<area>.gpkg", layer="trees_at_risk_points")

# Plot land use codes vs risk score
plt.figure(figsize=(10, 6))
for code in sorted(gdf['land_use_value'].unique()):
    subset = gdf[gdf['land_use_value'] == code]
    plt.scatter([code]*len(subset), subset['land_use_risk'], alpha=0.5, label=str(code))

plt.xlabel('Land Use Code')
plt.ylabel('Risk Score')
plt.title('Land Use Codes vs. Risk Scores')
plt.legend(bbox_to_anchor=(1.05, 1), loc='upper left')
plt.grid(True, alpha=0.3)
plt.show()

# Land use distribution
plt.figure(figsize=(10, 6))
gdf['land_use_value'].value_counts().sort_index().plot(kind='bar')
plt.xlabel('Land Use Code')
plt.ylabel('Count')
plt.title('Distribution of Land Use Types at Tree Locations')
plt.grid(True, alpha=0.3)
plt.show()

# Summary statistics
print("Risk Score Statistics:")
print(gdf['land_use_risk'].describe())

Known Limitations

  1. Static mapping: Uses single snapshot of land use (not time series)
  2. Code dependency: Risk assessment depends on accurate code mapping
  3. Resolution limits: 10m raster may miss micro-scale variations
  4. Dataset specificity: Mapping needs adjustment for different datasets

References

Changelog

2026-07-23: Planting-site terminology (#194)

2026-01-02: Initial Implementation


Author: BlueGreenCity-KI Team Last Updated: 2026-07-22