use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Image {
pub timestamp: u64,
pub width: u32,
pub height: u32,
pub encoding: String,
pub data: Vec<u8>,
#[serde(skip_serializing_if = "Option::is_none")]
pub frame_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CompressedImage {
pub timestamp: u64,
pub width: u32,
pub height: u32,
pub format: String,
pub data: Vec<u8>,
#[serde(skip_serializing_if = "Option::is_none")]
pub quality: Option<u8>,
#[serde(skip_serializing_if = "Option::is_none")]
pub frame_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LaserScan {
pub timestamp: u64,
pub angle_min: f32,
pub angle_max: f32,
pub angle_increment: f32,
pub time_increment: f32,
pub scan_time: f32,
pub range_min: f32,
pub range_max: f32,
pub ranges: Vec<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub intensities: Option<Vec<f32>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub frame_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Odometry {
pub timestamp: u64,
pub position: [f32; 3],
pub orientation: [f32; 4],
pub linear_velocity: [f32; 3],
pub angular_velocity: [f32; 3],
#[serde(skip_serializing_if = "Option::is_none")]
pub frame_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub child_frame_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Imu {
pub timestamp: u64,
pub orientation: [f32; 4],
#[serde(skip_serializing_if = "Option::is_none")]
pub orientation_covariance: Option<[f32; 9]>,
pub angular_velocity: [f32; 3],
#[serde(skip_serializing_if = "Option::is_none")]
pub angular_velocity_covariance: Option<[f32; 9]>,
pub linear_acceleration: [f32; 3],
#[serde(skip_serializing_if = "Option::is_none")]
pub linear_acceleration_covariance: Option<[f32; 9]>,
#[serde(skip_serializing_if = "Option::is_none")]
pub frame_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GpsData {
pub timestamp: u64,
pub latitude: f64,
pub longitude: f64,
pub altitude: f64,
#[serde(skip_serializing_if = "Option::is_none")]
pub horizontal_accuracy: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub vertical_accuracy: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub satellites: Option<u8>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fix_type: Option<u8>,
}
impl Image {
pub fn new_rgb8(width: u32, height: u32, data: Vec<u8>) -> Self {
Self {
timestamp: crate::prelude::now_micros(),
width,
height,
encoding: "rgb8".to_string(),
data,
frame_id: None,
}
}
pub fn new_depth(width: u32, height: u32, data: Vec<u8>) -> Self {
Self {
timestamp: crate::prelude::now_micros(),
width,
height,
encoding: "16UC1".to_string(),
data,
frame_id: None,
}
}
pub fn expected_size(&self) -> usize {
let bytes_per_pixel = match self.encoding.as_str() {
"rgb8" | "bgr8" => 3,
"rgba8" | "bgra8" => 4,
"mono8" => 1,
"mono16" | "16UC1" => 2,
_ => 1, };
(self.width * self.height) as usize * bytes_per_pixel
}
pub fn is_valid(&self) -> bool {
self.data.len() == self.expected_size()
}
}
impl LaserScan {
pub fn num_points(&self) -> usize {
self.ranges.len()
}
pub fn point_at(&self, index: usize) -> Option<(f32, f32)> {
if index >= self.ranges.len() {
return None;
}
let range = self.ranges[index];
if !self.is_valid_range(range) {
return None;
}
let angle = self.angle_min + (index as f32) * self.angle_increment;
Some((range * angle.cos(), range * angle.sin()))
}
pub fn is_valid_range(&self, range: f32) -> bool {
range.is_finite() && range >= self.range_min && range <= self.range_max
}
pub fn valid_points(&self) -> Vec<(f32, f32)> {
(0..self.ranges.len()).filter_map(|i| self.point_at(i)).collect()
}
}
impl Odometry {
pub fn new() -> Self {
Self {
timestamp: crate::prelude::now_micros(),
position: [0.0, 0.0, 0.0],
orientation: [0.0, 0.0, 0.0, 1.0], linear_velocity: [0.0, 0.0, 0.0],
angular_velocity: [0.0, 0.0, 0.0],
frame_id: None,
child_frame_id: None,
}
}
pub fn velocity_2d(&self) -> (f32, f32) {
(self.linear_velocity[0], self.linear_velocity[1])
}
pub fn speed(&self) -> f32 {
let vx = self.linear_velocity[0];
let vy = self.linear_velocity[1];
let vz = self.linear_velocity[2];
(vx * vx + vy * vy + vz * vz).sqrt()
}
pub fn yaw(&self) -> f32 {
let q = self.orientation;
(2.0 * (q[3] * q[2] + q[0] * q[1])).atan2(1.0 - 2.0 * (q[1] * q[1] + q[2] * q[2]))
}
pub fn distance_to(&self, other: &Odometry) -> f32 {
let dx = self.position[0] - other.position[0];
let dy = self.position[1] - other.position[1];
let dz = self.position[2] - other.position[2];
(dx * dx + dy * dy + dz * dz).sqrt()
}
pub fn distance_2d_to(&self, other: &Odometry) -> f32 {
let dx = self.position[0] - other.position[0];
let dy = self.position[1] - other.position[1];
(dx * dx + dy * dy).sqrt()
}
}
impl Default for Odometry {
fn default() -> Self {
Self::new()
}
}
impl Imu {
pub fn angular_speed(&self) -> f32 {
let w = self.angular_velocity;
(w[0] * w[0] + w[1] * w[1] + w[2] * w[2]).sqrt()
}
pub fn normalize_orientation(&mut self) {
let q = self.orientation;
let mag = (q[0] * q[0] + q[1] * q[1] + q[2] * q[2] + q[3] * q[3]).sqrt();
if mag > 0.0 {
self.orientation = [q[0] / mag, q[1] / mag, q[2] / mag, q[3] / mag];
}
}
}
impl GpsData {
pub fn distance_to(&self, other: &GpsData) -> f64 {
const EARTH_RADIUS_M: f64 = 6371000.0;
let lat1 = self.latitude.to_radians();
let lat2 = other.latitude.to_radians();
let dlat = (other.latitude - self.latitude).to_radians();
let dlon = (other.longitude - self.longitude).to_radians();
let a = (dlat / 2.0).sin().powi(2) + lat1.cos() * lat2.cos() * (dlon / 2.0).sin().powi(2);
let c = 2.0 * a.sqrt().atan2((1.0 - a).sqrt());
EARTH_RADIUS_M * c
}
}