#[derive(Debug, Clone)]
pub struct RouteRequest {
pub waypoints: Vec<(f64, f64)>,
pub profile: Profile,
}
impl RouteRequest {
pub fn from_to(origin: (f64, f64), destination: (f64, f64), profile: Profile) -> Self {
Self {
waypoints: vec![origin, destination],
profile,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Profile {
Driving,
Cycling,
Walking,
}
#[derive(Debug, Clone)]
pub struct Route {
pub geometry: Vec<(f64, f64)>,
pub distance_m: f64,
pub duration_s: f64,
pub maneuvers: Vec<Maneuver>,
}
#[derive(Debug, Clone)]
pub struct Maneuver {
pub location: (f64, f64),
pub kind: ManeuverKind,
pub distance_m: f64,
pub duration_s: f64,
pub road_name: Option<String>,
}
impl Maneuver {
pub fn instruction_text(&self) -> String {
use ManeuverKind::*;
let road = self
.road_name
.as_deref()
.map(|n| format!(" onto {n}"))
.unwrap_or_default();
let road_on = self
.road_name
.as_deref()
.map(|n| format!(" on {n}"))
.unwrap_or_default();
match self.kind {
Depart => format!("Head out{road_on}"),
Arrive => "Arrive at destination".to_string(),
Continue => format!("Continue straight{road_on}"),
TurnSlightLeft => format!("Bear left{road}"),
TurnLeft => format!("Turn left{road}"),
TurnSharpLeft => format!("Take a sharp left{road}"),
TurnSlightRight => format!("Bear right{road}"),
TurnRight => format!("Turn right{road}"),
TurnSharpRight => format!("Take a sharp right{road}"),
UTurn => format!("Make a U-turn{road}"),
Merge => format!("Merge{road}"),
Roundabout { exit } => {
format!("At the roundabout, take exit {exit}{road}")
}
Exit => format!("Take the exit{road}"),
Fork => format!("Keep at the fork{road}"),
Other => format!("Continue{road_on}"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ManeuverKind {
Depart,
Arrive,
Continue,
TurnSlightLeft,
TurnLeft,
TurnSharpLeft,
TurnSlightRight,
TurnRight,
TurnSharpRight,
UTurn,
Merge,
Roundabout {
exit: u8,
},
Exit,
Fork,
Other,
}
#[derive(Debug)]
pub enum RouteError {
InvalidRequest(String),
Network(String),
Parse(String),
NoRoute,
}
impl std::fmt::Display for RouteError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
RouteError::InvalidRequest(msg) => write!(f, "invalid route request: {msg}"),
RouteError::Network(msg) => write!(f, "routing network error: {msg}"),
RouteError::Parse(msg) => write!(f, "routing parse error: {msg}"),
RouteError::NoRoute => write!(f, "no route between waypoints"),
}
}
}
impl std::error::Error for RouteError {}
pub trait Router: Send + Sync {
fn route(&self, request: &RouteRequest) -> Result<Route, RouteError>;
}
impl<T: Router + ?Sized> Router for Box<T> {
fn route(&self, request: &RouteRequest) -> Result<Route, RouteError> {
(**self).route(request)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn instruction_text_uses_road_name_when_present() {
let m = Maneuver {
location: (0.0, 0.0),
kind: ManeuverKind::TurnLeft,
distance_m: 100.0,
duration_s: 30.0,
road_name: Some("Main Street".into()),
};
assert_eq!(m.instruction_text(), "Turn left onto Main Street");
}
#[test]
fn instruction_text_handles_missing_road_name() {
let m = Maneuver {
location: (0.0, 0.0),
kind: ManeuverKind::TurnRight,
distance_m: 100.0,
duration_s: 30.0,
road_name: None,
};
assert_eq!(m.instruction_text(), "Turn right");
}
#[test]
fn roundabout_includes_exit_number() {
let m = Maneuver {
location: (0.0, 0.0),
kind: ManeuverKind::Roundabout { exit: 3 },
distance_m: 50.0,
duration_s: 10.0,
road_name: Some("A1".into()),
};
assert!(m.instruction_text().contains("exit 3"));
assert!(m.instruction_text().contains("A1"));
}
#[test]
fn from_to_constructs_two_waypoint_request() {
let req = RouteRequest::from_to((-0.1276, 51.5074), (-0.0876, 51.5174), Profile::Driving);
assert_eq!(req.waypoints.len(), 2);
assert_eq!(req.profile, Profile::Driving);
}
}