koleo-izochrona/TECHNICAL.md

192 lines
4.7 KiB
Markdown

# 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](https://docs.astral.sh/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
```python
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:
```python
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
```python
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:**
```json
{
"origin_stop_id": "5100069",
"time_intervals": [30, 60, 90, 120]
}
```
**Response:**
```json
{
"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:
```python
# 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):
```dockerfile
FROM python:3.9
WORKDIR /app
COPY backend/ .
RUN pip install -r requirements.txt
CMD ["gunicorn", "app:app"]
```