feat: Add combined track functionality with repository and service layers
- Introduced CombinedTrackModel, CombinedTrackCreate, and CombinedTrackRepository for managing combined tracks. - Implemented logic to create combined tracks based on existing tracks between two stations. - Added methods to check for existing combined tracks and retrieve constituent track IDs. - Enhanced TrackModel and TrackRepository to support OSM ID and track updates. - Created migration scripts for adding combined tracks table and OSM ID to tracks. - Updated services and API endpoints to handle combined track operations. - Added tests for combined track creation, repository methods, and API interactions.
This commit is contained in:
@@ -22,6 +22,7 @@ from backend.app.repositories import StationRepository, TrackRepository
|
||||
@dataclass(slots=True)
|
||||
class ParsedTrack:
|
||||
coordinates: list[tuple[float, float]]
|
||||
osm_id: str | None = None
|
||||
name: str | None = None
|
||||
length_meters: float | None = None
|
||||
max_speed_kph: float | None = None
|
||||
@@ -97,7 +98,8 @@ def _parse_track_entries(entries: Iterable[Mapping[str, Any]]) -> list[ParsedTra
|
||||
processed_coordinates: list[tuple[float, float]] = []
|
||||
for pair in coordinates:
|
||||
if not isinstance(pair, Sequence) or len(pair) != 2:
|
||||
raise ValueError(f"Invalid coordinate pair {pair!r} in track entry")
|
||||
raise ValueError(
|
||||
f"Invalid coordinate pair {pair!r} in track entry")
|
||||
lat, lon = pair
|
||||
processed_coordinates.append((float(lat), float(lon)))
|
||||
|
||||
@@ -106,10 +108,12 @@ def _parse_track_entries(entries: Iterable[Mapping[str, Any]]) -> list[ParsedTra
|
||||
max_speed = _safe_float(entry.get("maxSpeedKph"))
|
||||
status = entry.get("status", "operational")
|
||||
is_bidirectional = entry.get("isBidirectional", True)
|
||||
osm_id = entry.get("osmId")
|
||||
|
||||
parsed.append(
|
||||
ParsedTrack(
|
||||
coordinates=processed_coordinates,
|
||||
osm_id=str(osm_id) if osm_id else None,
|
||||
name=str(name) if name else None,
|
||||
length_meters=length,
|
||||
max_speed_kph=max_speed,
|
||||
@@ -133,6 +137,12 @@ def load_tracks(tracks: Iterable[ParsedTrack], commit: bool = True) -> int:
|
||||
}
|
||||
|
||||
for track_data in tracks:
|
||||
# Skip if track with this OSM ID already exists
|
||||
if track_data.osm_id and track_repo.exists_by_osm_id(track_data.osm_id):
|
||||
print(
|
||||
f"Skipping track {track_data.osm_id} - already exists by OSM ID")
|
||||
continue
|
||||
|
||||
start_station = _nearest_station(
|
||||
track_data.coordinates[0],
|
||||
station_index,
|
||||
@@ -145,13 +155,19 @@ def load_tracks(tracks: Iterable[ParsedTrack], commit: bool = True) -> int:
|
||||
)
|
||||
|
||||
if not start_station or not end_station:
|
||||
print(
|
||||
f"Skipping track {track_data.osm_id} - no start/end stations found")
|
||||
continue
|
||||
|
||||
if start_station.id == end_station.id:
|
||||
print(
|
||||
f"Skipping track {track_data.osm_id} - start and end stations are the same")
|
||||
continue
|
||||
|
||||
pair = (start_station.id, end_station.id)
|
||||
if pair in existing_pairs:
|
||||
print(
|
||||
f"Skipping track {track_data.osm_id} - station pair {pair} already exists")
|
||||
continue
|
||||
|
||||
length = track_data.length_meters or _polyline_length(
|
||||
@@ -163,6 +179,7 @@ def load_tracks(tracks: Iterable[ParsedTrack], commit: bool = True) -> int:
|
||||
else None
|
||||
)
|
||||
create_schema = TrackCreate(
|
||||
osm_id=track_data.osm_id,
|
||||
name=track_data.name,
|
||||
start_station_id=start_station.id,
|
||||
end_station_id=end_station.id,
|
||||
@@ -193,7 +210,8 @@ def _nearest_station(
|
||||
best_station: StationRef | None = None
|
||||
best_distance = math.inf
|
||||
for station in stations:
|
||||
distance = _haversine(coordinate, (station.latitude, station.longitude))
|
||||
distance = _haversine(
|
||||
coordinate, (station.latitude, station.longitude))
|
||||
if distance < best_distance:
|
||||
best_station = station
|
||||
best_distance = distance
|
||||
|
||||
Reference in New Issue
Block a user