lanelet2_route_planning v2.0.0
Loading...
Searching...
No Matches
lanelet2_route_planning.cpp
Go to the documentation of this file.
1// Copyright Institute for Automotive Engineering (ika), RWTH Aachen University
2// SPDX-License-Identifier: Apache-2.0
3
4#include <algorithm>
5#include <functional>
6#include <limits>
7#include <thread>
8#include <tuple>
9#include <utility>
10
11#include <lanelet2_routing/RoutingGraph.h>
12#include <omp.h>
13#include <geometry_msgs/msg/point_stamped.hpp>
14#include <perception_msgs/msg/ego_data.hpp>
15#include <perception_msgs_utils/object_access.hpp>
16#include <route_planning_msgs_utils/route_access.hpp>
17#include <tf2_geometry_msgs/tf2_geometry_msgs.hpp>
18#include <tf2_perception_msgs/tf2_perception_msgs.hpp>
19
24
26
27Lanelet2RoutePlanning::Lanelet2RoutePlanning() : Node("lanelet2_route_planning"), diagnostic_updater_(this) {
28 this->declareAndLoadParameter("ll2_map_server_name", ll2_map_server_name_, "Name of lanelet2_map_server node", false, false,
29 true);
30 this->declareAndLoadParameter("publish_frequency", publish_frequency_, "Frequency of route publication [Hz]", true, false,
31 false, 0.1, 20.0);
32 this->declareAndLoadParameter("publish_frequency_global", publish_frequency_global_,
33 "Frequency of global route publication [Hz]", true, false, false, 0.1, 20.0);
34 this->declareAndLoadParameter("action_feedback_frequency", action_feedback_frequency_,
35 "Frequency of action feedback publication [Hz]", true, false, false, 0.1, 20.0);
36 this->declareAndLoadParameter("sampling_distance", sampling_distance_, "Distance between resampled points along route [m]",
37 true, false, false, 0.1, 3.0);
38 this->declareAndLoadParameter("sampling_max_lateral_error_global", sampling_max_lateral_error_global_,
39 "Maximum lateral error of the adaptively sampled global reference line [m]", true, false, false,
40 0.0, 100.0);
41 this->declareAndLoadParameter("project_destination_to_reference_line", project_destination_to_reference_line_,
42 "Whether to project destination to reference line", true, false, false);
43 this->declareAndLoadParameter("destination_distance_threshold", destination_distance_threshold_,
44 "Distance to destination where destination is considered reached [m]", true, false, false, 0.1,
45 200.0);
47 "required_traveled_distance_proportion", required_traveled_distance_proportion_,
48 "Proportion of route length that must have been traveled before considering destination reached [0..1]", true, false, false,
49 0.0, 1.0);
51 "enrich_route_ahead_ego_distance", enrich_route_ahead_ego_distance_,
52 "Distance ahead of ego position where global route is enriched with more information [m] (negative=unlimited)", true, false,
53 false, -1.0, 1000.0);
55 "enrich_route_behind_ego_distance", enrich_route_behind_ego_distance_,
56 "Distance behind ego position where global route is enriched with more information [m] (negative=unlimited)", true, false,
57 false, -1.0, 1000.0);
58 this->declareAndLoadParameter("route_undershoot_distance", route_undershoot_distance_,
59 "Undershoot route by this distance before ego position [m]", true, false, false, 0.0, 50.0);
60 this->declareAndLoadParameter("route_overshoot_distance", route_overshoot_distance_,
61 "Overshoot route by this distance behind destination [m]", true, false, false, 0.0, 100.0);
62 this->declareAndLoadParameter("max_drivable_space_radius", max_drivable_space_radius_,
63 "Maximum distance to left/right drivable space bounds, if not otherwise restricted [m]", true,
64 false, false, 3.0, 100.0);
65 this->declareAndLoadParameter("max_num_threads", max_num_threads_,
66 "Maximum number of threads for parallel processing (0=max available)", true, false, false, 0,
67 omp_get_max_threads(), 1);
68 this->declareAndLoadParameter("transform_timeout", transform_timeout_, "How long to wait for a transform to be available [s]",
69 true, false, false, 0.0, 1.0);
71 enrich_route_ahead_ego_distance_ = std::numeric_limits<double>::infinity();
72 }
74 enrich_route_behind_ego_distance_ = std::numeric_limits<double>::infinity();
75 }
76 if (max_num_threads_ <= 0) {
77 max_num_threads_ = omp_get_max_threads();
78 }
79 omp_set_num_threads(max_num_threads_);
80
81 this->declareAndLoadParameter("diagnostic_updater.ego_data_diagnostic.min_frequency", ego_data_diagnostic_config_.min_frequency,
82 "Minimum frequency for incoming ego data topic", false, false, false);
83 this->declareAndLoadParameter("diagnostic_updater.ego_data_diagnostic.max_frequency", ego_data_diagnostic_config_.max_frequency,
84 "Maximum frequency for incoming ego data topic", false, false, false);
85 this->declareAndLoadParameter("diagnostic_updater.ego_data_diagnostic.min_acceptable_timestamp_delta",
87 "Minimum acceptable timestamp delta for incoming ego data topic", false, false, false);
88 this->declareAndLoadParameter("diagnostic_updater.ego_data_diagnostic.max_acceptable_timestamp_delta",
90 "Maximum acceptable timestamp delta for incoming ego data topic", false, false, false);
91 this->declareAndLoadParameter("diagnostic_updater.route_timer_diagnostic.min_frequency",
92 route_timer_diagnostic_config_.min_frequency, "Minimum frequency for route timer", false, false,
93 false);
94 this->declareAndLoadParameter("diagnostic_updater.route_timer_diagnostic.max_frequency",
95 route_timer_diagnostic_config_.max_frequency, "Maximum frequency for route timer", false, false,
96 false);
97 this->declareAndLoadParameter("diagnostic_updater.global_route_timer_diagnostic.min_frequency",
98 global_route_timer_diagnostic_config_.min_frequency, "Minimum frequency for global route timer",
99 false, false, false);
100 this->declareAndLoadParameter("diagnostic_updater.global_route_timer_diagnostic.max_frequency",
101 global_route_timer_diagnostic_config_.max_frequency, "Maximum frequency for global route timer",
102 false, false, false);
103
104 this->setup();
105}
106
107template <typename T>
109 T& param,
110 const std::string& description,
111 const bool add_to_auto_reconfigurable_params,
112 const bool is_required,
113 const bool read_only,
114 const std::optional<double>& from_value,
115 const std::optional<double>& to_value,
116 const std::optional<double>& step_value,
117 const std::string& additional_constraints) {
118 rcl_interfaces::msg::ParameterDescriptor param_desc;
119 param_desc.description = description;
120 param_desc.additional_constraints = additional_constraints;
121 param_desc.read_only = read_only;
122
123 auto type = rclcpp::ParameterValue(param).get_type();
124
125 if (from_value.has_value() && to_value.has_value()) {
126 if constexpr (std::is_integral_v<T>) {
127 rcl_interfaces::msg::IntegerRange range;
128 range.set__from_value(static_cast<T>(from_value.value())).set__to_value(static_cast<T>(to_value.value()));
129 if (step_value.has_value()) range.set__step(static_cast<T>(step_value.value()));
130 param_desc.integer_range = {range};
131 } else if constexpr (std::is_floating_point_v<T>) {
132 rcl_interfaces::msg::FloatingPointRange range;
133 range.set__from_value(static_cast<T>(from_value.value())).set__to_value(static_cast<T>(to_value.value()));
134 if (step_value.has_value()) range.set__step(static_cast<T>(step_value.value()));
135 param_desc.floating_point_range = {range};
136 } else {
137 RCLCPP_WARN(this->get_logger(), "Parameter type of parameter '%s' does not support specifying a range", name.c_str());
138 }
139 }
140
141 this->declare_parameter(name, type, param_desc);
142
143 try {
144 param = this->get_parameter(name).get_value<T>();
145 std::stringstream ss;
146 ss << "Loaded parameter '" << name << "': ";
147 if constexpr (is_vector_v<T>) {
148 ss << "[";
149 for (const auto& element : param) ss << element << (&element != &param.back() ? ", " : "");
150 ss << "]";
151 } else {
152 ss << param;
153 }
154 RCLCPP_INFO_STREAM(this->get_logger(), ss.str());
155 } catch (rclcpp::exceptions::ParameterUninitializedException&) {
156 if (is_required) {
157 RCLCPP_FATAL_STREAM(this->get_logger(), "Missing required parameter '" << name << "', exiting");
158 exit(EXIT_FAILURE);
159 } else {
160 std::stringstream ss;
161 ss << "Missing parameter '" << name << "', using default value: ";
162 if constexpr (is_vector_v<T>) {
163 ss << "[";
164 for (const auto& element : param) ss << element << (&element != &param.back() ? ", " : "");
165 ss << "]";
166 } else {
167 ss << param;
168 }
169 RCLCPP_WARN_STREAM(this->get_logger(), ss.str());
170 this->set_parameters({rclcpp::Parameter(name, rclcpp::ParameterValue(param))});
171 }
172 }
173
174 if (add_to_auto_reconfigurable_params) {
175 std::function<void(const rclcpp::Parameter&)> setter = [&param](const rclcpp::Parameter& p) { param = p.get_value<T>(); };
176 auto_reconfigurable_params_.push_back(std::make_tuple(name, setter));
177 }
178}
179
180rcl_interfaces::msg::SetParametersResult Lanelet2RoutePlanning::parametersCallback(
181 const std::vector<rclcpp::Parameter>& parameters) {
182 for (const auto& param : parameters) {
183 for (auto& auto_reconfigurable_param : auto_reconfigurable_params_) {
184 if (param.get_name() == std::get<0>(auto_reconfigurable_param)) {
185 std::get<1>(auto_reconfigurable_param)(param);
186 RCLCPP_INFO(this->get_logger(), "Reconfigured parameter '%s'", param.get_name().c_str());
187 break;
188 }
189 }
190 }
191
192 // parameter-specific reconfigurations
193 for (const auto& param : parameters) {
194 if (param.get_name() == "publish_frequency") {
195 publish_timer_->cancel();
196 publish_timer_ = this->create_wall_timer(std::chrono::duration<double>(1.0 / publish_frequency_),
198 } else if (param.get_name() == "publish_frequency_global") {
201 this->create_wall_timer(std::chrono::duration<double>(1.0 / publish_frequency_global_),
203 }
204 }
206 enrich_route_ahead_ego_distance_ = std::numeric_limits<double>::infinity();
207 }
209 enrich_route_behind_ego_distance_ = std::numeric_limits<double>::infinity();
210 }
211 if (max_num_threads_ <= 0) {
212 max_num_threads_ = omp_get_max_threads();
213 }
214 omp_set_num_threads(max_num_threads_);
215
216 rcl_interfaces::msg::SetParametersResult result;
217 result.successful = true;
218
219 return result;
220}
221
222bool Lanelet2RoutePlanning::checkMap(bool handle_update) {
223 bool map_status = ll2_interface_->map_loaded_;
224 // update routing graph on map update
225 if (handle_update && ll2_interface_->update_pending_ && ll2_interface_->map_loaded_) {
226 if (this->buildRoutingGraph()) {
227 ll2_interface_->update_pending_ = false;
228 }
229 map_status = map_status && !ll2_interface_->update_pending_;
230 }
231 {
232 std::scoped_lock lock(health_kv_mutex_);
233 health_kv_["map_loaded"] = map_status ? "true" : "false";
234 }
235 return map_status;
236}
237
239 // map interface
240 ll2_interface_ = std::make_unique<Lanelet2MapInterface>(*this, ll2_map_server_name_);
241
242 // callback for dynamic parameter configuration
244 this->add_on_set_parameters_callback(std::bind(&Lanelet2RoutePlanning::parametersCallback, this, std::placeholders::_1));
245
246 // tf transform listener
247 tf_buffer_ = std::make_unique<tf2_ros::Buffer>(this->get_clock());
248 tf_listener_ = std::make_shared<tf2_ros::TransformListener>(*tf_buffer_);
249
250 // publishers
251 publisher_route_ = this->create_publisher<route_planning_msgs::msg::Route>("~/route", 1);
252 publisher_global_route_ = this->create_publisher<route_planning_msgs::msg::Route>("~/global_route", 1);
253 publish_timer_ = this->create_wall_timer(std::chrono::duration<double>(1.0 / publish_frequency_),
255 global_route_publish_timer_ = this->create_wall_timer(std::chrono::duration<double>(1.0 / publish_frequency_global_),
257 is_publishing_route_ = false;
258 RCLCPP_INFO(this->get_logger(), "Publishing enriched-only route to '%s'", publisher_route_->get_topic_name());
259 RCLCPP_INFO(this->get_logger(), "Publishing non-enriched global route to '%s'", publisher_global_route_->get_topic_name());
260
261 // subscribers
262 subscriber_ego_data_ = this->create_subscription<perception_msgs::msg::EgoData>(
263 "~/ego_data", 1, std::bind(&Lanelet2RoutePlanning::egoDataCallback, this, std::placeholders::_1));
264 RCLCPP_INFO(this->get_logger(), "Subscribed to '%s'", subscriber_ego_data_->get_topic_name());
265
266 // action server for handling action goal requests
267 action_callback_group_ = this->create_callback_group(rclcpp::CallbackGroupType::MutuallyExclusive);
268 action_server_ = rclcpp_action::create_server<route_planning_msgs::action::PlanRoute>(
269 this, "~/plan_route",
270 std::bind(&Lanelet2RoutePlanning::actionHandleGoal, this, std::placeholders::_1, std::placeholders::_2),
271 std::bind(&Lanelet2RoutePlanning::actionHandleCancel, this, std::placeholders::_1),
272 std::bind(&Lanelet2RoutePlanning::actionHandleAccepted, this, std::placeholders::_1),
273 rcl_action_server_get_default_options(), action_callback_group_);
274 RCLCPP_INFO(this->get_logger(), "Action server started");
275
276 // set up health diagnostic publisher and diagnostic updater for monitoring topic frequencies and timestamps
277 health_diagnostic_pub_ = this->create_publisher<diagnostic_msgs::msg::DiagnosticArray>("/diagnostics", 1);
278 diagnostic_updater_.setHardwareID("none");
279 ego_data_diagnostic_ = std::make_unique<diagnostic_updater::TopicDiagnostic>(
280 "~/ego_data", diagnostic_updater_,
281 diagnostic_updater::FrequencyStatusParam(&ego_data_diagnostic_config_.min_frequency,
283 diagnostic_updater::TimeStampStatusParam(ego_data_diagnostic_config_.min_acceptable_timestamp_delta,
285 route_timer_diagnostic_ = std::make_unique<diagnostic_updater::HeaderlessTopicDiagnostic>(
286 "route_timer", diagnostic_updater_,
287 diagnostic_updater::FrequencyStatusParam(&route_timer_diagnostic_config_.min_frequency,
289 global_route_timer_diagnostic_ = std::make_unique<diagnostic_updater::HeaderlessTopicDiagnostic>(
290 "global_route_timer", diagnostic_updater_,
291 diagnostic_updater::FrequencyStatusParam(&global_route_timer_diagnostic_config_.min_frequency,
293}
294
296 if (!this->checkMap(false)) {
297 RCLCPP_ERROR(this->get_logger(), "Cannot build routing graph, map not loaded by '%s'", ll2_map_server_name_.c_str());
298 return false;
299 }
300
301 // get map and traffic rules
302 lanelet::LaneletMapConstPtr map = ll2_interface_->getMapPtr();
303 lanelet::traffic_rules::TrafficRulesPtr traffic_rules = getTrafficRules();
304
305 // build routing graph
306 routing_graph_ = lanelet::routing::RoutingGraph::build(*map, *traffic_rules);
307 lanelet::routing::Route::Errors errors = routing_graph_->checkValidity();
308 if (!errors.empty()) {
309 RCLCPP_ERROR(this->get_logger(), "Failed to build valid routing graph");
310 for (size_t i = 0; i < errors.size(); ++i) {
311 RCLCPP_ERROR_STREAM(this->get_logger(), errors[i]);
312 }
313 return false;
314 }
315
316 RCLCPP_INFO(this->get_logger(), "Successfully built routing graph");
317 return true;
318}
319
320void Lanelet2RoutePlanning::egoDataCallback(const perception_msgs::msg::EgoData::SharedPtr msg) {
321 ego_data_diagnostic_->tick(msg->header.stamp);
322
323 if (!this->checkMap(false)) {
324 return;
325 }
326
327 // transform ego data to map frame
328 if (msg->header.frame_id != ll2_interface_->map_frame_id_) {
329 try {
330 latest_ego_data_ = tf_buffer_->transform(*msg, ll2_interface_->map_frame_id_, tf2::durationFromSec(transform_timeout_));
331 } catch (tf2::TransformException& ex) {
332 std::stringstream ss;
333 ss << "Could not transform ego data from frame '" << msg->header.frame_id << "' to frame '" << ll2_interface_->map_frame_id_
334 << "': " << ex.what();
335 RCLCPP_ERROR_STREAM(this->get_logger(), ss.str());
336 {
337 std::scoped_lock lock(health_kv_mutex_);
338 health_kv_["latest_error"] = ss.str();
339 }
340 }
341 } else {
342 latest_ego_data_ = *msg;
343 }
344
345 // recompute local route
347 auto t0 = std::chrono::steady_clock::now();
349 auto t1 = std::chrono::steady_clock::now();
350 auto dt = std::chrono::duration_cast<std::chrono::duration<double>>(t1 - t0).count();
351 RCLCPP_DEBUG(this->get_logger(), "Recomputed route (%.3fs)", dt);
352 {
353 std::scoped_lock lock(health_kv_mutex_);
354 health_kv_["dt_buildEnrichedRouteMessage"] = std::to_string(dt);
355 }
356 }
357}
358
363 }
364
365 // check health
366 unsigned char health_status = diagnostic_msgs::msg::DiagnosticStatus::OK;
367 std::string health_msg = "OK";
368 std::map<std::string, std::string> health_kv_snapshot;
369 {
370 std::scoped_lock lock(health_kv_mutex_);
371 health_kv_snapshot = health_kv_;
372 }
373 if (health_kv_snapshot.count("map_loaded") > 0 && health_kv_snapshot["map_loaded"] == "false") {
374 // map not loaded
375 health_status = diagnostic_msgs::msg::DiagnosticStatus::ERROR;
376 health_msg = "Map not loaded";
377 } else if (health_kv_snapshot.count("latest_error") > 0) {
378 // at least one error
379 health_status = diagnostic_msgs::msg::DiagnosticStatus::ERROR;
380 health_msg = health_kv_snapshot["latest_error"];
381 } else if (health_kv_snapshot.count("latest_warning") > 0) {
382 // at least one warning
383 health_status = diagnostic_msgs::msg::DiagnosticStatus::WARN;
384 health_msg = health_kv_snapshot["latest_warning"];
385 } else if (health_kv_snapshot.count("dt_buildEnrichedRouteMessage") > 0) {
386 // enrichment cannot keep up with ego data
387 double dt = std::stod(health_kv_snapshot["dt_buildEnrichedRouteMessage"]);
388 double callback_freq = ego_data_diagnostic_config_.max_frequency;
389 if (callback_freq > 0.0 && dt > 1.0 / callback_freq) {
390 health_status = diagnostic_msgs::msg::DiagnosticStatus::WARN;
391 health_msg = "Building enriched route takes longer than ego data frequency allows";
392 }
393 }
394
395 // publish health diagnostics
396 this->publishHealth(health_status, health_msg, this->now());
397
398 // reset latest warning and error
399 {
400 std::scoped_lock lock(health_kv_mutex_);
401 health_kv_.erase("latest_warning");
402 health_kv_.erase("latest_error");
403 }
404}
405
408 if (!is_publishing_route_ || latest_global_route_msg_.route_elements.empty()) {
409 return;
410 }
411 std::vector<Eigen::Vector2d> reference_line;
412 reference_line.reserve(latest_global_route_msg_.route_elements.size());
413 for (const auto& route_element : latest_global_route_msg_.route_elements) {
414 if (route_element.lane_elements.empty()) {
415 return;
416 }
417 reference_line.push_back(toEigen2d(route_element.lane_elements.front().reference_pose.position));
418 }
419 latest_global_route_msg_.current_route_element_idx =
420 matchPointToLineString(reference_line, toEigen2d(egoPosition(latest_ego_data_)), 0, true, true);
421 latest_global_route_msg_.header.stamp = latest_ego_data_.header.stamp;
423}
424
425rclcpp_action::GoalResponse Lanelet2RoutePlanning::actionHandleGoal(
426 const rclcpp_action::GoalUUID& uuid, route_planning_msgs::action::PlanRoute::Goal::ConstSharedPtr goal) {
427 (void)uuid;
428 (void)goal;
429
430 const geometry_msgs::msg::PointStamped& destination = goal->destination;
431 const std::vector<geometry_msgs::msg::PointStamped>& intermediate_destinations = goal->intermediate_destinations;
432 RCLCPP_INFO(this->get_logger(), "Received request to plan route to destination (%.3f, %.3f, %.3f) in frame '%s'",
433 destination.point.x, destination.point.y, destination.point.z, destination.header.frame_id.c_str());
434
435 // check for and handle map updates
436 if (!this->checkMap(true)) {
437 RCLCPP_ERROR(this->get_logger(), "Cannot plan route, map not loaded by '%s'", ll2_map_server_name_.c_str());
438 return rclcpp_action::GoalResponse::REJECT;
439 }
440
441 // plan route
442 auto t0 = std::chrono::steady_clock::now();
443 bool success = this->planRoute(destination, intermediate_destinations);
444 auto t1 = std::chrono::steady_clock::now();
445 auto dt = std::chrono::duration_cast<std::chrono::duration<double>>(t1 - t0).count();
446 if (!success) {
447 RCLCPP_ERROR(this->get_logger(), "Failed to plan route to destination, rejecting request");
448 return rclcpp_action::GoalResponse::REJECT;
449 }
450
451 // convert route to ROS message
453 RCLCPP_INFO(this->get_logger(), "Successfully planned route to destination (%.3fs)", dt);
454
455 // abort current action if running
456 if (action_goal_handle_ && action_goal_handle_->is_active()) {
457 RCLCPP_WARN(this->get_logger(), "Existing action detected, aborting before accepting new goal");
458 is_publishing_route_ = false; // stop publishing route
460 action_goal_handle_.reset();
461 }
462
463 // accept action goal request
464 return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE;
465}
466
467rclcpp_action::CancelResponse Lanelet2RoutePlanning::actionHandleCancel(
468 const std::shared_ptr<rclcpp_action::ServerGoalHandle<route_planning_msgs::action::PlanRoute>> goal_handle) {
469 (void)goal_handle;
470
471 RCLCPP_INFO(this->get_logger(), "Received request to cancel action goal");
472 is_publishing_route_ = false; // stop publishing route
473
474 return rclcpp_action::CancelResponse::ACCEPT;
475}
476
483 const std::shared_ptr<rclcpp_action::ServerGoalHandle<route_planning_msgs::action::PlanRoute>> goal_handle) {
484 action_goal_handle_ = goal_handle;
485
486 {
487 std::scoped_lock lock(health_kv_mutex_);
488 health_kv_["action_status"] = "accepted";
489 }
490
491 // initialize feedback and result
492 action_start_time_ = this->now();
493 action_feedback_ = std::make_shared<route_planning_msgs::action::PlanRoute::Feedback>();
494 action_feedback_->distance_traveled = 0.0;
495 action_feedback_->distance_remaining = 0.0;
497 action_feedback_->time_traveled = rclcpp::Duration::from_seconds(0.0);
498 action_feedback_->time_remaining =
499 rclcpp::Duration::from_seconds(route_planning_msgs::route_access::estimateRemainingTime(latest_full_route_msg_));
500 action_result_ = std::make_shared<route_planning_msgs::action::PlanRoute::Result>();
501 action_result_->distance_traveled = 0.0;
502 action_result_->time_traveled = rclcpp::Duration::from_seconds(0.0);
503 action_result_->destination_reached = false;
504
505 // start publishing route
507
508 // execute action in a separate thread to avoid blocking
509 std::thread{std::bind(&Lanelet2RoutePlanning::actionExecute, this, std::placeholders::_1), goal_handle}.detach();
510}
511
513 const std::shared_ptr<rclcpp_action::ServerGoalHandle<route_planning_msgs::action::PlanRoute>> goal_handle) {
514 RCLCPP_INFO(this->get_logger(), "Executing action goal");
515
516 {
517 std::scoped_lock lock(health_kv_mutex_);
518 health_kv_["action_status"] = "executing";
519 }
520
521 rclcpp::Rate feedback_rate(action_feedback_frequency_);
522 bool has_reached_destination = false;
523 while (goal_handle->is_executing() && !goal_handle->is_canceling() && !has_reached_destination) {
524 // update feedback and result
527 action_feedback_->time_traveled = this->now() - action_start_time_;
528 action_feedback_->time_remaining =
529 rclcpp::Duration::from_seconds(route_planning_msgs::route_access::estimateRemainingTime(latest_full_route_msg_));
530 action_result_->distance_traveled = action_feedback_->distance_traveled;
531 action_result_->time_traveled = action_feedback_->time_traveled;
532
533 // check if destination reached (criteria: close to destination and some distance traveled)
534 double distance_to_destination = (toEigen2d(egoPosition(latest_ego_data_)) - toEigen2d(destination_)).norm();
535 double total_route_distance = action_feedback_->distance_traveled + action_feedback_->distance_remaining;
536 bool is_close_to_destination = (distance_to_destination <= destination_distance_threshold_);
537 bool has_traveled_sufficient_distance =
538 (action_feedback_->distance_traveled >= required_traveled_distance_proportion_ * total_route_distance);
539 has_reached_destination = (is_close_to_destination && has_traveled_sufficient_distance);
540
541 // publish feedback
542 goal_handle->publish_feedback(action_feedback_);
543 if (!has_reached_destination) {
544 feedback_rate.sleep();
545 }
546 }
547
548 // prepare result
549 action_result_->distance_traveled = action_feedback_->distance_traveled + action_feedback_->distance_remaining;
550 action_result_->time_traveled = this->now() - action_start_time_;
551 action_result_->destination_reached = has_reached_destination;
552
553 // publish result
554 if (goal_handle->is_canceling()) {
555 goal_handle->canceled(action_result_);
556 {
557 std::scoped_lock lock(health_kv_mutex_);
558 health_kv_["action_status"] = "canceled";
559 }
560 RCLCPP_INFO(this->get_logger(), "Goal canceled");
561 } else if (!goal_handle->is_executing()) {
562 {
563 std::scoped_lock lock(health_kv_mutex_);
564 health_kv_["action_status"] = "aborted";
565 }
566 RCLCPP_INFO(this->get_logger(), "Goal aborted");
567 } else if (rclcpp::ok()) {
568 is_publishing_route_ = false; // stop publishing route
569 goal_handle->succeed(action_result_);
570 {
571 std::scoped_lock lock(health_kv_mutex_);
572 health_kv_["action_status"] = "succeeded";
573 }
574 RCLCPP_INFO(this->get_logger(), "Goal succeeded");
575 }
576}
577
578bool Lanelet2RoutePlanning::planRoute(const geometry_msgs::msg::PointStamped& destination,
579 const std::vector<geometry_msgs::msg::PointStamped>& intermediate_destinations) {
580 if (!this->checkMap(false)) {
581 RCLCPP_ERROR(this->get_logger(), "Cannot plan route, map not loaded by '%s'", ll2_map_server_name_.c_str());
582 return false;
583 }
584
585 // transform destination to map frame
586 geometry_msgs::msg::PointStamped destination_map_stamped;
587 if (destination.header.frame_id != ll2_interface_->map_frame_id_) {
588 try {
589 destination_map_stamped =
590 tf_buffer_->transform(destination, ll2_interface_->map_frame_id_, tf2::durationFromSec(transform_timeout_));
591 } catch (tf2::TransformException& ex) {
592 std::stringstream ss;
593 ss << "Could not transform destination from frame '" << destination.header.frame_id << "' to frame '"
594 << ll2_interface_->map_frame_id_ << "': " << ex.what();
595 RCLCPP_ERROR_STREAM(this->get_logger(), ss.str());
596 {
597 std::scoped_lock lock(health_kv_mutex_);
598 health_kv_["latest_error"] = ss.str();
599 }
600 return false;
601 }
602 } else {
603 destination_map_stamped = destination;
604 }
605 geometry_msgs::msg::Point& destination_map = destination_map_stamped.point;
606
607 // transform intermediate destinations to map frame
608 std::vector<geometry_msgs::msg::Point> intermediate_destinations_map;
609 for (const auto& intermediate : intermediate_destinations) {
610 geometry_msgs::msg::PointStamped intermediate_map_stamped;
611 if (intermediate.header.frame_id != ll2_interface_->map_frame_id_) {
612 try {
613 intermediate_map_stamped =
614 tf_buffer_->transform(intermediate, ll2_interface_->map_frame_id_, tf2::durationFromSec(transform_timeout_));
615 } catch (tf2::TransformException& ex) {
616 std::stringstream ss;
617 ss << "Could not transform intermediate destination from frame '" << intermediate.header.frame_id << "' to frame '"
618 << ll2_interface_->map_frame_id_ << "': " << ex.what();
619 RCLCPP_ERROR_STREAM(this->get_logger(), ss.str());
620 {
621 std::scoped_lock lock(health_kv_mutex_);
622 health_kv_["latest_error"] = ss.str();
623 }
624 return false;
625 }
626 intermediate_destinations_map.push_back(intermediate_map_stamped.point);
627 } else {
628 intermediate_destinations_map.push_back(intermediate.point);
629 }
630 }
631
632 // get map and traffic rules
633 lanelet::LaneletMapConstPtr map = ll2_interface_->getMapPtr();
634 lanelet::traffic_rules::TrafficRulesPtr traffic_rules = getTrafficRules();
635
636 // check validity of ego data
637 const double timeout_ego_data = 1.0;
638 if ((this->now() - latest_ego_data_.header.stamp).seconds() > timeout_ego_data) {
639 RCLCPP_WARN(this->get_logger(), "Ego data is outdated by %.3fs > %.3fs",
640 (this->now() - latest_ego_data_.header.stamp).seconds(), timeout_ego_data);
641 }
642 if (latest_ego_data_.header.frame_id != ll2_interface_->map_frame_id_) {
643 std::stringstream ss;
644 ss << "Ego data frame '" << latest_ego_data_.header.frame_id << "' does not match map frame '"
645 << ll2_interface_->map_frame_id_ << "'";
646 RCLCPP_ERROR_STREAM(this->get_logger(), ss.str());
647 {
648 std::scoped_lock lock(health_kv_mutex_);
649 health_kv_["latest_error"] = ss.str();
650 }
651 return false;
652 }
653
654 // project ego position to lanelet
655 lanelet::ConstLanelet ego_ll;
656 if (auto result = laneletAtPoint(toEigen2d(egoPosition(latest_ego_data_)), map)) {
657 ego_ll = *result;
658 } else {
659 std::string msg = "Failed to find lanelet at ego position";
660 RCLCPP_ERROR_STREAM(this->get_logger(), msg);
661 {
662 std::scoped_lock lock(health_kv_mutex_);
663 health_kv_["latest_error"] = msg;
664 }
665 return false;
666 }
667 Eigen::Vector2d ego_ll_position =
668 projectPointToLineString(toEigen2d(egoPosition(latest_ego_data_)), toEigen(ego_ll.centerline2d().basicLineString()));
669
670 // project destination to lanelet
671 lanelet::ConstLanelet destination_ll;
672 if (auto result = laneletAtPoint(toEigen2d(destination_map), map)) {
673 destination_ll = *result;
674 } else {
675 std::string msg = "Failed to find lanelet at destination";
676 RCLCPP_ERROR_STREAM(this->get_logger(), msg);
677 {
678 std::scoped_lock lock(health_kv_mutex_);
679 health_kv_["latest_error"] = msg;
680 }
681 return false;
682 }
683 Eigen::Vector2d destination_ll_position =
684 projectPointToLineString(toEigen2d(destination_map), toEigen(destination_ll.centerline2d().basicLineString()));
685
686 // project intermediate destinations to lanelets
687 std::vector<lanelet::ConstLanelet> intermediate_destination_lls;
688 std::vector<geometry_msgs::msg::Point> intermediate_destinations_on_route;
689 for (const auto& intermediate : intermediate_destinations_map) {
690 lanelet::ConstLanelet intermediate_ll;
691 if (auto result = laneletAtPoint(toEigen2d(intermediate), map)) {
692 intermediate_destination_lls.push_back(*result);
693 intermediate_destinations_on_route.push_back(intermediate);
694 } else {
695 std::stringstream ss;
696 ss << "Failed to find lanelet at intermediate point (" << intermediate.x << ", " << intermediate.y << "), skipping";
697 RCLCPP_WARN_STREAM(this->get_logger(), ss.str());
698 {
699 std::scoped_lock lock(health_kv_mutex_);
700 health_kv_["latest_warning"] = ss.str();
701 }
702 continue; // skip this intermediate if no lanelet found
703 }
704 }
705
706 // undershoot/overshoot route endpoints to enable context before start position and behind destination
707 lanelet::ConstLanelet undershot_ego_ll =
709 lanelet::ConstLanelet overshot_destination_ll =
710 followLaneletsAlongRoutingGraph(routing_graph_, destination_ll, destination_ll_position, route_overshoot_distance_);
711
712 // compute route from start to destination along intermediate destinations
713 std::vector<lanelet::ConstLanelet> route_lanelets = {undershot_ego_ll};
714 route_lanelets.insert(route_lanelets.end(), intermediate_destination_lls.begin(), intermediate_destination_lls.end());
715 route_lanelets.push_back(overshot_destination_ll);
716 std::optional<lanelet::routing::Route> planned_route = getRoute(routing_graph_, route_lanelets);
717
718 if (planned_route) {
720 destination_ = destination_map;
721 intermediate_destinations_ = intermediate_destinations_on_route;
722 latest_route_ = std::move(*planned_route);
723 return true;
724 } else {
725 std::stringstream ss;
726 ss << "Failed to plan route from lanelet " << ego_ll.id() << " to lanelet " << destination_ll.id();
727 RCLCPP_ERROR_STREAM(this->get_logger(), ss.str());
728 {
729 std::scoped_lock lock(health_kv_mutex_);
730 health_kv_["latest_error"] = ss.str();
731 }
732 return false;
733 }
734}
735
737 // initialize the complete minimal route before deriving local and global views
738 route_planning_msgs::msg::Route route_msg;
739 route_msg.header.stamp = latest_ego_data_.header.stamp;
740 route_msg.header.frame_id = ll2_interface_->map_frame_id_;
741 route_msg.destination = destination_;
742 route_msg.intermediate_destinations = intermediate_destinations_;
743
744 // resample the shortest path to form the reference line
745 const lanelet::routing::LaneletPath shortest_path = latest_route_.shortestPath();
746 const auto resampling_result = resampleCenterlinesAlongPath(shortest_path, sampling_distance_, true);
747 const std::vector<Eigen::Vector3d>& shortest_path_centerline_3d = resampling_result.centerline;
748 const std::vector<Eigen::Vector2d> shortest_path_centerline = to2d(shortest_path_centerline_3d);
749 latest_reference_line_ = shortest_path_centerline;
750 latest_lanelet_idx_by_reference_line_point_idx_ = resampling_result.lanelet_idx_by_point;
751
752 // create one minimal route element for each reference-line point
753 double accumulated_distance = 0.0;
754 for (size_t c = 0; c < shortest_path_centerline.size(); ++c) {
755 const Eigen::Vector2d& point = shortest_path_centerline[c];
756 const Eigen::Vector3d& point_3d = shortest_path_centerline_3d[c];
757 const Eigen::Vector2d& prev_point = c > 0 ? shortest_path_centerline[c - 1] : point;
758 const Eigen::Vector2d& next_point = c + 1 < shortest_path_centerline.size() ? shortest_path_centerline[c + 1] : point;
759 const lanelet::ConstLanelet& lanelet = shortest_path[latest_lanelet_idx_by_reference_line_point_idx_[c]];
760 const bool changes_lane_from_prev_point =
761 changesLaneFromPointToPoint(prev_point, point, sampling_distance_); // NOLINT(readability-suspicious-call-argument)
762 const bool changes_lane_to_next_point = changesLaneFromPointToPoint(point, next_point, sampling_distance_);
763 const Eigen::Vector2d orientation = tangentOfPointAlongLineString(point, changes_lane_from_prev_point ? point : prev_point,
764 changes_lane_to_next_point ? point : next_point);
765 accumulated_distance += (point - prev_point).norm();
766 route_msg.route_elements.push_back(createMinimalRouteElement(toRos(point_3d), toRosQuaternion(orientation),
767 accumulated_distance, changes_lane_to_next_point,
768 speedLimit(lanelet, point)));
769 }
770
771 // optionally align route endpoints with the reference line
773 starting_point_ = toRos(projectPointToLineString(toEigen(starting_point_), shortest_path_centerline_3d));
774 for (auto& destination : intermediate_destinations_) {
775 destination = toRos(projectPointToLineString(toEigen(destination), shortest_path_centerline_3d));
776 }
777 destination_ = toRos(projectPointToLineString(toEigen(destination_), shortest_path_centerline_3d));
778 }
779 route_msg.destination = destination_;
780 route_msg.intermediate_destinations = intermediate_destinations_;
781 // locate the route endpoints and current position in the complete route
782 route_msg.starting_route_element_idx =
783 matchPointToLineString(shortest_path_centerline, toEigen2d(starting_point_), 0, true, true);
784 route_msg.current_route_element_idx = matchPointToLineString(shortest_path_centerline, toEigen2d(egoPosition(latest_ego_data_)),
785 route_msg.starting_route_element_idx, true, true);
786 route_msg.destination_route_element_idx =
787 indexOfLineStringPointClosestToPoint(shortest_path_centerline, toEigen2d(destination_), true, false);
788
789 has_enriched_route_ = false;
790 latest_route_msg_ = route_planning_msgs::msg::Route();
791 latest_full_route_msg_ = route_planning_msgs::msg::Route();
792 latest_global_route_msg_ = route_planning_msgs::msg::Route();
793
794 const size_t start_idx = route_msg.starting_route_element_idx;
795 const size_t destination_idx = route_msg.destination_route_element_idx;
796 if (start_idx >= route_msg.route_elements.size() || destination_idx >= route_msg.route_elements.size() ||
797 start_idx > destination_idx) {
798 RCLCPP_WARN(this->get_logger(), "Invalid start or destination index, not building global route");
799 return;
800 }
801
802 latest_full_route_msg_ = route_msg;
803
804 // retain only the route segment from start to destination for the global route
805 std::vector<Eigen::Vector2d> global_reference_line;
806 global_reference_line.reserve(destination_idx - start_idx + 1);
807 std::vector<size_t> break_after_indices;
808 for (size_t c = start_idx; c <= destination_idx; ++c) {
809 global_reference_line.push_back(shortest_path_centerline[c]);
810 if (c < destination_idx && route_msg.route_elements[c].will_change_suggested_lane) {
811 break_after_indices.push_back(c - start_idx);
812 }
813 }
814 // adaptively sample each continuous global-route segment
815 const std::vector<size_t> retained_indices =
816 adaptivelySampleLineString(global_reference_line, sampling_max_lateral_error_global_, break_after_indices);
817 if (retained_indices.empty()) {
818 RCLCPP_WARN(this->get_logger(), "Global reference line is empty, not building global route");
819 return;
820 }
821
822 latest_global_route_msg_ = route_msg;
823 latest_global_route_msg_.route_elements.clear();
824 latest_global_route_msg_.route_elements.reserve(retained_indices.size());
825 for (const size_t retained_idx : retained_indices) {
826 latest_global_route_msg_.route_elements.push_back(route_msg.route_elements[start_idx + retained_idx]);
827 }
828 // the global route contains one lane element per route element, which always continues in lane zero
829 for (size_t i = 0; i + 1 < latest_global_route_msg_.route_elements.size(); ++i) {
830 auto& lane_element = latest_global_route_msg_.route_elements[i].lane_elements[0];
831 lane_element.has_following_lane_idx = true;
832 lane_element.following_lane_idx = 0;
833 }
834 latest_global_route_msg_.route_elements.back().lane_elements[0].has_following_lane_idx = false;
835
836 latest_global_route_msg_.starting_route_element_idx = 0;
837 latest_global_route_msg_.destination_route_element_idx = latest_global_route_msg_.route_elements.size() - 1;
838 latest_global_route_msg_.current_route_element_idx = 0;
839 RCLCPP_INFO(this->get_logger(), "Built global route with %ld/%ld route elements", retained_indices.size(),
840 global_reference_line.size());
841}
842
844 if (latest_full_route_msg_.route_elements.empty() || latest_reference_line_.empty()) {
845 return;
846 }
847
848 // find point of global reference line closest to and behind of ego position
849 const Eigen::Vector2d ego_position = toEigen2d(egoPosition(latest_ego_data_));
850 const size_t global_closest_point =
851 matchPointToLineString(latest_reference_line_, ego_position, latest_full_route_msg_.current_route_element_idx, true, true);
852 latest_full_route_msg_.current_route_element_idx = global_closest_point;
853 const auto& full_route_elements = latest_full_route_msg_.route_elements;
854 LocalRouteWindow local_route_window = extractLocalRouteWindow(
856 const size_t first_global_idx = local_route_window.first_global_idx;
857 route_planning_msgs::msg::Route route_msg = std::move(local_route_window.route);
858 std::vector<route_planning_msgs::msg::RouteElement>& route_elements = route_msg.route_elements;
859 if (route_elements.empty()) {
860 return;
861 }
862
864 std::vector<std::vector<int>>(route_elements.size());
865 const lanelet::routing::LaneletPath shortest_path = latest_route_.shortestPath();
866
867// parallelize independent geometry, map, and regulatory-element extraction per local route element
868#pragma omp parallel for
869 for (size_t c = 0; c < route_elements.size(); ++c) {
870 const size_t global_c = first_global_idx + c;
871 route_planning_msgs::msg::RouteElement& route_element_msg = route_elements[c];
872 route_planning_msgs::msg::LaneElement& lane_element_msg =
873 route_element_msg.lane_elements[route_element_msg.suggested_lane_idx];
874
875 // get current, previous and next centerline point
876 const route_planning_msgs::msg::LaneElement prev_lane_element_msg =
877 (global_c > 0) ? route_planning_msgs::route_access::getSuggestedLaneElement(full_route_elements[global_c - 1])
878 : lane_element_msg;
879 const route_planning_msgs::msg::LaneElement next_lane_element_msg =
880 (global_c + 1 < full_route_elements.size())
881 ? route_planning_msgs::route_access::getSuggestedLaneElement(full_route_elements[global_c + 1])
882 : lane_element_msg;
883 const Eigen::Vector2d point = toEigen2d(lane_element_msg.reference_pose.position);
884 const Eigen::Vector2d prev_point = toEigen2d(prev_lane_element_msg.reference_pose.position);
885 const Eigen::Vector2d next_point = toEigen2d(next_lane_element_msg.reference_pose.position);
886 const double point_z = lane_element_msg.reference_pose.position.z; // assuming constant z across route elements
887
888 // get lanelet corresponding to centerline point
889 const lanelet::ConstLanelet& lanelet = shortest_path[latest_lanelet_idx_by_reference_line_point_idx_[global_c]];
890
891 // identify lane changes
892 const bool changes_lane_from_prev_point =
893 changesLaneFromPointToPoint(prev_point, point, sampling_distance_); // NOLINT(readability-suspicious-call-argument)
894 const bool changes_lane_to_next_point = changesLaneFromPointToPoint(point, next_point, sampling_distance_);
895
896 // determine neighboring points for projection
897 Eigen::Vector2d prev_point_for_projection = changes_lane_from_prev_point ? point : prev_point;
898 Eigen::Vector2d next_point_for_projection = changes_lane_to_next_point ? point : next_point;
899
900 // get adjacent lanelets
901 std::vector<lanelet::ConstLanelet> adjacent_left_lanelets = adjacentLeftOrRightLanelets(lanelet, routing_graph_, true);
902 std::vector<lanelet::ConstLanelet> adjacent_right_lanelets = adjacentLeftOrRightLanelets(lanelet, routing_graph_, false);
903 const int suggested_lane_idx = static_cast<int>(adjacent_left_lanelets.size());
904 const int n_lanes = static_cast<int>(adjacent_left_lanelets.size() + 1 + adjacent_right_lanelets.size());
907 }
908
909 // project centerline point to lanelet and adjacent lanelet centerlines and bounds
910 auto lanelet_projected_points =
911 projectPointToLaneletLines(point, prev_point_for_projection, next_point_for_projection,
912 std::vector<lanelet::ConstLanelet>{lanelet}, this->get_logger())[0];
913 auto adjacent_left_lanelets_projected_points = projectPointToLaneletLines(
914 point, prev_point_for_projection, next_point_for_projection, adjacent_left_lanelets, this->get_logger());
915 auto adjacent_right_lanelets_projected_points = projectPointToLaneletLines(
916 point, prev_point_for_projection, next_point_for_projection, adjacent_right_lanelets, this->get_logger());
917
918 // compute offset of lane element indices from current to next route element
919 const lanelet::ConstLanelet& lanelet_of_next_point =
920 (global_c + 1 < full_route_elements.size()) ? shortest_path[latest_lanelet_idx_by_reference_line_point_idx_[global_c + 1]]
921 : lanelet;
922 int following_lane_idx_offset = 0;
923 if (auto result = computeFollowingLaneIdxOffset(lanelet, lanelet_of_next_point, routing_graph_)) {
924 following_lane_idx_offset = *result;
925 } else {
926 RCLCPP_ERROR(this->get_logger(),
927 "Failed to find following lane index offset for route element %ld on lanelet %ld, assuming no offset",
928 global_c, lanelet.id());
929 following_lane_idx_offset = 0;
930 }
931
932 // extract drivable space
933 Eigen::Vector2d drivable_space_left, drivable_space_right;
934 std::tie(drivable_space_left, drivable_space_right) =
935 extractDrivableSpace(ll2_interface_->getMapPtr()->lineStringLayer,
936 {prev_point_for_projection, point, next_point_for_projection}, max_drivable_space_radius_);
937
938 // extract regulatory elements
939 auto regulatory_element_extraction =
940 extractRegulatoryElements(lanelet, adjacent_left_lanelets, adjacent_right_lanelets, {prev_point, point, next_point});
941
942// enrich RouteElement with local route information
943#pragma omp critical // prevent race condition when accessing prev/next suggested lane element set by other threads
944 {
945 route_element_msg.lane_elements = {};
946 route_element_msg.is_enriched = true;
947 route_element_msg.left_boundary = toRos(to3d(drivable_space_left, point_z));
948 route_element_msg.right_boundary = toRos(to3d(drivable_space_right, point_z));
949 route_element_msg.regulatory_elements = regulatory_element_extraction.regulatory_element_msgs;
950 route_element_msg.suggested_lane_idx = suggested_lane_idx;
951 route_element_msg.will_change_suggested_lane = changes_lane_to_next_point;
952 // route_element_msg.s already set in global route
953 size_t lane_element_idx = route_element_msg.lane_elements.size();
954
955 // create LaneElements for left adjacent lanes
956 for (size_t a = 0; a < adjacent_left_lanelets_projected_points.size(); ++a) {
957 route_planning_msgs::msg::LaneElement lane_element_msg;
958 lane_element_msg.reference_pose.position =
959 toRos(to3d(adjacent_left_lanelets_projected_points[a].centerline_point, point_z));
960 // lane_element_msg.reference_pose.orientation computed in postprocessRouteMessage
961 lane_element_msg.left_boundary.point = toRos(to3d(adjacent_left_lanelets_projected_points[a].left_bound_point, point_z));
962 lane_element_msg.left_boundary.type = laneBoundaryType(adjacent_left_lanelets[a].leftBound2d());
963 lane_element_msg.right_boundary.point =
964 toRos(to3d(adjacent_left_lanelets_projected_points[a].right_bound_point, point_z));
965 lane_element_msg.right_boundary.type = laneBoundaryType(adjacent_left_lanelets[a].rightBound2d());
966 lane_element_msg.speed_limit =
967 speedLimit(adjacent_left_lanelets[a], adjacent_left_lanelets_projected_points[a].centerline_point);
968 lane_element_msg.regulatory_element_idcs = regulatory_element_extraction.adjacent_left_regulatory_element_idcs[a];
969 const int computed_following_lane_idx =
970 static_cast<int>(route_element_msg.lane_elements.size()) + following_lane_idx_offset;
971 lane_element_msg.has_following_lane_idx = (computed_following_lane_idx >= 0 && computed_following_lane_idx < n_lanes);
972 if (lane_element_msg.has_following_lane_idx) {
973 lane_element_msg.following_lane_idx = computed_following_lane_idx;
974 }
975 std::tie(lane_element_msg.suggested_turn_signal,
977 suggestedTurnSignal(adjacent_left_lanelets[a], this->get_logger());
978 route_element_msg.lane_elements.push_back(lane_element_msg);
979 lane_element_idx = route_element_msg.lane_elements.size();
980 }
981
982 // create LaneElement for centerline lane
983 route_planning_msgs::msg::LaneElement centerline_lane_element_msg;
984 centerline_lane_element_msg.reference_pose.position = toRos(to3d(point, point_z));
985 // centerline_lane_element_msg.reference_pose.orientation computed in postprocessRouteMessage
986 centerline_lane_element_msg.left_boundary.point = toRos(to3d(lanelet_projected_points.left_bound_point, point_z));
987 centerline_lane_element_msg.left_boundary.type = laneBoundaryType(lanelet.leftBound2d());
988 centerline_lane_element_msg.right_boundary.point = toRos(to3d(lanelet_projected_points.right_bound_point, point_z));
989 centerline_lane_element_msg.right_boundary.type = laneBoundaryType(lanelet.rightBound2d());
990 centerline_lane_element_msg.speed_limit = speedLimit(lanelet, point);
991 centerline_lane_element_msg.regulatory_element_idcs = regulatory_element_extraction.regulatory_element_idcs;
992 const int computed_following_lane_idx =
993 static_cast<int>(route_element_msg.lane_elements.size()) + following_lane_idx_offset;
994 centerline_lane_element_msg.has_following_lane_idx =
995 (computed_following_lane_idx >= 0 && computed_following_lane_idx < n_lanes);
996 if (centerline_lane_element_msg.has_following_lane_idx) {
997 centerline_lane_element_msg.following_lane_idx = computed_following_lane_idx;
998 }
999 std::tie(centerline_lane_element_msg.suggested_turn_signal,
1001 suggestedTurnSignal(lanelet, this->get_logger());
1002 route_element_msg.lane_elements.push_back(centerline_lane_element_msg);
1003 lane_element_idx = route_element_msg.lane_elements.size();
1004
1005 // create LaneElements for right adjacent lanes
1006 for (size_t a = 0; a < adjacent_right_lanelets_projected_points.size(); ++a) {
1007 route_planning_msgs::msg::LaneElement lane_element_msg;
1008 lane_element_msg.reference_pose.position =
1009 toRos(to3d(adjacent_right_lanelets_projected_points[a].centerline_point, point_z));
1010 // lane_element_msg.reference_pose.orientation computed in postprocessRouteMessage
1011 lane_element_msg.left_boundary.point = toRos(to3d(adjacent_right_lanelets_projected_points[a].left_bound_point, point_z));
1012 lane_element_msg.left_boundary.type = laneBoundaryType(adjacent_right_lanelets[a].leftBound2d());
1013 lane_element_msg.right_boundary.point =
1014 toRos(to3d(adjacent_right_lanelets_projected_points[a].right_bound_point, point_z));
1015 lane_element_msg.right_boundary.type = laneBoundaryType(adjacent_right_lanelets[a].rightBound2d());
1016 lane_element_msg.speed_limit =
1017 speedLimit(adjacent_right_lanelets[a], adjacent_right_lanelets_projected_points[a].centerline_point);
1018 lane_element_msg.regulatory_element_idcs = regulatory_element_extraction.adjacent_right_regulatory_element_idcs[a];
1019 const int computed_following_lane_idx =
1020 static_cast<int>(route_element_msg.lane_elements.size()) + following_lane_idx_offset;
1021 lane_element_msg.has_following_lane_idx = (computed_following_lane_idx >= 0 && computed_following_lane_idx < n_lanes);
1022 if (lane_element_msg.has_following_lane_idx) {
1023 lane_element_msg.following_lane_idx = computed_following_lane_idx;
1024 }
1025 std::tie(lane_element_msg.suggested_turn_signal,
1027 suggestedTurnSignal(adjacent_right_lanelets[a], this->get_logger());
1028 route_element_msg.lane_elements.push_back(lane_element_msg);
1029 lane_element_idx = route_element_msg.lane_elements.size();
1030 }
1031 }
1032 }
1033
1034 route_msg.header.stamp = latest_ego_data_.header.stamp;
1035
1036 // postprocess route message
1038
1039 // save as latest route message
1040 latest_route_msg_ = route_msg;
1041 has_enriched_route_ = true;
1042}
1043
1044void Lanelet2RoutePlanning::publishHealth(const unsigned char status, const std::string& msg, const rclcpp::Time& now) {
1045 diagnostic_msgs::msg::DiagnosticArray diagnostics;
1046 diagnostics.header.stamp = now;
1047 auto& health = diagnostics.status.emplace_back();
1048 health.name = this->get_fully_qualified_name() + std::string(": health");
1049 health.hardware_id = "none";
1050 health.level = status;
1051 health.message = msg;
1052 std::map<std::string, std::string> health_kv_snapshot;
1053 {
1054 std::scoped_lock lock(health_kv_mutex_);
1055 health_kv_snapshot = health_kv_;
1056 }
1057 for (const auto& [key, value] : health_kv_snapshot) {
1058 auto& key_value = health.values.emplace_back();
1059 key_value.key = key;
1060 key_value.value = value;
1061 }
1062
1063 health_diagnostic_pub_->publish(diagnostics);
1064}
1065
1066} // namespace lanelet2_route_planning
1067
1075int main(int argc, char* argv[]) {
1076 rclcpp::init(argc, argv);
1077 rclcpp::spin(std::make_shared<lanelet2_route_planning::Lanelet2RoutePlanning>());
1078 rclcpp::shutdown();
1079
1080 return 0;
1081}
double sampling_distance_
Distance between resampled points along route [m] (parameter)
rcl_interfaces::msg::SetParametersResult parametersCallback(const std::vector< rclcpp::Parameter > &parameters)
Handles reconfiguration when a parameter value is changed.
bool has_enriched_route_
Flag indicating that route has been enriched at least once.
geometry_msgs::msg::Point destination_
Destination point in map frame.
void egoDataCallback(const perception_msgs::msg::EgoData::SharedPtr msg)
Callback for EgoData.
std::vector< std::tuple< std::string, std::function< void(const rclcpp::Parameter &)> > > auto_reconfigurable_params_
Auto-reconfigurable parameters for dynamic reconfiguration.
bool is_publishing_route_
Controlling flag for route publication.
double max_drivable_space_radius_
Maximum distance to left/right drivable space bounds, if not otherwise restricted [m] (parameter)
double action_feedback_frequency_
Frequency of action feedback publication [Hz] (parameter)
rclcpp::TimerBase::SharedPtr publish_timer_
Timer for publishing the enriched route.
double required_traveled_distance_proportion_
Proportion of route length that must have been traveled before considering destination reached [0....
void buildGlobalRouteMessage()
Builds a global ROS route message from the latest planned lanelet route.
double sampling_max_lateral_error_global_
Maximum lateral error of the adaptively sampled global reference line [m] (parameter)
rclcpp::Publisher< diagnostic_msgs::msg::DiagnosticArray >::SharedPtr health_diagnostic_pub_
Health diagnostic publisher.
double enrich_route_behind_ego_distance_
Distance behind ego position where global route is enriched with more information [m] (negative=unlim...
double route_overshoot_distance_
Overshoot route by this distance behind destination [m] (parameter)
void setup()
Sets up subscribers, publishers, etc. to configure the node.
rclcpp_action::GoalResponse actionHandleGoal(const rclcpp_action::GoalUUID &uuid, std::shared_ptr< const route_planning_msgs::action::PlanRoute::Goal > goal)
Action goal callback: processes a route planning request.
std::map< std::string, std::string > health_kv_
Health key-value pairs for diagnostic message.
std::string ll2_map_server_name_
Name of lanelet2_map_server node (parameter)
route_planning_msgs::action::PlanRoute::Result::SharedPtr action_result_
Latest action result.
double publish_frequency_global_
Frequency of global route publication [Hz] (parameter)
std::unique_ptr< diagnostic_updater::HeaderlessTopicDiagnostic > global_route_timer_diagnostic_
Diagnostic to auto-diagnose global route timer frequency.
TopicDiagnosticConfig global_route_timer_diagnostic_config_
Configuration for auto-diagnosed global route timer.
bool buildRoutingGraph()
Builds the lanelet2 routing graph based on the current map.
diagnostic_updater::Updater diagnostic_updater_
Diagnostic updater for monitoring topic frequencies and timestamps.
rclcpp::Publisher< route_planning_msgs::msg::Route >::SharedPtr publisher_route_
Publisher for enriched route.
route_planning_msgs::action::PlanRoute::Feedback::SharedPtr action_feedback_
Latest action feedback.
double destination_distance_threshold_
Distance to destination where destination is considered reached [m] (parameter)
std::unique_ptr< diagnostic_updater::HeaderlessTopicDiagnostic > route_timer_diagnostic_
Diagnostic to auto-diagnose route timer frequency.
std::shared_ptr< tf2_ros::TransformListener > tf_listener_
Transform listener.
double publish_frequency_
Frequency of route publication [Hz] (parameter)
double enrich_route_ahead_ego_distance_
Distance ahead of ego position where global route is enriched with more information [m] (negative=unl...
std::unique_ptr< tf2_ros::Buffer > tf_buffer_
Transform buffer.
lanelet::routing::Route latest_route_
Latest planned lanelet route.
bool checkMap(bool handle_update)
Checks if map is loaded and handles map updates.
rclcpp::TimerBase::SharedPtr global_route_publish_timer_
Timer for publishing the global route.
geometry_msgs::msg::Point starting_point_
Starting point in map frame.
rclcpp::Subscription< perception_msgs::msg::EgoData >::SharedPtr subscriber_ego_data_
Subscriber for ego data.
void globalRoutePublishTimerCallback()
Callback to periodically publish the adaptively sampled global route.
route_planning_msgs::msg::Route latest_route_msg_
Latest route message to publish.
std::unique_ptr< diagnostic_updater::TopicDiagnostic > ego_data_diagnostic_
Diagnostic to auto-diagnose ego data topic.
std::vector< geometry_msgs::msg::Point > intermediate_destinations_
Intermediate points in map frame.
route_planning_msgs::msg::Route latest_global_route_msg_
Latest adaptively sampled non-enriched global route.
int max_num_threads_
Maximum number of threads for parallel processing (0=max available) (parameter)
lanelet::routing::RoutingGraphUPtr routing_graph_
Lanelet routing graph for current map.
double route_undershoot_distance_
Undershoot route by this distance before ego position [m] (parameter)
void publishHealth(const unsigned char status, const std::string &msg, const rclcpp::Time &now)
Publishes health diagnostic.
void buildEnrichedRouteMessage()
Builds an enriched ROS route message from the latest planned lanelet route.
TopicDiagnosticConfig route_timer_diagnostic_config_
Configuration for auto-diagnosed route timer.
std::vector< std::vector< int > > latest_suggested_turn_signal_distance_ahead_by_route_element_by_lane_element_
Latest suggested turn signal distance ahead by route element by lane element.
void actionHandleAccepted(const std::shared_ptr< rclcpp_action::ServerGoalHandle< route_planning_msgs::action::PlanRoute > > goal_handle)
Action accepted callback: starts action execution.
std::shared_ptr< rclcpp_action::ServerGoalHandle< route_planning_msgs::action::PlanRoute > > action_goal_handle_
Latest action goal handle.
std::vector< Eigen::Vector2d > latest_reference_line_
Latest complete dense reference line in the map frame.
perception_msgs::msg::EgoData latest_ego_data_
Latest ego data in map frame.
bool project_destination_to_reference_line_
Whether to project destination to reference line (parameter)
void declareAndLoadParameter(const std::string &name, T &param, const std::string &description, const bool add_to_auto_reconfigurable_params=true, const bool is_required=false, const bool read_only=false, const std::optional< double > &from_value=std::nullopt, const std::optional< double > &to_value=std::nullopt, const std::optional< double > &step_value=std::nullopt, const std::string &additional_constraints="")
Declares and loads a ROS parameter.
void publishTimerCallback()
Callback to periodically publish the route.
OnSetParametersCallbackHandle::SharedPtr parameters_callback_
Callback handle for dynamic parameter reconfiguration.
rclcpp_action::CancelResponse actionHandleCancel(const std::shared_ptr< rclcpp_action::ServerGoalHandle< route_planning_msgs::action::PlanRoute > > goal_handle)
Action cancel callback: cancels a running action.
rclcpp::CallbackGroup::SharedPtr action_callback_group_
Callback group for action server.
bool planRoute(const geometry_msgs::msg::PointStamped &destination, const std::vector< geometry_msgs::msg::PointStamped > &intermediate_destinations={})
Plans a lanelet route to the destination.
rclcpp::Time action_start_time_
Latest action start time to compute action duration.
TopicDiagnosticConfig ego_data_diagnostic_config_
Configuration for auto-diagnosed ego data topic.
std::vector< size_t > latest_lanelet_idx_by_reference_line_point_idx_
Latest mapping between global route reference line and lanelet indices.
rclcpp_action::Server< route_planning_msgs::action::PlanRoute >::SharedPtr action_server_
Action server.
void actionExecute(const std::shared_ptr< rclcpp_action::ServerGoalHandle< route_planning_msgs::action::PlanRoute > > goal_handle)
Action execution: continually publishes route progress.
rclcpp::Publisher< route_planning_msgs::msg::Route >::SharedPtr publisher_global_route_
Publisher for the adaptively sampled global route.
route_planning_msgs::msg::Route latest_full_route_msg_
Latest complete minimal route used internally for progress tracking.
double transform_timeout_
How long to wait for a transform to be available [s] (parameter)
std::unique_ptr< Lanelet2MapInterface > ll2_interface_
Lanelet2 map interface.
std::mutex health_kv_mutex_
Mutex protecting health key-value pairs across threads.
int main(int argc, char *argv[])
Starts the ROS node.
uint8_t laneBoundaryType(const lanelet::ConstLineString2d &line)
Extracts the lane boundary type of a lanelet line.
Definition utils.cpp:666
geometry_msgs::msg::Point egoPosition(const perception_msgs::msg::EgoData &ego_data)
Extracts an EgoData position as a ROS point.
lanelet::ConstLanelet followLaneletsAlongRoutingGraph(const lanelet::routing::RoutingGraphUPtr &routing_graph, const lanelet::ConstLanelet &lanelet, const Eigen::Vector2d &position, const double distance)
Follows a lanelet's and following lanelets' centerline for a given distance.
Definition utils.cpp:914
double distanceTraveled(const route_planning_msgs::msg::Route &route)
Computes the traveled distance along the route.
Definition utils.cpp:993
lanelet::traffic_rules::TrafficRulesPtr getTrafficRules()
Get traffic rules.
Definition utils.cpp:879
bool changesLaneFromPointToPoint(const Eigen::Vector2d &point, const Eigen::Vector2d &next_point, const double sampling_distance)
Identifies a lane change based on the distance between two reference line points.
Definition utils.cpp:177
std::optional< lanelet::routing::Route > getRoute(const lanelet::routing::RoutingGraphUPtr &routing_graph, const std::vector< lanelet::ConstLanelet > &route_lanelets)
Computes a route from start to destination along intermediate destinations.
Definition utils.cpp:26
size_t matchPointToLineString(const std::vector< Eigen::Vector2d > &line_string, const Eigen::Vector2d &point, const size_t idx_indication, const bool consider_order=false, const bool behind=true)
Finds the index of a point in a line string that is locally closest to another point.
Definition utils.cpp:94
Eigen::Vector2d tangentOfPointAlongLineString(const Eigen::Vector2d &point, const Eigen::Vector2d &prev_point, const Eigen::Vector2d &next_point)
Computes a unit vector tangential to a point along a line string.
Definition geometry.cpp:123
Eigen::Vector2d projectPointToLineString(const Eigen::Vector2d &point, const std::vector< Eigen::Vector2d > &line_string)
Projects a point to the closest line segment of a line string.
Definition geometry.cpp:231
LocalRouteWindow extractLocalRouteWindow(const route_planning_msgs::msg::Route &full_route, size_t current_global_idx, double distance_behind, double distance_ahead)
Extracts a local route window while preserving absolute route data.
Definition utils.cpp:1169
Eigen::Vector3d to3d(const Eigen::Vector2d &point)
Converts a 2D Eigen point to a 3D Eigen point.
std::vector< size_t > adaptivelySampleLineString(const std::vector< Eigen::Vector2d > &line_string, double max_lateral_error, const std::vector< size_t > &break_after_indices={})
Simplifies a 2D line string within a maximum lateral error.
Definition geometry.cpp:188
ExtractRegulatoryElementsResult extractRegulatoryElements(const lanelet::ConstLanelet &lanelet, const std::vector< lanelet::ConstLanelet > &adjacent_left_lanelets, const std::vector< lanelet::ConstLanelet > &adjacent_right_lanelets, const PointSequence &point_sequence)
Extracts regulatory element information for a route element.
Definition utils.cpp:481
route_planning_msgs::msg::RouteElement createMinimalRouteElement(const geometry_msgs::msg::Point &position, const geometry_msgs::msg::Quaternion &orientation, double s=0.0, bool will_change_suggested_lane=false, uint8_t speed_limit=0)
Create a minimal route element message.
Definition utils.cpp:345
uint8_t speedLimit(const lanelet::ConstLanelet &lanelet, const bool consider_regulatory_elements=true)
Extracts the speed limit of a lanelet.
Definition utils.cpp:704
geometry_msgs::msg::Quaternion toRosQuaternion(const Eigen::Vector2d &vector)
Converts a 2D Eigen vector pointing in a specific direction to a ROS quaternion.
std::optional< int > computeFollowingLaneIdxOffset(const lanelet::ConstLanelet &lanelet, const lanelet::ConstLanelet &lanelet_of_next_point, const lanelet::routing::RoutingGraphUPtr &routing_graph)
Computes the offset of lane element indices from current to next route element.
Definition utils.cpp:252
size_t indexOfLineStringPointClosestToPoint(const std::vector< Eigen::Vector2d > &line_string, const Eigen::Vector2d &point, const bool consider_order=false, const bool behind=true)
Finds the index of a point in a line string that is closest to another point.
Definition utils.cpp:67
Eigen::Vector2d to2d(const Eigen::Vector3d &point)
Converts a 3D Eigen point to a 2D Eigen point.
geometry_msgs::msg::Point toRos(const Eigen::Vector2d &point)
Converts a 2D Eigen point to a ROS point.
ResampleCenterlinesAlongPathResult resampleCenterlinesAlongPath(const lanelet::routing::LaneletPath &path, const double delta_s, bool monotonically)
Equidistantly resamples lanelet centerlines along a path to one joint centerline.
Definition utils.cpp:946
Eigen::Vector2d toEigen2d(const geometry_msgs::msg::Point &point)
Converts a ROS point to a 2D Eigen point.
void postprocessRouteMessage(route_planning_msgs::msg::Route &route_msg, std::vector< std::vector< int > > &suggested_turn_signal_distance_ahead_by_route_element_by_lane_element)
Postprocesses a route message, filling missing information that can be inferred from other message co...
Definition utils.cpp:1031
std::pair< Eigen::Vector2d, Eigen::Vector2d > extractDrivableSpace(const lanelet::LineStringLayer &line_string_layer, const PointSequence &point_sequence, const double max_distance)
Extracts drivable space boundaries for a route element.
Definition utils.cpp:375
std::vector< lanelet::ConstLanelet > adjacentLeftOrRightLanelets(const lanelet::ConstLanelet &lanelet, const lanelet::routing::RoutingGraphUPtr &routing_graph, bool left, bool sort_from_left=true)
Finds lanelets adjacent to the left or right of a given lanelet.
Definition utils.cpp:184
std::optional< lanelet::ConstLanelet > laneletAtPoint(const Eigen::Vector2d &point, const lanelet::LaneletMapConstPtr &map, const std::optional< lanelet::traffic_rules::TrafficRulesPtr > traffic_rules=std::nullopt)
Find lanelet at arbitrary point.
Definition utils.cpp:885
std::vector< ProjectedLaneletPoints > projectPointToLaneletLines(const Eigen::Vector2d &point, const Eigen::Vector2d &prev_point, const Eigen::Vector2d &next_point, const std::vector< lanelet::ConstLanelet > &lanelets, const rclcpp::Logger &logger=rclcpp::get_logger("lanelet2_route_planning"))
Projects a point to the centerline and bounds of a set of lanelets.
Definition utils.cpp:208
std::tuple< uint8_t, int > suggestedTurnSignal(const lanelet::ConstLanelet &lanelet, const rclcpp::Logger &logger)
Extracts the suggested turn signal of a lanelet.
Definition utils.cpp:833
Eigen::Vector3d toEigen(const geometry_msgs::msg::Point &point)
Converts a ROS point to a 3D Eigen point.
double distanceRemaining(const route_planning_msgs::msg::Route &route)
Computes the remaining distance along the route.
Definition utils.cpp:1004
A local route window and its offset in the complete route.
Definition utils.hpp:29
route_planning_msgs::msg::Route route
Definition utils.hpp:30
double max_acceptable_timestamp_delta
Maximum acceptable difference between message timestamp and receipt time (in seconds)
double min_acceptable_timestamp_delta
Minimum acceptable difference between message timestamp and receipt time (in seconds)