Article
path-planningroboticsa-star-searchpythonautonomous-systemsgrid-mapnavigation
Implement A* Path Planning for a 2D Grid
Learn the fundamentals of robotic Path Planning by implementing the A* search algorithm from scratch. This guide provides a complete Python script to find the shortest path on a 2D grid map, a core skill for autonomous navigation.
intermediate30 min4 steps
The play
- Define the Environment and Data StructuresRepresent the robot's world as a 2D grid using NumPy. '0' denotes free space and '1' denotes obstacles. A* Path Planning requires a priority queue (open list) to efficiently select the next node to visit based on cost.
- Define the Heuristic FunctionThe heuristic function estimates the cost from the current node to the goal. For grid-based Path Planning, the Manhattan distance (`|x1-x2| + |y1-y2|`) is a common and effective choice that ensures A* finds an optimal path.
- Implement the A* Search AlgorithmThe core of A* Path Planning. It iteratively expands nodes with the lowest 'f-cost' (g-cost + h-cost). 'g-cost' is the known cost from the start, and 'h-cost' is the heuristic estimate to the goal. This loop continues until the goal is found or there are no more nodes to explore.
- Reconstruct and Display the PathAfter the A* search successfully finds the goal, trace back from the goal to the start using the 'came_from' dictionary. This creates the final sequence of coordinates that constitutes the optimal path.
Starter code
import numpy as np
import heapq
import matplotlib.pyplot as plt
def heuristic(a, b):
"""Calculates the Manhattan distance between two points (nodes)."""
return abs(a[0] - b[0]) + abs(a[1] - b[1])
def a_star_search(grid, start, goal):
"""Performs A* Path Planning on a grid."""
neighbors_options = [(0, 1), (0, -1), (1, 0), (-1, 0)] # 4-directional
close_set = set()
came_from = {}
gscore = {start: 0}
fscore = {start: heuristic(start, goal)}
oheap = []
heapq.heappush(oheap, (fscore[start], start))
while oheap:
current = heapq.heappop(oheap)[1]
if current == goal:
path = []
while current in came_from:
path.append(current)
current = came_from[current]
path.append(start)
return path[::-1]
close_set.add(current)
for i, j in neighbors_options:
neighbor = current[0] + i, current[1] + j
if not (0 <= neighbor[0] < grid.shape[0] and 0 <= neighbor[1] < grid.shape[1]):
continue
if grid[neighbor[0]][neighbor[1]] == 1:
continue
tentative_g_score = gscore[current] + 1
if neighbor in close_set and tentative_g_score >= gscore.get(neighbor, 0):
continue
if tentative_g_score < gscore.get(neighbor, 0) or neighbor not in [i[1] for i in oheap]:
came_from[neighbor] = current
gscore[neighbor] = tentative_g_score
fscore[neighbor] = tentative_g_score + heuristic(neighbor, goal)
heapq.heappush(oheap, (fscore[neighbor], neighbor))
return False # No path found
# --- Main execution ---
if __name__ == '__main__':
# Define the grid: 0 = free space, 1 = obstacle
grid = np.array([
[0, 0, 0, 0, 1, 0, 0, 0],
[0, 1, 0, 0, 1, 0, 1, 0],
[0, 1, 0, 0, 0, 0, 1, 0],
[0, 0, 0, 1, 1, 0, 1, 0],
[0, 1, 0, 0, 0, 0, 0, 0],
[0, 1, 1, 1, 1, 1, 1, 0],
[0, 0, 0, 0, 0, 0, 0, 0]
])
start = (0, 0)
goal = (6, 7)
path = a_star_search(grid, start, goal)
if path:
print(f"Path found from {start} to {goal}:")
print(path)
# Visualization
path_grid = np.copy(grid).astype(float)
for p in path:
path_grid[p] = 0.5 # Mark path with a different value
path_grid[start] = 0.2 # Mark start
path_grid[goal] = 0.8 # Mark goal
plt.imshow(path_grid, cmap='viridis', interpolation='nearest')
plt.title('A* Path Planning Result')
plt.xticks(np.arange(grid.shape[1]))
plt.yticks(np.arange(grid.shape[0]))
plt.grid(True, which='both', color='white', linewidth=0.5)
plt.show()
else:
print(f"No path found from {start} to {goal}.")