├── .gitignore ├── LICENSE ├── README.md ├── ftssim ├── __init__.py ├── _common.py ├── gpx.py └── wander.py ├── gpx_example.py ├── requirements.txt └── wander_example.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | takpak/ 30 | .idea/ 31 | *.gpx 32 | # PyInstaller 33 | # Usually these files are written by a python script from a template 34 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 35 | *.manifest 36 | *.spec 37 | 38 | # Installer logs 39 | pip-log.txt 40 | pip-delete-this-directory.txt 41 | 42 | # Unit test / coverage reports 43 | htmlcov/ 44 | .tox/ 45 | .nox/ 46 | .coverage 47 | .coverage.* 48 | .cache 49 | nosetests.xml 50 | coverage.xml 51 | *.cover 52 | *.py,cover 53 | .hypothesis/ 54 | .pytest_cache/ 55 | 56 | # Translations 57 | *.mo 58 | *.pot 59 | 60 | # Django stuff: 61 | *.log 62 | local_settings.py 63 | db.sqlite3 64 | db.sqlite3-journal 65 | 66 | # Flask stuff: 67 | instance/ 68 | .webassets-cache 69 | 70 | # Scrapy stuff: 71 | .scrapy 72 | 73 | # Sphinx documentation 74 | docs/_build/ 75 | 76 | # PyBuilder 77 | target/ 78 | 79 | # Jupyter Notebook 80 | .ipynb_checkpoints 81 | 82 | # IPython 83 | profile_default/ 84 | ipython_config.py 85 | 86 | # pyenv 87 | .python-version 88 | 89 | # pipenv 90 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 91 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 92 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 93 | # install all needed dependencies. 94 | #Pipfile.lock 95 | 96 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 97 | __pypackages__/ 98 | 99 | # Celery stuff 100 | celerybeat-schedule 101 | celerybeat.pid 102 | 103 | # SageMath parsed files 104 | *.sage.py 105 | 106 | # Environments 107 | .env 108 | .venv 109 | env/ 110 | venv/ 111 | ENV/ 112 | env.bak/ 113 | venv.bak/ 114 | 115 | # Spyder project settings 116 | .spyderproject 117 | .spyproject 118 | 119 | # Rope project settings 120 | .ropeproject 121 | 122 | # mkdocs documentation 123 | /site 124 | 125 | # mypy 126 | .mypy_cache/ 127 | .dmypy.json 128 | dmypy.json 129 | 130 | # Pyre type checker 131 | .pyre/ 132 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 LT 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FreeTAKServer-Simulator 2 | Tool for Simulating moving objects in ATAK 3 | 4 | ### GPX 5 | This tool currently will take a gpx file you have generated of a route and "play" it into ATAK. 6 | Taking the data in from the gpx file and filling in gaps where there may not be any location data between 2 points to allow 7 | for fluid movement in ATAK at a given speed. 8 | 9 | You can also use the GPX class to: 10 | - Mimic a crowd/group following the same gpx file (all offset slightly to look realistic) 11 | 12 | #### Example 13 | ```python 14 | from ftssim import gpx 15 | 16 | player = gpx.GpxPlayer('192.168.3.2', 'test_file.gpx', "A1_Walk", speed_kph=5, max_time_step_secs=4) 17 | player.play_gpx() 18 | ``` 19 | 20 | 21 | ### Wandering 22 | Given a starting point and a few extra parameters this tool will make an object wander aimlessly 23 | in straight lines then change direction (if you want to have an object just head in one straight line 24 | set total_distance and distance_between_change to the same number) 25 | 26 | You can also use the Wonder class to make an object: 27 | - Loiter in the area (while not remain static) 28 | - Circle a point 29 | 30 | #### Example 31 | ```python 32 | from ftssim import wander 33 | 34 | wanderer = wander.Wander('192.168.3.2', total_distance_km=2, distance_between_change=1, callsign="lost_soul", speed_kph=5, 35 | max_time_step_secs=4, start_lat=38.897125, start_lon=-77.036255) 36 | # Wonder 37 | wanderer.start_wandering() 38 | 39 | # Loiter in an the area for 5 minutes 40 | wanderer.loiter_for_time(300) 41 | 42 | # Circle anti-clockwise around the point in a 50 meter radius 43 | wanderer.circle_point(50, clockwise=False) 44 | ``` 45 | 46 | 47 | Refer to gpx_example.py and wander_example.py for examples of running concurrently 48 | 49 | 50 | For an example, go [here](https://github.com/lennisthemenace/FreeTAKServer-Simulator-UI-Example) 51 | -------------------------------------------------------------------------------- /ftssim/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FreeTAKTeam/FreeTAKServer-Simulator/756915719926e114532fdc32cc1e774bba640a86/ftssim/__init__.py -------------------------------------------------------------------------------- /ftssim/_common.py: -------------------------------------------------------------------------------- 1 | import geopy.distance 2 | from typing import Tuple, Callable 3 | from takpak.takcot import takcot 4 | from takpak.mkcot import mkcot 5 | import random 6 | import time 7 | 8 | 9 | def move_from_location(lat: float, lon: float, distance_km: float, bearing: float) -> Tuple[float, float]: 10 | """ 11 | Get the coordinates at a given distance away in a set bearing 12 | 13 | Parameters 14 | ---------- 15 | lat : float 16 | starting latitude 17 | lon : float 18 | starting latitude 19 | distance_km : float 20 | distance to move in kilometers (1 and above) 21 | bearing : float 22 | the bearing to head in (0-360) 23 | 24 | Returns 25 | ------- 26 | Tuple[float, float] 27 | 28 | """ 29 | start_point = geopy.Point(lat, lon) 30 | d = geopy.distance.distance(kilometers=distance_km) 31 | end_point = d.destination(point=start_point, bearing=bearing) 32 | 33 | return end_point.latitude, end_point.longitude 34 | 35 | 36 | def move_from_location_in_random_direction(lat: float, lon: float, distance_km: float) -> Tuple[float, float]: 37 | """ 38 | Get the coordinates at a given distance away in a random bearing 39 | 40 | Parameters 41 | ---------- 42 | lat : float 43 | starting latitude 44 | lon : float 45 | starting latitude 46 | distance_km : float 47 | distance to move in kilometers (1 and above) 48 | 49 | Returns 50 | ------- 51 | Tuple[float, float] 52 | 53 | """ 54 | bearing = round(random.uniform(0, 360), 1) 55 | new_lat, new_lon = move_from_location(lat, lon, distance_km, bearing) 56 | 57 | return new_lat, new_lon 58 | 59 | 60 | def get_midway_coords(lat1: float, lon1: float, lat2: float, lon2: float) -> Tuple[float, float]: 61 | """ 62 | Get the coordinates half way between to coordinates 63 | 64 | Parameters 65 | ---------- 66 | lat1 : float 67 | first latitude 68 | lat2 : float 69 | second latitude 70 | lon1 : float 71 | first longitude 72 | lon2 : float 73 | second longitude 74 | 75 | Returns 76 | ------- 77 | Tuple[float, float] 78 | 79 | """ 80 | midlat = (lat1 + lat2) / 2 81 | midlong = (lon1 + lon2) / 2 82 | 83 | return midlat, midlong 84 | 85 | 86 | def generate_smooth_route(points: list, speed_kph: int, max_time_step_secs: int) -> Tuple[list, list]: 87 | """ 88 | Takes a list of coordinates and adds more points to the list to create a smooth journey 89 | 90 | Parameters 91 | ---------- 92 | points : list 93 | list of coordinate points 94 | speed_kph : int 95 | the speed the object needs to travel at 96 | max_time_step_secs : int 97 | the maximum gap between calling in in seconds 98 | 99 | Returns 100 | ------- 101 | Tuple[list, list] 102 | 103 | """ 104 | waits = [] 105 | pnt = 0 106 | while pnt < len(points) - 1: 107 | pnt_1 = pnt + 1 108 | dst = geopy.distance.distance(points[pnt], points[pnt_1]).km 109 | time_seconds = (dst / speed_kph) * 60 * 60 110 | if time_seconds > max_time_step_secs: 111 | points.insert(pnt_1, 112 | get_midway_coords(points[pnt][0], points[pnt][1], points[pnt_1][0], points[pnt_1][1])) 113 | else: 114 | waits.insert(pnt, time_seconds) 115 | pnt += 1 116 | 117 | return points, waits 118 | 119 | 120 | def iterate_and_send(points: list, waits: list, tak_server: str, tak_port: int, cot_type: str, 121 | callsign: str, uid: str, cot_identity: str, cot_dimension: str, cot_stale: int): 122 | """ 123 | Iterates through list of coordinate points and wait times and sends them to a TAKServer 124 | 125 | Parameters 126 | ---------- 127 | points : list 128 | list of coordinate points 129 | waits : list 130 | list of times in seconds to wait between locations 131 | tak_server : str 132 | the address of the target TAKServer 133 | tak_port : int 134 | the port for the target TAKServer 135 | cot_type : str 136 | cot type string e.g a-f-G-U-C 137 | cot_identity : str 138 | Cot identity e.g friend 139 | cot_dimension : str 140 | Cot dimension e.g land-unit 141 | cot_stale : int 142 | Time in minuets for the object to become stale in ATAK 143 | callsign : str 144 | the callsign of the object 145 | uid : str 146 | the uid for the object 147 | """ 148 | takserver = takcot() 149 | takserver.open(tak_server, tak_port) 150 | locator = 0 151 | takserver.flush() 152 | for location in points: 153 | takserver.send(mkcot.mkcot(cot_identity=str(cot_identity), 154 | cot_stale=int(cot_stale), 155 | cot_dimension=str(cot_dimension), 156 | cot_typesuffix=str(cot_type), 157 | cot_callsign=str(callsign), 158 | cot_id=str(uid), 159 | cot_lat=round(location[0], 5), cot_lon=round(location[1], 5))) 160 | takserver.flush() 161 | try: 162 | time.sleep(waits[locator]) 163 | except IndexError: 164 | break 165 | locator += 1 166 | takserver.close() 167 | 168 | 169 | def offset_route(points: list, distance_km: float) -> list: 170 | """ 171 | Iterates through list of coordinate points and wait times and sends them to a TAKServer 172 | 173 | Parameters 174 | ---------- 175 | points : list 176 | list of coordinate points 177 | distance_km : float 178 | distance in km to offset a route by 179 | 180 | Returns 181 | ------- 182 | List 183 | 184 | """ 185 | new_points = [] 186 | bearing = round(random.uniform(0, 360), 1) 187 | pnt = 0 188 | for point in points: 189 | new_lat, new_lon = move_from_location(point[0], point[1], distance_km, bearing) 190 | new_points.insert(pnt, (new_lat, new_lon)) 191 | pnt += 1 192 | return new_points 193 | 194 | 195 | def iterate_wrapper(points, waits, tak_server, tak_port, cot_type, callsign, uid, cot_identity, cot_dimension, 196 | cot_stale) -> Callable: 197 | """ 198 | Iterates through list of coordinate points and wait times and sends them to a TAKServer 199 | 200 | Parameters 201 | ---------- 202 | points : list 203 | list of coordinate points 204 | waits : list 205 | list of times in seconds to wait between locations 206 | tak_server : str 207 | the address of the target TAKServer 208 | tak_port : int 209 | the port for the target TAKServer 210 | cot_type : str 211 | cot type string e.g a-f-G-U-C 212 | callsign : str 213 | the callsign of the object 214 | uid : str 215 | the uid for the object 216 | cot_identity : str 217 | Cot identity e.g friend 218 | cot_dimension : str 219 | Cot dimension e.g land-unit 220 | cot_stale : int 221 | Time in minuets for the object to become stale in ATAK 222 | 223 | 224 | Returns 225 | ------- 226 | Callable 227 | 228 | """ 229 | def wrapper(): 230 | iterate_and_send(points, waits, tak_server, tak_port, cot_type, callsign, uid, cot_identity, cot_dimension, 231 | cot_stale) 232 | return wrapper 233 | -------------------------------------------------------------------------------- /ftssim/gpx.py: -------------------------------------------------------------------------------- 1 | import gpxpy.gpx 2 | import uuid 3 | from typing import Tuple 4 | from ftssim import _common 5 | from threading import Thread 6 | 7 | 8 | class GpxPlayer: 9 | def __init__(self, tak_server: str, filename: str, callsign: str, tak_port: int = 8087, speed_kph: int = 5, 10 | max_time_step_secs: int = 4, cot_identity: str = "friend", cot_dimension: str = "land-unit", 11 | cot_stale: int = 1, cot_type: str = "a-f-G-U-C", repeated_objects: int = 1): 12 | """ 13 | Constructs all the necessary attributes for the gpx object. 14 | 15 | Parameters 16 | ---------- 17 | tak_server : str 18 | address for the tak server to set CoT to 19 | filename : str 20 | filename/path to gpx file to play 21 | callsign : str 22 | callsign for user in ATAK 23 | tak_port : int 24 | port that takserver is listening on 25 | speed_kph : int 26 | speed the gpx will play back at in kph 27 | max_time_step_secs : int 28 | max time in seconds allows for a gap between CoT messages (the smaller the number the more 29 | fluid the movement) 30 | cot_identity : str 31 | Cot identity e.g friend 32 | cot_dimension : str 33 | Cot dimension e.g land-unit 34 | cot_stale : int 35 | Time in minuets for the object to become stale in ATAK 36 | cot_type : str 37 | CoT identifier string to use 38 | repeated_objects : int 39 | Number of repeated objects to create (mimicking a group) 40 | """ 41 | self.filename = filename 42 | self.callsign = callsign 43 | self.tak_port = tak_port 44 | self.tak_server = tak_server 45 | self.cot_identity = cot_identity 46 | self.cot_dimension = cot_dimension 47 | self.cot_stale = cot_stale 48 | self.cot_type = cot_type 49 | self.speed_kph = speed_kph 50 | self.max_time_step_secs = max_time_step_secs 51 | self.repeated_objects = repeated_objects 52 | self.uid = str(uuid.uuid4()) 53 | 54 | def _generate_steps_from_gpx(self) -> Tuple[list, list]: 55 | """ 56 | Ingest the gpx file and create the lists of coordinates and time needed to wait between each 57 | point for the given speed 58 | 59 | Returns 60 | ------- 61 | Tuple[list, list] 62 | """ 63 | gpx_file = open(self.filename, 'r') 64 | gpx = gpxpy.parse(gpx_file) 65 | points = [] 66 | for track in gpx.tracks: 67 | for segment in track.segments: 68 | for point in segment.points: 69 | points.append((point.latitude, point.longitude)) 70 | return _common.generate_smooth_route(points, self.speed_kph, self.max_time_step_secs) 71 | 72 | def play_gpx(self) -> None: 73 | """ 74 | Start playing the gpx file into tak 75 | """ 76 | points, waits = self._generate_steps_from_gpx() 77 | _common.iterate_and_send(points, waits, self.tak_server, self.tak_port, self.cot_type, self.callsign, self.uid, 78 | self.cot_identity, self.cot_dimension, self.cot_stale) 79 | 80 | def play_gpx_multiple(self, offset: float = 5) -> None: 81 | """ 82 | Start playing the gpx file into tak, one per object specified 83 | 84 | Parameters 85 | ---------- 86 | offset : float 87 | number of meters to offset each object (in a random direction) 88 | """ 89 | points, waits = self._generate_steps_from_gpx() 90 | pos = 0 91 | while pos < self.repeated_objects: 92 | new_points = _common.offset_route(points, (offset / 1000)) 93 | gpx_thread = Thread( 94 | target=_common.iterate_wrapper(new_points, waits, self.tak_server, self.tak_port, self.cot_type, 95 | self.callsign + "_" + str(pos), str(uuid.uuid4()), 96 | self.cot_identity, self.cot_dimension, self.cot_stale)) 97 | gpx_thread.start() 98 | pos += 1 99 | -------------------------------------------------------------------------------- /ftssim/wander.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | import time 3 | from ftssim import _common 4 | 5 | 6 | class Wander: 7 | def __init__(self, tak_server: str, total_distance_km: int, distance_between_change: int, start_lat: float, 8 | start_lon: float, callsign: str, tak_port: int = 8087, speed_kph: int = 5, max_time_step_secs: int = 4, 9 | cot_identity: str = "friend", cot_dimension: str = "land-unit", cot_stale: int = 1, 10 | cot_type: str = "a-f-G-U-C"): 11 | """ 12 | Constructs all the necessary attributes for a wandering object. 13 | 14 | Parameters 15 | ---------- 16 | tak_server : str 17 | address for the tak server to set CoT to 18 | total_distance_km : int 19 | total distance in kilometers to wander 20 | distance_between_change : int 21 | distance in km between direction changes 22 | start_lat : float 23 | the starting point latitude 24 | start_lon : float 25 | the starting point longitude 26 | callsign : str 27 | callsign for user in ATAK 28 | tak_port : int 29 | port that takserver is listening on 30 | speed_kph : int 31 | speed the gpx will play back at in kph 32 | max_time_step_secs : int 33 | max time in seconds allows for a gap between CoT messages (the smaller the number the more 34 | fluid the movement) 35 | cot_identity : str 36 | Cot identity e.g friend 37 | cot_dimension : str 38 | Cot dimension e.g land-unit 39 | cot_stale : int 40 | Time in minuets for the object to become stale in ATAK 41 | cot_type : str 42 | CoT identifier string to use 43 | """ 44 | self.callsign = callsign 45 | self.lat = start_lat 46 | self.lon = start_lon 47 | self.total_distance_km = total_distance_km 48 | self.distance_between_change = distance_between_change 49 | self.tak_port = tak_port 50 | self.tak_server = tak_server 51 | self.cot_identity = cot_identity 52 | self.cot_dimension = cot_dimension 53 | self.cot_stale = cot_stale 54 | self.cot_type = cot_type 55 | self.speed_kph = speed_kph 56 | self.max_time_step_secs = max_time_step_secs 57 | self.uid = str(uuid.uuid4()) 58 | 59 | def start_wandering(self) -> None: 60 | """ 61 | Start aimlessly wandering object 62 | """ 63 | points = [(self.lat, self.lon)] 64 | km_count = 0 65 | next_lat = self.lat 66 | next_lon = self.lon 67 | while km_count < self.total_distance_km: 68 | next_lat, next_lon = _common.move_from_location_in_random_direction(next_lat, next_lon, 69 | self.distance_between_change) 70 | points.insert(km_count + 1, (next_lat, next_lon)) 71 | km_count += self.distance_between_change 72 | points, waits = _common.generate_smooth_route(points, self.speed_kph, self.max_time_step_secs) 73 | _common.iterate_and_send(points, waits, self.tak_server, self.tak_port, self.cot_type, self.callsign, self.uid, 74 | self.cot_identity, self.cot_dimension, self.cot_stale) 75 | 76 | def loiter_for_time(self, loiter_time_secs: int) -> None: 77 | """ 78 | Loiter in the area 79 | 80 | Parameters 81 | ---------- 82 | loiter_time_secs : int 83 | Time in seconds for object to loiter in area 84 | """ 85 | centre_lat = self.lat 86 | centre_lon = self.lon 87 | points = [] 88 | waits = [] 89 | num_points = loiter_time_secs / self.max_time_step_secs 90 | point_counter = 0 91 | while point_counter < num_points: 92 | points.insert(point_counter, (centre_lat, centre_lon)) 93 | waits.insert(point_counter, self.max_time_step_secs) 94 | 95 | next_lat, next_lon = _common.move_from_location_in_random_direction(centre_lat, centre_lon, 0.005) 96 | points.insert(point_counter + 1, (next_lat, next_lon)) 97 | waits.insert(point_counter + 1, self.max_time_step_secs) 98 | 99 | point_counter += 2 100 | 101 | _common.iterate_and_send(points, waits, self.tak_server, self.tak_port, self.cot_type, self.callsign, self.uid, 102 | self.cot_identity, self.cot_dimension, self.cot_stale) 103 | 104 | def circle_point(self, radius: int, clockwise: bool = True) -> None: 105 | """ 106 | Loiter in the area 107 | 108 | Parameters 109 | ---------- 110 | radius : int 111 | Radius in Meters from starting point to circle 112 | clockwise : bool 113 | Bool value to denote direction (default is clockwise) 114 | """ 115 | centre_lat = self.lat 116 | centre_lon = self.lon 117 | points = [] 118 | point_counter = 0 119 | clock_bearing = 0 120 | anti_clock_bearing = 359 121 | if clockwise: 122 | while clock_bearing <= 359: 123 | next_lat, next_lon = _common.move_from_location(centre_lat, centre_lon, (radius/1000), clock_bearing) 124 | points.insert(point_counter, (next_lat, next_lon)) 125 | clock_bearing += 9.9 126 | point_counter += 1 127 | else: 128 | while anti_clock_bearing >= 0: 129 | next_lat, next_lon = _common.move_from_location(centre_lat, centre_lon, (radius/1000), anti_clock_bearing) 130 | points.insert(point_counter, (next_lat, next_lon)) 131 | anti_clock_bearing -= 9.9 132 | point_counter += 1 133 | points, waits = _common.generate_smooth_route(points, self.speed_kph, self.max_time_step_secs) 134 | _common.iterate_and_send(points, waits, self.tak_server, self.tak_port, self.cot_type, self.callsign, self.uid, 135 | self.cot_identity, self.cot_dimension, self.cot_stale) 136 | -------------------------------------------------------------------------------- /gpx_example.py: -------------------------------------------------------------------------------- 1 | from ftssim import gpx 2 | from threading import Thread 3 | 4 | player = gpx.GpxPlayer('192.168.3.2', 'test_file.gpx', "A1_Walk", speed_kph=5, max_time_step_secs=4) 5 | player2 = gpx.GpxPlayer('192.168.3.2', 'test.gpx', "A2_Walk", speed_kph=5, max_time_step_secs=4, repeated_objects=5) 6 | 7 | # Setup A thread for each player 8 | player_thread = Thread(target=player.play_gpx) 9 | player2_thread = Thread(target=player2.play_gpx) 10 | 11 | # Start each thread 12 | player_thread.start() 13 | player2_thread.start() 14 | 15 | # Kick off player 2 with its repeated objects 16 | player2.play_gpx_multiple() 17 | 18 | 19 | def print_info(wnd, thread): 20 | print(f""" 21 | _________________________________________________ 22 | + {wnd.callsign} is Running + 23 | + File: {wnd.filename} + 24 | + Process ID: {thread.native_id} + 25 | + UID: {wnd.uid} + 26 | + Server: {wnd.tak_server}:{wnd.tak_port} + 27 | + Speed: {wnd.speed_kph}Kph + 28 | _________________________________________________""") 29 | 30 | # Print out to the terminal useful information about wanderer 31 | print_info(player, player_thread) 32 | print_info(player2, player2_thread) -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | geopy 2 | gpxpy 3 | -------------------------------------------------------------------------------- /wander_example.py: -------------------------------------------------------------------------------- 1 | from ftssim import wander 2 | from threading import Thread 3 | 4 | wanderer = wander.Wander('192.168.3.2', total_distance_km=1, distance_between_change=1, callsign="lost_soul", 5 | speed_kph=120, max_time_step_secs=5, start_lat=38.897125, start_lon=-77.036255) 6 | wanderer1 = wander.Wander('192.168.3.2', total_distance_km=2, distance_between_change=1, callsign="lost_soul_1", 7 | speed_kph=5, max_time_step_secs=5, start_lat=38.897125, start_lon=-77.036255) 8 | 9 | # Setup A thread for each wanderer 10 | wanderer_tread = Thread(target=wanderer.start_wandering) 11 | wanderer1_thread = Thread(target=wanderer1.start_wandering) 12 | 13 | # Start each thread 14 | wanderer_tread.start() 15 | wanderer1_thread.start() 16 | 17 | 18 | def print_info(wnd, thread): 19 | print(f""" 20 | _________________________________________________ 21 | + {wnd.callsign} is Running + 22 | + Process ID: {thread.native_id} + 23 | + UID: {wnd.uid} + 24 | + Server: {wnd.tak_server}:{wnd.tak_port} + 25 | + Total Distance: {wnd.total_distance_km}Km + 26 | + Distance Between Direction Change: {wnd.distance_between_change}Km + 27 | + Speed: {wnd.speed_kph}Kph + 28 | _________________________________________________""") 29 | 30 | 31 | # Print out to the terminal useful information about wanderer 32 | print_info(wanderer, wanderer_tread) 33 | print_info(wanderer1, wanderer1_thread) 34 | 35 | # Loiter in an the area for 5 minutes 36 | wanderer1.loiter_for_time(300) 37 | 38 | # Circle anti-clockwise around the point in a 50 meter radius 39 | wanderer1.circle_point(50, clockwise=False) 40 | --------------------------------------------------------------------------------