Mapu142 commited on
Commit
687289c
·
verified ·
1 Parent(s): 30c70ca

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +158 -74
app.py CHANGED
@@ -5,7 +5,8 @@ import geopandas as gpd
5
  from shapely.geometry import LineString, Point
6
  import folium
7
  from streamlit_folium import st_folium
8
-
 
9
  import os, sys
10
  import traceback
11
 
@@ -31,108 +32,191 @@ shapes_gdf.columns = ["shape_id", "geometry"]
31
  crs_obj = CRS.from_epsg(4326) # Crea el objeto CRS correctamente
32
  shapes_gdf = gpd.GeoDataFrame(shapes_gdf, geometry="geometry", crs=crs_obj)
33
 
34
- # =========================
35
- # UI
36
- # =========================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  st.title("🚍 Planificador inteligente — TransMilenio")
 
38
 
39
- st.write("Esta app detecta si la ruta **en la que ya vas** te sirve para llegar al destino.")
40
-
41
-
42
- # =========================
43
- # INPUTS DEL USUARIO
44
- # =========================
45
-
46
  st.header("1️⃣ ¿En qué ruta vas?")
47
- ruta_seleccionada = st.selectbox(
48
- "Selecciona tu ruta",
49
- routes.route_short_name.unique(),
50
- )
51
 
52
  st.header("2️⃣ ¿Dónde estás?")
53
  col1, col2 = st.columns(2)
54
  with col1:
55
  modo_origen = st.radio("Selecciona cómo indicar dónde estás:", ["Parada", "Coordenadas"])
56
 
 
 
 
 
 
 
 
 
 
 
 
57
  if modo_origen == "Parada":
58
- parada_origen = st.selectbox("Selecciona tu parada actual", stops.stop_name.unique())
59
  stop_actual = stops[stops.stop_name == parada_origen].iloc[0]
60
-
61
  else:
62
  lat = st.number_input("Latitud", value=4.65)
63
  lon = st.number_input("Longitud", value=-74.1)
64
- stop_actual = {"stop_lat": lat, "stop_lon": lon}
65
-
 
 
 
 
 
 
 
66
 
67
  st.header("3️⃣ ¿A dónde vas?")
68
  destino = st.text_input("Escribe la parada o dirección de destino")
69
 
70
  calcular = st.button("Calcular ruta")
71
 
72
- # =========================
73
- # PROCESAMIENTO
74
- # =========================
75
-
76
  if calcular:
77
-
78
- # ---- 1. Filtrar rutas por route_short_name ----
79
- route_id = routes.loc[routes.route_short_name == ruta_seleccionada, "route_id"].iloc[0]
80
- trips_ruta = trips[trips.route_id == route_id]
81
-
82
- if trips_ruta.empty:
83
- st.error("No se encontraron viajes para esta ruta.")
84
  st.stop()
85
-
86
- # ---- 2. Seleccionar un trip (en GTFS cada ruta tiene varios) ----
87
- trip_id = trips_ruta.iloc[0].trip_id
88
  st.write(f"Usando trip: `{trip_id}`")
89
 
90
- stops_trip = stop_times[stop_times.trip_id == trip_id].merge(stops, on="stop_id")
91
 
92
- # ---- 3. Buscar destino por nombre aproximado ----
93
- destino_results = stops_trip[stops_trip.stop_name.str.contains(destino, case=False, na=False)]
94
 
95
- if not destino_results.empty:
96
- parada_destino = destino_results.iloc[0]
97
- st.success(f"Esta ruta **SÍ** te sirve. Debes bajarte en: **{parada_destino.stop_name}**")
 
 
 
 
 
 
 
 
 
 
 
 
 
98
  else:
99
- st.warning("Esta ruta NO te lleva directamente al destino. (Transbordos: próximo paso)")
100
- parada_destino = None
101
-
102
- # =========================
103
- # MAPA
104
- # =========================
105
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
  st.header("🗺️ Mapa de tu ruta y posición")
