ErzhanAb commited on
Commit
82c3369
·
verified ·
1 Parent(s): 389e8a7

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +200 -0
app.py ADDED
@@ -0,0 +1,200 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ==============================================================================
2
+ # ШАГ 1: ИМПОРТ И НАСТРОЙКА
3
+ # ==============================================================================
4
+ print("🚀 Шаг 1: Импорт всех библиотек...")
5
+
6
+ import osmnx as ox
7
+ import networkx as nx
8
+ import geopandas as gpd
9
+ import pandas as pd
10
+ import numpy as np
11
+ from catboost import CatBoostRegressor
12
+ import datetime
13
+ import folium
14
+ import gradio as gr
15
+ from geopy.geocoders import Nominatim
16
+ from geopy.extra.rate_limiter import RateLimiter
17
+
18
+ print("✅ Импорт завершен.")
19
+
20
+ # ==============================================================================
21
+ # ШАГ 2: ЗАГРУЗКА ДАННЫХ ГОРОДА (ГРАФ, СВЕТОФОРЫ, POI)
22
+ # Этот блок выполняется один раз при запуске приложения.
23
+ # ==============================================================================
24
+ print("\n🌍 Шаг 2: Подготовка данных о городе Бишкек...")
25
+
26
+ place_name = "Bishkek, Kyrgyzstan"
27
+
28
+ # --- Загрузка графа дорог ---
29
+ print(" - Загружаем дорожный граф... (это может занять несколько минут)")
30
+ G = ox.graph_from_place(place_name, network_type='drive', simplify=True)
31
+ G = ox.add_edge_speeds(G)
32
+ G = ox.add_edge_travel_times(G)
33
+ print(" - ✅ Граф дорог загружен.")
34
+
35
+ # --- Преобразование в таблицы и подготовка фичей ---
36
+ print(" - Готовим фичи для ребер графа...")
37
+ gdf_nodes, gdf_edges_raw = ox.graph_to_gdfs(G)
38
+ gdf_edges = gdf_edges_raw.copy()
39
+
40
+ # Очистка данных (взято из твоего ноутбука)
41
+ gdf_edges['maxspeed'] = gdf_edges['maxspeed'].apply(lambda x: x[0] if isinstance(x, list) else x).astype(str).str.extract('(\d+)').astype(float).fillna(40)
42
+ gdf_edges['lanes'] = gdf_edges['lanes'].apply(lambda x: x[0] if isinstance(x, list) else x)
43
+ gdf_edges['lanes'] = pd.to_numeric(gdf_edges['lanes'], errors='coerce').fillna(1)
44
+ gdf_edges['highway'] = gdf_edges['highway'].apply(lambda x: x[0] if isinstance(x, list) else x).fillna(gdf_edges['highway'].mode()[0])
45
+
46
+ # --- Загрузка и привязка светофоров ---
47
+ print(" - Загружаем данные о светофорах...")
48
+ tags_signals = {"highway": "traffic_signals"}
49
+ gdf_signals = ox.features_from_place(place_name, tags_signals)
50
+ signal_nodes = ox.nearest_nodes(G, X=gdf_signals.geometry.x, Y=gdf_signals.geometry.y)
51
+ signal_nodes_set = set(signal_nodes)
52
+ print(f" - ✅ Найдено и привязано {len(signal_nodes_set)} перекрестков со светофорами.")
53
+
54
+
55
+ # --- Загрузка и подсчет POI (точек интереса) ---
56
+ print(" - Загружаем POI и обогащаем граф... (это тоже может занять время)")
57
+ tags_poi = {
58
+ "amenity": ["restaurant", "cafe", "school", "university", "bank", "atm", "pharmacy", "hospital"],
59
+ "shop": ["supermarket", "mall"], "office": True, "public_transport": ["station", "platform"]
60
+ }
61
+ gdf_poi = ox.features_from_place(place_name, tags_poi)
62
+
63
+ # Расчет POI
64
+ utm_crs = gdf_edges.estimate_utm_crs()
65
+ projected_gdf_edges = gdf_edges.to_crs(utm_crs)
66
+ projected_gdf_poi = gdf_poi.to_crs(utm_crs)
67
+ buffer_gdf = gpd.GeoDataFrame(geometry=projected_gdf_edges.geometry.centroid.buffer(50), index=projected_gdf_edges.index)
68
+ joined_gdf = gpd.sjoin(buffer_gdf, projected_gdf_poi, how="inner", predicate="intersects")
69
+ poi_counts = joined_gdf.index.value_counts()
70
+ gdf_edges['poi_count'] = poi_counts.reindex(gdf_edges.index, fill_value=0)
71
+ print(" - ✅ Данные обогащены точками интереса (POI).")
72
+
73
+
74
+ print("✅ Данные о городе полностью подготовлены!")
75
+
76
+
77
+ # ==============================================================================
78
+ # ШАГ 3: ЗАГРУЗКА ОБУЧЕННОЙ МОДЕЛИ
79
+ # ==============================================================================
80
+ print("\n🧠 Шаг 3: Загрузка обученной модели предсказания трафика...")
81
+ model_filename = "bishkek_traffic_model.cbm"
82
+ model = CatBoostRegressor()
83
+ model.load_model(model_filename)
84
+ print("✅ Модель успешно загружена.")
85
+
86
+
87
+ # ==============================================================================
88
+ # ШАГ 4: ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ДЛЯ НАВИГАТОРА
89
+ # ==============================================================================
90
+ print("\n🧩 Шаг 4: Сборка функций-компонентов навигатора...")
91
+
92
+ def predict_graph_weights(graph_edges_df, model, timestamp):
93
+ df = graph_edges_df.copy()
94
+ df['hour'] = timestamp.hour
95
+ df['minute'] = timestamp.minute
96
+ df['day_of_week'] = timestamp.weekday()
97
+ df.rename(columns={'highway': 'highway_cat'}, inplace=True)
98
+ features_for_pred = ['lanes', 'maxspeed', 'length', 'poi_count', 'highway_cat', 'hour', 'minute', 'day_of_week']
99
+ predictions = model.predict(df[features_for_pred])
100
+ return predictions
101
+
102
+ def find_two_routes(G, start_node, end_node, weight='travel_time'):
103
+ try:
104
+ route1 = nx.shortest_path(G, source=start_node, target=end_node, weight=weight)
105
+ time1 = nx.shortest_path_length(G, source=start_node, target=end_node, weight=weight)
106
+ except nx.NetworkXNoPath: return None, None, None, None
107
+ G_penalized = G.copy()
108
+ for u, v in zip(route1[:-1], route1[1:]):
109
+ if G_penalized.has_edge(u, v): G_penalized[u][v][0][weight] *= 2
110
+ try:
111
+ route2 = nx.shortest_path(G_penalized, source=start_node, target=end_node, weight=weight)
112
+ time2 = sum(G[u][v][0][weight] for u, v in zip(route2[:-1], route2[1:]))
113
+ except nx.NetworkXNoPath: route2, time2 = None, None
114
+ return route1, time1, route2, time2
115
+
116
+ def plot_routes_on_folium_map(G, route1, route2, start_point, end_point):
117
+ map_center = [(start_point[0] + end_point[0]) / 2, (start_point[1] + end_point[1]) / 2]
118
+ m = folium.Map(location=map_center, zoom_start=13, tiles="OpenStreetMap")
119
+ if route1:
120
+ points1 = [(G.nodes[node]['y'], G.nodes[node]['x']) for node in route1]
121
+ folium.PolyLine(points1, color="white", weight=11, opacity=0.7).add_to(m)
122
+ folium.PolyLine(points1, color="#2ca02c", weight=6, opacity=0.85, tooltip="Маршрут 1").add_to(m)
123
+ if route2 and route1 != route2:
124
+ points2 = [(G.nodes[node]['y'], G.nodes[node]['x']) for node in route2]
125
+ folium.PolyLine(points2, color="white", weight=11, opacity=0.7).add_to(m)
126
+ folium.PolyLine(points2, color="#007FFF", weight=6, opacity=0.85, tooltip="Маршрут 2").add_to(m)
127
+ folium.Marker(location=start_point, popup="Точка А (Старт)", icon=folium.Icon(color="green")).add_to(m)
128
+ folium.Marker(location=end_point, popup="Точка Б (Финиш)", icon=folium.Icon(color="red")).add_to(m)
129
+ return m
130
+
131
+ print("✅ Вспомогательные функции готовы.")
132
+
133
+ # ==============================================================================
134
+ # ШАГ 5: ГЛАВНАЯ ФУНКЦИЯ И ИНТЕРФЕЙС GRADIO
135
+ # ==============================================================================
136
+ print("\n🚀 Шаг 5: Настройка и запуск веб-интерфейса Gradio...")
137
+
138
+ geolocator = Nominatim(user_agent="bishkek_navigator_app")
139
+ geocode = RateLimiter(geolocator.geocode, min_delay_seconds=1)
140
+
141
+ def find_and_plot_routes_by_address(start_address, end_address, day_of_week, hour, minute):
142
+ if not start_address or not end_address: return None, "Ошибка: Пожалуйста, введите оба адреса."
143
+ if hour is None or minute is None: return None, "Ошибка: Пожалуйста, установите значения для часа и минуты."
144
+ try:
145
+ location_a = geocode(f"{start_address}, Бишкек"); location_b = geocode(f"{end_address}, Бишкек")
146
+ if not location_a or not location_b: return None, "Не удалось найти один или оба адреса. Попробуйте уточнить."
147
+
148
+ start_point = (location_a.latitude, location_a.longitude); end_point = (location_b.latitude, location_b.longitude)
149
+ 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])
150
+
151
+ selected_time = datetime.datetime(2023, 1, 2 + day_of_week, hour, minute)
152
+ travel_times = predict_graph_weights(gdf_edges, model, selected_time)
153
+ nx.set_edge_attributes(G, values=pd.Series(travel_times, index=gdf_edges.index).to_dict(), name='travel_time')
154
+
155
+ r1, t1, r2, t2 = find_two_routes(G, start_node, end_node, weight='travel_time')
156
+ if r1 is None: return None, "Не удалось построить маршрут."
157
+
158
+ SIGNAL_DELAY_SECONDS = 30
159
+ signals_on_route1 = sum(1 for node in r1 if node in signal_nodes_set); t1_total = t1 + signals_on_route1 * SIGNAL_DELAY_SECONDS
160
+ distance1_km = sum(G[u][v][0]['length'] for u, v in zip(r1[:-1], r1[1:])) / 1000
161
+
162
+ t2_total, distance2_km = None, None
163
+ if r2 and r1 != r2:
164
+ signals_on_route2 = sum(1 for node in r2 if node in signal_nodes_set); t2_total = t2 + signals_on_route2 * SIGNAL_DELAY_SECONDS
165
+ distance2_km = sum(G[u][v][0]['length'] for u, v in zip(r2[:-1], r2[1:])) / 1000
166
+
167
+ final_map = plot_routes_on_folium_map(G, r1, r2, start_point, end_point)
168
+
169
+ output_text = f"Маршрут 1:\n - Время: ~{t1_total / 60:.1f} мин.\n - Расстояние: {distance1_km:.2f} км\n - Светофоров: {signals_on_route1}\n"
170
+ if t2_total is not None:
171
+ output_text += f"\nМаршрут 2:\n - Время: ~{t2_total / 60:.1f} мин.\n - Расстояние: {distance2_km:.2f} км\n - Светофоров: {signals_on_route2}"
172
+ else: output_text += "\nАльтернативный маршрут не найден."
173
+
174
+ return final_map._repr_html_(), output_text
175
+
176
+ except Exception as e:
177
+ return None, f"Произошла внутренняя ошибка: {e}"
178
+
179
+ with gr.Blocks(theme=gr.themes.Soft()) as demo:
180
+ gr.Markdown("# 🗺️ Умный навигатор по Бишкеку")
181
+ with gr.Row():
182
+ with gr.Column(scale=1):
183
+ start_address_input = gr.Textbox(label="Откуда?", placeholder="Например, 7-й микрорайон")
184
+ end_address_input = gr.Textbox(label="Куда?", placeholder="Например, Ошский базар")
185
+ day_dropdown = gr.Dropdown(label="День недели", choices=["Понедельник", "Вторник", "Среда", "Четверг", "Пятница", "Суббота", "Воскресенье"], value="Понедельник", type="index")
186
+ hour_slider = gr.Slider(label="Час", minimum=0, maximum=23, step=1, value=8)
187
+ minute_slider = gr.Slider(label="Минута", minimum=0, maximum=59, step=1, value=30)
188
+ build_btn = gr.Button("🚀 Построить маршруты", variant="primary")
189
+ output_textbox = gr.Textbox(label="Информация о маршрутах", interactive=False, lines=7)
190
+ with gr.Column(scale=2):
191
+ output_map_html = gr.HTML(label="Карта с маршрутами")
192
+ build_btn.click(
193
+ fn=find_and_plot_routes_by_address,
194
+ inputs=[start_address_input, end_address_input, day_dropdown, hour_slider, minute_slider],
195
+ outputs=[output_map_html, output_textbox]
196
+ )
197
+
198
+ demo.launch()
199
+
200
+ print("\n\n🌐 Веб-интерфейс запущен!")