192 lines
4.7 KiB
Markdown
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"]
|
|
```
|