import math from typing import List, Sequence from .geo_distance import distance from .geo_point_in_polygon import GeoPoint from .logistics_route_distance_types import RouteDistance def route_distance(stops: Sequence[GeoPoint], return_to_start: bool) -> RouteDistance: """Great-circle length of a route through the stops in order, leg by leg, optionally closing back to the first stop.""" if len(stops) == 0: raise ValueError("stops must not be empty") leg_metres: List[float] = [] # Each leg is a whole number of millimetres; adding them as integers and # dividing once avoids floating-point drift (and is the same double everywhere). total_millimetres = 0 def add_leg(a: GeoPoint, b: GeoPoint) -> None: nonlocal total_millimetres metres = distance(a.lat, a.lng, b.lat, b.lng) leg_metres.append(metres) total_millimetres += math.floor(metres * 1000 + 0.5) for i in range(1, len(stops)): add_leg(stops[i - 1], stops[i]) if return_to_start and len(stops) > 1: add_leg(stops[-1], stops[0]) return RouteDistance(total_metres=total_millimetres / 1000, leg_metres=leg_metres)