vincentiusyoshuac commited on
Commit
46bf7d3
·
verified ·
1 Parent(s): ccc6ec2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +103 -264
app.py CHANGED
@@ -1,283 +1,122 @@
1
  import streamlit as st
2
  import folium
 
 
 
3
  import numpy as np
4
- from typing import List, Tuple, Dict, Optional
5
- import time
6
- from collections import deque
7
- import requests
8
  import polyline
9
- from geopy.geocoders import Nominatim
10
 
11
- # Utility Functions
12
- def create_numbered_marker(number: int) -> folium.features.DivIcon:
13
- """Create a numbered marker icon for the map"""
14
- return folium.features.DivIcon(
15
- html=f'<div style="background-color: white; border-radius: 50%; width: 25px; height: 25px; '
16
- f'display: flex; align-items: center; justify-content: center; border: 2px solid blue; '
17
- f'font-weight: bold;">{number}</div>'
18
- )
19
 
20
- def get_place_coordinates(place_name: str) -> Optional[Tuple[float, float]]:
21
- """Get coordinates for a place using Nominatim geocoding"""
22
- try:
23
- geolocator = Nominatim(user_agent="travel_optimizer")
24
- location = geolocator.geocode(place_name)
25
- if location:
26
- return (location.latitude, location.longitude)
27
- except Exception as e:
28
- st.error(f"Error geocoding {place_name}: {str(e)}")
29
- return None
30
 
31
- def format_instructions(steps: List[Dict]) -> List[str]:
32
- """Format OSRM route steps into readable instructions"""
33
- instructions = []
34
- for step in steps:
35
- if 'maneuver' in step:
36
- instruction = step.get('maneuver', {}).get('instruction', '')
37
- if instruction:
38
- # Add distance information if available
39
- if 'distance' in step:
40
- distance_km = step['distance'] / 1000
41
- if distance_km >= 1:
42
- instruction += f" ({distance_km:.1f} km)"
43
- else:
44
- instruction += f" ({step['distance']:.0f} m)"
45
- instructions.append(instruction)
46
- return instructions
47
 
48
- def get_route_from_osrm(coord1: Tuple[float, float], coord2: Tuple[float, float]) -> Optional[Dict]:
49
- """Get route information from OSRM"""
50
- url = f"http://router.project-osrm.org/route/v1/driving/{coord1[1]},{coord1[0]};{coord2[1]},{coord2[0]}"
51
- params = {
52
- "overview": "full",
53
- "geometries": "polyline",
54
- "steps": "true",
55
- "annotations": "true"
56
- }
57
-
58
- # Add rate limiting
59
- time.sleep(1) # Respect OSRM usage guidelines
60
-
61
- try:
62
- response = requests.get(url, params=params)
63
- if response.status_code == 200:
64
- data = response.json()
65
- if data["code"] == "Ok" and len(data["routes"]) > 0:
66
- route = data["routes"][0]
67
- return {
68
- 'distance': route['distance'] / 1000, # Convert to km
69
- 'duration': route['duration'] / 60, # Convert to minutes
70
- 'geometry': route['geometry'],
71
- 'steps': route['legs'][0]['steps']
72
- }
73
- except Exception as e:
74
- st.warning(f"Error getting route: {str(e)}")
75
- return None
76
 
77
- def calculate_distance_matrix(places: List[Tuple[str, Tuple[float, float]]]) -> Tuple[np.ndarray, Dict]:
78
- """Calculate distance matrix and routing information using OSRM"""
79
- n = len(places)
80
- distances = np.zeros((n, n))
81
- routing_info = {}
82
-
83
- progress_bar = st.progress(0)
84
- total_calcs = n * (n-1)
85
- current_calc = 0
86
-
87
- for i in range(n):
88
- for j in range(n):
89
- if i != j:
90
- origin = places[i][1]
91
- destination = places[j][1]
92
-
93
- route_data = get_route_from_osrm(origin, destination)
94
-
95
- if route_data:
96
- distances[i,j] = route_data['distance']
97
- routing_info[(i,j)] = {
98
- 'distance_km': f"{route_data['distance']:.1f} km",
99
- 'duration_mins': f"{route_data['duration']:.0f} mins",
100
- 'geometry': route_data['geometry'],
101
- 'steps': route_data['steps']
102
- }
103
-
104
- current_calc += 1
105
- progress_bar.progress(current_calc / total_calcs)
106
-
107
- return distances, routing_info
108
 
