Nav2 Navigation Stack - humble  humble
ROS 2 Navigation Stack
polygon.cpp
1 // Copyright (c) 2022 Samsung R&D Institute Russia
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "nav2_collision_monitor/polygon.hpp"
16 
17 #include <exception>
18 #include <utility>
19 
20 #include "geometry_msgs/msg/point.hpp"
21 #include "geometry_msgs/msg/point32.hpp"
22 
23 #include "nav2_util/node_utils.hpp"
24 
25 #include "nav2_collision_monitor/kinematics.hpp"
26 
27 namespace nav2_collision_monitor
28 {
29 
31  const nav2_util::LifecycleNode::WeakPtr & node,
32  const std::string & polygon_name,
33  const std::shared_ptr<tf2_ros::Buffer> tf_buffer,
34  const std::string & base_frame_id,
35  const tf2::Duration & transform_tolerance)
36 : node_(node), polygon_name_(polygon_name), action_type_(DO_NOTHING),
37  slowdown_ratio_(0.0), footprint_sub_(nullptr), tf_buffer_(tf_buffer),
38  base_frame_id_(base_frame_id), transform_tolerance_(transform_tolerance)
39 {
40  RCLCPP_INFO(logger_, "[%s]: Creating Polygon", polygon_name_.c_str());
41 }
42 
44 {
45  RCLCPP_INFO(logger_, "[%s]: Destroying Polygon", polygon_name_.c_str());
46  poly_.clear();
47  dyn_params_handler_.reset();
48 }
49 
51 {
52  auto node = node_.lock();
53  if (!node) {
54  throw std::runtime_error{"Failed to lock node"};
55  }
56 
57  std::string polygon_pub_topic, footprint_topic;
58 
59  if (!getParameters(polygon_pub_topic, footprint_topic)) {
60  return false;
61  }
62 
63  if (!footprint_topic.empty()) {
64  footprint_sub_ = std::make_unique<nav2_costmap_2d::FootprintSubscriber>(
65  node, footprint_topic, *tf_buffer_,
66  base_frame_id_, tf2::durationToSec(transform_tolerance_));
67  }
68 
69  if (visualize_) {
70  // Fill polygon_ points for future usage
71  std::vector<Point> poly;
72  getPolygon(poly);
73  for (const Point & p : poly) {
74  geometry_msgs::msg::Point32 p_s;
75  p_s.x = p.x;
76  p_s.y = p.y;
77  // p_s.z will remain 0.0
78  polygon_.points.push_back(p_s);
79  }
80 
81  rclcpp::QoS polygon_qos = rclcpp::SystemDefaultsQoS(); // set to default
82  polygon_pub_ = node->create_publisher<geometry_msgs::msg::PolygonStamped>(
83  polygon_pub_topic, polygon_qos);
84  }
85 
86  // Add callback for dynamic parameters
87  dyn_params_handler_ = node->add_on_set_parameters_callback(
88  std::bind(&Polygon::dynamicParametersCallback, this, std::placeholders::_1));
89 
90  return true;
91 }
92 
94 {
95  if (visualize_) {
96  polygon_pub_->on_activate();
97  }
98 }
99 
101 {
102  if (visualize_) {
103  polygon_pub_->on_deactivate();
104  }
105 }
106 
107 std::string Polygon::getName() const
108 {
109  return polygon_name_;
110 }
111 
112 ActionType Polygon::getActionType() const
113 {
114  return action_type_;
115 }
116 
118 {
119  return enabled_;
120 }
121 
123 {
124  return max_points_;
125 }
126 
128 {
129  return slowdown_ratio_;
130 }
131 
133 {
134  return time_before_collision_;
135 }
136 
137 void Polygon::getPolygon(std::vector<Point> & poly) const
138 {
139  poly = poly_;
140 }
141 
143 {
144  if (footprint_sub_ != nullptr) {
145  // Get latest robot footprint from footprint subscriber
146  std::vector<geometry_msgs::msg::Point> footprint_vec;
147  std_msgs::msg::Header footprint_header;
148  footprint_sub_->getFootprintInRobotFrame(footprint_vec, footprint_header);
149 
150  std::size_t new_size = footprint_vec.size();
151  poly_.resize(new_size);
152  polygon_.points.resize(new_size);
153 
154  geometry_msgs::msg::Point32 p_s;
155  for (std::size_t i = 0; i < new_size; i++) {
156  poly_[i] = {footprint_vec[i].x, footprint_vec[i].y};
157  p_s.x = footprint_vec[i].x;
158  p_s.y = footprint_vec[i].y;
159  polygon_.points[i] = p_s;
160  }
161  }
162 }
163 
164 int Polygon::getPointsInside(const std::vector<Point> & points) const
165 {
166  int num = 0;
167  for (const Point & point : points) {
168  if (isPointInside(point)) {
169  num++;
170  }
171  }
172  return num;
173 }
174 
176  const std::vector<Point> & collision_points,
177  const Velocity & velocity) const
178 {
179  // Initial robot pose is {0,0} in base_footprint coordinates
180  Pose pose = {0.0, 0.0, 0.0};
181  Velocity vel = velocity;
182 
183  // Array of points transformed to the frame concerned with pose on each simulation step
184  std::vector<Point> points_transformed = collision_points;
185 
186  // Check static polygon
187  if (getPointsInside(points_transformed) >= max_points_) {
188  return 0.0;
189  }
190 
191  // Robot movement simulation
192  for (double time = 0.0; time <= time_before_collision_; time += simulation_time_step_) {
193  // Shift the robot pose towards to the vel during simulation_time_step_ time interval
194  // NOTE: vel is changing during the simulation
195  projectState(simulation_time_step_, pose, vel);
196  // Transform collision_points to the frame concerned with current robot pose
197  points_transformed = collision_points;
198  transformPoints(pose, points_transformed);
199  // If the collision occurred on this stage, return the actual time before a collision
200  // as if robot was moved with given velocity
201  if (getPointsInside(points_transformed) > max_points_) {
202  return time;
203  }
204  }
205 
206  // There is no collision
207  return -1.0;
208 }
209 
210 void Polygon::publish() const
211 {
212  if (!visualize_) {
213  return;
214  }
215 
216  auto node = node_.lock();
217  if (!node) {
218  throw std::runtime_error{"Failed to lock node"};
219  }
220 
221  // Fill PolygonStamped struct
222  std::unique_ptr<geometry_msgs::msg::PolygonStamped> poly_s =
223  std::make_unique<geometry_msgs::msg::PolygonStamped>();
224  poly_s->header.stamp = node->now();
225  poly_s->header.frame_id = base_frame_id_;
226  poly_s->polygon = polygon_;
227 
228  // Publish polygon
229  polygon_pub_->publish(std::move(poly_s));
230 }
231 
232 bool Polygon::getCommonParameters(std::string & polygon_pub_topic)
233 {
234  auto node = node_.lock();
235  if (!node) {
236  throw std::runtime_error{"Failed to lock node"};
237  }
238 
239  try {
240  // Get action type.
241  // Leave it not initialized: the will cause an error if it will not set.
242  nav2_util::declare_parameter_if_not_declared(
243  node, polygon_name_ + ".action_type", rclcpp::PARAMETER_STRING);
244  const std::string at_str =
245  node->get_parameter(polygon_name_ + ".action_type").as_string();
246  if (at_str == "stop") {
247  action_type_ = STOP;
248  } else if (at_str == "slowdown") {
249  action_type_ = SLOWDOWN;
250  } else if (at_str == "approach") {
251  action_type_ = APPROACH;
252  } else { // Error if something else
253  RCLCPP_ERROR(logger_, "[%s]: Unknown action type: %s", polygon_name_.c_str(), at_str.c_str());
254  return false;
255  }
256 
257  nav2_util::declare_parameter_if_not_declared(
258  node, polygon_name_ + ".enabled", rclcpp::ParameterValue(true));
259  enabled_ = node->get_parameter(polygon_name_ + ".enabled").as_bool();
260 
261  nav2_util::declare_parameter_if_not_declared(
262  node, polygon_name_ + ".max_points", rclcpp::ParameterValue(3));
263  max_points_ = node->get_parameter(polygon_name_ + ".max_points").as_int();
264 
265  if (action_type_ == SLOWDOWN) {
266  nav2_util::declare_parameter_if_not_declared(
267  node, polygon_name_ + ".slowdown_ratio", rclcpp::ParameterValue(0.5));
268  slowdown_ratio_ = node->get_parameter(polygon_name_ + ".slowdown_ratio").as_double();
269  }
270 
271  if (action_type_ == APPROACH) {
272  nav2_util::declare_parameter_if_not_declared(
273  node, polygon_name_ + ".time_before_collision", rclcpp::ParameterValue(2.0));
275  node->get_parameter(polygon_name_ + ".time_before_collision").as_double();
276  nav2_util::declare_parameter_if_not_declared(
277  node, polygon_name_ + ".simulation_time_step", rclcpp::ParameterValue(0.1));
279  node->get_parameter(polygon_name_ + ".simulation_time_step").as_double();
280  }
281 
282  nav2_util::declare_parameter_if_not_declared(
283  node, polygon_name_ + ".visualize", rclcpp::ParameterValue(false));
284  visualize_ = node->get_parameter(polygon_name_ + ".visualize").as_bool();
285  if (visualize_) {
286  // Get polygon topic parameter in case if it is going to be published
287  nav2_util::declare_parameter_if_not_declared(
288  node, polygon_name_ + ".polygon_pub_topic", rclcpp::ParameterValue(polygon_name_));
289  polygon_pub_topic = node->get_parameter(polygon_name_ + ".polygon_pub_topic").as_string();
290  }
291  } catch (const std::exception & ex) {
292  RCLCPP_ERROR(
293  logger_,
294  "[%s]: Error while getting common polygon parameters: %s",
295  polygon_name_.c_str(), ex.what());
296  return false;
297  }
298 
299  return true;
300 }
301 
302 bool Polygon::getParameters(std::string & polygon_pub_topic, std::string & footprint_topic)
303 {
304  auto node = node_.lock();
305  if (!node) {
306  throw std::runtime_error{"Failed to lock node"};
307  }
308 
309  if (!getCommonParameters(polygon_pub_topic)) {
310  return false;
311  }
312 
313  try {
314  if (action_type_ == APPROACH) {
315  // Obtain the footprint topic to make a footprint subscription for approach polygon
316  nav2_util::declare_parameter_if_not_declared(
317  node, polygon_name_ + ".footprint_topic",
318  rclcpp::ParameterValue("local_costmap/published_footprint"));
319  footprint_topic =
320  node->get_parameter(polygon_name_ + ".footprint_topic").as_string();
321 
322  // This is robot footprint: do not need to get polygon points from ROS parameters.
323  // It will be set dynamically later.
324  return true;
325  } else {
326  // Make it empty otherwise
327  footprint_topic.clear();
328  }
329 
330  // Leave it not initialized: the will cause an error if it will not set
331  nav2_util::declare_parameter_if_not_declared(
332  node, polygon_name_ + ".points", rclcpp::PARAMETER_DOUBLE_ARRAY);
333  std::vector<double> poly_row =
334  node->get_parameter(polygon_name_ + ".points").as_double_array();
335  // Check for points format correctness
336  if (poly_row.size() <= 6 || poly_row.size() % 2 != 0) {
337  RCLCPP_ERROR(
338  logger_,
339  "[%s]: Polygon has incorrect points description",
340  polygon_name_.c_str());
341  return false;
342  }
343 
344  // Obtain polygon vertices
345  Point point;
346  bool first = true;
347  for (double val : poly_row) {
348  if (first) {
349  point.x = val;
350  } else {
351  point.y = val;
352  poly_.push_back(point);
353  }
354  first = !first;
355  }
356  } catch (const std::exception & ex) {
357  RCLCPP_ERROR(
358  logger_,
359  "[%s]: Error while getting polygon parameters: %s",
360  polygon_name_.c_str(), ex.what());
361  return false;
362  }
363 
364  return true;
365 }
366 
367 rcl_interfaces::msg::SetParametersResult
369  std::vector<rclcpp::Parameter> parameters)
370 {
371  rcl_interfaces::msg::SetParametersResult result;
372 
373  for (auto parameter : parameters) {
374  const auto & param_type = parameter.get_type();
375  const auto & param_name = parameter.get_name();
376 
377  if (param_type == rcl_interfaces::msg::ParameterType::PARAMETER_BOOL) {
378  if (param_name == polygon_name_ + "." + "enabled") {
379  enabled_ = parameter.as_bool();
380  }
381  }
382  }
383  result.successful = true;
384  return result;
385 }
386 
387 inline bool Polygon::isPointInside(const Point & point) const
388 {
389  // Adaptation of Shimrat, Moshe. "Algorithm 112: position of point relative to polygon."
390  // Communications of the ACM 5.8 (1962): 434.
391  // Implementation of ray crossings algorithm for point in polygon task solving.
392  // Y coordinate is fixed. Moving the ray on X+ axis starting from given point.
393  // Odd number of intersections with polygon boundaries means the point is inside polygon.
394  const int poly_size = poly_.size();
395  int i, j; // Polygon vertex iterators
396  bool res = false; // Final result, initialized with already inverted value
397 
398  // Starting from the edge where the last point of polygon is connected to the first
399  i = poly_size - 1;
400  for (j = 0; j < poly_size; j++) {
401  // Checking the edge only if given point is between edge boundaries by Y coordinates.
402  // One of the condition should contain equality in order to exclude the edges
403  // parallel to X+ ray.
404  if ((point.y <= poly_[i].y) == (point.y > poly_[j].y)) {
405  // Calculating the intersection coordinate of X+ ray
406  const double x_inter = poly_[i].x +
407  (point.y - poly_[i].y) * (poly_[j].x - poly_[i].x) /
408  (poly_[j].y - poly_[i].y);
409  // If intersection with checked edge is greater than point.x coordinate, inverting the result
410  if (x_inter > point.x) {
411  res = !res;
412  }
413  }
414  i = j;
415  }
416  return res;
417 }
418 
419 } // namespace nav2_collision_monitor
nav2_util::LifecycleNode::WeakPtr node_
Collision Monitor node.
Definition: polygon.hpp:182
double getTimeBeforeCollision() const
Obtains required time before collision for current polygon. Applicable for APPROACH model.
Definition: polygon.cpp:132
int max_points_
Maximum number of data readings within a zone to not trigger the action.
Definition: polygon.hpp:194
bool getCommonParameters(std::string &polygon_pub_topic)
Supporting routine obtaining ROS-parameters common for all shapes.
Definition: polygon.cpp:232
virtual int getPointsInside(const std::vector< Point > &points) const
Gets number of points inside given polygon.
Definition: polygon.cpp:164
double time_before_collision_
Time before collision in seconds.
Definition: polygon.hpp:198
bool enabled_
Whether polygon is enabled.
Definition: polygon.hpp:204
rclcpp::Logger logger_
Collision monitor node logger stored for further usage.
Definition: polygon.hpp:184
ActionType action_type_
Action type for the polygon.
Definition: polygon.hpp:192
double getCollisionTime(const std::vector< Point > &collision_points, const Velocity &velocity) const
Obtains estimated (simulated) time before a collision. Applicable for APPROACH model.
Definition: polygon.cpp:175
int getMaxPoints() const
Obtains polygon maximum points to enter inside polygon causing no action.
Definition: polygon.cpp:122
void publish() const
Publishes polygon message into a its own topic.
Definition: polygon.cpp:210
void updatePolygon()
Updates polygon from footprint subscriber (if any)
Definition: polygon.cpp:142
rclcpp::node_interfaces::OnSetParametersCallbackHandle::SharedPtr dyn_params_handler_
Dynamic parameters handler.
Definition: polygon.hpp:186
double simulation_time_step_
Time step for robot movement simulation.
Definition: polygon.hpp:200
void activate()
Activates polygon lifecycle publisher.
Definition: polygon.cpp:93
bool configure()
Shape configuration routine. Obtains ROS-parameters related to shape object and creates polygon lifec...
Definition: polygon.cpp:50
geometry_msgs::msg::Polygon polygon_
Polygon points stored for later publishing.
Definition: polygon.hpp:218
std::string getName() const
Returns the name of polygon.
Definition: polygon.cpp:107
virtual void getPolygon(std::vector< Point > &poly) const
Gets polygon points.
Definition: polygon.cpp:137
tf2::Duration transform_tolerance_
Transform tolerance.
Definition: polygon.hpp:212
std::shared_ptr< tf2_ros::Buffer > tf_buffer_
TF buffer.
Definition: polygon.hpp:208
ActionType getActionType() const
Obtains polygon action type.
Definition: polygon.cpp:112
std::vector< Point > poly_
Polygon points (vertices)
Definition: polygon.hpp:223
Polygon(const nav2_util::LifecycleNode::WeakPtr &node, const std::string &polygon_name, const std::shared_ptr< tf2_ros::Buffer > tf_buffer, const std::string &base_frame_id, const tf2::Duration &transform_tolerance)
Polygon constructor.
Definition: polygon.cpp:30
rcl_interfaces::msg::SetParametersResult dynamicParametersCallback(std::vector< rclcpp::Parameter > parameters)
Checks if point is inside polygon.
Definition: polygon.cpp:368
void deactivate()
Deactivates polygon lifecycle publisher.
Definition: polygon.cpp:100
std::unique_ptr< nav2_costmap_2d::FootprintSubscriber > footprint_sub_
Footprint subscriber.
Definition: polygon.hpp:202
double getSlowdownRatio() const
Obtains speed slowdown ratio for current polygon. Applicable for SLOWDOWN model.
Definition: polygon.cpp:127
std::string polygon_name_
Name of polygon.
Definition: polygon.hpp:190
virtual ~Polygon()
Polygon destructor.
Definition: polygon.cpp:43
bool getEnabled() const
Obtains polygon enabled state.
Definition: polygon.cpp:117
rclcpp_lifecycle::LifecyclePublisher< geometry_msgs::msg::PolygonStamped >::SharedPtr polygon_pub_
Polygon publisher for visualization purposes.
Definition: polygon.hpp:220
bool visualize_
Whether to publish the polygon.
Definition: polygon.hpp:216
std::string base_frame_id_
Base frame ID.
Definition: polygon.hpp:210
virtual bool getParameters(std::string &polygon_pub_topic, std::string &footprint_topic)
Supporting routine obtaining polygon-specific ROS-parameters.
Definition: polygon.cpp:302
double slowdown_ratio_
Robot slowdown (share of its actual speed)
Definition: polygon.hpp:196
Velocity for 2D model of motion.
Definition: types.hpp:23