Nav2 Navigation Stack - rolling  main
ROS 2 Navigation Stack
geojson_graph_file_loader.cpp
1 // Copyright (c) 2025 Joshua Wallace
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 <memory>
16 #include <string>
17 #include <vector>
18 #include <fstream>
19 
20 #include "nav2_route/plugins/graph_file_loaders/geojson_graph_file_loader.hpp"
21 
22 namespace nav2_route
23 {
24 
26  const nav2::LifecycleNode::SharedPtr node)
27 {
28  RCLCPP_INFO(node->get_logger(), "Configuring geojson graph file loader");
29  logger_ = node->get_logger();
30 }
31 
33  Graph & graph, GraphToIDMap & graph_to_id_map, std::string filepath)
34 {
35  if (!doesFileExist(filepath)) {
36  RCLCPP_ERROR(logger_, "The filepath %s does not exist", filepath.c_str());
37  return false;
38  }
39 
40  std::ifstream graph_file(filepath);
41  Json json_graph;
42 
43  try {
44  json_graph = Json::parse(graph_file);
45  } catch (Json::parse_error & ex) {
46  RCLCPP_ERROR(logger_, "Failed to parse %s: %s", filepath.c_str(), ex.what());
47  return false;
48  }
49 
50  auto features = json_graph["features"];
51  std::vector<Json> nodes, edges;
52  getGraphElements(features, nodes, edges);
53 
54  if (nodes.empty() || edges.empty()) {
55  RCLCPP_ERROR(
56  logger_, "The graph is malformed. Is does not contain nodes or edges. Please check %s",
57  filepath.c_str());
58  return false;
59  }
60 
61  graph.resize(nodes.size());
62  addNodesToGraph(graph, graph_to_id_map, nodes);
63  addEdgesToGraph(graph, graph_to_id_map, edges);
64  return true;
65 }
66 
67 bool GeoJsonGraphFileLoader::doesFileExist(const std::string & filepath)
68 {
69  return std::filesystem::exists(filepath);
70 }
71 
73  const Json & features, std::vector<Json> & nodes, std::vector<Json> & edges)
74 {
75  for (const auto & feature : features) {
76  if (feature["geometry"]["type"] == "Point") {
77  nodes.emplace_back(feature);
78  } else if ( // NOLINT
79  feature["geometry"]["type"] == "MultiLineString" ||
80  feature["geometry"]["type"] == "LineString")
81  {
82  edges.emplace_back(feature);
83  }
84  }
85 }
86 
88  Graph & graph, GraphToIDMap & graph_to_id_map, std::vector<Json> & nodes)
89 {
90  int idx = 0;
91  for (const auto & node : nodes) {
92  const auto properties = node["properties"];
93  graph[idx].nodeid = properties["id"];
94  graph_to_id_map[graph[idx].nodeid] = idx;
95  graph[idx].coords = convertCoordinatesFromJson(node);
96  graph[idx].operations = convertOperationsFromJson(properties);
97  graph[idx].metadata = convertMetaDataFromJson(properties);
98  idx++;
99  }
100 }
101 
103  Graph & graph, GraphToIDMap & graph_to_id_map, std::vector<Json> & edges)
104 {
105  for (const auto & edge : edges) {
106  // Required data
107  const auto properties = edge["properties"];
108  unsigned int id = properties["id"];
109  unsigned int start_id = properties["startid"];
110  unsigned int end_id = properties["endid"];
111 
112  if (graph_to_id_map.find(start_id) == graph_to_id_map.end()) {
113  RCLCPP_ERROR(logger_, "Start id %u does not exist for edge id %u", start_id, id);
114  throw nav2_core::NoValidGraph("Start id does not exist");
115  }
116 
117  if (graph_to_id_map.find(end_id) == graph_to_id_map.end()) {
118  RCLCPP_ERROR(logger_, "End id of %u does not exist for edge id %u", end_id, id);
119  throw nav2_core::NoValidGraph("End id does not exist");
120  }
121 
122  EdgeCost edge_cost = convertEdgeCostFromJson(properties);
123  Operations operations = convertOperationsFromJson(properties);
124  Metadata metadata = convertMetaDataFromJson(properties);
125 
126  graph[graph_to_id_map[start_id]].addEdge(
127  edge_cost, &graph[graph_to_id_map[end_id]], id,
128  metadata, operations);
129  }
130 }
131 
133 {
134  Coordinates coords;
135  const auto & properties = node["properties"];
136  if (properties.contains("frame")) {
137  coords.frame_id = properties["frame"];
138  }
139 
140  const auto & coordinates = node["geometry"]["coordinates"];
141  coords.x = coordinates[0];
142  coords.y = coordinates[1];
143 
144  return coords;
145 }
146 
148  const Json & properties,
149  const std::string & key)
150 {
151  Metadata metadata;
152  if (!properties.contains(key)) {return metadata;}
153 
154  for (const auto & data : properties[key].items()) {
155  if (data.value().is_object() ) {
156  Metadata new_metadata = convertMetaDataFromJson(properties[key], data.key());
157  metadata.setValue(data.key(), new_metadata);
158  continue;
159  }
160 
161  const auto setPrimitiveType = [&](const auto & value) -> std::any
162  {
163  if (value.is_number()) {
164  if (value.is_number_unsigned()) {
165  return static_cast<unsigned int>(value);
166  } else if (value.is_number_integer()) {
167  return static_cast<int>(value);
168  } else {
169  return static_cast<float>(value);
170  }
171  }
172 
173  if (value.is_boolean()) {
174  return static_cast<bool>(value);
175  }
176 
177  if (value.is_string()) {
178  return static_cast<std::string>(value);
179  }
180  RCLCPP_ERROR(
181  logger_, "Failed to convert the key: %s to a value", data.key().c_str());
182  throw std::runtime_error("Failed to convert");
183  };
184 
185  if (data.value().is_array()) {
186  std::vector<std::any> array;
187  for (const auto & el : data.value()) {
188  auto value = setPrimitiveType(el);
189  array.push_back(value);
190  }
191  metadata.setValue(data.key(), array);
192  continue;
193  }
194 
195  auto value = setPrimitiveType(data.value());
196  metadata.setValue(data.key(), value);
197  }
198 
199  return metadata;
200 }
201 
203 {
204  Operation operation;
205  json_operation.at("type").get_to(operation.type);
206  Json trigger = json_operation.at("trigger");
207  operation.trigger = trigger.get<OperationTrigger>();
208  Metadata metadata = convertMetaDataFromJson(json_operation);
209  operation.metadata = metadata;
210 
211  return operation;
212 }
213 
214 Operations GeoJsonGraphFileLoader::convertOperationsFromJson(const Json & properties)
215 {
216  Operations operations;
217  if (properties.contains("operations")) {
218  for (const auto & json_operation : properties["operations"]) {
219  operations.push_back(convertOperationFromJson(json_operation));
220  }
221  }
222  return operations;
223 }
224 
226 {
227  EdgeCost edge_cost;
228  if (properties.contains("cost")) {
229  edge_cost.cost = properties["cost"];
230  }
231 
232  if (properties.contains("overridable")) {
233  edge_cost.overridable = properties["overridable"];
234  }
235  return edge_cost;
236 }
237 
238 } // namespace nav2_route
239 
240 #include "pluginlib/class_list_macros.hpp"
A GraphFileLoader plugin to load a geojson graph representation.
Coordinates convertCoordinatesFromJson(const Json &node)
Converts the coordinates from the json object into the Coordinates type.
bool doesFileExist(const std::string &filepath)
Checks if a file even exists on the filesystem.
Operation convertOperationFromJson(const Json &json_operation)
Converts the operation from the json object into the operation type.
Operations convertOperationsFromJson(const Json &properties)
Converts the operations data from the json object into the operations type if present.
void addNodesToGraph(Graph &graph, GraphToIDMap &graph_to_id_map, std::vector< Json > &nodes)
Add nodes into the graph.
Metadata convertMetaDataFromJson(const Json &properties, const std::string &key="metadata")
Converts the metadata from the json object into the metadata type.
void addEdgesToGraph(Graph &graph, GraphToIDMap &graph_to_id_map, std::vector< Json > &edges)
Add edges into the graph.
void configure(const nav2::LifecycleNode::SharedPtr node) override
Configure, but do not store the node.
EdgeCost convertEdgeCostFromJson(const Json &properties)
Converts the edge cost data from the json object into the edge cost type.
bool loadGraphFromFile(Graph &graph, GraphToIDMap &graph_to_id_map, std::string filepath) override
Loads the geojson file into the graph.
void getGraphElements(const Json &features, std::vector< Json > &nodes, std::vector< Json > &edges)
Get the nodes and edges from features.
A plugin interface to parse a file into the graph.
An object to store Node coordinates in different frames.
Definition: types.hpp:173
An object to store edge cost or cost metadata for scoring.
Definition: types.hpp:88
An object to store arbitrary metadata regarding nodes from the graph file.
Definition: types.hpp:35
An object to store operations to perform on events with types and metadata.
Definition: types.hpp:109