Spaces:
Sleeping
Sleeping
Commit ·
1777cca
1
Parent(s): d5cfcd0
inital commit
Browse files- .gitignore +6 -0
- app.py +1139 -0
- dockerfile +13 -0
- requirements.txt +13 -0
- route_optimizer.py +717 -0
- saved_maps/chandigarh_network.pkl +3 -0
- saved_models/congestion_classifier.pkl +3 -0
- saved_models/congestion_model.pkl +3 -0
- saved_models/time_model.pkl +3 -0
- saved_models/traffic_model.pkl +3 -0
- saved_models/traffic_predictor.pkl +3 -0
- saved_models/travel_time_predictor.pkl +3 -0
- wsgi.py +18 -0
.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()
|