map_merge.cpp
Go to the documentation of this file.
00001 /*********************************************************************
00002  *
00003  * Software License Agreement (BSD License)
00004  *
00005  *  Copyright (c) 2014, Zhi Yan.
00006  *  Copyright (c) 2015-2016, Jiri Horner.
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 Jiri Horner 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  *********************************************************************/
00037 
00038 #include <map_merge/map_merge.h>
00039 
00040 #include <thread>
00041 
00042 #include <ros/console.h>
00043 #include <ros/assert.h>
00044 #include <occupancy_grid_utils/combine_grids.h>
00045 #include <occupancy_grid_utils/coordinate_conversions.h>
00046 #include <tf/transform_datatypes.h>
00047 #include <combine_grids/estimate_transform.h>
00048 
00049 namespace map_merge
00050 {
00051 geometry_msgs::Pose& operator+=(geometry_msgs::Pose&,
00052                                 const geometry_msgs::Pose&);
00053 
00054 MapMerging::MapMerging() : maps_size_(0)
00055 {
00056   ros::NodeHandle private_nh("~");
00057   std::string frame_id;
00058   std::string merged_map_topic;
00059 
00060   private_nh.param("merging_rate", merging_rate_, 4.0);
00061   private_nh.param("discovery_rate", discovery_rate_, 0.05);
00062   private_nh.param("estimation_rate", estimation_rate_, 0.5);
00063   private_nh.param("known_init_poses", have_initial_poses_, true);
00064   private_nh.param("estimation_confidence", confidence_treshold_, 1.0);
00065   private_nh.param<std::string>("robot_map_topic", robot_map_topic_, "map");
00066   private_nh.param<std::string>("robot_map_updates_topic",
00067                                 robot_map_updates_topic_, "map_updates");
00068   private_nh.param<std::string>("robot_namespace", robot_namespace_, "");
00069   private_nh.param<std::string>("merged_map_topic", merged_map_topic, "map");
00070   private_nh.param<std::string>("world_frame", frame_id, "world");
00071 
00072   merged_map_.header.frame_id = frame_id;
00073 
00074   /* publishing */
00075   merged_map_publisher_ =
00076       node_.advertise<nav_msgs::OccupancyGrid>(merged_map_topic, 50, true);
00077 }
00078 
00079 /*
00080  * Subcribe to pose and map topics
00081  */
00082 void MapMerging::topicSubscribing()
00083 {
00084   ROS_DEBUG("Robot discovery started.");
00085 
00086   ros::master::V_TopicInfo topic_infos;
00087   geometry_msgs::Pose init_pose;
00088   std::string robot_name;
00089   std::string map_topic;
00090   std::string map_updates_topic;
00091 
00092   ros::master::getTopics(topic_infos);
00093   // default msg constructor does no properly initialize quaternion
00094   init_pose.orientation.w = 1;  // create identity quaternion
00095 
00096   for (const auto& topic : topic_infos) {
00097     // we check only map topic
00098     if (!isRobotMapTopic(topic)) {
00099       continue;
00100     }
00101 
00102     robot_name = robotNameFromTopic(topic.name);
00103     if (robots_.count(robot_name)) {
00104       // we already know this robot
00105       continue;
00106     }
00107 
00108     if (have_initial_poses_ && !getInitPose(robot_name, init_pose)) {
00109       ROS_WARN("Couldn't get initial position for robot [%s]\n"
00110                "did you defined parameters map_merge/init_pose_[xyz]? in robot "
00111                "namespace? If you want to run merging without known initial "
00112                "positions of robots please set `known_init_poses` parameter "
00113                "to false. See relavant documentation for details.",
00114                robot_name.c_str());
00115       continue;
00116     }
00117 
00118     ROS_INFO("adding robot [%s] to system", robot_name.c_str());
00119     {
00120       // we need only shared lock here. 1) Map updating callbacks are not
00121       // using grid_view_, but maps directly. 2) It is iterator-safe to push
00122       // to maps_, so callbacks's pointers (used in callback) will NOT be
00123       // invalidated. Of course this must NOT 1) run concurently with map
00124       // merging as all iterators may be invalidated for grid_view_ 2) run
00125       // concurently with estimation as estimation is iterating over maps_
00126       boost::shared_lock<boost::shared_mutex> lock(merging_mutex_);
00127       maps_.emplace_front();
00128       ++maps_size_;
00129       all_grids_view_.emplace_back(maps_.front().map);
00130     }
00131 
00132     // no locking here. robots_ are used only in this procedure
00133     PosedMap& map = maps_.front();
00134     robots_.insert({robot_name, &map});
00135     map.initial_pose = init_pose;
00136 
00137     /* subscribe callbacks */
00138     map_topic = ros::names::append(robot_name, robot_map_topic_);
00139     map_updates_topic =
00140         ros::names::append(robot_name, robot_map_updates_topic_);
00141     ROS_INFO("Subscribing to MAP topic: %s.", map_topic.c_str());
00142     map.map_sub = node_.subscribe<nav_msgs::OccupancyGrid>(
00143         map_topic, 50,
00144         [this, &map](const nav_msgs::OccupancyGrid::ConstPtr& msg) {
00145           fullMapUpdate(msg, map);
00146         });
00147     ROS_INFO("Subscribing to MAP updates topic: %s.",
00148              map_updates_topic.c_str());
00149     map.map_updates_sub = node_.subscribe<map_msgs::OccupancyGridUpdate>(
00150         map_updates_topic, 50,
00151         [this, &map](const map_msgs::OccupancyGridUpdate::ConstPtr& msg) {
00152           partialMapUpdate(msg, map);
00153         });
00154   }
00155 }
00156 
00157 /*
00158  * mapMerging()
00159  */
00160 void MapMerging::mapMerging()
00161 {
00162   grids_view_t* grids;
00163 
00164   ROS_DEBUG("Map merging started.");
00165 
00166   /* when estimating we need to merge only estimated grids, when init poses are
00167    * known we will always merging all grids (estimated grids are empty). */
00168   if (have_initial_poses_) {
00169     grids = &all_grids_view_;
00170   } else {
00171     grids = &estimated_grids_view_;
00172   }
00173 
00174   {
00175     // exclusive locking. we don't want map sizes to change during muxing.
00176     std::lock_guard<boost::shared_mutex> lock(merging_mutex_);
00177     occupancy_grid_utils::combineGrids(grids->cbegin(), grids->cend(),
00178                                        merged_map_);
00179   }
00180 
00181   ROS_DEBUG("all maps merged, publishing");
00182   ros::Time now = ros::Time::now();
00183   merged_map_.info.map_load_time = now;
00184   merged_map_.header.stamp = now;
00185   merged_map_publisher_.publish(merged_map_);
00186 }
00187 
00188 void MapMerging::poseEstimation()
00189 {
00190   ROS_DEBUG("Grid pose estimation started.");
00191 
00192   std::vector<geometry_msgs::Pose> transforms;
00193 
00194   // do allocations outside of lock
00195   transforms.resize(maps_size_);
00196   estimated_grids_view_.reserve(maps_size_);
00197 
00198   // exclusive locking. we don't want map sizes to change when finding features.
00199   // Although this could run simultaneously with merging, we don't currently do
00200   // that. Also could be improved to lock only when looking for features.
00201   std::lock_guard<boost::shared_mutex> lock(merging_mutex_);
00202 
00203   // do the same resize under the lock. we must make sure it has the proper size
00204   transforms.resize(maps_size_);
00205 
00206   size_t num_est_transforms = combine_grids::estimateGridTransform(
00207       all_grids_view_.cbegin(), all_grids_view_.cend(), transforms.begin(),
00208       confidence_treshold_);
00209 
00210   /* update poses. remove grids whose transforms could not be
00211    * established from merging */
00212 
00213   // update grids origins
00214   estimated_grids_view_.clear();
00215   estimated_grids_view_.reserve(num_est_transforms);
00216   auto map_it = maps_.begin();
00217   for (auto& transform : transforms) {
00218     // empty quaternion means empty transform (quaternions are normalized
00219     // normally). TODO: make this a function
00220     double norm =
00221         std::abs(transform.orientation.w) + std::abs(transform.orientation.x) +
00222         std::abs(transform.orientation.y) + std::abs(transform.orientation.z);
00223     if (norm < std::numeric_limits<double>::epsilon()) {
00224       // this trasformation could not be estimated
00225       continue;
00226     }
00227 
00228     tf::Pose init_pose_tf;
00229     tf::Pose origin_tf;
00230     tf::Pose transform_tf;
00231     tf::poseMsgToTF(map_it->initial_pose, init_pose_tf);
00232     tf::poseMsgToTF(map_it->map.info.origin, origin_tf);
00233     tf::poseMsgToTF(transform, transform_tf);
00234 
00235     // we need to compensate previous init pose and add new init pose
00236     origin_tf *= init_pose_tf.inverseTimes(transform_tf);
00237 
00238     // store back computed origin
00239     tf::poseTFToMsg(origin_tf, map_it->map.info.origin);
00240     // update init pose with the new one
00241     map_it->initial_pose = transform;
00242 
00243     // add to merging
00244     estimated_grids_view_.emplace_back(map_it->map);
00245 
00246     ++map_it;
00247   }
00248 
00249   ROS_ASSERT(estimated_grids_view_.size() == num_est_transforms);
00250 }
00251 
00252 void MapMerging::fullMapUpdate(const nav_msgs::OccupancyGrid::ConstPtr& msg,
00253                                PosedMap& map)
00254 {
00255   ROS_DEBUG("received full map update");
00256   // we don't want to access the same map twice
00257   std::lock_guard<std::mutex> lock(map.mutex);
00258   // it's ok if we do multiple updates of *different* maps concurently.
00259   // However we can't do any merging while updating any map metadata (size!)
00260   boost::shared_lock<boost::shared_mutex> merging_lock(merging_mutex_);
00261 
00262   /* We dont need to lock both locks at once. Deadlock will not occur here.
00263    * Circular waiting will never occur. We don't lock individual mutexes for
00264    * maps while merging. So merging and map update share only second lock. Lock
00265    * on map itself is here to avoid multiple accesses to the same map, which can
00266    * occur when this callback runs concurently. Also map could be accessed by
00267    * other callbacks (partials updates). */
00268 
00269   map.map = *msg;
00270 
00271   ROS_DEBUG("Adjusting origin");
00272   // compute global position
00273   ROS_DEBUG("origin %f %f, (%f, %f, %f, %f)", map.map.info.origin.position.x,
00274             map.map.info.origin.position.y, map.map.info.origin.orientation.x,
00275             map.map.info.origin.orientation.y,
00276             map.map.info.origin.orientation.z,
00277             map.map.info.origin.orientation.w);
00278   ROS_DEBUG("init_pose %f %f, (%f, %f, %f, %f)", map.initial_pose.position.x,
00279             map.initial_pose.position.y, map.initial_pose.orientation.x,
00280             map.initial_pose.orientation.y, map.initial_pose.orientation.z,
00281             map.initial_pose.orientation.w);
00282   map.map.info.origin += map.initial_pose;
00283   ROS_DEBUG("origin %f %f, (%f, %f, %f, %f)", map.map.info.origin.position.x,
00284             map.map.info.origin.position.y, map.map.info.origin.orientation.x,
00285             map.map.info.origin.orientation.y,
00286             map.map.info.origin.orientation.z,
00287             map.map.info.origin.orientation.w);
00288 }
00289 
00290 void MapMerging::partialMapUpdate(
00291     const map_msgs::OccupancyGridUpdate::ConstPtr& msg, PosedMap& map)
00292 {
00293   ROS_DEBUG("received partial map update");
00294 
00295   if (msg->x < 0 || msg->y < 0) {
00296     ROS_ERROR("negative coordinates, invalid update. x: %d, y: %d", msg->x,
00297               msg->y);
00298     return;
00299   }
00300 
00301   size_t x0 = static_cast<size_t>(msg->x);
00302   size_t y0 = static_cast<size_t>(msg->y);
00303   size_t xn = msg->width + x0;
00304   size_t yn = msg->height + y0;
00305 
00306   // we must lock map. Otherwise it could break horribly if fullMapUpdate would
00307   // run at the same time (reaalloc of vector)
00308   std::lock_guard<std::mutex> lock(map.mutex);
00309 
00310   /* map merging is not blocked by these small updates. It can cause minor
00311    * inconsitency, but thats not a big issue, it could only bring parts of new
00312    * data to merged map. No locking needed. */
00313 
00314   size_t grid_xn = map.map.info.width;
00315   size_t grid_yn = map.map.info.height;
00316 
00317   if (xn > grid_xn || x0 > grid_xn || yn > grid_yn || y0 > grid_yn) {
00318     ROS_WARN("received update doesn't fully fit into existing map, "
00319              "only part will be copied. received: [%lu, %lu], [%lu, %lu] "
00320              "map is: [0, %lu], [0, %lu]",
00321              x0, xn, y0, yn, grid_xn, grid_yn);
00322   }
00323 
00324   // update map with data
00325   size_t i = 0;
00326   for (size_t y = y0; y < yn && y < grid_yn; ++y) {
00327     for (size_t x = x0; x < xn && x < grid_xn; ++x) {
00328       size_t idx = y * grid_xn + x;  // index to grid for this specified cell
00329       map.map.data[idx] = msg->data[i];
00330       ++i;
00331     }
00332   }
00333 }
00334 
00335 std::string MapMerging::robotNameFromTopic(const std::string& topic)
00336 {
00337   return ros::names::parentNamespace(topic);
00338 }
00339 
00340 geometry_msgs::Pose& operator+=(geometry_msgs::Pose& p1,
00341                                 const geometry_msgs::Pose& p2)
00342 {
00343   tf::Pose tf_p1, tf_p2;
00344   tf::poseMsgToTF(p1, tf_p1);
00345   tf::poseMsgToTF(p2, tf_p2);
00346 
00347   tf_p1 *= tf_p2;
00348   tf::poseTFToMsg(tf_p1, p1);
00349 
00350   return p1;
00351 }
00352 
00353 /* identifies topic via suffix */
00354 bool MapMerging::isRobotMapTopic(const ros::master::TopicInfo& topic)
00355 {
00356   /* test whether topic is robot_map_topic_ */
00357   std::string topic_namespace = ros::names::parentNamespace(topic.name);
00358   bool is_map_topic =
00359       ros::names::append(topic_namespace, robot_map_topic_) == topic.name;
00360 
00361   /* test whether topic contains *anywhere* robot namespace */
00362   auto pos = topic.name.find(robot_namespace_);
00363   bool contains_robot_namespace = pos != std::string::npos;
00364 
00365   /* we support only occupancy grids as maps */
00366   bool is_occupancy_grid = topic.datatype == "nav_msgs/OccupancyGrid";
00367 
00368   /* we don't want to subcribe on published merged map */
00369   bool is_our_topic = merged_map_publisher_.getTopic() == topic.name;
00370 
00371   return is_occupancy_grid && !is_our_topic && contains_robot_namespace &&
00372          is_map_topic;
00373 }
00374 
00375 /*
00376  * Get robot's initial position
00377  */
00378 bool MapMerging::getInitPose(const std::string& name, geometry_msgs::Pose& pose)
00379 {
00380   std::string merging_namespace = ros::names::append(name, "map_merge");
00381   double yaw = 0.0;
00382 
00383   bool success =
00384       ros::param::get(ros::names::append(merging_namespace, "init_pose_x"),
00385                       pose.position.x) &&
00386       ros::param::get(ros::names::append(merging_namespace, "init_pose_y"),
00387                       pose.position.y) &&
00388       ros::param::get(ros::names::append(merging_namespace, "init_pose_z"),
00389                       pose.position.z) &&
00390       ros::param::get(ros::names::append(merging_namespace, "init_pose_yaw"),
00391                       yaw);
00392 
00393   pose.orientation = tf::createQuaternionMsgFromYaw(yaw);
00394 
00395   return success;
00396 }
00397 
00398 /*
00399  * execute()
00400  */
00401 void MapMerging::executemapMerging()
00402 {
00403   ros::Rate r(merging_rate_);
00404   while (node_.ok()) {
00405     mapMerging();
00406     r.sleep();
00407   }
00408 }
00409 
00410 void MapMerging::executetopicSubscribing()
00411 {
00412   ros::Rate r(discovery_rate_);
00413   while (node_.ok()) {
00414     topicSubscribing();
00415     r.sleep();
00416   }
00417 }
00418 
00419 void MapMerging::executeposeEstimation()
00420 {
00421   if (have_initial_poses_)
00422     return;
00423 
00424   ros::Rate r(estimation_rate_);
00425   while (node_.ok()) {
00426     poseEstimation();
00427     r.sleep();
00428   }
00429 }
00430 
00431 /*
00432  * spin()
00433  */
00434 void MapMerging::spin()
00435 {
00436   ros::spinOnce();
00437   std::thread merging_thr([this]() { executemapMerging(); });
00438   std::thread subscribing_thr([this]() { executetopicSubscribing(); });
00439   std::thread estimation_thr([this]() { executeposeEstimation(); });
00440   ros::spin();
00441   estimation_thr.join();
00442   merging_thr.join();
00443   subscribing_thr.join();
00444 }
00445 
00446 }  // namespace map_merge
00447 
00448 int main(int argc, char** argv)
00449 {
00450   ros::init(argc, argv, "map_merge");
00451   // this package is still in development -- start wil debugging enabled
00452   if (ros::console::set_logger_level(ROSCONSOLE_DEFAULT_NAME,
00453                                      ros::console::levels::Debug)) {
00454     ros::console::notifyLoggerLevelsChanged();
00455   }
00456   map_merge::MapMerging map_merging;
00457   map_merging.spin();
00458   return 0;
00459 }


map_merge
Author(s): Jiri Horner
autogenerated on Sun Mar 26 2017 03:48:10