ErzhanAb's picture
Update app.py
266af70 verified
# ==============================================================================
# ШАГ 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🌐 Веб-интерфейс запущен!")