#!/usr/bin/env python3 """ Flask API do obliczania izochron dla połączeń kolejowych """ from flask import Flask, jsonify, request from flask_cors import CORS from pathlib import Path from gtfs_loader import GTFSLoader from isochrone_calculator import IsochroneCalculator app = Flask(__name__) CORS(app) # Pozwala na żądania z frontendu # Globalne zmienne dla danych gtfs_loader = None calculator = None route_graph = None def init_data(): """Inicjalizuje dane GTFS i graf""" global gtfs_loader, calculator, route_graph print('=' * 60) print('🚂 INICJALIZACJA BACKENDU IZOCHRONA') print('=' * 60) data_dir = Path(__file__).parent.parent / 'data' gtfs_file = data_dir / 'polish_trains.zip' if not gtfs_file.exists(): print(f'❌ BŁĄD: Nie znaleziono pliku GTFS: {gtfs_file}') print('Uruchom: python backend/download_gtfs.py') return False try: print(f'\n📍 Plik GTFS: {gtfs_file}') print(f'📏 Rozmiar: {gtfs_file.stat().st_size / 1024 / 1024:.2f} MB\n') gtfs_loader = GTFSLoader(str(gtfs_file)) print() route_graph = gtfs_loader.build_route_graph() print() calculator = IsochroneCalculator(route_graph) print('=' * 60) print('✅ DANE ZAŁADOWANE POMYŚLNIE!') print('=' * 60) return True except Exception as e: print(f'\n❌ Błąd podczas ładowania danych: {e}') import traceback traceback.print_exc() return False @app.route('/api/stops', methods=['GET']) def get_stops(): """Zwraca listę wszystkich przystanków""" if gtfs_loader is None: return jsonify({'error': 'Dane nie zostały załadowane'}), 500 stops = gtfs_loader.get_stops() stops_list = stops.to_dict('records') return jsonify({'stops': stops_list, 'count': len(stops_list)}) @app.route('/api/stops/search', methods=['GET']) def search_stops(): """Wyszukuje przystanki po nazwie""" query = request.args.get('q', '') if not query or len(query) < 2: return jsonify({'error': 'Zapytanie musi mieć min. 2 znaki'}), 400 if gtfs_loader is None: return jsonify({'error': 'Dane nie zostały załadowane'}), 500 stops = gtfs_loader.find_stops_by_name(query) stops_list = stops.to_dict('records') return jsonify({'stops': stops_list, 'count': len(stops_list)}) @app.route('/api/isochrones', methods=['POST']) def calculate_isochrones(): """ Oblicza izochrony dla wybranego przystanku Body (JSON): { "origin_stop_id": "...", "time_intervals": [30, 60, 90, 120] # opcjonalne } """ if calculator is None: return jsonify({'error': 'Dane nie zostały załadowane'}), 500 data = request.get_json() origin_stop_id = data.get('origin_stop_id') if not origin_stop_id: return jsonify({'error': 'Brak origin_stop_id'}), 400 time_intervals = data.get('time_intervals', [30, 60, 90, 120]) try: isochrones = calculator.create_isochrones(origin_stop_id, time_intervals) reachable_stops = calculator.get_reachable_stops( origin_stop_id, max_time=max(time_intervals) ) return jsonify({ 'origin_stop_id': origin_stop_id, 'isochrones': isochrones, 'reachable_stops': reachable_stops }) except ValueError as e: return jsonify({'error': str(e)}), 404 except Exception as e: return jsonify({'error': f'Błąd obliczeń: {str(e)}'}), 500 @app.route('/api/reachable', methods=['POST']) def get_reachable(): """ Zwraca listę osiągalnych przystanków Body (JSON): { "origin_stop_id": "...", "max_time": 120 # w minutach } """ if calculator is None: return jsonify({'error': 'Dane nie zostały załadowane'}), 500 data = request.get_json() origin_stop_id = data.get('origin_stop_id') max_time = data.get('max_time', 120) if not origin_stop_id: return jsonify({'error': 'Brak origin_stop_id'}), 400 try: reachable = calculator.get_reachable_stops(origin_stop_id, max_time) return jsonify({'reachable_stops': reachable, 'count': len(reachable)}) except Exception as e: return jsonify({'error': str(e)}), 500 @app.route('/api/health', methods=['GET']) def health(): """Sprawdza status serwera""" status = { 'status': 'ok' if gtfs_loader is not None else 'error', 'data_loaded': gtfs_loader is not None, 'stops_count': len(gtfs_loader.get_stops()) if gtfs_loader else 0, 'graph_nodes': route_graph.number_of_nodes() if route_graph else 0, 'graph_edges': route_graph.number_of_edges() if route_graph else 0, } return jsonify(status) # Inicjalizacja danych przy starcie (działa zarówno z gunicornem jak i bezpośrednio) init_data() if __name__ == '__main__': print('=== Izochrona API Server ===\n') if gtfs_loader is not None: print('\n🚀 Serwer uruchomiony na http://localhost:5000') app.run(debug=True, host='0.0.0.0', port=5000) else: print('\n❌ Nie udało się załadować danych. Sprawdź logi powyżej.')