CoolFace
Apppublic

Droid210/FleetVision

sourceHugging Faceupdated 5mo agoView on Hugging Face
0likes
routing_service.py109 linesDownload Raw Back to utils
1"""Routing utility powered by OpenRouteService (ORS)."""2from __future__ import annotations3 4import os5from dataclasses import dataclass6from pathlib import Path7from typing import Tuple8 9import openrouteservice10 11 12class RoutingServiceError(RuntimeError):13    """Raised when ORS routing or geocoding fails."""14 15 16@dataclass(frozen=True)17class RouteSummary:18    distance_km: float19    duration_mins: float20    formatted_eta: str21 22 23def _load_env_file() -> None:24    """Lightweight .env loader to avoid hard dependency on dotenv package."""25    env_path = Path(__file__).resolve().parents[1] / ".env"26    if not env_path.exists():27        return28 29    for line in env_path.read_text(encoding="utf-8").splitlines():30        entry = line.strip()31        if not entry or entry.startswith("#") or "=" not in entry:32            continue33        key, value = entry.split("=", 1)34        key = key.strip()35        value = value.strip().strip('"').strip("'")36        if key and key not in os.environ:37            os.environ[key] = value38 39 40def _get_ors_client() -> openrouteservice.Client:41    _load_env_file()42    api_key = os.getenv("ORS_API_KEY", "").strip()43    if not api_key:44        raise RoutingServiceError("Missing ORS_API_KEY. Set it in .env or environment variables.")45    return openrouteservice.Client(key=api_key)46 47 48def _format_eta(duration_minutes: float) -> str:49    total_mins = int(round(duration_minutes))50    hours, mins = divmod(total_mins, 60)51    if hours:52        return f"{hours}h {mins}m"53    return f"{mins}m"54 55 56def geocode_destination(destination: str) -> Tuple[float, float]:57    """Convert a destination string into ORS coordinates (lon, lat)."""58    if not destination or not destination.strip():59        raise RoutingServiceError("Destination text is empty.")60 61    client = _get_ors_client()62    try:63        result = client.pelias_search(text=destination.strip(), size=1)64        features = result.get("features", [])65        if not features:66            raise RoutingServiceError(f"No geocoding result for destination: {destination}")67        coords = features[0]["geometry"]["coordinates"]68        return float(coords[0]), float(coords[1])69    except RoutingServiceError:70        raise71    except Exception as exc:  # noqa: BLE00172        raise RoutingServiceError(f"Geocoding failed: {exc}") from exc73 74 75def get_ride_details(start_coords: Tuple[float, float], end_coords: Tuple[float, float]) -> dict:76    """Get driving distance and ETA via ORS `driving-car` profile.77 78    Args:79        start_coords: Tuple(lon, lat) for origin.80        end_coords: Tuple(lon, lat) for destination.81 82    Returns:83        Dictionary with distance_km, duration_mins, formatted_eta.84    """85    client = _get_ors_client()86 87    try:88        route = client.directions(89            coordinates=[list(start_coords), list(end_coords)],90            profile="driving-car",91            format="geojson",92        )93        segment = route["features"][0]["properties"]["segments"][0]94        distance_km = float(segment["distance"]) / 1000.095        duration_mins = float(segment["duration"]) / 60.096 97        summary = RouteSummary(98            distance_km=round(distance_km, 2),99            duration_mins=round(duration_mins, 2),100            formatted_eta=_format_eta(duration_mins),101        )102        return {103            "distance_km": summary.distance_km,104            "duration_mins": summary.duration_mins,105            "formatted_eta": summary.formatted_eta,106        }107    except Exception as exc:  # noqa: BLE001108        raise RoutingServiceError(f"Routing request failed: {exc}") from exc109