Spaces:
Sleeping
Sleeping
| """ | |
| core/distance.py — Matrice delle distanze e tempi di percorrenza. | |
| Supporta Haversine (offline) e profilo turista per la velocità. | |
| """ | |
| from __future__ import annotations | |
| import math | |
| from typing import Union, Optional, TYPE_CHECKING | |
| from .models import PoI | |
| from config import ROUTE_DETOUR_FACTOR | |
| if TYPE_CHECKING: | |
| from .profile import TouristProfile | |
| def haversine_km(lat1: float, lon1: float, lat2: float, lon2: float) -> float: | |
| """Distanza geodetica tra due coordinate in chilometri.""" | |
| R = 6371.0 | |
| phi1, phi2 = math.radians(lat1), math.radians(lat2) | |
| dphi = math.radians(lat2 - lat1) | |
| dlambda = math.radians(lon2 - lon1) | |
| a = math.sin(dphi/2)**2 + math.cos(phi1)*math.cos(phi2)*math.sin(dlambda/2)**2 | |
| return R * 2 * math.asin(math.sqrt(a)) | |
| class DistanceMatrix: | |
| """ | |
| Precalcola tutte le distanze tra i PoI (km). | |
| I TEMPI vengono calcolati on-the-fly tramite il TouristProfile, | |
| così un cambio di modalità non richiede di ricostruire la matrice. | |
| """ | |
| def __init__(self, pois: list[PoI], profile: Optional["TouristProfile"] = None): | |
| self.pois = pois | |
| self.profile = profile | |
| self.idx = {poi.id: i for i, poi in enumerate(pois)} | |
| n = len(pois) | |
| self._dist = [[0.0] * n for _ in range(n)] # km (invariante) | |
| def build(self): | |
| """Popola la matrice delle distanze. Chiama una volta sola.""" | |
| for i, a in enumerate(self.pois): | |
| for j, b in enumerate(self.pois): | |
| if i == j: | |
| continue | |
| km = haversine_km(a.lat, a.lon, b.lat, b.lon) * ROUTE_DETOUR_FACTOR | |
| self._dist[i][j] = km | |
| def dist(self, a: Union[PoI, str], b: Union[PoI, str]) -> float: | |
| """Distanza in km tra due PoI.""" | |
| ia = self.idx[a.id if isinstance(a, PoI) else a] | |
| ib = self.idx[b.id if isinstance(b, PoI) else b] | |
| return self._dist[ia][ib] | |
| def time(self, a: Union[PoI, str], b: Union[PoI, str]) -> int: | |
| """Tempo di percorrenza in minuti, rispettando la modalità del profilo.""" | |
| km = self.dist(a, b) | |
| return self._km_to_min(km) | |
| def time_from_coord(self, lat: float, lon: float, poi: PoI) -> int: | |
| """Tempo in minuti da coordinate arbitrarie (es. hotel) a un PoI.""" | |
| km = haversine_km(lat, lon, poi.lat, poi.lon) * ROUTE_DETOUR_FACTOR | |
| return self._km_to_min(km) | |
| def _km_to_min(self, km: float) -> int: | |
| if self.profile is not None: | |
| return self.profile.travel_time_min(km) | |
| # Fallback sicuro: a piedi 4.5 km/h | |
| return max(1, int((km / 4.5) * 60)) |