Program Listing for File sensor_msgs.hpp

Return to documentation for file (/tmp/ws/src/gazebo_ros_pkgs/gazebo_ros/include/gazebo_ros/conversions/sensor_msgs.hpp)

// Copyright 2018 Open Source Robotics Foundation, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef GAZEBO_ROS__CONVERSIONS__SENSOR_MSGS_HPP_
#define GAZEBO_ROS__CONVERSIONS__SENSOR_MSGS_HPP_

#include <math.h>

#include <gazebo/msgs/laserscan_stamped.pb.h>
#include <geometry_msgs/msg/point32.hpp>
#include <rclcpp/logging.hpp>
#include <sensor_msgs/msg/point_cloud2.hpp>
#include <sensor_msgs/msg/point_cloud.hpp>
#include <sensor_msgs/msg/laser_scan.hpp>
#include <sensor_msgs/msg/range.hpp>
#include <sensor_msgs/point_cloud2_iterator.hpp>

#include <algorithm>
#include <limits>

#include "gazebo_ros/conversions/builtin_interfaces.hpp"
#include "gazebo_ros/conversions/generic.hpp"

namespace gazebo_ros
{
template<class T>
inline
T Convert(const gazebo::msgs::LaserScanStamped &, double min_intensity = 0.0)
{
  (void)min_intensity;
  T::ConversionNotImplemented;
}

template<>
inline
sensor_msgs::msg::LaserScan Convert(const gazebo::msgs::LaserScanStamped & in, double min_intensity)
{
  sensor_msgs::msg::LaserScan ls;
  ls.header.stamp = Convert<builtin_interfaces::msg::Time>(in.time());
  ls.angle_min = in.scan().angle_min();
  ls.angle_max = in.scan().angle_max();
  ls.angle_increment = in.scan().angle_step();
  ls.time_increment = 0;
  ls.scan_time = 0;
  ls.range_min = in.scan().range_min();
  ls.range_max = in.scan().range_max();

  auto count = in.scan().count();
  auto vertical_count = in.scan().vertical_count();

  // If there are multiple vertical beams, use the one in the middle
  size_t start = (vertical_count / 2) * count;

  // Copy ranges into ROS message
  ls.ranges.resize(count);
  std::copy(
    in.scan().ranges().begin() + start,
    in.scan().ranges().begin() + start + count,
    ls.ranges.begin());

  // Copy intensities into ROS message, clipping at min_intensity
  ls.intensities.resize(count);
  std::transform(
    in.scan().intensities().begin() + start,
    in.scan().intensities().begin() + start + count,
    ls.intensities.begin(), [min_intensity](double i) -> double {
      return i > min_intensity ? i : min_intensity;
    });

  return ls;
}

template<>
inline
sensor_msgs::msg::PointCloud Convert(
  const gazebo::msgs::LaserScanStamped & in,
  double min_intensity)
{
  // Create message to send
  sensor_msgs::msg::PointCloud pc;

  // Fill header
  pc.header.stamp = Convert<builtin_interfaces::msg::Time>(in.time());

  // Cache values that are repeatedly used
  auto count = in.scan().count();
  auto vertical_count = in.scan().vertical_count();
  auto angle_step = in.scan().angle_step();
  auto vertical_angle_step = in.scan().vertical_angle_step();

  // Gazebo sends an infinite vertical step if the number of samples is 1
  // Surprisingly, not setting the <vertical> tag results in nan instead of inf, which is ok
  if (std::isinf(vertical_angle_step)) {
    RCLCPP_WARN_ONCE(conversions_logger, "Infinite angle step results in wrong PointCloud");
  }

  // Setup point cloud fields
  pc.points.reserve(count * vertical_count);
  pc.channels.push_back(sensor_msgs::msg::ChannelFloat32());
  pc.channels[0].values.reserve(count * vertical_count);
  pc.channels[0].name = "intensity";

  // Iterators to range and intensities
  auto range_iter = in.scan().ranges().begin();
  auto intensity_iter = in.scan().intensities().begin();

  // Angles of ray currently processing, azimuth is horizontal, inclination is vertical
  double azimuth, inclination;

  // Index in vertical and horizontal loops
  size_t i, j;

  // Fill pointcloud with laser scan data, converting spherical to Cartesian
  for (j = 0, inclination = in.scan().vertical_angle_min();
    j < vertical_count;
    ++j, inclination += vertical_angle_step)
  {
    double c_inclination = cos(inclination);
    double s_inclination = sin(inclination);
    for (i = 0, azimuth = in.scan().angle_min();
      i < count;
      ++i, azimuth += angle_step, ++range_iter, ++intensity_iter)
    {
      double c_azimuth = cos(azimuth);
      double s_azimuth = sin(azimuth);

      double r = *range_iter;
      // Skip NaN / inf points
      if (!std::isfinite(r)) {
        continue;
      }

      // Get intensity, clipping at min_intensity
      double intensity = *intensity_iter;
      if (intensity < min_intensity) {
        intensity = min_intensity;
      }

      // Convert spherical coordinates to Cartesian for pointcloud
      // See https://en.wikipedia.org/wiki/Spherical_coordinate_system
      geometry_msgs::msg::Point32 point;
      point.x = r * c_inclination * c_azimuth;
      point.y = r * c_inclination * s_azimuth;
      point.z = r * s_inclination;
      pc.points.push_back(point);
      pc.channels[0].values.push_back(intensity);
    }
  }

  return pc;
}

template<>
inline
sensor_msgs::msg::PointCloud2 Convert(
  const gazebo::msgs::LaserScanStamped & in,
  double min_intensity)
{
  // Create message to send
  sensor_msgs::msg::PointCloud2 pc;

  // Pointcloud will be dense, unordered
  pc.height = 1;
  pc.is_dense = true;

  // Fill header
  pc.header.stamp = Convert<builtin_interfaces::msg::Time>(in.time());

  // Cache values that are repeatedly used
  auto count = in.scan().count();
  auto vertical_count = in.scan().vertical_count();
  auto angle_step = in.scan().angle_step();
  auto vertical_angle_step = in.scan().vertical_angle_step();

  // Gazebo sends an infinite vertical step if the number of samples is 1
  // Surprisingly, not setting the <vertical> tag results in nan instead of inf, which is ok
  if (std::isinf(vertical_angle_step)) {
    RCLCPP_WARN_ONCE(conversions_logger, "Infinite angle step results in wrong PointCloud2");
  }

  // Create fields in pointcloud
  sensor_msgs::PointCloud2Modifier pcd_modifier(pc);
  pcd_modifier.setPointCloud2Fields(
    4,
    "x", 1, sensor_msgs::msg::PointField::FLOAT32,
    "y", 1, sensor_msgs::msg::PointField::FLOAT32,
    "z", 1, sensor_msgs::msg::PointField::FLOAT32,
    "intensity", 1, sensor_msgs::msg::PointField::FLOAT32);
  pcd_modifier.resize(vertical_count * count);
  sensor_msgs::PointCloud2Iterator<float> iter_x(pc, "x");
  sensor_msgs::PointCloud2Iterator<float> iter_y(pc, "y");
  sensor_msgs::PointCloud2Iterator<float> iter_z(pc, "z");
  sensor_msgs::PointCloud2Iterator<float> iter_intensity(pc, "intensity");

  // Iterators to range and intensities
  auto range_iter = in.scan().ranges().begin();
  auto intensity_iter = in.scan().intensities().begin();

  // Number of points actually added
  size_t points_added = 0;

  // Angles of ray currently processing, azimuth is horizontal, inclination is vertical
  double azimuth, inclination;

  // Index in vertical and horizontal loops
  size_t i, j;

  // Fill pointcloud with laser scan data, converting spherical to Cartesian
  for (j = 0, inclination = in.scan().vertical_angle_min();
    j < vertical_count;
    ++j, inclination += vertical_angle_step)
  {
    double c_inclination = cos(inclination);
    double s_inclination = sin(inclination);
    for (i = 0, azimuth = in.scan().angle_min();
      i < count;
      ++i, azimuth += angle_step, ++range_iter, ++intensity_iter)
    {
      double c_azimuth = cos(azimuth);
      double s_azimuth = sin(azimuth);

      double r = *range_iter;
      // Skip NaN / inf points
      if (!std::isfinite(r)) {
        continue;
      }

      // Get intensity, clipping at min_intensity
      double intensity = *intensity_iter;
      if (intensity < min_intensity) {
        intensity = min_intensity;
      }

      // Convert spherical coordinates to Cartesian for pointcloud
      // See https://en.wikipedia.org/wiki/Spherical_coordinate_system
      *iter_x = r * c_inclination * c_azimuth;
      *iter_y = r * c_inclination * s_azimuth;
      *iter_z = r * s_inclination;
      *iter_intensity = intensity;

      // Increment ouput iterators
      ++points_added;
      ++iter_x;
      ++iter_y;
      ++iter_z;
      ++iter_intensity;
    }
  }

  pcd_modifier.resize(points_added);

  return pc;
}

template<>
inline
sensor_msgs::msg::Range Convert(const gazebo::msgs::LaserScanStamped & in, double min_intensity)
{
  (void) min_intensity;

  // Create message
  sensor_msgs::msg::Range range_msg;

  // Set stamp from header
  range_msg.header.stamp = Convert<builtin_interfaces::msg::Time>(in.time());

  double horizontal_fov = in.scan().angle_max() - in.scan().angle_min();
  double vertical_fov = in.scan().vertical_angle_max() - in.scan().vertical_angle_min();
  range_msg.field_of_view = std::max(horizontal_fov, vertical_fov);
  range_msg.min_range = in.scan().range_min();
  range_msg.max_range = in.scan().range_max();

  // Set range to the minimum of the ray ranges
  // For single rays, this will just be the range of the ray
  range_msg.range = std::numeric_limits<sensor_msgs::msg::Range::_range_type>::max();
  for (double range : in.scan().ranges()) {
    if (range < range_msg.range) {
      range_msg.range = range;
    }
  }
  return range_msg;
}

}  // namespace gazebo_ros
#endif  // GAZEBO_ROS__CONVERSIONS__SENSOR_MSGS_HPP_