109
- def dijkstra(distances: np.ndarray, start_idx: int) -> Tuple[List[int], Dict[int, float]]:
110
- """Implement Dijkstra's algorithm to find the optimal route"""
111
- n = len(distances)
112
- visited = [False] * n
113
- distances_to = [float('inf')] * n
114
- prev_node = [-1] * n
115
-
116
- distances_to[start_idx] = 0
117
- queue = deque([start_idx])
118
-
119
- while queue:
120
- current_node = queue.popleft()
121
- visited[current_node] = True
122
-
123
- for neighbor in range(n):
124
- if not visited[neighbor] and distances[current_node, neighbor] > 0:
125
- new_distance = distances_to[current_node] + distances[current_node, neighbor]
126
- if new_distance < distances_to[neighbor]:
127
- distances_to[neighbor] = new_distance
128
- prev_node[neighbor] = current_node
129
- queue.append(neighbor)
130
-
131
- # Reconstruct the optimal route
132
- optimal_order = []
133
- for i in range(n):
134
- node = i
135
- while prev_node[node] != -1:
136
- optimal_order.insert(0, node)
137
- node = prev_node[node]
138
- if len(optimal_order) > 0 and optimal_order[0] == i:
139
- optimal_order.insert(0, start_idx)
140
- break
141
-
142
- return optimal_order, distances_to
143
 
144
- def add_markers_and_route(m: folium.Map, places: List[Tuple[str, Tuple[float, float]]], optimal_order: List[int], routing_info: Dict):
145
- """Add markers and route lines to the map"""
146
- total_distance = 0
147
- total_duration = 0
148
-
149
- for i in range(len(optimal_order)):
150
- current_idx = optimal_order[i]
151
- next_idx = optimal_order[(i + 1) % len(optimal_order)]
152
-
153
- current_place, (lat, lon) = places[current_idx]
154
- segment_info = routing_info.get((current_idx, next_idx), {})
155
-
156
- # Add marker
157
- popup_content = f"""
158
- <b>Stop {i+1}: {current_place}</b><br>
159
- """
160
- if i < len(optimal_order) - 1:
161
- popup_content += f"""
162
- To next stop:<br>
163
- Distance: {segment_info.get('distance_km', 'N/A')}<br>
164
- Duration: {segment_info.get('duration_mins', 'N/A')}
165
- """
166
-
167
- folium.Marker(
168
- [lat, lon],
169
- popup=folium.Popup(popup_content, max_width=300),
170
- icon=create_numbered_marker(i+1)
171
- ).add_to(m)
172
-
173
- # Draw route line
174
- if i < len(optimal_order) - 1:
175
- if 'geometry' in segment_info:
176
- try:
177
- route_coords = polyline.decode(segment_info['geometry'])
178
- folium.PolyLine(
179
- route_coords,
180
- weight=2,
181
- color='blue',
182
- opacity=0.8
183
- ).add_to(m)
184
- except Exception as e:
185
- st.warning(f"Error drawing route: {str(e)}")
186
- next_place = places[next_idx][1]
187
- folium.PolyLine(
188
- [[lat, lon], [next_place[0], next_place[1]]],
189
- weight=2,
190
- color='red',
191
- opacity=0.8
192
- ).add_to(m)
193
-
194
- # Add to totals
195
- if 'distance_km' in segment_info:
196
- total_distance += float(segment_info['distance_km'].split()[0])
197
- if 'duration_mins' in segment_info:
198
- total_duration += float(segment_info['duration_mins'].split()[0])
199
-
200
- return total_distance, total_duration
201
 
