Skip to content

Add terrain planner module #1533

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

srmainwaring
Copy link
Contributor

@srmainwaring srmainwaring commented Mar 25, 2025

Add a module for airplane terrain planning. The module integrates a Python version of the ETHZ terrain navigation package that uses a Dubins airplane state space model with the OMPL RRT planner.

planner_with_fences

Main features are:

  • Integration with SRTM1 terrain data supplied by the MAVProxy mp_elevation module.
  • Command line settings for the main planner parameters.
  • Support for exclusion and inclusion polyfences (polygons and circles).
  • Generate flight plans that stay within min and max altitude bands given a climb angle.
  • Generate and upload waypoints sampled from the planned path.
  • Multiprocessing support for the planner to avoid Python GIL contention issues.

A guide on usage and further discussion on the planner used can be found here: https://discuss.ardupilot.org/t/offboard-terrain-navigation-for-plane-with-ros-2-ap-dds/114418/6?u=rhys

Related

Dependencies

The ompl dependency for recent Python versions running on either Linux or macOS are resolved by the terrain_nav_py installer (via the projects pyproject.toml).

Installing the terrain_nav_py package should pull in all dependencies:

python -m pip install git+https://github.com/srmainwaring/terrain_nav_py.git

Usage

Ensure the SRTM1 terrain data is available:

FBWA> terrain set source SRTM1

Load the module:

FBWA> module load terrainnav

Display settings:

FBWA> terrainnav set
     climb_angle_deg 8.0
         grid_length 10000.0
        grid_spacing 30.0
      loiter_agl_alt 60.0
       loiter_radius 60.0
         max_agl_alt 100.0
         min_agl_alt 50.0
          resolution 100.0
         time_budget 20.0
      turning_radius 60.0
          wp_spacing 60.0

Tasks

  • replace prints in upstream library with a Python logger to prevent flooding the console
  • remove or grey out currently unused buttons from UI
  • squash commits

@srmainwaring srmainwaring force-pushed the prs/pr-terrain-nav branch 4 times, most recently from 71b05c3 to 40efa07 Compare March 27, 2025 14:10
@Georacer
Copy link

Oh my god, this PR description has all the right keywords to make me scream with joy!

@srmainwaring
Copy link
Contributor Author

Oh my god, this PR description has all the right keywords to make me scream with joy!

Testers welcome @Georacer! I found a possible issue with installing the ompl Python bindings on an Ubuntu VM that did not show up in CI and local docker machines. The ompl wheel may be trying to install ompl system libraries as well, which needs sudo, and this caused complaints in a Python virtual environment. It can be forced, but I need to try and replicate the issue on a clean build.

Various other niggles to do with clashing numpy and matplotlib versions should now be resolved, as well as an issue where terrain tiles were not being loaded because I'd marked the planner process as a daemon, and the terrain loader needs to run multiproc children which a daemon process could not do.

@Ryanf55
Copy link
Contributor

Ryanf55 commented Apr 6, 2025

First test result, not running SITL, just loading the module after running mavproxy.py.

