#[cfg(feature = "internal")]
pub mod builders;
use std::collections::{HashMap, HashSet};
use std::fmt::Debug;
use std::hash::Hash;
use crate::{Duration, RouteIdx, SecondOfDay, StopIdx, Timetable, TripIdx};
pub struct SimpleTimetable<S = u32, R = u32, T = u32>
where
S: Hash + Eq + Clone,
R: Hash + Eq + Clone,
T: Hash + Eq + Clone,
{
stop_keys: Vec<S>,
stop_by_key: HashMap<S, StopIdx>,
route_keys: Vec<R>,
route_by_key: HashMap<R, RouteIdx>,
trip_keys: Vec<T>,
trip_by_key: HashMap<T, TripIdx>,
routes: Vec<Vec<StopIdx>>,
#[allow(clippy::type_complexity)]
trips: Vec<Option<(RouteIdx, Vec<(SecondOfDay, SecondOfDay)>)>>,
footpaths: Vec<Vec<StopIdx>>,
transfer_times: HashMap<(StopIdx, StopIdx), Duration>,
routes_for_stop: Vec<Vec<(RouteIdx, u32)>>,
no_pickup: HashSet<(TripIdx, u32)>,
no_drop_off: HashSet<(TripIdx, u32)>,
inaccessible_trips: HashSet<TripIdx>,
inaccessible_stops: HashSet<StopIdx>,
}
impl<S, R, T> SimpleTimetable<S, R, T>
where
S: Hash + Eq + Clone,
R: Hash + Eq + Clone,
T: Hash + Eq + Clone,
{
pub fn new() -> Self {
Self {
stop_keys: Vec::new(),
stop_by_key: HashMap::new(),
route_keys: Vec::new(),
route_by_key: HashMap::new(),
trip_keys: Vec::new(),
trip_by_key: HashMap::new(),
routes: Vec::new(),
trips: Vec::new(),
footpaths: Vec::new(),
transfer_times: HashMap::new(),
routes_for_stop: Vec::new(),
no_pickup: HashSet::new(),
no_drop_off: HashSet::new(),
inaccessible_trips: HashSet::new(),
inaccessible_stops: HashSet::new(),
}
}
fn intern_stop(&mut self, key: S) -> StopIdx {
if let Some(&idx) = self.stop_by_key.get(&key) {
return idx;
}
let idx = StopIdx::new(self.stop_keys.len() as u32);
self.stop_keys.push(key.clone());
self.stop_by_key.insert(key, idx);
self.footpaths.push(Vec::new());
self.routes_for_stop.push(Vec::new());
idx
}
fn intern_route(&mut self, key: R) -> RouteIdx {
if let Some(&idx) = self.route_by_key.get(&key) {
return idx;
}
let idx = RouteIdx::new(self.route_keys.len() as u32);
self.route_keys.push(key.clone());
self.route_by_key.insert(key, idx);
self.routes.push(Vec::new());
idx
}
fn intern_trip(&mut self, key: T) -> TripIdx {
if let Some(&idx) = self.trip_by_key.get(&key) {
return idx;
}
let idx = TripIdx::new(self.trip_keys.len() as u32);
self.trip_keys.push(key.clone());
self.trip_by_key.insert(key, idx);
idx
}
pub fn route(mut self, id: R, stops: &[S], trips: &[(T, &[(SecondOfDay, SecondOfDay)])]) -> Self
where
R: Debug,
T: Debug,
{
let route_idx = self.intern_route(id);
let stop_idxs: Vec<StopIdx> = stops.iter().map(|s| self.intern_stop(s.clone())).collect();
self.routes[route_idx.idx()] = stop_idxs.clone();
for (pos, &s) in stop_idxs.iter().enumerate() {
let entry = &mut self.routes_for_stop[s.idx()];
if !entry.iter().any(|(r, _)| *r == route_idx) {
entry.push((route_idx, pos as u32));
}
}
for (trip_id, times) in trips {
assert_eq!(
times.len(),
stops.len(),
"trip {trip_id:?} has {} times but route has {} stops",
times.len(),
stops.len()
);
let trip_idx = self.intern_trip(trip_id.clone());
while self.trips.len() <= trip_idx.idx() {
self.trips.push(None);
}
self.trips[trip_idx.idx()] = Some((route_idx, times.to_vec()));
}
self
}
pub fn register_stop(mut self, key: S) -> Self {
let _ = self.intern_stop(key);
self
}
pub fn footpath(mut self, from: S, to: S) -> Self {
let from_idx = self.intern_stop(from);
let to_idx = self.intern_stop(to);
self.footpaths[from_idx.idx()].push(to_idx);
self
}
pub fn transfer_time(mut self, from: S, to: S, time: Duration) -> Self {
let from_idx = self.intern_stop(from);
let to_idx = self.intern_stop(to);
self.transfer_times.insert((from_idx, to_idx), time);
self
}
pub fn no_pickup_at(mut self, trip: T, pos: u32) -> Self {
let trip_idx = self.trip_idx_of(&trip);
self.no_pickup.insert((trip_idx, pos));
self
}
pub fn no_drop_off_at(mut self, trip: T, pos: u32) -> Self {
let trip_idx = self.trip_idx_of(&trip);
self.no_drop_off.insert((trip_idx, pos));
self
}
pub fn no_wheelchair_on_trip(mut self, trip: T) -> Self {
let trip_idx = self.trip_idx_of(&trip);
self.inaccessible_trips.insert(trip_idx);
self
}
pub fn no_wheelchair_at_stop(mut self, stop: S) -> Self {
let stop_idx = self.stop_idx_of(&stop);
self.inaccessible_stops.insert(stop_idx);
self
}
pub fn stop_idx_of(&self, key: &S) -> StopIdx {
*self
.stop_by_key
.get(key)
.expect("stop key not registered with this SimpleTimetable")
}
pub fn route_idx_of(&self, key: &R) -> RouteIdx {
*self
.route_by_key
.get(key)
.expect("route key not registered with this SimpleTimetable")
}
pub fn trip_idx_of(&self, key: &T) -> TripIdx {
*self
.trip_by_key
.get(key)
.expect("trip key not registered with this SimpleTimetable")
}
}
impl<S, R, T> Default for SimpleTimetable<S, R, T>
where
S: Hash + Eq + Clone,
R: Hash + Eq + Clone,
T: Hash + Eq + Clone,
{
fn default() -> Self {
Self::new()
}
}
impl<S, R, T> Timetable for SimpleTimetable<S, R, T>
where
S: Hash + Eq + Clone + Debug,
R: Hash + Eq + Clone + Debug,
T: Hash + Eq + Clone + Debug,
{
fn n_stops(&self) -> usize {
self.stop_keys.len()
}
fn n_routes(&self) -> usize {
self.route_keys.len()
}
fn get_routes_serving_stop(&self, stop: StopIdx) -> &[(RouteIdx, u32)] {
self.routes_for_stop[stop.idx()].as_slice()
}
fn get_stops_after(&self, route: RouteIdx, pos: u32) -> &[StopIdx] {
&self.routes[route.idx()][pos as usize..]
}
fn stop_at(&self, route: RouteIdx, pos: u32) -> StopIdx {
self.routes[route.idx()][pos as usize]
}
fn get_earliest_trip(&self, route: RouteIdx, at: SecondOfDay, pos: u32) -> Option<TripIdx> {
let no_pickup_empty = self.no_pickup.is_empty();
self.trips
.iter()
.enumerate()
.filter_map(|(i, slot)| slot.as_ref().map(|entry| (i, entry)))
.filter(|(_, (r, _))| *r == route)
.filter(|(_, (_, times))| times[pos as usize].1 >= at)
.filter(|(i, _)| {
no_pickup_empty || !self.no_pickup.contains(&(TripIdx::new(*i as u32), pos))
})
.min_by_key(|(_, (_, times))| times[pos as usize].1)
.map(|(trip_idx, _)| TripIdx::new(trip_idx as u32))
}
fn earliest_accessible_trip(
&self,
route: RouteIdx,
at: SecondOfDay,
pos: u32,
require_wheelchair_accessible: bool,
) -> Option<TripIdx> {
if !require_wheelchair_accessible {
return self.get_earliest_trip(route, at, pos);
}
let no_pickup_empty = self.no_pickup.is_empty();
self.trips
.iter()
.enumerate()
.filter_map(|(i, slot)| slot.as_ref().map(|entry| (i, entry)))
.filter(|(_, (r, _))| *r == route)
.filter(|(_, (_, times))| times[pos as usize].1 >= at)
.filter(|(i, _)| {
no_pickup_empty || !self.no_pickup.contains(&(TripIdx::new(*i as u32), pos))
})
.filter(|(i, _)| !self.inaccessible_trips.contains(&TripIdx::new(*i as u32)))
.min_by_key(|(_, (_, times))| times[pos as usize].1)
.map(|(trip_idx, _)| TripIdx::new(trip_idx as u32))
}
fn get_arrival_time(&self, trip: TripIdx, pos: u32) -> SecondOfDay {
let (_route, times) = self.trips[trip.idx()].as_ref().expect(
"trip slot must be filled by SimpleTimetable::route() before any algorithm call",
);
times[pos as usize].0
}
fn get_departure_time(&self, trip: TripIdx, pos: u32) -> SecondOfDay {
let (_route, times) = self.trips[trip.idx()].as_ref().expect(
"trip slot must be filled by SimpleTimetable::route() before any algorithm call",
);
times[pos as usize].1
}
fn get_footpaths_from(&self, stop: StopIdx) -> &[StopIdx] {
self.footpaths[stop.idx()].as_slice()
}
fn get_transfer_time(&self, from: StopIdx, to: StopIdx) -> Duration {
self.transfer_times
.get(&(from, to))
.copied()
.unwrap_or(Duration(1))
}
fn pickup_allowed(&self, trip: TripIdx, pos: u32) -> bool {
self.no_pickup.is_empty() || !self.no_pickup.contains(&(trip, pos))
}
fn drop_off_allowed(&self, trip: TripIdx, pos: u32) -> bool {
self.no_drop_off.is_empty() || !self.no_drop_off.contains(&(trip, pos))
}
fn trip_wheelchair_accessible(&self, trip: TripIdx) -> bool {
self.inaccessible_trips.is_empty() || !self.inaccessible_trips.contains(&trip)
}
fn stop_wheelchair_accessible(&self, stop: StopIdx) -> bool {
self.inaccessible_stops.is_empty() || !self.inaccessible_stops.contains(&stop)
}
}
#[cfg(feature = "dotgraph")]
impl<S, R, T> SimpleTimetable<S, R, T>
where
S: Hash + Eq + Clone + Debug + std::fmt::Display,
R: Hash + Eq + Clone + Debug,
T: Hash + Eq + Clone + Debug,
{
pub fn to_dot(&self, name: &str) -> Result<String, std::io::Error> {
use dot_graph::{Edge, Graph, Kind, Node, Style};
let mut graph = Graph::new(name, Kind::Digraph);
for (idx, key) in self.stop_keys.iter().enumerate() {
let _ = idx;
graph.add_node(Node::new(&format!("s{key}")));
}
const COLORS: &[&str] = &[
"red",
"blue",
"green",
"orange",
"purple",
"brown",
"deeppink",
"darkgreen",
"navy",
"goldenrod",
];
for (route_idx, route_stops) in self.routes.iter().enumerate() {
let color = COLORS[route_idx % COLORS.len()];
let route_idx_typed = RouteIdx::new(route_idx as u32);
#[allow(clippy::type_complexity)]
let route_trips: Vec<(
usize,
&(RouteIdx, Vec<(SecondOfDay, SecondOfDay)>),
)> = self
.trips
.iter()
.enumerate()
.filter_map(|(i, slot)| slot.as_ref().map(|entry| (i, entry)))
.filter(|(_, (r, _))| *r == route_idx_typed)
.collect();
for (i, window) in route_stops.windows(2).enumerate() {
let from_key = &self.stop_keys[window[0].idx()];
let to_key = &self.stop_keys[window[1].idx()];
let route_key = &self.route_keys[route_idx];
let mut label = format!("R{route_key:?}");
for (trip_idx, (_, times)) in &route_trips {
let trip_key = &self.trip_keys[*trip_idx];
let dep = times[i].1;
let arr = times[i + 1].0;
label.push_str(&format!("\\nT{trip_key:?}: {dep}\u{2192}{arr}"));
}
graph.add_edge(
Edge::new(&format!("s{from_key}"), &format!("s{to_key}"), &label)
.color(Some(color)),
);
}
}
for (from_idx, targets) in self.footpaths.iter().enumerate() {
let from_key = &self.stop_keys[from_idx];
let from_typed = StopIdx::new(from_idx as u32);
for &to in targets {
let to_key = &self.stop_keys[to.idx()];
let time = self
.transfer_times
.get(&(from_typed, to))
.copied()
.unwrap_or(Duration(1));
graph.add_edge(
Edge::new(
&format!("s{from_key}"),
&format!("s{to_key}"),
&format!("t={time}"),
)
.style(Style::Dashed)
.color(Some("gray")),
);
}
}
graph.to_dot_string()
}
}