use super::video::video_detail_xml; use chrono::{Duration, SecondsFormat, Utc}; use uuid::Uuid; pub struct CursorOverTime { pub uuid: Option, pub r#type: Option, pub point_lat: f64, pub point_lon: f64, pub point_hae: f32, pub point_ce: Option, pub point_le: Option, pub contact_callsign: String, pub group_name: Option, pub group_role: Option, pub track_course: Option, pub track_speed: Option, pub link_uid: Option, pub remarker: Option, pub video_url: Option, pub stale_seconds: Option, } impl CursorOverTime { pub fn convert_to_xml(&self) -> String { let uuid = match &self.uuid { Some(uuid) => uuid, None => &Uuid::new_v4().to_string(), }; let marker_type = match &self.r#type { Some(marker_type) => marker_type, None => &"a-f-G-U-C-I".to_string(), }; let created_time = Utc::now().to_rfc3339_opts(SecondsFormat::Millis, true); let stale_seconds = self.stale_seconds.unwrap_or(360).max(1); let stale_time = (Utc::now() + Duration::seconds(stale_seconds)) .to_rfc3339_opts(SecondsFormat::Millis, true); let mut xml = String::new(); xml.push_str(""); xml.push_str( format!("", marker_type, uuid, created_time, created_time, stale_time).as_str()); let point_ce = match &self.point_ce { Some(point_ce) => point_ce, None => &9999999.0, }; let point_le = match &self.point_le { Some(point_le) => point_le, None => &9999999.0, }; xml.push_str( format!( "", point_ce, point_le, self.point_hae, self.point_lat, self.point_lon ) .as_str(), ); xml.push_str(""); xml.push_str(""); if let Some(linked_uid) = &self.link_uid { xml.push_str(""); xml.push_str( format!( "", linked_uid, ) .as_str(), ); xml.push_str(""); } xml.push_str(format!("", self.contact_callsign).as_str()); xml.push_str(format!("", self.contact_callsign).as_str()); if let (Some(track_course), Some(track_speed)) = (&self.track_course, &self.track_speed) { xml.push_str( format!( "", track_course, track_speed ) .as_str(), ); xml.push_str(""); } if let (Some(group_name), Some(group_role)) = (&self.group_name, &self.group_role) { xml.push_str( format!( "<__group name=\"{}\" role=\"{}\" />", group_name, group_role ) .as_str(), ); } if let Some(remark) = &self.remarker { xml.push_str(format!("ARMATAK | {}", remark).as_str()); } if let Some(video_url) = &self.video_url { if !video_url.trim().is_empty() { xml.push_str(&video_detail_xml(video_url, uuid, &self.contact_callsign)); } } xml.push_str(""); return xml; } } #[cfg(test)] mod tests { use super::CursorOverTime; fn minimal_marker() -> CursorOverTime { CursorOverTime { uuid: None, r#type: None, point_lat: -30.0, point_lon: -51.0, point_hae: 10.0, point_ce: None, point_le: None, contact_callsign: "Falcon".to_string(), group_name: None, group_role: None, track_course: None, track_speed: None, link_uid: None, remarker: None, video_url: None, stale_seconds: None, } } #[test] fn serializes_minimal_marker_with_tak_defaults() { let xml = minimal_marker().convert_to_xml(); assert!(xml.starts_with("")); assert!(xml.contains("")); assert!(xml.contains("")); assert!(xml.contains("")); assert!(!xml.contains("")); assert!(!xml.contains("<__video>")); assert!(xml.ends_with("")); } #[test] fn serializes_all_optional_marker_details() { let marker = CursorOverTime { uuid: Some("marker-1".to_string()), r#type: Some("a-f-G-U-C".to_string()), point_lat: 1.25, point_lon: 2.5, point_hae: 100.0, point_ce: Some(3.0), point_le: Some(4.0), contact_callsign: "Raven".to_string(), group_name: Some("Cyan".to_string()), group_role: Some("Team Member".to_string()), track_course: Some(180), track_speed: Some(12.5), link_uid: Some("parent-1".to_string()), remarker: Some("test remark".to_string()), video_url: Some("rtsp://video.example.test:8554/live".to_string()), stale_seconds: Some(0), }; let xml = marker.convert_to_xml(); assert!(xml.contains("type=\"a-f-G-U-C\"")); assert!(xml.contains("uid=\"marker-1\"")); assert!(xml.contains("")); assert!(xml.contains("")); assert!(xml.contains("")); assert!(xml.contains("")); assert!(xml.contains("")); assert!(xml.contains("")); assert!(xml.contains("<__group name=\"Cyan\" role=\"Team Member\" />")); assert!(xml.contains("ARMATAK | test remark")); assert!(xml.contains("