Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -71,31 +71,18 @@ def find_fastest_route(G, start_node, end_node, weight='travel_time'):
|
|
| 71 |
return None, None
|
| 72 |
|
| 73 |
def plot_route_on_map(G, route, start_point, end_point):
|
| 74 |
-
"""Отрисовывает один маршрут на стильной
|
| 75 |
-
# Используем минималистичные тайлы 'CartoDB positron' для чистого вида
|
| 76 |
m = folium.Map(tiles="CartoDB positron", attr='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors © <a href="https://carto.com/attributions">CARTO</a>')
|
| 77 |
|
| 78 |
if route:
|
| 79 |
points = [(G.nodes[node]['y'], G.nodes[node]['x']) for node in route]
|
| 80 |
-
|
| 81 |
-
# Белая "подложка" для контраста и лучшей видимости
|
| 82 |
-
folium.PolyLine(points, color="white", weight=10, opacity=0.8).add_to(m)
|
| 83 |
-
# Основная линия маршрута - яркий, приятный синий цвет
|
| 84 |
-
main_line = folium.PolyLine(points, color="#007FFF", weight=5, opacity=1, tooltip="Оптимальный маршрут")
|
| 85 |
main_line.add_to(m)
|
| 86 |
-
|
| 87 |
-
# Автоматическая настройка масштаба карты под маршрут
|
| 88 |
m.fit_bounds(main_line.get_bounds(), padding=(30, 30))
|
| 89 |
|
| 90 |
-
#
|
| 91 |
-
folium.Marker(
|
| 92 |
-
|
| 93 |
-
icon=folium.Icon(color="green", icon="play", prefix='fa')
|
| 94 |
-
).add_to(m)
|
| 95 |
-
folium.Marker(
|
| 96 |
-
location=end_point, popup="<b>Финиш</b>",
|
| 97 |
-
icon=folium.Icon(color="red", icon="flag-checkered", prefix='fa')
|
| 98 |
-
).add_to(m)
|
| 99 |
|
| 100 |
return m
|
| 101 |
|
|
@@ -106,7 +93,7 @@ print("✅ Вспомогательные функции готовы.")
|
|
| 106 |
# ==============================================================================
|
| 107 |
print("\n🚀 Шаг 5: Настройка и запуск веб-интерфейса Gradio...")
|
| 108 |
|
| 109 |
-
geolocator = Nominatim(user_agent="
|
| 110 |
geocode = RateLimiter(geolocator.geocode, min_delay_seconds=1)
|
| 111 |
|
| 112 |
def find_and_plot_route_by_address(start_address, end_address, day_of_week, hour, minute):
|
|
@@ -125,66 +112,78 @@ def find_and_plot_route_by_address(start_address, end_address, day_of_week, hour
|
|
| 125 |
start_node = ox.nearest_nodes(G, Y=start_point[0], X=start_point[1])
|
| 126 |
end_node = ox.nearest_nodes(G, Y=end_point[0], X=end_point[1])
|
| 127 |
|
| 128 |
-
# Устанавливаем веса ребер графа на основе прогноза модели
|
| 129 |
selected_time = datetime.datetime(2023, 1, 2 + day_of_week, int(hour), int(minute))
|
| 130 |
travel_times = predict_graph_weights(gdf_edges, model, selected_time)
|
| 131 |
nx.set_edge_attributes(G, values=pd.Series(travel_times, index=gdf_edges.index).to_dict(), name='travel_time')
|
| 132 |
|
| 133 |
-
# Ищем единственный лучший маршрут
|
| 134 |
route, travel_time = find_fastest_route(G, start_node, end_node, weight='travel_time')
|
| 135 |
if route is None:
|
| 136 |
return None, "### ❌ Ошибка\nНе удалось построить маршрут. Возможно, точки находятся в несвязанных частях города."
|
| 137 |
|
| 138 |
-
# Расчет итоговых параметров маршрута
|
| 139 |
SIGNAL_DELAY_SECONDS = 30
|
| 140 |
signals_on_route = sum(1 for node in route if node in signal_nodes_set)
|
| 141 |
total_time_sec = travel_time + signals_on_route * SIGNAL_DELAY_SECONDS
|
| 142 |
total_time_min = total_time_sec / 60
|
| 143 |
distance_km = sum(G[u][v][0]['length'] for u, v in zip(route[:-1], route[1:])) / 1000
|
| 144 |
|
| 145 |
-
# Создание карты
|
| 146 |
final_map = plot_route_on_map(G, route, start_point, end_point)
|
| 147 |
|
| 148 |
-
# Форматирование вывода в Markdown
|
| 149 |
output_md = f"""
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
|
|
|
| 154 |
"""
|
| 155 |
-
return final_map._repr_html_(), output_md
|
| 156 |
|
| 157 |
except Exception as e:
|
| 158 |
return None, f"### 💥 Произошла внутренняя ошибка:\n`{e}`"
|
| 159 |
|
| 160 |
-
# CSS для стилизации интерфейса
|
| 161 |
css = """
|
| 162 |
-
body { font-family: 'Inter', sans-serif;
|
| 163 |
-
.gradio-container { max-width: 1200px !important; margin: auto !important; padding-top:
|
| 164 |
-
|
|
|
|
|
|
|
| 165 |
.gr-button-primary {
|
| 166 |
-
background:
|
| 167 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 168 |
}
|
| 169 |
-
.gr-button-primary:hover { box-shadow: 0 7px 14px rgba(67, 233, 123, 0.3); transform: translateY(-2px); }
|
| 170 |
-
#info-box { background-color: white; padding: 1.5rem !important; border-radius: 1rem; box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); }
|
| 171 |
"""
|
| 172 |
|
| 173 |
-
with gr.Blocks(theme=gr.themes.Soft(primary_hue="teal", secondary_hue="gray"), css=css) as demo:
|
| 174 |
gr.Markdown(
|
| 175 |
"""
|
| 176 |
-
<div style="text-align: center;">
|
| 177 |
-
<h1 style="font-size: 2.5rem; font-weight: 700;">🗺️ Умный навигатор по Бишкеку</h1>
|
| 178 |
-
<p style="color: #
|
| 179 |
</div>
|
| 180 |
"""
|
| 181 |
)
|
| 182 |
with gr.Row(equal_height=False):
|
| 183 |
with gr.Column(scale=1):
|
| 184 |
-
# ИСПРАВЛЕНИЕ: gr.Box() заменен на gr.Group() для совместимости
|
| 185 |
with gr.Group():
|
| 186 |
-
start_address_input = gr.Textbox(label="Откуда?", placeholder="
|
| 187 |
-
end_address_input = gr.Textbox(label="Куда?", placeholder="
|
| 188 |
|
| 189 |
with gr.Accordion("🗓️ Указать время и дату", open=False):
|
| 190 |
day_dropdown = gr.Dropdown(label="День недели", choices=["Понедельник", "Вторник", "Среда", "Четверг", "Пятница", "Суббота", "Воскресенье"], value="Понедельник", type="index")
|
|
|
|
| 71 |
return None, None
|
| 72 |
|
| 73 |
def plot_route_on_map(G, route, start_point, end_point):
|
| 74 |
+
"""Отрисовывает один маршрут на стильной карте, как в скриншоте."""
|
|
|
|
| 75 |
m = folium.Map(tiles="CartoDB positron", attr='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors © <a href="https://carto.com/attributions">CARTO</a>')
|
| 76 |
|
| 77 |
if route:
|
| 78 |
points = [(G.nodes[node]['y'], G.nodes[node]['x']) for node in route]
|
| 79 |
+
main_line = folium.PolyLine(points, color="#007FFF", weight=5, opacity=0.9)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 80 |
main_line.add_to(m)
|
|
|
|
|
|
|
| 81 |
m.fit_bounds(main_line.get_bounds(), padding=(30, 30))
|
| 82 |
|
| 83 |
+
# Маркеры, как в скриншоте: красный - старт, зеленый - финиш
|
| 84 |
+
folium.Marker(location=start_point, popup="<b>Старт</b>", icon=folium.Icon(color="red")).add_to(m)
|
| 85 |
+
folium.Marker(location=end_point, popup="<b>Финиш</b>", icon=folium.Icon(color="green")).add_to(m)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 86 |
|
| 87 |
return m
|
| 88 |
|
|
|
|
| 93 |
# ==============================================================================
|
| 94 |
print("\n🚀 Шаг 5: Настройка и запуск веб-интерфейса Gradio...")
|
| 95 |
|
| 96 |
+
geolocator = Nominatim(user_agent="bishkek_navigator_app_v4")
|
| 97 |
geocode = RateLimiter(geolocator.geocode, min_delay_seconds=1)
|
| 98 |
|
| 99 |
def find_and_plot_route_by_address(start_address, end_address, day_of_week, hour, minute):
|
|
|
|
| 112 |
start_node = ox.nearest_nodes(G, Y=start_point[0], X=start_point[1])
|
| 113 |
end_node = ox.nearest_nodes(G, Y=end_point[0], X=end_point[1])
|
| 114 |
|
|
|
|
| 115 |
selected_time = datetime.datetime(2023, 1, 2 + day_of_week, int(hour), int(minute))
|
| 116 |
travel_times = predict_graph_weights(gdf_edges, model, selected_time)
|
| 117 |
nx.set_edge_attributes(G, values=pd.Series(travel_times, index=gdf_edges.index).to_dict(), name='travel_time')
|
| 118 |
|
|
|
|
| 119 |
route, travel_time = find_fastest_route(G, start_node, end_node, weight='travel_time')
|
| 120 |
if route is None:
|
| 121 |
return None, "### ❌ Ошибка\nНе удалось построить маршрут. Возможно, точки находятся в несвязанных частях города."
|
| 122 |
|
|
|
|
| 123 |
SIGNAL_DELAY_SECONDS = 30
|
| 124 |
signals_on_route = sum(1 for node in route if node in signal_nodes_set)
|
| 125 |
total_time_sec = travel_time + signals_on_route * SIGNAL_DELAY_SECONDS
|
| 126 |
total_time_min = total_time_sec / 60
|
| 127 |
distance_km = sum(G[u][v][0]['length'] for u, v in zip(route[:-1], route[1:])) / 1000
|
| 128 |
|
|
|
|
| 129 |
final_map = plot_route_on_map(G, route, start_point, end_point)
|
| 130 |
|
| 131 |
+
# Форматирование вывода в Markdown, как на скриншоте
|
| 132 |
output_md = f"""
|
| 133 |
+
**✅ Маршрут построен!**
|
| 134 |
+
|
| 135 |
+
* Время в пут��: ~{total_time_min:.1f} мин.
|
| 136 |
+
* Расстояние: {distance_km:.2f} км
|
| 137 |
+
* Светофоров на пути: {signals_on_route}
|
| 138 |
"""
|
| 139 |
+
return final_map._repr_html_(), output_md.replace(" *", "*") # Убираем лишние отступы для Markdown
|
| 140 |
|
| 141 |
except Exception as e:
|
| 142 |
return None, f"### 💥 Произошла внутренняя ошибка:\n`{e}`"
|
| 143 |
|
| 144 |
+
# CSS для стилизации интерфейса под дизайн со скриншота
|
| 145 |
css = """
|
| 146 |
+
body, .gradio-container { background-color: #f0fdfa; font-family: 'Inter', sans-serif; }
|
| 147 |
+
.gradio-container { max-width: 1200px !important; margin: auto !important; padding-top: 1.5rem; }
|
| 148 |
+
/* Стили для карты */
|
| 149 |
+
#map-output { border-radius: 1.5rem; overflow: hidden; box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); }
|
| 150 |
+
/* Стили для кнопки */
|
| 151 |
.gr-button-primary {
|
| 152 |
+
background-color: #14b8a6; color: white; font-weight: bold; border-radius: 0.75rem; border: none;
|
| 153 |
+
box-shadow: 0 4px 6px rgba(20, 184, 166, 0.2); transition: all 0.2s ease; padding: 12px 0;
|
| 154 |
+
}
|
| 155 |
+
.gr-button-primary:hover { background-color: #0d9488; box-shadow: 0 7px 10px rgba(20, 184, 166, 0.3); transform: translateY(-2px); }
|
| 156 |
+
/* Стили для информационного блока с результатом */
|
| 157 |
+
#info-box { background-color: white; padding: 1.5rem !important; border-radius: 1rem; box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1), 0 2px 4px -1px rgba(0,0,0,0.06); }
|
| 158 |
+
/* Стили для меток (Откуда?, Куда?, и т.д.) */
|
| 159 |
+
.gradio-container .form .gr-label span {
|
| 160 |
+
background-color: #ccfbf1; color: #134e4a; padding: 5px 12px;
|
| 161 |
+
border-radius: 9999px; font-size: 0.8em; font-weight: 600;
|
| 162 |
+
display: inline-block; margin-bottom: 6px;
|
| 163 |
+
}
|
| 164 |
+
/* Стили для полей ввода */
|
| 165 |
+
.gradio-container .gr-input-text, .gradio-container .gr-dropdown {
|
| 166 |
+
border-radius: 0.75rem !important; border-color: #cbd5e1 !important;
|
| 167 |
+
}
|
| 168 |
+
.gradio-container .gr-input-text:focus, .gradio-container .gr-dropdown:focus {
|
| 169 |
+
border-color: #14b8a6 !important; box-shadow: 0 0 0 2px rgba(20, 184, 166, 0.3) !important;
|
| 170 |
}
|
|
|
|
|
|
|
| 171 |
"""
|
| 172 |
|
| 173 |
+
with gr.Blocks(theme=gr.themes.Soft(primary_hue="teal", secondary_hue="gray", font=gr.themes.GoogleFont("Inter")), css=css) as demo:
|
| 174 |
gr.Markdown(
|
| 175 |
"""
|
| 176 |
+
<div style="text-align: center; margin-bottom: 2rem;">
|
| 177 |
+
<h1 style="font-size: 2.5rem; font-weight: 700; color: #0f766e;">🗺️ Умный навигатор по Бишкеку</h1>
|
| 178 |
+
<p style="color: #0d9488; font-size: 1.1rem;">Постройте оптимальный маршрут с учётом прогноза пробок</p>
|
| 179 |
</div>
|
| 180 |
"""
|
| 181 |
)
|
| 182 |
with gr.Row(equal_height=False):
|
| 183 |
with gr.Column(scale=1):
|
|
|
|
| 184 |
with gr.Group():
|
| 185 |
+
start_address_input = gr.Textbox(label="Откуда?", placeholder="7 микрорайон")
|
| 186 |
+
end_address_input = gr.Textbox(label="Куда?", placeholder="Ошский рынок")
|
| 187 |
|
| 188 |
with gr.Accordion("🗓️ Указать время и дату", open=False):
|
| 189 |
day_dropdown = gr.Dropdown(label="День недели", choices=["Понедельник", "Вторник", "Среда", "Четверг", "Пятница", "Суббота", "Воскресенье"], value="Понедельник", type="index")
|