APP_IA / app.py
Mapu142's picture
Update app.py
02c028c verified
Raw
History Blame Contribute Delete
9.94 kB
import streamlit as st
from pyproj import CRS
import pandas as pd
import geopandas as gpd
from shapely.geometry import LineString, Point
import folium
from streamlit_folium import st_folium
import networkx as nx
from collections import defaultdict # Add this line if missing
import os, sys
import traceback
print(">>> Ejecutando archivo:", os.path.abspath(__file__))
print("Python ejecutado:", sys.executable)
# Cargar shapes.txt manteniendo la precisión
shapes = pd.read_csv('shapes.txt')
routes = pd.read_csv('routes.txt')
stops = pd.read_csv('stops.txt')
stop_times = pd.read_csv('stop_times.txt')
trips = pd.read_csv('trips.txt')
# Convertir puntos a geometría
shapes_gdf = (
shapes.sort_values(["shape_id", "shape_pt_sequence"])
.groupby("shape_id")
.apply(lambda x: LineString(zip(x.shape_pt_lon, x.shape_pt_lat)))
.reset_index()
)
shapes_gdf.columns = ["shape_id", "geometry"]
crs_obj = CRS.from_epsg(4326) # Crea el objeto CRS correctamente
shapes_gdf = gpd.GeoDataFrame(shapes_gdf, geometry="geometry", crs=crs_obj)
@st.cache_resource
def build_graph(routes, stop_times, trips):
route_short = routes.set_index('route_id')['route_short_name'].to_dict()
stops_routes = defaultdict(set)
G = nx.DiGraph()
group_cols = ['route_id', 'shape_id']
trips_grouped = trips.groupby(group_cols)
transfer_penalty = 20
for name, group in trips_grouped:
if group.empty:
continue
route_id, shape_id = name
trip_id_sample = group['trip_id'].iloc[0]
stops_trip = stop_times[stop_times['trip_id'] == trip_id_sample].sort_values('stop_sequence')
stop_ids = stops_trip['stop_id'].tolist()
# Assign stops → route
for stop_id in stop_ids:
stops_routes[stop_id].add(route_id)
# Add ride edges
for i in range(len(stop_ids) - 1):
s1 = stop_ids[i]
s2 = stop_ids[i + 1]
G.add_edge((s1, route_id), (s2, route_id), weight=1)
# Add transfer edges
for stop_id, routes_set in stops_routes.items():
routes_list = list(routes_set)
for i in range(len(routes_list)):
for j in range(len(routes_list)):
if i != j:
G.add_edge((stop_id, routes_list[i]), (stop_id, routes_list[j]), weight=transfer_penalty)
return G, stops_routes, route_short
G, stops_routes, route_short = build_graph(routes, stop_times, trips)
# ============================
# STREAMLIT UI
# ============================
st.title("🚍 Planificador inteligente — TransMilenio")
st.write("Esta app detecta si la ruta **en la que ya vas** te sirve para llegar al destino, y sugiere transbordos si es necesario.")
# ----------------------------
# SELECT ROUTE
# ----------------------------
st.header("1️⃣ ¿En qué ruta vas?")
ruta_seleccionada = st.selectbox("Selecciona tu ruta", routes.route_short_name.unique())
route_id = routes.loc[routes.route_short_name == ruta_seleccionada, "route_id"].iloc[0]
trips_ruta = trips[trips.route_id == route_id]
if trips_ruta.empty:
st.error("No se encontraron viajes para esta ruta.")
st.stop()
trip_ids = trips_ruta['trip_id'].unique()
all_stops_route = stop_times[stop_times['trip_id'].isin(trip_ids)].merge(stops, on="stop_id")['stop_name'].unique()
# ----------------------------
# ORIGIN
# ----------------------------
st.header("2️⃣ ¿Dónde estás?")
modo_origen = st.radio("Selecciona cómo indicar dónde estás:", ["Parada", "Coordenadas"])
if modo_origen == "Parada":
parada_origen = st.selectbox("Selecciona tu parada actual", all_stops_route)
stop_actual = stops[stops.stop_name == parada_origen].iloc[0]
current_stop_id = stop_actual['stop_id']
else:
lat = st.number_input("Latitud", value=4.65)
lon = st.number_input("Longitud", value=-74.1)
all_stops_route_df = stop_times[stop_times['trip_id'].isin(trip_ids)].merge(stops, on="stop_id").drop_duplicates('stop_id')
stops_route_gdf = gpd.GeoDataFrame(
all_stops_route_df,
geometry=gpd.points_from_xy(all_stops_route_df.stop_lon, all_stops_route_df.stop_lat),
crs=4326
)
user_point = Point(lon, lat)
distances = stops_route_gdf.geometry.distance(user_point)
nearest_idx = distances.argmin()
stop_actual = stops_route_gdf.iloc[nearest_idx]
current_stop_id = stop_actual['stop_id']
st.write(f"Parada más cercana detectada: **{stop_actual.stop_name}**")
# ----------------------------
# DESTINATION
# ----------------------------
st.header("3️⃣ ¿A dónde vas?")
destino = st.text_input("Escribe la parada o dirección de destino")
calcular = st.button("Calcular ruta")
# ============================
# ROUTING ENGINE
# ============================
if calcular:
# Find trips with this stop
relevant_trips = stop_times[(stop_times['stop_id'] == current_stop_id) &
(stop_times['trip_id'].isin(trip_ids))]['trip_id'].unique()
if len(relevant_trips) == 0:
st.error("La parada actual no está en esta ruta.")
st.stop()
trip_id = relevant_trips[0]
stops_trip = stop_times[stop_times.trip_id == trip_id].merge(stops, on="stop_id").sort_values('stop_sequence')
# Current sequence
current_seq = stops_trip[stops_trip.stop_id == current_stop_id]['stop_sequence'].iloc[0]
# Destination match
destino_results = stops[stops.stop_name.str.contains(destino, case=False, na=False)]
if destino_results.empty:
st.error("No se encontró la parada de destino. Intenta con otro nombre.")
st.stop()
destino_stop = destino_results.iloc[0]
destination_id = destino_stop['stop_id']
st.write(f"Destino interpretado como: **{destino_stop.stop_name}**")
# ------------
# DIRECT ROUTE
# ------------
destino_in_trip = stops_trip[(stops_trip.stop_id == destination_id) &
(stops_trip.stop_sequence > current_seq)]
if not destino_in_trip.empty:
dest_seq = destino_in_trip['stop_sequence'].iloc[0]
num_paradas = dest_seq - current_seq
st.success(
f"Esta ruta **SÍ** te sirve directamente.\n\n"
f"Debes bajarte en **{num_paradas} paradas** en **{destino_stop.stop_name}**."
)
parada_destino = destino_stop
else:
# ------------
# MULTI-ROUTE GRAPH SEARCH
# ------------
source = (current_stop_id, route_id)
targets = [(destination_id, r) for r in stops_routes[destination_id]]
paths = {}
for t in targets:
try:
path = nx.shortest_path(G, source, t, weight="weight")
cost = nx.shortest_path_length(G, source, t, weight="weight")
paths[cost] = path
except nx.NetworkXNoPath:
pass
if not paths:
st.warning("No se encontró ninguna ruta con transbordos disponibles.")
parada_destino = None
else:
min_cost = min(paths.keys())
best_path = paths[min_cost]
legs = []
current_route = best_path[0][1]
start_id = best_path[0][0]
leg_stops = 0
for i in range(1, len(best_path)):
nxt_stop, nxt_route = best_path[i]
if nxt_route != current_route:
from_name = stops.loc[stops.stop_id == start_id, "stop_name"].iloc[0]
to_name = stops.loc[stops.stop_id == best_path[i-1][0], "stop_name"].iloc[0]
legs.append({
"route": route_short[current_route],
"from": from_name,
"to": to_name,
"num_paradas": leg_stops
})
current_route = nxt_route
start_id = best_path[i-1][0]
leg_stops = 0
leg_stops += 1
from_name = stops.loc[stops.stop_id == start_id, "stop_name"].iloc[0]
to_name = stops.loc[stops.stop_id == best_path[-1][0], "stop_name"].iloc[0]
legs.append({
"route": route_short[current_route],
"from": from_name,
"to": to_name,
"num_paradas": leg_stops
})
texto = "### 🚏 Ruta recomendada:\n"
for i, leg in enumerate(legs):
if i == 0:
texto += f"- Vas en **{leg['route']}** desde **{leg['from']}**, bájate en **{leg['num_paradas']}** paradas en **{leg['to']}**.\n"
else:
texto += f"- Luego, en **{leg['from']}**, toma **{leg['route']}** y bájate en **{leg['num_paradas']}** paradas en **{leg['to']}**.\n"
st.success(texto)
parada_destino = destino_stop
# ============================
# MAP RENDER
# ============================
st.header("🗺️ Mapa de tu ruta y posición")
m = folium.Map(
location=[stop_actual["stop_lat"], stop_actual["stop_lon"]],
zoom_start=13
)
folium.Marker(
[stop_actual["stop_lat"], stop_actual["stop_lon"]],
tooltip="Estás aquí",
icon=folium.Icon(color="blue")
).add_to(m)
# Draw shape
shape_id = trips.loc[trips.trip_id == trip_id, "shape_id"].iloc[0]
shape_geom = shapes_gdf.loc[shapes_gdf.shape_id == shape_id, "geometry"].iloc[0]
folium.PolyLine(
locations=[(lat, lon) for lon, lat in zip(shape_geom.coords.xy[0], shape_geom.coords.xy[1])],
weight=4,
color="red"
).add_to(m)
if parada_destino is not None:
folium.Marker(
[parada_destino.stop_lat, parada_destino.stop_lon],
tooltip=f"Destino: {parada_destino.stop_name}",
icon=folium.Icon(color="green")
).add_to(m)
st_folium(m, width=700, height=500)