Nav2 Navigation Stack - jazzy  jazzy
ROS 2 Navigation Stack
route_tool.cpp
1 // Copyright (c) 2024 John Chrosniak
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_rviz_plugins/route_tool.hpp"
16 #include <QDesktopServices>
17 #include <QUrl>
18 #include <sys/types.h>
19 #include <QFileDialog>
20 #include "rviz_common/display_context.hpp"
21 
22 
23 namespace nav2_rviz_plugins
24 {
25 RouteTool::RouteTool(QWidget * parent)
26 : rviz_common::Panel(parent),
27  ui_(std::make_unique<Ui::route_tool>())
28 {
29  // Extend the widget with all attributes and children from UI file
30  ui_->setupUi(this);
31  node_ = std::make_shared<nav2_util::LifecycleNode>("route_tool_node", "", rclcpp::NodeOptions());
32  node_->configure();
33  graph_vis_publisher_ = node_->create_publisher<visualization_msgs::msg::MarkerArray>(
34  "route_graph", rclcpp::QoS(rclcpp::KeepLast(1)).transient_local().reliable());
35  node_->activate();
36  tf_ = std::make_shared<tf2_ros::Buffer>(node_->get_clock());
37  graph_loader_ = std::make_shared<nav2_route::GraphLoader>(node_, tf_, "map");
38  graph_saver_ = std::make_shared<nav2_route::GraphSaver>(node_, tf_, "map");
39  ui_->add_node_button->setChecked(true);
40  ui_->edit_node_button->setChecked(true);
41  ui_->remove_node_button->setChecked(true);
42  // Needed to prevent memory addresses moving from resizing
43  // when adding nodes and edges
44  graph_.reserve(1000);
45 }
46 
47 void RouteTool::onInitialize(void)
48 {
49  auto ros_node_abstraction = getDisplayContext()->getRosNodeAbstraction().lock();
50  if (!ros_node_abstraction) {
51  RCLCPP_ERROR(
52  node_->get_logger(), "Unable to get ROS node abstraction");
53  return;
54  }
55  auto node = ros_node_abstraction->get_raw_node();
56 
57  clicked_point_subscription_ = node->create_subscription<geometry_msgs::msg::PointStamped>(
58  "clicked_point", 1, [this](const geometry_msgs::msg::PointStamped::SharedPtr msg) {
59  ui_->add_field_1->setText(std::to_string(msg->point.x).c_str());
60  ui_->add_field_2->setText(std::to_string(msg->point.y).c_str());
61  ui_->edit_field_1->setText(std::to_string(msg->point.x).c_str());
62  ui_->edit_field_2->setText(std::to_string(msg->point.y).c_str());
63  });
64 }
65 
66 void RouteTool::on_load_button_clicked(void)
67 {
68  QString filename = QFileDialog::getOpenFileName(
69  this,
70  tr("Open Address Book"), "",
71  tr("Address Book (*.geojson);;All Files (*)"));
72  if (filename.isEmpty()) {
73  RCLCPP_INFO(node_->get_logger(), "Load operation cancelled by user");
74  return;
75  }
76  graph_to_id_map_.clear();
77  edge_to_node_map_.clear();
78  graph_to_incoming_edges_map_.clear();
79  graph_.clear();
80  graph_loader_->loadGraphFromFile(graph_, graph_to_id_map_, filename.toStdString());
81  unsigned int max_node_id = 0;
82  for (const auto & node : graph_) {
83  max_node_id = std::max(node.nodeid, max_node_id);
84  for (const auto & edge : node.neighbors) {
85  max_node_id = std::max(edge.edgeid, max_node_id);
86  edge_to_node_map_[edge.edgeid] = node.nodeid;
87  if (graph_to_incoming_edges_map_.find(edge.end->nodeid) !=
88  graph_to_incoming_edges_map_.end())
89  {
90  graph_to_incoming_edges_map_[edge.end->nodeid].push_back(edge.edgeid);
91  } else {
92  graph_to_incoming_edges_map_[edge.end->nodeid] = std::vector<unsigned int> {edge.edgeid};
93  }
94  }
95  }
96  next_node_id_ = max_node_id + 1;
97  update_route_graph();
98 }
99 
100 void RouteTool::on_save_button_clicked(void)
101 {
102  QString filename = QFileDialog::getSaveFileName(
103  this,
104  tr("Open Address Book"), "",
105  tr("Address Book (*.geojson);;All Files (*)"));
106  RCLCPP_INFO(node_->get_logger(), "Save graph to: %s", filename.toStdString().c_str());
107  graph_saver_->saveGraphToFile(graph_, filename.toStdString());
108 }
109 
110 void RouteTool::on_create_button_clicked(void)
111 {
112  if (ui_->add_field_1->toPlainText() == "" || ui_->add_field_2->toPlainText() == "") {return;}
113  if (ui_->add_node_button->isChecked()) {
114  auto longitude = ui_->add_field_1->toPlainText().toFloat();
115  auto latitude = ui_->add_field_2->toPlainText().toFloat();
116  nav2_route::Node new_node;
117  new_node.nodeid = next_node_id_;
118  new_node.coords.x = longitude;
119  new_node.coords.y = latitude;
120  graph_.push_back(new_node);
121  graph_to_id_map_[next_node_id_++] = graph_.size() - 1;
122  RCLCPP_INFO(node_->get_logger(), "Adding node at: (%f, %f)", longitude, latitude);
123  update_route_graph();
124  } else if (ui_->add_edge_button->isChecked()) {
125  auto start_node = ui_->add_field_1->toPlainText().toInt();
126  auto end_node = ui_->add_field_2->toPlainText().toInt();
127  nav2_route::EdgeCost edge_cost;
128  graph_[graph_to_id_map_[start_node]].addEdge(
129  edge_cost, &(graph_[graph_to_id_map_[end_node]]),
130  next_node_id_);
131  if (graph_to_incoming_edges_map_.find(end_node) != graph_to_incoming_edges_map_.end()) {
132  graph_to_incoming_edges_map_[end_node].push_back(next_node_id_);
133  } else {
134  graph_to_incoming_edges_map_[end_node] = std::vector<unsigned int> {next_node_id_};
135  }
136  edge_to_node_map_[next_node_id_++] = start_node;
137  RCLCPP_INFO(node_->get_logger(), "Adding edge from %d to %d", start_node, end_node);
138  update_route_graph();
139  }
140  ui_->add_field_1->setText("");
141  ui_->add_field_2->setText("");
142 }
143 
144 void RouteTool::on_confirm_button_clicked(void)
145 {
146  if (ui_->edit_id->toPlainText() == "" || ui_->edit_field_1->toPlainText() == "" ||
147  ui_->edit_field_2->toPlainText() == "") {return;}
148  if (ui_->edit_node_button->isChecked()) {
149  auto node_id = ui_->edit_id->toPlainText().toInt();
150  auto new_longitude = ui_->edit_field_1->toPlainText().toFloat();
151  auto new_latitude = ui_->edit_field_2->toPlainText().toFloat();
152  if (graph_to_id_map_.find(node_id) != graph_to_id_map_.end()) {
153  graph_[graph_to_id_map_[node_id]].coords.x = new_longitude;
154  graph_[graph_to_id_map_[node_id]].coords.y = new_latitude;
155  update_route_graph();
156  }
157  } else if (ui_->edit_edge_button->isChecked()) {
158  auto edge_id = (unsigned int) ui_->edit_id->toPlainText().toInt();
159  auto new_start = ui_->edit_field_1->toPlainText().toInt();
160  auto new_end = ui_->edit_field_2->toPlainText().toInt();
161  // Find and remove current edge
162  auto current_start_node = &graph_[graph_to_id_map_[edge_to_node_map_[edge_id]]];
163  for (auto itr = current_start_node->neighbors.begin();
164  itr != current_start_node->neighbors.end(); itr++)
165  {
166  if (itr->edgeid == edge_id) {
167  current_start_node->neighbors.erase(itr);
168  break;
169  }
170  }
171  // Create new edge with same ID using new start and stop nodes
172  nav2_route::EdgeCost edge_cost;
173  graph_[graph_to_id_map_[new_start]].addEdge(
174  edge_cost, &(graph_[graph_to_id_map_[new_end]]),
175  edge_id);
176  edge_to_node_map_[edge_id] = new_start;
177  if (graph_to_incoming_edges_map_.find(new_end) != graph_to_incoming_edges_map_.end()) {
178  graph_to_incoming_edges_map_[new_end].push_back(edge_id);
179  } else {
180  graph_to_incoming_edges_map_[new_end] = std::vector<unsigned int> {edge_id};
181  }
182  update_route_graph();
183  }
184  ui_->edit_id->setText("");
185  ui_->edit_field_1->setText("");
186  ui_->edit_field_2->setText("");
187 }
188 
189 void RouteTool::on_delete_button_clicked(void)
190 {
191  if (ui_->remove_id->toPlainText() == "") {return;}
192  if (ui_->remove_node_button->isChecked()) {
193  unsigned int node_id = ui_->remove_id->toPlainText().toInt();
194  // Remove edges pointing to the removed node
195  for (auto edge_id : graph_to_incoming_edges_map_[node_id]) {
196  auto start_node = &graph_[graph_to_id_map_[edge_to_node_map_[edge_id]]];
197  for (auto itr = start_node->neighbors.begin(); itr != start_node->neighbors.end(); itr++) {
198  if (itr->edgeid == edge_id) {
199  start_node->neighbors.erase(itr);
200  edge_to_node_map_.erase(edge_id);
201  break;
202  }
203  }
204  }
205  if (graph_[graph_to_id_map_[node_id]].nodeid == node_id) {
206  // Use max int to mark the node as deleted
207  graph_[graph_to_id_map_[node_id]].nodeid = std::numeric_limits<int>::max();
208  graph_to_id_map_.erase(node_id);
209  graph_to_incoming_edges_map_.erase(node_id);
210  RCLCPP_INFO(node_->get_logger(), "Removed node %d", node_id);
211  }
212  update_route_graph();
213  } else if (ui_->remove_edge_button->isChecked()) {
214  auto edge_id = (unsigned int) ui_->remove_id->toPlainText().toInt();
215  auto start_node = &graph_[graph_to_id_map_[edge_to_node_map_[edge_id]]];
216  for (auto itr = start_node->neighbors.begin(); itr != start_node->neighbors.end(); itr++) {
217  if (itr->edgeid == edge_id) {
218  RCLCPP_INFO(node_->get_logger(), "Removed edge %d", edge_id);
219  start_node->neighbors.erase(itr);
220  edge_to_node_map_.erase(edge_id);
221  break;
222  }
223  }
224  update_route_graph();
225  }
226  ui_->remove_id->setText("");
227 }
228 
229 void RouteTool::on_add_node_button_toggled(void)
230 {
231  if (ui_->add_node_button->isChecked()) {
232  ui_->add_text->setText("Position:");
233  ui_->add_label_1->setText("X:");
234  ui_->add_label_2->setText("Y:");
235  } else {
236  ui_->add_text->setText("Connections:");
237  ui_->add_label_1->setText("Start Node ID:");
238  ui_->add_label_2->setText("End Node ID:");
239  }
240 }
241 
242 void RouteTool::on_edit_node_button_toggled(void)
243 {
244  if (ui_->edit_node_button->isChecked()) {
245  ui_->edit_text->setText("Position:");
246  ui_->edit_label_1->setText("X:");
247  ui_->edit_label_2->setText("Y:");
248  } else {
249  ui_->edit_text->setText("Connections:");
250  ui_->edit_label_1->setText("Start Node ID:");
251  ui_->edit_label_2->setText("End Node ID:");
252  }
253 }
254 
255 void RouteTool::update_route_graph(void)
256 {
257  graph_vis_publisher_->publish(nav2_route::utils::toMsg(graph_, "map", node_->now()));
258 }
259 
260 void RouteTool::save(rviz_common::Config config) const
261 {
262  rviz_common::Panel::save(config);
263 }
264 
265 void RouteTool::load(const rviz_common::Config & config)
266 {
267  rviz_common::Panel::load(config);
268 }
269 } // namespace nav2_rviz_plugins
270 
271 #include <pluginlib/class_list_macros.hpp>
272 PLUGINLIB_EXPORT_CLASS(nav2_rviz_plugins::RouteTool, rviz_common::Panel)
std::unique_ptr< Ui::route_tool > ui_
Definition: route_tool.hpp:99
virtual void save(rviz_common::Config config) const
Definition: route_tool.cpp:260
RouteTool(QWidget *parent=nullptr)
Definition: route_tool.cpp:25
An object to store edge cost or cost metadata for scoring.
Definition: types.hpp:88
An object to store the nodes in the graph file.
Definition: types.hpp:183