1use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct Image {
10 pub timestamp: u64,
12
13 pub width: u32,
15
16 pub height: u32,
18
19 pub encoding: String,
21
22 pub data: Vec<u8>,
24
25 #[serde(skip_serializing_if = "Option::is_none")]
27 pub frame_id: Option<String>,
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct CompressedImage {
36 pub timestamp: u64,
38
39 pub width: u32,
41
42 pub height: u32,
44
45 pub format: String,
47
48 pub data: Vec<u8>,
50
51 #[serde(skip_serializing_if = "Option::is_none")]
53 pub quality: Option<u8>,
54
55 #[serde(skip_serializing_if = "Option::is_none")]
57 pub frame_id: Option<String>,
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct LaserScan {
65 pub timestamp: u64,
67
68 pub angle_min: f32,
70
71 pub angle_max: f32,
73
74 pub angle_increment: f32,
76
77 pub time_increment: f32,
79
80 pub scan_time: f32,
82
83 pub range_min: f32,
85
86 pub range_max: f32,
88
89 pub ranges: Vec<f32>,
91
92 #[serde(skip_serializing_if = "Option::is_none")]
94 pub intensities: Option<Vec<f32>>,
95
96 #[serde(skip_serializing_if = "Option::is_none")]
98 pub frame_id: Option<String>,
99}
100
101#[derive(Debug, Clone, Serialize, Deserialize)]
105pub struct Odometry {
106 pub timestamp: u64,
108
109 pub position: [f32; 3],
111
112 pub orientation: [f32; 4],
114
115 pub linear_velocity: [f32; 3],
117
118 pub angular_velocity: [f32; 3],
120
121 #[serde(skip_serializing_if = "Option::is_none")]
123 pub frame_id: Option<String>,
124
125 #[serde(skip_serializing_if = "Option::is_none")]
127 pub child_frame_id: Option<String>,
128}
129
130#[derive(Debug, Clone, Serialize, Deserialize)]
134pub struct Imu {
135 pub timestamp: u64,
137
138 pub orientation: [f32; 4],
140
141 #[serde(skip_serializing_if = "Option::is_none")]
143 pub orientation_covariance: Option<[f32; 9]>,
144
145 pub angular_velocity: [f32; 3],
147
148 #[serde(skip_serializing_if = "Option::is_none")]
150 pub angular_velocity_covariance: Option<[f32; 9]>,
151
152 pub linear_acceleration: [f32; 3],
154
155 #[serde(skip_serializing_if = "Option::is_none")]
157 pub linear_acceleration_covariance: Option<[f32; 9]>,
158
159 #[serde(skip_serializing_if = "Option::is_none")]
161 pub frame_id: Option<String>,
162}
163
164#[derive(Debug, Clone, Serialize, Deserialize)]
168pub struct GpsData {
169 pub timestamp: u64,
171
172 pub latitude: f64,
174
175 pub longitude: f64,
177
178 pub altitude: f64,
180
181 #[serde(skip_serializing_if = "Option::is_none")]
183 pub horizontal_accuracy: Option<f32>,
184
185 #[serde(skip_serializing_if = "Option::is_none")]
187 pub vertical_accuracy: Option<f32>,
188
189 #[serde(skip_serializing_if = "Option::is_none")]
191 pub satellites: Option<u8>,
192
193 #[serde(skip_serializing_if = "Option::is_none")]
195 pub fix_type: Option<u8>,
196}
197
198impl Image {
200 pub fn new_rgb8(width: u32, height: u32, data: Vec<u8>) -> Self {
202 Self {
203 timestamp: crate::prelude::now_micros(),
204 width,
205 height,
206 encoding: "rgb8".to_string(),
207 data,
208 frame_id: None,
209 }
210 }
211
212 pub fn new_depth(width: u32, height: u32, data: Vec<u8>) -> Self {
214 Self {
215 timestamp: crate::prelude::now_micros(),
216 width,
217 height,
218 encoding: "16UC1".to_string(),
219 data,
220 frame_id: None,
221 }
222 }
223
224 pub fn expected_size(&self) -> usize {
226 let bytes_per_pixel = match self.encoding.as_str() {
227 "rgb8" | "bgr8" => 3,
228 "rgba8" | "bgra8" => 4,
229 "mono8" => 1,
230 "mono16" | "16UC1" => 2,
231 _ => 1, };
233 (self.width * self.height) as usize * bytes_per_pixel
234 }
235
236 pub fn is_valid(&self) -> bool {
238 self.data.len() == self.expected_size()
239 }
240
241 pub fn age_micros(&self) -> u64 {
243 crate::prelude::now_micros().saturating_sub(self.timestamp)
244 }
245}
246
247impl LaserScan {
249 pub fn num_points(&self) -> usize {
251 self.ranges.len()
252 }
253
254 pub fn angle_at(&self, index: usize) -> Option<f32> {
256 if index < self.ranges.len() {
257 Some(self.angle_min + (index as f32) * self.angle_increment)
258 } else {
259 None
260 }
261 }
262
263 pub fn point_at(&self, index: usize) -> Option<(f32, f32)> {
265 if index >= self.ranges.len() {
266 return None;
267 }
268
269 let range = self.ranges[index];
270 if !self.is_valid_range(range) {
271 return None;
272 }
273
274 let angle = self.angle_min + (index as f32) * self.angle_increment;
275 Some((range * angle.cos(), range * angle.sin()))
276 }
277
278 pub fn is_valid_range(&self, range: f32) -> bool {
280 range.is_finite() && range >= self.range_min && range <= self.range_max
281 }
282
283 pub fn valid_points(&self) -> Vec<(f32, f32)> {
285 (0..self.ranges.len()).filter_map(|i| self.point_at(i)).collect()
286 }
287
288 pub fn age_micros(&self) -> u64 {
290 crate::prelude::now_micros().saturating_sub(self.timestamp)
291 }
292}
293
294impl Odometry {
296 pub fn new() -> Self {
298 Self {
299 timestamp: crate::prelude::now_micros(),
300 position: [0.0, 0.0, 0.0],
301 orientation: [0.0, 0.0, 0.0, 1.0], linear_velocity: [0.0, 0.0, 0.0],
303 angular_velocity: [0.0, 0.0, 0.0],
304 frame_id: None,
305 child_frame_id: None,
306 }
307 }
308
309 pub fn position_2d(&self) -> (f32, f32) {
311 (self.position[0], self.position[1])
312 }
313
314 pub fn velocity_2d(&self) -> (f32, f32) {
316 (self.linear_velocity[0], self.linear_velocity[1])
317 }
318
319 pub fn speed(&self) -> f32 {
321 let vx = self.linear_velocity[0];
322 let vy = self.linear_velocity[1];
323 let vz = self.linear_velocity[2];
324 (vx * vx + vy * vy + vz * vz).sqrt()
325 }
326
327 pub fn yaw(&self) -> f32 {
329 let q = self.orientation;
330 (2.0 * (q[3] * q[2] + q[0] * q[1])).atan2(1.0 - 2.0 * (q[1] * q[1] + q[2] * q[2]))
331 }
332
333 pub fn distance_to(&self, other: &Odometry) -> f32 {
335 let dx = self.position[0] - other.position[0];
336 let dy = self.position[1] - other.position[1];
337 let dz = self.position[2] - other.position[2];
338 (dx * dx + dy * dy + dz * dz).sqrt()
339 }
340
341 pub fn distance_2d_to(&self, other: &Odometry) -> f32 {
343 let dx = self.position[0] - other.position[0];
344 let dy = self.position[1] - other.position[1];
345 (dx * dx + dy * dy).sqrt()
346 }
347}
348
349impl Default for Odometry {
350 fn default() -> Self {
351 Self::new()
352 }
353}
354
355impl Imu {
357 pub fn acceleration_magnitude(&self) -> f32 {
359 let a = self.linear_acceleration;
360 (a[0] * a[0] + a[1] * a[1] + a[2] * a[2]).sqrt()
361 }
362
363 pub fn angular_speed(&self) -> f32 {
365 let w = self.angular_velocity;
366 (w[0] * w[0] + w[1] * w[1] + w[2] * w[2]).sqrt()
367 }
368
369 pub fn normalize_orientation(&mut self) {
371 let q = self.orientation;
372 let mag = (q[0] * q[0] + q[1] * q[1] + q[2] * q[2] + q[3] * q[3]).sqrt();
373 if mag > 0.0 {
374 self.orientation = [q[0] / mag, q[1] / mag, q[2] / mag, q[3] / mag];
375 }
376 }
377
378 pub fn age_micros(&self) -> u64 {
380 crate::prelude::now_micros().saturating_sub(self.timestamp)
381 }
382}
383
384impl GpsData {
386 pub fn distance_to(&self, other: &GpsData) -> f64 {
388 const EARTH_RADIUS_M: f64 = 6371000.0;
389
390 let lat1 = self.latitude.to_radians();
391 let lat2 = other.latitude.to_radians();
392 let dlat = (other.latitude - self.latitude).to_radians();
393 let dlon = (other.longitude - self.longitude).to_radians();
394
395 let a = (dlat / 2.0).sin().powi(2) + lat1.cos() * lat2.cos() * (dlon / 2.0).sin().powi(2);
396 let c = 2.0 * a.sqrt().atan2((1.0 - a).sqrt());
397
398 EARTH_RADIUS_M * c
399 }
400
401 pub fn bearing_to(&self, other: &GpsData) -> f64 {
403 let lat1 = self.latitude.to_radians();
404 let lat2 = other.latitude.to_radians();
405 let dlon = (other.longitude - self.longitude).to_radians();
406
407 let y = dlon.sin() * lat2.cos();
408 let x = lat1.cos() * lat2.sin() - lat1.sin() * lat2.cos() * dlon.cos();
409 let bearing = y.atan2(x).to_degrees();
410
411 (bearing + 360.0) % 360.0
412 }
413
414 pub fn has_valid_fix(&self) -> bool {
416 self.fix_type.is_some_and(|t| t > 0)
417 }
418
419 pub fn is_rtk_fix(&self) -> bool {
421 self.fix_type.is_some_and(|t| t >= 4)
422 }
423
424 pub fn age_micros(&self) -> u64 {
426 crate::prelude::now_micros().saturating_sub(self.timestamp)
427 }
428}