# Pipeline percorsi pedonali multi-source - Parma

Calcola, per **ogni edificio di Parma** (~44.000), il **percorso pedonale
piu' breve verso il POI piu' vicino** di ciascuna categoria, usando
**multi-source Dijkstra** sul grafo OSM.

## Architettura

```
script/
├── data/                       # input gia' forniti
│   ├── path_building_to_pois_all_parma.gpkg   (44k edifici + 7 layer POI)
│   ├── clim_shel_2026.csv
│   └── altri csv POI (non usati: i layer GPKG sono piu' puliti)
├── output/                     # generata: 1 GPKG per categoria
│   ├── routes_cultural.gpkg
│   ├── routes_health.gpkg
│   ├── routes_park.gpkg
│   ├── routes_education.gpkg
│   ├── routes_entertainment.gpkg
│   ├── routes_mobility.gpkg
│   ├── routes_drinking_fountain.gpkg
│   └── routes_clim_shel_2026.gpkg
├── pipeline/
│   ├── config.py               # CRS, paths, lista categorie
│   ├── 01_download_osm.py      # scarica rete pedonale OSM
│   ├── 02_prepare_graph.py     # proietta + snap edifici (UNA volta)
│   ├── 03_compute_routes.py    # MULTI-SOURCE DIJKSTRA per categoria
│   └── _cache/                 # creato a runtime (grafo + edifici preparati)
├── requirements.txt
├── setup_env.bat               # crea venv + installa dipendenze
└── run_pipeline.bat            # lancia tutta la pipeline
```

## Come si lancia

```bat
:: 1) UNA VOLTA SOLA: crea il venv e installa le dipendenze
setup_env.bat

:: 2) Tutte le volte che vuoi rigenerare l'output:
run_pipeline.bat
```

I file di cache (`pipeline/_cache/`) fanno si' che i rilanci successivi
saltino gli step 01 e 02 e ripartano direttamente dal 03. Per forzare
una rigenerazione completa basta cancellare `_cache/`.

## Output: contenuto di ogni `routes_<categoria>.gpkg`

Tutti i layer sono in **EPSG:3857** (coerenti con la dashboard esistente).

| Layer                              | Geometria   | Campi chiave                                                                       |
| ---------------------------------- | ----------- | ---------------------------------------------------------------------------------- |
| `shortest_path`                    | LineString  | `origin_id`, `destination_id`, `entry_cost`, `network_cost`, `exit_cost`, `total_cost` |
| `most_used_path`                   | LineString  | `osmid`, `name`, `highway`, `length`, **`fid_count`**                              |
| `footprints_with_shortest_path`    | Polygon     | tutti i campi originali edifici + `assigned_poi_id`, `total_cost`                  |
| `pois`                             | Point       | tutti i campi originali POI + `n_buildings_served`, `avg/min/max_distance`         |
| `origin_points`                    | Point       | `fid`, `assigned_poi_id`, `total_cost`                                             |

### A cosa servono i layer

- **`shortest_path`**: il percorso reale (LineString) edificio -> POI piu' vicino.
- **`most_used_path`**: gli archi OSM piu' "trafficati" per arrivare a quella
  categoria, con un conteggio `fid_count`. **Usalo per mappe di flusso/calore**.
- **`footprints_with_shortest_path`**: gli edifici da colorare per accessibilita'.
- **`pois`**: i POI con il "bacino di utenza" - quanti edifici servono.
- **`origin_points`**: per costruire diagrammi di Voronoi su grafo.

## L'algoritmo in poche parole

Per ogni categoria POI:

1. Si snappa ogni POI sull'arco OSM piu' vicino (nodo virtuale `virt_p_*`).
2. Si snappa ogni edificio sull'arco OSM piu' vicino (nodo virtuale `virt_b_*`).
3. **UNA SOLA chiamata** a `nx.multi_source_dijkstra(G, sources=POIs)`:
   l'algoritmo parte simultaneamente da tutti i POI e propaga un fronte
   d'onda sul grafo. Quando il fronte raggiunge un nodo "edificio", scopre
   automaticamente il POI piu' vicino e la distanza minima.

**Costo computazionale**: O((V+E) log V) per categoria, **indipendente**
dal numero di edifici. Con ~50k nodi e ~200k archi, ogni categoria si
chiude in 1-3 minuti.

Brute force (Dijkstra per ogni coppia edificio-POI): ~2 settimane.
Multi-source: ~15-30 minuti per tutte e 9 le categorie.

## Personalizzazione

Per **aggiungere/rimuovere categorie**, modifica `pipeline/config.py`,
lista `POI_CATEGORIES`.

Per usare un **CRS metrico diverso** (es. ETRS89/UTM 32N) modifica
`METRIC_CRS` in `config.py`.

Per attivare un **cutoff distanza** (es. ignora edifici a > 2 km),
imposta `DIJKSTRA_CUTOFF_M = 2000` in `config.py`.
