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"};
104 node->declare_or_get_parameter(
"transform_staleness_threshold", 1.0);
114 if (desc.type ==
"circle") {
119 logger_,
"[%s]: circle exclusion zone requires a positive 'radius'",
124 }
else if (desc.type ==
"polygon") {
126 if (desc.points.size() < 3) {
128 logger_,
"[%s]: polygon exclusion zone requires at least 3 points, got %zu",
133 poly_.reserve(desc.points.size());
134 for (
const auto & p : desc.points) {
136 pt.x =
static_cast<double>(p.x);
137 pt.y =
static_cast<double>(p.y);
142 logger_,
"[%s]: unknown exclusion zone type: %s",
148 zone_pub_ = node->create_publisher<geometry_msgs::msg::PolygonStamped>(
157 auto node =
node_.lock();
159 throw std::runtime_error{
"Failed to lock node"};
164 const double default_transform_staleness_threshold =
165 node->declare_or_get_parameter(
"transform_staleness_threshold", 1.0);
168 zone_name_ +
".transform_staleness_threshold", default_transform_staleness_threshold);
171 frame_id_ = node->declare_or_get_parameter(
179 zone_name_ +
".min_height", -std::numeric_limits<double>::max());
181 zone_name_ +
".max_height", std::numeric_limits<double>::max());
183 const std::string type = node->declare_or_get_parameter(
184 zone_name_ +
".type", std::string(
"polygon"));
186 if (type ==
"circle") {
191 logger_,
"[%s]: circle exclusion zone requires a positive 'radius'",
196 }
else if (type ==
"polygon") {
200 const std::string points_str = node->declare_or_get_parameter(
203 if (!parsePolygonPoints(points_str, 3,
poly_, error)) {
210 logger_,
"[%s]: unknown exclusion zone type: %s",
220 std::lock_guard<std::mutex> lock(
mutex_);
229 tf2::Transform tf_zone_to_base;
236 const tf2::Vector3 center = tf_zone_to_base.getOrigin();
237 const double cx = center.x();
238 const double cy = center.y();
241 data.begin(), data.end(),
242 [&](
const Point & p) {
243 if (p.z < min_height_ || p.z > max_height_) {
246 const double dx = p.x - cx;
247 const double dy = p.y - cy;
253 std::vector<Point> poly_base;
254 transformPolygonPoints(tf_zone_to_base, poly_, poly_base);
257 data.begin(), data.end(),
258 [&](
const Point & p) {
259 if (p.z < min_height_ || p.z > max_height_) {
262 return nav2_util::geometry_utils::isPointInsidePolygon(p.x, p.y, poly_base);
268 bool ExclusionZone::getZoneToBaseTransform(
269 const rclcpp::Time & curr_time, tf2::Transform & tf_zone_to_base)
const
272 if (frame_id_ == base_frame_id_) {
273 tf_zone_to_base.setIdentity();
281 const tf2::Duration non_blocking = tf2::Duration::zero();
285 tf2::Transform tf_zone_to_global;
286 tf_zone_to_global.setIdentity();
287 if (frame_id_ != global_frame_id_) {
288 geometry_msgs::msg::TransformStamped zone_to_global_msg;
289 if (!nav2_util::lookupTransformWithStalenessCheck(
290 *tf_buffer_, global_frame_id_, frame_id_, curr_time,
291 transform_staleness_threshold_, zone_to_global_msg))
297 tf2::fromMsg(zone_to_global_msg.transform, tf_zone_to_global);
301 tf2::Transform tf_global_to_base;
303 if (base_shift_correction_) {
304 got_base = nav2_util::getTransform(
305 global_frame_id_, curr_time, base_frame_id_, curr_time, global_frame_id_,
306 non_blocking, tf_buffer_, tf_global_to_base);
308 geometry_msgs::msg::TransformStamped global_to_base_msg;
309 got_base = nav2_util::lookupTransformWithStalenessCheck(
310 *tf_buffer_, base_frame_id_, global_frame_id_, curr_time,
311 base_transform_staleness_threshold_, global_to_base_msg);
313 tf2::fromMsg(global_to_base_msg.transform, tf_global_to_base);
317 RCLCPP_WARN_THROTTLE(
318 logger_, *node_clock_, 2000,
319 "[%s]: cannot transform '%s' -> '%s'; not excluding any points",
320 zone_name_.c_str(), global_frame_id_.c_str(), base_frame_id_.c_str());
324 tf_zone_to_base = tf_global_to_base * tf_zone_to_global;
328 std::string ExclusionZone::getName()
const
333 bool ExclusionZone::getEnabled()
const
335 std::lock_guard<std::mutex> lock(mutex_);
339 void ExclusionZone::activate()
342 zone_pub_->on_activate();
346 void ExclusionZone::deactivate()
349 zone_pub_->on_deactivate();
353 void ExclusionZone::publish()
const
355 std::lock_guard<std::mutex> lock(mutex_);
356 if (!zone_pub_ || !enabled_) {
364 tf2::Transform tf_zone_to_base;
365 if (!getZoneToBaseTransform(node_clock_->now(), tf_zone_to_base)) {
369 const std::vector<Point> & vertices = is_circle_ ? circleToPolygon(radius_) : poly_;
370 std::vector<Point> vertices_base;
371 transformPolygonPoints(tf_zone_to_base, vertices, vertices_base);
373 auto msg = std::make_unique<geometry_msgs::msg::PolygonStamped>();
374 msg->header.frame_id = base_frame_id_;
375 msg->header.stamp = node_clock_->now();
376 for (
const Point & v : vertices_base) {
377 geometry_msgs::msg::Point32 p;
378 p.x =
static_cast<float>(v.x);
379 p.y =
static_cast<float>(v.y);
380 msg->polygon.points.push_back(p);
383 zone_pub_->publish(std::move(msg));
386 rcl_interfaces::msg::SetParametersResult ExclusionZone::validateParameterUpdatesCallback(
387 const std::vector<rclcpp::Parameter> & parameters)
389 rcl_interfaces::msg::SetParametersResult result;
390 result.successful =
true;
391 for (
const auto & parameter : parameters) {
392 const auto & param_name = parameter.get_name();
393 if (param_name.find(zone_name_ +
".") != 0) {
396 if (param_name == zone_name_ +
".radius" && is_circle_ &&
397 parameter.get_type() == rclcpp::ParameterType::PARAMETER_DOUBLE &&
398 parameter.as_double() <= 0.0)
400 result.successful =
false;
401 result.reason =
"radius must be > 0";
402 }
else if (param_name == zone_name_ +
".transform_staleness_threshold" &&
403 parameter.get_type() == rclcpp::ParameterType::PARAMETER_DOUBLE &&
404 parameter.as_double() < 0.0)
406 result.successful =
false;
407 result.reason =
"transform_staleness_threshold must be >= 0";
408 }
else if (param_name == zone_name_ +
".points" && !is_circle_ &&
409 parameter.get_type() == rclcpp::ParameterType::PARAMETER_STRING)
413 std::vector<Point> parsed;
415 if (!parsePolygonPoints(parameter.as_string(), 3, parsed, error)) {
416 result.successful =
false;
417 result.reason = error;
424 void ExclusionZone::updateParametersCallback(
425 const std::vector<rclcpp::Parameter> & parameters)
427 std::lock_guard<std::mutex> lock(mutex_);
428 for (
const auto & parameter : parameters) {
429 const auto & param_name = parameter.get_name();
430 if (param_name.find(zone_name_ +
".") != 0) {
433 if (param_name == zone_name_ +
".enabled" &&
434 parameter.get_type() == rclcpp::ParameterType::PARAMETER_BOOL)
436 enabled_ = parameter.as_bool();
437 }
else if (param_name == zone_name_ +
".radius" && is_circle_ &&
438 parameter.get_type() == rclcpp::ParameterType::PARAMETER_DOUBLE)
440 radius_ = parameter.as_double();
441 radius_squared_ = radius_ * radius_;
442 }
else if (param_name == zone_name_ +
".min_height" &&
443 parameter.get_type() == rclcpp::ParameterType::PARAMETER_DOUBLE)
445 min_height_ = parameter.as_double();
446 }
else if (param_name == zone_name_ +
".max_height" &&
447 parameter.get_type() == rclcpp::ParameterType::PARAMETER_DOUBLE)
449 max_height_ = parameter.as_double();
450 }
else if (param_name == zone_name_ +
".transform_staleness_threshold" &&
451 parameter.get_type() == rclcpp::ParameterType::PARAMETER_DOUBLE)
453 transform_staleness_threshold_ = parameter.as_double();
454 }
else if (param_name == zone_name_ +
".points" && !is_circle_ &&
455 parameter.get_type() == rclcpp::ParameterType::PARAMETER_STRING)
459 std::vector<Point> parsed;
461 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.
double base_transform_staleness_threshold_
Maximum age in seconds for the robot's global-frame transform.
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)
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.
double transform_staleness_threshold_
Maximum age in seconds for the zone-frame transform.
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.