simple-autonomous-car 0.1.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- simple_autonomous_car/__init__.py +96 -0
- simple_autonomous_car/alerts/__init__.py +5 -0
- simple_autonomous_car/alerts/track_bounds_alert.py +276 -0
- simple_autonomous_car/car/__init__.py +5 -0
- simple_autonomous_car/car/car.py +234 -0
- simple_autonomous_car/constants.py +112 -0
- simple_autonomous_car/control/__init__.py +7 -0
- simple_autonomous_car/control/base_controller.py +152 -0
- simple_autonomous_car/control/controller_viz.py +282 -0
- simple_autonomous_car/control/pid_controller.py +153 -0
- simple_autonomous_car/control/pure_pursuit_controller.py +578 -0
- simple_autonomous_car/costmap/__init__.py +12 -0
- simple_autonomous_car/costmap/base_costmap.py +187 -0
- simple_autonomous_car/costmap/grid_costmap.py +507 -0
- simple_autonomous_car/costmap/inflation.py +126 -0
- simple_autonomous_car/detection/__init__.py +5 -0
- simple_autonomous_car/detection/error_detector.py +165 -0
- simple_autonomous_car/filters/__init__.py +7 -0
- simple_autonomous_car/filters/base_filter.py +119 -0
- simple_autonomous_car/filters/kalman_filter.py +131 -0
- simple_autonomous_car/filters/particle_filter.py +162 -0
- simple_autonomous_car/footprint/__init__.py +7 -0
- simple_autonomous_car/footprint/base_footprint.py +128 -0
- simple_autonomous_car/footprint/circular_footprint.py +73 -0
- simple_autonomous_car/footprint/rectangular_footprint.py +123 -0
- simple_autonomous_car/frames/__init__.py +21 -0
- simple_autonomous_car/frames/frenet.py +267 -0
- simple_autonomous_car/maps/__init__.py +9 -0
- simple_autonomous_car/maps/frenet_map.py +97 -0
- simple_autonomous_car/maps/grid_ground_truth_map.py +83 -0
- simple_autonomous_car/maps/grid_map.py +361 -0
- simple_autonomous_car/maps/ground_truth_map.py +64 -0
- simple_autonomous_car/maps/perceived_map.py +169 -0
- simple_autonomous_car/perception/__init__.py +5 -0
- simple_autonomous_car/perception/perception.py +107 -0
- simple_autonomous_car/planning/__init__.py +7 -0
- simple_autonomous_car/planning/base_planner.py +184 -0
- simple_autonomous_car/planning/goal_planner.py +261 -0
- simple_autonomous_car/planning/track_planner.py +199 -0
- simple_autonomous_car/sensors/__init__.py +6 -0
- simple_autonomous_car/sensors/base_sensor.py +105 -0
- simple_autonomous_car/sensors/lidar_sensor.py +145 -0
- simple_autonomous_car/track/__init__.py +5 -0
- simple_autonomous_car/track/track.py +463 -0
- simple_autonomous_car/visualization/__init__.py +25 -0
- simple_autonomous_car/visualization/alert_viz.py +316 -0
- simple_autonomous_car/visualization/utils.py +169 -0
- simple_autonomous_car-0.1.2.dist-info/METADATA +324 -0
- simple_autonomous_car-0.1.2.dist-info/RECORD +50 -0
- simple_autonomous_car-0.1.2.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
"""Visualization utilities for alert systems."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
import matplotlib.pyplot as plt
|
|
6
|
+
import numpy as np
|
|
7
|
+
from matplotlib.patches import Polygon
|
|
8
|
+
|
|
9
|
+
from simple_autonomous_car.maps.frenet_map import FrenetMap
|
|
10
|
+
from simple_autonomous_car.perception.perception import PerceptionPoints
|
|
11
|
+
from simple_autonomous_car.track.track import Track
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class AlertVisualizer:
|
|
15
|
+
"""Visualization utilities for alert systems."""
|
|
16
|
+
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
track: Track,
|
|
20
|
+
frenet_map: FrenetMap | None = None,
|
|
21
|
+
figsize: tuple = (16, 8),
|
|
22
|
+
):
|
|
23
|
+
"""
|
|
24
|
+
Initialize alert visualizer.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
track: Track object
|
|
28
|
+
frenet_map: Optional Frenet map (will be created if not provided)
|
|
29
|
+
figsize: Figure size
|
|
30
|
+
"""
|
|
31
|
+
self.track = track
|
|
32
|
+
self.frenet_map = frenet_map if frenet_map is not None else FrenetMap(track)
|
|
33
|
+
self.figsize = figsize
|
|
34
|
+
self.fig: Any = None
|
|
35
|
+
self.axes: Any = None
|
|
36
|
+
|
|
37
|
+
def create_figure(self) -> None:
|
|
38
|
+
"""Create figure with world and car frame views."""
|
|
39
|
+
fig, (ax_world, ax_car) = plt.subplots(1, 2, figsize=self.figsize)
|
|
40
|
+
self.fig = fig
|
|
41
|
+
self.ax_world = ax_world
|
|
42
|
+
self.ax_car = ax_car
|
|
43
|
+
self._setup_axes()
|
|
44
|
+
|
|
45
|
+
def _setup_axes(self) -> None:
|
|
46
|
+
"""Set up axis labels and properties."""
|
|
47
|
+
# World frame
|
|
48
|
+
self.ax_world.set_title("World Frame - Track Bounds Alert", fontsize=14, fontweight="bold")
|
|
49
|
+
self.ax_world.set_xlabel("X (m)")
|
|
50
|
+
self.ax_world.set_ylabel("Y (m)")
|
|
51
|
+
self.ax_world.set_aspect("equal")
|
|
52
|
+
self.ax_world.grid(True, alpha=0.3)
|
|
53
|
+
|
|
54
|
+
# Car frame
|
|
55
|
+
self.ax_car.set_title("Car Frame - Perception Points", fontsize=14, fontweight="bold")
|
|
56
|
+
self.ax_car.set_xlabel("Forward (m)")
|
|
57
|
+
self.ax_car.set_ylabel("Left (m)")
|
|
58
|
+
self.ax_car.set_aspect("equal")
|
|
59
|
+
self.ax_car.grid(True, alpha=0.3)
|
|
60
|
+
|
|
61
|
+
def plot_track(self, ax: Any = None) -> None:
|
|
62
|
+
"""
|
|
63
|
+
Plot full track boundaries.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
ax: Matplotlib axis (uses self.ax_world if None)
|
|
67
|
+
"""
|
|
68
|
+
if ax is None:
|
|
69
|
+
ax = self.ax_world
|
|
70
|
+
|
|
71
|
+
ax.plot(
|
|
72
|
+
self.track.inner_bound[:, 0],
|
|
73
|
+
self.track.inner_bound[:, 1],
|
|
74
|
+
"k-",
|
|
75
|
+
linewidth=2.5,
|
|
76
|
+
label="Map (Inner)",
|
|
77
|
+
alpha=0.8,
|
|
78
|
+
)
|
|
79
|
+
ax.plot(
|
|
80
|
+
self.track.outer_bound[:, 0],
|
|
81
|
+
self.track.outer_bound[:, 1],
|
|
82
|
+
"k-",
|
|
83
|
+
linewidth=2.5,
|
|
84
|
+
label="Map (Outer)",
|
|
85
|
+
alpha=0.8,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
def plot_perception(
|
|
89
|
+
self,
|
|
90
|
+
perception_points: PerceptionPoints,
|
|
91
|
+
car_state: Any,
|
|
92
|
+
car: Any,
|
|
93
|
+
highlight_alerts: bool = False,
|
|
94
|
+
alert_points: list[Any] | None = None,
|
|
95
|
+
) -> None:
|
|
96
|
+
"""
|
|
97
|
+
Plot perception points in both world and car frames.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
perception_points: Perception points
|
|
101
|
+
car: Car object
|
|
102
|
+
highlight_alerts: Whether to highlight alert points
|
|
103
|
+
alert_points: List of alert points (if highlight_alerts is True)
|
|
104
|
+
"""
|
|
105
|
+
# Ensure points are in ego frame
|
|
106
|
+
if perception_points.frame != "ego":
|
|
107
|
+
perception_points = perception_points.to_ego_frame(car.state)
|
|
108
|
+
|
|
109
|
+
# Car frame view
|
|
110
|
+
if len(perception_points) > 0:
|
|
111
|
+
self.ax_car.scatter(
|
|
112
|
+
perception_points.points[:, 0],
|
|
113
|
+
perception_points.points[:, 1],
|
|
114
|
+
c="red",
|
|
115
|
+
s=15,
|
|
116
|
+
alpha=0.6,
|
|
117
|
+
label="Perception",
|
|
118
|
+
marker=".",
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# World frame view
|
|
122
|
+
if len(perception_points) > 0:
|
|
123
|
+
perception_global = perception_points.to_global_frame(car.state)
|
|
124
|
+
self.ax_world.scatter(
|
|
125
|
+
perception_global.points[:, 0],
|
|
126
|
+
perception_global.points[:, 1],
|
|
127
|
+
c="red",
|
|
128
|
+
s=15,
|
|
129
|
+
alpha=0.6,
|
|
130
|
+
label="Perception",
|
|
131
|
+
marker=".",
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
# Highlight alert points if requested
|
|
135
|
+
if highlight_alerts and alert_points:
|
|
136
|
+
alert_points_global = []
|
|
137
|
+
for alert_point in alert_points:
|
|
138
|
+
s, d = alert_point["s"], alert_point["d"]
|
|
139
|
+
point_global = self.frenet_map.frenet_frame.frenet_to_global(s, d)
|
|
140
|
+
alert_points_global.append(point_global)
|
|
141
|
+
|
|
142
|
+
if alert_points_global:
|
|
143
|
+
alert_points_global_arr = np.asarray(alert_points_global, dtype=np.float64) # type: ignore[assignment]
|
|
144
|
+
self.ax_world.scatter(
|
|
145
|
+
alert_points_global_arr[:, 0],
|
|
146
|
+
alert_points_global_arr[:, 1],
|
|
147
|
+
c="orange",
|
|
148
|
+
s=150,
|
|
149
|
+
marker="x",
|
|
150
|
+
linewidths=3,
|
|
151
|
+
label="Alert Points",
|
|
152
|
+
zorder=10,
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
def plot_car(self, car: Any, ax: Any = None) -> None:
|
|
156
|
+
"""
|
|
157
|
+
Plot car position.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
car: Car object
|
|
161
|
+
ax: Matplotlib axis (uses self.ax_world if None)
|
|
162
|
+
"""
|
|
163
|
+
if ax is None:
|
|
164
|
+
ax = self.ax_world
|
|
165
|
+
|
|
166
|
+
car_corners = car.get_corners()
|
|
167
|
+
car_poly = Polygon(car_corners, closed=True, color="blue", alpha=0.7)
|
|
168
|
+
ax.add_patch(car_poly)
|
|
169
|
+
|
|
170
|
+
# Also plot in car frame (at origin)
|
|
171
|
+
car_corners_car = np.array([[-2, -0.9], [-2, 0.9], [2, 0.9], [2, -0.9]])
|
|
172
|
+
car_poly_car = Polygon(car_corners_car, closed=True, color="blue", alpha=0.7)
|
|
173
|
+
self.ax_car.add_patch(car_poly_car)
|
|
174
|
+
|
|
175
|
+
def plot_alert_result(
|
|
176
|
+
self,
|
|
177
|
+
alert_result: dict[str, Any],
|
|
178
|
+
perception_points: PerceptionPoints,
|
|
179
|
+
car_state: Any,
|
|
180
|
+
car: Any,
|
|
181
|
+
) -> None:
|
|
182
|
+
"""
|
|
183
|
+
Plot complete alert visualization.
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
alert_result: Result from alert system check
|
|
187
|
+
perception_points: Perception points
|
|
188
|
+
car: Car object
|
|
189
|
+
"""
|
|
190
|
+
self.ax_world.clear()
|
|
191
|
+
self.ax_car.clear()
|
|
192
|
+
self._setup_axes()
|
|
193
|
+
|
|
194
|
+
# Plot track
|
|
195
|
+
self.plot_track()
|
|
196
|
+
|
|
197
|
+
# Plot perception with alerts
|
|
198
|
+
self.plot_perception(
|
|
199
|
+
perception_points,
|
|
200
|
+
car_state,
|
|
201
|
+
car,
|
|
202
|
+
highlight_alerts=True,
|
|
203
|
+
alert_points=alert_result.get("alert_points", []),
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
# Plot car
|
|
207
|
+
self.plot_car(car)
|
|
208
|
+
|
|
209
|
+
# Add alert info as text
|
|
210
|
+
if alert_result.get("has_critical"):
|
|
211
|
+
status_text = f"CRITICAL: Max deviation = {alert_result['max_deviation']:.2f}m"
|
|
212
|
+
color = "red"
|
|
213
|
+
elif alert_result.get("has_warning"):
|
|
214
|
+
status_text = f"WARNING: Max deviation = {alert_result['max_deviation']:.2f}m"
|
|
215
|
+
color = "orange"
|
|
216
|
+
else:
|
|
217
|
+
status_text = f"OK: Max deviation = {alert_result['max_deviation']:.2f}m"
|
|
218
|
+
color = "green"
|
|
219
|
+
|
|
220
|
+
self.ax_world.text(
|
|
221
|
+
0.02,
|
|
222
|
+
0.98,
|
|
223
|
+
status_text,
|
|
224
|
+
transform=self.ax_world.transAxes,
|
|
225
|
+
fontsize=12,
|
|
226
|
+
verticalalignment="top",
|
|
227
|
+
bbox=dict(boxstyle="round", facecolor="white", alpha=0.8, edgecolor=color, linewidth=2),
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
# Set limits
|
|
231
|
+
all_points = np.vstack([self.track.inner_bound, self.track.outer_bound])
|
|
232
|
+
padding = 10.0
|
|
233
|
+
self.ax_world.set_xlim(
|
|
234
|
+
np.min(all_points[:, 0]) - padding, np.max(all_points[:, 0]) + padding
|
|
235
|
+
)
|
|
236
|
+
self.ax_world.set_ylim(
|
|
237
|
+
np.min(all_points[:, 1]) - padding, np.max(all_points[:, 1]) + padding
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
self.ax_car.set_xlim(-50, 50)
|
|
241
|
+
self.ax_car.set_ylim(-50, 50)
|
|
242
|
+
|
|
243
|
+
self.ax_world.legend(loc="upper right", fontsize=9)
|
|
244
|
+
self.ax_car.legend(loc="upper right", fontsize=9)
|
|
245
|
+
|
|
246
|
+
plt.tight_layout()
|
|
247
|
+
|
|
248
|
+
def plot_alert_history(self, alert_history: list) -> None:
|
|
249
|
+
"""
|
|
250
|
+
Plot alert history over time.
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
alert_history: List of alert history entries
|
|
254
|
+
"""
|
|
255
|
+
if not alert_history:
|
|
256
|
+
return
|
|
257
|
+
|
|
258
|
+
fig, ax = plt.subplots(figsize=(12, 6))
|
|
259
|
+
|
|
260
|
+
steps = [h["step"] for h in alert_history]
|
|
261
|
+
deviations = [h["max_deviation"] for h in alert_history]
|
|
262
|
+
warnings = [h.get("has_warning", False) for h in alert_history]
|
|
263
|
+
criticals = [h.get("has_critical", False) for h in alert_history]
|
|
264
|
+
|
|
265
|
+
ax.plot(steps, deviations, "b-", label="Max Deviation", linewidth=2)
|
|
266
|
+
|
|
267
|
+
# Add threshold lines if available
|
|
268
|
+
if hasattr(self, "warning_threshold"):
|
|
269
|
+
ax.axhline(
|
|
270
|
+
y=self.warning_threshold,
|
|
271
|
+
color="orange",
|
|
272
|
+
linestyle="--",
|
|
273
|
+
label="Warning Threshold",
|
|
274
|
+
)
|
|
275
|
+
if hasattr(self, "critical_threshold"):
|
|
276
|
+
ax.axhline(
|
|
277
|
+
y=self.critical_threshold,
|
|
278
|
+
color="red",
|
|
279
|
+
linestyle="--",
|
|
280
|
+
label="Critical Threshold",
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
# Fill warning and critical zones
|
|
284
|
+
if any(warnings):
|
|
285
|
+
ax.fill_between(
|
|
286
|
+
steps,
|
|
287
|
+
0,
|
|
288
|
+
deviations,
|
|
289
|
+
where=np.array(warnings), # type: ignore[arg-type]
|
|
290
|
+
alpha=0.3,
|
|
291
|
+
color="orange",
|
|
292
|
+
label="Warning Zone",
|
|
293
|
+
)
|
|
294
|
+
if any(criticals):
|
|
295
|
+
ax.fill_between(
|
|
296
|
+
steps,
|
|
297
|
+
0,
|
|
298
|
+
deviations,
|
|
299
|
+
where=np.array(criticals), # type: ignore[arg-type]
|
|
300
|
+
alpha=0.3,
|
|
301
|
+
color="red",
|
|
302
|
+
label="Critical Zone",
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
ax.set_xlabel("Simulation Step")
|
|
306
|
+
ax.set_ylabel("Max Deviation (m)")
|
|
307
|
+
ax.set_title("Alert History - Deviation Over Time")
|
|
308
|
+
ax.legend()
|
|
309
|
+
ax.grid(True, alpha=0.3)
|
|
310
|
+
plt.tight_layout()
|
|
311
|
+
plt.show()
|
|
312
|
+
|
|
313
|
+
def show(self) -> None:
|
|
314
|
+
"""Display the figure."""
|
|
315
|
+
if self.fig is not None:
|
|
316
|
+
plt.show()
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"""Utility visualization functions for non-component data (perception, car)."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
import matplotlib.pyplot as plt
|
|
6
|
+
import numpy as np
|
|
7
|
+
from matplotlib.patches import Polygon
|
|
8
|
+
|
|
9
|
+
from simple_autonomous_car.car.car import Car, CarState
|
|
10
|
+
from simple_autonomous_car.perception.perception import PerceptionPoints
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def plot_perception(
|
|
14
|
+
perception_points: PerceptionPoints,
|
|
15
|
+
car_state: CarState,
|
|
16
|
+
ax: plt.Axes | None = None,
|
|
17
|
+
frame: str = "global",
|
|
18
|
+
color: str = "red",
|
|
19
|
+
label: str = "Perception",
|
|
20
|
+
**kwargs: Any,
|
|
21
|
+
) -> plt.Axes:
|
|
22
|
+
"""
|
|
23
|
+
Plot perception points from sensors.
|
|
24
|
+
|
|
25
|
+
Parameters
|
|
26
|
+
----------
|
|
27
|
+
perception_points : PerceptionPoints
|
|
28
|
+
Perception data points.
|
|
29
|
+
car_state : CarState
|
|
30
|
+
Car state for coordinate transformation.
|
|
31
|
+
ax : plt.Axes, optional
|
|
32
|
+
Axes to plot on. If None, creates new figure.
|
|
33
|
+
frame : str, default="global"
|
|
34
|
+
Frame to plot in: "global" or "ego".
|
|
35
|
+
color : str, default="red"
|
|
36
|
+
Color for perception points.
|
|
37
|
+
label : str, default="Perception"
|
|
38
|
+
Label for legend.
|
|
39
|
+
**kwargs
|
|
40
|
+
Additional arguments passed to scatter.
|
|
41
|
+
|
|
42
|
+
Returns
|
|
43
|
+
-------
|
|
44
|
+
plt.Axes
|
|
45
|
+
Axes object with perception plotted.
|
|
46
|
+
"""
|
|
47
|
+
if ax is None:
|
|
48
|
+
fig, ax = plt.subplots(figsize=(10, 8))
|
|
49
|
+
|
|
50
|
+
if len(perception_points.points) == 0:
|
|
51
|
+
return ax
|
|
52
|
+
|
|
53
|
+
# Convert to desired frame
|
|
54
|
+
if frame == "global":
|
|
55
|
+
if perception_points.frame != "global":
|
|
56
|
+
points = perception_points.to_global_frame(car_state).points
|
|
57
|
+
else:
|
|
58
|
+
points = perception_points.points
|
|
59
|
+
else: # ego frame
|
|
60
|
+
if perception_points.frame != "ego":
|
|
61
|
+
points = perception_points.to_ego_frame(car_state).points
|
|
62
|
+
else:
|
|
63
|
+
points = perception_points.points
|
|
64
|
+
|
|
65
|
+
# Extract alpha from kwargs if provided, otherwise use default
|
|
66
|
+
scatter_alpha = kwargs.pop("alpha", 0.6)
|
|
67
|
+
|
|
68
|
+
ax.scatter(
|
|
69
|
+
points[:, 0],
|
|
70
|
+
points[:, 1],
|
|
71
|
+
c=color,
|
|
72
|
+
s=25,
|
|
73
|
+
alpha=scatter_alpha,
|
|
74
|
+
label=label,
|
|
75
|
+
marker=".",
|
|
76
|
+
edgecolors="darkred" if color in ["crimson", "red"] else None,
|
|
77
|
+
linewidths=0.5 if color in ["crimson", "red"] else 0,
|
|
78
|
+
**kwargs,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
ax.set_aspect("equal")
|
|
82
|
+
ax.grid(True, alpha=0.3)
|
|
83
|
+
|
|
84
|
+
if frame == "global":
|
|
85
|
+
ax.set_xlabel("X (m)")
|
|
86
|
+
ax.set_ylabel("Y (m)")
|
|
87
|
+
else:
|
|
88
|
+
ax.set_xlabel("Forward (m)")
|
|
89
|
+
ax.set_ylabel("Left (m)")
|
|
90
|
+
|
|
91
|
+
return ax
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def plot_car(
|
|
95
|
+
car: Car,
|
|
96
|
+
ax: plt.Axes | None = None,
|
|
97
|
+
frame: str = "global",
|
|
98
|
+
color: str = "blue",
|
|
99
|
+
show_heading: bool = True,
|
|
100
|
+
**kwargs: Any,
|
|
101
|
+
) -> plt.Axes:
|
|
102
|
+
"""
|
|
103
|
+
Plot car position and orientation.
|
|
104
|
+
|
|
105
|
+
Parameters
|
|
106
|
+
----------
|
|
107
|
+
car : Car
|
|
108
|
+
Car to plot.
|
|
109
|
+
ax : plt.Axes, optional
|
|
110
|
+
Axes to plot on. If None, creates new figure.
|
|
111
|
+
frame : str, default="global"
|
|
112
|
+
Frame to plot in: "global" or "ego".
|
|
113
|
+
color : str, default="blue"
|
|
114
|
+
Color for car.
|
|
115
|
+
show_heading : bool, default=True
|
|
116
|
+
Whether to show heading arrow.
|
|
117
|
+
**kwargs
|
|
118
|
+
Additional arguments passed to Polygon.
|
|
119
|
+
|
|
120
|
+
Returns
|
|
121
|
+
-------
|
|
122
|
+
plt.Axes
|
|
123
|
+
Axes object with car plotted.
|
|
124
|
+
"""
|
|
125
|
+
if ax is None:
|
|
126
|
+
fig, ax = plt.subplots(figsize=(10, 8))
|
|
127
|
+
|
|
128
|
+
if frame == "global":
|
|
129
|
+
car_corners = car.get_corners()
|
|
130
|
+
car_pos = car.state.position()
|
|
131
|
+
else: # ego frame
|
|
132
|
+
car_corners = np.array([[-2, -0.9], [-2, 0.9], [2, 0.9], [2, -0.9]])
|
|
133
|
+
car_pos = np.array([0.0, 0.0])
|
|
134
|
+
|
|
135
|
+
car_poly = Polygon(car_corners, closed=True, color=color, alpha=0.7, label="Car", **kwargs)
|
|
136
|
+
ax.add_patch(car_poly)
|
|
137
|
+
|
|
138
|
+
if show_heading and frame == "global":
|
|
139
|
+
# Draw heading arrow
|
|
140
|
+
arrow_length = 3.0
|
|
141
|
+
dx = arrow_length * np.cos(car.state.heading)
|
|
142
|
+
dy = arrow_length * np.sin(car.state.heading)
|
|
143
|
+
ax.arrow(
|
|
144
|
+
car_pos[0],
|
|
145
|
+
car_pos[1],
|
|
146
|
+
dx,
|
|
147
|
+
dy,
|
|
148
|
+
head_width=1.0,
|
|
149
|
+
head_length=0.8,
|
|
150
|
+
fc=color,
|
|
151
|
+
ec=color,
|
|
152
|
+
)
|
|
153
|
+
elif show_heading and frame == "ego":
|
|
154
|
+
# Draw forward arrow in ego frame
|
|
155
|
+
ax.arrow(
|
|
156
|
+
0,
|
|
157
|
+
0,
|
|
158
|
+
3.0,
|
|
159
|
+
0,
|
|
160
|
+
head_width=1.2,
|
|
161
|
+
head_length=1.0,
|
|
162
|
+
fc=color,
|
|
163
|
+
ec=color,
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
ax.set_aspect("equal")
|
|
167
|
+
ax.grid(True, alpha=0.3)
|
|
168
|
+
|
|
169
|
+
return ax
|