Program Listing for File ground_truth.hpp

Return to documentation for file (/tmp/ws/src/aerostack2/as2_state_estimator/plugins/ground_truth/include/ground_truth.hpp)

// Copyright 2024 Universidad Politécnica de Madrid
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
//    * Redistributions of source code must retain the above copyright
//      notice, this list of conditions and the following disclaimer.
//
//    * Redistributions in binary form must reproduce the above copyright
//      notice, this list of conditions and the following disclaimer in the
//      documentation and/or other materials provided with the distribution.
//
//    * Neither the name of the Universidad Politécnica de Madrid nor the names of its
//      contributors may be used to endorse or promote products derived from
//      this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

#ifndef GROUND_TRUTH_HPP_
#define GROUND_TRUTH_HPP_

#include <utility>
#include <regex>
#include <memory>
#include <geographic_msgs/msg/geo_point.hpp>

#include <as2_core/names/services.hpp>
#include <as2_core/utils/gps_utils.hpp>
#include <as2_core/utils/tf_utils.hpp>
#include <as2_msgs/srv/get_origin.hpp>
#include <as2_msgs/srv/set_origin.hpp>

#include "as2_state_estimator/plugin_base.hpp"

namespace ground_truth
{

class Plugin : public as2_state_estimator_plugin_base::StateEstimatorBase
{
  rclcpp::Subscription<geometry_msgs::msg::PoseStamped>::SharedPtr pose_sub_;
  rclcpp::Subscription<geometry_msgs::msg::TwistStamped>::SharedPtr twist_sub_;
  rclcpp::Subscription<sensor_msgs::msg::NavSatFix>::SharedPtr gps_sub_;

  rclcpp::Service<as2_msgs::srv::SetOrigin>::SharedPtr set_origin_srv_;
  rclcpp::Service<as2_msgs::srv::GetOrigin>::SharedPtr get_origin_srv_;

  bool use_gps_ = false;
  bool set_origin_on_start_ = false;
  bool earth_to_map_set_ = false;
  double origin_lat_ = 0.0;
  double origin_lon_ = 0.0;
  double origin_alt_ = 0.0;

  geometry_msgs::msg::TransformStamped earth_to_map_;
  geographic_msgs::msg::GeoPoint::UniquePtr origin_;
  sensor_msgs::msg::NavSatFix::UniquePtr gps_pose_;

