Pathsolvers
Generic A*
astar_search is the low-level A* implementation shared by GraphPlanner and grid_astar.
It works on any hashable node type — you can use it directly to build a custom planner.
See Using astar_search directly for a worked example.
map_data.pathsolver.astar.astar_search(start_node, goal_node, get_neighbors_func, heuristic_func)
Search a graph using the A* algorithm.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
start_node
|
object
|
The starting node. |
required |
goal_node
|
object
|
The goal node. |
required |
get_neighbors_func
|
callable
|
A function that takes a node and returns an iterable of (neighbor, cost) tuples. |
required |
heuristic_func
|
callable
|
A function that takes a node and returns the estimated cost to the goal. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
path |
list or None
|
The path from start to goal as a list of nodes, or None if no path exists. |
Source code in map_data/pathsolver/astar.py
Graph Planner
Graph-based path planner that routes along the OSM road and footway network.
Builds an undirected weighted graph from the ways stored in a
:class:~map_data.map_data.MapData instance. Manually annotated paths
(negative-ID ways) are spliced into the graph by projecting their
endpoints onto the nearest OSM edge, so annotations extend the
traversable network seamlessly.
Planning is performed with A* (see :meth:plan). The planner operates
entirely in UTM coordinates.
map_data.pathsolver.graph_planner.GraphPlanner.__init__(map_data, highway_types=None)
Initialize the graph planner.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
map_data
|
MapData
|
Parsed map data containing the OSM ways and node cache. |
required |
highway_types
|
list of str
|
Way categories to include in the graph. Supported values are
|
None
|
Source code in map_data/pathsolver/graph_planner.py
map_data.pathsolver.graph_planner.GraphPlanner.plan(path_utm)
Plan a path through a sequence of UTM waypoints along the graph.
Each consecutive pair of waypoints is routed independently. The waypoints are snapped to the nearest graph edge before planning, so they do not need to lie exactly on the network.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path_utm
|
ndarray
|
Array of shape |
required |
Returns:
| Type | Description |
|---|---|
ndarray or None
|
Concatenated path as an |
Source code in map_data/pathsolver/graph_planner.py
249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 | |
map_data.pathsolver.graph_planner.GraphPlanner.a_star(start_node, goal_node, extra_nodes=None)
Run A* between two graph nodes and return the path as UTM coordinates.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
start_node
|
int or str
|
ID of the start node in the graph. |
required |
goal_node
|
int or str
|
ID of the goal node in the graph. |
required |
extra_nodes
|
dict
|
Temporary adjacency entries and positions for nodes not
permanently in the graph (e.g. snapped projection points).
Expected keys: |
None
|
Returns:
| Type | Description |
|---|---|
list of np.ndarray or None
|
Sequence of |
Source code in map_data/pathsolver/graph_planner.py
Grid A*
map_data.pathsolver.grid_astar.grid_astar(grid, start_utm, goal_utm, low, cs, *, simplify_path=True, grid_cost_weight=GRID_COST_WEIGHT)
Optimized A* search on a 2D grid.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
grid
|
ndarray
|
2D grid of costs (Y, X). |
required |
start_utm
|
tuple or ndarray
|
Starting point in UTM coordinates. |
required |
goal_utm
|
tuple or ndarray
|
Goal point in UTM coordinates. |
required |
low
|
tuple
|
|
required |
cs
|
float
|
Cell size of the grid in metres. |
required |
simplify_path
|
bool
|
Whether to simplify the resulting path with Douglas-Peucker. |
True
|
Returns:
| Type | Description |
|---|---|
ndarray or None
|
Found path as UTM coordinate array, or |
Source code in map_data/pathsolver/grid_astar.py
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 | |
RRT*
Rapidly-exploring Random Tree Star (RRT*) path planner.
Builds a collision-free tree by randomly sampling the free space and rewiring edges to minimise path cost. The planner is asymptotically optimal: given enough iterations it converges to the shortest feasible path.
Collision checking uses two sources simultaneously:
- A Shapely STRtree of barrier polygons (hard obstacles).
- A 2-D cost grid where cells at or above traversability_threshold are treated as blocked.
Traversable cells contribute a weighted cost to the edge cost, so the planner naturally prefers low-cost corridors (e.g. footways) over open terrain.
map_data.pathsolver.rrt_star.RRTStar.__init__(start, goal, obstacles, obstacles_tree, grid, low, grid_scale=1.0, max_iter=2000, step_size=2.0, neighbor_radius=5.0, traversability_threshold=10.0, grid_cost_weight=GRID_COST_WEIGHT, *, simplify=True, transfer_id=None, improve_after_goal=False, informed=True, adaptive_radius=True)
Initialize the RRT* planner.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
start
|
ndarray
|
Starting position as a 2-element array |
required |
goal
|
ndarray
|
Goal position as a 2-element array |
required |
obstacles
|
list
|
List of Shapely geometries representing hard barriers. Used together with obstacles_tree for fast spatial queries. |
required |
obstacles_tree
|
STRtree or None
|
Pre-built Shapely STRtree index over obstacles. Pass |
required |
grid
|
ndarray
|
2-D cost array with shape |
required |
low
|
tuple of float
|
|
required |
grid_scale
|
float
|
Metres per grid cell (default |
1.0
|
max_iter
|
int
|
Maximum number of RRT* iterations (default |
2000
|
step_size
|
float
|
Maximum distance the tree extends toward a sampled point per
iteration in metres (default |
2.0
|
neighbor_radius
|
float
|
Radius in metres within which nearby nodes are considered for
rewiring (default |
5.0
|
traversability_threshold
|
float
|
Grid cost at which a cell is considered an obstacle
(default |
10.0
|
simplify
|
bool
|
If |
True
|
transfer_id
|
str or None
|
Optional identifier used to check for external cancellation
signals during planning. Pass |
None
|
improve_after_goal
|
bool
|
If |
False
|
Source code in map_data/pathsolver/rrt_star.py
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 | |
map_data.pathsolver.rrt_star.RRTStar.find_path()
Run the RRT* algorithm and return the planned path.
Iterates up to max_iter times, growing the tree from start
toward randomly sampled points and rewiring edges to reduce cost.
The goal is sampled directly 10 % of the time to encourage
convergence.
Returns:
| Type | Description |
|---|---|
ndarray or None
|
Path as an |
Source code in map_data/pathsolver/rrt_star.py
416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 | |
map_data.pathsolver.rrt_star.RRTStar._is_collision(point1, point2=None)
Return True if a point or segment intersects an obstacle.
Checks both the Shapely obstacle polygons (via STRtree) and the cost grid (via Bresenham rasterisation). A point is in collision if its grid cost meets or exceeds traversability_threshold; a segment is in collision if any traversed cell does.
Source code in map_data/pathsolver/rrt_star.py
map_data.pathsolver.rrt_star.RRTStar._sample_point()
Sample a random point.
When a solution exists and informed is enabled, samples uniformly from the informed ellipse. Otherwise biases toward traversable grid cells 90 % of the time.
Source code in map_data/pathsolver/rrt_star.py
map_data.pathsolver.rrt_star.RRTStar._nearest_node(point)
Return the index of the tree node closest to point.
Uses a lazily rebuilt KD-tree for the bulk of the tree, plus a linear scan over nodes added since the last rebuild.
Source code in map_data/pathsolver/rrt_star.py
map_data.pathsolver.rrt_star.RRTStar._steer(start, target)
Return a point at most step_size metres from start toward target.
Source code in map_data/pathsolver/rrt_star.py
map_data.pathsolver.rrt_star.RRTStar._get_near_nodes(new_point, radius=None)
Return indices of all tree nodes within radius of new_point.
radius defaults to self.neighbor_radius when not supplied.
Source code in map_data/pathsolver/rrt_star.py
map_data.pathsolver.rrt_star.RRTStar._segment_cost(start, end)
Compute the cost of the segment from start to end.
Returns:
| Type | Description |
|---|---|
tuple of (bool, float)
|
|
Source code in map_data/pathsolver/rrt_star.py
map_data.pathsolver.rrt_star.RRTStar._reconstruct_path(goal_idx)
Walk the parent chain from goal_idx back to the root and return the path.
Source code in map_data/pathsolver/rrt_star.py
map_data.pathsolver.rrt_star.RRTStar._simplify_path(path)
Greedily remove intermediate waypoints that have line-of-sight to a later node.
Source code in map_data/pathsolver/rrt_star.py
ReplanPath
ReplanPath is the local replanning engine used by the viewer's Planner mode and the
replan CLI tool. It discretizes the area around the input waypoints into a cost grid,
assigns traversal costs based on OSM highway and surface types, then finds a collision-free
path using either Grid A or RRT.
Class attributes
These cost tables are loaded from config/planner_defaults.yaml at import time and can be
overridden per-instance:
| Attribute | Type | Description |
|---|---|---|
HIGHWAY_COSTS |
dict[str, float] |
Per OSM highway value cost (0.0 = free, 1.0 = obstacle) |
SURFACE_COSTS |
dict[str, float] |
Extra penalty per OSM surface value |
DEFAULT_OFF_PATH_COST |
float |
Cost for cells not near any known way (default 0.9) |
PATH_COST_CAP |
float |
Maximum cost a way cell can have (default 0.85, keeps ways preferred over off-path) |
Constructor
| Parameter | Type | Description |
|---|---|---|
args |
argparse.Namespace |
Planning parameters. Use parse_args([]) to get defaults. |
obstacles |
list[shapely.Geometry] |
Obstacle geometries (from ways_to_shapely(md.barriers_list)). |
transfer_id |
str \| None |
Optional UUID for cancellation via cancel_replan_backend(). |
args attributes used by ReplanPath:
| Attribute | Default | Description |
|---|---|---|
low |
— | (min_x, min_y) lower bound of the planning area in UTM metres |
high |
— | (max_x, max_y) upper bound of the planning area in UTM metres |
cell_size |
0.25 |
Grid resolution in metres |
inflate_obstacles |
0.25 |
Buffer added to obstacle geometries in metres |
simplify_path |
True |
Apply Douglas-Peucker simplification to the output |
smooth_path |
False |
Apply gradient-descent smoothing after planning |
Methods
fill_grid(map_data, highway_types=None, max_path_dist=2.0)
Populate the cost grid from map data. Must be called before replan().
| Parameter | Default | Description |
|---|---|---|
map_data |
— | A loaded MapData object |
highway_types |
["footway"] |
Which way categories to use. Pass ["footway", "road"] to include roads. |
max_path_dist |
2.0 |
Cells within this distance (m) of a known way receive an interpolated cost. Cells beyond receive DEFAULT_OFF_PATH_COST. |
Cost model per cell: way_cost + (DEFAULT_OFF_PATH_COST - way_cost) × (dist / max_path_dist)², where way_cost = min(PATH_COST_CAP, HIGHWAY_COSTS[highway] + SURFACE_COSTS[surface]). Obstacle cells are set to inf.
replan(path, algorithm="astar")
Plan a path through the cost grid.
| Parameter | Description |
|---|---|
path |
np.ndarray of shape (N, 2+) — input waypoints in UTM metres |
algorithm |
"astar" (Grid A) or "rrt" (RRT) |
Returns np.ndarray (replanned path), None (cancelled), or False (no path found).
Segments between consecutive waypoints are processed in parallel via joblib.
visualize(path, old_path=None)
Save a matplotlib debug plot as replan.png showing the cost grid, obstacles, and path.
Cancellation
To cancel a running replan() call from another thread, pass a transfer_id UUID to the
constructor and call:
Minimal usage example
from map_data.map_data import MapData
from map_data.pathsolver.replan import ReplanPath, parse_args
from map_data.utils.parsing import ways_to_shapely
from map_data.utils.gpx import parse_path
md = MapData.load("coords.mapdata")
path_data = parse_path("waypoints.gpx") # (utm_array, zone_num, zone_let)
args = parse_args([])
args.low = (md.min_x, md.min_y)
args.high = (md.max_x, md.max_y)
replanner = ReplanPath(args, ways_to_shapely(md.barriers_list))
replanner.fill_grid(md, highway_types=["footway"], max_path_dist=2.0)
new_path = replanner.replan(path_data[0], algorithm="astar")