vincentiusyoshuac commited on
Commit
f2cb27e
Β·
verified Β·
1 Parent(s): 1f0084d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +33 -33
app.py CHANGED
@@ -8,9 +8,9 @@ import time
8
  from functools import lru_cache
9
  from concurrent.futures import ThreadPoolExecutor
10
 
11
- def held_karp_tsp(dist_matrix: np.ndarray) -> tuple:
12
  """
13
- Held-Karp algorithm for solving TSP
14
  Returns: (minimum cost, optimal route)
15
  """
16
  if len(dist_matrix) < 2:
@@ -19,15 +19,12 @@ def held_karp_tsp(dist_matrix: np.ndarray) -> tuple:
19
  n = len(dist_matrix)
20
  inf = float('inf')
21
 
22
- # Use numpy arrays for better performance
23
  dp = np.full((1 << n, n), inf)
24
  parent = np.full((1 << n, n), -1, dtype=int)
25
 
26
- # Base cases
27
  for i in range(1, n):
28
  dp[1 << i][i] = dist_matrix[0][i]
29
 
30
- # Main DP loop
31
  for mask in range(1, 1 << n):
32
  if bin(mask).count('1') <= 1:
33
  continue
@@ -43,9 +40,14 @@ def held_karp_tsp(dist_matrix: np.ndarray) -> tuple:
43
  dp[mask][curr] = candidate
44
  parent[mask][curr] = prev
45
 
46
- # Reconstruct path
47
  mask = (1 << n) - 1
48
- curr = min(range(n), key=lambda x: dp[mask][x] + dist_matrix[x][0])
 
 
 
 
 
 
49
  path = []
50
  while curr != -1:
51
  path.append(curr)
@@ -53,10 +55,11 @@ def held_karp_tsp(dist_matrix: np.ndarray) -> tuple:
53
  curr = parent[mask][curr]
54
  mask = new_mask
55
 
56
- path.append(0)
 
57
  path.reverse()
58
 
59
- return dp[(1 << n) - 1][path[-2]] + dist_matrix[path[-2]][0], path
60
 
61
  @st.cache_data
62
  def get_route_osrm(start_coords: tuple, end_coords: tuple) -> tuple:
@@ -65,10 +68,7 @@ def get_route_osrm(start_coords: tuple, end_coords: tuple) -> tuple:
65
  Returns: (distance in km, encoded polyline of the route)
66
  """
67
  try:
68
- # Format coordinates for OSRM (lon,lat format)
69
  coords = f"{start_coords[1]},{start_coords[0]};{end_coords[1]},{end_coords[0]}"
70
-
71
- # OSRM public API endpoint
72
  url = f"http://router.project-osrm.org/route/v1/driving/{coords}"
73
  params = {
74
  "overview": "full",
@@ -82,8 +82,8 @@ def get_route_osrm(start_coords: tuple, end_coords: tuple) -> tuple:
82
  data = response.json()
83
  if data["code"] == "Ok" and len(data["routes"]) > 0:
84
  route = data["routes"][0]
85
- distance = route["distance"] / 1000 # Convert to km
86
- geometry = route["geometry"] # Already encoded polyline
87
  return distance, geometry
88
  else:
89
  st.warning("No route found")
@@ -120,18 +120,17 @@ def create_distance_matrix_with_routes(coordinates: list) -> tuple:
120
  return np.array([]), valid_coordinates, {}
121
 
122
  dist_matrix = np.zeros((n, n))
123
- routes_dict = {} # Store encoded polylines for routes
124
 
125
  def calculate_route(i, j):
126
  if i != j:
127
- # Add delay to avoid hitting rate limits
128
- time.sleep(0.1)
129
  distance, route = get_route_osrm(valid_coordinates[i], valid_coordinates[j])
130
  if distance is not None:
131
  return i, j, distance, route
132
  return i, j, 0, None
133
 
134
- with ThreadPoolExecutor(max_workers=5) as executor: # Limit concurrent requests
135
  futures = []
136
  for i in range(n):
137
  for j in range(i + 1, n):
@@ -149,21 +148,18 @@ def create_distance_matrix_with_routes(coordinates: list) -> tuple:
149
  return dist_matrix, valid_coordinates, routes_dict
150
 
151
  def plot_route_with_roads(map_obj: folium.Map, coordinates: list, route: list,
152
- city_names: list, routes_dict: dict) -> folium.Map:
153
  """
154
  Plot route using actual road paths from OSRM