107
-
108
- # Crear mapa centrado en la posición actual
109
  m = folium.Map(location=[stop_actual["stop_lat"], stop_actual["stop_lon"]], zoom_start=13)
110
-
111
- # ---- Marcar posición actual ----
112
- folium.Marker(
113
- [stop_actual["stop_lat"], stop_actual["stop_lon"]],
114
- tooltip="Estás aquí",
115
- icon=folium.Icon(color="blue")
116
- ).add_to(m)
117
-
118
- # ---- Dibujar route shape ----
119
- # Buscar shape_id asociado
120
- shape_id = trips_ruta.iloc[0].shape_id
121
  shape_geom = shapes_gdf[shapes_gdf.shape_id == shape_id].geometry.iloc[0]
122
-
123
- folium.PolyLine(
124
- locations=[(lat, lon) for lon, lat in zip(shape_geom.coords.xy[0], shape_geom.coords.xy[1])],
125
- weight=5,
126
- color="red",
127
- tooltip=f"Ruta {ruta_seleccionada}"
128
- ).add_to(m)
129
-
130
- # ---- Marcar destino si existe----
131
  if parada_destino is not None:
132
- folium.Marker(
133
- [parada_destino.stop_lat, parada_destino.stop_lon],
134
- tooltip=f"Destino: {parada_destino.stop_name}",
135
- icon=folium.Icon(color="green")
136
- ).add_to(m)
137
-
138
  st_folium(m, width=700, height=500)
 
5
  from shapely.geometry import LineString, Point
6
  import folium
7
  from streamlit_folium import st_folium
8
+ import networkx as nx
9
+ from collections import defaultdict # Add this line if missing
10
  import os, sys
11
  import traceback
12
 
 
32
  crs_obj = CRS.from_epsg(4326) # Crea el objeto CRS correctamente
33
  shapes_gdf = gpd.GeoDataFrame(shapes_gdf, geometry="geometry", crs=crs_obj)
34
 
35
+ # Precompute graph for routing
36
+ route_short = routes.set_index('route_id')['route_short_name'].to_dict()
37
+ stops_routes = defaultdict(set)
38
+ G = nx.DiGraph()
39
+
40
+ # Group by route_id and shape_id to capture variants/directions
41
+ group_cols = ['route_id', 'shape_id']
42
+ trips_grouped = trips.groupby(group_cols)
43
+
44
+ transfer_penalty = 20
45
+
46
+ for name, group in trips_grouped:
47
+ if group.empty:
48
+ continue
49
+ route_id, shape_id = name
50
+ trip_id_sample = group['trip_id'].iloc[0]
51
+ stops_trip = stop_times[stop_times['trip_id'] == trip_id_sample].sort_values('stop_sequence')
52
+ stop_ids = stops_trip['stop_id'].tolist()
53
+
54
+ # Add routes to stops
55
+ for stop_id in stop_ids:
56
+ stops_routes[stop_id].add(route_id)
57
+
58
+ # Add ride edges
59
+ for i in range(len(stop_ids) - 1):
60
+ stop1 = stop_ids[i]
61
+ stop2 = stop_ids[i + 1]
62
+ G.add_edge((stop1, route_id), (stop2, route_id), weight=1)
63
+
64
+ # Add transfer edges
65
+ for stop_id, routes_set in stops_routes.items():
66
+ routes_list = list(routes_set)
67
+ for i in range(len(routes_list)):
68
+ for j in range(len(routes_list)):
69
+ if i != j:
70
+ G.add_edge((stop_id, routes_list[i]), (stop_id, routes_list[j]), weight=transfer_penalty)
71
+
72
+ # UI
73
  st.title("🚍 Planificador inteligente — TransMilenio")
74
+ 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.")
75
 
76
+ # Inputs
 
 
 
 
 
 
77
  st.header("1️⃣ ¿En qué ruta vas?")
78
+ ruta_seleccionada = st.selectbox("Selecciona tu ruta", routes.route_short_name.unique())
 
 
 