202
- def display_itinerary_summary(places: List[Tuple[str, Tuple[float, float]]], optimal_order: List[int], routing_info: Dict):
203
- """Display the optimized itinerary summary"""
204
- # Display summary first
205
- st.markdown("### 📊 Summary")
206
- total_distance, total_duration = add_markers_and_route(folium.Map(), places, optimal_order, routing_info)
207
- st.info(f"""
208
- 🛣️ Total distance: **{total_distance:.1f} km**
209
- ⏱️ Total duration: **{total_duration:.0f} mins** ({total_duration/60:.1f} hours)
210
- """)
211
-
212
- st.markdown("### 🗺️ Route Details")
213
- for i in range(len(optimal_order)):
214
- current_idx = optimal_order[i]
215
- next_idx = optimal_order[(i + 1) % len(optimal_order)]
216
-
217
- current_place = places[current_idx][0]
218
- segment_info = routing_info.get((current_idx, next_idx), {})
219
-
220
- st.markdown(f"**Stop {i+1}: {current_place}**")
221
-
222
- if i < len(optimal_order) - 1:
223
- st.markdown(f"↓ *{segment_info.get('distance_km', 'N/A')}, "
224
- f"Duration: {segment_info.get('duration_mins', 'N/A')}*")
225
-
226
- if 'steps' in segment_info:
227
- with st.expander("📍 Turn-by-turn directions"):
228
- instructions = format_instructions(segment_info['steps'])
229
- for idx, instruction in enumerate(instructions, 1):
230
- st.write(f"{idx}. {instruction}")
231
 
232
- def main():
233
- st.set_page_config(page_title="AI Travel Route Optimizer", layout="wide")
234
- st.title("🌍 AI Travel Route Optimizer")
235
- st.markdown("""
236
- This app helps you find the optimal route between multiple destinations using real driving distances.
237
- Enter your destinations in the sidebar and get a customized route with turn-by-turn directions.
238
- """)
 
239
 
240
- # Sidebar for inputs
241
- with st.sidebar:
242
- st.header("📍 Destinations")
243
- num_places = st.number_input("Number of destinations", min_value=2, max_value=10, value=3)
244
- places = []
245
- for i in range(num_places):
246
- place = st.text_input(f"Destination {i+1}")
247
- if place:
248
- with st.spinner(f"Finding coordinates for {place}..."):
249
- coordinates = get_place_coordinates(place)
250
- if coordinates:
251
- places.append((place, coordinates))
252
- st.success(f"✓ Found coordinates for {place}")
253
- else:
254
- st.error(f"❌ Couldn't find coordinates for {place}")
255
 
256
- # Main content area
257
- if len(places) >= 2:
258
- col1, col2 = st.columns([2, 1])
259
- with col1:
260
- with st.spinner("🚗 Calculating optimal route..."):
261
- # Calculate distance matrix
262
- distances, routing_info = calculate_distance_matrix(places)
263
-
264
- # Get optimized route order using Dijkstra's algorithm
265
- optimal_order, _ = dijkstra(distances, 0)
266
-
267
- # Update the route if the user changes the input
268
- if st.button("Recalculate Route"):
269
- distances, routing_info = calculate_distance_matrix(places)
270
- optimal_order, _ = dijkstra(distances, 0)
271
 
272
- # Create map and display
273
- m = folium.Map()
274
- total_distance, total_duration = add_markers_and_route(m, places, optimal_order, routing_info)
275
- st.components.v1.html(m._repr_html_(), height=600)
276
 
277
- with col2:
278
- display_itinerary_summary(places, optimal_order, routing_info)
 
 
 
 
279
  else:
280
- st.info("👈 Please enter at least 2 destinations in the sidebar to get started")
 
 
 
 
 
 
281
 
282
- if __name__ == "__main__":
283
- main()
 
 
 
 
 
1
  import streamlit as st
2
  import folium
3
+ from geopy.geocoders import Nominatim
4
+ from geopy.distance import geodesic
5
+ from itertools import combinations
6
  import numpy as np
 
 
 
 
7
  import polyline
8
+ import requests
9
 
10
+ # Algoritma Held-Karp untuk TSP
11
+ def held_karp_tsp(dist_matrix):
12
+ n = len(dist_matrix)
13
+ inf = float('inf')
14
+ memo = {}
 
 
 
15
 
16
+ # Initialize memo table for subsets of size 2
17
+ for i in range(1, n):
18
+ memo[(1 << i, i)] = dist_matrix[0][i]
 
 
 
 
 
 
 
19
 
20
+ # Fill memo table for larger subsets
21
+ for subset_size in range(2, n):
22
+ for subset in combinations(range(1, n), subset_size):
23
+ bits = 0
24
+ for bit in subset:
25
+ bits |= 1 << bit
26
+ for next_city in subset:
27
+ prev_bits = bits & ~(1 << next_city)
28
+ min_dist = inf
29
+ for k in subset:
30
+ if k == next_city:
31
+ continue
32
+ current_dist = memo[(prev_bits, k)] + dist_matrix[k][next_city]
33
+ if current_dist < min_dist:
34
+ min_dist = current_dist
35
+ memo[(bits, next_city)] = min_dist
36
 