155
  """
156
  route_group = folium.FeatureGroup(name="Route")
157
 
158
- # Plot road segments between consecutive points
159
- total_distance = 0
160
  for i in range(len(route)-1):
161
  start_idx = route[i]
162
  end_idx = route[i+1]
163
  route_key = f"{start_idx}-{end_idx}"
164
 
165
  if route_key in routes_dict:
166
- # Decode and plot the polyline
167
  decoded_path = polyline.decode(routes_dict[route_key])
168
  folium.PolyLine(
169
  decoded_path,
@@ -173,9 +169,12 @@ def plot_route_with_roads(map_obj: folium.Map, coordinates: list, route: list,
173
  tooltip=f"Segment {i+1}: {city_names[start_idx]} β†’ {city_names[end_idx]}"
174
  ).add_to(route_group)
175
 
176
- # Add markers with custom icons
177
  for i, point_idx in enumerate(route):
178
- icon_color = "green" if i == 0 else "red" if i == len(route)-1 else "blue"
 
 
 
 
179
  popup_text = f"""
180
  <div style='font-size: 12px'>
181
  <b>City:</b> {city_names[point_idx]}<br>
@@ -191,7 +190,6 @@ def plot_route_with_roads(map_obj: folium.Map, coordinates: list, route: list,
191
 
192
  route_group.add_to(map_obj)
193
 
194
- # Add OpenStreetMap tile layer
195
  folium.TileLayer(
196
  tiles='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
197
  attr='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
@@ -212,6 +210,14 @@ def main():
212
 
213
  with col1:
214
  st.subheader("πŸ“ Masukkan Lokasi")
 
 
 
 
 
 
 
 
215
  city_count = st.number_input("Jumlah lokasi", min_value=2, max_value=10, value=3, step=1,
216
  help="Maksimum 10 lokasi direkomendasikan karena batasan API")
217
 
@@ -257,29 +263,23 @@ def main():
257
  with st.spinner("Menghitung rute optimal..."):
258
  start_time = time.time()
259
 
260
- # Get distance matrix and routes
261
  dist_matrix, valid_coordinates, routes_dict = create_distance_matrix_with_routes(city_coords)
262
-
263
- # Calculate optimal route
264
- min_cost, optimal_route = held_karp_tsp(dist_matrix)
265
 
266
  end_time = time.time()
267
 
268
  if min_cost == float('inf'):
269
  st.error("❌ Tidak dapat menemukan rute yang valid")
270
  else:
271
- # Display results
272
  st.success(f"βœ… Rute dihitung dalam {end_time - start_time:.2f} detik")
273
  st.write(f"πŸ›£οΈ Total jarak berkendara: {min_cost:.2f} km")
274
  st.write("πŸ“ Rute optimal:")
275
  route_text = " β†’ ".join([city_names[i] for i in optimal_route])
276
  st.code(route_text)
277
 
278
- # Update map with route
279
  map_obj = plot_route_with_roads(map_obj, valid_coordinates, optimal_route,
280
- city_names, routes_dict)
281
 
282
- # Display map
283
  st.components.v1.html(folium.Map._repr_html_(map_obj), height=600)
284
 
285
  if __name__ == "__main__":
 
8
  from functools import lru_cache
9
  from concurrent.futures import ThreadPoolExecutor
10
 
11
+ def held_karp_tsp(dist_matrix: np.ndarray, return_to_start: bool = True) -> tuple:
12
  """
13
+ Modified Held-Karp algorithm for solving TSP
14
  Returns: (minimum cost, optimal route)
15
  """
16
  if len(dist_matrix) < 2:
 
19
  n = len(dist_matrix)
20
  inf = float('inf')
21
 
 
22
  dp = np.full((1 << n, n), inf)
23
  parent = np.full((1 << n, n), -1, dtype=int)
24
 
 
25
  for i in range(1, n):
26
  dp[1 << i][i] = dist_matrix[0][i]
27
 
 
28
  for mask in range(1, 1 << n):
29
  if bin(mask).count('1') <= 1:
30
  continue
 
40
  dp[mask][curr] = candidate
41
  parent[mask][curr] = prev
42
 
 
43
  mask = (1 << n) - 1
44
+ if return_to_start:
45
+ curr = min(range(n), key=lambda x: dp[mask][x] + dist_matrix[x][0])
46
+ final_cost = dp[mask][curr] + dist_matrix[curr][0]
47
+ else:
48
+ curr = min(range(n), key=lambda x: dp[mask][x])
49
+ final_cost = dp[mask][curr]
50
+
51
  path = []
52
  while curr != -1:
53
  path.append(curr)
 
55
  curr = parent[mask][curr]
56
  mask = new_mask
57
 
58
+ if return_to_start:
59
+ path.append(0)
60
  path.reverse()
61
 
62
+ return final_cost, path
63
 
64
  @st.cache_data
65
  def get_route_osrm(start_coords: tuple, end_coords: tuple) -> tuple:
 
68
  Returns: (distance in km, encoded polyline of the route)
69
  """
70
  try:
 
71
  coords = f"{start_coords[1]},{start_coords[0]};{end_coords[1]},{end_coords[0]}"
 
 
72
  url = f"http://router.project-osrm.org/route/v1/driving/{coords}"
73
  params = {
74
  "overview": "full",
 
82
  data = response.json()
83
  if data["code"] == "Ok" and len(data["routes"]) > 0:
84
  route = data["routes"][0]
85
+ distance = route["distance"] / 1000
86
+ geometry = route["geometry"]
87
  return distance, geometry
88
  else:
89
  st.warning("No route found")
 
120
  return np.array([]), valid_coordinates, {}
121
 
122
  dist_matrix = np.zeros((n, n))
123
+ routes_dict = {}
124
 
125
  def calculate_route(i, j):
126
  if i != j:
127
+ time.sleep(0.1)
 
128
  distance, route = get_route_osrm(valid_coordinates[i], valid_coordinates[j])
129
  if distance is not None:
130
  return i, j, distance, route
131
  return i, j, 0, None
132
 
133
+ with ThreadPoolExecutor(max_workers=5) as executor:
134
  futures = []
135
  for i in range(n):
136
  for j in range(i + 1, n):
 
148
  return dist_matrix, valid_coordinates, routes_dict
149
 
150
  def plot_route_with_roads(map_obj: folium.Map, coordinates: list, route: list,
151
+ city_names: list, routes_dict: dict, return_to_start: bool) -> folium.Map:
152
  """
153
  Plot route using actual road paths from OSRM
154
  """
155
  route_group = folium.FeatureGroup(name="Route")
156
 
 
 
157
  for i in range(len(route)-1):
158
  start_idx = route[i]
159
  end_idx = route[i+1]
160
  route_key = f"{start_idx}-{end_idx}"
161
 
162
  if route_key in routes_dict:
 
163
  decoded_path = polyline.decode(routes_dict[route_key])
164
  folium.PolyLine(
165
  decoded_path,
 
169
  tooltip=f"Segment {i+1}: {city_names[start_idx]} β†’ {city_names[end_idx]}"
170
  ).add_to(route_group)
171
 
 
172
  for i, point_idx in enumerate(route):
173
+ if return_to_start:
174
+ icon_color = "green" if i == 0 or i == len(route)-1 else "blue"
175
+ else:
176
+ icon_color = "green" if i == 0 else "red" if i == len(route)-1 else "blue"
177
+
178
  popup_text = f"""
179
  <div style='font-size: 12px'>
180
  <b>City:</b> {city_names[point_idx]}<br>
 
190
 
191
  route_group.add_to(map_obj)
192
 
 
193
  folium.TileLayer(
194
  tiles='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
195
  attr='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
 
210
 
211
  with col1:
212
  st.subheader("πŸ“ Masukkan Lokasi")
213
+
214
+ trip_type = st.radio(
215
+ "Tipe Perjalanan",
216
+ ["Closed Loop (Kembali ke titik awal)", "Single Trip (Tidak kembali ke titik awal)"],
217
+ help="Pilih apakah Anda ingin kembali ke lokasi awal atau tidak"
218
+ )
219
+ return_to_start = trip_type.startswith("Closed Loop")
220
+
221
  city_count = st.number_input("Jumlah lokasi", min_value=2, max_value=10, value=3, step=1,
222
  help="Maksimum 10 lokasi direkomendasikan karena batasan API")
223
 
 
263
  with st.spinner("Menghitung rute optimal..."):
264
  start_time = time.time()
265
 
 
266
  dist_matrix, valid_coordinates, routes_dict = create_distance_matrix_with_routes(city_coords)
267
+ min_cost, optimal_route = held_karp_tsp(dist_matrix, return_to_start)
 
 
268
 
269
  end_time = time.time()
270
 
271
  if min_cost == float('inf'):
272
  st.error("❌ Tidak dapat menemukan rute yang valid")
273
  else:
 
274
  st.success(f"βœ… Rute dihitung dalam {end_time - start_time:.2f} detik")
275
  st.write(f"πŸ›£οΈ Total jarak berkendara: {min_cost:.2f} km")
276
  st.write("πŸ“ Rute optimal:")
277
  route_text = " β†’ ".join([city_names[i] for i in optimal_route])
278
  st.code(route_text)
279
 
 
280
  map_obj = plot_route_with_roads(map_obj, valid_coordinates, optimal_route,
281
+ city_names, routes_dict, return_to_start)
282
 
 
283
  st.components.v1.html(folium.Map._repr_html_(map_obj), height=600)
284
 
285
  if __name__ == "__main__":