Mapu142 commited on
Commit
0edbbf1
·
verified ·
1 Parent(s): d4768b4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +197 -53
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
 
@@ -19,6 +20,7 @@ stops = pd.read_csv('stops.txt')
19
  stop_times = pd.read_csv('stop_times.txt')
20
  trips = pd.read_csv('trips.txt')
21
 
 
22
  # Convertir puntos a geometría
23
  shapes_gdf = (
24
  shapes.sort_values(["shape_id", "shape_pt_sequence"])
@@ -31,103 +33,245 @@ 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],
 
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
 
 
20
  stop_times = pd.read_csv('stop_times.txt')
21
  trips = pd.read_csv('trips.txt')
22
 
23
+
24
  # Convertir puntos a geometría
25
  shapes_gdf = (
26
  shapes.sort_values(["shape_id", "shape_pt_sequence"])
 
33
  crs_obj = CRS.from_epsg(4326) # Crea el objeto CRS correctamente
34
  shapes_gdf = gpd.GeoDataFrame(shapes_gdf, geometry="geometry", crs=crs_obj)
35
 
36
+ @st.cache_resource
37
+ def build_graph(routes, stop_times, trips):
38
+ route_short = routes.set_index('route_id')['route_short_name'].to_dict()
39
+ stops_routes = defaultdict(set)
40
+ G = nx.DiGraph()
41
+
42
+ group_cols = ['route_id', 'shape_id']
43
+ trips_grouped = trips.groupby(group_cols)
44
+
45
+ transfer_penalty = 20
46
+
47
+ for name, group in trips_grouped:
48
+ if group.empty:
49
+ continue
50
+ route_id, shape_id = name
51
+ trip_id_sample = group['trip_id'].iloc[0]
52
+
53
+ stops_trip = stop_times[stop_times['trip_id'] == trip_id_sample].sort_values('stop_sequence')
54
+ stop_ids = stops_trip['stop_id'].tolist()
55
+
56
+ # Assign stops → route
57
+ for stop_id in stop_ids:
58
+ stops_routes[stop_id].add(route_id)
59
+
60
+ # Add ride edges
61
+ for i in range(len(stop_ids) - 1):
62
+ s1 = stop_ids[i]
63
+ s2 = stop_ids[i + 1]
64
+ G.add_edge((s1, route_id), (s2, route_id), weight=1)
65
+
66
+ # Add transfer edges
67
+ for stop_id, routes_set in stops_routes.items():
68
+ routes_list = list(routes_set)
69
+ for i in range(len(routes_list)):
70
+ for j in range(len(routes_list)):
71
+ if i != j:
72
+ G.add_edge((stop_id, routes_list[i]), (stop_id, routes_list[j]), weight=transfer_penalty)
73
+
74
+ return G, stops_routes, route_short
75
+
76
 
77
+ G, stops_routes, route_short = build_graph(routes, stop_times, trips)
78
 
79
+ # ============================
80
+ # STREAMLIT UI
81
+ # ============================
82
 
83
+ st.title("🚍 Planificador inteligente — TransMilenio")
84
+ 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.")
 
85
 
86
+ # ----------------------------
87
+ # SELECT ROUTE
88
+ # ----------------------------
89
  st.header("1️⃣ ¿En qué ruta vas?")
90
+ ruta_seleccionada = st.selectbox("Selecciona tu ruta", routes.route_short_name.unique())
91
+
92
+ route_id = routes.loc[routes.route_short_name == ruta_seleccionada, "route_id"].iloc[0]
93
+ trips_ruta = trips[trips.route_id == route_id]
94
 
95
+ if trips_ruta.empty:
96
+ st.error("No se encontraron viajes para esta ruta.")
97
+ st.stop()
98
+
99
+ trip_ids = trips_ruta['trip_id'].unique()
100
+ all_stops_route = stop_times[stop_times['trip_id'].isin(trip_ids)].merge(stops, on="stop_id")['stop_name'].unique()
101
+
102
+ # ----------------------------
103
+ # ORIGIN
104
+ # ----------------------------
105
  st.header("2️⃣ ¿Dónde estás?")
106
+ modo_origen = st.radio("Selecciona cómo indicar dónde estás:", ["Parada", "Coordenadas"])
 
 
107
 
108
  if modo_origen == "Parada":
109
+ parada_origen = st.selectbox("Selecciona tu parada actual", all_stops_route)
110
  stop_actual = stops[stops.stop_name == parada_origen].iloc[0]
111
+ current_stop_id = stop_actual['stop_id']
112
 
113
  else:
114
  lat = st.number_input("Latitud", value=4.65)
115
  lon = st.number_input("Longitud", value=-74.1)
 
116
 
117
+ all_stops_route_df = stop_times[stop_times['trip_id'].isin(trip_ids)].merge(stops, on="stop_id").drop_duplicates('stop_id')
118
+ stops_route_gdf = gpd.GeoDataFrame(
119
+ all_stops_route_df,
120
+ geometry=gpd.points_from_xy(all_stops_route_df.stop_lon, all_stops_route_df.stop_lat),
121
+ crs=4326
122
+ )
123
+
124
+ user_point = Point(lon, lat)
125
+ distances = stops_route_gdf.geometry.distance(user_point)
126
+ nearest_idx = distances.argmin()
127
 
128
+ stop_actual = stops_route_gdf.iloc[nearest_idx]
129
+ current_stop_id = stop_actual['stop_id']
130
+
131
+ st.write(f"Parada más cercana detectada: **{stop_actual.stop_name}**")
132
+
133
+ # ----------------------------
134
+ # DESTINATION
135
+ # ----------------------------
136
  st.header("3️⃣ ¿A dónde vas?")
137
  destino = st.text_input("Escribe la parada o dirección de destino")
 
138
  calcular = st.button("Calcular ruta")
139
 
140
+ # ============================
141
+ # ROUTING ENGINE
142
+ # ============================
143
 
144
  if calcular:
145
 
146
+ # Find trips with this stop
147
+ relevant_trips = stop_times[(stop_times['stop_id'] == current_stop_id) &
148
+ (stop_times['trip_id'].isin(trip_ids))]['trip_id'].unique()
149
 
150
+ if len(relevant_trips) == 0:
151
+ st.error("La parada actual no está en esta ruta.")
152
  st.stop()
153
 
154
+ trip_id = relevant_trips[0]
155
+ stops_trip = stop_times[stop_times.trip_id == trip_id].merge(stops, on="stop_id").sort_values('stop_sequence')
 
156
 
157
+ # Current sequence
158
+ current_seq = stops_trip[stops_trip.stop_id == current_stop_id]['stop_sequence'].iloc[0]
159
 
160
+ # Destination match
161
+ destino_results = stops[stops.stop_name.str.contains(destino, case=False, na=False)]
162
+ if destino_results.empty:
163
+ st.error("No se encontró la parada de destino. Intenta con otro nombre.")
164
+ st.stop()
165
 
166
+ destino_stop = destino_results.iloc[0]
167
+ destination_id = destino_stop['stop_id']
168
+ st.write(f"Destino interpretado como: **{destino_stop.stop_name}**")
169
+
170
+ # ------------
171
+ # DIRECT ROUTE
172
+ # ------------
173
+ destino_in_trip = stops_trip[(stops_trip.stop_id == destination_id) &
174
+ (stops_trip.stop_sequence > current_seq)]
175
+
176
+ if not destino_in_trip.empty:
177
+ dest_seq = destino_in_trip['stop_sequence'].iloc[0]
178
+ num_paradas = dest_seq - current_seq
179
+ st.success(
180
+ f"Esta ruta **SÍ** te sirve directamente.\n\n"
181
+ f"Debes bajarte en **{num_paradas} paradas** en **{destino_stop.stop_name}**."
182
+ )
183
+ parada_destino = destino_stop
184
 
185
+ else:
186
+ # ------------
187
+ # MULTI-ROUTE GRAPH SEARCH
188
+ # ------------
189
+ source = (current_stop_id, route_id)
190
+ targets = [(destination_id, r) for r in stops_routes[destination_id]]
191
+
192
+ paths = {}
193
+ for t in targets:
194
+ try:
195
+ path = nx.shortest_path(G, source, t, weight="weight")
196
+ cost = nx.shortest_path_length(G, source, t, weight="weight")
197
+ paths[cost] = path
198
+ except nx.NetworkXNoPath:
199
+ pass
200
+
201
+ if not paths:
202
+ st.warning("No se encontró ninguna ruta con transbordos disponibles.")
203
+ parada_destino = None
204
+ else:
205
+ min_cost = min(paths.keys())
206
+ best_path = paths[min_cost]
207
+
208
+ legs = []
209
+ current_route = best_path[0][1]
210
+ start_id = best_path[0][0]
211
+ leg_stops = 0
212
+
213
+ for i in range(1, len(best_path)):
214
+ nxt_stop, nxt_route = best_path[i]
215
+ if nxt_route != current_route:
216
+ from_name = stops.loc[stops.stop_id == start_id, "stop_name"].iloc[0]
217
+ to_name = stops.loc[stops.stop_id == best_path[i-1][0], "stop_name"].iloc[0]
218
+ legs.append({
219
+ "route": route_short[current_route],
220
+ "from": from_name,
221
+ "to": to_name,
222
+ "num_paradas": leg_stops
223
+ })
224
+ current_route = nxt_route
225
+ start_id = best_path[i-1][0]
226
+ leg_stops = 0
227
+ leg_stops += 1
228
+
229
+ from_name = stops.loc[stops.stop_id == start_id, "stop_name"].iloc[0]
230
+ to_name = stops.loc[stops.stop_id == best_path[-1][0], "stop_name"].iloc[0]
231
+ legs.append({
232
+ "route": route_short[current_route],
233
+ "from": from_name,
234
+ "to": to_name,
235
+ "num_paradas": leg_stops
236
+ })
237
+
238
+ texto = "### 🚏 Ruta recomendada:\n"
239
+ for i, leg in enumerate(legs):
240
+ if i == 0:
241
+ texto += f"- Vas en **{leg['route']}** desde **{leg['from']}**, bájate en **{leg['num_paradas']}** paradas en **{leg['to']}**.\n"
242
+ else:
243
+ texto += f"- Luego, en **{leg['from']}**, toma **{leg['route']}** y bájate en **{leg['num_paradas']}** paradas en **{leg['to']}**.\n"
244
+
245
+ st.success(texto)
246
+ parada_destino = destino_stop
247
+
248
+ # ============================
249
+ # MAP RENDER
250
+ # ============================
251
 
252
  st.header("🗺️ Mapa de tu ruta y posición")
253
 
254
+ m = folium.Map(
255
+ location=[stop_actual["stop_lat"], stop_actual["stop_lon"]],
256
+ zoom_start=13
257
+ )
258
 
 
259
  folium.Marker(
260
  [stop_actual["stop_lat"], stop_actual["stop_lon"]],
261
  tooltip="Estás aquí",
262
  icon=folium.Icon(color="blue")
263
  ).add_to(m)
264
 
265
+ # Draw shape
266
+ shape_id = trips.loc[trips.trip_id == trip_id, "shape_id"].iloc[0]
267
+ shape_geom = shapes_gdf.loc[shapes_gdf.shape_id == shape_id, "geometry"].iloc[0]
 
268
 
269
  folium.PolyLine(
270
  locations=[(lat, lon) for lon, lat in zip(shape_geom.coords.xy[0], shape_geom.coords.xy[1])],
271
+ weight=4,
272
+ color="red"
 
273
  ).add_to(m)
274
 
 
275
  if parada_destino is not None:
276
  folium.Marker(
277
  [parada_destino.stop_lat, parada_destino.stop_lon],