1use crate::data::SurveyData;
8use crate::station::Point;
9use crate::survex;
10use log::trace;
11use std::error::Error;
12use std::ffi::{c_char, CStr};
13use std::path::PathBuf;
14use std::ptr;
15use uuid::Uuid;
16
17pub fn load_from_path(path: PathBuf) -> Result<SurveyData, Box<dyn Error>> {
24 let filename = path
26 .to_str()
27 .expect("Could not convert path to string")
28 .as_ptr() as *const c_char;
29
30 let mut data = SurveyData::new();
32
33 let mut connections = Vec::new();
39
40 let pimg;
48 let (mut x, mut y, mut z) = (-1.0, -1.0, -1.0);
49 let mut label = "";
50 let mut p = survex::img_point {
51 x: 0.0,
52 y: 0.0,
53 z: 0.0,
54 };
55
56 trace!(
58 "Opening Survex file '{:?}' in load_from_path function via Survex img library.",
59 path
60 );
61 unsafe {
62 pimg = survex::img_open_survey(filename, ptr::null_mut());
63 }
64 if pimg.is_null() {
65 trace!("Survex library returned a null pointer. Read failed.");
66 return Err("Could not open Survex file".into());
67 }
68
69 trace!("Reading Survex file in load_from_path function.");
73 loop {
74 let result = unsafe { survex::img_read_item(pimg, &mut p) };
75
76 #[allow(clippy::if_same_then_else)]
77 if result == -2 {
78 panic!("Bad data in Survex file.");
80 } else if result == -1 {
81 trace!("STOP: End of Survex file reached.");
82 break;
84 } else if result == 0 {
85 (x, y, z) = (p.x, p.y, p.z);
87 trace!("MOVE: {}, {}, {}.", x, y, z);
88 } else if result == 1 {
89 let from_coords = Point::new(x, y, z);
95 let to_coords = Point::new(p.x, p.y, p.z);
96 connections.push((from_coords, to_coords));
97 trace!("LINE: {} -> {}.", from_coords, to_coords);
98 (x, y, z) = (p.x, p.y, p.z);
99 } else if result == 2 {
100 trace!("CROSS command received. Ignoring.");
102 } else if result == 3 {
103 let flags;
105 unsafe {
106 label = CStr::from_ptr((*pimg).label).to_str().unwrap();
107 flags = (*pimg).flags & 0x7f;
108 }
109 let coords = Point::new(p.x, p.y, p.z);
110 let (station, _) = data.add_or_update(coords, label);
111 trace!("LABEL: {} -> {}.", coords, label);
112
113 if flags & 0x01 != 0 {
115 station.borrow_mut().surface = true;
116 trace!("LABEL: surface flag set for station '{}'.", label);
117 }
118 if flags & 0x02 != 0 {
119 station.borrow_mut().underground = true;
120 trace!("LABEL: underground flag set for station '{}'.", label);
121 }
122 if flags & 0x04 != 0 {
123 station.borrow_mut().entrance = true;
124 trace!("LABEL: entrance flag set for station '{}'.", label);
125 }
126 if flags & 0x08 != 0 {
127 station.borrow_mut().exported = true;
128 trace!("LABEL: exported flag set for station '{}'.", label);
129 }
130 if flags & 0x10 != 0 {
131 station.borrow_mut().fixed = true;
132 trace!("LABEL: fixed flag set for station '{}'.", label);
133 }
134 if flags & 0x20 != 0 {
135 station.borrow_mut().anonymous = true;
137 trace!("LABEL: anonymous flag set for station '{}'.", label);
138 station.borrow_mut().label = Uuid::new_v4().to_string();
139 trace!(
140 "LABEL: UUID '{}' set for anonymous station.",
141 station.borrow().label,
142 );
143 }
144 if flags & 0x40 != 0 {
145 station.borrow_mut().wall = true;
146 trace!("LABEL: wall flag set for station '{}'.", label);
147 }
148 } else if result == 4 {
149 let (l, r, u, d, flags);
151 unsafe {
152 l = (*pimg).l;
153 r = (*pimg).r;
154 u = (*pimg).u;
155 d = (*pimg).d;
156 flags = (*pimg).flags & 0x7f;
157
158 if flags & 0x20 == 0 {
161 label = CStr::from_ptr((*pimg).label).to_str().unwrap();
162 trace!("XSECT: label set to '{}'.", label);
163 } else {
164 trace!("XSECT: label not from {}.", label);
165 }
166 }
167 trace!("XSECT: l={}, r={}, u={}, d={} for {}.", l, r, u, d, label);
168 data.get_by_label(label)
169 .unwrap_or_else(|| panic!("Could not find station with label {:?}", label))
170 .borrow_mut()
171 .lrud
172 .update(l, r, u, d);
173 } else if result == 5 {
174 trace!("XSECT_END command received. Ignoring.");
176 } else if result == 6 {
177 trace!("ERROR_INFO command received. Ignoring.");
179 } else {
180 panic!("Unknown item type in Survex file");
181 }
182 }
183
184 trace!(
185 "Survex file reading complete. Processed {} stations and {} connections.",
186 data.stations.len(),
187 connections.len()
188 );
189
190 for (p1, p2) in connections.iter() {
194 let from_station_node_index = data
195 .get_by_coords(p1)
196 .unwrap_or_else(|| panic!("Could not find station with coordinates {:?}", p1))
197 .borrow()
198 .index;
199 let to_station_node_index = data
200 .get_by_coords(p2)
201 .unwrap_or_else(|| panic!("Could not find station with coordinates {:?}", p2))
202 .borrow()
203 .index;
204 data.graph.add_edge(
205 from_station_node_index,
206 to_station_node_index,
207 p1.distance(p2),
208 );
209 }
210
211 trace!(
212 "Graph now has {} nodes and {} edges.",
213 data.graph.node_count(),
214 data.graph.edge_count()
215 );
216
217 Ok(data)
218}
219
220#[cfg(test)]
221mod tests {
222 use super::*;
223
224 #[test]
225 fn load_file() {
226 let path = PathBuf::from("tests/data/0733.3d");
227 assert!(load_from_path(path).is_ok());
228 }
229
230 #[test]
231 fn load_invalid_file() {
232 let path = PathBuf::from("tests/data/this-file-does-not-exist.3d");
233 assert!(load_from_path(path).is_err());
234 }
235
236 #[test]
240 fn check_correct_number_nodes_generated() {
241 let path = PathBuf::from("tests/data/0733.3d");
242 let manager = load_from_path(path).unwrap();
243 assert_eq!(manager.stations.len(), 6104);
244
245 let path = PathBuf::from("tests/data/nottsii.3d");
246 let manager = load_from_path(path).unwrap();
247 assert_eq!(manager.stations.len(), 1904);
248 }
249
250 #[test]
251 fn check_correct_number_legs_generated() {
254 let path = PathBuf::from("tests/data/0733.3d");
255 let manager = load_from_path(path).unwrap();
256 assert_eq!(manager.graph.edge_count(), 5929);
257
258 let path = PathBuf::from("tests/data/nottsii.3d");
259 let manager = load_from_path(path).unwrap();
260 assert_eq!(manager.graph.edge_count(), 1782);
261 }
262
263 #[test]
264 fn test_absent_lrud_measurements_are_represented_correctly() {
265 let path = PathBuf::from("tests/data/nottsii.3d");
266 let manager = load_from_path(path).unwrap();
267 let station = manager
268 .get_by_label("nottsii.inlet5.inlet5-resurvey-4.22")
269 .unwrap();
270 let station = station.borrow();
271 assert_eq!(station.lrud.left, None);
272 assert_eq!(station.lrud.right, None);
273 assert_eq!(station.lrud.up, None);
274 assert_eq!(station.lrud.down, Some(9.0));
275 }
276
277 #[test]
278 fn test_lrud_measurements_are_represented_correctly() {
279 let path = PathBuf::from("tests/data/nottsii.3d");
280 let manager = load_from_path(path).unwrap();
281 let station = manager
282 .get_by_label("nottsii.inlet5.inlet5-resurvey-4.26")
283 .unwrap();
284 let station = station.borrow();
285 assert_eq!(station.lrud.left, Some(1.0));
286 assert_eq!(station.lrud.right, Some(0.0));
287 assert_eq!(station.lrud.up, Some(0.3));
288 assert_eq!(station.lrud.down, Some(0.6));
289 }
290
291 #[test]
292 fn test_flags_are_set_correctly() {
293 let path = PathBuf::from("tests/data/nottsii.3d");
294 let manager = load_from_path(path).unwrap();
295 let station = manager.get_by_label("nottsii.entrance").unwrap();
296 let station = station.borrow();
297 assert_eq!(station.surface, false);
298 assert_eq!(station.underground, false);
299 assert_eq!(station.entrance, true);
300 assert_eq!(station.exported, true);
301 assert_eq!(station.fixed, true);
302 assert_eq!(station.anonymous, false);
303 assert_eq!(station.wall, false);
304
305 let station = manager
306 .get_by_label("nottsii.inlet5.inlet5-resurvey-2.3.17")
307 .unwrap();
308 let station = station.borrow();
309 assert_eq!(station.surface, false);
310 assert_eq!(station.underground, true);
311 assert_eq!(station.entrance, false);
312 assert_eq!(station.exported, true);
313 assert_eq!(station.fixed, false);
314 assert_eq!(station.anonymous, false);
315 assert_eq!(station.wall, false);
316
317 let station = manager
318 .get_by_label("nottsii.mainstreamway.mainstreamway3.27")
319 .unwrap();
320 let station = station.borrow();
321 assert_eq!(station.surface, false);
322 assert_eq!(station.underground, true);
323 assert_eq!(station.entrance, false);
324 assert_eq!(station.exported, false);
325 assert_eq!(station.fixed, false);
326 assert_eq!(station.anonymous, false);
327 assert_eq!(station.wall, false);
328
329 let station = manager
330 .get_by_label("nottsii.countlazloall.thecupcake.009")
331 .unwrap();
332 let station = station.borrow();
333 assert_eq!(station.surface, true);
334 assert_eq!(station.underground, false);
335 assert_eq!(station.entrance, false);
336 assert_eq!(station.exported, false);
337 assert_eq!(station.fixed, false);
338 assert_eq!(station.anonymous, false);
339 assert_eq!(station.wall, false);
340 }
341}