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