1use crate::io::{EdgeSE2, EdgeSE3, Graph, GraphLoader, IoError, VertexSE2, VertexSE3};
2use memmap2;
3use rayon::prelude::*;
4use std::collections::HashMap;
5use std::{fs::File, io::Write, path::Path};
6
7pub struct G2oLoader;
9
10impl GraphLoader for G2oLoader {
11 fn load<P: AsRef<Path>>(path: P) -> Result<Graph, IoError> {
12 let path_ref = path.as_ref();
13 let file = File::open(path_ref).map_err(|e| {
14 IoError::Io(e).log_with_source(format!("Failed to open G2O file: {:?}", path_ref))
15 })?;
16 let mmap = unsafe {
17 memmap2::Mmap::map(&file).map_err(|e| {
18 IoError::Io(e)
19 .log_with_source(format!("Failed to memory-map G2O file: {:?}", path_ref))
20 })?
21 };
22 let content = std::str::from_utf8(&mmap).map_err(|e| {
23 IoError::Parse {
24 line: 0,
25 message: format!("Invalid UTF-8: {e}"),
26 }
27 .log()
28 })?;
29
30 Self::parse_content(content)
31 }
32
33 fn write<P: AsRef<Path>>(graph: &Graph, path: P) -> Result<(), IoError> {
34 let path_ref = path.as_ref();
35 let mut file = File::create(path_ref).map_err(|e| {
36 IoError::Io(e).log_with_source(format!("Failed to create G2O file: {:?}", path_ref))
37 })?;
38
39 writeln!(file, "# G2O file written by Apex Solver")
41 .map_err(|e| IoError::Io(e).log_with_source("Failed to write G2O header"))?;
42 writeln!(
43 file,
44 "# Timestamp: {}",
45 chrono::Local::now().format("%Y-%m-%d %H:%M:%S")
46 )
47 .map_err(|e| IoError::Io(e).log_with_source("Failed to write G2O timestamp"))?;
48 writeln!(
49 file,
50 "# SE2 vertices: {}, SE3 vertices: {}, SE2 edges: {}, SE3 edges: {}",
51 graph.vertices_se2.len(),
52 graph.vertices_se3.len(),
53 graph.edges_se2.len(),
54 graph.edges_se3.len()
55 )
56 .map_err(|e| IoError::Io(e).log_with_source("Failed to write G2O statistics"))?;
57 writeln!(file)
58 .map_err(|e| IoError::Io(e).log_with_source("Failed to write G2O header newline"))?;
59
60 let mut se2_ids: Vec<_> = graph.vertices_se2.keys().collect();
62 se2_ids.sort();
63
64 for id in se2_ids {
65 let vertex = &graph.vertices_se2[id];
66 writeln!(
67 file,
68 "VERTEX_SE2 {} {:.17e} {:.17e} {:.17e}",
69 vertex.id,
70 vertex.x(),
71 vertex.y(),
72 vertex.theta()
73 )
74 .map_err(|e| {
75 IoError::Io(e).log_with_source(format!("Failed to write SE2 vertex {}", vertex.id))
76 })?;
77 }
78
79 let mut se3_ids: Vec<_> = graph.vertices_se3.keys().collect();
81 se3_ids.sort();
82
83 for id in se3_ids {
84 let vertex = &graph.vertices_se3[id];
85 let trans = vertex.translation();
86 let quat = vertex.rotation();
87 writeln!(
88 file,
89 "VERTEX_SE3:QUAT {} {:.17e} {:.17e} {:.17e} {:.17e} {:.17e} {:.17e} {:.17e}",
90 vertex.id, trans.x, trans.y, trans.z, quat.i, quat.j, quat.k, quat.w
91 )
92 .map_err(|e| {
93 IoError::Io(e).log_with_source(format!("Failed to write SE3 vertex {}", vertex.id))
94 })?;
95 }
96
97 for edge in &graph.edges_se2 {
99 let meas = &edge.measurement;
100 let info = &edge.information;
101
102 writeln!(
104 file,
105 "EDGE_SE2 {} {} {:.17e} {:.17e} {:.17e} {:.17e} {:.17e} {:.17e} {:.17e} {:.17e} {:.17e}",
106 edge.from,
107 edge.to,
108 meas.x(),
109 meas.y(),
110 meas.angle(),
111 info[(0, 0)],
112 info[(0, 1)],
113 info[(1, 1)],
114 info[(2, 2)],
115 info[(0, 2)],
116 info[(1, 2)]
117 )
118 .map_err(|e| {
119 IoError::Io(e).log_with_source(format!(
120 "Failed to write SE2 edge {} -> {}",
121 edge.from, edge.to
122 ))
123 })?;
124 }
125
126 for edge in &graph.edges_se3 {
128 let trans = edge.measurement.translation();
129 let quat = edge.measurement.rotation_quaternion();
130 let info = &edge.information;
131
132 write!(
134 file,
135 "EDGE_SE3:QUAT {} {} {:.17e} {:.17e} {:.17e} {:.17e} {:.17e} {:.17e} {:.17e}",
136 edge.from, edge.to, trans.x, trans.y, trans.z, quat.i, quat.j, quat.k, quat.w
137 )
138 .map_err(|e| {
139 IoError::Io(e).log_with_source(format!(
140 "Failed to write SE3 edge {} -> {}",
141 edge.from, edge.to
142 ))
143 })?;
144
145 for i in 0..6 {
147 for j in i..6 {
148 write!(file, " {:.17e}", info[(i, j)]).map_err(|e| {
149 IoError::Io(e).log_with_source(format!(
150 "Failed to write SE3 edge {} -> {} information matrix",
151 edge.from, edge.to
152 ))
153 })?;
154 }
155 }
156 writeln!(file).map_err(|e| {
157 IoError::Io(e).log_with_source(format!(
158 "Failed to write SE3 edge {} -> {} newline",
159 edge.from, edge.to
160 ))
161 })?;
162 }
163
164 Ok(())
165 }
166}
167
168impl G2oLoader {
169 fn parse_content(content: &str) -> Result<Graph, IoError> {
171 let lines: Vec<&str> = content.lines().collect();
172 let minimum_lines_for_parallel = 1000;
173
174 let estimated_vertices = lines.len() / 4;
176 let estimated_edges = estimated_vertices * 3;
177 let mut graph = Graph {
178 vertices_se2: HashMap::with_capacity(estimated_vertices),
179 vertices_se3: HashMap::with_capacity(estimated_vertices),
180 edges_se2: Vec::with_capacity(estimated_edges),
181 edges_se3: Vec::with_capacity(estimated_edges),
182 };
183
184 if lines.len() > minimum_lines_for_parallel {
186 Self::parse_parallel(&lines, &mut graph)?;
187 } else {
188 Self::parse_sequential(&lines, &mut graph)?;
189 }
190
191 Ok(graph)
192 }
193
194 fn parse_sequential(lines: &[&str], graph: &mut Graph) -> Result<(), IoError> {
196 for (line_num, line) in lines.iter().enumerate() {
197 Self::parse_line(line, line_num + 1, graph)?;
198 }
199 Ok(())
200 }
201
202 fn parse_parallel(lines: &[&str], graph: &mut Graph) -> Result<(), IoError> {
204 let results: Result<Vec<_>, IoError> = lines
206 .par_iter()
207 .enumerate()
208 .map(|(line_num, line)| Self::parse_line_to_enum(line, line_num + 1))
209 .collect();
210
211 let parsed_items = results?;
212
213 for item in parsed_items.into_iter().flatten() {
215 match item {
216 ParsedItem::VertexSE2(vertex) => {
217 let id = vertex.id;
218 if graph.vertices_se2.insert(id, vertex).is_some() {
219 return Err(IoError::DuplicateVertex { id });
220 }
221 }
222 ParsedItem::VertexSE3(vertex) => {
223 let id = vertex.id;
224 if graph.vertices_se3.insert(id, vertex).is_some() {
225 return Err(IoError::DuplicateVertex { id });
226 }
227 }
228 ParsedItem::EdgeSE2(edge) => {
229 graph.edges_se2.push(edge);
230 }
231 ParsedItem::EdgeSE3(edge) => {
232 graph.edges_se3.push(*edge);
233 }
234 }
235 }
236
237 Ok(())
238 }
239
240 fn parse_line(line: &str, line_num: usize, graph: &mut Graph) -> Result<(), IoError> {
242 let line = line.trim();
243
244 if line.is_empty() || line.starts_with('#') {
246 return Ok(());
247 }
248
249 let parts: Vec<&str> = line.split_whitespace().collect();
250 if parts.is_empty() {
251 return Ok(());
252 }
253
254 match parts[0] {
255 "VERTEX_SE2" => {
256 let vertex = Self::parse_vertex_se2(&parts, line_num)?;
257 let id = vertex.id;
258 if graph.vertices_se2.insert(id, vertex).is_some() {
259 return Err(IoError::DuplicateVertex { id });
260 }
261 }
262 "VERTEX_SE3:QUAT" => {
263 let vertex = Self::parse_vertex_se3(&parts, line_num)?;
264 let id = vertex.id;
265 if graph.vertices_se3.insert(id, vertex).is_some() {
266 return Err(IoError::DuplicateVertex { id });
267 }
268 }
269 "EDGE_SE2" => {
270 let edge = Self::parse_edge_se2(&parts, line_num)?;
271 graph.edges_se2.push(edge);
272 }
273 "EDGE_SE3:QUAT" => {
274 let edge = Self::parse_edge_se3(&parts, line_num)?;
275 graph.edges_se3.push(edge);
276 }
277 _ => {
278 }
280 }
281
282 Ok(())
283 }
284
285 fn parse_line_to_enum(line: &str, line_num: usize) -> Result<Option<ParsedItem>, IoError> {
287 let line = line.trim();
288
289 if line.is_empty() || line.starts_with('#') {
291 return Ok(None);
292 }
293
294 let parts: Vec<&str> = line.split_whitespace().collect();
295 if parts.is_empty() {
296 return Ok(None);
297 }
298
299 let item = match parts[0] {
300 "VERTEX_SE2" => Some(ParsedItem::VertexSE2(Self::parse_vertex_se2(
301 &parts, line_num,
302 )?)),
303 "VERTEX_SE3:QUAT" => Some(ParsedItem::VertexSE3(Self::parse_vertex_se3(
304 &parts, line_num,
305 )?)),
306 "EDGE_SE2" => Some(ParsedItem::EdgeSE2(Self::parse_edge_se2(&parts, line_num)?)),
307 "EDGE_SE3:QUAT" => Some(ParsedItem::EdgeSE3(Box::new(Self::parse_edge_se3(
308 &parts, line_num,
309 )?))),
310 _ => None, };
312
313 Ok(item)
314 }
315
316 pub fn parse_vertex_se2(parts: &[&str], line_num: usize) -> Result<VertexSE2, IoError> {
318 if parts.len() < 5 {
319 return Err(IoError::MissingFields { line: line_num });
320 }
321
322 let id = parts[1]
323 .parse::<usize>()
324 .map_err(|_| IoError::InvalidNumber {
325 line: line_num,
326 value: parts[1].to_string(),
327 })?;
328
329 let x = parts[2]
330 .parse::<f64>()
331 .map_err(|_| IoError::InvalidNumber {
332 line: line_num,
333 value: parts[2].to_string(),
334 })?;
335
336 let y = parts[3]
337 .parse::<f64>()
338 .map_err(|_| IoError::InvalidNumber {
339 line: line_num,
340 value: parts[3].to_string(),
341 })?;
342
343 let theta = parts[4]
344 .parse::<f64>()
345 .map_err(|_| IoError::InvalidNumber {
346 line: line_num,
347 value: parts[4].to_string(),
348 })?;
349
350 Ok(VertexSE2::new(id, x, y, theta))
351 }
352
353 pub fn parse_vertex_se3(parts: &[&str], line_num: usize) -> Result<VertexSE3, IoError> {
355 if parts.len() < 9 {
356 return Err(IoError::MissingFields { line: line_num });
357 }
358
359 let id = parts[1]
360 .parse::<usize>()
361 .map_err(|_| IoError::InvalidNumber {
362 line: line_num,
363 value: parts[1].to_string(),
364 })?;
365
366 let x = parts[2]
367 .parse::<f64>()
368 .map_err(|_| IoError::InvalidNumber {
369 line: line_num,
370 value: parts[2].to_string(),
371 })?;
372
373 let y = parts[3]
374 .parse::<f64>()
375 .map_err(|_| IoError::InvalidNumber {
376 line: line_num,
377 value: parts[3].to_string(),
378 })?;
379
380 let z = parts[4]
381 .parse::<f64>()
382 .map_err(|_| IoError::InvalidNumber {
383 line: line_num,
384 value: parts[4].to_string(),
385 })?;
386
387 let qx = parts[5]
388 .parse::<f64>()
389 .map_err(|_| IoError::InvalidNumber {
390 line: line_num,
391 value: parts[5].to_string(),
392 })?;
393
394 let qy = parts[6]
395 .parse::<f64>()
396 .map_err(|_| IoError::InvalidNumber {
397 line: line_num,
398 value: parts[6].to_string(),
399 })?;
400
401 let qz = parts[7]
402 .parse::<f64>()
403 .map_err(|_| IoError::InvalidNumber {
404 line: line_num,
405 value: parts[7].to_string(),
406 })?;
407
408 let qw = parts[8]
409 .parse::<f64>()
410 .map_err(|_| IoError::InvalidNumber {
411 line: line_num,
412 value: parts[8].to_string(),
413 })?;
414
415 let translation = nalgebra::Vector3::new(x, y, z);
416 let quaternion = nalgebra::Quaternion::new(qw, qx, qy, qz);
417
418 let quat_norm = (qw * qw + qx * qx + qy * qy + qz * qz).sqrt();
420 if (quat_norm - 1.0).abs() > 0.01 {
421 return Err(IoError::InvalidQuaternion {
422 line: line_num,
423 norm: quat_norm,
424 });
425 }
426
427 let quaternion = quaternion.normalize();
429
430 Ok(VertexSE3::from_translation_quaternion(
431 id,
432 translation,
433 quaternion,
434 ))
435 }
436
437 fn parse_edge_se2(parts: &[&str], line_num: usize) -> Result<EdgeSE2, IoError> {
439 if parts.len() < 12 {
440 return Err(IoError::MissingFields { line: line_num });
441 }
442
443 let from = parts[1]
444 .parse::<usize>()
445 .map_err(|_| IoError::InvalidNumber {
446 line: line_num,
447 value: parts[1].to_string(),
448 })?;
449
450 let to = parts[2]
451 .parse::<usize>()
452 .map_err(|_| IoError::InvalidNumber {
453 line: line_num,
454 value: parts[2].to_string(),
455 })?;
456
457 let dx = parts[3]
459 .parse::<f64>()
460 .map_err(|_| IoError::InvalidNumber {
461 line: line_num,
462 value: parts[3].to_string(),
463 })?;
464 let dy = parts[4]
465 .parse::<f64>()
466 .map_err(|_| IoError::InvalidNumber {
467 line: line_num,
468 value: parts[4].to_string(),
469 })?;
470 let dtheta = parts[5]
471 .parse::<f64>()
472 .map_err(|_| IoError::InvalidNumber {
473 line: line_num,
474 value: parts[5].to_string(),
475 })?;
476
477 let info_values: Result<Vec<f64>, _> =
479 parts[6..12].iter().map(|s| s.parse::<f64>()).collect();
480
481 let info_values = info_values.map_err(|_| IoError::Parse {
482 line: line_num,
483 message: "Invalid information matrix values".to_string(),
484 })?;
485
486 let information = nalgebra::Matrix3::new(
487 info_values[0],
488 info_values[1],
489 info_values[2],
490 info_values[1],
491 info_values[3],
492 info_values[4],
493 info_values[2],
494 info_values[4],
495 info_values[5],
496 );
497
498 Ok(EdgeSE2::new(from, to, dx, dy, dtheta, information))
499 }
500
501 fn parse_edge_se3(parts: &[&str], line_num: usize) -> Result<EdgeSE3, IoError> {
503 if parts.len() < 10 {
505 return Err(IoError::MissingFields { line: line_num });
506 }
507
508 let from = parts[1]
510 .parse::<usize>()
511 .map_err(|_| IoError::InvalidNumber {
512 line: line_num,
513 value: parts[1].to_string(),
514 })?;
515
516 let to = parts[2]
517 .parse::<usize>()
518 .map_err(|_| IoError::InvalidNumber {
519 line: line_num,
520 value: parts[2].to_string(),
521 })?;
522
523 let tx = parts[3]
525 .parse::<f64>()
526 .map_err(|_| IoError::InvalidNumber {
527 line: line_num,
528 value: parts[3].to_string(),
529 })?;
530
531 let ty = parts[4]
532 .parse::<f64>()
533 .map_err(|_| IoError::InvalidNumber {
534 line: line_num,
535 value: parts[4].to_string(),
536 })?;
537
538 let tz = parts[5]
539 .parse::<f64>()
540 .map_err(|_| IoError::InvalidNumber {
541 line: line_num,
542 value: parts[5].to_string(),
543 })?;
544
545 let translation = nalgebra::Vector3::new(tx, ty, tz);
546
547 let qx = parts[6]
549 .parse::<f64>()
550 .map_err(|_| IoError::InvalidNumber {
551 line: line_num,
552 value: parts[6].to_string(),
553 })?;
554
555 let qy = parts[7]
556 .parse::<f64>()
557 .map_err(|_| IoError::InvalidNumber {
558 line: line_num,
559 value: parts[7].to_string(),
560 })?;
561
562 let qz = parts[8]
563 .parse::<f64>()
564 .map_err(|_| IoError::InvalidNumber {
565 line: line_num,
566 value: parts[8].to_string(),
567 })?;
568
569 let qw = parts[9]
570 .parse::<f64>()
571 .map_err(|_| IoError::InvalidNumber {
572 line: line_num,
573 value: parts[9].to_string(),
574 })?;
575
576 let rotation =
577 nalgebra::UnitQuaternion::from_quaternion(nalgebra::Quaternion::new(qw, qx, qy, qz));
578
579 let info_values: Result<Vec<f64>, _> =
581 parts[10..31].iter().map(|s| s.parse::<f64>()).collect();
582
583 let info_values = info_values.map_err(|_| IoError::Parse {
584 line: line_num,
585 message: "Invalid information matrix values".to_string(),
586 })?;
587
588 let information = nalgebra::Matrix6::new(
589 info_values[0],
590 info_values[1],
591 info_values[2],
592 info_values[3],
593 info_values[4],
594 info_values[5],
595 info_values[1],
596 info_values[6],
597 info_values[7],
598 info_values[8],
599 info_values[9],
600 info_values[10],
601 info_values[2],
602 info_values[7],
603 info_values[11],
604 info_values[12],
605 info_values[13],
606 info_values[14],
607 info_values[3],
608 info_values[8],
609 info_values[12],
610 info_values[15],
611 info_values[16],
612 info_values[17],
613 info_values[4],
614 info_values[9],
615 info_values[13],
616 info_values[16],
617 info_values[18],
618 info_values[19],
619 info_values[5],
620 info_values[10],
621 info_values[14],
622 info_values[17],
623 info_values[19],
624 info_values[20],
625 );
626
627 Ok(EdgeSE3::new(from, to, translation, rotation, information))
628 }
629}
630
631enum ParsedItem {
633 VertexSE2(VertexSE2),
634 VertexSE3(VertexSE3),
635 EdgeSE2(EdgeSE2),
636 EdgeSE3(Box<EdgeSE3>),
637}
638
639#[cfg(test)]
640mod tests {
641 use super::*;
642
643 type TestResult = Result<(), Box<dyn std::error::Error>>;
644
645 #[test]
646 fn test_parse_vertex_se2() -> TestResult {
647 let parts = vec!["VERTEX_SE2", "0", "1.0", "2.0", "0.5"];
648 let vertex = G2oLoader::parse_vertex_se2(&parts, 1)?;
649
650 assert_eq!(vertex.id(), 0);
651 assert_eq!(vertex.x(), 1.0);
652 assert_eq!(vertex.y(), 2.0);
653 assert_eq!(vertex.theta(), 0.5);
654
655 Ok(())
656 }
657
658 #[test]
659 fn test_parse_vertex_se3() -> TestResult {
660 let parts = vec![
661 "VERTEX_SE3:QUAT",
662 "1",
663 "1.0",
664 "2.0",
665 "3.0",
666 "0.0",
667 "0.0",
668 "0.0",
669 "1.0",
670 ];
671 let vertex = G2oLoader::parse_vertex_se3(&parts, 1)?;
672
673 assert_eq!(vertex.id(), 1);
674 assert_eq!(vertex.translation(), nalgebra::Vector3::new(1.0, 2.0, 3.0));
675 assert!(vertex.rotation().quaternion().w > 0.99); Ok(())
678 }
679
680 #[test]
681 fn test_error_handling() {
682 let parts = vec!["VERTEX_SE2", "invalid", "1.0", "2.0", "0.5"];
684 let result = G2oLoader::parse_vertex_se2(&parts, 1);
685 assert!(matches!(result, Err(IoError::InvalidNumber { .. })));
686
687 let parts = vec!["VERTEX_SE2", "0"];
689 let result = G2oLoader::parse_vertex_se2(&parts, 1);
690 assert!(matches!(result, Err(IoError::MissingFields { .. })));
691 }
692}