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