16 from datetime
import datetime
19 from pathlib
import Path
21 from typing
import Any, cast, Dict, List, TypedDict
23 import matplotlib.pyplot
as plt
24 from nav2_smac_planner.lattice_primitives
import constants
25 from nav2_smac_planner.lattice_primitives.lattice_generator
import ConfigDict, LatticeGenerator
26 from nav2_smac_planner.lattice_primitives.trajectory
import Trajectory
29 logging.basicConfig(level=logging.INFO)
30 logger = logging.getLogger(__name__)
36 lattice_metadata: Dict[str, Any]
37 primitives: List[Dict[str, Any]]
40 def handle_arg_parsing() -> argparse.Namespace:
42 Handle the parsing of arguments.
47 An object containing all parsed arguments
50 parser = argparse.ArgumentParser(
51 description=
"Generate motionprimitives for Nav2's State Lattice Planner"
56 default=
'./config.json',
57 help=
'The config file containing the ' 'parameters to be used',
62 default=
'./output.json',
63 help=
'The output file containing the ' 'trajectory data',
68 default=
'./visualizations',
69 help=
'The output folder where the '
70 'visualizations of the trajectories will be saved',
73 return parser.parse_args()
76 def create_heading_angle_list(minimal_set_trajectories: Dict[float, List[Trajectory]]
79 Create a sorted list of heading angles from the minimal trajectory set.
83 minimal_set_trajectories: dict
84 The minimal spanning set
89 A sorted list of heading angles
92 heading_angles = set(minimal_set_trajectories.keys())
93 return sorted(heading_angles, key=
lambda x: (x < 0, x))
96 def read_config(config_path: Path) -> ConfigDict:
98 Read in the user defined parameters via JSON.
103 Path to the config file
108 Dictionary containing the user defined parameters
111 with open(config_path)
as config_file:
112 config = json.load(config_file)
114 return cast(ConfigDict, config)
117 def create_header(config: ConfigDict, minimal_set_trajectories: Dict[float, List[Trajectory]]
120 Create a dict containing all the fields to populate the header with.
125 The dict containing user specified parameters
126 minimal_set_trajectories: dict
127 The minimal spanning set
132 A dictionary containing the fields to populate the header with
135 header_dict: HeaderDict = {
136 'version': constants.VERSION,
137 'date_generated': datetime.today().strftime(
'%Y-%m-%d'),
138 'lattice_metadata': {},
142 for key, value
in config.items():
143 header_dict[
'lattice_metadata'][key] = value
145 heading_angles = create_heading_angle_list(minimal_set_trajectories)
146 adjusted_heading_angles = [
147 angle + 2 * np.pi
if angle < 0
else angle
for angle
in heading_angles
150 header_dict[
'lattice_metadata'][
'heading_angles'] = adjusted_heading_angles
156 output_path: Path, minimal_set_trajectories: Dict[float, List[Trajectory]], config: ConfigDict
159 Write the minimal spanning set to an output file.
164 The output file for the json data
165 minimal_set_trajectories: dict
166 The minimal spanning set
168 The dict containing user specified parameters
171 output_dict = create_header(config, minimal_set_trajectories)
173 trajectory_start_angles = list(minimal_set_trajectories.keys())
175 heading_angle_list = create_heading_angle_list(minimal_set_trajectories)
176 heading_lookup = {angle: idx
for idx, angle
in enumerate(heading_angle_list)}
179 for start_angle
in sorted(trajectory_start_angles, key=
lambda x: (x < 0, x)):
181 for trajectory
in sorted(
182 minimal_set_trajectories[start_angle], key=
lambda x: x.parameters.end_angle
185 traj_info: Dict[str, Any] = {}
186 traj_info[
'trajectory_id'] = idx
187 traj_info[
'start_angle_index'] = heading_lookup[
188 trajectory.parameters.start_angle
190 traj_info[
'end_angle_index'] = heading_lookup[
191 trajectory.parameters.end_angle
193 traj_info[
'left_turn'] = bool(trajectory.parameters.left_turn)
194 traj_info[
'trajectory_radius'] = trajectory.parameters.turning_radius
195 traj_info[
'trajectory_length'] = round(
196 trajectory.parameters.total_length, 5
198 traj_info[
'arc_length'] = round(trajectory.parameters.arc_length, 5)
199 traj_info[
'straight_length'] = round(
200 trajectory.parameters.start_straight_length
201 + trajectory.parameters.end_straight_length,
204 traj_info[
'poses'] = trajectory.path.to_output_format()
206 output_dict[
'primitives'].append(traj_info)
209 output_dict[
'lattice_metadata'][
'number_of_trajectories'] = idx
211 with open(output_path,
'w')
as output_file:
212 json.dump(output_dict, output_file, indent=
'\t')
215 def save_visualizations(
216 visualizations_folder: Path, minimal_set_trajectories: Dict[float, List[Trajectory]]
219 Draw the visualizations for every trajectory and save it as an image.
223 visualizations_folder: Path
224 The path to the folder for where to save the images
225 minimal_set_trajectories: dict
226 The minimal spanning set
230 visualizations_folder.mkdir(exist_ok=
True)
232 for start_angle
in minimal_set_trajectories.keys():
234 for trajectory
in minimal_set_trajectories[start_angle]:
235 plt.plot(trajectory.path.xs, trajectory.path.ys,
'b')
239 left_x, right_x = plt.xlim()
240 left_y, right_y = plt.ylim()
242 output_path = visualizations_folder /
'all_trajectories.png'
243 plt.savefig(output_path)
246 for start_angle
in minimal_set_trajectories.keys():
248 angle_in_deg = np.rad2deg(start_angle)
250 if start_angle < 0
or start_angle > np.pi / 2:
253 for trajectory
in minimal_set_trajectories[start_angle]:
254 plt.plot(trajectory.path.xs, trajectory.path.ys,
'b')
255 plt.xlim(left_x, right_x)
256 plt.ylim(left_y, right_y)
260 output_path = visualizations_folder / f
'{angle_in_deg}.png'
261 plt.savefig(output_path)
265 if __name__ ==
'__main__':
267 args = handle_arg_parsing()
268 config = read_config(args.config)
271 lattice_gen = LatticeGenerator(config)
272 minimal_set_trajectories = lattice_gen.run()
273 print(f
'Finished Generating. Took {time.time() - start} seconds')
275 write_to_json(args.output, minimal_set_trajectories, config)
276 save_visualizations(args.visualizations, minimal_set_trajectories)