Add all local circuit work — year-by-year SVGs and tooling
This commit is contained in:
@@ -0,0 +1,198 @@
|
||||
import os
|
||||
import json
|
||||
import matplotlib.pyplot as plt
|
||||
from dataclasses import dataclass
|
||||
from typing import List, Dict, Any, Optional, Tuple
|
||||
from pathlib import Path
|
||||
|
||||
@dataclass
|
||||
class Circuit:
|
||||
"""Model class representing an F1 circuit."""
|
||||
id: str
|
||||
name: str
|
||||
location: str
|
||||
opened: Optional[int] = None
|
||||
first_gp: Optional[int] = None
|
||||
length: Optional[int] = None # in meters
|
||||
altitude: Optional[int] = None
|
||||
coordinates: List[Tuple[float, float]] = None
|
||||
bbox: Optional[List[float]] = None
|
||||
|
||||
def __post_init__(self):
|
||||
if self.coordinates is None:
|
||||
self.coordinates = []
|
||||
|
||||
def plot(self):
|
||||
"""Generate a simple plot of the circuit with transparent background and no grid/axes."""
|
||||
if not self.coordinates:
|
||||
print(f"No coordinates available for {self.name}")
|
||||
return
|
||||
|
||||
# Extract longitude and latitude from coordinates
|
||||
lons, lats = zip(*self.coordinates)
|
||||
|
||||
# Create figure with transparent background
|
||||
fig = plt.figure(figsize=(10, 8), facecolor="none")
|
||||
ax = fig.add_subplot(111)
|
||||
|
||||
# Plot the circuit with black line
|
||||
ax.plot(lons, lats, 'k-', linewidth=2)
|
||||
|
||||
# Remove grid, axes, and title
|
||||
ax.grid(False)
|
||||
ax.set_xticks([])
|
||||
ax.set_yticks([])
|
||||
ax.spines['top'].set_visible(False)
|
||||
ax.spines['right'].set_visible(False)
|
||||
ax.spines['bottom'].set_visible(False)
|
||||
ax.spines['left'].set_visible(False)
|
||||
|
||||
# Keep aspect ratio equal
|
||||
ax.set_aspect('equal')
|
||||
|
||||
# Add padding around the circuit
|
||||
ax.margins(0.1)
|
||||
|
||||
# Tight layout
|
||||
plt.tight_layout()
|
||||
|
||||
return fig
|
||||
|
||||
def create_empty_geojson(circuit_name: str, location: str, file_path: Path):
|
||||
"""Create an empty GeoJSON template file"""
|
||||
template = {
|
||||
"type": "FeatureCollection",
|
||||
"features": [{
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"Name": circuit_name,
|
||||
"Location": location,
|
||||
"opened": None,
|
||||
"firstgp": None,
|
||||
"length": None,
|
||||
"altitude": None
|
||||
},
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": []
|
||||
}
|
||||
}]
|
||||
}
|
||||
|
||||
file_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(file_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(template, f, indent=2)
|
||||
print(f"Created empty GeoJSON template: {file_path}")
|
||||
|
||||
def load_circuits_json() -> dict:
|
||||
"""Load the circuits.json file"""
|
||||
with open("circuits.json", 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
|
||||
def process_circuit_files():
|
||||
"""Process all circuit files based on circuits.json data"""
|
||||
data = load_circuits_json()
|
||||
|
||||
for country, country_data in data.items():
|
||||
country_slug = country_data['slug']
|
||||
|
||||
for city, city_data in country_data['cities'].items():
|
||||
city_slug = city_data['slug']
|
||||
|
||||
for circuit_name, circuit_data in city_data['circuits'].items():
|
||||
circuit_slug = circuit_data['slug']
|
||||
|
||||
if 'layouts' in circuit_data:
|
||||
for layout_years, layout_data in circuit_data['layouts'].items():
|
||||
layout_slug = layout_data['slug']
|
||||
|
||||
# Construct file paths
|
||||
base_path = Path(f"{country_slug}/{city_slug}/{circuit_slug}")
|
||||
geo_path = base_path / f"{layout_slug}.geo.json"
|
||||
svg_path = base_path / f"{layout_slug}.svg"
|
||||
png_path = base_path / f"{layout_slug}.png"
|
||||
|
||||
# Create empty GeoJSON if missing
|
||||
if not geo_path.exists():
|
||||
create_empty_geojson(circuit_name, f"{city}, {country}", geo_path)
|
||||
continue # Skip SVG/PNG generation as we have no coordinates
|
||||
|
||||
# Parse existing GeoJSON and generate SVG/PNG if needed
|
||||
circuit = parse_geojson(geo_path)
|
||||
if circuit:
|
||||
if not svg_path.exists():
|
||||
print(f"Generating SVG: {svg_path}")
|
||||
save_svg(circuit, svg_path)
|
||||
|
||||
if not png_path.exists():
|
||||
print(f"Generating PNG: {png_path}")
|
||||
fig = circuit.plot()
|
||||
if fig:
|
||||
fig.savefig(png_path, transparent=True, bbox_inches='tight', pad_inches=0.1)
|
||||
plt.close(fig)
|
||||
|
||||
|
||||
def save_svg(circuit: Circuit, output_path: str):
|
||||
"""Generate and save an SVG representation of the circuit."""
|
||||
if not circuit.coordinates:
|
||||
print(f"No coordinates available for {circuit.name}")
|
||||
return
|
||||
|
||||
# Extract coordinates
|
||||
lons, lats = zip(*circuit.coordinates)
|
||||
|
||||
# Normalize coordinates to fit in SVG
|
||||
min_lon, max_lon = min(lons), max(lons)
|
||||
min_lat, max_lat = min(lats), max(lats)
|
||||
|
||||
# Add some 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
|
||||
|
||||
# SVG dimensions
|
||||
svg_width = 800
|
||||
svg_height = int(svg_width * (max_lat - min_lat) / (max_lon - min_lon))
|
||||
|
||||
# Create SVG header
|
||||
svg = [
|
||||
f'<?xml version="1.0" encoding="UTF-8" standalone="no"?>',
|
||||
f'<svg width="{svg_width}" height="{svg_height}" viewBox="0 0 {svg_width} {svg_height}" xmlns="http://www.w3.org/2000/svg">',
|
||||
f' <title>{circuit.name} - {circuit.location}</title>'
|
||||
]
|
||||
|
||||
# Create path data
|
||||
path_data = []
|
||||
for i, (lon, lat) in enumerate(circuit.coordinates):
|
||||
# Convert geo coordinates to SVG coordinates
|
||||
x = svg_width * (lon - min_lon) / (max_lon - min_lon)
|
||||
# Flip y-axis because SVG has origin at top-left
|
||||
y = svg_height * (1 - (lat - min_lat) / (max_lat - min_lat))
|
||||
|
||||
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
|
||||
os.makedirs(os.path.dirname(output_path), exist_ok=True)
|
||||
with open(output_path, 'w', encoding='utf-8') as f:
|
||||
f.write('\n'.join(svg))
|
||||
|
||||
print(f"SVG saved to: {output_path}")
|
||||
|
||||
def main():
|
||||
print("Processing circuit files...")
|
||||
process_circuit_files()
|
||||
print("Done!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user