1use itertools::Itertools;
2use log::info;
3
4use crate::{edge::InputEdge, geometry::FPCoordinate, graph::NodeID, io::read_lines};
5
6pub enum WeightType {
7 Unit,
8 Original,
9}
10
11pub enum Direction {
12 Both = 0,
13 Forward = 1,
14 Reverse = 2,
15 Closed = 3,
16}
17
18impl TryFrom<i32> for Direction {
19 type Error = ();
20
21 fn try_from(v: i32) -> Result<Self, Self::Error> {
22 match v {
23 x if x == Direction::Both as i32 => Ok(Direction::Both),
24 x if x == Direction::Forward as i32 => Ok(Direction::Forward),
25 x if x == Direction::Reverse as i32 => Ok(Direction::Reverse),
26 x if x == Direction::Closed as i32 => Ok(Direction::Closed),
27 _ => Err(()),
28 }
29 }
30}
31
32pub fn read_graph<T: std::cmp::Eq + From<usize>>(
33 filename: &str,
34 _weight_type: WeightType,
35) -> Vec<InputEdge<T>> {
36 let mut edges = Vec::new();
37
38 let mut lines = read_lines(filename).expect("could not load graph file");
39
40 let first_line = lines.next().unwrap().unwrap();
41 let sizes = first_line
42 .get(..)
43 .unwrap_or("")
44 .split_ascii_whitespace()
45 .collect_vec();
46 let number_of_nodes = sizes[0].parse::<NodeID>().unwrap();
47
48 for (source, line) in lines.enumerate() {
50 let line = line.unwrap();
51 let tokens = line.get(..).unwrap_or("").split_whitespace().collect_vec();
52
53 assert!(source < number_of_nodes);
54
55 for token in tokens {
56 let target = token.parse::<NodeID>().unwrap() - 1;
57 assert!(target < number_of_nodes);
58 if source == target {
60 continue;
61 }
62
63 edges.push(InputEdge {
64 source,
65 target,
66 data: T::from(1),
67 });
68 }
69 }
70 info!("loaded {} directed edges", edges.len());
71 edges
72}
73
74pub fn read_coordinates(filename: &str) -> Vec<FPCoordinate> {
75 let mut coordinates = Vec::new();
76 for line in read_lines(filename).expect("could not load coordinates file") {
77 let line = line.unwrap();
78 let tokens = line.get(..).unwrap_or("").split_whitespace().collect_vec();
79 let lon = tokens[0].parse::<f64>().unwrap() / 100_000.;
80 let lat = tokens[1].parse::<f64>().unwrap() / 100_000.;
81 coordinates.push(FPCoordinate::new_from_lat_lon(lat, lon));
83 }
84
85 coordinates
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91 use std::fs::write;
92 use tempfile::NamedTempFile;
93
94 #[test]
95 fn read_graph_valid() {
96 let graph_content = "4 8\n3 2\n2 3\n1 3\n1 2\n";
97 let tmp_file = NamedTempFile::new().unwrap();
98 write(tmp_file.path(), graph_content).unwrap();
99
100 let edges = read_graph::<usize>(tmp_file.path().to_str().unwrap(), WeightType::Unit);
101
102 assert_eq!(edges.len(), 6);
103 assert_eq!(
104 edges[0],
105 InputEdge {
106 source: 0,
107 target: 2,
108 data: 1
109 }
110 );
111 assert_eq!(
112 edges[1],
113 InputEdge {
114 source: 0,
115 target: 1,
116 data: 1
117 }
118 );
119 assert_eq!(
120 edges[2],
121 InputEdge {
122 source: 1,
123 target: 2,
124 data: 1
125 }
126 );
127 assert_eq!(
128 edges[3],
129 InputEdge {
130 source: 2,
131 target: 0,
132 data: 1
133 }
134 );
135 assert_eq!(
136 edges[4],
137 InputEdge {
138 source: 3,
139 target: 0,
140 data: 1
141 }
142 );
143 assert_eq!(
144 edges[5],
145 InputEdge {
146 source: 3,
147 target: 1,
148 data: 1
149 }
150 );
151 }
152
153 #[test]
154 fn read_coordinates_valid() {
155 let coord_content = "1234567 4567890\n2345678 5678901\n";
156 let tmp_file = NamedTempFile::new().unwrap();
157 write(tmp_file.path(), coord_content).unwrap();
158
159 let coords = read_coordinates(tmp_file.path().to_str().unwrap());
160
161 assert_eq!(coords.len(), 2);
162 let (lon, lat) = coords[0].to_lon_lat_pair();
163 assert!((lat - 45.67890).abs() < 1e-5);
164 assert!((lon - 12.34567).abs() < 1e-5);
165 }
166
167 #[test]
168 #[should_panic]
169 fn read_graph_invalid_file() {
170 read_graph::<usize>("nonexistent_file.txt", WeightType::Unit);
171 }
172
173 #[test]
174 #[should_panic]
175 fn read_graph_invalid_format() {
176 let graph_content = "not a number\n1 2\n";
177 let tmp_file = NamedTempFile::new().unwrap();
178 write(tmp_file.path(), graph_content).unwrap();
179
180 read_graph::<usize>(tmp_file.path().to_str().unwrap(), WeightType::Unit);
181 }
182
183 #[test]
184 #[should_panic]
185 fn read_graph_node_out_of_bounds() {
186 let graph_content = "2 1\n3\n1\n"; let tmp_file = NamedTempFile::new().unwrap();
188 write(tmp_file.path(), graph_content).unwrap();
189
190 read_graph::<usize>(tmp_file.path().to_str().unwrap(), WeightType::Unit);
191 }
192
193 #[test]
194 fn read_graph_skip_eigenloops() {
195 let graph_content = "2 1\n1 1\n2\n";
196 let tmp_file = NamedTempFile::new().unwrap();
197 write(tmp_file.path(), graph_content).unwrap();
198
199 let edges = read_graph::<usize>(tmp_file.path().to_str().unwrap(), WeightType::Unit);
200
201 assert_eq!(edges.len(), 0); }
203
204 #[test]
205 fn direction_try_from() {
206 assert!(matches!(Direction::try_from(0), Ok(Direction::Both)));
208 assert!(matches!(Direction::try_from(1), Ok(Direction::Forward)));
209 assert!(matches!(Direction::try_from(2), Ok(Direction::Reverse)));
210 assert!(matches!(Direction::try_from(3), Ok(Direction::Closed)));
211
212 assert!(Direction::try_from(-1).is_err());
214 assert!(Direction::try_from(4).is_err());
215 }
216
217 #[test]
218 fn direction_as_i32() {
219 assert_eq!(Direction::Both as i32, 0);
220 assert_eq!(Direction::Forward as i32, 1);
221 assert_eq!(Direction::Reverse as i32, 2);
222 assert_eq!(Direction::Closed as i32, 3);
223 }
224}