11#include <lanelet2_routing/RoutingGraph.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>
33 "Frequency of global route publication [Hz]",
true,
false,
false, 0.1, 20.0);
35 "Frequency of action feedback publication [Hz]",
true,
false,
false, 0.1, 20.0);
37 true,
false,
false, 0.1, 3.0);
39 "Maximum lateral error of the adaptively sampled global reference line [m]",
true,
false,
false,
42 "Whether to project destination to reference line",
true,
false,
false);
44 "Distance to destination where destination is considered reached [m]",
true,
false,
false, 0.1,
48 "Proportion of route length that must have been traveled before considering destination reached [0..1]",
true,
false,
false,
52 "Distance ahead of ego position where global route is enriched with more information [m] (negative=unlimited)",
true,
false,
56 "Distance behind ego position where global route is enriched with more information [m] (negative=unlimited)",
true,
false,
59 "Undershoot route by this distance before ego position [m]",
true,
false,
false, 0.0, 50.0);
61 "Overshoot route by this distance behind destination [m]",
true,
false,
false, 0.0, 100.0);
63 "Maximum distance to left/right drivable space bounds, if not otherwise restricted [m]",
true,
64 false,
false, 3.0, 100.0);
66 "Maximum number of threads for parallel processing (0=max available)",
true,
false,
false, 0,
67 omp_get_max_threads(), 1);
69 true,
false,
false, 0.0, 1.0);
82 "Minimum frequency for incoming ego data topic",
false,
false,
false);
84 "Maximum frequency for incoming ego data topic",
false,
false,
false);
87 "Minimum acceptable timestamp delta for incoming ego data topic",
false,
false,
false);
90 "Maximum acceptable timestamp delta for incoming ego data topic",
false,
false,
false);
102 false,
false,
false);
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;
123 auto type = rclcpp::ParameterValue(param).get_type();
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};
137 RCLCPP_WARN(this->get_logger(),
"Parameter type of parameter '%s' does not support specifying a range", name.c_str());
141 this->declare_parameter(name, type, param_desc);
144 param = this->get_parameter(name).get_value<T>();
145 std::stringstream ss;
146 ss <<
"Loaded parameter '" << name <<
"': ";
149 for (
const auto& element : param) ss << element << (&element != ¶m.back() ?
", " :
"");
154 RCLCPP_INFO_STREAM(this->get_logger(), ss.str());
155 }
catch (rclcpp::exceptions::ParameterUninitializedException&) {
157 RCLCPP_FATAL_STREAM(this->get_logger(),
"Missing required parameter '" << name <<
"', exiting");
160 std::stringstream ss;
161 ss <<
"Missing parameter '" << name <<
"', using default value: ";
164 for (
const auto& element : param) ss << element << (&element != ¶m.back() ?
", " :
"");
169 RCLCPP_WARN_STREAM(this->get_logger(), ss.str());
170 this->set_parameters({rclcpp::Parameter(name, rclcpp::ParameterValue(param))});
174 if (add_to_auto_reconfigurable_params) {
175 std::function<void(
const rclcpp::Parameter&)> setter = [¶m](
const rclcpp::Parameter& p) { param = p.get_value<T>(); };
181 const std::vector<rclcpp::Parameter>& parameters) {
182 for (
const auto& param : parameters) {
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());
193 for (
const auto& param : parameters) {
194 if (param.get_name() ==
"publish_frequency") {
198 }
else if (param.get_name() ==
"publish_frequency_global") {
216 rcl_interfaces::msg::SetParametersResult result;
217 result.successful =
true;
233 health_kv_[
"map_loaded"] = map_status ?
"true" :
"false";
247 tf_buffer_ = std::make_unique<tf2_ros::Buffer>(this->get_clock());
251 publisher_route_ = this->create_publisher<route_planning_msgs::msg::Route>(
"~/route", 1);
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());
268 action_server_ = rclcpp_action::create_server<route_planning_msgs::action::PlanRoute>(
269 this,
"~/plan_route",
274 RCLCPP_INFO(this->get_logger(),
"Action server started");
277 health_diagnostic_pub_ = this->create_publisher<diagnostic_msgs::msg::DiagnosticArray>(
"/diagnostics", 1);
297 RCLCPP_ERROR(this->get_logger(),
"Cannot build routing graph, map not loaded by '%s'",
ll2_map_server_name_.c_str());
303 lanelet::traffic_rules::TrafficRulesPtr traffic_rules =
getTrafficRules();
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]);
316 RCLCPP_INFO(this->get_logger(),
"Successfully built routing graph");
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());
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);
354 health_kv_[
"dt_buildEnrichedRouteMessage"] = std::to_string(dt);
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;
373 if (health_kv_snapshot.count(
"map_loaded") > 0 && health_kv_snapshot[
"map_loaded"] ==
"false") {
375 health_status = diagnostic_msgs::msg::DiagnosticStatus::ERROR;
376 health_msg =
"Map not loaded";
377 }
else if (health_kv_snapshot.count(
"latest_error") > 0) {
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) {
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) {
387 double dt = std::stod(health_kv_snapshot[
"dt_buildEnrichedRouteMessage"]);
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";
411 std::vector<Eigen::Vector2d> reference_line;
414 if (route_element.lane_elements.empty()) {
417 reference_line.push_back(
toEigen2d(route_element.lane_elements.front().reference_pose.position));
426 const rclcpp_action::GoalUUID& uuid, route_planning_msgs::action::PlanRoute::Goal::ConstSharedPtr goal) {
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());
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;
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();
447 RCLCPP_ERROR(this->get_logger(),
"Failed to plan route to destination, rejecting request");
448 return rclcpp_action::GoalResponse::REJECT;
453 RCLCPP_INFO(this->get_logger(),
"Successfully planned route to destination (%.3fs)", dt);
457 RCLCPP_WARN(this->get_logger(),
"Existing action detected, aborting before accepting new goal");
464 return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE;
468 const std::shared_ptr<rclcpp_action::ServerGoalHandle<route_planning_msgs::action::PlanRoute>> goal_handle) {
471 RCLCPP_INFO(this->get_logger(),
"Received request to cancel action goal");
474 return rclcpp_action::CancelResponse::ACCEPT;
483 const std::shared_ptr<rclcpp_action::ServerGoalHandle<route_planning_msgs::action::PlanRoute>> goal_handle) {
493 action_feedback_ = std::make_shared<route_planning_msgs::action::PlanRoute::Feedback>();
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>();
502 action_result_->time_traveled = rclcpp::Duration::from_seconds(0.0);
513 const std::shared_ptr<rclcpp_action::ServerGoalHandle<route_planning_msgs::action::PlanRoute>> goal_handle) {
514 RCLCPP_INFO(this->get_logger(),
"Executing action goal");
522 bool has_reached_destination =
false;
523 while (goal_handle->is_executing() && !goal_handle->is_canceling() && !has_reached_destination) {
529 rclcpp::Duration::from_seconds(route_planning_msgs::route_access::estimateRemainingTime(
latest_full_route_msg_));
537 bool has_traveled_sufficient_distance =
539 has_reached_destination = (is_close_to_destination && has_traveled_sufficient_distance);
543 if (!has_reached_destination) {
544 feedback_rate.sleep();
554 if (goal_handle->is_canceling()) {
560 RCLCPP_INFO(this->get_logger(),
"Goal canceled");
561 }
else if (!goal_handle->is_executing()) {
566 RCLCPP_INFO(this->get_logger(),
"Goal aborted");
567 }
else if (rclcpp::ok()) {
574 RCLCPP_INFO(this->get_logger(),
"Goal succeeded");
579 const std::vector<geometry_msgs::msg::PointStamped>& intermediate_destinations) {
581 RCLCPP_ERROR(this->get_logger(),
"Cannot plan route, map not loaded by '%s'",
ll2_map_server_name_.c_str());
586 geometry_msgs::msg::PointStamped destination_map_stamped;
587 if (destination.header.frame_id !=
ll2_interface_->map_frame_id_) {
589 destination_map_stamped =
591 }
catch (tf2::TransformException& ex) {
592 std::stringstream ss;
593 ss <<
"Could not transform destination from frame '" << destination.header.frame_id <<
"' to frame '"
595 RCLCPP_ERROR_STREAM(this->get_logger(), ss.str());
603 destination_map_stamped = destination;
605 geometry_msgs::msg::Point& destination_map = destination_map_stamped.point;
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_) {
613 intermediate_map_stamped =
615 }
catch (tf2::TransformException& ex) {
616 std::stringstream ss;
617 ss <<
"Could not transform intermediate destination from frame '" << intermediate.header.frame_id <<
"' to frame '"
619 RCLCPP_ERROR_STREAM(this->get_logger(), ss.str());
626 intermediate_destinations_map.push_back(intermediate_map_stamped.point);
628 intermediate_destinations_map.push_back(intermediate.point);
634 lanelet::traffic_rules::TrafficRulesPtr traffic_rules =
getTrafficRules();
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",
643 std::stringstream ss;
644 ss <<
"Ego data frame '" <<
latest_ego_data_.header.frame_id <<
"' does not match map frame '"
646 RCLCPP_ERROR_STREAM(this->get_logger(), ss.str());
655 lanelet::ConstLanelet ego_ll;
659 std::string msg =
"Failed to find lanelet at ego position";
660 RCLCPP_ERROR_STREAM(this->get_logger(), msg);
667 Eigen::Vector2d ego_ll_position =
671 lanelet::ConstLanelet destination_ll;
673 destination_ll = *result;
675 std::string msg =
"Failed to find lanelet at destination";
676 RCLCPP_ERROR_STREAM(this->get_logger(), msg);
683 Eigen::Vector2d destination_ll_position =
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;
692 intermediate_destination_lls.push_back(*result);
693 intermediate_destinations_on_route.push_back(intermediate);
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());
707 lanelet::ConstLanelet undershot_ego_ll =
709 lanelet::ConstLanelet overshot_destination_ll =
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);
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());
738 route_planning_msgs::msg::Route route_msg;
745 const lanelet::routing::LaneletPath shortest_path =
latest_route_.shortestPath();
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);
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;
760 const bool changes_lane_from_prev_point =
764 changes_lane_to_next_point ? point : next_point);
765 accumulated_distance += (point - prev_point).norm();
767 accumulated_distance, changes_lane_to_next_point,
782 route_msg.starting_route_element_idx =
785 route_msg.starting_route_element_idx,
true,
true);
786 route_msg.destination_route_element_idx =
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");
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);
815 const std::vector<size_t> retained_indices =
817 if (retained_indices.empty()) {
818 RCLCPP_WARN(this->get_logger(),
"Global reference line is empty, not building global route");
825 for (
const size_t retained_idx : retained_indices) {
831 lane_element.has_following_lane_idx =
true;
832 lane_element.following_lane_idx = 0;
839 RCLCPP_INFO(this->get_logger(),
"Built global route with %ld/%ld route elements", retained_indices.size(),
840 global_reference_line.size());
850 const size_t global_closest_point =
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()) {
864 std::vector<std::vector<int>>(route_elements.size());
865 const lanelet::routing::LaneletPath shortest_path =
latest_route_.shortestPath();
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];
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])
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])
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;
892 const bool changes_lane_from_prev_point =
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;
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());
910 auto lanelet_projected_points =
912 std::vector<lanelet::ConstLanelet>{lanelet}, this->get_logger())[0];
914 point, prev_point_for_projection, next_point_for_projection, adjacent_left_lanelets, this->get_logger());
916 point, prev_point_for_projection, next_point_for_projection, adjacent_right_lanelets, this->get_logger());
919 const lanelet::ConstLanelet& lanelet_of_next_point =
922 int following_lane_idx_offset = 0;
924 following_lane_idx_offset = *result;
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;
933 Eigen::Vector2d drivable_space_left, drivable_space_right;
934 std::tie(drivable_space_left, drivable_space_right) =
939 auto regulatory_element_extraction =
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;
953 size_t lane_element_idx = route_element_msg.lane_elements.size();
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));
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;
975 std::tie(lane_element_msg.suggested_turn_signal,
978 route_element_msg.lane_elements.push_back(lane_element_msg);
979 lane_element_idx = route_element_msg.lane_elements.size();
983 route_planning_msgs::msg::LaneElement centerline_lane_element_msg;
984 centerline_lane_element_msg.reference_pose.position =
toRos(
to3d(point, point_z));
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;
999 std::tie(centerline_lane_element_msg.suggested_turn_signal,
1002 route_element_msg.lane_elements.push_back(centerline_lane_element_msg);
1003 lane_element_idx = route_element_msg.lane_elements.size();
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));
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;
1025 std::tie(lane_element_msg.suggested_turn_signal,
1028 route_element_msg.lane_elements.push_back(lane_element_msg);
1029 lane_element_idx = route_element_msg.lane_elements.size();
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;
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;
1076 rclcpp::init(argc, argv);
1077 rclcpp::spin(std::make_shared<lanelet2_route_planning::Lanelet2RoutePlanning>());
Lanelet2RoutePlanning()
Constructor.
double sampling_distance_
Distance between resampled points along route [m] (parameter)
rcl_interfaces::msg::SetParametersResult parametersCallback(const std::vector< rclcpp::Parameter > ¶meters)
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 ¶m, 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.
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.
double distanceTraveled(const route_planning_msgs::msg::Route &route)
Computes the traveled distance along the route.
lanelet::traffic_rules::TrafficRulesPtr getTrafficRules()
Get traffic rules.
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.
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.
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.
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.
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.
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.
Eigen::Vector3d to3d(const Eigen::Vector2d &point)
Converts a 2D Eigen point to a 3D Eigen point.
constexpr bool is_vector_v
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.
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.
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.
uint8_t speedLimit(const lanelet::ConstLanelet &lanelet, const bool consider_regulatory_elements=true)
Extracts the speed limit of a lanelet.
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.
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.
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.
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...
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.
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.
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.
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.
std::tuple< uint8_t, int > suggestedTurnSignal(const lanelet::ConstLanelet &lanelet, const rclcpp::Logger &logger)
Extracts the suggested turn signal of a lanelet.
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.
A local route window and its offset in the complete route.
route_planning_msgs::msg::Route route
double max_acceptable_timestamp_delta
Maximum acceptable difference between message timestamp and receipt time (in seconds)
double min_frequency
Minimum acceptable frequency.
double max_frequency
Maximum acceptable frequency.
double min_acceptable_timestamp_delta
Minimum acceptable difference between message timestamp and receipt time (in seconds)