79
 
80
  st.header("2️⃣ ¿Dónde estás?")
81
  col1, col2 = st.columns(2)
82
  with col1:
83
  modo_origen = st.radio("Selecciona cómo indicar dónde estás:", ["Parada", "Coordenadas"])
84
 
85
+ route_id = routes.loc[routes.route_short_name == ruta_seleccionada, "route_id"].iloc[0]
86
+ trips_ruta = trips[trips.route_id == route_id]
87
+
88
+ if trips_ruta.empty:
89
+ st.error("No se encontraron viajes para esta ruta.")
90
+ st.stop()
91
+
92
+ # Get all unique stops for the route across all trips
93
+ trip_ids = trips_ruta['trip_id'].unique()
94
+ all_stops_route = stop_times[stop_times['trip_id'].isin(trip_ids)].merge(stops, on="stop_id")['stop_name'].unique()
95
+
96
  if modo_origen == "Parada":
97
+ parada_origen = st.selectbox("Selecciona tu parada actual", all_stops_route)
98
  stop_actual = stops[stops.stop_name == parada_origen].iloc[0]
99
+ current_stop_id = stop_actual['stop_id']
100
  else:
101
  lat = st.number_input("Latitud", value=4.65)
102
  lon = st.number_input("Longitud", value=-74.1)
103
+ # Find nearest stop among all stops on the route
104
+ all_stops_route_df = stop_times[stop_times['trip_id'].isin(trip_ids)].merge(stops, on="stop_id").drop_duplicates('stop_id')
105
+ 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)
106
+ user_point = Point(lon, lat)
107
+ distances = stops_route_gdf.geometry.distance(user_point)
108
+ nearest_idx = distances.argmin()
109
+ stop_actual = stops_route_gdf.iloc[nearest_idx]
110
+ current_stop_id = stop_actual['stop_id']
111
+ st.write(f"Parada más cercana detectada: {stop_actual.stop_name}")
112
 
113
  st.header("3️⃣ ¿A dónde vas?")
114
  destino = st.text_input("Escribe la parada o dirección de destino")
115
 
116
  calcular = st.button("Calcular ruta")
117
 
 
 
 
 
118
  if calcular:
119
+ # Find a trip that includes the current stop (to determine variant)
120
+ relevant_trips = stop_times[(stop_times['stop_id'] == current_stop_id) & (stop_times['trip_id'].isin(trip_ids))]['trip_id'].unique()
121
+ if len(relevant_trips) == 0:
122
+ st.error("La parada actual no está en esta ruta.")
 
 
 
123
  st.stop()
124
+ trip_id = relevant_trips[0] # Pick first matching trip
 
 
125
  st.write(f"Usando trip: `{trip_id}`")
126
 
127
+ stops_trip = stop_times[stop_times.trip_id == trip_id].merge(stops, on="stop_id").sort_values('stop_sequence')
128
 
129
+ # Current sequence
130
+ current_sequence = stops_trip[stops_trip.stop_id == current_stop_id]['stop_sequence'].iloc[0]
131
 
132
+ # Find destination
133
+ destino_results = stops[stops.stop_name.str.contains(destino, case=False, na=False)]
134
+ if destino_results.empty:
135
+ st.error("No se encontró la parada de destino. Intenta con otro nombre.")
136
+ st.stop()
137
+ destino_stop = destino_results.iloc[0]
138
+ destination_id = destino_stop['stop_id']
139
+ st.write(f"Destino interpretado como: {destino_stop.stop_name}")
140
+
141
+ # Direct check
142
+ destino_in_trip = stops_trip[(stops_trip.stop_id == destination_id) & (stops_trip.stop_sequence > current_sequence)]
143
+ if not destino_in_trip.empty:
144
+ dest_seq = destino_in_trip['stop_sequence'].iloc[0]
145
+ num_paradas = dest_seq - current_sequence
146
+ st.success(f"Esta ruta **SÍ** te sirve directamente. Debes bajarte en {num_paradas} paradas: **{destino_stop.stop_name}**")
147
+ parada_destino = destino_stop
148
  else:
