area_division.cpp
Go to the documentation of this file.
1 #include "area_division.h"
2 
6 void to_sync ()
7 {
8  state = SYNC;
9  ROS_DEBUG("Start synchronizing...");
10 
11  // reset information about swarm
12  swarm_pose.clear();
13 
14  // stop moving
15  geometry_msgs::PoseStamped goal_pose;
16  goal_pose.header.stamp = Time::now();
17  goal_pose.pose = pose;
18  pos_pub.publish(goal_pose);
19 
20  // start of synchronization time window
21  sync_start = Time::now();
22 }
23 
28 void behavior_state_callback (const cpswarm_msgs::StateEvent::ConstPtr& msg)
29 {
30  // store new state in class variables
31  behavior = msg->state;
32 
33  // valid state received
34  if (msg->header.stamp.isValid())
35  behavior_valid = true;
36 
37  // cps switched to a behavior state that requires area division
38  if (state == IDLE && find(behaviors.begin(), behaviors.end(), behavior) != behaviors.end())
39  state = INIT;
40 
41  // cps switched to a behavior state that does not require area division
42  if (state == ACTIVE && find(behaviors.begin(), behaviors.end(), behavior) == behaviors.end())
43  state = DEINIT;
44 }
45 
50 void division_callback (const cpswarm_msgs::AreaDivisionEvent::ConstPtr& msg)
51 {
52  // only divide area if active
53  if (state == ACTIVE)
54  to_sync();
55 
56  // only synchronize if ready
57  if (state != SYNC)
58  return;
59 
60  // check if cps is already known
61  auto idx = swarm_pose.find(msg->swarmio.node);
62 
63  // add new cps
64  if (idx == swarm_pose.end()) {
65  ROS_DEBUG("Add CPS %s", msg->swarmio.node.c_str());
66 
67  // add cps
68  swarm_pose.emplace(msg->swarmio.node, msg->pose);
69  }
70 
71  // update existing cps
72  else {
73  ROS_DEBUG("Update CPS %s", msg->swarmio.node.c_str());
74 
75  // update cps
76  idx->second.header.stamp = Time::now();
77  idx->second = msg->pose;
78  }
79 }
80 
85 void map_callback (const nav_msgs::OccupancyGrid::ConstPtr& msg)
86 {
87  // store map in class variable
88  global_map = *msg;
89 
90  // valid map received
91  map_valid = true;
92 
93  // divide area
94  if (state == ACTIVE)
95  to_sync();
96 }
97 
102 void pose_callback (const geometry_msgs::PoseStamped::ConstPtr& msg)
103 {
104  // store new position and orientation in class variables
105  pose = msg->pose;
106 
107  // valid pose received
108  if (msg->header.stamp.isValid())
109  pose_valid = true;
110 }
111 
116 void swarm_state_callback (const cpswarm_msgs::ArrayOfStates::ConstPtr& msg)
117 {
118  // update cps uuids
119  for (auto cps : msg->states) {
120  // only consider cpss in the same behavior states
121  if (find(behaviors.begin(), behaviors.end(), cps.state) == behaviors.end())
122  continue;
123 
124  // index of cps in map
125  auto idx = swarm.find(cps.swarmio.node);
126 
127  // add new cps
128  if (idx == swarm.end()) {
129  swarm.emplace(cps.swarmio.node, Time::now());
130 
131  ROS_DEBUG("New CPS %s", cps.swarmio.node.c_str());
132  }
133 
134  // update existing cps
135  else {
136  idx->second = Time::now();
137  }
138  }
139 
140  // remove old cps
141  for (auto cps=swarm.cbegin(); cps!=swarm.cend();) {
142  if (cps->second + Duration(swarm_timeout) < Time::now()) {
143  ROS_DEBUG("Remove CPS %s", cps->first.c_str());
144  swarm.erase(cps++);
145 
146  // divide area
147  if (state == ACTIVE)
148  to_sync();
149  }
150  else {
151  ++cps;
152  }
153  }
154 
155  swarm_valid = true;
156 }
157 
162 void uuid_callback (const swarmros::String::ConstPtr& msg)
163 {
164  uuid = msg->value;
165 }
166 
170 void init ()
171 {
172  NodeHandle nh;
173 
174  // initialize flags
175  uuid = "";
176  pose_valid = false;
177  swarm_valid = false;
178  map_valid = false;
179 
180  // publishers, subscribers, and service clients
181  int queue_size;
182  nh.param(this_node::getName() + "/queue_size", queue_size, 10);
183  uuid_sub = nh.subscribe("bridge/uuid", queue_size, uuid_callback);
184  pose_sub = nh.subscribe("pos_provider/pose", queue_size, pose_callback);
185  swarm_sub = nh.subscribe("swarm_state", queue_size, swarm_state_callback);
186  map_sub = nh.subscribe("area/map", queue_size, map_callback); // TODO: use explored/merged map
187  division_sub = nh.subscribe("bridge/events/area_division", queue_size, division_callback);
188  pos_pub = nh.advertise<geometry_msgs::PoseStamped>("pos_controller/goal_position", queue_size, true);
189  swarm_pub = nh.advertise<cpswarm_msgs::AreaDivisionEvent>("area_division", queue_size, true);
190  area_pub = nh.advertise<nav_msgs::OccupancyGrid>("area/assigned", queue_size, true);
191  if (visualize) {
192  map_rot_pub = nh.advertise<nav_msgs::OccupancyGrid>("area/rotated", queue_size, true);
193  map_ds_pub = nh.advertise<nav_msgs::OccupancyGrid>("area/downsampled", queue_size, true);
194  }
195  rotater_cli = nh.serviceClient<cpswarm_msgs::GetDouble>("area/get_rotation");
196  rotater_cli.waitForExistence();
197 
198  // init uuid
199  while (ok() && uuid == "") {
200  ROS_DEBUG_ONCE("Waiting for UUID...");
201  rate->sleep();
202  spinOnce();
203  }
204 
205  // init position
206  while (ok() && pose_valid == false) {
207  ROS_DEBUG_ONCE("Waiting for valid position information...");
208  rate->sleep();
209  spinOnce();
210  }
211 
212  // init swarm
213  while (ok() && swarm_valid == false) {
214  ROS_DEBUG_ONCE("Waiting for valid swarm information...");
215  rate->sleep();
216  spinOnce();
217  }
218 
219  // init map
220  while (ok() && map_valid == false) {
221  ROS_DEBUG_ONCE("Waiting for valid grid map...");
222  rate->sleep();
223  spinOnce();
224  }
225 
226  // create area division object
227  division = new area_division();
228 
229  // start area division
230  to_sync();
231 }
232 
236 void deinit ()
237 {
238  // shutdown ros communication
239  uuid_sub.shutdown();
240  pose_sub.shutdown();
241  swarm_sub.shutdown();
242  map_sub.shutdown();
243  division_sub.shutdown();
244  pos_pub.shutdown();
245  swarm_pub.shutdown();
246  area_pub.shutdown();
247  map_rot_pub.shutdown();
248  map_ds_pub.shutdown();
249  rotater_cli.shutdown();
250 
251  // destroy optimizer
252  delete division;
253 
254  state = IDLE;
255 }
256 
260 void sync ()
261 {
262  // publish synchronization event for some time
263  if (sync_start + Duration(swarm_timeout) > Time::now()) {
264  cpswarm_msgs::AreaDivisionEvent event;
265  event.header.stamp = Time::now();
266  event.swarmio.name = "area_division";
267  geometry_msgs::PoseStamped ps;
268  ps.header.frame_id = "local_origin_ned";
269  ps.pose = pose;
270  event.pose = ps;
271  swarm_pub.publish(event);
272  }
273 
274  // divide area
275  else
276  state = DIVIDE;
277 }
278 
285 geometry_msgs::Point rotate (geometry_msgs::Point point, double angle)
286 {
287  // no rotation required
288  if (angle < 0.0001)
289  return point;
290 
291  // return point around origin
292  geometry_msgs::Point rotated;
293  rotated.x = point.x*cos(angle) - point.y*sin(angle);
294  rotated.y = point.x*sin(angle) + point.y*cos(angle);
295  return rotated;
296 }
297 
303 double rotate (nav_msgs::OccupancyGrid& map)
304 {
305  // get angle
306  cpswarm_msgs::GetDouble angle;
307  if (rotater_cli.call(angle) == false) {
308  ROS_DEBUG("Not rotating map!");
309  return 0.0;
310  }
311  double a = angle.response.value;
312 
313  ROS_DEBUG("Rotate map by %.2f...", a);
314 
315  // rotate origin
316  geometry_msgs::Pose origin_new;
317  origin_new.position.x = map.info.origin.position.x*cos(a) - map.info.origin.position.y*sin(a);
318  origin_new.position.y = map.info.origin.position.x*sin(a) + map.info.origin.position.y*cos(a);
319 
320  // create empty rotated map extra large
321  vector<vector<signed char>> rt;
322  for (int i=0; i<2*map.info.height; ++i) {
323  vector<signed char> row(2*map.info.width, 100);
324  rt.push_back(row);
325  }
326 
327  // rotate map
328  int i_new, j_new, width_new=0, height_new=0;
329  double x, y, x_new, y_new;
330  for (int i=0; i<map.info.height; ++i) {
331  for (int j=0; j<map.info.width; ++j) {
332  // rotate coordinates
333  x = double(j) * map.info.resolution + map.info.origin.position.x;
334  y = double(i) * map.info.resolution + map.info.origin.position.y;
335  x_new = x*cos(a) - y*sin(a);
336  y_new = x*sin(a) + y*cos(a);
337  j_new = int(round((x_new - origin_new.position.x) / map.info.resolution));
338  i_new = int(round((y_new - origin_new.position.y) / map.info.resolution));
339 
340  // skip negative indexes
341  if (i_new >= rt.size()) {
342  continue;
343  }
344  if (j_new >= rt[i_new].size()) {
345  continue;
346  }
347 
348  // assign grid cell value
349  rt[i_new][j_new] = map.data[i*map.info.width + j];
350 
351  // measure maximum required size
352  if (rt[i_new][j_new] == 0) {
353  if (i_new > height_new)
354  height_new = i_new;
355  if (j_new > width_new)
356  width_new = j_new;
357  }
358  }
359  }
360 
361  // truncate rotated map
362  rt.resize(height_new);
363  for (int i=0; i<rt.size(); ++i)
364  rt[i].resize(width_new);
365 
366  // collapse map to one dimensional vector
367  vector<signed char> rt_col;
368  for (int i=0; i<rt.size(); ++i) {
369  for (int j=0; j<rt[i].size(); ++j) {
370  rt_col.push_back(rt[i][j]);
371  }
372  }
373 
374  // assign map data
375  map.data = rt_col;
376 
377  // update meta data
378  map.info.map_load_time = Time::now();
379  map.info.width = width_new;
380  map.info.height= height_new;
381  map.info.origin = origin_new;
382 
383  return a;
384 }
385 
390 void translate (nav_msgs::OccupancyGrid& map)
391 {
392  // compute required translation
393  translation.x = round(map.info.origin.position.x) - map.info.origin.position.x;
394  translation.y = round(map.info.origin.position.y) - map.info.origin.position.y;
395  ROS_DEBUG("Translate map by (%.2f,%.2f)...", translation.x, translation.y);
396 
397  // translate origin
398  map.info.origin.position.x += translation.x;
399  map.info.origin.position.y += translation.y;
400 
401  // update meta data
402  map.info.map_load_time = Time::now();
403 }
404 
409 void downsample (nav_msgs::OccupancyGrid& map)
410 {
411  // do not increase resolution
412  if (map.info.resolution >= resolution)
413  return;
414 
415  // reduction factor
416  int f = int(round(resolution / map.info.resolution));
417 
418  ROS_DEBUG("Downsample map by %d...", f);
419 
420  // downsample map data
421  vector<signed char> lr;
422  for (int i=0; i+f<=map.info.height; i+=f) {
423  for (int j=0; j+f<=map.info.width; j+=f) {
424  // count frequency of map data values
425  vector<unsigned int> values(256, 0);
426  for (int m=i; m<i+f; ++m) {
427  for (int n=j; n<j+f; ++n) {
428  values[map.data[m*map.info.width + n]]++;
429  }
430  }
431 
432  // choose value with highest frequency
433  unsigned char value = 0;
434  unsigned int freq = 0;
435  for (int k=0; k<values.size(); ++k) {
436  if (values[k] > freq) {
437  value = k;
438  freq = values[k];
439  }
440  }
441 
442  // push back most seen value
443  lr.push_back(value);
444  }
445  }
446  map.data = lr;
447 
448  // update meta data
449  map.info.map_load_time = Time::now();
450  map.info.resolution = resolution;
451  map.info.width = int(floor(double(map.info.width) / double(f)));
452  map.info.height = int(floor(double(map.info.height) / double(f)));
453 
454  // remove rows with obstacles only
455  for (int i=0; i<map.info.height; ++i) {
456  // count number of occupied cells in a row
457  int obst = 0;
458  for (int j=0; j<map.info.width; ++j) {
459  if (map.data[i*map.info.width + j] == 100) {
460  ++obst;
461  }
462  }
463 
464  // remove row
465  if (obst == map.info.width) {
466  // delete grid cells
467  map.data.erase(map.data.begin() + i*map.info.width, map.data.begin() + (i+1)*map.info.width);
468 
469  // update meta data
470  map.info.map_load_time = Time::now();
471  --map.info.height;
472  map.info.origin.position.y += map.info.resolution;
473 
474  // stay in current row
475  --i;
476  }
477  }
478 }
479 
483 void divide_area ()
484 {
485  // initialize map
486  nav_msgs::OccupancyGrid gridmap = global_map;
487 
488  // rotate map
489  double angle = rotate(gridmap);
490 
491  if (visualize)
492  map_rot_pub.publish(gridmap);
493 
494  // downsample resolution
495  if (gridmap.info.resolution < resolution) {
496  downsample(gridmap);
497  }
498 
499  // shift map
500  translate(gridmap);
501 
502  if (visualize)
503  map_ds_pub.publish(gridmap);
504 
505  // convert swarm pose to grid
506  map<string, vector<int>> swarm_grid;
507  for (auto cps : swarm_pose) {
508  geometry_msgs::Point rotated = rotate(cps.second.pose.position, angle);
509  rotated.x += translation.x;
510  rotated.y += translation.y;
511  vector<int> pos(2);
512  pos[0] = int(round((rotated.x - gridmap.info.origin.position.x) / gridmap.info.resolution));
513  pos[1] = int(round((rotated.y - gridmap.info.origin.position.y) / gridmap.info.resolution));
514  swarm_grid.emplace(cps.first, pos);
515  ROS_DEBUG("Other CPS %s at (%d,%d)", cps.first.c_str(), pos[0], pos[1]);
516  }
517 
518  // add this robot to swarm grid
519  geometry_msgs::Point rotated = rotate(pose.position, angle);
520  rotated.x += translation.x;
521  rotated.y += translation.y;
522  vector<int> pos(2);
523  pos[0] = int(round((rotated.x - gridmap.info.origin.position.x) / gridmap.info.resolution));
524  pos[1] = int(round((rotated.y - gridmap.info.origin.position.y) / gridmap.info.resolution));
525  swarm_grid.emplace(uuid, pos);
526  ROS_DEBUG("Me %s at (%d,%d)", uuid.c_str(), pos[0], pos[1]);
527 
528  // divide area
529  ROS_INFO("Dividing area...");
530  vector<signed char, allocator<signed char>> map = gridmap.data;
531  division->initialize_map((int)gridmap.info.height, (int)gridmap.info.width, map);
532  division->initialize_cps(swarm_grid);
533  division->divide();
534 
535  // publish result
536  area_pub.publish(division->get_grid(gridmap, uuid));
537 
538  // wait a bit to avoid directly redividing again
539  Time sleep_start = Time::now();
540  while (ok() && sleep_start + Duration(swarm_timeout) > Time::now()) {
541  spinOnce();
542  rate->sleep();
543  }
544 
545  // area division done
546  state = ACTIVE;
547 }
548 
555 int main (int argc, char **argv)
556 {
557  // init ros node
558  init(argc, argv, "area_division");
559  NodeHandle nh;
560 
561  // read parameters
562  double loop_rate;
563  nh.param(this_node::getName() + "/loop_rate", loop_rate, 1.5);
564  rate = new Rate(loop_rate);
565  int queue_size;
566  nh.param(this_node::getName() + "/queue_size", queue_size, 10);
567  nh.param(this_node::getName() + "/resolution", resolution, 1.0);
568  nh.param(this_node::getName() + "/swarm_timeout", swarm_timeout, 5.0);
569  nh.param(this_node::getName() + "/visualize", visualize, false);
570  nh.getParam(this_node::getName() + "/states", behaviors);
571 
572  // initially, this cps does not perform area division
573  state = IDLE;
574 
575  // init behavior state
576  behavior_valid = false;
577  Subscriber behavior_state_subscriber = nh.subscribe("state", queue_size, behavior_state_callback);
578  while (ok() && behavior_valid == false) {
579  ROS_DEBUG_ONCE("Waiting for valid behavior information...");
580  rate->sleep();
581  spinOnce();
582  }
583 
584  // check if area division is necessary
585  while (ok()) {
586  spinOnce();
587 
588  switch (state) {
589  // start up this node functionality
590  case INIT:
591  init();
592  break;
593 
594  // synchronize with the other cpss
595  case SYNC:
596  sync();
597  break;
598 
599  // divide area
600  case DIVIDE:
601  divide_area();
602  break;
603 
604  // shutdown this node functionality
605  case DEINIT:
606  deinit();
607  break;
608  }
609 
610  rate->sleep();
611  }
612 
613  // clean up
614  delete rate;
615 
616  return 0;
617 }
Publisher map_ds_pub
Publisher to visualize the downsampled map.
Definition: area_division.h:87
Publisher area_pub
Publisher to visualize the assigned area grid map.
Definition: area_division.h:77
bool visualize
Whether to publish the area division on a topic for visualization.
bool swarm_valid
Whether valid state information has been received from the other swarm members.
A class to divide the environment optimally among multiple cyber physical systems (CPSs)...
f
void translate(nav_msgs::OccupancyGrid &map)
Shift a map to be aligned with the grid, i.e., the origin should be an even number.
void divide_area()
Divide the area of the grid map equally among multiple CPSs.
void uuid_callback(const swarmros::String::ConstPtr &msg)
Callback function to receive the UUID from the communication library.
Publisher map_rot_pub
Publisher to visualize the rotated map.
Definition: area_division.h:82
double swarm_timeout
The time in seconds communication in the swarm can be delayed at most. Used to wait after an area div...
bool pose_valid
Whether a valid position has been received.
void division_callback(const cpswarm_msgs::AreaDivisionEvent::ConstPtr &msg)
Callback function to receive area division requests from other CPSs.
Subscriber division_sub
Subscriber to get area division requests from other CPSs.
Definition: area_division.h:62
bool behavior_valid
Whether a valid state has been received.
geometry_msgs::Pose pose
Current position of the CPS.
#define ROS_DEBUG_ONCE(...)
state_t state
The state of the area division node.
Definition: area_division.h:37
Publisher swarm_pub
Publisher to syncronize with other CPSs.
Definition: area_division.h:72
void pose_callback(const geometry_msgs::PoseStamped::ConstPtr &msg)
Callback function for position updates.
ServiceClient rotater_cli
Service client to get the angle which the area has to be rotated by.
Definition: area_division.h:92
void swarm_state_callback(const cpswarm_msgs::ArrayOfStates::ConstPtr &msg)
Callback function to receive the states of the other CPSs.
Subscriber pose_sub
Subscriber to get CPS position.
Definition: area_division.h:47
Time sync_start
The time at which the synchronization for the area division started.
void initialize_cps(map< string, vector< int >> cpss)
Define the CPS positions.
void divide()
Perform the area division.
area_division * division
The object encapsulating the area division optimization algorithm.
map< string, Time > swarm
The UUIDs of the other swarm members.
nav_msgs::OccupancyGrid global_map
The complete grid map.
#define ROS_INFO(...)
Subscriber map_sub
Subscriber to get grid map.
Definition: area_division.h:57
geometry_msgs::Vector3 translation
The translation by which the area has been shifted.
string uuid
UUID of this CPS.
ROSCPP_DECL bool ok()
void initialize_map(int r, int c, vector< signed char > src)
Define the grid map.
geometry_msgs::Point rotate(geometry_msgs::Point point, double angle)
Rotate a point by a given angle around the origin.
vector< string > behaviors
The behavior states in which area division is active.
double resolution
The grid map underlying the area division will be downsampled to this resolution in meter / cell...
void init()
Initialize this node.
void sync()
Synchronize The CPSs by exchanging an event.
map< string, geometry_msgs::PoseStamped > swarm_pose
The positions of the other swarm members.
int main(int argc, char **argv)
A ROS node that divides the available area among a swarm of CPSs.
Subscriber uuid_sub
Subscriber to get CPS UUID.
Definition: area_division.h:42
Publisher pos_pub
Publisher to stop the CPS.
Definition: area_division.h:67
void behavior_state_callback(const cpswarm_msgs::StateEvent::ConstPtr &msg)
Callback function for behavior state updates.
void deinit()
Shutdown ROS communication.
nav_msgs::OccupancyGrid get_grid(nav_msgs::OccupancyGrid map, string cps)
Get the region assigned to a CPS.
bool map_valid
Whether a valid grid map has been received.
void downsample(nav_msgs::OccupancyGrid &map)
Decrease the resolution of a occupancy grid map.
Subscriber swarm_sub
Subscriber to get information about other CPSs in the swarm.
Definition: area_division.h:52
void to_sync()
Switch to the synchronization state.
string behavior
The current behavior state of this CPS.
ROSCPP_DECL void spinOnce()
Rate * rate
ROS rate object for controlling loop rates.
Definition: area_division.h:97
void map_callback(const nav_msgs::OccupancyGrid::ConstPtr &msg)
Callback function to receive the grid map.
#define ROS_DEBUG(...)


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