routee_compass/plugin/output/default/traversal/traversal_output_format.rs
1use std::sync::Arc;
2
3use super::traversal_ops as ops;
4use crate::plugin::output::OutputPluginError;
5use geo::{CoordFloat, Geometry, TryConvert};
6use routee_compass_core::{
7 algorithm::search::{EdgeTraversal, SearchTree},
8 model::{map::MapModel, state::StateModel},
9};
10use serde::{Deserialize, Serialize};
11use wkb::writer::WriteOptions;
12use wkt::ToWkt;
13
14#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
15#[serde(rename_all = "snake_case")]
16pub enum TraversalOutputFormat {
17 // concatenates all LINESTRINGS and returns the geometry as a WKT
18 Wkt,
19 // concatenates all LINESTRINGS and returns the geometry as a WKB
20 Wkb,
21 // returns the properties of each link traversal as a JSON array of objects
22 Json,
23 // returns the geometries and properties as GeoJSON
24 GeoJson,
25 EdgeId,
26}
27
28impl TraversalOutputFormat {
29 /// generates output for a route based on the configured TraversalOutputFormat
30 pub fn generate_route_output(
31 &self,
32 route: &Vec<EdgeTraversal>,
33 map_model: Arc<MapModel>,
34 state_model: Arc<StateModel>,
35 ) -> Result<serde_json::Value, OutputPluginError> {
36 match self {
37 TraversalOutputFormat::Wkt => {
38 let route_geometry = ops::create_route_linestring(route, map_model.clone())?;
39 let route_wkt = route_geometry.wkt_string();
40 Ok(serde_json::Value::String(route_wkt))
41 }
42 TraversalOutputFormat::Wkb => {
43 let linestring = ops::create_route_linestring(route, map_model.clone())?;
44 let geometry = geo::Geometry::LineString(linestring);
45 let wkb_str = geometry_to_wkb_string(&geometry)?;
46 Ok(serde_json::Value::String(wkb_str))
47 }
48 TraversalOutputFormat::Json => {
49 let result = serde_json::to_value(route)?;
50 Ok(result)
51 }
52 TraversalOutputFormat::GeoJson => {
53 let result = ops::create_route_geojson(route, map_model, state_model)?;
54 Ok(result)
55 }
56 TraversalOutputFormat::EdgeId => {
57 let route_ids = route.iter().map(|e| e.edge_id).collect::<Vec<_>>();
58 let json = serde_json::json![route_ids];
59 Ok(json)
60 }
61 }
62 }
63
64 /// generates output for a tree based on the configured TraversalOutputFormat
65 pub fn generate_tree_output(
66 &self,
67 tree: &SearchTree,
68 map_model: Arc<MapModel>,
69 state_model: Arc<StateModel>,
70 ) -> Result<serde_json::Value, OutputPluginError> {
71 match self {
72 TraversalOutputFormat::Wkt => {
73 let route_geometry = ops::create_tree_multilinestring(tree, map_model)?;
74 let route_wkt = route_geometry.wkt_string();
75 Ok(serde_json::Value::String(route_wkt))
76 }
77 TraversalOutputFormat::Wkb => {
78 let route_geometry = ops::create_tree_multilinestring(tree, map_model)?;
79 let geometry = geo::Geometry::MultiLineString(route_geometry);
80 let wkb_str = geometry_to_wkb_string(&geometry)?;
81 Ok(serde_json::Value::String(wkb_str))
82 }
83 TraversalOutputFormat::Json => {
84 let result = serde_json::to_value(tree.values().collect::<Vec<_>>())?;
85 Ok(result)
86 }
87 TraversalOutputFormat::GeoJson => {
88 let result = ops::create_tree_geojson(tree, map_model, state_model)?;
89 Ok(result)
90 }
91 TraversalOutputFormat::EdgeId => {
92 let tree_ids = tree
93 .values()
94 .filter_map(|b| b.incoming_edge().map(|e| (e.edge_list_id, e.edge_id)))
95 .collect::<Vec<_>>();
96 let json = serde_json::json![tree_ids];
97 Ok(json)
98 }
99 }
100 }
101}
102
103fn geometry_to_wkb_string<T: CoordFloat + Into<f64>>(
104 geometry: &Geometry<T>,
105) -> Result<String, OutputPluginError> {
106 let mut out_bytes = vec![];
107 let geom: Geometry<f64> = geometry.try_convert().map_err(|e| {
108 OutputPluginError::OutputPluginFailed(format!("unable to convert geometry to f64: {e}"))
109 })?;
110 let write_options = WriteOptions {
111 endianness: wkb::Endianness::BigEndian,
112 };
113 wkb::writer::write_geometry(&mut out_bytes, &geom, &write_options).map_err(|e| {
114 OutputPluginError::OutputPluginFailed(format!("failed to write geometry as WKB: {e}"))
115 })?;
116 let out_string = String::from_utf8(out_bytes).map_err(|e| {
117 OutputPluginError::OutputPluginFailed(format!("failed to read WKB as utf8: {e}"))
118 })?;
119 Ok(out_string)
120}
121
122// #[cfg(test)]
123// mod test {
124
125// use crate::app::search::SearchAppResult;
126// use chrono::Local;
127// use geo::{coord, LineString};
128// use routee_compass_core::{
129// algorithm::search::EdgeTraversal,
130// model::{network::EdgeId, state::StateVariable, unit::Cost},
131// };
132// use std::time::Duration;
133
134// // #[ignore = "needs mocked graph for map model integration in test"]
135// // fn test_e2e() {
136// // let route = vec![
137// // EdgeTraversal {
138// // edge_id: EdgeId(0),
139// // access_cost: Cost::from(0.0),
140// // traversal_cost: Cost::from(10.0),
141// // result_state: vec![StateVariable(10.0)],
142// // },
143// // EdgeTraversal {
144// // edge_id: EdgeId(1),
145// // access_cost: Cost::from(5.0),
146// // traversal_cost: Cost::from(9.0),
147// // result_state: vec![StateVariable(24.0)],
148// // },
149// // EdgeTraversal {
150// // edge_id: EdgeId(2),
151// // access_cost: Cost::from(0.0),
152// // traversal_cost: Cost::from(11.0),
153// // result_state: vec![StateVariable(35.0)],
154// // },
155// // ];
156// // let _ = SearchAppResult {
157// // routes: vec![route],
158// // trees: vec![],
159// // search_executed_time: Local::now().to_rfc3339(),
160// // search_runtime: Duration::ZERO,
161// // iterations: 0,
162// // };
163
164// // let geoms = vec![
165// // LineString(vec![
166// // coord! { x: 1.0, y: 0.0 },
167// // coord! { x: 1.0, y: 0.0 },
168// // coord! { x: 1.0, y: 1.0 },
169// // ]),
170// // LineString(vec![coord! { x: 2.0, y: 2.0 }, coord! { x: 2.0, y: 3.0 }]),
171// // LineString(vec![coord! { x: 3.0, y: 3.0 }, coord! { x: 3.0, y: 4.0 }]),
172// // ]
173// // .into_boxed_slice();
174
175// // // let map_model = MapModel::new(graph, config)
176
177// // // println!(
178// // // "{:?}",
179// // // TraversalOutputFormat::Wkt
180// // // .generate_route_output(&result.routes[0], &geoms)
181// // // .map(|r| serde_json::to_string_pretty(&r))
182// // // );
183// // // println!(
184// // // "{:?}",
185// // // TraversalOutputFormat::Json
186// // // .generate_route_output(&result.routes[0], &geoms)
187// // // .map(|r| serde_json::to_string_pretty(&r))
188// // // );
189// // // println!(
190// // // "{:?}",
191// // // TraversalOutputFormat::GeoJson
192// // // .generate_route_output(&result.routes[0], &geoms)
193// // // .map(|r| serde_json::to_string_pretty(&r))
194// // // );
195// // // println!(
196// // // "{:?}",
197// // // TraversalOutputFormat::EdgeId
198// // // .generate_route_output(&result.routes[0], &geoms)
199// // // .map(|r| serde_json::to_string_pretty(&r))
200// // // );
201// // }
202// }