Skip to content

File point_cloud_conversions.hpp

File List > include > nebula_core_ros > point_cloud_conversions.hpp

Go to the documentation of this file

// Copyright 2026 TIER IV, 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.

#pragma once

#include <nebula_core_common/point_cloud.hpp>

#include <sensor_msgs/msg/point_cloud2.hpp>
#include <sensor_msgs/msg/point_field.hpp>

#include <cstdint>
#include <cstring>
#include <memory>
#include <stdexcept>

namespace nebula::ros
{

namespace detail
{

inline uint8_t to_ros_datatype(drivers::PointField::DataType datatype)
{
  switch (datatype) {
    case drivers::PointField::DataType::Int8:
      return sensor_msgs::msg::PointField::INT8;
    case drivers::PointField::DataType::UInt8:
      return sensor_msgs::msg::PointField::UINT8;
    case drivers::PointField::DataType::Int16:
      return sensor_msgs::msg::PointField::INT16;
    case drivers::PointField::DataType::UInt16:
      return sensor_msgs::msg::PointField::UINT16;
    case drivers::PointField::DataType::Int32:
      return sensor_msgs::msg::PointField::INT32;
    case drivers::PointField::DataType::UInt32:
      return sensor_msgs::msg::PointField::UINT32;
    case drivers::PointField::DataType::Float32:
      return sensor_msgs::msg::PointField::FLOAT32;
    case drivers::PointField::DataType::Float64:
      return sensor_msgs::msg::PointField::FLOAT64;
    default:
      throw std::runtime_error("Invalid PointField::DataType");
  }
}

inline uint32_t datatype_size(drivers::PointField::DataType datatype)
{
  switch (datatype) {
    case drivers::PointField::DataType::Int8:
    case drivers::PointField::DataType::UInt8:
      return 1;
    case drivers::PointField::DataType::Int16:
    case drivers::PointField::DataType::UInt16:
      return 2;
    case drivers::PointField::DataType::Int32:
    case drivers::PointField::DataType::UInt32:
    case drivers::PointField::DataType::Float32:
      return 4;
    case drivers::PointField::DataType::Float64:
      return 8;
    default:
      throw std::runtime_error("Invalid PointField::DataType");
  }
}

}  // namespace detail

template <typename PointT>
sensor_msgs::msg::PointCloud2 to_ros_msg(const drivers::PointCloud<PointT> & cloud)
{
  sensor_msgs::msg::PointCloud2 msg;

  // Set dimensions
  msg.height = 1;
  msg.width = static_cast<uint32_t>(cloud.size());

  // Build fields from point type metadata
  const auto point_fields = PointT::fields();
  msg.fields.reserve(point_fields.size());

  for (const auto & field : point_fields) {
    sensor_msgs::msg::PointField ros_field;
    ros_field.name = field.name;
    ros_field.offset = field.offset;
    ros_field.datatype = detail::to_ros_datatype(field.datatype);
    ros_field.count = field.count;
    msg.fields.push_back(ros_field);
  }

  // Set point step (size of one point)
  msg.point_step = static_cast<uint32_t>(sizeof(PointT));
  msg.row_step = msg.point_step * msg.width;

  // Set endianness (assume little endian for x86/ARM)
  msg.is_bigendian = false;

  // Assume dense point cloud (no invalid points)
  msg.is_dense = true;

  // Copy point data
  const size_t data_size = cloud.size() * sizeof(PointT);
  msg.data.resize(data_size);
  if (!cloud.empty()) {
    std::memcpy(msg.data.data(), cloud.data(), data_size);
  }

  return msg;
}

template <typename PointT>
sensor_msgs::msg::PointCloud2 to_ros_msg(const std::shared_ptr<drivers::PointCloud<PointT>> & cloud)
{
  if (!cloud) {
    return sensor_msgs::msg::PointCloud2{};
  }
  return to_ros_msg(*cloud);
}

template <typename PointT>
sensor_msgs::msg::PointCloud2 to_ros_msg(
  const std::shared_ptr<const drivers::PointCloud<PointT>> & cloud)
{
  if (!cloud) {
    return sensor_msgs::msg::PointCloud2{};
  }
  return to_ros_msg(*cloud);
}

}  // namespace nebula::ros