Nav2 Navigation Stack - lyrical  lyrical
ROS 2 Navigation Stack
vector_object_server.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_server.hpp"
16 
17 #include <chrono>
18 #include <exception>
19 #include <functional>
20 #include <limits>
21 #include <stdexcept>
22 #include <utility>
23 
24 #include "rclcpp/create_timer.hpp"
25 
26 #include "nav2_util/occ_grid_values.hpp"
27 
28 using namespace std::placeholders;
29 
30 namespace nav2_map_server
31 {
32 
33 VectorObjectServer::VectorObjectServer(const rclcpp::NodeOptions & options)
34 : nav2::LifecycleNode("vector_object_server", "", options), process_map_(false)
35 {}
36 
37 nav2::CallbackReturn
38 VectorObjectServer::on_configure(const rclcpp_lifecycle::State & /*state*/)
39 {
40  RCLCPP_INFO(get_logger(), "Configuring");
41  // Obtaining ROS parameters
42  if (!obtainParams()) {
43  return nav2::CallbackReturn::FAILURE;
44  }
45 
47  // Transform buffer and listener initialization
48  tf_buffer_ = nav2::create_transform_buffer(this);
49  tf_listener_ = nav2::create_transform_listener(*tf_buffer_, this);
50  } else {
51  RCLCPP_INFO(
52  get_logger(),
53  "Parameter enforce_global_frame_id is true. TF listener is disabled. "
54  "All incoming shapes must have frame_id empty or equal to global_frame_id '%s'.",
55  global_frame_id_.c_str());
56  }
57 
58  map_pub_ = create_publisher<nav_msgs::msg::OccupancyGrid>(
59  map_topic_,
61 
62  add_shapes_service_ = create_service<nav2_msgs::srv::AddShapes>(
63  "~/add_shapes",
64  std::bind(&VectorObjectServer::addShapesCallback, this, _1, _2, _3));
65 
66  get_shapes_service_ = create_service<nav2_msgs::srv::GetShapes>(
67  "~/get_shapes",
68  std::bind(&VectorObjectServer::getShapesCallback, this, _1, _2, _3));
69 
70  remove_shapes_service_ = create_service<nav2_msgs::srv::RemoveShapes>(
71  "~/remove_shapes",
72  std::bind(&VectorObjectServer::removeShapesCallback, this, _1, _2, _3));
73 
74  return nav2::CallbackReturn::SUCCESS;
75 }
76 
77 nav2::CallbackReturn
78 VectorObjectServer::on_activate(const rclcpp_lifecycle::State & /*state*/)
79 {
80  RCLCPP_INFO(get_logger(), "Activating");
81 
82  map_pub_->on_activate();
83 
84  // Trigger map to be published
85  process_map_ = true;
87 
88  // Creating bond connection
89  createBond();
90 
91  return nav2::CallbackReturn::SUCCESS;
92 }
93 
94 nav2::CallbackReturn
95 VectorObjectServer::on_deactivate(const rclcpp_lifecycle::State & /*state*/)
96 {
97  RCLCPP_INFO(get_logger(), "Deactivating");
98 
99  if (map_timer_) {
100  map_timer_->cancel();
101  map_timer_.reset();
102  }
103  process_map_ = false;
104 
105  map_pub_->on_deactivate();
106 
107  // Destroying bond connection
108  destroyBond();
109 
110  return nav2::CallbackReturn::SUCCESS;
111 }
112 
113 nav2::CallbackReturn
114 VectorObjectServer::on_cleanup(const rclcpp_lifecycle::State & /*state*/)
115 {
116  RCLCPP_INFO(get_logger(), "Cleaning up");
117 
118  add_shapes_service_.reset();
119  get_shapes_service_.reset();
120  remove_shapes_service_.reset();
121 
122  map_pub_.reset();
123  map_.reset();
124 
125  shapes_.clear();
126 
127  tf_listener_.reset();
128  tf_buffer_.reset();
129 
130  return nav2::CallbackReturn::SUCCESS;
131 }
132 
133 nav2::CallbackReturn
134 VectorObjectServer::on_shutdown(const rclcpp_lifecycle::State & /*state*/)
135 {
136  RCLCPP_INFO(get_logger(), "Shutting down");
137  return nav2::CallbackReturn::SUCCESS;
138 }
139 
141 {
142  auto node = shared_from_this();
143 
144  // Main ROS-parameters
145  map_topic_ = nav2::declare_or_get_parameter(node, "map_topic", std::string{"vo_map"});
146  global_frame_id_ = nav2::declare_or_get_parameter(node, "global_frame_id", std::string{"map"});
147  enforce_global_frame_id_ = nav2::declare_or_get_parameter(node, "enforce_global_frame_id", false);
148  resolution_ = nav2::declare_or_get_parameter(node, "resolution", 0.05);
149  default_value_ = nav2::declare_or_get_parameter(
150  node, "default_value",
151  static_cast<int>(nav2_util::OCC_GRID_UNKNOWN));
152  overlay_type_ = static_cast<OverlayType>(nav2::declare_or_get_parameter(
153  node, "overlay_type",
154  static_cast<int>(OverlayType::OVERLAY_SEQ)));
155  update_frequency_ = nav2::declare_or_get_parameter(node, "update_frequency", 1.0);
156  transform_tolerance_ = nav2::declare_or_get_parameter(node, "transform_tolerance", 0.1);
157 
158  // Shapes
159  auto shape_names = nav2::declare_or_get_parameter(node, "shapes", std::vector<std::string>());
160  for (std::string shape_name : shape_names) {
161  std::string shape_type;
162  try {
163  shape_type = nav2::declare_or_get_parameter<std::string>(node, shape_name + ".type");
164  } catch (const std::exception & ex) {
165  RCLCPP_ERROR(
166  get_logger(), "Error while getting shape %s type: %s", shape_name.c_str(), ex.what());
167  return false;
168  }
169 
170  if (shape_type == "polygon") {
171  auto polygon = std::make_shared<Polygon>(node);
172  if (!polygon->obtainParams(shape_name)) {
173  return false;
174  }
175  shapes_.push_back(polygon);
176  } else if (shape_type == "circle") {
177  auto circle = std::make_shared<Circle>(node);
178  if (!circle->obtainParams(shape_name)) {
179  return false;
180  }
181  shapes_.push_back(circle);
182  } else {
183  RCLCPP_ERROR(
184  get_logger(),
185  "Please specify the correct type for shape %s. Supported types are 'polygon' and 'circle'",
186  shape_name.c_str());
187  return false;
188  }
189  }
190 
191  // if any shapes has non-global frame id, no shapes will be added and return false.
193  for (const auto & shape : shapes_) {
194  const std::string & frame_id = shape->getFrameID();
195  if (!frame_id.empty() && frame_id != global_frame_id_) {
196  RCLCPP_ERROR(
197  get_logger(),
198  "Shape '%s' has frame_id '%s' which differs from global_frame_id '%s'. "
199  "All shapes must have frame_id empty or equal to global_frame_id "
200  "when enforce_global_frame_id is true.",
201  shape->getUUID().c_str(), frame_id.c_str(), global_frame_id_.c_str());
202  shapes_.clear();
203  return false;
204  }
205  }
206  }
207 
208  return true;
209 }
210 
211 std::vector<std::shared_ptr<Shape>>::iterator
212 VectorObjectServer::findShape(const unsigned char * uuid)
213 {
214  for (auto it = shapes_.begin(); it != shapes_.end(); it++) {
215  if ((*it)->isUUID(uuid)) {
216  return it;
217  }
218  }
219  return shapes_.end();
220 }
221 
223 {
224  for (auto shape : shapes_) {
225  if (shape->getFrameID() != global_frame_id_ && !shape->getFrameID().empty()) {
226  // Shape to be updated dynamically
227  if (!shape->toFrame(global_frame_id_, tf_buffer_, transform_tolerance_)) {
228  RCLCPP_ERROR(
229  get_logger(), "Can not transform vector object from %s to %s frame",
230  shape->getFrameID().c_str(), global_frame_id_.c_str());
231  return false;
232  }
233  }
234  }
235 
236  return true;
237 }
238 
240  double & min_x, double & min_y, double & max_x, double & max_y) const
241 {
242  min_x = std::numeric_limits<double>::max();
243  min_y = std::numeric_limits<double>::max();
244  max_x = std::numeric_limits<double>::lowest();
245  max_y = std::numeric_limits<double>::lowest();
246 
247  double min_p_x, min_p_y, max_p_x, max_p_y;
248  for (auto shape : shapes_) {
249  shape->getBoundaries(min_p_x, min_p_y, max_p_x, max_p_y);
250  min_x = std::min(min_x, min_p_x);
251  min_y = std::min(min_y, min_p_y);
252  max_x = std::max(max_x, max_p_x);
253  max_y = std::max(max_y, max_p_y);
254  }
255 
256  if (
257  min_x == std::numeric_limits<double>::max() ||
258  min_y == std::numeric_limits<double>::max() ||
259  max_x == std::numeric_limits<double>::lowest() ||
260  max_y == std::numeric_limits<double>::lowest())
261  {
262  throw std::runtime_error("Can not obtain map boundaries");
263  }
264 }
265 
267  const double & min_x, const double & min_y, const double & max_x, const double & max_y)
268 {
269  // Calculate size of update map
270  int size_x = static_cast<int>((max_x - min_x) / resolution_) + 1;
271  int size_y = static_cast<int>((max_y - min_y) / resolution_) + 1;
272 
273  if (size_x < 0) {
274  throw std::runtime_error("Incorrect map x-size");
275  }
276 
277  if (size_y < 0) {
278  throw std::runtime_error("Incorrect map y-size");
279  }
280 
281  if (!map_) {
282  map_ = std::make_shared<nav_msgs::msg::OccupancyGrid>();
283  }
284 
285  if (
286  map_->info.width != static_cast<unsigned int>(size_x) ||
287  map_->info.height != static_cast<unsigned int>(size_y))
288  {
289  // Map size was changed
290  map_->data = std::vector<int8_t>(size_x * size_y, default_value_);
291  map_->info.width = size_x;
292  map_->info.height = size_y;
293  } else if (size_x > 0 && size_y > 0) {
294  // Map size was not changed
295  memset(map_->data.data(), default_value_, size_x * size_y * sizeof(int8_t));
296  }
297 
298  map_->header.frame_id = global_frame_id_;
299  map_->info.resolution = resolution_;
300  map_->info.origin.position.x = min_x;
301  map_->info.origin.position.y = min_y;
302 }
303 
305 {
306  // Filling the shapes
307  for (auto shape : shapes_) {
308  if (shape->isFill()) {
309  if (!shape->putFill(map_, overlay_type_)) {
310  RCLCPP_ERROR(
311  get_logger(),
312  "Error to get shape boundaries on map (UUID: %s)", shape->getUUID().c_str());
313  return;
314  }
315  } else {
316  // Put shape borders on map
317  shape->putBorders(map_, overlay_type_);
318  }
319  }
320 }
321 
323 {
324  if (map_) {
325  auto map = std::make_unique<nav_msgs::msg::OccupancyGrid>(*map_);
326  map_pub_->publish(std::move(map));
327  }
328 }
329 
331 {
332  if (!process_map_) {
333  return;
334  }
335 
336  try {
337  if (shapes_.size() > 0) {
338  if (!transformVectorObjects()) {
339  return;
340  }
341  double min_x, min_y, max_x, max_y;
342 
343  getMapBoundaries(min_x, min_y, max_x, max_y);
344  updateMap(min_x, min_y, max_x, max_y);
346  } else {
347  updateMap(0.0, 0.0, 0.0, 0.0);
348  }
349  } catch (const std::exception & ex) {
350  RCLCPP_ERROR(get_logger(), "Can not update map: %s", ex.what());
351  return;
352  }
353 
354  publishMap();
355 }
356 
358 {
359  for (auto shape : shapes_) {
360  if (shape->getFrameID() != global_frame_id_ && !shape->getFrameID().empty()) {
361  if (!map_timer_) {
362  map_timer_ = this->create_timer(
363  std::chrono::duration<double>(1.0 / update_frequency_),
364  std::bind(&VectorObjectServer::processMap, this));
365  }
366  RCLCPP_INFO(get_logger(), "Publishing map dynamically at %f Hz rate", update_frequency_);
367  return;
368  }
369  }
370 
371  if (map_timer_) {
372  map_timer_->cancel();
373  map_timer_.reset();
374  }
375  RCLCPP_INFO(get_logger(), "Publishing map once");
376  processMap();
377 }
378 
380  const std::shared_ptr<rmw_request_id_t>/*request_header*/,
381  const std::shared_ptr<nav2_msgs::srv::AddShapes::Request> request,
382  std::shared_ptr<nav2_msgs::srv::AddShapes::Response> response)
383 {
384  // Initialize result with true. If one of the required vector object was not added properly,
385  // set it to false.
386  response->success = true;
387 
389  // Lambda for checking frame_id consistency
390  auto check_frame_id = [this](
391  const auto & shapes, const std::string & shape_type_name)
392  {
393  for (const auto & shape : shapes) {
394  if (!shape.header.frame_id.empty() && shape.header.frame_id != global_frame_id_) {
395  RCLCPP_ERROR(
396  get_logger(),
397  "%s frame_id '%s' must be empty or equal to global_frame_id '%s' "
398  "when enforce_global_frame_id is true. Rejecting request.",
399  shape_type_name.c_str(), shape.header.frame_id.c_str(), global_frame_id_.c_str());
400  return false;
401  }
402  }
403  return true;
404  };
405 
406  if (!check_frame_id(request->polygons, "Polygon") ||
407  !check_frame_id(request->circles, "Circle"))
408  {
409  response->success = false;
410  return;
411  }
412  }
413 
414  auto node = shared_from_this();
415 
416  // Process polygons
417  for (auto req_poly : request->polygons) {
418  nav2_msgs::msg::PolygonObject::SharedPtr new_params =
419  std::make_shared<nav2_msgs::msg::PolygonObject>(req_poly);
420 
421  auto it = findShape(new_params->uuid.uuid.data());
422  if (it != shapes_.end()) {
423  // Vector Object with given UUID was found: updating it
424  // Check that found shape has correct type
425  if ((*it)->getType() != POLYGON) {
426  RCLCPP_ERROR(
427  get_logger(),
428  "Shape (UUID: %s) is not a polygon type for a polygon update. Not adding shape.",
429  (*it)->getUUID().c_str());
430  response->success = false;
431  // Do not add this shape
432  continue;
433  }
434 
435  std::shared_ptr<Polygon> polygon = std::static_pointer_cast<Polygon>(*it);
436 
437  // Preserving old parameters for the case, if new ones to be incorrect
438  nav2_msgs::msg::PolygonObject::SharedPtr old_params = polygon->getParams();
439  if (!polygon->setParams(new_params)) {
440  RCLCPP_ERROR(
441  get_logger(),
442  "Failed to update existing polygon object (UUID: %s) with new params. "
443  "Reverting to old polygon params.",
444  (*it)->getUUID().c_str());
445  // Restore old parameters
446  polygon->setParams(old_params);
447  // ... and set the failure to return
448  response->success = false;
449  }
450  } else {
451  // Vector Object with given UUID was not found: creating a new one
452  std::shared_ptr<Polygon> polygon = std::make_shared<Polygon>(node);
453  if (polygon->setParams(new_params)) {
454  shapes_.push_back(polygon);
455  } else {
456  RCLCPP_ERROR(
457  get_logger(), "Failed to create a new polygon object using the provided params.");
458  response->success = false;
459  }
460  }
461  }
462 
463  // Process circles
464  for (auto req_crcl : request->circles) {
465  nav2_msgs::msg::CircleObject::SharedPtr new_params =
466  std::make_shared<nav2_msgs::msg::CircleObject>(req_crcl);
467 
468  auto it = findShape(new_params->uuid.uuid.data());
469  if (it != shapes_.end()) {
470  // Vector object with given UUID was found: updating it
471  // Check that found shape has correct type
472  if ((*it)->getType() != CIRCLE) {
473  RCLCPP_ERROR(
474  get_logger(),
475  "Shape (UUID: %s) is not a circle type for a circle update. Not adding shape.",
476  (*it)->getUUID().c_str());
477  response->success = false;
478  // Do not add this shape
479  continue;
480  }
481 
482  std::shared_ptr<Circle> circle = std::static_pointer_cast<Circle>(*it);
483 
484  // Preserving old parameters for the case, if new ones to be incorrect
485  nav2_msgs::msg::CircleObject::SharedPtr old_params = circle->getParams();
486  if (!circle->setParams(new_params)) {
487  RCLCPP_ERROR(
488  get_logger(),
489  "Failed to update existing circle object (UUID: %s) with new params. "
490  "Reverting to old circle params.",
491  (*it)->getUUID().c_str());
492  // Restore old parameters
493  circle->setParams(old_params);
494  // ... and set the failure to return
495  response->success = false;
496  }
497  } else {
498  // Vector Object with given UUID was not found: creating a new one
499  std::shared_ptr<Circle> circle = std::make_shared<Circle>(node);
500  if (circle->setParams(new_params)) {
501  shapes_.push_back(circle);
502  } else {
503  RCLCPP_ERROR(
504  get_logger(), "Failed to create a new circle object using the provided params.");
505  response->success = false;
506  }
507  }
508  }
509 
510  switchMapUpdate();
511 }
512 
514  const std::shared_ptr<rmw_request_id_t>/*request_header*/,
515  const std::shared_ptr<nav2_msgs::srv::GetShapes::Request>/*request*/,
516  std::shared_ptr<nav2_msgs::srv::GetShapes::Response> response)
517 {
518  std::shared_ptr<Polygon> polygon;
519  std::shared_ptr<Circle> circle;
520 
521  for (auto shape : shapes_) {
522  switch (shape->getType()) {
523  case POLYGON:
524  polygon = std::static_pointer_cast<Polygon>(shape);
525  response->polygons.push_back(*(polygon->getParams()));
526  break;
527  case CIRCLE:
528  circle = std::static_pointer_cast<Circle>(shape);
529  response->circles.push_back(*(circle->getParams()));
530  break;
531  default:
532  RCLCPP_WARN(get_logger(), "Unknown shape type (UUID: %s)", shape->getUUID().c_str());
533  }
534  }
535 }
536 
538  const std::shared_ptr<rmw_request_id_t>/*request_header*/,
539  const std::shared_ptr<nav2_msgs::srv::RemoveShapes::Request> request,
540  std::shared_ptr<nav2_msgs::srv::RemoveShapes::Response> response)
541 {
542  // Initialize result with true. If one of the required vector object was not found,
543  // set it to false.
544  response->success = true;
545 
546  if (request->all_objects) {
547  // Clear all objects
548  shapes_.clear();
549  } else {
550  // Find objects to remove
551  for (auto req_uuid : request->uuids) {
552  auto it = findShape(req_uuid.uuid.data());
553  if (it != shapes_.end()) {
554  // Shape with given UUID was found: remove it
555  (*it).reset();
556  shapes_.erase(it);
557  } else {
558  // Required vector object was not found
559  RCLCPP_ERROR(
560  get_logger(),
561  "Can not find shape to remove with UUID: %s",
562  unparseUUID(req_uuid.uuid.data()).c_str());
563  response->success = false;
564  }
565  }
566  }
567 
568  switchMapUpdate();
569 }
570 
571 } // namespace nav2_map_server
572 
573 #include "rclcpp_components/register_node_macro.hpp"
574 
575 // Register the component with class_loader.
576 // This acts as a sort of entry point, allowing the component to be discoverable when its library
577 // is being loaded into a running process.
578 RCLCPP_COMPONENTS_REGISTER_NODE(nav2_map_server::VectorObjectServer)
void destroyBond()
Destroy bond connection to lifecycle manager.
nav2::LifecycleNode::SharedPtr shared_from_this()
Get a shared pointer of this.
rclcpp::GenericTimer< CallbackT >::SharedPtr create_timer(std::chrono::duration< DurationRepT, DurationT > period, CallbackT callback, rclcpp::CallbackGroup::SharedPtr group=nullptr)
Create a sim-time-aware timer for Nav2 lifecycle nodes.
void createBond()
Create bond connection to lifecycle manager.
A QoS profile for latched, reliable topics with a history of 1 messages.
void addShapesCallback(const std::shared_ptr< rmw_request_id_t > request_header, const std::shared_ptr< nav2_msgs::srv::AddShapes::Request > request, std::shared_ptr< nav2_msgs::srv::AddShapes::Response > response)
Callback for AddShapes service call. Reads all input vector objects from service request,...
void switchMapUpdate()
If map to be update dynamically, creates map processing timer, otherwise process map once.
void publishMap()
Publishes output map.
nav2::CallbackReturn on_cleanup(const rclcpp_lifecycle::State &state) override
: Resets all services, publishers, map and TF-s
double process_map_
Whether to process and publish map.
nav2::ServiceServer< nav2_msgs::srv::GetShapes >::SharedPtr get_shapes_service_
GetShapes service.
bool transformVectorObjects()
Transform all vector shapes from their local frame to output map frame.
void processMap()
Calculates new map sizes, updates map, processes all vector objects on it and publishes output map on...
std::vector< std::shared_ptr< Shape > > shapes_
All shapes vector.
double update_frequency_
Frequency to dynamically update/publish the map (if necessary)
int8_t default_value_
Default value the output map to be filled with.
void removeShapesCallback(const std::shared_ptr< rmw_request_id_t > request_header, const std::shared_ptr< nav2_msgs::srv::RemoveShapes::Request > request, std::shared_ptr< nav2_msgs::srv::RemoveShapes::Response > response)
Callback for RemoveShapes service call. Try to remove requested vector objects and switches map proce...
std::vector< std::shared_ptr< Shape > >::iterator findShape(const unsigned char *uuid)
Finds the shape with given UUID.
double resolution_
Output map resolution.
nav2::CallbackReturn on_activate(const rclcpp_lifecycle::State &state) override
: Activates output map publisher and creates bond connection
bool enforce_global_frame_id_
If true, disables TF listener and requires all incoming shapes to have frame_id empty or equal to glo...
nav2::Publisher< nav_msgs::msg::OccupancyGrid >::SharedPtr map_pub_
Output map publisher.
nav_msgs::msg::OccupancyGrid::SharedPtr map_
Output map with vector objects on it.
void putVectorObjectsOnMap()
Processes all vector objects on raster output map.
double transform_tolerance_
Transform tolerance.
std::string global_frame_id_
Frame of output map.
std::string map_topic_
@beirf Topic name where the output map to be published to
nav2::CallbackReturn on_shutdown(const rclcpp_lifecycle::State &state) override
Called in shutdown state.
rclcpp::TimerBase::SharedPtr map_timer_
Map update timer.
void getMapBoundaries(double &min_x, double &min_y, double &max_x, double &max_y) const
Obtains map boundaries to place all vector objects inside.
void getShapesCallback(const std::shared_ptr< rmw_request_id_t > request_header, const std::shared_ptr< nav2_msgs::srv::GetShapes::Request > request, std::shared_ptr< nav2_msgs::srv::GetShapes::Response > response)
Callback for GetShapes service call. Gets all shapes and returns them to the service response.
nav2::CallbackReturn on_deactivate(const rclcpp_lifecycle::State &state) override
: Deactivates map publisher and timer (if any), destroys bond connection
nav2::ServiceServer< nav2_msgs::srv::AddShapes >::SharedPtr add_shapes_service_
AddShapes service.
nav2::TransformListener::SharedPtr tf_listener_
TF listener.
nav2::TransformBuffer::SharedPtr tf_buffer_
TF buffer.
void updateMap(const double &min_x, const double &min_y, const double &max_x, const double &max_y)
Creates or updates existing map with required sizes and fills it with default value.
nav2::CallbackReturn on_configure(const rclcpp_lifecycle::State &state) override
: Initializes TF buffer/listener, obtains ROS-parameters, creates incoming services,...
OverlayType overlay_type_
@Overlay Type of overlay of vector objects on the map
bool obtainParams()
Supporting routine obtaining all ROS-parameters.
nav2::ServiceServer< nav2_msgs::srv::RemoveShapes >::SharedPtr remove_shapes_service_
RemoveShapes service.