slamkit_rs/odometry/
trajectory.rs1use nalgebra as na;
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Clone, Serialize, Deserialize)]
6pub struct TrajectoryPoint {
7 pub frame: usize,
8 pub position: [f64; 3],
9 pub timestamp: f64,
10}
11
12pub struct Trajectory {
14 points: Vec<TrajectoryPoint>,
15 global_pose: na::Matrix4<f64>,
16}
17
18impl Trajectory {
19 pub fn new() -> Self {
21 Self {
22 points: vec![TrajectoryPoint {
23 frame: 0,
24 position: [0.0, 0.0, 0.0],
25 timestamp: 0.0,
26 }],
27 global_pose: na::Matrix4::identity(),
28 }
29 }
30
31 pub fn update(
33 &mut self,
34 rotation: &na::Matrix3<f64>,
35 translation: &na::Vector3<f64>,
36 frame: usize,
37 timestamp: f64,
38 ) {
39 let mut relative_transform = na::Matrix4::identity();
41
42 for i in 0..3 {
44 for j in 0..3 {
45 relative_transform[(i, j)] = rotation[(i, j)];
46 }
47 }
48
49 relative_transform[(0, 3)] = translation[0];
51 relative_transform[(1, 3)] = translation[1];
52 relative_transform[(2, 3)] = translation[2];
53
54 self.global_pose = self.global_pose * relative_transform;
56
57 let position = [
59 self.global_pose[(0, 3)],
60 self.global_pose[(1, 3)],
61 self.global_pose[(2, 3)],
62 ];
63
64 self.points.push(TrajectoryPoint {
65 frame,
66 position,
67 timestamp,
68 });
69 }
70
71 pub fn current_pose(&self) -> &na::Matrix4<f64> {
73 &self.global_pose
74 }
75
76 pub fn current_pose_rt(&self) -> (na::Matrix3<f64>, na::Vector3<f64>) {
78 let mut rotation = na::Matrix3::zeros();
79 for i in 0..3 {
80 for j in 0..3 {
81 rotation[(i, j)] = self.global_pose[(i, j)];
82 }
83 }
84
85 let translation = na::Vector3::new(
86 self.global_pose[(0, 3)],
87 self.global_pose[(1, 3)],
88 self.global_pose[(2, 3)],
89 );
90
91 (rotation, translation)
92 }
93
94 pub fn points(&self) -> &[TrajectoryPoint] {
96 &self.points
97 }
98
99 pub fn total_distance(&self) -> f64 {
101 let mut distance = 0.0;
102 for i in 1..self.points.len() {
103 let p1 = &self.points[i - 1].position;
104 let p2 = &self.points[i].position;
105
106 let dx = p2[0] - p1[0];
107 let dy = p2[1] - p1[1];
108 let dz = p2[2] - p1[2];
109
110 distance += (dx * dx + dy * dy + dz * dz).sqrt();
111 }
112 distance
113 }
114
115 pub fn len(&self) -> usize {
117 self.points.len()
118 }
119
120 pub fn is_empty(&self) -> bool {
122 self.points.is_empty()
123 }
124
125 pub fn to_json(&self) -> Result<String, Box<dyn std::error::Error>> {
127 let json = serde_json::to_string_pretty(&self.points)?;
128 Ok(json)
129 }
130
131 pub fn save_to_file(&self, path: &str) -> Result<(), Box<dyn std::error::Error>> {
133 let json = self.to_json()?;
134 std::fs::write(path, json)?;
135 Ok(())
136 }
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142
143 #[test]
144 fn test_trajectory_creation() {
145 let traj = Trajectory::new();
146 assert_eq!(traj.len(), 1);
147 assert_eq!(traj.points()[0].frame, 0);
148 }
149
150 #[test]
151 fn test_trajectory_update() {
152 let mut traj = Trajectory::new();
153 let r = na::Matrix3::identity();
154 let t = na::Vector3::new(1.0, 0.0, 0.0);
155
156 traj.update(&r, &t, 1, 0.1);
157
158 assert_eq!(traj.len(), 2);
159 assert_eq!(traj.points()[1].frame, 1);
160 assert!((traj.points()[1].position[0] - 1.0).abs() < 1e-6);
161 }
162
163 #[test]
164 fn test_total_distance() {
165 let mut traj = Trajectory::new();
166 let r = na::Matrix3::identity();
167
168 let t1 = na::Vector3::new(3.0, 0.0, 0.0);
170 traj.update(&r, &t1, 1, 0.1);
171
172 let t2 = na::Vector3::new(0.0, 4.0, 0.0);
174 traj.update(&r, &t2, 2, 0.2);
175
176 let distance = traj.total_distance();
177 assert!((distance - 7.0).abs() < 1e-6);
178 }
179
180 #[test]
181 fn test_json_export() {
182 let mut traj = Trajectory::new();
183 let r = na::Matrix3::identity();
184 let t = na::Vector3::new(1.0, 2.0, 3.0);
185 traj.update(&r, &t, 1, 0.1);
186
187 let json = traj.to_json();
188 assert!(json.is_ok());
189 assert!(json.unwrap().contains("position"));
190 }
191}