Integrating advances in artificial intelligence has a long tradition in development of cockpit assistance system. Recently, Large Language Models (LLMs)
- The use of artificial intelligence (AI) in the flight deck: Enhancing human-AI teamwork in aviation: https://www.sciencedirect.com/science/article/pii/S2941198X25000430
- AviationGPT: A Large Language Model for the Aviation Domain: https://arxiv.org/pdf/2311.17686
- LeRAAT: LLM-Enabled Real-Time Aviation Advisory Tool: https://arxiv.org/pdf/2503.16477
This workspace consumes Isaac Sim terrain-local x/y/z values in the renderer, but drone GPS arrives over MAVLink and must be translated back into that same local frame in the backend.
The converter logic is derived from the same Jsonnet mission config used by the simulator:
- Source config:
/home/qnc/Desktop/pip_isaacsim/env_isaacsim/lib/python3.11/site-packages/isaacsim/extsUser/auspex.platform_interface/auspex_platform_interface_python/config/config.jsonnet - Mission manifest endpoint:
GET /api/mission-config - Backend config loader:
server/mission-config.ts - Shared converter:
shared/isaacsim_coordinates.ts - Frontend mission wrapper:
src/isaacsim_mission.ts - Desktop shell:
electron/main.ts
There are two related coordinate paths in the platform code:
-
Jsonnet helper path in
config.jsonnetgps_to_local()andoffset_to_gps()are used while building config data.- In the current config,
terrain_config.lat/lonare the AOI center.
-
Runtime spawn path in
spawner.pyandcore/geo_ref.pySpawnContext.resolve_position()callsTerrainGeoConverter.gps_to_terrain().TerrainGeoConverterapplies:- GPS origin from
terrain_config.lat/lon/origin_alt - optional UTM conversion when
use_utm = true - yaw rotation via
yaw_deg - a terrain XY offset passed in from
spawner.py
- GPS origin from
The app mirrors the runtime spawn path for simulator-published local poses, but drones need one extra distinction:
- the backend converts drone MAVLink GPS into terrain-local positions before broadcasting
/ws - the renderer converts dropped GPS targets back into terrain-local coordinates for ROS-targeted ground actors
There is an important split in the current Isaac Sim and Pegasus stack:
SpawnContextandTerrainGeoConverterspawn actors in a terrain-local frame that uses the half-AOI XY offset fromspawner.py- Pegasus GPS sensors and PX4 MAVLink telemetry use the simulated world origin
(0, 0, 0)as their geodetic anchor
That means PX4 GPS does not come back in the same geodetic frame that the terrain-local gps_to_terrain() helper uses for people/carters.
In practice:
- simulator-local people/carter poses use the terrain converter with the half-AOI offset
- drone MAVLink GPS must be translated with a zero-XY-offset converter that matches Pegasus world coordinates
- when the operator drops a drone target on the map, the backend first resolves the clicked map location into Isaac local coordinates, then reprojects that local point into PX4/Pegasus GPS before sending
DO_REPOSITION
If this translation is skipped, drones get shifted by roughly half the AOI size twice, which is why they can appear hundreds of meters away from the expected map position.
The current platform runtime always passes:
terrain_xy_offset_m = (aoi_length_m / 2, aoi_width_m / 2, 0)
from spawner.py into TerrainGeoConverter.
That means published ROS poses behave like:
(0, 0)= south-west terrain corner(aoi_length_m / 2, aoi_width_m / 2)= mission GPS anchor / AOI center
This now matches the current config.jsonnet comments directly, so the half-AOI offset is not a workaround for an old mismatch. It is the intended runtime convention.
From the active Jsonnet mission:
terrain_config.lat = 47.836262terrain_config.lon = 11.614310aoi_length_m = 1000aoi_width_m = 1000yaw_deg = 0use_utm = true
So the effective terrain center in local coordinates is:
(500, 500)
and the browser converts local x/y back to GPS using the same:
- origin lat/lon
- half-AOI offset
- yaw rotation
- UTM preference
as the runtime Isaac Sim spawner.
This workspace now runs as an Electron desktop app instead of a browser-only Vite page.
- Electron starts the ROS backend with
scripts/run-backend.sh. scripts/run-backend.shbuilds and sources the local ROS workspace inros2_ws/, starts the control adapter, and then runsserver/index.ts.- There is no
rosbridgeorroslibin the data path anymore. - The renderer loads mission config from
/api/mission-configand live telemetry from/ws.
The local ROS interface package is built into generated workspace folders under ros2_ws/:
ros2_ws/build/ros2_ws/install/ros2_ws/log/
The app also writes runtime/build artifacts into:
dist/log/
These directories are ignored in .gitignore because they are generated outputs, not source.
The app no longer sends backend-specific control commands directly.
- Renderer ->
POST /api/agents/:agentName/target - HTTP backend -> ROS action client/service client
- ROS control adapter -> concrete backend implementation
The generic ROS contract is defined in ros2_ws/src/tactical_map_interfaces:
- Action:
/tactical_map/command_agent_target - Service:
/tactical_map/list_agent_command_capabilities
The action accepts mission-level commands, not transport-specific ones:
coordinate_frame = isaacsim_localwithx/y/zcoordinate_frame = wgs84withlat/lon/relative_alt_m
This keeps the Electron app and the HTTP backend independent from whether the real control path is PX4 ROS, Pegasus services, MAVSDK, or raw MAVLink.
Today, the ROS control adapter in server/control_adapter.ts translates the generic ROS command contract into the currently available backends:
- People and carters:
- publish
std_msgs/msg/Float32MultiArrayto/<agent>/set_target
- publish
- Drones:
- use the direct PX4 MAVLink guided controller in
server/mavlink.ts
- use the direct PX4 MAVLink guided controller in
So the current implementation is still mixed under the hood, but that detail is now hidden behind one ROS action/service boundary.
The control adapter also owns the drone-side telemetry bridge so the HTTP backend does not open its own competing PX4 control link.
- Each drone still uses its direct PX4 MAVLink socket pair internally:
Drone_1: receive14541, send14581Drone_2: receive14542, send14582
server/mavlink.tsreads:HEARTBEATSYS_STATUSEXTENDED_SYS_STATESTATUSTEXTGLOBAL_POSITION_INT
- The control adapter converts drone GPS into Isaac Sim terrain-local coordinates with
shared/isaacsim_coordinates.ts. - It republishes drones into ROS as:
/<drone>/poseasgeometry_msgs/msg/PoseStamped/<drone>/statusastactical_map_interfaces/msg/DroneStatus
server/index.tsnow consumes those ROS topics just like any other telemetry source and forwards them to the renderer on/ws.
This means the renderer now sees one ROS-only command/telemetry contract even though the current drone implementation still talks MAVLink behind the adapter.
For drones specifically, server/control_adapter.ts now uses two coordinate converters:
- mission/map converter: Isaac terrain-local with the half-AOI offset
- PX4/Pegasus converter: same lat/lon/yaw/UTM settings, but zero XY offset
That split matches how Pegasus computes GPS from state.position and fixes the previous +500 m / +500 m style drift on the tactical map.
The current MAVLink guided implementation still sends the same PX4 guided commands QGroundControl uses:
MAV_CMD_NAV_TAKEOFFMAV_CMD_COMPONENT_ARM_DISARMMAV_CMD_DO_REPOSITION
On first control-adapter connection, if a drone is still on the ground, the controller automatically arms and sends takeoff before the operator drags any new target.
- Default auto-takeoff height:
5 m - Override with
DRONE_AUTO_TAKEOFF_ALT_METERS - Disable with
DRONE_AUTO_TAKEOFF_ON_CONNECT=0
npm run dev: starts Vite plus Electron for local development.npm run start: builds the renderer and launches the Electron app.npm run preview: serves the built renderer through the backend without Electron.npm run build:ros: builds the local ROS interface workspace manually.
npm run build:ros builds the tactical_map_interfaces package that defines the generic ROS action/service contract used by the app backend and control adapter.
- Right-click
Person_1orPerson_2on the map to enter drag mode. - Move the mouse to the new target location and left-click to drop.
- People and carters convert the dropped GPS point back into Isaac Sim local
x/y/zcoordinates and sendframe=isaacsim_localto the backend. - Drones prompt for a target height above ground and send
frame=wgs84withlat/lon/relativeAltMeters. - The HTTP backend forwards those commands to
/tactical_map/command_agent_target. - The ROS control adapter decides how to execute them with the currently configured backend.
- The people controller is patched so externally published targets clear the existing patrol queue instead of being appended after it.
- A trail is shown once an agent has been drag-commanded and starts moving.