navsat_transform.cpp
Go to the documentation of this file.
00001 /*
00002  * Copyright (c) 2015, Charles River Analytics, Inc.
00003  * All rights reserved.
00004  *
00005  * Redistribution and use in source and binary forms, with or without
00006  * modification, are permitted provided that the following conditions
00007  * are met:
00008  *
00009  * 1. Redistributions of source code must retain the above copyright
00010  * notice, this list of conditions and the following disclaimer.
00011  * 2. Redistributions in binary form must reproduce the above
00012  * copyright notice, this list of conditions and the following
00013  * disclaimer in the documentation and/or other materials provided
00014  * with the distribution.
00015  * 3. Neither the name of the copyright holder nor the names of its
00016  * contributors may be used to endorse or promote products derived
00017  * from this software without specific prior written permission.
00018  *
00019  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00020  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00021  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
00022  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
00023  * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
00024  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
00025  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00026  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
00027  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00028  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
00029  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00030  * POSSIBILITY OF SUCH DAMAGE.
00031  */
00032 
00033 #include "robot_localization/navsat_transform.h"
00034 #include "robot_localization/filter_common.h"
00035 #include "robot_localization/navsat_conversions.h"
00036 
00037 #include <tf/tfMessage.h>
00038 #include <tf/transform_broadcaster.h>
00039 
00040 namespace RobotLocalization
00041 {
00042   NavSatTransform::NavSatTransform() :
00043     magneticDeclination_(0.0),
00044     utmOdomTfYaw_(0.0),
00045     yawOffset_(0.0),
00046     broadcastUtmTransform_(false),
00047     hasOdom_(false),
00048     hasGps_(false),
00049     hasImu_(false),
00050     transformGood_(false),
00051     gpsUpdated_(false),
00052     odomUpdated_(false),
00053     publishGps_(false),
00054     useOdometryYaw_(false),
00055     worldFrameId_("odom"),
00056     utmZone_("")
00057  {
00058     latestUtmCovariance_.resize(POSE_SIZE, POSE_SIZE);
00059   }
00060 
00061   NavSatTransform::~NavSatTransform()
00062   {
00063 
00064   }
00065 
00066   void NavSatTransform::odomCallback(const nav_msgs::OdometryConstPtr& msg)
00067   {
00068     tf::poseMsgToTF(msg->pose.pose, latestWorldPose_);
00069     worldFrameId_ = msg->header.frame_id;
00070     hasOdom_ = true;
00071     odomUpdateTime_ = msg->header.stamp;
00072     odomUpdated_ = true;
00073 
00074     // Users can optionally use the (potentially fused) heading from
00075     // the odometry source, which may have multiple fused sources of
00076     // heading data, and so would act as a better heading for the
00077     // UTM->world_frame transform.
00078     if(useOdometryYaw_ && !transformGood_)
00079     {
00080       tf::quaternionMsgToTF(msg->pose.pose.orientation, latestOrientation_);
00081       hasImu_ = true;
00082     }
00083   }
00084 
00085   void NavSatTransform::gpsFixCallback(const sensor_msgs::NavSatFixConstPtr& msg)
00086   {
00087     hasGps_ = (msg->status.status != sensor_msgs::NavSatStatus::STATUS_NO_FIX &&
00088                !std::isnan(msg->altitude) &&
00089                !std::isnan(msg->latitude) &&
00090                !std::isnan(msg->longitude));
00091 
00092     if(hasGps_)
00093     {
00094       double utmX = 0;
00095       double utmY = 0;
00096       NavsatConversions::LLtoUTM(msg->latitude, msg->longitude, utmY, utmX, utmZone_);
00097       latestUtmPose_.setOrigin(tf::Vector3(utmX, utmY, msg->altitude));
00098       latestUtmCovariance_.setZero();
00099 
00100       // Copy the measurement's covariance matrix so that we can rotate it later
00101       for (size_t i = 0; i < POSITION_SIZE; i++)
00102       {
00103         for (size_t j = 0; j < POSITION_SIZE; j++)
00104         {
00105           latestUtmCovariance_(i, j) = msg->position_covariance[POSITION_SIZE * i + j];
00106         }
00107       }
00108 
00109       gpsUpdateTime_ = msg->header.stamp;
00110       gpsUpdated_ = true;
00111     }
00112   }
00113 
00114   void NavSatTransform::imuCallback(const sensor_msgs::ImuConstPtr& msg)
00115   {
00116     tf::quaternionMsgToTF(msg->orientation, latestOrientation_);
00117     hasImu_ = true;
00118   }
00119 
00120   void NavSatTransform::computeTransform()
00121   {
00122     // Only do this if:
00123     // 1. We haven't computed the odom_frame->utm_frame transform before
00124     // 2. We've received the data we need
00125     if(!transformGood_ &&
00126        hasOdom_ &&
00127        hasGps_ &&
00128        hasImu_)
00129     {
00130       // Get the IMU's current RPY values. Need the raw values (for yaw, anyway).
00131       tf::Matrix3x3 mat(latestOrientation_);
00132 
00133       // Convert to RPY
00134       double imuRoll;
00135       double imuPitch;
00136       double imuYaw;
00137       mat.getRPY(imuRoll, imuPitch, imuYaw);
00138 
00139       /* The IMU's heading was likely originally reported w.r.t. magnetic north.
00140        * However, all the nodes in robot_localization assume that orientation data,
00141        * including that reported by IMUs, is reported in an ENU frame, with a 0 yaw
00142        * value being reported when facing east and increasing counter-clockwise (i.e.,
00143        * towards north). Conveniently, this aligns with the UTM grid, where X is east
00144        * and Y is north. However, we have two additional considerations:
00145        *   1. The IMU may have its non-ENU frame data transformed to ENU, but there's
00146        *      a possibility that its data has not been corrected for magnetic
00147        *      declination. We need to account for this. A positive magnetic
00148        *      declination is counter-clockwise in an ENU frame. Therefore, if
00149        *      we have a magnetic declination of N radians, then when the sensor
00150        *      is facing a heading of N, it reports 0. Therefore, we need to add
00151        *      the declination angle.
00152        *   2. To account for any other offsets that may not be accounted for by the
00153        *      IMU driver or any interim processing node, we expose a yaw offset that
00154        *      lets users work with navsat_transform_node.
00155        */
00156       imuYaw += (magneticDeclination_ + yawOffset_);
00157 
00158       ROS_INFO_STREAM("Corrected for magnetic declination of " << std::fixed << magneticDeclination_ <<
00159                       ", user-specified offset of " << yawOffset_ << ", and fixed offset of " << (M_PI / 2.0) <<
00160                       ". Transform heading factor is now " << imuYaw);
00161 
00162       // Convert to tf-friendly structures
00163       tf::Quaternion imuQuat;
00164       imuQuat.setRPY(0.0, 0.0, imuYaw);
00165 
00166       // The transform order will be orig_odom_pos * orig_utm_pos_inverse * cur_utm_pos.
00167       // Doing it this way will allow us to cope with having non-zero odometry position
00168       // when we get our first GPS message.
00169       tf::Pose utmPoseWithOrientation;
00170       utmPoseWithOrientation.setOrigin(latestUtmPose_.getOrigin());
00171       utmPoseWithOrientation.setRotation(imuQuat);
00172       utmWorldTransform_.mult(latestWorldPose_, utmPoseWithOrientation.inverse());
00173 
00174       utmWorldTransInverse_ = utmWorldTransform_.inverse();
00175 
00176       double roll = 0;
00177       double pitch = 0;
00178       double yaw = 0;
00179       mat.setRotation(latestWorldPose_.getRotation());
00180       mat.getRPY(roll, pitch, yaw);
00181 
00182       ROS_INFO_STREAM("Latest world frame pose is: " << std::fixed <<
00183                       "\nPosition: (" << latestWorldPose_.getOrigin().getX() << ", " <<
00184                                          latestWorldPose_.getOrigin().getY() << ", " <<
00185                                          latestWorldPose_.getOrigin().getZ() << ")" <<
00186                       "\nOrientation: (" << roll << ", " <<
00187                                             pitch << ", " <<
00188                                             yaw << ")");
00189 
00190       mat.setRotation(utmWorldTransform_.getRotation());
00191       mat.getRPY(roll, pitch, yaw);
00192 
00193       ROS_INFO_STREAM("World frame->utm transform is " << std::fixed <<
00194                        "\nPosition: (" << utmWorldTransform_.getOrigin().getX() << ", " <<
00195                                           utmWorldTransform_.getOrigin().getY() << ", " <<
00196                                           utmWorldTransform_.getOrigin().getZ() << ")" <<
00197                        "\nOrientation: (" << roll << ", " <<
00198                                              pitch << ", " <<
00199                                              yaw << ")");
00200 
00201       transformGood_ = true;
00202     }
00203   }
00204 
00205   bool NavSatTransform::prepareGpsOdometry(nav_msgs::Odometry &gpsOdom)
00206   {
00207     bool newData = false;
00208 
00209     if(transformGood_ && gpsUpdated_)
00210     {
00211       tf::Pose transformedUtm;
00212 
00213       transformedUtm.mult(utmWorldTransform_, latestUtmPose_);
00214       transformedUtm.setRotation(tf::Quaternion::getIdentity());
00215 
00216       // Rotate the covariance as well
00217       tf::Matrix3x3 rot(utmWorldTransform_.getRotation());
00218       Eigen::MatrixXd rot6d(POSE_SIZE, POSE_SIZE);
00219       rot6d.setIdentity();
00220 
00221       for(size_t rInd = 0; rInd < POSITION_SIZE; ++rInd)
00222       {
00223         rot6d(rInd, 0) = rot.getRow(rInd).getX();
00224         rot6d(rInd, 1) = rot.getRow(rInd).getY();
00225         rot6d(rInd, 2) = rot.getRow(rInd).getZ();
00226         rot6d(rInd+POSITION_SIZE, 3) = rot.getRow(rInd).getX();
00227         rot6d(rInd+POSITION_SIZE, 4) = rot.getRow(rInd).getY();
00228         rot6d(rInd+POSITION_SIZE, 5) = rot.getRow(rInd).getZ();
00229       }
00230 
00231       // Rotate the covariance
00232       latestUtmCovariance_ = rot6d * latestUtmCovariance_.eval() * rot6d.transpose();
00233 
00234       // Now fill out the message. Set the orientation to the identity.
00235       tf::poseTFToMsg(transformedUtm, gpsOdom.pose.pose);
00236       gpsOdom.pose.pose.position.z = (zeroAltitude_ ? 0.0 : gpsOdom.pose.pose.position.z);
00237 
00238       // Copy the measurement's covariance matrix so that we can rotate it later
00239       for (size_t i = 0; i < POSE_SIZE; i++)
00240       {
00241         for (size_t j = 0; j < POSE_SIZE; j++)
00242         {
00243           gpsOdom.pose.covariance[POSE_SIZE * i + j] = latestUtmCovariance_(i, j);
00244         }
00245       }
00246 
00247       gpsOdom.header.frame_id = worldFrameId_;
00248       gpsOdom.header.stamp = gpsUpdateTime_;
00249 
00250       // Mark this GPS as used
00251       gpsUpdated_ = false;
00252       newData = true;
00253     }
00254 
00255     return newData;
00256   }
00257 
00258   bool NavSatTransform::prepareFilteredGps(sensor_msgs::NavSatFix &filteredGps)
00259   {
00260     bool newData = false;
00261 
00262     if(transformGood_ && odomUpdated_)
00263     {
00264       tf::Pose odomAsUtm;
00265 
00266       odomAsUtm.mult(utmWorldTransInverse_, latestWorldPose_);
00267       odomAsUtm.setRotation(tf::Quaternion::getIdentity());
00268 
00269       // Rotate the covariance as well
00270       tf::Matrix3x3 rot(utmWorldTransInverse_.getRotation());
00271       Eigen::MatrixXd rot6d(POSE_SIZE, POSE_SIZE);
00272       rot6d.setIdentity();
00273 
00274       for(size_t rInd = 0; rInd < POSITION_SIZE; ++rInd)
00275       {
00276         rot6d(rInd, 0) = rot.getRow(rInd).getX();
00277         rot6d(rInd, 1) = rot.getRow(rInd).getY();
00278         rot6d(rInd, 2) = rot.getRow(rInd).getZ();
00279         rot6d(rInd+POSITION_SIZE, 3) = rot.getRow(rInd).getX();
00280         rot6d(rInd+POSITION_SIZE, 4) = rot.getRow(rInd).getY();
00281         rot6d(rInd+POSITION_SIZE, 5) = rot.getRow(rInd).getZ();
00282       }
00283 
00284       // Rotate the covariance
00285       latestOdomCovariance_ = rot6d * latestOdomCovariance_.eval() * rot6d.transpose();
00286 
00287       // Now convert the data back to lat/long and place into the message
00288       NavsatConversions::UTMtoLL(odomAsUtm.getOrigin().getY(), odomAsUtm.getOrigin().getX(), utmZone_, filteredGps.latitude, filteredGps.longitude);
00289       filteredGps.altitude = odomAsUtm.getOrigin().getZ();
00290 
00291       // Copy the measurement's covariance matrix back
00292       for (size_t i = 0; i < POSITION_SIZE; i++)
00293       {
00294         for (size_t j = 0; j < POSITION_SIZE; j++)
00295         {
00296           filteredGps.position_covariance[POSITION_SIZE * i + j] = latestUtmCovariance_(i, j);
00297         }
00298       }
00299 
00300       filteredGps.position_covariance_type = sensor_msgs::NavSatFix::COVARIANCE_TYPE_KNOWN;
00301       filteredGps.status.status = sensor_msgs::NavSatStatus::STATUS_GBAS_FIX;
00302       filteredGps.header.frame_id = "gps";
00303       filteredGps.header.stamp = odomUpdateTime_;
00304 
00305       // Mark this GPS as used
00306       odomUpdated_ = false;
00307       newData = true;
00308     }
00309 
00310     return newData;
00311   }
00312 
00313   void NavSatTransform::run()
00314   {
00315     ros::Time::init();
00316 
00317     double frequency = 10.0;
00318     double delay = 0.0;
00319 
00320     ros::NodeHandle nh;
00321     ros::NodeHandle nhPriv("~");
00322 
00323     // Load the parameters we need
00324     nhPriv.getParam("magnetic_declination_radians", magneticDeclination_);
00325     nhPriv.param("yaw_offset", yawOffset_, 0.0);
00326     nhPriv.param("broadcast_utm_transform", broadcastUtmTransform_, false);
00327     nhPriv.param("zero_altitude", zeroAltitude_, false);
00328     nhPriv.param("publish_filtered_gps", publishGps_, false);
00329     nhPriv.param("use_odometry_yaw", useOdometryYaw_, false);
00330     nhPriv.param("frequency", frequency, 10.0);
00331     nhPriv.param("delay", delay, 0.0);
00332 
00333     // Subscribe to the messages we need
00334     ros::Subscriber odomSub = nh.subscribe("odometry/filtered", 1, &NavSatTransform::odomCallback, this);
00335     ros::Subscriber gpsSub = nh.subscribe("gps/fix", 1, &NavSatTransform::gpsFixCallback, this);
00336     ros::Subscriber imuSub;
00337 
00338     if(!useOdometryYaw_)
00339     {
00340       imuSub = nh.subscribe("imu/data", 1, &NavSatTransform::imuCallback, this);
00341     }
00342 
00343     ros::Publisher gpsOdomPub = nh.advertise<nav_msgs::Odometry>("odometry/gps", 10);
00344     ros::Publisher filteredGpsPub;
00345 
00346     if(publishGps_)
00347     {
00348       filteredGpsPub = nh.advertise<sensor_msgs::NavSatFix>("gps/filtered", 10);
00349     }
00350 
00351     tf::TransformBroadcaster utmBroadcaster;
00352     tf::StampedTransform utmTransformStamped;
00353     utmTransformStamped.child_frame_id_ = "utm";
00354 
00355     // Sleep for the parameterized amount of time, to give
00356     // other nodes time to start up (not always necessary)
00357     ros::Duration startDelay(delay);
00358     startDelay.sleep();
00359 
00360     ros::Rate rate(frequency);
00361     while(ros::ok())
00362     {
00363       ros::spinOnce();
00364 
00365       if(!transformGood_)
00366       {
00367         computeTransform();
00368 
00369         if(transformGood_ && !useOdometryYaw_)
00370         {
00371           // Once we have the transform, we don't need the IMU
00372           imuSub.shutdown();
00373         }
00374       }
00375       else
00376       {
00377         nav_msgs::Odometry gpsOdom;
00378         if(prepareGpsOdometry(gpsOdom))
00379         {
00380           gpsOdomPub.publish(gpsOdom);
00381         }
00382 
00383         if(publishGps_)
00384         {
00385           sensor_msgs::NavSatFix odomGps;
00386           if(prepareFilteredGps(odomGps))
00387           {
00388             filteredGpsPub.publish(odomGps);
00389           }
00390         }
00391 
00392         // Send out the UTM transform in case anyone
00393         // else would like to use it.
00394         if(transformGood_ && broadcastUtmTransform_)
00395         {
00396           utmTransformStamped.setData(utmWorldTransform_);
00397           utmTransformStamped.frame_id_ = worldFrameId_;
00398           utmTransformStamped.stamp_ = ros::Time::now();
00399           utmBroadcaster.sendTransform(utmTransformStamped);
00400         }
00401       }
00402 
00403       rate.sleep();
00404     }
00405 
00406   }
00407 }


robot_localization
Author(s): Tom Moore
autogenerated on Fri Aug 28 2015 12:26:20