trajectory_planner_ros.cpp
Go to the documentation of this file.
00001 /*********************************************************************
00002 *
00003 * Software License Agreement (BSD License)
00004 *
00005 *  Copyright (c) 2008, Willow Garage, Inc.
00006 *  All rights reserved.
00007 *
00008 *  Redistribution and use in source and binary forms, with or without
00009 *  modification, are permitted provided that the following conditions
00010 *  are met:
00011 *
00012 *   * Redistributions of source code must retain the above copyright
00013 *     notice, this list of conditions and the following disclaimer.
00014 *   * Redistributions in binary form must reproduce the above
00015 *     copyright notice, this list of conditions and the following
00016 *     disclaimer in the documentation and/or other materials provided
00017 *     with the distribution.
00018 *   * Neither the name of the Willow Garage nor the names of its
00019 *     contributors may be used to endorse or promote products derived
00020 *     from this software without specific prior written permission.
00021 *
00022 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00023 *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00024 *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
00025 *  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
00026 *  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
00027 *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
00028 *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00029 *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
00030 *  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00031 *  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
00032 *  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00033 *  POSSIBILITY OF SUCH DAMAGE.
00034 *
00035 * Author: Eitan Marder-Eppstein
00036 *********************************************************************/
00037 
00038 #include <trajectory_planner_ros.h>
00039 #include <ros/console.h>
00040 #include <sys/time.h>
00041 #include <pluginlib/class_list_macros.h>
00042 #include <boost/tokenizer.hpp>
00043 
00044 #include "geometry_msgs/PolygonStamped.h"
00045 #include "nav_msgs/Path.h"
00046 #include "goal_functions.h"
00047 
00048 using namespace std;
00049 using namespace costmap_2d;
00050 
00051 //register this planner as a BaseLocalPlanner plugin
00052 PLUGINLIB_DECLARE_CLASS(iri_ackermann_local_planner, TrajectoryPlannerROS, iri_ackermann_local_planner::TrajectoryPlannerROS, nav_core::BaseLocalPlanner)
00053 
00054 namespace iri_ackermann_local_planner {
00055 
00056   void TrajectoryPlannerROS::reconfigureCB(AckermannLocalPlannerConfig &config, uint32_t level) {
00057       if(setup_ && config.restore_defaults) {
00058         config = default_config_;
00059         //Avoid looping
00060         config.restore_defaults = false;
00061       }
00062       if(!setup_) {
00063         default_config_ = config;
00064         setup_ = true;
00065       }
00066       else if(setup_) {
00067         tc_->reconfigure(config);
00068       }
00069   }
00070 
00071   TrajectoryPlannerROS::TrajectoryPlannerROS() : world_model_(NULL), tc_(NULL), costmap_ros_(NULL), tf_(NULL), initialized_(false), setup_(false) 
00072   {
00073   }
00074 
00075   TrajectoryPlannerROS::TrajectoryPlannerROS(std::string name, tf::TransformListener* tf, Costmap2DROS* costmap_ros) 
00076     : world_model_(NULL), tc_(NULL), costmap_ros_(NULL), tf_(NULL), initialized_(false), setup_(false) {
00077       //initialize the planner
00078       initialize(name, tf, costmap_ros);
00079   }
00080 
00081   void TrajectoryPlannerROS::initialize(std::string name, tf::TransformListener* tf, Costmap2DROS* costmap_ros){
00082     if(!initialized_)
00083     {
00084       tf_ = tf;
00085       costmap_ros_ = costmap_ros;
00086       rot_stopped_velocity_ = 1e-2;
00087       trans_stopped_velocity_ = 1e-2;
00088       double sim_time, sim_granularity, angular_sim_granularity;
00089       int vx_samples, vtheta_samples;
00090       double pdist_scale, gdist_scale, occdist_scale, hdiff_scale;
00091       double ack_acc_max,ack_vel_max,ack_vel_min;
00092       double ack_steer_acc_max,ack_steer_vel_max,ack_steer_vel_min,ack_steer_angle_max,ack_steer_angle_min;
00093       double axis_distance;
00094       bool simple_attractor;
00095       int heading_points;
00096       string world_model_type;
00097       rotating_to_goal_ = false;
00098 
00099       //initialize the copy of the costmap the controller will use
00100       costmap_ros_->getCostmapCopy(costmap_);
00101 
00102       ros::NodeHandle private_nh("~/" + name);
00103       
00104       g_plan_pub_ = private_nh.advertise<nav_msgs::Path>("global_plan", 1);
00105       l_plan_pub_ = private_nh.advertise<nav_msgs::Path>("local_plan", 1);
00106 
00107       global_frame_ = costmap_ros_->getGlobalFrameID();
00108       robot_base_frame_ = costmap_ros_->getBaseFrameID();
00109       private_nh.param("prune_plan", prune_plan_, true);
00110 
00111       private_nh.param("yaw_goal_tolerance", yaw_goal_tolerance_, 0.05);
00112       private_nh.param("xy_goal_tolerance", xy_goal_tolerance_, 0.10);
00113 
00114       //we'll get the parameters for the robot radius from the costmap we're associated with
00115       inflation_radius_ = costmap_ros_->getInflationRadius();
00116 
00117       private_nh.param("ack_acc_max", ack_acc_max, 1.0);
00118       private_nh.param("ack_vel_max", ack_vel_max, 0.6);
00119       private_nh.param("ack_vel_min", ack_vel_min, -0.6);
00120       private_nh.param("ack_steer_acc_max", ack_steer_acc_max, 1.0);
00121       private_nh.param("ack_steer_speed_max", ack_steer_vel_max, 0.5);
00122       private_nh.param("ack_steer_speed_min", ack_steer_vel_min, -0.5);
00123       private_nh.param("ack_steer_angle_max", ack_steer_angle_max, 0.35);
00124       private_nh.param("ack_steer_angle_min", ack_steer_angle_min, -0.35);
00125       private_nh.param("ack_axis_distance", axis_distance, 1.65);
00126 
00127       private_nh.param("latch_xy_goal_tolerance", latch_xy_goal_tolerance_, false);
00128 
00129       //Assuming this planner is being run within the navigation stack, we can
00130       //just do an upward search for the frequency at which its being run. This
00131       //also allows the frequency to be overwritten locally.
00132       std::string controller_frequency_param_name;
00133 
00134       private_nh.param("sim_time", sim_time, 10.0);
00135       private_nh.param("sim_granularity", sim_granularity, 0.025);
00136       private_nh.param("angular_sim_granularity", angular_sim_granularity, sim_granularity);
00137       private_nh.param("vx_samples", vx_samples, 10);//3
00138       private_nh.param("vtheta_samples", vtheta_samples, 20);
00139 
00140       private_nh.param("pdist_scale", pdist_scale, 0.6);
00141       private_nh.param("gdist_scale", gdist_scale, 0.8);
00142       private_nh.param("hdiff_scale", hdiff_scale, 1.0);
00143       private_nh.param("occdist_scale", occdist_scale, 0.01);
00144       private_nh.param("heading_points", heading_points, 8);
00145 
00146       private_nh.param("world_model", world_model_type, string("costmap")); 
00147 
00148       simple_attractor = false;
00149 
00150       //parameters for using the freespace controller
00151       double min_pt_separation, max_obstacle_height, grid_resolution;
00152       private_nh.param("point_grid/max_sensor_range", max_sensor_range_, 2.0);
00153       private_nh.param("point_grid/min_pt_separation", min_pt_separation, 0.01);
00154       private_nh.param("point_grid/max_obstacle_height", max_obstacle_height, 2.0);
00155       private_nh.param("point_grid/grid_resolution", grid_resolution, 0.2);
00156 
00157       ROS_ASSERT_MSG(world_model_type == "costmap", "At this time, only costmap world models are supported by this controller");
00158       world_model_ = new CostmapModel(costmap_);
00159 
00160       tc_ = new TrajectoryPlanner(*world_model_, costmap_, costmap_ros_->getRobotFootprint(),
00161           ack_acc_max, ack_vel_max, ack_vel_min, ack_steer_acc_max,ack_steer_vel_max,ack_steer_vel_min,ack_steer_angle_max,ack_steer_angle_min,axis_distance,
00162           sim_time, sim_granularity, vx_samples, vtheta_samples, pdist_scale,gdist_scale, occdist_scale, hdiff_scale,simple_attractor,angular_sim_granularity,
00163           heading_points,xy_goal_tolerance_);
00164 
00165       map_viz_.initialize(name, &costmap_, boost::bind(&TrajectoryPlanner::getCellCosts, tc_, _1, _2, _3, _4, _5, _6));
00166       initialized_ = true;
00167 
00168       dsrv_ = new dynamic_reconfigure::Server<AckermannLocalPlannerConfig>(private_nh);
00169       dynamic_reconfigure::Server<AckermannLocalPlannerConfig>::CallbackType cb = boost::bind(&TrajectoryPlannerROS::reconfigureCB, this, _1, _2);
00170       dsrv_->setCallback(cb);
00171 
00172       ros::NodeHandle global_node;
00173       odom_sub_ = global_node.subscribe<nav_msgs::Odometry>("odom", 1, boost::bind(&TrajectoryPlannerROS::odomCallback, this, _1));
00174     }
00175     else
00176       ROS_WARN("This planner has already been initialized, you can't call it twice, doing nothing");
00177   }
00178 
00179   TrajectoryPlannerROS::~TrajectoryPlannerROS(){
00180     //make sure to clean things up
00181     delete dsrv_;
00182 
00183     if(tc_ != NULL)
00184       delete tc_;
00185 
00186     if(world_model_ != NULL)
00187       delete world_model_;
00188   }
00189 
00190   void TrajectoryPlannerROS::odomCallback(const nav_msgs::Odometry::ConstPtr& msg){
00191     //we assume that the odometry is published in the frame of the base
00192     boost::recursive_mutex::scoped_lock lock(odom_lock_);
00193     base_odom_.twist.twist.linear.x = msg->twist.twist.linear.x;
00194     base_odom_.twist.twist.linear.y = msg->twist.twist.linear.y;
00195     base_odom_.twist.twist.angular.z = msg->twist.twist.angular.z;
00196     ackermann_state_.linear.z=msg->twist.twist.linear.z;
00197     ackermann_state_.angular.x=msg->twist.twist.angular.x;
00198     ackermann_state_.angular.y=msg->twist.twist.angular.y;
00199     ROS_DEBUG("In the odometry callback with velocity values: (%.2f, %.2f, %.2f)",
00200         base_odom_.twist.twist.linear.x, base_odom_.twist.twist.linear.y, base_odom_.twist.twist.angular.z);
00201   }
00202 
00203   bool TrajectoryPlannerROS::setPlan(const std::vector<geometry_msgs::PoseStamped>& orig_global_plan)
00204   {
00205     if(!initialized_)
00206     {
00207       ROS_ERROR("This planner has not been initialized, please call initialize() before using this planner");
00208       return false;
00209     }
00210 
00212     subPathList.clear();
00213     subPath.clear();
00214     subPathIndex      = 0;
00215     double pathLength = 0;
00216     for(unsigned int i = 0; i < orig_global_plan.size(); ++i)
00217     {
00218       if(i>1 && i<orig_global_plan.size())
00219       {
00220         double x0 = orig_global_plan[i  ].pose.position.x;
00221         double x1 = orig_global_plan[i-1].pose.position.x;
00222         double x2 = orig_global_plan[i-2].pose.position.x;
00223         double y0 = orig_global_plan[i  ].pose.position.y;
00224         double y1 = orig_global_plan[i-1].pose.position.y;
00225         double y2 = orig_global_plan[i-2].pose.position.y;
00226         double angle=std::acos(((x0-x1)*(x1-x2)+(y0-y1)*(y1-y2))/(std::sqrt(std::pow(x0-x1,2)+std::pow(y0-y1,2))*std::sqrt(std::pow(x1-x2,2)+std::pow(y1-y2,2))));
00227         pathLength+= std::sqrt(std::pow(x0-x1,2)+std::pow(y0-y1,2));
00228         if(fabs(angle)>1.57) //if changes of direction detected
00229         {
00230           if(pathLength>1.0)
00231           {
00232             ROS_INFO("TrajectoryPlannerROS::setPlan: subPath split at i=%d, angle=%f, length=%f", i, angle, pathLength);
00233             subPathList.push_back(subPath);
00234           }
00235           else //ignored subpaths shorter than 1.0m
00236           {
00237             ROS_INFO("TrajectoryPlannerROS::setPlan: subPath split at i=%d, angle=%f, Ignored by length=%f", i, angle, pathLength);
00238           }
00239           subPath.clear();
00240           pathLength=0.0;
00241         }
00242       }
00243       subPath.push_back(orig_global_plan[i]);
00244     }
00245     subPathList.push_back(subPath);
00246     ROS_INFO("TrajectoryPlannerROS::setPlan: subPath last length=%f", pathLength);
00247     ROS_INFO("TrajectoryPlannerROS::setPlan: Global plan (%lu points) split in %lu paths", orig_global_plan.size(), subPathList.size());
00248     global_plan_.clear();
00249     global_plan_ = subPathList[subPathIndex];
00251 
00252     //reset the global plan
00255 
00256     //when we get a new plan, we also want to clear any latch we may have on goal tolerances
00257     xy_tolerance_latch_ = false;
00258 
00259     return true;
00260   }
00261 
00262   bool TrajectoryPlannerROS::computeVelocityCommands(geometry_msgs::Twist& cmd_vel){
00263     if(!initialized_){
00264       ROS_ERROR("This planner has not been initialized, please call initialize() before using this planner");
00265       return false;
00266     }
00267     ROS_INFO("TrajectoryPlannerROS::computeVelocityCommands!");
00268 
00269     std::vector<geometry_msgs::PoseStamped> local_plan;
00270     tf::Stamped<tf::Pose> global_pose;
00271     if(!costmap_ros_->getRobotPose(global_pose))
00272       return false;
00273 
00274     std::vector<geometry_msgs::PoseStamped> transformed_plan;
00275     //get the global plan in our frame
00276     if(!transformGlobalPlan(*tf_, global_plan_, *costmap_ros_, global_frame_, transformed_plan)){
00277       ROS_WARN("Could not transform the global plan to the frame of the controller");
00278       return false;
00279     }
00280 
00281     //now we'll prune the plan based on the position of the robot
00282     if(prune_plan_)
00283       prunePlan(global_pose, transformed_plan, global_plan_);
00284 
00285 
00286     //we also want to clear the robot footprint from the costmap we're using
00287     costmap_ros_->clearRobotFootprint();
00288 
00289     //make sure to update the costmap we'll use for this cycle
00290     costmap_ros_->getCostmapCopy(costmap_);
00291 
00292     // Set current velocities from odometry
00293     geometry_msgs::Twist global_vel;
00294 
00295     odom_lock_.lock();
00296     global_vel.linear.x = base_odom_.twist.twist.linear.x;
00297     global_vel.linear.y = base_odom_.twist.twist.linear.y;
00298     global_vel.angular.z = base_odom_.twist.twist.angular.z;
00299     odom_lock_.unlock();
00300 
00301     tf::Stamped<tf::Pose> drive_cmds;
00302     drive_cmds.frame_id_ = robot_base_frame_;
00303 
00304     tf::Stamped<tf::Pose> robot_vel;
00305     robot_vel.setData(tf::Transform(tf::createQuaternionFromYaw(global_vel.angular.z), tf::Vector3(global_vel.linear.x, global_vel.linear.y, 0)));
00306     robot_vel.frame_id_ = robot_base_frame_;
00307     robot_vel.stamp_ = ros::Time();
00308 
00309     //if the global plan passed in is empty... we won't do anything
00310     if(transformed_plan.empty())
00311       return false;
00312 
00313     tf::Stamped<tf::Pose> goal_point;
00314     tf::poseStampedMsgToTF(transformed_plan.back(), goal_point);
00315     //we assume the global goal is the last point in the global plan
00316     double goal_x = goal_point.getOrigin().getX();
00317     double goal_y = goal_point.getOrigin().getY();
00318 
00319     double yaw = tf::getYaw(goal_point.getRotation());
00320 
00321     double goal_th = yaw;
00322 
00323     //check to see if we've reached the goal position
00324     if(goalPositionReached(global_pose, goal_x, goal_y, xy_goal_tolerance_) || xy_tolerance_latch_){
00325 
00327       if(subPathIndex < subPathList.size()-1)
00328       {
00329         subPathIndex++;
00330         global_plan_.clear();
00331         global_plan_ = subPathList[subPathIndex];
00332         return true;
00333       }
00335 
00336       //if the user wants to latch goal tolerance, if we ever reach the goal location, we'll
00337       //just rotate in place
00338       if(latch_xy_goal_tolerance_)
00339         xy_tolerance_latch_ = true;
00340 
00341       //check to see if the goal orientation has been reached
00342       if(goalOrientationReached(global_pose, goal_th, yaw_goal_tolerance_)){
00343         //set the velocity command to zero
00344         cmd_vel.linear.x = 0.0;
00345         cmd_vel.linear.y = 0.0;
00346         cmd_vel.angular.z = 0.0;
00347         rotating_to_goal_ = false;
00348         xy_tolerance_latch_ = false;
00349       }
00350 
00351       //publish an empty plan because we've reached our goal position
00352       publishPlan(transformed_plan, g_plan_pub_, 0.0, 1.0, 0.0, 0.0);
00353       publishPlan(local_plan, l_plan_pub_, 0.0, 0.0, 1.0, 0.0);
00354 
00355       //we don't actually want to run the controller when we're just rotating to goal
00356       return true;
00357     }
00358 
00359     tc_->updatePlan(transformed_plan);
00360 
00361     //compute what trajectory to drive along
00362     Trajectory path = tc_->findBestPath(global_pose, robot_vel, drive_cmds,ackermann_state_);
00363 
00364     ROS_INFO("Speed command x,y,yaw: %f,%f,%f",drive_cmds.getOrigin().getX(),drive_cmds.getOrigin().getY(),tf::getYaw(drive_cmds.getRotation()));
00365 
00366     map_viz_.publishCostCloud();
00367 
00368     //pass along drive commands
00369     cmd_vel.linear.x = drive_cmds.getOrigin().getX();
00370     cmd_vel.linear.y = drive_cmds.getOrigin().getY();
00371     yaw = tf::getYaw(drive_cmds.getRotation());
00372 
00373     cmd_vel.angular.z = yaw;
00374 
00375     //if we cannot move... tell someone
00376     if(path.cost_ < 0){
00377       local_plan.clear();
00378       publishPlan(transformed_plan, g_plan_pub_, 0.0, 1.0, 0.0, 0.0);
00379       publishPlan(local_plan, l_plan_pub_, 0.0, 0.0, 1.0, 0.0);
00380       return false;
00381     }
00382 
00383     // Fill out the local plan
00384     for(unsigned int i = 0; i < path.getPointsSize(); ++i){
00385       double p_x, p_y, p_th;
00386       path.getPoint(i, p_x, p_y, p_th);
00387 
00388       tf::Stamped<tf::Pose> p = tf::Stamped<tf::Pose>(tf::Pose(tf::createQuaternionFromYaw(p_th), tf::Point(p_x, p_y, 0.0)), ros::Time::now(), global_frame_);
00389       geometry_msgs::PoseStamped pose;
00390       tf::poseStampedTFToMsg(p, pose);
00391       local_plan.push_back(pose);
00392     }
00393 
00394     //publish information to the visualizer
00395     publishPlan(transformed_plan, g_plan_pub_, 0.0, 1.0, 0.0, 0.0);
00396     publishPlan(local_plan, l_plan_pub_, 0.0, 0.0, 1.0, 0.0);
00397     return true;
00398   }
00399 
00400   bool TrajectoryPlannerROS::checkTrajectory(double vx_samp, double vy_samp, double vtheta_samp, bool update_map){
00401     tf::Stamped<tf::Pose> global_pose;
00402     if(costmap_ros_->getRobotPose(global_pose)){
00403       if(update_map){
00404         //we also want to clear the robot footprint from the costmap we're using
00405         costmap_ros_->clearRobotFootprint();
00406 
00407         //make sure to update the costmap we'll use for this cycle
00408         costmap_ros_->getCostmapCopy(costmap_);
00409 
00410         //we need to give the planne some sort of global plan, since we're only checking for legality
00411         //we'll just give the robots current position
00412         std::vector<geometry_msgs::PoseStamped> plan;
00413         geometry_msgs::PoseStamped pose_msg;
00414         tf::poseStampedTFToMsg(global_pose, pose_msg);
00415         plan.push_back(pose_msg);
00416         tc_->updatePlan(plan, true);
00417       }
00418 
00419       //copy over the odometry information
00420       nav_msgs::Odometry base_odom;
00421       {
00422         boost::recursive_mutex::scoped_lock lock(odom_lock_);
00423         base_odom = base_odom_;
00424       }
00425 
00426       return tc_->checkTrajectory(global_pose.getOrigin().x(), global_pose.getOrigin().y(), tf::getYaw(global_pose.getRotation()),
00427           base_odom.twist.twist.linear.x,
00428           base_odom.twist.twist.linear.y,
00429           base_odom.twist.twist.angular.z, vx_samp, vy_samp, vtheta_samp);
00430 
00431     }
00432     ROS_WARN("Failed to get the pose of the robot. No trajectories will pass as legal in this case.");
00433     return false;
00434   }
00435 
00436 
00437   double TrajectoryPlannerROS::scoreTrajectory(double vx_samp, double vy_samp, double vtheta_samp, bool update_map){
00438     // Copy of checkTrajectory that returns a score instead of True / False
00439     tf::Stamped<tf::Pose> global_pose;
00440     if(costmap_ros_->getRobotPose(global_pose)){
00441       if(update_map){
00442         //we also want to clear the robot footprint from the costmap we're using
00443         costmap_ros_->clearRobotFootprint();
00444 
00445         //make sure to update the costmap we'll use for this cycle
00446         costmap_ros_->getCostmapCopy(costmap_);
00447 
00448         //we need to give the planne some sort of global plan, since we're only checking for legality
00449         //we'll just give the robots current position
00450         std::vector<geometry_msgs::PoseStamped> plan;
00451         geometry_msgs::PoseStamped pose_msg;
00452         tf::poseStampedTFToMsg(global_pose, pose_msg);
00453         plan.push_back(pose_msg);
00454         tc_->updatePlan(plan, true);
00455       }
00456 
00457       //copy over the odometry information
00458       nav_msgs::Odometry base_odom;
00459       {
00460         boost::recursive_mutex::scoped_lock lock(odom_lock_);
00461         base_odom = base_odom_;
00462       }
00463 
00464       return tc_->scoreTrajectory(global_pose.getOrigin().x(), global_pose.getOrigin().y(), tf::getYaw(global_pose.getRotation()),
00465           base_odom.twist.twist.linear.x,
00466           base_odom.twist.twist.linear.y,
00467           base_odom.twist.twist.angular.z, vx_samp, vy_samp, vtheta_samp);
00468 
00469     }
00470     ROS_WARN("Failed to get the pose of the robot. No trajectories will pass as legal in this case.");
00471     return -1.0;
00472   }
00473 
00474   bool TrajectoryPlannerROS::isGoalReached(){
00475     if(!initialized_){
00476       ROS_ERROR("This planner has not been initialized, please call initialize() before using this planner");
00477       return false;
00478     }
00479 
00480     //copy over the odometry information
00481     nav_msgs::Odometry base_odom;
00482     {
00483       boost::recursive_mutex::scoped_lock lock(odom_lock_);
00484       base_odom = base_odom_;
00485     }
00486 
00487     return iri_ackermann_local_planner::isGoalReached(*tf_, global_plan_, *costmap_ros_, global_frame_, base_odom, 
00488         rot_stopped_velocity_, trans_stopped_velocity_, xy_goal_tolerance_, yaw_goal_tolerance_);
00489   }
00490 };


iri_ackermann_local_planner
Author(s): Sergi Hernandez Juan
autogenerated on Fri Dec 6 2013 23:50:18