-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapp.py
More file actions
176 lines (146 loc) · 6.14 KB
/
app.py
File metadata and controls
176 lines (146 loc) · 6.14 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
from flask import Flask, render_template, request, jsonify
import os
import googlemaps
from dotenv import load_dotenv
from haversine import haversine
from supabase import create_client, Client
app = Flask(__name__)
load_dotenv()
GOOGLE_MAPS_API_KEY = os.getenv('GOOGLE_MAPS_API_KEY')
GOOGLE_MAPS_BROWSER_API_KEY = os.getenv('GOOGLE_MAPS_BROWSER_API_KEY', GOOGLE_MAPS_API_KEY)
SUPABASE_URL = os.getenv('SUPABASE_URL')
SUPABASE_SERVICE_ROLE_KEY = os.getenv('SUPABASE_SERVICE_ROLE_KEY')
# Validate Google Maps API key
if not GOOGLE_MAPS_API_KEY:
raise ValueError('Missing GOOGLE_MAPS_API_KEY in .env file')
# Initialize Google Maps client
gmaps = googlemaps.Client(key=GOOGLE_MAPS_API_KEY)
# Validate Supabase environment variables
if not SUPABASE_URL or not SUPABASE_SERVICE_ROLE_KEY:
raise ValueError('Missing Supabase environment variables in .env file')
# Initialize Supabase client
supabase: Client = create_client(SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY)
# Helper function to fetch crime data from Supabase
def fetch_crimes(crime_type: str):
query = supabase.table('crimes').select('*')
if crime_type != 'All':
query = query.eq('crime_type', crime_type)
res = query.execute()
return res.data if res.data else []
# Fetch all crimes at startup for risk calculation (could be optimized with caching or pagination)
CRIMES = fetch_crimes('All')
# Allowed crime types for validation
ALLOWED_CRIME_TYPES = {
'All',
'Anti-social behaviour',
'Possession of weapons',
'Public order',
'Robbery',
'Theft from the person',
'Vehicle crime',
'Violence and sexual offences',
'Drugs',
}
# Test database connection
@app.route("/health/db", methods=["GET"])
def health_db():
try:
# very small query, just to validate auth + connectivity + table access
res = supabase.table("crimes").select("id", count="exact").limit(1).execute()
return jsonify({
"ok": True,
"count": res.count
}), 200
except Exception as e:
app.logger.exception("DB health check failed")
return jsonify({
"ok": False,
"error": str(e)
}), 500
#Render the html page
@app.route("/")
def map():
return render_template(
"index.html",
title="SafeWalk",
maps_api_key=GOOGLE_MAPS_BROWSER_API_KEY,
)
#Filter data based on crime type
@app.route('/crimedata', methods=['GET'])
def filter_crime_data():
try:
crime_type = request.args.get('crime_type')
#Validation
if not crime_type:
return jsonify({"error":"missing crime_type field"}),400
if crime_type not in ALLOWED_CRIME_TYPES:
return jsonify({"error":"invalid crime_type"}),400
crimes = fetch_crimes(crime_type)
result = [
{
"Month": c.get("month"),
"Latitude": c.get("latitude"),
"Longitude": c.get("longitude"),
"CrimeType": c.get("crime_type")
}
for c in crimes
]
return jsonify(result)
except Exception as e:
app.logger.exception("Failed to filter crime data")
return jsonify({"error":"Internal server error"}),500
# Safe route calculation
@app.route('/calculate-route', methods=['GET'])
def calculate_route():
try:
start = request.args.get('start')
end = request.args.get('end')
crime_to_avoid = request.args.get('crime_to_avoid')
#Validation
if not start or not end or not crime_to_avoid:
return jsonify({'error': 'Missing input parameters'}), 400
if crime_to_avoid not in ALLOWED_CRIME_TYPES or crime_to_avoid == 'All':
return jsonify({'error': 'Invalid crime_to_avoid value'}), 400
#Get the directions from Google Maps API
directions= gmaps.directions(start, end, mode='walking', alternatives=True)
#Directions is a list of routes
#Each route has legs (A list of dictionaries, where each dictionary represents a leg of the journey.
#which has steps (represents a segment of the journey.)
#Each step has start_location and end_location (latitude and longitude)
#We will calculate the risk score based on the distance between the start_location of each step and the crime location in calculate_risk_score function
#Debugging: Print the directions
# print(directions) - it's working
safest_route = None
lowest_risk = float('inf')
for route in directions:
risk_score = calculate_risk_score(route, crime_to_avoid)
if risk_score < lowest_risk:
lowest_risk = risk_score
safest_route = route
# print(f"Safest Route: {safest_route}, Lowest Risk: {lowest_risk}")
return jsonify({'route': safest_route, 'risk': lowest_risk})
except Exception as e:
app.logger.exception("Failed to calculate route")
return jsonify({'error': 'Unable to calculate route at this time'}), 500
def calculate_risk_score(route, crime_to_avoid):
""" Calculate the risk score for a given route based on the crime data
"""
risk_score = 0
for leg in route['legs']:
for step in leg['steps']:
lat_start = step['start_location']['lat']
lng_start = step['start_location']['lng']
print(f"Start Location: {lat_start}, {lng_start}")
for crime in [crime for crime in CRIMES if crime['crime_type'] == crime_to_avoid]:
crime_lat = crime['latitude']
crime_lng = crime['longitude']
distance = haversine((lat_start, lng_start), (crime_lat, crime_lng))
# print(f"Crime Location: {crime_lat}, {crime_lng}, Distance: {distance}") - Debugging
## haversine formula in python - distance in km
## We consider the risk score as 1 if the distance is less than 100 meters
if distance < 0.1:
risk_score += 1
print(f"The Risk of coming across {crime_to_avoid} for the route: {risk_score}")
return risk_score
if __name__ == "__main__":
app.run(debug=os.getenv('FLASK_DEBUG', '0') == '1')