teb_local_planner_ros.cpp
Go to the documentation of this file.
00001 /*********************************************************************
00002  *
00003  * Software License Agreement (BSD License)
00004  *
00005  *  Copyright (c) 2016,
00006  *  TU Dortmund - Institute of Control Theory and Systems Engineering.
00007  *  All rights reserved.
00008  *
00009  *  Redistribution and use in source and binary forms, with or without
00010  *  modification, are permitted provided that the following conditions
00011  *  are met:
00012  *
00013  *   * Redistributions of source code must retain the above copyright
00014  *     notice, this list of conditions and the following disclaimer.
00015  *   * Redistributions in binary form must reproduce the above
00016  *     copyright notice, this list of conditions and the following
00017  *     disclaimer in the documentation and/or other materials provided
00018  *     with the distribution.
00019  *   * Neither the name of the institute nor the names of its
00020  *     contributors may be used to endorse or promote products derived
00021  *     from this software without specific prior written permission.
00022  *
00023  *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00024  *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00025  *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
00026  *  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
00027  *  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
00028  *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
00029  *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00030  *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
00031  *  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00032  *  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
00033  *  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00034  *  POSSIBILITY OF SUCH DAMAGE.
00035  *
00036  * Author: Christoph Rösmann
00037  *********************************************************************/
00038 
00039 #include <teb_local_planner/teb_local_planner_ros.h>
00040 
00041 #include <tf_conversions/tf_eigen.h>
00042 
00043 #include <boost/algorithm/string.hpp>
00044 
00045 // pluginlib macros
00046 #include <pluginlib/class_list_macros.h>
00047 
00048 #include "g2o/core/sparse_optimizer.h"
00049 #include "g2o/core/block_solver.h"
00050 #include "g2o/core/factory.h"
00051 #include "g2o/core/optimization_algorithm_gauss_newton.h"
00052 #include "g2o/core/optimization_algorithm_levenberg.h"
00053 #include "g2o/solvers/csparse/linear_solver_csparse.h"
00054 #include "g2o/solvers/cholmod/linear_solver_cholmod.h"
00055 
00056 
00057 // register this planner as a BaseLocalPlanner plugin
00058 PLUGINLIB_DECLARE_CLASS(teb_local_planner, TebLocalPlannerROS, teb_local_planner::TebLocalPlannerROS, nav_core::BaseLocalPlanner)
00059 
00060 namespace teb_local_planner
00061 {
00062   
00063 
00064 TebLocalPlannerROS::TebLocalPlannerROS() : costmap_ros_(NULL), tf_(NULL), costmap_model_(NULL),
00065                                            costmap_converter_loader_("costmap_converter", "costmap_converter::BaseCostmapToPolygons"), 
00066                                            dynamic_recfg_(NULL), goal_reached_(false), horizon_reduced_(false), initialized_(false)
00067 {
00068 }
00069 
00070 
00071 TebLocalPlannerROS::~TebLocalPlannerROS()
00072 {
00073 }
00074 
00075 void TebLocalPlannerROS::reconfigureCB(TebLocalPlannerReconfigureConfig& config, uint32_t level)
00076 {
00077   cfg_.reconfigure(config);
00078 }
00079 
00080 void TebLocalPlannerROS::initialize(std::string name, tf::TransformListener* tf, costmap_2d::Costmap2DROS* costmap_ros)
00081 {
00082   // check if the plugin is already initialized
00083   if(!initialized_)
00084   {     
00085     // create Node Handle with name of plugin (as used in move_base for loading)
00086     ros::NodeHandle nh("~/" + name);
00087                 
00088     // get parameters of TebConfig via the nodehandle and override the default config
00089     cfg_.loadRosParamFromNodeHandle(nh);       
00090     
00091     // reserve some memory for obstacles
00092     obstacles_.reserve(500);
00093         
00094     // create visualization instance    
00095     visualization_ = TebVisualizationPtr(new TebVisualization(nh, cfg_)); 
00096         
00097     // create robot footprint/contour model for optimization
00098     RobotFootprintModelPtr robot_model = getRobotFootprintFromParamServer(nh);
00099     
00100     // create the planner instance
00101     if (cfg_.hcp.enable_homotopy_class_planning)
00102     {
00103       planner_ = PlannerInterfacePtr(new HomotopyClassPlanner(cfg_, &obstacles_, robot_model, visualization_, &via_points_));
00104       ROS_INFO("Parallel planning in distinctive topologies enabled.");
00105     }
00106     else
00107     {
00108       planner_ = PlannerInterfacePtr(new TebOptimalPlanner(cfg_, &obstacles_, robot_model, visualization_, &via_points_));
00109       ROS_INFO("Parallel planning in distinctive topologies disabled.");
00110     }
00111     
00112     // init other variables
00113     tf_ = tf;
00114     costmap_ros_ = costmap_ros;
00115     costmap_ = costmap_ros_->getCostmap(); // locking should be done in MoveBase.
00116     
00117     costmap_model_ = boost::make_shared<base_local_planner::CostmapModel>(*costmap_);
00118 
00119     global_frame_ = costmap_ros_->getGlobalFrameID();
00120     cfg_.map_frame = global_frame_; // TODO
00121     robot_base_frame_ = costmap_ros_->getBaseFrameID();
00122 
00123     //Initialize a costmap to polygon converter
00124     if (!cfg_.obstacles.costmap_converter_plugin.empty())
00125     {
00126       try
00127       {
00128         costmap_converter_ = costmap_converter_loader_.createInstance(cfg_.obstacles.costmap_converter_plugin);
00129         std::string converter_name = costmap_converter_loader_.getName(cfg_.obstacles.costmap_converter_plugin);
00130         // replace '::' by '/' to convert the c++ namespace to a NodeHandle namespace
00131         boost::replace_all(converter_name, "::", "/");
00132         costmap_converter_->initialize(ros::NodeHandle(nh, "costmap_converter/" + converter_name));
00133         costmap_converter_->setCostmap2D(costmap_);
00134         
00135         costmap_converter_->startWorker(ros::Rate(cfg_.obstacles.costmap_converter_rate), costmap_, cfg_.obstacles.costmap_converter_spin_thread);
00136         ROS_INFO_STREAM("Costmap conversion plugin " << cfg_.obstacles.costmap_converter_plugin << " loaded.");        
00137       }
00138       catch(pluginlib::PluginlibException& ex)
00139       {
00140         ROS_WARN("The specified costmap converter plugin cannot be loaded. All occupied costmap cells are treaten as point obstacles. Error message: %s", ex.what());
00141         costmap_converter_.reset();
00142       }
00143     }
00144     else 
00145       ROS_INFO("No costmap conversion plugin specified. All occupied costmap cells are treaten as point obstacles.");
00146   
00147     
00148     // Get footprint of the robot and minimum and maximum distance from the center of the robot to its footprint vertices.
00149     footprint_spec_ = costmap_ros_->getRobotFootprint();
00150     costmap_2d::calculateMinAndMaxDistances(footprint_spec_, robot_inscribed_radius_, robot_circumscribed_radius);    
00151 
00152     // init the odom helper to receive the robot's velocity from odom messages
00153     odom_helper_.setOdomTopic(cfg_.odom_topic);
00154 
00155     // setup dynamic reconfigure
00156     dynamic_recfg_ = boost::make_shared< dynamic_reconfigure::Server<TebLocalPlannerReconfigureConfig> >(nh);
00157     dynamic_reconfigure::Server<TebLocalPlannerReconfigureConfig>::CallbackType cb = boost::bind(&TebLocalPlannerROS::reconfigureCB, this, _1, _2);
00158     dynamic_recfg_->setCallback(cb);
00159         
00160     // setup callback for custom obstacles
00161     custom_obst_sub_ = nh.subscribe("obstacles", 1, &TebLocalPlannerROS::customObstacleCB, this);
00162     
00163     // set initialized flag
00164     initialized_ = true;
00165 
00166     ROS_DEBUG("teb_local_planner plugin initialized.");
00167   }
00168   else
00169   {
00170     ROS_WARN("teb_local_planner has already been initialized, doing nothing.");
00171   }
00172 }
00173 
00174 
00175 
00176 bool TebLocalPlannerROS::setPlan(const std::vector<geometry_msgs::PoseStamped>& orig_global_plan)
00177 {
00178   // check if plugin is initialized
00179   if(!initialized_)
00180   {
00181     ROS_ERROR("teb_local_planner has not been initialized, please call initialize() before using this planner");
00182     return false;
00183   }
00184 
00185   // store the global plan
00186   global_plan_.clear();
00187   global_plan_ = orig_global_plan;
00188 
00189   // we do not clear the local planner here, since setPlan is called frequently whenever the global planner updates the plan.
00190   // the local planner checks whether it is required to reinitialize the trajectory or not within each velocity computation step.  
00191             
00192   // reset goal_reached_ flag
00193   goal_reached_ = false;
00194   
00195   return true;
00196 }
00197 
00198 
00199 bool TebLocalPlannerROS::computeVelocityCommands(geometry_msgs::Twist& cmd_vel)
00200 {
00201   // check if plugin initialized
00202   if(!initialized_)
00203   {
00204     ROS_ERROR("teb_local_planner has not been initialized, please call initialize() before using this planner");
00205     return false;
00206   }
00207 
00208   cmd_vel.linear.x = 0;
00209   cmd_vel.angular.z = 0;
00210   goal_reached_ = false;  
00211   
00212   // Get robot pose
00213   tf::Stamped<tf::Pose> robot_pose;
00214   costmap_ros_->getRobotPose(robot_pose);
00215   robot_pose_ = PoseSE2(robot_pose);
00216     
00217   // Get robot velocity
00218   tf::Stamped<tf::Pose> robot_vel_tf;
00219   odom_helper_.getRobotVel(robot_vel_tf);
00220   robot_vel_ = tfPoseToEigenVector2dTransRot(robot_vel_tf);
00221   geometry_msgs::Twist robot_vel_twist;
00222   robot_vel_twist.linear.x = robot_vel_[0];
00223   robot_vel_twist.angular.z = robot_vel_[1];   
00224   
00225   // prune global plan to cut off parts of the past (spatially before the robot)
00226   pruneGlobalPlan(*tf_, robot_pose, global_plan_);
00227 
00228   // Transform global plan to the frame of interest (w.r.t. the local costmap)
00229   std::vector<geometry_msgs::PoseStamped> transformed_plan;
00230   int goal_idx;
00231   tf::StampedTransform tf_plan_to_global;
00232   if (!transformGlobalPlan(*tf_, global_plan_, robot_pose, *costmap_, global_frame_, cfg_.trajectory.max_global_plan_lookahead_dist, 
00233                            transformed_plan, &goal_idx, &tf_plan_to_global))
00234   {
00235     ROS_WARN("Could not transform the global plan to the frame of the controller");
00236     return false;
00237   }
00238 
00239   // check if global goal is reached
00240   tf::Stamped<tf::Pose> global_goal;
00241   tf::poseStampedMsgToTF(global_plan_.back(), global_goal);
00242   global_goal.setData( tf_plan_to_global * global_goal );
00243   double dx = global_goal.getOrigin().getX() - robot_pose_.x();
00244   double dy = global_goal.getOrigin().getY() - robot_pose_.y();
00245   double delta_orient = g2o::normalize_theta( tf::getYaw(global_goal.getRotation()) - robot_pose_.theta() );
00246   if(fabs(std::sqrt(dx*dx+dy*dy)) < cfg_.goal_tolerance.xy_goal_tolerance
00247     && fabs(delta_orient) < cfg_.goal_tolerance.yaw_goal_tolerance)
00248   {
00249     goal_reached_ = true;
00250     return true;
00251   }
00252   
00253   // Shorten horizon if requested
00254   if (horizon_reduced_)
00255   {
00256     // reduce to 50 percent:
00257     int horizon_reduction = goal_idx/2;
00258     // we have a small overhead here, since we already transformed 50% more of the trajectory.
00259     // But that's ok for now, since we do not need to make transformGlobalPlan more complex 
00260     // and a reduced horizon should occur just rarely.
00261     int new_goal_idx_transformed_plan = int(transformed_plan.size()) - horizon_reduction - 1;
00262     goal_idx -= horizon_reduction;
00263     if (new_goal_idx_transformed_plan>0 && goal_idx >= 0)
00264       transformed_plan.erase(transformed_plan.begin()+new_goal_idx_transformed_plan, transformed_plan.end());
00265     else goal_idx += horizon_reduction; // this should not happen, but safety first ;-)
00266   }
00267     
00268   // Return false if the transformed global plan is empty
00269   if (transformed_plan.empty())
00270   {
00271     ROS_WARN("Transformed plan is empty. Cannot determine a local plan.");
00272     return false;
00273   }
00274               
00275   // Get current goal point (last point of the transformed plan)
00276   tf::Stamped<tf::Pose> goal_point;
00277   tf::poseStampedMsgToTF(transformed_plan.back(), goal_point);
00278   robot_goal_.x() = goal_point.getOrigin().getX();
00279   robot_goal_.y() = goal_point.getOrigin().getY();      
00280   if (cfg_.trajectory.global_plan_overwrite_orientation)
00281   {
00282     robot_goal_.theta() = estimateLocalGoalOrientation(global_plan_, goal_point, goal_idx, tf_plan_to_global);
00283     // overwrite/update goal orientation of the transformed plan with the actual goal (enable using the plan as initialization)
00284     transformed_plan.back().pose.orientation = tf::createQuaternionMsgFromYaw(robot_goal_.theta());
00285   }  
00286   else
00287   {
00288     robot_goal_.theta() = tf::getYaw(goal_point.getRotation());
00289   }
00290 
00291   // overwrite/update start of the transformed plan with the actual robot position (allows using the plan as initial trajectory)
00292   if (transformed_plan.size()==1) // plan only contains the goal
00293   {
00294     transformed_plan.insert(transformed_plan.begin(), geometry_msgs::PoseStamped()); // insert start (not yet initialized)
00295   }
00296   tf::poseTFToMsg(robot_pose, transformed_plan.front().pose); // update start;
00297     
00298   // clear currently existing obstacles
00299   obstacles_.clear();
00300   
00301   // Update obstacle container with costmap information or polygons provided by a costmap_converter plugin
00302   if (costmap_converter_)
00303     updateObstacleContainerWithCostmapConverter();
00304   else
00305     updateObstacleContainerWithCostmap();
00306   
00307   // also consider custom obstacles (must be called after other updates, since the container is not cleared)
00308   updateObstacleContainerWithCustomObstacles();
00309   
00310   // update via-points container
00311   updateViaPointsContainer(transformed_plan, cfg_.trajectory.global_plan_viapoint_sep);
00312     
00313   // Do not allow config changes during the following optimization step
00314   boost::mutex::scoped_lock cfg_lock(cfg_.configMutex());
00315     
00316   // Now perform the actual planning
00317 //   bool success = planner_->plan(robot_pose_, robot_goal_, robot_vel_, cfg_.goal_tolerance.free_goal_vel); // straight line init
00318   bool success = planner_->plan(transformed_plan, &robot_vel_twist, cfg_.goal_tolerance.free_goal_vel);
00319   if (!success)
00320   {
00321     planner_->clearPlanner(); // force reinitialization for next time
00322     ROS_WARN("teb_local_planner was not able to obtain a local plan for the current setting.");
00323     return false;
00324   }
00325     
00326   // Undo temporary horizon reduction
00327   if (horizon_reduced_ && (ros::Time::now()-horizon_reduced_stamp_).toSec() >= 10 && !planner_->isHorizonReductionAppropriate(transformed_plan)) // 10s are hardcoded for now...
00328   {
00329     horizon_reduced_ = false;
00330     ROS_INFO("Switching back to full horizon length.");
00331   }
00332      
00333   // Check feasibility (but within the first few states only)
00334   bool feasible = planner_->isTrajectoryFeasible(costmap_model_.get(), footprint_spec_, robot_inscribed_radius_, robot_circumscribed_radius, cfg_.trajectory.feasibility_check_no_poses);
00335   if (!feasible)
00336   {
00337     cmd_vel.linear.x = 0;
00338     cmd_vel.angular.z = 0;
00339    
00340     if (!horizon_reduced_ && cfg_.trajectory.shrink_horizon_backup && planner_->isHorizonReductionAppropriate(transformed_plan))
00341     {
00342       horizon_reduced_ = true;
00343       ROS_WARN("TebLocalPlannerROS: trajectory is not feasible, but the planner suggests a shorter horizon temporary. Complying with its wish for at least 10s...");
00344       horizon_reduced_stamp_ = ros::Time::now();
00345       return true; // commanded velocity is zero for this step
00346     }
00347     // now we reset everything to start again with the initialization of new trajectories.
00348     planner_->clearPlanner();
00349     ROS_WARN("TebLocalPlannerROS: trajectory is not feasible. Resetting planner...");
00350        
00351     return false;
00352   }
00353 
00354   // Get the velocity command for this sampling interval
00355   if (!planner_->getVelocityCommand(cmd_vel.linear.x, cmd_vel.angular.z))
00356   {
00357     planner_->clearPlanner();
00358     ROS_WARN("TebLocalPlannerROS: velocity command invalid. Resetting planner...");
00359     return false;
00360   }
00361   
00362   // Saturate velocity, if the optimization results violates the constraints (could be possible due to soft constraints).
00363   saturateVelocity(cmd_vel.linear.x, cmd_vel.angular.z, cfg_.robot.max_vel_x, cfg_.robot.max_vel_theta, cfg_.robot.max_vel_x_backwards);
00364 
00365   // convert rot-vel to steering angle if desired (carlike robot).
00366   // The min_turning_radius is allowed to be slighly smaller since it is a soft-constraint
00367   // and opposed to the other constraints not affected by penalty_epsilon. The user might add a safety margin to the parameter itself.
00368   if (cfg_.robot.cmd_angle_instead_rotvel)
00369   {
00370     cmd_vel.angular.z = convertTransRotVelToSteeringAngle(cmd_vel.linear.x, cmd_vel.angular.z, cfg_.robot.wheelbase, 0.95*cfg_.robot.min_turning_radius);
00371     if (!std::isfinite(cmd_vel.angular.z))
00372     {
00373       cmd_vel.linear.x = cmd_vel.angular.z = 0;
00374       planner_->clearPlanner();
00375       ROS_WARN("TebLocalPlannerROS: Resulting steering angle is not finite. Resetting planner...");
00376       return false;
00377     }
00378   }
00379   
00380   // Now visualize everything               
00381   planner_->visualize();
00382   visualization_->publishObstacles(obstacles_);
00383   visualization_->publishViaPoints(via_points_);
00384   visualization_->publishGlobalPlan(global_plan_);
00385   return true;
00386 }
00387 
00388 
00389 bool TebLocalPlannerROS::isGoalReached()
00390 {
00391   if (goal_reached_)
00392   {
00393     ROS_INFO("GOAL Reached!");
00394     planner_->clearPlanner();
00395     return true;
00396   }
00397   return false;
00398 }
00399 
00400 
00401 
00402 void TebLocalPlannerROS::updateObstacleContainerWithCostmap()
00403 {  
00404   // Add costmap obstacles if desired
00405   if (cfg_.obstacles.include_costmap_obstacles)
00406   {
00407     Eigen::Vector2d robot_orient = robot_pose_.orientationUnitVec();
00408     
00409     for (unsigned int i=0; i<costmap_->getSizeInCellsX()-1; ++i)
00410     {
00411       for (unsigned int j=0; j<costmap_->getSizeInCellsY()-1; ++j)
00412       {
00413         if (costmap_->getCost(i,j) == costmap_2d::LETHAL_OBSTACLE)
00414         {
00415           Eigen::Vector2d obs;
00416           costmap_->mapToWorld(i,j,obs.coeffRef(0), obs.coeffRef(1));
00417             
00418           // check if obstacle is interesting (e.g. not far behind the robot)
00419           Eigen::Vector2d obs_dir = obs-robot_pose_.position();
00420           if ( obs_dir.dot(robot_orient) < 0 && obs_dir.norm() > cfg_.obstacles.costmap_obstacles_behind_robot_dist  )
00421             continue;
00422             
00423           obstacles_.push_back(ObstaclePtr(new PointObstacle(obs)));
00424         }
00425       }
00426     }
00427   }
00428 }
00429 
00430 void TebLocalPlannerROS::updateObstacleContainerWithCostmapConverter()
00431 {
00432   if (!costmap_converter_)
00433     return;
00434     
00435   //Get obstacles from costmap converter
00436   costmap_converter::PolygonContainerConstPtr polygons =  costmap_converter_->getPolygons();
00437   if (!polygons)
00438     return;
00439   
00440   for (std::size_t i=0; i<polygons->size(); ++i)
00441   {
00442     if (polygons->at(i).points.size()==1) // Point
00443     {
00444       obstacles_.push_back(ObstaclePtr(new PointObstacle(polygons->at(i).points[0].x, polygons->at(i).points[0].y)));
00445     }
00446     else if (polygons->at(i).points.size()==2) // Line
00447     {
00448       obstacles_.push_back(ObstaclePtr(new LineObstacle(polygons->at(i).points[0].x, polygons->at(i).points[0].y,
00449                                                         polygons->at(i).points[1].x, polygons->at(i).points[1].y )));
00450     }
00451     else if (polygons->at(i).points.size()>2) // Real polygon
00452     {
00453         PolygonObstacle* polyobst = new PolygonObstacle;
00454         for (std::size_t j=0; j<polygons->at(i).points.size(); ++j)
00455         {
00456             polyobst->pushBackVertex(polygons->at(i).points[j].x, polygons->at(i).points[j].y);
00457         }
00458         polyobst->finalizePolygon();
00459         obstacles_.push_back(ObstaclePtr(polyobst));
00460     }
00461   }
00462 }
00463 
00464 
00465 void TebLocalPlannerROS::updateObstacleContainerWithCustomObstacles()
00466 {
00467   // Add custom obstacles obtained via message
00468   boost::mutex::scoped_lock l(custom_obst_mutex_);
00469   
00470   if (!custom_obstacle_msg_.obstacles.empty())
00471   {
00472     // We only use the global header to specify the obstacle coordinate system instead of individual ones
00473     Eigen::Affine3d obstacle_to_map_eig;
00474     try 
00475     {
00476       tf::StampedTransform obstacle_to_map;
00477       tf_->waitForTransform(global_frame_, ros::Time(0),
00478             custom_obstacle_msg_.header.frame_id, ros::Time(0),
00479             custom_obstacle_msg_.header.frame_id, ros::Duration(0.5));
00480       tf_->lookupTransform(global_frame_, ros::Time(0),
00481           custom_obstacle_msg_.header.frame_id, ros::Time(0), 
00482           custom_obstacle_msg_.header.frame_id, obstacle_to_map);
00483       tf::transformTFToEigen(obstacle_to_map, obstacle_to_map_eig);
00484     }
00485     catch (tf::TransformException ex)
00486     {
00487       ROS_ERROR("%s",ex.what());
00488       obstacle_to_map_eig.setIdentity();
00489     }
00490     
00491     for (std::vector<geometry_msgs::PolygonStamped>::const_iterator obst_it = custom_obstacle_msg_.obstacles.begin(); obst_it != custom_obstacle_msg_.obstacles.end(); ++obst_it)
00492     {
00493       if (obst_it->polygon.points.size() == 1 ) // point
00494       {
00495         Eigen::Vector3d pos( obst_it->polygon.points.front().x, obst_it->polygon.points.front().y, obst_it->polygon.points.front().z );
00496         obstacles_.push_back(ObstaclePtr(new PointObstacle( (obstacle_to_map_eig * pos).head(2) )));
00497       }
00498       else if (obst_it->polygon.points.size() == 2 ) // line
00499       {
00500         Eigen::Vector3d line_start( obst_it->polygon.points.front().x, obst_it->polygon.points.front().y, obst_it->polygon.points.front().z );
00501         Eigen::Vector3d line_end( obst_it->polygon.points.back().x, obst_it->polygon.points.back().y, obst_it->polygon.points.back().z );
00502         obstacles_.push_back(ObstaclePtr(new LineObstacle( (obstacle_to_map_eig * line_start).head(2), (obstacle_to_map_eig * line_end).head(2) )));
00503       }
00504       else // polygon
00505       {
00506         PolygonObstacle* polyobst = new PolygonObstacle;
00507         for (int i=0; i<(int)obst_it->polygon.points.size(); ++i)
00508         {
00509           Eigen::Vector3d pos( obst_it->polygon.points[i].x, obst_it->polygon.points[i].y, obst_it->polygon.points[i].z );
00510           polyobst->pushBackVertex( (obstacle_to_map_eig * pos).head(2) );
00511         }
00512         polyobst->finalizePolygon();
00513         obstacles_.push_back(ObstaclePtr(polyobst));
00514       }
00515     }
00516   }
00517 }
00518 
00519 void TebLocalPlannerROS::updateViaPointsContainer(const std::vector<geometry_msgs::PoseStamped>& transformed_plan, double min_separation)
00520 {
00521   via_points_.clear();
00522   
00523   if (min_separation<0)
00524     return;
00525   
00526   std::size_t prev_idx = 0;
00527   for (std::size_t i=1; i < transformed_plan.size(); ++i) // skip first one, since we do not need any point before the first min_separation [m]
00528   {
00529     // check separation to the previous via-point inserted
00530     if (distance_points2d( transformed_plan[prev_idx].pose.position, transformed_plan[i].pose.position ) < min_separation)
00531       continue;
00532         
00533     // add via-point
00534     via_points_.push_back( Eigen::Vector2d( transformed_plan[i].pose.position.x, transformed_plan[i].pose.position.y ) );
00535     prev_idx = i;
00536   }
00537   
00538 }
00539       
00540 Eigen::Vector2d TebLocalPlannerROS::tfPoseToEigenVector2dTransRot(const tf::Pose& tf_vel)
00541 {
00542   Eigen::Vector2d vel;
00543   vel.coeffRef(0) = std::sqrt( tf_vel.getOrigin().getX() * tf_vel.getOrigin().getX() + tf_vel.getOrigin().getY() * tf_vel.getOrigin().getY() );
00544   vel.coeffRef(1) = tf::getYaw(tf_vel.getRotation());
00545   return vel;
00546 }
00547       
00548       
00549 bool TebLocalPlannerROS::pruneGlobalPlan(const tf::TransformListener& tf, const tf::Stamped<tf::Pose>& global_pose, std::vector<geometry_msgs::PoseStamped>& global_plan, double dist_behind_robot)
00550 {
00551   if (global_plan.empty())
00552     return true;
00553   
00554   try
00555   {
00556     // transform robot pose into the plan frame (we do not wait here, since pruning not crucial, if missed a few times)
00557     tf::StampedTransform global_to_plan_transform;
00558     tf.lookupTransform(global_plan.front().header.frame_id, global_pose.frame_id_, ros::Time(0), global_to_plan_transform);
00559     tf::Stamped<tf::Pose> robot;
00560     robot.setData( global_to_plan_transform * global_pose );
00561     
00562     double dist_thresh_sq = dist_behind_robot*dist_behind_robot;
00563     
00564     // iterate plan until a pose close the robot is found
00565     std::vector<geometry_msgs::PoseStamped>::iterator it = global_plan.begin();
00566     std::vector<geometry_msgs::PoseStamped>::iterator erase_end = it;
00567     while (it != global_plan.end())
00568     {
00569       double dx = robot.getOrigin().x() - it->pose.position.x;
00570       double dy = robot.getOrigin().y() - it->pose.position.y;
00571       double dist_sq = dx * dx + dy * dy;
00572       if (dist_sq < dist_thresh_sq)
00573       {
00574          erase_end = it;
00575          break;
00576       }
00577       ++it;
00578     }
00579     if (erase_end == global_plan.end())
00580       return false;
00581     
00582     if (erase_end != global_plan.begin())
00583       global_plan.erase(global_plan.begin(), erase_end);
00584   }
00585   catch (const tf::TransformException& ex)
00586   {
00587     ROS_DEBUG("Cannot prune path since no transform is available: %s\n", ex.what());
00588     return false;
00589   }
00590   return true;
00591 }
00592       
00593 
00594 bool TebLocalPlannerROS::transformGlobalPlan(const tf::TransformListener& tf, const std::vector<geometry_msgs::PoseStamped>& global_plan,
00595                   const tf::Stamped<tf::Pose>& global_pose, const costmap_2d::Costmap2D& costmap, const std::string& global_frame, double max_plan_length,
00596                   std::vector<geometry_msgs::PoseStamped>& transformed_plan, int* current_goal_idx, tf::StampedTransform* tf_plan_to_global) const
00597 {
00598   // this method is a slightly modified version of base_local_planner/goal_functions.h
00599 
00600   const geometry_msgs::PoseStamped& plan_pose = global_plan[0];
00601 
00602   transformed_plan.clear();
00603 
00604   try 
00605   {
00606     if (global_plan.empty())
00607     {
00608       ROS_ERROR("Received plan with zero length");
00609       *current_goal_idx = 0;
00610       return false;
00611     }
00612 
00613     // get plan_to_global_transform from plan frame to global_frame
00614     tf::StampedTransform plan_to_global_transform;
00615     tf.waitForTransform(global_frame, ros::Time::now(),
00616     plan_pose.header.frame_id, plan_pose.header.stamp,
00617     plan_pose.header.frame_id, ros::Duration(0.5));
00618     tf.lookupTransform(global_frame, ros::Time(),
00619     plan_pose.header.frame_id, plan_pose.header.stamp, 
00620     plan_pose.header.frame_id, plan_to_global_transform);
00621 
00622     //let's get the pose of the robot in the frame of the plan
00623     tf::Stamped<tf::Pose> robot_pose;
00624     tf.transformPose(plan_pose.header.frame_id, global_pose, robot_pose);
00625 
00626     //we'll discard points on the plan that are outside the local costmap
00627     double dist_threshold = std::max(costmap.getSizeInCellsX() * costmap.getResolution() / 2.0,
00628                                      costmap.getSizeInCellsY() * costmap.getResolution() / 2.0);
00629     dist_threshold *= 0.85; // just consider 85% of the costmap size to better incorporate point obstacle that are
00630                            // located on the border of the local costmap
00631     
00632 
00633     int i = 0;
00634     double sq_dist_threshold = dist_threshold * dist_threshold;
00635     double sq_dist = 1e10;
00636     
00637     //we need to loop to a point on the plan that is within a certain distance of the robot
00638     while(i < (int)global_plan.size())
00639     {
00640       double x_diff = robot_pose.getOrigin().x() - global_plan[i].pose.position.x;
00641       double y_diff = robot_pose.getOrigin().y() - global_plan[i].pose.position.y;
00642       double new_sq_dist = x_diff * x_diff + y_diff * y_diff;
00643       if (new_sq_dist > sq_dist && sq_dist < sq_dist_threshold) // find first distance that is greater
00644       {
00645         sq_dist = new_sq_dist;
00646         break;
00647       }
00648       sq_dist = new_sq_dist;
00649       ++i;
00650     }
00651     
00652     tf::Stamped<tf::Pose> tf_pose;
00653     geometry_msgs::PoseStamped newer_pose;
00654     
00655     double plan_length = 0; // check cumulative Euclidean distance along the plan
00656     
00657     //now we'll transform until points are outside of our distance threshold
00658     while(i < (int)global_plan.size() && sq_dist <= sq_dist_threshold && (max_plan_length<=0 || plan_length <= max_plan_length))
00659     {
00660       const geometry_msgs::PoseStamped& pose = global_plan[i];
00661       tf::poseStampedMsgToTF(pose, tf_pose);
00662       tf_pose.setData(plan_to_global_transform * tf_pose);
00663       tf_pose.stamp_ = plan_to_global_transform.stamp_;
00664       tf_pose.frame_id_ = global_frame;
00665       tf::poseStampedTFToMsg(tf_pose, newer_pose);
00666 
00667       transformed_plan.push_back(newer_pose);
00668 
00669       double x_diff = robot_pose.getOrigin().x() - global_plan[i].pose.position.x;
00670       double y_diff = robot_pose.getOrigin().y() - global_plan[i].pose.position.y;
00671       sq_dist = x_diff * x_diff + y_diff * y_diff;
00672       
00673       // caclulate distance to previous pose
00674       if (i>0 && max_plan_length>0)
00675         plan_length += distance_points2d(global_plan[i-1].pose.position, global_plan[i].pose.position);
00676 
00677       ++i;
00678     }
00679         
00680     // if we are really close to the goal (<sq_dist_threshold) and the goal is not yet reached (e.g. orientation error >>0)
00681     // the resulting transformed plan can be empty. In that case we explicitly inject the global goal.
00682     if (transformed_plan.empty())
00683     {
00684       tf::poseStampedMsgToTF(global_plan.back(), tf_pose);
00685       tf_pose.setData(plan_to_global_transform * tf_pose);
00686       tf_pose.stamp_ = plan_to_global_transform.stamp_;
00687       tf_pose.frame_id_ = global_frame;
00688       tf::poseStampedTFToMsg(tf_pose, newer_pose);
00689 
00690       transformed_plan.push_back(newer_pose);
00691       
00692       // Return the index of the current goal point (inside the distance threshold)
00693       if (current_goal_idx) *current_goal_idx = int(global_plan.size())-1;
00694     }
00695     else
00696     {
00697       // Return the index of the current goal point (inside the distance threshold)
00698       if (current_goal_idx) *current_goal_idx = i-1; // subtract 1, since i was increased once before leaving the loop
00699     }
00700     
00701     // Return the transformation from the global plan to the global planning frame if desired
00702     if (tf_plan_to_global) *tf_plan_to_global = plan_to_global_transform;
00703   }
00704   catch(tf::LookupException& ex)
00705   {
00706     ROS_ERROR("No Transform available Error: %s\n", ex.what());
00707     return false;
00708   }
00709   catch(tf::ConnectivityException& ex) 
00710   {
00711     ROS_ERROR("Connectivity Error: %s\n", ex.what());
00712     return false;
00713   }
00714   catch(tf::ExtrapolationException& ex) 
00715   {
00716     ROS_ERROR("Extrapolation Error: %s\n", ex.what());
00717     if (global_plan.size() > 0)
00718       ROS_ERROR("Global Frame: %s Plan Frame size %d: %s\n", global_frame.c_str(), (unsigned int)global_plan.size(), global_plan[0].header.frame_id.c_str());
00719 
00720     return false;
00721   }
00722 
00723   return true;
00724 }
00725 
00726     
00727       
00728       
00729 double TebLocalPlannerROS::estimateLocalGoalOrientation(const std::vector<geometry_msgs::PoseStamped>& global_plan, const tf::Stamped<tf::Pose>& local_goal,
00730                                 int current_goal_idx, const tf::StampedTransform& tf_plan_to_global, int moving_average_length) const
00731 {
00732   int n = (int)global_plan.size();
00733   
00734   // check if we are near the global goal already
00735   if (current_goal_idx > n-moving_average_length-2)
00736   {
00737     if (current_goal_idx >= n-1) // we've exactly reached the goal
00738     {
00739       return tf::getYaw(local_goal.getRotation());
00740     }
00741     else
00742     {
00743       tf::Quaternion global_orientation;
00744       tf::quaternionMsgToTF(global_plan.back().pose.orientation, global_orientation);
00745       return  tf::getYaw(tf_plan_to_global.getRotation() *  global_orientation );
00746     }     
00747   }
00748   
00749   // reduce number of poses taken into account if the desired number of poses is not available
00750   moving_average_length = std::min(moving_average_length, n-current_goal_idx-1 ); // maybe redundant, since we have checked the vicinity of the goal before
00751   
00752   std::vector<double> candidates;
00753   tf::Stamped<tf::Pose> tf_pose_k = local_goal;
00754   tf::Stamped<tf::Pose> tf_pose_kp1;
00755   
00756   int range_end = current_goal_idx + moving_average_length;
00757   for (int i = current_goal_idx; i < range_end; ++i)
00758   {
00759     // Transform pose of the global plan to the planning frame
00760     const geometry_msgs::PoseStamped& pose = global_plan.at(i+1);
00761     tf::poseStampedMsgToTF(pose, tf_pose_kp1);
00762     tf_pose_kp1.setData(tf_plan_to_global * tf_pose_kp1);
00763       
00764     // calculate yaw angle  
00765     candidates.push_back( std::atan2(tf_pose_kp1.getOrigin().getY() - tf_pose_k.getOrigin().getY(),
00766                           tf_pose_kp1.getOrigin().getX() - tf_pose_k.getOrigin().getX() ) );
00767     
00768     if (i<range_end-1) 
00769       tf_pose_k = tf_pose_kp1;
00770   }
00771   return average_angles(candidates);
00772 }
00773       
00774       
00775 void TebLocalPlannerROS::saturateVelocity(double& v, double& omega, double max_vel_x, double max_vel_theta, double max_vel_x_backwards) const
00776 {
00777   // Limit translational velocity for forward driving
00778   if (v > max_vel_x)
00779     v = max_vel_x;
00780   
00781   // Limit angular velocity
00782   if (omega > max_vel_theta)
00783     omega = max_vel_theta;
00784   else if (omega < -max_vel_theta)
00785     omega = -max_vel_theta;
00786   
00787   // Limit backwards velocity
00788   if (max_vel_x_backwards<=0)
00789   {
00790     ROS_WARN_ONCE("TebLocalPlannerROS(): Do not choose max_vel_x_backwards to be <=0. Disable backwards driving by increasing the optimization weight for penalyzing backwards driving.");
00791   }
00792   else if (v < -max_vel_x_backwards)
00793     v = -max_vel_x_backwards;
00794 }
00795      
00796      
00797 double TebLocalPlannerROS::convertTransRotVelToSteeringAngle(double v, double omega, double wheelbase, double min_turning_radius) const
00798 {
00799   if (omega==0 || v==0)
00800     return 0;
00801     
00802   double radius = v/omega;
00803   
00804   if (fabs(radius) < min_turning_radius)
00805     radius = double(g2o::sign(radius)) * min_turning_radius; 
00806 
00807   return std::atan(wheelbase / radius);
00808 }
00809      
00810      
00811 void TebLocalPlannerROS::customObstacleCB(const teb_local_planner::ObstacleMsg::ConstPtr& obst_msg)
00812 {
00813   boost::mutex::scoped_lock l(custom_obst_mutex_);
00814   custom_obstacle_msg_ = *obst_msg;  
00815 }
00816      
00817 RobotFootprintModelPtr TebLocalPlannerROS::getRobotFootprintFromParamServer(const ros::NodeHandle& nh)
00818 {
00819   std::string model_name; 
00820   if (!nh.getParam("footprint_model/type", model_name))
00821   {
00822     ROS_INFO("No robot footprint model specified for trajectory optimization. Using point-shaped model.");
00823     return boost::make_shared<PointRobotFootprint>();
00824   }
00825     
00826   // point  
00827   if (model_name.compare("point") == 0)
00828   {
00829     ROS_INFO("Footprint model 'point' loaded for trajectory optimization.");
00830     return boost::make_shared<PointRobotFootprint>();
00831   }
00832   
00833   // circular
00834   if (model_name.compare("circular") == 0)
00835   {
00836     // get radius
00837     double radius;
00838     if (!nh.getParam("footprint_model/radius", radius))
00839     {
00840       ROS_ERROR_STREAM("Footprint model 'circular' cannot be loaded for trajectory optimization, since param '" << nh.getNamespace() 
00841                        << "/footprint_model/radius' does not exist. Using point-model instead.");
00842       return boost::make_shared<PointRobotFootprint>();
00843     }
00844     ROS_INFO_STREAM("Footprint model 'circular' (radius: " << radius <<"m) loaded for trajectory optimization.");
00845     return boost::make_shared<CircularRobotFootprint>(radius);
00846   }
00847   
00848   // line
00849   if (model_name.compare("line") == 0)
00850   {
00851     // check parameters
00852     if (!nh.hasParam("footprint_model/line_start") || !nh.hasParam("footprint_model/line_end"))
00853     {
00854       ROS_ERROR_STREAM("Footprint model 'line' cannot be loaded for trajectory optimization, since param '" << nh.getNamespace() 
00855                        << "/footprint_model/line_start' and/or '.../line_end' do not exist. Using point-model instead.");
00856       return boost::make_shared<PointRobotFootprint>();
00857     }
00858     // get line coordinates
00859     std::vector<double> line_start, line_end;
00860     nh.getParam("footprint_model/line_start", line_start);
00861     nh.getParam("footprint_model/line_end", line_end);
00862     if (line_start.size() != 2 || line_end.size() != 2)
00863     {
00864       ROS_ERROR_STREAM("Footprint model 'line' cannot be loaded for trajectory optimization, since param '" << nh.getNamespace() 
00865                        << "/footprint_model/line_start' and/or '.../line_end' do not contain x and y coordinates (2D). Using point-model instead.");
00866       return boost::make_shared<PointRobotFootprint>();
00867     }
00868     
00869     ROS_INFO_STREAM("Footprint model 'line' (line_start: [" << line_start[0] << "," << line_start[1] <<"]m, line_end: ["
00870                      << line_end[0] << "," << line_end[1] << "]m) loaded for trajectory optimization.");
00871     return boost::make_shared<LineRobotFootprint>(Eigen::Map<const Eigen::Vector2d>(line_start.data()), Eigen::Map<const Eigen::Vector2d>(line_end.data()));
00872   }
00873   
00874   // two circles
00875   if (model_name.compare("two_circles") == 0)
00876   {
00877     // check parameters
00878     if (!nh.hasParam("footprint_model/front_offset") || !nh.hasParam("footprint_model/front_radius") 
00879         || !nh.hasParam("footprint_model/rear_offset") || !nh.hasParam("footprint_model/rear_radius"))
00880     {
00881       ROS_ERROR_STREAM("Footprint model 'two_circles' cannot be loaded for trajectory optimization, since params '" << nh.getNamespace()
00882                        << "/footprint_model/front_offset', '.../front_radius', '.../rear_offset' and '.../rear_radius' do not exist. Using point-model instead.");
00883       return boost::make_shared<PointRobotFootprint>();
00884     }
00885     double front_offset, front_radius, rear_offset, rear_radius;
00886     nh.getParam("footprint_model/front_offset", front_offset);
00887     nh.getParam("footprint_model/front_radius", front_radius);
00888     nh.getParam("footprint_model/rear_offset", rear_offset);
00889     nh.getParam("footprint_model/rear_radius", rear_radius);
00890     ROS_INFO_STREAM("Footprint model 'two_circles' (front_offset: " << front_offset <<"m, front_radius: " << front_radius 
00891                     << "m, rear_offset: " << rear_offset << "m, rear_radius: " << rear_radius << "m) loaded for trajectory optimization.");
00892     return boost::make_shared<TwoCirclesRobotFootprint>(front_offset, front_radius, rear_offset, rear_radius);
00893   }
00894 
00895   // polygon
00896   if (model_name.compare("polygon") == 0)
00897   {
00898 
00899     // check parameters
00900     XmlRpc::XmlRpcValue footprint_xmlrpc;
00901     if (!nh.getParam("footprint_model/vertices", footprint_xmlrpc) )
00902     {
00903       ROS_ERROR_STREAM("Footprint model 'polygon' cannot be loaded for trajectory optimization, since param '" << nh.getNamespace() 
00904                        << "/footprint_model/vertices' does not exist. Using point-model instead.");
00905       return boost::make_shared<PointRobotFootprint>();
00906     }
00907     // get vertices
00908     if (footprint_xmlrpc.getType() == XmlRpc::XmlRpcValue::TypeArray)
00909     {
00910       try
00911       {
00912         Point2dContainer polygon = makeFootprintFromXMLRPC(footprint_xmlrpc, "/footprint_model/vertices");
00913         ROS_INFO_STREAM("Footprint model 'polygon' loaded for trajectory optimization.");
00914         return boost::make_shared<PolygonRobotFootprint>(polygon);
00915       } 
00916       catch(const std::exception& ex)
00917       {
00918         ROS_ERROR_STREAM("Footprint model 'polygon' cannot be loaded for trajectory optimization: " << ex.what() << ". Using point-model instead.");
00919         return boost::make_shared<PointRobotFootprint>();
00920       }
00921     }
00922     else
00923     {
00924       ROS_ERROR_STREAM("Footprint model 'polygon' cannot be loaded for trajectory optimization, since param '" << nh.getNamespace() 
00925                        << "/footprint_model/vertices' does not define an array of coordinates. Using point-model instead.");
00926       return boost::make_shared<PointRobotFootprint>();
00927     }
00928     
00929   }
00930   
00931   // otherwise
00932   ROS_WARN_STREAM("Unknown robot footprint model specified with parameter '" << nh.getNamespace() << "/footprint_model/type'. Using point model instead.");
00933   return boost::make_shared<PointRobotFootprint>();
00934 }
00935          
00936        
00937        
00938        
00939 Point2dContainer TebLocalPlannerROS::makeFootprintFromXMLRPC(XmlRpc::XmlRpcValue& footprint_xmlrpc, const std::string& full_param_name)
00940 {
00941    // Make sure we have an array of at least 3 elements.
00942    if (footprint_xmlrpc.getType() != XmlRpc::XmlRpcValue::TypeArray ||
00943        footprint_xmlrpc.size() < 3)
00944    {
00945      ROS_FATAL("The footprint must be specified as list of lists on the parameter server, %s was specified as %s",
00946                 full_param_name.c_str(), std::string(footprint_xmlrpc).c_str());
00947      throw std::runtime_error("The footprint must be specified as list of lists on the parameter server with at least "
00948                               "3 points eg: [[x1, y1], [x2, y2], ..., [xn, yn]]");
00949    }
00950  
00951    Point2dContainer footprint;
00952    Eigen::Vector2d pt;
00953  
00954    for (int i = 0; i < footprint_xmlrpc.size(); ++i)
00955    {
00956      // Make sure each element of the list is an array of size 2. (x and y coordinates)
00957      XmlRpc::XmlRpcValue point = footprint_xmlrpc[ i ];
00958      if (point.getType() != XmlRpc::XmlRpcValue::TypeArray ||
00959          point.size() != 2)
00960      {
00961        ROS_FATAL("The footprint (parameter %s) must be specified as list of lists on the parameter server eg: "
00962                  "[[x1, y1], [x2, y2], ..., [xn, yn]], but this spec is not of that form.",
00963                   full_param_name.c_str());
00964        throw std::runtime_error("The footprint must be specified as list of lists on the parameter server eg: "
00965                                "[[x1, y1], [x2, y2], ..., [xn, yn]], but this spec is not of that form");
00966     }
00967 
00968     pt.x() = getNumberFromXMLRPC(point[ 0 ], full_param_name);
00969     pt.y() = getNumberFromXMLRPC(point[ 1 ], full_param_name);
00970 
00971     footprint.push_back(pt);
00972   }
00973   return footprint;
00974 }
00975 
00976 double TebLocalPlannerROS::getNumberFromXMLRPC(XmlRpc::XmlRpcValue& value, const std::string& full_param_name)
00977 {
00978   // Make sure that the value we're looking at is either a double or an int.
00979   if (value.getType() != XmlRpc::XmlRpcValue::TypeInt &&
00980       value.getType() != XmlRpc::XmlRpcValue::TypeDouble)
00981   {
00982     std::string& value_string = value;
00983     ROS_FATAL("Values in the footprint specification (param %s) must be numbers. Found value %s.",
00984                full_param_name.c_str(), value_string.c_str());
00985      throw std::runtime_error("Values in the footprint specification must be numbers");
00986    }
00987    return value.getType() == XmlRpc::XmlRpcValue::TypeInt ? (int)(value) : (double)(value);
00988 }
00989 
00990 } // end namespace teb_local_planner
00991 
00992 


teb_local_planner
Author(s): Christoph Rösmann
autogenerated on Mon Oct 24 2016 05:31:15