vincentiusyoshuac commited on
Commit
2a063fd
Β·
verified Β·
1 Parent(s): 4e34654

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +167 -110
app.py CHANGED
@@ -6,10 +6,49 @@ from folium import plugins
6
  import requests
7
  from datetime import datetime
8
  from geopy.geocoders import Nominatim
 
 
 
9
 
10
- def get_route_from_osrm(coord1, coord2):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  """Get route information from OSRM"""
12
- # OSRM expects coordinates in format: longitude,latitude
13
  url = f"http://router.project-osrm.org/route/v1/driving/{coord1[1]},{coord1[0]};{coord2[1]},{coord2[0]}"
14
  params = {
15
  "overview": "full",
@@ -18,6 +57,9 @@ def get_route_from_osrm(coord1, coord2):
18
  "annotations": "true"
19
  }
20
 
 
 
 
21
  try:
22
  response = requests.get(url, params=params)
23
  if response.status_code == 200:
@@ -30,21 +72,25 @@ def get_route_from_osrm(coord1, coord2):
30
  'geometry': route['geometry'],
31
  'steps': route['legs'][0]['steps']
32
  }
33
- except:
34
- pass
35
  return None
36
 
37
- def calculate_real_distances(places):
38
  """Calculate real driving distances using OSRM"""
39
  n = len(places)
40
  distances = np.zeros((n, n))
41
  routing_info = {}
42
 
 
 
 
 
43
  for i in range(n):
44
  for j in range(n):
45
  if i != j:
46
- origin = places[i][1] # coordinates
47
- destination = places[j][1] # coordinates
48
 
49
  route_data = get_route_from_osrm(origin, destination)
50
 
@@ -56,22 +102,20 @@ def calculate_real_distances(places):
56
  'geometry': route_data['geometry'],
57
  'steps': route_data['steps']
58
  }
 
 
 
59
 
60
  return distances, routing_info
61
 
62
- def optimize_route(distances):
63
- """
64
- Optimize route using modified Hungarian algorithm for TSP.
65
- Returns the optimal order of visits.
66
- """
67
  n = len(distances)
68
-
69
- # Solve assignment problem
70
  row_ind, col_ind = linear_sum_assignment(distances)
71
 
72
  # Convert assignment to tour
73
  tour = []
74
- current = 0 # Start from first city
75
  for _ in range(n):
76
  tour.append(current)
77
  current = col_ind[current]
@@ -79,102 +123,119 @@ def optimize_route(distances):
79
  return tour
80
 
81
  def main():
82
- st.title("AI Travel Route Optimizer (with OSRM)")
83
-
84
- # Sidebar for inputs
85
- st.sidebar.header("Travel Parameters")
86
 
87
- # Input for places
88
- places = []
89
- num_places = st.sidebar.number_input("Number of places to visit", min_value=2, max_value=10, value=3)
 
 
90
 
91
- for i in range(num_places):
92
- place = st.sidebar.text_input(f"Place {i+1}")
93
- if place:
94
- coordinates = get_place_coordinates(place)
95
- if coordinates:
96
- places.append((place, coordinates))
97
- else:
98
- st.error(f"Couldn't find coordinates for {place}")
 
 
 
 
 
 
 
 
 
 
99
 
100
- # Only proceed if we have all places
101
  if len(places) == num_places and num_places >= 2:
102
- with st.spinner("Calculating optimal route..."):
103
- # Calculate real distances
104
- distances, routing_info = calculate_real_distances(places)
105
-
106
- # Get optimized route order
107
- optimal_order = optimize_route(distances)
108
-
109
- # Create map
110
- center_lat = sum(coord[0] for _, coord in places) / len(places)
111
- center_lon = sum(coord[1] for _, coord in places) / len(places)
112
- m = folium.Map(location=[center_lat, center_lon], zoom_start=4)
113
-
114
- # Add markers and route lines following optimal order
115
- total_distance = 0
116
- total_duration = 0
117
-
118
- for i in range(len(optimal_order)):
119
- current_idx = optimal_order[i]
120
- next_idx = optimal_order[(i + 1) % len(optimal_order)]
121
-
122
- # Get current and next place info
123
- current_place, (lat, lon) = places[current_idx]
124
 
125
- # Get routing info for this segment
126
- segment_info = routing_info.get((current_idx, next_idx), {})
127
 
