area_to_path.py
Go to the documentation of this file.
1 #!/usr/bin/env python3
2 
3 import math
4 import rospy
5 import itertools
6 
7 from nav_msgs.msg import Path
8 from geometry_msgs.msg import PoseStamped, PolygonStamped, Point
9 
10 from dynamic_reconfigure.server import Server as DynamicReconfigureServer
11 
13  def __init__(self):
14  rospy.init_node("area_to_path_node")
15 
16  self.path_pub = rospy.Publisher("/move_base_simple/waypoints", Path, queue_size=10)
17 
18  self.lawnmower_sub = rospy.Subscriber("/area_to_path/lawnmower", PolygonStamped, self.lawnmower_callback)
19  self.square_sub = rospy.Subscriber("/area_to_path/expanding_square", PolygonStamped, self.square_callback)
20  self.sierra_sub = rospy.Subscriber("/area_to_path/victor_sierra", PolygonStamped, self.sierra_callback)
21  self.home_sub = rospy.Subscriber("/area_to_path/home", PolygonStamped, self.home_callback)
22 
23  self.step_size = rospy.get_param("~step_size", 2.0) # The step size (X meters) in the path
24 
25  self.home_point = None
26  self.home_header = None
27 
28  def send_path(self, header, poses):
29  if len(poses) == 0:
30  rospy.logwarn("Send a larger polygon or reduce step size.")
31  return
32 
33  path = Path()
34  path.header = header
35  path.poses = poses
36  self.path_pub.publish(path)
37 
38  def home_callback(self, msg):
39  p = msg.polygon.points
40 
41  if len(p) != 4:
42  rospy.logwarn("Invalid Polygon, path creation requires a 4 vertex square.")
43  return
44 
45  self.home_point = Point(
46  x=(p[0].x + p[2].x) / 2,
47  y=(p[0].y + p[2].y) / 2
48  )
49 
50  self.home_header = msg.header
51 
52  rospy.loginfo("Home point set.")
53 
54  def norm(self, point):
55  p = Point()
56 
57  psum = math.sqrt(point.x **2 + point.y **2 + point.z **2)
58  if psum == 0:
59  return p
60 
61  p.x = point.x / psum
62  p.y = point.y / psum
63  p.z = point.z / psum
64 
65  return p
66 
67  def get_pose(self, header, x, y):
68  pose = PoseStamped()
69  pose.header = header
70  pose.pose.position.x = x
71  pose.pose.position.y = y
72  pose.pose.position.z = 0
73  pose.pose.orientation.w = 1.0
74  return pose
75 
76  def sierra_callback(self, msg):
77  p = msg.polygon.points
78 
79  if len(p) != 4:
80  rospy.logwarn("Invalid Polygon, path creation requires a 4 vertex square.")
81  return
82 
83  poses = []
84 
85  # Calculate vectors between points
86  vec_side_1 = Point(x=p[1].x - p[0].x, y=p[1].y - p[0].y)
87  vec_side_2 = Point(x=p[3].x - p[0].x, y=p[3].y - p[0].y)
88 
89  # Compute the lengths of the two sides and the average (which will be our radius)
90  length_side_1 = math.sqrt(vec_side_1.x**2 + vec_side_1.y**2)
91  length_side_2 = math.sqrt(vec_side_2.x**2 + vec_side_2.y**2)
92  radius = (length_side_1 + length_side_2) / 4
93 
94  # Calculate the center point of the search area
95  center = Point(
96  x=(p[0].x + p[2].x) / 2,
97  y=(p[0].y + p[2].y) / 2
98  )
99 
100  poses.append(self.get_pose(msg.header, center.x, center.y))
101 
102  angle = math.radians(0)
103  poses.append(self.get_pose(msg.header,
104  center.x + radius * math.cos(angle),
105  center.y + radius * math.sin(angle)
106  ))
107 
108  angle = math.radians(60)
109  poses.append(self.get_pose(msg.header,
110  center.x + radius * math.cos(angle),
111  center.y + radius * math.sin(angle)
112  ))
113 
114  angle = math.radians(240)
115  poses.append(self.get_pose(msg.header,
116  center.x + radius * math.cos(angle),
117  center.y + radius * math.sin(angle)
118  ))
119 
120  angle = math.radians(300)
121  poses.append(self.get_pose(msg.header,
122  center.x + radius * math.cos(angle),
123  center.y + radius * math.sin(angle)
124  ))
125 
126  angle = math.radians(120)
127  poses.append(self.get_pose(msg.header,
128  center.x + radius * math.cos(angle),
129  center.y + radius * math.sin(angle)
130  ))
131 
132  angle = math.radians(180)
133  poses.append(self.get_pose(msg.header,
134  center.x + radius * math.cos(angle),
135  center.y + radius * math.sin(angle)
136  ))
137 
138  poses.append(self.get_pose(msg.header, center.x, center.y))
139 
140  if self.home_point != None:
141  poses.append(self.get_pose(self.home_header, self.home_point.x, self.home_point.y))
142 
143  self.send_path(msg.header, poses)
144 
145  def square_callback(self, msg):
146  p = msg.polygon.points
147 
148  if len(p) != 4:
149  rospy.logwarn("Invalid Polygon, path creation requires a 4 vertex square.")
150  return
151 
152  poses = []
153 
154  # Calculate vectors between points
155  vec_side_1 = Point(x=p[1].x - p[0].x, y=p[1].y - p[0].y)
156  vec_side_2 = Point(x=p[3].x - p[0].x, y=p[3].y - p[0].y)
157 
158  # Compute the lengths of the two sides
159  length_side_1 = math.sqrt(vec_side_1.x**2 + vec_side_1.y**2)
160  length_side_2 = math.sqrt(vec_side_2.x**2 + vec_side_2.y**2)
161 
162  # Determine the center of the square
163  center = Point(x=p[0].x + vec_side_1.x/2 + vec_side_2.x/2, y=p[0].y + vec_side_1.y/2 + vec_side_2.y/2)
164 
165  # The spiral is drawn as a series of line segments, starting from the center of the square
166  pos = [center.x, center.y]
167  dir = [0, -1] # Initial direction: up
168 
169  # Continue drawing the spiral until we reach the edge of the square
170  for step in itertools.count(start=1, step=1):
171  if self.step_size * step > max(length_side_1, length_side_2): # If we would go beyond the edge of the square, end the spiral
172  break
173 
174  for _ in range(2): # Each "layer" of the spiral consists of two steps
175  for _ in range(step):
176  if abs(pos[0] - center.x) <= length_side_1 / 2 and abs(pos[1] - center.y) <= length_side_2 / 2:
177  pos[0] += dir[0] * self.step_size
178  pos[1] += dir[1] * self.step_size
179 
180 
181  poses.append(self.get_pose(msg.header, pos[0], pos[1]))
182  dir = [-dir[1], dir[0]] # Rotate direction 90 degrees to the right
183 
184  if self.home_point != None:
185  poses.append(self.get_pose(self.home_header, self.home_point.x, self.home_point.y))
186 
187  self.send_path(msg.header, poses)
188 
189 
190  def lawnmower_callback(self, msg):
191  p = msg.polygon.points
192 
193  if len(p) != 4:
194  rospy.logwarn("Invalid Polygon, path creation requires a 4 vertex square.")
195  return
196 
197  poses = []
198 
199  # Calculate vectors between points
200  vec_side_1 = Point(x=p[1].x - p[0].x, y=p[1].y - p[0].y)
201  vec_side_2 = Point(x=p[3].x - p[0].x, y=p[3].y - p[0].y)
202 
203  # Compute the lengths of the two sides
204  length_side_1 = math.sqrt(vec_side_1.x**2 + vec_side_1.y**2)
205  length_side_2 = math.sqrt(vec_side_2.x**2 + vec_side_2.y**2)
206 
207  # Decide which side to move along based on their lengths
208  if length_side_1 > length_side_2:
209  vec_step = self.norm(vec_side_2)
210  vec_side = vec_side_1
211  steps = math.ceil(length_side_2 / self.step_size)
212  else:
213  vec_step = self.norm(vec_side_1)
214  vec_side = vec_side_2
215  steps = math.ceil(length_side_1 / self.step_size)
216 
217  vec_step.x *= self.step_size
218  vec_step.y *= self.step_size
219 
220  for i in range(steps):
221  if i % 2 == 0: # For every other line, the direction should be reversed
222  # Start of the line
223  poses.append(self.get_pose(
224  msg.header,
225  p[0].x + i * vec_step.x,
226  p[0].y + i * vec_step.y
227  ))
228 
229  # End of the line
230  poses.append(self.get_pose(
231  msg.header,
232  p[0].x + i * vec_step.x + vec_side.x,
233  p[0].y + i * vec_step.y + vec_side.y,
234  ))
235  else:
236  # Start of the line
237  poses.append(self.get_pose(
238  msg.header,
239  p[0].x + i * vec_step.x + vec_side.x,
240  p[0].y + i * vec_step.y + vec_side.y,
241  ))
242 
243  # End of the line
244  poses.append(self.get_pose(
245  msg.header,
246  p[0].x + i * vec_step.x,
247  p[0].y + i * vec_step.y,
248  ))
249 
250  if self.home_point != None:
251  poses.append(self.get_pose(self.home_header, self.home_point.x, self.home_point.y))
252 
253  self.send_path(msg.header, poses)
254 
255 
257 rospy.spin()
258 
259 
area_to_path.BoundingBox3DListener.lawnmower_callback
def lawnmower_callback(self, msg)
Definition: area_to_path.py:190
area_to_path.BoundingBox3DListener.home_sub
home_sub
Definition: area_to_path.py:21
area_to_path.BoundingBox3DListener.sierra_sub
sierra_sub
Definition: area_to_path.py:20
area_to_path.BoundingBox3DListener.square_callback
def square_callback(self, msg)
Definition: area_to_path.py:145
area_to_path.BoundingBox3DListener.square_sub
square_sub
Definition: area_to_path.py:19
area_to_path.BoundingBox3DListener.home_point
home_point
Definition: area_to_path.py:25
area_to_path.BoundingBox3DListener.__init__
def __init__(self)
Definition: area_to_path.py:13
area_to_path.BoundingBox3DListener
Definition: area_to_path.py:12
area_to_path.BoundingBox3DListener.lawnmower_sub
lawnmower_sub
Definition: area_to_path.py:18
area_to_path.BoundingBox3DListener.sierra_callback
def sierra_callback(self, msg)
Definition: area_to_path.py:76
area_to_path.BoundingBox3DListener.home_header
home_header
Definition: area_to_path.py:26
area_to_path.BoundingBox3DListener.step_size
step_size
Definition: area_to_path.py:23
area_to_path.BoundingBox3DListener.path_pub
path_pub
Definition: area_to_path.py:16
area_to_path.BoundingBox3DListener.norm
def norm(self, point)
Definition: area_to_path.py:54
area_to_path.BoundingBox3DListener.send_path
def send_path(self, header, poses)
Definition: area_to_path.py:28
area_to_path.BoundingBox3DListener.get_pose
def get_pose(self, header, x, y)
Definition: area_to_path.py:67
area_to_path.BoundingBox3DListener.home_callback
def home_callback(self, msg)
Definition: area_to_path.py:38


line_planner
Author(s):
autogenerated on Sat Jun 10 2023 02:19:03