File size: 8,688 Bytes
faea948
 
 
 
59c425e
4687ad4
 
 
faea948
4687ad4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
faea948
4687ad4
faea948
 
 
 
4687ad4
 
faea948
4687ad4
faea948
4687ad4
 
 
 
 
 
 
 
 
 
faea948
4687ad4
faea948
 
 
 
 
 
 
 
 
 
 
 
 
59c425e
 
 
 
 
 
 
 
 
 
 
4687ad4
 
 
 
 
 
 
 
 
 
faea948
4687ad4
faea948
 
 
 
 
 
 
 
 
 
 
 
 
 
4687ad4
 
faea948
 
 
 
4687ad4
 
 
faea948
4687ad4
 
faea948
4687ad4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
faea948
4687ad4
 
 
 
faea948
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
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()