From 5015f09d1db4afbfbeed42a27b3611c0ce957e2e Mon Sep 17 00:00:00 2001 From: Valmo Trindade Date: Mon, 11 May 2026 16:26:17 -0300 Subject: [PATCH] [WIP] Adding Drawing Cursor Over Time functions --- addons/main/CfgFunctions.hpp | 18 + addons/main/functions/api/fn_draw_circle.sqf | 41 +++ addons/main/functions/api/fn_draw_ellipse.sqf | 65 ++++ .../main/functions/api/fn_draw_polyline.sqf | 78 +++++ .../main/functions/api/fn_draw_rectangle.sqf | 89 +++++ .../api/fn_draw_tactical_graphic.sqf | 75 +++++ src/cot/draws/mod.rs | 1 + src/cot/draws/shape.rs | 317 ++++++++++++++++++ src/tcp/cot.rs | 10 + src/tcp/draw.rs | 66 +++- 10 files changed, 745 insertions(+), 15 deletions(-) create mode 100644 addons/main/functions/api/fn_draw_circle.sqf create mode 100644 addons/main/functions/api/fn_draw_ellipse.sqf create mode 100644 addons/main/functions/api/fn_draw_polyline.sqf create mode 100644 addons/main/functions/api/fn_draw_rectangle.sqf create mode 100644 addons/main/functions/api/fn_draw_tactical_graphic.sqf create mode 100644 src/cot/draws/shape.rs diff --git a/addons/main/CfgFunctions.hpp b/addons/main/CfgFunctions.hpp index 76c8591..2ae223c 100644 --- a/addons/main/CfgFunctions.hpp +++ b/addons/main/CfgFunctions.hpp @@ -19,6 +19,24 @@ class CfgFunctions { class send_marker_cot { file = "\armatak\armatak\addons\main\functions\api\fn_send_marker_cot.sqf"; }; + class report_marker { + file = "\armatak\armatak\addons\main\functions\api\fn_report_marker.sqf"; + }; + class draw_circle { + file = "\armatak\armatak\addons\main\functions\api\fn_draw_circle.sqf"; + }; + class draw_ellipse { + file = "\armatak\armatak\addons\main\functions\api\fn_draw_ellipse.sqf"; + }; + class draw_rectangle { + file = "\armatak\armatak\addons\main\functions\api\fn_draw_rectangle.sqf"; + }; + class draw_polyline { + file = "\armatak\armatak\addons\main\functions\api\fn_draw_polyline.sqf"; + }; + class draw_tactical_graphic { + file = "\armatak\armatak\addons\main\functions\api\fn_draw_tactical_graphic.sqf"; + }; class send_uas_platform_cot { file = "\armatak\armatak\addons\main\functions\api\fn_send_uas_platform_cot.sqf"; }; diff --git a/addons/main/functions/api/fn_draw_circle.sqf b/addons/main/functions/api/fn_draw_circle.sqf new file mode 100644 index 0000000..fb21c60 --- /dev/null +++ b/addons/main/functions/api/fn_draw_circle.sqf @@ -0,0 +1,41 @@ +// function name: armatak_fnc_draw_circle +// function author: Valmo +// function description: Sends an ATAK Drawing Tools circle CoT. +// +// Arguments: +// 0: Center position or object +// 1: Radius in meters +// 2: Callsign/title (default: "ArmaTAK Circle") +// 3: Stale time in seconds (default: 86400) +// 4: Stroke color as signed ARGB int (default: -1) +// 5: Fill color as signed ARGB int (default: -1761607681) +// 6: Stroke weight (default: 3) +// +// Example: +// [player, 300, "Mortar Risk Area"] call armatak_fnc_draw_circle; +// +// Public: Yes + +params [ + ["_center", objNull, [objNull, []]], + ["_radius", 100, [0]], + ["_callsign", "ArmaTAK Circle", [""]], + ["_staleSeconds", 86400, [0]], + ["_strokeColor", -1, [0]], + ["_fillColor", -1761607681, [0]], + ["_strokeWeight", 3, [0]] +]; + +[ + _center, + _radius, + _radius, + 360, + _callsign, + _staleSeconds, + _strokeColor, + _fillColor, + _strokeWeight, + "", + "u-d-c-c" +] call armatak_fnc_draw_ellipse; diff --git a/addons/main/functions/api/fn_draw_ellipse.sqf b/addons/main/functions/api/fn_draw_ellipse.sqf new file mode 100644 index 0000000..155d8f8 --- /dev/null +++ b/addons/main/functions/api/fn_draw_ellipse.sqf @@ -0,0 +1,65 @@ +// function name: armatak_fnc_draw_ellipse +// function author: Valmo +// function description: Sends an ATAK Drawing Tools ellipse or circle CoT. +// +// Arguments: +// 0: Center position or object +// 1: Major radius in meters +// 2: Minor radius in meters +// 3: Rotation angle in degrees (default: 0) +// 4: Callsign/title (default: "ArmaTAK Ellipse") +// 5: Stale time in seconds (default: 86400) +// 6: Stroke color as signed ARGB int (default: -1) +// 7: Fill color as signed ARGB int (default: -1761607681) +// 8: Stroke weight (default: 3) +// 9: MilSym SIDC for tactical overlay (default: "") +// 10: CoT type (default: "u-d-c-e") +// +// Example: +// [screenToWorld [0.5, 0.5], 250, 100, 45, "Support by Fire"] call armatak_fnc_draw_ellipse; +// +// Public: Yes + +params [ + ["_center", objNull, [objNull, []]], + ["_major", 100, [0]], + ["_minor", 50, [0]], + ["_angle", 0, [0]], + ["_callsign", "ArmaTAK Ellipse", [""]], + ["_staleSeconds", 86400, [0]], + ["_strokeColor", -1, [0]], + ["_fillColor", -1761607681, [0]], + ["_strokeWeight", 3, [0]], + ["_milsym", "", [""]], + ["_cotType", "u-d-c-e", [""]] +]; + +private _position = if (_center isEqualType objNull) then { + getPos _center +} else { + _center +}; + +if ((count _position) < 2) exitWith {""}; + +private _altitude = _position param [2, 0, [0]]; +private _realLocation = [_position select 0, _position select 1, _altitude] call armatak_client_fnc_convertClientLocation; +private _uuid = "armatak" callExtension ["uuid", []] select 0; +private _payload = [ + _uuid, + _cotType, + _realLocation select 0, + _realLocation select 1, + _realLocation select 2, + _major max 1, + _minor max 1, + _angle, + _callsign, + _staleSeconds max 1, + _strokeColor, + _fillColor, + _strokeWeight max 1, + _milsym +]; + +"armatak" callExtension ["tcp_socket:draw:ellipse", [_payload]]; diff --git a/addons/main/functions/api/fn_draw_polyline.sqf b/addons/main/functions/api/fn_draw_polyline.sqf new file mode 100644 index 0000000..903008f --- /dev/null +++ b/addons/main/functions/api/fn_draw_polyline.sqf @@ -0,0 +1,78 @@ +// function name: armatak_fnc_draw_polyline +// function author: Valmo +// function description: Sends an ATAK Drawing Tools freeform line or polygon CoT. +// +// Arguments: +// 0: Positions or objects +// 1: Callsign/title (default: "ArmaTAK Line") +// 2: Closed polygon (default: false) +// 3: Stale time in seconds (default: 86400) +// 4: Stroke color as signed ARGB int (default: -1) +// 5: Fill color as signed ARGB int (default: -1761607681) +// 6: Stroke weight (default: 3) +// 7: Stroke style (default: "solid") +// 8: MilSym SIDC for tactical overlay (default: "") +// 9: CoT type (default: "u-d-f") +// +// Example: +// [[pos player, screenToWorld [0.5, 0.5]], "Phase Line Blue"] call armatak_fnc_draw_polyline; +// +// Public: Yes + +params [ + ["_points", [], [[]]], + ["_callsign", "ArmaTAK Line", [""]], + ["_closed", false, [true]], + ["_staleSeconds", 86400, [0]], + ["_strokeColor", -1, [0]], + ["_fillColor", -1761607681, [0]], + ["_strokeWeight", 3, [0]], + ["_strokeStyle", "solid", [""]], + ["_milsym", "", [""]], + ["_cotType", "u-d-f", [""]] +]; + +if ((count _points) < 2) exitWith {""}; + +private _pointStrings = []; +private _center = []; + +{ + private _position = if (_x isEqualType objNull) then { + getPos _x + } else { + _x + }; + + if ((count _position) >= 2) then { + private _altitude = _position param [2, 0, [0]]; + private _realLocation = [_position select 0, _position select 1, _altitude] call armatak_client_fnc_convertClientLocation; + _pointStrings pushBack format ["%1,%2,%3", _realLocation select 0, _realLocation select 1, _realLocation select 2]; + + if (_center isEqualTo []) then { + _center = _realLocation; + }; + }; +} forEach _points; + +if ((count _pointStrings) < 2) exitWith {""}; + +private _uuid = "armatak" callExtension ["uuid", []] select 0; +private _payload = [ + _uuid, + _cotType, + _center select 0, + _center select 1, + _center select 2, + _pointStrings joinString ";", + _callsign, + _staleSeconds max 1, + _strokeColor, + _fillColor, + _strokeWeight max 1, + _strokeStyle, + _closed, + _milsym +]; + +"armatak" callExtension ["tcp_socket:draw:free", [_payload]]; diff --git a/addons/main/functions/api/fn_draw_rectangle.sqf b/addons/main/functions/api/fn_draw_rectangle.sqf new file mode 100644 index 0000000..0661b90 --- /dev/null +++ b/addons/main/functions/api/fn_draw_rectangle.sqf @@ -0,0 +1,89 @@ +// function name: armatak_fnc_draw_rectangle +// function author: Valmo +// function description: Sends an ATAK Drawing Tools rectangle CoT from an Arma center, width, length, and bearing. +// +// Arguments: +// 0: Center position or object +// 1: Width in meters +// 2: Length in meters +// 3: Bearing in degrees (default: 0) +// 4: Callsign/title (default: "ArmaTAK Rectangle") +// 5: Stale time in seconds (default: 86400) +// 6: Stroke color as signed ARGB int (default: -1) +// 7: Fill color as signed ARGB int (default: -1761607681) +// 8: Stroke weight (default: 3) +// 9: MilSym SIDC for tactical overlay (default: "") +// +// Example: +// [screenToWorld [0.5, 0.5], 200, 500, 30, "Engagement Area"] call armatak_fnc_draw_rectangle; +// +// Public: Yes + +params [ + ["_center", objNull, [objNull, []]], + ["_width", 100, [0]], + ["_length", 100, [0]], + ["_bearing", 0, [0]], + ["_callsign", "ArmaTAK Rectangle", [""]], + ["_staleSeconds", 86400, [0]], + ["_strokeColor", -1, [0]], + ["_fillColor", -1761607681, [0]], + ["_strokeWeight", 3, [0]], + ["_milsym", "", [""]] +]; + +private _centerPos = if (_center isEqualType objNull) then { + getPos _center +} else { + _center +}; + +if ((count _centerPos) < 2) exitWith {""}; + +private _altitude = _centerPos param [2, 0, [0]]; +private _halfWidth = (_width max 1) / 2; +private _halfLength = (_length max 1) / 2; +private _sin = sin _bearing; +private _cos = cos _bearing; +private _forward = [_sin, _cos, 0]; +private _right = [_cos, -_sin, 0]; +private _offsets = [ + [(_right vectorMultiply -_halfWidth), (_forward vectorMultiply _halfLength)], + [(_right vectorMultiply _halfWidth), (_forward vectorMultiply _halfLength)], + [(_right vectorMultiply _halfWidth), (_forward vectorMultiply -_halfLength)], + [(_right vectorMultiply -_halfWidth), (_forward vectorMultiply -_halfLength)] +]; +private _points = []; + +{ + private _offset = (_x select 0) vectorAdd (_x select 1); + _points pushBack ([_centerPos select 0, _centerPos select 1, _altitude] vectorAdd _offset); +} forEach _offsets; + +private _centerReal = [_centerPos select 0, _centerPos select 1, _altitude] call armatak_client_fnc_convertClientLocation; +private _pointStrings = []; + +{ + private _realLocation = [_x select 0, _x select 1, _x select 2] call armatak_client_fnc_convertClientLocation; + _pointStrings pushBack format ["%1,%2,%3", _realLocation select 0, _realLocation select 1, _realLocation select 2]; +} forEach _points; + +private _uuid = "armatak" callExtension ["uuid", []] select 0; +private _payload = [ + _uuid, + "u-d-r", + _centerReal select 0, + _centerReal select 1, + _centerReal select 2, + _pointStrings joinString ";", + _callsign, + _staleSeconds max 1, + _strokeColor, + _fillColor, + _strokeWeight max 1, + "solid", + false, + _milsym +]; + +"armatak" callExtension ["tcp_socket:draw:rectangle", [_payload]]; diff --git a/addons/main/functions/api/fn_draw_tactical_graphic.sqf b/addons/main/functions/api/fn_draw_tactical_graphic.sqf new file mode 100644 index 0000000..aef8e64 --- /dev/null +++ b/addons/main/functions/api/fn_draw_tactical_graphic.sqf @@ -0,0 +1,75 @@ +// function name: armatak_fnc_draw_tactical_graphic +// function author: Valmo +// function description: Sends an ATAK Drawing Tools shape with a MilSym tactical graphic overlay. +// +// Arguments: +// 0: Positions or objects +// 1: MilSym SIDC +// 2: Callsign/title (default: "ArmaTAK Tactical Graphic") +// 3: Closed polygon (default: false) +// 4: Stale time in seconds (default: 86400) +// 5: Stroke color as signed ARGB int (default: -1) +// 6: Fill color as signed ARGB int (default: -1761607681) +// 7: Stroke weight (default: 3) +// +// Example: +// [[pos player, screenToWorld [0.5, 0.5]], "GFGPOLAGM-----X", "Axis of Advance"] call armatak_fnc_draw_tactical_graphic; +// +// Public: Yes + +params [ + ["_points", [], [[]]], + ["_milsym", "", [""]], + ["_callsign", "ArmaTAK Tactical Graphic", [""]], + ["_closed", false, [true]], + ["_staleSeconds", 86400, [0]], + ["_strokeColor", -1, [0]], + ["_fillColor", -1761607681, [0]], + ["_strokeWeight", 3, [0]] +]; + +if (_milsym isEqualTo "") exitWith {""}; +if ((count _points) < 2) exitWith {""}; + +private _pointStrings = []; +private _center = []; + +{ + private _position = if (_x isEqualType objNull) then { + getPos _x + } else { + _x + }; + + if ((count _position) >= 2) then { + private _altitude = _position param [2, 0, [0]]; + private _realLocation = [_position select 0, _position select 1, _altitude] call armatak_client_fnc_convertClientLocation; + _pointStrings pushBack format ["%1,%2,%3", _realLocation select 0, _realLocation select 1, _realLocation select 2]; + + if (_center isEqualTo []) then { + _center = _realLocation; + }; + }; +} forEach _points; + +if ((count _pointStrings) < 2) exitWith {""}; + +private _uuid = "armatak" callExtension ["uuid", []] select 0; +private _payload = [ + _uuid, + "u-d-f", + _center select 0, + _center select 1, + _center select 2, + _pointStrings joinString ";", + _callsign, + _staleSeconds max 1, + _strokeColor, + _fillColor, + _strokeWeight max 1, + "solid", + _closed, + _milsym +]; + +"armatak" callExtension ["tcp_socket:draw:vector", [_payload]]; diff --git a/src/cot/draws/mod.rs b/src/cot/draws/mod.rs index 0241c06..95fc753 100644 --- a/src/cot/draws/mod.rs +++ b/src/cot/draws/mod.rs @@ -1 +1,2 @@ pub mod circle; +pub mod shape; diff --git a/src/cot/draws/shape.rs b/src/cot/draws/shape.rs new file mode 100644 index 0000000..b0bb37c --- /dev/null +++ b/src/cot/draws/shape.rs @@ -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 { + 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 { + 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#"{shape_detail}<__shapeExtras cpvis="true" editable="true" />{milsym_detail}"#, + 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#""#, + 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#""#, + lat = point.lat, + lon = point.lon, + hae = point.hae + ) +} + +fn parse_points(raw: &str) -> Vec { + 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('&', "&") + .replace('"', """) + .replace('<', "<") + .replace('>', ">") +} diff --git a/src/tcp/cot.rs b/src/tcp/cot.rs index 935fc6c..5ffc69b 100644 --- a/src/tcp/cot.rs +++ b/src/tcp/cot.rs @@ -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, diff --git a/src/tcp/draw.rs b/src/tcp/draw.rs index c52fb2c..7555871 100644 --- a/src/tcp/draw.rs +++ b/src/tcp/draw.rs @@ -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" }