Next-Generation Positioning System (NGPS) for high-altitude drone navigation without GPS.
This codebase implements a visual geo-localization system for drones that matches down-facing camera images against satellite reference images using deep learning-based feature matching. The system provides absolute position estimates to correct drift in Visual-Inertial Odometry (VIO) systems.
-
ap_ngps_ros2: ROS2 node that performs visual geo-localization by matching camera images to satellite reference images using LightGlue/SuperPoint deep learning features. Runs at 1-2 Hz.
-
ap_ukf: Unscented Kalman Filter that fuses multiple sensor inputs:
- NGPS absolute position (1-2 Hz)
- VIO relative pose (10-20 Hz)
- IMU data (high frequency)
Outputs fused odometry at 10-20 Hz for flight control.
-
ap_vo: Standalone monocular visual odometry (SIFT + metric
solvePnPRansacon a plane at configurable depth). Publishesnav_msgs/Odometrycompatible withvio_origin_relay/ap_ukf. -
ap_vo2: Standalone map-matching VPS node using classical features (AKAZE + MAGSAC homography against pre-computed reference tiles). A lighter-weight alternative to the LightGlue pipeline, with no dependency on other NGPS packages.
Full VIO is expected as an external package publishing relative pose; it is not bundled here.
- NGPS module matches real-time camera frames to a georeferenced satellite reference image
- Provides absolute position estimates at low frequency (1-2 Hz)
- UKF fuses NGPS absolute positions with high-frequency VIO estimates
- Fused output sent to ArduPilot's EKF for final state estimation
- GSoC 2024: High Altitude Non-GPS Navigation - Initial GSoC project summary
- Transformer & Optimization Based High Altitude GPS-Denied Fusion - Updated implementation and architecture details
- ap_nongps - Earlier prototype implementation with SIFT-based feature matching and optical flow methods
- Add an intereactive initial guess interface
- Add a fallback VO pipeline
- Add global optimisation for fusion
- Update AP to accept position and odometry as separate sources to be fused internally
- Add support for multiple reference images
- Optimize feature matching for faster performance
- Add calibration tools and documentation
- Improve error handling and recovery
- Add more unit tests
- Document configuration parameters
- Add example launch files for different scenarios
- Performance profiling and optimization
- Convert lightglue model to jetson friendly compute capable
- Support for different camera models
- GTSAM and SFM support
Target platform: NVIDIA Jetson Orin with JetPack 7.2 (L4T r39.x, Ubuntu 24.04, CUDA 13.2).
The container stack is ROS 2 Jazzy + TensorRT 10.16.2 + PyTorch cu130 (Dockerfile.jp7.dev).
Step 1: Install Docker Engine
- Follow the official installation guide: Install Docker Engine.
- Apply the Linux post-installation configuration as non-root user: Linux post-installation steps for Docker Engine.
Step 2: NVIDIA Container Toolkit
- Install the toolkit: NVIDIA Container Toolkit.
- Configure Docker to use the NVIDIA runtime and restart the daemon:
sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart dockerStep 3: Clone the repository
mkdir -p ~/ngps_ws/src
cd ~/ngps_ws/src
git clone https://github.com/snktshrma/ngps_flight.git -b main_jazzyStep 4: Build the Docker image (30–60 min on device)
cd ~/ngps_ws/src/ngps_flight
docker build -f Dockerfile.jp7.dev -t ngps-vps-dev-arm:jp72-cu132-jazzy-v2 .Docker only (skip Steps 5–6). Distrobox is recommended - it handles the home mount and display/audio passthrough - but plain Docker works and needs no
--init-hooks, because the image's user is already in the GPU groups.docker run -it \ --name vps-jp7 \ --network host \ --ipc host \ --runtime nvidia \ -u $(id -u) \ -v "$HOME:$HOME" -w "$HOME/ngps_ws" \ -e DISPLAY="$DISPLAY" -e XAUTHORITY="$XAUTHORITY" \ -v /tmp/.X11-unix:/tmp/.X11-unix \ --device /dev/ttyACM0 \ ngps-vps-dev-arm:jp72-cu132-jazzy-v2Mount the home directory at the same path as on the host (
$HOME:$HOME). The workspace build andap_ngps_ros2/config/ngps_config.yamlstore absolute paths, so remapping the mount point (e.g. to/home/dev/ngps_ws) breaks engine and reference-image lookups.Drop
--deviceif no flight controller is attached. GUI apps may also needxhost +local:on the host.To start again:
docker start vps-jp7 docker exec -it -u $(id -u) -w "$HOME/ngps_ws" vps-jp7 /bin/bash
Step 5: Install Distrobox
For Ubuntu:
sudo apt install distrobox
export DBX_CONTAINER_MANAGER=docker
echo "export DBX_CONTAINER_MANAGER=docker" >> ~/.bashrcStep 6: Create the Distrobox
distrobox create \
--name vps-jp7 \
--image ngps-vps-dev-arm:jp72-cu132-jazzy-v2 \
--additional-flags "--runtime nvidia --ipc=host" \
--init-hooks "usermod -aG video,debug,render,dialout $USER 2>/dev/null || true"The
--init-hooksline is required for GPU access. Distrobox replaces the image's user with one matching the host, dropping its group memberships, and the Jetson GPU device nodes are group-restricted -/dev/nvgpu/igpu0/{ctxsw,dbg}todebug(GID 982) and the rest tovideo(44) /render(993). The image defines these groups at the host GIDs, so the hook only needs to add the user to them. Without it CUDA fails withcuInit: operation not supported.
Distrobox: The host user home directory is mounted; workspace paths such as
~/ngps_wsmatch the host, while binaries and libraries resolve from the container image.
Step 7: Import sources and build the workspace
distrobox enter vps-jp7Everything below runs inside the container - it provides vcs, colcon and the ROS
toolchain, so nothing extra is needed on the host.
# Verify the image:
python3 -c 'import torch, cv2, lightglue, tensorrt; print("ok", torch.cuda.is_available())'
cd ~/ngps_ws
vcs import --recursive --input src/ngps_flight/ros2.jazzy.repos src
source /opt/ros/jazzy/setup.bash
colcon build --packages-select micro_ros_msgs micro_ros_agent ardupilot_msgs ap_ngps_ros2Step 8: Build SITL with DDS (inside the container):
cd ~/ngps_ws/src/ardupilot
./waf configure --board sitl --enable-DDS && ./waf copterStep 9: Verify (from the host):
~/ngps_ws/src/ngps_flight/scripts/run_sitl_stack.sh/ap/* topics appear once MAVProxy connects to SITL:
~/ngps_ws/src/ngps_flight/scripts/_distrobox_ros.sh ros2 topic listKeep
ROS_DOMAIN_IDunset (or match it to theDDS_DOMAIN_IDparameter, default 0).
Distrobox:
distrobox stop vps-jp7
distrobox rm vps-jp7Docker:
docker stop vps-jp7
docker rm vps-jp7See individual package READMEs:
export MAPBOX_API_KEY=''From the host (no need to distrobox enter or source ROS manually, scripts handle that):
~/ngps_ws/src/ngps_flight/scripts/run_sitl_stack.sh~/ngps_ws/src/ngps_flight/scripts/run_sat_cam.sh(after GPS in SITL)~/ngps_ws/src/ngps_flight/scripts/run_ngps.sh
Optional aliases (add to ~/.bashrc):
alias ngps-sitl='~/ngps_ws/src/ngps_flight/scripts/run_sitl_stack.sh'
alias ngps-cam='~/ngps_ws/src/ngps_flight/scripts/run_sat_cam.sh'
alias ngps-run='~/ngps_ws/src/ngps_flight/scripts/run_ngps.sh'Full fusion stack: LAUNCH=unified_localization_simple.launch.py ~/ngps_ws/src/ngps_flight/scripts/run_ngps.sh
Run this inside the container - its TensorRT matches the host exactly, and engines are locked to the TensorRT version that built them.
Clone LightGlue-ONNX (not bundled; the home directory is shared with the container):
git clone https://github.com/fabio-sim/LightGlue-ONNX.git ~/LightGlue-ONNXThen, inside distrobox enter vps-jp7:
cd ~/LightGlue-ONNX
# 1. Export ONNX (uses the container's torch; no uv/venv needed)
python3 -m lightglue_dynamo.cli export superpoint --num-keypoints 1024 -b 2 -h 360 -w 640 \
-o weights/superpoint_lightglue_k1024_640x360.onnx
# 2. Build the FP16 engine
~/ngps_ws/src/ngps_flight/ap_ngps_ros2/scripts/build_tensorrt_engine.shSet tensorrt_engine_path and reference_image_path in ap_ngps_ros2/config/ngps_config.yaml.
Full copy-paste steps and options: ap_ngps_ros2/README.md.
To rebuild for a different resolution, change
-h/-win the export and rerun both steps.
ros2 run micro_ros_agent micro_ros_agent udp4 -p 2019./Tools/autotest/sim_vehicle.py -v ArduCopter --enable-DDS --location OSRF0Add
-DGto build with debug symbols and run under gdb. It triggers a full rebuild the first time and slows the vehicle loop, so keep it off for timing-sensitive runs. Via the launcher:SITL_EXTRA_ARGS=-DG ~/ngps_ws/src/ngps_flight/scripts/run_sitl_stack.sh
Then in another terminal, after GPS is detected in sitl, run:
python3 ./Tools/autotest/sat_cam_emulator.py --port 14550 --airfield-radius-m 1500 --airfield-zoom 20 --http-mjpeg-port 8090 --no-hud --ros --no-display --ros-compressed --ros-size 640x360 --pose-source simIf want to record bag file:
ros2 bag record -o <location> /camera/image_raw/compressed /ap/imu/experimental/data /ap/clock /ap/tf_static /ap/navsat /ap/gps_global_origin/filtered /ap/geopose/filtered /ap/time /ap/tf /ap/pose/filteredTo replay:
ros2 bag play <bag location>
Now to run our VPS (with bag for debugging or with SITL (with non-GPS EKF params for realtime test),
Set the .tif file location in ap_ngps_ros2/config/ngps_config.yaml.
ros2 launch ap_ngps_ros2 ngps_localization.launch.pyTo change location and get new .tif for that location, please follow steps in the gazebo_terrain_generator fork specifically for this: https://github.com/snktshrma/gazebo_terrain_generator/tree/dev/geotiff
After changing, please change the location for sitl launch as well.
For now the steps to generate a .tif are very manual but addition to sat_camemulator.py already sets a base to automaticallyt manage and autogenerate .TIF using MAPBOX. So in next updates, I'll add that feature as well and that will also help wiith setting initial guess.
- ap_ngps_ros2/README.md - TensorRT build, config, troubleshooting
- Changelog - Project history and version timeline
- Camera-IMU Calibration - Google Docs
- Non-GPS Navigation Setup - Google Docs
IMPORTANT DISCLAIMER: This software is provided for research and educational applications only. The developers and contributors of this project doesn't promote and are not responsible for any misuse.
See LICENSE file.