Nav2 Navigation Stack - kilted  kilted
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 import numpy as np
30 
31 NO_INFORMATION = 255
32 LETHAL_OBSTACLE = 254
33 INSCRIBED_INFLATED_OBSTACLE = 253
34 MAX_NON_OBSTACLE = 252
35 FREE_SPACE = 0
36 
37 
39  """
40  FootprintCollisionChecker.
41 
42  FootprintCollisionChecker Class for getting the cost
43  and checking the collisions of a Footprint
44  """
45 
46  def __init__(self) -> None:
47  """Initialize the FootprintCollisionChecker Object."""
48  self.costmap_costmap_ = None
49  pass
50 
51  def footprintCost(self, footprint: Polygon) -> float:
52  """
53  Iterate over all the points in a footprint and check for collision.
54 
55  Args
56  ----
57  footprint (Polygon): The footprint to calculate the collision cost for
58 
59  Returns
60  -------
61  LETHAL_OBSTACLE (int): If collision was found, 254 will be returned
62  footprint_cost (float): The maximum cost found in the footprint points
63 
64  """
65  footprint_cost = 0.0
66  x1 = 0.0
67  y1 = 0.0
68 
69  x0, y0 = self.worldToMapValidatedworldToMapValidated(footprint.points[0].x, footprint.points[0].y)
70 
71  if x0 is None or y0 is None:
72  return LETHAL_OBSTACLE
73 
74  xstart = x0
75  ystart = y0
76 
77  for i in range(len(footprint.points) - 1):
78  x1, y1 = self.worldToMapValidatedworldToMapValidated(
79  footprint.points[i + 1].x, footprint.points[i + 1].y
80  )
81 
82  if x1 is None or y1 is None:
83  return LETHAL_OBSTACLE
84 
85  footprint_cost = max(float(self.lineCostlineCost(x0, x1, y0, y1)), footprint_cost)
86  x0 = x1
87  y0 = y1
88 
89  if footprint_cost == LETHAL_OBSTACLE:
90  return footprint_cost
91 
92  return max(float(self.lineCostlineCost(xstart, x1, ystart, y1)), footprint_cost)
93 
94  def lineCost(self, x0: float, x1: float,
95  y0: float, y1: float, step_size: float = 0.5) -> float:
96  """
97  Iterate over all the points along a line and check for collision.
98 
99  Args
100  ----
101  x0 (float): Abscissa of the initial point in map coordinates
102  y0 (float): Ordinate of the initial point in map coordinates
103  x1 (float): Abscissa of the final point in map coordinates
104  y1 (float): Ordinate of the final point in map coordinates
105  step_size (float): Optional, Increments' resolution, defaults to 0.5
106 
107  Returns
108  -------
109  LETHAL_OBSTACLE (int): If collision was found, 254 will be returned
110  line_cost (float): The maximum cost found in the line points
111 
112  """
113  line_cost = 0.0
114  point_cost = -1.0
115  line_iterator = LineIterator(x0, y0, x1, y1, step_size)
116 
117  while line_iterator.isValid():
118  point_cost = float(self.pointCostpointCost(
119  int(line_iterator.getX()), int(line_iterator.getY())
120  ))
121 
122  if point_cost == LETHAL_OBSTACLE:
123  return point_cost
124 
125  if line_cost < point_cost:
126  line_cost = point_cost
127 
128  line_iterator.advance()
129 
130  return line_cost
131 
132  def worldToMapValidated(self, wx: float, wy: float) -> tuple[int, int]:
133  """
134  Get the map coordinate XY using world coordinate XY.
135 
136  Args
137  ----
138  wx (float): world coordinate X
139  wy (float): world coordinate Y
140 
141  Returns
142  -------
143  None: if coordinates are invalid
144  tuple of int: mx, my (if coordinates are valid)
145  mx (int): map coordinate X
146  my (int): map coordinate Y
147 
148  """
149  if self.costmap_costmap_ is None:
150  raise ValueError(
151  'Costmap not specified, use setCostmap to specify the costmap first'
152  )
153  return self.costmap_costmap_.worldToMapValidated(wx, wy)
154 
155  def pointCost(self, x: int, y: int) -> np.uint8:
156  """
157  Get the cost of a point in the costmap using map coordinates XY.
158 
159  Args
160  ----
161  mx (int): map coordinate X
162  my (int): map coordinate Y
163 
164  Returns
165  -------
166  np.uint8: cost of a point
167 
168  """
169  if self.costmap_costmap_ is None:
170  raise ValueError(
171  'Costmap not specified, use setCostmap to specify the costmap first'
172  )
173  return self.costmap_costmap_.getCostXY(x, y)
174 
175  def setCostmap(self, costmap: PyCostmap2D) -> None:
176  """
177  Specify which costmap to use.
178 
179  Args
180  ----
181  costmap (PyCostmap2D): costmap to use in the object's methods
182 
183  Returns
184  -------
185  None
186 
187  """
188  self.costmap_costmap_ = costmap
189  return None
190 
191  def footprintCostAtPose(self, x: float, y: float,
192  theta: float, footprint: Polygon) -> float:
193  """
194  Get the cost of a footprint at a specific Pose in map coordinates.
195 
196  Args
197  ----
198  x (float): map coordinate X
199  y (float): map coordinate Y
200  theta (float): absolute rotation angle of the footprint
201  footprint (Polygon): the footprint to calculate its cost at the given Pose
202 
203  Returns
204  -------
205  LETHAL_OBSTACLE (int): If collision was found, 254 will be returned
206  footprint_cost (float): The maximum cost found in the footprint points
207 
208  """
209  cos_th = cos(theta)
210  sin_th = sin(theta)
211  oriented_footprint = Polygon()
212 
213  for i in range(len(footprint.points)):
214  new_pt = Point32()
215  new_pt.x = x + (
216  footprint.points[i].x * cos_th - footprint.points[i].y * sin_th
217  )
218  new_pt.y = y + (
219  footprint.points[i].x * sin_th + footprint.points[i].y * cos_th
220  )
221  oriented_footprint.points.append(new_pt)
222 
223  return self.footprintCostfootprintCost(oriented_footprint)
float lineCost(self, float x0, float x1, float y0, float y1, float step_size=0.5)
float footprintCostAtPose(self, float x, float y, float theta, Polygon footprint)