ros_robot_localization_listener.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2016, TNO IVS Helmond.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1. Redistributions of source code must retain the above copyright
10  * notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above
12  * copyright notice, this list of conditions and the following
13  * disclaimer in the documentation and/or other materials provided
14  * with the distribution.
15  * 3. Neither the name of the copyright holder nor the names of its
16  * contributors may be used to endorse or promote products derived
17  * from this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
22  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
23  * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
24  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
25  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
26  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
29  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30  * POSSIBILITY OF SUCH DAMAGE.
31  */
32 
35 
36 #include <map>
37 #include <string>
38 #include <vector>
39 
40 #include <Eigen/Dense>
44 #include <yaml-cpp/yaml.h>
46 
47 #include <XmlRpcException.h>
48 
49 namespace RobotLocalization
50 {
51 
52 FilterType filterTypeFromString(const std::string& filter_type_str)
53 {
54  if ( filter_type_str == "ekf" )
55  {
56  return FilterTypes::EKF;
57  }
58  else if ( filter_type_str == "ukf" )
59  {
60  return FilterTypes::UKF;
61  }
62  else
63  {
65  }
66 }
67 
69  nh_p_("robot_localization"),
70  odom_sub_(nh_, "odometry/filtered", 1),
71  accel_sub_(nh_, "accel/filtered", 1),
72  sync_(odom_sub_, accel_sub_, 10),
73  tf_listener_(tf_buffer_),
74  base_frame_id_(""),
75  world_frame_id_("")
76 {
77  int buffer_size;
78  nh_p_.param("buffer_size", buffer_size, 10);
79 
80  std::string param_ns;
81  nh_p_.param("parameter_namespace", param_ns, nh_p_.getNamespace());
82 
83  ros::NodeHandle nh_param(param_ns);
84 
85  std::string filter_type_str;
86  nh_param.param("filter_type", filter_type_str, std::string("ekf"));
87  FilterType filter_type = filterTypeFromString(filter_type_str);
88  if ( filter_type == FilterTypes::NotDefined )
89  {
90  ROS_ERROR("RosRobotLocalizationListener: Parameter filter_type invalid");
91  return;
92  }
93 
94  // Load up the process noise covariance (from the launch file/parameter server)
95  // TODO(someone): this code is copied from ros_filter. In a refactor, this could be moved to a function in
96  // ros_filter_utilities
97  Eigen::MatrixXd process_noise_covariance(STATE_SIZE, STATE_SIZE);
98  process_noise_covariance.setZero();
99  XmlRpc::XmlRpcValue process_noise_covar_config;
100 
101  if (!nh_param.hasParam("process_noise_covariance"))
102  {
103  ROS_FATAL_STREAM("Process noise covariance not found in the robot localization listener config (namespace " <<
104  nh_param.getNamespace() << ")! Use the ~parameter_namespace to specify the parameter namespace.");
105  }
106  else
107  {
108  try
109  {
110  nh_param.getParam("process_noise_covariance", process_noise_covar_config);
111 
112  ROS_ASSERT(process_noise_covar_config.getType() == XmlRpc::XmlRpcValue::TypeArray);
113 
114  int mat_size = process_noise_covariance.rows();
115 
116  for (int i = 0; i < mat_size; i++)
117  {
118  for (int j = 0; j < mat_size; j++)
119  {
120  try
121  {
122  // These matrices can cause problems if all the types
123  // aren't specified with decimal points. Handle that
124  // using string streams.
125  std::ostringstream ostr;
126  process_noise_covar_config[mat_size * i + j].write(ostr);
127  std::istringstream istr(ostr.str());
128  istr >> process_noise_covariance(i, j);
129  }
130  catch(XmlRpc::XmlRpcException &e)
131  {
132  throw e;
133  }
134  catch(...)
135  {
136  throw;
137  }
138  }
139  }
140 
141  ROS_DEBUG_STREAM("Process noise covariance is:\n" << process_noise_covariance << "\n");
142  }
143  catch (XmlRpc::XmlRpcException &e)
144  {
145  ROS_ERROR_STREAM("ERROR reading robot localization listener config: " <<
146  e.getMessage() <<
147  " for process_noise_covariance (type: " <<
148  process_noise_covar_config.getType() << ")");
149  }
150  }
151 
152  std::vector<double> filter_args;
153  nh_param.param("filter_args", filter_args, std::vector<double>());
154 
155  estimator_ = new RobotLocalizationEstimator(buffer_size, filter_type, process_noise_covariance, filter_args);
156 
158 
159  ROS_INFO_STREAM("Ros Robot Localization Listener: Listening to topics " <<
160  odom_sub_.getTopic() << " and " << accel_sub_.getTopic());
161 
162  // Wait until the base and world frames are set by the incoming messages
163  while (ros::ok() && base_frame_id_.empty())
164  {
165  ros::spinOnce();
166  ROS_INFO_STREAM_THROTTLE(1.0, "Ros Robot Localization Listener: Waiting for incoming messages on topics " <<
167  odom_sub_.getTopic() << " and " << accel_sub_.getTopic());
168  ros::Duration(0.1).sleep();
169  }
170 }
171 
173 {
174  delete estimator_;
175 }
176 
177 void RosRobotLocalizationListener::odomAndAccelCallback(const nav_msgs::Odometry& odom,
178  const geometry_msgs::AccelWithCovarianceStamped& accel)
179 {
180  // Instantiate a state that can be added to the robot localization estimator
181  EstimatorState state;
182 
183  // Set its time stamp and the state received from the messages
184  state.time_stamp = odom.header.stamp.toSec();
185 
186  // Get the base frame id from the odom message
187  if ( base_frame_id_.empty() )
188  base_frame_id_ = odom.child_frame_id;
189 
190  // Get the world frame id from the odom message
191  if ( world_frame_id_.empty() )
192  world_frame_id_ = odom.header.frame_id;
193 
194  // Pose: Position
195  state.state(StateMemberX) = odom.pose.pose.position.x;
196  state.state(StateMemberY) = odom.pose.pose.position.y;
197  state.state(StateMemberZ) = odom.pose.pose.position.z;
198 
199  // Pose: Orientation
200  tf2::Quaternion orientation_quat;
201  tf2::fromMsg(odom.pose.pose.orientation, orientation_quat);
202  double roll, pitch, yaw;
203  RosFilterUtilities::quatToRPY(orientation_quat, roll, pitch, yaw);
204 
205  state.state(StateMemberRoll) = roll;
206  state.state(StateMemberPitch) = pitch;
207  state.state(StateMemberYaw) = yaw;
208 
209  // Pose: Covariance
210  for ( unsigned int i = 0; i < POSE_SIZE; i++ )
211  {
212  for ( unsigned int j = 0; j < POSE_SIZE; j++ )
213  {
214  state.covariance(POSITION_OFFSET + i, POSITION_OFFSET + j) = odom.pose.covariance[i*POSE_SIZE + j];
215  }
216  }
217 
218  // Velocity: Linear
219  state.state(StateMemberVx) = odom.twist.twist.linear.x;
220  state.state(StateMemberVy) = odom.twist.twist.linear.y;
221  state.state(StateMemberVz) = odom.twist.twist.linear.z;
222 
223  // Velocity: Angular
224  state.state(StateMemberVroll) = odom.twist.twist.angular.x;
225  state.state(StateMemberVpitch) = odom.twist.twist.angular.y;
226  state.state(StateMemberVyaw) = odom.twist.twist.angular.z;
227 
228  // Velocity: Covariance
229  for ( unsigned int i = 0; i < TWIST_SIZE; i++ )
230  {
231  for ( unsigned int j = 0; j < TWIST_SIZE; j++ )
232  {
233  state.covariance(POSITION_V_OFFSET + i, POSITION_V_OFFSET + j) = odom.twist.covariance[i*TWIST_SIZE + j];
234  }
235  }
236 
237  // Acceleration: Linear
238  state.state(StateMemberAx) = accel.accel.accel.linear.x;
239  state.state(StateMemberAy) = accel.accel.accel.linear.y;
240  state.state(StateMemberAz) = accel.accel.accel.linear.z;
241 
242  // Acceleration: Angular is not available in state
243 
244  // Acceleration: Covariance
245  for ( unsigned int i = 0; i < ACCELERATION_SIZE; i++ )
246  {
247  for ( unsigned int j = 0; j < ACCELERATION_SIZE; j++ )
248  {
249  state.covariance(POSITION_A_OFFSET + i, POSITION_A_OFFSET + j) = accel.accel.covariance[i*TWIST_SIZE + j];
250  }
251  }
252 
253  // Add the state to the buffer, so that we can later interpolate between this and earlier states
254  estimator_->setState(state);
255 
256  return;
257 }
258 
259 bool findAncestorRecursiveYAML(YAML::Node& tree, const std::string& source_frame, const std::string& target_frame)
260 {
261  if ( source_frame == target_frame )
262  {
263  return true;
264  }
265 
266  std::string parent_frame = tree[source_frame]["parent"].Scalar();
267 
268  if ( parent_frame.empty() )
269  {
270  return false;
271  }
272 
273  return findAncestorRecursiveYAML(tree, parent_frame, target_frame);
274 }
275 
276 // Cache, assumption that the tree parent child order does not change over time
277 static std::map<std::string, std::vector<std::string> > ancestor_map;
278 static std::map<std::string, std::vector<std::string> > descendant_map;
279 bool findAncestor(const tf2_ros::Buffer& buffer, const std::string& source_frame, const std::string& target_frame)
280 {
281  // Check cache
282  const std::vector<std::string>& ancestors = ancestor_map[source_frame];
283  if (std::find(ancestors.begin(), ancestors.end(), target_frame) != ancestors.end())
284  {
285  return true;
286  }
287  const std::vector<std::string>& descendants = descendant_map[source_frame];
288  if (std::find(descendants.begin(), descendants.end(), target_frame) != descendants.end())
289  {
290  return false;
291  }
292 
293  std::stringstream frames_stream(buffer.allFramesAsYAML());
294  YAML::Node frames_yaml = YAML::Load(frames_stream);
295 
296  bool target_frame_is_ancestor = findAncestorRecursiveYAML(frames_yaml, source_frame, target_frame);
297  bool target_frame_is_descendant = findAncestorRecursiveYAML(frames_yaml, target_frame, source_frame);
298 
299  // Caching
300  if (target_frame_is_ancestor)
301  {
302  ancestor_map[source_frame].push_back(target_frame);
303  }
304  if (target_frame_is_descendant)
305  {
306  descendant_map[source_frame].push_back(target_frame);
307  }
308 
309  return target_frame_is_ancestor;
310 }
311 
312 
314  const std::string& frame_id,
315  Eigen::VectorXd& state,
316  Eigen::MatrixXd& covariance,
317  std::string world_frame_id) const
318 {
319  EstimatorState estimator_state;
320  state.resize(STATE_SIZE);
321  state.setZero();
322  covariance.resize(STATE_SIZE, STATE_SIZE);
323  covariance.setZero();
324 
325  if ( base_frame_id_.empty() || world_frame_id_.empty() )
326  {
327  if ( estimator_->getSize() == 0 )
328  {
329  ROS_WARN("Ros Robot Localization Listener: The base or world frame id is not set. "
330  "No odom/accel messages have come in.");
331  }
332  else
333  {
334  ROS_ERROR("Ros Robot Localization Listener: The base or world frame id is not set. "
335  "Are the child_frame_id and the header.frame_id in the odom messages set?");
336  }
337 
338  return false;
339  }
340 
341  if ( estimator_->getState(time, estimator_state) == EstimatorResults::ExtrapolationIntoPast )
342  {
343  ROS_WARN("Ros Robot Localization Listener: A state is requested at a time stamp older than the oldest in the "
344  "estimator buffer. The result is an extrapolation into the past. Maybe you should increase the buffer "
345  "size?");
346  }
347 
348  // If no world_frame_id is specified, we will default to the world frame_id of the received odometry message
349  if (world_frame_id.empty())
350  {
351  world_frame_id = world_frame_id_;
352  }
353 
354  if ( frame_id == base_frame_id_ && world_frame_id == world_frame_id_ )
355  {
356  // If the state of the base frame is requested and the world frame equals the world frame of the robot_localization
357  // estimator, we can simply return the state we got from the state estimator
358  state = estimator_state.state;
359  covariance = estimator_state.covariance;
360  return true;
361  }
362 
363  // - - - - - - - - - - - - - - - - - -
364  // Get the transformation between the requested world frame and the world_frame of the estimator
365  // - - - - - - - - - - - - - - - - - -
366  Eigen::Affine3d world_pose_requested_frame;
367 
368  // If the requested frame is the same as the tracker, set to identity
369  if (world_frame_id == world_frame_id_)
370  {
371  world_pose_requested_frame.setIdentity();
372  }
373  else
374  {
375  geometry_msgs::TransformStamped world_requested_to_world_transform;
376  try
377  {
378  world_requested_to_world_transform = tf_buffer_.lookupTransform(world_frame_id,
380  ros::Time(time),
381  ros::Duration(0.1)); // TODO(someone): magic #
382 
383  if ( findAncestor(tf_buffer_, world_frame_id, base_frame_id_) )
384  {
385  ROS_ERROR_STREAM("You are trying to get the state with respect to world frame " << world_frame_id <<
386  ", but this frame is a child of robot base frame " << base_frame_id_ <<
387  ", so this doesn't make sense.");
388  return false;
389  }
390  }
391  catch ( tf2::TransformException e )
392  {
393  ROS_WARN_STREAM("Ros Robot Localization Listener: Could not look up transform: " << e.what());
394  return false;
395  }
396 
397  // Convert to pose
398  tf::transformMsgToEigen(world_requested_to_world_transform.transform, world_pose_requested_frame);
399  }
400 
401  // - - - - - - - - - - - - - - - - - -
402  // Calculate the state of the requested frame from the state of the base frame.
403  // - - - - - - - - - - - - - - - - - -
404 
405  // First get the transform from base to target
406  geometry_msgs::TransformStamped base_to_target_transform;
407  try
408  {
409  base_to_target_transform = tf_buffer_.lookupTransform(base_frame_id_,
410  frame_id,
411  ros::Time(time),
412  ros::Duration(0.1)); // TODO(someone): magic number
413 
414  // Check that frame_id is a child of the base frame. If it is not, it does not make sense to request its state.
415  // Do this after tf lookup, so we know that there is a connection.
416  if ( !findAncestor(tf_buffer_, frame_id, base_frame_id_) )
417  {
418  ROS_ERROR_STREAM("You are trying to get the state of " << frame_id << ", but this frame is not a child of the "
419  "base frame: " << base_frame_id_ << ".");
420  return false;
421  }
422  }
423  catch ( tf2::TransformException e )
424  {
425  ROS_WARN_STREAM("Ros Robot Localization Listener: Could not look up transform: " << e.what());
426  return false;
427  }
428 
429  // And convert it to an eigen Affine transformation
430  Eigen::Affine3d target_pose_base;
431  tf::transformMsgToEigen(base_to_target_transform.transform, target_pose_base);
432 
433  // Then convert the base pose to an Eigen Affine transformation
434  Eigen::Vector3d base_position(estimator_state.state(StateMemberX),
435  estimator_state.state(StateMemberY),
436  estimator_state.state(StateMemberZ));
437 
438  Eigen::AngleAxisd roll_angle(estimator_state.state(StateMemberRoll), Eigen::Vector3d::UnitX());
439  Eigen::AngleAxisd pitch_angle(estimator_state.state(StateMemberPitch), Eigen::Vector3d::UnitY());
440  Eigen::AngleAxisd yaw_angle(estimator_state.state(StateMemberYaw), Eigen::Vector3d::UnitZ());
441 
442  Eigen::Quaterniond base_orientation(yaw_angle * pitch_angle * roll_angle);
443 
444  Eigen::Affine3d base_pose(Eigen::Translation3d(base_position) * base_orientation);
445 
446  // Now we can calculate the transform from odom to the requested frame (target)...
447  Eigen::Affine3d target_pose_odom = world_pose_requested_frame * base_pose * target_pose_base;
448 
449  // ... and put it in the output state
450  state(StateMemberX) = target_pose_odom.translation().x();
451  state(StateMemberY) = target_pose_odom.translation().y();
452  state(StateMemberZ) = target_pose_odom.translation().z();
453 
454  Eigen::Vector3d ypr = target_pose_odom.rotation().eulerAngles(2, 1, 0);
455 
456  state(StateMemberRoll) = ypr[2];
457  state(StateMemberPitch) = ypr[1];
458  state(StateMemberYaw) = ypr[0];
459 
460  // Now let's calculate the twist of the target frame
461  // First get the base's twist
462  Twist base_velocity;
463  Twist target_velocity_base;
464  base_velocity.linear = Eigen::Vector3d(estimator_state.state(StateMemberVx),
465  estimator_state.state(StateMemberVy),
466  estimator_state.state(StateMemberVz));
467  base_velocity.angular = Eigen::Vector3d(estimator_state.state(StateMemberVroll),
468  estimator_state.state(StateMemberVpitch),
469  estimator_state.state(StateMemberVyaw));
470 
471  // Then calculate the target frame's twist as a result of the base's twist.
472  /*
473  * We first calculate the coordinates of the velocity vectors (linear and angular) in the base frame. We have to keep
474  * in mind that a rotation of the base frame, together with the translational offset of the target frame from the base
475  * frame, induces a translational velocity of the target frame.
476  */
477  target_velocity_base.linear = base_velocity.linear + base_velocity.angular.cross(target_pose_base.translation());
478  target_velocity_base.angular = base_velocity.angular;
479 
480  // Now we can transform that to the target frame
481  Twist target_velocity;
482  target_velocity.linear = target_pose_base.rotation().transpose() * target_velocity_base.linear;
483  target_velocity.angular = target_pose_base.rotation().transpose() * target_velocity_base.angular;
484 
485  state(StateMemberVx) = target_velocity.linear(0);
486  state(StateMemberVy) = target_velocity.linear(1);
487  state(StateMemberVz) = target_velocity.linear(2);
488 
489  state(StateMemberVroll) = target_velocity.angular(0);
490  state(StateMemberVpitch) = target_velocity.angular(1);
491  state(StateMemberVyaw) = target_velocity.angular(2);
492 
493  // Rotate the covariance as well
494  Eigen::MatrixXd rot_6d(POSE_SIZE, POSE_SIZE);
495  rot_6d.setIdentity();
496 
497  rot_6d.block<POSITION_SIZE, POSITION_SIZE>(POSITION_OFFSET, POSITION_OFFSET) = target_pose_base.rotation();
499  target_pose_base.rotation();
500 
501  // Rotate the covariance
502  covariance.block<POSE_SIZE, POSE_SIZE>(POSITION_OFFSET, POSITION_OFFSET) =
503  rot_6d * estimator_state.covariance.eval() * rot_6d.transpose();
504 
505  return true;
506 }
507 
508 bool RosRobotLocalizationListener::getState(const ros::Time& ros_time, const std::string& frame_id,
509  Eigen::VectorXd& state, Eigen::MatrixXd& covariance,
510  const std::string& world_frame_id) const
511 {
512  double time;
513  if ( ros_time.isZero() )
514  {
515  ROS_INFO("Ros Robot Localization Listener: State requested at time = zero, returning state at current time");
516  time = ros::Time::now().toSec();
517  }
518  else
519  {
520  time = ros_time.toSec();
521  }
522 
523  return getState(time, frame_id, state, covariance, world_frame_id);
524 }
525 
527 {
528  return base_frame_id_;
529 }
530 
532 {
533  return world_frame_id_;
534 }
535 
536 } // namespace RobotLocalization
537 
std::string getTopic() const
const std::string & getMessage() const
bool findAncestor(const tf2_ros::Buffer &buffer, const std::string &source_frame, const std::string &target_frame)
const int STATE_SIZE
Global constants that define our state vector size and offsets to groups of values within that state...
Definition: filter_common.h:75
const int TWIST_SIZE
Definition: filter_common.h:85
const int POSITION_A_OFFSET
Definition: filter_common.h:80
bool findAncestorRecursiveYAML(YAML::Node &tree, const std::string &source_frame, const std::string &target_frame)
std::string world_frame_id_
Frame id received from the odometry message.
bool getState(const double time, const std::string &frame_id, Eigen::VectorXd &state, Eigen::MatrixXd &covariance, std::string world_frame_id="") const
Get a state from the localization estimator.
bool sleep() const
virtual geometry_msgs::TransformStamped lookupTransform(const std::string &target_frame, const std::string &source_frame, const ros::Time &time, const ros::Duration timeout) const
ros::NodeHandle nh_p_
A private handle to the ROS node.
message_filters::Subscriber< geometry_msgs::AccelWithCovarianceStamped > accel_sub_
Subscriber to the acceleration state topic (output of a filter node)
Type const & getType() const
const int POSITION_V_OFFSET
Definition: filter_common.h:78
#define ROS_WARN(...)
const std::string & getBaseFrameId() const
getBaseFrameId Returns the base frame id of the localization listener
void odomAndAccelCallback(const nav_msgs::Odometry &odom, const geometry_msgs::AccelWithCovarianceStamped &accel)
Callback for odom and accel.
FilterType filterTypeFromString(const std::string &filter_type_str)
const int ORIENTATION_SIZE
Definition: filter_common.h:87
static std::map< std::string, std::vector< std::string > > ancestor_map
static std::map< std::string, std::vector< std::string > > descendant_map
message_filters::TimeSynchronizer< nav_msgs::Odometry, geometry_msgs::AccelWithCovarianceStamped > sync_
Waits for both an Odometry and an Accel message before calling a single callback function.
Connection registerCallback(C &callback)
double time_stamp
Time at which this state is/was achieved.
#define ROS_FATAL_STREAM(args)
void setState(const EstimatorState &state)
Sets the current internal state of the listener.
const int POSITION_OFFSET
Definition: filter_common.h:76
#define ROS_INFO(...)
bool param(const std::string &param_name, T &param_val, const T &default_val) const
EstimatorResult getState(const double time, EstimatorState &state) const
Returns the state at a given time.
void fromMsg(const A &, B &b)
ROSCPP_DECL bool ok()
const std::string & getNamespace() const
const int ACCELERATION_SIZE
Definition: filter_common.h:88
const int POSITION_SIZE
Definition: filter_common.h:86
#define ROS_WARN_STREAM(args)
Eigen::VectorXd state
System state at time = time_stamp.
RobotLocalizationEstimator * estimator_
The core state estimator that facilitates inter- and extrapolation between buffered states...
#define ROS_DEBUG_STREAM(args)
std::string allFramesAsYAML(double current_time) const
const std::string & getWorldFrameId() const
getWorldFrameId Returns the world frame id of the localization listener
std::ostream & write(std::ostream &os) const
Eigen::MatrixXd covariance
System state covariance at time = time_stamp.
void transformMsgToEigen(const geometry_msgs::Transform &m, Eigen::Affine3d &e)
#define ROS_INFO_STREAM(args)
const int POSE_SIZE
Pose and twist messages each contain six variables.
Definition: filter_common.h:84
const int ORIENTATION_OFFSET
Definition: filter_common.h:77
bool getParam(const std::string &key, std::string &s) const
unsigned int getSize() const
Returns the current buffer size.
static Time now()
Robot Localization Estimator State.
tf2_ros::Buffer tf_buffer_
Tf buffer for looking up transforms.
bool hasParam(const std::string &key) const
#define ROS_ERROR_STREAM(args)
#define ROS_ASSERT(cond)
#define ROS_INFO_STREAM_THROTTLE(rate, args)
std::string base_frame_id_
Child frame id received from the Odometry message.
ROSCPP_DECL void spinOnce()
void quatToRPY(const tf2::Quaternion &quat, double &roll, double &pitch, double &yaw)
Utility method for converting quaternion to RPY.
message_filters::Subscriber< nav_msgs::Odometry > odom_sub_
Subscriber to the odometry state topic (output of a filter node)
#define ROS_ERROR(...)


robot_localization
Author(s): Tom Moore
autogenerated on Wed Feb 3 2021 03:34:02