Tour_Generator_GA / core /distance.py
GaetanoParente's picture
first commit
639f871
"""
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))