File size: 7,332 Bytes
b553871 05062ac b553871 |
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 |
import gradio as gr
import geocoder
import pandas as pd
import requests
import urllib.parse
from typing import Dict, Any
from datetime import datetime
import math
from typing import Tuple, List, Optional
import json
class CrimeData:
def __init__(self, incident_type: str, date_time: datetime, location: Tuple[float, float],
address: str, narrative: str = None):
self.incident_type = incident_type
self.date_time = date_time
self.location = location
self.address = address
self.narrative = narrative
def haversine_distance(coord1: Tuple[float, float], coord2: Tuple[float, float]) -> float:
"""Calculate the distance between two coordinates in kilometers."""
R = 6371.0
lat1, lon1 = math.radians(coord1[0]), math.radians(coord1[1])
lat2, lon2 = math.radians(coord2[0]), math.radians(coord2[1])
dlat = lat2 - lat1
dlon = lon2 - lon1
a = math.sin(dlat/2)**2 + math.cos(lat1) * math.cos(lat2) * math.sin(dlon/2)**2
c = 2 * math.asin(math.sqrt(a))
return R * c
def geocode_address(address: str, city: str = "Kingston", province: str = "ON") -> Optional[Tuple[float, float]]:
"""Convert address to latitude/longitude coordinates."""
full_address = f"{address}, {city}, {province}, Canada"
location = geocoder.osm(full_address, headers={
'User-Agent': 'CrimeLookupApp/1.0 ([email protected])'
})
if location.ok:
return (location.lat, location.lng)
# Fallback: Direct request to Nominatim API
import requests
import urllib.parse
url = f"https://nominatim.openstreetmap.org/search"
params = {
'q': full_address,
'format': 'jsonv2',
'addressdetails': 1,
'limit': 1
}
headers = {
'User-Agent': 'CrimeLookupApp/1.0 ([email protected])'
}
try:
response = requests.get(url, params=params, headers=headers)
response.raise_for_status()
results = response.json()
if results:
return (float(results[0]['lat']), float(results[0]['lon']))
except Exception as e:
print(f"Geocoding error: {e}")
return None
def load_crime_data() -> List[CrimeData]:
"""Load crime data from the police incidents API."""
url = "https://ce-portal-service.commandcentral.com/api/v1.0/public/incidents"
# Define the request payload with the Kingston area boundaries
payload = {
"limit": 2000,
"offset": 0,
"geoJson": {
"type": "Polygon",
"coordinates": [[
[-76.5167293, 44.2255476],
[-76.5167293, 44.2435476],
[-76.4987293, 44.2435476],
[-76.4987293, 44.2255476],
[-76.5167293, 44.2255476]
]]
},
"projection": False,
"propertyMap": {
"pageSize": "2000",
"zoomLevel": "15",
"latitude": "44.2345476",
"longitude": "-76.5077293",
"relativeDate": "custom",
"fromDate": "2024-01-11T14:00:00.000Z",
"toDate": "2025-01-11T13:00:00.000Z",
"days": "",
"startHour": "0",
"endHour": "24",
"parentIncidentTypeIds": "149,150,148,8,97,104,165,98,100,179,178,180,101,99,103,163,168,166,12,161,14,16,15,160,121,162,164,167,173,169,170,172,171,151",
"agencyIds": "407,1358,ottawapolice.ca,kpf.ca"
}
}
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Origin': 'https://www.cityprotect.com',
'Referer': 'https://www.cityprotect.com/'
}
try:
response = requests.post(url, json=payload, headers=headers)
response.raise_for_status()
data = response.json()
crimes = []
if 'result' in data and 'list' in data['result'] and 'incidents' in data['result']['list']:
for incident in data['result']['list']['incidents']:
crimes.append(CrimeData(
incident_type=incident['incidentType'],
date_time=datetime.fromisoformat(incident['date'].rstrip('Z')),
location=(incident['location']['coordinates'][1], incident['location']['coordinates'][0]),
address=incident['blockizedAddress'],
narrative=incident.get('narrative', '')
))
return crimes
except Exception as e:
print(f"Error loading crime data: {e}")
return []
def filter_crimes(center: Tuple[float, float], radius_km: float,
crimes: List[CrimeData], start_date: datetime, end_date: datetime) -> List[CrimeData]:
"""Filter crimes by distance and date range."""
return [crime for crime in crimes
if (haversine_distance(center, crime.location) <= radius_km and
start_date <= crime.date_time <= end_date)]
def format_crime_report(crimes: List[CrimeData]) -> pd.DataFrame:
"""Format crimes into a DataFrame for display."""
data = []
for crime in crimes:
data.append({
'Date': crime.date_time.strftime('%Y-%m-%d'),
'Time': crime.date_time.strftime('%H:%M'),
'Type': crime.incident_type,
'Location': crime.address,
})
return pd.DataFrame(data)
def lookup_crimes(address: str, radius: float = 1.0,
start_date: str = None, end_date: str = None) -> pd.DataFrame:
"""Main function to lookup crimes near an address within a date range."""
# Validate and parse dates
try:
start = datetime.strptime(start_date, '%Y-%m-%d') if start_date else datetime(2023, 1, 1)
end = datetime.strptime(end_date, '%Y-%m-%d') if end_date else datetime.now()
except ValueError:
return pd.DataFrame({'Error': ['Invalid date format. Please use YYYY-MM-DD']})
# Geocode the address
coords = geocode_address(address)
if not coords:
return pd.DataFrame({'Error': ['Address not found']})
# Load and filter crimes
all_crimes = load_crime_data()
filtered_crimes = filter_crimes(coords, radius, all_crimes, start, end)
# Format results
if not filtered_crimes:
return pd.DataFrame({'Message': [f'No crimes found in specified radius between {start_date} and {end_date}']})
return format_crime_report(filtered_crimes)
# Create Gradio interface
iface = gr.Interface(
fn=lookup_crimes,
inputs=[
gr.Textbox(label="Address (e.g., '503 Victoria St, Kingston')"),
gr.Slider(minimum=0.1, maximum=5.0, value=1.0, label="Radius (km)"),
gr.Textbox(label="Start Date (YYYY-MM-DD)", value="2024-02-01"),
gr.Textbox(label="End Date (YYYY-MM-DD)", value=datetime.now().strftime('%Y-%m-%d'))
],
outputs=gr.Dataframe(),
title="Neighborhood Crime Lookup",
description="Enter an address and date range to see crimes in the area. Add street name and number only - city is assumed to be Kingston, ON.",
examples=[
["503 Victoria St", 1.0, "2024-01-01", "2024-01-10"],
["417 Princess St", 0.5, "2024-06-01", "2024-12-31"]
]
)
# Launch the app
if __name__ == "__main__":
iface.launch() |