128
- # Add marker with proper numbering
129
- popup_content = f"""
130
- <b>Stop {i+1}: {current_place}</b><br>
131
- """
132
- if i < len(optimal_order) - 1:
133
- popup_content += f"""
134
- To next stop:<br>
135
- Distance: {segment_info.get('distance_km', 'N/A')}<br>
136
- Duration: {segment_info.get('duration_mins', 'N/A')}
137
- """
138
 
139
- folium.Marker(
140
- [lat, lon],
141
- popup=folium.Popup(popup_content, max_width=300),
142
- icon=create_numbered_marker(i+1)
143
- ).add_to(m)
144
 
145
- # Draw route line to next point
146
- if i < len(optimal_order) - 1:
147
- if 'geometry' in segment_info:
148
- try:
149
- route_coords = polyline.decode(segment_info['geometry'])
150
- folium.PolyLine(
151
- route_coords,
152
- weight=2,
153
- color='blue',
154
- opacity=0.8
155
- ).add_to(m)
156
- except:
157
- next_place = places[next_idx][1]
158
- folium.PolyLine(
159
- [[lat, lon], [next_place[0], next_place[1]]],
160
- weight=2,
161
- color='red',
162
- opacity=0.8
163
- ).add_to(m)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
164
 
165
- # Add to totals if not last point
166
- if i < len(optimal_order) - 1:
167
- if 'distance_km' in segment_info:
168
- total_distance += float(segment_info['distance_km'].split()[0])
169
- if 'duration_mins' in segment_info:
170
- total_duration += float(segment_info['duration_mins'].split()[0])
171
 
172
- # Display map
173
- st.components.v1.html(m._repr_html_(), height=600)
174
-
175
- # Display optimized itinerary
176
- st.header("Optimized Itinerary")
 
177
 
 
178
  for i in range(len(optimal_order)):
179
  current_idx = optimal_order[i]
180
  next_idx = optimal_order[(i + 1) % len(optimal_order)]
@@ -182,23 +243,19 @@ def main():
182
  current_place = places[current_idx][0]
183
  segment_info = routing_info.get((current_idx, next_idx), {})
184
 
185
- st.write(f"{i+1}. {current_place}")
186
 
187
  if i < len(optimal_order) - 1:
188
- st.write(f" ↓ ({segment_info.get('distance_km', 'N/A')}, "
189
- f"Duration: {segment_info.get('duration_mins', 'N/A')})")
190
 
191
- # Show turn-by-turn directions
192
  if 'steps' in segment_info:
193
- with st.expander(f"Route instructions to next stop"):
194
  instructions = format_instructions(segment_info['steps'])
195
  for idx, instruction in enumerate(instructions, 1):
196
  st.write(f"{idx}. {instruction}")
197
-
198
- # Display totals
199
- st.write("\nSummary:")
200
- st.write(f"Total distance: {total_distance:.1f} km")
201
- st.write(f"Total estimated duration: {total_duration:.0f} minutes ({total_duration/60:.1f} hours)")
202
 
203
  if __name__ == "__main__":
204
  main()
 
6
  import requests
7
  from datetime import datetime
8
  from geopy.geocoders import Nominatim
9
+ import polyline
10
+ from typing import List, Tuple, Dict, Optional
11
+ import time
12
 
13
+ # Utility Functions
14
+ def create_numbered_marker(number: int) -> folium.features.DivIcon:
15
+ """Create a numbered marker icon for the map"""
16
+ return folium.features.DivIcon(
17
+ html=f'<div style="background-color: white; border-radius: 50%; width: 25px; height: 25px; '
18
+ f'display: flex; align-items: center; justify-content: center; border: 2px solid blue; '
19
+ f'font-weight: bold;">{number}</div>'
20
+ )
21
+
22
+ def get_place_coordinates(place_name: str) -> Optional[Tuple[float, float]]:
23
+ """Get coordinates for a place using Nominatim geocoding"""
24
+ try:
25
+ geolocator = Nominatim(user_agent="travel_optimizer")
26
+ location = geolocator.geocode(place_name)
27
+ if location:
28
+ return (location.latitude, location.longitude)
29
+ except Exception as e:
30
+ st.error(f"Error geocoding {place_name}: {str(e)}")
31
+ return None
32
+
33
+ def format_instructions(steps: List[Dict]) -> List[str]:
34
+ """Format OSRM route steps into readable instructions"""
35
+ instructions = []
36
+ for step in steps:
37
+ if 'maneuver' in step:
38
+ instruction = step.get('maneuver', {}).get('instruction', '')
39
+ if instruction:
40
+ # Add distance information if available
41
+ if 'distance' in step:
42
+ distance_km = step['distance'] / 1000
43
+ if distance_km >= 1:
44
+ instruction += f" ({distance_km:.1f} km)"
45
+ else:
46
+ instruction += f" ({step['distance']:.0f} m)"
47
+ instructions.append(instruction)
48
+ return instructions
49
+
50
+ def get_route_from_osrm(coord1: Tuple[float, float], coord2: Tuple[float, float]) -> Optional[Dict]:
51
  """Get route information from OSRM"""
 
