hrdf_routing_engine/
lib.rs

1mod app;
2mod debug;
3mod isochrone;
4mod routing;
5mod service;
6mod utils;
7
8#[cfg(feature = "hectare")]
9pub use app::run_surface_per_ha;
10pub use app::{run_average, run_comparison, run_optimal, run_simple, run_worst};
11pub use debug::run_debug;
12pub use isochrone::externals::{ExcludedPolygons, LAKES_GEOJSON_URLS};
13pub use isochrone::{IsochroneArgs, IsochroneDisplayMode};
14#[cfg(feature = "hectare")]
15pub use isochrone::{IsochroneHectareArgs, externals::HectareData};
16pub use routing::{Route, plan_journey, plan_shortest_journey};
17pub use service::run_service;
18
19#[cfg(test)]
20mod tests {
21    use std::error::Error;
22
23    use crate::debug::{test_find_reachable_stops_within_time_limit, test_plan_journey};
24    use chrono::{TimeDelta, Timelike};
25    use hrdf_parser::{Hrdf, Version};
26    use ojp_rs::{OJP, SimplifiedLeg, SimplifiedTrip};
27
28    use test_log::test;
29
30    use crate::{Route, plan_shortest_journey};
31    use futures::future::join_all;
32
33    struct STrip(SimplifiedTrip);
34
35    impl STrip {
36        fn from(value: &Route, hrdf: &Hrdf) -> Self {
37            let mut prev_arr_time = value.departure_at();
38            let legs = value
39                .sections()
40                .iter()
41                .map(|s| {
42                    let departure_id = s.departure_stop_id();
43                    let departure_stop = s.departure_stop_name(hrdf.data_storage());
44                    let arrival_id = s.arrival_stop_id();
45                    let arrival_stop = s.arrival_stop_name(hrdf.data_storage());
46                    let departure_time = s.departure_at().unwrap_or(prev_arr_time);
47                    let arrival_time = s.arrival_at().unwrap_or(
48                        prev_arr_time + TimeDelta::minutes(s.duration().unwrap_or(0) as i64),
49                    );
50                    prev_arr_time = arrival_time;
51                    SimplifiedLeg::new(
52                        departure_id,
53                        departure_stop,
54                        arrival_id,
55                        arrival_stop,
56                        departure_time,
57                        arrival_time,
58                        format!("{:?}", s.transport()),
59                    )
60                })
61                .collect::<Vec<_>>();
62            STrip(SimplifiedTrip::new(legs))
63        }
64    }
65
66    static IDS: [(i32, i32); 34] = [
67        (8577820, 8501120),
68        (8572662, 8576724),
69        (8593320, 8579237),
70        (8592862, 8500236),
71        (8592458, 8595922),
72        (8591921, 8589143),
73        (8591915, 8583275),
74        (8591611, 8595689),
75        (8590925, 8592776),
76        (8589645, 8583274),
77        (8589632, 8591245),
78        (8589164, 8592567),
79        (8591610, 8575154),
80        (8591921, 8581062),
81        (8592837, 8588351),
82        (8591363, 8504100),
83        (8580798, 8588731),
84        (8592587, 8593462),
85        (8596094, 8589007),
86        (8583005, 8591046),
87        (8589151, 8592547),
88        (8588949, 8580456),
89        (8573693, 8504354),
90        (8509076, 8587619),
91        (8501120, 8579006),
92        (8591418, 8592834),
93        (8570732, 8573673),
94        (8578997, 8576815),
95        (8585206, 8506302),
96        (8589587, 8592133),
97        (22, 8592904),
98        (8592889, 8589566),
99        (8572453, 8591998),
100        (8500236, 8511236),
101    ];
102
103    pub async fn test_paths_validity(
104        hrdf: &Hrdf,
105        ids: &[(i32, i32)],
106    ) -> Result<Vec<(Option<SimplifiedTrip>, Option<SimplifiedTrip>)>, Box<dyn Error>> {
107        let ref_trips = ids
108            .iter()
109            .map(|(from_id, to_id)| {
110                let fname = format!("test_xml/{from_id}_{to_id}_trip.xml");
111                let xml = std::fs::read_to_string(fname).unwrap();
112                let ojp = OJP::try_from(xml.as_str()).unwrap();
113
114                let ref_trip = ojp.fastest_trip().unwrap();
115
116                SimplifiedTrip::try_from(ref_trip).unwrap()
117            })
118            .collect::<Vec<_>>();
119
120        let hrdf_trips = ref_trips
121            .iter()
122            .map(|st| async move {
123                let from_id = st.departure_id();
124                let to_id = st.arrival_id();
125                let date_time = st.departure_time().with_second(0).unwrap();
126                plan_shortest_journey(hrdf, from_id, to_id, date_time, 10, false)
127                    .as_ref()
128                    .map(|r| STrip::from(r, hrdf).0)
129            })
130            .collect::<Vec<_>>();
131        let hrdf_trips: Vec<_> = join_all(hrdf_trips).await;
132        // We are only interested in the "failures" of the hrdf routing engine
133        let failed_comparison = ref_trips
134            .into_iter()
135            .zip(hrdf_trips.into_iter())
136            .filter_map(|(rt, ht)| {
137                if let Some(ht) = ht
138                    && !rt.approx_equal(&ht, 0.1)
139                {
140                    Some((Some(rt), Some(ht)))
141                } else {
142                    None
143                }
144            })
145            .collect::<Vec<_>>();
146
147        Ok(failed_comparison)
148    }
149
150    #[test(tokio::test)]
151    async fn test_journeys() {
152        // First build hrdf file
153        let hrdf = Hrdf::new(
154            Version::V_5_40_41_2_0_7,
155            "https://data.opentransportdata.swiss/en/dataset/timetable-54-2025-hrdf/permalink",
156            false,
157            None,
158        )
159        .await
160        .unwrap();
161        let failures = test_paths_validity(&hrdf, &IDS).await.unwrap();
162        for f in failures.iter() {
163            if let (Some(ojp_trip), Some(hrdf_trip)) = f {
164                eprintln!("{} - {}", ojp_trip.departure_id(), ojp_trip.arrival_id());
165                eprintln!("OJP: \n{ojp_trip}");
166                eprintln!("HRDF: \n{hrdf_trip}");
167            }
168        }
169        assert!(failures.is_empty());
170        test_plan_journey(&hrdf);
171        test_find_reachable_stops_within_time_limit(&hrdf);
172    }
173}