  bool using_ignition_tf_ = false;

public:
  Plugin()
  : as2_state_estimator_plugin_base::StateEstimatorBase() {}
  void on_setup() override
  {
    node_ptr_->get_parameter("use_gps", use_gps_);
    node_ptr_->get_parameter("set_origin_on_start", set_origin_on_start_);

    pose_sub_ = node_ptr_->create_subscription<geometry_msgs::msg::PoseStamped>(
      as2_names::topics::ground_truth::pose, as2_names::topics::ground_truth::qos,
      std::bind(&Plugin::pose_callback, this, std::placeholders::_1));
    twist_sub_ = node_ptr_->create_subscription<geometry_msgs::msg::TwistStamped>(
      as2_names::topics::ground_truth::twist, as2_names::topics::ground_truth::qos,
      std::bind(&Plugin::twist_callback, this, std::placeholders::_1));

    // publish static transform from earth to map and map to odom
    earth_to_map_ =
      as2::tf::getTransformation(get_earth_frame(), get_map_frame(), 0, 0, 0, 0, 0, 0);
    if (!use_gps_) {
      // TODO(javilinos): MODIFY this to a initial earth to map transform (reading initial position
      // from parameters or msgs )
      publish_static_transform(earth_to_map_);
    } else {
      set_origin_srv_ = node_ptr_->create_service<as2_msgs::srv::SetOrigin>(
        as2_names::services::gps::set_origin,
        std::bind(
          &Plugin::setOriginCallback, this, std::placeholders::_1,
          std::placeholders::_2));
      get_origin_srv_ = node_ptr_->create_service<as2_msgs::srv::GetOrigin>(
        as2_names::services::gps::get_origin,
        std::bind(
          &Plugin::getOriginCallback, this, std::placeholders::_1,
          std::placeholders::_2));
      gps_sub_ = node_ptr_->create_subscription<sensor_msgs::msg::NavSatFix>(
        as2_names::topics::sensor_measurements::gps, as2_names::topics::sensor_measurements::qos,
        std::bind(&Plugin::gps_callback, this, std::placeholders::_1));

      if (set_origin_on_start_ && node_ptr_->has_parameter("set_origin.lat") &&
        node_ptr_->has_parameter("set_origin.lon") &&
        node_ptr_->has_parameter("set_origin.alt"))
      {
        node_ptr_->get_parameter("set_origin.lat", origin_lat_);
        node_ptr_->get_parameter("set_origin.lon", origin_lon_);
        node_ptr_->get_parameter("set_origin.alt", origin_alt_);

        origin_ = std::make_unique<geographic_msgs::msg::GeoPoint>();
        origin_->latitude = origin_lat_;
        origin_->longitude = origin_lon_;
        origin_->altitude = origin_alt_;

        RCLCPP_INFO(
          node_ptr_->get_logger(), "Origin set to %f, %f, %f", origin_lat_, origin_lon_,
          origin_alt_);
      } else {
        RCLCPP_INFO(node_ptr_->get_logger(), "Waiting for origin to be set");
      }
    }

    geometry_msgs::msg::TransformStamped map_to_odom =
      as2::tf::getTransformation(get_map_frame(), get_odom_frame(), 0, 0, 0, 0, 0, 0);

    // TODO(javilinos): CHECK IF WE NEED TO PUBLISH THIS PERIODICALLY
    if (node_ptr_->has_parameter("use_ignition_tf")) {
      node_ptr_->get_parameter("use_ignition_tf", using_ignition_tf_);
      if (using_ignition_tf_) {RCLCPP_INFO(node_ptr_->get_logger(), "Using ignition tfs");}
    }
    publish_static_transform(earth_to_map_);
    publish_static_transform(map_to_odom);
  }

private:
  void generate_map_frame_from_gps(
    const geographic_msgs::msg::GeoPoint & origin,
    const sensor_msgs::msg::NavSatFix & gps_pose)
  {
    as2::gps::GpsHandler gps_handler;
    gps_handler.setOrigin(origin.latitude, origin.longitude, origin.altitude);
    double x, y, z;
    gps_handler.LatLon2Local(gps_pose.latitude, gps_pose.longitude, gps_pose.altitude, x, y, z);
    earth_to_map_ =
      as2::tf::getTransformation(get_earth_frame(), get_map_frame(), x, y, z, 0, 0, 0);
    publish_static_transform(earth_to_map_);
  }

  void generate_map_frame_from_ground_truth_pose(const geometry_msgs::msg::PoseStamped & pose)
  {
    earth_to_map_.transform.translation.x = pose.pose.position.x;
    earth_to_map_.transform.translation.y = pose.pose.position.y;
    earth_to_map_.transform.translation.z = pose.pose.position.z;
    earth_to_map_.transform.rotation.x = pose.pose.orientation.x;
    earth_to_map_.transform.rotation.y = pose.pose.orientation.y;
    earth_to_map_.transform.rotation.z = pose.pose.orientation.z;
    earth_to_map_.transform.rotation.w = pose.pose.orientation.w;
    publish_static_transform(earth_to_map_);
  }

