dwb_local_planner.cpp
Go to the documentation of this file.
00001 /*
00002  * Software License Agreement (BSD License)
00003  *
00004  *  Copyright (c) 2017, Locus Robotics
00005  *  All rights reserved.
00006  *
00007  *  Redistribution and use in source and binary forms, with or without
00008  *  modification, are permitted provided that the following conditions
00009  *  are met:
00010  *
00011  *   * Redistributions of source code must retain the above copyright
00012  *     notice, this list of conditions and the following disclaimer.
00013  *   * Redistributions in binary form must reproduce the above
00014  *     copyright notice, this list of conditions and the following
00015  *     disclaimer in the documentation and/or other materials provided
00016  *     with the distribution.
00017  *   * Neither the name of the copyright holder nor the names of its
00018  *     contributors may be used to endorse or promote products derived
00019  *     from this software without specific prior written permission.
00020  *
00021  *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00022  *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00023  *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
00024  *  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
00025  *  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
00026  *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
00027  *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00028  *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
00029  *  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00030  *  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
00031  *  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00032  *  POSSIBILITY OF SUCH DAMAGE.
00033  */
00034 
00035 #include <nav_core2/exceptions.h>
00036 #include <dwb_local_planner/dwb_local_planner.h>
00037 #include <dwb_local_planner/backwards_compatibility.h>
00038 #include <dwb_local_planner/illegal_trajectory_tracker.h>
00039 #include <nav_2d_utils/conversions.h>
00040 #include <nav_2d_utils/tf_help.h>
00041 #include <nav_2d_msgs/Twist2D.h>
00042 #include <dwb_msgs/CriticScore.h>
00043 #include <pluginlib/class_list_macros.h>
00044 #include <string>
00045 #include <vector>
00046 #include <algorithm>
00047 
00048 namespace dwb_local_planner
00049 {
00050 
00051 DWBLocalPlanner::DWBLocalPlanner() :
00052   traj_gen_loader_("dwb_local_planner", "dwb_local_planner::TrajectoryGenerator"),
00053   goal_checker_loader_("dwb_local_planner", "dwb_local_planner::GoalChecker"),
00054   critic_loader_("dwb_local_planner", "dwb_local_planner::TrajectoryCritic")
00055 {
00056 }
00057 
00058 void DWBLocalPlanner::initialize(const ros::NodeHandle& parent, const std::string& name,
00059                                  TFListenerPtr tf, nav_core2::Costmap::Ptr costmap)
00060 {
00061   tf_ = tf;
00062   costmap_ = costmap;
00063   planner_nh_ = ros::NodeHandle(parent, name);
00064 
00065   // This is needed when using the CostmapAdapter to ensure that the costmap's info matches the rolling window
00066   planner_nh_.param("update_costmap_before_planning", update_costmap_before_planning_, true);
00067 
00068   planner_nh_.param("prune_plan", prune_plan_, true);
00069   planner_nh_.param("prune_distance", prune_distance_, 1.0);
00070   planner_nh_.param("short_circuit_trajectory_evaluation", short_circuit_trajectory_evaluation_, true);
00071   planner_nh_.param("debug_trajectory_details", debug_trajectory_details_, false);
00072   pub_.initialize(planner_nh_);
00073 
00074   // Plugins
00075   std::string traj_generator_name;
00076   planner_nh_.param("trajectory_generator_name", traj_generator_name,
00077                     getBackwardsCompatibleDefaultGenerator(planner_nh_));
00078   ROS_INFO_NAMED("DWBLocalPlanner", "Using Trajectory Generator \"%s\"", traj_generator_name.c_str());
00079   traj_generator_ = std::move(traj_gen_loader_.createUniqueInstance(traj_generator_name));
00080   traj_generator_->initialize(planner_nh_);
00081 
00082   std::string goal_checker_name;
00083   planner_nh_.param("goal_checker_name", goal_checker_name, std::string("dwb_plugins::SimpleGoalChecker"));
00084   ROS_INFO_NAMED("DWBLocalPlanner", "Using Goal Checker \"%s\"", goal_checker_name.c_str());
00085   goal_checker_ = std::move(goal_checker_loader_.createUniqueInstance(goal_checker_name));
00086   goal_checker_->initialize(planner_nh_);
00087 
00088   loadCritics(name);
00089 }
00090 
00091 std::string DWBLocalPlanner::resolveCriticClassName(std::string base_name)
00092 {
00093   if (base_name.find("Critic") == std::string::npos)
00094   {
00095     base_name = base_name + "Critic";
00096   }
00097 
00098   if (base_name.find("::") == std::string::npos)
00099   {
00100     for (unsigned int j = 0; j < default_critic_namespaces_.size(); j++)
00101     {
00102       std::string full_name = default_critic_namespaces_[j] + "::" + base_name;
00103       if (critic_loader_.isClassAvailable(full_name))
00104       {
00105         return full_name;
00106       }
00107     }
00108   }
00109   return base_name;
00110 }
00111 
00112 void DWBLocalPlanner::loadCritics(const std::string name)
00113 {
00114   planner_nh_.param("default_critic_namespaces", default_critic_namespaces_);
00115   if (default_critic_namespaces_.size() == 0)
00116   {
00117     default_critic_namespaces_.push_back("dwb_critics");
00118   }
00119 
00120   if (!planner_nh_.hasParam("critics"))
00121   {
00122     loadBackwardsCompatibleParameters(planner_nh_);
00123   }
00124 
00125   std::vector<std::string> critic_names;
00126   planner_nh_.getParam("critics", critic_names);
00127   for (unsigned int i = 0; i < critic_names.size(); i++)
00128   {
00129     std::string plugin_name = critic_names[i];
00130     std::string plugin_class;
00131     planner_nh_.param(plugin_name + "/class", plugin_class, plugin_name);
00132     plugin_class = resolveCriticClassName(plugin_class);
00133 
00134     TrajectoryCritic::Ptr plugin = std::move(critic_loader_.createUniqueInstance(plugin_class));
00135     ROS_INFO_NAMED("DWBLocalPlanner", "Using critic \"%s\" (%s)", plugin_name.c_str(), plugin_class.c_str());
00136     critics_.push_back(plugin);
00137     plugin->initialize(planner_nh_, plugin_name, costmap_);
00138   }
00139 }
00140 
00141 bool DWBLocalPlanner::isGoalReached(const nav_2d_msgs::Pose2DStamped& pose, const nav_2d_msgs::Twist2D& velocity)
00142 {
00143   if (goal_pose_.header.frame_id == "")
00144   {
00145     ROS_WARN_NAMED("DWBLocalPlanner", "Cannot check if the goal is reached without the goal being set!");
00146     return false;
00147   }
00148 
00149   // Update time stamp of goal pose
00150   goal_pose_.header.stamp = pose.header.stamp;
00151 
00152   bool ret = goal_checker_->isGoalReached(transformPoseToLocal(pose), transformPoseToLocal(goal_pose_), velocity);
00153   if (ret)
00154   {
00155     ROS_INFO_THROTTLE_NAMED(1.0, "DWBLocalPlanner", "Goal reached!");
00156   }
00157   return ret;
00158 }
00159 
00160 void DWBLocalPlanner::setGoalPose(const nav_2d_msgs::Pose2DStamped& goal_pose)
00161 {
00162   ROS_INFO_NAMED("DWBLocalPlanner", "New Goal Received.");
00163   goal_pose_ = goal_pose;
00164   traj_generator_->reset();
00165   goal_checker_->reset();
00166   for (TrajectoryCritic::Ptr critic : critics_)
00167   {
00168     critic->reset();
00169   }
00170 }
00171 
00172 void DWBLocalPlanner::setPlan(const nav_2d_msgs::Path2D& path)
00173 {
00174   pub_.publishGlobalPlan(path);
00175   global_plan_ = path;
00176 }
00177 
00178 nav_2d_msgs::Twist2DStamped DWBLocalPlanner::computeVelocityCommands(const nav_2d_msgs::Pose2DStamped& pose,
00179                                                                      const nav_2d_msgs::Twist2D& velocity)
00180 {
00181   std::shared_ptr<dwb_msgs::LocalPlanEvaluation> results = nullptr;
00182   if (pub_.shouldRecordEvaluation())
00183   {
00184     results = std::make_shared<dwb_msgs::LocalPlanEvaluation>();
00185   }
00186 
00187   try
00188   {
00189     nav_2d_msgs::Twist2DStamped cmd_vel = computeVelocityCommands(pose, velocity, results);
00190     pub_.publishEvaluation(results);
00191     return cmd_vel;
00192   }
00193   catch (const nav_core2::PlannerException& e)
00194   {
00195     pub_.publishEvaluation(results);
00196     throw;
00197   }
00198 }
00199 
00200 void DWBLocalPlanner::prepare(const nav_2d_msgs::Pose2DStamped& pose, const nav_2d_msgs::Twist2D& velocity)
00201 {
00202   if (update_costmap_before_planning_)
00203   {
00204     costmap_->update();
00205   }
00206 
00207   nav_2d_msgs::Path2D transformed_plan = transformGlobalPlan(pose);
00208   pub_.publishTransformedPlan(transformed_plan);
00209 
00210   // Update time stamp of goal pose
00211   goal_pose_.header.stamp = pose.header.stamp;
00212 
00213   geometry_msgs::Pose2D local_start_pose = transformPoseToLocal(pose),
00214                         local_goal_pose = transformPoseToLocal(goal_pose_);
00215 
00216   pub_.publishInputParams(costmap_->getInfo(), local_start_pose, velocity, local_goal_pose);
00217 
00218   for (TrajectoryCritic::Ptr critic : critics_)
00219   {
00220     if (!critic->prepare(local_start_pose, velocity, local_goal_pose, transformed_plan))
00221     {
00222       ROS_WARN_NAMED("DWBLocalPlanner", "Critic \"%s\" failed to prepare", critic->getName().c_str());
00223     }
00224   }
00225 }
00226 
00227 nav_2d_msgs::Twist2DStamped DWBLocalPlanner::computeVelocityCommands(const nav_2d_msgs::Pose2DStamped& pose,
00228     const nav_2d_msgs::Twist2D& velocity, std::shared_ptr<dwb_msgs::LocalPlanEvaluation>& results)
00229 {
00230   if (results)
00231   {
00232     results->header.frame_id = pose.header.frame_id;
00233     results->header.stamp = ros::Time::now();
00234   }
00235 
00236   prepare(pose, velocity);
00237 
00238   try
00239   {
00240     dwb_msgs::TrajectoryScore best = coreScoringAlgorithm(pose.pose, velocity, results);
00241 
00242     // Return Value
00243     nav_2d_msgs::Twist2DStamped cmd_vel;
00244     cmd_vel.header.stamp = ros::Time::now();
00245     cmd_vel.velocity = best.traj.velocity;
00246 
00247     // debrief stateful critics
00248     for (TrajectoryCritic::Ptr critic : critics_)
00249     {
00250       critic->debrief(cmd_vel.velocity);
00251     }
00252 
00253     pub_.publishLocalPlan(pose.header, best.traj);
00254     pub_.publishCostGrid(costmap_, critics_);
00255 
00256     return cmd_vel;
00257   }
00258   catch (const NoLegalTrajectoriesException& e)
00259   {
00260     nav_2d_msgs::Twist2D empty_cmd;
00261     dwb_msgs::Trajectory2D empty_traj;
00262     // debrief stateful scoring functions
00263     for (TrajectoryCritic::Ptr critic : critics_)
00264     {
00265       critic->debrief(empty_cmd);
00266     }
00267     pub_.publishLocalPlan(pose.header, empty_traj);
00268     pub_.publishCostGrid(costmap_, critics_);
00269 
00270     throw;
00271   }
00272 }
00273 
00274 dwb_msgs::TrajectoryScore DWBLocalPlanner::coreScoringAlgorithm(const geometry_msgs::Pose2D& pose,
00275                                                                 const nav_2d_msgs::Twist2D velocity,
00276                                                                 std::shared_ptr<dwb_msgs::LocalPlanEvaluation>& results)
00277 {
00278   nav_2d_msgs::Twist2D twist;
00279   dwb_msgs::Trajectory2D traj;
00280   dwb_msgs::TrajectoryScore best, worst;
00281   best.total = -1;
00282   worst.total = -1;
00283   IllegalTrajectoryTracker tracker;
00284 
00285   traj_generator_->startNewIteration(velocity);
00286   while (traj_generator_->hasMoreTwists())
00287   {
00288     twist = traj_generator_->nextTwist();
00289     traj = traj_generator_->generateTrajectory(pose, velocity, twist);
00290 
00291     try
00292     {
00293       dwb_msgs::TrajectoryScore score = scoreTrajectory(traj, best.total);
00294       tracker.addLegalTrajectory();
00295       if (results)
00296       {
00297         results->twists.push_back(score);
00298       }
00299       if (best.total < 0 || score.total < best.total)
00300       {
00301         best = score;
00302         if (results)
00303         {
00304           results->best_index = results->twists.size() - 1;
00305         }
00306       }
00307       if (worst.total < 0 || score.total > worst.total)
00308       {
00309         worst = score;
00310         if (results)
00311         {
00312           results->worst_index = results->twists.size() - 1;
00313         }
00314       }
00315     }
00316     catch (const nav_core2::IllegalTrajectoryException& e)
00317     {
00318       if (results)
00319       {
00320         dwb_msgs::TrajectoryScore failed_score;
00321         failed_score.traj = traj;
00322 
00323         dwb_msgs::CriticScore cs;
00324         cs.name = e.getCriticName();
00325         cs.raw_score = -1.0;
00326         failed_score.scores.push_back(cs);
00327         failed_score.total = -1.0;
00328         results->twists.push_back(failed_score);
00329       }
00330       tracker.addIllegalTrajectory(e);
00331     }
00332   }
00333 
00334   if (best.total < 0)
00335   {
00336     if (debug_trajectory_details_)
00337     {
00338       ROS_ERROR_NAMED("DWBLocalPlanner", "%s", tracker.getMessage().c_str());
00339       for (auto const& x : tracker.getPercentages())
00340       {
00341         ROS_ERROR_NAMED("DWBLocalPlanner", "%.2f: %10s/%s", x.second, x.first.first.c_str(), x.first.second.c_str());
00342       }
00343     }
00344     throw NoLegalTrajectoriesException(tracker);
00345   }
00346 
00347   return best;
00348 }
00349 
00350 dwb_msgs::TrajectoryScore DWBLocalPlanner::scoreTrajectory(const dwb_msgs::Trajectory2D& traj,
00351                                                            double best_score)
00352 {
00353   dwb_msgs::TrajectoryScore score;
00354   score.traj = traj;
00355 
00356   for (TrajectoryCritic::Ptr critic : critics_)
00357   {
00358     dwb_msgs::CriticScore cs;
00359     cs.name = critic->getName();
00360     cs.scale = critic->getScale();
00361 
00362     if (cs.scale == 0.0)
00363     {
00364       score.scores.push_back(cs);
00365       continue;
00366     }
00367 
00368     double critic_score = critic->scoreTrajectory(traj);
00369     cs.raw_score = critic_score;
00370     score.scores.push_back(cs);
00371     score.total += critic_score * cs.scale;
00372     if (short_circuit_trajectory_evaluation_ && best_score > 0 && score.total > best_score)
00373     {
00374       // since we keep adding positives, once we are worse than the best, we will stay worse
00375       break;
00376     }
00377   }
00378 
00379   return score;
00380 }
00381 
00382 double getSquareDistance(const geometry_msgs::Pose2D& pose_a, const geometry_msgs::Pose2D& pose_b)
00383 {
00384   double x_diff = pose_a.x - pose_b.x;
00385   double y_diff = pose_a.y - pose_b.y;
00386   return x_diff * x_diff + y_diff * y_diff;
00387 }
00388 
00389 nav_2d_msgs::Path2D DWBLocalPlanner::transformGlobalPlan(const nav_2d_msgs::Pose2DStamped& pose)
00390 {
00391   nav_2d_msgs::Path2D transformed_plan;
00392   if (global_plan_.poses.size() == 0)
00393   {
00394     throw nav_core2::PlannerException("Received plan with zero length");
00395   }
00396 
00397   // let's get the pose of the robot in the frame of the plan
00398   nav_2d_msgs::Pose2DStamped robot_pose;
00399   if (!nav_2d_utils::transformPose(tf_, global_plan_.header.frame_id, pose, robot_pose))
00400   {
00401     throw nav_core2::PlannerTFException("Unable to transform robot pose into global plan's frame");
00402   }
00403 
00404   transformed_plan.header.frame_id = costmap_->getFrameId();
00405   transformed_plan.header.stamp = pose.header.stamp;
00406 
00407   // we'll discard points on the plan that are outside the local costmap
00408   double dist_threshold = std::max(costmap_->getWidth(), costmap_->getHeight()) * costmap_->getResolution() / 2.0;
00409   double sq_dist_threshold = dist_threshold * dist_threshold;
00410   nav_2d_msgs::Pose2DStamped stamped_pose;
00411   stamped_pose.header.frame_id = global_plan_.header.frame_id;
00412 
00413   for (unsigned int i = 0; i < global_plan_.poses.size(); i++)
00414   {
00415     bool should_break = false;
00416     if (getSquareDistance(robot_pose.pose, global_plan_.poses[i]) > sq_dist_threshold)
00417     {
00418       if (transformed_plan.poses.size() == 0)
00419       {
00420         // we need to skip to a point on the plan that is within a certain distance of the robot
00421         continue;
00422       }
00423       else
00424       {
00425         // we're done transforming points
00426         should_break = true;
00427       }
00428     }
00429 
00430     // now we'll transform until points are outside of our distance threshold
00431     stamped_pose.pose = global_plan_.poses[i];
00432     transformed_plan.poses.push_back(transformPoseToLocal(stamped_pose));
00433     if (should_break) break;
00434   }
00435 
00436   // Prune both plans based on robot position
00437   // Note that this part of the algorithm assumes that the global plan starts near the robot (at one point)
00438   // Otherwise it may take a few iterations to converge to the proper behavior
00439   if (prune_plan_)
00440   {
00441     // let's get the pose of the robot in the frame of the transformed_plan/costmap
00442     nav_2d_msgs::Pose2DStamped costmap_pose;
00443     if (!nav_2d_utils::transformPose(tf_, transformed_plan.header.frame_id, pose, costmap_pose))
00444     {
00445       throw nav_core2::PlannerTFException("Unable to transform robot pose into costmap's frame");
00446     }
00447 
00448     ROS_ASSERT(global_plan_.poses.size() >= transformed_plan.poses.size());
00449     std::vector<geometry_msgs::Pose2D>::iterator it = transformed_plan.poses.begin();
00450     std::vector<geometry_msgs::Pose2D>::iterator global_it = global_plan_.poses.begin();
00451     double sq_prune_dist = prune_distance_ * prune_distance_;
00452     while (it != transformed_plan.poses.end())
00453     {
00454       const geometry_msgs::Pose2D& w = *it;
00455       // Fixed error bound of 1 meter for now. Can reduce to a portion of the map size or based on the resolution
00456       if (getSquareDistance(costmap_pose.pose, w) < sq_prune_dist)
00457       {
00458         ROS_DEBUG_NAMED("DWBLocalPlanner", "Nearest waypoint to <%f, %f> is <%f, %f>\n",
00459                                            costmap_pose.pose.x, costmap_pose.pose.y, w.x, w.y);
00460         break;
00461       }
00462       it = transformed_plan.poses.erase(it);
00463       global_it = global_plan_.poses.erase(global_it);
00464     }
00465     pub_.publishGlobalPlan(global_plan_);
00466   }
00467 
00468   if (transformed_plan.poses.size() == 0)
00469   {
00470     throw nav_core2::PlannerException("Resulting plan has 0 poses in it.");
00471   }
00472   return transformed_plan;
00473 }
00474 
00475 geometry_msgs::Pose2D DWBLocalPlanner::transformPoseToLocal(const nav_2d_msgs::Pose2DStamped& pose)
00476 {
00477   return nav_2d_utils::transformStampedPose(tf_, pose, costmap_->getFrameId());
00478 }
00479 
00480 }  // namespace dwb_local_planner
00481 
00482 
00483 //  register this planner as a LocalPlanner plugin
00484 PLUGINLIB_EXPORT_CLASS(dwb_local_planner::DWBLocalPlanner, nav_core2::LocalPlanner)


dwb_local_planner
Author(s): David V. Lu!!
autogenerated on Wed Jun 26 2019 20:09:38