From 3a82dba8542e7113f9a8c9bf9b30ed65c95c639d Mon Sep 17 00:00:00 2001 From: Valmo Trindade Date: Sun, 24 May 2026 16:07:23 -0300 Subject: [PATCH] Addded draw route rust structs --- src/cot/draws/mod.rs | 1 + src/cot/draws/route.rs | 149 +++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 3 +- src/tcp/draw.rs | 17 +++++ 4 files changed, 169 insertions(+), 1 deletion(-) create mode 100644 src/cot/draws/route.rs diff --git a/src/cot/draws/mod.rs b/src/cot/draws/mod.rs index 95fc753..3a45068 100644 --- a/src/cot/draws/mod.rs +++ b/src/cot/draws/mod.rs @@ -1,2 +1,3 @@ pub mod circle; +pub mod route; pub mod shape; diff --git a/src/cot/draws/route.rs b/src/cot/draws/route.rs new file mode 100644 index 0000000..6f476a0 --- /dev/null +++ b/src/cot/draws/route.rs @@ -0,0 +1,149 @@ +use arma_rs::{FromArma, FromArmaError}; +use uuid::Uuid; + +#[derive(Clone)] +struct RoutePoint { + lat: f64, + lon: f64, + hae: f32, +} + +pub struct RoutePayload { + pub uuid: String, + pub points: String, + pub callsign: String, + pub stale_seconds: i64, + pub color: i32, + pub stroke_weight: f64, + pub method: String, + pub route_type: String, + pub direction: String, + pub checkpoint_interval: usize, +} + +impl FromArma for RoutePayload { + fn from_arma(data: String) -> Result { + let ( + uuid, + points, + callsign, + stale_seconds, + color, + stroke_weight, + method, + route_type, + direction, + checkpoint_interval, + ) = <( + String, + String, + String, + i64, + i32, + f64, + String, + String, + String, + i32, + )>::from_arma(data)?; + + Ok(Self { + uuid, + points, + callsign, + stale_seconds, + color, + stroke_weight, + method, + route_type, + direction, + checkpoint_interval: checkpoint_interval.max(1) as usize, + }) + } +} + +impl RoutePayload { + pub fn to_xml(&self, now: &str, stale: &str) -> String { + let points = parse_points(&self.points); + let links = route_links(&self.callsign, &points, self.checkpoint_interval); + + format!( + r#"{links}<__routeinfo><__navcues/>1"#, + uid = escape_attr(&self.uuid), + now = now, + stale = stale, + links = links, + direction = escape_attr(&self.direction), + color = self.color, + method = escape_attr(&self.method), + stroke_weight = self.stroke_weight.max(1.0), + route_type = escape_attr(&self.route_type), + callsign = escape_attr(&self.callsign) + ) + } +} + +fn route_links(callsign: &str, points: &[RoutePoint], checkpoint_interval: usize) -> String { + let mut xml = String::new(); + let last_index = points.len().saturating_sub(1); + let mut checkpoint_number = 1; + + for (index, point) in points.iter().enumerate() { + let is_start = index == 0; + let is_end = index == last_index; + let is_checkpoint = !is_start && !is_end && index % checkpoint_interval == 0; + let point_callsign = if is_start { + format!("{} SP", callsign) + } else if is_end { + "VDO".to_string() + } else if is_checkpoint { + let name = format!("CP{}", checkpoint_number); + checkpoint_number += 1; + name + } else { + String::new() + }; + let link_type = if point_callsign.is_empty() { + "b-m-p-c" + } else { + "b-m-p-w" + }; + + xml.push_str(&format!( + r#""#, + link_uid = escape_attr(&Uuid::new_v4().to_string()), + callsign = escape_attr(&point_callsign), + link_type = link_type, + lat = point.lat, + lon = point.lon, + hae = point.hae + )); + } + + xml +} + +fn parse_points(raw: &str) -> Vec { + raw.split(';') + .filter_map(|entry| { + let parts: Vec<_> = entry.split(',').collect(); + if parts.len() != 3 { + return None; + } + + Some(RoutePoint { + lat: parts[0].parse().ok()?, + lon: parts[1].parse().ok()?, + hae: parts[2].parse().ok()?, + }) + }) + .collect() +} + +fn escape_attr(value: &str) -> String { + value + .replace('&', "&") + .replace('"', """) + .replace('<', "<") + .replace('>', ">") +} diff --git a/src/lib.rs b/src/lib.rs index ae82e3b..f02e733 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -100,7 +100,8 @@ pub fn init() -> Extension { .command("ellipse", tcp::draw::send_ellipse_cot) .command("rectangle", tcp::draw::send_rectangle_cot) .command("free", tcp::draw::send_freedraw_cot) - .command("vector", tcp::draw::send_vectordraw_cot), + .command("vector", tcp::draw::send_vectordraw_cot) + .command("route", tcp::draw::send_route_cot), ), ) .group( diff --git a/src/tcp/draw.rs b/src/tcp/draw.rs index 7555871..f0f8ae4 100644 --- a/src/tcp/draw.rs +++ b/src/tcp/draw.rs @@ -1,4 +1,5 @@ use arma_rs::Context; +use log::info; use crate::{cot, tcp::send_payload}; @@ -71,3 +72,19 @@ pub fn send_vectordraw_cot( "Sending Tactical Graphic CoT to TCP server" } + +pub fn send_route_cot( + ctx: Context, + route_payload: cot::draws::route::RoutePayload, +) -> &'static str { + let (now, stale) = payload_stale(route_payload.stale_seconds); + let payload = route_payload.to_xml(&now, &stale); + info!( + "Sending ATAK route '{}' ({} bytes)", + route_payload.callsign, + payload.len() + ); + send_payload(ctx, payload); + + "Sending Route CoT to TCP server" +}