Files
f1-circuits/circuits_bak/svg-generator.py
T

211 lines
6.9 KiB
Python

import os
import json
import matplotlib.pyplot as plt
from dataclasses import dataclass
from typing import List, Dict, Any, Optional, Tuple
@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 parse_geojson(file_path: str) -> Optional[Circuit]:
"""Parse a GeoJSON file and return a Circuit object."""
try:
with open(file_path, 'r', encoding='utf-8') as f:
data = json.load(f)
# Get the first feature (typically there's only one per file)
if not data.get('features'):
print(f"No features found in {file_path}")
return None
feature = data['features'][0]
properties = feature.get('properties', {})
geometry = feature.get('geometry', {})
# Extract coordinates
coordinates = []
if geometry.get('type') == 'LineString':
coordinates = geometry.get('coordinates', [])
# Create Circuit object
circuit = Circuit(
id=properties.get('id', ''),
name=properties.get('Name', ''),
location=properties.get('Location', ''),
opened=properties.get('opened'),
first_gp=properties.get('firstgp'),
length=properties.get('length'),
altitude=properties.get('altitude'),
coordinates=coordinates,
bbox=feature.get('bbox') or data.get('bbox')
)
return circuit
except Exception as e:
print(f"Error parsing {file_path}: {str(e)}")
return None
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():
circuits_dir = "./"
# List to store all .geo.json files
geo_json_files = []
# Walk through all subdirectories
for root, dirs, files in os.walk(circuits_dir):
for file in files:
# Check if file ends with .geo.json
if file.endswith(".geo.json"):
file_path = os.path.join(root, file)
geo_json_files.append(file_path)
# List to store all parsed circuits
circuits = []
# Process each .geo.json file
for geo_json_file in geo_json_files:
print(f"Processing: {geo_json_file}")
# Parse the GeoJSON file
circuit = parse_geojson(geo_json_file)
if circuit:
circuits.append(circuit)
# Print circuit info
print(f" Circuit: {circuit.name} ({circuit.location})")
print(f" ID: {circuit.id}")
print(f" Length: {circuit.length}m, Altitude: {circuit.altitude}m")
print(f" Coordinates: {len(circuit.coordinates)} points")
# Generate output file paths in the same directory as the GeoJSON
file_dir = os.path.dirname(geo_json_file)
file_basename = os.path.basename(geo_json_file).replace('.geo.json', '')
svg_path = os.path.join(file_dir, f"{file_basename}.svg")
png_path = os.path.join(file_dir, f"{file_basename}.png")
# Generate SVG
save_svg(circuit, svg_path)
# Generate PNG plot with transparent background
fig = circuit.plot()
if fig:
fig.savefig(png_path, transparent=True, bbox_inches='tight', pad_inches=0.1)
plt.close(fig)
print(f"Plot saved to: {png_path}")
else:
print(f"Failed to parse {geo_json_file}")
print(f"\nProcessed {len(circuits)} circuits")
if __name__ == "__main__":
main()