shivam121215 commited on
Commit
1777cca
·
1 Parent(s): d5cfcd0

inital commit

Browse files
.gitignore ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ .__pycache__
2
+ *.pyc
3
+ *.pyo
4
+ *.pyd
5
+ *.db
6
+ cache/
app.py ADDED
@@ -0,0 +1,1139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, request, jsonify, send_file
2
+ from flask_cors import CORS
3
+ import joblib
4
+ import os
5
+ import folium
6
+ import tempfile
7
+ import random
8
+ import numpy as np
9
+ import networkx as nx
10
+ from datetime import datetime
11
+ from route_optimizer import (
12
+ get_or_load_road_network,
13
+ get_simplified_road_network,
14
+ create_synthetic_network,
15
+ add_base_speeds_to_graph,
16
+ MLTrafficPredictor,
17
+ RoadCongestionClassifier,
18
+ TravelTimePredictor,
19
+ EnhancedDynamicRouter,
20
+ user_location_selection_feature,
21
+ check_graph_connectivity,
22
+ find_nearest_accessible_node,
23
+ create_enhanced_route_map,
24
+ create_route_focused_map,
25
+ display_route_details
26
+ )
27
+
28
+ app = Flask(__name__)
29
+ CORS(app)
30
+
31
+ MODEL_DIR = 'saved_models'
32
+ TRAFFIC_MODEL_PATH = os.path.join(MODEL_DIR, 'traffic_predictor.pkl')
33
+ CONGESTION_MODEL_PATH = os.path.join(MODEL_DIR, 'congestion_classifier.pkl')
34
+ TRAVEL_TIME_MODEL_PATH = os.path.join(MODEL_DIR, 'travel_time_predictor.pkl')
35
+
36
+ G = None
37
+ edge_ids = None
38
+ traffic_predictor = None
39
+ congestion_classifier = None
40
+ travel_predictor = None
41
+ router = None
42
+ congestion_risk = None
43
+
44
+ def load_or_train_model(model_class, filepath, *args):
45
+ """Helper function to load model or train if not exists"""
46
+ if os.path.exists(filepath):
47
+ print(f"Loading {filepath}")
48
+ return model_class.load(filepath, *args)
49
+ else:
50
+ print(f"Training and saving {filepath}")
51
+ model = model_class(*args)
52
+ model.save(filepath)
53
+ return model
54
+
55
+ def initialize_system():
56
+ """Initialize the route optimization system and cache the results"""
57
+ global G, edge_ids, traffic_predictor, congestion_classifier, travel_predictor, router, congestion_risk
58
+
59
+ if G is None:
60
+
61
+ # Get road network with better error handling
62
+ try:
63
+ G = get_or_load_road_network()
64
+ except Exception as e:
65
+ print(f"Error obtaining road network: {str(e)}")
66
+ print("Falling back to synthetic network")
67
+ G = create_synthetic_network()
68
+
69
+ G = add_base_speeds_to_graph(G)
70
+ edge_ids = list(G.edges())
71
+
72
+ os.makedirs(MODEL_DIR, exist_ok=True)
73
+
74
+ traffic_predictor = load_or_train_model(
75
+ MLTrafficPredictor,
76
+ TRAFFIC_MODEL_PATH,
77
+ edge_ids
78
+ )
79
+
80
+ congestion_classifier = load_or_train_model(
81
+ RoadCongestionClassifier,
82
+ CONGESTION_MODEL_PATH,
83
+ G
84
+ )
85
+
86
+ travel_predictor = load_or_train_model(
87
+ TravelTimePredictor,
88
+ TRAVEL_TIME_MODEL_PATH,
89
+ G
90
+ )
91
+
92
+ # Classify congestion risk
93
+ congestion_risk = congestion_classifier.classify_roads()
94
+
95
+ # Set up router
96
+ router = EnhancedDynamicRouter(G, edge_ids, travel_predictor)
97
+
98
+ return {
99
+ "G": G,
100
+ "edge_ids": edge_ids,
101
+ "traffic_predictor": traffic_predictor,
102
+ "congestion_classifier": congestion_classifier,
103
+ "travel_predictor": travel_predictor,
104
+ "router": router,
105
+ "congestion_risk": congestion_risk
106
+ }
107
+
108
+
109
+ @app.route('/api/status', methods=['GET'])
110
+ def status():
111
+ """Health check endpoint"""
112
+ return jsonify({"status": "online", "timestamp": datetime.now().isoformat()})
113
+
114
+ @app.route('/api/network/stats', methods=['GET'])
115
+ def get_network_stats():
116
+ """Get basic statistics about the road network"""
117
+ system = initialize_system()
118
+ G = system["G"]
119
+
120
+ node_count = len(G.nodes())
121
+ edge_count = len(G.edges())
122
+
123
+ # Get network bounding box
124
+ min_lat = min(G.nodes[node]['y'] for node in G.nodes())
125
+ max_lat = max(G.nodes[node]['y'] for node in G.nodes())
126
+ min_lon = min(G.nodes[node]['x'] for node in G.nodes())
127
+ max_lon = max(G.nodes[node]['x'] for node in G.nodes())
128
+
129
+ # Road type statistics
130
+ road_types = {}
131
+ for _, _, data in G.edges(data=True):
132
+ road_type = data.get('highway', 'unknown')
133
+ if road_type in road_types:
134
+ road_types[road_type] += 1
135
+ else:
136
+ road_types[road_type] = 1
137
+
138
+ return jsonify({
139
+ "node_count": node_count,
140
+ "edge_count": edge_count,
141
+ "bounding_box": {
142
+ "min_lat": min_lat,
143
+ "max_lat": max_lat,
144
+ "min_lon": min_lon,
145
+ "max_lon": max_lon
146
+ },
147
+ "road_types": road_types
148
+ })
149
+
150
+
151
+
152
+ # Add these routes to the Flask backend
153
+
154
+ @app.route('/api/map/initial', methods=['GET'])
155
+ def get_initial_map():
156
+ """Return an initial map centered on the network"""
157
+ system = initialize_system()
158
+ G = system["G"]
159
+
160
+ # Calculate network center
161
+ lat_values = [data['y'] for _, data in G.nodes(data=True) if 'y' in data]
162
+ lon_values = [data['x'] for _, data in G.nodes(data=True) if 'x' in data]
163
+
164
+ if not lat_values or not lon_values:
165
+ # Default center if no nodes with coordinates'
166
+ center_lat, center_lon = 0, 0
167
+ else:
168
+ center_lat = sum(lat_values) / len(lat_values)
169
+ center_lon = sum(lon_values) / len(lon_values)
170
+
171
+ # Create a basic map centered on the network
172
+ m = folium.Map(location=[center_lat, center_lon], zoom_start=13)
173
+
174
+ # Add a sample of network nodes for reference (limit to avoid heavy map)
175
+ node_sample = random.sample(list(G.nodes()), min(100, len(G.nodes())))
176
+
177
+ for node_id in node_sample:
178
+ node_data = G.nodes[node_id]
179
+ if 'y' in node_data and 'x' in node_data:
180
+ folium.CircleMarker(
181
+ location=[node_data['y'], node_data['x']],
182
+ radius=2,
183
+ color='blue',
184
+ fill=True,
185
+ fill_opacity=0.7,
186
+ popup=f"Node ID: {node_id}"
187
+ ).add_to(m)
188
+
189
+ # Add JavaScript to handle click events on the map
190
+ # Add JavaScript to handle click events on the map with visual feedback
191
+ click_script = """
192
+ <script>
193
+ document.addEventListener('DOMContentLoaded', function() {
194
+ var map = document.querySelector('.folium-map');
195
+
196
+ // Ensure map exists and has been initialized
197
+ if (!map || !map._leaflet_id) {
198
+ console.error('Map element not found or not initialized');
199
+ return;
200
+ }
201
+
202
+ // Access the Leaflet map instance
203
+ var leafletMap = window.L.DomUtil.get('map')._leaflet_map || map._leaflet;
204
+
205
+ // Add click event listener to the map with clear visual feedback
206
+ leafletMap.on('click', function(e) {
207
+ // Get lat/lon from the click event
208
+ var lat = e.latlng.lat;
209
+ var lon = e.latlng.lng;
210
+
211
+ // Add a marker to show the selected point
212
+ var marker = L.marker([lat, lon], {
213
+ icon: L.divIcon({
214
+ className: 'selected-node-marker',
215
+ html: '<div style="background-color: red; width: 10px; height: 10px; border-radius: 50%; border: 2px solid white;"></div>'
216
+ })
217
+ }).addTo(leafletMap);
218
+
219
+ // Send message to parent window with node selection info
220
+ window.parent.postMessage({
221
+ type: 'NODE_SELECTED',
222
+ data: {
223
+ lat: lat,
224
+ lon: lon
225
+ }
226
+ }, '*');
227
+
228
+ // Show visual feedback about the selection
229
+ var popup = L.popup()
230
+ .setLatLng(e.latlng)
231
+ .setContent('<p>Point selected at ' + lat.toFixed(6) + ', ' + lon.toFixed(6) + '</p>')
232
+ .openOn(leafletMap);
233
+
234
+ // Optional: Make an API call to get nearby nodes
235
+ fetch('/api/network/nearby-nodes?lat=' + lat + '&lon=' + lon)
236
+ .then(response => response.json())
237
+ .then(data => {
238
+ if (data.success && data.nodes.length > 0) {
239
+ // Show closest node
240
+ var closestNode = data.nodes[0];
241
+ L.circleMarker([closestNode.lat, closestNode.lon], {
242
+ radius: 8,
243
+ color: 'blue',
244
+ fillColor: '#3186cc',
245
+ fillOpacity: 0.7
246
+ }).addTo(leafletMap)
247
+ .bindPopup('Selected Node ID: ' + closestNode.id)
248
+ .openPopup();
249
+ }
250
+ })
251
+ .catch(error => console.error('Error fetching nearby nodes:', error));
252
+ });
253
+ });
254
+ </script>
255
+ """
256
+
257
+
258
+
259
+ m.get_root().html.add_child(folium.Element(click_script))
260
+
261
+ # Save to temporary file
262
+ with tempfile.NamedTemporaryFile(delete=False, suffix='.html') as tmp:
263
+ m.save(tmp.name)
264
+ tmp_filename = tmp.name
265
+
266
+ return send_file(tmp_filename, mimetype='text/html')
267
+
268
+ @app.route('/api/map/update-with-nodes', methods=['POST'])
269
+ def update_map_with_nodes():
270
+ """Update map to show search results"""
271
+ system = initialize_system()
272
+ G = system["G"]
273
+
274
+ data = request.json
275
+ nodes = data.get('nodes', [])
276
+
277
+ if not nodes:
278
+ return jsonify({
279
+ "error": "No nodes provided",
280
+ "success": False
281
+ }), 400
282
+
283
+ # Calculate map center based on provided nodes
284
+ lat_values = [node['lat'] for node in nodes if 'lat' in node]
285
+ lon_values = [node['lon'] for node in nodes if 'lon' in node]
286
+
287
+ if not lat_values or not lon_values:
288
+ # Default center if no nodes with coordinates
289
+ lat_values = [data['y'] for _, data in G.nodes(data=True) if 'y' in data]
290
+ lon_values = [data['x'] for _, data in G.nodes(data=True) if 'x' in data]
291
+
292
+ if not lat_values or not lon_values:
293
+ center_lat, center_lon = 0, 0
294
+ else:
295
+ center_lat = sum(lat_values) / len(lat_values)
296
+ center_lon = sum(lon_values) / len(lon_values)
297
+ else:
298
+ center_lat = sum(lat_values) / len(lat_values)
299
+ center_lon = sum(lon_values) / len(lon_values)
300
+
301
+ # Create map centered on search results
302
+ m = folium.Map(location=[center_lat, center_lon], zoom_start=14)
303
+
304
+ # Add the search result nodes with distinct styling
305
+ for node in nodes:
306
+ if 'lat' in node and 'lon' in node:
307
+ folium.CircleMarker(
308
+ location=[node['lat'], node['lon']],
309
+ radius=5,
310
+ color='red',
311
+ fill=True,
312
+ fill_opacity=0.7,
313
+ popup=f"Node ID: {node['id']}"
314
+ ).add_to(m)
315
+
316
+ # Add some surrounding nodes for context
317
+ nearby_nodes = []
318
+ for node_id, data in G.nodes(data=True):
319
+ if 'y' in data and 'x' in data:
320
+ for search_node in nodes:
321
+ if 'lat' in search_node and 'lon' in search_node:
322
+ dist = ((data['y'] - search_node['lat'])**2 + (data['x'] - search_node['lon'])**2)**0.5
323
+ if dist <= 0.005: # Small radius in coordinate units
324
+ nearby_nodes.append((node_id, data))
325
+ break
326
+
327
+ # Sample some nearby nodes to avoid cluttering the map
328
+ nearby_sample = random.sample(nearby_nodes, min(30, len(nearby_nodes)))
329
+
330
+ for node_id, node_data in nearby_sample:
331
+ folium.CircleMarker(
332
+ location=[node_data['y'], node_data['x']],
333
+ radius=2,
334
+ color='blue',
335
+ fill=True,
336
+ fill_opacity=0.3,
337
+ popup=f"Node ID: {node_id}"
338
+ ).add_to(m)
339
+
340
+ # Add click handler for selecting nodes
341
+ # Add JavaScript to handle click events on the map with visual feedback
342
+ click_script = """
343
+ <script>
344
+ document.addEventListener('DOMContentLoaded', function() {
345
+ var map = document.querySelector('.folium-map');
346
+
347
+ // Ensure map exists and has been initialized
348
+ if (!map || !map._leaflet_id) {
349
+ console.error('Map element not found or not initialized');
350
+ return;
351
+ }
352
+
353
+ // Access the Leaflet map instance
354
+ var leafletMap = window.L.DomUtil.get('map')._leaflet_map || map._leaflet;
355
+
356
+ // Add click event listener to the map with clear visual feedback
357
+ leafletMap.on('click', function(e) {
358
+ // Get lat/lon from the click event
359
+ var lat = e.latlng.lat;
360
+ var lon = e.latlng.lng;
361
+
362
+ // Add a marker to show the selected point
363
+ var marker = L.marker([lat, lon], {
364
+ icon: L.divIcon({
365
+ className: 'selected-node-marker',
366
+ html: '<div style="background-color: red; width: 10px; height: 10px; border-radius: 50%; border: 2px solid white;"></div>'
367
+ })
368
+ }).addTo(leafletMap);
369
+
370
+ // Send message to parent window with node selection info
371
+ window.parent.postMessage({
372
+ type: 'NODE_SELECTED',
373
+ data: {
374
+ lat: lat,
375
+ lon: lon
376
+ }
377
+ }, '*');
378
+
379
+ // Show visual feedback about the selection
380
+ var popup = L.popup()
381
+ .setLatLng(e.latlng)
382
+ .setContent('<p>Point selected at ' + lat.toFixed(6) + ', ' + lon.toFixed(6) + '</p>')
383
+ .openOn(leafletMap);
384
+
385
+ // Optional: Make an API call to get nearby nodes
386
+ fetch('/api/network/nearby-nodes?lat=' + lat + '&lon=' + lon)
387
+ .then(response => response.json())
388
+ .then(data => {
389
+ if (data.success && data.nodes.length > 0) {
390
+ // Show closest node
391
+ var closestNode = data.nodes[0];
392
+ L.circleMarker([closestNode.lat, closestNode.lon], {
393
+ radius: 8,
394
+ color: 'blue',
395
+ fillColor: '#3186cc',
396
+ fillOpacity: 0.7
397
+ }).addTo(leafletMap)
398
+ .bindPopup('Selected Node ID: ' + closestNode.id)
399
+ .openPopup();
400
+ }
401
+ })
402
+ .catch(error => console.error('Error fetching nearby nodes:', error));
403
+ });
404
+ });
405
+ </script>
406
+ """
407
+
408
+
409
+ m.get_root().html.add_child(folium.Element(click_script))
410
+
411
+
412
+ with tempfile.NamedTemporaryFile(delete=False, suffix='.html') as tmp:
413
+ m.save(tmp.name)
414
+ tmp_filename = tmp.name
415
+
416
+ return send_file(tmp_filename, mimetype='text/html')
417
+
418
+ @app.route('/api/map/update-route', methods=['POST'])
419
+ def update_route_map():
420
+ """Update map to show selected depot and stops"""
421
+ system = initialize_system()
422
+ G = system["G"]
423
+ congestion_risk = system["congestion_risk"]
424
+ router = system["router"]
425
+ traffic_predictor = system["traffic_predictor"]
426
+
427
+ data = request.json
428
+ stops = data.get('stops', [])
429
+ depot = data.get('depot')
430
+ hour = data.get('hour', 12)
431
+ day_of_week = data.get('day_of_week', 1)
432
+ is_weekend = day_of_week >= 5
433
+
434
+ if not depot and not stops:
435
+ return jsonify({
436
+ "error": "No depot or stops provided",
437
+ "success": False
438
+ }), 400
439
+
440
+ all_points = [depot] + stops if depot else stops
441
+ all_points = [p for p in all_points if p is not None]
442
+
443
+ if len(all_points) < 1:
444
+ return jsonify({
445
+ "error": "Need at least one valid location",
446
+ "success": False
447
+ }), 400
448
+
449
+ # Check connectivity and find valid nodes if needed
450
+ valid_points = []
451
+ for point in all_points:
452
+ if point in G:
453
+ valid_points.append(point)
454
+ else:
455
+ closest_node = find_nearest_accessible_node(G, point)
456
+ if closest_node:
457
+ valid_points.append(closest_node)
458
+
459
+ if len(valid_points) < 1:
460
+ return jsonify({
461
+ "error": "No valid nodes found",
462
+ "success": False
463
+ }), 400
464
+
465
+ # Update traffic for routing
466
+ traffic_multipliers = traffic_predictor.predict_for_time(hour, day_of_week)
467
+ router.update_edge_speeds(traffic_multipliers)
468
+
469
+ # Generate a route if we have more than one point
470
+ route = None
471
+ if len(valid_points) > 1:
472
+ route = router.optimize_route(valid_points)
473
+
474
+ # Create map
475
+ if route:
476
+ time_period_name = f"Hour_{hour}_{'Weekend' if is_weekend else 'Weekday'}"
477
+ route_map = create_enhanced_route_map(G, valid_points, route, time_period_name, congestion_risk)
478
+ else:
479
+ # Just create a map showing the points without a route
480
+ lat_values = [G.nodes[p]['y'] for p in valid_points if p in G.nodes()]
481
+ lon_values = [G.nodes[p]['x'] for p in valid_points if p in G.nodes()]
482
+
483
+ if not lat_values or not lon_values:
484
+ return jsonify({
485
+ "error": "No valid coordinates found for selected nodes",
486
+ "success": False
487
+ }), 400
488
+
489
+ center_lat = sum(lat_values) / len(lat_values)
490
+ center_lon = sum(lon_values) / len(lon_values)
491
+
492
+ route_map = folium.Map(location=[center_lat, center_lon], zoom_start=14)
493
+
494
+ for i, point_id in enumerate(valid_points):
495
+ if point_id in G.nodes():
496
+ color = 'green' if i == 0 and depot else 'blue'
497
+ folium.CircleMarker(
498
+ location=[G.nodes[point_id]['y'], G.nodes[point_id]['x']],
499
+ radius=7,
500
+ color=color,
501
+ fill=True,
502
+ fill_opacity=0.7,
503
+ popup=f"{'Depot' if i == 0 and depot else 'Stop'} ID: {point_id}"
504
+ ).add_to(route_map)
505
+
506
+
507
+ click_script = """
508
+ <script>
509
+ document.addEventListener('DOMContentLoaded', function() {
510
+ var map = document.querySelector('.folium-map');
511
+
512
+ // Ensure map exists and has been initialized
513
+ if (!map || !map._leaflet_id) {
514
+ console.error('Map element not found or not initialized');
515
+ return;
516
+ }
517
+
518
+ // Access the Leaflet map instance
519
+ var leafletMap = window.L.DomUtil.get('map')._leaflet_map || map._leaflet;
520
+
521
+ // Add click event listener to the map with clear visual feedback
522
+ leafletMap.on('click', function(e) {
523
+ // Get lat/lon from the click event
524
+ var lat = e.latlng.lat;
525
+ var lon = e.latlng.lng;
526
+
527
+ // Add a marker to show the selected point
528
+ var marker = L.marker([lat, lon], {
529
+ icon: L.divIcon({
530
+ className: 'selected-node-marker',
531
+ html: '<div style="background-color: red; width: 10px; height: 10px; border-radius: 50%; border: 2px solid white;"></div>'
532
+ })
533
+ }).addTo(leafletMap);
534
+
535
+ // Send message to parent window with node selection info
536
+ window.parent.postMessage({
537
+ type: 'NODE_SELECTED',
538
+ data: {
539
+ lat: lat,
540
+ lon: lon
541
+ }
542
+ }, '*');
543
+
544
+ // Show visual feedback about the selection
545
+ var popup = L.popup()
546
+ .setLatLng(e.latlng)
547
+ .setContent('<p>Point selected at ' + lat.toFixed(6) + ', ' + lon.toFixed(6) + '</p>')
548
+ .openOn(leafletMap);
549
+
550
+ // Optional: Make an API call to get nearby nodes
551
+ fetch('/api/network/nearby-nodes?lat=' + lat + '&lon=' + lon)
552
+ .then(response => response.json())
553
+ .then(data => {
554
+ if (data.success && data.nodes.length > 0) {
555
+ // Show closest node
556
+ var closestNode = data.nodes[0];
557
+ L.circleMarker([closestNode.lat, closestNode.lon], {
558
+ radius: 8,
559
+ color: 'blue',
560
+ fillColor: '#3186cc',
561
+ fillOpacity: 0.7
562
+ }).addTo(leafletMap)
563
+ .bindPopup('Selected Node ID: ' + closestNode.id)
564
+ .openPopup();
565
+ }
566
+ })
567
+ .catch(error => console.error('Error fetching nearby nodes:', error));
568
+ });
569
+ });
570
+ </script>
571
+ """
572
+
573
+ route_map.get_root().html.add_child(folium.Element(click_script))
574
+
575
+ with tempfile.NamedTemporaryFile(delete=False, suffix='.html') as tmp:
576
+ route_map.save(tmp.name)
577
+ tmp_filename = tmp.name
578
+
579
+ return send_file(tmp_filename, mimetype='text/html')
580
+
581
+
582
+ @app.route('/api/network/nodes', methods=['GET'])
583
+ def get_network_nodes():
584
+ """Get all nodes in the network with their coordinates"""
585
+ system = initialize_system()
586
+ G = system["G"]
587
+
588
+ limit = int(request.args.get('limit', 100))
589
+ nodes_data = []
590
+
591
+ for i, (node_id, data) in enumerate(G.nodes(data=True)):
592
+ if i >= limit:
593
+ break
594
+
595
+ nodes_data.append({
596
+ "id": node_id,
597
+ "lat": data.get('y'),
598
+ "lon": data.get('x')
599
+ })
600
+
601
+ return jsonify({
602
+ "nodes": nodes_data,
603
+ "total": len(G.nodes()),
604
+ "returned": len(nodes_data)
605
+ })
606
+
607
+ @app.route('/api/traffic/predict', methods=['POST'])
608
+ def predict_traffic():
609
+ """Predict traffic conditions based on time"""
610
+ system = initialize_system()
611
+ traffic_predictor = system["traffic_predictor"]
612
+
613
+ data = request.json
614
+ hour = data.get('hour', 12)
615
+ day_of_week = data.get('day_of_week', 1) # 0=Monday, 6=Sunday
616
+ is_weekend = day_of_week >= 5
617
+
618
+ # Predict traffic multipliers
619
+ traffic_multipliers = traffic_predictor.predict_for_time(hour, day_of_week)
620
+
621
+ # Return only a subset of multipliers to avoid large payloads
622
+ return jsonify({
623
+ "hour": hour,
624
+ "day_of_week": day_of_week,
625
+ "is_weekend": is_weekend,
626
+ "traffic_sample": traffic_multipliers[:10].tolist(), # Convert numpy array to list
627
+ "average_multiplier": float(np.mean(traffic_multipliers)),
628
+ "min_multiplier": float(np.min(traffic_multipliers)),
629
+ "max_multiplier": float(np.max(traffic_multipliers))
630
+ })
631
+
632
+ @app.route('/api/congestion/classify', methods=['GET'])
633
+ def get_congestion():
634
+ """Get road congestion classification"""
635
+ system = initialize_system()
636
+ G = system["G"]
637
+ congestion_risk = system["congestion_risk"]
638
+
639
+ # Convert edge tuples to strings for JSON serialization
640
+ congestion_data = {}
641
+ for (u, v), risk in congestion_risk.items():
642
+ edge_key = f"{u}-{v}"
643
+ congestion_data[edge_key] = {
644
+ "risk_level": int(risk),
645
+ "risk_label": "Low" if risk == 0 else "Medium" if risk == 1 else "High",
646
+ "from_node": u,
647
+ "to_node": v
648
+ }
649
+
650
+ # Count by risk level
651
+ risk_counts = {
652
+ "low": sum(1 for r in congestion_risk.values() if r == 0),
653
+ "medium": sum(1 for r in congestion_risk.values() if r == 1),
654
+ "high": sum(1 for r in congestion_risk.values() if r == 2)
655
+ }
656
+
657
+ # Return only a sample of congestion data to avoid large payloads
658
+ sample_size = min(100, len(congestion_data))
659
+ sample_keys = random.sample(list(congestion_data.keys()), sample_size)
660
+ sample_data = {k: congestion_data[k] for k in sample_keys}
661
+
662
+ return jsonify({
663
+ "congestion_sample": sample_data,
664
+ "risk_counts": risk_counts,
665
+ "total_roads": len(congestion_risk)
666
+ })
667
+
668
+ @app.route('/api/route/optimize', methods=['POST'])
669
+ def optimize_route():
670
+ """Optimize a delivery route based on stops and time"""
671
+ system = initialize_system()
672
+ G = system["G"]
673
+ router = system["router"]
674
+ traffic_predictor = system["traffic_predictor"]
675
+ congestion_risk = system["congestion_risk"]
676
+
677
+ data = request.json
678
+
679
+ # Get time parameters
680
+ hour = data.get('hour', 12)
681
+ day_of_week = data.get('day_of_week', 1)
682
+ is_weekend = day_of_week >= 5
683
+
684
+ # Get stop parameters
685
+ depot_id = data.get('depot_id')
686
+ stop_ids = data.get('stop_ids', [])
687
+
688
+ # If depot_id is not provided, use first node in the graph
689
+ if depot_id is None:
690
+ depot_id = list(G.nodes())[0]
691
+
692
+ # If no stops are provided, select random nodes
693
+ if not stop_ids:
694
+ nodes = list(G.nodes())
695
+ if depot_id in nodes:
696
+ nodes.remove(depot_id)
697
+ stop_count = min(5, len(nodes))
698
+ stop_ids = random.sample(nodes, stop_count)
699
+
700
+ # Combine depot and stops
701
+ all_stops = [depot_id] + stop_ids
702
+
703
+ # Check connectivity and fix if needed
704
+ if not check_graph_connectivity(G, all_stops):
705
+ new_stops = []
706
+ for stop in all_stops:
707
+ if stop not in G:
708
+ closest_node = find_nearest_accessible_node(G, stop)
709
+ if closest_node:
710
+ new_stops.append(closest_node)
711
+ else:
712
+ new_stops.append(stop)
713
+ all_stops = new_stops
714
+
715
+ # Update traffic conditions
716
+ traffic_multipliers = traffic_predictor.predict_for_time(hour, day_of_week)
717
+ router.update_edge_speeds(traffic_multipliers)
718
+
719
+ # Optimize route
720
+ route = router.optimize_route(all_stops)
721
+
722
+ if not route:
723
+ return jsonify({
724
+ "error": "Failed to find a valid route",
725
+ "success": False
726
+ }), 400
727
+
728
+ # Calculate time matrix and details
729
+ time_matrix = router.calculate_time_matrix(all_stops, hour=hour, is_weekend=is_weekend)
730
+
731
+ # Calculate total time and segment details
732
+ total_time = 0
733
+ segments = []
734
+
735
+ for i in range(len(route) - 1):
736
+ start_node = route[i]
737
+ end_node = route[i+1]
738
+
739
+ start_idx = all_stops.index(start_node) if start_node in all_stops else i
740
+ end_idx = all_stops.index(end_node) if end_node in all_stops else i+1
741
+
742
+ segment_time = time_matrix[start_idx][end_idx]
743
+
744
+ # Handle unrealistic travel times as shown in display_route_details from second code
745
+ if segment_time > 1000: # Unrealistic travel time
746
+ start_lat = G.nodes[start_node]['y']
747
+ start_lon = G.nodes[start_node]['x']
748
+ end_lat = G.nodes[end_node]['y']
749
+ end_lon = G.nodes[end_node]['x']
750
+
751
+ dist_km = ((start_lat - end_lat)**2 + (start_lon - end_lon)**2)**0.5 * 111
752
+ segment_time = (dist_km / 30) * 60 # Estimate based on distance
753
+
754
+ total_time += segment_time
755
+
756
+ segments.append({
757
+ "from_node": start_node,
758
+ "to_node": end_node,
759
+ "time_minutes": float(segment_time),
760
+ "cumulative_time": float(total_time)
761
+ })
762
+
763
+ # Prepare node details
764
+ nodes_info = []
765
+ for i, node_id in enumerate(route):
766
+ if node_id in G.nodes():
767
+ nodes_info.append({
768
+ "id": node_id,
769
+ "lat": float(G.nodes[node_id].get('y', 0)),
770
+ "lon": float(G.nodes[node_id].get('x', 0)),
771
+ "is_depot": i == 0,
772
+ "stop_number": i
773
+ })
774
+
775
+ return jsonify({
776
+ "success": True,
777
+ "route": route,
778
+ "total_time_minutes": float(total_time),
779
+ "segments": segments,
780
+ "nodes": nodes_info,
781
+ "hour": hour,
782
+ "day_of_week": day_of_week,
783
+ "is_weekend": is_weekend
784
+ })
785
+
786
+ @app.route('/api/map/route', methods=['POST'])
787
+ def generate_route_map():
788
+ """Generate an HTML map of the optimized route"""
789
+ system = initialize_system()
790
+ G = system["G"]
791
+ router = system["router"]
792
+ traffic_predictor = system["traffic_predictor"]
793
+ congestion_risk = system["congestion_risk"]
794
+
795
+ data = request.json
796
+
797
+ # Get time parameters
798
+ hour = data.get('hour', 12)
799
+ day_of_week = data.get('day_of_week', 1)
800
+ is_weekend = day_of_week >= 5
801
+
802
+ # Get route parameters
803
+ route = data.get('route')
804
+
805
+ if not route:
806
+ # If no route is provided, use the optimize_route endpoint logic
807
+ depot_id = data.get('depot_id')
808
+ stop_ids = data.get('stop_ids', [])
809
+
810
+ if depot_id is None:
811
+ depot_id = list(G.nodes())[0]
812
+
813
+ if not stop_ids:
814
+ nodes = list(G.nodes())
815
+ if depot_id in nodes:
816
+ nodes.remove(depot_id)
817
+ stop_count = min(5, len(nodes))
818
+ stop_ids = random.sample(nodes, stop_count)
819
+
820
+ all_stops = [depot_id] + stop_ids
821
+
822
+ if not check_graph_connectivity(G, all_stops):
823
+ new_stops = []
824
+ for stop in all_stops:
825
+ if stop not in G:
826
+ closest_node = find_nearest_accessible_node(G, stop)
827
+ if closest_node:
828
+ new_stops.append(closest_node)
829
+ else:
830
+ new_stops.append(stop)
831
+ all_stops = new_stops
832
+
833
+ traffic_multipliers = traffic_predictor.predict_for_time(hour, day_of_week)
834
+ router.update_edge_speeds(traffic_multipliers)
835
+
836
+ route = router.optimize_route(all_stops)
837
+
838
+ if not route:
839
+ return jsonify({
840
+ "error": "Failed to generate route map",
841
+ "success": False
842
+ }), 400
843
+
844
+ all_stops = route # Use the route as our stops
845
+ time_matrix = router.calculate_time_matrix(all_stops, hour=hour, is_weekend=is_weekend)
846
+
847
+ # Create map
848
+ time_period_name = f"Hour_{hour}_{'Weekend' if is_weekend else 'Weekday'}"
849
+
850
+ # Based on the map_type parameter, create different map views
851
+ map_type = data.get('map_type', 'enhanced')
852
+
853
+ if map_type == 'focused':
854
+ route_map = create_route_focused_map(G, all_stops, route, time_matrix)
855
+ else: # default to enhanced
856
+ route_map = create_enhanced_route_map(G, all_stops, route, time_period_name, congestion_risk)
857
+
858
+ # Save map to temporary file
859
+ with tempfile.NamedTemporaryFile(delete=False, suffix='.html') as tmp:
860
+ route_map.save(tmp.name)
861
+ tmp_filename = tmp.name
862
+
863
+ # Return the HTML file
864
+ return send_file(tmp_filename, mimetype='text/html')
865
+
866
+ @app.route('/api/travel-time/predict', methods=['POST'])
867
+ def predict_travel_time():
868
+ """Predict travel time between two nodes"""
869
+ system = initialize_system()
870
+ G = system["G"]
871
+ travel_predictor = system["travel_predictor"]
872
+
873
+ data = request.json
874
+ from_node = data.get('from_node')
875
+ to_node = data.get('to_node')
876
+ hour = data.get('hour', 12)
877
+ day_of_week = data.get('day_of_week', 1)
878
+ is_weekend = day_of_week >= 5
879
+
880
+ if from_node is None or to_node is None:
881
+ return jsonify({
882
+ "error": "from_node and to_node are required parameters",
883
+ "success": False
884
+ }), 400
885
+
886
+ if from_node not in G or to_node not in G:
887
+ return jsonify({
888
+ "error": "One or both nodes not found in the network",
889
+ "success": False
890
+ }), 404
891
+ try:
892
+ travel_time = travel_predictor.predict_travel_time(from_node, to_node, hour, is_weekend)
893
+
894
+ try:
895
+ path = nx.shortest_path(G, from_node, to_node, weight="travel_time")
896
+
897
+ distance = 0
898
+ for i in range(len(path) - 1):
899
+ u, v = path[i], path[i+1]
900
+ if 'length' in G[u][v]:
901
+ distance += G[u][v]['length']
902
+
903
+ distance_km = distance / 1000
904
+
905
+ path_coords = []
906
+ for node in path:
907
+ if node in G.nodes():
908
+ path_coords.append({
909
+ "lat": float(G.nodes[node].get('y', 0)),
910
+ "lon": float(G.nodes[node].get('x', 0))
911
+ })
912
+
913
+ return jsonify({
914
+ "success": True,
915
+ "from_node": from_node,
916
+ "to_node": to_node,
917
+ "travel_time_minutes": float(travel_time),
918
+ "distance_km": float(distance_km),
919
+ "path": path,
920
+ "path_coords": path_coords,
921
+ "hour": hour,
922
+ "day_of_week": day_of_week,
923
+ "is_weekend": is_weekend
924
+ })
925
+
926
+ except nx.NetworkXNoPath:
927
+ return jsonify({
928
+ "success": False,
929
+ "error": "No path found between the given nodes",
930
+ "travel_time_minutes": float(travel_time) if travel_time != float('inf') else None
931
+ }), 404
932
+
933
+ except Exception as e:
934
+ return jsonify({
935
+ "success": False,
936
+ "error": str(e)
937
+ }), 500
938
+
939
+ @app.route('/api/settings', methods=['GET'])
940
+ def get_settings():
941
+ """Get system settings and parameters"""
942
+ return jsonify({
943
+ "time_periods": [
944
+ {"label": "Morning Rush (8 AM)", "hour": 8, "day_of_week": 1, "is_weekend": False},
945
+ {"label": "Mid-day (12 PM)", "hour": 12, "day_of_week": 1, "is_weekend": False},
946
+ {"label": "Evening Rush (6 PM)", "hour": 18, "day_of_week": 1, "is_weekend": False},
947
+ {"label": "Night (11 PM)", "hour": 23, "day_of_week": 1, "is_weekend": False},
948
+ {"label": "Weekend (12 PM)", "hour": 12, "day_of_week": 6, "is_weekend": True}
949
+ ],
950
+ "map_types": [
951
+ {"value": "enhanced", "label": "Enhanced (Full Traffic View)"},
952
+ {"value": "focused", "label": "Focused (Route Only)"}
953
+ ],
954
+ "congestion_levels": [
955
+ {"value": 0, "label": "Low", "color": "green"},
956
+ {"value": 1, "label": "Medium", "color": "orange"},
957
+ {"value": 2, "label": "High", "color": "red"}
958
+ ]
959
+ })
960
+
961
+ @app.route('/api/network/nearby-nodes', methods=['GET'])
962
+ def get_nearby_nodes():
963
+ """Find nodes near specified coordinates"""
964
+ system = initialize_system()
965
+ G = system["G"]
966
+
967
+ lat = float(request.args.get('lat', 0))
968
+ lon = float(request.args.get('lon', 0))
969
+ radius = float(request.args.get('radius', 0.001)) # Default radius in coordinate units
970
+ limit = int(request.args.get('limit', 10))
971
+
972
+ nearby_nodes = []
973
+
974
+ for node_id, data in G.nodes(data=True):
975
+ if 'y' in data and 'x' in data:
976
+ dist = ((data['y'] - lat)**2 + (data['x'] - lon)**2)**0.5
977
+ if dist <= radius:
978
+ nearby_nodes.append({
979
+ "id": node_id,
980
+ "lat": float(data['y']),
981
+ "lon": float(data['x']),
982
+ "distance": float(dist),
983
+ "distance_km": float(dist * 111)
984
+ })
985
+
986
+ nearby_nodes.sort(key=lambda x: x["distance"])
987
+
988
+ nearby_nodes = nearby_nodes[:limit]
989
+
990
+ return jsonify({
991
+ "success": True,
992
+ "query": {
993
+ "lat": lat,
994
+ "lon": lon,
995
+ "radius": radius
996
+ },
997
+ "nodes": nearby_nodes,
998
+ "count": len(nearby_nodes)
999
+ })
1000
+
1001
+ @app.route('/api/network/congestion-hotspots', methods=['GET'])
1002
+ def get_congestion_hotspots():
1003
+ """Get the most congested areas in the network"""
1004
+ system = initialize_system()
1005
+ G = system["G"]
1006
+ congestion_risk = system["congestion_risk"]
1007
+
1008
+ limit = int(request.args.get('limit', 10))
1009
+
1010
+ high_risk_edges = [(u, v) for (u, v), risk in congestion_risk.items() if risk == 2]
1011
+
1012
+ node_risk_count = {}
1013
+ for u, v in high_risk_edges:
1014
+ if u in node_risk_count:
1015
+ node_risk_count[u] += 1
1016
+ else:
1017
+ node_risk_count[u] = 1
1018
+
1019
+ if v in node_risk_count:
1020
+ node_risk_count[v] += 1
1021
+ else:
1022
+ node_risk_count[v] = 1
1023
+
1024
+ hotspot_nodes = sorted(node_risk_count.items(), key=lambda x: x[1], reverse=True)[:limit]
1025
+
1026
+ hotspots = []
1027
+ for node_id, risk_count in hotspot_nodes:
1028
+ if node_id in G.nodes():
1029
+ hotspots.append({
1030
+ "id": node_id,
1031
+ "lat": float(G.nodes[node_id].get('y', 0)),
1032
+ "lon": float(G.nodes[node_id].get('x', 0)),
1033
+ "high_risk_road_count": risk_count,
1034
+ "connected_roads": list(G.edges(node_id))[:5]
1035
+ })
1036
+
1037
+ return jsonify({
1038
+ "success": True,
1039
+ "hotspots": hotspots,
1040
+ "count": len(hotspots)
1041
+ })
1042
+
1043
+ @app.route('/api/system/reload', methods=['POST'])
1044
+ def reload_system():
1045
+ """Force reload of the road network and models (admin use)"""
1046
+ global G, edge_ids, traffic_predictor, congestion_classifier, travel_predictor, router, congestion_risk
1047
+
1048
+ G = None
1049
+ edge_ids = None
1050
+ traffic_predictor = None
1051
+ congestion_classifier = None
1052
+ travel_predictor = None
1053
+ router = None
1054
+ congestion_risk = None
1055
+
1056
+ system = initialize_system()
1057
+
1058
+ return jsonify({
1059
+ "success": True,
1060
+ "message": "System reloaded successfully",
1061
+ "network_nodes": len(system["G"].nodes()),
1062
+ "network_edges": len(system["G"].edges())
1063
+ })
1064
+
1065
+ @app.route('/api/user-location', methods=['POST'])
1066
+ def user_location_selection():
1067
+ """Process user-selected locations for routing"""
1068
+ system = initialize_system()
1069
+ G = system["G"]
1070
+ router = system["router"]
1071
+ travel_predictor = system["travel_predictor"]
1072
+ traffic_predictor = system["traffic_predictor"]
1073
+ congestion_risk = system["congestion_risk"]
1074
+
1075
+ data = request.json
1076
+ locations = data.get('locations', [])
1077
+ hour = data.get('hour', datetime.now().hour)
1078
+ day_of_week = data.get('day_of_week', datetime.now().weekday())
1079
+ is_weekend = day_of_week >= 5
1080
+
1081
+ if not locations or len(locations) < 2:
1082
+ return jsonify({
1083
+ "error": "At least 2 locations are required",
1084
+ "success": False
1085
+ }), 400
1086
+
1087
+ # Find nearest nodes to selected coordinates
1088
+ selected_nodes = []
1089
+ for loc in locations:
1090
+ lat = loc.get('lat')
1091
+ lon = loc.get('lon')
1092
+ if lat is None or lon is None:
1093
+ continue
1094
+
1095
+ # Find nearest node
1096
+ nearest_node = None
1097
+ min_dist = float('inf')
1098
+ for node_id, data in G.nodes(data=True):
1099
+ if 'y' in data and 'x' in data:
1100
+ dist = ((data['y'] - lat)**2 + (data['x'] - lon)**2)**0.5
1101
+ if dist < min_dist:
1102
+ min_dist = dist
1103
+ nearest_node = node_id
1104
+
1105
+ if nearest_node:
1106
+ selected_nodes.append(nearest_node)
1107
+
1108
+ if len(selected_nodes) < 2:
1109
+ return jsonify({
1110
+ "error": "Could not map at least 2 locations to network nodes",
1111
+ "success": False
1112
+ }), 400
1113
+
1114
+ # Update traffic conditions
1115
+ traffic_multipliers = traffic_predictor.predict_for_time(hour, day_of_week)
1116
+ router.update_edge_speeds(traffic_multipliers)
1117
+
1118
+ route = router.optimize_route(selected_nodes)
1119
+
1120
+ if not route:
1121
+ return jsonify({
1122
+ "error": "Failed to find a valid route between selected locations",
1123
+ "success": False
1124
+ }), 400
1125
+
1126
+ time_matrix = router.calculate_time_matrix(selected_nodes, hour=hour, is_weekend=is_weekend)
1127
+
1128
+ time_period_name = f"User_Selected_Route_{hour}h"
1129
+ route_map = create_enhanced_route_map(G, selected_nodes, route, time_period_name, congestion_risk)
1130
+
1131
+ with tempfile.NamedTemporaryFile(delete=False, suffix='.html') as tmp:
1132
+ route_map.save(tmp.name)
1133
+ tmp_filename = tmp.name
1134
+
1135
+ return send_file(tmp_filename, mimetype='text/html')
1136
+
1137
+ if __name__ == '__main__':
1138
+ initialize_system()
1139
+ app.run(debug=False, host='0.0.0.0', port=7860)
dockerfile ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10-slim
2
+
3
+
4
+ WORKDIR /backend
5
+
6
+ COPY requirements.txt .
7
+ RUN pip install --no-cache-dir -r requirements.txt
8
+
9
+ COPY . .
10
+
11
+ EXPOSE 7860
12
+
13
+ CMD ["gunicorn", "--bind", "0.0.0.0:7860", "app:app"]
requirements.txt ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Flask
2
+ flask-cors
3
+ joblib==1.4.2
4
+ numpy==2.0.0
5
+ networkx
6
+ folium
7
+ osmnx
8
+ tensorflow
9
+ ortools
10
+ scikit-learn==1.5.0
11
+ pickle-mixin
12
+ pandas==2.2.2
13
+ gunicorn>=20.1.0
route_optimizer.py ADDED
@@ -0,0 +1,717 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import osmnx as ox
2
+ import numpy as np
3
+ import tensorflow as tf
4
+ from ortools.constraint_solver import pywrapcp, routing_enums_pb2
5
+ import folium
6
+ from sklearn.preprocessing import MinMaxScaler
7
+ import random
8
+ from sklearn.ensemble import RandomForestRegressor
9
+ from datetime import datetime, timedelta
10
+ from sklearn.ensemble import GradientBoostingRegressor
11
+ from sklearn.ensemble import RandomForestClassifier
12
+ import pickle
13
+ import joblib
14
+ import networkx as nx
15
+ import os
16
+
17
+ ox.settings.use_cache = True
18
+ ox.settings.log_console = True
19
+ ox.settings.timeout=180
20
+
21
+ def get_simplified_road_network():
22
+ """Get only major roads in Chandigarh for cleaner visualization"""
23
+ try:
24
+ custom_filter = '["highway"~"primary|secondary|tertiary"]'
25
+ G = ox.graph_from_place("Chandigarh, India",
26
+ network_type="drive",
27
+ simplify=True,
28
+ custom_filter=custom_filter)
29
+
30
+ G = ox.add_edge_speeds(G)
31
+ G = ox.add_edge_travel_times(G)
32
+ print(f"Road Network successfully loaded with {len(G.nodes)} nodes and {len(G.edges)} edges")
33
+
34
+ sector_17_point = (30.7417, 76.7875)
35
+
36
+
37
+ G_simplified = nx.DiGraph()
38
+
39
+ for node, data in G.nodes(data=True):
40
+ if 'y' in data and 'x' in data:
41
+ dist = ((data['y'] - sector_17_point[0])**2 +
42
+ (data['x'] - sector_17_point[1])**2)**0.5
43
+ dist_km = dist * 111
44
+ if dist_km <= 2:
45
+ G_simplified.add_node(node, **data)
46
+
47
+ for u, v, data in G.edges(data=True):
48
+ if G_simplified.has_node(u) and G_simplified.has_node(v):
49
+ G_simplified.add_edge(u, v, **data)
50
+
51
+ print(f"Simplified road network created with {len(G_simplified.nodes)} nodes")
52
+
53
+ largest_cc = max(nx.weakly_connected_components(G_simplified), key=len)
54
+ G_simplified = G_simplified.subgraph(largest_cc).copy()
55
+
56
+ print(f"Final connected graph has {len(G_simplified.nodes)} nodes and {len(G_simplified.edges)} edges")
57
+ return G_simplified
58
+
59
+ except Exception as e:
60
+ print(f"Error obtaining road network: {str(e)}")
61
+ return create_synthetic_network()
62
+
63
+ def create_synthetic_network():
64
+ """Create a synthetic road network as a fallback"""
65
+ print("Creating synthetic grid network that resembles Chandigarh's sector layout...")
66
+
67
+ rows, cols = 6, 6
68
+ G = nx.DiGraph()
69
+
70
+ base_lat, base_lon = 30.72, 76.76
71
+ grid_spacing = 0.01
72
+
73
+ for i in range(rows):
74
+ for j in range(cols):
75
+ node_id = i * cols + j
76
+ lat_offset = random.uniform(-0.0005, 0.0005)
77
+ lon_offset = random.uniform(-0.0005, 0.0005)
78
+
79
+ G.add_node(node_id,
80
+ y=base_lat + i * grid_spacing + lat_offset,
81
+ x=base_lon + j * grid_spacing + lon_offset)
82
+
83
+ for i in range(rows):
84
+ for j in range(cols):
85
+ node_id = i * cols + j
86
+
87
+ if j < cols - 1:
88
+ target = i * cols + (j + 1)
89
+ length = random.uniform(900, 1100)
90
+ G.add_edge(node_id, target,
91
+ length=length,
92
+ speed_kph=50.0,
93
+ highway="secondary",
94
+ travel_time=length/1000/50.0*60,
95
+ base_speed=50.0)
96
+ G.add_edge(target, node_id,
97
+ length=length,
98
+ speed_kph=50.0,
99
+ highway="secondary",
100
+ travel_time=length/1000/50.0*60,
101
+ base_speed=50.0)
102
+
103
+ if i < rows - 1:
104
+ target = (i + 1) * cols + j
105
+ length = random.uniform(900, 1100)
106
+ G.add_edge(node_id, target,
107
+ length=length,
108
+ speed_kph=50.0,
109
+ highway="secondary",
110
+ travel_time=length/1000/50.0*60,
111
+ base_speed=50.0)
112
+ G.add_edge(target, node_id,
113
+ length=length,
114
+ speed_kph=50.0,
115
+ highway="secondary",
116
+ travel_time=length/1000/50.0*60,
117
+ base_speed=50.0)
118
+
119
+ for i in range(rows - 1):
120
+ for j in range(cols - 1):
121
+ if random.random() < 0.3:
122
+ node_id = i * cols + j
123
+ target = (i + 1) * cols + (j + 1)
124
+ length = random.uniform(1200, 1400)
125
+ G.add_edge(node_id, target,
126
+ length=length,
127
+ speed_kph=40.0,
128
+ highway="tertiary",
129
+ travel_time=length/1000/40.0*60,
130
+ base_speed=40.0)
131
+
132
+ print(f"Created synthetic grid network with {len(G.nodes)} nodes and {len(G.edges)} edges")
133
+ return G
134
+
135
+
136
+ class MLTrafficPredictor:
137
+ def __init__(self, edge_ids, num_time_periods=24):
138
+ self.edge_ids = edge_ids
139
+ self.model = None
140
+
141
+ def predict_for_time(self, hour, day_of_week):
142
+ """Predict traffic multipliers for a specific time"""
143
+ time_features = [
144
+ hour,
145
+ day_of_week,
146
+ 1 if hour in [7, 8, 9, 17, 18, 19] else 0,
147
+ 1 if day_of_week < 5 else 0
148
+ ]
149
+
150
+ predictions = self.model.predict([time_features])[0]
151
+ return predictions
152
+
153
+ def predict_for_time_period(self, time_period):
154
+ """Compatible with original interface for easy integration"""
155
+ if time_period == 0:
156
+ hour, day = 8, 1
157
+ elif time_period == 1:
158
+ hour, day = 12, 1
159
+ elif time_period == 2:
160
+ hour, day = 18, 1
161
+ else:
162
+ hour, day = 23, 1
163
+
164
+ return self.predict_for_time(hour, day)
165
+
166
+ @staticmethod
167
+ def load(filepath, edge_ids):
168
+ predictor = MLTrafficPredictor(edge_ids)
169
+ predictor.model = joblib.load(filepath)
170
+ print(f"Traffic prediction model loaded from {filepath}")
171
+ return predictor
172
+
173
+
174
+ class RoadCongestionClassifier:
175
+ def __init__(self, G):
176
+ self.G = G
177
+ self.model = None
178
+
179
+ def classify_roads(self):
180
+ """Classify all roads by congestion risk"""
181
+ congestion_risk = {}
182
+
183
+ for u, v, data in self.G.edges(data=True):
184
+ features = [
185
+ data.get('length', 0) / 1000,
186
+ 1 if data.get('highway') == 'primary' else 0,
187
+ 1 if data.get('highway') == 'secondary' else 0,
188
+ 1 if data.get('highway') == 'tertiary' else 0,
189
+ data.get('lanes', 1) if isinstance(data.get('lanes'), int) else 1,
190
+ 1 if 'oneway' in data and data['oneway'] else 0
191
+ ]
192
+
193
+ risk = self.model.predict([features])[0]
194
+ congestion_risk[(u, v)] = risk
195
+
196
+ return congestion_risk
197
+
198
+ @staticmethod
199
+ def load(filepath, G):
200
+ classifier = RoadCongestionClassifier(G)
201
+ classifier.model = joblib.load(filepath)
202
+ print(f"Road congestion classifier loaded from {filepath}")
203
+ return classifier
204
+
205
+ class TravelTimePredictor:
206
+ def __init__(self, G):
207
+ self.G = G
208
+ self.model = None
209
+
210
+ def predict_travel_time(self, u, v, hour, is_weekend=False):
211
+ """Predict travel time between two nodes"""
212
+ if not self.G.has_edge(u, v):
213
+ return float('inf')
214
+
215
+ data = self.G[u][v]
216
+ distance = data.get('length', 0) / 1000
217
+ base_speed = data.get('base_speed', 40.0)
218
+
219
+ features = [
220
+ distance,
221
+ base_speed,
222
+ 1 if data.get('highway') == 'primary' else 0,
223
+ 1 if data.get('highway') == 'secondary' else 0,
224
+ 1 if data.get('highway') == 'tertiary' else 0,
225
+ hour,
226
+ 1 if hour in [7, 8, 9, 17, 18, 19] else 0,
227
+ 1 if is_weekend else 0
228
+ ]
229
+
230
+ travel_time = self.model.predict([features])[0]
231
+ return travel_time
232
+
233
+ @staticmethod
234
+ def load(filepath, G):
235
+ predictor = TravelTimePredictor(G)
236
+ predictor.model = joblib.load(filepath)
237
+ print(f"Travel time prediction model loaded from {filepath}")
238
+ return predictor
239
+
240
+ def user_location_selection_feature(G):
241
+ center_y = G.nodes[list(G.nodes)[0]]['y']
242
+ center_x = G.nodes[list(G.nodes)[0]]['x']
243
+
244
+ m = folium.Map(location=[center_y, center_x], zoom_start=13)
245
+
246
+
247
+ user_selected_coords = [
248
+ (30.74, 76.78),
249
+ (30.73, 76.77),
250
+ (30.72, 76.79),
251
+ # etc.
252
+ ]
253
+
254
+ selected_nodes = []
255
+ for lat, lon in user_selected_coords:
256
+ nearest_node = ox.distance.nearest_nodes(G, lon, lat)
257
+ selected_nodes.append(nearest_node)
258
+
259
+ depot = selected_nodes[0]
260
+ all_stops = selected_nodes
261
+
262
+ edge_ids = list(G.edges())
263
+ traffic_predictor = MLTrafficPredictor.load("traffic_model.pkl", edge_ids)
264
+ travel_predictor = TravelTimePredictor.load("travel_time_predictor.pkl", G)
265
+ congestion_classifier = RoadCongestionClassifier.load("congestion_classifier.pkl", G)
266
+
267
+ now = datetime.now()
268
+ hour = now.hour
269
+ is_weekend = now.weekday() >= 5
270
+
271
+ traffic_multipliers = traffic_predictor.predict_for_time(hour, now.weekday())
272
+ congestion_risk = congestion_classifier.classify_roads()
273
+
274
+ router = EnhancedDynamicRouter(G, edge_ids, travel_predictor)
275
+ router.update_edge_speeds(traffic_multipliers)
276
+
277
+ route = router.optimize_route(all_stops)
278
+
279
+ time_matrix = router.calculate_time_matrix(all_stops, hour=hour, is_weekend=is_weekend)
280
+
281
+ route_map = create_enhanced_route_map(G, all_stops, route, f"User_Selected_Route_{hour}h", congestion_risk)
282
+
283
+ return route_map
284
+
285
+ class EnhancedDynamicRouter:
286
+ def __init__(self, G, edge_ids, travel_predictor):
287
+ self.G = G
288
+ self.edge_ids = edge_ids
289
+ self.travel_predictor = travel_predictor
290
+
291
+ def update_edge_speeds(self, traffic_multipliers):
292
+ """Update road network with traffic multipliers"""
293
+ for i, (u, v) in enumerate(self.edge_ids):
294
+ if i < len(traffic_multipliers) and self.G.has_edge(u, v):
295
+ self.G[u][v]["current_speed"] = max(5.0, traffic_multipliers[i] * self.G[u][v]["base_speed"])
296
+
297
+ def calculate_time_matrix(self, stops, hour=8, is_weekend=False):
298
+ """Compute travel time matrix using ML prediction"""
299
+ time_matrix = np.zeros((len(stops), len(stops)))
300
+
301
+ for i, source in enumerate(stops):
302
+ for j, target in enumerate(stops):
303
+ if i != j:
304
+ try:
305
+ path = nx.shortest_path(self.G, source, target, weight="travel_time")
306
+
307
+ travel_time = 0
308
+ for idx in range(len(path)-1):
309
+ u, v = path[idx], path[idx+1]
310
+ segment_time = self.travel_predictor.predict_travel_time(u, v, hour, is_weekend)
311
+ travel_time += segment_time
312
+
313
+ time_matrix[i][j] = travel_time
314
+ except (nx.NetworkXNoPath, nx.NodeNotFound):
315
+ print(f"Warning: No path found between stops {i} and {j} (nodes {source} and {target})")
316
+ time_matrix[i][j] = 60.0
317
+
318
+ return time_matrix
319
+
320
+ def optimize_route(self, stops):
321
+ """Solve VRP using OR-Tools"""
322
+ time_matrix = self.calculate_time_matrix(stops)
323
+
324
+ manager = pywrapcp.RoutingIndexManager(len(stops), 1, 0)
325
+ routing = pywrapcp.RoutingModel(manager)
326
+
327
+ def time_callback(from_index, to_index):
328
+ from_node = manager.IndexToNode(from_index)
329
+ to_node = manager.IndexToNode(to_index)
330
+ return int(time_matrix[from_node][to_node] * 1000)
331
+
332
+ transit_callback_index = routing.RegisterTransitCallback(time_callback)
333
+ routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)
334
+
335
+ search_parameters = pywrapcp.DefaultRoutingSearchParameters()
336
+ search_parameters.first_solution_strategy = (
337
+ routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC
338
+ )
339
+
340
+ solution = routing.SolveWithParameters(search_parameters)
341
+ if solution:
342
+ return self._extract_route(stops, manager, routing, solution)
343
+ return None
344
+
345
+ def _extract_route(self, stops, manager, routing, solution):
346
+ index = routing.Start(0)
347
+ route = []
348
+ while not routing.IsEnd(index):
349
+ node = manager.IndexToNode(index)
350
+ route.append(stops[node])
351
+ index = solution.Value(routing.NextVar(index))
352
+ route.append(stops[manager.IndexToNode(index)])
353
+ return route
354
+
355
+ def check_graph_connectivity(G, stops):
356
+ """Verify that all stops are reachable from each other"""
357
+ problems = []
358
+
359
+ for i, source in enumerate(stops):
360
+ for j, target in enumerate(stops):
361
+ if i != j:
362
+ try:
363
+ path = nx.shortest_path(G, source, target)
364
+ except (nx.NetworkXNoPath, nx.NodeNotFound):
365
+ problems.append((i, j, source, target))
366
+
367
+ if problems:
368
+ print("WARNING: The following stop pairs are not connected in the graph:")
369
+ for i, j, source, target in problems:
370
+ print(f" - Stop {i} (Node {source}) to Stop {j} (Node {target})")
371
+
372
+ print("\nPossible solutions:")
373
+ print("1. Check if these nodes exist in the graph")
374
+ print("2. Ensure the graph is fully connected")
375
+ print("3. Pick alternative nearby locations for problematic stops")
376
+
377
+ return False
378
+
379
+ return True
380
+
381
+ def find_nearest_accessible_node(G, original_node, max_distance=0.001):
382
+ """Find the nearest node that is well-connected to the network"""
383
+ if not G.has_node(original_node):
384
+ print(f"Node {original_node} not found in graph!")
385
+ return None
386
+
387
+ orig_y = G.nodes[original_node]['y']
388
+ orig_x = G.nodes[original_node]['x']
389
+
390
+ largest_cc = max(nx.weakly_connected_components(G), key=len)
391
+
392
+ connected_nodes = []
393
+ for node in largest_cc:
394
+ if node == original_node:
395
+ continue
396
+
397
+ node_y = G.nodes[node]['y']
398
+ node_x = G.nodes[node]['x']
399
+
400
+ dist = ((node_y - orig_y)**2 + (node_x - orig_x)**2)**0.5
401
+ connected_nodes.append((node, dist))
402
+
403
+ connected_nodes.sort(key=lambda x: x[1])
404
+
405
+ for node, dist in connected_nodes:
406
+ if dist <= max_distance:
407
+ print(f"Found alternative for {original_node}: {node} at distance {dist*111:.2f} km")
408
+ return node
409
+
410
+ if connected_nodes:
411
+ closest_node, dist = connected_nodes[0]
412
+ print(f"WARNING: Closest alternative to {original_node} is {closest_node} at {dist*111:.2f} km away")
413
+ return closest_node
414
+
415
+ return None
416
+
417
+ def create_enhanced_route_map(G, stops, route, time_period_name, congestion_risk):
418
+ center_y = G.nodes[list(G.nodes)[0]]['y']
419
+ center_x = G.nodes[list(G.nodes)[0]]['x']
420
+
421
+ m = folium.Map(location=[center_y, center_x], zoom_start=13)
422
+
423
+ for u, v, data in G.edges(data=True):
424
+ u_y = G.nodes[u].get('y', 0)
425
+ u_x = G.nodes[u].get('x', 0)
426
+ v_y = G.nodes[v].get('y', 0)
427
+ v_x = G.nodes[v].get('x', 0)
428
+
429
+ risk = congestion_risk.get((u, v), 0)
430
+
431
+ if risk == 0:
432
+ color = "green"
433
+ weight = 3
434
+ elif risk == 1:
435
+ color = "orange"
436
+ weight = 4
437
+ else:
438
+ color = "red"
439
+ weight = 5
440
+
441
+ popup = f"""
442
+ <b>Road Type:</b> {data.get('highway', 'Unknown')}<br>
443
+ <b>Current Speed:</b> {data.get('current_speed', 0):.1f} km/h<br>
444
+ <b>Base Speed:</b> {data.get('base_speed', 0):.1f} km/h<br>
445
+ <b>ML Congestion Risk:</b> {"Low" if risk == 0 else "Medium" if risk == 1 else "High"}<br>
446
+ <b>Congestion:</b> {(1-data.get('current_speed', 0)/data.get('base_speed', 1))*100:.1f}%
447
+ """
448
+
449
+ folium.PolyLine(
450
+ locations=[(u_y, u_x), (v_y, v_x)],
451
+ color=color,
452
+ weight=weight,
453
+ opacity=0.7,
454
+ popup=popup
455
+ ).add_to(m)
456
+
457
+ for i, node in enumerate(stops):
458
+ node_y = G.nodes[node]['y']
459
+ node_x = G.nodes[node]['x']
460
+
461
+ if i == 0:
462
+ folium.Marker(
463
+ location=[node_y, node_x],
464
+ tooltip="Depot",
465
+ popup="<b>Depot</b><br>Starting point for delivery route",
466
+ icon=folium.Icon(color="green", icon="home")
467
+ ).add_to(m)
468
+ else:
469
+ folium.Marker(
470
+ location=[node_y, node_x],
471
+ tooltip=f"Delivery Stop {i}",
472
+ popup=f"<b>Delivery Stop {i}</b>",
473
+ icon=folium.Icon(color="blue", icon="info-sign")
474
+ ).add_to(m)
475
+
476
+ if route:
477
+ route_coords = []
478
+ for node in route:
479
+ point = [G.nodes[node]['y'], G.nodes[node]['x']]
480
+ route_coords.append(point)
481
+
482
+ route_line = folium.PolyLine(
483
+ locations=route_coords,
484
+ color="purple",
485
+ weight=4,
486
+ opacity=0.8,
487
+ popup="Optimized Delivery Route"
488
+ ).add_to(m)
489
+
490
+ for i in range(len(route_coords) - 1):
491
+ midpoint = [(route_coords[i][0] + route_coords[i+1][0]) / 2,
492
+ (route_coords[i][1] + route_coords[i+1][1]) / 2]
493
+
494
+ folium.RegularPolygonMarker(
495
+ location=midpoint,
496
+ number_of_sides=3,
497
+ radius=6,
498
+ rotation=45,
499
+ color="purple",
500
+ fill_color="purple",
501
+ fill_opacity=0.7,
502
+ popup=f"Segment {i+1}"
503
+ ).add_to(m)
504
+
505
+ title_html = f'''
506
+ <div style="position: fixed; top: 10px; left: 50%; transform: translateX(-50%);
507
+ z-index:9999; font-size:18px; background-color:white; padding:10px;
508
+ border-radius:5px; box-shadow: 0 0 5px rgba(0,0,0,0.3);">
509
+ <h3 style="margin:0;">Optimized Delivery Route - {time_period_name}</h3>
510
+ </div>
511
+ '''
512
+
513
+ legend_html = '''
514
+ <div style="position: fixed; bottom: 50px; left: 50px; width: 200px;
515
+ background-color:white; padding:10px; border-radius:5px;
516
+ box-shadow: 0 0 5px rgba(0,0,0,0.3); z-index:9999; font-size:12px;">
517
+ <h4 style="margin-top:0;">Legend</h4>
518
+ <div><span style="color:green; font-size:16px;">━━</span> Light Traffic (< 10% slowdown)</div>
519
+ <div><span style="color:orange; font-size:16px;">━━</span> Moderate Traffic (10-30% slowdown)</div>
520
+ <div><span style="color:red; font-size:16px;">━━</span> Heavy Traffic (> 30% slowdown)</div>
521
+ <div><span style="color:purple; font-size:16px;">━━</span> Optimized Route</div>
522
+ <div><span style="color:green;">●</span> Depot</div>
523
+ <div><span style="color:blue;">●</span> Delivery Stops</div>
524
+ </div>
525
+ '''
526
+
527
+ m.get_root().html.add_child(folium.Element(title_html))
528
+ m.get_root().html.add_child(folium.Element(legend_html))
529
+
530
+ return m
531
+ def get_or_load_road_network():
532
+ """Get road network from cache or download if not available"""
533
+ GRAPH_CACHE_FILE = 'saved_maps/chandigarh_network.pkl'
534
+
535
+ # Create directory if it doesn't exist
536
+ os.makedirs('saved_maps', exist_ok=True)
537
+
538
+ # Try to load cached graph
539
+ if os.path.exists(GRAPH_CACHE_FILE):
540
+ try:
541
+ print(f"Loading road network from {GRAPH_CACHE_FILE}")
542
+ with open(GRAPH_CACHE_FILE, 'rb') as f:
543
+ G = pickle.load(f)
544
+ print(f"Loaded road network with {len(G.nodes)} nodes and {len(G.edges)} edges")
545
+ return G
546
+ except Exception as e:
547
+ print(f"Error loading cached road network: {str(e)}")
548
+
549
+ # Download and create new graph
550
+ print("Downloading road network...")
551
+ G = get_simplified_road_network()
552
+
553
+ # Save for future use
554
+ try:
555
+ print(f"Saving road network to {GRAPH_CACHE_FILE}")
556
+ with open(GRAPH_CACHE_FILE, 'wb') as f:
557
+ pickle.dump(G, f)
558
+ except Exception as e:
559
+ print(f"Error saving road network: {str(e)}")
560
+
561
+ return G
562
+
563
+ def create_route_focused_map(G, stops, route, time_matrix):
564
+ """Create a map focused just on the optimal route with time estimates"""
565
+ center_y = G.nodes[route[0]]['y']
566
+ center_x = G.nodes[route[0]]['x']
567
+
568
+ m = folium.Map(location=[center_y, center_x], zoom_start=13)
569
+
570
+ total_time = 0
571
+
572
+ for i in range(len(route) - 1):
573
+ start_node = route[i]
574
+ end_node = route[i+1]
575
+
576
+ start_idx = route.index(start_node)
577
+ end_idx = route.index(end_node)
578
+
579
+ segment_time = time_matrix[start_idx][end_idx]
580
+ total_time += segment_time
581
+
582
+ start_y = G.nodes[start_node]['y']
583
+ start_x = G.nodes[start_node]['x']
584
+ end_y = G.nodes[end_node]['y']
585
+ end_x = G.nodes[end_node]['x']
586
+
587
+ popup_text = f"""
588
+ <b>Segment {i+1}:</b> Stop {i} → Stop {i+1}<br>
589
+ <b>Travel Time:</b> {segment_time:.1f} minutes<br>
590
+ <b>Running Total:</b> {total_time:.1f} minutes
591
+ """
592
+
593
+ folium.PolyLine(
594
+ locations=[(start_y, start_x), (end_y, end_x)],
595
+ color="blue",
596
+ weight=5,
597
+ opacity=0.8,
598
+ popup=popup_text
599
+ ).add_to(m)
600
+
601
+ midpoint = [(start_y + end_y) / 2, (start_x + end_x) / 2]
602
+ folium.RegularPolygonMarker(
603
+ location=midpoint,
604
+ number_of_sides=3,
605
+ radius=6,
606
+ rotation=45,
607
+ color="blue",
608
+ fill_color="blue",
609
+ fill_opacity=0.7
610
+ ).add_to(m)
611
+
612
+ for i, node in enumerate(route):
613
+ node_y = G.nodes[node]['y']
614
+ node_x = G.nodes[node]['x']
615
+
616
+ if i == 0:
617
+ popup_text = "<b>Depot</b><br>Starting Point"
618
+ icon = folium.Icon(color="green", icon="home")
619
+ else:
620
+ cumulative_time = sum(time_matrix[route.index(route[j])][route.index(route[j+1])]
621
+ for j in range(i))
622
+ popup_text = f"""
623
+ <b>Stop {i}</b><br>
624
+ <b>Estimated Arrival:</b> {cumulative_time:.1f} minutes from start
625
+ """
626
+ icon = folium.Icon(color="red", icon="info-sign")
627
+
628
+ folium.Marker(
629
+ location=[node_y, node_x],
630
+ tooltip=f"Stop {i}",
631
+ popup=popup_text,
632
+ icon=icon
633
+ ).add_to(m)
634
+
635
+ title_html = f'''
636
+ <div style="position: fixed; top: 10px; left: 50%; transform: translateX(-50%);
637
+ z-index:9999; font-size:18px; background-color:white; padding:10px;
638
+ border-radius:5px; box-shadow: 0 0 5px rgba(0,0,0,0.3);">
639
+ <h3 style="margin:0;">Optimized Delivery Route</h3>
640
+ <div style="text-align:center; margin-top:5px; font-weight:bold;">
641
+ Total Time: {total_time:.1f} minutes
642
+ </div>
643
+ </div>
644
+ '''
645
+
646
+ m.get_root().html.add_child(folium.Element(title_html))
647
+
648
+ return m
649
+
650
+ def add_base_speeds_to_graph(G):
651
+ """Add base_speed attribute to all edges if it doesn't exist"""
652
+ print("Adding base speeds to graph edges...")
653
+ for u, v, data in G.edges(data=True):
654
+ if 'speed_kph' in data:
655
+ data['base_speed'] = data['speed_kph']
656
+ elif 'maxspeed' in data and data['maxspeed'] is not None:
657
+ try:
658
+ speed_str = data['maxspeed'].split()[0]
659
+ speed = float(speed_str)
660
+ if 'mph' in data['maxspeed']:
661
+ speed = speed * 1.60934
662
+ data['base_speed'] = speed
663
+ except (ValueError, IndexError):
664
+ data['base_speed'] = 40.0
665
+ else:
666
+ if data.get('highway') == 'primary':
667
+ data['base_speed'] = 50.0
668
+ elif data.get('highway') == 'secondary':
669
+ data['base_speed'] = 40.0
670
+ elif data.get('highway') == 'tertiary':
671
+ data['base_speed'] = 30.0
672
+ else:
673
+ data['base_speed'] = 40.0
674
+
675
+ print("Base speeds added to all edges")
676
+ return G
677
+
678
+ def display_route_details(G, route, time_matrix):
679
+ """Display the optimal route and estimated arrival times clearly"""
680
+ total_time = 0
681
+ print("\n===== OPTIMIZED DELIVERY ROUTE =====")
682
+ print(f"Starting from depot at Node {route[0]}")
683
+
684
+ for i in range(1, len(route)):
685
+ prev_stop = route[i-1]
686
+ curr_stop = route[i]
687
+
688
+ prev_idx = route.index(prev_stop)
689
+ curr_idx = route.index(curr_stop)
690
+
691
+ segment_time = time_matrix[prev_idx][curr_idx]
692
+
693
+ if segment_time > 1000:
694
+ print(f"\nWARNING: Unrealistic travel time detected for segment {i-1} to {i}")
695
+ print(f"Stop {i}: Node {curr_stop}")
696
+ print(f" Travel time calculation error - please check connectivity")
697
+ prev_lat = G.nodes[prev_stop]['y']
698
+ prev_lon = G.nodes[prev_stop]['x']
699
+ curr_lat = G.nodes[curr_stop]['y']
700
+ curr_lon = G.nodes[curr_stop]['x']
701
+
702
+ dist_km = ((prev_lat - curr_lat)**2 + (prev_lon - curr_lon)**2)**0.5 * 111
703
+
704
+ estimated_time = (dist_km / 30) * 60
705
+ print(f" Estimated travel time based on distance: {estimated_time:.1f} minutes")
706
+
707
+ total_time += estimated_time
708
+ else:
709
+ total_time += segment_time
710
+ print(f"\nStop {i}: Node {curr_stop} ({G.nodes[curr_stop]['y']:.4f}, {G.nodes[curr_stop]['x']:.4f})")
711
+ print(f" Travel from previous stop: {segment_time:.1f} minutes")
712
+ print(f" Cumulative travel time: {total_time:.1f} minutes")
713
+
714
+ print(f"\nTotal route time: {total_time:.1f} minutes")
715
+ print("==================================")
716
+
717
+ return total_time
saved_maps/chandigarh_network.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8708e7a3cc12107943c48a0b0f923a7834209e965d3dbf2fec8b3bb9187cff71
3
+ size 104727
saved_models/congestion_classifier.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:45048a5e01617b0f1340c0c070d38d818b4d2ba22f68be899217dfbe6265eb8f
3
+ size 1458529
saved_models/congestion_model.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4ae576914985bf6548fa641d2c12d6b0a1759da801af9ef51071a1e74be3d952
3
+ size 1505505
saved_models/time_model.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b44047f31eb859e4c9aa55c2b8498c643f9d82d8409c828a45130ebbe6c85f2b
3
+ size 195080
saved_models/traffic_model.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:fdcdf1ea909407065aa1443bf96115a6a6336f2e7bb2fc58ef89f1d5d4493c41
3
+ size 73876329
saved_models/traffic_predictor.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1186100cc20940e0a7535ad241137ee39aa01f536f163e12f5476910fbbecfb2
3
+ size 73876329
saved_models/travel_time_predictor.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ca888201b6c230b286aa89fcc7a11dd21a1302777df07aa2dacf413bc0694a6e
3
+ size 189468
wsgi.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+
4
+ if not os.path.exists('route_optimizer.py'):
5
+ print("Error: route_optimizer.py file not found!")
6
+ print("Please save your original code as 'route_optimizer.py' before running this application.")
7
+ sys.exit(1)
8
+ import app
9
+
10
+ if __name__ == '__main__':
11
+ print("Starting Route Optimization API Server...")
12
+
13
+ from app import app, initialize_system
14
+ print("Initializing route optimization system...")
15
+ system = initialize_system()
16
+ print(f"System initialized with {len(system['G'].nodes())} nodes and {len(system['G'].edges())} edges")
17
+
18
+ app.run()