---
name: "flood-riverine"
description: "🛤️ Flood Hazard: HAND + Strahler dinamico + 4 classi (2/4/5/6m), validato Sentinel-1. Output: {AOI}_Flood_Hazard.zip/shp/tif/gpkg/qml."
model_tier: 1
triggers:
  - "flood hazard"
  - "flood risk"
  - "flood-prone"
  - "riverine flood"
  - "HAND analysis"
  - "Kobe flood"
tools:
  - exec
  - cron
---

# 🛤️ Flood Riverine — SCOLPITO NELLA ROCCIA

## REGOLA ASSOLUTA
Quando serve una **flood hazard map**, segui:
1. RICEVERE: DTM cropped (in METRI!), streams cropped (con attributo `strahler`), AOI polygon
2. ESEGUIRE il workflow GRASS qui sotto
3. CONSEGNARE: shapefile + raster + Flood_Hazard.qml in ZIP via HTTP

## Workflow GRASS

### Setup
```bash
# 1. Crea location GRASS dal DTM cropped
grass -c /path/to/DTM_cropped.tif -e /path/to/grassdata/location_crop

# 2. Importa DTM
grass grassdata/location_crop/PERMANENT --exec r.in.gdal -o input=DTM_cropped.tif output=dem_crop
grass grassdata/location_crop/PERMANENT --exec g.region raster=dem_crop

# 3. Importa streams cropped (input streams_croped.gpkg → GRASS)
grass grassdata/location_crop/PERMANENT --exec v.in.ogr -o input=streams_croped.gpkg output=streams --overwrite

# 3.5. Esporta streams in GPKG per SQL query (serve per MAX_STR detection)
ogr2ogr -f GPKG streams.gpkg streams_croped.gpkg -nln streams -overwrite 2>/dev/null
# Fallback: se GPKG non disponibile, crea da GRASS vector
ogrinfo streams.gpkg -sql "SELECT COUNT(*) FROM streams" &>/dev/null || \
  grass grassdata/location_crop/PERMANENT --exec v.out.ogr -f GPKG input=streams output=streams.gpkg format=GPKG --overwrite 2>/dev/null

# 4. Estrai solo l'arteria principale (top 2 ordini Strahler)
# ⚠️ Strahler 7 era usato per Kobe (fiume Wabishebele). Per altri AOI, adattare!
# Regola: usa strahler >= (max_strahler - 1), con minimo 5.
# Esempio: max=5 → usa >=4; max=7 → usa >=6
MAX_STR=$(ogrinfo -q -dialect SQLite -sql "SELECT MAX(strahler) FROM streams" streams.gpkg 2>/dev/null | grep -oP '\d+' | head -1)
MAIN_STR=$(( MAX_STR > 6 ? MAX_STR - 1 : MAX_STR ))
# Fallback: se non trova il max nel GPKG, usa 7 (default storico per Kobe)
MAIN_STR=${MAIN_STR:-7}
echo "Strahler max: $MAX_STR → usando >= $MAIN_STR come arteria principale"
grass grassdata/location_crop/PERMANENT --exec v.extract input=streams where="strahler >= ${MAIN_STR}" output=streams_main --overwrite
```

### HAND Calculation
```bash
# 5. Converti streams to raster
grass grassdata/location_crop/PERMANENT --exec v.to.rast input=streams_main type=line output=streams_rast use=attr attribute_column=strahler --overwrite

# 6. River mask + elevation
grass grassdata/location_crop/PERMANENT --exec r.mapcalc "river_mask = if(streams_rast, 1, null())"
grass grassdata/location_crop/PERMANENT --exec r.mapcalc "river_elev = if(river_mask == 1, dem_crop, null())"

# 7. Distance + HAND
grass grassdata/location_crop/PERMANENT --exec r.grow.distance input=river_elev distance=dist_to_river value=river_elev_prop
grass grassdata/location_crop/PERMANENT --exec r.mapcalc "hand = dem_crop - river_elev_prop"
```

