dwb_local_planner.cpp
Go to the documentation of this file.
1 /*
2  * Software License Agreement (BSD License)
3  *
4  * Copyright (c) 2017, Locus Robotics
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * * Redistributions of source code must retain the above copyright
12  * notice, this list of conditions and the following disclaimer.
13  * * Redistributions in binary form must reproduce the above
14  * copyright notice, this list of conditions and the following
15  * disclaimer in the documentation and/or other materials provided
16  * with the distribution.
17  * * Neither the name of the copyright holder nor the names of its
18  * contributors may be used to endorse or promote products derived
19  * from this software without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25  * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32  * POSSIBILITY OF SUCH DAMAGE.
33  */
34 
35 #include <nav_core2/exceptions.h>
40 #include <nav_2d_utils/tf_help.h>
41 #include <nav_2d_msgs/Twist2D.h>
42 #include <dwb_msgs/CriticScore.h>
44 #include <string>
45 #include <vector>
46 #include <algorithm>
47 
48 namespace dwb_local_planner
49 {
50 
52  traj_gen_loader_("dwb_local_planner", "dwb_local_planner::TrajectoryGenerator"),
53  goal_checker_loader_("dwb_local_planner", "dwb_local_planner::GoalChecker"),
54  critic_loader_("dwb_local_planner", "dwb_local_planner::TrajectoryCritic")
55 {
56 }
57 
58 void DWBLocalPlanner::initialize(const ros::NodeHandle& parent, const std::string& name,
60 {
61  tf_ = tf;
62  costmap_ = costmap;
63  planner_nh_ = ros::NodeHandle(parent, name);
64 
65  // This is needed when using the CostmapAdapter to ensure that the costmap's info matches the rolling window
66  planner_nh_.param("update_costmap_before_planning", update_costmap_before_planning_, true);
67 
68  planner_nh_.param("prune_plan", prune_plan_, true);
69  planner_nh_.param("prune_distance", prune_distance_, 1.0);
70  planner_nh_.param("short_circuit_trajectory_evaluation", short_circuit_trajectory_evaluation_, true);
71  planner_nh_.param("debug_trajectory_details", debug_trajectory_details_, false);
73 
74  // Plugins
75  std::string traj_generator_name;
76  planner_nh_.param("trajectory_generator_name", traj_generator_name,
78  ROS_INFO_NAMED("DWBLocalPlanner", "Using Trajectory Generator \"%s\"", traj_generator_name.c_str());
79  traj_generator_ = std::move(traj_gen_loader_.createUniqueInstance(traj_generator_name));
80  traj_generator_->initialize(planner_nh_);
81 
82  std::string goal_checker_name;
83  planner_nh_.param("goal_checker_name", goal_checker_name, std::string("dwb_plugins::SimpleGoalChecker"));
84  ROS_INFO_NAMED("DWBLocalPlanner", "Using Goal Checker \"%s\"", goal_checker_name.c_str());
85  goal_checker_ = std::move(goal_checker_loader_.createUniqueInstance(goal_checker_name));
86  goal_checker_->initialize(planner_nh_);
87 
88  loadCritics(name);
89 }
90 
91 std::string DWBLocalPlanner::resolveCriticClassName(std::string base_name)
92 {
93  if (base_name.find("Critic") == std::string::npos)
94  {
95  base_name = base_name + "Critic";
96  }
97 
98  if (base_name.find("::") == std::string::npos)
99  {
100  for (unsigned int j = 0; j < default_critic_namespaces_.size(); j++)
101  {
102  std::string full_name = default_critic_namespaces_[j] + "::" + base_name;
103  if (critic_loader_.isClassAvailable(full_name))
104  {
105  return full_name;
106  }
107  }
108  }
109  return base_name;
110 }
111 
112 void DWBLocalPlanner::loadCritics(const std::string name)
113 {
114  planner_nh_.param("default_critic_namespaces", default_critic_namespaces_);
115  if (default_critic_namespaces_.size() == 0)
116  {
117  default_critic_namespaces_.push_back("dwb_critics");
118  }
119 
120  if (!planner_nh_.hasParam("critics"))
121  {
123  }
124 
125  std::vector<std::string> critic_names;
126  planner_nh_.getParam("critics", critic_names);
127  for (unsigned int i = 0; i < critic_names.size(); i++)
128  {
129  std::string plugin_name = critic_names[i];
130  std::string plugin_class;
131  planner_nh_.param(plugin_name + "/class", plugin_class, plugin_name);
132  plugin_class = resolveCriticClassName(plugin_class);
133 
134  TrajectoryCritic::Ptr plugin = std::move(critic_loader_.createUniqueInstance(plugin_class));
135  ROS_INFO_NAMED("DWBLocalPlanner", "Using critic \"%s\" (%s)", plugin_name.c_str(), plugin_class.c_str());
136  critics_.push_back(plugin);
137  plugin->initialize(planner_nh_, plugin_name, costmap_);
138  }
139 }
140 
141 bool DWBLocalPlanner::isGoalReached(const nav_2d_msgs::Pose2DStamped& pose, const nav_2d_msgs::Twist2D& velocity)
142 {
143  if (goal_pose_.header.frame_id == "")
144  {
145  ROS_WARN_NAMED("DWBLocalPlanner", "Cannot check if the goal is reached without the goal being set!");
146  return false;
147  }
148 
149  // Update time stamp of goal pose
150  goal_pose_.header.stamp = pose.header.stamp;
151 
152  bool ret = goal_checker_->isGoalReached(transformPoseToLocal(pose), transformPoseToLocal(goal_pose_), velocity);
153  if (ret)
154  {
155  ROS_INFO_THROTTLE_NAMED(1.0, "DWBLocalPlanner", "Goal reached!");
156  }
157  return ret;
158 }
159 
160 void DWBLocalPlanner::setGoalPose(const nav_2d_msgs::Pose2DStamped& goal_pose)
161 {
162  ROS_INFO_NAMED("DWBLocalPlanner", "New Goal Received.");
163  goal_pose_ = goal_pose;
164  traj_generator_->reset();
165  goal_checker_->reset();
166  for (TrajectoryCritic::Ptr critic : critics_)
167  {
168  critic->reset();
169  }
170 }
171 
172 void DWBLocalPlanner::setPlan(const nav_2d_msgs::Path2D& path)
173 {
174  pub_.publishGlobalPlan(path);
175  global_plan_ = path;
176 }
177 
178 nav_2d_msgs::Twist2DStamped DWBLocalPlanner::computeVelocityCommands(const nav_2d_msgs::Pose2DStamped& pose,
179  const nav_2d_msgs::Twist2D& velocity)
180 {
181  std::shared_ptr<dwb_msgs::LocalPlanEvaluation> results = nullptr;
183  {
184  results = std::make_shared<dwb_msgs::LocalPlanEvaluation>();
185  }
186 
187  try
188  {
189  nav_2d_msgs::Twist2DStamped cmd_vel = computeVelocityCommands(pose, velocity, results);
190  pub_.publishEvaluation(results);
191  return cmd_vel;
192  }
193  catch (const nav_core2::PlannerException& e)
194  {
195  pub_.publishEvaluation(results);
196  throw;
197  }
198 }
199 
200 void DWBLocalPlanner::prepare(const nav_2d_msgs::Pose2DStamped& pose, const nav_2d_msgs::Twist2D& velocity)
201 {
203  {
204  costmap_->update();
205  }
206 
207  nav_2d_msgs::Path2D transformed_plan = transformGlobalPlan(pose);
208  pub_.publishTransformedPlan(transformed_plan);
209 
210  // Update time stamp of goal pose
211  goal_pose_.header.stamp = pose.header.stamp;
212 
213  geometry_msgs::Pose2D local_start_pose = transformPoseToLocal(pose),
214  local_goal_pose = transformPoseToLocal(goal_pose_);
215 
216  pub_.publishInputParams(costmap_->getInfo(), local_start_pose, velocity, local_goal_pose);
217 
218  for (TrajectoryCritic::Ptr critic : critics_)
219  {
220  if (!critic->prepare(local_start_pose, velocity, local_goal_pose, transformed_plan))
221  {
222  ROS_WARN_NAMED("DWBLocalPlanner", "Critic \"%s\" failed to prepare", critic->getName().c_str());
223  }
224  }
225 }
226 
227 nav_2d_msgs::Twist2DStamped DWBLocalPlanner::computeVelocityCommands(const nav_2d_msgs::Pose2DStamped& pose,
228  const nav_2d_msgs::Twist2D& velocity, std::shared_ptr<dwb_msgs::LocalPlanEvaluation>& results)
229 {
230  if (results)
231  {
232  results->header.frame_id = pose.header.frame_id;
233  results->header.stamp = ros::Time::now();
234  }
235 
236  prepare(pose, velocity);
237 
238  try
239  {
240  dwb_msgs::TrajectoryScore best = coreScoringAlgorithm(pose.pose, velocity, results);
241 
242  // Return Value
243  nav_2d_msgs::Twist2DStamped cmd_vel;
244  cmd_vel.header.stamp = ros::Time::now();
245  cmd_vel.velocity = best.traj.velocity;
246 
247  // debrief stateful critics
248  for (TrajectoryCritic::Ptr critic : critics_)
249  {
250  critic->debrief(cmd_vel.velocity);
251  }
252 
253  pub_.publishLocalPlan(pose.header, best.traj);
254  pub_.publishCostGrid(costmap_, critics_);
255 
256  return cmd_vel;
257  }
258  catch (const NoLegalTrajectoriesException& e)
259  {
260  nav_2d_msgs::Twist2D empty_cmd;
261  dwb_msgs::Trajectory2D empty_traj;
262  // debrief stateful scoring functions
263  for (TrajectoryCritic::Ptr critic : critics_)
264  {
265  critic->debrief(empty_cmd);
266  }
267  pub_.publishLocalPlan(pose.header, empty_traj);
268  pub_.publishCostGrid(costmap_, critics_);
269 
270  throw;
271  }
272 }
273 
274 dwb_msgs::TrajectoryScore DWBLocalPlanner::coreScoringAlgorithm(const geometry_msgs::Pose2D& pose,
275  const nav_2d_msgs::Twist2D velocity,
276  std::shared_ptr<dwb_msgs::LocalPlanEvaluation>& results)
277 {
278  nav_2d_msgs::Twist2D twist;
279  dwb_msgs::Trajectory2D traj;
280  dwb_msgs::TrajectoryScore best, worst;
281  best.total = -1;
282  worst.total = -1;
283  IllegalTrajectoryTracker tracker;
284 
285  traj_generator_->startNewIteration(velocity);
286  while (traj_generator_->hasMoreTwists())
287  {
288  twist = traj_generator_->nextTwist();
289  traj = traj_generator_->generateTrajectory(pose, velocity, twist);
290 
291  try
292  {
293  dwb_msgs::TrajectoryScore score = scoreTrajectory(traj, best.total);
294  tracker.addLegalTrajectory();
295  if (results)
296  {
297  results->twists.push_back(score);
298  }
299  if (best.total < 0 || score.total < best.total)
300  {
301  best = score;
302  if (results)
303  {
304  results->best_index = results->twists.size() - 1;
305  }
306  }
307  if (worst.total < 0 || score.total > worst.total)
308  {
309  worst = score;
310  if (results)
311  {
312  results->worst_index = results->twists.size() - 1;
313  }
314  }
315  }
317  {
318  if (results)
319  {
320  dwb_msgs::TrajectoryScore failed_score;
321  failed_score.traj = traj;
322 
323  dwb_msgs::CriticScore cs;
324  cs.name = e.getCriticName();
325  cs.raw_score = -1.0;
326  failed_score.scores.push_back(cs);
327  failed_score.total = -1.0;
328  results->twists.push_back(failed_score);
329  }
330  tracker.addIllegalTrajectory(e);
331  }
332  }
333 
334  if (best.total < 0)
335  {
337  {
338  ROS_ERROR_NAMED("DWBLocalPlanner", "%s", tracker.getMessage().c_str());
339  for (auto const& x : tracker.getPercentages())
340  {
341  ROS_ERROR_NAMED("DWBLocalPlanner", "%.2f: %10s/%s", x.second, x.first.first.c_str(), x.first.second.c_str());
342  }
343  }
344  throw NoLegalTrajectoriesException(tracker);
345  }
346 
347  return best;
348 }
349 
350 dwb_msgs::TrajectoryScore DWBLocalPlanner::scoreTrajectory(const dwb_msgs::Trajectory2D& traj,
351  double best_score)
352 {
353  dwb_msgs::TrajectoryScore score;
354  score.traj = traj;
355 
356  for (TrajectoryCritic::Ptr critic : critics_)
357  {
358  dwb_msgs::CriticScore cs;
359  cs.name = critic->getName();
360  cs.scale = critic->getScale();
361 
362  if (cs.scale == 0.0)
363  {
364  score.scores.push_back(cs);
365  continue;
366  }
367 
368  double critic_score = critic->scoreTrajectory(traj);
369  cs.raw_score = critic_score;
370  score.scores.push_back(cs);
371  score.total += critic_score * cs.scale;
372  if (short_circuit_trajectory_evaluation_ && best_score > 0 && score.total > best_score)
373  {
374  // since we keep adding positives, once we are worse than the best, we will stay worse
375  break;
376  }
377  }
378 
379  return score;
380 }
381 
382 double getSquareDistance(const geometry_msgs::Pose2D& pose_a, const geometry_msgs::Pose2D& pose_b)
383 {
384  double x_diff = pose_a.x - pose_b.x;
385  double y_diff = pose_a.y - pose_b.y;
386  return x_diff * x_diff + y_diff * y_diff;
387 }
388 
389 nav_2d_msgs::Path2D DWBLocalPlanner::transformGlobalPlan(const nav_2d_msgs::Pose2DStamped& pose)
390 {
391  nav_2d_msgs::Path2D transformed_plan;
392  if (global_plan_.poses.size() == 0)
393  {
394  throw nav_core2::PlannerException("Received plan with zero length");
395  }
396 
397  // let's get the pose of the robot in the frame of the plan
398  nav_2d_msgs::Pose2DStamped robot_pose;
399  if (!nav_2d_utils::transformPose(tf_, global_plan_.header.frame_id, pose, robot_pose))
400  {
401  throw nav_core2::PlannerTFException("Unable to transform robot pose into global plan's frame");
402  }
403 
404  transformed_plan.header.frame_id = costmap_->getFrameId();
405  transformed_plan.header.stamp = pose.header.stamp;
406 
407  // we'll discard points on the plan that are outside the local costmap
408  double dist_threshold = std::max(costmap_->getWidth(), costmap_->getHeight()) * costmap_->getResolution() / 2.0;
409  double sq_dist_threshold = dist_threshold * dist_threshold;
410  nav_2d_msgs::Pose2DStamped stamped_pose;
411  stamped_pose.header.frame_id = global_plan_.header.frame_id;
412 
413  for (unsigned int i = 0; i < global_plan_.poses.size(); i++)
414  {
415  bool should_break = false;
416  if (getSquareDistance(robot_pose.pose, global_plan_.poses[i]) > sq_dist_threshold)
417  {
418  if (transformed_plan.poses.size() == 0)
419  {
420  // we need to skip to a point on the plan that is within a certain distance of the robot
421  continue;
422  }
423  else
424  {
425  // we're done transforming points
426  should_break = true;
427  }
428  }
429 
430  // now we'll transform until points are outside of our distance threshold
431  stamped_pose.pose = global_plan_.poses[i];
432  transformed_plan.poses.push_back(transformPoseToLocal(stamped_pose));
433  if (should_break) break;
434  }
435 
436  // Prune both plans based on robot position
437  // Note that this part of the algorithm assumes that the global plan starts near the robot (at one point)
438  // Otherwise it may take a few iterations to converge to the proper behavior
439  if (prune_plan_)
440  {
441  // let's get the pose of the robot in the frame of the transformed_plan/costmap
442  nav_2d_msgs::Pose2DStamped costmap_pose;
443  if (!nav_2d_utils::transformPose(tf_, transformed_plan.header.frame_id, pose, costmap_pose))
444  {
445  throw nav_core2::PlannerTFException("Unable to transform robot pose into costmap's frame");
446  }
447 
448  ROS_ASSERT(global_plan_.poses.size() >= transformed_plan.poses.size());
449  std::vector<geometry_msgs::Pose2D>::iterator it = transformed_plan.poses.begin();
450  std::vector<geometry_msgs::Pose2D>::iterator global_it = global_plan_.poses.begin();
451  double sq_prune_dist = prune_distance_ * prune_distance_;
452  while (it != transformed_plan.poses.end())
453  {
454  const geometry_msgs::Pose2D& w = *it;
455  // Fixed error bound of 1 meter for now. Can reduce to a portion of the map size or based on the resolution
456  if (getSquareDistance(costmap_pose.pose, w) < sq_prune_dist)
457  {
458  ROS_DEBUG_NAMED("DWBLocalPlanner", "Nearest waypoint to <%f, %f> is <%f, %f>\n",
459  costmap_pose.pose.x, costmap_pose.pose.y, w.x, w.y);
460  break;
461  }
462  it = transformed_plan.poses.erase(it);
463  global_it = global_plan_.poses.erase(global_it);
464  }
466  }
467 
468  if (transformed_plan.poses.size() == 0)
469  {
470  throw nav_core2::PlannerException("Resulting plan has 0 poses in it.");
471  }
472  return transformed_plan;
473 }
474 
475 geometry_msgs::Pose2D DWBLocalPlanner::transformPoseToLocal(const nav_2d_msgs::Pose2DStamped& pose)
476 {
477  return nav_2d_utils::transformStampedPose(tf_, pose, costmap_->getFrameId());
478 }
479 
480 } // namespace dwb_local_planner
481 
482 
483 // register this planner as a LocalPlanner plugin
void addIllegalTrajectory(const nav_core2::IllegalTrajectoryException &e)
void publishCostGrid(const nav_core2::Costmap::Ptr costmap, const std::vector< TrajectoryCritic::Ptr > critics)
Definition: publisher.cpp:161
std::map< std::pair< std::string, std::string >, double > getPercentages() const
#define ROS_INFO_THROTTLE_NAMED(period, name,...)
#define ROS_INFO_NAMED(name,...)
pluginlib::ClassLoader< TrajectoryCritic > critic_loader_
#define ROS_WARN_NAMED(name,...)
std::shared_ptr< dwb_local_planner::TrajectoryCritic > Ptr
void publishGlobalPlan(const nav_2d_msgs::Path2D plan)
Definition: publisher.cpp:212
void publishTransformedPlan(const nav_2d_msgs::Path2D plan)
Definition: publisher.cpp:217
std::string getBackwardsCompatibleDefaultGenerator(const ros::NodeHandle &nh)
bool transformPose(const TFListenerPtr tf, const std::string frame, const geometry_msgs::PoseStamped &in_pose, geometry_msgs::PoseStamped &out_pose, const bool extrapolation_fallback=true)
TrajectoryGenerator::Ptr traj_generator_
virtual void prepare(const nav_2d_msgs::Pose2DStamped &pose, const nav_2d_msgs::Twist2D &velocity)
Helper method for preparing for the core scoring algorithm.
double getSquareDistance(const geometry_msgs::Pose2D &pose_a, const geometry_msgs::Pose2D &pose_b)
void publishInputParams(const nav_grid::NavGridInfo &info, const geometry_msgs::Pose2D &start_pose, const nav_2d_msgs::Twist2D &velocity, const geometry_msgs::Pose2D &goal_pose)
Definition: publisher.cpp:234
void initialize(const ros::NodeHandle &parent, const std::string &name, TFListenerPtr tf, nav_core2::Costmap::Ptr costmap) override
nav_core2 initialization
nav_2d_msgs::Pose2DStamped goal_pose_
Saved Goal Pose.
Plugin-based flexible local_planner.
nav_2d_msgs::Twist2DStamped computeVelocityCommands(const nav_2d_msgs::Pose2DStamped &pose, const nav_2d_msgs::Twist2D &velocity) override
nav_core2 computeVelocityCommands - calculates the best command given the current pose and velocity ...
#define ROS_DEBUG_NAMED(name,...)
pluginlib::ClassLoader< GoalChecker > goal_checker_loader_
void loadBackwardsCompatibleParameters(const ros::NodeHandle &nh)
Load parameters to emulate dwa_local_planner.
bool param(const std::string &param_name, T &param_val, const T &default_val) const
pluginlib::ClassLoader< TrajectoryGenerator > traj_gen_loader_
void publishEvaluation(std::shared_ptr< dwb_msgs::LocalPlanEvaluation > results)
If the pointer is not null, publish the evaluation and trajectories as needed.
Definition: publisher.cpp:87
TFSIMD_FORCE_INLINE const tfScalar & x() const
void setPlan(const nav_2d_msgs::Path2D &path) override
nav_core2 setPlan - Sets the global plan
bool isGoalReached(const nav_2d_msgs::Pose2DStamped &pose, const nav_2d_msgs::Twist2D &velocity) override
nav_core2 isGoalReached - Check whether the robot has reached its goal, given the current pose & velo...
nav_2d_msgs::Path2D global_plan_
Saved Global Plan.
TFSIMD_FORCE_INLINE const tfScalar & w() const
virtual void loadCritics(const std::string name)
Load the critic parameters from the namespace.
void publishLocalPlan(const std_msgs::Header &header, const dwb_msgs::Trajectory2D &traj)
Definition: publisher.cpp:152
std::vector< std::string > default_critic_namespaces_
std::string resolveCriticClassName(std::string base_name)
try to resolve a possibly shortened critic name with the default namespaces and the suffix "Critic" ...
bool getParam(const std::string &key, std::string &s) const
virtual nav_2d_msgs::Path2D transformGlobalPlan(const nav_2d_msgs::Pose2DStamped &pose)
Transforms global plan into same frame as pose, clips far away poses and possibly prunes passed poses...
#define ROS_ERROR_NAMED(name,...)
static Time now()
geometry_msgs::Pose2D transformStampedPose(const TFListenerPtr tf, const nav_2d_msgs::Pose2DStamped &pose, const std::string &frame_id)
Thrown when all the trajectories explored are illegal.
std::vector< TrajectoryCritic::Ptr > critics_
std::shared_ptr< Costmap > Ptr
bool hasParam(const std::string &key) const
std::shared_ptr< tf::TransformListener > TFListenerPtr
virtual dwb_msgs::TrajectoryScore coreScoringAlgorithm(const geometry_msgs::Pose2D &pose, const nav_2d_msgs::Twist2D velocity, std::shared_ptr< dwb_msgs::LocalPlanEvaluation > &results)
Iterate through all the twists and find the best one.
#define ROS_ASSERT(cond)
#define PLUGINLIB_EXPORT_CLASS(class_type, base_class_type)
geometry_msgs::Pose2D transformPoseToLocal(const nav_2d_msgs::Pose2DStamped &pose)
Helper method to transform a given pose to the local costmap frame.
bool shouldRecordEvaluation()
Does the publisher require that the LocalPlanEvaluation be saved.
Definition: publisher.h:72
virtual dwb_msgs::TrajectoryScore scoreTrajectory(const dwb_msgs::Trajectory2D &traj, double best_score=-1)
Score a given command. Can be used for testing.
void setGoalPose(const nav_2d_msgs::Pose2DStamped &goal_pose) override
nav_core2 setGoalPose - Sets the global goal pose
void initialize(ros::NodeHandle &nh)
Load the parameters and advertise topics as needed.
Definition: publisher.cpp:46
DWBLocalPlanner()
Constructor that brings up pluginlib loaders.


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