37
+ # Find optimal tour
38
+ bits = (2 ** n - 1) - 1
39
+ min_tour_cost = inf
40
+ last_city = None
41
+ for k in range(1, n):
42
+ tour_cost = memo[(bits, k)] + dist_matrix[k][0]
43
+ if tour_cost < min_tour_cost:
44
+ min_tour_cost = tour_cost
45
+ last_city = k
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
 
47
+ # Backtrack to find the full tour
48
+ tour = [0]
49
+ bits = (2 ** n - 1) - 1
50
+ for _ in range(n - 1):
51
+ tour.append(last_city)
52
+ bits &= ~(1 << last_city)
53
+ next_city = min(
54
+ [(memo[(bits, k)] + dist_matrix[k][last_city], k) for k in range(n) if (bits, k) in memo],
55
+ key=lambda x: x[0],
56
+ )[1]
57
+ last_city = next_city
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
 
59
+ tour.append(0)
60
+ return min_tour_cost, tour
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
 
62
+ # Mendapatkan koordinat kota dari nama
63
+ def get_coordinates(city_name):
64
+ geolocator = Nominatim(user_agent="tsp_app")
65
+ location = geolocator.geocode(city_name)
66
+ if location:
67
+ return (location.latitude, location.longitude)
68
+ else:
69
+ return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
 
71
+ # Menghitung jarak antar semua pasangan kota
72
+ def create_distance_matrix(coordinates):
73
+ n = len(coordinates)
74
+ dist_matrix = np.zeros((n, n))
75
+ for i in range(n):
76
+ for j in range(i + 1, n):
77
+ dist_matrix[i][j] = geodesic(coordinates[i], coordinates[j]).kilometers
78
+ dist_matrix[j][i] = dist_matrix[i][j]
79
+ return dist_matrix
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
 
81
+ # Fungsi untuk menampilkan peta rute
82
+ def plot_route(map_obj, coordinates, route):
83
+ for i in range(len(route) - 1):
84
+ start = coordinates[route[i]]
85
+ end = coordinates[route[i + 1]]
86
+ folium.Marker(location=start, tooltip=f'City {route[i] + 1}').add_to(map_obj)
87
+ folium.Marker(location=end, tooltip=f'City {route[i + 1] + 1}').add_to(map_obj)
88
+ folium.PolyLine([start, end], color="blue", weight=2.5, opacity=1).add_to(map_obj)
89
 
90
+ # Add markers for start and end points
91
+ folium.Marker(location=coordinates[0], tooltip='Start', icon=folium.Icon(color="green")).add_to(map_obj)
92
+ folium.Marker(location=coordinates[route[-2]], tooltip='End', icon=folium.Icon(color="red")).add_to(map_obj)
93
+ return map_obj
 
 
 
 
 
 
 
 
 
 
 
94
 
95
+ # Streamlit UI
96
+ st.title("Traveling Salesman Problem Solver")
 
 
 
 
 
 
 
 
 
 
 
 
 
97
 
98
+ # Input kota
99
+ city_names = st.text_input("Masukkan nama kota (pisahkan dengan koma)", "Jakarta, Bandung, Surabaya, Yogyakarta")
100
+ city_list = [city.strip() for city in city_names.split(",")]
 
101
 
102
+ if len(city_list) < 2:
103
+ st.warning("Masukkan setidaknya dua kota.")
104
+ else:
105
+ coordinates = [get_coordinates(city) for city in city_list]
106
+ if None in coordinates:
107
+ st.error("Ada kota yang tidak ditemukan. Pastikan nama kota benar.")
108
  else:
109
+ # Buat distance matrix
110
+ dist_matrix = create_distance_matrix(coordinates)
111
+ min_cost, optimal_route = held_karp_tsp(dist_matrix)
112
+
113
+ # Tampilkan hasil
114
+ st.write(f"Biaya total minimum: {min_cost:.2f} km")
115
+ st.write("Rute optimal:", " -> ".join([city_list[i] for i in optimal_route]))
116
 
117
+ # Buat peta
118
+ map_obj = folium.Map(location=coordinates[0], zoom_start=5)
119
+ plot_route(map_obj, coordinates, optimal_route)
120
+
121
+ # Render map
122
+ st_folium = st.components.v1.html(folium.Map._repr_html_(map_obj), width=700, height=500)