### Flood Risk Classification
```bash
# 8. Import AOI
grass grassdata/location_crop/PERMANENT --exec v.in.ogr -o input=AOI.shp output=aoi
grass grassdata/location_crop/PERMANENT --exec v.to.rast input=aoi type=area output=aoi_mask use=val val=1

# 9. Classify within AOI (HAND in METRI!) — soglie validate con Sentinel-1
# Class 5: Very_High (HAND ≤ 2 m)
# Class 4: High (2 < HAND ≤ 4 m) — corrisponde a flooding osservato Nov 2023
# Class 3: Medium (4 < HAND ≤ 5 m)
# Class 2: Low (5 < HAND ≤ 6 m)
# Oltre 6 m: non classificato (null)
# NOTA: usare espressioni booleane, NON if() annidati (>3 livelli)
grass grassdata/location_crop/PERMANENT --exec r.mapcalc "fp_aoi = if(aoi_mask, (hand <= 2) * 5 + (hand > 2 && hand <= 4) * 4 + (hand > 4 && hand <= 5) * 3 + (hand > 5 && hand <= 6) * 2, null())"
```

### Export + Style
```bash
# 10. Smooth + vectorize
grass grassdata/location_crop/PERMANENT --exec r.neighbors input=fp_aoi output=fp_aoi_smooth method=mode size=5 --overwrite
grass grassdata/location_crop/PERMANENT --exec r.to.vect -s input=fp_aoi_smooth output=fp_aoi_v type=area --overwrite

# 11. Add attributes
grass grassdata/location_crop/PERMANENT --exec v.db.addcolumn map=fp_aoi_v columns="risk_class varchar(20)"
grass grassdata/location_crop/PERMANENT --exec db.execute sql="UPDATE fp_aoi_v SET risk_class = CASE WHEN value=5 THEN 'Very_High' WHEN value=4 THEN 'High' WHEN value=3 THEN 'Medium' WHEN value=2 THEN 'Low' END"
grass grassdata/location_crop/PERMANENT --exec v.db.addcolumn map=fp_aoi_v columns="area_ha double precision"
grass grassdata/location_crop/PERMANENT --exec v.to.db map=fp_aoi_v option=area units=hectares columns=area_ha --overwrite

# 12. Export (nome output dinamico: usa AOI name se presente, default = Flood_Hazard)
OUT_NAME="${AOI_NAME:-Flood_Hazard}"
grass grassdata/location_crop/PERMANENT --exec v.out.ogr input=fp_aoi_v output="${OUT_NAME}.shp" format=ESRI_Shapefile --overwrite
grass grassdata/location_crop/PERMANENT --exec r.out.gdal -c input=fp_aoi_smooth output="${OUT_NAME}.tif" format=GTiff --overwrite

# 13. GPKG + area_ha via SQL (affidabile)
ogr2ogr -f GPKG "${OUT_NAME}.gpkg" "${OUT_NAME}.shp" -nln flood_hazard -overwrite
ogrinfo "${OUT_NAME}.gpkg" -sql "UPDATE flood_hazard SET area_ha = ROUND(ST_Area(geom) / 10000.0, 1)"

# 14. ZIP con QML
QML="references/Flood_Hazard.qml"
[ -f "$QML" ] && cp "$QML" "./${OUT_NAME}.qml"
zip -9 "${OUT_NAME}.zip" "${OUT_NAME}".* 2>/dev/null
echo "✅ Output: ${OUT_NAME}.zip / .gpkg / .shp / .tif"
```

## QGIS Style
- **Very_High**: blu #1F78B4 (opaco)
- **High**: blu #1F78B4 (50% trasp.)
- **Medium**: blu #1F78B4 (25% trasp.)
- **Low**: verde #33A02C (30% trasp.)
- Oltre 6 m HAND: **non classificato** (null)

## Criteri di Qualità
- HAND in **METRI** (DEM deve essere in metri!)
- Soglie relative all'altezza del fiume (HAND)
- **Strahler dinamico**: usa max_strahler-1 (non più hardcoded 7)
- 4 classi: Very_High (≤2m), High (2-4m), Medium (4-5m), Low (5-6m)
- Oltre 6m: non classificato
- Smoothing: r.neighbors size=5 (mode)
- ✔ Espressioni booleane in r.mapcalc

## ⚠️ Pitfalls

