F1 circuit layouts with year-by-year SVGs — manually traced track variations
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

121 строка
5.3KB

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