|
- 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()
-
|