52
  url = f"http://router.project-osrm.org/route/v1/driving/{coord1[1]},{coord1[0]};{coord2[1]},{coord2[0]}"
53
  params = {
54
  "overview": "full",
 
57
  "annotations": "true"
58
  }
59
 
60
+ # Add rate limiting
61
+ time.sleep(1) # Respect OSRM usage guidelines
62
+
63
  try:
64
  response = requests.get(url, params=params)
65
  if response.status_code == 200:
 
72
  'geometry': route['geometry'],
73
  'steps': route['legs'][0]['steps']
74
  }
75
+ except Exception as e:
76
+ st.warning(f"Error getting route: {str(e)}")
77
  return None
78
 
79
+ def calculate_real_distances(places: List[Tuple[str, Tuple[float, float]]]) -> Tuple[np.ndarray, Dict]:
80
  """Calculate real driving distances using OSRM"""
81
  n = len(places)
82
  distances = np.zeros((n, n))
83
  routing_info = {}
84
 
85
+ progress_bar = st.progress(0)
86
+ total_calcs = n * (n-1)
87
+ current_calc = 0
88
+
89
  for i in range(n):
90
  for j in range(n):
91
  if i != j:
92
+ origin = places[i][1]
93
+ destination = places[j][1]
94
 
95
  route_data = get_route_from_osrm(origin, destination)
96
 
 
102
  'geometry': route_data['geometry'],
103
  'steps': route_data['steps']
104
  }
105
+
106
+ current_calc += 1
107
+ progress_bar.progress(current_calc / total_calcs)
108
 
109
  return distances, routing_info
110
 
111
+ def optimize_route(distances: np.ndarray) -> List[int]:
112
+ """Optimize route using modified Hungarian algorithm for TSP"""
 
 
 
113
  n = len(distances)
 
 
114
  row_ind, col_ind = linear_sum_assignment(distances)
115
 
116
  # Convert assignment to tour
117
  tour = []
118
+ current = 0
119
  for _ in range(n):
120
  tour.append(current)
121
  current = col_ind[current]
 
123
  return tour
124
 
125
  def main():
126
+ st.set_page_config(page_title="AI Travel Route Optimizer", layout="wide")
 
 
 
127
 
128
+ st.title("🌍 AI Travel Route Optimizer")
129
+ st.markdown("""
130
+ This app helps you find the optimal route between multiple destinations using real driving distances.
131
+ Enter your destinations in the sidebar and get a customized route with turn-by-turn directions.
132
+ """)
133
 
134
+ # Sidebar for inputs
135
+ with st.sidebar:
136
+ st.header("πŸ“ Destinations")
137
+
138
+ # Input for places
139
+ num_places = st.number_input("Number of destinations", min_value=2, max_value=10, value=3)
140
+
141
+ places = []
142
+ for i in range(num_places):
143
+ place = st.text_input(f"Destination {i+1}")
144
+ if place:
145
+ with st.spinner(f"Finding coordinates for {place}..."):
146
+ coordinates = get_place_coordinates(place)
147
+ if coordinates:
148
+ places.append((place, coordinates))
149
+ st.success(f"βœ“ Found coordinates for {place}")
150
+ else:
151
+ st.error(f"❌ Couldn't find coordinates for {place}")
152
 
153
+ # Main content area
154
  if len(places) == num_places and num_places >= 2:
155
+ col1, col2 = st.columns([2, 1])
156
+
157
+ with col1:
158
+ with st.spinner("πŸš— Calculating optimal route..."):
159
+ # Calculate real distances
160
+ distances, routing_info = calculate_real_distances(places)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
161
 
