use super::funejson::Value; use super::geo_distance::distance; use super::geo_point_in_polygon::{geo_point_from_value, GeoPoint}; /// Great-circle length of a route through the stops in order, leg by leg, /// optionally closing back to the first stop. /// /// # Panics /// Panics on an empty list or a coordinate out of range. pub fn route_distance(stops: &[GeoPoint], return_to_start: bool) -> RouteDistance { if stops.is_empty() { panic!("stops must not be empty"); } let mut leg_metres: Vec = Vec::new(); // 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). let mut total_millimetres: i64 = 0; let mut add_leg = |a: &GeoPoint, b: &GeoPoint| { let metres = distance(a.lat, a.lng, b.lat, b.lng); leg_metres.push(metres); total_millimetres += (metres * 1000.0 + 0.5).floor() as i64; }; for i in 1..stops.len() { add_leg(&stops[i - 1], &stops[i]); } if return_to_start && stops.len() > 1 { add_leg(&stops[stops.len() - 1], &stops[0]); } RouteDistance { total_metres: total_millimetres as f64 / 1000.0, leg_metres, } } pub fn route_distance_to_value(r: &RouteDistance) -> Value { Value::obj(vec![ ("totalMetres", Value::Float(r.total_metres)), ("legMetres", Value::Arr(r.leg_metres.iter().map(|m| Value::Float(*m)).collect())), ]) } pub fn fune_vector(args: &[Value]) -> Value { let stops: Vec = args[0].as_arr().iter().map(geo_point_from_value).collect(); route_distance_to_value(&route_distance(&stops, args[1].as_bool())) }