-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathrisk_aware_navigation.py
More file actions
410 lines (341 loc) · 13.4 KB
/
Copy pathrisk_aware_navigation.py
File metadata and controls
410 lines (341 loc) · 13.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
import networkx as nx
from csv_graph_loader import load_graph_from_csv, nearest_nodes
import pandas as pd
import numpy as np
import shapely.geometry
from shapely.geometry import Point, LineString
import joblib
import os
import requests
from realtime_inference_utils import prepare_live_features, get_live_weather, get_temporal_features
# --- Connectors ---
MODEL_PATH = "accident_model.pkl"
def calculate_curvature(geom):
"""
Calculates sinuosity: Curve Length / Euclidean Distance.
"""
if not isinstance(geom, LineString):
return 1.0
length = geom.length
if length == 0: return 1.0
start = Point(geom.coords[0])
end = Point(geom.coords[-1])
euclidean = start.distance(end)
if euclidean == 0: return 1.0
return length / euclidean
def get_maxspeed(edge_data):
"""Parses maxspeed tag to float with fallback based on highway type."""
speed_val = edge_data.get('maxspeed')
if isinstance(speed_val, list):
speed_val = speed_val[0]
try:
if not pd.isna(speed_val):
return float(str(speed_val).split()[0])
except:
pass
highway = edge_data.get('highway', 'road')
if isinstance(highway, list):
highway = highway[0]
highway = str(highway).lower()
# Realistic speeds in town areas
speed_map = {
'motorway': 80.0,
'trunk': 60.0,
'primary': 50.0,
'secondary': 40.0,
'tertiary': 35.0,
'unclassified': 30.0,
'residential': 20.0,
'living_street': 15.0,
'service': 15.0,
'pedestrian': 10.0,
'track': 15.0,
'path': 10.0
}
return speed_map.get(highway, 30.0)
def get_road_name(edge_data):
"""
Provides a descriptive name for a road segment using various tags.
Order of preference: name -> ref -> highway
"""
# 1. Try Name
name = edge_data.get('name')
if name:
if isinstance(name, list):
name = name[0]
return str(name)
# 2. Try Ref (e.g., NH-66)
ref = edge_data.get('ref')
if ref:
if isinstance(ref, list):
ref = ref[0]
return f"Road {ref}"
# 3. Fallback to Highway type
hway = edge_data.get('highway', 'road')
if isinstance(hway, list):
hway = hway[0]
hway_str = str(hway).lower()
osm_map = {
'unclassified': 'Minor Road',
'residential': 'Residential Street',
'tertiary': 'Local Road',
'secondary': 'Connecting Road',
'primary': 'Main Road',
'trunk': 'Highway',
'living_street': 'Shared Street',
'pedestrian': 'Pedestrian Zone',
'track': 'Local Track',
'path': 'Path'
}
if hway_str in osm_map:
return osm_map[hway_str]
# Beautify unknown highway types
hway = hway_str.replace('_', ' ').capitalize()
if not hway.lower().endswith('road') and not hway.lower().endswith('way'):
hway = f"{hway} Road"
return hway
def get_current_location():
"""
Tries to get approximate location via IP.
Fallback: Kunnamangalam, Kerala.
"""
print(" Fetching approximate location via IP...")
try:
r = requests.get('https://ipinfo.io/json')
if r.status_code == 200:
data = r.json()
if 'loc' in data:
lat, lon = map(float, data['loc'].split(','))
print(f" Detected Location: {data.get('city', 'Unknown')} ({lat}, {lon})")
return lat, lon
except Exception as e:
print(f" Location fetch failed: {e}")
print(" Using Default Fallback: Kunnamangalam")
# Kunnamangalam Coordinates
return 11.3067, 75.8767
def get_coordinates(place_name):
"""
Geocodes a place name to (lat, lon).
Handling 'Current Location' specially.
"""
place_name = place_name.strip()
if not place_name or place_name.lower() in ["current location", "here", "me"]:
return get_current_location() # For analysis logic
print(f" Geocoding '{place_name}'...")
try:
# Use an explicit web request to Nominatim instead of OSMnx to save memory
headers = {'User-Agent': 'RiskAwareNavigation/1.0'}
url = f"https://nominatim.openstreetmap.org/search?q={place_name}&format=json&limit=1"
res = requests.get(url, headers=headers).json()
if res:
lat = float(res[0]['lat'])
lon = float(res[0]['lon'])
return lat, lon
else:
return None
except Exception as e:
print(f" Error finding '{place_name}': {e}")
return None
def main():
print("1. Loading Real-Time Nervous System...")
if not os.path.exists(MODEL_PATH):
print("Error: Trained model not found.")
return
model = joblib.load(MODEL_PATH)
weather = get_live_weather()
time_ctx = get_temporal_features()
print(f" Context: {weather}, Night={time_ctx['is_night']}, Hour={time_ctx['hour_of_day']}")
# --- User Interaction ---
print("\n--- Plan Your Journey ---")
origin_input = input("Enter Origin (Press Enter for Current Location): ").strip()
dest_input = input("Enter Destination: ").strip()
if not dest_input:
print("Error: Destination is required.")
return
# 1. Resolve Coordinates for Analysis
orig_coords = get_coordinates(origin_input)
dest_coords = get_coordinates(dest_input)
if not orig_coords or not dest_coords:
print("Could not resolve locations. Try explicit names (e.g. 'Calicut Beach').")
return
orig_lat, orig_lon = orig_coords
dest_lat, dest_lon = dest_coords
def analyze_route(origin_input, dest_input, model=None, G=None, user_location=None):
"""
Analyzes the route between origin and destination.
Returns a dictionary with route details, risks, and map link, or None if failed.
"""
if not model:
if os.path.exists(MODEL_PATH):
model = joblib.load(MODEL_PATH)
else:
return {"error": "Model not found."}
# 1. Resolve Coordinates
# First, normalize the origin input to catch variations of "current location" safely
norm_origin = str(origin_input).strip().lower() if origin_input else ""
# If they passed accurate GPS coordinates and didn't type a specific city name, USE IT.
if (not norm_origin or norm_origin in ["current location", "here", "me"]) and user_location is not None:
print(f" Using high-accuracy browser GPS: {user_location}")
orig_lat, orig_lon = user_location
# Use destination resolution as usual
dest_coords = get_coordinates(dest_input)
if not dest_coords:
return {"error": "Could not resolve destination."}
dest_lat, dest_lon = dest_coords
else:
# Standard Resolution
orig_coords = get_coordinates(origin_input)
dest_coords = get_coordinates(dest_input)
if not orig_coords or not dest_coords:
return {"error": "Could not resolve locations."}
orig_lat, orig_lon = orig_coords
dest_lat, dest_lon = dest_coords
# Region Validation (Exact Graph Bounds for 'Kozhikode, Kerala, India')
# minLat: 11.125, maxLat: 11.805, minLon: 75.535, maxLon: 76.120
if not (11.125 <= dest_lat <= 11.805 and 75.535 <= dest_lon <= 76.120):
return {"error": "Destination is outside the supported Kozhikode region. Please select a local destination."}
# 2. Get Graph
if G is None:
try:
# optimize: cache this in the calling app
G = load_graph_from_csv("kozhikode_roads.csv")
except Exception as e:
return {"error": f"Graph load failed: {e}"}
# 3. Enrich Graph (if not already enriched)
# Check if a random edge has 'curvature_score' to see if enriched
first_edge = list(G.edges(data=True))[0][2]
if 'curvature_score' not in first_edge:
node_degrees = dict(G.degree())
for u, v, k, data in G.edges(keys=True, data=True):
if 'geometry' in data:
data['curvature_score'] = calculate_curvature(data['geometry'])
else:
data['curvature_score'] = 1.0
data['maxspeed_clean'] = get_maxspeed(data)
is_junc = 1 if (node_degrees[u] > 2 or node_degrees[v] > 2) else 0
data['is_junction'] = is_junc
# Travel Time
length_m = data.get('length', 10)
speed_mps = max(data['maxspeed_clean'], 10.0) / 3.6
data['travel_time'] = length_m / speed_mps
# 4. Find Path
orig_node = nearest_nodes(G, (orig_lon, orig_lat))
dest_node = nearest_nodes(G, (dest_lon, dest_lat))
try:
route = nx.shortest_path(G, orig_node, dest_node, weight='length')
except nx.NetworkXNoPath:
return {"error": "No route found."}
# 5. Audit Risks
weather = get_live_weather()
time_ctx = get_temporal_features()
segment_risks = []
route_coords = []
for i in range(len(route)):
node_data = G.nodes[route[i]]
route_coords.append((node_data['y'], node_data['x']))
if i < len(route) - 1:
u = route[i]
v = route[i+1]
edge_data = G.get_edge_data(u, v)[0]
features = {
'curvature_score': edge_data.get('curvature_score', 1.0),
'maxspeed': edge_data.get('maxspeed_clean', 40.0),
'is_junction': edge_data.get('is_junction', 0)
}
input_df = prepare_live_features(features, weather, time_ctx)
prob = model.predict_proba(input_df)[0][1]
name = get_road_name(edge_data)
node_data_v = G.nodes[v]
segment_risks.append({
'lat': node_data_v['y'],
'lon': node_data_v['x'],
'prob': prob,
'features': features,
'order': i,
'name': name
})
# Top 5 Risks
segment_risks.sort(key=lambda x: x['prob'], reverse=True)
top_5 = segment_risks[:5]
top_5.sort(key=lambda x: x['order'])
# Overall Route Risk
if segment_risks:
avg_prob = sum(r['prob'] for r in segment_risks) / len(segment_risks)
if avg_prob > 0.6:
overall_risk = "High"
elif avg_prob > 0.3:
overall_risk = "Medium"
else:
overall_risk = "Low"
else:
overall_risk = "Unknown"
avg_prob = 0.0
# URL Construction
# Use resolved coordinates for Origin to match analysis exactly
url_origin = f"{orig_lat},{orig_lon}"
url_dest = f"{dest_lat},{dest_lon}"
waypoints = [f"{risk['lat']},{risk['lon']}" for risk in top_5]
wp_str = "%7C".join(waypoints)
maps_url = f"https://www.google.com/maps/dir/?api=1&origin={url_origin}&destination={url_dest}&waypoints={wp_str}"
return {
"route_nodes": route,
"route_coords": route_coords,
"top_5_risks": top_5,
"overall_risk": overall_risk,
"avg_risk_prob": float(avg_prob),
"maps_url": maps_url,
"weather": weather,
"time_ctx": time_ctx,
"G": G # Return graph in case it was created here
}
def haversine_distance(lat1, lon1, lat2, lon2):
"""
Calculates the great-circle distance between two points on the Earth surface.
Returns distance in meters.
"""
import math
R = 6371000 # Earth radius in meters
phi1 = math.radians(lat1)
phi2 = math.radians(lat2)
delta_phi = math.radians(lat2 - lat1)
delta_lambda = math.radians(lon2 - lon1)
a = math.sin(delta_phi / 2)**2 + math.cos(phi1) * math.cos(phi2) * math.sin(delta_lambda / 2)**2
c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
return R * c
def main():
print("1. Loading Real-Time Nervous System...")
# Loading model strictly for CLI main check; analyze_route handles it too but efficient to pass it if we had it.
if not os.path.exists(MODEL_PATH):
print("Error: Trained model not found.")
return
model = joblib.load(MODEL_PATH)
# Just printing context for CLI user
weather = get_live_weather()
time_ctx = get_temporal_features()
print(f" Context: {weather}, Night={time_ctx['is_night']}, Hour={time_ctx['hour_of_day']}")
# --- User Interaction ---
print("\n--- Plan Your Journey ---")
origin_input = input("Enter Origin (Press Enter for Current Location): ").strip()
dest_input = input("Enter Destination: ").strip()
if not dest_input:
print("Error: Destination is required.")
return
print("\n2. Analyzing Route...")
result = analyze_route(origin_input, dest_input, model=model)
if "error" in result:
print(f"Error: {result['error']}")
return
print(f" Route found with {len(result['route_nodes'])-1} segments.")
print("\n" + "="*40)
print("TOP 5 MOST DANGEROUS POINTS ON ROUTE")
print("="*40)
for i, risk in enumerate(result['top_5_risks']):
risk_level = 'High' if risk['prob']>0.7 else 'Moderate' if risk['prob']>0.4 else 'Low'
print(f"{i+1}. Risk Level: {risk_level} | Curv: {risk['features']['curvature_score']:.2f}")
print("\n" + "="*40)
print("NAVIGATION LINK (Safe Route)")
print("="*40)
print(result['maps_url'])
print("="*40)
if __name__ == "__main__":
main()