Nav2 Navigation Stack - rolling  main
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::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  graph_to_id_map_.clear();
69  edge_to_node_map_.clear();
70  graph_to_incoming_edges_map_.clear();
71  graph_.clear();
72  QString filename = QFileDialog::getOpenFileName(
73  this,
74  tr("Open Address Book"), "",
75  tr("Address Book (*.geojson);;All Files (*)"));
76  graph_loader_->loadGraphFromFile(graph_, graph_to_id_map_, filename.toStdString());
77  unsigned int max_node_id = 0;
78  for (const auto & node : graph_) {
79  max_node_id = std::max(node.nodeid, max_node_id);
80  for (const auto & edge : node.neighbors) {
81  max_node_id = std::max(edge.edgeid, max_node_id);
82  edge_to_node_map_[edge.edgeid] = node.nodeid;
83  if (graph_to_incoming_edges_map_.find(edge.end->nodeid) !=
84  graph_to_incoming_edges_map_.end())
85  {
86  graph_to_incoming_edges_map_[edge.end->nodeid].push_back(edge.edgeid);
87  } else {
88  graph_to_incoming_edges_map_[edge.end->nodeid] = std::vector<unsigned int> {edge.edgeid};
89  }
90  }
91  }
92  next_node_id_ = max_node_id + 1;
93  update_route_graph();
94 }
95 
96 void RouteTool::on_save_button_clicked(void)
97 {
98  QString filename = QFileDialog::getSaveFileName(
99  this,
100  tr("Open Address Book"), "",
101  tr("Address Book (*.geojson);;All Files (*)"));
102  RCLCPP_INFO(node_->get_logger(), "Save graph to: %s", filename.toStdString().c_str());
103  graph_saver_->saveGraphToFile(graph_, filename.toStdString());
104 }
105 
106 void RouteTool::on_create_button_clicked(void)
107 {
108  if (ui_->add_field_1->toPlainText() == "" || ui_->add_field_2->toPlainText() == "") {return;}
109  if (ui_->add_node_button->isChecked()) {
110  auto longitude = ui_->add_field_1->toPlainText().toFloat();
111  auto latitude = ui_->add_field_2->toPlainText().toFloat();
112  nav2_route::Node new_node;
113  new_node.nodeid = next_node_id_;
114  new_node.coords.x = longitude;
115  new_node.coords.y = latitude;
116  graph_.push_back(new_node);
117  graph_to_id_map_[next_node_id_++] = graph_.size() - 1;
118  RCLCPP_INFO(node_->get_logger(), "Adding node at: (%f, %f)", longitude, latitude);
119  update_route_graph();
120  } else if (ui_->add_edge_button->isChecked()) {
121  auto start_node = ui_->add_field_1->toPlainText().toInt();
122  auto end_node = ui_->add_field_2->toPlainText().toInt();
123  nav2_route::EdgeCost edge_cost;
124  graph_[graph_to_id_map_[start_node]].addEdge(
125  edge_cost, &(graph_[graph_to_id_map_[end_node]]),
126  next_node_id_);
127  if (graph_to_incoming_edges_map_.find(end_node) != graph_to_incoming_edges_map_.end()) {
128  graph_to_incoming_edges_map_[end_node].push_back(next_node_id_);
129  } else {
130  graph_to_incoming_edges_map_[end_node] = std::vector<unsigned int> {next_node_id_};
131  }
132  edge_to_node_map_[next_node_id_++] = start_node;
133  RCLCPP_INFO(node_->get_logger(), "Adding edge from %d to %d", start_node, end_node);
134  update_route_graph();
135  }
136  ui_->add_field_1->setText("");
137  ui_->add_field_2->setText("");
138 }
139 
140 void RouteTool::on_confirm_button_clicked(void)
141 {
142  if (ui_->edit_id->toPlainText() == "" || ui_->edit_field_1->toPlainText() == "" ||
143  ui_->edit_field_2->toPlainText() == "") {return;}
144  if (ui_->edit_node_button->isChecked()) {
145  auto node_id = ui_->edit_id->toPlainText().toInt();
146  auto new_longitude = ui_->edit_field_1->toPlainText().toFloat();
147  auto new_latitude = ui_->edit_field_2->toPlainText().toFloat();
148  if (graph_to_id_map_.find(node_id) != graph_to_id_map_.end()) {
149  graph_[graph_to_id_map_[node_id]].coords.x = new_longitude;
150  graph_[graph_to_id_map_[node_id]].coords.y = new_latitude;
151  update_route_graph();
152  }
153  } else if (ui_->edit_edge_button->isChecked()) {
154  auto edge_id = (unsigned int) ui_->edit_id->toPlainText().toInt();
155  auto new_start = ui_->edit_field_1->toPlainText().toInt();
156  auto new_end = ui_->edit_field_2->toPlainText().toInt();
157  // Find and remove current edge
158  auto current_start_node = &graph_[graph_to_id_map_[edge_to_node_map_[edge_id]]];
159  for (auto itr = current_start_node->neighbors.begin();
160  itr != current_start_node->neighbors.end(); itr++)
161  {
162  if (itr->edgeid == edge_id) {
163  current_start_node->neighbors.erase(itr);
164  break;
165  }
166  }
167  // Create new edge with same ID using new start and stop nodes
168  nav2_route::EdgeCost edge_cost;
169  graph_[graph_to_id_map_[new_start]].addEdge(
170  edge_cost, &(graph_[graph_to_id_map_[new_end]]),
171  edge_id);
172  edge_to_node_map_[edge_id] = new_start;
173  if (graph_to_incoming_edges_map_.find(new_end) != graph_to_incoming_edges_map_.end()) {
174  graph_to_incoming_edges_map_[new_end].push_back(edge_id);
175  } else {
176  graph_to_incoming_edges_map_[new_end] = std::vector<unsigned int> {edge_id};
177  }
178  update_route_graph();
179  }
180  ui_->edit_id->setText("");
181  ui_->edit_field_1->setText("");
182  ui_->edit_field_2->setText("");
183 }
184 
185 void RouteTool::on_delete_button_clicked(void)
186 {
187  if (ui_->remove_id->toPlainText() == "") {return;}
188  if (ui_->remove_node_button->isChecked()) {
189  unsigned int node_id = ui_->remove_id->toPlainText().toInt();
190  // Remove edges pointing to the removed node
191  for (auto edge_id : graph_to_incoming_edges_map_[node_id]) {
192  auto start_node = &graph_[graph_to_id_map_[edge_to_node_map_[edge_id]]];
193  for (auto itr = start_node->neighbors.begin(); itr != start_node->neighbors.end(); itr++) {
194  if (itr->edgeid == edge_id) {
195  start_node->neighbors.erase(itr);
196  edge_to_node_map_.erase(edge_id);
197  break;
198  }
199  }
200  }
201  if (graph_[graph_to_id_map_[node_id]].nodeid == node_id) {
202  // Use max int to mark the node as deleted
203  graph_[graph_to_id_map_[node_id]].nodeid = std::numeric_limits<int>::max();
204  graph_to_id_map_.erase(node_id);
205  graph_to_incoming_edges_map_.erase(node_id);
206  RCLCPP_INFO(node_->get_logger(), "Removed node %d", node_id);
207  }
208  update_route_graph();
209  } else if (ui_->remove_edge_button->isChecked()) {
210  auto edge_id = (unsigned int) ui_->remove_id->toPlainText().toInt();
211  auto start_node = &graph_[graph_to_id_map_[edge_to_node_map_[edge_id]]];
212  for (auto itr = start_node->neighbors.begin(); itr != start_node->neighbors.end(); itr++) {
213  if (itr->edgeid == edge_id) {
214  RCLCPP_INFO(node_->get_logger(), "Removed edge %d", edge_id);
215  start_node->neighbors.erase(itr);
216  edge_to_node_map_.erase(edge_id);
217  break;
218  }
219  }
220  update_route_graph();
221  }
222  ui_->remove_id->setText("");
223 }
224 
225 void RouteTool::on_add_node_button_toggled(void)
226 {
227  if (ui_->add_node_button->isChecked()) {
228  ui_->add_text->setText("Position:");
229  ui_->add_label_1->setText("X:");
230  ui_->add_label_2->setText("Y:");
231  } else {
232  ui_->add_text->setText("Connections:");
233  ui_->add_label_1->setText("Start Node ID:");
234  ui_->add_label_2->setText("End Node ID:");
235  }
236 }
237 
238 void RouteTool::on_edit_node_button_toggled(void)
239 {
240  if (ui_->edit_node_button->isChecked()) {
241  ui_->edit_text->setText("Position:");
242  ui_->edit_label_1->setText("X:");
243  ui_->edit_label_2->setText("Y:");
244  } else {
245  ui_->edit_text->setText("Connections:");
246  ui_->edit_label_1->setText("Start Node ID:");
247  ui_->edit_label_2->setText("End Node ID:");
248  }
249 }
250 
251 void RouteTool::update_route_graph(void)
252 {
253  graph_vis_publisher_->publish(nav2_route::utils::toMsg(graph_, "map", node_->now()));
254 }
255 
256 void RouteTool::save(rviz_common::Config config) const
257 {
258  rviz_common::Panel::save(config);
259 }
260 
261 void RouteTool::load(const rviz_common::Config & config)
262 {
263  rviz_common::Panel::load(config);
264 }
265 } // namespace nav2_rviz_plugins
266 
267 #include <pluginlib/class_list_macros.hpp>
268 PLUGINLIB_EXPORT_CLASS(nav2_rviz_plugins::RouteTool, rviz_common::Panel)
std::unique_ptr< Ui::route_tool > ui_
Definition: route_tool.hpp:98
virtual void save(rviz_common::Config config) const
Definition: route_tool.cpp:256
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