// function name: armatak_fnc_draw_route // function author: Valmo // function description: Sends an ATAK navigable route CoT. // // Arguments: // 0: Positions or objects // 1: Callsign/title (default: "ArmaTAK Route") // 2: Stale time in seconds (default: 86400) // 3: Color as signed ARGB int (default: -1) // 4: Stroke weight (default: 3) // 5: Navigation method (default: "Driving") // 6: Route type (default: "Primary") // 7: Direction/planning method (default: "Infil") // 8: Checkpoint interval among route points (default: 5) // 9: Optional registration scope (default: "") // // Example: // [[pos player, screenToWorld [0.5, 0.5]], "Patrol Route"] call armatak_fnc_draw_route; // // Public: Yes params [ ["_points", [], [[]]], ["_callsign", "ArmaTAK Route", [""]], ["_staleSeconds", 86400, [0]], ["_color", -1, [0]], ["_strokeWeight", 3, [0]], ["_method", "Driving", [""]], ["_routeType", "Primary", [""]], ["_direction", "Infil", [""]], ["_checkpointInterval", 5, [0]], ["_scope", "", [""]] ]; if ((count _points) < 2) exitWith {""}; private _pointStrings = []; { 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]; }; } forEach _points; if ((count _pointStrings) < 2) exitWith {""}; private _uuid = "armatak" callExtension ["uuid", []] select 0; private _pad = { params ["_value", "_digits"]; private _text = str (floor _value); while {(count _text) < _digits} do { _text = "0" + _text; }; _text }; private _formatCotTime = { params ["_dateParts"]; format [ "%1-%2-%3T%4:%5:%6.%7Z", _dateParts param [0, 1970, [0]], [_dateParts param [1, 1, [0]], 2] call _pad, [_dateParts param [2, 1, [0]], 2] call _pad, [_dateParts param [3, 0, [0]], 2] call _pad, [_dateParts param [4, 0, [0]], 2] call _pad, [_dateParts param [5, 0, [0]], 2] call _pad, [_dateParts param [6, 0, [0]], 3] call _pad ] }; private _addSecondsUtc = { params ["_dateParts", "_secondsToAdd"]; private _year = _dateParts param [0, 1970, [0]]; private _month = _dateParts param [1, 1, [0]]; private _day = _dateParts param [2, 1, [0]]; private _hour = _dateParts param [3, 0, [0]]; private _minute = _dateParts param [4, 0, [0]]; private _second = (_dateParts param [5, 0, [0]]) + (floor _secondsToAdd); private _millisecond = _dateParts param [6, 0, [0]]; while {_second >= 60} do { _second = _second - 60; _minute = _minute + 1; }; while {_minute >= 60} do { _minute = _minute - 60; _hour = _hour + 1; }; while {_hour >= 24} do { _hour = _hour - 24; _day = _day + 1; private _daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; private _isLeapYear = ((_year mod 4) isEqualTo 0) && {((_year mod 100) isNotEqualTo 0) || {(_year mod 400) isEqualTo 0}}; if (_isLeapYear) then {_daysInMonth set [1, 29];}; if (_day > (_daysInMonth select (_month - 1))) then { _day = 1; _month = _month + 1; if (_month > 12) then { _month = 1; _year = _year + 1; }; }; }; [_year, _month, _day, _hour, _minute, _second, _millisecond] }; private _nowDate = systemTimeUTC; private _now = [_nowDate] call _formatCotTime; private _stale = [[_nowDate, _staleSeconds max 60] call _addSecondsUtc] call _formatCotTime; private _links = ""; private _routeWaypoints = []; private _lastIndex = (count _pointStrings) - 1; private _checkpointNumber = 1; private _checkpointIntervalSafe = _checkpointInterval max 1; { private _linkUuid = "armatak" callExtension ["uuid", []] select 0; private _pointCallsign = ""; if (_forEachIndex isEqualTo 0) then { _pointCallsign = format ["%1 SP", _callsign]; } else { if (_forEachIndex isEqualTo _lastIndex) then { _pointCallsign = "VDO"; } else { if ((_forEachIndex mod _checkpointIntervalSafe) isEqualTo 0) then { _pointCallsign = format ["CP%1", _checkpointNumber]; _checkpointNumber = _checkpointNumber + 1; }; }; }; private _linkType = ["b-m-p-w", "b-m-p-c"] select (_pointCallsign isEqualTo ""); _links = _links + format [ "", _linkUuid, _pointCallsign, _linkType, _x ]; if (_pointCallsign isNotEqualTo "") then { _routeWaypoints pushBack [_linkUuid, _pointCallsign, _x]; }; } forEach _pointStrings; private _xml = format [ "%4<__routeinfo><__navcues/>1", _uuid, _now, _stale, _links, _direction, _color, _method, _strokeWeight max 1, _routeType, _callsign ]; "armatak" callExtension ["log", [["info", format ["Sending ATAK route '%1' with %2 points (%3 bytes)", _callsign, count _pointStrings, count _xml]]]]; "armatak" callExtension ["tcp_socket:send_payload", [_xml]]; [_scope, _uuid, "b-m-r", 0, 0, 9999999] call armatak_fnc_register_cot; { _x params ["_waypointUid", "_waypointCallsign", "_pointString"]; private _pointParts = _pointString splitString ","; if ((count _pointParts) >= 3) then { private _waypointXml = format [ "", _waypointUid, _now, _stale, _pointParts select 0, _pointParts select 1, _pointParts select 2, _waypointCallsign, _uuid ]; "armatak" callExtension ["tcp_socket:send_payload", [_waypointXml]]; [_scope, _waypointUid, "b-m-p-w", parseNumber (_pointParts select 0), parseNumber (_pointParts select 1), parseNumber (_pointParts select 2)] call armatak_fnc_register_cot; }; } forEach _routeWaypoints; _uuid