MAV> module load terrainnav
MAV> /usr/lib/python3/dist-packages/scipy/__init__.py:146: UserWarning: A NumPy version >=1.17.3 and <1.25.0 is required for this version of SciPy (detected version 1.26.4
  warnings.warn(f"A NumPy version >={np_minversion} and <{np_maxversion}"
ERROR in command ['load', 'terrainnav']: 'NoneType' object has no attribute 'messages'
Traceback (most recent call last):
  File "/home/ryan/Dev/ardu_ws/src/MAVProxy/MAVProxy/mavproxy.py", line 825, in process_stdin
    fn(args[1:])
  File "/home/ryan/Dev/ardu_ws/src/MAVProxy/MAVProxy/mavproxy.py", line 623, in cmd_module
    mpstate.load_module(modname, **kwargs)
  File "/home/ryan/Dev/ardu_ws/src/MAVProxy/MAVProxy/mavproxy.py", line 387, in load_module
    module = m.init(mpstate, **kwargs)
  File "/home/ryan/Dev/ardu_ws/src/MAVProxy/MAVProxy/modules/mavproxy_terrainnav/__init__.py", line 14, in init
    return TerrainNavModule(mpstate)
  File "/home/ryan/Dev/ardu_ws/src/MAVProxy/MAVProxy/modules/mavproxy_terrainnav/terrainnav.py", line 129, in __init__
    self.start_planner()
  File "/home/ryan/Dev/ardu_ws/src/MAVProxy/MAVProxy/modules/mavproxy_terrainnav/terrainnav.py", line 520, in start_planner
    home = wp_module.get_home()
  File "/home/ryan/Dev/ardu_ws/src/MAVProxy/MAVProxy/modules/mavproxy_wp.py", line 269, in get_home
    return self.get_WP0(home_only=True)
  File "/home/ryan/Dev/ardu_ws/src/MAVProxy/MAVProxy/modules/mavproxy_wp.py", line 244, in get_WP0
    if 'HOME_POSITION' in self.master.messages:
AttributeError: 'NoneType' object has no attribute 'messages'

@Georacer
Copy link

Georacer commented Apr 9, 2025

@srmainwaring it F*****G works!

Screenshot from 2025-04-09 21-24-56

But, I refused to alter my system MAVProxy or other python packages. I really wanted to make this work in a Python virtual environment.
So here's what I had to do:

  1. Clone your branch.
  2. Create a new virtual environment:
mkdir ~/deleteme && cd deleteme
uv init
uv python pin 3.10 (my system Python version, perhaps unneded)
  1. Install the necessary dependencies. In the end they look like this:
❯ uv tree
Resolved 42 packages in 5ms
mavproxy-terrain-planner v0.1.0
├── mavproxy v1.8.71
│   ├── numpy v2.2.4
│   ├── pymavlink v2.4.43
│   │   ├── future v1.0.0
│   │   └── lxml v5.3.2
│   ├── pynmeagps v1.0.49
│   └── pyserial v3.5
├── opencv-python v4.11.0.86
│   └── numpy v2.2.4
├── pygame v2.6.1
├── terrain-nav-py v0.0.1
│   ├── matplotlib v3.10.1
│   │   ├── contourpy v1.3.1
│   │   │   └── numpy v2.2.4
│   │   ├── cycler v0.12.1
│   │   ├── fonttools v4.57.0
│   │   ├── kiwisolver v1.4.8
│   │   ├── numpy v2.2.4
│   │   ├── packaging v24.2
│   │   ├── pillow v11.1.0
│   │   ├── pyparsing v3.2.3
│   │   └── python-dateutil v2.9.0.post0
│   │       └── six v1.17.0
│   ├── mavproxy v1.8.71 (*)
│   ├── numpy v2.2.4
│   ├── ompl v1.7.0
│   ├── pymavlink v2.4.43 (*)
│   ├── pytest v8.3.5
│   │   ├── exceptiongroup v1.2.2
│   │   ├── iniconfig v2.1.0
│   │   ├── packaging v24.2
│   │   ├── pluggy v1.5.0
│   │   └── tomli v2.2.1
│   ├── scipy v1.15.2
│   │   └── numpy v2.2.4
│   └── shapely v2.1.0
│       └── numpy v2.2.4
└── wxpython v4.2.2
    ├── numpy v2.2.4
    └── six v1.17.0
(*) Package tree already displayed

Note that your custom MAVProxy branch is installed from local sources.
wxPython tool about 10mins to build. But ChatGPT suggests I could have done

uv pip install -f https://wxpython.org/Phoenix/snapshot-builds/ wxPython

Didn't try that.

  1. Then I had to open a fresh terminal and shadow mavproxy:
ln -s ~/deleteme/.venv/bin/mavproxy.py mavproxy.py
  1. Finally I could run the command:
sim_vehicle.py --debug -v ArduPlane -f quadplane --custom-location="51.872565163811664, -3.4788544705811075, 452, 0" --console --map

Some visual artifacts didn't work seamlessly. E.g. the contours didn't appear until I zoomed out. Then I could zoom back in and see them still. Or perhaps they just took a long time to load?

In general, I was always unhappy with our lack of venv support for MAVProxy, but perhaps this workflow kind of works.
Now I'll be able to tinker with MAVProxy with peace of mind.

@Georacer
Copy link

Georacer commented Apr 9, 2025

I see 3 loiters towards the end
image

but the actual waypoints are more sparse and actually simple waypoints, not loiters:
image
(Sorry, closed the sim, had to use QGC.)

Is this a shortcoming or is it intentional?

@srmainwaring
Copy link
Contributor Author

@Georacer thanks for testing.

I use the venv module for virtual environments, which seems to work well for me on macOS and Ubuntu docker images and VMs. Setting up a new environment should be quick, the steps I use are outlined below:

  • Using the '—system-site-packages` flag when creating the venv should save having to rebuild wxPython and reinstall most existing packages.

  • this mavproxy fork needs to be cloned and installed. Do this before installing terrain_nav_py as the latter includes mavproxy in its pyproject.toml dependencies and will install it if not already present.

  • cloning terrain_nav_py and running pip install . in the project root directory should install all its dependencies.

  • the contours may take a little while to display, depending on whether the terrain tiles are present locally or not. The contours are a separate feature to this PR, but I think they can be made faster by using a different opencv object to display them.

  • the three spirals at the end of the path may have been sampled too sparsely. The setting wp_spacing can be reduced to increase the number of waypoints generated. Alternatively the turning_radius can be increased.

I think the generator needs a different spacing for curved sections to straight ones.

The most recent version of OMPL includes an OwenStateSpace model whose Python bindings can be used to replace much of the performance critical Python code in terrain_nav_py. This makes significant difference (about 10x faster in the planner). There are some bugs to resolve and testing to complete before that can be used, but it should not change the use or behaviour of the module (aside from speed).

@Georacer
Copy link

Georacer commented Apr 10, 2025

I think the generator needs a different spacing for curved sections to straight ones.

Ideally you would convert the turns to NAV_LOITER_ALT (perhaps with a CONDITION_YAW if necessary, I don't remember well).
But I understand that's not plug and play with the planner, it requires post-processing of the path.

@srmainwaring
Copy link
Contributor Author

The circles are placed to gain or lose altitude, so will need to understand the mission commands for that. The raw output of the planner is a set of points that may be connected by Dubins curves. These are then post processed to construct a trajectory, in which the position, altitude, path tangent and current curvature are deduced. From that the options are to generate an input for a path following controller (eg offboard control using DDS or MAVLink), or generate a mission. Only the latter is provided at the moment, and it’s pretty basic. I’d like to get that improved and the path guidance is for a bit later, there are PRs to support this in the Plane library already and this tool will help generate examples to complete the autotests needed.

- Add module folder.
- Add module and app backend.
- Add wx ui frame.
- Add buttons and send commands to backend.

Signed-off-by: Rhys Mainwaring <rhys.mainwaring@me.com>

terrainnav: enable control of map contours

Signed-off-by: Rhys Mainwaring <rhys.mainwaring@me.com>

terrainnav: add start and goal to map

Signed-off-by: Rhys Mainwaring <rhys.mainwaring@me.com>

terrainnav: initial integration of terrain planner

- Add dependency on (py-)ompl and terrain_nav_py.

Signed-off-by: Rhys Mainwaring <rhys.mainwaring@me.com>

terrainnav: use updated GridMap interface

- Named args have changed home_lat -> map_lat etc.

Signed-off-by: Rhys Mainwaring <rhys.mainwaring@me.com>

terrainnav: add initial version of waypoint generator

Signed-off-by: Rhys Mainwaring <rhys.mainwaring@me.com>

terrainnav: formatting and remove commented code

Signed-off-by: Rhys Mainwaring <rhys.mainwaring@me.com>

terrainnav: add gen waypoints button

Signed-off-by: Rhys Mainwaring <rhys.mainwaring@me.com>

terrainnav: add gen waypoints button

Signed-off-by: Rhys Mainwaring <rhys.mainwaring@me.com>

terrainnav: draw the solution states as polygon circles

Signed-off-by: Rhys Mainwaring <rhys.mainwaring@me.com>

terrainnav: path segment states use Vector3

terrainnav: run planner on thread

Signed-off-by: Rhys Mainwaring <rhys.mainwaring@me.com>

terrainnav: update waypoint generator

- Calculate strides for each segment.
- Skip duplicates at ends of each Dubins curve.

Signed-off-by: Rhys Mainwaring <rhys.mainwaring@me.com>

terrainnav: rename init_map_layer to init_slip_map_layer

Signed-off-by: Rhys Mainwaring <rhys.mainwaring@me.com>

terrainnav: validate start and goal positions when set

- Move map initialisation out of planner thread.
- Add validation to start and goal positions before drawing.
- Consolidate conversion from (lat, lon) to (east, north) in static method.

Signed-off-by: Rhys Mainwaring <rhys.mainwaring@me.com>

terrainnav: correct module variable name

Signed-off-by: Rhys Mainwaring <rhys.mainwaring@me.com>

terrainnav: enable show, hide, move planner boundary

- Add buttons to show or hide the current planner boundary.
- Ensure start and goal ENU positions in terrain map frame are updated.

Signed-off-by: Rhys Mainwaring <rhys.mainwaring@me.com>

terrainnav: clean up handling of planner thread

Signed-off-by: Rhys Mainwaring <rhys.mainwaring@me.com>

terrainnav: only print messages if moddebug > 1

Signed-off-by: Rhys Mainwaring <rhys.mainwaring@me.com>

terrainnav: add buttons to clear paths and waypoints

Signed-off-by: Rhys Mainwaring <rhys.mainwaring@me.com>

terrainnav: fix bug in planner thread

- Thread was not set to None on early return.

Signed-off-by: Rhys Mainwaring <rhys.mainwaring@me.com>

terrainnav: check path is above terrain and colour code in map

Signed-off-by: Rhys Mainwaring <rhys.mainwaring@me.com>

terrainnav: add exception handling round planner calls that may raise runtime errors

Signed-off-by: Rhys Mainwaring <rhys.mainwaring@me.com>

terrainnav: recreate planner each time

- Recreate as inputs may have changed between planner runs.

Signed-off-by: Rhys Mainwaring <rhys.mainwaring@me.com>

terrainnav: add support for exclusion polygons

Signed-off-by: Rhys Mainwaring <rhys.mainwaring@me.com>

terrainnav: use UnclosedSlipPolygon for drawing planner boundary

Signed-off-by: Rhys Mainwaring <rhys.mainwaring@me.com>

terrainnav: add support for (ex/in)clusion polygons and circles

Signed-off-by: Rhys Mainwaring <rhys.mainwaring@me.com>

terrainnav: use planner validity checker on start and goal circles

- Move planner initialisation and fence setup to separate function
- Initialise planner early so the state validity checker is available.
- Start and goal circles are now checked they satisfy fence conditions.

Signed-off-by: Rhys Mainwaring <rhys.mainwaring@me.com>

terain_ompl_rrt: ensure planner is re-initialised if boundary moves

Signed-off-by: Rhys Mainwaring <rhys.mainwaring@me.com>

terrainnav: monitor fence for updates

- Re-initialise the planner if fence points change.

Signed-off-by: Rhys Mainwaring <rhys.mainwaring@me.com>

terrainnav: use MPSettings for planner parameters

Signed-off-by: Rhys Mainwaring <rhys.mainwaring@me.com>

terrainnav: set default time budget to 20s

- Inadvertently set to 60s previously.

Signed-off-by: Rhys Mainwaring <rhys.mainwaring@me.com>

terrainnav: add commands to set and clear

- Set start circle to use loiter radius.
- Set goal circle to use turning radius (current default for planner).
- Pass radius as arg to draw circle command.

Signed-off-by: Rhys Mainwaring <rhys.mainwaring@me.com>

terrainnav: add setting for spacing waypoints

Signed-off-by: Rhys Mainwaring <rhys.mainwaring@me.com>

terrainnav: update start and goal positions if loiter alt changes

Signed-off-by: Rhys Mainwaring <rhys.mainwaring@me.com>

terrainnav: move planner to separate process

- Add class TerrainPlanner(Process).
- Set up pipes for communication.
- Set up process control methods: start, stop, kill.
- Add message types for inter-proc comms.
- Implement planner functionality (no fences).

Signed-off-by: Rhys Mainwaring <rhys.mainwaring@me.com>

terrainnav: remove unused imports

Signed-off-by: Rhys Mainwaring <rhys.mainwaring@me.com>

terrainnav: prefix print messages with module name

Signed-off-by: Rhys Mainwaring <rhys.mainwaring@me.com>

terrainnav: process messages from planner

- Add validation state to start and goal lat, lon messages.
- Draw validated start and goal states.

Signed-off-by: Rhys Mainwaring <rhys.mainwaring@me.com>

terrainnav: display path from planner process

- Remove code moved to the planner process.
- Comment code to be removed.
- Process plans published by the planner process.

Signed-off-by: Rhys Mainwaring <rhys.mainwaring@me.com>

terrainnav: move fence handling to planner process

- Remove code moved to the planner process.
- Handle fences on planner process.

Signed-off-by: Rhys Mainwaring <rhys.mainwaring@me.com>

terrainnav: send Dubins states from planner process

- Convert ompl states to list of tuples(x, y, z, yaw).
- Fix bug in naming of poly fences properties.
- Update draw_states method.
- Remove stale comments and imports.

Signed-off-by: Rhys Mainwaring <rhys.mainwaring@me.com>

terrainnav: ensure all settings are sent to planner process

Signed-off-by: Rhys Mainwaring <rhys.mainwaring@me.com>

terrainnav: remove unused variables

Signed-off-by: Rhys Mainwaring <rhys.mainwaring@me.com>

terrainnav: remove debug prints

Signed-off-by: Rhys Mainwaring <rhys.mainwaring@me.com>

terrainnav: catch ValueError when retrieving terrain elevation data

Signed-off-by: Rhys Mainwaring <rhys.mainwaring@me.com>

terrainnav: catch ValueError in event loop and release lock

Signed-off-by: Rhys Mainwaring <rhys.mainwaring@me.com>

terrainnav: planner process must be non-daemonic

- Must be able to run child processes to download tiles.

Signed-off-by: Rhys Mainwaring <rhys.mainwaring@me.com>
@srmainwaring
Copy link
Contributor Author

@Georacer I've added an alternative waypoint generator that uses NAV_LOITER_TO_ALT for turns, where the turn angle is greater than some configurable angle.

There is a dialog to facilitate editing the settings:

settings_dialog

The choice of WP generator can now be toggled - the planner does not need to be re-run in order to regenerate the mission using a different method.

Figure: simple waypoints sampled along the path
wp_gen_simple

Figure: waypoints generated using NAV_LOITER_TO_ALT for turns
wp_gen_use_loiter_to_alt

Figure: mission details
wp_gen_use_loiter_to_alt_editor

For the most case, the plane follows the mission with NAV_LOITER_TO_ALT well. However I have noticed that the plane may fail to achieve the required altitude gain for some turns and then completes a full circle before continuing. This is not ideal, as the planner does not check that such circles are safe (unless a spiral is requested by the planner).

I am still not sure why the plane is not achieving the alt change, the climb rate demanded is consistent with the planner settings (climb_angle_deg), so I'd have thought that provided TECS_CLMB_MAX was greater than this, it should be satisfied.

@Ryanf55
Copy link
Contributor

Ryanf55 commented Apr 25, 2025

Seems like planner start/goal are hard to see over large plans. Over a large area, when you zoom out, they disappear. Meanwhile, for a mission, the circles are fixed pixel size so they are always visible. Not critical, just a small enhancement opportunity.

image

@srmainwaring
Copy link
Contributor Author

Seems like planner start/goal are hard to see over large plans.

This is because they are sized at the loiter radius, rather than as general markers. The reasoning is that this lets you see how the initial and final loiters will fit in between terrain contours. We could add an additional symbol that scales with the map zoom?

- Add WP generator that uses MAV_CMD_NAV_LOITER_TO_ALT
- Update window size
- Use wxsettings module for dialog.
- Ensure dialog retrieves settings from correct thread.
- Add setting callback, ensure the planner is updated when a setting changes.
- Remove duplicated setting update code.
- Add home, start loiter and goal loiter waypoints
  - Add waypoint at home position
  - Add start and goal loiter mission items.
  - Calculate start and goal loiter directions.
- Remove waypoint at loiter exit
  - Extra waypoint on loiter radius results in a full turn as heading is not achieved.
- Correct simple waypoint count
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants