[WIP] Adding Drawing Cursor Over Time functions

This commit is contained in:
2026-05-11 16:26:17 -03:00
parent 882a35c2cd
commit 5015f09d1d
10 changed files with 745 additions and 15 deletions

View File

@@ -1 +1,2 @@
pub mod circle;
pub mod shape;

317
src/cot/draws/shape.rs Normal file
View File

@@ -0,0 +1,317 @@
use arma_rs::{FromArma, FromArmaError};
#[derive(Clone)]
struct DrawPoint {
lat: f64,
lon: f64,
hae: f32,
}
pub struct DrawEllipsePayload {
pub uuid: String,
pub cot_type: String,
pub center_lat: f64,
pub center_lon: f64,
pub center_hae: f32,
pub major: f64,
pub minor: f64,
pub angle: f32,
pub callsign: String,
pub stale_seconds: i64,
pub stroke_color: i32,
pub fill_color: i32,
pub stroke_weight: f64,
pub milsym: String,
}
pub struct DrawLinksPayload {
pub uuid: String,
pub cot_type: String,
pub center_lat: f64,
pub center_lon: f64,
pub center_hae: f32,
pub points: String,
pub callsign: String,
pub stale_seconds: i64,
pub stroke_color: i32,
pub fill_color: i32,
pub stroke_weight: f64,
pub stroke_style: String,
pub closed: bool,
pub milsym: String,
}
impl FromArma for DrawEllipsePayload {
fn from_arma(data: String) -> Result<Self, FromArmaError> {
let (
uuid,
cot_type,
center_lat,
center_lon,
center_hae,
major,
minor,
angle,
callsign,
stale_seconds,
stroke_color,
fill_color,
stroke_weight,
milsym,
) = <(
String,
String,
f64,
f64,
f32,
f64,
f64,
f32,
String,
i64,
i32,
i32,
f64,
String,
)>::from_arma(data)?;
Ok(Self {
uuid,
cot_type,
center_lat,
center_lon,
center_hae,
major,
minor,
angle,
callsign,
stale_seconds,
stroke_color,
fill_color,
stroke_weight,
milsym,
})
}
}
impl FromArma for DrawLinksPayload {
fn from_arma(data: String) -> Result<Self, FromArmaError> {
let (
uuid,
cot_type,
center_lat,
center_lon,
center_hae,
points,
callsign,
stale_seconds,
stroke_color,
fill_color,
stroke_weight,
stroke_style,
closed,
milsym,
) = <(
String,
String,
f64,
f64,
f32,
String,
String,
i64,
i32,
i32,
f64,
String,
bool,
String,
)>::from_arma(data)?;
Ok(Self {
uuid,
cot_type,
center_lat,
center_lon,
center_hae,
points,
callsign,
stale_seconds,
stroke_color,
fill_color,
stroke_weight,
stroke_style,
closed,
milsym,
})
}
}
impl DrawEllipsePayload {
pub fn to_xml(&self, now: &str, stale: &str) -> String {
shape_event(
&self.uuid,
&self.cot_type,
self.center_lat,
self.center_lon,
self.center_hae,
now,
stale,
&self.callsign,
&ellipse_shape_detail(
&self.uuid,
self.major,
self.minor,
self.angle,
self.stroke_color,
self.fill_color,
self.stroke_weight,
),
self.stroke_color,
self.fill_color,
self.stroke_weight,
"solid",
&self.milsym,
)
}
}
impl DrawLinksPayload {
pub fn to_xml(&self, now: &str, stale: &str) -> String {
let points = parse_points(&self.points);
let shape_detail = links_detail(&points, self.closed);
shape_event(
&self.uuid,
&self.cot_type,
self.center_lat,
self.center_lon,
self.center_hae,
now,
stale,
&self.callsign,
&shape_detail,
self.stroke_color,
self.fill_color,
self.stroke_weight,
&self.stroke_style,
&self.milsym,
)
}
}
fn shape_event(
uid: &str,
cot_type: &str,
lat: f64,
lon: f64,
hae: f32,
now: &str,
stale: &str,
callsign: &str,
shape_detail: &str,
stroke_color: i32,
fill_color: i32,
stroke_weight: f64,
stroke_style: &str,
milsym: &str,
) -> String {
let milsym_detail = if milsym.trim().is_empty() {
String::new()
} else {
format!(r#"<__milsym id="{}" />"#, escape_attr(milsym))
};
format!(
r#"<?xml version="1.0" encoding="UTF-8" ?><event version="2.0" uid="{uid}" type="{cot_type}" time="{now}" start="{now}" stale="{stale}" how="h-e" access="Undefined"><point lat="{lat}" lon="{lon}" hae="{hae}" ce="10.9" le="9999999.0" /><detail>{shape_detail}<__shapeExtras cpvis="true" editable="true" /><contact callsign="{callsign}" /><remarks /><archive /><labels_on value="true" /><strokeColor value="{stroke_color}" /><strokeWeight value="{stroke_weight}" /><strokeStyle value="{stroke_style}" /><fillColor value="{fill_color}" /><precisionlocation altsrc="GPS" geopointsrc="GPS" />{milsym_detail}</detail></event>"#,
uid = escape_attr(uid),
cot_type = escape_attr(cot_type),
now = now,
stale = stale,
lat = lat,
lon = lon,
hae = hae,
shape_detail = shape_detail,
callsign = escape_attr(callsign),
stroke_color = stroke_color,
stroke_weight = stroke_weight,
stroke_style = escape_attr(stroke_style),
fill_color = fill_color,
milsym_detail = milsym_detail
)
}
fn ellipse_shape_detail(
uid: &str,
major: f64,
minor: f64,
angle: f32,
stroke_color: i32,
fill_color: i32,
stroke_weight: f64,
) -> String {
format!(
r#"<shape><ellipse major="{major}" minor="{minor}" angle="{angle}" /><link uid="{style_uid}" type="b-x-KmlStyle" relation="p-c"><Style><LineStyle><color>{stroke_hex}</color><width>{stroke_weight}</width></LineStyle><PolyStyle><color>{fill_hex}</color></PolyStyle></Style></link></shape>"#,
major = major,
minor = minor,
angle = angle,
style_uid = escape_attr(&format!("{}.Style", uid)),
stroke_hex = argb_hex(stroke_color),
stroke_weight = stroke_weight,
fill_hex = argb_hex(fill_color)
)
}
fn links_detail(points: &[DrawPoint], closed: bool) -> String {
let mut detail = String::new();
for point in points {
detail.push_str(&link_detail(point));
}
if closed {
if let Some(first) = points.first() {
detail.push_str(&link_detail(first));
}
}
detail
}
fn link_detail(point: &DrawPoint) -> String {
format!(
r#"<link point="{lat},{lon},{hae}" />"#,
lat = point.lat,
lon = point.lon,
hae = point.hae
)
}
fn parse_points(raw: &str) -> Vec<DrawPoint> {
raw.split(';')
.filter_map(|entry| {
let parts: Vec<_> = entry.split(',').collect();
if parts.len() != 3 {
return None;
}
Some(DrawPoint {
lat: parts[0].parse().ok()?,
lon: parts[1].parse().ok()?,
hae: parts[2].parse().ok()?,
})
})
.collect()
}
fn argb_hex(color: i32) -> String {
format!("{:08x}", color as u32)
}
fn escape_attr(value: &str) -> String {
value
.replace('&', "&amp;")
.replace('"', "&quot;")
.replace('<', "&lt;")
.replace('>', "&gt;")
}

View File

@@ -19,6 +19,16 @@ pub fn send_marker_cot(
"Sending Marker Cursor Over Time to TCP server"
}
pub fn send_report_marker_cot(
ctx: Context,
cursor_over_time: cot::report_marker::ReportMarkerCoTPayload,
) -> &'static str {
let payload = cursor_over_time.to_cot().convert_to_xml();
send_payload(ctx, payload);
"Sending Report Marker Cursor Over Time to TCP server"
}
pub fn send_digital_pointer_cot(
ctx: Context,
cursor_over_time: cot::digital_pointer::DigitalPointerPayload,

View File

@@ -2,36 +2,72 @@ use arma_rs::Context;
use crate::{cot, tcp::send_payload};
fn day_stale() -> (String, String) {
let now = chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Millis, true);
let stale = (chrono::Utc::now() + chrono::Duration::days(1))
.to_rfc3339_opts(chrono::SecondsFormat::Millis, true);
(now, stale)
}
fn payload_stale(stale_seconds: i64) -> (String, String) {
let now = chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Millis, true);
let stale = (chrono::Utc::now() + chrono::Duration::seconds(stale_seconds.max(1)))
.to_rfc3339_opts(chrono::SecondsFormat::Millis, true);
(now, stale)
}
pub fn send_circle_cot(
ctx: Context,
circle_payload: cot::draws::circle::CircleCoTPayload,
) -> &'static str {
let shape_circle_cot = circle_payload.to_cot();
let now = chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Millis, true);
let stale = (chrono::Utc::now() + chrono::Duration::days(1))
.to_rfc3339_opts(chrono::SecondsFormat::Millis, true);
let (now, stale) = day_stale();
let payload = shape_circle_cot.to_xml(&now, &stale);
send_payload(ctx, payload);
"Sending Circle CoT to TCP server"
}
pub fn send_ellipse_cot(ctx: Context) -> &'static str {
let _ = ctx;
"Not implemented: send_ellipse_cot"
pub fn send_ellipse_cot(
ctx: Context,
ellipse_payload: cot::draws::shape::DrawEllipsePayload,
) -> &'static str {
let (now, stale) = payload_stale(ellipse_payload.stale_seconds);
let payload = ellipse_payload.to_xml(&now, &stale);
send_payload(ctx, payload);
"Sending Ellipse CoT to TCP server"
}
pub fn send_rectangle_cot(ctx: Context) -> &'static str {
let _ = ctx;
"Not implemented: send_ellipse_cot"
pub fn send_rectangle_cot(
ctx: Context,
rectangle_payload: cot::draws::shape::DrawLinksPayload,
) -> &'static str {
let (now, stale) = payload_stale(rectangle_payload.stale_seconds);
let payload = rectangle_payload.to_xml(&now, &stale);
send_payload(ctx, payload);
"Sending Rectangle CoT to TCP server"
}
pub fn send_freedraw_cot(ctx: Context) -> &'static str {
let _ = ctx;
"Not implemented: send_ellipse_cot"
pub fn send_freedraw_cot(
ctx: Context,
freedraw_payload: cot::draws::shape::DrawLinksPayload,
) -> &'static str {
let (now, stale) = payload_stale(freedraw_payload.stale_seconds);
let payload = freedraw_payload.to_xml(&now, &stale);
send_payload(ctx, payload);
"Sending Free Draw CoT to TCP server"
}
pub fn send_vectordraw_cot(ctx: Context) -> &'static str {
let _ = ctx;
"Not implemented: send_ellipse_cot"
pub fn send_vectordraw_cot(
ctx: Context,
vector_payload: cot::draws::shape::DrawLinksPayload,
) -> &'static str {
let (now, stale) = payload_stale(vector_payload.stale_seconds);
let payload = vector_payload.to_xml(&now, &stale);
send_payload(ctx, payload);
"Sending Tactical Graphic CoT to TCP server"
}