| 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 |
| import os, sys |
| import traceback |
|
|
| print(">>> Ejecutando archivo:", os.path.abspath(__file__)) |
| print("Python ejecutado:", sys.executable) |
|
|
| |
| 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') |
|
|
|
|
| |
| 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) |
| 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() |
|
|
| |
| for stop_id in stop_ids: |
| stops_routes[stop_id].add(route_id) |
|
|
| |
| 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) |
|
|
| |
| 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) |
|
|
| |
| |
| |
|
|
| 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.") |
|
|
| |
| |
| |
| 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() |
|
|
| |
| |
| |
| 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}**") |
|
|
| |
| |
| |
| st.header("3️⃣ ¿A dónde vas?") |
| destino = st.text_input("Escribe la parada o dirección de destino") |
| calcular = st.button("Calcular ruta") |
|
|
| |
| |
| |
|
|
| if calcular: |
|
|
| |
| 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_seq = stops_trip[stops_trip.stop_id == current_stop_id]['stop_sequence'].iloc[0] |
|
|
| |
| 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}**") |
|
|
| |
| |
| |
| 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: |
| |
| |
| |
| 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 |
|
|
| |
| |
| |
|
|
| 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) |
|
|
| |
| 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) |