navsat_transform.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2014, 2015, 2016, Charles River Analytics, Inc.
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 
38 
40 #include <XmlRpcException.h>
41 
42 #include <string>
43 
44 namespace RobotLocalization
45 {
47  magnetic_declination_(0.0),
48  utm_odom_tf_yaw_(0.0),
49  yaw_offset_(0.0),
50  transform_timeout_(ros::Duration(0)),
51  broadcast_utm_transform_(false),
52  broadcast_utm_transform_as_parent_frame_(false),
53  has_transform_odom_(false),
54  has_transform_gps_(false),
55  has_transform_imu_(false),
56  transform_good_(false),
57  gps_frame_id_(""),
58  gps_updated_(false),
59  odom_updated_(false),
60  publish_gps_(false),
61  use_odometry_yaw_(false),
62  use_manual_datum_(false),
63  world_frame_param_loaded_(false),
64  base_link_frame_param_loaded_(false),
65  zero_altitude_(false),
66  world_frame_id_("odom"),
67  base_link_frame_id_("base_link"),
68  utm_zone_(""),
69  tf_listener_(tf_buffer_)
70  {
73  }
74 
76  {
77  }
78 
80  {
81  ROS_INFO("Waiting for valid clock time...");
83  ROS_INFO("Valid clock time received. Starting node.");
84 
85  double frequency = 10.0;
86  double delay = 0.0;
87  double transform_timeout = 0.0;
88 
89  ros::NodeHandle nh;
90  ros::NodeHandle nh_priv("~");
91 
92  // Load the parameters we need
93  nh_priv.getParam("magnetic_declination_radians", magnetic_declination_);
94  nh_priv.param("yaw_offset", yaw_offset_, 0.0);
95  nh_priv.param("broadcast_utm_transform", broadcast_utm_transform_, false);
96  nh_priv.param("broadcast_utm_transform_as_parent_frame", broadcast_utm_transform_as_parent_frame_, false);
97  nh_priv.param("zero_altitude", zero_altitude_, false);
98  nh_priv.param("publish_filtered_gps", publish_gps_, false);
99  nh_priv.param("use_odometry_yaw", use_odometry_yaw_, false);
100  nh_priv.param("wait_for_datum", use_manual_datum_, false);
101  nh_priv.param("frequency", frequency, 10.0);
102  nh_priv.param("delay", delay, 0.0);
103  nh_priv.param("transform_timeout", transform_timeout, 0.0);
104  transform_timeout_.fromSec(transform_timeout);
105 
106  // Set frames if wait_for_datum = false
107  if (use_manual_datum_)
108  {
109  if (nh_priv.hasParam("world_frame"))
110  {
111  nh_priv.param("world_frame", world_frame_id_, std::string("odom"));
113  }
114  if (nh_priv.hasParam("base_link_frame"))
115  {
116  nh_priv.param("base_link_frame", base_link_frame_id_, std::string("base_link"));
118  }
119  }
120  else
121  {
122  if (nh_priv.hasParam("world_frame"))
123  {
124  ROS_WARN_STREAM("Parameter 'world_frame' is set and will be unused."
125  "This parameter is only used if parameter 'wait_for_datum' is true.");
126  }
127  if (nh_priv.hasParam("base_link_frame"))
128  {
129  ROS_WARN_STREAM("Parameter 'base_link_frame' is set and will be unused."
130  "This parameter is only used if parameter 'wait_for_datum' is true.");
131  }
132  }
133 
134  // Subscribe to the messages and services we need
136 
137  if (use_manual_datum_ && nh_priv.hasParam("datum"))
138  {
139  XmlRpc::XmlRpcValue datum_config;
140 
141  try
142  {
143  double datum_lat;
144  double datum_lon;
145  double datum_yaw;
146 
147  nh_priv.getParam("datum", datum_config);
148 
149  // Handle datum specification. Users should always specify a baseLinkFrameId_ in the
150  // datum config, but we had a release where it wasn't used, so we'll maintain compatibility.
152  ROS_ASSERT(datum_config.size() >= 3);
153 
154  if (datum_config.size() > 3)
155  {
156  ROS_WARN_STREAM("Deprecated datum parameter configuration detected. Only the first three parameters "
157  "(latitude, longitude, yaw) will be used. frame_ids will be derived from odometry and navsat inputs.");
158  }
159 
160  std::ostringstream ostr;
161  ostr << std::setprecision(20) << datum_config[0] << " " << datum_config[1] << " " << datum_config[2];
162  std::istringstream istr(ostr.str());
163  istr >> datum_lat >> datum_lon >> datum_yaw;
164 
165  // Try to resolve tf_prefix
166  std::string tf_prefix = "";
167  std::string tf_prefix_path = "";
168  if (nh_priv.searchParam("tf_prefix", tf_prefix_path))
169  {
170  nh_priv.getParam(tf_prefix_path, tf_prefix);
171  }
172 
173  // Append the tf prefix in a tf2-friendly manner
176 
177  robot_localization::SetDatum::Request request;
178  request.geo_pose.position.latitude = datum_lat;
179  request.geo_pose.position.longitude = datum_lon;
180  request.geo_pose.position.altitude = 0.0;
181  tf2::Quaternion quat;
182  quat.setRPY(0.0, 0.0, datum_yaw);
183  request.geo_pose.orientation = tf2::toMsg(quat);
184  robot_localization::SetDatum::Response response;
185  datumCallback(request, response);
186  }
187  catch (XmlRpc::XmlRpcException &e)
188  {
189  ROS_ERROR_STREAM("ERROR reading sensor config: " << e.getMessage() <<
190  " for process_noise_covariance (type: " << datum_config.getType() << ")");
191  }
192  }
193 
194  ros::Subscriber odom_sub = nh.subscribe("odometry/filtered", 1, &NavSatTransform::odomCallback, this);
195  ros::Subscriber gps_sub = nh.subscribe("gps/fix", 1, &NavSatTransform::gpsFixCallback, this);
196  ros::Subscriber imu_sub;
197 
199  {
200  imu_sub = nh.subscribe("imu/data", 1, &NavSatTransform::imuCallback, this);
201  }
202 
203  ros::Publisher gps_odom_pub = nh.advertise<nav_msgs::Odometry>("odometry/gps", 10);
204  ros::Publisher filtered_gps_pub;
205 
206  if (publish_gps_)
207  {
208  filtered_gps_pub = nh.advertise<sensor_msgs::NavSatFix>("gps/filtered", 10);
209  }
210 
211  // Sleep for the parameterized amount of time, to give
212  // other nodes time to start up (not always necessary)
213  ros::Duration start_delay(delay);
214  start_delay.sleep();
215 
216  ros::Rate rate(frequency);
217  while (ros::ok())
218  {
219  ros::spinOnce();
220 
221  if (!transform_good_)
222  {
224 
226  {
227  // Once we have the transform, we don't need the IMU
228  imu_sub.shutdown();
229  }
230  }
231  else
232  {
233  nav_msgs::Odometry gps_odom;
234  if (prepareGpsOdometry(gps_odom))
235  {
236  gps_odom_pub.publish(gps_odom);
237  }
238 
239  if (publish_gps_)
240  {
241  sensor_msgs::NavSatFix odom_gps;
242  if (prepareFilteredGps(odom_gps))
243  {
244  filtered_gps_pub.publish(odom_gps);
245  }
246  }
247  }
248 
249  rate.sleep();
250  }
251  }
252 
254  {
255  // Only do this if:
256  // 1. We haven't computed the odom_frame->utm_frame transform before
257  // 2. We've received the data we need
258  if (!transform_good_ &&
262  {
263  // The UTM pose we have is given at the location of the GPS sensor on the robot. We need to get the UTM pose of
264  // the robot's origin.
265  tf2::Transform transform_utm_pose_corrected;
266  if (!use_manual_datum_)
267  {
268  getRobotOriginUtmPose(transform_utm_pose_, transform_utm_pose_corrected, ros::Time(0));
269  }
270  else
271  {
272  transform_utm_pose_corrected = transform_utm_pose_;
273  }
274 
275  // Get the IMU's current RPY values. Need the raw values (for yaw, anyway).
277 
278  // Convert to RPY
279  double imu_roll;
280  double imu_pitch;
281  double imu_yaw;
282  mat.getRPY(imu_roll, imu_pitch, imu_yaw);
283 
284  /* The IMU's heading was likely originally reported w.r.t. magnetic north.
285  * However, all the nodes in robot_localization assume that orientation data,
286  * including that reported by IMUs, is reported in an ENU frame, with a 0 yaw
287  * value being reported when facing east and increasing counter-clockwise (i.e.,
288  * towards north). To make the world frame ENU aligned, where X is east
289  * and Y is north, we have to take into account three additional considerations:
290  * 1. The IMU may have its non-ENU frame data transformed to ENU, but there's
291  * a possibility that its data has not been corrected for magnetic
292  * declination. We need to account for this. A positive magnetic
293  * declination is counter-clockwise in an ENU frame. Therefore, if
294  * we have a magnetic declination of N radians, then when the sensor
295  * is facing a heading of N, it reports 0. Therefore, we need to add
296  * the declination angle.
297  * 2. To account for any other offsets that may not be accounted for by the
298  * IMU driver or any interim processing node, we expose a yaw offset that
299  * lets users work with navsat_transform_node.
300  * 3. UTM grid isn't aligned with True East\North. To account for the difference
301  * we need to add meridian convergence angle.
302  */
304 
305  ROS_INFO_STREAM("Corrected for magnetic declination of " << std::fixed << magnetic_declination_ <<
306  ", user-specified offset of " << yaw_offset_ <<
307  " and meridian convergence of " << utm_meridian_convergence_ << "." <<
308  " Transform heading factor is now " << imu_yaw);
309 
310  // Convert to tf-friendly structures
311  tf2::Quaternion imu_quat;
312  imu_quat.setRPY(0.0, 0.0, imu_yaw);
313 
314  // The transform order will be orig_odom_pos * orig_utm_pos_inverse * cur_utm_pos.
315  // Doing it this way will allow us to cope with having non-zero odometry position
316  // when we get our first GPS message.
317  tf2::Transform utm_pose_with_orientation;
318  utm_pose_with_orientation.setOrigin(transform_utm_pose_corrected.getOrigin());
319  utm_pose_with_orientation.setRotation(imu_quat);
320 
321  utm_world_transform_.mult(transform_world_pose_, utm_pose_with_orientation.inverse());
322 
324 
325  ROS_INFO_STREAM("Transform world frame pose is: " << transform_world_pose_);
326  ROS_INFO_STREAM("World frame->utm transform is " << utm_world_transform_);
327 
328  transform_good_ = true;
329 
330  // Send out the (static) UTM transform in case anyone else would like to use it.
332  {
333  geometry_msgs::TransformStamped utm_transform_stamped;
334  utm_transform_stamped.header.stamp = ros::Time::now();
335  utm_transform_stamped.header.frame_id = (broadcast_utm_transform_as_parent_frame_ ? "utm" : world_frame_id_);
336  utm_transform_stamped.child_frame_id = (broadcast_utm_transform_as_parent_frame_ ? world_frame_id_ : "utm");
337  utm_transform_stamped.transform = (broadcast_utm_transform_as_parent_frame_ ?
339  utm_transform_stamped.transform.translation.z = (zero_altitude_ ?
340  0.0 : utm_transform_stamped.transform.translation.z);
341  utm_broadcaster_.sendTransform(utm_transform_stamped);
342  }
343  }
344  }
345 
346  bool NavSatTransform::datumCallback(robot_localization::SetDatum::Request& request,
347  robot_localization::SetDatum::Response&)
348  {
349  // If we get a service call with a manual datum, even if we already computed the transform using the robot's
350  // initial pose, then we want to assume that we are using a datum from now on, and we want other methods to
351  // not attempt to transform the values we are specifying here.
352  use_manual_datum_ = true;
353 
354  transform_good_ = false;
355 
356  sensor_msgs::NavSatFix *fix = new sensor_msgs::NavSatFix();
357  fix->latitude = request.geo_pose.position.latitude;
358  fix->longitude = request.geo_pose.position.longitude;
359  fix->altitude = request.geo_pose.position.altitude;
360  fix->header.stamp = ros::Time::now();
361  fix->position_covariance[0] = 0.1;
362  fix->position_covariance[4] = 0.1;
363  fix->position_covariance[8] = 0.1;
364  fix->position_covariance_type = sensor_msgs::NavSatStatus::STATUS_FIX;
365  sensor_msgs::NavSatFixConstPtr fix_ptr(fix);
366  setTransformGps(fix_ptr);
367 
368  nav_msgs::Odometry *odom = new nav_msgs::Odometry();
369  odom->pose.pose.orientation.x = 0;
370  odom->pose.pose.orientation.y = 0;
371  odom->pose.pose.orientation.z = 0;
372  odom->pose.pose.orientation.w = 1;
373  odom->pose.pose.position.x = 0;
374  odom->pose.pose.position.y = 0;
375  odom->pose.pose.position.z = 0;
376  odom->header.frame_id = world_frame_id_;
377  odom->child_frame_id = base_link_frame_id_;
378  nav_msgs::OdometryConstPtr odom_ptr(odom);
379  setTransformOdometry(odom_ptr);
380 
381  sensor_msgs::Imu *imu = new sensor_msgs::Imu();
382  imu->orientation = request.geo_pose.orientation;
383  imu->header.frame_id = base_link_frame_id_;
384  sensor_msgs::ImuConstPtr imu_ptr(imu);
385  imuCallback(imu_ptr);
386 
387  return true;
388  }
389 
391  tf2::Transform &robot_utm_pose,
392  const ros::Time &transform_time)
393  {
394  robot_utm_pose.setIdentity();
395 
396  // Get linear offset from origin for the GPS
397  tf2::Transform offset;
401  transform_time,
403  offset);
404 
405  if (can_transform)
406  {
407  // Get the orientation we'll use for our UTM->world transform
408  tf2::Quaternion utm_orientation = transform_orientation_;
409  tf2::Matrix3x3 mat(utm_orientation);
410 
411  // Add the offsets
412  double roll;
413  double pitch;
414  double yaw;
415  mat.getRPY(roll, pitch, yaw);
417  utm_orientation.setRPY(roll, pitch, yaw);
418 
419  // Rotate the GPS linear offset by the orientation
420  // Zero out the orientation, because the GPS orientation is meaningless, and if it's non-zero, it will make the
421  // the computation of robot_utm_pose erroneous.
422  offset.setOrigin(tf2::quatRotate(utm_orientation, offset.getOrigin()));
424 
425  // Update the initial pose
426  robot_utm_pose = offset.inverse() * gps_utm_pose;
427  }
428  else
429  {
430  if (gps_frame_id_ != "")
431  {
432  ROS_WARN_STREAM_ONCE("Unable to obtain " << base_link_frame_id_ << "->" << gps_frame_id_ <<
433  " transform. Will assume navsat device is mounted at robot's origin");
434  }
435 
436  robot_utm_pose = gps_utm_pose;
437  }
438  }
439 
441  tf2::Transform &robot_odom_pose,
442  const ros::Time &transform_time)
443  {
444  robot_odom_pose.setIdentity();
445 
446  // Remove the offset from base_link
447  tf2::Transform gps_offset_rotated;
451  transform_time,
453  gps_offset_rotated);
454 
455  if (can_transform)
456  {
457  tf2::Transform robot_orientation;
461  transform_time,
463  robot_orientation);
464 
465  if (can_transform)
466  {
467  // Zero out rotation because we don't care about the orientation of the
468  // GPS receiver relative to base_link
469  gps_offset_rotated.setOrigin(tf2::quatRotate(robot_orientation.getRotation(), gps_offset_rotated.getOrigin()));
470  gps_offset_rotated.setRotation(tf2::Quaternion::getIdentity());
471  robot_odom_pose = gps_offset_rotated.inverse() * gps_odom_pose;
472  }
473  else
474  {
475  ROS_WARN_STREAM_THROTTLE(5.0, "Could not obtain " << world_frame_id_ << "->" << base_link_frame_id_ <<
476  " transform. Will not remove offset of navsat device from robot's origin.");
477  }
478  }
479  else
480  {
481  ROS_WARN_STREAM_THROTTLE(5.0, "Could not obtain " << base_link_frame_id_ << "->" << gps_frame_id_ <<
482  " transform. Will not remove offset of navsat device from robot's origin.");
483  }
484  }
485 
486  void NavSatTransform::gpsFixCallback(const sensor_msgs::NavSatFixConstPtr& msg)
487  {
488  gps_frame_id_ = msg->header.frame_id;
489 
490  if (gps_frame_id_.empty())
491  {
492  ROS_WARN_STREAM_ONCE("NavSatFix message has empty frame_id. Will assume navsat device is mounted at robot's "
493  "origin.");
494  }
495 
496  // Make sure the GPS data is usable
497  bool good_gps = (msg->status.status != sensor_msgs::NavSatStatus::STATUS_NO_FIX &&
498  !std::isnan(msg->altitude) &&
499  !std::isnan(msg->latitude) &&
500  !std::isnan(msg->longitude));
501 
502  if (good_gps)
503  {
504  // If we haven't computed the transform yet, then
505  // store this message as the initial GPS data to use
507  {
508  setTransformGps(msg);
509  }
510 
511  double utmX = 0;
512  double utmY = 0;
513  std::string utm_zone_tmp;
514  NavsatConversions::LLtoUTM(msg->latitude, msg->longitude, utmY, utmX, utm_zone_tmp);
515  latest_utm_pose_.setOrigin(tf2::Vector3(utmX, utmY, msg->altitude));
516  latest_utm_covariance_.setZero();
517 
518  // Copy the measurement's covariance matrix so that we can rotate it later
519  for (size_t i = 0; i < POSITION_SIZE; i++)
520  {
521  for (size_t j = 0; j < POSITION_SIZE; j++)
522  {
523  latest_utm_covariance_(i, j) = msg->position_covariance[POSITION_SIZE * i + j];
524  }
525  }
526 
527  gps_update_time_ = msg->header.stamp;
528  gps_updated_ = true;
529  }
530  }
531 
532  void NavSatTransform::imuCallback(const sensor_msgs::ImuConstPtr& msg)
533  {
534  // We need the baseLinkFrameId_ from the odometry message, so
535  // we need to wait until we receive it.
537  {
538  /* This method only gets called if we don't yet have the
539  * IMU data (the subscriber gets shut down once we compute
540  * the transform), so we can assumed that every IMU message
541  * that comes here is meant to be used for that purpose. */
542  tf2::fromMsg(msg->orientation, transform_orientation_);
543 
544  // Correct for the IMU's orientation w.r.t. base_link
545  tf2::Transform target_frame_trans;
548  msg->header.frame_id,
549  msg->header.stamp,
551  target_frame_trans);
552 
553  if (can_transform)
554  {
555  double roll_offset = 0;
556  double pitch_offset = 0;
557  double yaw_offset = 0;
558  double roll = 0;
559  double pitch = 0;
560  double yaw = 0;
561  RosFilterUtilities::quatToRPY(target_frame_trans.getRotation(), roll_offset, pitch_offset, yaw_offset);
563 
564  ROS_DEBUG_STREAM("Initial orientation is " << transform_orientation_);
565 
566  // Apply the offset (making sure to bound them), and throw them in a vector
567  tf2::Vector3 rpy_angles(FilterUtilities::clampRotation(roll - roll_offset),
568  FilterUtilities::clampRotation(pitch - pitch_offset),
569  FilterUtilities::clampRotation(yaw - yaw_offset));
570 
571  // Now we need to rotate the roll and pitch by the yaw offset value.
572  // Imagine a case where an IMU is mounted facing sideways. In that case
573  // pitch for the IMU's world frame is roll for the robot.
574  tf2::Matrix3x3 mat;
575  mat.setRPY(0.0, 0.0, yaw_offset);
576  rpy_angles = mat * rpy_angles;
577  transform_orientation_.setRPY(rpy_angles.getX(), rpy_angles.getY(), rpy_angles.getZ());
578 
579  ROS_DEBUG_STREAM("Initial corrected orientation roll, pitch, yaw is (" <<
580  rpy_angles.getX() << ", " << rpy_angles.getY() << ", " << rpy_angles.getZ() << ")");
581 
582  has_transform_imu_ = true;
583  }
584  }
585  }
586 
587  void NavSatTransform::odomCallback(const nav_msgs::OdometryConstPtr& msg)
588  {
589  if (world_frame_param_loaded_ && (world_frame_id_ != msg->header.frame_id))
590  {
591  ROS_WARN_STREAM_ONCE("World frame from odometry message (" << msg->header.frame_id << ")"
592  " does not match world frame loaded from parameter (" << world_frame_id_ << ")."
593  " World frame updating to " << msg->header.frame_id);
594  }
595  if (base_link_frame_param_loaded_ && (base_link_frame_id_ != msg->child_frame_id))
596  {
597  ROS_WARN_STREAM_ONCE("Base link frame from odometry message (" << msg->child_frame_id << ")"
598  " does not match base link frame loaded from parameter (" << base_link_frame_id_ << ")."
599  " Base link frame updating to " << msg->child_frame_id);
600  }
601  world_frame_id_ = msg->header.frame_id;
602  base_link_frame_id_ = msg->child_frame_id;
603 
605  {
607  }
608 
609  tf2::fromMsg(msg->pose.pose, latest_world_pose_);
610  latest_odom_covariance_.setZero();
611  for (size_t row = 0; row < POSE_SIZE; ++row)
612  {
613  for (size_t col = 0; col < POSE_SIZE; ++col)
614  {
615  latest_odom_covariance_(row, col) = msg->pose.covariance[row * POSE_SIZE + col];
616  }
617  }
618 
619  odom_update_time_ = msg->header.stamp;
620  odom_updated_ = true;
621  }
622 
623 
624  bool NavSatTransform::prepareFilteredGps(sensor_msgs::NavSatFix &filtered_gps)
625  {
626  bool new_data = false;
627 
629  {
630  tf2::Transform odom_as_utm;
631 
634 
635  // Rotate the covariance as well
637  Eigen::MatrixXd rot_6d(POSE_SIZE, POSE_SIZE);
638  rot_6d.setIdentity();
639 
640  for (size_t rInd = 0; rInd < POSITION_SIZE; ++rInd)
641  {
642  rot_6d(rInd, 0) = rot.getRow(rInd).getX();
643  rot_6d(rInd, 1) = rot.getRow(rInd).getY();
644  rot_6d(rInd, 2) = rot.getRow(rInd).getZ();
645  rot_6d(rInd+POSITION_SIZE, 3) = rot.getRow(rInd).getX();
646  rot_6d(rInd+POSITION_SIZE, 4) = rot.getRow(rInd).getY();
647  rot_6d(rInd+POSITION_SIZE, 5) = rot.getRow(rInd).getZ();
648  }
649 
650  // Rotate the covariance
651  latest_odom_covariance_ = rot_6d * latest_odom_covariance_.eval() * rot_6d.transpose();
652 
653  // Now convert the data back to lat/long and place into the message
654  NavsatConversions::UTMtoLL(odom_as_utm.getOrigin().getY(),
655  odom_as_utm.getOrigin().getX(),
656  utm_zone_,
657  filtered_gps.latitude,
658  filtered_gps.longitude);
659  filtered_gps.altitude = odom_as_utm.getOrigin().getZ();
660 
661  // Copy the measurement's covariance matrix back
662  for (size_t i = 0; i < POSITION_SIZE; i++)
663  {
664  for (size_t j = 0; j < POSITION_SIZE; j++)
665  {
666  filtered_gps.position_covariance[POSITION_SIZE * i + j] = latest_odom_covariance_(i, j);
667  }
668  }
669 
670  filtered_gps.position_covariance_type = sensor_msgs::NavSatFix::COVARIANCE_TYPE_KNOWN;
671  filtered_gps.status.status = sensor_msgs::NavSatStatus::STATUS_GBAS_FIX;
672  filtered_gps.header.frame_id = "gps";
673  filtered_gps.header.stamp = odom_update_time_;
674 
675  // Mark this GPS as used
676  odom_updated_ = false;
677  new_data = true;
678  }
679 
680  return new_data;
681  }
682 
683  bool NavSatTransform::prepareGpsOdometry(nav_msgs::Odometry &gps_odom)
684  {
685  bool new_data = false;
686 
688  {
689  tf2::Transform transformed_utm_gps;
690 
691  transformed_utm_gps.mult(utm_world_transform_, latest_utm_pose_);
692  transformed_utm_gps.setRotation(tf2::Quaternion::getIdentity());
693 
694  // Set header information stamp because we would like to know the robot's position at that timestamp
695  gps_odom.header.frame_id = world_frame_id_;
696  gps_odom.header.stamp = gps_update_time_;
697 
698  // Want the pose of the vehicle origin, not the GPS
699  tf2::Transform transformed_utm_robot;
700  getRobotOriginWorldPose(transformed_utm_gps, transformed_utm_robot, gps_odom.header.stamp);
701 
702  // Rotate the covariance as well
704  Eigen::MatrixXd rot_6d(POSE_SIZE, POSE_SIZE);
705  rot_6d.setIdentity();
706 
707  for (size_t rInd = 0; rInd < POSITION_SIZE; ++rInd)
708  {
709  rot_6d(rInd, 0) = rot.getRow(rInd).getX();
710  rot_6d(rInd, 1) = rot.getRow(rInd).getY();
711  rot_6d(rInd, 2) = rot.getRow(rInd).getZ();
712  rot_6d(rInd+POSITION_SIZE, 3) = rot.getRow(rInd).getX();
713  rot_6d(rInd+POSITION_SIZE, 4) = rot.getRow(rInd).getY();
714  rot_6d(rInd+POSITION_SIZE, 5) = rot.getRow(rInd).getZ();
715  }
716 
717  // Rotate the covariance
718  latest_utm_covariance_ = rot_6d * latest_utm_covariance_.eval() * rot_6d.transpose();
719 
720  // Now fill out the message. Set the orientation to the identity.
721  tf2::toMsg(transformed_utm_robot, gps_odom.pose.pose);
722  gps_odom.pose.pose.position.z = (zero_altitude_ ? 0.0 : gps_odom.pose.pose.position.z);
723 
724  // Copy the measurement's covariance matrix so that we can rotate it later
725  for (size_t i = 0; i < POSE_SIZE; i++)
726  {
727  for (size_t j = 0; j < POSE_SIZE; j++)
728  {
729  gps_odom.pose.covariance[POSE_SIZE * i + j] = latest_utm_covariance_(i, j);
730  }
731  }
732 
733  // Mark this GPS as used
734  gps_updated_ = false;
735  new_data = true;
736  }
737 
738  return new_data;
739  }
740 
741  void NavSatTransform::setTransformGps(const sensor_msgs::NavSatFixConstPtr& msg)
742  {
743  double utm_x = 0;
744  double utm_y = 0;
745  NavsatConversions::LLtoUTM(msg->latitude, msg->longitude, utm_y, utm_x, utm_zone_, utm_meridian_convergence_);
747 
748  ROS_INFO_STREAM("Datum (latitude, longitude, altitude) is (" << std::fixed << msg->latitude << ", " <<
749  msg->longitude << ", " << msg->altitude << ")");
750  ROS_INFO_STREAM("Datum UTM coordinate is (" << std::fixed << utm_x << ", " << utm_y << ")");
751 
752  transform_utm_pose_.setOrigin(tf2::Vector3(utm_x, utm_y, msg->altitude));
754  has_transform_gps_ = true;
755  }
756 
757  void NavSatTransform::setTransformOdometry(const nav_msgs::OdometryConstPtr& msg)
758  {
759  tf2::fromMsg(msg->pose.pose, transform_world_pose_);
760  has_transform_odom_ = true;
761 
762  ROS_INFO_STREAM("Initial odometry pose is " << transform_world_pose_);
763 
764  // Users can optionally use the (potentially fused) heading from
765  // the odometry source, which may have multiple fused sources of
766  // heading data, and so would act as a better heading for the
767  // UTM->world_frame transform.
769  {
770  sensor_msgs::Imu *imu = new sensor_msgs::Imu();
771  imu->orientation = msg->pose.pose.orientation;
772  imu->header.frame_id = msg->child_frame_id;
773  imu->header.stamp = msg->header.stamp;
774  sensor_msgs::ImuConstPtr imuPtr(imu);
775  imuCallback(imuPtr);
776  }
777  }
778 
779 } // namespace RobotLocalization
tf2_ros::Buffer tf_buffer_
Transform buffer for managing coordinate transforms.
tf2::Transform transform_world_pose_
Latest IMU orientation.
bool zero_altitude_
Whether or not to report 0 altitude.
std::string world_frame_id_
Frame ID of the GPS odometry output.
tf2_ros::StaticTransformBroadcaster utm_broadcaster_
Used for publishing the static world_frame->utm transform.
const std::string & getMessage() const
void setTransformGps(const sensor_msgs::NavSatFixConstPtr &msg)
Used for setting the GPS data that will be used to compute the transform.
void setRPY(const tf2Scalar &roll, const tf2Scalar &pitch, const tf2Scalar &yaw)
Eigen::MatrixXd latest_odom_covariance_
Covariance for most recent odometry data.
bool datumCallback(robot_localization::SetDatum::Request &request, robot_localization::SetDatum::Response &)
Callback for the datum service.
void getRPY(tf2Scalar &roll, tf2Scalar &pitch, tf2Scalar &yaw, unsigned int solution_number=1) const
void appendPrefix(std::string tfPrefix, std::string &frameId)
Utility method for appending tf2 prefixes cleanly.
bool publish_gps_
Whether or not we publish filtered GPS messages.
void getRobotOriginUtmPose(const tf2::Transform &gps_utm_pose, tf2::Transform &robot_utm_pose, const ros::Time &transform_time)
Given the pose of the navsat sensor in the UTM frame, removes the offset from the vehicle&#39;s centroid ...
static const Quaternion & getIdentity()
bool has_transform_gps_
Whether or not the GPS fix is usable.
void publish(const boost::shared_ptr< M > &message) const
ros::Duration transform_timeout_
Parameter that specifies the how long we wait for a transform to become available.
bool world_frame_param_loaded_
Whether world frame parameter was loaded.
Subscriber subscribe(const std::string &topic, uint32_t queue_size, void(T::*fp)(M), T *obj, const TransportHints &transport_hints=TransportHints())
Quaternion getRotation() const
TF2SIMD_FORCE_INLINE void setRotation(const Quaternion &q)
double utm_meridian_convergence_
UTM&#39;s meridian convergence.
int size() const
std::string base_link_frame_id_
Frame ID of the robot&#39;s body frame.
bool has_transform_imu_
Signifies that we have received a usable IMU message.
std::string utm_zone_
UTM zone as determined after transforming GPS message.
tf2::Transform utm_world_transform_
Holds the UTM->odom transform.
ros::Time gps_update_time_
Timestamp of the latest good GPS message.
ServiceServer advertiseService(const std::string &service, bool(T::*srv_func)(MReq &, MRes &), T *obj)
Type const & getType() const
tf2::Transform utm_world_trans_inverse_
Holds the odom->UTM transform for filtered GPS broadcast.
tf2::Transform latest_world_pose_
Latest odometry pose data.
void setIdentity()
void setRPY(tf2Scalar roll, tf2Scalar pitch, tf2Scalar yaw)
bool prepareGpsOdometry(nav_msgs::Odometry &gps_odom)
Prepares the GPS odometry message before sending.
Duration & fromSec(double t)
TF2SIMD_FORCE_INLINE Vector3 & getOrigin()
#define ROS_INFO(...)
bool searchParam(const std::string &key, std::string &result) const
bool param(const std::string &param_name, T &param_val, const T &default_val) const
bool use_odometry_yaw_
Whether we get the transform&#39;s yaw from the odometry or IMU source.
void fromMsg(const A &, B &b)
void computeTransform()
Computes the transform from the UTM frame to the odom frame.
ROSCPP_DECL bool ok()
Transform inverse() const
bool use_manual_datum_
Whether we get our datum from the first GPS message or from the set_datum service/parameter.
static void LLtoUTM(const double Lat, const double Long, double &UTMNorthing, double &UTMEasting, std::string &UTMZone, double &gamma)
Publisher advertise(const std::string &topic, uint32_t queue_size, bool latch=false)
const int POSITION_SIZE
Definition: filter_common.h:86
TF2SIMD_FORCE_INLINE void setOrigin(const Vector3 &origin)
#define ROS_WARN_STREAM(args)
#define ROS_DEBUG_STREAM(args)
bool base_link_frame_param_loaded_
Whether base link frame parameter was loaded.
TF2SIMD_FORCE_INLINE void mult(const Transform &t1, const Transform &t2)
bool odom_updated_
Whether or not we have new odometry data.
tf2::Transform latest_utm_pose_
Latest GPS data, stored as UTM coords.
double clampRotation(double rotation)
Utility method keeping RPY angles in the range [-pi, pi].
double yaw_offset_
IMU&#39;s yaw offset.
Eigen::MatrixXd latest_utm_covariance_
Covariance for most recent GPS/UTM data.
bool sleep()
bool gps_updated_
Whether or not we have new GPS data.
#define ROS_WARN_STREAM_THROTTLE(rate, args)
void getRobotOriginWorldPose(const tf2::Transform &gps_odom_pose, tf2::Transform &robot_odom_pose, const ros::Time &transform_time)
Given the pose of the navsat sensor in the world frame, removes the offset from the vehicle&#39;s centroi...
B toMsg(const A &a)
void setTransformOdometry(const nav_msgs::OdometryConstPtr &msg)
Used for setting the odometry data that will be used to compute the transform.
bool has_transform_odom_
Signifies that we have received a usable odometry message.
TF2SIMD_FORCE_INLINE Vector3 quatRotate(const Quaternion &rotation, const Vector3 &v)
#define ROS_INFO_STREAM(args)
tf2::Quaternion transform_orientation_
Latest IMU orientation.
const int POSE_SIZE
Pose and twist messages each contain six variables.
Definition: filter_common.h:84
void imuCallback(const sensor_msgs::ImuConstPtr &msg)
Callback for the IMU data.
#define ROS_WARN_STREAM_ONCE(args)
bool getParam(const std::string &key, std::string &s) const
double magnetic_declination_
Parameter that specifies the magnetic declination for the robot&#39;s environment.
static Time now()
bool transform_good_
Whether or not we&#39;ve computed a good heading.
bool lookupTransformSafe(const tf2_ros::Buffer &buffer, const std::string &targetFrame, const std::string &sourceFrame, const ros::Time &time, const ros::Duration &timeout, tf2::Transform &targetFrameTrans, const bool silent=false)
Method for safely obtaining transforms.
bool prepareFilteredGps(sensor_msgs::NavSatFix &filtered_gps)
Converts the odometry data back to GPS and broadcasts it.
bool hasParam(const std::string &key) const
#define ROS_ERROR_STREAM(args)
#define ROS_ASSERT(cond)
void gpsFixCallback(const sensor_msgs::NavSatFixConstPtr &msg)
Callback for the GPS fix data.
void sendTransform(const geometry_msgs::TransformStamped &transform)
static void UTMtoLL(const double UTMNorthing, const double UTMEasting, const std::string &UTMZone, double &Lat, double &Long, double &gamma)
ROSCPP_DECL void spinOnce()
static bool waitForValid()
void quatToRPY(const tf2::Quaternion &quat, double &roll, double &pitch, double &yaw)
Utility method for converting quaternion to RPY.
tf2::Transform transform_utm_pose_
Holds the UTM pose that is used to compute the transform.
bool broadcast_utm_transform_as_parent_frame_
Whether to broadcast the UTM transform as parent frame, default as child.
void odomCallback(const nav_msgs::OdometryConstPtr &msg)
Callback for the odom data.
bool broadcast_utm_transform_
Whether or not we broadcast the UTM transform.
std::string gps_frame_id_
The frame_id of the GPS message (specifies mounting location)
ros::Time odom_update_time_
Timestamp of the latest good odometry message.


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