dubins_interpolator.py
Go to the documentation of this file.
1 # Copyright (c) 2016-2019 The UUV Simulator Authors.
2 # All rights reserved.
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
7 #
8 # http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15 
16 import numpy as np
17 from copy import deepcopy
18 
19 from tf_quaternion.transformations import quaternion_multiply, \
20  quaternion_about_axis, quaternion_conjugate, \
21  quaternion_from_matrix, euler_from_matrix
22 from visualization_msgs.msg import MarkerArray, Marker
23 from geometry_msgs.msg import Point
24 from uuv_waypoints import Waypoint, WaypointSet
25 
26 from ..trajectory_point import TrajectoryPoint
27 from .._log import get_logger
28 from .helical_segment import HelicalSegment
29 from .bezier_curve import BezierCurve
30 from .line_segment import LineSegment
31 from .path_generator import PathGenerator
32 
33 class DubinsInterpolator(PathGenerator):
34  """3D Dubins path interpolator implemented based on the sources
35  below.
36 
37  !!! note
38 
39  Owen, Mark, Randal W. Beard, and Timothy W. McLain.
40  "Implementing Dubins Airplane Paths on Fixed-Wing UAVs."
41  Handbook of Unmanned Aerial Vehicles (2014): 1677-1701.
42 
43  Cai, Wenyu, Meiyan Zhang, and Yahong Zheng. "Task Assignment
44  and Path Planning for Multiple Autonomous Underwater Vehicles
45  Using 3D Dubins Curves." Sensors 17.7 (2017):1607.
46 
47  Hansen, Karl D., and Anders La Cour-Harbo. "Waypoint Planning
48  with Dubins Curves Using Genetic Algorithms." 2016 European
49  Control Conference (ECC) (2016).
50 
51  Lin, Yucong, and Srikanth Saripalli. "Path Planning Using
52  3D Dubins Curve for Unmanned Aerial Vehicles." 2014
53  International Conference on Unmanned Aircraft Systems (ICUAS)
54  (2014).
55  """
56  LABEL = 'dubins'
57 
58  def __init__(self):
59  super(DubinsInterpolator, self).__init__(self)
60 
61  self._radius = 5
62  self._max_pitch_angle = 5.0 * np.pi / 180
63  self._interp_fcns = list()
65 
66  def init_interpolator(self):
67  """Initialize the interpolator. To have the path segments generated,
68  `init_waypoints()` must be called beforehand by providing a set of
69  waypoints as `uuv_waypoints.WaypointSet` type.
70 
71  > *Returns*
72 
73  `bool`: `True` if the path segments were successfully generated.
74  """
75  if self._waypoints is None:
76  self._logger.error('List of waypoints is empty')
77  return False
78 
79  if self._waypoints.num_waypoints < 2:
80  self._logger.error('At least 2 waypoints are necessary')
81  return False
82 
83  self._markers_msg = MarkerArray()
84  self._marker_id = 0
85 
86  self._interp_fcns = list()
87  path = list()
88  last_heading = self._waypoints.get_waypoint(0).heading_offset
89 
90  dist = lambda x, y: np.sqrt(np.sum((x - y)**2))
91 
92  for i in range(1, self._waypoints.num_waypoints):
93  heading_init = 0.0
94  heading_final = 0.0
95 
96  if i - 1 == 0:
97  heading_init = self._waypoints.get_waypoint(i - 1).heading_offset
98  else:
99  if not np.isclose(dist(self._waypoints.get_waypoint(i - 1).pos[0:2], self._waypoints.get_waypoint(i).pos[0:2]), 0):
100  heading_init = self._waypoints.get_waypoint(i - 1).calculate_heading(self._waypoints.get_waypoint(i))
101  else:
102  heading_init = last_heading
103 
104  if i == self._waypoints.num_waypoints - 1:
105  if not np.isclose(dist(self._waypoints.get_waypoint(i - 1).pos[0:2], self._waypoints.get_waypoint(i).pos[0:2]), 0):
106  heading_final = self._waypoints.get_waypoint(i - 1).calculate_heading(self._waypoints.get_waypoint(i))
107  else:
108  heading_final = last_heading
109  else:
110  if not np.isclose(dist(self._waypoints.get_waypoint(i + 1).pos[0:2], self._waypoints.get_waypoint(i).pos[0:2]), 0):
111  heading_final = self._waypoints.get_waypoint(i).calculate_heading(self._waypoints.get_waypoint(i + 1))
112  else:
113  heading_final = last_heading
114 
115  last_heading = heading_final
116 
117  path += self._generate_path(
118  self._waypoints.get_waypoint(i - 1), heading_init,
119  self._waypoints.get_waypoint(i), heading_final)
120 
121  inter_pnts = list()
122  for i in range(len(path) - 1):
123  if not np.isclose(np.sqrt(np.sum((path[i + 1] - path[i])**2)), 0):
124  inter_pnts += [path[i]]
125 
126  if not np.isclose(np.sqrt(np.sum((path[-1] - path[-2])**2)), 0):
127  inter_pnts += [path[-1]]
128 
129  self._interp_fcns, tangent = BezierCurve.generate_cubic_curve(inter_pnts)
130 
131  # Reparametrizing the curves
132  lengths = [seg.get_length() for seg in self._interp_fcns]
133  lengths = [0] + lengths
134  self._s = np.cumsum(lengths) / np.sum(lengths)
135  mean_vel = np.mean(
136  [self._waypoints.get_waypoint(k).max_forward_speed for k in range(self._waypoints.num_waypoints)])
137 
138  if self._duration is None:
139  self._duration = np.sum(lengths) / mean_vel
140  if self._start_time is None:
141  self._start_time = 0.0
142 
143  return True
144 
145  def _get_frame(self, heading):
146  """Return the 2D rotation matrix for a desired heading.
147 
148  > *Input arguments*
149 
150  * `heading` (*type:* `float`): Heading angle in radians
151 
152  > *Returns*
153 
154  `numpy.array`: 2D rotation matrix
155  """
156  return np.array([[np.cos(heading), -np.sin(heading)],[np.sin(heading), np.cos(heading)]])
157 
158  def _get_distance(self, pnt_1, pnt_2):
159  """Compute the distance between two points.
160 
161  > *Input arguments*
162 
163  * `pnt_1` (*type:* `numpy.array`): Point 1
164  * `pnt_2` (*type:* `numpy.array`): Point 2
165 
166  > *Returns*
167 
168  `float`: Distance between points
169  """
170  return np.sqrt(np.sum((pnt_1 - pnt_2)**2))
171 
172  def _get_circles_center_pos(self, waypoint, heading, radius=None):
173  """Return the centers of the circles on the left and right of the
174  waypoint with respect to the direction described the desired heading.
175 
176  > *Input arguments*
177 
178  * `waypoint` (*type:* `uuv_wapoints.Waypoint`): Waypoint
179  * `heading` (*type:* `float`): Desired heading in radians
180  * `radius` (*type:* `float`, *default:* `None`): Desired radius for the
181  circles. If `None` is provided, then the internal radius will be used.
182 
183  > *Returns*
184 
185  `dict` with the left `L` and right `R` center of the circles as `numpy.array`
186  """
187  # Use the default radius if none is provided
188  r = self._radius if radius is None else radius
189 
190  if r <= 0:
191  msg = 'Radius must be greater than zero'
192  self._logger.error(msg)
193  raise ValueError('Radius must be greater than zero')
194  # Get the 2D frame using the heading angle information
195  frame = self._get_frame(heading)
196  # Compute the position of the center of right and left circles wrt
197  # the waypoint given
198  circles = dict(R=waypoint.pos[0:2] - r * frame[:, 1].flatten(),
199  L=waypoint.pos[0:2] + r * frame[:, 1].flatten())
200 
201  return circles
202 
203  def _get_phi(self, u, delta, heading):
204  return 2 * np.pi * u * delta + heading - delta * np.pi / 2
205 
206  def _compute_u(self, angle, delta, heading):
207  """Compute the parametric input for the circle path.
208 
209  > *Input arguments*
210 
211  * `angle` (*type:* `float`): Angle in the circle's path
212  in radians
213  * `delta` (*type:* `int`): Generate the points in counter-clockwise
214  direction if `delta` is -1 and clockwise if `delta` is 1
215  * `heading` (*type:* `float`): Heading offset angle in radians
216 
217  > *Returns*
218 
219  `float`: Circle's parametric variable
220  """
221  u = (angle - heading + delta * np.pi / 2) / (delta * 2 * np.pi)
222  if u < 0:
223  u += 1
224  return u
225 
226  def _get_tangent(self, u, delta, radius, heading):
227  """Function description
228 
229  > *Input arguments*
230 
231  * `param` (*type:* `data_type`, *default:* `data`): Parameter description
232 
233  > *Returns*
234 
235  Description of return values
236  """
237  return np.cross(np.array([0, 0, 1]),
238  np.array([delta * radius * np.cos(self._get_phi(u, delta, heading)),
239  delta * radius * np.sin(self._get_phi(u, delta, heading)),
240  0]))[0:2]
241 
242  def _get_circle(self, u, center, radius, delta, heading):
243  """Compute the 2D coordinates for a circle.
244 
245  > *Input arguments*
246 
247  * `u` (*type:* `float`): Parametric variable in interval [0, 1]
248  * `center` (*type:* `numpy.array`): Center of the circle in meters
249  * `radius` (*type:* `float`): Radius of the circle in meters
250  * `delta` (*type:* `int`): Generate the points in counter-clockwise
251  direction if `delta` is -1 and clockwise if `delta` is 1
252  * `heading` (*type:* `float`): Heading associated to the waypoint
253 
254  > *Returns*
255 
256  Circle coordinates as `numpy.array`.
257  """
258  return center + radius * np.array([np.cos(self._get_phi(u, delta, heading)),
259  np.sin(self._get_phi(u, delta, heading))])
260 
261  def _get_2d_dubins_path(self, center_1, radius_1, heading_1, delta_1, center_2, radius_2, heading_2, delta_2):
262  """Compute the 2D Dubins path algorithm. It computes the shortest curve
263  that connects two points. Given two circles which are tangent to the
264  origin and target waypoints, with a chosen heading to depart the first
265  and reach the second, find the path that will be first tangent
266  on circle tangent to the first waypoint, travel to towards the
267  second in a straight line and reach the closest tangent on the
268  circle around the second waypoint.
269 
270  > *Input arguments*
271 
272  * `center_1` (*type:* `numpy.array`): 2D center of the circle tangent to
273  the origin waypoint.
274  * `radius_1` (*type:* `float`): Radius of the circle related to the origin
275  waypoint in meters
276  * `heading_1` (*type:* `float`): Desired heading associated to the origin
277  waypoint
278  * `delta_1` (*type:* `int`): Direction to travel around the circle, -1 for
279  counter-clockwise and 1 for clockwise
280  * `center_2` (*type:* `numpy.array`): 2D center of the circle tangent to
281  the target waypoint.
282  * `radius_2` (*type:* `float`): Radius of the circle related to the target
283  waypoint in meters
284  * `heading_2` (*type:* `float`): Desired heading associated to the target
285  waypoint
286  * `delta_2` (*type:* `int`): Direction to travel around the circle, -1 for
287  counter-clockwise and 1 for clockwise
288 
289  > *Returns*
290 
291  Description of return values
292  """
293  output = list()
294 
295  u1_func = lambda angle: self._compute_u(angle, delta_1, heading_1)
296  u2_func = lambda angle: self._compute_u(angle, delta_2, heading_2)
297 
298  tan_func_1 = lambda u: self._get_tangent(u, delta_1, radius_1, heading_1)
299  tan_func_2 = lambda u: self._get_tangent(u, delta_2, radius_2, heading_2)
300 
301  circle_1 = lambda u: self._get_circle(u, center_1, radius_1, delta_1, heading_1)
302  circle_2 = lambda u: self._get_circle(u, center_2, radius_2, delta_2, heading_2)
303 
304  # Computing outer tangents
305  # Compute vector connecting the centers of the two circles
306  d = center_2 - center_1
307  ## Compute the normal vector to the vector connecting the two circle centers
308  n = np.dot(self._get_frame(np.pi / 2), d / np.linalg.norm(d))
309  ## Compute the angle of the normal vector
310  n_angle = np.arctan2(n[1], n[0])
311  ## First tangent
312  ### Compute the points on the circles belonging to the first tangent and the normal vector
313  u1 = u1_func(n_angle)
314  u2 = u2_func(n_angle)
315 
316  ### Compute the points on the circles belonging to the tangent
317  c1 = circle_1(u1)
318  c2 = circle_2(u2)
319 
320  ### Compute the tangent vector on points c1 and c2 according to the direction of rotation
321  ### provided by delta_1 and delta_2
322  t1 = tan_func_1(u1)
323  t1 /= np.linalg.norm(t1)
324  t2 = tan_func_2(u2)
325  t2 /= np.linalg.norm(t2)
326 
327  tangent_1 = c2 - c1
328  tangent_1 /= np.linalg.norm(tangent_1)
329 
330  ### Find out if the tangents on the circles and the tangents connecting the two circles
331  ### are equal
332  diff = np.linalg.norm(tangent_1 - t1) + np.linalg.norm(tangent_1 - t2)
333 
334  if np.isclose(diff, 0):
335  # First tangent is the feasible path between the two circles
336  dist = 0.0
337  output = list()
338  if not np.isclose(u1, 0):
339  u = np.arange(0, u1, u1 / 10.0)
340  # Compute the points for the path on circle 1
341  output = [circle_1(ui) for ui in u]
342  dist = 2 * radius_1 * np.pi * u1
343  output += [circle_1(u1)]
344  # Compute the line segment between both circles
345  dist += np.linalg.norm(circle_2(u2) - circle_1(u1))
346  # Compute the points for the path on circle 2
347  if not np.isclose(u2, 1):
348  u = np.arange(u2, 1, (1 - u2) / 10.0)
349  output += [circle_2(ui) for ui in u]
350  dist += 2 * radius_2 * np.pi * (1 - u2)
351 
352  output += [circle_2(1)]
353 
354  return output, dist
355 
356  ## Second tangent
357  ### Compute the angle of the normal vector
358  n_angle = np.arctan2(-n[1], -n[0])
359  ### Compute the points on the circles belonging to the first tangent and the normal vector
360  u1 = u1_func(n_angle)
361  u2 = u2_func(n_angle)
362 
363  ### Compute the points on the circles belonging to the tangent
364  c1 = circle_1(u1)
365  c2 = circle_2(u2)
366 
367  ### Compute the tangent vector on points c1 and c2 according to the direction of rotation
368  ### provided by delta_1 and delta_2
369  t1 = tan_func_1(u1)
370  t1 /= np.linalg.norm(t1)
371  t2 = tan_func_2(u2)
372  t2 /= np.linalg.norm(t2)
373 
374  # Compute the vector from circle 1 to circle 2
375  tangent_2 = c2 - c1
376  tangent_2 /= np.linalg.norm(tangent_2)
377 
378  ### Find out if the tangents on the circles and the tangents connecting the two circles
379  ### are equal
380  diff = np.linalg.norm(tangent_2 - t1) + np.linalg.norm(tangent_2 - t2)
381 
382  if np.isclose(diff, 0):
383  # Second tangent is the feasible path between the two circles
384  dist = 0.0
385  output = list()
386  if not np.isclose(u1, 0):
387  u = np.arange(0, u1, u1 / 10.0)
388  # Compute the points for the path on circle 1
389  output = [circle_1(ui) for ui in u]
390  dist = 2 * radius_1 * np.pi * u1
391  output += [circle_1(u1)]
392  # Compute the line segment between both circles
393  dist += np.linalg.norm(circle_2(u2) - circle_1(u1))
394  # Compute the points for the path on circle 2
395  if not np.isclose(u2, 1):
396  u = np.arange(u2, 1, (1 - u2) / 10.0)
397  output += [circle_2(ui) for ui in u]
398  dist += 2 * radius_2 * np.pi * (1 - u2)
399 
400  output += [circle_2(1)]
401 
402  return output, dist
403 
404  # Computing paths with inner tangents if dist(center_1, center_2) > radius_1 + radius_2
405  if self._get_distance(center_1, center_2) > (radius_1 + radius_2):
406  # Calculate the intersection point of the two tangent lines
407  xp = (center_1[0] * radius_1 + center_2[0] * radius_2) / (radius_1 + radius_2)
408  yp = (center_1[1] * radius_1 + center_2[1] * radius_2) / (radius_1 + radius_2)
409 
410  # Compute the points beloging to the inner tangents and the circles
411  xt1 = (radius_1**2 * (xp - center_1[0]) + radius_1 * (yp - center_1[1]) * np.sqrt((xp - center_1[0])**2 + (yp - center_1[1])**2 - radius_1**2)) / ((xp - center_1[0])**2 + (yp - center_1[1])**2) + center_1[0]
412  xt2 = (radius_1**2 * (xp - center_1[0]) - radius_1 * (yp - center_1[1]) * np.sqrt((xp - center_1[0])**2 + (yp - center_1[1])**2 - radius_1**2)) / ((xp - center_1[0])**2 + (yp - center_1[1])**2) + center_1[0]
413 
414  yt1 = ((radius_1**2 * (yp - center_1[1])) - radius_1 * (xp - center_1[0]) * np.sqrt((xp - center_1[0])**2 + (yp - center_1[1])**2 - radius_1**2)) / ((xp - center_1[0])**2 + (yp - center_1[1])**2) + center_1[1]
415  yt2 = ((radius_1**2 * (yp - center_1[1])) + radius_1 * (xp - center_1[0]) * np.sqrt((xp - center_1[0])**2 + (yp - center_1[1])**2 - radius_1**2)) / ((xp - center_1[0])**2 + (yp - center_1[1])**2) + center_1[1]
416 
417  xt3 = (radius_2**2 * (xp - center_2[0]) + radius_2 * (yp - center_2[1]) * np.sqrt((xp - center_2[0])**2 + (yp - center_2[1])**2 - radius_2**2)) / ((xp - center_2[0])**2 + (yp - center_2[1])**2) + center_2[0]
418  xt4 = (radius_2**2 * (xp - center_2[0]) - radius_2 * (yp - center_2[1]) * np.sqrt((xp - center_2[0])**2 + (yp - center_2[1])**2 - radius_2**2)) / ((xp - center_2[0])**2 + (yp - center_2[1])**2) + center_2[0]
419 
420  yt3 = ((radius_2**2 * (yp - center_2[1])) - radius_2 * (xp - center_2[0]) * np.sqrt((xp - center_2[0])**2 + (yp - center_2[1])**2 - radius_2**2)) / ((xp - center_2[0])**2 + (yp - center_2[1])**2) + center_2[1]
421  yt4 = ((radius_2**2 * (yp - center_2[1])) + radius_2 * (xp - center_2[0]) * np.sqrt((xp - center_2[0])**2 + (yp - center_2[1])**2 - radius_2**2)) / ((xp - center_2[0])**2 + (yp - center_2[1])**2) + center_2[1]
422 
423  # Third tangent
424  ## Compute the points on the circles belonging to the first tangent and the normal vector
425  u1 = u1_func(np.arctan2(yt1 - center_1[1], xt1 - center_1[0]))
426  u2 = u2_func(np.arctan2(yt3 - center_2[1], xt3 - center_2[0]))
427 
428  ## Compute the points on the circles belonging to the tangent
429  c1 = circle_1(u1)
430  c2 = circle_2(u2)
431 
432  ### Compute the tangent vector on points c1 and c2 according to the direction of rotation
433  ### provided by delta_1 and delta_2
434  t1 = tan_func_1(u1)
435  t1 /= np.linalg.norm(t1)
436  t2 = tan_func_2(u2)
437  t2 /= np.linalg.norm(t2)
438 
439  tangent_3 = np.array([xt3 - xt1, yt3 - yt1])
440  tangent_3 /= np.linalg.norm(tangent_3)
441 
442  ### Find out if the tangents on the circles and the tangents connecting the two circles
443  ### are equal
444  diff = np.linalg.norm(tangent_3 - t1) + np.linalg.norm(tangent_3 - t2)
445 
446  if np.isclose(diff, 0):
447  # Third tangent is the feasible path between the two circles
448  dist = 0.0
449  output = list()
450  if not np.isclose(u1, 0):
451  u = np.arange(0, u1, u1 / 10.0)
452  # Compute the points for the path on circle 1
453  output = [circle_1(ui) for ui in u]
454  dist = 2 * radius_1 * np.pi * u1
455  output += [circle_1(u1)]
456  # Compute the line segment between both circles
457  dist += np.linalg.norm(circle_2(u2) - circle_1(u1))
458  # Compute the points for the path on circle 2
459  if not np.isclose(u2, 1):
460  u = np.arange(u2, 1, (1 - u2) / 10.0)
461  output += [circle_2(ui) for ui in u]
462  dist += 2 * radius_2 * np.pi * (1 - u2)
463 
464  output += [circle_2(1)]
465 
466  return output, dist
467 
468  # Fourth tangent
469  ## Compute the points on the circles belonging to the first tangent and the normal vector
470  u1 = u1_func(np.arctan2(yt2 - center_1[1], xt2 - center_1[0]))
471  u2 = u2_func(np.arctan2(yt4 - center_2[1], xt4 - center_2[0]))
472 
473  ## Compute the points on the circles belonging to the tangent
474  c1 = circle_1(u1)
475  c2 = circle_2(u2)
476 
477  ### Compute the tangent vector on points c1 and c2 according to the direction of rotation
478  ### provided by delta_1 and delta_2
479  t1 = tan_func_1(u1)
480  t1 /= np.linalg.norm(t1)
481  t2 = tan_func_2(u2)
482  t2 /= np.linalg.norm(t2)
483 
484  tangent_4 = np.array([xt4 - xt2, yt4 - yt2])
485  tangent_4 /= np.linalg.norm(tangent_4)
486 
487  ### Find out if the tangents on the circles and the tangents connecting the two circles
488  ### are equal
489  diff = np.linalg.norm(tangent_4 - t1) + np.linalg.norm(tangent_4 - t2)
490 
491  if np.isclose(diff, 0):
492  # Fourth tangent is the feasible path between the two circles
493  dist = 0.0
494  output = list()
495  if not np.isclose(u1, 0):
496  u = np.arange(0, u1, u1 / 10.0)
497  # Compute the points for the path on circle 1
498  output = [circle_1(ui) for ui in u]
499  dist = 2 * radius_1 * np.pi * u1
500  output += [circle_1(u1)]
501  # Compute the line segment between both circles
502  dist += np.linalg.norm(circle_2(u2) - circle_1(u1))
503  # Compute the points for the path on circle 2
504  if not np.isclose(u2, 1):
505  u = np.arange(u2, 1, (1 - u2) / 10.0)
506  output += [circle_2(ui) for ui in u]
507  dist += 2 * radius_2 * np.pi * (1 - u2)
508 
509  output += [circle_2(1)]
510 
511  return output, dist
512 
513  return output, 0
514 
515  def _get_center(self, side, y_vec, wp):
516  if side == 'R':
517  return wp.pos - self._radius * y_vec
518  else:
519  return wp.pos + self._radius * y_vec
520 
521  def _get_circle_marker(self, center, radius, heading, delta, frame_id,
522  circle_color=[0, 1, 0]):
523  import rospy
524  marker = Marker()
525  marker.header.frame_id = frame_id
526  try:
527  if not rospy.is_shutdown():
528  marker.header.stamp = rospy.Time.now()
529  except:
530  pass
531  marker.ns = 'dubins'
532  marker.id = self._marker_id
533  self._marker_id += 1
534  marker.type = Marker.LINE_STRIP
535  marker.action = Marker.ADD;
536 
537  marker.scale.x = 0.05
538  marker.scale.y = 0.1
539  marker.scale.z = 0.1
540  marker.color.a = 1.0
541  marker.color.r = circle_color[0]
542  marker.color.g = circle_color[1]
543  marker.color.b = circle_color[2]
544 
545  for i in np.linspace(0, 1, 50):
546  c_pnt = self._get_circle(i, center[0:2], radius, delta, heading)
547  marker.points.append(Point(c_pnt[0], c_pnt[1], center[2]))
548 
549  self._marker_id += 1
550  marker_pnt = Marker()
551  marker_pnt.header.frame_id = frame_id
552  try:
553  if not rospy.is_shutdown():
554  marker_pnt.header.stamp = rospy.Time.now()
555  except:
556  pass
557  marker_pnt.ns = 'dubins'
558  marker_pnt.id = self._marker_id
559  self._marker_id += 1
560  marker_pnt.type = Marker.SPHERE
561  marker_pnt.action = Marker.ADD;
562 
563  marker_pnt.scale.x = 0.2
564  marker_pnt.scale.y = 0.2
565  marker_pnt.scale.z = 0.2
566  marker_pnt.color.a = 1.0
567  marker_pnt.color.r = 1.0
568  marker_pnt.color.g = 0.2
569  marker_pnt.color.b = 0.0
570 
571  c_pnt = self._get_circle(0, center[0:2], radius, delta, heading)
572  marker_pnt.pose.position.x = c_pnt[0]
573  marker_pnt.pose.position.y = c_pnt[1]
574  marker_pnt.pose.position.z = center[2]
575  return [marker, marker_pnt]
576 
577  def _generate_path(self, wp_init, heading_init, wp_final, heading_final):
578  pnts = list()
579 
580  max_step_z = 2 * np.pi * self._radius * np.cos(self._max_pitch_angle)
581 
582  frame_init = np.array([[np.cos(heading_init), -np.sin(heading_init), 0],
583  [np.sin(heading_init), np.cos(heading_init), 0],
584  [0, 0, 1]])
585 
586  frame_final = np.array([[np.cos(heading_final), -np.sin(heading_final), 0],
587  [np.sin(heading_final), np.cos(heading_final), 0],
588  [0, 0, 1]])
589 
590  # Wrap the angle difference to find out whether both waypoints have
591  # the same target heading
592  heading_diff = (heading_final - heading_init + np.pi) % (2.0 * np.pi) - np.pi
593  dist_xy = np.sqrt(np.sum((wp_init.pos[0:2] - wp_final.pos[0:2])**2))
594 
595  if np.isclose(heading_diff, 0) or np.isclose(dist_xy, 0):
596  if abs(wp_init.z - wp_final.z) <= max_step_z and not np.isclose(dist_xy, 0):
597  pnts = [wp_init.pos, wp_final.pos]
598  else:
599  z = wp_final.z - wp_init.z
600  if z >= max_step_z:
601  delta_z = z / (np.floor(abs(z) / max_step_z) + np.ceil(abs(z) % max_step_z))
602  else:
603  delta_z = z
604  n = z / delta_z
605 
606  center = self._get_center('R', frame_final[:, 1].flatten(), wp_final)
607  center[2] = wp_init.z
608 
609  delta = -1
610  helix = HelicalSegment(
611  center, self._radius, n, wp_final.z - wp_init.z, heading_final - delta * np.pi / 2, False)
612 
613  for i in np.linspace(0, 1, n * 10):
614  pnts.append(helix.interpolate(i))
615 
616  return pnts
617 
618  center_init = None
619  center_final = None
620 
621  modes = ['RSR', 'RSL', 'LSR', 'LSL']
622 
623  # Create visual markers for the left and right circle paths possible
624  # for evaluation of the Dubins path
625  c = self._get_center('R', frame_init[:,1].flatten(), wp_init)
626  delta = -1
627  self._markers_msg.markers += self._get_circle_marker(c, self._radius, heading_init, delta, wp_final.inertial_frame_id)
628 
629  c = self._get_center('L', frame_init[:,1].flatten(), wp_init)
630  delta = 1
631  self._markers_msg.markers += self._get_circle_marker(c, self._radius, heading_init, delta, wp_final.inertial_frame_id)
632 
633  c = self._get_center('R', frame_final[:,1].flatten(), wp_final)
634  delta = -1
635  self._markers_msg.markers += self._get_circle_marker(c, self._radius, heading_final, delta, wp_final.inertial_frame_id)
636 
637  c = self._get_center('L', frame_final[:,1].flatten(), wp_final)
638  delta = 1
639  self._markers_msg.markers += self._get_circle_marker(c, self._radius, heading_final, delta, wp_final.inertial_frame_id)
640 
641  path = None
642  min_dist = None
643  mode = None
644 
645  for c in modes:
646  center_1 = self._get_center(c[0], frame_init[:,1].flatten(), wp_init)
647  center_2 = self._get_center(c[-1], frame_final[:,1].flatten(), wp_final)
648 
649  delta_1 = -1 if c[0] == 'R' else 1
650  delta_2 = -1 if c[-1] == 'R' else 1
651 
652  output, dist = self._get_2d_dubins_path(
653  center_1[0:2], self._radius, heading_init, delta_1,
654  center_2[0:2], self._radius, heading_final, delta_2)
655 
656  if len(output) > 0:
657  if min_dist is None:
658  path = output
659  min_dist = dist
660  mode = c
661  else:
662  if dist < min_dist:
663  min_dist = dist
664  path = output
665  mode = c
666 
667  pnts = list()
668 
669  if np.isclose(abs(wp_init.z - wp_final.z), 0):
670  for pnt in path:
671  pnts.append(np.array([pnt[0], pnt[1], wp_final.z]))
672  elif abs(wp_init.z - wp_final.z) <= max_step_z and not np.isclose(abs(wp_init.z - wp_final.z), 0):
673 
674  d = [0.0] + [np.sqrt(np.sum((path[i] - path[i - 1])**2)) for i in range(1, len(path))]
675  dz = float(wp_final.z - wp_init.z) * np.cumsum(d) / np.sum(d)
676  for i in range(len(path)):
677  pnts.append(np.array([path[i][0], path[i][1], wp_init.z + dz[i]]))
678  else:
679  z = wp_final.z - wp_init.z
680  delta_z = z / (np.floor(abs(z) / max_step_z) + np.ceil(abs(z) % max_step_z))
681  n = z / delta_z
682 
683  d = [0.0] + [np.sqrt(np.sum((path[i] - path[i - 1])**2)) for i in range(1, len(path))]
684  dz = delta_z * np.cumsum(d) / np.sum(d)
685 
686  for i in range(len(path)):
687  pnts.append(np.array([path[i][0], path[i][1], wp_init.z + dz[i]]))
688 
689  center = self._get_center(mode[-1], frame_final[:, 1].flatten(), wp_final)
690  center[2] = wp_init.z + delta_z
691 
692  delta = -1 if mode[-1] == 'R' else 1
693  helix = HelicalSegment(
694  center, self._radius, n - 1, wp_final.z - center[2], heading_final - delta * np.pi / 2, False if mode[-1] == 'R' else True)
695 
696  for i in np.linspace(0, 1, (n - 1) * 10):
697  pnts.append(helix.interpolate(i))
698 
699  return pnts
700 
701  def set_parameters(self, params):
702  """Set interpolator's parameters. All the options
703  for the `params` input can be seen below:
704 
705  ```python
706  params=dict(
707  radius=0.0,
708  max_pitch=0.0
709  )
710  ```
711 
712  * `radius` (*type:* `float`): Turning radius
713  * `max_pitch` (*type:* `float`): Max. pitch angle allowed
714  between two waypoints. If the pitch exceeds `max_pitch`, a
715  helical path is computed to perform steep climbs or dives.
716 
717  > *Input arguments*
718 
719  * `params` (*type:* `dict`): `dict` containing interpolator's
720  configurable elements.
721  """
722  if 'radius' in params:
723  assert params['radius'] > 0, 'Radius must be greater than zero'
724  self._radius = params['radius']
725  if 'max_pitch' in params:
726  assert params['max_pitch'] > 0 and params['max_pitch'] <= np.pi, 'Invalid max. pitch'
727  self._max_pitch_angle = params['max_pitch']
728  return True
729 
730  def get_samples(self, max_time, step=0.001):
731  """Sample the full path for position and quaternion vectors.
732  `step` is represented in the path's parametric space.
733 
734  > *Input arguments*
735 
736  * `step` (*type:* `float`, *default:* `0.001`): Parameter description
737 
738  > *Returns*
739 
740  List of `uuv_trajectory_generator.TrajectoryPoint`.
741  """
742  if self._waypoints is None:
743  return None
744  if len(self._interp_fcns) == 0:
745  return None
746  s = np.arange(0, 1 + step, step)
747 
748  pnts = list()
749  for i in s:
750  pnt = TrajectoryPoint()
751  pnt.pos = self.generate_pos(i).tolist()
752  pnt.t = 0.0
753  pnts.append(pnt)
754  return pnts
755 
756  def generate_pos(self, s):
757  """Generate a position vector for the path sampled point
758  interpolated on the position related to `s`, `s` being
759  represented in the curve's parametric space.
760 
761  > *Input arguments*
762 
763  * `s` (*type:* `float`): Curve's parametric input expressed in the
764  interval of [0, 1]
765 
766  > *Returns*
767 
768  3D position vector as a `numpy.array`.
769  """
770  if len(self._interp_fcns) == 0:
771  return None
772  idx = self.get_segment_idx(s)
773  if idx == 0:
774  u_k = 0
775  pos = self._interp_fcns[idx].interpolate(u_k)
776  else:
777  u_k = (s - self._s[idx - 1]) / (self._s[idx] - self._s[idx - 1])
778  pos = self._interp_fcns[idx - 1].interpolate(u_k)
779  return pos
780 
781  def generate_pnt(self, s, t, *args):
782  """Compute a point that belongs to the path on the
783  interpolated space related to `s`, `s` being represented
784  in the curve's parametric space.
785 
786  > *Input arguments*
787 
788  * `s` (*type:* `float`): Curve's parametric input expressed in the
789  interval of [0, 1]
790  * `t` (*type:* `float`): Trajectory point's timestamp
791 
792  > *Returns*
793 
794  `uuv_trajectory_generator.TrajectoryPoint` including position
795  and quaternion vectors.
796  """
797  pnt = TrajectoryPoint()
798  # Trajectory time stamp
799  pnt.t = t
800  # Set position vector
801  pnt.pos = self.generate_pos(s).tolist()
802  # Set rotation quaternion
803  pnt.rotq = self.generate_quat(s)
804  return pnt
805 
806  def generate_quat(self, s):
807  """Compute the quaternion of the path reference for a interpolated
808  point related to `s`, `s` being represented in the curve's parametric
809  space.
810  The quaternion is computed assuming the heading follows the direction
811  of the path towards the target. Roll and pitch can also be computed
812  in case the `full_dof` is set to `True`.
813 
814  > *Input arguments*
815 
816  * `s` (*type:* `float`): Curve's parametric input expressed in the
817  interval of [0, 1]
818 
819  > *Returns*
820 
821  Rotation quaternion as a `numpy.array` as `(x, y, z, w)`
822  """
823  s = max(0, s)
824  s = min(s, 1)
825 
826  if s == 0:
827  self._last_rot = deepcopy(self._init_rot)
828  return self._init_rot
829 
830  last_s = max(0, s - self._s_step)
831 
832  this_pos = self.generate_pos(s)
833  last_pos = self.generate_pos(last_s)
834 
835  dx = this_pos[0] - last_pos[0]
836  dy = this_pos[1] - last_pos[1]
837  dz = this_pos[2] - last_pos[2]
838 
839  rotq = self._compute_rot_quat(dx, dy, dz)
840  self._last_rot = rotq
841  return rotq
def _generate_path(self, wp_init, heading_init, wp_final, heading_final)
def _get_2d_dubins_path(self, center_1, radius_1, heading_1, delta_1, center_2, radius_2, heading_2, delta_2)
def _get_circle_marker(self, center, radius, heading, delta, frame_id, circle_color=[0)


uuv_trajectory_control
Author(s):
autogenerated on Thu Jun 18 2020 03:28:42