// Copyright (c) 2024, RoboVerse community // SPDX-License-Identifier: BSD-3-Clause #include "lidar_processor_cpp/lidar_to_pointcloud_node.hpp" #include namespace lidar_processor_cpp { LidarToPointCloudNode::LidarToPointCloudNode() : Node("lidar_to_pointcloud") { declareParameters(); config_ = loadConfiguration(); setupSubscriptions(); setupPublishers(); if (config_.save_map) { save_timer_ = this->create_wall_timer( std::chrono::duration(config_.save_interval), std::bind(&LidarToPointCloudNode::saveMapCallback, this) ); RCLCPP_INFO(this->get_logger(), "Map saving enabled: %s.ply every %.0fs", config_.map_name.c_str(), config_.save_interval); } logConfiguration(); } void LidarToPointCloudNode::declareParameters() { this->declare_parameter("robot_ip_lst", std::vector{}); this->declare_parameter("map_name", "3d_map"); this->declare_parameter("map_save", "false"); this->declare_parameter("save_interval", 30.0); this->declare_parameter("max_points", 500000); this->declare_parameter("voxel_size", 0.05); } LidarConfig LidarToPointCloudNode::loadConfiguration() { LidarConfig config; config.robot_ip_list = this->get_parameter("robot_ip_lst").as_string_array(); config.map_name = this->get_parameter("map_name").as_string(); config.save_map = (this->get_parameter("map_save").as_string() == "true"); config.save_interval = this->get_parameter("save_interval").as_double(); config.max_points = this->get_parameter("max_points").as_int(); config.voxel_size = this->get_parameter("voxel_size").as_double(); return config; } void LidarToPointCloudNode::setupSubscriptions() { auto qos = rclcpp::QoS(1) .reliability(rclcpp::ReliabilityPolicy::BestEffort) .history(rclcpp::HistoryPolicy::KeepLast); if (config_.robot_ip_list.size() <= 1) { subscriptions_.push_back( this->create_subscription( "robot0/point_cloud2", qos, std::bind(&LidarToPointCloudNode::lidarCallback, this, std::placeholders::_1) ) ); } else { for (size_t i = 0; i < config_.robot_ip_list.size(); ++i) { subscriptions_.push_back( this->create_subscription( "/robot" + std::to_string(i) + "/point_cloud2", qos, std::bind(&LidarToPointCloudNode::lidarCallback, this, std::placeholders::_1) ) ); } } } void LidarToPointCloudNode::setupPublishers() { auto qos = rclcpp::QoS(1) .reliability(rclcpp::ReliabilityPolicy::BestEffort) .history(rclcpp::HistoryPolicy::KeepLast); pointcloud_pub_ = this->create_publisher( "/point_cloud2", qos); } void LidarToPointCloudNode::lidarCallback( const sensor_msgs::msg::PointCloud2::SharedPtr msg) { try { pcl::PointCloud::Ptr cloud(new pcl::PointCloud); pcl::fromROSMsg(*msg, *cloud); if (cloud->points.empty()) return; pcl::PointCloud::Ptr filtered(new pcl::PointCloud); if (config_.voxel_size > 0.0) { pcl::VoxelGrid vox; vox.setInputCloud(cloud); vox.setLeafSize( static_cast(config_.voxel_size), static_cast(config_.voxel_size), static_cast(config_.voxel_size)); vox.filter(*filtered); } else { filtered = cloud; } if (filtered->points.empty()) return; sensor_msgs::msg::PointCloud2 out; pcl::toROSMsg(*filtered, out); out.header = msg->header; pointcloud_pub_->publish(out); if (config_.save_map) { std::lock_guard lock(save_mutex_); latest_cloud_ = filtered; } } catch (const std::exception& e) { RCLCPP_ERROR(this->get_logger(), "lidarCallback error: %s", e.what()); } } void LidarToPointCloudNode::publishAggregatedPointcloud( const std_msgs::msg::Header&) {} void LidarToPointCloudNode::saveMapCallback() { pcl::PointCloud::Ptr cloud_to_save; { std::lock_guard lock(save_mutex_); if (!latest_cloud_ || latest_cloud_->points.empty()) return; cloud_to_save = latest_cloud_; } if (static_cast(cloud_to_save->points.size()) > config_.max_points) { pcl::PointCloud::Ptr ds(new pcl::PointCloud); pcl::VoxelGrid vox; vox.setInputCloud(cloud_to_save); float leaf = static_cast(config_.voxel_size) * 2.0f; vox.setLeafSize(leaf, leaf, leaf); vox.filter(*ds); cloud_to_save = ds; } const std::string filename = config_.map_name + ".ply"; if (pcl::io::savePLYFileBinary(filename, *cloud_to_save) == 0) { RCLCPP_INFO(this->get_logger(), "Saved map: %s (%zu points)", filename.c_str(), cloud_to_save->points.size()); } else { RCLCPP_ERROR(this->get_logger(), "Failed to save map: %s", filename.c_str()); } } void LidarToPointCloudNode::logConfiguration() { RCLCPP_INFO(this->get_logger(), "LiDAR to PointCloud node ready"); RCLCPP_INFO(this->get_logger(), " Robots : %zu", config_.robot_ip_list.size()); RCLCPP_INFO(this->get_logger(), " Voxel : %.3fm", config_.voxel_size); RCLCPP_INFO(this->get_logger(), " Save : %s", config_.save_map ? "true" : "false"); } } // namespace lidar_processor_cpp int main(int argc, char* argv[]) { rclcpp::init(argc, argv); try { auto node = std::make_shared(); rclcpp::spin(node); } catch (const std::exception& e) { std::cerr << "Fatal: " << e.what() << std::endl; return 1; } rclcpp::shutdown(); return 0; }