Nav2 Navigation Stack - lyrical  lyrical
ROS 2 Navigation Stack
vector_object_shapes.cpp
1 // Copyright (c) 2023 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_map_server/vector_object_shapes.hpp"
16 
17 #include <uuid/uuid.h>
18 #include <cmath>
19 #include <exception>
20 #include <limits>
21 #include <stdexcept>
22 #include <vector>
23 
24 #include "geometry_msgs/msg/pose_stamped.hpp"
25 
26 #include "nav2_util/occ_grid_utils.hpp"
27 #include "nav2_util/occ_grid_values.hpp"
28 #include "nav2_util/geometry_utils.hpp"
29 #include "nav2_util/raytrace_line_2d.hpp"
30 #include "nav2_util/robot_utils.hpp"
31 #include "nav2_ros_common/tf2_factories.hpp"
32 
33 namespace nav2_map_server
34 {
35 
36 // ---------- Shape ----------
37 
38 Shape::Shape(const nav2::LifecycleNode::WeakPtr & node)
39 : type_(UNKNOWN), node_(node)
40 {}
41 
43 {}
44 
45 ShapeType Shape::getType()
46 {
47  return type_;
48 }
49 
50 bool Shape::obtainShapeUUID(const std::string & shape_name, unsigned char * out_uuid)
51 {
52  auto node = node_.lock();
53  if (!node) {
54  throw std::runtime_error{"Failed to lock node"};
55  }
56 
57  try {
58  // Try to get shape UUID from ROS-parameters
59  std::string uuid_str = nav2::declare_or_get_parameter<std::string>(
60  node, shape_name + ".uuid");
61  if (uuid_parse(uuid_str.c_str(), out_uuid) != 0) {
62  RCLCPP_ERROR(
63  node->get_logger(),
64  "[%s] Can not parse UUID string for shape: %s",
65  shape_name.c_str(), uuid_str.c_str());
66  return false;
67  }
68  } catch (const std::exception &) {
69  // If no UUID was specified, generate a new one
70  uuid_generate(out_uuid);
71 
72  char uuid_str[37];
73  uuid_unparse(out_uuid, uuid_str);
74  RCLCPP_INFO(
75  node->get_logger(),
76  "[%s] No UUID is specified for shape. Generating a new one: %s",
77  shape_name.c_str(), uuid_str);
78  }
79 
80  return true;
81 }
82 
83 // ---------- Polygon ----------
84 
85 Polygon::Polygon(
86  const nav2::LifecycleNode::WeakPtr & node)
87 : Shape::Shape(node)
88 {
89  type_ = POLYGON;
90 }
91 
92 int8_t Polygon::getValue() const
93 {
94  return params_->value;
95 }
96 
97 std::string Polygon::getFrameID() const
98 {
99  return params_->header.frame_id;
100 }
101 
102 std::string Polygon::getUUID() const
103 {
104  return unparseUUID(params_->uuid.uuid.data());
105 }
106 
107 bool Polygon::isUUID(const unsigned char * uuid) const
108 {
109  return uuid_compare(params_->uuid.uuid.data(), uuid) == 0;
110 }
111 
112 bool Polygon::isFill() const
113 {
114  return params_->closed;
115 }
116 
117 bool Polygon::obtainParams(const std::string & shape_name)
118 {
119  auto node = node_.lock();
120  if (!node) {
121  throw std::runtime_error{"Failed to lock node"};
122  }
123 
124  if (!params_) {
125  params_ = std::make_shared<nav2_msgs::msg::PolygonObject>();
126  }
127  if (!polygon_) {
128  polygon_ = std::make_shared<geometry_msgs::msg::Polygon>();
129  }
130 
131  params_->header.frame_id = nav2::declare_or_get_parameter(
132  node, shape_name + ".frame_id", std::string{"map"});
133  params_->value = nav2::declare_or_get_parameter(
134  node, shape_name + ".value", static_cast<int>(nav2_util::OCC_GRID_OCCUPIED));
135  params_->closed = nav2::declare_or_get_parameter(
136  node, shape_name + ".closed", true);
137 
138  std::vector<double> poly_row;
139  try {
140  poly_row = nav2::declare_or_get_parameter<std::vector<double>>(
141  node, shape_name + ".points");
142  } catch (const std::exception & ex) {
143  RCLCPP_ERROR(
144  node->get_logger(),
145  "[%s] Error while getting polygon parameters: %s",
146  shape_name.c_str(), ex.what());
147  return false;
148  }
149  // Check for points format correctness
150  if (poly_row.size() < 6 || poly_row.size() % 2 != 0) {
151  RCLCPP_ERROR(
152  node->get_logger(),
153  "[%s] Polygon has incorrect points description",
154  shape_name.c_str());
155  return false;
156  }
157 
158  // Obtain polygon vertices
159  geometry_msgs::msg::Point32 point;
160  bool first = true;
161  for (double val : poly_row) {
162  if (first) {
163  point.x = val;
164  } else {
165  point.y = val;
166  params_->points.push_back(point);
167  }
168  first = !first;
169  }
170 
171  // Filling the polygon_ with obtained points in map's frame
172  polygon_->points = params_->points;
173 
174  // Getting shape UUID
175  return obtainShapeUUID(shape_name, params_->uuid.uuid.data());
176 }
177 
178 nav2_msgs::msg::PolygonObject::SharedPtr Polygon::getParams() const
179 {
180  return params_;
181 }
182 
183 bool Polygon::setParams(const nav2_msgs::msg::PolygonObject::SharedPtr params)
184 {
185  params_ = params;
186 
187  if (!polygon_) {
188  polygon_ = std::make_shared<geometry_msgs::msg::Polygon>();
189  }
190  polygon_->points = params_->points;
191 
192  // If no UUID was specified, generate a new one
193  if (uuid_is_null(params_->uuid.uuid.data())) {
194  uuid_generate(params_->uuid.uuid.data());
195  }
196 
197  return checkConsistency();
198 }
199 
201  const std::string & to_frame,
202  const nav2::TransformBuffer::SharedPtr tf_buffer,
203  const double transform_tolerance)
204 {
205  geometry_msgs::msg::PoseStamped from_pose, to_pose;
206  from_pose.header = params_->header;
207  for (unsigned int i = 0; i < params_->points.size(); i++) {
208  from_pose.pose.position.x = params_->points[i].x;
209  from_pose.pose.position.y = params_->points[i].y;
210  from_pose.pose.position.z = params_->points[i].z;
211  if (
212  nav2_util::transformPoseInTargetFrame(
213  from_pose, to_pose, *tf_buffer, to_frame, transform_tolerance))
214  {
215  polygon_->points[i].x = to_pose.pose.position.x;
216  polygon_->points[i].y = to_pose.pose.position.y;
217  polygon_->points[i].z = to_pose.pose.position.z;
218  } else {
219  return false;
220  }
221  }
222 
223  return true;
224 }
225 
226 void Polygon::getBoundaries(double & min_x, double & min_y, double & max_x, double & max_y)
227 {
228  min_x = std::numeric_limits<double>::max();
229  min_y = std::numeric_limits<double>::max();
230  max_x = std::numeric_limits<double>::lowest();
231  max_y = std::numeric_limits<double>::lowest();
232 
233  for (auto point : polygon_->points) {
234  min_x = std::min(min_x, static_cast<double>(point.x));
235  min_y = std::min(min_y, static_cast<double>(point.y));
236  max_x = std::max(max_x, static_cast<double>(point.x));
237  max_y = std::max(max_y, static_cast<double>(point.y));
238  }
239 }
240 
241 bool Polygon::isPointInside(const double px, const double py) const
242 {
243  return nav2_util::geometry_utils::isPointInsidePolygon(px, py, polygon_->points);
244 }
245 
247  nav_msgs::msg::OccupancyGrid::SharedPtr map, const OverlayType overlay_type)
248 {
249  unsigned int mx0, my0, mx1, my1;
250 
251  auto node = node_.lock();
252  if (!node) {
253  throw std::runtime_error{"Failed to lock node"};
254  }
255 
256  if (!nav2_util::worldToMap(map, polygon_->points[0].x, polygon_->points[0].y, mx1, my1)) {
257  RCLCPP_ERROR(
258  node->get_logger(),
259  "[UUID: %s] Can not convert (%f, %f) point to map",
260  getUUID().c_str(), polygon_->points[0].x, polygon_->points[0].y);
261  return;
262  }
263 
264  MapAction ma(map, params_->value, overlay_type);
265  for (unsigned int i = 1; i < polygon_->points.size(); i++) {
266  mx0 = mx1;
267  my0 = my1;
268  if (!nav2_util::worldToMap(map, polygon_->points[i].x, polygon_->points[i].y, mx1, my1)) {
269  RCLCPP_ERROR(
270  node->get_logger(),
271  "[UUID: %s] Can not convert (%f, %f) point to map",
272  getUUID().c_str(), polygon_->points[i].x, polygon_->points[i].y);
273  return;
274  }
275  nav2_util::raytraceLine(ma, mx0, my0, mx1, my1, map->info.width);
276  }
277 }
278 
280 {
281  if (params_->points.size() < 3) {
282  auto node = node_.lock();
283  if (!node) {
284  throw std::runtime_error{"Failed to lock node"};
285  }
286 
287  RCLCPP_ERROR(
288  node->get_logger(),
289  "[UUID: %s] Polygon has incorrect number of vertices: %li",
290  getUUID().c_str(), params_->points.size());
291  return false;
292  }
293 
294  return true;
295 }
296 
297 // ---------- Circle ----------
298 
299 Circle::Circle(
300  const nav2::LifecycleNode::WeakPtr & node)
301 : Shape::Shape(node)
302 {
303  type_ = CIRCLE;
304 }
305 
306 int8_t Circle::getValue() const
307 {
308  return params_->value;
309 }
310 
311 std::string Circle::getFrameID() const
312 {
313  return params_->header.frame_id;
314 }
315 
316 std::string Circle::getUUID() const
317 {
318  return unparseUUID(params_->uuid.uuid.data());
319 }
320 
321 bool Circle::isUUID(const unsigned char * uuid) const
322 {
323  return uuid_compare(params_->uuid.uuid.data(), uuid) == 0;
324 }
325 
326 bool Circle::isFill() const
327 {
328  return params_->fill;
329 }
330 
331 bool Circle::obtainParams(const std::string & shape_name)
332 {
333  auto node = node_.lock();
334  if (!node) {
335  throw std::runtime_error{"Failed to lock node"};
336  }
337 
338  if (!params_) {
339  params_ = std::make_shared<nav2_msgs::msg::CircleObject>();
340  }
341  if (!center_) {
342  center_ = std::make_shared<geometry_msgs::msg::Point32>();
343  }
344 
345  params_->header.frame_id = nav2::declare_or_get_parameter(
346  node, shape_name + ".frame_id", std::string{"map"});
347  params_->value = nav2::declare_or_get_parameter(
348  node, shape_name + ".value", static_cast<int>(nav2_util::OCC_GRID_OCCUPIED));
349  params_->fill = nav2::declare_or_get_parameter(
350  node, shape_name + ".fill", true);
351 
352  std::vector<double> center_row;
353  try {
354  center_row = nav2::declare_or_get_parameter<std::vector<double>>(
355  node, shape_name + ".center");
356  params_->radius = nav2::declare_or_get_parameter<double>(
357  node, shape_name + ".radius");
358  if (params_->radius < 0) {
359  RCLCPP_ERROR(
360  node->get_logger(),
361  "[%s] Circle has incorrect radius less than zero",
362  shape_name.c_str());
363  return false;
364  }
365  } catch (const std::exception & ex) {
366  RCLCPP_ERROR(
367  node->get_logger(),
368  "[%s] Error while getting circle parameters: %s",
369  shape_name.c_str(), ex.what());
370  return false;
371  }
372  // Check for points format correctness
373  if (center_row.size() != 2) {
374  RCLCPP_ERROR(
375  node->get_logger(),
376  "[%s] Circle has incorrect center description",
377  shape_name.c_str());
378  return false;
379  }
380 
381  // Obtain circle center
382  params_->center.x = center_row[0];
383  params_->center.y = center_row[1];
384  // Setting the center_ with obtained circle center in map's frame
385  *center_ = params_->center;
386 
387  // Getting shape UUID
388  return obtainShapeUUID(shape_name, params_->uuid.uuid.data());
389 }
390 
391 nav2_msgs::msg::CircleObject::SharedPtr Circle::getParams() const
392 {
393  return params_;
394 }
395 
396 bool Circle::setParams(const nav2_msgs::msg::CircleObject::SharedPtr params)
397 {
398  params_ = params;
399 
400  if (!center_) {
401  center_ = std::make_shared<geometry_msgs::msg::Point32>();
402  }
403  *center_ = params_->center;
404 
405  // If no UUID was specified, generate a new one
406  if (uuid_is_null(params_->uuid.uuid.data())) {
407  uuid_generate(params_->uuid.uuid.data());
408  }
409 
410  return checkConsistency();
411 }
412 
414  const std::string & to_frame,
415  const nav2::TransformBuffer::SharedPtr tf_buffer,
416  const double transform_tolerance)
417 {
418  geometry_msgs::msg::PoseStamped from_pose, to_pose;
419  from_pose.header = params_->header;
420  from_pose.pose.position.x = params_->center.x;
421  from_pose.pose.position.y = params_->center.y;
422  from_pose.pose.position.z = params_->center.z;
423  if (
424  nav2_util::transformPoseInTargetFrame(
425  from_pose, to_pose, *tf_buffer, to_frame, transform_tolerance))
426  {
427  center_->x = to_pose.pose.position.x;
428  center_->y = to_pose.pose.position.y;
429  center_->z = to_pose.pose.position.z;
430  } else {
431  return false;
432  }
433 
434  return true;
435 }
436 
437 void Circle::getBoundaries(double & min_x, double & min_y, double & max_x, double & max_y)
438 {
439  min_x = center_->x - params_->radius;
440  min_y = center_->y - params_->radius;
441  max_x = center_->x + params_->radius;
442  max_y = center_->y + params_->radius;
443 }
444 
445 bool Circle::isPointInside(const double px, const double py) const
446 {
447  return ( (px - center_->x) * (px - center_->x) + (py - center_->y) * (py - center_->y) ) <=
448  params_->radius * params_->radius;
449 }
450 
452  nav_msgs::msg::OccupancyGrid::SharedPtr map, const OverlayType overlay_type)
453 {
454  unsigned int mcx, mcy;
455  if (!centerToMap(map, mcx, mcy)) {
456  return;
457  }
458 
459  // Implementation of the circle generation algorithm, based on the following work:
460  // Berthold K.P. Horn "Circle generators for display devices"
461  // Computer Graphics and Image Processing 5.2 (1976): 280-288.
462 
463  // Inputs initialization
464  const int r = static_cast<int>(std::round(params_->radius / map->info.resolution));
465  int x = r;
466  int y = 1;
467 
468  // Error initialization
469  int s = -r;
470 
471  // Calculation algorithm
472  while (x > y) { // Calculating only first circle octant
473  // Put 8 points in each octant reflecting symmetrically
474  putPoint(mcx + x, mcy + y, map, overlay_type);
475  putPoint(mcx + y, mcy + x, map, overlay_type);
476  putPoint(mcx - x + 1, mcy + y, map, overlay_type);
477  putPoint(mcx + y, mcy - x + 1, map, overlay_type);
478  putPoint(mcx - x + 1, mcy - y + 1, map, overlay_type);
479  putPoint(mcx - y + 1, mcy - x + 1, map, overlay_type);
480  putPoint(mcx + x, mcy - y + 1, map, overlay_type);
481  putPoint(mcx - y + 1, mcy + x, map, overlay_type);
482 
483  s = s + 2 * y + 1;
484  y++;
485  if (s > 0) {
486  s = s - 2 * x + 2;
487  x--;
488  }
489  }
490 
491  // Corner case for x == y: do not put end points twice
492  if (x == y) {
493  putPoint(mcx + x, mcy + y, map, overlay_type);
494  putPoint(mcx - x + 1, mcy + y, map, overlay_type);
495  putPoint(mcx - x + 1, mcy - y + 1, map, overlay_type);
496  putPoint(mcx + x, mcy - y + 1, map, overlay_type);
497  }
498 }
499 
501 {
502  if (params_->radius < 0.0) {
503  auto node = node_.lock();
504  if (!node) {
505  throw std::runtime_error{"Failed to lock node"};
506  }
507 
508  RCLCPP_ERROR(
509  node->get_logger(),
510  "[UUID: %s] Circle has incorrect radius less than zero",
511  getUUID().c_str());
512  return false;
513  }
514  return true;
515 }
516 
518  nav_msgs::msg::OccupancyGrid::ConstSharedPtr map,
519  unsigned int & mcx, unsigned int & mcy)
520 {
521  auto node = node_.lock();
522  if (!node) {
523  throw std::runtime_error{"Failed to lock node"};
524  }
525 
526  // Get center of circle in map coordinates
527  if (center_->x < map->info.origin.position.x || center_->y < map->info.origin.position.y) {
528  RCLCPP_ERROR(
529  node->get_logger(),
530  "[UUID: %s] Can not convert (%f, %f) circle center to map",
531  getUUID().c_str(), center_->x, center_->y);
532  return false;
533  }
534  // We need the circle center to be always shifted one cell less its logical center
535  // and to avoid any FP-accuracy losing on small values, so we are using another
536  // than nav2_util::worldToMap() approach
537  mcx = static_cast<unsigned int>(
538  std::round((center_->x - map->info.origin.position.x) / map->info.resolution)) - 1;
539  mcy = static_cast<unsigned int>(
540  std::round((center_->y - map->info.origin.position.y) / map->info.resolution)) - 1;
541  if (mcx >= map->info.width || mcy >= map->info.height) {
542  RCLCPP_ERROR(
543  node->get_logger(),
544  "[UUID: %s] Can not convert (%f, %f) point to map",
545  getUUID().c_str(), center_->x, center_->y);
546  return false;
547  }
548 
549  return true;
550 }
551 
552 inline void Circle::putPoint(
553  unsigned int mx, unsigned int my,
554  nav_msgs::msg::OccupancyGrid::SharedPtr map,
555  const OverlayType overlay_type)
556 {
557  processCell(map, my * map->info.width + mx, params_->value, overlay_type);
558 }
559 
560 } // namespace nav2_map_server
bool isUUID(const unsigned char *uuid) const
Checks whether the shape is equal to a given UUID.
int8_t getValue() const
Gets the value of the shape.
bool toFrame(const std::string &to_frame, const nav2::TransformBuffer::SharedPtr tf_buffer, const double transform_tolerance)
Transforms shape coordinates to a new frame.
void putPoint(unsigned int mx, unsigned int my, nav_msgs::msg::OccupancyGrid::SharedPtr map, const OverlayType overlay_type)
Put Circle's point on map.
bool isPointInside(const double px, const double py) const
Is the point inside the shape.
std::string getUUID() const
Gets UUID of the shape.
void getBoundaries(double &min_x, double &min_y, double &max_x, double &max_y)
Gets shape box-boundaries.
std::string getFrameID() const
Gets frame ID of the shape.
bool centerToMap(nav_msgs::msg::OccupancyGrid::ConstSharedPtr map, unsigned int &mcx, unsigned int &mcy)
Converts circle center to map coordinates considering FP-accuracy losing on small values when using c...
bool isFill() const
Whether the shape to be filled or only its borders to be put on map.
nav2_msgs::msg::CircleObject::SharedPtr params_
Input circle parameters (could be in any frame)
bool setParams(const nav2_msgs::msg::CircleObject::SharedPtr params)
Tries to update Circle parameters.
void putBorders(nav_msgs::msg::OccupancyGrid::SharedPtr map, const OverlayType overlay_type)
Puts shape borders on map.
nav2_msgs::msg::CircleObject::SharedPtr getParams() const
Gets Circle parameters.
bool checkConsistency()
Checks that shape is consistent for further operation.
geometry_msgs::msg::Point32::SharedPtr center_
Circle center in the map's frame.
bool obtainParams(const std::string &shape_name)
Supporting routine obtaining ROS-parameters for the given vector object.
Functor class used in raytraceLine algorithm.
std::string getUUID() const
Gets UUID of the shape.
void getBoundaries(double &min_x, double &min_y, double &max_x, double &max_y)
Gets shape box-boundaries.
void putBorders(nav_msgs::msg::OccupancyGrid::SharedPtr map, const OverlayType overlay_type)
Puts shape borders on map.
bool toFrame(const std::string &to_frame, const nav2::TransformBuffer::SharedPtr tf_buffer, const double transform_tolerance)
Transforms shape coordinates to a new frame.
bool isUUID(const unsigned char *uuid) const
Checks whether the shape is equal to a given UUID.
nav2_msgs::msg::PolygonObject::SharedPtr getParams() const
Gets Polygon parameters.
int8_t getValue() const
Gets the value of the shape.
bool setParams(const nav2_msgs::msg::PolygonObject::SharedPtr params)
Tries to update Polygon parameters.
std::string getFrameID() const
Gets frame ID of the shape.
nav2_msgs::msg::PolygonObject::SharedPtr params_
Input polygon parameters (could be in any frame)
bool checkConsistency()
Checks that shape is consistent for further operation.
bool isPointInside(const double px, const double py) const
Is the point inside the shape.
bool isFill() const
Whether the shape to be filled or only its borders to be put on map.
bool obtainParams(const std::string &shape_name)
Supporting routine obtaining ROS-parameters for the given vector object.
geometry_msgs::msg::Polygon::SharedPtr polygon_
Polygon in the map's frame.
Basic class, other vector objects to be inherited from.
bool obtainShapeUUID(const std::string &shape_name, unsigned char *out_uuid)
Supporting routine obtaining shape UUID from ROS-parameters for the given shape object.
ShapeType type_
Type of shape.
ShapeType getType()
Returns type of the shape.
nav2::LifecycleNode::WeakPtr node_
VectorObjectServer node.
virtual ~Shape()
Shape destructor.
Shape(const nav2::LifecycleNode::WeakPtr &node)
Shape basic class constructor.