///|
const EARTH_RADIUS_M : Double = 6371008.8

///|
pub fn haversine_meters(a : Point, b : Point) -> Result[Double, RouteError] {
  match validate_point(a, 0) {
    Ok(_) => ()
    Err(err) => return Err(err)
  }
  match validate_point(b, 1) {
    Ok(_) => ()
    Err(err) => return Err(err)
  }
  let lat1 = deg_to_rad(a.lat)
  let lat2 = deg_to_rad(b.lat)
  let dlat = deg_to_rad(b.lat - a.lat)
  let dlon = deg_to_rad(b.lon - a.lon)
  let sin_lat = @math.sin(dlat / 2.0)
  let sin_lon = @math.sin(dlon / 2.0)
  let h = sin_lat * sin_lat +
    @math.cos(lat1) * @math.cos(lat2) * sin_lon * sin_lon
  let clamped = h.clamp(min=0.0, max=1.0)
  let central = 2.0 * @math.atan2(clamped.sqrt(), (1.0 - clamped).sqrt())
  Ok(EARTH_RADIUS_M * central)
}

///|
pub fn route_distance_meters(
  points : ArrayView[Point],
) -> Result[Double, RouteError] {
  match require_points(points) {
    Ok(_) => ()
    Err(err) => return Err(err)
  }
  if points.length() < 2 {
    return Ok(0.0)
  }
  let mut total = 0.0
  for i in 1.. total = total + distance
      Err(err) => return Err(err)
    }
  }
  Ok(total)
}

///|
pub fn bounding_box(points : ArrayView[Point]) -> Result[BBox, RouteError] {
  match require_points(points) {
    Ok(_) => ()
    Err(err) => return Err(err)
  }
  let first = points[0]
  let mut min_lat = first.lat
  let mut min_lon = first.lon
  let mut max_lat = first.lat
  let mut max_lon = first.lon
  for i in 1.. max_lat {
      max_lat = point.lat
    }
    if point.lon > max_lon {
      max_lon = point.lon
    }
  }
  Ok({ min_lat, min_lon, max_lat, max_lon })
}

///|
pub fn BBox::center(self : BBox) -> Point {
  Point(
    (self.min_lat + self.max_lat) / 2.0,
    (self.min_lon + self.max_lon) / 2.0,
  )
}