Spaces:
Sleeping
Sleeping
File size: 2,649 Bytes
639f871 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | """
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)) |