├── CMakeLists.txt ├── LICENSE ├── README.md ├── include └── octomap_merger.h ├── launch ├── align_test.launch ├── octomap_merger.launch ├── octomap_merger_base.launch ├── octomap_merger_robot.launch └── octomap_merger_sim.launch ├── msg ├── OctomapArray.msg └── OctomapNeighbors.msg ├── package.xml └── src ├── align_test_node.cpp ├── icp_align.cpp ├── map_merger.cpp ├── octomap_merger.cpp └── octomap_merger_node.cpp /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8.3) 2 | project(octomap_merger) 3 | 4 | find_package(catkin REQUIRED COMPONENTS 5 | octomap_ros 6 | octomap_msgs 7 | roscpp 8 | std_msgs 9 | sensor_msgs 10 | nav_msgs 11 | message_generation 12 | pcl_conversions 13 | pcl_ros 14 | ) 15 | 16 | add_message_files( 17 | FILES 18 | OctomapArray.msg 19 | OctomapNeighbors.msg 20 | ) 21 | 22 | generate_messages( 23 | DEPENDENCIES 24 | nav_msgs 25 | octomap_msgs 26 | ) 27 | 28 | include_directories( 29 | include 30 | ${catkin_INCLUDE_DIRS} 31 | ) 32 | 33 | catkin_package( 34 | CATKIN_DEPENDS message_runtime 35 | ) 36 | 37 | add_library(icp_align src/icp_align.cpp) 38 | target_link_libraries(icp_align ${catkin_LIBRARIES}) 39 | 40 | add_library(map_merger src/map_merger.cpp) 41 | target_link_libraries(map_merger ${catkin_LIBRARIES}) 42 | 43 | add_executable(octomap_merger src/octomap_merger.cpp) 44 | target_link_libraries(octomap_merger icp_align map_merger ${catkin_LIBRARIES}) 45 | 46 | add_executable(octomap_merger_node src/octomap_merger_node.cpp) 47 | target_link_libraries(octomap_merger_node icp_align map_merger ${catkin_LIBRARIES}) 48 | 49 | add_executable(align_test_node src/align_test_node.cpp) 50 | target_link_libraries(align_test_node icp_align map_merger ${catkin_LIBRARIES}) 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Danny Riley 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # octomap_merger 2 | 3 | NOTE: This node has been replaced by https://github.com/dan-riley/marble_mapping which generates differences Octomaps for sharing more efficiently. 4 | 5 | Merges multiple Octomaps and optionally aligns them using ICP prior to merging. 6 | 7 | Derived from James Jessup, Sidney N. Givigi, and Alain Beaulieu. "Merging of octree based 3d occupancy grid maps." In 2014 IEEE International Systems Conference Proceedings, pp. 371-377. IEEE, 2014. DOI: 10.1109/SMC.2014.6974556 8 | 9 |
10 | Usage: octomap_merger <input_file_1> <input_file_2> <output_file\> [align]
11 |                       [<translation_x\> <translation_y\> <translation_z\>]
12 |                       [<roll\> <pitch\> <yaw\>]
13 | 
14 | 15 | Input files can be binary or full, but both must the same. 16 | Output can be either, but does not have to be the same as input. 17 | Use .bt or .ot file extension to specify 18 | 19 | Add 'align' option to run ICP alignment on maps before merging. 20 | 21 | ## Source Description 22 | 23 | octomap_merger.cpp - Command-line merger 24 | 25 | octomap_merger_node.cpp - ROS node for merging multiple maps in an array 26 | 27 | map_merger.cpp - Core functions that manage actual Octomap merging 28 | 29 | icp_align.cpp - Converts Octomaps to point clouds, finds ICP alignment, and transforms the second map to align with the first. 30 | -------------------------------------------------------------------------------- /include/octomap_merger.h: -------------------------------------------------------------------------------- 1 | #ifndef OCTOMAP_MERGER_H_ 2 | #define OCTOMAP_MERGER_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include "octomap_merger/OctomapArray.h" 28 | #include "octomap_merger/OctomapNeighbors.h" 29 | 30 | using std::cout; 31 | using std::endl; 32 | using namespace octomap; 33 | using namespace octomath; 34 | 35 | // convenient typedefs 36 | typedef pcl::PointXYZ PointT; 37 | typedef pcl::PointCloud PointCloud; 38 | typedef pcl::PointNormal PointNormalT; 39 | typedef pcl::PointCloud PointCloudWithNormals; 40 | 41 | #define MAXITER 500 42 | 43 | // Convert a OcTree or OcTreeStamped to a PointCloud2 44 | template 45 | void tree2PointCloud(T *tree, pcl::PointCloud& pclCloud) { 46 | for (typename T::leaf_iterator it = tree->begin_leafs(), 47 | end = tree->end_leafs(); it != end; ++it) 48 | { 49 | if (tree->isNodeOccupied(*it)) { 50 | pclCloud.push_back( 51 | pcl::PointXYZ(it.getX(), 52 | it.getY(), 53 | it.getZ() 54 | ) 55 | ); 56 | } 57 | } 58 | } 59 | 60 | bool pointInBBox(pcl::PointXYZ& point, 61 | pcl::PointXYZ& bboxMin, 62 | pcl::PointXYZ& bboxMax); 63 | 64 | Eigen::Matrix4f getICPTransformation( 65 | pcl::PointCloud& cloud1, 66 | pcl::PointCloud& cloud2, 67 | Eigen::Matrix4f& tfEst, 68 | double mapRes); 69 | 70 | void transformTree(OcTree *tree, Eigen::Matrix4f& transform); 71 | 72 | void align_maps(OcTreeStamped *tree1, OcTree *tree2, point3d translation, 73 | double roll, double pitch, double yaw, double res); 74 | 75 | void merge_maps(OcTreeStamped *tree1, OcTree *tree2, bool full_merge, 76 | bool replace, bool overwrite, bool free_prioritize); 77 | 78 | double build_diff_tree(OcTree *tree1, OcTree *tree2, OcTree *tree_diff); 79 | 80 | class OctomapMerger { 81 | public: 82 | // Constructor 83 | OctomapMerger(ros::NodeHandle* nodehandle); 84 | // Destructor 85 | ~OctomapMerger(); 86 | // Callbacks 87 | void callback_myMap(const octomap_msgs::Octomap::ConstPtr& msg); 88 | void callback_neighborMaps(const octomap_merger::OctomapNeighborsConstPtr &msg); 89 | void callback_alignMap(const octomap_msgs::Octomap::ConstPtr& msg); 90 | // Public Methods 91 | void merge(); 92 | // Variables 93 | bool myMapNew; 94 | bool otherMapsNew; 95 | std::string id; 96 | std::string type; 97 | bool full_merge; 98 | bool free_prioritize; 99 | bool publish_merged_pcl; 100 | bool publish_diff_pcl; 101 | int octo_type; 102 | double resolution; 103 | int map_thresh; 104 | std::string map_topic; 105 | std::string neighbors_topic; 106 | std::string merged_topic; 107 | std::string map_diffs_topic; 108 | std::string num_diffs_topic; 109 | std::string pcl_diff_topic; 110 | std::string pcl_merged_topic; 111 | 112 | /* Private Variables and Methods */ 113 | private: 114 | ros::NodeHandle nh_; 115 | 116 | octomap_msgs::Octomap myMap; 117 | octomap_msgs::Octomap alignMap; 118 | octomap_merger::OctomapArray mapdiffs; 119 | octomap_merger::OctomapNeighbors neighbors; 120 | octomap::OcTreeStamped *tree_merged; 121 | octomap::OcTreeStamped *tree_pcl; 122 | octomap::OcTree *tree_sys; 123 | octomap::OcTree *tree_old; 124 | octomap::OcTree *tree_temp; 125 | octomap::OcTree *tree_diff; 126 | int num_diffs; 127 | std::map> seqs; 128 | 129 | ros::Subscriber sub_mymap; 130 | ros::Subscriber sub_neighbors; 131 | 132 | ros::Publisher pub_merged; 133 | ros::Publisher pub_size; 134 | ros::Publisher pub_mapdiffs; 135 | ros::Publisher pub_merged_pcl; 136 | ros::Publisher pub_diff_pcl; 137 | 138 | void initializeSubscribers(); 139 | void initializePublishers(); 140 | sensor_msgs::PointCloud2 buildPCL(OcTreeStamped *tree); 141 | }; 142 | 143 | #endif 144 | -------------------------------------------------------------------------------- /launch/align_test.launch: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /launch/octomap_merger.launch: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /launch/octomap_merger_base.launch: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /launch/octomap_merger_robot.launch: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /launch/octomap_merger_sim.launch: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /msg/OctomapArray.msg: -------------------------------------------------------------------------------- 1 | Header header 2 | octomap_msgs/Octomap[] octomaps 3 | string owner 4 | uint8 num_octomaps 5 | -------------------------------------------------------------------------------- /msg/OctomapNeighbors.msg: -------------------------------------------------------------------------------- 1 | Header header 2 | OctomapArray[] neighbors 3 | uint8 num_neighbors 4 | -------------------------------------------------------------------------------- /package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | octomap_merger 4 | 0.1.0 5 | The octomap_merger package 6 | 7 | Dan Riley 8 | 9 | TODO 10 | 11 | https://www.github.com/dan-riley/octomap_merger 12 | 13 | Dan Riley 14 | 15 | catkin 16 | 17 | octomap_ros 18 | octomap_msgs 19 | roscpp 20 | rospy 21 | std_msgs 22 | sensor_msgs 23 | nav_msgs 24 | message_generation 25 | pcl_conversions 26 | pcl_ros 27 | 28 | octomap_ros 29 | octomap_msgs 30 | roscpp 31 | rospy 32 | std_msgs 33 | sensor_msgs 34 | nav_msgs 35 | message_generation 36 | pcl_conversions 37 | pcl_ros 38 | 39 | roscpp 40 | rospy 41 | std_msgs 42 | octomap_ros 43 | octomap_msgs 44 | sensor_msgs 45 | nav_msgs 46 | message_runtime 47 | pcl_conversions 48 | pcl_ros 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /src/align_test_node.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | OctomapMerger::OctomapMerger(ros::NodeHandle* nodehandle):nh_(*nodehandle) { 4 | ROS_INFO("Constructing OctomapMerger Class"); 5 | 6 | std::string nn = ros::this_node::getName(); 7 | // Load parameters from launch file 8 | nh_.param(nn + "/vehicle", id, "H01"); 9 | // Type of agent (robot or base) 10 | nh_.param(nn + "/type", type, "robot"); 11 | // Full merging or prioritize own map 12 | nh_.param(nn + "/fullMerge", full_merge, false); 13 | // Keep free space from any agent instead of adding obstacles 14 | nh_.param(nn + "/freePrioritize", free_prioritize, false); 15 | // Whether to publish PCL of merged map 16 | nh_.param(nn + "/publishMergedPCL", publish_merged_pcl, false); 17 | // Whether to publish PCL of diff map 18 | nh_.param(nn + "/publishDiffPCL", publish_diff_pcl, false); 19 | // Octomap type: 0: Binary, 1: Full 20 | nh_.param(nn + "/octoType", octo_type, 0); 21 | // Map resolution 22 | nh_.param(nn + "/resolution", resolution, (double)0.2); 23 | // Map size threshold to trigger a map merge 24 | nh_.param(nn + "/mapThresh", map_thresh, 50); 25 | 26 | // Topics for Subscribing and Publishing 27 | nh_.param(nn + "/mapTopic", map_topic, "octomap_binary"); 28 | nh_.param(nn + "/neighborsTopic", neighbors_topic, "neighbor_maps"); 29 | nh_.param(nn + "/mergedTopic", merged_topic, "merged_map"); 30 | nh_.param(nn + "/mapDiffsTopic", map_diffs_topic, "map_diffs"); 31 | nh_.param(nn + "/numDiffsTopic", num_diffs_topic, "numDiffs"); 32 | nh_.param(nn + "/pclMergedTopic", pcl_merged_topic, "pc2_merged"); 33 | nh_.param(nn + "/pclDiffTopic", pcl_diff_topic, "pc2_diff"); 34 | 35 | initializeSubscribers(); 36 | initializePublishers(); 37 | myMapNew = false; 38 | otherMapsNew = false; 39 | 40 | // Initialize Octomap holders once, assign/overwrite each loop 41 | tree_merged = new octomap::OcTreeStamped(resolution); 42 | tree_sys = new octomap::OcTree(resolution); 43 | tree_old = new octomap::OcTree(resolution); 44 | tree_temp = new octomap::OcTree(resolution); 45 | tree_diff = new octomap::OcTree(resolution); 46 | num_diffs = 0; 47 | 48 | if (publish_diff_pcl) tree_pcl = new octomap::OcTreeStamped(resolution); 49 | } 50 | 51 | // Destructor 52 | OctomapMerger::~OctomapMerger() { 53 | } 54 | 55 | void OctomapMerger::initializeSubscribers() { 56 | ROS_INFO("Initializing Subscribers"); 57 | sub_mymap = nh_.subscribe(map_topic, 100, 58 | &OctomapMerger::callback_myMap, this); 59 | sub_neighbors = nh_.subscribe(neighbors_topic, 100, 60 | &OctomapMerger::callback_alignMap, this); 61 | } 62 | 63 | void OctomapMerger::initializePublishers() { 64 | ROS_INFO("Initializing Publishers"); 65 | pub_merged = nh_.advertise(merged_topic, 1, true); 66 | pub_size = nh_.advertise(num_diffs_topic, 1, true); 67 | pub_mapdiffs = nh_.advertise(map_diffs_topic, 1, true); 68 | if (publish_merged_pcl) 69 | pub_merged_pcl = nh_.advertise(pcl_merged_topic, 1, true); 70 | if (publish_diff_pcl) 71 | pub_diff_pcl = nh_.advertise(pcl_diff_topic, 1, true); 72 | } 73 | 74 | // Callbacks 75 | void OctomapMerger::callback_myMap(const octomap_msgs::OctomapConstPtr& msg) { 76 | myMap = *msg; 77 | myMapNew = true; 78 | } 79 | 80 | void OctomapMerger::callback_alignMap(const octomap_msgs::OctomapConstPtr& msg) { 81 | alignMap = *msg; 82 | otherMapsNew = true; 83 | } 84 | 85 | sensor_msgs::PointCloud2 OctomapMerger::buildPCL(OcTreeStamped *tree) { 86 | tree->expand(); 87 | sensor_msgs::PointCloud2 pcl; 88 | PointCloud::Ptr occupiedCells(new PointCloud); 89 | tree2PointCloud(tree, *occupiedCells); 90 | pcl::toROSMsg(*occupiedCells, pcl); 91 | pcl.header.stamp = ros::Time::now(); 92 | pcl.header.frame_id = "world"; 93 | 94 | return pcl; 95 | } 96 | 97 | void OctomapMerger::merge() { 98 | double num_nodes = 0; 99 | if (type == "robot") { 100 | if (octo_type == 0) 101 | tree_sys = (octomap::OcTree*)octomap_msgs::binaryMsgToMap(myMap); 102 | else 103 | tree_sys = (octomap::OcTree*)octomap_msgs::fullMsgToMap(myMap); 104 | 105 | if (!tree_sys) return; 106 | 107 | // Get the diff tree from the current robot map and the last one saved 108 | num_nodes = build_diff_tree(tree_old, tree_sys, tree_diff); 109 | } 110 | 111 | octomap_msgs::Octomap msg; 112 | 113 | // If there are enough new nodes, save the robot map for next iter, and merge differences 114 | if (num_nodes > map_thresh) { 115 | tree_old->swapContent(*tree_sys); 116 | merge_maps(tree_merged, tree_diff, full_merge, true, false, free_prioritize); 117 | 118 | // Publish the diffs octomap 119 | tree_diff->prune(); 120 | num_diffs++; 121 | if (octo_type == 0) 122 | octomap_msgs::binaryMapToMsg(*tree_diff, msg); 123 | else 124 | octomap_msgs::fullMapToMsg(*tree_diff, msg); 125 | msg.header.stamp = ros::Time::now(); 126 | msg.header.frame_id = "world"; 127 | msg.header.seq = num_diffs - 1; 128 | 129 | // Add the diff to the map diffs array 130 | mapdiffs.octomaps.push_back(msg); 131 | mapdiffs.num_octomaps = num_diffs; 132 | pub_mapdiffs.publish(mapdiffs); 133 | 134 | // Publish the number of diffs so multi_agent doesn't have to subscribe to the whole map 135 | std_msgs::UInt32 size_msg; 136 | size_msg.data = num_diffs; 137 | pub_size.publish(size_msg); 138 | } 139 | 140 | // Remove all of the nodes whether we used them or not, for the next iter 141 | tree_diff->clear(); 142 | 143 | // Merge each neighbors' diff map to the merged map 144 | if (octo_type == 0) 145 | tree_temp = (octomap::OcTree*)octomap_msgs::binaryMsgToMap(alignMap); 146 | else 147 | tree_temp = (octomap::OcTree*)octomap_msgs::fullMsgToMap(alignMap); 148 | 149 | point3d translation; 150 | align_maps(tree_merged, tree_temp, translation, 0, 0.047, 0, resolution); 151 | 152 | // Merge neighbor map 153 | merge_maps(tree_merged, tree_temp, full_merge, false, true, free_prioritize); 154 | 155 | // Free the memory before the next neighbor 156 | delete tree_temp; 157 | 158 | // Prune and publish the Octomap 159 | tree_merged->prune(); 160 | if (octo_type == 0) 161 | octomap_msgs::binaryMapToMsg(*tree_merged, msg); 162 | else 163 | octomap_msgs::fullMapToMsg(*tree_merged, msg); 164 | msg.header.stamp = ros::Time::now(); 165 | msg.header.frame_id = "world"; 166 | msg.id = "OcTree"; // Required to convert OcTreeStamped into regular OcTree 167 | pub_merged.publish(msg); 168 | 169 | if (type == "robot") delete tree_sys; 170 | } 171 | 172 | int main (int argc, char **argv) { 173 | ros::init(argc, argv, "octomap_merger", ros::init_options::AnonymousName); 174 | ros::NodeHandle nh; 175 | 176 | double rate; 177 | std::string nn = ros::this_node::getName(); 178 | nh.param(nn + "/rate", rate, (double)0.1); 179 | 180 | OctomapMerger *octomap_merger = new OctomapMerger(&nh); 181 | 182 | ros::Rate r(rate); 183 | while(nh.ok()) { 184 | ros::spinOnce(); 185 | if(octomap_merger->myMapNew || octomap_merger->otherMapsNew) { 186 | octomap_merger->myMapNew = false; 187 | octomap_merger->otherMapsNew = false; 188 | octomap_merger->merge(); 189 | } 190 | r.sleep(); 191 | } 192 | return 0; 193 | } 194 | -------------------------------------------------------------------------------- /src/icp_align.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | bool pointInBBox(pcl::PointXYZ& point, 4 | pcl::PointXYZ& bboxMin, 5 | pcl::PointXYZ& bboxMax) { 6 | 7 | return (point.x < bboxMax.x && point.x > bboxMin.x) && 8 | (point.y < bboxMax.y && point.y > bboxMin.y) && 9 | (point.z < bboxMax.z && point.z > bboxMin.z); 10 | } 11 | 12 | Eigen::Matrix4f getICPTransformation( 13 | pcl::PointCloud& cloud1, 14 | pcl::PointCloud& cloud2, 15 | Eigen::Matrix4f& tfEst, 16 | double mapRes) { 17 | 18 | // apply the tfEst to cloud2 19 | pcl::transformPointCloud(cloud2, cloud2, tfEst); 20 | 21 | // get the bounding region of cloud1 to 22 | // extract the points from cloud2 contained in the region 23 | pcl::PointXYZ minCloud1; pcl::PointXYZ maxCloud1; 24 | pcl::getMinMax3D(cloud1, minCloud1, maxCloud1); 25 | 26 | // filter out the points in cloud2 that are not in cloud1’s range 27 | pcl::PointCloud::Ptr cloud2filtered( 28 | new pcl::PointCloud); 29 | 30 | for (pcl::PointCloud::iterator it = cloud2.begin(); 31 | it != cloud2.end(); it++) { 32 | 33 | if (pointInBBox(*it, minCloud1, maxCloud1)) { 34 | cloud2filtered->push_back(*it); 35 | } 36 | } 37 | 38 | // filter out the points in cloud1 that are not in cloud2’s range 39 | pcl::PointCloud::Ptr cloud1filtered( 40 | new pcl::PointCloud); 41 | 42 | // same for other cloud 43 | pcl::PointXYZ minCloud2filtered; pcl::PointXYZ maxCloud2filtered; 44 | pcl::getMinMax3D(*cloud2filtered, minCloud2filtered, maxCloud2filtered); 45 | 46 | minCloud2filtered = pcl::PointXYZ( 47 | minCloud2filtered.x - 1, 48 | minCloud2filtered.y - 1, 49 | minCloud2filtered.z - 1 50 | ); 51 | 52 | maxCloud2filtered = pcl::PointXYZ( 53 | maxCloud2filtered.x + 1, 54 | maxCloud2filtered.y + 1, 55 | maxCloud2filtered.z + 1 56 | ); 57 | 58 | for (pcl::PointCloud::iterator it = cloud1.begin(); 59 | it != cloud1.end(); it++) { 60 | 61 | if (pointInBBox(*it, minCloud2filtered, maxCloud2filtered)) { 62 | cloud1filtered->push_back(*it); 63 | } 64 | } 65 | 66 | // Downsample for consistency and speed 67 | PointCloud::Ptr src(new PointCloud); 68 | PointCloud::Ptr tgt(new PointCloud); 69 | pcl::VoxelGrid grid; 70 | grid.setLeafSize(10 * mapRes, 10 * mapRes, 10 * mapRes); 71 | grid.setInputCloud(cloud1filtered); 72 | grid.filter(*tgt); 73 | 74 | grid.setInputCloud(cloud2filtered); 75 | grid.filter(*src); 76 | 77 | // Align 78 | pcl::IterativeClosestPointNonLinear reg; 79 | reg.setTransformationEpsilon(mapRes / 60); 80 | // Increased this from 10 to 200 because of errors when running 81 | // Not sure what the right numbers should be 82 | reg.setMaxCorrespondenceDistance(200 * mapRes); 83 | 84 | Eigen::Matrix4f Ti = Eigen::Matrix4f::Identity(), prev; 85 | PointCloud::Ptr reg_result; 86 | 87 | if (src->size() < tgt->size()) { 88 | reg.setInputSource(src); 89 | reg.setInputTarget(tgt); 90 | 91 | // Run the same optimization in a loop and visualize the results 92 | reg_result = src; 93 | reg.setMaximumIterations(2); 94 | for (int i=0; i < MAXITER; ++i) { 95 | // save cloud for visualization purpose 96 | src = reg_result; 97 | 98 | // Estimate 99 | reg.setInputSource(src); 100 | reg.align(*reg_result); 101 | 102 | // accumulate transformation between each Iteration 103 | Ti = reg.getFinalTransformation() * Ti; 104 | 105 | // if the difference between this transformation and the previous one 106 | // is smaller than the threshold, refine the process by reducing 107 | // the maximal correspondence distance 108 | if (reg.getMaxCorrespondenceDistance() > 0.2) { 109 | if (fabs((reg.getLastIncrementalTransformation().sum() - prev.sum())) 110 | < reg.getTransformationEpsilon()) 111 | reg.setMaxCorrespondenceDistance( 112 | reg.getMaxCorrespondenceDistance() - 0.1 113 | ); 114 | } else if (reg.getMaxCorrespondenceDistance() > 0.002) { 115 | if (fabs((reg.getLastIncrementalTransformation().sum() - prev.sum())) 116 | < reg.getTransformationEpsilon()) 117 | reg.setMaxCorrespondenceDistance( 118 | reg.getMaxCorrespondenceDistance() - 0.001 119 | ); 120 | } 121 | 122 | prev = reg.getLastIncrementalTransformation(); 123 | } 124 | } else { 125 | reg.setInputSource(tgt); 126 | reg.setInputTarget(src); 127 | 128 | // Run the same optimization in a loop and visualize the results 129 | reg_result = tgt; 130 | reg.setMaximumIterations(2); 131 | for (int i=0; i < MAXITER; ++i) { 132 | // save cloud for visualization purpose 133 | tgt = reg_result; 134 | 135 | // Estimate 136 | reg.setInputSource(tgt); 137 | reg.align(*reg_result); 138 | 139 | // accumulate transformation between each Iteration 140 | Ti = reg.getFinalTransformation() * Ti; 141 | 142 | // if the difference between this transformation and the previous one 143 | // is smaller than the threshold, refine the process by reducing 144 | // the maximal correspondence distance 145 | if (reg.getMaxCorrespondenceDistance() > 0.2) { 146 | if (fabs((reg.getLastIncrementalTransformation().sum() - prev.sum())) 147 | < reg.getTransformationEpsilon()) 148 | reg.setMaxCorrespondenceDistance( 149 | reg.getMaxCorrespondenceDistance() - 0.1 150 | ); 151 | } else if (reg.getMaxCorrespondenceDistance() > 0.002) { 152 | if (fabs((reg.getLastIncrementalTransformation().sum() - prev.sum())) 153 | < reg.getTransformationEpsilon()) 154 | reg.setMaxCorrespondenceDistance( 155 | reg.getMaxCorrespondenceDistance() - 0.001 156 | ); 157 | } 158 | 159 | prev = reg.getLastIncrementalTransformation(); 160 | } 161 | Ti = Ti.inverse(); 162 | } 163 | 164 | return Ti * tfEst; 165 | } 166 | 167 | double getSign(double x) { 168 | if (x < 0) return -1; 169 | else return 1; 170 | } 171 | 172 | void transformTree(OcTree *tree, Eigen::Matrix4f& transform) { 173 | double treeRes = tree->getResolution(); 174 | OcTree* transformed = new OcTree(treeRes); 175 | 176 | // build inverse transform 177 | Eigen::Matrix3f rotation; 178 | Eigen::Matrix3f invRotation; 179 | Eigen::Matrix4f invTransform; 180 | rotation << transform(0, 0), transform(0, 1), transform(0, 2), 181 | transform(1, 0), transform(1, 1), transform(1, 2), 182 | transform(2, 0), transform(0, 2), transform(2, 2); 183 | invRotation = rotation.transpose(); 184 | invTransform << 185 | invRotation(0, 0), invRotation(0, 1), invRotation(0, 2), -transform(0, 3), 186 | invRotation(1, 0), invRotation(1, 1), invRotation(1, 2), -transform( 1, 3), 187 | invRotation(2, 0), invRotation(2, 1), invRotation(2, 2), -transform( 2, 3), 188 | 0, 0, 0, 1; 189 | 190 | // size in each coordinate of each axis. 191 | double minX, maxX, minY, maxY, minZ, maxZ; 192 | 193 | // get the minimum and maxin y so we can step along each row 194 | tree->getMetricMin(minX, minY, minZ); 195 | tree->getMetricMax(maxX, maxY, maxZ); 196 | 197 | // allocate a vector of points 198 | std::vector points; 199 | 200 | // make 8 points to make a map bounding box, performing the tf on them 201 | // to get the range of values in the transformed map 202 | points.push_back(point3d(maxX,minY,minZ)); 203 | points.push_back(point3d(minX,minY,minZ)); 204 | points.push_back(point3d(minX,maxY,minZ)); 205 | points.push_back(point3d(maxX,maxY,minZ)); 206 | points.push_back(point3d(maxX,minY,maxZ)); 207 | points.push_back(point3d(minX,minY,maxZ)); 208 | points.push_back(point3d(minX,maxY,maxZ)); 209 | points.push_back(point3d(maxX,maxY,maxZ)); 210 | 211 | // transform the points 212 | for (unsigned i=0; i < points.size(); i++) { 213 | Eigen::Vector4f point(points[i].x(), points[i].y(), points[i].z(), 1); 214 | point = transform * point; 215 | points[i] = point3d(point(0), point(1), point(2)); 216 | } 217 | 218 | // go through tf’d points to get a newb box 219 | minX = points[0].x(); minY = points[0].y(); minZ = points[0].z(); 220 | maxX = points[0].x(); maxY = points[0].y(); maxZ = points[0].z(); 221 | for (unsigned i=0; i < points.size(); i++) { 222 | minX = (points[i].x() < minX) ? points[i].x() : minX; 223 | minY = (points[i].y() < minY) ? points[i].y() : minY; 224 | minZ = (points[i].z() < minZ) ? points[i].z() : minZ; 225 | maxX = (points[i].x() > maxX) ? points[i].x() : maxX; 226 | maxY = (points[i].y() > maxY) ? points[i].y() : maxY; 227 | maxZ = (points[i].z() > maxZ) ? points[i].z() : maxZ; 228 | } 229 | 230 | // go through the possible destination voxels on a row by row basis 231 | // and calculate occupancy from source voxels with inverse tf 232 | for (double z = minZ - treeRes / 2; z < (maxZ + treeRes / 2); z += treeRes) { 233 | for (double y = minY - treeRes / 2; y < (maxY + treeRes / 2); y += treeRes) { 234 | for (double x = minX - treeRes / 2; x < (maxX + treeRes / 2); x += treeRes) { 235 | OcTreeKey destVoxel = transformed->coordToKey( 236 | point3d(x,y,z) 237 | ); 238 | Eigen::Vector4f point(x,y,z,1); 239 | point = invTransform * point; 240 | point3d sourcePoint = point3d(point(0), point(1), point(2)); 241 | OcTreeKey sourceVoxel = tree->coordToKey(sourcePoint); 242 | point3d nn = tree->keyToCoord(sourceVoxel); 243 | 244 | // use nearest neighbour to set new occupancy in the transformed map 245 | OcTreeNode *oldNode = tree->search(sourceVoxel); 246 | 247 | // Occupancies to interpolate between 248 | double c000, c001, c010, c011, c100, c101, c110, 249 | c111, c00, c01, c10, c11, c0, c1; 250 | double xd,yd,zd; 251 | 252 | // differences in each direction between next closest voxel 253 | xd = (sourcePoint.x() - nn.x()) / treeRes; 254 | yd = (sourcePoint.y() - nn.y()) / treeRes; 255 | zd = (sourcePoint.z() - nn.z()) / treeRes; 256 | 257 | if (oldNode != NULL) { 258 | c000 = oldNode->getOccupancy(); 259 | OcTreeNode *node; 260 | 261 | // c001 262 | if ((node = tree->search( 263 | point3d(nn.x(), nn.y(), nn.z() + 264 | getSign(zd) * treeRes))) 265 | != NULL) { 266 | c001 = node->getOccupancy(); 267 | } else 268 | c001 = 0; 269 | 270 | // c010 271 | if ((node=tree->search( 272 | point3d(nn.x(), 273 | nn.y() + getSign(yd) * treeRes, 274 | nn.z()))) 275 | != NULL) { 276 | c010 =node->getOccupancy(); 277 | } else 278 | c010 = 0; 279 | 280 | // c011 281 | if ((node=tree->search( 282 | point3d(nn.x(), 283 | nn.y() + getSign(yd) * treeRes, 284 | nn.z() + getSign(zd) * treeRes))) 285 | != NULL) { 286 | c011 = node->getOccupancy(); 287 | } else 288 | c011 = 0; 289 | 290 | // c100 291 | if ((node=tree->search( 292 | point3d(nn.x() + getSign(xd) * treeRes, 293 | nn.y(), 294 | nn.z()))) 295 | != NULL) { 296 | c100 = node->getOccupancy(); 297 | } else 298 | c100 = 0; 299 | 300 | // c101 301 | if ((node=tree->search( 302 | point3d(nn.x() + getSign(xd) * treeRes, 303 | nn.y(), 304 | nn.z() + getSign(zd) * treeRes))) 305 | != NULL) { 306 | c101 = node->getOccupancy(); 307 | } else 308 | c101 = 0; 309 | 310 | // c110 311 | if ((node=tree->search( 312 | point3d(nn.x() + getSign(xd) * treeRes, 313 | nn.y() + getSign(yd) * treeRes, 314 | nn.z()))) 315 | != NULL) { 316 | c110 = node->getOccupancy(); 317 | } else 318 | c110 = 0; 319 | 320 | // c111 321 | if ((node=tree->search( 322 | point3d(nn.x() + getSign(xd) * treeRes, 323 | nn.y() + getSign(yd) * treeRes, 324 | nn.z() + getSign(zd) * treeRes))) 325 | != NULL) { 326 | c111 = node->getOccupancy(); 327 | } else 328 | c111 = 0; 329 | 330 | // Interpolate in x 331 | c00 = (1-fabs(xd)) * c000 + fabs(xd) * c100; 332 | c10 = (1-fabs(xd)) * c010 + fabs(xd) * c110; 333 | c01 = (1-fabs(xd)) * c001 + fabs(xd) * c101; 334 | c11 = (1-fabs(xd)) * c011 + fabs(xd) * c111; 335 | 336 | // interpolate in y 337 | c0 = (1-fabs(yd)) * c00 + fabs(yd) * c10; 338 | c1 = (1-fabs(yd)) * c01 + fabs(yd) * c11; 339 | 340 | // now let’s assign the new node value 341 | OcTreeNode *newNode = transformed->updateNode(destVoxel, true); 342 | newNode->setLogOdds(logodds((1 - fabs(zd)) * c0 + fabs(zd) * c1)); 343 | } 344 | } 345 | } 346 | } 347 | 348 | tree->swapContent(*transformed); 349 | 350 | delete transformed; 351 | } 352 | 353 | void align_maps(OcTreeStamped *tree1, OcTree *tree2, point3d translation, 354 | double roll, double pitch, double yaw, double res) { 355 | Pose6D pose(translation.x(), 356 | translation.y(), 357 | translation.z(), 358 | roll, pitch, yaw); 359 | 360 | // build a transform matrix 361 | Eigen::Matrix4f transform; 362 | std::vector coeffs; 363 | pose.rot().toRotMatrix(coeffs); 364 | 365 | transform << coeffs[0], coeffs[1], coeffs[2], translation.x(), 366 | coeffs[3], coeffs[4], coeffs[5], translation.y(), 367 | coeffs[6], coeffs[7], coeffs[8], translation.z(), 368 | 0, 0, 0, 1; 369 | 370 | // initial TF Matrix 371 | cout << transform << endl; 372 | 373 | // make point clouds from each map 374 | pcl::PointCloud tree1Points; 375 | tree2PointCloud(tree1, tree1Points); 376 | pcl::PointCloud tree2Points; 377 | tree2PointCloud(tree2, tree2Points); 378 | 379 | // get refined matrix 380 | transform = getICPTransformation(tree1Points, tree2Points, transform, res); 381 | 382 | // Resulting transform after correction 383 | cout << transform << endl; 384 | 385 | if (roll != 0 || 386 | pitch != 0 || 387 | yaw != 0 || 388 | translation.x() != 0 || 389 | translation.y() != 0 || 390 | translation.z() != 0 ) { 391 | transformTree(tree2, transform); 392 | } 393 | } 394 | -------------------------------------------------------------------------------- /src/map_merger.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void merge_maps(OcTreeStamped *tree1, OcTree *tree2, bool full_merge, 4 | bool replace, bool overwrite, bool free_prioritize) { 5 | // full_merge = add node values instead of replace/overwrite 6 | // replace = always replace an existing node 7 | // overwrite = replace an existing node if the node is marked with timestamp = 0 8 | // free_prioritize = only available with full_merge; if any map shows free node, make free 9 | // Expand tree so we search all nodes 10 | tree2->expand(); 11 | 12 | // Timestamp to mark a node as an original from the owner or not 13 | int ts = replace ? 1 : 0; 14 | 15 | // traverse nodes in tree2 to add them to tree1 16 | for (OcTree::leaf_iterator it = tree2->begin_leafs(); it != tree2->end_leafs(); ++it) { 17 | OcTreeKey nodeKey = it.getKey(); 18 | OcTreeNodeStamped *nodeIn1 = tree1->search(nodeKey); 19 | if (nodeIn1 != NULL) { 20 | // Merge or replace the node in tree1 based on input parameters 21 | if (full_merge) { 22 | // If set, if any map shows a node free, make it free 23 | if (free_prioritize) { 24 | if ((nodeIn1->getOccupancy() < 0.5) || (it->getOccupancy() < 0.5)) 25 | tree1->setNodeValue(nodeKey, octomap::logodds(0)); 26 | else 27 | tree1->updateNode(nodeKey, it->getLogOdds()); 28 | } else { 29 | tree1->updateNode(nodeKey, it->getLogOdds()); 30 | } 31 | 32 | nodeIn1->setTimestamp(ts); 33 | } else if (replace || (overwrite && (nodeIn1->getTimestamp() == 0))) { 34 | tree1->setNodeValue(nodeKey, it->getLogOdds()); 35 | nodeIn1->setTimestamp(ts); 36 | } 37 | } else { 38 | // Add the node to tree1 39 | OcTreeNodeStamped *newNode = tree1->setNodeValue(nodeKey, it->getLogOdds()); 40 | newNode->setTimestamp(ts); 41 | } 42 | } 43 | } 44 | 45 | double build_diff_tree(OcTree *tree1, OcTree *tree2, OcTree *tree_diff) { 46 | // Find the differences in tree2 from tree1 and write to a new diff tree 47 | 48 | // Expand tree so we search all nodes 49 | tree2->expand(); 50 | double num_new_nodes = 0; 51 | 52 | // traverse nodes in tree2 to add them to tree1 53 | for (OcTree::leaf_iterator it = tree2->begin_leafs(); it != tree2->end_leafs(); ++it) { 54 | OcTreeKey nodeKey = it.getKey(); 55 | OcTreeNode *nodeIn1 = tree1->search(nodeKey); 56 | if (nodeIn1 != NULL) { 57 | // Change the diff tree (may be add, or may be new) if the two maps are different 58 | if (nodeIn1->getOccupancy() != it->getOccupancy()) { 59 | tree_diff->setNodeValue(nodeKey, it->getLogOdds()); 60 | } 61 | } else { 62 | // Add to the diff tree if it's a new node 63 | OcTreeNode *newDiffNode = tree_diff->setNodeValue(nodeKey, it->getLogOdds()); 64 | num_new_nodes++; 65 | } 66 | } 67 | 68 | return num_new_nodes; 69 | } 70 | -------------------------------------------------------------------------------- /src/octomap_merger.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main(int argc, char** argv) { 4 | if (argc < 4) { 5 | cout << "\nUsage: octomap_merger [align]"; 6 | cout << "\n "; 7 | cout << "[ ]"; 8 | cout << "\n "; 9 | cout << "[ ]"; 10 | cout << "\n\nInput files can be binary or full, but both must the same.\n"; 11 | cout << "Output can be either, but does not have to be the same as input.\n"; 12 | cout << "Use .bt or .ot file extension to specify\n\n"; 13 | cout << "Add 'align' option to run ICP alignment on maps before merging.\n\n"; 14 | exit(0); 15 | } 16 | 17 | std::string filename1 = std::string(argv[1]); 18 | std::string filename2 = std::string(argv[2]); 19 | std::string outputFilename = std::string(argv[3]); 20 | 21 | cout << "\nReading octree files...\n"; 22 | 23 | OcTree *treet, *tree2; 24 | if (filename1.substr(filename1.length() - 2) == "ot") { 25 | treet = dynamic_cast(OcTree::read(filename1)); 26 | tree2 = dynamic_cast(OcTree::read(filename2)); 27 | } else { 28 | treet = new OcTree(filename1); 29 | tree2 = new OcTree(filename2); 30 | } 31 | 32 | // Assume the resolution of each map is the same 33 | double res = treet->getResolution(); 34 | 35 | // Convert treet to OcTreeStamped 36 | OcTreeStamped *tree1 = new OcTreeStamped(res); 37 | for (OcTree::leaf_iterator it = treet->begin_leafs(); it != treet->end_leafs(); ++it) { 38 | OcTreeKey nodeKey = it.getKey(); 39 | tree1->setNodeValue(nodeKey, it->getLogOdds()); 40 | } 41 | 42 | // Align the maps if desired. Will slow down the process! 43 | if ((argc > 4) && (std::string(argv[4]) == "align")) { 44 | cout << "Registering map to Improve TF Estimate" << endl << endl; 45 | 46 | double roll, pitch, yaw; 47 | 48 | point3d translation; 49 | if (argc == 8 || argc == 11) { 50 | translation = point3d(atof(argv[5]), atof(argv[6]), atof(argv[7])); 51 | } 52 | if (argc == 11) { 53 | roll = atof(argv[8]); 54 | pitch = atof(argv[9]); 55 | yaw = atof(argv[10]); 56 | } else { 57 | roll = 0; 58 | pitch = 0; 59 | yaw = 0; 60 | } 61 | 62 | align_maps(tree1, tree2, translation, roll, pitch, yaw, res); 63 | } 64 | 65 | cout << "about to merge maps" << endl; 66 | merge_maps(tree1, tree2, true, false, false, false); 67 | 68 | cout << "Compressing merged result\n"; 69 | tree1->prune(); 70 | // tree1 is now the compressed merged map 71 | 72 | // write merged map to file 73 | if (outputFilename.substr(outputFilename.length() - 2) == "ot") { 74 | tree1->write(outputFilename); 75 | } else { 76 | tree1->writeBinary(outputFilename); 77 | } 78 | 79 | delete tree1; 80 | delete tree2; 81 | } 82 | -------------------------------------------------------------------------------- /src/octomap_merger_node.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | OctomapMerger::OctomapMerger(ros::NodeHandle* nodehandle):nh_(*nodehandle) { 4 | ROS_INFO("Constructing OctomapMerger Class"); 5 | 6 | std::string nn = ros::this_node::getName(); 7 | // Load parameters from launch file 8 | nh_.param(nn + "/vehicle", id, "H01"); 9 | // Type of agent (robot or base) 10 | nh_.param(nn + "/type", type, "robot"); 11 | // Full merging or prioritize own map 12 | nh_.param(nn + "/fullMerge", full_merge, false); 13 | // Keep free space from any agent instead of adding obstacles 14 | nh_.param(nn + "/freePrioritize", free_prioritize, false); 15 | // Whether to publish PCL of merged map 16 | nh_.param(nn + "/publishMergedPCL", publish_merged_pcl, false); 17 | // Whether to publish PCL of diff map 18 | nh_.param(nn + "/publishDiffPCL", publish_diff_pcl, false); 19 | // Octomap type: 0: Binary, 1: Full 20 | nh_.param(nn + "/octoType", octo_type, 0); 21 | // Map resolution 22 | nh_.param(nn + "/resolution", resolution, (double)0.2); 23 | // Map size threshold to trigger a map merge 24 | nh_.param(nn + "/mapThresh", map_thresh, 50); 25 | 26 | // Topics for Subscribing and Publishing 27 | nh_.param(nn + "/mapTopic", map_topic, "octomap_binary"); 28 | nh_.param(nn + "/neighborsTopic", neighbors_topic, "neighbor_maps"); 29 | nh_.param(nn + "/mergedTopic", merged_topic, "merged_map"); 30 | nh_.param(nn + "/mapDiffsTopic", map_diffs_topic, "map_diffs"); 31 | nh_.param(nn + "/numDiffsTopic", num_diffs_topic, "numDiffs"); 32 | nh_.param(nn + "/pclMergedTopic", pcl_merged_topic, "pc2_merged"); 33 | nh_.param(nn + "/pclDiffTopic", pcl_diff_topic, "pc2_diff"); 34 | 35 | initializeSubscribers(); 36 | initializePublishers(); 37 | myMapNew = false; 38 | otherMapsNew = false; 39 | 40 | // Initialize Octomap holders once, assign/overwrite each loop 41 | tree_merged = new octomap::OcTreeStamped(resolution); 42 | tree_sys = new octomap::OcTree(resolution); 43 | tree_old = new octomap::OcTree(resolution); 44 | tree_temp = new octomap::OcTree(resolution); 45 | tree_diff = new octomap::OcTree(resolution); 46 | num_diffs = 0; 47 | 48 | if (publish_diff_pcl) tree_pcl = new octomap::OcTreeStamped(resolution); 49 | } 50 | 51 | // Destructor 52 | OctomapMerger::~OctomapMerger() { 53 | } 54 | 55 | void OctomapMerger::initializeSubscribers() { 56 | ROS_INFO("Initializing Subscribers"); 57 | sub_mymap = nh_.subscribe(map_topic, 100, 58 | &OctomapMerger::callback_myMap, this); 59 | sub_neighbors = nh_.subscribe(neighbors_topic, 100, 60 | &OctomapMerger::callback_neighborMaps, this); 61 | } 62 | 63 | void OctomapMerger::initializePublishers() { 64 | ROS_INFO("Initializing Publishers"); 65 | pub_merged = nh_.advertise(merged_topic, 1, true); 66 | pub_size = nh_.advertise(num_diffs_topic, 1, true); 67 | pub_mapdiffs = nh_.advertise(map_diffs_topic, 1, true); 68 | if (publish_merged_pcl) 69 | pub_merged_pcl = nh_.advertise(pcl_merged_topic, 1, true); 70 | if (publish_diff_pcl) 71 | pub_diff_pcl = nh_.advertise(pcl_diff_topic, 1, true); 72 | } 73 | 74 | // Callbacks 75 | void OctomapMerger::callback_myMap(const octomap_msgs::OctomapConstPtr& msg) { 76 | myMap = *msg; 77 | myMapNew = true; 78 | } 79 | 80 | void OctomapMerger::callback_neighborMaps( 81 | const octomap_merger::OctomapNeighborsConstPtr& msg) { 82 | neighbors = *msg; 83 | otherMapsNew = true; 84 | } 85 | 86 | sensor_msgs::PointCloud2 OctomapMerger::buildPCL(OcTreeStamped *tree) { 87 | tree->expand(); 88 | sensor_msgs::PointCloud2 pcl; 89 | PointCloud::Ptr occupiedCells(new PointCloud); 90 | tree2PointCloud(tree, *occupiedCells); 91 | pcl::toROSMsg(*occupiedCells, pcl); 92 | pcl.header.stamp = ros::Time::now(); 93 | pcl.header.frame_id = "world"; 94 | 95 | return pcl; 96 | } 97 | 98 | void OctomapMerger::merge() { 99 | double num_nodes = 0; 100 | if (type == "robot") { 101 | if (octo_type == 0) 102 | tree_sys = (octomap::OcTree*)octomap_msgs::binaryMsgToMap(myMap); 103 | else 104 | tree_sys = (octomap::OcTree*)octomap_msgs::fullMsgToMap(myMap); 105 | 106 | if (!tree_sys) return; 107 | 108 | // Get the diff tree from the current robot map and the last one saved 109 | num_nodes = build_diff_tree(tree_old, tree_sys, tree_diff); 110 | } 111 | 112 | octomap_msgs::Octomap msg; 113 | 114 | // If there are enough new nodes, save the robot map for next iter, and merge differences 115 | if (num_nodes > map_thresh) { 116 | tree_old->swapContent(*tree_sys); 117 | merge_maps(tree_merged, tree_diff, full_merge, true, false, free_prioritize); 118 | 119 | // Publish the diffs octomap 120 | tree_diff->prune(); 121 | num_diffs++; 122 | if (octo_type == 0) 123 | octomap_msgs::binaryMapToMsg(*tree_diff, msg); 124 | else 125 | octomap_msgs::fullMapToMsg(*tree_diff, msg); 126 | msg.header.stamp = ros::Time::now(); 127 | msg.header.frame_id = "world"; 128 | msg.header.seq = num_diffs - 1; 129 | 130 | // Add the diff to the map diffs array 131 | mapdiffs.octomaps.push_back(msg); 132 | mapdiffs.num_octomaps = num_diffs; 133 | pub_mapdiffs.publish(mapdiffs); 134 | 135 | // Publish the number of diffs so multi_agent doesn't have to subscribe to the whole map 136 | std_msgs::UInt32 size_msg; 137 | size_msg.data = num_diffs; 138 | pub_size.publish(size_msg); 139 | } 140 | 141 | // Remove all of the nodes whether we used them or not, for the next iter 142 | tree_diff->clear(); 143 | 144 | // Merge each neighbors' diff map to the merged map 145 | bool overwrite_node; 146 | for (int i=0; i < neighbors.num_neighbors; i++) { 147 | std::string nid = neighbors.neighbors[i].owner; 148 | // Check each diff for new ones to merge 149 | for (int j=0; j < neighbors.neighbors[i].num_octomaps; j++) { 150 | uint32_t cur_seq = neighbors.neighbors[i].octomaps[j].header.seq; 151 | bool exists = std::count(seqs[nid.data()].cbegin(), seqs[nid.data()].cend(), cur_seq); 152 | 153 | if (!exists) { 154 | // ROS_INFO("%s Merging neighbor %s seq %d", id.data(), nid.data(), cur_seq); 155 | seqs[nid.data()].push_back(cur_seq); 156 | if (octo_type == 0) 157 | tree_temp = (octomap::OcTree*)octomap_msgs::binaryMsgToMap(neighbors.neighbors[i].octomaps[j]); 158 | else 159 | tree_temp = (octomap::OcTree*)octomap_msgs::fullMsgToMap(neighbors.neighbors[i].octomaps[j]); 160 | 161 | // TODO Still problem where only replacing, not merging. If multiple neighbors see the same node, only the last one received gets used 162 | // If it's latest, merge and append. If not, only append 163 | if (cur_seq >= *std::max_element(seqs[nid.data()].cbegin(), seqs[nid.data()].cend())) 164 | overwrite_node = true; 165 | else 166 | overwrite_node = false; 167 | 168 | // Merge neighbor map 169 | merge_maps(tree_merged, tree_temp, full_merge, false, overwrite_node, free_prioritize); 170 | 171 | if (publish_diff_pcl) 172 | merge_maps(tree_pcl, tree_temp, true, false, false, free_prioritize); 173 | 174 | // Free the memory before the next neighbor 175 | delete tree_temp; 176 | } 177 | } 178 | } 179 | 180 | // Publish the merged PCL 181 | if (publish_merged_pcl && (tree_merged->getRoot() != NULL)) { 182 | pub_merged_pcl.publish(buildPCL(tree_merged)); 183 | } 184 | 185 | // Publish the PCL diff 186 | if (publish_diff_pcl && (tree_pcl->getRoot() != NULL)) { 187 | pub_diff_pcl.publish(buildPCL(tree_pcl)); 188 | tree_pcl->clear(); 189 | } 190 | 191 | // Prune and publish the Octomap 192 | tree_merged->prune(); 193 | if (octo_type == 0) 194 | octomap_msgs::binaryMapToMsg(*tree_merged, msg); 195 | else 196 | octomap_msgs::fullMapToMsg(*tree_merged, msg); 197 | msg.header.stamp = ros::Time::now(); 198 | msg.header.frame_id = "world"; 199 | msg.id = "OcTree"; // Required to convert OcTreeStamped into regular OcTree 200 | pub_merged.publish(msg); 201 | 202 | if (type == "robot") delete tree_sys; 203 | } 204 | 205 | int main (int argc, char **argv) { 206 | ros::init(argc, argv, "octomap_merger", ros::init_options::AnonymousName); 207 | ros::NodeHandle nh; 208 | 209 | double rate; 210 | std::string nn = ros::this_node::getName(); 211 | nh.param(nn + "/rate", rate, (double)0.1); 212 | 213 | OctomapMerger *octomap_merger = new OctomapMerger(&nh); 214 | 215 | ros::Rate r(rate); 216 | while(nh.ok()) { 217 | ros::spinOnce(); 218 | if(octomap_merger->myMapNew || octomap_merger->otherMapsNew) { 219 | octomap_merger->myMapNew = false; 220 | octomap_merger->otherMapsNew = false; 221 | octomap_merger->merge(); 222 | } 223 | r.sleep(); 224 | } 225 | return 0; 226 | } 227 | --------------------------------------------------------------------------------