-
-
Notifications
You must be signed in to change notification settings - Fork 261
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
ENH: add track markers from MV API and expose session info
- Loading branch information
Showing
15 changed files
with
470 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
.. _circuit_info: | ||
|
||
Circuit Information | ||
=================== | ||
|
||
.. autoclass:: fastf1.mvapi.CircuitInfo | ||
:members: | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -135,6 +135,7 @@ Contents | |
events | ||
api | ||
ergast | ||
circuit_info | ||
utils | ||
plotting | ||
livetiming | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
"""Draw a track map with numbered corners | ||
========================================= | ||
Use the position data of a single lap to draw a track map. | ||
Then annotate the map with corner numbers. | ||
""" | ||
############################################################################## | ||
# Import FastF1 and load the data. Use the telemetry from the fastest for the | ||
# track map. (You could also use any other lap instead.) | ||
|
||
import fastf1 | ||
|
||
import matplotlib.pyplot as plt | ||
import numpy as np | ||
|
||
|
||
session = fastf1.get_session(2023, 'Silverstone', 'Q') | ||
session.load() | ||
|
||
lap = session.laps.pick_fastest() | ||
pos = lap.get_pos_data() | ||
|
||
circuit_info = session.get_circuit_info() | ||
|
||
|
||
############################################################################## | ||
# Define a helper function for rotating points around the origin of the | ||
# coordinate system. | ||
# | ||
# The matrix ``[[cos, sin], [-sin, cos]]`` is called a rotation matrix. | ||
# | ||
# By matrix multiplication of the rotation matrix with a vector [x, y], a new | ||
# rotated vector [x_rot, y_rot] is obtained. | ||
# (See also: https://en.wikipedia.org/wiki/Rotation_matrix) | ||
|
||
def rotate(xy, *, angle): | ||
rot_mat = np.array([[np.cos(angle), np.sin(angle)], | ||
[-np.sin(angle), np.cos(angle)]]) | ||
return np.matmul(xy, rot_mat) | ||
|
||
|
||
############################################################################## | ||
# Get the coordinates of the track map from the telemetry of the lap and | ||
# rotate the coordinates using the rotation from ``circuit_info`` so that | ||
# the track map is oriented correctly. After that, plot the rotated track map. | ||
|
||
# Get an array of shape [n, 2] where n is the number of points and the second | ||
# axis is x and y. | ||
track = pos.loc[:, ('X', 'Y')].to_numpy() | ||
|
||
# Convert the rotation angle from degrees to radian. | ||
track_angle = circuit_info.rotation / 180 * np.pi | ||
|
||
# Rotate and plot the track map. | ||
rotated_track = rotate(track, angle=track_angle) | ||
plt.plot(rotated_track[:, 0], rotated_track[:, 1]) | ||
|
||
# sphinx_gallery_defer_figures | ||
|
||
|
||
############################################################################## | ||
# Finally, the corner markers are plotted. To plot the numbers next to the | ||
# track, an offset vector that points straight up is defined. This offset | ||
# vector is then rotated by the angle that is given for each corner marker. | ||
# A line and circular bubble are drawn and the corner marker text is printed | ||
# inside the bubble. | ||
|
||
offset_vector = [500, 0] # offset length is chosen arbitrarily to 'look good' | ||
|
||
# Iterate over all corners. | ||
for _, corner in circuit_info.corners.iterrows(): | ||
# Create a string from corner number and letter | ||
txt = f"{corner['Number']}{corner['Letter']}" | ||
|
||
# Convert the angle from degrees to radian. | ||
offset_angle = corner['Angle'] / 180 * np.pi | ||
|
||
# Rotate the offset vector so that it points sideways from the track. | ||
offset_x, offset_y = rotate(offset_vector, angle=offset_angle) | ||
|
||
# Add the offset to the position of the corner | ||
text_x = corner['X'] + offset_x | ||
text_y = corner['Y'] + offset_y | ||
|
||
# Rotate the text position equivalently to the rest of the track map | ||
text_x, text_y = rotate([text_x, text_y], angle=track_angle) | ||
|
||
# Rotate the center of the corner equivalently to the rest of the track map | ||
track_x, track_y = rotate([corner['X'], corner['Y']], angle=track_angle) | ||
|
||
# Draw a circle next to the track. | ||
plt.scatter(text_x, text_y, color='grey', s=140) | ||
|
||
# Draw a line from the track to this circle. | ||
plt.plot([track_x, text_x], [track_y, text_y], color='grey') | ||
|
||
# Finally, print the corner number inside the circle. | ||
plt.text(text_x, text_y, txt, | ||
va='center_baseline', ha='center', size='small', color='white') | ||
|
||
# sphinx_gallery_defer_figures | ||
|
||
|
||
############################################################################## | ||
# Add a title, remove tick labels to clean up the plot, set equal axis ratio, | ||
# so that the track is not distorted and show the plot. | ||
|
||
plt.title(session.event['Location']) | ||
plt.xticks([]) | ||
plt.yticks([]) | ||
plt.axis('equal') | ||
plt.show() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
"""Plot speed traces with corner annotations | ||
============================================ | ||
Plot the speed over the course of a lap and add annotations to mark corners. | ||
""" | ||
|
||
|
||
import matplotlib.pyplot as plt | ||
|
||
import fastf1.plotting | ||
|
||
|
||
# enable some matplotlib patches for plotting timedelta values and load | ||
# FastF1's default color scheme | ||
fastf1.plotting.setup_mpl(misc_mpl_mods=False) | ||
|
||
# load a session and its telemetry data | ||
session = fastf1.get_session(2021, 'Spanish Grand Prix', 'Q') | ||
session.load() | ||
|
||
############################################################################## | ||
# First, we select the fastest lap and get the car telemetry data for this | ||
# lap. | ||
|
||
fastest_lap = session.laps.pick_fastest() | ||
car_data = fastest_lap.get_car_data().add_distance() | ||
|
||
############################################################################## | ||
# Next, load the circuit info that includes the information about the location | ||
# of the corners. | ||
|
||
circuit_info = session.get_circuit_info() | ||
|
||
############################################################################## | ||
# Finally, we create a plot and plot the speed trace as well as the corner | ||
# markers. | ||
|
||
team_color = fastf1.plotting.team_color(fastest_lap['Team']) | ||
|
||
fig, ax = plt.subplots() | ||
ax.plot(car_data['Distance'], car_data['Speed'], | ||
color=team_color, label=fastest_lap['Driver']) | ||
|
||
# Draw vertical dotted lines at each corner that range from slightly below the | ||
# minimum speed to slightly above the maximum speed. | ||
v_min = car_data['Speed'].min() | ||
v_max = car_data['Speed'].max() | ||
ax.vlines(x=circuit_info.corners['Distance'], ymin=v_min-20, ymax=v_max+20, | ||
linestyles='dotted', colors='grey') | ||
|
||
# Plot the corner number just below each vertical line. | ||
# For corners that are very close together, the text may overlap. A more | ||
# complicated approach would be necessary to reliably prevent this. | ||
for _, corner in circuit_info.corners.iterrows(): | ||
txt = f"{corner['Number']}{corner['Letter']}" | ||
ax.text(corner['Distance'], v_min-30, txt, | ||
va='center_baseline', ha='center', size='small') | ||
|
||
ax.set_xlabel('Distance in m') | ||
ax.set_ylabel('Speed in km/h') | ||
ax.legend() | ||
|
||
# Manually adjust the y-axis limits to include the corner numbers, because | ||
# Matplotlib does not automatically account for text that was manually added. | ||
ax.set_ylim([v_min - 40, v_max + 20]) | ||
|
||
plt.show() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from fastf1.mvapi.data import get_circuit_info, CircuitInfo # noqa F401 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import requests.exceptions | ||
|
||
from typing import Optional | ||
|
||
from fastf1.mvapi.internals import _logger | ||
from fastf1.req import Cache | ||
from fastf1.version import __version__ | ||
|
||
|
||
PROTO = "https" | ||
HOST = "api.multiviewer.app" | ||
HEADERS = {'User-Agent': f'FastF1/{__version__}'} | ||
|
||
|
||
def _make_url(path: str): | ||
return f"{PROTO}://{HOST}{path}" | ||
|
||
|
||
def get_circuit(*, year: int, circuit_key: int) -> Optional[dict]: | ||
""":meta private: | ||
Request circuit data from the MultiViewer API and return the JSON | ||
response.""" | ||
url = _make_url(f"/api/v1/circuits/{circuit_key}/{year}") | ||
response = Cache.requests_get(url, headers=HEADERS) | ||
if response.status_code != 200: | ||
_logger.debug(f"[{response.status_code}] {response.content.decode()}") | ||
return None | ||
|
||
try: | ||
return response.json() | ||
except requests.exceptions.JSONDecodeError: | ||
return None |
Oops, something went wrong.