Skip to main content

routee_compass_core/model/map/
geometry_model.rs

1use std::sync::Arc;
2
3use super::map_error::MapError;
4use crate::{
5    model::network::{EdgeId, EdgeListId, Graph},
6    util::{fs::read_utils, geo::geo_io_utils},
7};
8use geo::LineString;
9use kdam::{Bar, BarExt};
10
11/// model for link geometries by edge id. can be constructed either
12/// from edge geometry dataset ([`GeometryModel::new_from_edges`]) or
13/// from the vertices ([`GeometryModel::new_from_vertices`]) by simply
14/// drawing lines between coordinates.
15pub struct GeometryModel(Vec<LineString<f32>>);
16
17impl GeometryModel {
18    /// with no provided geometries, create minimal LineStrings from pairs of vertex Points
19    pub fn new_from_vertices(
20        graph: Arc<Graph>,
21        edge_list_id: EdgeListId,
22    ) -> Result<GeometryModel, MapError> {
23        let edges = create_linestrings_from_vertices(graph, edge_list_id)?;
24        Ok(GeometryModel(edges))
25    }
26
27    /// use a user-provided enumerated textfile input to load LineString geometries
28    pub fn new_from_edges(
29        geometry_input_file: &String,
30        edge_list_id: EdgeListId,
31        graph: Arc<Graph>,
32    ) -> Result<GeometryModel, MapError> {
33        let edge_list = graph.get_edge_list(&edge_list_id).map_err(|e| {
34            MapError::BuildError(format!(
35                "while creating GeometryModel for input file {geometry_input_file}, {e}"
36            ))
37        })?;
38
39        let edge_list_len = edge_list.len();
40        let linestrings = read_linestrings(geometry_input_file, edge_list_len)?;
41
42        if linestrings.len() != edge_list_len {
43            Err(MapError::BuildError(format!("edge list {edge_list_id} geometry file {geometry_input_file} should have {edge_list_len} rows, found {}", linestrings.len())))
44        } else {
45            Ok(GeometryModel(linestrings))
46        }
47    }
48
49    /// iterate through the geometries of this model
50    pub fn geometries<'a>(&'a self) -> Box<dyn Iterator<Item = &'a LineString<f32>> + 'a> {
51        Box::new(self.0.iter())
52    }
53
54    /// get a single geometry by it's EdgeId
55    pub fn get<'a>(&'a self, edge_id: &EdgeId) -> Option<&'a LineString<f32>> {
56        self.0.get(edge_id.0)
57    }
58}
59
60fn read_linestrings(
61    geometry_input_file: &String,
62    n_edges: usize,
63) -> Result<Vec<geo::LineString<f32>>, MapError> {
64    let geoms = read_utils::read_raw_file(
65        geometry_input_file,
66        geo_io_utils::parse_wkt_linestring,
67        Some(Bar::builder().desc("link geometries").total(n_edges)),
68        None,
69    )
70    .map_err(|e: std::io::Error| {
71        MapError::BuildError(format!("error loading {geometry_input_file}: {e}"))
72    })?
73    .to_vec();
74    eprintln!();
75    Ok(geoms)
76}
77
78fn create_linestrings_from_vertices(
79    graph: Arc<Graph>,
80    edge_list_id: EdgeListId,
81) -> Result<Vec<LineString<f32>>, MapError> {
82    let edge_list = graph.get_edge_list(&edge_list_id).map_err(|e| {
83        MapError::BuildError(format!("while creating GeometryModel from vertices, {e}"))
84    })?;
85
86    let n_edges = edge_list.len();
87    let mut pb = kdam::Bar::builder()
88        .total(n_edges)
89        .animation("fillup")
90        .desc("edge LineString geometry file")
91        .build()
92        .map_err(MapError::InternalError)?;
93
94    let edges = edge_list
95        .edges()
96        .map(|e| {
97            let src_v = graph.get_vertex(&e.src_vertex_id).map_err(|_| {
98                MapError::InternalError(format!(
99                    "edge {} src vertex {} missing",
100                    e.edge_id, e.src_vertex_id
101                ))
102            })?;
103            let dst_v = graph.get_vertex(&e.dst_vertex_id).map_err(|_| {
104                MapError::InternalError(format!(
105                    "edge {} dst vertex {} missing",
106                    e.edge_id, e.dst_vertex_id
107                ))
108            })?;
109
110            let linestring = geo::line_string![src_v.coordinate.0, dst_v.coordinate.0,];
111            let _ = pb.update(1);
112            Ok(linestring)
113        })
114        .collect::<Result<Vec<_>, MapError>>()?;
115
116    eprintln!();
117    Ok(edges)
118}
119
120// TODO:
121//   - the API for OutputPlugin now expects a SearchInstance which is non-trivial to instantiate.
122//   the logic for adding geometries should be refactored into a separate function and this test
123//   should be moved to the file where that function exists.
124//   - the loading of geometries is now handled by the MapModel. testing geometry retrieval and
125//   linestring reconstruction should be moved to the map_model.rs file.
126
127#[cfg(test)]
128mod tests {
129
130    use crate::util::{fs::read_utils::read_raw_file, geo::geo_io_utils::parse_wkt_linestring};
131
132    use std::path::PathBuf;
133
134    fn mock_geometry_file() -> PathBuf {
135        PathBuf::from(env!("CARGO_MANIFEST_DIR"))
136            .join("src")
137            .join("model")
138            .join("map")
139            .join("test")
140            .join("geometries.txt")
141    }
142
143    // fn mock_graph() -> Graph {
144    //     let edges = Box::new([
145    //         Edge::new(0, 0, 2, 2.8284271247),
146    //         Edge::new(1, 3, 5, 2.8284271247),
147    //         Edge::new(2, 6, 8, 2.8284271247),
148    //     ]);
149    //     let vertices = Box::new([
150    //         Vertex::new(0, 0.0, 0.0),
151    //         Vertex::new(1, 1.0, 1.0),
152    //         Vertex::new(2, 2.0, 2.0),
153    //         Vertex::new(3, 3.0, 3.0),
154    //         Vertex::new(4, 4.0, 4.0),
155    //         Vertex::new(5, 5.0, 5.0),
156    //         Vertex::new(6, 6.0, 6.0),
157    //         Vertex::new(7, 7.0, 7.0),
158    //         Vertex::new(8, 8.0, 8.0),
159    //     ]);
160    //     let adj = Box::new([
161    //         CompactOrderedHashMap::new(vec![(EdgeId(0), VertexId(2))]),
162    //         CompactOrderedHashMap::empty(),
163    //         CompactOrderedHashMap::empty(),
164    //         CompactOrderedHashMap::new(vec![(EdgeId(1), VertexId(5))]),
165    //         CompactOrderedHashMap::empty(),
166    //         CompactOrderedHashMap::empty(),
167    //         CompactOrderedHashMap::new(vec![(EdgeId(2), VertexId(8))]),
168    //         CompactOrderedHashMap::empty(),
169    //         CompactOrderedHashMap::empty(),
170    //     ]);
171    //     let rev = Box::new([
172    //         CompactOrderedHashMap::empty(),
173    //         CompactOrderedHashMap::empty(),
174    //         CompactOrderedHashMap::new(vec![(EdgeId(0), VertexId(0))]),
175    //         CompactOrderedHashMap::empty(),
176    //         CompactOrderedHashMap::empty(),
177    //         CompactOrderedHashMap::new(vec![(EdgeId(1), VertexId(3))]),
178    //         CompactOrderedHashMap::empty(),
179    //         CompactOrderedHashMap::empty(),
180    //         CompactOrderedHashMap::new(vec![(EdgeId(2), VertexId(6))]),
181    //     ]);
182    //     Graph {
183    //         adj,
184    //         rev,
185    //         edges,
186    //         vertices,
187    //     }
188    // }
189
190    #[test]
191    fn test_geometry_deserialization() {
192        let result = read_raw_file(mock_geometry_file(), parse_wkt_linestring, None, None).unwrap();
193        assert_eq!(result.len(), 3);
194    }
195
196    // #[ignore = "no ideal candidate module for this unit test. TraversalOutputFormat concatenates linestrings but is too high-level for this test"]
197    // fn test_add_geometry() {
198    // let geoms_filepath = mock_geometry_file();
199    // let geoms_file_string = geoms_filepath.to_str().unwrap().to_string();
200    // let graph = Arc::new(mock_graph());
201    // let geometry_model =
202    //     GeometryModel::new_from_edges(&geoms_file_string, graph.clone()).unwrap();
203
204    // OLD TEST STUB:
205    // let expected_geometry = String::from("LINESTRING(0 0,1 1,2 2,3 3,4 4,5 5,6 6,7 7,8 8)");
206    // let mut output_result = serde_json::json!({});
207    // let route = vec![
208    //     EdgeTraversal {
209    //         edge_id: EdgeId(0),
210    //         access_cost: Cost::from(0.0),
211    //         traversal_cost: Cost::from(0.0),
212    //         result_state: vec![StateVar(0.0)],
213    //     },
214    //     EdgeTraversal {
215    //         edge_id: EdgeId(1),
216    //         access_cost: Cost::from(0.0),
217    //         traversal_cost: Cost::from(0.0),
218    //         result_state: vec![StateVar(0.0)],
219    //     },
220    //     EdgeTraversal {
221    //         edge_id: EdgeId(2),
222    //         access_cost: Cost::from(0.0),
223    //         traversal_cost: Cost::from(0.0),
224    //         result_state: vec![StateVar(0.0)],
225    //     },
226    // ];
227    // let search_result = SearchAppResult {
228    //     route,
229    //     tree: HashMap::new(),
230    //     search_executed_time: Local::now().to_rfc3339(),
231    //     algorithm_runtime: Duration::ZERO,
232    //     route_runtime: Duration::ZERO,
233    //     search_app_runtime: Duration::ZERO,
234    //     iterations: 0,
235    // };
236    // let filename = mock_geometry_file();
237    // let _route_geometry = true;
238    // let _tree_geometry = false;
239    // let geom_plugin =
240    //     TraversalPlugin::from_file(&filename, Some(TraversalOutputFormat::Wkt), None).unwrap();
241
242    // geom_plugin
243    //     .process(&mut output_result, &Ok(search_result))
244    //     .unwrap();
245    // let geometry_wkt = output_result.get_route_geometry_wkt().unwrap();
246    // assert_eq!(geometry_wkt, expected_geometry);
247    // }
248}