F1 circuit layouts with year-by-year SVGs — manually traced track variations
Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

198 řádky
6.6KB

  1. import os
  2. import json
  3. import matplotlib.pyplot as plt
  4. from dataclasses import dataclass
  5. from typing import List, Dict, Any, Optional, Tuple
  6. from pathlib import Path
  7. @dataclass
  8. class Circuit:
  9. """Model class representing an F1 circuit."""
  10. id: str
  11. name: str
  12. location: str
  13. opened: Optional[int] = None
  14. first_gp: Optional[int] = None
  15. length: Optional[int] = None # in meters
  16. altitude: Optional[int] = None
  17. coordinates: List[Tuple[float, float]] = None
  18. bbox: Optional[List[float]] = None
  19. def __post_init__(self):
  20. if self.coordinates is None:
  21. self.coordinates = []
  22. def plot(self):
  23. """Generate a simple plot of the circuit with transparent background and no grid/axes."""
  24. if not self.coordinates:
  25. print(f"No coordinates available for {self.name}")
  26. return
  27. # Extract longitude and latitude from coordinates
  28. lons, lats = zip(*self.coordinates)
  29. # Create figure with transparent background
  30. fig = plt.figure(figsize=(10, 8), facecolor="none")
  31. ax = fig.add_subplot(111)
  32. # Plot the circuit with black line
  33. ax.plot(lons, lats, 'k-', linewidth=2)
  34. # Remove grid, axes, and title
  35. ax.grid(False)
  36. ax.set_xticks([])
  37. ax.set_yticks([])
  38. ax.spines['top'].set_visible(False)
  39. ax.spines['right'].set_visible(False)
  40. ax.spines['bottom'].set_visible(False)
  41. ax.spines['left'].set_visible(False)
  42. # Keep aspect ratio equal
  43. ax.set_aspect('equal')
  44. # Add padding around the circuit
  45. ax.margins(0.1)
  46. # Tight layout
  47. plt.tight_layout()
  48. return fig
  49. def create_empty_geojson(circuit_name: str, location: str, file_path: Path):
  50. """Create an empty GeoJSON template file"""
  51. template = {
  52. "type": "FeatureCollection",
  53. "features": [{
  54. "type": "Feature",
  55. "properties": {
  56. "Name": circuit_name,
  57. "Location": location,
  58. "opened": None,
  59. "firstgp": None,
  60. "length": None,
  61. "altitude": None
  62. },
  63. "geometry": {
  64. "type": "LineString",
  65. "coordinates": []
  66. }
  67. }]
  68. }
  69. file_path.parent.mkdir(parents=True, exist_ok=True)
  70. with open(file_path, 'w', encoding='utf-8') as f:
  71. json.dump(template, f, indent=2)
  72. print(f"Created empty GeoJSON template: {file_path}")
  73. def load_circuits_json() -> dict:
  74. """Load the circuits.json file"""
  75. with open("circuits.json", 'r', encoding='utf-8') as f:
  76. return json.load(f)
  77. def process_circuit_files():
  78. """Process all circuit files based on circuits.json data"""
  79. data = load_circuits_json()
  80. for country, country_data in data.items():
  81. country_slug = country_data['slug']
  82. for city, city_data in country_data['cities'].items():
  83. city_slug = city_data['slug']
  84. for circuit_name, circuit_data in city_data['circuits'].items():
  85. circuit_slug = circuit_data['slug']
  86. if 'layouts' in circuit_data:
  87. for layout_years, layout_data in circuit_data['layouts'].items():
  88. layout_slug = layout_data['slug']
  89. # Construct file paths
  90. base_path = Path(f"{country_slug}/{city_slug}/{circuit_slug}")
  91. geo_path = base_path / f"{layout_slug}.geo.json"
  92. svg_path = base_path / f"{layout_slug}.svg"
  93. png_path = base_path / f"{layout_slug}.png"
  94. # Create empty GeoJSON if missing
  95. if not geo_path.exists():
  96. create_empty_geojson(circuit_name, f"{city}, {country}", geo_path)
  97. continue # Skip SVG/PNG generation as we have no coordinates
  98. # Parse existing GeoJSON and generate SVG/PNG if needed
  99. circuit = parse_geojson(geo_path)
  100. if circuit:
  101. if not svg_path.exists():
  102. print(f"Generating SVG: {svg_path}")
  103. save_svg(circuit, svg_path)
  104. if not png_path.exists():
  105. print(f"Generating PNG: {png_path}")
  106. fig = circuit.plot()
  107. if fig:
  108. fig.savefig(png_path, transparent=True, bbox_inches='tight', pad_inches=0.1)
  109. plt.close(fig)
  110. def save_svg(circuit: Circuit, output_path: str):
  111. """Generate and save an SVG representation of the circuit."""
  112. if not circuit.coordinates:
  113. print(f"No coordinates available for {circuit.name}")
  114. return
  115. # Extract coordinates
  116. lons, lats = zip(*circuit.coordinates)
  117. # Normalize coordinates to fit in SVG
  118. min_lon, max_lon = min(lons), max(lons)
  119. min_lat, max_lat = min(lats), max(lats)
  120. # Add some padding
  121. lon_padding = (max_lon - min_lon) * 0.05
  122. lat_padding = (max_lat - min_lat) * 0.05
  123. min_lon -= lon_padding
  124. max_lon += lon_padding
  125. min_lat -= lat_padding
  126. max_lat += lat_padding
  127. # SVG dimensions
  128. svg_width = 800
  129. svg_height = int(svg_width * (max_lat - min_lat) / (max_lon - min_lon))
  130. # Create SVG header
  131. svg = [
  132. f'<?xml version="1.0" encoding="UTF-8" standalone="no"?>',
  133. f'<svg width="{svg_width}" height="{svg_height}" viewBox="0 0 {svg_width} {svg_height}" xmlns="http://www.w3.org/2000/svg">',
  134. f' <title>{circuit.name} - {circuit.location}</title>'
  135. ]
  136. # Create path data
  137. path_data = []
  138. for i, (lon, lat) in enumerate(circuit.coordinates):
  139. # Convert geo coordinates to SVG coordinates
  140. x = svg_width * (lon - min_lon) / (max_lon - min_lon)
  141. # Flip y-axis because SVG has origin at top-left
  142. y = svg_height * (1 - (lat - min_lat) / (max_lat - min_lat))
  143. if i == 0:
  144. path_data.append(f"M{x:.2f},{y:.2f}")
  145. else:
  146. path_data.append(f"L{x:.2f},{y:.2f}")
  147. # Add path to SVG
  148. svg.append(f' <path d="{" ".join(path_data)}" fill="none" stroke="black" stroke-width="2"/>')
  149. svg.append('</svg>')
  150. # Write SVG to file
  151. os.makedirs(os.path.dirname(output_path), exist_ok=True)
  152. with open(output_path, 'w', encoding='utf-8') as f:
  153. f.write('\n'.join(svg))
  154. print(f"SVG saved to: {output_path}")
  155. def main():
  156. print("Processing circuit files...")
  157. process_circuit_files()
  158. print("Done!")
  159. if __name__ == "__main__":
  160. main()