maliput_query/
maliput_query.rs

1// BSD 3-Clause License
2//
3// Copyright (c) 2025, Woven by Toyota.
4// All rights reserved.
5//
6// Redistribution and use in source and binary forms, with or without
7// modification, are permitted provided that the following conditions are met:
8//
9// * Redistributions of source code must retain the above copyright notice, this
10//   list of conditions and the following disclaimer.
11//
12// * Redistributions in binary form must reproduce the above copyright notice,
13//   this list of conditions and the following disclaimer in the documentation
14//   and/or other materials provided with the distribution.
15//
16// * Neither the name of the copyright holder nor the names of its
17//   contributors may be used to endorse or promote products derived from
18//   this software without specific prior written permission.
19//
20// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
31//! A command line tool to query a road network built from an OpenDRIVE file.
32//!
33//! e.g: cargo run --bin maliput_query -- maliput/data/xodr/ArcLane.xodr
34
35use clap::Parser;
36
37use maliput::api::{LanePosition, RoadNetwork};
38use std::collections::HashMap;
39
40// Define the MaliputQuery enum to represent different types of queries that can be made to the road network.
41enum MaliputQuery {
42    PrintAllLanes,
43    GetNumberOfLanes,
44    GetTotalLengthOfTheRoadGeometry,
45    GetLaneLength(String),
46    GetLaneBounds(String, f64),                // lane_id, s
47    GetSegmentBounds(String, f64),             // lane_id, s
48    ToRoadPosition(f64, f64, f64),             // x, y, z
49    ToLanePosition(String, f64, f64, f64),     // lane_id, x, y, z
50    ToSegmentPosition(String, f64, f64, f64),  // lane_id, x, y, z
51    ToInertialPosition(String, f64, f64, f64), // lane_id, s, r, h
52    GetOrientaiton(String, f64, f64, f64),     // lane_id, s, r, h
53    Invalid,
54}
55
56impl From<Vec<&str>> for MaliputQuery {
57    fn from(args: Vec<&str>) -> Self {
58        match args.as_slice() {
59            ["PrintAllLanes"] => MaliputQuery::PrintAllLanes,
60            ["GetNumberOfLanes"] => MaliputQuery::GetNumberOfLanes,
61            ["GetTotalLengthOfTheRoadGeometry"] => MaliputQuery::GetTotalLengthOfTheRoadGeometry,
62            ["GetLaneLength", lane_id] => MaliputQuery::GetLaneLength(lane_id.to_string()),
63            ["GetLaneBounds", lane_id, s] => MaliputQuery::GetLaneBounds(lane_id.to_string(), s.parse().unwrap()),
64            ["GetSegmentBounds", lane_id, s] => MaliputQuery::GetSegmentBounds(lane_id.to_string(), s.parse().unwrap()),
65            ["ToRoadPosition", x, y, z] => {
66                MaliputQuery::ToRoadPosition(x.parse().unwrap(), y.parse().unwrap(), z.parse().unwrap())
67            }
68            ["ToLanePosition", lane_id, x, y, z] => MaliputQuery::ToLanePosition(
69                lane_id.to_string(),
70                x.parse().unwrap(),
71                y.parse().unwrap(),
72                z.parse().unwrap(),
73            ),
74            ["ToSegmentPosition", lane_id, x, y, z] => MaliputQuery::ToSegmentPosition(
75                lane_id.to_string(),
76                x.parse().unwrap(),
77                y.parse().unwrap(),
78                z.parse().unwrap(),
79            ),
80            ["ToInertialPosition", lane_id, s, r, h] => MaliputQuery::ToInertialPosition(
81                lane_id.to_string(),
82                s.parse().unwrap(),
83                r.parse().unwrap(),
84                h.parse().unwrap(),
85            ),
86            ["GetOrientation", lane_id, s, r, h] => MaliputQuery::GetOrientaiton(
87                lane_id.to_string(),
88                s.parse().unwrap(),
89                r.parse().unwrap(),
90                h.parse().unwrap(),
91            ),
92            _ => MaliputQuery::Invalid,
93        }
94    }
95}
96
97impl MaliputQuery {
98    fn print_available_queries() {
99        println!("-> Available commands:");
100        println!("\t1. PrintAllLanes");
101        println!("\t2. GetNumberOfLanes");
102        println!("\t3. GetTotalLengthOfTheRoadGeometry");
103        println!("\t4. GetLaneLength <lane_id>");
104        println!("\t5. GetLaneBounds <lane_id> <s>");
105        println!("\t6. GetSegmentBounds <lane_id> <s>");
106        println!("\t7. ToRoadPosition <x> <y> <z>");
107        println!("\t8. ToLanePosition <lane_id> <x> <y> <z>");
108        println!("\t9. ToSegmentPosition <lane_id> <x> <y> <z>");
109        println!("\t10 ToInertialPosition <lane_id> <s> <r> <h>");
110        println!("\t11. GetOrientation <lane_id> <s> <r> <h>");
111    }
112}
113
114struct RoadNetworkQuery<'a> {
115    rn: &'a RoadNetwork,
116}
117
118impl<'a> RoadNetworkQuery<'a> {
119    fn new(rn: &'a RoadNetwork) -> Self {
120        RoadNetworkQuery { rn }
121    }
122
123    fn execute_query(&self, query: MaliputQuery) {
124        let rg = self.rn.road_geometry();
125        let start_time = std::time::Instant::now();
126        match query {
127            MaliputQuery::PrintAllLanes => {
128                // Sort it by lane length in descending order and then print them
129                let mut lanes = rg.get_lanes();
130                lanes.sort_by(|a, b| b.length().partial_cmp(&a.length()).unwrap());
131                print_elapsed_time(start_time);
132                println!("-> All lanes in the road geometry: {} lanes", lanes.len());
133                for lane in lanes {
134                    println!(
135                        "\t* Lane ID: {}\t Length: {} meters \t InertiaPos at (s=0,r=0,h=0): {}",
136                        lane.id(),
137                        lane.length(),
138                        lane.to_inertial_position(&LanePosition::new(0., 0., 0.))
139                    );
140                }
141            }
142            MaliputQuery::GetNumberOfLanes => {
143                let len = rg.get_lanes().len();
144                print_elapsed_time(start_time);
145                println!("-> Number of lanes: {}", len);
146            }
147            MaliputQuery::GetTotalLengthOfTheRoadGeometry => {
148                let lanes_num = rg.get_lanes().len();
149                let total_length = rg.get_lanes().iter().map(|lane| lane.length()).sum::<f64>();
150                print_elapsed_time(start_time);
151                println!("-> Total length of the road geometry: {} meters along {} lanes. The average lane length is {} meters.",
152                total_length,
153                lanes_num,
154                total_length / lanes_num as f64);
155            }
156            MaliputQuery::GetLaneLength(lane_id) => {
157                if let Some(lane) = rg.get_lane(&lane_id) {
158                    let lane_length = lane.length();
159                    print_elapsed_time(start_time);
160                    println!("-> Length of lane {}: {} meters", lane_id, lane_length);
161                } else {
162                    println!("-> Lane with ID {} not found.", lane_id);
163                }
164            }
165            MaliputQuery::GetLaneBounds(lane_id, s) => {
166                if let Some(lane) = rg.get_lane(&lane_id) {
167                    let lane_bounds = lane.lane_bounds(s);
168                    print_elapsed_time(start_time);
169                    println!(
170                        "-> Bounds of lane {} at s={}: max: {}, min: {}",
171                        lane_id,
172                        s,
173                        lane_bounds.max(),
174                        lane_bounds.min()
175                    );
176                } else {
177                    println!("-> Lane with ID {} not found.", lane_id);
178                }
179            }
180            MaliputQuery::GetSegmentBounds(lane_id, s) => {
181                if let Some(lane) = rg.get_lane(&lane_id) {
182                    let segment_bounds = lane.segment_bounds(s);
183                    print_elapsed_time(start_time);
184                    println!(
185                        "-> Segment bounds of lane {} at s={}: max: {}, min: {}",
186                        lane_id,
187                        s,
188                        segment_bounds.max(),
189                        segment_bounds.min()
190                    );
191                } else {
192                    println!("-> Lane with ID {} not found.", lane_id);
193                }
194            }
195            MaliputQuery::ToRoadPosition(x, y, z) => {
196                let road_position_result = rg.to_road_position(&maliput::api::InertialPosition::new(x, y, z));
197                print_elapsed_time(start_time);
198                println!("-> Road Position Result:");
199                println!("\t* Road Position:");
200                println!("\t\t* lane_id: {}", road_position_result.road_position.lane().id());
201                println!("\t\t* lane_position: {}", road_position_result.road_position.pos());
202                println!("\t* Nearest Position:");
203                println!("\t\t* inertial_position: {}", road_position_result.nearest_position);
204                println!("\t* Distance:");
205                println!("\t\t* distance: {}", road_position_result.distance);
206            }
207            MaliputQuery::ToLanePosition(lane_id, x, y, z) => {
208                if let Some(lane) = rg.get_lane(&lane_id) {
209                    let lane_position_result = lane.to_lane_position(&maliput::api::InertialPosition::new(x, y, z));
210                    print_elapsed_time(start_time);
211                    println!("-> Lane Position Result for lane {}:", lane_id);
212                    println!("\t* Lane Position:");
213                    println!("\t\t* lane_position: {}", lane_position_result.lane_position);
214                    println!("\t* Nearest Position:");
215                    println!("\t\t* inertial_position: {}", lane_position_result.nearest_position);
216                    println!("\t* Distance:");
217                    println!("\t\t* distance: {}", lane_position_result.distance);
218                } else {
219                    println!("Lane with ID {} not found.", lane_id);
220                }
221            }
222            MaliputQuery::ToSegmentPosition(lane_id, x, y, z) => {
223                if let Some(lane) = rg.get_lane(&lane_id) {
224                    let lane_position_result = lane.to_segment_position(&maliput::api::InertialPosition::new(x, y, z));
225                    print_elapsed_time(start_time);
226                    println!("-> Segment Position Result for lane {}:", lane_id);
227                    println!("\t* Lane Position:");
228                    println!("\t\t* lane_position: {}", lane_position_result.lane_position);
229                    println!("\t* Nearest Position:");
230                    println!("\t\t* inertial_position: {}", lane_position_result.nearest_position);
231                    println!("\t* Distance:");
232                    println!("\t\t* distance: {}", lane_position_result.distance);
233                } else {
234                    println!("Lane with ID {} not found.", lane_id);
235                }
236            }
237            MaliputQuery::ToInertialPosition(lane_id, s, r, h) => {
238                if let Some(lane) = rg.get_lane(&lane_id) {
239                    let inertial_position = lane.to_inertial_position(&maliput::api::LanePosition::new(s, r, h));
240                    print_elapsed_time(start_time);
241                    println!("-> Inertial Position Result for lane {}:", lane_id);
242                    println!("\t* inertial_position: {}", inertial_position);
243                } else {
244                    println!("-> Lane with ID {} not found.", lane_id);
245                }
246            }
247            MaliputQuery::GetOrientaiton(lane_id, s, r, h) => {
248                if let Some(lane) = rg.get_lane(&lane_id) {
249                    let orientation = lane.get_orientation(&maliput::api::LanePosition::new(s, r, h));
250                    print_elapsed_time(start_time);
251                    println!("-> Orientation Result for lane {}:", lane_id);
252                    println!("\t* orientation:");
253                    println!(
254                        "\t\t* roll: {}, pitch: {}, yaw: {}",
255                        orientation.roll(),
256                        orientation.pitch(),
257                        orientation.yaw()
258                    );
259                } else {
260                    println!("-> Lane with ID {} not found.", lane_id);
261                }
262            }
263            MaliputQuery::Invalid => {
264                println!("-> Invalid query command. Please try again.");
265                println!();
266                MaliputQuery::print_available_queries();
267            }
268        }
269        /// Print the elapsed time for the query execution.
270        fn print_elapsed_time(start_time: std::time::Instant) {
271            let elapsed = start_time.elapsed();
272            println!("-> Query executed in {:.2?}.", elapsed);
273        }
274    }
275}
276
277#[derive(Parser, Debug)]
278#[command(author, version, about)]
279struct Args {
280    road_network_xodr_file_path: String,
281
282    /// Tolerance indicates the precision of the geometry being built in order
283    /// to make G1 continuity guarantees. A value of 0.5 is high. Ideally,
284    /// tolerances of 0.05 or 0.005 is ideal.
285    #[arg(short, long, default_value_t = 0.05)]
286    linear_tolerance: f64,
287
288    #[arg(short, long)]
289    max_linear_tolerance: Option<f64>,
290
291    #[arg(short, long, default_value_t = 0.01)]
292    angular_tolerance: f64,
293
294    #[arg(long, default_value_t = false)]
295    allow_non_drivable_lanes: bool,
296
297    #[arg(long, default_value_t = false)]
298    disable_parallel_builder_policy: bool,
299
300    #[arg(long, default_value_t = maliput::common::LogLevel::Error)]
301    set_log_level: maliput::common::LogLevel,
302}
303
304// Parses the OpenDRIVE file path from the command line arguments.
305//  - if this is a relative path, it will be resolved against the current working directory.
306//  - if this is an absolute path, it will be used as is.
307use std::path::PathBuf;
308fn parse_xodr_file_path(path: &str) -> PathBuf {
309    let mut path_buf = PathBuf::from(path);
310    if !path_buf.is_absolute() {
311        // If the path is relative, resolve it against the current working directory.
312        if let Ok(current_dir) = std::env::current_dir() {
313            path_buf = current_dir.join(path_buf);
314        }
315    }
316    path_buf
317}
318
319fn main() -> Result<(), Box<dyn std::error::Error>> {
320    println!("Welcome to maliput_query app:");
321    println!("\t* This application allows you to query a road network built from an OpenDRIVE file.");
322    println!("\t* Usage: maliput_query <XODR_FILE_PATH> [OPTIONS]");
323    println!();
324    let args = Args::parse();
325
326    // Configuration for the road network.
327    let xodr_path = parse_xodr_file_path(args.road_network_xodr_file_path.as_str());
328    let linear_tolerance: String = args.linear_tolerance.to_string();
329    let max_linear_tolerance = args.max_linear_tolerance;
330    let angular_tolerance = args.angular_tolerance.to_string();
331    let omit_non_drivable_lanes = if args.allow_non_drivable_lanes { "false" } else { "true" };
332    let parallel_builder_policy = !args.disable_parallel_builder_policy;
333
334    let mut road_network_properties = HashMap::from([
335        ("road_geometry_id", "maliput_query_rg"),
336        ("opendrive_file", xodr_path.to_str().unwrap()),
337        ("linear_tolerance", linear_tolerance.as_str()),
338        ("omit_nondrivable_lanes", omit_non_drivable_lanes),
339        ("angular_tolerance", angular_tolerance.as_str()),
340        (
341            "build_policy",
342            if parallel_builder_policy {
343                "parallel"
344            } else {
345                "sequential"
346            },
347        ),
348    ]);
349    let max_linear_tolerance_str;
350    if let Some(max_tolerance) = max_linear_tolerance {
351        max_linear_tolerance_str = max_tolerance.to_string();
352        road_network_properties.insert("max_linear_tolerance", max_linear_tolerance_str.as_str());
353    }
354
355    println!();
356    println!("-> Setting maliput log level to: {}", args.set_log_level);
357    maliput::common::set_log_level(args.set_log_level);
358    println!("-> Road network builder options: {:?}", road_network_properties);
359    println!("->");
360    println!(
361        "-> Building Maliput Road Network from OpenDRIVE file: {}",
362        xodr_path.display()
363    );
364    println!("-> This may take a while, depending on the size of the OpenDRIVE file and the complexity of the road network...");
365    println!("-> ...");
366    let now = std::time::Instant::now();
367    let road_network = RoadNetwork::new("maliput_malidrive", &road_network_properties);
368    let elapsed = now.elapsed();
369    println!("-> Road network loaded in {:.2?}.", elapsed);
370
371    // Print available commands
372    println!();
373    MaliputQuery::print_available_queries();
374    println!("To exit the application, type 'exit'.");
375
376    loop {
377        println!();
378        let mut input = String::new();
379        println!("-> Enter a query command (or 'exit' to quit):");
380        std::io::stdin().read_line(&mut input)?;
381        let input = input.trim();
382
383        if input == "exit" {
384            break;
385        }
386
387        let query: MaliputQuery = input.split_whitespace().collect::<Vec<&str>>().into();
388        let query_handler = RoadNetworkQuery::new(&road_network);
389        query_handler.execute_query(query);
390    }
391
392    Ok(())
393}