Spaces:
Sleeping
Sleeping
| # ============================================================================== | |
| # ШАГ 1: ИМПОРТ И НАСТРОЙКА | |
| # ============================================================================== | |
| print("🚀 Шаг 1: Импорт всех библиотек...") | |
| import osmnx as ox | |
| import networkx as nx | |
| import pandas as pd | |
| import datetime | |
| import folium | |
| import gradio as gr | |
| from geopy.geocoders import Nominatim | |
| from geopy.extra.rate_limiter import RateLimiter | |
| import pickle | |
| from catboost import CatBoostRegressor | |
| print("✅ Импорт завершен.") | |
| # ============================================================================== | |
| # ШАГ 2: ЗАГРУЗКА ПРЕДОБРАБОТАННЫХ ДАННЫХ | |
| # ============================================================================== | |
| print("\n🌍 Шаг 2: Загрузка данных о городе из 'graph_data.pkl'...") | |
| with open("graph_data.pkl", "rb") as f: | |
| city_data = pickle.load(f) | |
| G = city_data["graph"] | |
| gdf_edges = city_data["edges_gdf"] | |
| signal_nodes_set = city_data["signal_nodes"] | |
| print(f" ✅ Граф ({G.number_of_nodes()} узлов, {G.number_of_edges()} ребер) загружен.") | |
| print(f" ✅ Данные о светофорах ({len(signal_nodes_set)} перекрестков) загружены.") | |
| print("✅ Данные о городе полностью готовы!") | |
| # ============================================================================== | |
| # ШАГ 3: ЗАГРУЗКА ОБУЧЕННОЙ МОДЕЛИ | |
| # ============================================================================== | |
| print("\n🧠 Шаг 3: Загрузка обученной модели предсказания трафика...") | |
| model = CatBoostRegressor() | |
| model.load_model("bishkek_traffic_model.cbm") | |
| print("✅ Модель успешно загружена.") | |
| # ============================================================================== | |
| # ШАГ 4: ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ | |
| # ============================================================================== | |
| print("\n🧩 Шаг 4: Сборка функций-компонентов навигатора...") | |
| def predict_graph_weights(graph_edges_df, model, timestamp): | |
| """Предсказывает время проезда по каждому ребру графа.""" | |
| df = graph_edges_df.copy() | |
| df['hour'] = timestamp.hour | |
| df['minute'] = timestamp.minute | |
| df['day_of_week'] = timestamp.weekday() | |
| df.rename(columns={'highway': 'highway_cat'}, inplace=True) | |
| required_features = ['lanes', 'maxspeed', 'length', 'poi_count', 'highway_cat', 'hour', 'minute', 'day_of_week'] | |
| for col in required_features: | |
| if col not in df.columns: | |
| df[col] = 0 | |
| return model.predict(df[required_features]) | |
| def find_fastest_route(G, start_node, end_node, weight='travel_time'): | |
| """Находит единственный кратчайший маршрут.""" | |
| try: | |
| route = nx.shortest_path(G, source=start_node, target=end_node, weight=weight) | |
| time = nx.shortest_path_length(G, source=start_node, target=end_node, weight=weight) | |
| return route, time | |
| except nx.NetworkXNoPath: | |
| return None, None | |
| def get_signal_delay(day_of_week, hour): | |
| """Определяет задержку на светофоре согласно правилам.""" | |
| is_weekday = 0 <= day_of_week <= 4 | |
| is_morning_rush = 8 <= hour <= 10 | |
| is_evening_rush = 17 <= hour <= 20 | |
| if is_weekday and (is_morning_rush or is_evening_rush): | |
| return 35 | |
| is_night_time = hour >= 23 or hour <= 7 | |
| if is_night_time: | |
| return 20 | |
| if is_weekday: | |
| return 25 | |
| else: | |
| return 20 | |
| # --- ИЗМЕНЕНИЕ ДИЗАЙНА ЗДЕСЬ --- | |
| def plot_route_on_map(G, route, start_point, end_point): | |
| """Отрисовывает красивый зеленый маршрут с эффектом подсветки.""" | |
| m = folium.Map(tiles="OpenStreetMap") | |
| if route: | |
| points = [(G.nodes[node]['y'], G.nodes[node]['x']) for node in route] | |
| # Слой 1: Внешняя подсветка (широкая, полупрозрачная) | |
| folium.PolyLine(points, color='#6ee7b7', weight=12, opacity=0.7).add_to(m) | |
| # Слой 2: Белый контур для контрастности | |
| folium.PolyLine(points, color="white", weight=8, opacity=1).add_to(m) | |
| # Слой 3: Основная линия маршрута (насыщенный зеленый) | |
| main_line = folium.PolyLine(points, color="#16a34a", weight=5, opacity=1, tooltip="Оптимальный маршрут") | |
| main_line.add_to(m) | |
| m.fit_bounds(main_line.get_bounds(), padding=(30, 30)) | |
| # Стильные маркеры | |
| folium.Marker( | |
| location=start_point, popup="<b>Точка А (Старт)</b>", | |
| icon=folium.Icon(color="green", icon="play-circle", prefix='fa') | |
| ).add_to(m) | |
| folium.Marker( | |
| location=end_point, popup="<b>Точка Б (Финиш)</b>", | |
| icon=folium.Icon(color="red", icon="flag-checkered", prefix='fa') | |
| ).add_to(m) | |
| return m | |
| print("✅ Вспомогательные функции готовы.") | |
| # ============================================================================== | |
| # ШАГ 5: ГЛАВНАЯ ФУНКЦИЯ И ИНТЕРФЕЙС GRADIO | |
| # ============================================================================== | |
| print("\n🚀 Шаг 5: Настройка и запуск веб-интерфейса Gradio...") | |
| geolocator = Nominatim(user_agent="bishkek_navigator_app_v9") | |
| geocode = RateLimiter(geolocator.geocode, min_delay_seconds=1) | |
| def find_and_plot_route_by_address(start_address, end_address, day_of_week, hour, minute): | |
| """Основная функция: принимает адреса, строит маршрут и возвращает карту и информацию.""" | |
| if not start_address or not end_address: | |
| return None, "### Внимание\nПожалуйста, введите оба адреса." | |
| try: | |
| location_a = geocode(f"{start_address}, Бишкек") | |
| location_b = geocode(f"{end_address}, Бишкек") | |
| if not location_a or not location_b: | |
| return None, "### Ошибка\nНе удалось найти один или оба адреса. Попробуйте уточнить запрос." | |
| start_point = (location_a.latitude, location_a.longitude) | |
| end_point = (location_b.latitude, location_b.longitude) | |
| start_node = ox.nearest_nodes(G, Y=start_point[0], X=start_point[1]) | |
| end_node = ox.nearest_nodes(G, Y=end_point[0], X=end_point[1]) | |
| current_hour = int(hour) | |
| selected_time = datetime.datetime(2023, 1, 2 + day_of_week, current_hour, int(minute)) | |
| travel_times = predict_graph_weights(gdf_edges, model, selected_time) | |
| nx.set_edge_attributes(G, values=pd.Series(travel_times, index=gdf_edges.index).to_dict(), name='travel_time') | |
| route, travel_time = find_fastest_route(G, start_node, end_node, weight='travel_time') | |
| if route is None: | |
| return None, "### Ошибка\nНе удалось построить маршрут. Возможно, точки находятся в несвязанных частях города." | |
| SIGNAL_DELAY_SECONDS = get_signal_delay(day_of_week, current_hour) | |
| signals_on_route = sum(1 for node in route if node in signal_nodes_set) | |
| total_time_sec = travel_time + signals_on_route * SIGNAL_DELAY_SECONDS | |
| total_time_min = total_time_sec / 60 | |
| distance_km = sum(G[u][v][0]['length'] for u, v in zip(route[:-1], route[1:])) / 1000 | |
| final_map = plot_route_on_map(G, route, start_point, end_point) | |
| # --- ИЗМЕНЕНИЕ ЦВЕТА ТЕКСТА ЗДЕСЬ --- | |
| output_md = f""" | |
| ### Маршрут построен! | |
| - **Время в пути:** <span style="font-size: 1.1em; color: #16a34a;">**~{total_time_min:.1f} мин.**</span> | |
| - **Расстояние:** **{distance_km:.2f} км** | |
| - **Светофоров на пути:** **{signals_on_route}** | |
| <p style="font-size: 0.8em; color: #6b7280;">(задержка на светофор: {SIGNAL_DELAY_SECONDS} сек.)</p> | |
| """ | |
| return final_map._repr_html_(), output_md | |
| except Exception as e: | |
| return None, f"### Произошла внутренняя ошибка:\n`{e}`" | |
| # CSS для новой сине-голубой темы | |
| css = """ | |
| body { font-family: 'Inter', sans-serif; } | |
| .gradio-container { max-width: 100% !important; } | |
| .gr-button-primary { | |
| background: linear-gradient(to right, #60a5fa, #3b82f6); | |
| color: white; font-weight: bold; border: none; transition: all 0.3s ease; | |
| box-shadow: 0 4px 14px 0 rgba(59, 130, 246, 0.39); | |
| } | |
| .gr-button-primary:hover { | |
| background: linear-gradient(to right, #3b82f6, #2563eb); | |
| box-shadow: 0 6px 20px 0 rgba(59, 130, 246, 0.45); transform: translateY(-1px); | |
| } | |
| #info-box { background-color: #f9fafb; padding: 1.5rem !important; border-radius: 0.75rem; border: 1px solid #e5e7eb;} | |
| """ | |
| with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="blue", font=gr.themes.GoogleFont("Inter")), css=css) as demo: | |
| gr.Markdown( | |
| """ | |
| <div style="text-align: center;"> | |
| <h1 style="font-size: 2.5rem; font-weight: 700;"> Умный навигатор по Бишкеку</h1> | |
| <p style="color: #4b5563; font-size: 1.1rem;">Постройте оптимальный маршрут с учётом прогноза пробок</p> | |
| </div> | |
| """ | |
| ) | |
| with gr.Row(equal_height=False): | |
| with gr.Column(scale=1): | |
| with gr.Group(): | |
| start_address_input = gr.Textbox(label="Откуда?", placeholder="Например, 7 микрорайон") | |
| end_address_input = gr.Textbox(label="Куда?", placeholder="Например, Филармония") | |
| day_dropdown = gr.Dropdown(label="День недели", choices=["Понедельник", "Вторник", "Среда", "Четверг", "Пятница", "Суббота", "Воскресенье"], value="Понедельник", type="index") | |
| current_time = datetime.datetime.now() | |
| hour_slider = gr.Slider(label="Час", minimum=0, maximum=23, step=1, value=current_time.hour) | |
| minute_slider = gr.Slider(label="Минута", minimum=0, maximum=59, step=1, value=current_time.minute) | |
| build_btn = gr.Button("Найти лучший маршрут", variant="primary") | |
| output_info = gr.Markdown(label="Информация о маршруте", elem_id="info-box") | |
| with gr.Column(scale=3): | |
| output_map_html = gr.HTML(label="Карта с маршрутом", elem_id="map-output") | |
| build_btn.click( | |
| fn=find_and_plot_route_by_address, | |
| inputs=[start_address_input, end_address_input, day_dropdown, hour_slider, minute_slider], | |
| outputs=[output_map_html, output_info] | |
| ) | |
| demo.launch() | |
| print("\n\n🌐 Веб-интерфейс запущен!") |