add humble-navigation2

This commit is contained in:
X-lanni
2025-05-27 19:03:40 +08:00
parent 974abb5e1e
commit e74ec539c2
1280 changed files with 204114 additions and 0 deletions
@@ -0,0 +1,268 @@
/*********************************************************************
*
* Software License Agreement (BSD License)
*
* Copyright (c) 2022 Samsung R&D Institute Russia
* All rights reserved.
*
* 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 <ORGANIZATION> 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 OWNER 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.
*
* Author: Alexey Merzlyakov
*********************************************************************/
#include "nav2_costmap_2d/costmap_filters/binary_filter.hpp"
#include <cmath>
#include <utility>
#include <memory>
#include <string>
#include "nav2_costmap_2d/costmap_filters/filter_values.hpp"
#include "nav2_util/occ_grid_values.hpp"
namespace nav2_costmap_2d
{
BinaryFilter::BinaryFilter()
: filter_info_sub_(nullptr), mask_sub_(nullptr),
binary_state_pub_(nullptr), filter_mask_(nullptr), mask_frame_(""), global_frame_(""),
default_state_(false), binary_state_(default_state_)
{
}
void BinaryFilter::initializeFilter(
const std::string & filter_info_topic)
{
std::lock_guard<CostmapFilter::mutex_t> guard(*getMutex());
rclcpp_lifecycle::LifecycleNode::SharedPtr node = node_.lock();
if (!node) {
throw std::runtime_error{"Failed to lock node"};
}
// Declare parameters specific to BinaryFilter only
std::string binary_state_topic;
declareParameter("default_state", rclcpp::ParameterValue(false));
node->get_parameter(name_ + "." + "default_state", default_state_);
declareParameter("binary_state_topic", rclcpp::ParameterValue("binary_state"));
node->get_parameter(name_ + "." + "binary_state_topic", binary_state_topic);
declareParameter("flip_threshold", rclcpp::ParameterValue(50.0));
node->get_parameter(name_ + "." + "flip_threshold", flip_threshold_);
filter_info_topic_ = filter_info_topic;
// Setting new costmap filter info subscriber
RCLCPP_INFO(
logger_,
"BinaryFilter: Subscribing to \"%s\" topic for filter info...",
filter_info_topic_.c_str());
filter_info_sub_ = node->create_subscription<nav2_msgs::msg::CostmapFilterInfo>(
filter_info_topic_, rclcpp::QoS(rclcpp::KeepLast(1)).transient_local().reliable(),
std::bind(&BinaryFilter::filterInfoCallback, this, std::placeholders::_1));
// Get global frame required for binary state publisher
global_frame_ = layered_costmap_->getGlobalFrameID();
// Create new binary state publisher
binary_state_pub_ = node->create_publisher<std_msgs::msg::Bool>(
binary_state_topic, rclcpp::QoS(10));
binary_state_pub_->on_activate();
// Reset parameters
base_ = BASE_DEFAULT;
multiplier_ = MULTIPLIER_DEFAULT;
// Initialize state as "false" by-default
changeState(default_state_);
}
void BinaryFilter::filterInfoCallback(
const nav2_msgs::msg::CostmapFilterInfo::SharedPtr msg)
{
std::lock_guard<CostmapFilter::mutex_t> guard(*getMutex());
rclcpp_lifecycle::LifecycleNode::SharedPtr node = node_.lock();
if (!node) {
throw std::runtime_error{"Failed to lock node"};
}
if (!mask_sub_) {
RCLCPP_INFO(
logger_,
"BinaryFilter: Received filter info from %s topic.", filter_info_topic_.c_str());
} else {
RCLCPP_WARN(
logger_,
"BinaryFilter: New costmap filter info arrived from %s topic. Updating old filter info.",
filter_info_topic_.c_str());
// Resetting previous subscriber each time when new costmap filter information arrives
mask_sub_.reset();
}
if (msg->type != BINARY_FILTER) {
RCLCPP_ERROR(logger_, "BinaryFilter: Mode %i is not supported", msg->type);
return;
}
// Set base_ and multiplier_
base_ = msg->base;
multiplier_ = msg->multiplier;
// Set topic name to receive filter mask from
mask_topic_ = msg->filter_mask_topic;
// Setting new filter mask subscriber
RCLCPP_INFO(
logger_,
"BinaryFilter: Subscribing to \"%s\" topic for filter mask...",
mask_topic_.c_str());
mask_sub_ = node->create_subscription<nav_msgs::msg::OccupancyGrid>(
mask_topic_, rclcpp::QoS(rclcpp::KeepLast(1)).transient_local().reliable(),
std::bind(&BinaryFilter::maskCallback, this, std::placeholders::_1));
}
void BinaryFilter::maskCallback(
const nav_msgs::msg::OccupancyGrid::SharedPtr msg)
{
std::lock_guard<CostmapFilter::mutex_t> guard(*getMutex());
if (!filter_mask_) {
RCLCPP_INFO(
logger_,
"BinaryFilter: Received filter mask from %s topic.", mask_topic_.c_str());
} else {
RCLCPP_WARN(
logger_,
"BinaryFilter: New filter mask arrived from %s topic. Updating old filter mask.",
mask_topic_.c_str());
filter_mask_.reset();
}
filter_mask_ = msg;
mask_frame_ = msg->header.frame_id;
}
void BinaryFilter::process(
nav2_costmap_2d::Costmap2D & /*master_grid*/,
int /*min_i*/, int /*min_j*/, int /*max_i*/, int /*max_j*/,
const geometry_msgs::msg::Pose2D & pose)
{
std::lock_guard<CostmapFilter::mutex_t> guard(*getMutex());
if (!filter_mask_) {
// Show warning message every 2 seconds to not litter an output
RCLCPP_WARN_THROTTLE(
logger_, *(clock_), 2000,
"BinaryFilter: Filter mask was not received");
return;
}
geometry_msgs::msg::Pose2D mask_pose; // robot coordinates in mask frame
// Transforming robot pose from current layer frame to mask frame
if (!transformPose(global_frame_, pose, mask_frame_, mask_pose)) {
return;
}
// Converting mask_pose robot position to filter_mask_ indexes (mask_robot_i, mask_robot_j)
unsigned int mask_robot_i, mask_robot_j;
if (!worldToMask(filter_mask_, mask_pose.x, mask_pose.y, mask_robot_i, mask_robot_j)) {
// Robot went out of mask range. Set "false" state by-default
RCLCPP_WARN(
logger_,
"BinaryFilter: Robot is outside of filter mask. Resetting binary state to default.");
changeState(default_state_);
return;
}
// Getting filter_mask data from cell where the robot placed
int8_t mask_data = getMaskData(filter_mask_, mask_robot_i, mask_robot_j);
if (mask_data == nav2_util::OCC_GRID_UNKNOWN) {
// Corresponding filter mask cell is unknown.
// Warn and do nothing.
RCLCPP_WARN_THROTTLE(
logger_, *(clock_), 2000,
"BinaryFilter: Filter mask [%i, %i] data is unknown. Do nothing.",
mask_robot_i, mask_robot_j);
return;
}
// Check and flip binary state, if necessary
if (base_ + mask_data * multiplier_ > flip_threshold_) {
if (binary_state_ == default_state_) {
changeState(!default_state_);
}
} else {
if (binary_state_ != default_state_) {
changeState(default_state_);
}
}
}
void BinaryFilter::resetFilter()
{
std::lock_guard<CostmapFilter::mutex_t> guard(*getMutex());
RCLCPP_INFO(logger_, "BinaryFilter: Resetting the filter to default state");
changeState(default_state_);
filter_info_sub_.reset();
mask_sub_.reset();
if (binary_state_pub_) {
binary_state_pub_->on_deactivate();
binary_state_pub_.reset();
}
}
bool BinaryFilter::isActive()
{
std::lock_guard<CostmapFilter::mutex_t> guard(*getMutex());
if (filter_mask_) {
return true;
}
return false;
}
void BinaryFilter::changeState(const bool state)
{
binary_state_ = state;
if (state) {
RCLCPP_INFO(logger_, "BinaryFilter: Switched on");
} else {
RCLCPP_INFO(logger_, "BinaryFilter: Switched off");
}
// Forming and publishing new BinaryState message
std::unique_ptr<std_msgs::msg::Bool> msg =
std::make_unique<std_msgs::msg::Bool>();
msg->data = state;
binary_state_pub_->publish(std::move(msg));
}
} // namespace nav2_costmap_2d
#include "pluginlib/class_list_macros.hpp"
PLUGINLIB_EXPORT_CLASS(nav2_costmap_2d::BinaryFilter, nav2_costmap_2d::Layer)
@@ -0,0 +1,212 @@
/*********************************************************************
*
* Software License Agreement (BSD License)
*
* Copyright (c) 2008, 2013, Willow Garage, Inc.
* Copyright (c) 2020 Samsung Research Russia
* All rights reserved.
*
* 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 <ORGANIZATION> 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 OWNER 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.
*
* Author: Eitan Marder-Eppstein
* David V. Lu!!
* Alexey Merzlyakov
*********************************************************************/
#include "nav2_costmap_2d/costmap_filters/costmap_filter.hpp"
#include <exception>
#include "tf2_geometry_msgs/tf2_geometry_msgs.hpp"
#include "geometry_msgs/msg/point_stamped.hpp"
namespace nav2_costmap_2d
{
CostmapFilter::CostmapFilter()
: filter_info_topic_(""), mask_topic_("")
{
access_ = new mutex_t();
}
CostmapFilter::~CostmapFilter()
{
delete access_;
}
void CostmapFilter::onInitialize()
{
rclcpp_lifecycle::LifecycleNode::SharedPtr node = node_.lock();
if (!node) {
throw std::runtime_error{"Failed to lock node"};
}
try {
// Declare common for all costmap filters parameters
declareParameter("enabled", rclcpp::ParameterValue(true));
declareParameter("filter_info_topic", rclcpp::PARAMETER_STRING);
declareParameter("transform_tolerance", rclcpp::ParameterValue(0.1));
// Get parameters
node->get_parameter(name_ + "." + "enabled", enabled_);
filter_info_topic_ = node->get_parameter(name_ + "." + "filter_info_topic").as_string();
double transform_tolerance {};
node->get_parameter(name_ + "." + "transform_tolerance", transform_tolerance);
transform_tolerance_ = tf2::durationFromSec(transform_tolerance);
// Costmap Filter enabling service
enable_service_ = node->create_service<std_srvs::srv::SetBool>(
name_ + "/toggle_filter",
std::bind(
&CostmapFilter::enableCallback, this,
std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
} catch (const std::exception & ex) {
RCLCPP_ERROR(logger_, "Parameter problem: %s", ex.what());
throw ex;
}
}
void CostmapFilter::activate()
{
initializeFilter(filter_info_topic_);
}
void CostmapFilter::deactivate()
{
resetFilter();
}
void CostmapFilter::reset()
{
resetFilter();
initializeFilter(filter_info_topic_);
current_ = false;
}
void CostmapFilter::updateBounds(
double robot_x, double robot_y, double robot_yaw,
double * /*min_x*/, double * /*min_y*/, double * /*max_x*/, double * /*max_y*/)
{
if (!enabled_) {
return;
}
latest_pose_.x = robot_x;
latest_pose_.y = robot_y;
latest_pose_.theta = robot_yaw;
}
void CostmapFilter::updateCosts(
nav2_costmap_2d::Costmap2D & master_grid,
int min_i, int min_j, int max_i, int max_j)
{
if (!enabled_) {
return;
}
process(master_grid, min_i, min_j, max_i, max_j, latest_pose_);
current_ = true;
}
void CostmapFilter::enableCallback(
const std::shared_ptr<rmw_request_id_t>/*request_header*/,
const std::shared_ptr<std_srvs::srv::SetBool::Request> request,
std::shared_ptr<std_srvs::srv::SetBool::Response> response)
{
enabled_ = request->data;
response->success = true;
if (enabled_) {
response->message = "Enabled";
} else {
response->message = "Disabled";
}
}
bool CostmapFilter::transformPose(
const std::string global_frame,
const geometry_msgs::msg::Pose2D & global_pose,
const std::string mask_frame,
geometry_msgs::msg::Pose2D & mask_pose) const
{
if (mask_frame != global_frame) {
// Filter mask and current layer are in different frames:
// Transform (global_pose.x, global_pose.y) point from current layer frame (global_frame)
// to mask_pose point in mask_frame
geometry_msgs::msg::TransformStamped transform;
geometry_msgs::msg::PointStamped in, out;
in.header.stamp = clock_->now();
in.header.frame_id = global_frame;
in.point.x = global_pose.x;
in.point.y = global_pose.y;
in.point.z = 0;
try {
tf_->transform(in, out, mask_frame, transform_tolerance_);
} catch (tf2::TransformException & ex) {
RCLCPP_ERROR(
logger_,
"CostmapFilter: failed to get costmap frame (%s) "
"transformation to mask frame (%s) with error: %s",
global_frame.c_str(), mask_frame.c_str(), ex.what());
return false;
}
mask_pose.x = out.point.x;
mask_pose.y = out.point.y;
} else {
// Filter mask and current layer are in the same frame:
// Just use global_pose coordinates
mask_pose = global_pose;
}
return true;
}
bool CostmapFilter::worldToMask(
nav_msgs::msg::OccupancyGrid::ConstSharedPtr filter_mask,
double wx, double wy, unsigned int & mx, unsigned int & my) const
{
const double origin_x = filter_mask->info.origin.position.x;
const double origin_y = filter_mask->info.origin.position.y;
const double resolution = filter_mask->info.resolution;
const unsigned int size_x = filter_mask->info.width;
const unsigned int size_y = filter_mask->info.height;
if (wx < origin_x || wy < origin_y) {
return false;
}
mx = static_cast<unsigned int>((wx - origin_x) / resolution);
my = static_cast<unsigned int>((wy - origin_y) / resolution);
if (mx >= size_x || my >= size_y) {
return false;
}
return true;
}
} // namespace nav2_costmap_2d
@@ -0,0 +1,315 @@
/*********************************************************************
*
* Software License Agreement (BSD License)
*
* Copyright (c) 2020 Samsung Research Russia
* All rights reserved.
*
* 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 <ORGANIZATION> 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 OWNER 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.
*
* Author: Alexey Merzlyakov
*********************************************************************/
#include <string>
#include <memory>
#include <algorithm>
#include "tf2/convert.h"
#include "tf2_geometry_msgs/tf2_geometry_msgs.hpp"
#include "nav2_costmap_2d/costmap_filters/keepout_filter.hpp"
#include "nav2_costmap_2d/costmap_filters/filter_values.hpp"
namespace nav2_costmap_2d
{
KeepoutFilter::KeepoutFilter()
: filter_info_sub_(nullptr), mask_sub_(nullptr), mask_costmap_(nullptr),
mask_frame_(""), global_frame_("")
{
}
void KeepoutFilter::initializeFilter(
const std::string & filter_info_topic)
{
std::lock_guard<CostmapFilter::mutex_t> guard(*getMutex());
rclcpp_lifecycle::LifecycleNode::SharedPtr node = node_.lock();
if (!node) {
throw std::runtime_error{"Failed to lock node"};
}
filter_info_topic_ = filter_info_topic;
// Setting new costmap filter info subscriber
RCLCPP_INFO(
logger_,
"KeepoutFilter: Subscribing to \"%s\" topic for filter info...",
filter_info_topic_.c_str());
filter_info_sub_ = node->create_subscription<nav2_msgs::msg::CostmapFilterInfo>(
filter_info_topic_, rclcpp::QoS(rclcpp::KeepLast(1)).transient_local().reliable(),
std::bind(&KeepoutFilter::filterInfoCallback, this, std::placeholders::_1));
global_frame_ = layered_costmap_->getGlobalFrameID();
}
void KeepoutFilter::filterInfoCallback(
const nav2_msgs::msg::CostmapFilterInfo::SharedPtr msg)
{
std::lock_guard<CostmapFilter::mutex_t> guard(*getMutex());
rclcpp_lifecycle::LifecycleNode::SharedPtr node = node_.lock();
if (!node) {
throw std::runtime_error{"Failed to lock node"};
}
if (!mask_sub_) {
RCLCPP_INFO(
logger_,
"KeepoutFilter: Received filter info from %s topic.", filter_info_topic_.c_str());
} else {
RCLCPP_WARN(
logger_,
"KeepoutFilter: New costmap filter info arrived from %s topic. Updating old filter info.",
filter_info_topic_.c_str());
// Resetting previous subscriber each time when new costmap filter information arrives
mask_sub_.reset();
}
// Checking that base and multiplier are set to their default values
if (msg->base != BASE_DEFAULT || msg->multiplier != MULTIPLIER_DEFAULT) {
RCLCPP_ERROR(
logger_,
"KeepoutFilter: For proper use of keepout filter base and multiplier"
" in CostmapFilterInfo message should be set to their default values (%f and %f)",
BASE_DEFAULT, MULTIPLIER_DEFAULT);
}
mask_topic_ = msg->filter_mask_topic;
// Setting new filter mask subscriber
RCLCPP_INFO(
logger_,
"KeepoutFilter: Subscribing to \"%s\" topic for filter mask...",
mask_topic_.c_str());
mask_sub_ = node->create_subscription<nav_msgs::msg::OccupancyGrid>(
mask_topic_, rclcpp::QoS(rclcpp::KeepLast(1)).transient_local().reliable(),
std::bind(&KeepoutFilter::maskCallback, this, std::placeholders::_1));
}
void KeepoutFilter::maskCallback(
const nav_msgs::msg::OccupancyGrid::SharedPtr msg)
{
std::lock_guard<CostmapFilter::mutex_t> guard(*getMutex());
rclcpp_lifecycle::LifecycleNode::SharedPtr node = node_.lock();
if (!node) {
throw std::runtime_error{"Failed to lock node"};
}
if (!mask_costmap_) {
RCLCPP_INFO(
logger_,
"KeepoutFilter: Received filter mask from %s topic.", mask_topic_.c_str());
} else {
RCLCPP_WARN(
logger_,
"KeepoutFilter: New filter mask arrived from %s topic. Updating old filter mask.",
mask_topic_.c_str());
mask_costmap_.reset();
}
// Making a new mask_costmap_
mask_costmap_ = std::make_unique<Costmap2D>(*msg);
mask_frame_ = msg->header.frame_id;
}
void KeepoutFilter::process(
nav2_costmap_2d::Costmap2D & master_grid,
int min_i, int min_j, int max_i, int max_j,
const geometry_msgs::msg::Pose2D & /*pose*/)
{
std::lock_guard<CostmapFilter::mutex_t> guard(*getMutex());
if (!mask_costmap_) {
// Show warning message every 2 seconds to not litter an output
RCLCPP_WARN_THROTTLE(
logger_, *(clock_), 2000,
"KeepoutFilter: Filter mask was not received");
return;
}
tf2::Transform tf2_transform;
tf2_transform.setIdentity(); // initialize by identical transform
int mg_min_x, mg_min_y; // masger_grid indexes of bottom-left window corner
int mg_max_x, mg_max_y; // masger_grid indexes of top-right window corner
if (mask_frame_ != global_frame_) {
// Filter mask and current layer are in different frames:
// prepare frame transformation if mask_frame_ != global_frame_
geometry_msgs::msg::TransformStamped transform;
try {
transform = tf_->lookupTransform(
mask_frame_, global_frame_, tf2::TimePointZero,
transform_tolerance_);
} catch (tf2::TransformException & ex) {
RCLCPP_ERROR(
logger_,
"KeepoutFilter: Failed to get costmap frame (%s) "
"transformation to mask frame (%s) with error: %s",
global_frame_.c_str(), mask_frame_.c_str(), ex.what());
return;
}
tf2::fromMsg(transform.transform, tf2_transform);
mg_min_x = min_i;
mg_min_y = min_j;
mg_max_x = max_i;
mg_max_y = max_j;
} else {
// Filter mask and current layer are in the same frame:
// apply the following optimization - iterate only in overlapped
// (min_i, min_j)..(max_i, max_j) & mask_costmap_ area.
//
// mask_costmap_
// *----------------------------*
// | |
// | |
// | (2) |
// *-----+-------* |
// | |///////|<- overlapped area |
// | |///////| to iterate in |
// | *-------+--------------------*
// | (1) |
// | |
// *-------------*
// master_grid (min_i, min_j)..(max_i, max_j) window
//
// ToDo: after costmap rotation will be added, this should be re-worked.
double wx, wy; // world coordinates
// Calculating bounds corresponding to bottom-left overlapping (1) corner
// mask_costmap_ -> master_grid intexes conversion
const double half_cell_size = 0.5 * mask_costmap_->getResolution();
wx = mask_costmap_->getOriginX() + half_cell_size;
wy = mask_costmap_->getOriginY() + half_cell_size;
master_grid.worldToMapNoBounds(wx, wy, mg_min_x, mg_min_y);
// Calculation of (1) corner bounds
if (mg_min_x >= max_i || mg_min_y >= max_j) {
// There is no overlapping. Do nothing.
return;
}
mg_min_x = std::max(min_i, mg_min_x);
mg_min_y = std::max(min_j, mg_min_y);
// Calculating bounds corresponding to top-right window (2) corner
// mask_costmap_ -> master_grid intexes conversion
wx = mask_costmap_->getOriginX() +
mask_costmap_->getSizeInCellsX() * mask_costmap_->getResolution() + half_cell_size;
wy = mask_costmap_->getOriginY() +
mask_costmap_->getSizeInCellsY() * mask_costmap_->getResolution() + half_cell_size;
master_grid.worldToMapNoBounds(wx, wy, mg_max_x, mg_max_y);
// Calculation of (2) corner bounds
if (mg_max_x <= min_i || mg_max_y <= min_j) {
// There is no overlapping. Do nothing.
return;
}
mg_max_x = std::min(max_i, mg_max_x);
mg_max_y = std::min(max_j, mg_max_y);
}
// unsigned<-signed conversions.
unsigned const int mg_min_x_u = static_cast<unsigned int>(mg_min_x);
unsigned const int mg_min_y_u = static_cast<unsigned int>(mg_min_y);
unsigned const int mg_max_x_u = static_cast<unsigned int>(mg_max_x);
unsigned const int mg_max_y_u = static_cast<unsigned int>(mg_max_y);
unsigned int i, j; // master_grid iterators
unsigned int index; // corresponding index of master_grid
double gl_wx, gl_wy; // world coordinates in a global_frame_
double msk_wx, msk_wy; // world coordinates in a mask_frame_
unsigned int mx, my; // mask_costmap_ coordinates
unsigned char data, old_data; // master_grid element data
// Main master_grid updating loop
// Iterate in costmap window by master_grid indexes
unsigned char * master_array = master_grid.getCharMap();
for (i = mg_min_x_u; i < mg_max_x_u; i++) {
for (j = mg_min_y_u; j < mg_max_y_u; j++) {
index = master_grid.getIndex(i, j);
old_data = master_array[index];
// Calculating corresponding to (i, j) point at mask_costmap_:
// Get world coordinates in global_frame_
master_grid.mapToWorld(i, j, gl_wx, gl_wy);
if (mask_frame_ != global_frame_) {
// Transform (i, j) point from global_frame_ to mask_frame_
tf2::Vector3 point(gl_wx, gl_wy, 0);
point = tf2_transform * point;
msk_wx = point.x();
msk_wy = point.y();
} else {
// In this case master_grid and filter-mask are in the same frame
msk_wx = gl_wx;
msk_wy = gl_wy;
}
// Get mask coordinates corresponding to (i, j) point at mask_costmap_
if (mask_costmap_->worldToMap(msk_wx, msk_wy, mx, my)) {
data = mask_costmap_->getCost(mx, my);
// Update if mask_ data is valid and greater than existing master_grid's one
if (data == NO_INFORMATION) {
continue;
}
if (data > old_data || old_data == NO_INFORMATION) {
master_array[index] = data;
}
}
}
}
}
void KeepoutFilter::resetFilter()
{
std::lock_guard<CostmapFilter::mutex_t> guard(*getMutex());
filter_info_sub_.reset();
mask_sub_.reset();
}
bool KeepoutFilter::isActive()
{
std::lock_guard<CostmapFilter::mutex_t> guard(*getMutex());
if (mask_costmap_) {
return true;
}
return false;
}
} // namespace nav2_costmap_2d
#include "pluginlib/class_list_macros.hpp"
PLUGINLIB_EXPORT_CLASS(nav2_costmap_2d::KeepoutFilter, nav2_costmap_2d::Layer)
@@ -0,0 +1,288 @@
/*********************************************************************
*
* Software License Agreement (BSD License)
*
* Copyright (c) 2020 Samsung Research Russia
* All rights reserved.
*
* 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 <ORGANIZATION> 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 OWNER 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.
*
* Author: Alexey Merzlyakov
*********************************************************************/
#include "nav2_costmap_2d/costmap_filters/speed_filter.hpp"
#include <cmath>
#include <utility>
#include <memory>
#include <string>
#include "nav2_costmap_2d/costmap_filters/filter_values.hpp"
namespace nav2_costmap_2d
{
SpeedFilter::SpeedFilter()
: filter_info_sub_(nullptr), mask_sub_(nullptr),
speed_limit_pub_(nullptr), filter_mask_(nullptr), mask_frame_(""), global_frame_(""),
speed_limit_(NO_SPEED_LIMIT), speed_limit_prev_(NO_SPEED_LIMIT)
{
}
void SpeedFilter::initializeFilter(
const std::string & filter_info_topic)
{
std::lock_guard<CostmapFilter::mutex_t> guard(*getMutex());
rclcpp_lifecycle::LifecycleNode::SharedPtr node = node_.lock();
if (!node) {
throw std::runtime_error{"Failed to lock node"};
}
// Declare "speed_limit_topic" parameter specific to SpeedFilter only
std::string speed_limit_topic;
declareParameter("speed_limit_topic", rclcpp::ParameterValue("speed_limit"));
node->get_parameter(name_ + "." + "speed_limit_topic", speed_limit_topic);
filter_info_topic_ = filter_info_topic;
// Setting new costmap filter info subscriber
RCLCPP_INFO(
logger_,
"SpeedFilter: Subscribing to \"%s\" topic for filter info...",
filter_info_topic_.c_str());
filter_info_sub_ = node->create_subscription<nav2_msgs::msg::CostmapFilterInfo>(
filter_info_topic_, rclcpp::QoS(rclcpp::KeepLast(1)).transient_local().reliable(),
std::bind(&SpeedFilter::filterInfoCallback, this, std::placeholders::_1));
// Get global frame required for speed limit publisher
global_frame_ = layered_costmap_->getGlobalFrameID();
// Create new speed limit publisher
speed_limit_pub_ = node->create_publisher<nav2_msgs::msg::SpeedLimit>(
speed_limit_topic, rclcpp::QoS(10));
speed_limit_pub_->on_activate();
// Reset speed conversion states
base_ = BASE_DEFAULT;
multiplier_ = MULTIPLIER_DEFAULT;
percentage_ = false;
}
void SpeedFilter::filterInfoCallback(
const nav2_msgs::msg::CostmapFilterInfo::SharedPtr msg)
{
std::lock_guard<CostmapFilter::mutex_t> guard(*getMutex());
rclcpp_lifecycle::LifecycleNode::SharedPtr node = node_.lock();
if (!node) {
throw std::runtime_error{"Failed to lock node"};
}
if (!mask_sub_) {
RCLCPP_INFO(
logger_,
"SpeedFilter: Received filter info from %s topic.", filter_info_topic_.c_str());
} else {
RCLCPP_WARN(
logger_,
"SpeedFilter: New costmap filter info arrived from %s topic. Updating old filter info.",
filter_info_topic_.c_str());
// Resetting previous subscriber each time when new costmap filter information arrives
mask_sub_.reset();
}
// Set base_/multiplier_ or use speed limit in % of maximum speed
base_ = msg->base;
multiplier_ = msg->multiplier;
if (msg->type == SPEED_FILTER_PERCENT) {
// Using speed limit in % of maximum speed
percentage_ = true;
RCLCPP_INFO(
logger_,
"SpeedFilter: Using expressed in a percent from maximum speed"
"speed_limit = %f + filter_mask_data * %f",
base_, multiplier_);
} else if (msg->type == SPEED_FILTER_ABSOLUTE) {
// Using speed limit in m/s
percentage_ = false;
RCLCPP_INFO(
logger_,
"SpeedFilter: Using absolute speed_limit = %f + filter_mask_data * %f",
base_, multiplier_);
} else {
RCLCPP_ERROR(logger_, "SpeedFilter: Mode is not supported");
return;
}
mask_topic_ = msg->filter_mask_topic;
// Setting new filter mask subscriber
RCLCPP_INFO(
logger_,
"SpeedFilter: Subscribing to \"%s\" topic for filter mask...",
mask_topic_.c_str());
mask_sub_ = node->create_subscription<nav_msgs::msg::OccupancyGrid>(
mask_topic_, rclcpp::QoS(rclcpp::KeepLast(1)).transient_local().reliable(),
std::bind(&SpeedFilter::maskCallback, this, std::placeholders::_1));
}
void SpeedFilter::maskCallback(
const nav_msgs::msg::OccupancyGrid::SharedPtr msg)
{
std::lock_guard<CostmapFilter::mutex_t> guard(*getMutex());
if (!filter_mask_) {
RCLCPP_INFO(
logger_,
"SpeedFilter: Received filter mask from %s topic.", mask_topic_.c_str());
} else {
RCLCPP_WARN(
logger_,
"SpeedFilter: New filter mask arrived from %s topic. Updating old filter mask.",
mask_topic_.c_str());
filter_mask_.reset();
}
filter_mask_ = msg;
mask_frame_ = msg->header.frame_id;
}
void SpeedFilter::process(
nav2_costmap_2d::Costmap2D & /*master_grid*/,
int /*min_i*/, int /*min_j*/, int /*max_i*/, int /*max_j*/,
const geometry_msgs::msg::Pose2D & pose)
{
std::lock_guard<CostmapFilter::mutex_t> guard(*getMutex());
if (!filter_mask_) {
// Show warning message every 2 seconds to not litter an output
RCLCPP_WARN_THROTTLE(
logger_, *(clock_), 2000,
"SpeedFilter: Filter mask was not received");
return;
}
geometry_msgs::msg::Pose2D mask_pose; // robot coordinates in mask frame
// Transforming robot pose from current layer frame to mask frame
if (!transformPose(global_frame_, pose, mask_frame_, mask_pose)) {
return;
}
// Converting mask_pose robot position to filter_mask_ indexes (mask_robot_i, mask_robot_j)
unsigned int mask_robot_i, mask_robot_j;
if (!worldToMask(filter_mask_, mask_pose.x, mask_pose.y, mask_robot_i, mask_robot_j)) {
return;
}
// Getting filter_mask data from cell where the robot placed and
// calculating speed limit value
int8_t speed_mask_data = getMaskData(filter_mask_, mask_robot_i, mask_robot_j);
if (speed_mask_data == SPEED_MASK_NO_LIMIT) {
// Corresponding filter mask cell is free.
// Setting no speed limit there.
speed_limit_ = NO_SPEED_LIMIT;
} else if (speed_mask_data == SPEED_MASK_UNKNOWN) {
// Corresponding filter mask cell is unknown.
// Do nothing.
RCLCPP_ERROR(
logger_,
"SpeedFilter: Found unknown cell in filter_mask[%i, %i], "
"which is invalid for this kind of filter",
mask_robot_i, mask_robot_j);
return;
} else {
// Normal case: speed_mask_data in range of [1..100]
speed_limit_ = speed_mask_data * multiplier_ + base_;
if (percentage_) {
if (speed_limit_ < 0.0 || speed_limit_ > 100.0) {
RCLCPP_WARN(
logger_,
"SpeedFilter: Speed limit in filter_mask[%i, %i] is %f%%, "
"out of bounds of [0, 100]. Setting it to no-limit value.",
mask_robot_i, mask_robot_j, speed_limit_);
speed_limit_ = NO_SPEED_LIMIT;
}
} else {
if (speed_limit_ < 0.0) {
RCLCPP_WARN(
logger_,
"SpeedFilter: Speed limit in filter_mask[%i, %i] is less than 0 m/s, "
"which can not be true. Setting it to no-limit value.",
mask_robot_i, mask_robot_j);
speed_limit_ = NO_SPEED_LIMIT;
}
}
}
if (speed_limit_ != speed_limit_prev_) {
if (speed_limit_ != NO_SPEED_LIMIT) {
RCLCPP_DEBUG(logger_, "SpeedFilter: Speed limit is set to %f", speed_limit_);
} else {
RCLCPP_DEBUG(logger_, "SpeedFilter: Speed limit is set to its default value");
}
// Forming and publishing new SpeedLimit message
std::unique_ptr<nav2_msgs::msg::SpeedLimit> msg =
std::make_unique<nav2_msgs::msg::SpeedLimit>();
msg->header.frame_id = global_frame_;
msg->header.stamp = clock_->now();
msg->percentage = percentage_;
msg->speed_limit = speed_limit_;
speed_limit_pub_->publish(std::move(msg));
speed_limit_prev_ = speed_limit_;
}
}
void SpeedFilter::resetFilter()
{
std::lock_guard<CostmapFilter::mutex_t> guard(*getMutex());
filter_info_sub_.reset();
mask_sub_.reset();
if (speed_limit_pub_) {
speed_limit_pub_->on_deactivate();
speed_limit_pub_.reset();
}
}
bool SpeedFilter::isActive()
{
std::lock_guard<CostmapFilter::mutex_t> guard(*getMutex());
if (filter_mask_) {
return true;
}
return false;
}
} // namespace nav2_costmap_2d
#include "pluginlib/class_list_macros.hpp"
PLUGINLIB_EXPORT_CLASS(nav2_costmap_2d::SpeedFilter, nav2_costmap_2d::Layer)
@@ -0,0 +1,211 @@
// Copyright (c) 2023 Andrey Ryzhikov
//
// 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.
#include "nav2_costmap_2d/denoise_layer.hpp"
#include <string>
#include <vector>
#include <algorithm>
#include <memory>
#include "rclcpp/rclcpp.hpp"
namespace nav2_costmap_2d
{
void
DenoiseLayer::onInitialize()
{
// Enable/disable plugin
declareParameter("enabled", rclcpp::ParameterValue(true));
// Smaller groups should be filtered
declareParameter("minimal_group_size", rclcpp::ParameterValue(2));
// Pixels connectivity type
declareParameter("group_connectivity_type", rclcpp::ParameterValue(8));
const auto node = node_.lock();
if (!node) {
throw std::runtime_error("DenoiseLayer::onInitialize: Failed to lock node");
}
node->get_parameter(name_ + "." + "enabled", enabled_);
auto getInt = [&](const std::string & parameter_name) {
int param{};
node->get_parameter(name_ + "." + parameter_name, param);
return param;
};
const int minimal_group_size_param = getInt("minimal_group_size");
if (minimal_group_size_param <= 1) {
RCLCPP_WARN(
logger_,
"DenoiseLayer::onInitialize(): param minimal_group_size: %i."
" A value of 1 or less means that all map cells will be left as they are.",
minimal_group_size_param);
minimal_group_size_ = 1;
} else {
minimal_group_size_ = static_cast<size_t>(minimal_group_size_param);
}
const int group_connectivity_type_param = getInt("group_connectivity_type");
if (group_connectivity_type_param == 4) {
group_connectivity_type_ = ConnectivityType::Way4;
} else {
group_connectivity_type_ = ConnectivityType::Way8;
if (group_connectivity_type_param != 8) {
RCLCPP_WARN(
logger_, "DenoiseLayer::onInitialize(): param group_connectivity_type: %i."
" Possible values are 4 (neighbors pixels are connected horizontally and vertically) "
"or 8 (neighbors pixels are connected horizontally, vertically and diagonally)."
"The default value 8 will be used",
group_connectivity_type_param);
}
}
current_ = true;
}
void
DenoiseLayer::reset()
{
current_ = false;
}
bool
DenoiseLayer::isClearable()
{
return false;
}
void
DenoiseLayer::updateBounds(
double /*robot_x*/, double /*robot_y*/, double /*robot_yaw*/,
double * /*min_x*/, double * /*min_y*/,
double * /*max_x*/, double * /*max_y*/) {}
void
DenoiseLayer::updateCosts(
nav2_costmap_2d::Costmap2D & master_grid, int min_x, int min_y, int max_x, int max_y)
{
if (!enabled_) {
return;
}
if (min_x >= max_x || min_y >= max_y) {
return;
}
no_information_is_obstacle_ = master_grid.getDefaultValue() != NO_INFORMATION;
// wrap roi_image over existing costmap2d buffer
unsigned char * master_array = master_grid.getCharMap();
const int step = static_cast<int>(master_grid.getSizeInCellsX());
const size_t width = max_x - min_x;
const size_t height = max_y - min_y;
Image<uint8_t> roi_image(height, width, master_array + min_y * step + min_x, step);
try {
denoise(roi_image);
} catch (std::exception & ex) {
RCLCPP_ERROR(logger_, "%s", (std::string("Inner error: ") + ex.what()).c_str());
}
current_ = true;
}
void
DenoiseLayer::denoise(Image<uint8_t> & image) const
{
if (image.empty()) {
return;
}
if (minimal_group_size_ <= 1) {
return; // A smaller group cannot exist. No one pixel will be changed
}
if (minimal_group_size_ == 2) {
// Performs fast filtration based on erosion function
removeSinglePixels(image);
} else {
// Performs a slower segmentation-based operation
removeGroups(image);
}
}
void
DenoiseLayer::removeGroups(Image<uint8_t> & image) const
{
groups_remover_.removeGroups(
image, buffer_, group_connectivity_type_, minimal_group_size_,
[this](uint8_t pixel) {return isBackground(pixel);});
}
void
DenoiseLayer::removeSinglePixels(Image<uint8_t> & image) const
{
// Building a map of 4 or 8-connected neighbors.
// The pixel of the map is 255 if there is an obstacle nearby
uint8_t * buf = buffer_.get<uint8_t>(image.rows() * image.columns());
Image<uint8_t> max_neighbors_image(image.rows(), image.columns(), buf, image.columns());
// If NO_INFORMATION (=255) isn't obstacle, we can't use a simple max() to check
// any obstacle nearby. In this case, we interpret NO_INFORMATION as an empty space.
if (!no_information_is_obstacle_) {
auto replace_to_free = [](uint8_t v) {
return v == NO_INFORMATION ? FREE_SPACE : v;
};
auto max = [&](const std::initializer_list<uint8_t> lst) {
std::array<uint8_t, 3> buf = {
replace_to_free(*lst.begin()),
replace_to_free(*(lst.begin() + 1)),
replace_to_free(*(lst.begin() + 2))
};
return *std::max_element(buf.begin(), buf.end());
};
dilate(image, max_neighbors_image, group_connectivity_type_, max);
} else {
auto max = [](const std::initializer_list<uint8_t> lst) {
return std::max(lst);
};
dilate(image, max_neighbors_image, group_connectivity_type_, max);
}
max_neighbors_image.convert(
image, [this](uint8_t maxNeighbor, uint8_t & img) {
if (!isBackground(img) && isBackground(maxNeighbor)) {
img = FREE_SPACE;
}
});
}
bool DenoiseLayer::isBackground(uint8_t pixel) const
{
bool is_obstacle =
pixel == LETHAL_OBSTACLE ||
pixel == INSCRIBED_INFLATED_OBSTACLE ||
(pixel == NO_INFORMATION && no_information_is_obstacle_);
return !is_obstacle;
}
} // namespace nav2_costmap_2d
// This is the macro allowing a DenoiseLayer class
// to be registered in order to be dynamically loadable of base type nav2_costmap_2d::Layer.
#include "pluginlib/class_list_macros.hpp"
PLUGINLIB_EXPORT_CLASS(nav2_costmap_2d::DenoiseLayer, nav2_costmap_2d::Layer)
@@ -0,0 +1,488 @@
/*********************************************************************
*
* Software License Agreement (BSD License)
*
* Copyright (c) 2008, 2013, Willow Garage, Inc.
* All rights reserved.
*
* 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 Willow Garage, Inc. 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 OWNER 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.
*
* Author: Eitan Marder-Eppstein
* David V. Lu!!
*********************************************************************/
#include "nav2_costmap_2d/inflation_layer.hpp"
#include <limits>
#include <map>
#include <vector>
#include <algorithm>
#include <utility>
#include "nav2_costmap_2d/costmap_math.hpp"
#include "nav2_costmap_2d/footprint.hpp"
#include "pluginlib/class_list_macros.hpp"
#include "rclcpp/parameter_events_filter.hpp"
PLUGINLIB_EXPORT_CLASS(nav2_costmap_2d::InflationLayer, nav2_costmap_2d::Layer)
using nav2_costmap_2d::LETHAL_OBSTACLE;
using nav2_costmap_2d::INSCRIBED_INFLATED_OBSTACLE;
using nav2_costmap_2d::NO_INFORMATION;
using rcl_interfaces::msg::ParameterType;
namespace nav2_costmap_2d
{
InflationLayer::InflationLayer()
: inflation_radius_(0),
inscribed_radius_(0),
cost_scaling_factor_(0),
inflate_unknown_(false),
inflate_around_unknown_(false),
cell_inflation_radius_(0),
cached_cell_inflation_radius_(0),
resolution_(0),
cache_length_(0),
last_min_x_(std::numeric_limits<double>::lowest()),
last_min_y_(std::numeric_limits<double>::lowest()),
last_max_x_(std::numeric_limits<double>::max()),
last_max_y_(std::numeric_limits<double>::max())
{
access_ = new mutex_t();
}
InflationLayer::~InflationLayer()
{
dyn_params_handler_.reset();
delete access_;
}
void
InflationLayer::onInitialize()
{
declareParameter("enabled", rclcpp::ParameterValue(true));
declareParameter("inflation_radius", rclcpp::ParameterValue(0.55));
declareParameter("cost_scaling_factor", rclcpp::ParameterValue(10.0));
declareParameter("inflate_unknown", rclcpp::ParameterValue(false));
declareParameter("inflate_around_unknown", rclcpp::ParameterValue(false));
{
auto node = node_.lock();
if (!node) {
throw std::runtime_error{"Failed to lock node"};
}
node->get_parameter(name_ + "." + "enabled", enabled_);
node->get_parameter(name_ + "." + "inflation_radius", inflation_radius_);
node->get_parameter(name_ + "." + "cost_scaling_factor", cost_scaling_factor_);
node->get_parameter(name_ + "." + "inflate_unknown", inflate_unknown_);
node->get_parameter(name_ + "." + "inflate_around_unknown", inflate_around_unknown_);
dyn_params_handler_ = node->add_on_set_parameters_callback(
std::bind(
&InflationLayer::dynamicParametersCallback,
this, std::placeholders::_1));
}
current_ = true;
seen_.clear();
cached_distances_.clear();
cached_costs_.clear();
need_reinflation_ = false;
cell_inflation_radius_ = cellDistance(inflation_radius_);
matchSize();
}
void
InflationLayer::matchSize()
{
std::lock_guard<Costmap2D::mutex_t> guard(*getMutex());
nav2_costmap_2d::Costmap2D * costmap = layered_costmap_->getCostmap();
resolution_ = costmap->getResolution();
cell_inflation_radius_ = cellDistance(inflation_radius_);
computeCaches();
seen_ = std::vector<bool>(costmap->getSizeInCellsX() * costmap->getSizeInCellsY(), false);
}
void
InflationLayer::updateBounds(
double /*robot_x*/, double /*robot_y*/, double /*robot_yaw*/, double * min_x,
double * min_y, double * max_x, double * max_y)
{
std::lock_guard<Costmap2D::mutex_t> guard(*getMutex());
if (need_reinflation_) {
last_min_x_ = *min_x;
last_min_y_ = *min_y;
last_max_x_ = *max_x;
last_max_y_ = *max_y;
*min_x = std::numeric_limits<double>::lowest();
*min_y = std::numeric_limits<double>::lowest();
*max_x = std::numeric_limits<double>::max();
*max_y = std::numeric_limits<double>::max();
need_reinflation_ = false;
} else {
double tmp_min_x = last_min_x_;
double tmp_min_y = last_min_y_;
double tmp_max_x = last_max_x_;
double tmp_max_y = last_max_y_;
last_min_x_ = *min_x;
last_min_y_ = *min_y;
last_max_x_ = *max_x;
last_max_y_ = *max_y;
*min_x = std::min(tmp_min_x, *min_x) - inflation_radius_;
*min_y = std::min(tmp_min_y, *min_y) - inflation_radius_;
*max_x = std::max(tmp_max_x, *max_x) + inflation_radius_;
*max_y = std::max(tmp_max_y, *max_y) + inflation_radius_;
}
}
void
InflationLayer::onFootprintChanged()
{
std::lock_guard<Costmap2D::mutex_t> guard(*getMutex());
inscribed_radius_ = layered_costmap_->getInscribedRadius();
cell_inflation_radius_ = cellDistance(inflation_radius_);
computeCaches();
need_reinflation_ = true;
if (inflation_radius_ < inscribed_radius_) {
RCLCPP_ERROR(
logger_,
"The configured inflation radius (%.3f) is smaller than "
"the computed inscribed radius (%.3f) of your footprint, "
"it is highly recommended to set inflation radius to be at "
"least as big as the inscribed radius to avoid collisions",
inflation_radius_, inscribed_radius_);
}
RCLCPP_DEBUG(
logger_, "InflationLayer::onFootprintChanged(): num footprint points: %zu,"
" inscribed_radius_ = %.3f, inflation_radius_ = %.3f",
layered_costmap_->getFootprint().size(), inscribed_radius_, inflation_radius_);
}
void
InflationLayer::updateCosts(
nav2_costmap_2d::Costmap2D & master_grid, int min_i, int min_j,
int max_i,
int max_j)
{
std::lock_guard<Costmap2D::mutex_t> guard(*getMutex());
if (!enabled_ || (cell_inflation_radius_ == 0)) {
return;
}
// make sure the inflation list is empty at the beginning of the cycle (should always be true)
for (auto & dist : inflation_cells_) {
RCLCPP_FATAL_EXPRESSION(
logger_,
!dist.empty(), "The inflation list must be empty at the beginning of inflation");
}
unsigned char * master_array = master_grid.getCharMap();
unsigned int size_x = master_grid.getSizeInCellsX(), size_y = master_grid.getSizeInCellsY();
if (seen_.size() != size_x * size_y) {
RCLCPP_WARN(
logger_, "InflationLayer::updateCosts(): seen_ vector size is wrong");
seen_ = std::vector<bool>(size_x * size_y, false);
}
std::fill(begin(seen_), end(seen_), false);
// We need to include in the inflation cells outside the bounding
// box min_i...max_j, by the amount cell_inflation_radius_. Cells
// up to that distance outside the box can still influence the costs
// stored in cells inside the box.
const int base_min_i = min_i;
const int base_min_j = min_j;
const int base_max_i = max_i;
const int base_max_j = max_j;
min_i -= static_cast<int>(cell_inflation_radius_);
min_j -= static_cast<int>(cell_inflation_radius_);
max_i += static_cast<int>(cell_inflation_radius_);
max_j += static_cast<int>(cell_inflation_radius_);
min_i = std::max(0, min_i);
min_j = std::max(0, min_j);
max_i = std::min(static_cast<int>(size_x), max_i);
max_j = std::min(static_cast<int>(size_y), max_j);
// Inflation list; we append cells to visit in a list associated with
// its distance to the nearest obstacle
// We use a map<distance, list> to emulate the priority queue used before,
// with a notable performance boost
// Start with lethal obstacles: by definition distance is 0.0
auto & obs_bin = inflation_cells_[0];
for (int j = min_j; j < max_j; j++) {
for (int i = min_i; i < max_i; i++) {
int index = static_cast<int>(master_grid.getIndex(i, j));
unsigned char cost = master_array[index];
if (cost == LETHAL_OBSTACLE || (inflate_around_unknown_ && cost == NO_INFORMATION)) {
obs_bin.emplace_back(index, i, j, i, j);
}
}
}
// Process cells by increasing distance; new cells are appended to the
// corresponding distance bin, so they
// can overtake previously inserted but farther away cells
for (const auto & dist_bin : inflation_cells_) {
for (std::size_t i = 0; i < dist_bin.size(); ++i) {
// Do not use iterator or for-range based loops to
// iterate though dist_bin, since it's size might
// change when a new cell is enqueued, invalidating all iterators
unsigned int index = dist_bin[i].index_;
// ignore if already visited
if (seen_[index]) {
continue;
}
seen_[index] = true;
unsigned int mx = dist_bin[i].x_;
unsigned int my = dist_bin[i].y_;
unsigned int sx = dist_bin[i].src_x_;
unsigned int sy = dist_bin[i].src_y_;
// assign the cost associated with the distance from an obstacle to the cell
unsigned char cost = costLookup(mx, my, sx, sy);
unsigned char old_cost = master_array[index];
// In order to avoid artifacts appeared out of boundary areas
// when some layer is going after inflation_layer,
// we need to apply inflation_layer only to inside of given bounds
if (static_cast<int>(mx) >= base_min_i &&
static_cast<int>(my) >= base_min_j &&
static_cast<int>(mx) < base_max_i &&
static_cast<int>(my) < base_max_j)
{
if (old_cost == NO_INFORMATION &&
(inflate_unknown_ ? (cost > FREE_SPACE) : (cost >= INSCRIBED_INFLATED_OBSTACLE)))
{
master_array[index] = cost;
} else {
master_array[index] = std::max(old_cost, cost);
}
}
// attempt to put the neighbors of the current cell onto the inflation list
if (mx > 0) {
enqueue(index - 1, mx - 1, my, sx, sy);
}
if (my > 0) {
enqueue(index - size_x, mx, my - 1, sx, sy);
}
if (mx < size_x - 1) {
enqueue(index + 1, mx + 1, my, sx, sy);
}
if (my < size_y - 1) {
enqueue(index + size_x, mx, my + 1, sx, sy);
}
}
}
for (auto & dist : inflation_cells_) {
dist.clear();
dist.reserve(200);
}
current_ = true;
}
/**
* @brief Given an index of a cell in the costmap, place it into a list pending for obstacle inflation
* @param grid The costmap
* @param index The index of the cell
* @param mx The x coordinate of the cell (can be computed from the index, but saves time to store it)
* @param my The y coordinate of the cell (can be computed from the index, but saves time to store it)
* @param src_x The x index of the obstacle point inflation started at
* @param src_y The y index of the obstacle point inflation started at
*/
void
InflationLayer::enqueue(
unsigned int index, unsigned int mx, unsigned int my,
unsigned int src_x, unsigned int src_y)
{
if (!seen_[index]) {
// we compute our distance table one cell further than the
// inflation radius dictates so we can make the check below
double distance = distanceLookup(mx, my, src_x, src_y);
// we only want to put the cell in the list if it is within
// the inflation radius of the obstacle point
if (distance > cell_inflation_radius_) {
return;
}
const unsigned int r = cell_inflation_radius_ + 2;
// push the cell data onto the inflation list and mark
inflation_cells_[distance_matrix_[mx - src_x + r][my - src_y + r]].emplace_back(
index, mx, my, src_x, src_y);
}
}
void
InflationLayer::computeCaches()
{
std::lock_guard<Costmap2D::mutex_t> guard(*getMutex());
if (cell_inflation_radius_ == 0) {
return;
}
cache_length_ = cell_inflation_radius_ + 2;
// based on the inflation radius... compute distance and cost caches
if (cell_inflation_radius_ != cached_cell_inflation_radius_) {
cached_costs_.resize(cache_length_ * cache_length_);
cached_distances_.resize(cache_length_ * cache_length_);
for (unsigned int i = 0; i < cache_length_; ++i) {
for (unsigned int j = 0; j < cache_length_; ++j) {
cached_distances_[i * cache_length_ + j] = hypot(i, j);
}
}
cached_cell_inflation_radius_ = cell_inflation_radius_;
}
for (unsigned int i = 0; i < cache_length_; ++i) {
for (unsigned int j = 0; j < cache_length_; ++j) {
cached_costs_[i * cache_length_ + j] = computeCost(cached_distances_[i * cache_length_ + j]);
}
}
int max_dist = generateIntegerDistances();
inflation_cells_.clear();
inflation_cells_.resize(max_dist + 1);
for (auto & dist : inflation_cells_) {
dist.reserve(200);
}
}
int
InflationLayer::generateIntegerDistances()
{
const int r = cell_inflation_radius_ + 2;
const int size = r * 2 + 1;
std::vector<std::pair<int, int>> points;
for (int y = -r; y <= r; y++) {
for (int x = -r; x <= r; x++) {
if (x * x + y * y <= r * r) {
points.emplace_back(x, y);
}
}
}
std::sort(
points.begin(), points.end(),
[](const std::pair<int, int> & a, const std::pair<int, int> & b) -> bool {
return a.first * a.first + a.second * a.second < b.first * b.first + b.second * b.second;
}
);
std::vector<std::vector<int>> distance_matrix(size, std::vector<int>(size, 0));
std::pair<int, int> last = {0, 0};
int level = 0;
for (auto const & p : points) {
if (p.first * p.first + p.second * p.second !=
last.first * last.first + last.second * last.second)
{
level++;
}
distance_matrix[p.first + r][p.second + r] = level;
last = p;
}
distance_matrix_ = distance_matrix;
return level;
}
/**
* @brief Callback executed when a parameter change is detected
* @param event ParameterEvent message
*/
rcl_interfaces::msg::SetParametersResult
InflationLayer::dynamicParametersCallback(
std::vector<rclcpp::Parameter> parameters)
{
std::lock_guard<Costmap2D::mutex_t> guard(*getMutex());
rcl_interfaces::msg::SetParametersResult result;
bool need_cache_recompute = false;
for (auto parameter : parameters) {
const auto & param_type = parameter.get_type();
const auto & param_name = parameter.get_name();
if (param_type == ParameterType::PARAMETER_DOUBLE) {
if (param_name == name_ + "." + "inflation_radius" &&
inflation_radius_ != parameter.as_double())
{
inflation_radius_ = parameter.as_double();
need_reinflation_ = true;
need_cache_recompute = true;
} else if (param_name == name_ + "." + "cost_scaling_factor" && // NOLINT
cost_scaling_factor_ != parameter.as_double())
{
cost_scaling_factor_ = parameter.as_double();
need_reinflation_ = true;
need_cache_recompute = true;
}
} else if (param_type == ParameterType::PARAMETER_BOOL) {
if (param_name == name_ + "." + "enabled" && enabled_ != parameter.as_bool()) {
enabled_ = parameter.as_bool();
need_reinflation_ = true;
current_ = false;
} else if (param_name == name_ + "." + "inflate_unknown" && // NOLINT
inflate_unknown_ != parameter.as_bool())
{
inflate_unknown_ = parameter.as_bool();
need_reinflation_ = true;
} else if (param_name == name_ + "." + "inflate_around_unknown" && // NOLINT
inflate_around_unknown_ != parameter.as_bool())
{
inflate_around_unknown_ = parameter.as_bool();
need_reinflation_ = true;
}
}
}
if (need_cache_recompute) {
matchSize();
}
result.successful = true;
return result;
}
} // namespace nav2_costmap_2d
@@ -0,0 +1,766 @@
/*********************************************************************
*
* Software License Agreement (BSD License)
*
* Copyright (c) 2008, 2013, Willow Garage, Inc.
* All rights reserved.
*
* 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 Willow Garage, Inc. 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 OWNER 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.
*
* Author: Eitan Marder-Eppstein
* David V. Lu!!
* Steve Macenski
*********************************************************************/
#include "nav2_costmap_2d/obstacle_layer.hpp"
#include <algorithm>
#include <memory>
#include <string>
#include <vector>
#include "pluginlib/class_list_macros.hpp"
#include "sensor_msgs/point_cloud2_iterator.hpp"
#include "nav2_costmap_2d/costmap_math.hpp"
PLUGINLIB_EXPORT_CLASS(nav2_costmap_2d::ObstacleLayer, nav2_costmap_2d::Layer)
using nav2_costmap_2d::NO_INFORMATION;
using nav2_costmap_2d::LETHAL_OBSTACLE;
using nav2_costmap_2d::FREE_SPACE;
using nav2_costmap_2d::ObservationBuffer;
using nav2_costmap_2d::Observation;
using rcl_interfaces::msg::ParameterType;
namespace nav2_costmap_2d
{
ObstacleLayer::~ObstacleLayer()
{
dyn_params_handler_.reset();
for (auto & notifier : observation_notifiers_) {
notifier.reset();
}
}
void ObstacleLayer::onInitialize()
{
bool track_unknown_space;
double transform_tolerance;
// The topics that we'll subscribe to from the parameter server
std::string topics_string;
declareParameter("enabled", rclcpp::ParameterValue(true));
declareParameter("footprint_clearing_enabled", rclcpp::ParameterValue(true));
declareParameter("min_obstacle_height", rclcpp::ParameterValue(0.0));
declareParameter("max_obstacle_height", rclcpp::ParameterValue(2.0));
declareParameter("combination_method", rclcpp::ParameterValue(1));
declareParameter("observation_sources", rclcpp::ParameterValue(std::string("")));
auto node = node_.lock();
if (!node) {
throw std::runtime_error{"Failed to lock node"};
}
node->get_parameter(name_ + "." + "enabled", enabled_);
node->get_parameter(name_ + "." + "footprint_clearing_enabled", footprint_clearing_enabled_);
node->get_parameter(name_ + "." + "min_obstacle_height", min_obstacle_height_);
node->get_parameter(name_ + "." + "max_obstacle_height", max_obstacle_height_);
node->get_parameter(name_ + "." + "combination_method", combination_method_);
node->get_parameter("track_unknown_space", track_unknown_space);
node->get_parameter("transform_tolerance", transform_tolerance);
node->get_parameter(name_ + "." + "observation_sources", topics_string);
dyn_params_handler_ = node->add_on_set_parameters_callback(
std::bind(
&ObstacleLayer::dynamicParametersCallback,
this,
std::placeholders::_1));
RCLCPP_INFO(
logger_,
"Subscribed to Topics: %s", topics_string.c_str());
rolling_window_ = layered_costmap_->isRolling();
if (track_unknown_space) {
default_value_ = NO_INFORMATION;
} else {
default_value_ = FREE_SPACE;
}
ObstacleLayer::matchSize();
current_ = true;
was_reset_ = false;
global_frame_ = layered_costmap_->getGlobalFrameID();
auto sub_opt = rclcpp::SubscriptionOptions();
sub_opt.callback_group = callback_group_;
// now we need to split the topics based on whitespace which we can use a stringstream for
std::stringstream ss(topics_string);
std::string source;
while (ss >> source) {
// get the parameters for the specific topic
double observation_keep_time, expected_update_rate, min_obstacle_height, max_obstacle_height;
std::string topic, sensor_frame, data_type;
bool inf_is_valid, clearing, marking;
declareParameter(source + "." + "topic", rclcpp::ParameterValue(source));
declareParameter(source + "." + "sensor_frame", rclcpp::ParameterValue(std::string("")));
declareParameter(source + "." + "observation_persistence", rclcpp::ParameterValue(0.0));
declareParameter(source + "." + "expected_update_rate", rclcpp::ParameterValue(0.0));
declareParameter(source + "." + "data_type", rclcpp::ParameterValue(std::string("LaserScan")));
declareParameter(source + "." + "min_obstacle_height", rclcpp::ParameterValue(0.0));
declareParameter(source + "." + "max_obstacle_height", rclcpp::ParameterValue(0.0));
declareParameter(source + "." + "inf_is_valid", rclcpp::ParameterValue(false));
declareParameter(source + "." + "marking", rclcpp::ParameterValue(true));
declareParameter(source + "." + "clearing", rclcpp::ParameterValue(false));
declareParameter(source + "." + "obstacle_max_range", rclcpp::ParameterValue(2.5));
declareParameter(source + "." + "obstacle_min_range", rclcpp::ParameterValue(0.0));
declareParameter(source + "." + "raytrace_max_range", rclcpp::ParameterValue(3.0));
declareParameter(source + "." + "raytrace_min_range", rclcpp::ParameterValue(0.0));
node->get_parameter(name_ + "." + source + "." + "topic", topic);
node->get_parameter(name_ + "." + source + "." + "sensor_frame", sensor_frame);
node->get_parameter(
name_ + "." + source + "." + "observation_persistence",
observation_keep_time);
node->get_parameter(
name_ + "." + source + "." + "expected_update_rate",
expected_update_rate);
node->get_parameter(name_ + "." + source + "." + "data_type", data_type);
node->get_parameter(name_ + "." + source + "." + "min_obstacle_height", min_obstacle_height);
node->get_parameter(name_ + "." + source + "." + "max_obstacle_height", max_obstacle_height);
node->get_parameter(name_ + "." + source + "." + "inf_is_valid", inf_is_valid);
node->get_parameter(name_ + "." + source + "." + "marking", marking);
node->get_parameter(name_ + "." + source + "." + "clearing", clearing);
if (!(data_type == "PointCloud2" || data_type == "LaserScan")) {
RCLCPP_FATAL(
logger_,
"Only topics that use point cloud2s or laser scans are currently supported");
throw std::runtime_error(
"Only topics that use point cloud2s or laser scans are currently supported");
}
// get the obstacle range for the sensor
double obstacle_max_range, obstacle_min_range;
node->get_parameter(name_ + "." + source + "." + "obstacle_max_range", obstacle_max_range);
node->get_parameter(name_ + "." + source + "." + "obstacle_min_range", obstacle_min_range);
// get the raytrace ranges for the sensor
double raytrace_max_range, raytrace_min_range;
node->get_parameter(name_ + "." + source + "." + "raytrace_min_range", raytrace_min_range);
node->get_parameter(name_ + "." + source + "." + "raytrace_max_range", raytrace_max_range);
RCLCPP_DEBUG(
logger_,
"Creating an observation buffer for source %s, topic %s, frame %s",
source.c_str(), topic.c_str(),
sensor_frame.c_str());
// create an observation buffer
observation_buffers_.push_back(
std::shared_ptr<ObservationBuffer
>(
new ObservationBuffer(
node, topic, observation_keep_time, expected_update_rate,
min_obstacle_height,
max_obstacle_height, obstacle_max_range, obstacle_min_range, raytrace_max_range,
raytrace_min_range, *tf_,
global_frame_,
sensor_frame, tf2::durationFromSec(transform_tolerance))));
// check if we'll add this buffer to our marking observation buffers
if (marking) {
marking_buffers_.push_back(observation_buffers_.back());
}
// check if we'll also add this buffer to our clearing observation buffers
if (clearing) {
clearing_buffers_.push_back(observation_buffers_.back());
}
RCLCPP_DEBUG(
logger_,
"Created an observation buffer for source %s, topic %s, global frame: %s, "
"expected update rate: %.2f, observation persistence: %.2f",
source.c_str(), topic.c_str(),
global_frame_.c_str(), expected_update_rate, observation_keep_time);
rmw_qos_profile_t custom_qos_profile = rmw_qos_profile_sensor_data;
custom_qos_profile.depth = 50;
// create a callback for the topic
if (data_type == "LaserScan") {
auto sub = std::make_shared<message_filters::Subscriber<sensor_msgs::msg::LaserScan,
rclcpp_lifecycle::LifecycleNode>>(node, topic, custom_qos_profile, sub_opt);
sub->unsubscribe();
auto filter = std::make_shared<tf2_ros::MessageFilter<sensor_msgs::msg::LaserScan>>(
*sub, *tf_, global_frame_, 50,
node->get_node_logging_interface(),
node->get_node_clock_interface(),
tf2::durationFromSec(transform_tolerance));
if (inf_is_valid) {
filter->registerCallback(
std::bind(
&ObstacleLayer::laserScanValidInfCallback, this, std::placeholders::_1,
observation_buffers_.back()));
} else {
filter->registerCallback(
std::bind(
&ObstacleLayer::laserScanCallback, this, std::placeholders::_1,
observation_buffers_.back()));
}
observation_subscribers_.push_back(sub);
observation_notifiers_.push_back(filter);
observation_notifiers_.back()->setTolerance(rclcpp::Duration::from_seconds(0.05));
} else {
auto sub = std::make_shared<message_filters::Subscriber<sensor_msgs::msg::PointCloud2,
rclcpp_lifecycle::LifecycleNode>>(node, topic, custom_qos_profile, sub_opt);
sub->unsubscribe();
if (inf_is_valid) {
RCLCPP_WARN(
logger_,
"obstacle_layer: inf_is_valid option is not applicable to PointCloud observations.");
}
auto filter = std::make_shared<tf2_ros::MessageFilter<sensor_msgs::msg::PointCloud2>>(
*sub, *tf_, global_frame_, 50,
node->get_node_logging_interface(),
node->get_node_clock_interface(),
tf2::durationFromSec(transform_tolerance));
filter->registerCallback(
std::bind(
&ObstacleLayer::pointCloud2Callback, this, std::placeholders::_1,
observation_buffers_.back()));
observation_subscribers_.push_back(sub);
observation_notifiers_.push_back(filter);
}
if (sensor_frame != "") {
std::vector<std::string> target_frames;
target_frames.push_back(global_frame_);
target_frames.push_back(sensor_frame);
observation_notifiers_.back()->setTargetFrames(target_frames);
}
}
}
rcl_interfaces::msg::SetParametersResult
ObstacleLayer::dynamicParametersCallback(
std::vector<rclcpp::Parameter> parameters)
{
std::lock_guard<Costmap2D::mutex_t> guard(*getMutex());
rcl_interfaces::msg::SetParametersResult result;
for (auto parameter : parameters) {
const auto & param_type = parameter.get_type();
const auto & param_name = parameter.get_name();
if (param_type == ParameterType::PARAMETER_DOUBLE) {
if (param_name == name_ + "." + "min_obstacle_height") {
min_obstacle_height_ = parameter.as_double();
} else if (param_name == name_ + "." + "max_obstacle_height") {
max_obstacle_height_ = parameter.as_double();
}
} else if (param_type == ParameterType::PARAMETER_BOOL) {
if (param_name == name_ + "." + "enabled" && enabled_ != parameter.as_bool()) {
enabled_ = parameter.as_bool();
if (enabled_) {
current_ = false;
}
} else if (param_name == name_ + "." + "footprint_clearing_enabled") {
footprint_clearing_enabled_ = parameter.as_bool();
}
} else if (param_type == ParameterType::PARAMETER_INTEGER) {
if (param_name == name_ + "." + "combination_method") {
combination_method_ = parameter.as_int();
}
}
}
result.successful = true;
return result;
}
void
ObstacleLayer::laserScanCallback(
sensor_msgs::msg::LaserScan::ConstSharedPtr message,
const std::shared_ptr<nav2_costmap_2d::ObservationBuffer> & buffer)
{
// project the laser into a point cloud
sensor_msgs::msg::PointCloud2 cloud;
cloud.header = message->header;
// project the scan into a point cloud
try {
projector_.transformLaserScanToPointCloud(message->header.frame_id, *message, cloud, *tf_);
} catch (tf2::TransformException & ex) {
RCLCPP_WARN(
logger_,
"High fidelity enabled, but TF returned a transform exception to frame %s: %s",
global_frame_.c_str(),
ex.what());
projector_.projectLaser(*message, cloud);
} catch (std::runtime_error & ex) {
RCLCPP_WARN(
logger_,
"transformLaserScanToPointCloud error, it seems the message from laser is malformed."
" Ignore this message. what(): %s",
ex.what());
return;
}
// buffer the point cloud
buffer->lock();
buffer->bufferCloud(cloud);
buffer->unlock();
}
void
ObstacleLayer::laserScanValidInfCallback(
sensor_msgs::msg::LaserScan::ConstSharedPtr raw_message,
const std::shared_ptr<nav2_costmap_2d::ObservationBuffer> & buffer)
{
// Filter positive infinities ("Inf"s) to max_range.
float epsilon = 0.0001; // a tenth of a millimeter
sensor_msgs::msg::LaserScan message = *raw_message;
for (size_t i = 0; i < message.ranges.size(); i++) {
float range = message.ranges[i];
if (!std::isfinite(range) && range > 0) {
message.ranges[i] = message.range_max - epsilon;
}
}
// project the laser into a point cloud
sensor_msgs::msg::PointCloud2 cloud;
cloud.header = message.header;
// project the scan into a point cloud
try {
projector_.transformLaserScanToPointCloud(message.header.frame_id, message, cloud, *tf_);
} catch (tf2::TransformException & ex) {
RCLCPP_WARN(
logger_,
"High fidelity enabled, but TF returned a transform exception to frame %s: %s",
global_frame_.c_str(), ex.what());
projector_.projectLaser(message, cloud);
} catch (std::runtime_error & ex) {
RCLCPP_WARN(
logger_,
"transformLaserScanToPointCloud error, it seems the message from laser is malformed."
" Ignore this message. what(): %s",
ex.what());
return;
}
// buffer the point cloud
buffer->lock();
buffer->bufferCloud(cloud);
buffer->unlock();
}
void
ObstacleLayer::pointCloud2Callback(
sensor_msgs::msg::PointCloud2::ConstSharedPtr message,
const std::shared_ptr<ObservationBuffer> & buffer)
{
// buffer the point cloud
buffer->lock();
buffer->bufferCloud(*message);
buffer->unlock();
}
void
ObstacleLayer::updateBounds(
double robot_x, double robot_y, double robot_yaw, double * min_x,
double * min_y, double * max_x, double * max_y)
{
std::lock_guard<Costmap2D::mutex_t> guard(*getMutex());
if (rolling_window_) {
updateOrigin(robot_x - getSizeInMetersX() / 2, robot_y - getSizeInMetersY() / 2);
}
if (!enabled_) {
return;
}
useExtraBounds(min_x, min_y, max_x, max_y);
bool current = true;
std::vector<Observation> observations, clearing_observations;
// get the marking observations
current = current && getMarkingObservations(observations);
// get the clearing observations
current = current && getClearingObservations(clearing_observations);
// update the global current status
current_ = current;
// raytrace freespace
for (unsigned int i = 0; i < clearing_observations.size(); ++i) {
raytraceFreespace(clearing_observations[i], min_x, min_y, max_x, max_y);
}
// place the new obstacles into a priority queue... each with a priority of zero to begin with
for (std::vector<Observation>::const_iterator it = observations.begin();
it != observations.end(); ++it)
{
const Observation & obs = *it;
const sensor_msgs::msg::PointCloud2 & cloud = *(obs.cloud_);
double sq_obstacle_max_range = obs.obstacle_max_range_ * obs.obstacle_max_range_;
double sq_obstacle_min_range = obs.obstacle_min_range_ * obs.obstacle_min_range_;
sensor_msgs::PointCloud2ConstIterator<float> iter_x(cloud, "x");
sensor_msgs::PointCloud2ConstIterator<float> iter_y(cloud, "y");
sensor_msgs::PointCloud2ConstIterator<float> iter_z(cloud, "z");
for (; iter_x != iter_x.end(); ++iter_x, ++iter_y, ++iter_z) {
double px = *iter_x, py = *iter_y, pz = *iter_z;
// if the obstacle is too low, we won't add it
if (pz < min_obstacle_height_) {
RCLCPP_DEBUG(logger_, "The point is too low");
continue;
}
// if the obstacle is too high or too far away from the robot we won't add it
if (pz > max_obstacle_height_) {
RCLCPP_DEBUG(logger_, "The point is too high");
continue;
}
// compute the squared distance from the hitpoint to the pointcloud's origin
double sq_dist =
(px -
obs.origin_.x) * (px - obs.origin_.x) + (py - obs.origin_.y) * (py - obs.origin_.y) +
(pz - obs.origin_.z) * (pz - obs.origin_.z);
// if the point is far enough away... we won't consider it
if (sq_dist >= sq_obstacle_max_range) {
RCLCPP_DEBUG(logger_, "The point is too far away");
continue;
}
// if the point is too close, do not conisder it
if (sq_dist < sq_obstacle_min_range) {
RCLCPP_DEBUG(logger_, "The point is too close");
continue;
}
// now we need to compute the map coordinates for the observation
unsigned int mx, my;
if (!worldToMap(px, py, mx, my)) {
RCLCPP_DEBUG(logger_, "Computing map coords failed");
continue;
}
unsigned int index = getIndex(mx, my);
costmap_[index] = LETHAL_OBSTACLE;
touch(px, py, min_x, min_y, max_x, max_y);
}
}
updateFootprint(robot_x, robot_y, robot_yaw, min_x, min_y, max_x, max_y);
}
void
ObstacleLayer::updateFootprint(
double robot_x, double robot_y, double robot_yaw,
double * min_x, double * min_y,
double * max_x,
double * max_y)
{
if (!footprint_clearing_enabled_) {return;}
transformFootprint(robot_x, robot_y, robot_yaw, getFootprint(), transformed_footprint_);
for (unsigned int i = 0; i < transformed_footprint_.size(); i++) {
touch(transformed_footprint_[i].x, transformed_footprint_[i].y, min_x, min_y, max_x, max_y);
}
}
void
ObstacleLayer::updateCosts(
nav2_costmap_2d::Costmap2D & master_grid, int min_i, int min_j,
int max_i,
int max_j)
{
std::lock_guard<Costmap2D::mutex_t> guard(*getMutex());
if (!enabled_) {
return;
}
// if not current due to reset, set current now after clearing
if (!current_ && was_reset_) {
was_reset_ = false;
current_ = true;
}
if (footprint_clearing_enabled_) {
setConvexPolygonCost(transformed_footprint_, nav2_costmap_2d::FREE_SPACE);
}
switch (combination_method_) {
case 0: // Overwrite
updateWithOverwrite(master_grid, min_i, min_j, max_i, max_j);
break;
case 1: // Maximum
updateWithMax(master_grid, min_i, min_j, max_i, max_j);
break;
default: // Nothing
break;
}
}
void
ObstacleLayer::addStaticObservation(
nav2_costmap_2d::Observation & obs,
bool marking, bool clearing)
{
if (marking) {
static_marking_observations_.push_back(obs);
}
if (clearing) {
static_clearing_observations_.push_back(obs);
}
}
void
ObstacleLayer::clearStaticObservations(bool marking, bool clearing)
{
if (marking) {
static_marking_observations_.clear();
}
if (clearing) {
static_clearing_observations_.clear();
}
}
bool
ObstacleLayer::getMarkingObservations(std::vector<Observation> & marking_observations) const
{
bool current = true;
// get the marking observations
for (unsigned int i = 0; i < marking_buffers_.size(); ++i) {
marking_buffers_[i]->lock();
marking_buffers_[i]->getObservations(marking_observations);
current = marking_buffers_[i]->isCurrent() && current;
marking_buffers_[i]->unlock();
}
marking_observations.insert(
marking_observations.end(),
static_marking_observations_.begin(), static_marking_observations_.end());
return current;
}
bool
ObstacleLayer::getClearingObservations(std::vector<Observation> & clearing_observations) const
{
bool current = true;
// get the clearing observations
for (unsigned int i = 0; i < clearing_buffers_.size(); ++i) {
clearing_buffers_[i]->lock();
clearing_buffers_[i]->getObservations(clearing_observations);
current = clearing_buffers_[i]->isCurrent() && current;
clearing_buffers_[i]->unlock();
}
clearing_observations.insert(
clearing_observations.end(),
static_clearing_observations_.begin(), static_clearing_observations_.end());
return current;
}
void
ObstacleLayer::raytraceFreespace(
const Observation & clearing_observation, double * min_x,
double * min_y,
double * max_x,
double * max_y)
{
double ox = clearing_observation.origin_.x;
double oy = clearing_observation.origin_.y;
const sensor_msgs::msg::PointCloud2 & cloud = *(clearing_observation.cloud_);
// get the map coordinates of the origin of the sensor
unsigned int x0, y0;
if (!worldToMap(ox, oy, x0, y0)) {
RCLCPP_WARN(
logger_,
"Sensor origin at (%.2f, %.2f) is out of map bounds (%.2f, %.2f) to (%.2f, %.2f). "
"The costmap cannot raytrace for it.",
ox, oy,
origin_x_, origin_y_,
origin_x_ + getSizeInMetersX(), origin_y_ + getSizeInMetersY());
return;
}
// we can pre-compute the enpoints of the map outside of the inner loop... we'll need these later
double origin_x = origin_x_, origin_y = origin_y_;
double map_end_x = origin_x + size_x_ * resolution_;
double map_end_y = origin_y + size_y_ * resolution_;
touch(ox, oy, min_x, min_y, max_x, max_y);
// for each point in the cloud, we want to trace a line from the origin
// and clear obstacles along it
sensor_msgs::PointCloud2ConstIterator<float> iter_x(cloud, "x");
sensor_msgs::PointCloud2ConstIterator<float> iter_y(cloud, "y");
for (; iter_x != iter_x.end(); ++iter_x, ++iter_y) {
double wx = *iter_x;
double wy = *iter_y;
// now we also need to make sure that the enpoint we're raytracing
// to isn't off the costmap and scale if necessary
double a = wx - ox;
double b = wy - oy;
// the minimum value to raytrace from is the origin
if (wx < origin_x) {
double t = (origin_x - ox) / a;
wx = origin_x;
wy = oy + b * t;
}
if (wy < origin_y) {
double t = (origin_y - oy) / b;
wx = ox + a * t;
wy = origin_y;
}
// the maximum value to raytrace to is the end of the map
if (wx > map_end_x) {
double t = (map_end_x - ox) / a;
wx = map_end_x - .001;
wy = oy + b * t;
}
if (wy > map_end_y) {
double t = (map_end_y - oy) / b;
wx = ox + a * t;
wy = map_end_y - .001;
}
// now that the vector is scaled correctly... we'll get the map coordinates of its endpoint
unsigned int x1, y1;
// check for legality just in case
if (!worldToMap(wx, wy, x1, y1)) {
continue;
}
unsigned int cell_raytrace_max_range = cellDistance(clearing_observation.raytrace_max_range_);
unsigned int cell_raytrace_min_range = cellDistance(clearing_observation.raytrace_min_range_);
MarkCell marker(costmap_, FREE_SPACE);
// and finally... we can execute our trace to clear obstacles along that line
raytraceLine(marker, x0, y0, x1, y1, cell_raytrace_max_range, cell_raytrace_min_range);
updateRaytraceBounds(
ox, oy, wx, wy, clearing_observation.raytrace_max_range_,
clearing_observation.raytrace_min_range_, min_x, min_y, max_x,
max_y);
}
}
void
ObstacleLayer::activate()
{
for (auto & notifier : observation_notifiers_) {
notifier->clear();
}
// if we're stopped we need to re-subscribe to topics
for (unsigned int i = 0; i < observation_subscribers_.size(); ++i) {
if (observation_subscribers_[i] != NULL) {
observation_subscribers_[i]->subscribe();
}
}
resetBuffersLastUpdated();
}
void
ObstacleLayer::deactivate()
{
for (unsigned int i = 0; i < observation_subscribers_.size(); ++i) {
if (observation_subscribers_[i] != NULL) {
observation_subscribers_[i]->unsubscribe();
}
}
}
void
ObstacleLayer::updateRaytraceBounds(
double ox, double oy, double wx, double wy, double max_range, double min_range,
double * min_x, double * min_y, double * max_x, double * max_y)
{
double dx = wx - ox, dy = wy - oy;
double full_distance = hypot(dx, dy);
if (full_distance < min_range) {
return;
}
double scale = std::min(1.0, max_range / full_distance);
double ex = ox + dx * scale, ey = oy + dy * scale;
touch(ex, ey, min_x, min_y, max_x, max_y);
}
void
ObstacleLayer::reset()
{
resetMaps();
resetBuffersLastUpdated();
current_ = false;
was_reset_ = true;
}
void
ObstacleLayer::resetBuffersLastUpdated()
{
for (unsigned int i = 0; i < observation_buffers_.size(); ++i) {
if (observation_buffers_[i]) {
observation_buffers_[i]->resetLastUpdated();
}
}
}
} // namespace nav2_costmap_2d
@@ -0,0 +1,542 @@
/*
* Software License Agreement (BSD License)
*
* Copyright (c) 2018 David V. Lu!!
* Copyright (c) 2020, Bytes Robotics
* All rights reserved.
*
* 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 copyright holder 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.
*/
#include <angles/angles.h>
#include <algorithm>
#include <list>
#include <limits>
#include <string>
#include <vector>
#include "pluginlib/class_list_macros.hpp"
#include "geometry_msgs/msg/point_stamped.hpp"
#include "nav2_costmap_2d/range_sensor_layer.hpp"
PLUGINLIB_EXPORT_CLASS(nav2_costmap_2d::RangeSensorLayer, nav2_costmap_2d::Layer)
using nav2_costmap_2d::LETHAL_OBSTACLE;
using nav2_costmap_2d::INSCRIBED_INFLATED_OBSTACLE;
using nav2_costmap_2d::NO_INFORMATION;
using namespace std::literals::chrono_literals;
namespace nav2_costmap_2d
{
RangeSensorLayer::RangeSensorLayer() {}
void RangeSensorLayer::onInitialize()
{
current_ = true;
was_reset_ = false;
buffered_readings_ = 0;
last_reading_time_ = clock_->now();
default_value_ = to_cost(0.5);
matchSize();
resetRange();
auto node = node_.lock();
if (!node) {
throw std::runtime_error{"Failed to lock node"};
}
declareParameter("enabled", rclcpp::ParameterValue(true));
node->get_parameter(name_ + "." + "enabled", enabled_);
declareParameter("phi", rclcpp::ParameterValue(1.2));
node->get_parameter(name_ + "." + "phi", phi_v_);
declareParameter("inflate_cone", rclcpp::ParameterValue(1.0));
node->get_parameter(name_ + "." + "inflate_cone", inflate_cone_);
declareParameter("no_readings_timeout", rclcpp::ParameterValue(0.0));
node->get_parameter(name_ + "." + "no_readings_timeout", no_readings_timeout_);
declareParameter("clear_threshold", rclcpp::ParameterValue(0.2));
node->get_parameter(name_ + "." + "clear_threshold", clear_threshold_);
declareParameter("mark_threshold", rclcpp::ParameterValue(0.8));
node->get_parameter(name_ + "." + "mark_threshold", mark_threshold_);
declareParameter("clear_on_max_reading", rclcpp::ParameterValue(false));
node->get_parameter(name_ + "." + "clear_on_max_reading", clear_on_max_reading_);
double temp_tf_tol = 0.0;
node->get_parameter("transform_tolerance", temp_tf_tol);
transform_tolerance_ = tf2::durationFromSec(temp_tf_tol);
std::vector<std::string> topic_names{};
declareParameter("topics", rclcpp::ParameterValue(topic_names));
node->get_parameter(name_ + "." + "topics", topic_names);
InputSensorType input_sensor_type = InputSensorType::ALL;
std::string sensor_type_name;
declareParameter("input_sensor_type", rclcpp::ParameterValue("ALL"));
node->get_parameter(name_ + "." + "input_sensor_type", sensor_type_name);
std::transform(
sensor_type_name.begin(), sensor_type_name.end(),
sensor_type_name.begin(), ::toupper);
RCLCPP_INFO(
logger_, "%s: %s as input_sensor_type given",
name_.c_str(), sensor_type_name.c_str());
if (sensor_type_name == "VARIABLE") {
input_sensor_type = InputSensorType::VARIABLE;
} else if (sensor_type_name == "FIXED") {
input_sensor_type = InputSensorType::FIXED;
} else if (sensor_type_name == "ALL") {
input_sensor_type = InputSensorType::ALL;
} else {
RCLCPP_ERROR(
logger_, "%s: Invalid input sensor type: %s. Defaulting to ALL.",
name_.c_str(), sensor_type_name.c_str());
}
// Validate topic names list: it must be a (normally non-empty) list of strings
if (topic_names.empty()) {
RCLCPP_FATAL(
logger_, "Invalid topic names list: it must"
"be a non-empty list of strings");
return;
}
// Traverse the topic names list subscribing to all of them with the same callback method
for (auto & topic_name : topic_names) {
if (input_sensor_type == InputSensorType::VARIABLE) {
processRangeMessageFunc_ = std::bind(
&RangeSensorLayer::processVariableRangeMsg, this,
std::placeholders::_1);
} else if (input_sensor_type == InputSensorType::FIXED) {
processRangeMessageFunc_ = std::bind(
&RangeSensorLayer::processFixedRangeMsg, this,
std::placeholders::_1);
} else if (input_sensor_type == InputSensorType::ALL) {
processRangeMessageFunc_ = std::bind(
&RangeSensorLayer::processRangeMsg, this,
std::placeholders::_1);
} else {
RCLCPP_ERROR(
logger_,
"%s: Invalid input sensor type: %s. Did you make a new type"
"and forgot to choose the subscriber for it?",
name_.c_str(), sensor_type_name.c_str());
}
range_subs_.push_back(
node->create_subscription<sensor_msgs::msg::Range>(
topic_name, rclcpp::SensorDataQoS(), std::bind(
&RangeSensorLayer::bufferIncomingRangeMsg, this,
std::placeholders::_1)));
RCLCPP_INFO(
logger_, "RangeSensorLayer: subscribed to "
"topic %s", range_subs_.back()->get_topic_name());
}
global_frame_ = layered_costmap_->getGlobalFrameID();
}
double RangeSensorLayer::gamma(double theta)
{
if (fabs(theta) > max_angle_) {
return 0.0;
} else {
return 1 - pow(theta / max_angle_, 2);
}
}
double RangeSensorLayer::delta(double phi)
{
return 1 - (1 + tanh(2 * (phi - phi_v_))) / 2;
}
void RangeSensorLayer::get_deltas(double angle, double * dx, double * dy)
{
double ta = tan(angle);
if (ta == 0) {
*dx = 0;
} else {
*dx = resolution_ / ta;
}
*dx = copysign(*dx, cos(angle));
*dy = copysign(resolution_, sin(angle));
}
double RangeSensorLayer::sensor_model(double r, double phi, double theta)
{
double lbda = delta(phi) * gamma(theta);
double delta = resolution_;
if (phi >= 0.0 && phi < r - 2 * delta * r) {
return (1 - lbda) * (0.5);
} else if (phi < r - delta * r) {
return lbda * 0.5 * pow((phi - (r - 2 * delta * r)) / (delta * r), 2) +
(1 - lbda) * .5;
} else if (phi < r + delta * r) {
double J = (r - phi) / (delta * r);
return lbda * ((1 - (0.5) * pow(J, 2)) - 0.5) + 0.5;
} else {
return 0.5;
}
}
void RangeSensorLayer::bufferIncomingRangeMsg(
const sensor_msgs::msg::Range::SharedPtr range_message)
{
range_message_mutex_.lock();
range_msgs_buffer_.push_back(*range_message);
range_message_mutex_.unlock();
}
void RangeSensorLayer::updateCostmap()
{
std::list<sensor_msgs::msg::Range> range_msgs_buffer_copy;
range_message_mutex_.lock();
range_msgs_buffer_copy = std::list<sensor_msgs::msg::Range>(range_msgs_buffer_);
range_msgs_buffer_.clear();
range_message_mutex_.unlock();
for (auto & range_msgs_it : range_msgs_buffer_copy) {
processRangeMessageFunc_(range_msgs_it);
}
}
void RangeSensorLayer::processRangeMsg(sensor_msgs::msg::Range & range_message)
{
if (range_message.min_range == range_message.max_range) {
processFixedRangeMsg(range_message);
} else {
processVariableRangeMsg(range_message);
}
}
void RangeSensorLayer::processFixedRangeMsg(sensor_msgs::msg::Range & range_message)
{
if (!std::isinf(range_message.range)) {
RCLCPP_ERROR(
logger_,
"Fixed distance ranger (min_range == max_range) in frame %s sent invalid value. "
"Only -Inf (== object detected) and Inf (== no object detected) are valid.",
range_message.header.frame_id.c_str());
return;
}
bool clear_sensor_cone = false;
if (range_message.range > 0) { // +inf
if (!clear_on_max_reading_) {
return; // no clearing at all
}
clear_sensor_cone = true;
}
range_message.range = range_message.min_range;
updateCostmap(range_message, clear_sensor_cone);
}
void RangeSensorLayer::processVariableRangeMsg(sensor_msgs::msg::Range & range_message)
{
if (range_message.range < range_message.min_range || range_message.range >
range_message.max_range)
{
return;
}
bool clear_sensor_cone = false;
if (range_message.range >= range_message.max_range && clear_on_max_reading_) {
clear_sensor_cone = true;
}
updateCostmap(range_message, clear_sensor_cone);
}
void RangeSensorLayer::updateCostmap(
sensor_msgs::msg::Range & range_message,
bool clear_sensor_cone)
{
max_angle_ = range_message.field_of_view / 2;
geometry_msgs::msg::PointStamped in, out;
in.header.stamp = range_message.header.stamp;
in.header.frame_id = range_message.header.frame_id;
if (!tf_->canTransform(
in.header.frame_id, global_frame_,
tf2_ros::fromMsg(in.header.stamp),
tf2_ros::fromRclcpp(transform_tolerance_)))
{
RCLCPP_INFO(
logger_, "Range sensor layer can't transform from %s to %s",
global_frame_.c_str(), in.header.frame_id.c_str());
return;
}
tf_->transform(in, out, global_frame_, transform_tolerance_);
double ox = out.point.x, oy = out.point.y;
in.point.x = range_message.range;
tf_->transform(in, out, global_frame_, transform_tolerance_);
double tx = out.point.x, ty = out.point.y;
// calculate target props
double dx = tx - ox, dy = ty - oy, theta = atan2(dy, dx), d = sqrt(dx * dx + dy * dy);
// Integer Bounds of Update
int bx0, by0, bx1, by1;
// Triangle that will be really updated; the other cells within bounds are ignored
// This triangle is formed by the origin and left and right sides of sonar cone
int Ox, Oy, Ax, Ay, Bx, By;
// Bounds includes the origin
worldToMapNoBounds(ox, oy, Ox, Oy);
bx1 = bx0 = Ox;
by1 = by0 = Oy;
touch(ox, oy, &min_x_, &min_y_, &max_x_, &max_y_);
// Update Map with Target Point
unsigned int aa, ab;
if (worldToMap(tx, ty, aa, ab)) {
setCost(aa, ab, 233);
touch(tx, ty, &min_x_, &min_y_, &max_x_, &max_y_);
}
double mx, my;
// Update left side of sonar cone
mx = ox + cos(theta - max_angle_) * d * 1.2;
my = oy + sin(theta - max_angle_) * d * 1.2;
worldToMapNoBounds(mx, my, Ax, Ay);
bx0 = std::min(bx0, Ax);
bx1 = std::max(bx1, Ax);
by0 = std::min(by0, Ay);
by1 = std::max(by1, Ay);
touch(mx, my, &min_x_, &min_y_, &max_x_, &max_y_);
// Update right side of sonar cone
mx = ox + cos(theta + max_angle_) * d * 1.2;
my = oy + sin(theta + max_angle_) * d * 1.2;
worldToMapNoBounds(mx, my, Bx, By);
bx0 = std::min(bx0, Bx);
bx1 = std::max(bx1, Bx);
by0 = std::min(by0, By);
by1 = std::max(by1, By);
touch(mx, my, &min_x_, &min_y_, &max_x_, &max_y_);
// Limit Bounds to Grid
bx0 = std::max(0, bx0);
by0 = std::max(0, by0);
bx1 = std::min(static_cast<int>(size_x_), bx1);
by1 = std::min(static_cast<int>(size_y_), by1);
for (unsigned int x = bx0; x <= (unsigned int)bx1; x++) {
for (unsigned int y = by0; y <= (unsigned int)by1; y++) {
bool update_xy_cell = true;
// Unless inflate_cone_ is set to 100 %, we update cells only within the
// (partially inflated) sensor cone, projected on the costmap as a triangle.
// 0 % corresponds to just the triangle, but if your sensor fov is very
// narrow, the covered area can become zero due to cell discretization.
// See wiki description for more details
if (inflate_cone_ < 1.0) {
// Determine barycentric coordinates
int w0 = orient2d(Ax, Ay, Bx, By, x, y);
int w1 = orient2d(Bx, By, Ox, Oy, x, y);
int w2 = orient2d(Ox, Oy, Ax, Ay, x, y);
// Barycentric coordinates inside area threshold; this is not mathematically
// sound at all, but it works!
float bcciath = -static_cast<float>(inflate_cone_) * area(Ax, Ay, Bx, By, Ox, Oy);
update_xy_cell = w0 >= bcciath && w1 >= bcciath && w2 >= bcciath;
}
if (update_xy_cell) {
double wx, wy;
mapToWorld(x, y, wx, wy);
update_cell(ox, oy, theta, range_message.range, wx, wy, clear_sensor_cone);
}
}
}
buffered_readings_++;
last_reading_time_ = clock_->now();
}
void RangeSensorLayer::update_cell(
double ox, double oy, double ot, double r,
double nx, double ny, bool clear)
{
unsigned int x, y;
if (worldToMap(nx, ny, x, y)) {
double dx = nx - ox, dy = ny - oy;
double theta = atan2(dy, dx) - ot;
theta = angles::normalize_angle(theta);
double phi = sqrt(dx * dx + dy * dy);
double sensor = 0.0;
if (!clear) {
sensor = sensor_model(r, phi, theta);
}
double prior = to_prob(getCost(x, y));
double prob_occ = sensor * prior;
double prob_not = (1 - sensor) * (1 - prior);
double new_prob = prob_occ / (prob_occ + prob_not);
RCLCPP_DEBUG(
logger_,
"%f %f | %f %f = %f", dx, dy, theta, phi, sensor);
RCLCPP_DEBUG(
logger_,
"%f | %f %f | %f", prior, prob_occ, prob_not, new_prob);
unsigned char c = to_cost(new_prob);
setCost(x, y, c);
}
}
void RangeSensorLayer::resetRange()
{
min_x_ = min_y_ = std::numeric_limits<double>::max();
max_x_ = max_y_ = -std::numeric_limits<double>::max();
}
void RangeSensorLayer::updateBounds(
double robot_x, double robot_y,
double robot_yaw, double * min_x, double * min_y,
double * max_x, double * max_y)
{
robot_yaw = 0 + robot_yaw; // Avoid error if variable not in use
if (layered_costmap_->isRolling()) {
updateOrigin(robot_x - getSizeInMetersX() / 2, robot_y - getSizeInMetersY() / 2);
}
updateCostmap();
*min_x = std::min(*min_x, min_x_);
*min_y = std::min(*min_y, min_y_);
*max_x = std::max(*max_x, max_x_);
*max_y = std::max(*max_y, max_y_);
resetRange();
if (!enabled_) {
current_ = true;
return;
}
if (buffered_readings_ == 0) {
if (no_readings_timeout_ > 0.0 &&
(clock_->now() - last_reading_time_).seconds() >
no_readings_timeout_)
{
RCLCPP_WARN(
logger_,
"No range readings received for %.2f seconds, while expected at least every %.2f seconds.",
(clock_->now() - last_reading_time_).seconds(),
no_readings_timeout_);
current_ = false;
}
}
}
void RangeSensorLayer::updateCosts(
nav2_costmap_2d::Costmap2D & master_grid,
int min_i, int min_j, int max_i, int max_j)
{
if (!enabled_) {
return;
}
unsigned char * master_array = master_grid.getCharMap();
unsigned int span = master_grid.getSizeInCellsX();
unsigned char clear = to_cost(clear_threshold_), mark = to_cost(mark_threshold_);
for (int j = min_j; j < max_j; j++) {
unsigned int it = j * span + min_i;
for (int i = min_i; i < max_i; i++) {
unsigned char prob = costmap_[it];
unsigned char current;
if (prob == nav2_costmap_2d::NO_INFORMATION) {
it++;
continue;
} else if (prob > mark) {
current = nav2_costmap_2d::LETHAL_OBSTACLE;
} else if (prob < clear) {
current = nav2_costmap_2d::FREE_SPACE;
} else {
it++;
continue;
}
unsigned char old_cost = master_array[it];
if (old_cost == NO_INFORMATION || old_cost < current) {
master_array[it] = current;
}
it++;
}
}
buffered_readings_ = 0;
// if not current due to reset, set current now after clearing
if (!current_ && was_reset_) {
was_reset_ = false;
current_ = true;
}
}
void RangeSensorLayer::reset()
{
RCLCPP_DEBUG(logger_, "Reseting range sensor layer...");
deactivate();
resetMaps();
was_reset_ = true;
activate();
}
void RangeSensorLayer::deactivate()
{
range_msgs_buffer_.clear();
}
void RangeSensorLayer::activate()
{
range_msgs_buffer_.clear();
}
} // namespace nav2_costmap_2d
@@ -0,0 +1,512 @@
/*********************************************************************
*
* Software License Agreement (BSD License)
*
* Copyright (c) 2008, 2013, Willow Garage, Inc.
* Copyright (c) 2015, Fetch Robotics, Inc.
* All rights reserved.
*
* 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 Willow Garage, Inc. 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 OWNER 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.
*
* Author: Eitan Marder-Eppstein
* David V. Lu!!
*********************************************************************/
#include "nav2_costmap_2d/static_layer.hpp"
#include <algorithm>
#include <string>
#include "pluginlib/class_list_macros.hpp"
#include "tf2/convert.h"
#include "tf2_geometry_msgs/tf2_geometry_msgs.hpp"
#include "nav2_util/validate_messages.hpp"
PLUGINLIB_EXPORT_CLASS(nav2_costmap_2d::StaticLayer, nav2_costmap_2d::Layer)
using nav2_costmap_2d::NO_INFORMATION;
using nav2_costmap_2d::LETHAL_OBSTACLE;
using nav2_costmap_2d::FREE_SPACE;
using rcl_interfaces::msg::ParameterType;
namespace nav2_costmap_2d
{
StaticLayer::StaticLayer()
: map_buffer_(nullptr)
{
}
StaticLayer::~StaticLayer()
{
}
void
StaticLayer::onInitialize()
{
global_frame_ = layered_costmap_->getGlobalFrameID();
getParameters();
rclcpp::QoS map_qos(10); // initialize to default
if (map_subscribe_transient_local_) {
map_qos.transient_local();
map_qos.reliable();
map_qos.keep_last(1);
}
RCLCPP_INFO(
logger_,
"Subscribing to the map topic (%s) with %s durability",
map_topic_.c_str(),
map_subscribe_transient_local_ ? "transient local" : "volatile");
auto node = node_.lock();
if (!node) {
throw std::runtime_error{"Failed to lock node"};
}
map_sub_ = node->create_subscription<nav_msgs::msg::OccupancyGrid>(
map_topic_, map_qos,
std::bind(&StaticLayer::incomingMap, this, std::placeholders::_1));
if (subscribe_to_updates_) {
RCLCPP_INFO(logger_, "Subscribing to updates");
map_update_sub_ = node->create_subscription<map_msgs::msg::OccupancyGridUpdate>(
map_topic_ + "_updates",
rclcpp::SystemDefaultsQoS(),
std::bind(&StaticLayer::incomingUpdate, this, std::placeholders::_1));
}
}
void
StaticLayer::activate()
{
}
void
StaticLayer::deactivate()
{
dyn_params_handler_.reset();
}
void
StaticLayer::reset()
{
has_updated_data_ = true;
current_ = false;
}
void
StaticLayer::getParameters()
{
int temp_lethal_threshold = 0;
double temp_tf_tol = 0.0;
declareParameter("enabled", rclcpp::ParameterValue(true));
declareParameter("subscribe_to_updates", rclcpp::ParameterValue(false));
declareParameter("map_subscribe_transient_local", rclcpp::ParameterValue(true));
declareParameter("transform_tolerance", rclcpp::ParameterValue(0.0));
declareParameter("map_topic", rclcpp::ParameterValue(""));
declareParameter("footprint_clearing_enabled", rclcpp::ParameterValue(false));
auto node = node_.lock();
if (!node) {
throw std::runtime_error{"Failed to lock node"};
}
node->get_parameter(name_ + "." + "enabled", enabled_);
node->get_parameter(name_ + "." + "subscribe_to_updates", subscribe_to_updates_);
node->get_parameter(name_ + "." + "footprint_clearing_enabled", footprint_clearing_enabled_);
std::string private_map_topic, global_map_topic;
node->get_parameter(name_ + "." + "map_topic", private_map_topic);
node->get_parameter("map_topic", global_map_topic);
if (!private_map_topic.empty()) {
map_topic_ = private_map_topic;
} else {
map_topic_ = global_map_topic;
}
node->get_parameter(
name_ + "." + "map_subscribe_transient_local",
map_subscribe_transient_local_);
node->get_parameter("track_unknown_space", track_unknown_space_);
node->get_parameter("use_maximum", use_maximum_);
node->get_parameter("lethal_cost_threshold", temp_lethal_threshold);
node->get_parameter("unknown_cost_value", unknown_cost_value_);
node->get_parameter("trinary_costmap", trinary_costmap_);
node->get_parameter("transform_tolerance", temp_tf_tol);
// Enforce bounds
lethal_threshold_ = std::max(std::min(temp_lethal_threshold, 100), 0);
map_received_ = false;
map_received_in_update_bounds_ = false;
transform_tolerance_ = tf2::durationFromSec(temp_tf_tol);
// Add callback for dynamic parameters
dyn_params_handler_ = node->add_on_set_parameters_callback(
std::bind(
&StaticLayer::dynamicParametersCallback,
this, std::placeholders::_1));
}
void
StaticLayer::processMap(const nav_msgs::msg::OccupancyGrid & new_map)
{
RCLCPP_DEBUG(logger_, "StaticLayer: Process map");
unsigned int size_x = new_map.info.width;
unsigned int size_y = new_map.info.height;
RCLCPP_DEBUG(
logger_,
"StaticLayer: Received a %d X %d map at %f m/pix", size_x, size_y,
new_map.info.resolution);
// resize costmap if size, resolution or origin do not match
Costmap2D * master = layered_costmap_->getCostmap();
if (!layered_costmap_->isRolling() && (master->getSizeInCellsX() != size_x ||
master->getSizeInCellsY() != size_y ||
master->getResolution() != new_map.info.resolution ||
master->getOriginX() != new_map.info.origin.position.x ||
master->getOriginY() != new_map.info.origin.position.y ||
!layered_costmap_->isSizeLocked()))
{
// Update the size of the layered costmap (and all layers, including this one)
RCLCPP_INFO(
logger_,
"StaticLayer: Resizing costmap to %d X %d at %f m/pix", size_x, size_y,
new_map.info.resolution);
layered_costmap_->resizeMap(
size_x, size_y, new_map.info.resolution,
new_map.info.origin.position.x,
new_map.info.origin.position.y,
true);
} else if (size_x_ != size_x || size_y_ != size_y || // NOLINT
resolution_ != new_map.info.resolution ||
origin_x_ != new_map.info.origin.position.x ||
origin_y_ != new_map.info.origin.position.y)
{
// only update the size of the costmap stored locally in this layer
RCLCPP_INFO(
logger_,
"StaticLayer: Resizing static layer to %d X %d at %f m/pix", size_x, size_y,
new_map.info.resolution);
resizeMap(
size_x, size_y, new_map.info.resolution,
new_map.info.origin.position.x, new_map.info.origin.position.y);
}
unsigned int index = 0;
// we have a new map, update full size of map
std::lock_guard<Costmap2D::mutex_t> guard(*getMutex());
// initialize the costmap with static data
for (unsigned int i = 0; i < size_y; ++i) {
for (unsigned int j = 0; j < size_x; ++j) {
unsigned char value = new_map.data[index];
costmap_[index] = interpretValue(value);
++index;
}
}
map_frame_ = new_map.header.frame_id;
x_ = y_ = 0;
width_ = size_x_;
height_ = size_y_;
has_updated_data_ = true;
current_ = true;
}
void
StaticLayer::matchSize()
{
// If we are using rolling costmap, the static map size is
// unrelated to the size of the layered costmap
if (!layered_costmap_->isRolling()) {
Costmap2D * master = layered_costmap_->getCostmap();
resizeMap(
master->getSizeInCellsX(), master->getSizeInCellsY(), master->getResolution(),
master->getOriginX(), master->getOriginY());
}
}
unsigned char
StaticLayer::interpretValue(unsigned char value)
{
// check if the static value is above the unknown or lethal thresholds
if (track_unknown_space_ && value == unknown_cost_value_) {
return NO_INFORMATION;
} else if (!track_unknown_space_ && value == unknown_cost_value_) {
return FREE_SPACE;
} else if (value >= lethal_threshold_) {
return LETHAL_OBSTACLE;
} else if (trinary_costmap_) {
return FREE_SPACE;
}
double scale = static_cast<double>(value) / lethal_threshold_;
return scale * LETHAL_OBSTACLE;
}
void
StaticLayer::incomingMap(const nav_msgs::msg::OccupancyGrid::SharedPtr new_map)
{
if (!nav2_util::validateMsg(*new_map)) {
RCLCPP_ERROR(logger_, "Received map message is malformed. Rejecting.");
return;
}
if (!map_received_) {
processMap(*new_map);
map_received_ = true;
return;
}
std::lock_guard<Costmap2D::mutex_t> guard(*getMutex());
map_buffer_ = new_map;
}
void
StaticLayer::incomingUpdate(map_msgs::msg::OccupancyGridUpdate::ConstSharedPtr update)
{
std::lock_guard<Costmap2D::mutex_t> guard(*getMutex());
if (update->y < static_cast<int32_t>(y_) ||
y_ + height_ < update->y + update->height ||
update->x < static_cast<int32_t>(x_) ||
x_ + width_ < update->x + update->width)
{
RCLCPP_WARN(
logger_,
"StaticLayer: Map update ignored. Exceeds bounds of static layer.\n"
"Static layer origin: %d, %d bounds: %d X %d\n"
"Update origin: %d, %d bounds: %d X %d",
x_, y_, width_, height_, update->x, update->y, update->width,
update->height);
return;
}
if (update->header.frame_id != map_frame_) {
RCLCPP_WARN(
logger_,
"StaticLayer: Map update ignored. Current map is in frame %s "
"but update was in frame %s",
map_frame_.c_str(), update->header.frame_id.c_str());
return;
}
unsigned int di = 0;
for (unsigned int y = 0; y < update->height; y++) {
unsigned int index_base = (update->y + y) * size_x_;
for (unsigned int x = 0; x < update->width; x++) {
unsigned int index = index_base + x + update->x;
costmap_[index] = interpretValue(update->data[di++]);
}
}
has_updated_data_ = true;
}
void
StaticLayer::updateBounds(
double robot_x, double robot_y, double robot_yaw, double * min_x,
double * min_y,
double * max_x,
double * max_y)
{
if (!map_received_) {
map_received_in_update_bounds_ = false;
return;
}
map_received_in_update_bounds_ = true;
std::lock_guard<Costmap2D::mutex_t> guard(*getMutex());
// If there is a new available map, load it.
if (map_buffer_) {
processMap(*map_buffer_);
map_buffer_ = nullptr;
}
if (!layered_costmap_->isRolling() ) {
if (!(has_updated_data_ || has_extra_bounds_)) {
return;
}
}
useExtraBounds(min_x, min_y, max_x, max_y);
double wx, wy;
mapToWorld(x_, y_, wx, wy);
*min_x = std::min(wx, *min_x);
*min_y = std::min(wy, *min_y);
mapToWorld(x_ + width_, y_ + height_, wx, wy);
*max_x = std::max(wx, *max_x);
*max_y = std::max(wy, *max_y);
has_updated_data_ = false;
updateFootprint(robot_x, robot_y, robot_yaw, min_x, min_y, max_x, max_y);
}
void
StaticLayer::updateFootprint(
double robot_x, double robot_y, double robot_yaw,
double * min_x, double * min_y,
double * max_x,
double * max_y)
{
if (!footprint_clearing_enabled_) {return;}
transformFootprint(robot_x, robot_y, robot_yaw, getFootprint(), transformed_footprint_);
for (unsigned int i = 0; i < transformed_footprint_.size(); i++) {
touch(transformed_footprint_[i].x, transformed_footprint_[i].y, min_x, min_y, max_x, max_y);
}
}
void
StaticLayer::updateCosts(
nav2_costmap_2d::Costmap2D & master_grid,
int min_i, int min_j, int max_i, int max_j)
{
std::lock_guard<Costmap2D::mutex_t> guard(*getMutex());
if (!enabled_) {
return;
}
if (!map_received_in_update_bounds_) {
static int count = 0;
// throttle warning down to only 1/10 message rate
if (++count == 10) {
RCLCPP_WARN(logger_, "Can't update static costmap layer, no map received");
count = 0;
}
return;
}
if (footprint_clearing_enabled_) {
setConvexPolygonCost(transformed_footprint_, nav2_costmap_2d::FREE_SPACE);
}
if (!layered_costmap_->isRolling()) {
// if not rolling, the layered costmap (master_grid) has same coordinates as this layer
if (!use_maximum_) {
updateWithTrueOverwrite(master_grid, min_i, min_j, max_i, max_j);
} else {
updateWithMax(master_grid, min_i, min_j, max_i, max_j);
}
} else {
// If rolling window, the master_grid is unlikely to have same coordinates as this layer
unsigned int mx, my;
double wx, wy;
// Might even be in a different frame
geometry_msgs::msg::TransformStamped transform;
try {
transform = tf_->lookupTransform(
map_frame_, global_frame_, tf2::TimePointZero,
transform_tolerance_);
} catch (tf2::TransformException & ex) {
RCLCPP_ERROR(logger_, "StaticLayer: %s", ex.what());
return;
}
// Copy map data given proper transformations
tf2::Transform tf2_transform;
tf2::fromMsg(transform.transform, tf2_transform);
for (int i = min_i; i < max_i; ++i) {
for (int j = min_j; j < max_j; ++j) {
// Convert master_grid coordinates (i,j) into global_frame_(wx,wy) coordinates
layered_costmap_->getCostmap()->mapToWorld(i, j, wx, wy);
// Transform from global_frame_ to map_frame_
tf2::Vector3 p(wx, wy, 0);
p = tf2_transform * p;
// Set master_grid with cell from map
if (worldToMap(p.x(), p.y(), mx, my)) {
if (!use_maximum_) {
master_grid.setCost(i, j, getCost(mx, my));
} else {
master_grid.setCost(i, j, std::max(getCost(mx, my), master_grid.getCost(i, j)));
}
}
}
}
}
current_ = true;
}
/**
* @brief Callback executed when a parameter change is detected
* @param event ParameterEvent message
*/
rcl_interfaces::msg::SetParametersResult
StaticLayer::dynamicParametersCallback(
std::vector<rclcpp::Parameter> parameters)
{
std::lock_guard<Costmap2D::mutex_t> guard(*getMutex());
rcl_interfaces::msg::SetParametersResult result;
for (auto parameter : parameters) {
const auto & param_type = parameter.get_type();
const auto & param_name = parameter.get_name();
if (param_name == name_ + "." + "map_subscribe_transient_local" ||
param_name == name_ + "." + "map_topic" ||
param_name == name_ + "." + "subscribe_to_updates")
{
RCLCPP_WARN(
logger_, "%s is not a dynamic parameter "
"cannot be changed while running. Rejecting parameter update.", param_name.c_str());
} else if (param_type == ParameterType::PARAMETER_DOUBLE) {
if (param_name == name_ + "." + "transform_tolerance") {
transform_tolerance_ = tf2::durationFromSec(parameter.as_double());
}
} else if (param_type == ParameterType::PARAMETER_BOOL) {
if (param_name == name_ + "." + "enabled" && enabled_ != parameter.as_bool()) {
enabled_ = parameter.as_bool();
x_ = y_ = 0;
width_ = size_x_;
height_ = size_y_;
has_updated_data_ = true;
current_ = false;
}
} else if (param_type == ParameterType::PARAMETER_BOOL) {
if (param_name == name_ + "." + "footprint_clearing_enabled") {
footprint_clearing_enabled_ = parameter.as_bool();
}
}
}
result.successful = true;
return result;
}
} // namespace nav2_costmap_2d
@@ -0,0 +1,538 @@
/*********************************************************************
*
* Software License Agreement (BSD License)
*
* Copyright (c) 2008, 2013, Willow Garage, Inc.
* All rights reserved.
*
* 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 Willow Garage, Inc. 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 OWNER 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.
*
* Author: Eitan Marder-Eppstein
* David V. Lu!!
*********************************************************************/
#include "nav2_costmap_2d/voxel_layer.hpp"
#include <algorithm>
#include <cassert>
#include <vector>
#include <memory>
#include <utility>
#include "pluginlib/class_list_macros.hpp"
#include "sensor_msgs/point_cloud2_iterator.hpp"
#define VOXEL_BITS 16
PLUGINLIB_EXPORT_CLASS(nav2_costmap_2d::VoxelLayer, nav2_costmap_2d::Layer)
using nav2_costmap_2d::NO_INFORMATION;
using nav2_costmap_2d::LETHAL_OBSTACLE;
using nav2_costmap_2d::FREE_SPACE;
using rcl_interfaces::msg::ParameterType;
namespace nav2_costmap_2d
{
void VoxelLayer::onInitialize()
{
ObstacleLayer::onInitialize();
declareParameter("enabled", rclcpp::ParameterValue(true));
declareParameter("footprint_clearing_enabled", rclcpp::ParameterValue(true));
declareParameter("max_obstacle_height", rclcpp::ParameterValue(2.0));
declareParameter("z_voxels", rclcpp::ParameterValue(10));
declareParameter("origin_z", rclcpp::ParameterValue(0.0));
declareParameter("z_resolution", rclcpp::ParameterValue(0.2));
declareParameter("unknown_threshold", rclcpp::ParameterValue(15));
declareParameter("mark_threshold", rclcpp::ParameterValue(0));
declareParameter("combination_method", rclcpp::ParameterValue(1));
declareParameter("publish_voxel_map", rclcpp::ParameterValue(false));
auto node = node_.lock();
if (!node) {
throw std::runtime_error{"Failed to lock node"};
}
node->get_parameter(name_ + "." + "enabled", enabled_);
node->get_parameter(name_ + "." + "footprint_clearing_enabled", footprint_clearing_enabled_);
node->get_parameter(name_ + "." + "max_obstacle_height", max_obstacle_height_);
node->get_parameter(name_ + "." + "z_voxels", size_z_);
node->get_parameter(name_ + "." + "origin_z", origin_z_);
node->get_parameter(name_ + "." + "z_resolution", z_resolution_);
node->get_parameter(name_ + "." + "unknown_threshold", unknown_threshold_);
node->get_parameter(name_ + "." + "mark_threshold", mark_threshold_);
node->get_parameter(name_ + "." + "combination_method", combination_method_);
node->get_parameter(name_ + "." + "publish_voxel_map", publish_voxel_);
auto custom_qos = rclcpp::QoS(rclcpp::KeepLast(1)).transient_local().reliable();
if (publish_voxel_) {
voxel_pub_ = node->create_publisher<nav2_msgs::msg::VoxelGrid>(
"voxel_grid", custom_qos);
voxel_pub_->on_activate();
}
clearing_endpoints_pub_ = node->create_publisher<sensor_msgs::msg::PointCloud2>(
"clearing_endpoints", custom_qos);
clearing_endpoints_pub_->on_activate();
unknown_threshold_ += (VOXEL_BITS - size_z_);
matchSize();
// Add callback for dynamic parameters
dyn_params_handler_ = node->add_on_set_parameters_callback(
std::bind(
&VoxelLayer::dynamicParametersCallback,
this, std::placeholders::_1));
}
VoxelLayer::~VoxelLayer()
{
dyn_params_handler_.reset();
}
void VoxelLayer::matchSize()
{
std::lock_guard<Costmap2D::mutex_t> guard(*getMutex());
ObstacleLayer::matchSize();
voxel_grid_.resize(size_x_, size_y_, size_z_);
assert(voxel_grid_.sizeX() == size_x_ && voxel_grid_.sizeY() == size_y_);
}
void VoxelLayer::reset()
{
// Call the base class method before adding our own functionality
ObstacleLayer::reset();
resetMaps();
}
void VoxelLayer::resetMaps()
{
// Call the base class method before adding our own functionality
// Note: at the time this was written, ObstacleLayer doesn't implement
// resetMaps so this goes to the next layer down Costmap2DLayer which also
// doesn't implement this, so it actually goes all the way to Costmap2D
ObstacleLayer::resetMaps();
voxel_grid_.reset();
}
void VoxelLayer::updateBounds(
double robot_x, double robot_y, double robot_yaw, double * min_x,
double * min_y, double * max_x, double * max_y)
{
std::lock_guard<Costmap2D::mutex_t> guard(*getMutex());
if (rolling_window_) {
updateOrigin(robot_x - getSizeInMetersX() / 2, robot_y - getSizeInMetersY() / 2);
}
if (!enabled_) {
return;
}
useExtraBounds(min_x, min_y, max_x, max_y);
bool current = true;
std::vector<Observation> observations, clearing_observations;
// get the marking observations
current = getMarkingObservations(observations) && current;
// get the clearing observations
current = getClearingObservations(clearing_observations) && current;
// update the global current status
current_ = current;
// raytrace freespace
for (unsigned int i = 0; i < clearing_observations.size(); ++i) {
raytraceFreespace(clearing_observations[i], min_x, min_y, max_x, max_y);
}
// place the new obstacles into a priority queue... each with a priority of zero to begin with
for (std::vector<Observation>::const_iterator it = observations.begin(); it != observations.end();
++it)
{
const Observation & obs = *it;
const sensor_msgs::msg::PointCloud2 & cloud = *(obs.cloud_);
double sq_obstacle_max_range = obs.obstacle_max_range_ * obs.obstacle_max_range_;
double sq_obstacle_min_range = obs.obstacle_min_range_ * obs.obstacle_min_range_;
sensor_msgs::PointCloud2ConstIterator<float> iter_x(cloud, "x");
sensor_msgs::PointCloud2ConstIterator<float> iter_y(cloud, "y");
sensor_msgs::PointCloud2ConstIterator<float> iter_z(cloud, "z");
for (; iter_x != iter_x.end(); ++iter_x, ++iter_y, ++iter_z) {
// if the obstacle is too high or too far away from the robot we won't add it
if (*iter_z > max_obstacle_height_) {
continue;
}
// compute the squared distance from the hitpoint to the pointcloud's origin
double sq_dist = (*iter_x - obs.origin_.x) * (*iter_x - obs.origin_.x) +
(*iter_y - obs.origin_.y) * (*iter_y - obs.origin_.y) +
(*iter_z - obs.origin_.z) * (*iter_z - obs.origin_.z);
// if the point is far enough away... we won't consider it
if (sq_dist >= sq_obstacle_max_range) {
continue;
}
// If the point is too close, do not consider it
if (sq_dist < sq_obstacle_min_range) {
continue;
}
// now we need to compute the map coordinates for the observation
unsigned int mx, my, mz;
if (*iter_z < origin_z_) {
if (!worldToMap3D(*iter_x, *iter_y, origin_z_, mx, my, mz)) {
continue;
}
} else if (!worldToMap3D(*iter_x, *iter_y, *iter_z, mx, my, mz)) {
continue;
}
// mark the cell in the voxel grid and check if we should also mark it in the costmap
if (voxel_grid_.markVoxelInMap(mx, my, mz, mark_threshold_)) {
unsigned int index = getIndex(mx, my);
costmap_[index] = LETHAL_OBSTACLE;
touch(
static_cast<double>(*iter_x), static_cast<double>(*iter_y),
min_x, min_y, max_x, max_y);
}
}
}
if (publish_voxel_) {
auto grid_msg = std::make_unique<nav2_msgs::msg::VoxelGrid>();
unsigned int size = voxel_grid_.sizeX() * voxel_grid_.sizeY();
grid_msg->size_x = voxel_grid_.sizeX();
grid_msg->size_y = voxel_grid_.sizeY();
grid_msg->size_z = voxel_grid_.sizeZ();
grid_msg->data.resize(size);
memcpy(&grid_msg->data[0], voxel_grid_.getData(), size * sizeof(unsigned int));
grid_msg->origin.x = origin_x_;
grid_msg->origin.y = origin_y_;
grid_msg->origin.z = origin_z_;
grid_msg->resolutions.x = resolution_;
grid_msg->resolutions.y = resolution_;
grid_msg->resolutions.z = z_resolution_;
grid_msg->header.frame_id = global_frame_;
grid_msg->header.stamp = clock_->now();
voxel_pub_->publish(std::move(grid_msg));
}
updateFootprint(robot_x, robot_y, robot_yaw, min_x, min_y, max_x, max_y);
}
void VoxelLayer::raytraceFreespace(
const Observation & clearing_observation, double * min_x,
double * min_y,
double * max_x,
double * max_y)
{
auto clearing_endpoints_ = std::make_unique<sensor_msgs::msg::PointCloud2>();
if (clearing_observation.cloud_->height == 0 || clearing_observation.cloud_->width == 0) {
return;
}
double sensor_x, sensor_y, sensor_z;
double ox = clearing_observation.origin_.x;
double oy = clearing_observation.origin_.y;
double oz = clearing_observation.origin_.z;
if (!worldToMap3DFloat(ox, oy, oz, sensor_x, sensor_y, sensor_z)) {
RCLCPP_WARN(
logger_,
"Sensor origin at (%.2f, %.2f %.2f) is out of map bounds "
"(%.2f, %.2f, %.2f) to (%.2f, %.2f, %.2f). "
"The costmap cannot raytrace for it.",
ox, oy, oz,
origin_x_, origin_y_, origin_z_,
origin_x_ + getSizeInMetersX(), origin_y_ + getSizeInMetersY(),
origin_z_ + getSizeInMetersZ());
return;
}
bool publish_clearing_points;
{
auto node = node_.lock();
if (!node) {
throw std::runtime_error{"Failed to lock node"};
}
publish_clearing_points = (node->count_subscribers("clearing_endpoints") > 0);
}
clearing_endpoints_->data.clear();
clearing_endpoints_->width = clearing_observation.cloud_->width;
clearing_endpoints_->height = clearing_observation.cloud_->height;
clearing_endpoints_->is_dense = true;
clearing_endpoints_->is_bigendian = false;
sensor_msgs::PointCloud2Modifier modifier(*clearing_endpoints_);
modifier.setPointCloud2Fields(
3, "x", 1, sensor_msgs::msg::PointField::FLOAT32,
"y", 1, sensor_msgs::msg::PointField::FLOAT32,
"z", 1, sensor_msgs::msg::PointField::FLOAT32);
sensor_msgs::PointCloud2Iterator<float> clearing_endpoints_iter_x(*clearing_endpoints_, "x");
sensor_msgs::PointCloud2Iterator<float> clearing_endpoints_iter_y(*clearing_endpoints_, "y");
sensor_msgs::PointCloud2Iterator<float> clearing_endpoints_iter_z(*clearing_endpoints_, "z");
// we can pre-compute the enpoints of the map outside of the inner loop... we'll need these later
double map_end_x = origin_x_ + getSizeInMetersX();
double map_end_y = origin_y_ + getSizeInMetersY();
double map_end_z = origin_z_ + getSizeInMetersZ();
sensor_msgs::PointCloud2ConstIterator<float> iter_x(*(clearing_observation.cloud_), "x");
sensor_msgs::PointCloud2ConstIterator<float> iter_y(*(clearing_observation.cloud_), "y");
sensor_msgs::PointCloud2ConstIterator<float> iter_z(*(clearing_observation.cloud_), "z");
for (; iter_x != iter_x.end(); ++iter_x, ++iter_y, ++iter_z) {
double wpx = *iter_x;
double wpy = *iter_y;
double wpz = *iter_z;
double distance = dist(ox, oy, oz, wpx, wpy, wpz);
double scaling_fact = 1.0;
scaling_fact = std::max(std::min(scaling_fact, (distance - 2 * resolution_) / distance), 0.0);
wpx = scaling_fact * (wpx - ox) + ox;
wpy = scaling_fact * (wpy - oy) + oy;
wpz = scaling_fact * (wpz - oz) + oz;
double a = wpx - ox;
double b = wpy - oy;
double c = wpz - oz;
double t = 1.0;
// we can only raytrace to a maximum z height
if (wpz > map_end_z) {
// we know we want the vector's z value to be max_z
t = std::max(0.0, std::min(t, (map_end_z - 0.01 - oz) / c));
} else if (wpz < origin_z_) {
// and we can only raytrace down to the floor
// we know we want the vector's z value to be 0.0
t = std::min(t, (origin_z_ - oz) / c);
}
// the minimum value to raytrace from is the origin
if (wpx < origin_x_) {
t = std::min(t, (origin_x_ - ox) / a);
}
if (wpy < origin_y_) {
t = std::min(t, (origin_y_ - oy) / b);
}
// the maximum value to raytrace to is the end of the map
if (wpx > map_end_x) {
t = std::min(t, (map_end_x - ox) / a);
}
if (wpy > map_end_y) {
t = std::min(t, (map_end_y - oy) / b);
}
wpx = ox + a * t;
wpy = oy + b * t;
wpz = oz + c * t;
double point_x, point_y, point_z;
if (worldToMap3DFloat(wpx, wpy, wpz, point_x, point_y, point_z)) {
unsigned int cell_raytrace_max_range = cellDistance(clearing_observation.raytrace_max_range_);
unsigned int cell_raytrace_min_range = cellDistance(clearing_observation.raytrace_min_range_);
// voxel_grid_.markVoxelLine(sensor_x, sensor_y, sensor_z, point_x, point_y, point_z);
voxel_grid_.clearVoxelLineInMap(
sensor_x, sensor_y, sensor_z, point_x, point_y, point_z,
costmap_,
unknown_threshold_, mark_threshold_, FREE_SPACE, NO_INFORMATION,
cell_raytrace_max_range, cell_raytrace_min_range);
updateRaytraceBounds(
ox, oy, wpx, wpy, clearing_observation.raytrace_max_range_,
clearing_observation.raytrace_min_range_, min_x, min_y,
max_x,
max_y);
if (publish_clearing_points) {
*clearing_endpoints_iter_x = wpx;
*clearing_endpoints_iter_y = wpy;
*clearing_endpoints_iter_z = wpz;
++clearing_endpoints_iter_x;
++clearing_endpoints_iter_y;
++clearing_endpoints_iter_z;
}
}
}
if (publish_clearing_points) {
clearing_endpoints_->header.frame_id = global_frame_;
clearing_endpoints_->header.stamp = clearing_observation.cloud_->header.stamp;
clearing_endpoints_pub_->publish(std::move(clearing_endpoints_));
}
}
void VoxelLayer::updateOrigin(double new_origin_x, double new_origin_y)
{
// project the new origin into the grid
int cell_ox, cell_oy;
cell_ox = static_cast<int>((new_origin_x - origin_x_) / resolution_);
cell_oy = static_cast<int>((new_origin_y - origin_y_) / resolution_);
// compute the associated world coordinates for the origin cell
// beacuase we want to keep things grid-aligned
double new_grid_ox, new_grid_oy;
new_grid_ox = origin_x_ + cell_ox * resolution_;
new_grid_oy = origin_y_ + cell_oy * resolution_;
// To save casting from unsigned int to int a bunch of times
int size_x = size_x_;
int size_y = size_y_;
// we need to compute the overlap of the new and existing windows
int lower_left_x, lower_left_y, upper_right_x, upper_right_y;
lower_left_x = std::min(std::max(cell_ox, 0), size_x);
lower_left_y = std::min(std::max(cell_oy, 0), size_y);
upper_right_x = std::min(std::max(cell_ox + size_x, 0), size_x);
upper_right_y = std::min(std::max(cell_oy + size_y, 0), size_y);
unsigned int cell_size_x = upper_right_x - lower_left_x;
unsigned int cell_size_y = upper_right_y - lower_left_y;
// we need a map to store the obstacles in the window temporarily
unsigned char * local_map = new unsigned char[cell_size_x * cell_size_y];
unsigned int * local_voxel_map = new unsigned int[cell_size_x * cell_size_y];
unsigned int * voxel_map = voxel_grid_.getData();
// copy the local window in the costmap to the local map
copyMapRegion(
costmap_, lower_left_x, lower_left_y, size_x_, local_map, 0, 0, cell_size_x,
cell_size_x,
cell_size_y);
copyMapRegion(
voxel_map, lower_left_x, lower_left_y, size_x_, local_voxel_map, 0, 0, cell_size_x,
cell_size_x,
cell_size_y);
// we'll reset our maps to unknown space if appropriate
resetMaps();
// update the origin with the appropriate world coordinates
origin_x_ = new_grid_ox;
origin_y_ = new_grid_oy;
// compute the starting cell location for copying data back in
int start_x = lower_left_x - cell_ox;
int start_y = lower_left_y - cell_oy;
// now we want to copy the overlapping information back into the map, but in its new location
copyMapRegion(
local_map, 0, 0, cell_size_x, costmap_, start_x, start_y, size_x_, cell_size_x,
cell_size_y);
copyMapRegion(
local_voxel_map, 0, 0, cell_size_x, voxel_map, start_x, start_y, size_x_,
cell_size_x,
cell_size_y);
// make sure to clean up
delete[] local_map;
delete[] local_voxel_map;
}
/**
* @brief Callback executed when a parameter change is detected
* @param event ParameterEvent message
*/
rcl_interfaces::msg::SetParametersResult
VoxelLayer::dynamicParametersCallback(
std::vector<rclcpp::Parameter> parameters)
{
std::lock_guard<Costmap2D::mutex_t> guard(*getMutex());
rcl_interfaces::msg::SetParametersResult result;
bool resize_map_needed = false;
for (auto parameter : parameters) {
const auto & param_type = parameter.get_type();
const auto & param_name = parameter.get_name();
if (param_type == ParameterType::PARAMETER_DOUBLE) {
if (param_name == name_ + "." + "max_obstacle_height") {
max_obstacle_height_ = parameter.as_double();
} else if (param_name == name_ + "." + "origin_z") {
origin_z_ = parameter.as_double();
resize_map_needed = true;
} else if (param_name == name_ + "." + "z_resolution") {
z_resolution_ = parameter.as_double();
resize_map_needed = true;
}
} else if (param_type == ParameterType::PARAMETER_BOOL) {
if (param_name == name_ + "." + "enabled") {
enabled_ = parameter.as_bool();
current_ = false;
} else if (param_name == name_ + "." + "footprint_clearing_enabled") {
footprint_clearing_enabled_ = parameter.as_bool();
} else if (param_name == name_ + "." + "publish_voxel_map") {
RCLCPP_WARN(
logger_, "publish voxel map is not a dynamic parameter "
"cannot be changed while running. Rejecting parameter update.");
continue;
}
} else if (param_type == ParameterType::PARAMETER_INTEGER) {
if (param_name == name_ + "." + "z_voxels") {
size_z_ = parameter.as_int();
resize_map_needed = true;
} else if (param_name == name_ + "." + "unknown_threshold") {
unknown_threshold_ = parameter.as_int() + (VOXEL_BITS - size_z_);
} else if (param_name == name_ + "." + "mark_threshold") {
mark_threshold_ = parameter.as_int();
} else if (param_name == name_ + "." + "combination_method") {
combination_method_ = parameter.as_int();
}
}
}
if (resize_map_needed) {
matchSize();
}
result.successful = true;
return result;
}
} // namespace nav2_costmap_2d