### 1. Strahler insufficiente
- **SINTOMO**: v.extract produce output vuoto con `strahler >= 7`.
- **CAUSA**: AOI piccolo o reticolo poco gerarchizzato. Max Strahler locale < 7.
- **SOLUZIONE**: usare `strahler >= max_strahler` o `>= max_strahler - 1`.
- La skill ora rileva automaticamente MAX_STR e adatta.

### 2. HAND rivers from the edge — bordi artefatti
- Le celle di bordo del DTM spesso non hanno un valore di fiume a monte → HAND calcolato come -9999.
- **SOLUZIONE**: mascherare con AOI e tagliare le celle di bordo.

### 3. DTM in EPSG:4326 (gradi)
- HAND va in **METRI**. Se il DTM è in gradi, l'elevazione è in metri ma le distanze sono in gradi → HAND sballato.
- **SOLUZIONE**: convertire a UTM prima di iniziare.

### 4. AOI CRS mismatch con DTM
- **SINTOMO**: `v.in.ogr` importa AOI in EPSG:4326 ma la location GRASS è in UTM (dal DTM) → coordinate AOI sballate.
- **CAUSA**: l'utente dà l'AOI in WGS84 (come richiesto), ma il DTM è in UTM.
- **SOLUZIONE**: riproiettare l'AOI prima di importare in GRASS:
  ```bash
  ogr2ogr -t_srs EPSG:32637 AOI_UTM.shp AOI_WGS84.shp
  grass ... --exec v.in.ogr -o input=AOI_UTM.shp output=aoi
  ```
  Oppure creare la location GRASS in 4326 e importare DTM + AOI nella stessa CRS, ma HAND funziona solo in metri → necessaria conversione a UTM.

### 5. area_ha in GPKG (evitare null) o Telegram non supporta .qml
- Lo stile QGIS è disponibile in `references/Flood_Hazard.qml`.
- Telegram NON supporta upload di file .qml. Copiarlo via HTTP o crearlo:
  ```bash
  # Dopo l'export, il QML viene incluso automaticamente nello ZIP.
  # Scarica lo ZIP da HTTP ed estrai.
  ```
- Se il QML manca comunque: QGIS → Layer Properties → Symbology → Categorized su `value` → palette dal blu al verde.

## Output
- `{AOI_Flood_Hazard}.shp` (risk_class + area_ha)
- `{AOI_Flood_Hazard}.tif` (raster 1-5, NULL=255)
- `{AOI_Flood_Hazard}.qml` (stile QGIS categorizzato)
- `{AOI_Flood_Hazard}.gpkg` (con area_ha calcolata via SQL)
- `{AOI_Flood_Hazard}.zip` (tutto incluso)

## Esempio output reale (Etiopia Melkadida, 12.06.2026)
- DTM: `DTM_UTM37_100_Croped.tif`, FathomDEM 30m, UTM37N (EPSG:32637)
- Strahler max rilevato: 5 → usato >= 5 come arteria principale
- AOI: 41.68-42.10°E, 4.22-4.52°N (~154,764 ha)
- Risultati HAND:

| Classe | Soglia | Area (ha) | Poligoni |
|---|---|---|---|
| Very_High | ≤ 2m | 1,089 | 12 |
| High | 2-4m | 3,499 | 110 |
| Medium | 4-5m | 5,253 | 180 |
| Low | 5-6m | 4,533 | 224 |
| Safe | > 6m | 139,091 | — |

- Totale a rischio (≤ 6m HAND): **14,374 ha** (9.3% dell'AOI)
- Smoothing: r.neighbors size=5 (mode)
- ZIP finale: 592 KB (shp+tif+qml+gpkg)

## Riferimenti
- `references/Flood_Hazard.qml` — stile QGIS categorizzato per value 2-5
- `references/ethiopia-melkadida-2026-06-12.md` — log completo del run Etiopia
- Skill: `watershed` — produce i streams in input a questa skill

## 🛤️ SCOLPITO NELLA ROCCIA il 09.06.2026 | CORRETTO il 11.06.2026 (v5: soglie 2/4/5/6m)
## NESSUN CAMBIAMENTO SENZA APPROVAZIONE ESPLICITA DI BULDO
