Skip to content

Commit 2b6eb67

Browse files
authored
Merge pull request #34 from fmrico/improve_astar_costmap
Improve efficiency in A*
2 parents 6f19f31 + 75be507 commit 2b6eb67

1 file changed

Lines changed: 67 additions & 10 deletions

File tree

planners/easynav_costmap_planner/src/easynav_costmap_planner/CostmapPlanner.cpp

Lines changed: 67 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,38 @@ void CostmapPlanner::update(NavState & nav_state)
141141

142142
current_goal_ = goal;
143143

144+
// Lightweight skip: if inputs unchanged, avoid recomputation for a short window
145+
static int last_sx = -1;
146+
static int last_sy = -1;
147+
static geometry_msgs::msg::Pose last_goal_pose;
148+
static rclcpp::Time last_plan_time;
149+
150+
unsigned int sx_chk, sy_chk;
151+
if (map.worldToMap(robot_pose.pose.pose.position.x, robot_pose.pose.pose.position.y, sx_chk,
152+
sy_chk))
153+
{
154+
const bool same_start_cell = (static_cast<int>(sx_chk) == last_sx) &&
155+
(static_cast<int>(sy_chk) == last_sy);
156+
const bool same_goal_pose = (
157+
std::fabs(goal.position.x - last_goal_pose.position.x) < 1e-6 &&
158+
std::fabs(goal.position.y - last_goal_pose.position.y) < 1e-6 &&
159+
goal.orientation.x == last_goal_pose.orientation.x &&
160+
goal.orientation.y == last_goal_pose.orientation.y &&
161+
goal.orientation.z == last_goal_pose.orientation.z &&
162+
goal.orientation.w == last_goal_pose.orientation.w);
163+
164+
// Initialize last_plan_time on first use to current node time
165+
if (last_plan_time.nanoseconds() == 0) {
166+
last_plan_time = get_node()->now();
167+
}
168+
const double since_last = (get_node()->now() - last_plan_time).seconds();
169+
if (continuous_replan_ && same_start_cell && same_goal_pose && since_last < 0.05) {
170+
// Skip re-planning when nothing relevant changed recently
171+
nav_state.set("path", current_path_);
172+
return;
173+
}
174+
}
175+
144176
auto poses = a_star_path(map, robot_pose.pose.pose, goal);
145177
if (!poses.empty()) {
146178
current_path_.header.stamp = get_node()->now();
@@ -152,9 +184,22 @@ void CostmapPlanner::update(NavState & nav_state)
152184
pose_stamped.pose = pose;
153185
current_path_.poses.push_back(pose_stamped);
154186
}
155-
if (path_pub_->get_subscription_count() > 0) {
156-
path_pub_->publish(current_path_);
187+
// Publish only when the content changed (size as a cheap proxy)
188+
static size_t last_published_size = 0;
189+
if (current_path_.poses.size() != last_published_size) {
190+
if (path_pub_->get_subscription_count() > 0) {
191+
path_pub_->publish(current_path_);
192+
}
193+
last_published_size = current_path_.poses.size();
194+
}
195+
// Update last inputs snapshot
196+
unsigned int sx, sy;
197+
if (map.worldToMap(robot_pose.pose.pose.position.x, robot_pose.pose.pose.position.y, sx, sy)) {
198+
last_sx = static_cast<int>(sx);
199+
last_sy = static_cast<int>(sy);
157200
}
201+
last_goal_pose = goal;
202+
last_plan_time = get_node()->now();
158203
}
159204
nav_state.set("path", current_path_);
160205
}
@@ -169,11 +214,19 @@ std::vector<geometry_msgs::msg::Pose> CostmapPlanner::a_star_path(
169214
if (!map.worldToMap(goal.position.x, goal.position.y, gx, gy)) {return {};}
170215

171216
int width = map.getSizeInCellsX();
217+
// Precompute constants used inside the neighbor loop
218+
const double lethal_norm = 1.0 / static_cast<double>(LETHAL_OBSTACLE);
219+
const double axial_cost = cost_axial_;
220+
const double diagonal_cost = cost_diagonal_;
172221
auto idx = [&](int x, int y) {return y * width + x;};
173222

174223
std::priority_queue<GridNode, std::vector<GridNode>, std::greater<>> open;
175-
std::unordered_map<int, std::pair<int, int>> came_from;
176-
std::unordered_map<int, double> cost_so_far;
224+
225+
const int height = map.getSizeInCellsY();
226+
const int total_cells = width * height;
227+
std::vector<int> parent_x(total_cells, -1);
228+
std::vector<int> parent_y(total_cells, -1);
229+
std::vector<double> cost_so_far(total_cells, std::numeric_limits<double>::infinity());
177230

178231
open.push(GridNode{static_cast<int>(sx), static_cast<int>(sy), 0.0, heuristic(sx, sy, gx, gy)});
179232
cost_so_far[idx(sx, sy)] = 0.0;
@@ -193,31 +246,35 @@ std::vector<geometry_msgs::msg::Pose> CostmapPlanner::a_star_path(
193246
if (cell_cost >= INSCRIBED_INFLATED_OBSTACLE) {continue;}
194247

195248
// Calculate traversal cost for free and lightly inflated cells
196-
double traversal_cost = 1.0 + cost_factor_ * static_cast<double>(cell_cost) / LETHAL_OBSTACLE;
249+
double traversal_cost = 1.0 + cost_factor_ * static_cast<double>(cell_cost) * lethal_norm;
197250

198-
double step_cost = (dx == 0 || dy == 0) ? cost_axial_ : cost_diagonal_;
251+
double step_cost = (dx == 0 || dy == 0) ? axial_cost : diagonal_cost;
199252
double new_cost = cost_so_far[idx(current.x, current.y)] + traversal_cost * step_cost;
200253
int nid = idx(nx, ny);
201254

202-
if (!cost_so_far.contains(nid) || new_cost < cost_so_far[nid]) {
255+
if (new_cost < cost_so_far[nid]) {
203256
cost_so_far[nid] = new_cost;
204257
open.push(GridNode{nx, ny, new_cost, new_cost + heuristic(nx, ny, gx, gy)});
205-
came_from[nid] = {current.x, current.y};
258+
parent_x[nid] = current.x;
259+
parent_y[nid] = current.y;
206260
}
207261
}
208262
}
209263

210264
std::vector<geometry_msgs::msg::Pose> path;
211265
int cx = static_cast<int>(gx), cy = static_cast<int>(gy);
212-
while (came_from.contains(idx(cx, cy))) {
266+
while (parent_x[idx(cx, cy)] != -1) {
213267
double wx, wy;
214268
map.mapToWorld(cx, cy, wx, wy);
215269
geometry_msgs::msg::Pose pose;
216270
pose.position.x = wx;
217271
pose.position.y = wy;
218272
pose.orientation = goal.orientation;
219273
path.push_back(pose);
220-
std::tie(cx, cy) = came_from[idx(cx, cy)];
274+
int px = parent_x[idx(cx, cy)];
275+
int py = parent_y[idx(cx, cy)];
276+
cx = px;
277+
cy = py;
221278
}
222279
std::reverse(path.begin(), path.end());
223280

0 commit comments

Comments
 (0)