diff --git a/.idea/.gitignore b/.gitignore similarity index 77% rename from .idea/.gitignore rename to .gitignore index 26d33521af10bcc7fd8cea344038eaaeb78d0ef5..703bf10709f4c2d066bdaea72d6324fc668b24bd 100644 --- a/.idea/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ # Default ignored files /shelf/ /workspace.xml +/__pycache__/* \ No newline at end of file diff --git a/DataAnalyser.py b/DataAnalyser.py index 2a2af99b02c46ee2f8931a2bfb99fe304370fdaf..63a244b26d21a3f90a4bed76f0e9ff056b19b27c 100644 --- a/DataAnalyser.py +++ b/DataAnalyser.py @@ -19,8 +19,6 @@ class DataAnalyser(DataHandler): return overtakesInRaces def analyseRaceForOvertakes(self, race: Session): - - # Collect grid positions allLapPositions: List[dict[str, int]] = [] @@ -46,10 +44,11 @@ class DataAnalyser(DataHandler): if self.activateDebugOvertakeAnalysis: print(f"\nLap: 0") - for position in range(len(gridPositions)): + for i in range(len(gridPositions)): + position: int = i + 1 gridPositions.values() - driverAtPosition = list(gridPositions.keys())[list(gridPositions.values()).index(position + 1)] # get dictionary keys (driverIds) by values (current race position) - print(f"P{position + 1}: {driverAtPosition}") + driverAtPosition = self.getDriverByPositionFromMap(gridPositions, position) + print(f"P{position}: {driverAtPosition}") return gridPositions @@ -75,10 +74,11 @@ class DataAnalyser(DataHandler): for raceLapIndex in range(race.total_laps): print(f"\nLap: {raceLapIndex + 1}") runningLapPositions = runningLapsPositions[raceLapIndex] - for position in range(len(runningLapPositions)): + for i in range(len(runningLapPositions)): runningLapPositions.values() - driverAtPosition = list(runningLapPositions.keys())[list(runningLapPositions.values()).index(position + 1)] - print(f"P{position + 1}: {driverAtPosition}") + position: int = i + 1 + driverAtPosition = self.getDriverByPositionFromMap(runningLapPositions, position) + print(f"P{position}: {driverAtPosition}") return runningLapsPositions @@ -110,20 +110,30 @@ class DataAnalyser(DataHandler): #print(orderToSort[i]) - overtakes: int = 0 - index: int = 0 - while index < len(orderToSort) - 1: - if orderToSort[index] > orderToSort[index + 1]: - temp: int = orderToSort[index] - orderToSort[index] = orderToSort[index + 1] - orderToSort[index + 1] = temp + i: int = 0 + while i < len(orderToSort) - 1: + if self.countOutPitstops: + driverPittedThisLap: bool = False + currentDriversEndPosition = orderToSort[i] + driver = self.getDriverByPositionFromMap(endOrder, currentDriversEndPosition) + + + if orderToSort[i] > orderToSort[i + 1]: + temp: int = orderToSort[i] + orderToSort[i] = orderToSort[i + 1] + orderToSort[i + 1] = temp overtakes += 1 - index = -1 - index += 1 + i = -1 + i += 1 return overtakes + def getDriverByPositionFromMap(self, positions: dict[str, int], position: int): + driver = list(positions.keys())[list(positions.values()).index(position)] + return driver + + # ===== Weather ===== def analyseRacesForWeather(self, races: List[Session]): @@ -183,6 +193,8 @@ class DataAnalyser(DataHandler): if noStartingCompoundsLeft: return currentLap currentLap += 1 + return -1 # no lap without compound found; all laps use same compound type + def getCompoundsForRace(self, race: Session): compoundsPerLap: List[List[str]] = [[]] @@ -225,6 +237,7 @@ class DataAnalyser(DataHandler): if compound not in filter: return currentLap currentLap += 1 + return -1 # no lap with opposite compound found; all laps use same compound type def setFilter(self, startingCompound: str): if startingCompound == 'SLICK': return self.slickCompounds diff --git a/DataHandler.py b/DataHandler.py index c718a439337e95e47d859e77b70da8cc75129b50..c62cc6f71489986b6fb624f1c696be8e64eadccb 100644 --- a/DataHandler.py +++ b/DataHandler.py @@ -7,4 +7,5 @@ class DataHandler(ABC): self.invalidDriverId = "NO_DRIVER" self.enableTestingMode = False # only partially import data to speed up testing, because HTTP 429 limits import speed self.activateDebugOvertakeAnalysis = False - self.slickCompounds = ('SOFT', 'MEDIUM', 'HARD') \ No newline at end of file + self.slickCompounds = ('SOFT', 'MEDIUM', 'HARD') + self.countOutPitstops = True \ No newline at end of file diff --git a/DataPlotter.py b/DataPlotter.py index 31bc35cf563d0c66dc8f912e9b1d840614860c33..19bf93c625474f96a03dd502378c5260b11ad13c 100644 --- a/DataPlotter.py +++ b/DataPlotter.py @@ -12,7 +12,7 @@ from DataHandler import DataHandler class DataPlotter(DataHandler): - def plotOvertakesWithTireChangeWindow(self, overtakesPerLap: List[int], earliestTireChange: int, latestTireChange: int): + def plotOvertakesWithTireChangeWindow(self, overtakesPerLap: List[int], earliestTireChange: int, latestTireChange: int, raceName: str): overtakesPerLap.insert(0, 0) # Insert 0th lap, which cannot have overtakes laps: int = len(overtakesPerLap) @@ -24,6 +24,7 @@ class DataPlotter(DataHandler): axis.set_xlabel('Lap') axis.set_ylabel('Position changes in lap') + axis.set_title(raceName) major_ticks_laps = np.arange(0, laps, 5) major_ticks_overtakes = np.arange(0, max(overtakesPerLap) + 1, 5) diff --git a/Todos.md b/Todos.md new file mode 100644 index 0000000000000000000000000000000000000000..ff505f80d7da625bd80c7342b876e00425408a00 --- /dev/null +++ b/Todos.md @@ -0,0 +1,15 @@ +# Todo (sorted by priority) +- Fetch rain races via API, not Reddit +- Adjust for pitstop discreptancies +- Include safety car periods & driver crashes +- Automatically determine if race is DWR or SWR +- Adjust for situations like Canada 2024 -> 2 drivers taking wets at the start does not constitute a weather change + - Implement actual 1/2 of drivers changing compounds rule +- Save data in CSV file +- Adjust for finding multiple weather changes in a race, not just one +- Read out number of drivers participating in session, rather than hardcoding number of drivers (since it might change from 20 to more (or less) in future) +- Also read out direct weather data from API + + +# Done +- Automatically title graph by race name (no more hardcoding the graph name) \ No newline at end of file diff --git a/main.py b/main.py index cb9736e123d740785f6f2b6b62beae1d78482283..a74cedabdaaad05459f7ef12fe1d413099c8110a 100644 --- a/main.py +++ b/main.py @@ -19,10 +19,10 @@ class Main: def main(self): # contains pairs of season & race number to identify specific races race_indexes = [ - [2022, 4] # Imola 2022 (DWR) - # [2024, 7] # Imola 2024 (SWR) - # [2024, 9] # Canada 2024 (DWR) - # + [2022, 4], # Imola 2022 (DWR) + [2024, 7], # Imola 2024 (SWR) + [2024, 9], # Canada 2024 (DWR) + [2023, 8] # Canada 2023 (SWR) # TODO: add more races ] @@ -31,10 +31,14 @@ class Main: plotter = DataPlotter() for race_index in race_indexes: + + # Import raceSession = importer.importRaceSession(race_index) + raceName: str = f"{raceSession.event['EventName']} {raceSession.event.year}" print("Import done") + # Analyse overtakesInRaces: List[int] = analyser.analyseRaceForOvertakes(raceSession) print("Overtake analysis done") @@ -44,11 +48,13 @@ class Main: latestTireChange: int = analyser.getLatestTireChange(raceSession) print("Last tire change done") - plotter.plotOvertakesWithTireChangeWindow(overtakesInRaces, earliestTireChange, latestTireChange) - print("Plot done") + # Plot + plotter.plotOvertakesWithTireChangeWindow(overtakesInRaces, earliestTireChange, latestTireChange, raceName) + print("Plot done") + # Print data print(f"\n\n===== Data for race {race_index[0]}/{race_index[1]} =====") print("Lap\tOvertakes") currentLap = 0 @@ -60,70 +66,6 @@ class Main: print(f"Weather change window is therefore: laps {earliestTireChange-1} - {latestTireChange+1}") - - - def fastF1Example(self): - # Load FastF1's dark color scheme - fastf1.plotting.setup_mpl(mpl_timedelta_support=False, misc_mpl_mods=False, - color_scheme='fastf1') - - - session = fastf1.get_session(2024, 7, 'R') - session.load(laps=True, weather=True) - - figure, axis = plt.subplots(figsize=(8.0, 4.9)) - - - for driver in session.drivers: - driverLaps = session.laps.pick_drivers(driver) - - driverAbbreviation = driverLaps['Driver'].iloc[0] - style = fastf1.plotting.get_driver_style(identifier=driverAbbreviation, - style=['color', 'linestyle'], - session=session) - - axis.plot(driverLaps['LapNumber'], driverLaps['Position'], - label=driverAbbreviation, **style) - - - axis.set_ylim([20.5, 0.5]) - axis.set_yticks([1, 5, 10, 15, 20]) - axis.set_xlabel('Lap') - axis.set_ylabel('Position') - - - axis.legend(bbox_to_anchor=(1.0, 1.02)) - plt.tight_layout() - - plt.show() - - - def oldJavaTransfer(self): - # contains pairs of season & race number to identify specific races - race_indexes = [ - [2024, 7], # Imola 2024 - [2022, 4] # Imola 2022 - # TODO: add more races - ] - - importer = DataImporter() - checker = DataChecker() - analyser = DataAnalyser() - # plotter = DataPlotter() # Commented as it's not used - - races = importer.import_laps_for_races(race_indexes) - checker.check_imported_races(races) - overtakes_in_races = analyser.count_overtakes_in_races(races) - - print(f"Data for race {race_indexes[0][1]}/{race_indexes[0][0]}") - for current_lap in range(len(overtakes_in_races[0])): - print(f"Overtakes in lap {current_lap + 1}: {overtakes_in_races[0][current_lap]}") - - return overtakes_in_races - - - - if __name__ == '__main__': app = Main()