Nav2 Navigation Stack - lyrical  lyrical
ROS 2 Navigation Stack
footprint_collision_checker.py
1 #! /usr/bin/env python3
2 # Copyright 2021 Samsung Research America
3 # Copyright 2022 Afif Swaidan
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16 
17 """
18 This is a Python3 API for a Footprint Collision Checker.
19 
20 It provides the needed methods to manipulate the coordinates
21 and calculate the cost of a Footprint
22 """
23 
24 from math import cos, sin
25 
26 from geometry_msgs.msg import Point32, Polygon
27 from nav2_simple_commander.costmap_2d import PyCostmap2D
28 from nav2_simple_commander.line_iterator import LineIterator
29 
30 NO_INFORMATION = 255
31 LETHAL_OBSTACLE = 254
32 INSCRIBED_INFLATED_OBSTACLE = 253
33 MAX_NON_OBSTACLE = 252
34 FREE_SPACE = 0
35 
36 
38  """
39  FootprintCollisionChecker.
40 
41  FootprintCollisionChecker Class for getting the cost
42  and checking the collisions of a Footprint
43  """
44 
45  def __init__(self):
46  """Initialize the FootprintCollisionChecker Object."""
47  self.costmap_costmap_ = None
48  pass
49 
50  def footprintCost(self, footprint: Polygon):
51  """
52  Iterate over all the points in a footprint and check for collision.
53 
54  Args
55  ----
56  footprint (Polygon): The footprint to calculate the collision cost for
57 
58  Returns
59  -------
60  LETHAL_OBSTACLE (int): If collision was found, 254 will be returned
61  footprint_cost (float): The maximum cost found in the footprint points
62 
63  """
64  footprint_cost = 0.0
65  x1 = 0.0
66  y1 = 0.0
67 
68  x0, y0 = self.worldToMapValidatedworldToMapValidated(footprint.points[0].x, footprint.points[0].y)
69 
70  if x0 is None or y0 is None:
71  return LETHAL_OBSTACLE
72 
73  xstart = x0
74  ystart = y0
75 
76  for i in range(len(footprint.points) - 1):
77  x1, y1 = self.worldToMapValidatedworldToMapValidated(
78  footprint.points[i + 1].x, footprint.points[i + 1].y
79  )
80 
81  if x1 is None or y1 is None:
82  return LETHAL_OBSTACLE
83 
84  footprint_cost = max(float(self.lineCostlineCost(x0, x1, y0, y1)), footprint_cost)
85  x0 = x1
86  y0 = y1
87 
88  if footprint_cost == LETHAL_OBSTACLE:
89  return footprint_cost
90 
91  return max(float(self.lineCostlineCost(xstart, x1, ystart, y1)), footprint_cost)
92 
93  def lineCost(self, x0: float, x1: float,
94  y0: float, y1: float, step_size: float = 0.5):
95  """
96  Iterate over all the points along a line and check for collision.
97 
98  Args
99  ----
100  x0 (float): Abscissa of the initial point in map coordinates
101  y0 (float): Ordinate of the initial point in map coordinates
102  x1 (float): Abscissa of the final point in map coordinates
103  y1 (float): Ordinate of the final point in map coordinates
104  step_size (float): Optional, Increments' resolution, defaults to 0.5
105 
106  Returns
107  -------
108  LETHAL_OBSTACLE (int): If collision was found, 254 will be returned
109  line_cost (float): The maximum cost found in the line points
110 
111  """
112  line_cost = 0.0
113  point_cost = -1.0
114  line_iterator = LineIterator(x0, y0, x1, y1, step_size)
115 
116  while line_iterator.isValid():
117  point_cost = float(self.pointCostpointCost(
118  int(line_iterator.getX()), int(line_iterator.getY())
119  ))
120 
121  if point_cost == LETHAL_OBSTACLE:
122  return point_cost
123 
124  if line_cost < point_cost:
125  line_cost = point_cost
126 
127  line_iterator.advance()
128 
129  return line_cost
130 
131  def worldToMapValidated(self, wx: float, wy: float):
132  """
133  Get the map coordinate XY using world coordinate XY.
134 
135  Args
136  ----
137  wx (float): world coordinate X
138  wy (float): world coordinate Y
139 
140  Returns
141  -------
142  None: if coordinates are invalid
143  tuple of int: mx, my (if coordinates are valid)
144  mx (int): map coordinate X
145  my (int): map coordinate Y
146 
147  """
148  if self.costmap_costmap_ is None:
149  raise ValueError(
150  'Costmap not specified, use setCostmap to specify the costmap first'
151  )
152  return self.costmap_costmap_.worldToMapValidated(wx, wy)
153 
154  def pointCost(self, x: int, y: int):
155  """
156  Get the cost of a point in the costmap using map coordinates XY.
157 
158  Args
159  ----
160  mx (int): map coordinate X
161  my (int): map coordinate Y
162 
163  Returns
164  -------
165  np.uint8: cost of a point
166 
167  """
168  if self.costmap_costmap_ is None:
169  raise ValueError(
170  'Costmap not specified, use setCostmap to specify the costmap first'
171  )
172  return self.costmap_costmap_.getCostXY(x, y)
173 
174  def setCostmap(self, costmap: PyCostmap2D):
175  """
176  Specify which costmap to use.
177 
178  Args
179  ----
180  costmap (PyCostmap2D): costmap to use in the object's methods
181 
182  Returns
183  -------
184  None
185 
186  """
187  self.costmap_costmap_ = costmap
188  return None
189 
190  def footprintCostAtPose(self, x: float, y: float,
191  theta: float, footprint: Polygon):
192  """
193  Get the cost of a footprint at a specific Pose in map coordinates.
194 
195  Args
196  ----
197  x (float): map coordinate X
198  y (float): map coordinate Y
199  theta (float): absolute rotation angle of the footprint
200  footprint (Polygon): the footprint to calculate its cost at the given Pose
201 
202  Returns
203  -------
204  LETHAL_OBSTACLE (int): If collision was found, 254 will be returned
205  footprint_cost (float): The maximum cost found in the footprint points
206 
207  """
208  cos_th = cos(theta)
209  sin_th = sin(theta)
210  oriented_footprint = Polygon()
211 
212  for i in range(len(footprint.points)):
213  new_pt = Point32()
214  new_pt.x = x + (
215  footprint.points[i].x * cos_th - footprint.points[i].y * sin_th
216  )
217  new_pt.y = y + (
218  footprint.points[i].x * sin_th + footprint.points[i].y * cos_th
219  )
220  oriented_footprint.points.append(new_pt)
221 
222  return self.footprintCostfootprintCost(oriented_footprint)
def lineCost(self, float x0, float x1, float y0, float y1, float step_size=0.5)
def footprintCostAtPose(self, float x, float y, float theta, Polygon footprint)