coverage_path.cpp
Go to the documentation of this file.
1 #include "coverage_path.h"
2 
11 void analyze_area (vector<geometry_msgs::Point> coords, geometry_msgs::Point& origin, double& rotation, double& width, double& height)
12 {
13  // get coordinates
14  geometry_msgs::Point pl, pb, pr, pt;
15  pl.x = numeric_limits<double>::max();
16  pb.y = numeric_limits<double>::max();
17  pr.x = numeric_limits<double>::min();
18  pt.y = numeric_limits<double>::min();
19  for (auto p : coords) {
20  // left most point
21  if (p.x < pl.x || (p.x == pl.x && p.y < pl.y))
22  pl = p;
23 
24  // bottom most point
25  if (p.y < pb.y)
26  pb = p;
27 
28  // right most point
29  if (p.x > pr.x || (p.x == pr.x && p.y < pr.y))
30  pr = p;
31 
32  // top most point
33  if (p.y > pt.y)
34  pt = p;
35  }
36 
37  // no rotation required
38  if ((pl.x == pb.x && pl.y == pb.y) || (pr.x == pb.x && pr.y == pb.y)) {
39  origin = pl;
40  rotation = 0;
41  width = pr.x - pl.x;
42  height = pt.y - pb.y;
43  }
44 
45  // rotate clockwise
46  else if (pr.y < pl.y) {
47  origin = pb;
48  rotation = -atan2(pr.y - pb.y, pr.x - pb.x);
49  width = hypot(pr.x - pb.x, pr.y - pb.y);
50  height = hypot(pl.x - pb.x, pl.y - pb.y);
51  }
52 
53  // rotate counter clockwise
54  else {
55  origin = pl;
56  rotation = -atan2(pb.y - pl.y, pb.x - pl.x);
57  width = hypot(pl.x - pb.x, pl.y - pb.y);
58  height = hypot(pr.x - pb.x, pr.y - pb.y);
59  }
60 
61  ROS_DEBUG("Origin (%.2f,%.2f)", origin.x, origin.y);
62  ROS_DEBUG("Rotation %.2f", rotation);
63  ROS_DEBUG("Size %.2fx%.2f", width, height);
64 }
65 
71 void rotate (nav_msgs::OccupancyGrid& map, double angle)
72 {
73  ROS_DEBUG("Rotate map by %.2f...", angle);
74 
75  // rotate origin
76  geometry_msgs::Pose origin_new;
77  origin_new.position.x = map.info.origin.position.x*cos(angle) - map.info.origin.position.y*sin(angle);
78  origin_new.position.y = map.info.origin.position.x*sin(angle) + map.info.origin.position.y*cos(angle);
79 
80  // create empty rotated map extra large
81  vector<vector<signed char>> rt;
82  for (int i=0; i<2*map.info.height; ++i) {
83  vector<signed char> row(2*map.info.width, 100);
84  rt.push_back(row);
85  }
86 
87  // rotate map
88  int i_new, j_new, width_new=0, height_new=0;
89  double x, y, x_new, y_new;
90  for (int i=0; i<map.info.height; ++i) {
91  for (int j=0; j<map.info.width; ++j) {
92  // rotate coordinates
93  x = double(j) * map.info.resolution + map.info.origin.position.x;
94  y = double(i) * map.info.resolution + map.info.origin.position.y;
95  x_new = x*cos(angle) - y*sin(angle);
96  y_new = x*sin(angle) + y*cos(angle);
97  j_new = int(round((x_new - origin_new.position.x) / map.info.resolution));
98  i_new = int(round((y_new - origin_new.position.y) / map.info.resolution));
99 
100  // skip negative indexes
101  if (i_new >= rt.size()) {
102  continue;
103  }
104  if (j_new >= rt[i_new].size()) {
105  continue;
106  }
107 
108  // assign grid cell value
109  rt[i_new][j_new] = map.data[i*map.info.width + j];
110 
111  // measure maximum required size
112  if (rt[i_new][j_new] == 0) {
113  if (i_new > height_new)
114  height_new = i_new;
115  if (j_new > width_new)
116  width_new = j_new;
117  }
118  }
119  }
120 
121  // truncate rotated map
122  rt.resize(height_new);
123  for (int i=0; i<rt.size(); ++i)
124  rt[i].resize(width_new);
125 
126  // collapse map to one dimensional vector
127  vector<signed char> rt_col;
128  for (int i=0; i<rt.size(); ++i) {
129  for (int j=0; j<rt[i].size(); ++j) {
130  rt_col.push_back(rt[i][j]);
131  }
132  }
133 
134  // assign map data
135  map.data = rt_col;
136 
137  // update meta data
138  map.info.map_load_time = Time::now();
139  map.info.width = width_new;
140  map.info.height= height_new;
141  map.info.origin = origin_new;
142 }
143 
148 void downsample (nav_msgs::OccupancyGrid& map)
149 {
150  // do not increase resolution
151  if (map.info.resolution >= resolution)
152  return;
153 
154  // reduction factor
155  int f = int(round(resolution / map.info.resolution));
156 
157  ROS_DEBUG("Downsample map by %d...", f);
158 
159  // downsample map data
160  vector<signed char> lr;
161  for (int i=0; i+f<map.info.height; i+=f) {
162  for (int j=0; j+f<map.info.width; j+=f) {
163  // count frequency of map data values
164  vector<unsigned int> values(256, 0);
165  for (int m=i; m<i+f; ++m) {
166  for (int n=j; n<j+f; ++n) {
167  values[map.data[m*map.info.width + n]]++;
168  }
169  }
170 
171  // choose value with highest frequency
172  unsigned char value = 0;
173  unsigned int freq = 0;
174  for (int k=0; k<values.size(); ++k) {
175  if (values[k] > freq) {
176  value = k;
177  freq = values[k];
178  }
179  }
180 
181  // push back most seen value
182  lr.push_back(value);
183  }
184  }
185  map.data = lr;
186 
187  // update meta data
188  map.info.map_load_time = Time::now();
189  map.info.resolution = resolution;
190  map.info.width = int(floor(double(map.info.width) / double(f)));
191  map.info.height = int(floor(double(map.info.height) / double(f)));
192 
193  // remove rows with obstacles only
194  for (int i=0; i<map.info.height; ++i) {
195  // count number of occupied cells in a row
196  int obst = 0;
197  for (int j=0; j<map.info.width; ++j) {
198  if (map.data[i*map.info.width + j] == 100) {
199  ++obst;
200  }
201  }
202 
203  // remove row
204  if (obst == map.info.width) {
205  // delete grid cells
206  map.data.erase(map.data.begin() + i*map.info.width, map.data.begin() + (i+1)*map.info.width);
207 
208  // update meta data
209  map.info.map_load_time = Time::now();
210  --map.info.height;
211  map.info.origin.position.y += map.info.resolution;
212 
213  // stay in current row
214  --i;
215  }
216  }
217 }
218 
224 bool generate_path (geometry_msgs::Point start)
225 {
226  ROS_DEBUG("Starting at (%.2f,%.2f)", start.x, start.y);
227 
228  // get coordinates of area to cover
229  vector<geometry_msgs::Point> coords;
230  cpswarm_msgs::GetPoints get_coords;
231  if (area_getter.call(get_coords))
232  coords = get_coords.response.points;
233  else {
234  ROS_ERROR("Failed to get area coordinates!");
235  return false;
236  }
237 
238  // get properties of area to cover
239  geometry_msgs::Point origin;
240  double rotation, width, height;
241  analyze_area(coords, origin, rotation, width, height);
242 
243  // original map still needs rotation and downsampling
244  if (divide_area == false) {
245  // rotate map
246  rotate(area, rotation);
247 
248  // downsample resolution
249  if (area.info.resolution < resolution) {
250  downsample(area);
251  }
252  }
253 
254  ROS_INFO("Generate new coverage path...");
255 
256  // construct minimum spanning tree
257  ROS_DEBUG("Construct minimum-spanning-tree...");
259  tree.construct();
260 
261  // visualize path
262  if (visualize)
264 
265  // generate path
266  ROS_DEBUG("Generate coverage path...");
268  path.initialize_map(origin, rotation, width, height);
270  if (path.generate_path(start) == false)
271  return false;
272  if (turning_points)
273  path.reduce();
274 
275  // visualize path
276  if (visualize)
278 
279  reconfigure = false;
280 
281  return true;
282 }
283 
290 bool get_path (nav_msgs::GetPlan::Request &req, nav_msgs::GetPlan::Response &res)
291 {
292  // compute new path if area map has changed
293  if (reconfigure) {
294  if (generate_path(req.start.pose.position) == false)
295  return false;
296  }
297 
298  // return coverage path
299  res.plan = path.get_path();
300 
301  return true;
302 }
303 
310 bool get_waypoint (cpswarm_msgs::GetWaypoint::Request &req, cpswarm_msgs::GetWaypoint::Response &res)
311 {
312  // compute new path if area map has changed
313  if (reconfigure) {
314  if (generate_path(req.position) == false)
315  return false;
316  }
317 
318  // return waypoint
319  res.point = path.get_waypoint(req.position, req.tolerance);
320  res.valid = path.valid();
321 
322  // visualize waypoint
323  if (visualize) {
324  geometry_msgs::PointStamped wp;
325  wp.header.stamp = Time::now();
326  wp.header.frame_id = "local_origin_ned";
327  wp.point = res.point;
328  wp_publisher.publish(wp);
329  }
330 
331  return true;
332 }
333 
338 void area_callback (const nav_msgs::OccupancyGrid::ConstPtr& msg)
339 {
340  area = *msg;
341  map_valid = true;
342  reconfigure = true;
343 }
344 
349 void swarm_state_callback (const cpswarm_msgs::ArrayOfStates::ConstPtr& msg)
350 {
351  // update cps uuids
352  for (auto cps : msg->states) {
353  // only consider cpss in the same behavior states
354  if (find(behaviors.begin(), behaviors.end(), cps.state) == behaviors.end())
355  continue;
356 
357  // index of cps in map
358  auto idx = swarm.find(cps.swarmio.node);
359 
360  // add new cps
361  if (idx == swarm.end()) {
362  swarm.emplace(cps.swarmio.node, Time::now());
363 
364  ROS_DEBUG("New CPS %s", cps.swarmio.node.c_str());
365 
366  // regenerate coverage path
367  reconfigure = true;
368  }
369 
370  // update existing cps
371  else {
372  idx->second = Time::now();
373  }
374  }
375 
376  // remove old cps
377  for (auto cps=swarm.cbegin(); cps!=swarm.cend();) {
378  if (cps->second + Duration(swarm_timeout) < Time::now()) {
379  ROS_DEBUG("Remove CPS %s", cps->first.c_str());
380  swarm.erase(cps++);
381 
382  // regenerate coverage path
383  reconfigure = true;
384  }
385  else {
386  ++cps;
387  }
388  }
389 }
390 
397 int main (int argc, char **argv)
398 {
399  // init ros node
400  init(argc, argv, "coverage_path");
401  NodeHandle nh;
402 
403  // init global variables
404  reconfigure = true;
405 
406  // read parameters
407  double loop_rate;
408  nh.param(this_node::getName() + "/loop_rate", loop_rate, 1.5);
409  int queue_size;
410  nh.param(this_node::getName() + "/queue_size", queue_size, 1);
411  nh.param(this_node::getName() + "/resolution", resolution, 1.0);
412  nh.param(this_node::getName() + "/visualize", visualize, false);
413  nh.param(this_node::getName() + "/divide_area", divide_area, false);
414  nh.param(this_node::getName() + "/vertical", vertical, false);
415  nh.param(this_node::getName() + "/turning_points", turning_points, false);
416  nh.param(this_node::getName() + "/swarm_path", swarm_path, false);
417 
418  // publishers, subscribers, and service clients
419  if (visualize) {
420  path_publisher = nh.advertise<nav_msgs::Path>("coverage_path/path", queue_size, true);
421  wp_publisher = nh.advertise<geometry_msgs::PointStamped>("coverage_path/waypoint", queue_size, true);
422  mst_publisher = nh.advertise<geometry_msgs::PoseArray>("coverage_path/mst", queue_size, true);
423  }
424  if (divide_area)
425  map_subscriber = nh.subscribe("area/assigned", queue_size, area_callback);
426  else
427  map_subscriber = nh.subscribe("area/map", queue_size, area_callback);
428  if (swarm_path) {
429  nh.param(this_node::getName() + "/swarm_timeout", swarm_timeout, 5.0);
430  nh.getParam(this_node::getName() + "/states", behaviors);
431  swarm_sub = nh.subscribe("swarm_state", queue_size, swarm_state_callback);
432  }
433  area_getter = nh.serviceClient<cpswarm_msgs::GetPoints>("area/get_area");
435 
436  // init loop rate
437  Rate rate(loop_rate);
438 
439  // initialize flags
440  map_valid = false;
441 
442  // init map
443  while (ok() && map_valid == false) {
444  ROS_DEBUG_ONCE("Waiting for grid map...");
445  rate.sleep();
446  spinOnce();
447  }
448 
449  // provide coverage path services
450  ServiceServer path_service = nh.advertiseService("coverage_path/path", get_path);
451  ServiceServer wp_service = nh.advertiseService("coverage_path/waypoint", get_waypoint);
452  spin();
453 
454  return 0;
455 }
bool get_path(nav_msgs::GetPlan::Request &req, nav_msgs::GetPlan::Response &res)
Callback function to get the coverage path.
ServiceClient serviceClient(const std::string &service_name, bool persistent=false, const M_string &header_values=M_string())
void initialize_map(geometry_msgs::Point origin, double rotation, double width, double height)
Initialize properties of the area to be covered.
Definition: mst_path.cpp:201
bool get_waypoint(cpswarm_msgs::GetWaypoint::Request &req, cpswarm_msgs::GetWaypoint::Response &res)
Callback function to get the current waypoint of the path.
nav_msgs::OccupancyGrid area
The grid map representing the environment to be covered.
Definition: coverage_path.h:55
ROSCPP_DECL void start()
void publish(const boost::shared_ptr< M > &message) const
void construct()
Generate the MST using Kruskal&#39;s algorithm.
f
Subscriber swarm_sub
Subscriber to get information about other CPSs in the swarm.
Definition: coverage_path.h:45
std::vector< double > values
bool generate_path(geometry_msgs::Point start)
Generate an optimal coverage path for a given area.
Subscriber subscribe(const std::string &topic, uint32_t queue_size, void(T::*fp)(M), T *obj, const TransportHints &transport_hints=TransportHints())
void init(const M_string &remappings)
void reduce()
Remove waypoints that are within straight line segments of the path. Only keep turning points of the ...
Definition: mst_path.cpp:243
Publisher path_publisher
Publisher to visualize the coverage path.
Definition: coverage_path.h:25
bool call(MReq &req, MRes &res)
bool turning_points
Whether there are only waypoints at turning points of the path or also waypoints regularly spaced on ...
#define ROS_DEBUG_ONCE(...)
bool swarm_path
Whether to regnerate the path when new CPSs join or leave the swarm.
vector< edge > get_mst_edges()
Get the edges of the MST.
ServiceServer advertiseService(const std::string &service, bool(T::*srv_func)(MReq &, MRes &), T *obj)
Publisher mst_publisher
Publisher to visualize the minimum spanning tree.
Definition: coverage_path.h:35
void rotate(nav_msgs::OccupancyGrid &map, double angle)
Rotate an occupancy grid map so the lower boundary is horizontal.
double resolution
The grid map underlying the path planning will be downsampled to this resolution in meter / cell...
Definition: coverage_path.h:80
geometry_msgs::Point get_waypoint(geometry_msgs::Point position, double tolerance)
Get the current waypoint and possibly select next waypoint, if close enough.
Definition: mst_path.cpp:123
ServiceClient area_getter
Service client to get coordinates of the area to cover.
Definition: coverage_path.h:50
bool waitForExistence(ros::Duration timeout=ros::Duration(-1))
double swarm_timeout
The time in seconds communication in the swarm can be delayed at most. Used to wait after an area div...
#define ROS_INFO(...)
bool param(const std::string &param_name, T &param_val, const T &default_val) const
Publisher wp_publisher
Publisher to visualize the current waypoint.
Definition: coverage_path.h:30
void downsample(nav_msgs::OccupancyGrid &map)
Decrease the resolution of a occupancy grid map.
void swarm_state_callback(const cpswarm_msgs::ArrayOfStates::ConstPtr &msg)
Callback function to receive the states of the other CPSs.
ROSCPP_DECL bool ok()
Publisher advertise(const std::string &topic, uint32_t queue_size, bool latch=false)
ROSCPP_DECL void spin()
Subscriber map_subscriber
Service client to get the assigned area.
Definition: coverage_path.h:40
bool vertical
Whether the sweeping pattern is vertical or horizontal.
Definition: coverage_path.h:95
void analyze_area(vector< geometry_msgs::Point > coords, geometry_msgs::Point &origin, double &rotation, double &width, double &height)
Determine properties of an area.
vector< string > behaviors
The behavior states in which CPSs are considered part of the swarm.
void initialize_graph(nav_msgs::OccupancyGrid gridmap, bool vertical=false, bool connect4=true)
Initialize the internal tree structure from a given grid map.
bool visualize
Whether to publish the coverage path on a topic for visualization.
Definition: coverage_path.h:85
bool sleep()
spanning_tree tree
The minimum-spanning-tree (MST) that defines the coverage path.
Definition: coverage_path.h:65
mst_path path
The coverage path.
Definition: coverage_path.h:70
geometry_msgs::PoseArray get_tree()
Get the generated MST for visualization.
bool reconfigure
Whether the swarm configuration has changed which requires a replanning of the path.
bool getParam(const std::string &key, std::string &s) const
map< string, Time > swarm
The UUIDs of the other swarm members.
Definition: coverage_path.h:75
void initialize_graph(nav_msgs::OccupancyGrid gridmap, bool vertical=false, bool connect4=true)
Initialize the internal graph structure that represents the area division.
Definition: mst_path.cpp:139
bool map_valid
Whether a valid grid map has been received.
Definition: coverage_path.h:60
void area_callback(const nav_msgs::OccupancyGrid::ConstPtr &msg)
Callback function to receive the grid map.
bool valid()
Check whether the current waypoint index is valid.
Definition: mst_path.cpp:264
void initialize_tree(vector< edge > mst)
Remove edges of the graph that overlap with the tree.
Definition: mst_path.cpp:216
ROSCPP_DECL void spinOnce()
bool divide_area
Whether to divide the area among the CPSs before generating the path or to generate the path on the c...
Definition: coverage_path.h:90
#define ROS_ERROR(...)
int main(int argc, char **argv)
A ROS node that computes the optimal paths for area coverage with a swarm of CPSs.
#define ROS_DEBUG(...)
nav_msgs::Path get_path()
Get the complete path.
Definition: mst_path.cpp:101
bool generate_path(geometry_msgs::Point start)
Generate all way points for the path.
Definition: mst_path.cpp:7


coverage_path
Author(s): Micha Sende
autogenerated on Tue Jan 19 2021 03:30:03