bluegreencity-ki

Air Temperature Criterion

Overview

The air_temperature module evaluates drought and heat stress risk based on air temperature data from FITNAH 3D climate model rasters. It provides three independent criteria, each registered separately in the catalog:

For day/night: higher temperatures = higher risk. For UHI: higher heat island effect = higher risk (deviation of the modelled near-surface night air temperature from the green/open space mean of 14.8 °C).

Implementation

Location

Architecture

The AirTemperature class uses a factory pattern with mode="day"|"night"|"uhi":

from bluegreencity_ki.criteria.air_temperature import AirTemperature

day_criterion  = AirTemperature(mode="day")   # → air_temperature_day
night_criterion = AirTemperature(mode="night") # → air_temperature_night
uhi_criterion  = AirTemperature(mode="uhi")   # → uhi_intensity

Registration in the catalog via build_day() / build_night() / build_uhi() factory functions.

How It Works (Day/Night)

  1. Load raster: 14h raster (day) or 04h raster (night)
  2. Sampling: Zonal statistics over tree crown buffers (MEAN) or point sampling (fallback)
  3. Compute thresholds: Depending on method (tree_percentile, spatial_percentile, static)
  4. Risk scoring: Linear interpolation between thresholds → score [0, 1]

How It Works (UHI, GEONET Waermeinsel — issue #171)

  1. Load rasters: uhi_siedlungsraum (settlement areas, excluding buildings) and uhi_verkehr (traffic areas) — two complementary 10 m rasters from the GEONET Klimaanalyse 2022. Values = deviation (K) of the modelled near-surface night air temperature from the green/open space mean (14.8 °C in the study area).
  2. Sampling: Both rasters sampled at tree locations (zonal/point); siedlungsraum takes precedence, verkehr fills the gaps.
  3. Green space reference: Trees outside both rasters stand in green/open spaces — the reference level of the GEONET method — and receive outside_coverage_value (default 0.0 K).
  4. Compute thresholds: From the UHI distribution (tree_percentile) or static values.
  5. Direct risk scoring: score = clip((uhi − low) / (high − low), 0, 1)
    • High heat island effect → score ≈ 1.0 (high risk)
    • Green space level (0 K) → score ≈ 0.0 (low risk)

Method change (issue #171): The previous implementation derived a UHI proxy from the day-night difference ΔT = T_14h − T_04h of the FITNAH rasters with inverted scoring. It was replaced by the proper GEONET Waermeinsel datasets, which model the heat island effect directly. The scoring direction is now direct (higher effect = higher risk) and the extras column m_uhi_intensity_value now stores the UHI effect in K (deviation from the green space mean) instead of ΔT.

Scoring Logic

Day / Night (direct):

Temperature < temp_low  → Risk Score = 0.0 (no risk)
Temperature = temp_low  → Risk Score = 0.0
Temperature in range    → Risk Score = (temp - temp_low) / (temp_high - temp_low)
Temperature = temp_high → Risk Score = 1.0
Temperature > temp_high → Risk Score = 1.0 (high risk, clipped)
Missing data (NoData)   → Risk Score = 0.5 (neutral)

UHI (direct, GEONET Waermeinsel):

UHI effect < uhi_low   → Risk Score = 0.0 (green space level, low risk)
UHI effect = uhi_low   → Risk Score = 0.0
UHI effect in range    → Risk Score = (uhi − uhi_low) / (uhi_high − uhi_low)
UHI effect = uhi_high  → Risk Score = 1.0
UHI effect > uhi_high  → Risk Score = 1.0 (strong heat island, clipped)
Outside both rasters   → UHI effect = outside_coverage_value (default 0.0 K)
No rasters available   → Risk Score = 0.5 (neutral)

Sampling Method

When crown diameter (kr_durchm) is available and use_zonal_statistics: true is set:

  1. A circular buffer (radius = crown diameter / 2) is created for each tree
  2. All raster pixels within the buffer are extracted
  3. The mean (MEAN) is used as the temperature value
  4. Trees without a valid crown diameter automatically fall back to point sampling
criteria:
  - name: "air_temperature_day"
    params:
      use_zonal_statistics: true   # default: true

Advantages:

Metadata fields:

Point Sampling (fallback)

When use_zonal_statistics: false or no crown diameter column is available:

Threshold Methods

The criterion supports three methods for threshold determination:

Computes thresholds from the temperatures actually sampled at tree locations:

criteria:
  - name: "air_temperature_day"
    weight: 1.0
    params:
      threshold_method: "tree_percentile"
      low_percentile: 10    # Coolest 10% of trees → Score 0.0
      high_percentile: 90   # Hottest 10% of trees → Score 1.0
      use_zonal_statistics: true

How it works:

  1. Sample temperatures at all tree locations (zonal or point)
  2. Compute p10 and p90 (or configured percentiles) from the sampled values
  3. Use these as low/high thresholds for linear mapping

Advantages over spatial_percentile:

2. Spatial Percentile

Computes thresholds from the entire raster distribution (all pixels):

criteria:
  - name: "air_temperature_day"
    params:
      threshold_method: "spatial_percentile"
      low_percentile: 25   # Coolest 25% of the city
      high_percentile: 75  # Hottest 25% of the city

Drawbacks:

3. Static Thresholds

Fixed temperature values:

criteria:
  - name: "air_temperature_day"
    params:
      threshold_method: "static"
      temp_low: 20.0    # or temp_day_low
      temp_high: 30.0   # or temp_day_high

Default thresholds:

Configuration

criteria:
  - name: "air_temperature_day"
    weight: 1.0
    params:
      threshold_method: "tree_percentile"
      low_percentile: 5
      high_percentile: 95
      use_zonal_statistics: true

  - name: "air_temperature_night"
    weight: 1.0
    params:
      threshold_method: "tree_percentile"
      low_percentile: 5
      high_percentile: 95
      use_zonal_statistics: true

  - name: "uhi_intensity"
    weight: 0.5  # example; current calibrated weight in config/config.yaml
    params:
      threshold_method: "tree_percentile"
      low_percentile: 5
      high_percentile: 95
      use_zonal_statistics: true
      uhi_low: 0.0     # static fallback (K) — green space reference
      uhi_high: 4.0    # static fallback (K)
      outside_coverage_value: 0.0  # UHI effect for trees in green/open spaces

Data Source Configuration

Ensure the temperature rasters are configured in the inputs section:

inputs:
  airtemp_raster_14h:
    type: "file"
    path: "Klimaanalyse Hannover/Grid-Dateien_Klimaanalyse2022/2_21_059_ist_t14_rev00.tif"  # relative to raster_base_dir

  airtemp_raster_04h:
    type: "file"
    path: "Klimaanalyse Hannover/Grid-Dateien_Klimaanalyse2022/2_21_059_ist_t04_rev00.tif"  # relative to raster_base_dir

  # GEONET Waermeinsel rasters (uhi_intensity, issue #171)
  uhi_siedlungsraum_raster:
    type: "file"
    path: "Klimaanalyse Hannover/Waermeinsel/waermeinsel_siedlungsraum.tif"
  uhi_verkehr_raster:
    type: "file"
    path: "Klimaanalyse Hannover/Waermeinsel/waermeinsel_verkehr.tif"

Parameter Reference

Parameter Type Default Description
threshold_method str "static" "tree_percentile", "spatial_percentile", or "static"
low_percentile int 10 (tree) / 25 (spatial) Lower percentile for dynamic thresholds
high_percentile int 90 (tree) / 75 (spatial) Upper percentile for dynamic thresholds
use_zonal_statistics bool true Enable zonal stats over tree crowns
temp_low / temp_day_low float 20.0 Static lower threshold for day (°C)
temp_high / temp_day_high float 30.0 Static upper threshold for day (°C)
temp_night_low float 15.0 Static lower threshold for night (°C)
temp_night_high float 22.0 Static upper threshold for night (°C)
uhi_low float 0.0 Static lower UHI effect threshold (K)
uhi_high float 4.0 Static upper UHI effect threshold (K)
outside_coverage_value float 0.0 UHI effect (K) for trees outside both Waermeinsel rasters (green/open spaces)

Data Requirements

Raster Format

Tree Crown Data (for Zonal Statistics)

Output

Scores

Each criterion returns its own CriterionResult:

Extras (raw data)

Metadata

{
    "mode": "day",                     # or "night"
    "threshold_method": "tree_percentile",
    "sampling_method": "zonal",        # or "point"
    "temp_low_threshold": 27.3,        # Computed lower threshold
    "temp_high_threshold": 31.8,       # Computed upper threshold
    "temp_min": 22.5,                  # Min sampled temperature
    "temp_max": 34.1,                  # Max sampled temperature
    "temp_mean": 29.2,                 # Mean sampled temperature
    "n_trees_sampled": 48219,          # Total trees
    "n_valid_samples": 48150,          # Trees with valid temperature value
    "n_missing_samples": 69,           # Trees with NoData
    "n_zonal": 42000,                  # Sampled via zonal statistics
    "n_point_fallback": 6150,          # Sampled via point sampling (no crown diameter)
}

Integration with Other Criteria

The air temperature criterion complements:

Tests

conda activate <project-conda-env>
python -m pytest tests/test_air_temperature.py -v

31 tests covering:

Known Limitations

  1. Static snapshot: Single point in time (no time-series approach)
  2. No heatwave duration: Does not account for cumulative heat stress
  3. Raster resolution: Microclimatic variations may be lost
  4. Species-independent: Species-specific heat tolerance is not integrated (→ separate criterion)

References

Changelog

2026-07: UHI from GEONET Waermeinsel rasters (Issue #171)

2026-03: UHI Intensity Criterion (Issue #86)

2025-03: Zonal Statistics, Tree Percentile & Architecture Refactor

2025-01-13: Added Nighttime Risk Score

2025-01-13 (earlier): Added 04h Temperature Sampling

2025-10-13: Initial Implementation


Last Updated: 2026-03