149
+ # Graph routing
150
+ source = (current_stop_id, route_id)
151
+ targets = [(destination_id, r) for r in stops_routes[destination_id]]
152
+
153
+ paths = {}
154
+ for t in targets:
155
+ try:
156
+ path = nx.shortest_path(G, source, t, weight='weight')
157
+ cost = nx.shortest_path_length(G, source, t, weight='weight')
158
+ paths[cost] = path
159
+ except nx.NetworkXNoPath:
160
+ pass
161
+
162
+ if paths:
163
+ min_cost = min(paths.keys())
164
+ best_path = paths[min_cost]
165
+
166
+ # Reconstruct legs
167
+ legs = []
168
+ current_leg_route = best_path[0][1]
169
+ start_stop_id = best_path[0][0]
170
+ leg_stops = 0
171
+ for i in range(1, len(best_path)):
172
+ next_stop_id, next_route = best_path[i]
173
+ if next_route != current_leg_route:
174
+ from_name = stops[stops.stop_id == start_stop_id]['stop_name'].iloc[0]
175
+ to_name = stops[stops.stop_id == best_path[i-1][0]]['stop_name'].iloc[0]
176
+ legs.append({
177
+ 'route': route_short[current_leg_route],
178
+ 'from': from_name,
179
+ 'to': to_name,
180
+ 'num_paradas': leg_stops
181
+ })
182
+ start_stop_id = best_path[i-1][0]
183
+ current_leg_route = next_route
184
+ leg_stops = 0
185
+ leg_stops += 1
186
+ # Last leg
187
+ from_name = stops[stops.stop_id == start_stop_id]['stop_name'].iloc[0]
188
+ to_name = stops[stops.stop_id == best_path[-1][0]]['stop_name'].iloc[0]
189
+ legs.append({
190
+ 'route': route_short[current_leg_route],
191
+ 'from': from_name,
192
+ 'to': to_name,
193
+ 'num_paradas': leg_stops
194
+ })
195
+
196
+ # Recommendation
197
+ rec = "Ruta recomendada:\n"
198
+ for idx, leg in enumerate(legs):
199
+ if idx == 0:
200
+ rec += f"- Vas en {leg['route']} desde {leg['from']}, bájate en {leg['num_paradas']} paradas en {leg['to']}.\n"
201
+ else:
202
+ rec += f"- Luego, en {leg['from']}, toma {leg['route']} y bájate en {leg['num_paradas']} paradas en {leg['to']}.\n"
203
+ st.success(rec)
204
+ parada_destino = destino_stop
205
+ else:
206
+ st.warning("No se encontró ruta con transbordos.")
207
+ parada_destino = None
208
+
209
+ # Map
210
  st.header("🗺️ Mapa de tu ruta y posición")
 
 
211
  m = folium.Map(location=[stop_actual["stop_lat"], stop_actual["stop_lon"]], zoom_start=13)
212
+ folium.Marker([stop_actual["stop_lat"], stop_actual["stop_lon"]], tooltip="Estás aquí", icon=folium.Icon(color="blue")).add_to(m)
213
+
214
+ # Draw shape
215
+ shape_id = trips[trips.trip_id == trip_id]['shape_id'].iloc[0]
 
 
 
 
 
 
 
216
  shape_geom = shapes_gdf[shapes_gdf.shape_id == shape_id].geometry.iloc[0]
217
+ folium.PolyLine(locations=[(lat, lon) for lon, lat in zip(shape_geom.coords.xy[0], shape_geom.coords.xy[1])], weight=5, color="red", tooltip=f"Ruta {ruta_seleccionada}").add_to(m)
218
+
 
 
 
 
 
 
 
219
  if parada_destino is not None:
220
+ 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)
221
+
 
 
 
 
222
  st_folium(m, width=700, height=500)