F1 circuit layouts with year-by-year SVGs — manually traced track variations
25'ten fazla konu seçemezsiniz Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir.

127 satır
5.4KB

  1. import json
  2. import logging
  3. from typing import Optional
  4. import functools
  5. from enum import Enum
  6. # Import FastF1 for dynamic circuit data
  7. import fastf1
  8. from ergast_service import ErgastService
  9. from models.circuit import Circuit
  10. from models.country import Country
  11. from models.locality import Locality
  12. from models.model_builder import parse_circuits_json
  13. from models.track_layout import TrackLayout
  14. # Define supported formats
  15. class Format(str, Enum):
  16. PNG = "png"
  17. SVG = "svg"
  18. GEOJSON = "geojson"
  19. class CircuitService:
  20. def __init__(self, circuit_data_path: str = "circuits/circuits.json"):
  21. self.circuit_data_path = circuit_data_path
  22. self.circuit_data = self._load_circuit_data()
  23. self._layout_cache = {}
  24. self._circuit_file_cache = {}
  25. self._grand_prix_cache = {}
  26. self.ergast_service = ErgastService(self)
  27. # Configure logging
  28. logging.basicConfig(level=logging.INFO)
  29. self.logger = logging.getLogger(__name__)
  30. # Initialize FastF1 cache
  31. fastf1.Cache.enable_cache('cache')
  32. @functools.lru_cache()
  33. def _load_circuit_data(self) -> dict[str, Country]:
  34. """Load and cache circuit data"""
  35. with open(self.circuit_data_path) as f:
  36. return parse_circuits_json(json.load(f))
  37. def find_layout_slug_for_year(self, circuit: Circuit, year: int) -> Optional[str]:
  38. """Find the appropriate layout slug for a given year"""
  39. cache_key = f"{circuit.name}-{year}"
  40. if cache_key in self._layout_cache:
  41. return self._layout_cache[cache_key]
  42. for layout_slug, _ in circuit.layouts.items():
  43. # Check different layout slug formats
  44. if '-' in layout_slug:
  45. # Handle range format
  46. start_year, *end = layout_slug.split('-')
  47. start_year = int(start_year)
  48. if not end[0]:
  49. # Format: 1967- (no end year)
  50. if year >= start_year:
  51. self._layout_cache[cache_key] = layout_slug
  52. return layout_slug
  53. else:
  54. # Format: 1967-1968
  55. end_year = int(end[0])
  56. if start_year <= year <= end_year:
  57. self._layout_cache[cache_key] = layout_slug
  58. return layout_slug
  59. else:
  60. # Format: 1967 (single year)
  61. if year == int(layout_slug):
  62. self._layout_cache[cache_key] = layout_slug
  63. return layout_slug
  64. return None
  65. def get_countries_list(self) -> Optional[list[Country]]:
  66. """Get list of all available countries with their slugs"""
  67. return [country for _, country in self.circuit_data.items()]
  68. def get_country_details(self, country_slug: str) -> Optional[Country]:
  69. """Get details for a specific country"""
  70. return self.circuit_data.get(country_slug)
  71. def get_localities_list(self, country_slug: str) -> Optional[list[Locality]]:
  72. """Get list of cities for a specific country"""
  73. return [locality for _, locality in self.circuit_data[country_slug].localities.items()]
  74. def get_locality_details(self, country_slug: str, locality_slug: str) -> Optional[Locality]:
  75. """Get details for a specific city"""
  76. return self.circuit_data[country_slug].localities.get(locality_slug)
  77. def get_circuits_list(self, country_slug: str, locality_slug: str) -> Optional[list[Circuit]]:
  78. """Get list of circuits for a specific city"""
  79. return [circuit_data for _, circuit_data in self.circuit_data[country_slug].localities.get(locality_slug).circuits.items()]
  80. def get_circuit_details(self, country_slug: str, locality_slug: str, circuit_slug: str) -> Optional[Circuit]:
  81. """Get circuit details and available formats"""
  82. return self.circuit_data[country_slug].localities.get(locality_slug).circuits.get(circuit_slug)
  83. def get_layout_details(self, country_slug: str, locality_slug: str, circuit_slug: str, layout_slug: str) -> Optional[TrackLayout]:
  84. """Get details for a specific layout"""
  85. return self.circuit_data[country_slug].localities.get(locality_slug).circuits.get(circuit_slug).layouts.get(
  86. layout_slug)
  87. def get_circuit_layout_by_ergast_data(self, grand_prix_name: str, season: int) -> Optional[TrackLayout]:
  88. """
  89. Find and return the circuit layout file path based on Ergast F1 API race data format.
  90. """
  91. country_slug, locality_slug, circuit_slug = self.ergast_service.find_slugs_for_grand_prix(grand_prix_name, season)
  92. if country_slug is None or locality_slug is None or circuit_slug is None:
  93. self.logger.error(f"Error finding circuit layout in Ergast for grand prix {grand_prix_name} in season {season}")
  94. return None
  95. circuit = self.get_circuit_details(country_slug, locality_slug, circuit_slug)
  96. if circuit is None:
  97. self.logger.error(f"Error finding circuit details for Ergast country, locality and circuit slug: {country_slug}, {locality_slug}, {circuit_slug}")
  98. return None
  99. layout_slug = self.find_layout_slug_for_year(circuit, season)
  100. if layout_slug is None:
  101. self.logger.error(f"Error finding layout slug for year {season} for circuit {circuit_slug}")
  102. return None
  103. return self.get_layout_details(country_slug, locality_slug, circuit_slug, layout_slug)