use geo::{Bearing, Distance, Haversine, LineString};
use routers_network::{Entry, Metadata, Network, Node};
#[derive(Clone, Debug)]
pub struct Trip<E>(Vec<Node<E>>)
where
E: Entry;
impl<E> From<Vec<Node<E>>> for Trip<E>
where
E: Entry,
{
fn from(nodes: Vec<Node<E>>) -> Self {
Trip(nodes)
}
}
impl<E> Trip<E>
where
E: Entry,
{
pub fn new(nodes: impl IntoIterator<Item = Node<E>>) -> Self {
Self(nodes.into_iter().collect::<Vec<_>>())
}
pub(crate) fn linestring(&self) -> LineString {
self.0.iter().map(|v| v.position).collect::<LineString>()
}
pub fn new_with_map<M: Metadata>(map: &dyn Network<E, M>, nodes: &[E]) -> Self {
let resolved = map.line(nodes);
let nodes = resolved
.into_iter()
.zip(nodes)
.map(|(point, id)| Node::new(point, *id));
Trip::new(nodes)
}
pub fn delta_angle(&self) -> Vec<f64> {
self.headings()
.windows(2)
.map(|bearings| {
if let [prev, curr] = bearings {
let mut turn_angle = (curr - prev).abs();
if turn_angle > 180.0 {
turn_angle -= 360.0;
} else if turn_angle < -180.0 {
turn_angle += 360.0;
}
turn_angle.abs()
} else {
0.0
}
})
.collect()
}
pub fn headings(&self) -> Vec<f64> {
self.0
.windows(2)
.filter_map(|entries| {
if let [a, b] = entries {
if Haversine.distance(a.position, b.position) < 1.0 {
return None;
}
Some(Haversine.bearing(a.position, b.position))
} else {
None
}
})
.collect::<Vec<_>>()
}
pub fn total_angle(&self) -> f64 {
self.delta_angle().into_iter().sum()
}
pub fn immediate_angle(&self) -> f64 {
self.total_angle() / (self.0.len() as f64)
}
pub fn angular_complexity(&self, distance: f64) -> f64 {
const U_TURN: f64 = 179.;
const DIST_BETWEEN_ZIGZAG: f64 = 100.0; const ZIG_ZAG: f64 = 180.0;
let num_zig_zags: f64 = (distance / DIST_BETWEEN_ZIGZAG).max(1.);
let sum = self.total_angle();
if self.delta_angle().iter().any(|v| v.abs() >= U_TURN) {
return 0.0;
}
let theoretical_max = num_zig_zags * ZIG_ZAG;
1.0 - (sum / theoretical_max).sqrt().clamp(0.0, 1.0)
}
pub fn length(&self) -> f64 {
self.0.windows(2).fold(0.0, |length, node| {
if let [a, b] = node {
return length + Haversine.distance(a.position, b.position);
}
length
})
}
}