Autoware.Auto
localization_node.hpp
Go to the documentation of this file.
1 // Copyright 2019 the Autoware Foundation
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //    http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 //
15 // Co-developed by Tier IV, Inc. and Apex.AI, Inc.
16 
17 #ifndef LOCALIZATION_NODES__LOCALIZATION_NODE_HPP_
18 #define LOCALIZATION_NODES__LOCALIZATION_NODE_HPP_
19 
22 #include <rclcpp/rclcpp.hpp>
23 #include <tf2/buffer_core.h>
24 #include <tf2_ros/transform_listener.h>
25 #include <tf2_geometry_msgs/tf2_geometry_msgs.h>
26 #include <sensor_msgs/msg/point_cloud2.hpp>
31 #include <memory>
32 #include <string>
33 #include <tuple>
34 #include <utility>
35 
36 namespace autoware
37 {
38 namespace localization
39 {
40 namespace localization_nodes
41 {
44 
46 struct TopicQoS
47 {
48  std::string topic;
49  rclcpp::QoS qos;
50 };
51 
54 {
55  PUBLISH_TF,
57 };
58 
65 template<typename ObservationMsgT,
66  typename MapMsgT,
67  typename MapT,
68  typename LocalizerT,
69  typename PoseInitializerT,
72 class LOCALIZATION_NODES_PUBLIC RelativeLocalizerNode : public rclcpp::Node
73 {
74 public:
75  using PoseWithCovarianceStamped = geometry_msgs::msg::PoseWithCovarianceStamped;
78 
90  const std::string & node_name, const std::string & name_space,
91  const TopicQoS & observation_sub_config,
92  const TopicQoS & map_sub_config,
93  const TopicQoS & pose_pub_config,
94  const TopicQoS & initial_pose_sub_config,
95  const PoseInitializerT & pose_initializer,
97  : Node(node_name, name_space),
98  m_pose_initializer(pose_initializer),
99  m_tf_listener(m_tf_buffer, std::shared_ptr<rclcpp::Node>(this, [](auto) {}), false),
100  m_observation_sub(create_subscription<ObservationMsgT>(
101  observation_sub_config.topic,
102  observation_sub_config.qos,
103  [this](typename ObservationMsgT::ConstSharedPtr msg) {observation_callback(msg);})),
104  m_map_sub(
105  create_subscription<MapMsgT>(
106  map_sub_config.topic, map_sub_config.qos,
107  [this](typename MapMsgT::ConstSharedPtr msg) {map_callback(msg);})),
108  m_pose_publisher(
109  create_publisher<PoseWithCovarianceStamped>(
110  pose_pub_config.topic,
111  pose_pub_config.qos)),
112  m_initial_pose_sub(
113  create_subscription<PoseWithCovarianceStamped>(
114  initial_pose_sub_config.topic, initial_pose_sub_config.qos,
115  [this](const typename PoseWithCovarianceStamped::ConstSharedPtr msg) {
116  initial_pose_callback(msg);
117  })) {
118  if (publish_tf == LocalizerPublishMode::PUBLISH_TF) {
119  m_tf_publisher = create_publisher<tf2_msgs::msg::TFMessage>("/tf", pose_pub_config.qos);
120  }
121  }
122 
123  // Constructor for ros2 components
124  // TODO(yunus.caliskan): refactor constructors together reduce the repeated code.
126  const std::string & node_name,
127  const rclcpp::NodeOptions & options,
128  const PoseInitializerT & pose_initializer)
129  : Node(node_name, options),
130  m_pose_initializer(pose_initializer),
131  m_tf_listener(m_tf_buffer, std::shared_ptr<rclcpp::Node>(this, [](auto) {}), false),
132  m_observation_sub(create_subscription<ObservationMsgT>(
133  "points_in",
134  rclcpp::QoS{rclcpp::KeepLast{
135  static_cast<size_t>(declare_parameter("observation_sub.history_depth").template
136  get<size_t>())}},
137  [this](typename ObservationMsgT::ConstSharedPtr msg) {observation_callback(msg);})),
138  m_map_sub(
139  create_subscription<MapMsgT>(
140  "ndt_map",
141  rclcpp::QoS{rclcpp::KeepLast{
142  static_cast<size_t>(declare_parameter("map_sub.history_depth").
143  template get<size_t>())}}.transient_local(),
144  [this](typename MapMsgT::ConstSharedPtr msg) {map_callback(msg);})),
145  m_pose_publisher(
146  create_publisher<PoseWithCovarianceStamped>(
147  "ndt_pose",
148  rclcpp::QoS{rclcpp::KeepLast{
149  static_cast<size_t>(declare_parameter(
150  "pose_pub.history_depth").template get<size_t>())}})),
151  m_initial_pose_sub(
152  create_subscription<PoseWithCovarianceStamped>(
153  "initialpose",
154  rclcpp::QoS{rclcpp::KeepLast{10}},
155  [this](const typename PoseWithCovarianceStamped::ConstSharedPtr msg) {
156  initial_pose_callback(msg);
157  }))
158  {
159  init();
160  }
161 
167  const std::string & node_name, const std::string & name_space,
168  const PoseInitializerT & pose_initializer)
169  : Node(node_name, name_space),
170  m_pose_initializer(pose_initializer),
171  m_tf_listener(m_tf_buffer, std::shared_ptr<rclcpp::Node>(this, [](auto) {}), false),
172  m_observation_sub(create_subscription<ObservationMsgT>(
173  "points_in",
174  rclcpp::QoS{rclcpp::KeepLast{
175  static_cast<size_t>(declare_parameter("observation_sub.history_depth").template
176  get<size_t>())}},
177  [this](typename ObservationMsgT::ConstSharedPtr msg) {observation_callback(msg);})),
178  m_map_sub(
179  create_subscription<MapMsgT>(
180  "ndt_map",
181  rclcpp::QoS{rclcpp::KeepLast{
182  static_cast<size_t>(declare_parameter("map_sub.history_depth").
183  template get<size_t>())}}.transient_local(),
184  [this](typename MapMsgT::ConstSharedPtr msg) {map_callback(msg);})),
185  m_pose_publisher(
186  create_publisher<PoseWithCovarianceStamped>(
187  "ndt_pose",
188  rclcpp::QoS{rclcpp::KeepLast{
189  static_cast<size_t>(declare_parameter(
190  "pose_pub.history_depth").template get<size_t>())}})),
191  m_initial_pose_sub(
192  create_subscription<PoseWithCovarianceStamped>(
193  "initialpose",
194  rclcpp::QoS{rclcpp::KeepLast{10}},
195  [this](const typename PoseWithCovarianceStamped::ConstSharedPtr msg) {
196  initial_pose_callback(msg);
197  }))
198  {
199  init();
200  }
201 
203  const typename rclcpp::Publisher<PoseWithCovarianceStamped>::ConstSharedPtr get_publisher()
204  {
205  return m_pose_publisher;
206  }
207 
208 protected:
211  void set_localizer(std::unique_ptr<LocalizerT> && localizer_ptr)
212  {
213  m_localizer_ptr = std::forward<std::unique_ptr<LocalizerT>>(localizer_ptr);
214  }
215 
216  void set_map(std::unique_ptr<MapT> && map_ptr)
217  {
218  m_map_ptr = std::forward<std::unique_ptr<MapT>>(map_ptr);
219  }
220 
222  virtual void on_bad_registration(std::exception_ptr eptr) // NOLINT
223  {
224  on_exception(eptr, "on_bad_registration");
225  }
226 
228  virtual void on_bad_map(std::exception_ptr eptr) // NOLINT
229  {
230  on_exception(eptr, "on_bad_map");
231  }
232 
233  void on_exception(std::exception_ptr eptr, const std::string & error_source) // NOLINT
234  {
235  try {
236  if (eptr) {
237  std::rethrow_exception(eptr);
238  } else {
239  RCLCPP_ERROR(get_logger(), error_source + ": error nullptr");
240  }
241  } catch (const std::exception & e) {
242  RCLCPP_ERROR(get_logger(), e.what());
243  }
244  }
245 
247  virtual void on_observation_with_invalid_map(typename ObservationMsgT::ConstSharedPtr)
248  {
249  RCLCPP_WARN(
250  get_logger(), "Received observation without a valid map, "
251  "ignoring the observation.");
252  }
253 
256  virtual void on_invalid_output(const PoseWithCovarianceStamped & pose)
257  {
258  (void) pose;
259  RCLCPP_WARN(
260  get_logger(), "Relative localizer has an invalid pose estimate. "
261  "The result is ignored.");
262  }
263 
264 
271  virtual bool validate_output(
272  const RegistrationSummary & summary,
273  const PoseWithCovarianceStamped & pose, const TransformStamped & guess)
274  {
275  (void) summary;
276  (void) pose;
277  (void) guess;
278  return true;
279  }
280 
281 private:
283  template<typename PtrT>
284  void assert_ptr_not_null(const PtrT & ptr, const std::string & name) const
285  {
286  if (!ptr) {
287  throw std::runtime_error(
288  name + " pointer is null. Make sure it is properly initialized before using.");
289  }
290  }
291 
292  void init()
293  {
294  if (declare_parameter("publish_tf").template get<bool>()) {
295  m_tf_publisher = create_publisher<tf2_msgs::msg::TFMessage>(
296  "/tf",
297  rclcpp::QoS{rclcpp::KeepLast{m_pose_publisher->get_queue_size()}});
298  }
299 
300  if (declare_parameter("init_hack.enabled", false)) {
302  // TODO(yunus.caliskan): Remove in #425
303  // Since this hack is only needed for the demo, it is not provided in the non-ros constructor.
304  geometry_msgs::msg::TransformStamped init_hack_transform;
305  auto & tf = init_hack_transform.transform;
306  tf.rotation.x = declare_parameter("init_hack.quaternion.x").template get<float64_t>();
307  tf.rotation.y = declare_parameter("init_hack.quaternion.y").template get<float64_t>();
308  tf.rotation.z = declare_parameter("init_hack.quaternion.z").template get<float64_t>();
309  tf.rotation.w = declare_parameter("init_hack.quaternion.w").template get<float64_t>();
310  tf.translation.x = declare_parameter("init_hack.translation.x").template get<float64_t>();
311  tf.translation.y = declare_parameter("init_hack.translation.y").template get<float64_t>();
312  tf.translation.z = declare_parameter("init_hack.translation.z").template get<float64_t>();
313  init_hack_transform.header.frame_id = "map";
314  init_hack_transform.child_frame_id = "base_link";
315  m_external_pose = init_hack_transform;
316  m_external_pose_available = true;
317  m_use_hack = true;
318  // we currently need the hack for the AVP demo MS2.
320  }
321  }
322 
324  virtual void handle_registration_summary(const RegistrationSummary &) {}
325 
328  void observation_callback(typename ObservationMsgT::ConstSharedPtr msg_ptr)
329  {
330  // Check to ensure the pointers are initialized.
331  assert_ptr_not_null(m_localizer_ptr, "localizer");
332  assert_ptr_not_null(m_map_ptr, "map");
333 
334  if (!m_map_ptr->valid()) {
335  on_observation_with_invalid_map(msg_ptr);
336  return;
337  }
338 
339  const auto observation_time = ::time_utils::from_message(get_stamp(*msg_ptr));
340  const auto & observation_frame = get_frame_id(*msg_ptr);
341  const auto & map_frame = m_map_ptr->frame_id();
342 
343  try {
345  if (m_external_pose_available) {
346  // If someone set a transform and then requests a different transform, that's an error
347  if (m_external_pose.header.frame_id != map_frame ||
348  m_external_pose.child_frame_id != observation_frame)
349  {
350  throw std::runtime_error(
351  "The pose initializer's set_external_pose() "
352  "and guess() methods were called with different frames.");
353  }
354  m_external_pose_available = false;
355  initial_guess = m_external_pose;
356  initial_guess.header.stamp = get_stamp(*msg_ptr);
357  } else {
358  initial_guess =
359  m_pose_initializer.guess(m_tf_buffer, observation_time, map_frame, observation_frame);
360  }
361 
362  RegistrationSummary summary{};
363  const auto pose_out =
364  m_localizer_ptr->register_measurement(*msg_ptr, initial_guess, *m_map_ptr, &summary);
365  if (validate_output(summary, pose_out, initial_guess)) {
366  m_pose_publisher->publish(pose_out);
367  // This is to be used when no state estimator or alternative source of
368  // localization is available.
369  if (m_tf_publisher) {
370  publish_tf(pose_out);
371  // republish point cloud so visualization has no issues with the timestamp
372  // being too new (no transform yet). Reset the timestamp to zero so visualization
373  // is not bothered if odom->base_link transformation is available
374  // only at different time stamps.
375  auto msg = *msg_ptr;
376  msg.header.stamp = time_utils::to_message(tf2::TimePointZero);
377  m_obs_republisher->publish(msg);
378  }
379 
380  handle_registration_summary(summary);
381  } else {
382  on_invalid_output(pose_out);
383  }
384  } catch (...) {
385  // TODO(mitsudome-r) remove this hack in #458
386  if (m_tf_publisher && m_use_hack) {
387  republish_tf(get_stamp(*msg_ptr));
388  }
389  on_bad_registration(std::current_exception());
390  }
391  }
392 
395  void map_callback(typename MapMsgT::ConstSharedPtr msg_ptr)
396  {
397  assert_ptr_not_null(m_map_ptr, "map");
398  try {
399  m_map_ptr->set(*msg_ptr);
400  } catch (...) {
401  on_bad_map(std::current_exception());
402  }
403  }
404 
406  void publish_tf(const PoseWithCovarianceStamped & pose_msg)
407  {
408  const auto & pose = pose_msg.pose.pose;
409  const auto & map_frame_id = m_map_ptr->frame_id();
410  tf2::Quaternion rotation{pose.orientation.x, pose.orientation.y, pose.orientation.z,
411  pose.orientation.w};
412  tf2::Vector3 translation{pose.position.x, pose.position.y, pose.position.z};
413  const tf2::Transform map_base_link_transform{rotation, translation};
414 
415  // Wait for odom to base_link transform to be available
416  bool odom_to_bl_found = m_tf_buffer.canTransform("odom", "base_link", tf2::TimePointZero);
417 
418  while (!odom_to_bl_found) {
419  RCLCPP_INFO(get_logger(), "Waiting for odom to base_link transform to be available.");
420  std::this_thread::sleep_for(std::chrono::seconds(1));
421  odom_to_bl_found = m_tf_buffer.canTransform("odom", "base_link", tf2::TimePointZero);
422  }
423 
425  try {
426  odom_tf = m_tf_buffer.lookupTransform(
427  "odom", "base_link",
428  time_utils::from_message(pose_msg.header.stamp));
429  } catch (const tf2::ExtrapolationException &) {
430  odom_tf = m_tf_buffer.lookupTransform("odom", "base_link", tf2::TimePointZero);
431  }
432  tf2::Quaternion odom_rotation{odom_tf.transform.rotation.x,
433  odom_tf.transform.rotation.y, odom_tf.transform.rotation.z, odom_tf.transform.rotation.w};
434  tf2::Vector3 odom_translation{odom_tf.transform.translation.x, odom_tf.transform.translation.y,
435  odom_tf.transform.translation.z};
436  const tf2::Transform odom_base_link_transform{odom_rotation, odom_translation};
437 
438  const auto map_odom_tf = map_base_link_transform * odom_base_link_transform.inverse();
439 
440  tf2_msgs::msg::TFMessage tf_message;
442  tf_stamped.header.stamp = pose_msg.header.stamp;
443  tf_stamped.header.frame_id = map_frame_id;
444  tf_stamped.child_frame_id = "odom";
445  const auto & tf_trans = map_odom_tf.getOrigin();
446  const auto & tf_rot = map_odom_tf.getRotation();
447  tf_stamped.transform.translation.set__x(tf_trans.x()).set__y(tf_trans.y()).
448  set__z(tf_trans.z());
449  tf_stamped.transform.rotation.set__x(tf_rot.x()).set__y(tf_rot.y()).set__z(tf_rot.z()).
450  set__w(tf_rot.w());
451  tf_message.transforms.push_back(tf_stamped);
452  m_tf_publisher->publish(tf_message);
453  }
454 
455  // TODO(mitsudome-r) remove this hack in #458
457  void republish_tf(builtin_interfaces::msg::Time stamp)
458  {
459  // no need to check the m_map_ptr for null as it is already done in the callbacks.
460  auto map_odom_tf = m_tf_buffer.lookupTransform(
461  m_map_ptr->frame_id(), "odom",
462  tf2::TimePointZero);
463  map_odom_tf.header.stamp = stamp;
464  tf2_msgs::msg::TFMessage tf_message;
465  tf_message.transforms.push_back(map_odom_tf);
466  m_tf_publisher->publish(tf_message);
467  }
468 
469  void initial_pose_callback(const typename PoseWithCovarianceStamped::ConstSharedPtr msg_ptr)
470  {
471  // The child frame is implicitly base_link.
472  // Ensure the parent frame is the map frame
473  assert_ptr_not_null(m_map_ptr, "map");
474  const std::string & map_frame = m_map_ptr->frame_id();
475  if (!m_tf_buffer.canTransform(map_frame, msg_ptr->header.frame_id, tf2::TimePointZero)) {
476  RCLCPP_ERROR(
477  get_logger(),
478  "Failed to find transform from %s to %s frame. Failed to give initial pose.",
479  msg_ptr->header.frame_id, map_frame);
480  return;
481  }
482  const auto transform = m_tf_buffer.lookupTransform(
483  map_frame, msg_ptr->header.frame_id,
484  tf2::TimePointZero);
485 
486 
487  geometry_msgs::msg::TransformStamped input_pose_stamped;
488  input_pose_stamped.header = msg_ptr->header;
489  input_pose_stamped.child_frame_id = "base_link";
490  input_pose_stamped.transform.rotation = msg_ptr->pose.pose.orientation;
491  input_pose_stamped.transform.translation.x = msg_ptr->pose.pose.position.x;
492  input_pose_stamped.transform.translation.y = msg_ptr->pose.pose.position.y;
493  input_pose_stamped.transform.translation.z = msg_ptr->pose.pose.position.z;
494  geometry_msgs::msg::TransformStamped transformed_pose_stamped;
495  tf2::doTransform(input_pose_stamped, transformed_pose_stamped, transform);
496 
497  // Note: The frame_id in the result is already the map_frame, no need to set it.
498  transformed_pose_stamped.child_frame_id = "base_link";
499 
500  // For future reference: We can't write this pose to /tf here.
501  // If this message comes from RViz and we're running in simulation, RViz
502  // and data coming from LGSVL (the observations) will sometimes have very
503  // large differences in their timestamps (e.g. RViz being 40s newer).
504  // If this pose was published to /tf, it would be seen as being way in the
505  // future and the localizer couldn't use it as its next initial pose.
506  // We'd need to know the current time before it can be published, and set the
507  // time in the header to a recent time.
508  m_external_pose = transformed_pose_stamped;
509  m_external_pose_available = true;
510  }
511 
512  std::unique_ptr<LocalizerT> m_localizer_ptr;
513  std::unique_ptr<MapT> m_map_ptr;
514  PoseInitializerT m_pose_initializer;
515  tf2::BufferCore m_tf_buffer;
516  tf2_ros::TransformListener m_tf_listener;
517  typename rclcpp::Subscription<ObservationMsgT>::SharedPtr m_observation_sub;
518  typename rclcpp::Subscription<MapMsgT>::SharedPtr m_map_sub;
519  typename rclcpp::Publisher<PoseWithCovarianceStamped>::SharedPtr m_pose_publisher;
520  typename rclcpp::Publisher<tf2_msgs::msg::TFMessage>::SharedPtr m_tf_publisher{nullptr};
521  typename rclcpp::Publisher<ObservationMsgT>::SharedPtr m_obs_republisher{
522  create_publisher<ObservationMsgT>("observation_republish", 10)};
523 
524  // Receive updates from "/initialpose" (e.g. rviz2)
525  typename rclcpp::Subscription<PoseWithCovarianceStamped>::SharedPtr m_initial_pose_sub;
526  // Stores "/initialpose", the timestamp is not used/valid
527  geometry_msgs::msg::TransformStamped m_external_pose;
528  bool m_external_pose_available{false};
529  bool m_use_hack{false};
530 };
531 } // namespace localization_nodes
532 } // namespace localization
533 } // namespace autoware
534 #endif // LOCALIZATION_NODES__LOCALIZATION_NODE_HPP_
const rclcpp::Publisher< PoseWithCovarianceStamped >::ConstSharedPtr get_publisher()
Get a const pointer of the output publisher. Can be used for matching against subscriptions.
Definition: localization_node.hpp:203
RelativeLocalizerNode(const std::string &node_name, const std::string &name_space, const PoseInitializerT &pose_initializer)
Definition: localization_node.hpp:166
geometry_msgs::msg::TransformStamped TransformStamped
Definition: motion_testing_publisher.hpp:37
RelativeLocalizerNode(const std::string &node_name, const std::string &name_space, const TopicQoS &observation_sub_config, const TopicQoS &map_sub_config, const TopicQoS &pose_pub_config, const TopicQoS &initial_pose_sub_config, const PoseInitializerT &pose_initializer, LocalizerPublishMode publish_tf=LocalizerPublishMode::NO_PUBLISH_TF)
Definition: localization_node.hpp:89
TIME_UTILS_PUBLIC builtin_interfaces::msg::Time to_message(std::chrono::system_clock::time_point t)
Convert from std::chrono::time_point to time message.
Definition: time_utils.cpp:67
void on_exception(std::exception_ptr eptr, const std::string &error_source)
Definition: localization_node.hpp:233
virtual void on_invalid_output(const PoseWithCovarianceStamped &pose)
Definition: localization_node.hpp:256
const TimeStamp & get_stamp(const T &msg) noexcept
Definition: message_adapters.hpp:101
std::string topic
Definition: localization_node.hpp:48
RelativeLocalizerNode(const std::string &node_name, const rclcpp::NodeOptions &options, const PoseInitializerT &pose_initializer)
Definition: localization_node.hpp:125
const std::string & get_frame_id(const T &msg) noexcept
Definition: message_adapters.hpp:83
Definition: optimized_registration_summary.hpp:32
virtual void on_bad_map(std::exception_ptr eptr)
Handle the exceptions during map setting.
Definition: localization_node.hpp:228
tf2_msgs::msg::TFMessage TFMessage
Definition: motion_testing_publisher.hpp:34
void doTransform(const geometry_msgs::msg::Point32 &t_in, geometry_msgs::msg::Point32 &t_out, const geometry_msgs::msg::TransformStamped &transform)
Apply a geometry_msgs TransformStamped to an geometry_msgs Point32 type. This function is a specializ...
Definition: tf2_autoware_auto_msgs.hpp:54
TIME_UTILS_PUBLIC std::chrono::system_clock::time_point from_message(builtin_interfaces::msg::Time t) noexcept
Convert from std::chrono::time_point from time message.
Definition: time_utils.cpp:80
This struct specifies the interface requirements of LocalizerT, given the InputT, MapT and SummaryT p...
Definition: localization_nodes/include/localization_nodes/constraints.hpp:95
virtual void on_bad_registration(std::exception_ptr eptr)
Handle the exceptions during registration.
Definition: localization_node.hpp:222
virtual void on_observation_with_invalid_map(typename ObservationMsgT::ConstSharedPtr)
Default behavior when an observation is received with no valid existing map.
Definition: localization_node.hpp:247
void set_map(std::unique_ptr< MapT > &&map_ptr)
Definition: localization_node.hpp:216
LocalizerPublishMode
Enum to specify if the localizer node must publish to /tf topic or not.
Definition: localization_node.hpp:53
Requires
Helper class for concepts-like API.
Definition: localization_nodes/include/localization_nodes/constraints.hpp:32
This file includes common helper functions.
geometry_msgs::msg::TransformStamped Transform
Definition: recordreplay_planner_node.hpp:56
rclcpp::QoS qos
Definition: localization_node.hpp:49
This struct specifies the interface requirements of MapT given MapMsgT parameter of a relative locali...
Definition: localization_nodes/include/localization_nodes/constraints.hpp:49
Helper struct that groups topic name and QoS setting for a publisher or subscription.
Definition: localization_node.hpp:46
virtual bool validate_output(const RegistrationSummary &summary, const PoseWithCovarianceStamped &pose, const TransformStamped &guess)
Definition: localization_node.hpp:271
void set_localizer(std::unique_ptr< LocalizerT > &&localizer_ptr)
Definition: localization_node.hpp:211
This file defines the lanelet2_map_provider_node class.
Definition: quick_sort.hpp:24