Spaces:
Sleeping
Sleeping
import streamlit as st | |
import folium | |
import numpy as np | |
from scipy.optimize import linear_sum_assignment | |
from folium import plugins | |
import requests | |
from datetime import datetime | |
from geopy.geocoders import Nominatim | |
def get_route_from_osrm(coord1, coord2): | |
"""Get route information from OSRM""" | |
# OSRM expects coordinates in format: longitude,latitude | |
url = f"http://router.project-osrm.org/route/v1/driving/{coord1[1]},{coord1[0]};{coord2[1]},{coord2[0]}" | |
params = { | |
"overview": "full", | |
"geometries": "polyline", | |
"steps": "true", | |
"annotations": "true" | |
} | |
try: | |
response = requests.get(url, params=params) | |
if response.status_code == 200: | |
data = response.json() | |
if data["code"] == "Ok" and len(data["routes"]) > 0: | |
route = data["routes"][0] | |
return { | |
'distance': route['distance'] / 1000, # Convert to km | |
'duration': route['duration'] / 60, # Convert to minutes | |
'geometry': route['geometry'], | |
'steps': route['legs'][0]['steps'] | |
} | |
except: | |
pass | |
return None | |
def calculate_real_distances(places): | |
"""Calculate real driving distances using OSRM""" | |
n = len(places) | |
distances = np.zeros((n, n)) | |
routing_info = {} | |
for i in range(n): | |
for j in range(n): | |
if i != j: | |
origin = places[i][1] # coordinates | |
destination = places[j][1] # coordinates | |
route_data = get_route_from_osrm(origin, destination) | |
if route_data: | |
distances[i,j] = route_data['distance'] | |
routing_info[(i,j)] = { | |
'distance_km': f"{route_data['distance']:.1f} km", | |
'duration_mins': f"{route_data['duration']:.0f} mins", | |
'geometry': route_data['geometry'], | |
'steps': route_data['steps'] | |
} | |
return distances, routing_info | |
def optimize_route(distances): | |
"""Optimize route using Hungarian algorithm""" | |
row_ind, col_ind = linear_sum_assignment(distances) | |
return col_ind | |
def get_place_coordinates(place_name): | |
"""Get coordinates using Nominatim geocoding""" | |
try: | |
geolocator = Nominatim(user_agent="my_travel_app") | |
location = geolocator.geocode(place_name) | |
return (location.latitude, location.longitude) if location else None | |
except: | |
return None | |
def create_numbered_marker(number): | |
"""Create a custom marker with a number""" | |
return plugins.BeautifyIcon( | |
number=number, | |
border_color='#b4b4b4', | |
border_width=1, | |
text_color='black', | |
background_color='white', | |
inner_icon_style='margin-top:0;' | |
) | |
def format_instructions(steps): | |
"""Format routing instructions from OSRM steps""" | |
instructions = [] | |
for step in steps: | |
if 'maneuver' in step: | |
instruction = step.get('maneuver', {}).get('instruction', '') | |
if instruction: | |
instructions.append(instruction) | |
return instructions | |
def main(): | |
st.title("AI Travel Route Optimizer (with OSRM)") | |
# Sidebar for inputs | |
st.sidebar.header("Travel Parameters") | |
# Input for places | |
places = [] | |
num_places = st.sidebar.number_input("Number of places to visit", min_value=2, max_value=10, value=3) | |
for i in range(num_places): | |
place = st.sidebar.text_input(f"Place {i+1}") | |
if place: | |
coordinates = get_place_coordinates(place) | |
if coordinates: | |
places.append((place, coordinates)) | |
else: | |
st.error(f"Couldn't find coordinates for {place}") | |
# Only proceed if we have all places | |
if len(places) == num_places and num_places >= 2: | |
with st.spinner("Calculating optimal route..."): | |
# Calculate real distances | |
distances, routing_info = calculate_real_distances(places) | |
# Optimize route | |
optimized_indices = optimize_route(distances) | |
# Create map | |
center_lat = sum(coord[0] for _, coord in places) / len(places) | |
center_lon = sum(coord[1] for _, coord in places) / len(places) | |
m = folium.Map(location=[center_lat, center_lon], zoom_start=4) | |
# Create optimized itinerary list | |
optimized_places = [(i+1, places[idx][0]) for i, idx in enumerate(optimized_indices)] | |
# Add markers and route lines | |
for i in range(len(optimized_indices)): | |
current_idx = optimized_indices[i] | |
next_idx = optimized_indices[(i + 1) % len(optimized_indices)] | |
# Add numbered marker | |
place_name, (lat, lon) = places[current_idx] | |
next_place = places[next_idx] | |
# Get routing info for this segment | |
segment_info = routing_info.get((current_idx, next_idx), {}) | |
# Create detailed popup content | |
popup_content = f""" | |
<b>Stop {i+1}: {place_name}</b><br> | |
To next stop:<br> | |
Distance: {segment_info.get('distance_km', 'N/A')}<br> | |
Duration: {segment_info.get('duration_mins', 'N/A')} | |
""" | |
folium.Marker( | |
[lat, lon], | |
popup=folium.Popup(popup_content, max_width=300), | |
icon=create_numbered_marker(i+1) | |
).add_to(m) | |
# Draw route line using polyline from OSRM | |
if 'geometry' in segment_info: | |
try: | |
import polyline | |
route_coords = polyline.decode(segment_info['geometry']) | |
folium.PolyLine( | |
route_coords, | |
weight=2, | |
color='blue', | |
opacity=0.8 | |
).add_to(m) | |
except: | |
# Fallback to straight line if polyline decoding fails | |
folium.PolyLine( | |
[[lat, lon], [next_place[1][0], next_place[1][1]]], | |
weight=2, | |
color='red', | |
opacity=0.8 | |
).add_to(m) | |
# Display map | |
st.components.v1.html(m._repr_html_(), height=600) | |
# Display optimized itinerary with route information | |
st.header("Optimized Itinerary") | |
total_distance = 0 | |
total_duration = 0 | |
for i in range(len(optimized_indices)): | |
current_idx = optimized_indices[i] | |
next_idx = optimized_indices[(i + 1) % len(optimized_indices)] | |
place_name = places[current_idx][0] | |
segment_info = routing_info.get((current_idx, next_idx), {}) | |
st.write(f"{i+1}. {place_name}") | |
if i < len(optimized_indices) - 1: | |
st.write(f" ↓ ({segment_info.get('distance_km', 'N/A')}, " | |
f"Duration: {segment_info.get('duration_mins', 'N/A')})") | |
# Display turn-by-turn instructions | |
if 'steps' in segment_info: | |
with st.expander(f"Route instructions to next stop"): | |
instructions = format_instructions(segment_info['steps']) | |
for idx, instruction in enumerate(instructions, 1): | |
st.write(f"{idx}. {instruction}") | |
# Add to totals | |
if 'distance_km' in segment_info: | |
total_distance += float(segment_info['distance_km'].split()[0]) | |
if 'duration_mins' in segment_info: | |
total_duration += float(segment_info['duration_mins'].split()[0]) | |
# Display totals | |
st.write("\nSummary:") | |
st.write(f"Total distance: {total_distance:.1f} km") | |
st.write(f"Total estimated duration: {total_duration:.0f} minutes ({total_duration/60:.1f} hours)") | |
if __name__ == "__main__": | |
main() |