162
+ # Get optimized route order
163
+ optimal_order = optimize_route(distances)
164
 
165
+ # Create map
166
+ center_lat = sum(coord[0] for _, coord in places) / len(places)
167
+ center_lon = sum(coord[1] for _, coord in places) / len(places)
168
+ m = folium.Map(location=[center_lat, center_lon], zoom_start=4)
 
 
 
 
 
 
169
 
170
+ # Add markers and route lines
171
+ total_distance = 0
172
+ total_duration = 0
 
 
173
 
174
+ for i in range(len(optimal_order)):
175
+ current_idx = optimal_order[i]
176
+ next_idx = optimal_order[(i + 1) % len(optimal_order)]
177
+
178
+ current_place, (lat, lon) = places[current_idx]
179
+ segment_info = routing_info.get((current_idx, next_idx), {})
180
+
181
+ # Add marker
182
+ popup_content = f"""
183
+ <b>Stop {i+1}: {current_place}</b><br>
184
+ """
185
+ if i < len(optimal_order) - 1:
186
+ popup_content += f"""
187
+ To next stop:<br>
188
+ Distance: {segment_info.get('distance_km', 'N/A')}<br>
189
+ Duration: {segment_info.get('duration_mins', 'N/A')}
190
+ """
191
+
192
+ folium.Marker(
193
+ [lat, lon],
194
+ popup=folium.Popup(popup_content, max_width=300),
195
+ icon=create_numbered_marker(i+1)
196
+ ).add_to(m)
197
+
198
+ # Draw route line
199
+ if i < len(optimal_order) - 1:
200
+ if 'geometry' in segment_info:
201
+ try:
202
+ route_coords = polyline.decode(segment_info['geometry'])
203
+ folium.PolyLine(
204
+ route_coords,
205
+ weight=2,
206
+ color='blue',
207
+ opacity=0.8
208
+ ).add_to(m)
209
+ except Exception as e:
210
+ st.warning(f"Error drawing route: {str(e)}")
211
+ next_place = places[next_idx][1]
212
+ folium.PolyLine(
213
+ [[lat, lon], [next_place[0], next_place[1]]],
214
+ weight=2,
215
+ color='red',
216
+ opacity=0.8
217
+ ).add_to(m)
218
+
219
+ # Add to totals
220
+ if 'distance_km' in segment_info:
221
+ total_distance += float(segment_info['distance_km'].split()[0])
222
+ if 'duration_mins' in segment_info:
223
+ total_duration += float(segment_info['duration_mins'].split()[0])
224
 
225
+ # Display map
226
+ st.components.v1.html(m._repr_html_(), height=600)
227
+
228
+ with col2:
229
+ st.header("πŸ“ Optimized Itinerary")
 
230
 
231
+ # Display summary first
232
+ st.markdown("### πŸ“Š Summary")
233
+ st.info(f"""
234
+ πŸ›£οΈ Total distance: **{total_distance:.1f} km**
235
+ ⏱️ Total duration: **{total_duration:.0f} mins** ({total_duration/60:.1f} hours)
236
+ """)
237
 
238
+ st.markdown("### πŸ—ΊοΈ Route Details")
239
  for i in range(len(optimal_order)):
240
  current_idx = optimal_order[i]
241
  next_idx = optimal_order[(i + 1) % len(optimal_order)]
 
243
  current_place = places[current_idx][0]
244
  segment_info = routing_info.get((current_idx, next_idx), {})
245
 
246
+ st.markdown(f"**Stop {i+1}: {current_place}**")
247
 
248
  if i < len(optimal_order) - 1:
249
+ st.markdown(f"↓ *{segment_info.get('distance_km', 'N/A')}, "
250
+ f"Duration: {segment_info.get('duration_mins', 'N/A')}*")
251
 
 
252
  if 'steps' in segment_info:
253
+ with st.expander("πŸ“ Turn-by-turn directions"):
254
  instructions = format_instructions(segment_info['steps'])
255
  for idx, instruction in enumerate(instructions, 1):
256
  st.write(f"{idx}. {instruction}")
257
+ else:
258
+ st.info("πŸ‘ˆ Please enter all destinations in the sidebar to get started")
 
 
 
259
 
260
  if __name__ == "__main__":
261
  main()