DubinsAirplanePlot.py
1 #!/usr/bin/env python3
2 
3 
36 
37 # Author: Mark Moll
38 
39 import shutil
40 from pathlib import Path
41 from tempfile import TemporaryDirectory
42 import matplotlib
43 import matplotlib.pyplot as plt
44 import numpy as np
45 from skspatial.objects import Sphere
46 from subprocess import run
47 
48 cmap = plt.cm.tab10
49 
50 # Change the projection type of an existing Matplotlib axis object
51 def updateProjection(ax, axi, projection='3d', fig=None):
52  if fig is None:
53  fig = plt.gcf()
54  rows, cols, start, stop = axi.get_subplotspec().get_geometry()
55  ax.flat[start].remove()
56  ax.flat[start] = fig.add_subplot(rows, cols, start+1, projection=projection)
57 
58 # read the contents of a file containing a path, each line representing one
59 # waypoint. Each waypoint consists of 5 numbers, separated by whitespace.
60 def readPath(path_file):
61  return np.array([[float(x) for x in line.split()] \
62  for line in open(path_file).readlines()
63  if len(line)>1])
64 
65 # Run demo_DubinsAirplane, print the output on stdout and return the path that was computed
66 # as an nx5 numpy array, where n is the number of waypoints and the columns
67 # represent X, Y, Z, pitch, and yaw.
68 def getPath(exec_path, **kwargs):
69  with TemporaryDirectory() as tmpdir:
70  path_file = Path(tmpdir) / "path.dat"
71  demo_args = ' '.join([f'--{key} {value}' for key, value in kwargs.items()])
72  result = run(f'{exec_path} {demo_args} --savepath {path_file}', shell=True, capture_output=True)
73  print(result.stdout.decode("utf-8"))
74  return readPath(path_file)
75 
76 # Create 3 plots of a path:
77 # 1. A 3D plot of the path as well as the spherical obstacles that the path avoids
78 # 2. A plot of the X-, Y-, and Z-coordinate as a function of path waypoint index
79 # 3. A plot of pitch and yaw as a function of path waypoint index
80 def plotPath(path, radius, spheres=True):
81  fig, axs = plt.subplots(3, 1, gridspec_kw={'height_ratios': [6, 1, 1]})
82  updateProjection(axs, axs[0])
83  axs[0].plot(path[:,0], path[:,1], path[:,2], color='k')
84  diameter = 2. * radius
85  n = int(10 // diameter)
86  if spheres:
87  for xi in range(-n, n):
88  for yi in range(-n, n):
89  for zi in range(-n, n):
90  sphere = Sphere([xi*diameter + radius, yi*diameter + radius, zi*diameter + radius], .75*radius)
91  sphere.plot_3d(axs[0], alpha=.2)
92  axs[0].set_xlabel('X')
93  axs[0].set_ylabel('Y')
94  axs[0].set_zlabel('Z')
95  axs[0].set_aspect('equal')
96  px, = axs[1].plot(path[:,0], color=cmap(0), label='X')
97  py, = axs[1].plot(path[:,1], color=cmap(1), label='Y')
98  pz, = axs[1].plot(path[:,2], color=cmap(2), label='Z')
99  axs[1].legend(handles=[px, py, pz])
100  if path.shape[1]>4:
101  ppitch, = axs[2].plot(path[:,3], color=cmap(0), label='pitch')
102  pyaw, = axs[2].plot(path[:,4], color=cmap(1), label='yaw')
103  axs[2].legend(handles=[ppitch, pyaw])
104  else:
105  axs[2].plot(path[:,3], color=cmap(0), label='yaw')
106  axs[2].set_ylabel('yaw')
107  plt.grid()
108  plt.show()
109  return axs
110 
111 # return the full path to the demo_DubinsAirplane executable or exit
112 def findExecutable(exec_name='demo_DubinsAirplane'):
113  # check if demo_DubinsAirplane is in the $PATH already
114  executable = shutil.which(exec_name)
115  # If not check in the current directory, parent directory, and "grandparent" directory (recursively)
116  # (This should discover demo_Veno for in-source builds.)
117  if executable == None:
118  for match in Path(__file__).parent.glob(f'**/{exec_name}'):
119  executable = str(match.resolve())
120  break
121  else:
122  for match in Path(__file__).parent.parent.glob(f'**/{exec_name}'):
123  executable = str(match.resolve())
124  break
125  else:
126  for match in Path(__file__).parent.parent.parent.glob(f'**/{exec_name}'):
127  executable = str(match.resolve())
128  break
129  else:
130  print(f"Could not find the {exec_name} executable; please update the path in this script")
131  exit(1)
132  return executable
133 
134 if __name__ == "__main__":
135  # hard code path to demo_DubinsAirplane executable here if findExecutable() fails to find it
136  exec_path = findExecutable()
137  radius = 2
138  # change command line arguments for demo_DubinsAirplane as needed here
139  path = getPath(exec_path,
140  plan='',
141  radius=radius,
142  planner="aps",
143  time=10,
144  start="0 0 -8 0 0",
145  goal="2 2 8 0 -3")
146  # or call readPath() on a precomputed path
147  #path = readPath('/my/path.dat')
148 
149  plotPath(path, radius)