@@ -0,0 +1,66 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from math import asin, cos, radians, sin, sqrt
|
||||
from typing import Protocol
|
||||
|
||||
|
||||
def haversine_km(lat1: float, lon1: float, lat2: float, lon2: float) -> float:
|
||||
radius_km = 6371.0088
|
||||
dlat = radians(lat2 - lat1)
|
||||
dlon = radians(lon2 - lon1)
|
||||
a = sin(dlat / 2) ** 2 + cos(radians(lat1)) * cos(radians(lat2)) * sin(dlon / 2) ** 2
|
||||
return 2 * radius_km * asin(sqrt(a))
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class CommuteEstimate:
|
||||
minutes: int
|
||||
km: float
|
||||
source: str
|
||||
source_version: str
|
||||
confidence: float = 0.55
|
||||
is_estimate: bool = True
|
||||
|
||||
|
||||
class CommuteEstimator(Protocol):
|
||||
name: str
|
||||
version: str
|
||||
|
||||
def estimate(self, distance_km: float) -> CommuteEstimate:
|
||||
...
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ConservativeRoadEstimator:
|
||||
name: str = "road"
|
||||
version: str = "offline-heuristic-1"
|
||||
avg_kmh: float = 34.0
|
||||
route_factor: float = 1.35
|
||||
confidence: float = 0.55
|
||||
|
||||
def estimate(self, distance_km: float) -> CommuteEstimate:
|
||||
if distance_km <= 0:
|
||||
minutes = 0
|
||||
else:
|
||||
minutes = int(((distance_km / self.avg_kmh) * 60.0) * self.route_factor)
|
||||
return CommuteEstimate(
|
||||
minutes=max(minutes, 1),
|
||||
km=distance_km,
|
||||
source=self.name,
|
||||
source_version=self.version,
|
||||
confidence=self.confidence,
|
||||
is_estimate=True,
|
||||
)
|
||||
|
||||
|
||||
def estimate_commute(
|
||||
distance_km: float | None,
|
||||
*,
|
||||
estimator: CommuteEstimator | None = None,
|
||||
) -> CommuteEstimate | None:
|
||||
if distance_km is None:
|
||||
return None
|
||||
if estimator is None:
|
||||
estimator = ConservativeRoadEstimator()
|
||||
return estimator.estimate(float(distance_km))
|
||||
Reference in New Issue
Block a user