// Copyright (c) 2022 Samsung R&D Institute Russia // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "nav2_collision_monitor/polygon.hpp" #include #include #include "geometry_msgs/msg/point.hpp" #include "geometry_msgs/msg/point32.hpp" #include "nav2_util/node_utils.hpp" #include "nav2_collision_monitor/kinematics.hpp" namespace nav2_collision_monitor { Polygon::Polygon( const nav2_util::LifecycleNode::WeakPtr & node, const std::string & polygon_name, const std::shared_ptr tf_buffer, const std::string & base_frame_id, const tf2::Duration & transform_tolerance) : node_(node), polygon_name_(polygon_name), action_type_(DO_NOTHING), slowdown_ratio_(0.0), footprint_sub_(nullptr), tf_buffer_(tf_buffer), base_frame_id_(base_frame_id), transform_tolerance_(transform_tolerance) { RCLCPP_INFO(logger_, "[%s]: Creating Polygon", polygon_name_.c_str()); } Polygon::~Polygon() { RCLCPP_INFO(logger_, "[%s]: Destroying Polygon", polygon_name_.c_str()); poly_.clear(); dyn_params_handler_.reset(); } bool Polygon::configure() { auto node = node_.lock(); if (!node) { throw std::runtime_error{"Failed to lock node"}; } std::string polygon_pub_topic, footprint_topic; if (!getParameters(polygon_pub_topic, footprint_topic)) { return false; } if (!footprint_topic.empty()) { footprint_sub_ = std::make_unique( node, footprint_topic, *tf_buffer_, base_frame_id_, tf2::durationToSec(transform_tolerance_)); } if (visualize_) { // Fill polygon_ points for future usage std::vector poly; getPolygon(poly); for (const Point & p : poly) { geometry_msgs::msg::Point32 p_s; p_s.x = p.x; p_s.y = p.y; // p_s.z will remain 0.0 polygon_.points.push_back(p_s); } rclcpp::QoS polygon_qos = rclcpp::SystemDefaultsQoS(); // set to default polygon_pub_ = node->create_publisher( polygon_pub_topic, polygon_qos); } // Add callback for dynamic parameters dyn_params_handler_ = node->add_on_set_parameters_callback( std::bind(&Polygon::dynamicParametersCallback, this, std::placeholders::_1)); return true; } void Polygon::activate() { if (visualize_) { polygon_pub_->on_activate(); } } void Polygon::deactivate() { if (visualize_) { polygon_pub_->on_deactivate(); } } std::string Polygon::getName() const { return polygon_name_; } ActionType Polygon::getActionType() const { return action_type_; } bool Polygon::getEnabled() const { return enabled_; } int Polygon::getMaxPoints() const { return max_points_; } double Polygon::getSlowdownRatio() const { return slowdown_ratio_; } double Polygon::getTimeBeforeCollision() const { return time_before_collision_; } void Polygon::getPolygon(std::vector & poly) const { poly = poly_; } void Polygon::updatePolygon() { if (footprint_sub_ != nullptr) { // Get latest robot footprint from footprint subscriber std::vector footprint_vec; std_msgs::msg::Header footprint_header; footprint_sub_->getFootprintInRobotFrame(footprint_vec, footprint_header); std::size_t new_size = footprint_vec.size(); poly_.resize(new_size); polygon_.points.resize(new_size); geometry_msgs::msg::Point32 p_s; for (std::size_t i = 0; i < new_size; i++) { poly_[i] = {footprint_vec[i].x, footprint_vec[i].y}; p_s.x = footprint_vec[i].x; p_s.y = footprint_vec[i].y; polygon_.points[i] = p_s; } } } int Polygon::getPointsInside(const std::vector & points) const { int num = 0; for (const Point & point : points) { if (isPointInside(point)) { num++; } } return num; } double Polygon::getCollisionTime( const std::vector & collision_points, const Velocity & velocity) const { // Initial robot pose is {0,0} in base_footprint coordinates Pose pose = {0.0, 0.0, 0.0}; Velocity vel = velocity; // Array of points transformed to the frame concerned with pose on each simulation step std::vector points_transformed = collision_points; // Check static polygon if (getPointsInside(points_transformed) >= max_points_) { return 0.0; } // Robot movement simulation for (double time = 0.0; time <= time_before_collision_; time += simulation_time_step_) { // Shift the robot pose towards to the vel during simulation_time_step_ time interval // NOTE: vel is changing during the simulation projectState(simulation_time_step_, pose, vel); // Transform collision_points to the frame concerned with current robot pose points_transformed = collision_points; transformPoints(pose, points_transformed); // If the collision occurred on this stage, return the actual time before a collision // as if robot was moved with given velocity if (getPointsInside(points_transformed) > max_points_) { return time; } } // There is no collision return -1.0; } void Polygon::publish() const { if (!visualize_) { return; } auto node = node_.lock(); if (!node) { throw std::runtime_error{"Failed to lock node"}; } // Fill PolygonStamped struct std::unique_ptr poly_s = std::make_unique(); poly_s->header.stamp = node->now(); poly_s->header.frame_id = base_frame_id_; poly_s->polygon = polygon_; // Publish polygon polygon_pub_->publish(std::move(poly_s)); } bool Polygon::getCommonParameters(std::string & polygon_pub_topic) { auto node = node_.lock(); if (!node) { throw std::runtime_error{"Failed to lock node"}; } try { // Get action type. // Leave it not initialized: the will cause an error if it will not set. nav2_util::declare_parameter_if_not_declared( node, polygon_name_ + ".action_type", rclcpp::PARAMETER_STRING); const std::string at_str = node->get_parameter(polygon_name_ + ".action_type").as_string(); if (at_str == "stop") { action_type_ = STOP; } else if (at_str == "slowdown") { action_type_ = SLOWDOWN; } else if (at_str == "approach") { action_type_ = APPROACH; } else { // Error if something else RCLCPP_ERROR(logger_, "[%s]: Unknown action type: %s", polygon_name_.c_str(), at_str.c_str()); return false; } nav2_util::declare_parameter_if_not_declared( node, polygon_name_ + ".enabled", rclcpp::ParameterValue(true)); enabled_ = node->get_parameter(polygon_name_ + ".enabled").as_bool(); nav2_util::declare_parameter_if_not_declared( node, polygon_name_ + ".max_points", rclcpp::ParameterValue(3)); max_points_ = node->get_parameter(polygon_name_ + ".max_points").as_int(); if (action_type_ == SLOWDOWN) { nav2_util::declare_parameter_if_not_declared( node, polygon_name_ + ".slowdown_ratio", rclcpp::ParameterValue(0.5)); slowdown_ratio_ = node->get_parameter(polygon_name_ + ".slowdown_ratio").as_double(); } if (action_type_ == APPROACH) { nav2_util::declare_parameter_if_not_declared( node, polygon_name_ + ".time_before_collision", rclcpp::ParameterValue(2.0)); time_before_collision_ = node->get_parameter(polygon_name_ + ".time_before_collision").as_double(); nav2_util::declare_parameter_if_not_declared( node, polygon_name_ + ".simulation_time_step", rclcpp::ParameterValue(0.1)); simulation_time_step_ = node->get_parameter(polygon_name_ + ".simulation_time_step").as_double(); } nav2_util::declare_parameter_if_not_declared( node, polygon_name_ + ".visualize", rclcpp::ParameterValue(false)); visualize_ = node->get_parameter(polygon_name_ + ".visualize").as_bool(); if (visualize_) { // Get polygon topic parameter in case if it is going to be published nav2_util::declare_parameter_if_not_declared( node, polygon_name_ + ".polygon_pub_topic", rclcpp::ParameterValue(polygon_name_)); polygon_pub_topic = node->get_parameter(polygon_name_ + ".polygon_pub_topic").as_string(); } } catch (const std::exception & ex) { RCLCPP_ERROR( logger_, "[%s]: Error while getting common polygon parameters: %s", polygon_name_.c_str(), ex.what()); return false; } return true; } bool Polygon::getParameters(std::string & polygon_pub_topic, std::string & footprint_topic) { auto node = node_.lock(); if (!node) { throw std::runtime_error{"Failed to lock node"}; } if (!getCommonParameters(polygon_pub_topic)) { return false; } try { if (action_type_ == APPROACH) { // Obtain the footprint topic to make a footprint subscription for approach polygon nav2_util::declare_parameter_if_not_declared( node, polygon_name_ + ".footprint_topic", rclcpp::ParameterValue("local_costmap/published_footprint")); footprint_topic = node->get_parameter(polygon_name_ + ".footprint_topic").as_string(); // This is robot footprint: do not need to get polygon points from ROS parameters. // It will be set dynamically later. return true; } else { // Make it empty otherwise footprint_topic.clear(); } // Leave it not initialized: the will cause an error if it will not set nav2_util::declare_parameter_if_not_declared( node, polygon_name_ + ".points", rclcpp::PARAMETER_DOUBLE_ARRAY); std::vector poly_row = node->get_parameter(polygon_name_ + ".points").as_double_array(); // Check for points format correctness if (poly_row.size() <= 6 || poly_row.size() % 2 != 0) { RCLCPP_ERROR( logger_, "[%s]: Polygon has incorrect points description", polygon_name_.c_str()); return false; } // Obtain polygon vertices Point point; bool first = true; for (double val : poly_row) { if (first) { point.x = val; } else { point.y = val; poly_.push_back(point); } first = !first; } } catch (const std::exception & ex) { RCLCPP_ERROR( logger_, "[%s]: Error while getting polygon parameters: %s", polygon_name_.c_str(), ex.what()); return false; } return true; } rcl_interfaces::msg::SetParametersResult Polygon::dynamicParametersCallback( std::vector parameters) { rcl_interfaces::msg::SetParametersResult result; for (auto parameter : parameters) { const auto & param_type = parameter.get_type(); const auto & param_name = parameter.get_name(); if (param_type == rcl_interfaces::msg::ParameterType::PARAMETER_BOOL) { if (param_name == polygon_name_ + "." + "enabled") { enabled_ = parameter.as_bool(); } } } result.successful = true; return result; } inline bool Polygon::isPointInside(const Point & point) const { // Adaptation of Shimrat, Moshe. "Algorithm 112: position of point relative to polygon." // Communications of the ACM 5.8 (1962): 434. // Implementation of ray crossings algorithm for point in polygon task solving. // Y coordinate is fixed. Moving the ray on X+ axis starting from given point. // Odd number of intersections with polygon boundaries means the point is inside polygon. const int poly_size = poly_.size(); int i, j; // Polygon vertex iterators bool res = false; // Final result, initialized with already inverted value // Starting from the edge where the last point of polygon is connected to the first i = poly_size - 1; for (j = 0; j < poly_size; j++) { // Checking the edge only if given point is between edge boundaries by Y coordinates. // One of the condition should contain equality in order to exclude the edges // parallel to X+ ray. if ((point.y <= poly_[i].y) == (point.y > poly_[j].y)) { // Calculating the intersection coordinate of X+ ray const double x_inter = poly_[i].x + (point.y - poly_[i].y) * (poly_[j].x - poly_[i].x) / (poly_[j].y - poly_[i].y); // If intersection with checked edge is greater than point.x coordinate, inverting the result if (x_inter > point.x) { res = !res; } } i = j; } return res; } } // namespace nav2_collision_monitor