Add all local circuit work — year-by-year SVGs and tooling

This commit is contained in:
2026-03-25 22:17:47 +01:00
parent 6ceeba7fb9
commit c94ebcfb22
1149 changed files with 171650 additions and 421 deletions
+135
View File
@@ -0,0 +1,135 @@
import json
import os
from dataclasses import dataclass
from pathlib import Path
from typing import Optional
from models.geo_json.geo_json import GeoJSON
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from models.circuit import Circuit
@dataclass
class TrackLayout:
slug: str
description: str
imageUrl: Optional[str]
circuit: 'Circuit'
_geo_data: Optional[GeoJSON] = None
@classmethod
def from_dict(cls, circuit: 'Circuit', slug: str, data: dict) -> 'TrackLayout':
return cls(
slug=slug,
description=data["description"],
imageUrl=data["imageUrl"],
circuit=circuit
)
@property
def coordinates(self) -> list[tuple[float, float]]:
return self._geo_data.features[0].geometry.coordinates
def _relative_filepath(self, extension: str) -> Path:
return Path(f"{self.circuit.locality.country.slug}/"
f"{self.circuit.locality.slug}/"
f"{self.circuit.slug}/"
f"{self.slug}.{extension}")
@property
def relative_geojson_filepath(self) -> Optional[Path]:
return self._relative_filepath("geo.json")
@property
def relative_svg_filepath(self) -> Optional[Path]:
return self._relative_filepath("svg")
@property
def relative_png_filepath(self) -> Optional[Path]:
return self._relative_filepath("png")
def load_geo_json_data(self):
"""Load GeoJSON data for this layout"""
file_path = Path(f"./circuits").joinpath(self.relative_geojson_filepath)
if not file_path.exists():
return None
try:
with open(file_path, 'r') as f:
data = json.load(f)
self._geo_data = GeoJSON.from_dict(self.circuit, data)
except Exception as e:
print(f"Error loading GeoJSON data: {e}")
return None
def save_svg(self, path: Path, default_width: int=150, aspect_ratio: float=1.6):
if not self.coordinates:
print(f"No coordinates available for {self.circuit.name}")
return
# Extract coordinates
lons, lats = zip(*self.coordinates)
# Calculate canvas dimensions based on provided parameters
svg_width = default_width
svg_height = int(svg_width / aspect_ratio) # Higher aspect ratio = wider rectangle
# Normalize coordinates to fit in SVG while maintaining proportions
min_lon, max_lon = min(lons), max(lons)
min_lat, max_lat = min(lats), max(lats)
# Add padding
lon_padding = (max_lon - min_lon) * 0.05
lat_padding = (max_lat - min_lat) * 0.05
min_lon -= lon_padding
max_lon += lon_padding
min_lat -= lat_padding
max_lat += lat_padding
# Calculate scaling factors for both dimensions
lon_scale = svg_width / (max_lon - min_lon)
lat_scale = svg_height / (max_lat - min_lat)
# Use the smaller scaling factor to ensure the track fits within bounds
scale = min(lon_scale, lat_scale)
# Calculate centering offsets
lon_offset = (svg_width - (max_lon - min_lon) * scale) / 2
lat_offset = (svg_height - (max_lat - min_lat) * scale) / 2
# Create SVG header
svg = [
f'<?xml version="1.0" encoding="UTF-8" standalone="no"?>',
f'<svg width="{svg_width}px" height="{svg_height}px" viewBox="0 0 {svg_width} {svg_height}" '
'xmlns="http://www.w3.org/2000/svg">',
f' <title>{self.circuit.name} - {self.circuit.locality.name}</title>'
]
# Create path data
path_data = []
for i, (lon, lat) in enumerate(self.coordinates):
# Scale and center the coordinates
x = ((lon - min_lon) * scale) + lon_offset
y = svg_height - ((lat - min_lat) * scale) - lat_offset
if i == 0:
path_data.append(f"M{x:.2f},{y:.2f}")
else:
path_data.append(f"L{x:.2f},{y:.2f}")
# Add path to SVG
svg.append(f' <path d="{" ".join(path_data)}" fill="none" stroke="black" stroke-width="2"/>')
svg.append('</svg>')
# Write SVG to file
svg_path = path.joinpath(self.relative_svg_filepath)
os.makedirs(os.path.dirname(svg_path), exist_ok=True)
with open(svg_path, 'w', encoding='utf-8') as f:
f.write('\n'.join(svg))
print(f"SVG saved to: {self.relative_svg_filepath}")