  void pose_callback(const geometry_msgs::msg::PoseStamped::SharedPtr msg)
  {
    // since it's ground_truth we consider that the frame obtained is the world frame (earth)
    // so we need to publish the transform from world to base_link, with map and odom as the
    // identity transform in order to keep the continuity of the tf tree we will modify the
    // odom->base_link transform

    if (msg->header.frame_id != get_earth_frame()) {
      RCLCPP_ERROR(
        node_ptr_->get_logger(), "Received pose in frame %s, expected %s",
        msg->header.frame_id.c_str(), get_earth_frame().c_str());
      return;
    }

    // if (!earth_to_map_set_) {
    //   generate_map_frame_from_ground_truth_pose(*msg);
    //   earth_to_map_set_ = true;
    // }

    earth_to_baselink.setOrigin(
      tf2::Vector3(msg->pose.position.x, msg->pose.position.y, msg->pose.position.z));
    earth_to_baselink.setRotation(
      tf2::Quaternion(
        msg->pose.orientation.x, msg->pose.orientation.y,
        msg->pose.orientation.z,
        msg->pose.orientation.w));

    tf2::fromMsg(earth_to_map_.transform, earth_to_map);

    convert_earth_to_baselink_2_odom_to_baselink_transform(
      earth_to_baselink, odom_to_baselink,
      earth_to_map);

    auto odom_to_baselink_msg = geometry_msgs::msg::TransformStamped();
    odom_to_baselink_msg.header.stamp = msg->header.stamp;
    odom_to_baselink_msg.header.frame_id = get_odom_frame();
    if (using_ignition_tf_) {
      odom_to_baselink_msg.child_frame_id = as2::tf::generateTfName("", node_ptr_->get_namespace());
    } else {
      odom_to_baselink_msg.child_frame_id = get_base_frame();
    }
    odom_to_baselink_msg.transform = tf2::toMsg(odom_to_baselink);

    publish_transform(odom_to_baselink_msg);

    // Pose should be published in the earth frame (world)
    // so we dont need to modify the frame_id
    publish_pose(*msg);
  }

  void twist_callback(const geometry_msgs::msg::TwistStamped::SharedPtr msg)
  {
    if (msg->header.frame_id != get_base_frame()) {
      RCLCPP_ERROR(
        node_ptr_->get_logger(), "Received twist in frame %s, expected %s",
        msg->header.frame_id.c_str(), get_base_frame().c_str());
      // TODO(javilinos): convert it to the base_link frame if needed
      return;
    }
    publish_twist(*msg);
  }

  void getOriginCallback(
    const as2_msgs::srv::GetOrigin::Request::SharedPtr request,
    as2_msgs::srv::GetOrigin::Response::SharedPtr response)
  {
    if (origin_) {
      response->origin = *origin_;
      response->success = true;
    } else {
      RCLCPP_WARN(node_ptr_->get_logger(), "Origin not set");
      response->success = false;
    }
  }

  void setOriginCallback(
    const as2_msgs::srv::SetOrigin::Request::SharedPtr request,
    as2_msgs::srv::SetOrigin::Response::SharedPtr response)
  {
    if (origin_) {
      RCLCPP_WARN(node_ptr_->get_logger(), "Origin already set");
      response->success = false;
    } else {
      origin_ = std::make_unique<geographic_msgs::msg::GeoPoint>(request->origin);
      RCLCPP_INFO(
        node_ptr_->get_logger(), "Origin set to %f, %f, %f", origin_->latitude,
        origin_->longitude, origin_->altitude);
      response->success = true;
      generate_map_frame_from_gps(request->origin, *gps_pose_);
    }
  }

  void gps_callback(sensor_msgs::msg::NavSatFix::UniquePtr msg)
  {
    // This sould only be called when the use_gps_origin is true
    if (gps_pose_) {
      gps_sub_.reset();
      return;
    }
    gps_pose_ = std::move(msg);

    if (set_origin_on_start_) {
      if (!origin_) {
        origin_ = std::make_unique<geographic_msgs::msg::GeoPoint>();
        origin_->latitude = gps_pose_->latitude;
        origin_->longitude = gps_pose_->longitude;
        origin_->altitude = gps_pose_->altitude;
        RCLCPP_WARN(node_ptr_->get_logger(), "Careful, using GPS pose as origin");
        RCLCPP_INFO(
          node_ptr_->get_logger(), "Origin set to %f, %f, %f", origin_lat_, origin_lon_,
          origin_alt_);
      }

      RCLCPP_INFO(
        node_ptr_->get_logger(), "GPS Callback: Map GPS pose set to %f, %f, %f",
        gps_pose_->latitude, gps_pose_->longitude, gps_pose_->altitude);

      generate_map_frame_from_gps(*origin_, *gps_pose_);
      earth_to_map_set_ = true;
    }
  }
};      // class GroundTruth
}       // namespace ground_truth
#endif  // GROUND_TRUTH_HPP_