15 #include "nav2_collision_monitor/exclusion_zone.hpp"
21 #include "geometry_msgs/msg/point32.hpp"
22 #include "geometry_msgs/msg/transform_stamped.hpp"
23 #include "tf2/transform_datatypes.hpp"
24 #include "tf2_geometry_msgs/tf2_geometry_msgs.hpp"
26 #include "nav2_ros_common/node_utils.hpp"
27 #include "nav2_util/geometry_utils.hpp"
28 #include "nav2_util/robot_utils.hpp"
30 #include "nav2_collision_monitor/polygon_utils.hpp"
32 namespace nav2_collision_monitor
36 const nav2::LifecycleNode::WeakPtr & node,
37 const std::string & zone_name,
38 const nav2::TransformBuffer::SharedPtr tf_buffer,
39 const std::string & base_frame_id,
40 const std::string & global_frame_id,
41 const tf2::Duration & transform_tolerance,
42 const bool base_shift_correction)
43 : node_(node), zone_name_(zone_name), tf_buffer_(tf_buffer),
44 base_frame_id_(base_frame_id), global_frame_id_(global_frame_id),
45 transform_tolerance_(transform_tolerance), base_shift_correction_(base_shift_correction),
46 min_height_(-std::numeric_limits<double>::max()),
47 max_height_(std::numeric_limits<double>::max())
58 auto node =
node_.lock();
59 if (post_set_params_handler_ && node) {
60 node->remove_post_set_parameters_callback(post_set_params_handler_.get());
62 post_set_params_handler_.reset();
63 if (on_set_params_handler_ && node) {
64 node->remove_on_set_parameters_callback(on_set_params_handler_.get());
66 on_set_params_handler_.reset();
71 auto node =
node_.lock();
73 throw std::runtime_error{
"Failed to lock node"};
83 zone_pub_ = node->create_publisher<geometry_msgs::msg::PolygonStamped>(
87 post_set_params_handler_ = node->add_post_set_parameters_callback(
89 on_set_params_handler_ = node->add_on_set_parameters_callback(
97 auto node =
node_.lock();
99 throw std::runtime_error{
"Failed to lock node"};
112 if (desc.type ==
"circle") {
117 logger_,
"[%s]: circle exclusion zone requires a positive 'radius'",
122 }
else if (desc.type ==
"polygon") {
124 if (desc.points.size() < 3) {
126 logger_,
"[%s]: polygon exclusion zone requires at least 3 points, got %zu",
131 poly_.reserve(desc.points.size());
132 for (
const auto & p : desc.points) {
134 pt.x =
static_cast<double>(p.x);
135 pt.y =
static_cast<double>(p.y);
140 logger_,
"[%s]: unknown exclusion zone type: %s",
146 zone_pub_ = node->create_publisher<geometry_msgs::msg::PolygonStamped>(
155 auto node =
node_.lock();
157 throw std::runtime_error{
"Failed to lock node"};
164 frame_id_ = node->declare_or_get_parameter(
174 zone_name_ +
".min_height", -std::numeric_limits<double>::max());
176 zone_name_ +
".max_height", std::numeric_limits<double>::max());
178 const std::string type = node->declare_or_get_parameter(
179 zone_name_ +
".type", std::string(
"polygon"));
181 if (type ==
"circle") {
186 logger_,
"[%s]: circle exclusion zone requires a positive 'radius'",
191 }
else if (type ==
"polygon") {
195 const std::string points_str = node->declare_or_get_parameter(
198 if (!parsePolygonPoints(points_str, 3,
poly_, error)) {
205 logger_,
"[%s]: unknown exclusion zone type: %s",
215 std::lock_guard<std::mutex> lock(
mutex_);
224 tf2::Transform tf_zone_to_base;
231 const tf2::Vector3 center = tf_zone_to_base.getOrigin();
232 const double cx = center.x();
233 const double cy = center.y();
236 data.begin(), data.end(),
237 [&](
const Point & p) {
238 if (p.z < min_height_ || p.z > max_height_) {
241 const double dx = p.x - cx;
242 const double dy = p.y - cy;
248 std::vector<Point> poly_base;
249 transformPolygonPoints(tf_zone_to_base, poly_, poly_base);
252 data.begin(), data.end(),
253 [&](
const Point & p) {
254 if (p.z < min_height_ || p.z > max_height_) {
257 return nav2_util::geometry_utils::isPointInsidePolygon(p.x, p.y, poly_base);
263 bool ExclusionZone::getZoneToBaseTransform(
264 const rclcpp::Time & curr_time, tf2::Transform & tf_zone_to_base)
const
267 if (frame_id_ == base_frame_id_) {
268 tf_zone_to_base.setIdentity();
277 const tf2::Duration non_blocking = tf2::Duration::zero();
281 rclcpp::Time zone_stamp = curr_time;
282 tf2::Transform tf_zone_to_global;
283 tf_zone_to_global.setIdentity();
284 if (frame_id_ != global_frame_id_) {
285 geometry_msgs::msg::TransformStamped zone_to_global_msg;
286 if (!nav2_util::getTransform(
287 frame_id_, global_frame_id_, non_blocking, tf_buffer_, zone_to_global_msg))
289 RCLCPP_WARN_THROTTLE(
290 logger_, *node_clock_, 2000,
291 "[%s]: no transform available for exclusion zone frame '%s'; not excluding any points",
292 zone_name_.c_str(), frame_id_.c_str());
295 zone_stamp = rclcpp::Time(zone_to_global_msg.header.stamp, curr_time.get_clock_type());
301 const double age = (curr_time - zone_stamp).seconds();
302 const double max_age =
303 std::max(frame_hold_timeout_, tf2::durationToSec(transform_tolerance_));
305 RCLCPP_WARN_THROTTLE(
306 logger_, *node_clock_, 2000,
307 "[%s]: exclusion zone frame '%s' stale for %.2fs (> %.2fs hold window); "
308 "not excluding any points",
309 zone_name_.c_str(), frame_id_.c_str(), age, max_age);
317 tf2::fromMsg(zone_to_global_msg.transform, tf_zone_to_global);
323 tf2::Transform tf_global_to_base;
325 if (base_shift_correction_) {
326 got_base = nav2_util::getTransform(
327 global_frame_id_, curr_time, base_frame_id_, curr_time, global_frame_id_,
328 non_blocking, tf_buffer_, tf_global_to_base);
330 got_base = nav2_util::getTransform(
331 global_frame_id_, base_frame_id_, non_blocking, tf_buffer_, tf_global_to_base);
334 RCLCPP_WARN_THROTTLE(
335 logger_, *node_clock_, 2000,
336 "[%s]: cannot transform '%s' -> '%s'; not excluding any points",
337 zone_name_.c_str(), global_frame_id_.c_str(), base_frame_id_.c_str());
341 tf_zone_to_base = tf_global_to_base * tf_zone_to_global;
345 std::string ExclusionZone::getName()
const
350 bool ExclusionZone::getEnabled()
const
352 std::lock_guard<std::mutex> lock(mutex_);
356 void ExclusionZone::activate()
359 zone_pub_->on_activate();
363 void ExclusionZone::deactivate()
366 zone_pub_->on_deactivate();
370 void ExclusionZone::publish()
const
372 std::lock_guard<std::mutex> lock(mutex_);
373 if (!zone_pub_ || !enabled_) {
381 tf2::Transform tf_zone_to_base;
382 if (!getZoneToBaseTransform(node_clock_->now(), tf_zone_to_base)) {
386 const std::vector<Point> & vertices = is_circle_ ? circleToPolygon(radius_) : poly_;
387 std::vector<Point> vertices_base;
388 transformPolygonPoints(tf_zone_to_base, vertices, vertices_base);
390 auto msg = std::make_unique<geometry_msgs::msg::PolygonStamped>();
391 msg->header.frame_id = base_frame_id_;
392 msg->header.stamp = node_clock_->now();
393 for (
const Point & v : vertices_base) {
394 geometry_msgs::msg::Point32 p;
395 p.x =
static_cast<float>(v.x);
396 p.y =
static_cast<float>(v.y);
397 msg->polygon.points.push_back(p);
400 zone_pub_->publish(std::move(msg));
403 rcl_interfaces::msg::SetParametersResult ExclusionZone::validateParameterUpdatesCallback(
404 const std::vector<rclcpp::Parameter> & parameters)
406 rcl_interfaces::msg::SetParametersResult result;
407 result.successful =
true;
408 for (
const auto & parameter : parameters) {
409 const auto & param_name = parameter.get_name();
410 if (param_name.find(zone_name_ +
".") != 0) {
413 if (param_name == zone_name_ +
".radius" && is_circle_ &&
414 parameter.get_type() == rclcpp::ParameterType::PARAMETER_DOUBLE &&
415 parameter.as_double() <= 0.0)
417 result.successful =
false;
418 result.reason =
"radius must be > 0";
419 }
else if (param_name == zone_name_ +
".frame_hold_timeout" &&
420 parameter.get_type() == rclcpp::ParameterType::PARAMETER_DOUBLE &&
421 parameter.as_double() < 0.0)
423 result.successful =
false;
424 result.reason =
"frame_hold_timeout must be >= 0";
425 }
else if (param_name == zone_name_ +
".points" && !is_circle_ &&
426 parameter.get_type() == rclcpp::ParameterType::PARAMETER_STRING)
430 std::vector<Point> parsed;
432 if (!parsePolygonPoints(parameter.as_string(), 3, parsed, error)) {
433 result.successful =
false;
434 result.reason = error;
441 void ExclusionZone::updateParametersCallback(
442 const std::vector<rclcpp::Parameter> & parameters)
444 std::lock_guard<std::mutex> lock(mutex_);
445 for (
const auto & parameter : parameters) {
446 const auto & param_name = parameter.get_name();
447 if (param_name.find(zone_name_ +
".") != 0) {
450 if (param_name == zone_name_ +
".enabled" &&
451 parameter.get_type() == rclcpp::ParameterType::PARAMETER_BOOL)
453 enabled_ = parameter.as_bool();
454 }
else if (param_name == zone_name_ +
".radius" && is_circle_ &&
455 parameter.get_type() == rclcpp::ParameterType::PARAMETER_DOUBLE)
457 radius_ = parameter.as_double();
458 radius_squared_ = radius_ * radius_;
459 }
else if (param_name == zone_name_ +
".min_height" &&
460 parameter.get_type() == rclcpp::ParameterType::PARAMETER_DOUBLE)
462 min_height_ = parameter.as_double();
463 }
else if (param_name == zone_name_ +
".max_height" &&
464 parameter.get_type() == rclcpp::ParameterType::PARAMETER_DOUBLE)
466 max_height_ = parameter.as_double();
467 }
else if (param_name == zone_name_ +
".frame_hold_timeout" &&
468 parameter.get_type() == rclcpp::ParameterType::PARAMETER_DOUBLE)
470 frame_hold_timeout_ = parameter.as_double();
471 }
else if (param_name == zone_name_ +
".points" && !is_circle_ &&
472 parameter.get_type() == rclcpp::ParameterType::PARAMETER_STRING)
476 std::vector<Point> parsed;
478 if (parsePolygonPoints(parameter.as_string(), 3, parsed, error)) {
double min_height_
Lower bound of the height band (base-frame z) a point must be within to be excluded.
void apply(const rclcpp::Time &curr_time, std::vector< Point > &data) const
Removes from data all points that fall inside the (enabled) zone. No-op when the zone is disabled....
rclcpp::Logger logger_
Collision monitor node logger.
bool enabled_
Whether the zone is currently active.
double radius_
Circle radius (for circle type)
double radius_squared_
radius squared, cached
~ExclusionZone()
ExclusionZone destructor.
bool getParameters()
Reads ROS parameters for the zone.
ExclusionZone(const nav2::LifecycleNode::WeakPtr &node, const std::string &zone_name, const nav2::TransformBuffer::SharedPtr tf_buffer, const std::string &base_frame_id, const std::string &global_frame_id, const tf2::Duration &transform_tolerance, const bool base_shift_correction)
ExclusionZone constructor.
bool is_circle_
Whether the zone shape is a circle (otherwise polygon)
double frame_hold_timeout_
Extra time (s) beyond the transform tolerance that a stale zone-frame pose may keep being used before...
bool getZoneToBaseTransform(const rclcpp::Time &curr_time, tf2::Transform &tf_zone_to_base) const
Resolve the zone-frame -> base-frame transform for this cycle.
std::vector< Point > poly_
Zone polygon vertices, expressed in frame_id_ (for polygon type)
bool visualize_
Whether to publish the zone footprint for visualization.
std::string zone_name_
Name of the zone.
bool configure()
Reads ROS parameters and configures the zone.
std::string frame_id_
Frame the zone shape is anchored to (tracked via TF). Defaults to base_frame_id_.
std::string base_frame_id_
Robot base frame ID.
nav2::LifecycleNode::WeakPtr node_
Collision Monitor node.
rclcpp::Clock::SharedPtr node_clock_
Node clock (for throttled logging and message stamps)
void updateParametersCallback(const std::vector< rclcpp::Parameter > ¶meters)
Apply parameter updates after validation (dynamic reconfigure)
std::mutex mutex_
Dynamic parameters handlers.
double max_height_
Upper bound of the height band (base-frame z) a point must be within to be excluded.
rcl_interfaces::msg::SetParametersResult validateParameterUpdatesCallback(const std::vector< rclcpp::Parameter > ¶meters)
Validate incoming parameter updates before applying them.
nav2::Publisher< geometry_msgs::msg::PolygonStamped >::SharedPtr zone_pub_
Zone footprint publisher.
Point with 2D collision-check coordinates and optional z from the source.