UAV Coverage Path Planner
torres_etal_2016.hpp
Go to the documentation of this file.
1 
7 /*
8  * Copyright (c) 2017 Takaki Ueno
9  * Released under the MIT license
10  */
11 
12 #ifndef INCLUDED_torres_etal_2016_hpp_
13 #define INCLUDED_torres_etal_2016_hpp_
14 
15 // cgutil
16 #include <cgutil.hpp>
17 
18 // cpp standard libraries
19 #include <array>
20 #include <string>
21 #include <unordered_map>
22 
23 // Boost
24 #include <boost/range/adaptor/indexed.hpp>
25 #include <boost/range/adaptor/reversed.hpp>
26 
27 // std_msgs
28 #include <std_msgs/Float64.h>
29 
30 // geometry_msgs
31 #include <geometry_msgs/Point32.h>
32 #include <geometry_msgs/Polygon.h>
33 
34 // Service
35 #include "cpp_uav/Torres16.h"
36 
43 struct Direction
44 {
46  geometry_msgs::Point opposedVertex;
47 };
48 
55 inline bool isClockWise(const PointVector& path)
56 {
57  return path.at(0).x < path.at(1).x ? true : false;
58 }
59 
66 {
67  Direction sweepDirection;
68 
69  PointVector convexHull = computeConvexHull(polygon);
70 
71  // Edges of polygon
72  LineSegmentVector edges;
73 
74  // Make a list of edges of polygon
75  for (std::size_t i = 0; i < convexHull.size(); ++i)
76  {
77  LineSegment ar;
78 
79  ar.at(0) = convexHull.at(i);
80 
81  // if vertex is the last one,
82  // that vertex makes an edge whose end is the first vertex
83  if (i == convexHull.size() - 1)
84  {
85  ar.at(1) = convexHull.at(0);
86  }
87  else
88  {
89  ar.at(1) = convexHull.at(i + 1);
90  }
91  edges.push_back(ar);
92  }
93 
94  double optimalDistance = 0;
95 
96  // Calculate line sweep direction
97  // Algorithm 1 in Torres et al, 2016
98  for (const auto& edge : edges | boost::adaptors::indexed())
99  {
100  double edgeMaxDistance = 0;
101  geometry_msgs::Point opposedVertex;
102 
103  for (const geometry_msgs::Point& vertex : convexHull)
104  {
105  // calculateDistance() function returns distance
106  // between given edge and vertex
107  double distance = calculateDistance(edge.value(), vertex);
108 
109  if (distance > edgeMaxDistance)
110  {
111  edgeMaxDistance = distance;
112  opposedVertex = vertex;
113  }
114  }
115 
116  if ((edgeMaxDistance < optimalDistance) or edge.index() == 0)
117  {
118  optimalDistance = edgeMaxDistance;
119  sweepDirection.baseEdge = edge.value();
120  sweepDirection.opposedVertex = opposedVertex;
121  }
122  }
123 
124  return sweepDirection;
125 }
126 
134 PointVector reshapePath(const PointVector& path, double padding)
135 {
136  PointVector zigzagPath;
137 
138  // reshape every traverse
139  for (int i = 0; i < std::round(path.size() / 2); ++i)
140  {
141  // even-numbered traverse
142  if (i % 2 == 0)
143  {
144  try
145  {
146  // in case that the first point of the traverse is located on LEFT side
147  if (path.at(2 * i).x < path.at(2 * i + 1).x)
148  {
149  geometry_msgs::Point p1 = path.at(2 * i);
150  geometry_msgs::Point p2 = path.at(2 * i + 1);
151 
152  // add padding
153  p1.x += padding;
154  p2.x -= padding;
155 
156  // be careful with the order of points
157  zigzagPath.push_back(p1);
158  zigzagPath.push_back(p2);
159  }
160  // in case that the first point of the traverse is located on RIGHT side
161  else
162  {
163  geometry_msgs::Point p1 = path.at(2 * i);
164  geometry_msgs::Point p2 = path.at(2 * i + 1);
165 
166  // add padding
167  p1.x -= padding;
168  p2.x += padding;
169 
170  // be careful with the order of points
171  zigzagPath.push_back(p2);
172  zigzagPath.push_back(p1);
173  }
174  }
175  // in case that the traverse has only one vertex
176  catch (std::out_of_range& ex)
177  {
178  geometry_msgs::Point p = path.at(2 * i);
179  if (isClockWise(path))
180  {
181  // the first vertex of even-numbered traverse of clockwise path is located on RIGHT side of polygon
182  p.x += padding;
183  zigzagPath.push_back(p);
184  }
185  else
186  {
187  // the first vertex of even-numbered traverse of counterclockwise path is located on LEFT side of polygon
188  p.x -= padding;
189  zigzagPath.push_back(p);
190  }
191  ROS_ERROR("%s", ex.what());
192  }
193  }
194  // odd-numbered traverse
195  else
196  {
197  try
198  {
199  // in case that the first point of the traverse is located on RIGHT side
200  if (path.at(2 * i).x > path.at(2 * i + 1).x)
201  {
202  geometry_msgs::Point p1 = path.at(2 * i);
203  geometry_msgs::Point p2 = path.at(2 * i + 1);
204 
205  // add padding
206  p1.x -= padding;
207  p2.x += padding;
208 
209  // be careful with the order of points
210  zigzagPath.push_back(p1);
211  zigzagPath.push_back(p2);
212  }
213  // in case that the first point of the traverse is located on LEFT side
214  else
215  {
216  geometry_msgs::Point p1 = path.at(2 * i);
217  geometry_msgs::Point p2 = path.at(2 * i + 1);
218 
219  // add padding
220  p1.x += padding;
221  p2.x -= padding;
222 
223  // be careful with the order of points
224  zigzagPath.push_back(p2);
225  zigzagPath.push_back(p1);
226  }
227  }
228  // in case that the traverse has only one vertex
229  catch (std::out_of_range& ex)
230  {
231  geometry_msgs::Point p = path.at(2 * i);
232  if (isClockWise(path))
233  {
234  // the first vertex of odd-numbered traverse of clockwise path is located on LEFT side of polygon
235  p.x -= padding;
236  zigzagPath.push_back(p);
237  }
238  else
239  {
240  // the first vertex of odd-numbered traverse of clockwise path is located on RIGHT side of polygon
241  p.x += padding;
242  zigzagPath.push_back(p);
243  }
244  ROS_ERROR("%s", ex.what());
245  }
246  }
247  }
248  return zigzagPath;
249 }
250 
260 bool computeConvexCoverage(const PointVector& polygon, double footprintWidth, double horizontalOverwrap,
261  const Direction& sweepDirection, PointVector& path)
262 {
263  // Unable to make polygon with less than 3 points
264  if (polygon.size() < 3)
265  {
266  return false;
267  }
268 
269  // TODO: Change to configurable
270  const double padding = footprintWidth/2.0;
271 
272  // rotate input polygon so that baseEdge become horizontal
273  double rotationAngle = calculateHorizontalAngle(sweepDirection.baseEdge.front(), sweepDirection.baseEdge.back());
274  PointVector rotatedPolygon = rotatePoints(polygon, -rotationAngle);
275 
276  // find x coordinate of most left and most right point
277  double minX(0), maxX(0);
278  for (const auto& vertex : rotatedPolygon)
279  {
280  if (vertex.x < minX)
281  {
282  minX = vertex.x;
283  }
284  else if (vertex.x > maxX)
285  {
286  maxX = vertex.x;
287  }
288  }
289 
290  double stepWidth = footprintWidth * (1 - horizontalOverwrap);
291 
292  // calculate sweep direction of rotated polygon
293  PointVector dir{ sweepDirection.opposedVertex, sweepDirection.baseEdge.front(), sweepDirection.baseEdge.back() };
294  dir = rotatePoints(dir, -rotationAngle);
295  Direction rotatedDir;
296  rotatedDir.opposedVertex = dir.at(0);
297  rotatedDir.baseEdge.front() = dir.at(1);
298  rotatedDir.baseEdge.back() = dir.at(2);
299 
300  int stepNum = std::ceil(calculateDistance(rotatedDir.baseEdge, rotatedDir.opposedVertex) / stepWidth);
301 
302  LineSegmentVector sweepLines;
303 
304  // generate list of sweep lines which is horizontal against the base edge
305  for (int i = 0; i < stepNum; ++i)
306  {
307  LineSegment ar;
308  geometry_msgs::Point p1, p2;
309  p1.x = minX;
310  p1.y = rotatedDir.baseEdge.at(0).y + (i * stepWidth) + padding;
311  p2.x = maxX;
312  p2.y = rotatedDir.baseEdge.at(1).y + (i * stepWidth) + padding;
313 
314  ar.at(0) = p1;
315  ar.at(1) = p2;
316 
317  sweepLines.push_back(ar);
318  }
319 
320  // Localize intersections of sweeplines and edges of rotated polygon
321  LineSegmentVector rotatedEdges = generateEdgeVector(rotatedPolygon, true);
322 
323  PointVector intersections;
324 
325  for (const auto& sweepLine : sweepLines)
326  {
327  int intersectionCount = 0;
328  for (const auto& edge : rotatedEdges)
329  {
330  if (hasIntersection(sweepLine, edge))
331  {
332  intersections.push_back(localizeIntersection(edge, sweepLine));
333  ++intersectionCount;
334  }
335 
336  // sweep line in optimal path does not have more than 2 intersections
337  if (intersectionCount >= 3)
338  {
339  return false;
340  }
341  }
342  }
343 
344  // sort points by y coordinate in ascending order
345  std::stable_sort(intersections.begin(), intersections.end(),
346  [](const geometry_msgs::Point& p1, const geometry_msgs::Point& p2) { return p1.y < p2.y; });
347 
348  PointVector rotatedPath = reshapePath(intersections, padding);
349 
350  path = rotatePoints(rotatedPath, rotationAngle);
351 
352  if (hasIntersection(generateEdgeVector(polygon, true), generateEdgeVector(path, false)) == true)
353  {
354  return false;
355  }
356 
357  return true;
358 }
359 
368 bool computeConvexCoverage(const PointVector& polygon, double footprintWidth, double horizontalOverwrap,
369  PointVector& path)
370 {
371  Direction sweepDirection = identifyOptimalSweepDir(polygon);
372  return computeConvexCoverage(polygon, footprintWidth, horizontalOverwrap, sweepDirection, path);
373 }
374 
380 double calculatePathLength(const PointVector& path)
381 {
382  if (path.size() < 2)
383  {
384  return 0;
385  }
386 
387  double pathLength = 0;
388  for (int i = 0; i < path.size() - 1; ++i)
389  {
390  pathLength += calculateDistance(path.at(i), path.at(i + 1));
391  }
392  return pathLength;
393 }
394 
401 {
402  for (int i = 0; i < std::round(path.size() / 2); ++i)
403  {
404  // swap the first point and the last point in each sweep line
405  geometry_msgs::Point tmp = path.at(2 * i);
406 
407  path.at(2 * i) = path.at(2 * i + 1);
408  try
409  {
410  path.at(2 * i + 1) = tmp;
411  }
412  catch (std::out_of_range& ex)
413  {
414  ROS_ERROR("%s", ex.what());
415  }
416  }
417  return path;
418 }
419 
426 {
427  PointVector oppositePath;
428 
429  // inversely iterate given points
430  for (int i = path.size() - 1; i >= 0; --i)
431  {
432  oppositePath.push_back(path.at(i));
433  }
434 
435  return oppositePath;
436 }
437 
448  const geometry_msgs::Point& start, const geometry_msgs::Point& end)
449 {
450  // The naming of the following variable follows torres et al. 2016
451  std::unordered_map<int, std::unordered_map<std::string, geometry_msgs::Point>> coverageAlternatives;
452  std::unordered_map<std::string, geometry_msgs::Point> a1, a2, a3, a4;
453 
454  PointVector pathCW = isClockWise(path) ? path : computeCCWPath(path);
455  PointVector pathCCW = isClockWise(path) ? computeCCWPath(path) : path;
456 
457  // a1: clockwise current path
458  a1["SP"] = pathCW.front();
459  a1["EP"] = pathCW.back();
460 
461  // a2: counterclockwise current path
462  a2["SP"] = pathCCW.front();
463  a2["EP"] = pathCCW.back();
464 
465  // a3: clockwise opposite path
466  a3["SP"] = pathCW.back();
467  a3["EP"] = pathCW.front();
468 
469  // a4: counterclockwise opposite path
470  a4["SP"] = pathCCW.back();
471  a4["EP"] = pathCCW.front();
472 
473  coverageAlternatives[1] = a1;
474  coverageAlternatives[2] = a2;
475  coverageAlternatives[3] = a3;
476  coverageAlternatives[4] = a4;
477 
478  bool hasIntersectionCW = hasIntersection(generateEdgeVector(polygon, true), generateEdgeVector(pathCW, false));
479  bool hasIntersectionCCW = hasIntersection(generateEdgeVector(polygon, true), generateEdgeVector(pathCCW, false));
480 
481  double minDistance;
482  int optimalPath;
483 
484  // check which coverage alternative has the shortest path
485  for (const auto& coverage : coverageAlternatives | boost::adaptors::indexed())
486  {
487  // skip calculating length if the path has intersections
488  if ((hasIntersectionCW and coverage.index() % 2 == 0) or (hasIntersectionCCW and coverage.index() % 2 != 0))
489  {
490  continue;
491  }
492 
493  // only length of transition need to be considered because the length of coverage is almost same
494  double distance = calculateDistance(coverage.value().second.at("SP"), start) +
495  calculateDistance(end, coverage.value().second.at("EP"));
496 
497  if (distance < minDistance or coverage.index() == 0)
498  {
499  minDistance = distance;
500  optimalPath = coverage.value().first;
501  }
502  }
503 
504  switch (optimalPath)
505  {
506  case 1:
507  {
508  return pathCW;
509  }
510  case 2:
511  {
512  return pathCCW;
513  }
514  case 3:
515  {
516  return computeOppositePath(pathCW);
517  }
518  default:
519  {
520  return computeOppositePath(pathCCW);
521  }
522  }
523 }
524 
534  const geometry_msgs::Point& start)
535 {
536  return identifyOptimalAlternative(polygon, path, start, start);
537 }
538 
547 bool findSecondOptimalPath(const PointVector& polygon, double footprintWidth, double horizontalOverwrap,
548  PointVector& path)
549 {
550  std::vector<Direction> sweepDirections;
551  PointVector convexHull = computeConvexHull(polygon);
552  LineSegmentVector edges = generateEdgeVector(convexHull, true);
553 
554  // compute optimal sweep directions for each edge
555  for (const auto& edge : edges)
556  {
557  double maxDistance = 0;
558  Direction direction;
559  direction.baseEdge = edge;
560  for (const auto& vertex : convexHull)
561  {
562  double distance = calculateDistance(edge, vertex);
563 
564  // optimal sweep direction for a edge is the direction with the largest distance
565  if (distance > maxDistance)
566  {
567  maxDistance = distance;
568  direction.opposedVertex = vertex;
569  }
570  }
571  sweepDirections.push_back(direction);
572  }
573 
574  // compute second optimal path which has the shortest coverage path
575  double pathLength = 0;
576  PointVector tempPath;
577  for (const auto& sweepDirection : sweepDirections)
578  {
579  PointVector p;
580 
581  // isValidPath is true if computed coverage does not have intersection
582  bool isValidPath = computeConvexCoverage(polygon, footprintWidth, horizontalOverwrap, sweepDirection, p);
583 
584  // second optimal path is the shortest path without intersection
585  if (isValidPath and ((calculatePathLength(tempPath) < pathLength) or (pathLength == 0)))
586  {
587  tempPath = p;
588  pathLength = calculatePathLength(tempPath);
589  }
590  }
591 
592  if (tempPath.size() <= 1)
593  {
594  return false;
595  }
596  else
597  {
598  path = tempPath;
599  return true;
600  }
601 }
602 
609 bool isAdjacent(const PointVector& polygon1, const PointVector& polygon2)
610 {
611  for (const auto& vertex1 : polygon1)
612  {
613  for (const auto& vertex2 : polygon2)
614  {
615  // consider that two polygons are adjacent if they have at least one point in common
616  if (vertex1 == vertex2)
617  {
618  return true;
619  }
620  }
621  }
622  return false;
623 }
624 
634 PointVector computeMultiplePolygonCoverage(std::vector<PointVector> subPolygons, double footprintWidth,
635  double horizontalOverwrap, int adjacencyCriteria = 1)
636 {
637  PointVector path;
638 
639  std::vector<int> permutation(subPolygons.size());
640  std::iota(permutation.begin(), permutation.end(), 0);
641 
642  double minPathLength = -1;
643  bool hasIntersection = false;
644 
645  int numPermutation = 0;
646  int numIntersectSets = 0;
647 
648  do
649  {
650  // ignore permutations which do not start from the same polygon as the first one of given subpolygons
651  if (permutation.front() != 0)
652  {
653  continue;
654  }
655 
656  // count adjacent polygons
657  int adjacencyCount = 0;
658  for (auto itr = permutation.begin(); itr != permutation.end() - 1; ++itr)
659  {
660  if (isAdjacent(subPolygons.at(*itr), subPolygons.at(*(itr + 1))) == true)
661  {
662  ++adjacencyCount;
663  }
664  }
665 
666  // ignore if enough number of polygons do not exist
667  if (adjacencyCount < adjacencyCriteria)
668  {
669  continue;
670  }
671 
672  double pathLength = 0;
673  std::vector<PointVector> candidatePath;
674 
675  // computes coverage path for each polygons and connect them
676  for (auto itr = permutation.begin(); itr != permutation.end(); ++itr)
677  {
678  PointVector partPath, optimalAlternative;
679  // first polygon of given subpolygon
680  if (itr == permutation.begin())
681  {
682  try
683  {
684  // start point and end point of first subpolygon are ...
685  // start point: the last point of coverage of the last subpolygon
686  // end point : the first point of coverage of the second subpolygon
687  geometry_msgs::Point start = subPolygons.at(*(permutation.end() - 1)).back();
688  geometry_msgs::Point end;
689  PointVector polygon = subPolygons.at(*itr);
690  if (permutation.size() == 1)
691  {
692  // end point is the same as start point if subPolygons.at(*(itr+1)) is out of range
693  end = start;
694  }
695  else
696  {
697  end = subPolygons.at(*(itr + 1)).front();
698  }
699 
700  // break if computed path has intersections
701  if (computeConvexCoverage(polygon, footprintWidth, horizontalOverwrap, partPath) == false)
702  {
703  hasIntersection = true;
704  break;
705  }
706 
707  // set optimal alternative as candidate
708  optimalAlternative = identifyOptimalAlternative(polygon, partPath, start, end);
709  candidatePath.push_back(optimalAlternative);
710 
711  // calculate the length of candidate path
712  pathLength += calculatePathLength(optimalAlternative);
713  }
714  catch (std::out_of_range& ex)
715  {
716  ROS_ERROR("%s", ex.what());
717  }
718  }
719  // the last polygon of the given subpolygons
720  else if (itr == permutation.end() - 1)
721  {
722  try
723  {
724  // start point and end point of the last subpolygon are ...
725  // start point: the last point of coverage of the previous subpolygon
726  // end point : the first point of coverage of the first subpolygon
727  geometry_msgs::Point start = subPolygons.at(*(itr - 1)).back();
728  geometry_msgs::Point end = subPolygons.at(*permutation.begin()).front();
729  PointVector polygon = subPolygons.at(*itr);
730 
731  // break if computed path has intersections
732  if (computeConvexCoverage(polygon, footprintWidth, horizontalOverwrap, partPath) == false)
733  {
734  hasIntersection = true;
735  break;
736  }
737 
738  // set optimal alternative as candidate
739  optimalAlternative = identifyOptimalAlternative(polygon, partPath, start, end);
740  candidatePath.push_back(optimalAlternative);
741 
742  // calculate the length of candidate path
743  pathLength += calculatePathLength(optimalAlternative);
744  }
745  catch (std::out_of_range& ex)
746  {
747  ROS_ERROR("%s", ex.what());
748  }
749  }
750  // middle polygons
751  else
752  {
753  try
754  {
755  // start point and end point of middle subpolygons are ...
756  // start point: the last point of coverage of the previous subpolygon
757  // end point : the first point of coverage of the next subpolygon
758  geometry_msgs::Point start = subPolygons.at(*(itr - 1)).back();
759  geometry_msgs::Point end = subPolygons.at(*(itr + 1)).front();
760  PointVector polygon = subPolygons.at(*itr);
761 
762  // break if computed path has intersections
763  if (computeConvexCoverage(polygon, footprintWidth, horizontalOverwrap, partPath) == false)
764  {
765  hasIntersection = true;
766  break;
767  }
768 
769  // set optimal alternative as candidate
770  optimalAlternative = identifyOptimalAlternative(polygon, partPath, start, end);
771  candidatePath.push_back(optimalAlternative);
772 
773  // calculate the length of candidate path
774  pathLength += calculatePathLength(optimalAlternative);
775  }
776  catch (std::out_of_range& ex)
777  {
778  ROS_ERROR("%s", ex.what());
779  }
780  }
781  }
782 
783  numPermutation++;
784 
785  if(hasIntersection)
786  {
787  numIntersectSets++;
788  hasIntersection = false;
789  break;
790  }
791 
792  // update coverage path and minimum path length
793  if (minPathLength < 0 or pathLength < minPathLength)
794  {
795  minPathLength = pathLength;
796 
797  if (not path.empty())
798  {
799  path.clear();
800  }
801 
802  // insert coverages of subpolygons
803  for (const auto& part : candidatePath)
804  {
805  path.insert(path.begin(), part.begin(), part.end());
806  }
807  }
808 
809  } while (next_permutation(permutation.begin(), permutation.end()));
810 
811  if(minPathLength<0)
812  {
813  ROS_ERROR("Unable to generate path.");
814  }
815 
816  return path;
817 }
818 
819 #endif
Direction identifyOptimalSweepDir(const PointVector &polygon)
Calculates line sweep direction for given polygon.
bool findSecondOptimalPath(const PointVector &polygon, double footprintWidth, double horizontalOverwrap, PointVector &path)
Find second optimal path.
std::array< geometry_msgs::Point, 2 > LineSegment
Definition: cgutil.hpp:38
double calculateHorizontalAngle(const geometry_msgs::Point &p1, const geometry_msgs::Point &p2)
Calculates angle between segment p1p2 and horizontal line.
Definition: cgutil.hpp:202
geometry_msgs::Point opposedVertex
PointVector identifyOptimalAlternative(const PointVector &polygon, const PointVector &path, const geometry_msgs::Point &start, const geometry_msgs::Point &end)
Identify optimal path from 4 coverage alternatives.
PointVector reshapePath(const PointVector &path, double padding)
Reshape given path.
bool isClockWise(const PointVector &path)
Checks if given path is clockwise (the first turn made to the left) or not.
LineSegmentVector generateEdgeVector(const PointVector &vec, bool isClosed)
Generate Vector of line segment from given PointVector.
Definition: cgutil.hpp:116
std::vector< LineSegment > LineSegmentVector
Definition: cgutil.hpp:39
PointVector computeMultiplePolygonCoverage(std::vector< PointVector > subPolygons, double footprintWidth, double horizontalOverwrap, int adjacencyCriteria=1)
Compute coverage path for multiple convex polygons.
PointVector computeOppositePath(const PointVector &path)
Return opposite path of given path.
double calculateDistance(const geometry_msgs::Point &p1, const geometry_msgs::Point &p2)
Calculates distance between given two points.
Definition: cgutil.hpp:158
PointVector rotatePoints(const PointVector &points, double angle_rad)
Rotate points by given angle around the origin.
Definition: cgutil.hpp:437
LineSegment baseEdge
std::vector< geometry_msgs::Point > PointVector
Definition: cgutil.hpp:37
PointVector computeConvexHull(PointVector points)
Returns convex hull of given points.
Definition: cgutil.hpp:249
Storage for line sweep direction.
geometry_msgs::Point localizeIntersection(const LineSegment &edge1, const LineSegment &edge2)
Find the location where given edges intersect each other.
Definition: cgutil.hpp:398
bool computeConvexCoverage(const PointVector &polygon, double footprintWidth, double horizontalOverwrap, const Direction &sweepDirection, PointVector &path)
Compute coverage path for convex polygon.
double calculatePathLength(const PointVector &path)
Calculates length of path.
bool hasIntersection(const LineSegment &edge1, const LineSegment &edge2)
Checks if given edges intersect each other.
Definition: cgutil.hpp:335
bool isAdjacent(const PointVector &polygon1, const PointVector &polygon2)
Check if given two polygons are adjacent.
PointVector computeCCWPath(PointVector path)
Return counter clock wise-ed path of given path.