koleo-izochrona/TECHNICAL.md

4.7 KiB

Dokumentacja techniczna

Stack technologiczny

  • Backend: Python 3.9+, Flask, gtfs-kit, NetworkX, GeoPandas
  • Frontend: Leaflet, OpenStreetMap, vanilla JavaScript
  • Dane: GTFS (General Transit Feed Specification)
  • Zarządzanie zależnościami: uv - szybki menedżer pakietów

Architektura

Backend (Python)

backend/
├── app.py                    # Flask API server
├── gtfs_loader.py           # Ładowanie i parsowanie GTFS
├── isochrone_calculator.py  # Algorytm obliczania izochron
└── download_gtfs.py         # Pobieranie danych GTFS

Frontend (JavaScript)

frontend/
├── index.html    # Struktura strony
├── map.js        # Logika mapy Leaflet i API
└── style.css     # Stylowanie

Algorytm obliczania izochron

1. Budowanie grafu połączeń

  • Węzły: przystanki kolejowe (stop_id)
  • Krawędzie: bezpośrednie połączenia między przystankami
  • Wagi: czas podróży w minutach
G = nx.DiGraph()
G.add_node(stop_id, name=..., lat=..., lon=...)
G.add_edge(from_stop, to_stop, weight=travel_time_minutes)

2. Algorytm Dijkstry

Obliczamy najkrótsze ścieżki z punktu startowego do wszystkich osiągalnych przystanków:

lengths = nx.single_source_dijkstra_path_length(
    graph, origin_stop_id, cutoff=max_time, weight='weight'
)

3. Generowanie wielokątów izochron

Dla każdego przedziału czasowego (30, 60, 90, 120 min):

  1. Filtruj przystanki - wybierz te osiągalne w danym czasie
  2. Zbierz punkty - współrzędne geograficzne przystanków
  3. Generuj wielokąt:
    • Jeśli < 3 punkty: bufor wokół punktów
    • Jeśli ≥ 3 punkty: convex hull z buforem
points = [(lon, lat) for each reachable stop]
polygon = MultiPoint(points).convex_hull.buffer(0.05)

Format danych GTFS

Używane pliki:

  • stops.txt - przystanki (stop_id, stop_name, stop_lat, stop_lon)
  • routes.txt - linie (route_id, route_short_name, route_type)
  • trips.txt - kursy (trip_id, route_id, service_id)
  • stop_times.txt - rozkład jazdy (trip_id, stop_id, arrival_time, departure_time)

Przykład struktury:

stops.txt:
stop_id,stop_name,stop_lat,stop_lon
5100069,Wrocław Główny,51.0989,17.0368

stop_times.txt:
trip_id,arrival_time,departure_time,stop_id,stop_sequence
trip_1,08:00:00,08:00:00,5100069,1
trip_1,08:45:00,08:46:00,5100011,2

API Reference

POST /api/isochrones

Oblicza izochrony dla wybranej stacji.

Request:

{
  "origin_stop_id": "5100069",
  "time_intervals": [30, 60, 90, 120]
}

Response:

{
  "origin_stop_id": "5100069",
  "isochrones": [
    {
      "time": 30,
      "geometry": { "type": "Polygon", "coordinates": [...] },
      "stops": ["id1", "id2", ...],
      "stop_count": 15
    }
  ],
  "reachable_stops": [
    {
      "stop_id": "...",
      "name": "...",
      "lat": 51.0,
      "lon": 17.0,
      "time": 25.5
    }
  ]
}

Optymalizacja

Wydajność:

  • Cache grafu - graf budowany raz przy starcie
  • Cutoff w Dijkstra - tylko obliczamy ścieżki do max_time
  • Buforowanie wyników - możliwość dodania Redis dla cache'owania

Możliwe ulepszenia:

  1. GTFS Realtime - aktualne opóźnienia pociągów
  2. Różne godziny wyjazdu - zmienny graf w ciągu dnia
  3. Dni tygodnia - uwzględnienie calendar.txt
  4. Przesiadki - dodanie czasu na przesiadkę (np. 5 min)
  5. Concave hull - bardziej precyzyjne wielokąty (alpha shapes)
  6. Filtrowanie przewoźników - tylko PKP IC lub tylko KD

Znane limity

  • Uproszczony model: jeden "reprezentatywny" dzień, bez uwzględnienia różnic w rozkładzie
  • Convex hull: wielokąty mogą obejmować obszary bez realnych połączeń
  • Brak czasu przesiadki: zakłada natychmiastową przesiadkę
  • Brak uwzględnienia opóźnień: tylko statyczne rozkłady

Testy

Przykładowe testy dla backendu:

# Testowanie ładowania GTFS
loader = GTFSLoader('data/polish_trains.zip')
assert len(loader.get_stops()) > 0

# Testowanie budowania grafu
graph = loader.build_route_graph()
assert graph.number_of_nodes() > 0

# Testowanie obliczeń izochron
calc = IsochroneCalculator(graph)
isochrones = calc.create_isochrones('5100069', [30, 60])
assert len(isochrones) == 2

Deployment

Lokalne:

  • Backend: Flask development server
  • Frontend: Otwarcie pliku HTML lub python -m http.server

Produkcja:

  • Backend: Gunicorn + Nginx
  • Frontend: Nginx / GitHub Pages / Netlify
  • Dane: CDN dla GTFS lub okresowe aktualizacje

Docker (przyszłe):

FROM python:3.9
WORKDIR /app
COPY backend/ .
RUN pip install -r requirements.txt
CMD ["gunicorn", "app:app"]