ludekcizinsky commited on
Commit
667c69c
Β·
1 Parent(s): 3d2c916
Files changed (2) hide show
  1. app.py +8 -13
  2. utils.py +73 -51
app.py CHANGED
@@ -17,7 +17,7 @@ with gr.Blocks(
17
  with gr.Row() as header:
18
  gr.Markdown(
19
  """
20
- ## πŸš€ Search For You Next Trip
21
  """,
22
  )
23
 
@@ -26,8 +26,10 @@ with gr.Blocks(
26
 
27
  with gr.Row() as metainfo:
28
  limit = gr.Slider(minimum=1, maximum=5, step=1, label="Limit", interactive=True, value=3)
29
- days = gr.Textbox(label="Day", placeholder="YYYY-MM-DD")
30
- time = gr.Textbox(label="Time", placeholder="hh:mm")
 
 
31
 
32
  submit = gr.Button(value="Search", variant="primary")
33
 
@@ -35,17 +37,10 @@ with gr.Blocks(
35
  with gr.Column(visible=False) as output:
36
  route = gr.Markdown()
37
 
38
- def search(A, B, limit, day, time):
39
  """Search for the best route from A to B"""
40
- md = f"""
41
- ---
42
 
43
- πŸš€ **{A}** to 🏁 **{B}** at ⌚ **{time}** on πŸ“… **{day}**
44
-
45
- ---
46
- """
47
-
48
- md = get_best_path(A, B, day, time, limit)
49
 
50
  return {
51
  output: gr.Column(visible=True),
@@ -54,7 +49,7 @@ with gr.Blocks(
54
 
55
  submit.click(
56
  search,
57
- inputs=[A, B, limit, days, time],
58
  outputs=[route, output]
59
  )
60
 
 
17
  with gr.Row() as header:
18
  gr.Markdown(
19
  """
20
+ ## Search For You Next Trip
21
  """,
22
  )
23
 
 
26
 
27
  with gr.Row() as metainfo:
28
  limit = gr.Slider(minimum=1, maximum=5, step=1, label="Limit", interactive=True, value=3)
29
+ days = gr.Textbox(label="πŸ“… Day", placeholder="YYYY-MM-DD")
30
+ time = gr.Textbox(label="πŸ•’ Time", placeholder="hh:mm")
31
+ sustainable = gr.Checkbox(label="🌎 Sustainable", value=False)
32
+ outage = gr.Checkbox(label="🚧 Outage", value=False)
33
 
34
  submit = gr.Button(value="Search", variant="primary")
35
 
 
37
  with gr.Column(visible=False) as output:
38
  route = gr.Markdown()
39
 
40
+ def search(A, B, limit, day, time, sustainable, outage):
41
  """Search for the best route from A to B"""
 
 
42
 
43
+ md = get_best_path(A, B, day, time, limit, outage, sustainable)
 
 
 
 
 
44
 
45
  return {
46
  output: gr.Column(visible=True),
 
49
 
50
  submit.click(
51
  search,
52
+ inputs=[A, B, limit, days, time, sustainable, outage],
53
  outputs=[route, output]
54
  )
55
 
utils.py CHANGED
@@ -38,12 +38,7 @@ PENALITIES = {
38
 
39
  def get_penalties(sustainability: bool):
40
  if sustainability:
41
- PENALITIES["bike"] *= 1.1
42
- PENALITIES["train"] *= 1.2
43
  PENALITIES["car"] *= 5
44
- else:
45
- PENALITIES["car"] *= 2
46
-
47
  return PENALITIES
48
 
49
 
@@ -76,13 +71,6 @@ def get_args():
76
  parser.add_argument(
77
  "--limit", type=int, default=3, help="Number of journeys to return"
78
  )
79
- parser.add_argument(
80
- "--transportations",
81
- type,
82
- choices=transportation_types,
83
- default=["train"],
84
- help=transportation_help,
85
- )
86
  parser.add_argument("--exact-travel-time", action="store_true", help=time_help)
87
  parser.add_argument(
88
  "--change-penalty", type=int, default=300, help="Change penalty"
@@ -199,6 +187,7 @@ def dijkstra(G, start, end, start_time, change_penalty=300, mode_penalties=PENAL
199
  node: {
200
  "distance": float("infinity"),
201
  "time": start_time,
 
202
  }
203
  for node in list(G.nodes())
204
  }
@@ -207,7 +196,7 @@ def dijkstra(G, start, end, start_time, change_penalty=300, mode_penalties=PENAL
207
  edges_to = {node: None for node in list(G.nodes())}
208
 
209
  # Set the distance from the start node to itself to 0
210
- distances[start]["distance"] = 0
211
  edges_to[start] = ("", "Start", {"type": "foot", "journey_id": None, "duration": 0})
212
 
213
  # Priority queue to keep track of nodes with their current distances
@@ -218,7 +207,7 @@ def dijkstra(G, start, end, start_time, change_penalty=300, mode_penalties=PENAL
218
  current_distance, current_node = heapq.heappop(priority_queue)
219
 
220
  if current_node == end:
221
- return distances[end], edges_to
222
 
223
  # Check if the current distance is smaller than the stored distance
224
  if current_distance > distances[current_node]["distance"]:
@@ -247,8 +236,11 @@ def dijkstra(G, start, end, start_time, change_penalty=300, mode_penalties=PENAL
247
  train_departed = (
248
  distances[current_node]["time"] > attributes["departure"]
249
  )
 
 
 
250
 
251
- if train_departed:
252
  continue
253
 
254
  if prev_is_train:
@@ -263,24 +255,36 @@ def dijkstra(G, start, end, start_time, change_penalty=300, mode_penalties=PENAL
263
  # Add max of waiting time or changing penalty to current dist
264
  if changed_mode or changed_trip:
265
  distance += max(wait, change_penalty)
 
 
266
 
267
  # Overall dist (prev dist + dist to neighbor)
268
- distance = current_distance + distance
269
 
270
  # If the new distance is smaller, update the distance and add to the priority queue
271
- if distance < distances[neighbor]["distance"]:
272
  # Update distance and time of arrival for neighbor
273
  time_of_arrival = distances[current_node]["time"] + pd.Timedelta(
274
  seconds=distance
275
  )
276
- distances[neighbor]["distance"] = distance
 
277
  distances[neighbor]["time"] = time_of_arrival
 
 
 
 
 
278
 
279
  # Add edge that leads to neighbor
280
- edges_to[neighbor] = (current_node, neighbor, attributes)
 
 
 
 
281
 
282
  # Update priority queue
283
- heapq.heappush(priority_queue, (distance, neighbor))
284
 
285
  print(f"Probably there is no path between {start} and {end}")
286
 
@@ -396,8 +400,16 @@ def pretty_print(edges, args):
396
  dst = args.end
397
 
398
  duration = pretty_time_delta(attr["duration"])
 
 
 
 
 
 
 
 
 
399
 
400
- print(f"{i+1}. Go by {attr['type']} from {src} to {dst} for {duration}")
401
 
402
  """
403
  Remove all the trains from one station to another to simulate an outage.
@@ -419,10 +431,12 @@ def remove_all_trains(G, from_station, to_station):
419
 
420
 
421
  def get_final_path_md(edges, start, end, date, time, sustainability):
422
-
423
  md = f"## Your Journey from {start} to {end}\n\n"
 
424
  md += f"πŸ“… Date/ Time: {date} at {time}\n"
425
- md += "### Travel Information\n"
 
 
426
 
427
  for i, (src, dst, attr) in enumerate(edges):
428
  if src == "Start":
@@ -431,23 +445,44 @@ def get_final_path_md(edges, start, end, date, time, sustainability):
431
  dst = end
432
 
433
  duration = pretty_time_delta(attr["duration"])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
434
 
435
- travel_type = attr['type']
436
- emoji = "πŸš‰" if travel_type == "train" else "πŸš—" if travel_type == "car" else "🚢" if travel_type == "foot" else "πŸš€"
437
 
438
- md += f"{i+1}. {emoji} Go by {travel_type} from {src} to {dst} for {duration}\n\n"
439
-
440
  return md
441
 
442
- def get_best_path(start, end, date, time, limit, exact_travel_time=False, outage=False, sustainability=False, change_penalty=300):
443
-
 
 
 
 
 
 
 
 
 
444
  # Load graph
445
  with open("graph.pkl", "rb") as f:
446
  G = pickle.load(f)
447
 
448
  if outage:
449
  # Remove all the edges from 2 stations to simulate an outage
450
- remove_all_trains(G, from_station="St-Maurice", to_station="Martigny")
451
 
452
  # Convert start and destination to location (lon, lat)
453
  start_loc = get_location(G, start)
@@ -463,36 +498,24 @@ def get_best_path(start, end, date, time, limit, exact_travel_time=False, outage
463
  for station, attr in G.nodes(data=True):
464
  try:
465
  station_pos = attr["pos"]
466
- dists_from_start.append(
467
- (station, get_distance(start_loc, station_pos))
468
- )
469
  dists_to_end.append((station, get_distance(end_loc, station_pos)))
470
  except Exception as e:
471
  continue
472
 
473
  # Sort the distances in place
474
- start_k_closest = sorted(dists_from_start, key=lambda x: x[1])[: limit]
475
- end_k_closest = sorted(dists_to_end, key=lambda x: x[1])[: limit]
476
 
477
  # Compute travel time from start to k closest stations
478
  for mode in ["foot", "bike", "car"]:
479
  for station, dist in start_k_closest:
480
- if exact_travel_time:
481
- travel_time = get_exact_travel_time(
482
- start_loc, station, method=mode
483
- )
484
- G.add_edge("Start", station, duration=travel_time, type=mode)
485
- else:
486
- travel_time = get_approx_travel_time(dist, method=mode)
487
- G.add_edge("Start", station, duration=travel_time, type=mode)
488
 
489
  for station, dist in end_k_closest:
490
- if exact_travel_time:
491
- travel_time = get_exact_travel_time(dist, method=mode)
492
- G.add_edge(station, "End", duration=travel_time, type=mode)
493
- else:
494
- travel_time = get_approx_travel_time(dist, method=mode)
495
- G.add_edge(station, "End", duration=travel_time, type=mode)
496
 
497
  # Run Dijkstra on graph
498
  start_time = pd.to_datetime(f"{date} {time}")
@@ -512,7 +535,6 @@ def get_best_path(start, end, date, time, limit, exact_travel_time=False, outage
512
  # Postprocess path
513
  path = postprocess_path(edges[:-1])
514
 
515
- # Print journey
516
  md = get_final_path_md(path, start, end, date, time, sustainability)
517
 
518
- return md
 
38
 
39
  def get_penalties(sustainability: bool):
40
  if sustainability:
 
 
41
  PENALITIES["car"] *= 5
 
 
 
42
  return PENALITIES
43
 
44
 
 
71
  parser.add_argument(
72
  "--limit", type=int, default=3, help="Number of journeys to return"
73
  )
 
 
 
 
 
 
 
74
  parser.add_argument("--exact-travel-time", action="store_true", help=time_help)
75
  parser.add_argument(
76
  "--change-penalty", type=int, default=300, help="Change penalty"
 
187
  node: {
188
  "distance": float("infinity"),
189
  "time": start_time,
190
+ "visited": False,
191
  }
192
  for node in list(G.nodes())
193
  }
 
196
  edges_to = {node: None for node in list(G.nodes())}
197
 
198
  # Set the distance from the start node to itself to 0
199
+ distances[start] = {"distance": 0, "time": start_time, "visited": True}
200
  edges_to[start] = ("", "Start", {"type": "foot", "journey_id": None, "duration": 0})
201
 
202
  # Priority queue to keep track of nodes with their current distances
 
207
  current_distance, current_node = heapq.heappop(priority_queue)
208
 
209
  if current_node == end:
210
+ return distances, edges_to
211
 
212
  # Check if the current distance is smaller than the stored distance
213
  if current_distance > distances[current_node]["distance"]:
 
236
  train_departed = (
237
  distances[current_node]["time"] > attributes["departure"]
238
  )
239
+ train_too_far_away = (
240
+ distances[current_node]["time"] - attributes["departure"]
241
+ ).total_seconds() > 60 * 60 * 1
242
 
243
+ if train_departed or train_too_far_away:
244
  continue
245
 
246
  if prev_is_train:
 
255
  # Add max of waiting time or changing penalty to current dist
256
  if changed_mode or changed_trip:
257
  distance += max(wait, change_penalty)
258
+ elif next_is_train:
259
+ distance += wait
260
 
261
  # Overall dist (prev dist + dist to neighbor)
262
+ total_distance = current_distance + distance
263
 
264
  # If the new distance is smaller, update the distance and add to the priority queue
265
+ if total_distance < distances[neighbor]["distance"]:
266
  # Update distance and time of arrival for neighbor
267
  time_of_arrival = distances[current_node]["time"] + pd.Timedelta(
268
  seconds=distance
269
  )
270
+ # print(f"Arrived at {neighbor} at {time_of_arrival}")
271
+ distances[neighbor]["distance"] = total_distance
272
  distances[neighbor]["time"] = time_of_arrival
273
+ distances[neighbor]["visited"] = True
274
+
275
+ # for _, nneighbor, attributes in G.out_edges(neighbor, data=True):
276
+ # if nneighbor != current_node:
277
+ # distances[nneighbor]["visited"] = False
278
 
279
  # Add edge that leads to neighbor
280
+ edges_to[neighbor] = (
281
+ current_node,
282
+ neighbor,
283
+ attributes,
284
+ )
285
 
286
  # Update priority queue
287
+ heapq.heappush(priority_queue, (total_distance, neighbor))
288
 
289
  print(f"Probably there is no path between {start} and {end}")
290
 
 
400
  dst = args.end
401
 
402
  duration = pretty_time_delta(attr["duration"])
403
+ departure = attr.get("departure", None)
404
+ arrival = attr.get("arrival", None)
405
+
406
+ msg = f"{i+1}. Go by {attr['type']} from {src} to {dst} for {duration}."
407
+ if attr["type"] == "train":
408
+ msg += "\n -> Take {} ({} - {})".format(
409
+ attr["trip_name"], departure, arrival
410
+ )
411
+ print(msg)
412
 
 
413
 
414
  """
415
  Remove all the trains from one station to another to simulate an outage.
 
431
 
432
 
433
  def get_final_path_md(edges, start, end, date, time, sustainability):
 
434
  md = f"## Your Journey from {start} to {end}\n\n"
435
+
436
  md += f"πŸ“… Date/ Time: {date} at {time}\n"
437
+ md += f"🌍 Sustainable?: {sustainability}\n\n"
438
+
439
+ md += "\n### Travel Information\n"
440
 
441
  for i, (src, dst, attr) in enumerate(edges):
442
  if src == "Start":
 
445
  dst = end
446
 
447
  duration = pretty_time_delta(attr["duration"])
448
+ departure = attr.get("departure", None)
449
+ arrival = attr.get("arrival", None)
450
+
451
+ emoji = {
452
+ "foot": "🚢",
453
+ "bike": "🚴",
454
+ "car": "πŸš—",
455
+ "train": "πŸš†",
456
+ }
457
+
458
+ msg = f"{i+1}. {emoji[attr['type']]} Go by {attr['type']} from {src} to {dst} for {duration}.\n"
459
+ if attr["type"] == "train":
460
+ msg += " -> Take {} ({} - {})\n".format(
461
+ attr["trip_name"], departure, arrival
462
+ )
463
 
464
+ md += msg
 
465
 
 
 
466
  return md
467
 
468
+
469
+ def get_best_path(
470
+ start,
471
+ end,
472
+ date,
473
+ time,
474
+ limit,
475
+ outage=False,
476
+ sustainability=False,
477
+ change_penalty=300,
478
+ ):
479
  # Load graph
480
  with open("graph.pkl", "rb") as f:
481
  G = pickle.load(f)
482
 
483
  if outage:
484
  # Remove all the edges from 2 stations to simulate an outage
485
+ remove_all_trains(G, from_station="Renens VD", to_station="Lausanne")
486
 
487
  # Convert start and destination to location (lon, lat)
488
  start_loc = get_location(G, start)
 
498
  for station, attr in G.nodes(data=True):
499
  try:
500
  station_pos = attr["pos"]
501
+ dists_from_start.append((station, get_distance(start_loc, station_pos)))
 
 
502
  dists_to_end.append((station, get_distance(end_loc, station_pos)))
503
  except Exception as e:
504
  continue
505
 
506
  # Sort the distances in place
507
+ start_k_closest = sorted(dists_from_start, key=lambda x: x[1])[:limit]
508
+ end_k_closest = sorted(dists_to_end, key=lambda x: x[1])[:limit]
509
 
510
  # Compute travel time from start to k closest stations
511
  for mode in ["foot", "bike", "car"]:
512
  for station, dist in start_k_closest:
513
+ travel_time = get_approx_travel_time(dist, method=mode)
514
+ G.add_edge("Start", station, duration=travel_time, type=mode)
 
 
 
 
 
 
515
 
516
  for station, dist in end_k_closest:
517
+ travel_time = get_approx_travel_time(dist, method=mode)
518
+ G.add_edge(station, "End", duration=travel_time, type=mode)
 
 
 
 
519
 
520
  # Run Dijkstra on graph
521
  start_time = pd.to_datetime(f"{date} {time}")
 
535
  # Postprocess path
536
  path = postprocess_path(edges[:-1])
537
 
 
538
  md = get_final_path_md(path, start, end, date, time, sustainability)
539
 
540
+ return md