Add all local circuit work — year-by-year SVGs and tooling
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,23 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional, List, Dict, Any, TYPE_CHECKING
|
||||
|
||||
from models.geo_json.geometry import GeoJSONGeometry
|
||||
from models.geo_json.properties import GeoJSONProperties
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from models.circuit import Circuit
|
||||
|
||||
@dataclass
|
||||
class GeoJSONFeature:
|
||||
type: str
|
||||
properties: GeoJSONProperties
|
||||
bbox: Optional[List[float]]
|
||||
geometry: GeoJSONGeometry
|
||||
@classmethod
|
||||
def from_dict(cls, circuit: 'Circuit', data: Dict[str, Any]) -> 'GeoJSONFeature':
|
||||
return cls(
|
||||
type=data['type'],
|
||||
properties=GeoJSONProperties.from_dict(circuit, data['properties']),
|
||||
geometry=GeoJSONGeometry.from_dict(data['geometry']),
|
||||
bbox=data.get('bbox')
|
||||
)
|
||||
@@ -0,0 +1,27 @@
|
||||
from dataclasses import dataclass, asdict
|
||||
from typing import List, Dict, Any, TYPE_CHECKING, Optional
|
||||
|
||||
from models.geo_json.feature import GeoJSONFeature
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from models.circuit import Circuit
|
||||
|
||||
@dataclass
|
||||
class GeoJSON:
|
||||
type: str
|
||||
bbox: Optional[list[float]]
|
||||
features: List[GeoJSONFeature]
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, circuit: 'Circuit', data: Dict[str, Any]) -> 'GeoJSON':
|
||||
return cls(
|
||||
type=data['type'],
|
||||
bbox=data['bbox'] if 'bbox' in data else None,
|
||||
features=[GeoJSONFeature.from_dict(circuit, f) for f in data['features']],
|
||||
)
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Convert the GeoJSON object back to a dictionary"""
|
||||
data = asdict(self)
|
||||
# Remove None values
|
||||
return {k: v for k, v in data.items() if v is not None}
|
||||
@@ -0,0 +1,56 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import List, Dict, Any
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
||||
@dataclass
|
||||
class GeoJSONGeometry:
|
||||
type: str
|
||||
coordinates: List[tuple[float, float]]
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Dict[str, Any]) -> 'GeoJSONGeometry':
|
||||
return cls(
|
||||
type=data['type'],
|
||||
coordinates=data['coordinates']
|
||||
)
|
||||
|
||||
def find_longest_straight(self, window_size=5) -> (List[float], List[float]):
|
||||
"""Find the longest approximately straight section of the track, including wrap-around."""
|
||||
max_distance = 0
|
||||
best_start_idx = 0
|
||||
n = len(self.coordinates)
|
||||
|
||||
# Helper function to check straightness
|
||||
def is_straight(points, start, end, length):
|
||||
direction = (end - start) / length
|
||||
distances = []
|
||||
for point in points:
|
||||
projection = start + np.dot(point - start, direction) * direction
|
||||
distance = np.linalg.norm(point - projection)
|
||||
distances.append(distance)
|
||||
return max(distances) < length * 0.05 # 5% tolerance
|
||||
|
||||
# Check all possible segments, including wrap-around
|
||||
for i in range(n):
|
||||
# Get window_size points, handling wrap-around
|
||||
segment = []
|
||||
for j in range(window_size):
|
||||
idx = (i + j) % n
|
||||
segment.append(np.array(self.coordinates[idx]))
|
||||
|
||||
start = np.array(segment[0])
|
||||
end = np.array(segment[-1])
|
||||
length = np.linalg.norm(end - start)
|
||||
|
||||
if length > max_distance:
|
||||
# Check if all points are roughly on the line
|
||||
if is_straight(segment, start, end, length):
|
||||
max_distance = length
|
||||
best_start_idx = i
|
||||
|
||||
# Return indices that might wrap around
|
||||
end_idx = (best_start_idx + window_size) % len(self.coordinates)
|
||||
return self.coordinates[best_start_idx], self.coordinates[end_idx]
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional, Dict, Any, TYPE_CHECKING
|
||||
if TYPE_CHECKING:
|
||||
from models.circuit import Circuit
|
||||
|
||||
|
||||
@dataclass
|
||||
class GeoJSONProperties:
|
||||
id: str
|
||||
country: str
|
||||
locality: str
|
||||
circuit: str
|
||||
length: Optional[int] = None
|
||||
altitude: Optional[int] = None
|
||||
rotation: Optional[float] = None
|
||||
direction: Optional[str] = None
|
||||
fastest_laptime: Optional[float] = None
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, circuit: 'Circuit', data: Dict[str, Any]) -> 'GeoJSONProperties':
|
||||
return cls(
|
||||
id=data.get("id"),
|
||||
country=circuit.locality.country.name,
|
||||
locality=circuit.locality.name,
|
||||
circuit=circuit.name,
|
||||
**{k: v for k, v in data.items() if
|
||||
k in cls.__annotations__ and k not in ['id', 'country', 'locality',
|
||||
'circuit']}
|
||||
)
|
||||
Reference in New Issue
Block a user