Skip to main content

rustsim_transit/
route.rs

1//! Transit routes: an ordered sequence of stops plus a schedule.
2
3use crate::schedule::Schedule;
4use crate::stop::StopId;
5
6/// Stable identifier for a [`Route`].
7pub type RouteId = u64;
8
9/// An ordered list of stops served by one transit line.
10#[derive(Debug, Clone)]
11pub struct Route {
12    /// Unique identifier.
13    pub id: RouteId,
14    /// Line name (e.g. "N17", "Central").
15    pub name: String,
16    /// Ordered stop IDs.
17    pub stops: Vec<StopId>,
18    /// Typical inter-stop travel times (s). Length == `stops.len() - 1`.
19    pub inter_stop_times: Vec<f64>,
20    /// Departure pattern from the first stop.
21    pub schedule: Schedule,
22}
23
24impl Route {
25    /// Construct a route.
26    pub fn new(
27        id: RouteId,
28        name: impl Into<String>,
29        stops: Vec<StopId>,
30        inter_stop_times: Vec<f64>,
31        schedule: Schedule,
32    ) -> Self {
33        assert!(stops.len() >= 2, "route needs at least two stops");
34        assert_eq!(
35            inter_stop_times.len(),
36            stops.len() - 1,
37            "inter_stop_times length must be stops.len() - 1"
38        );
39        Self {
40            id,
41            name: name.into(),
42            stops,
43            inter_stop_times,
44            schedule,
45        }
46    }
47
48    /// Total scheduled runtime from first to last stop, ignoring dwell.
49    pub fn scheduled_runtime(&self) -> f64 {
50        self.inter_stop_times.iter().sum()
51    }
52
53    /// Zero-based index of `stop_id` in this route, or `None` if the route
54    /// does not serve it.
55    pub fn index_of(&self, stop_id: StopId) -> Option<usize> {
56        self.stops.iter().position(|s| *s == stop_id)
57    }
58}
59
60#[cfg(test)]
61mod tests {
62    use super::*;
63
64    #[test]
65    fn route_runtime_sums_inter_stop() {
66        let r = Route::new(
67            42,
68            "N17",
69            vec![1, 2, 3, 4],
70            vec![60.0, 90.0, 75.0],
71            Schedule::fixed_headway(600.0),
72        );
73        assert_eq!(r.scheduled_runtime(), 225.0);
74        assert_eq!(r.index_of(3), Some(2));
75    }
76
77    #[test]
78    #[should_panic]
79    fn inter_stop_length_mismatch_panics() {
80        Route::new(
81            1,
82            "bad",
83            vec![1, 2, 3],
84            vec![60.0], // only one, needs two
85            Schedule::fixed_headway(600.0),
86        );
87    }
88}