use crate::Timetable;
use crate::ids::RouteIdx;
use crate::ids::StopIdx;
use crate::ids::TripIdx;
use crate::label::ArrivalTime;
use crate::label::Label;
use crate::time::Duration;
use crate::time::SecondOfDay;
#[derive(Debug, Clone)]
pub struct Journey<L: Label = ArrivalTime> {
pub origin: StopIdx,
pub target: StopIdx,
pub target_walk: Duration,
pub plan: Vec<(RouteIdx, StopIdx)>,
pub label: L,
}
impl<L: Label> Journey<L> {
pub fn arrival(&self) -> SecondOfDay {
self.label.arrival()
}
pub fn with_timing<T: Timetable>(
&self,
tt: &T,
depart: SecondOfDay,
origin_walk: Duration,
) -> Result<Vec<TimedLeg>, TimingError> {
let mut legs = Vec::with_capacity(self.plan.len());
let mut current_time = depart + origin_walk;
let mut current_stop = self.origin;
for (leg, &(route, alight)) in self.plan.iter().enumerate() {
let serving_here = tt.get_routes_serving_stop(current_stop);
let (board, board_pos, walk_time) =
if let Some(&(_, pos)) = serving_here.iter().find(|(r, _)| *r == route) {
(current_stop, pos, Duration::ZERO)
} else {
let mut found = None;
for &neighbour in tt.get_footpaths_from(current_stop) {
if let Some(&(_, pos)) = tt
.get_routes_serving_stop(neighbour)
.iter()
.find(|(r, _)| *r == route)
{
let walk = tt.get_transfer_time(current_stop, neighbour);
found = Some((neighbour, pos, walk));
break;
}
}
found.ok_or(TimingError::NoBoardingStop {
leg,
route,
from_stop: current_stop,
})?
};
current_time = current_time + walk_time;
let trip = tt.get_earliest_trip(route, current_time, board_pos).ok_or(
TimingError::NoCatchableTrip {
leg,
route,
board_pos,
at: current_time,
},
)?;
let depart = tt.get_departure_time(trip, board_pos);
let stops_ahead = tt.get_stops_after(route, board_pos);
let alight_offset = stops_ahead.iter().position(|&s| s == alight).ok_or(
TimingError::UnreachableAlight {
leg,
route,
board_pos,
alight,
},
)?;
let alight_pos = board_pos + alight_offset as u32;
let arrive = tt.get_arrival_time(trip, alight_pos);
legs.push(TimedLeg {
route,
board,
alight,
trip,
depart,
arrive,
});
current_time = arrive;
current_stop = alight;
}
Ok(legs)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TimedLeg {
pub route: RouteIdx,
pub board: StopIdx,
pub alight: StopIdx,
pub trip: TripIdx,
pub depart: SecondOfDay,
pub arrive: SecondOfDay,
}
#[non_exhaustive]
#[derive(Debug, Clone, thiserror::Error)]
pub enum TimingError {
#[error(
"leg {leg}: no boarding stop for route {route} reachable from {from_stop} \
(or any one-hop walk neighbour)"
)]
NoBoardingStop {
leg: usize,
route: RouteIdx,
from_stop: StopIdx,
},
#[error(
"leg {leg}: no trip on route {route} departs at or after {at} from position {board_pos}"
)]
NoCatchableTrip {
leg: usize,
route: RouteIdx,
board_pos: u32,
at: SecondOfDay,
},
#[error("leg {leg}: route {route} does not reach stop {alight} from position {board_pos}")]
UnreachableAlight {
leg: usize,
route: RouteIdx,
board_pos: u32,
alight: StopIdx,
},
}