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'', f'', f' {circuit.name} - {circuit.location}' ] # 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' ') svg.append('') # 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()