01
Rows, not places
Each call is a row with a point. Nothing says where one busy area ends and the next begins.
GEOSPATIAL ML · FULL STACK
I clustered Metro Nashville's public 911 call data from 2024 into 131 hotspots with DBSCAN, weighted each call by how serious it is, and put the result on a map with location alerts. Nashville gets a lot of visitors, and I wanted them, and locals, to have a simple way to see which areas draw the most calls.

THE GAP
Metro Nashville publishes its 911 calls for service as open data. The 2024 export I used has 369,380 calls, from January 1 to November 14. Each has a call type and a time, and 39% have a location, rounded to about 100 meters.
That's a table, not an answer. To help someone deciding where to walk, it has to become areas with edges, ranked by how serious the calls inside them are.
01
Each call is a row with a point. Nothing says where one busy area ends and the next begins.
02
A shooting and a traffic violation are both one row. Counting rows treats them the same.
03
Nobody knows in advance how many hotspots a city has, so the method has to find that number itself.
WHAT IT DOES
A Python script turns the calls into hotspots once and saves them as JSON. A FastAPI backend serves them, and a React and TypeScript front end draws them on a Leaflet map with filters, location alerts and a route check. There's also a simpler Streamlit version for a quick look.
Each call scores 3, 2 or 1 by type, so shootings and robberies outweigh traffic violations and business checks.
Click a circle to see its severity, call count and a generated note naming its most common call types.
With browser GPS on, the map warns you inside a hotspot's radius plus 50 meters. The check stays in your browser.
Enter two coordinates and it scores the straight line between them, plus three offset variants, against the hotspot circles.
HOW IT WORKS
Every hotspot is a circle: red for High, orange for Medium, green for Low.
Hide the severity levels you don't need from the filter panel.
Click a circle for its call count, severity and a short note on why.
Turn on GPS or type coordinates; a warning appears when you're inside a hotspot.
Enter start and end coordinates to score the straight line and three variants.
SYSTEM DESIGN
The heavy work happens once, offline: a script weights the calls, clusters them and writes hotspots.json. The API serves that file, and the map draws it.
Data
Metro Nashville 911 export
369,380 calls, Jan to mid-Nov 2024
Analysis
Risk weights
3, 2 or 1 per call type
DBSCAN
30 calls within 400 m seed a cluster
Hotspot summary
center, radius, counts, severity, plain note
Storage
hotspots.json
131 hotspots, sorted by total risk
API
FastAPI
hotspot, filter, route and trend endpoints
Service modules
route scoring, distances, trends, preferences
Clients
React + Leaflet
map, filters, GPS alerts, route check
Streamlit + Folium
simpler version of the same map
RESULTS
145,226
2024 911 calls with usable coordinates, of 369,380
131
DBSCAN hotspots at a 400 m radius and 30 calls
87
of those 131 rated High by the severity rule
107,836
calls in one core cluster, 18.5 km in radius
That core cluster holds 78% of every call that landed in a hotspot.
WHAT IT TAUGHT ME
One radius can't fit a whole city. At 400 meters, DBSCAN chained the dense middle of Nashville into a single cluster of 107,836 calls, so the React map draws one High circle 18.5 km in radius over the center of town; the Streamlit version just hides it. The data has limits too: only 39% of calls have a location, it's rounded to about 100 meters, and a 911 call isn't a crime. The most common one is a general request for an officer. And the route check is a prototype: it scores straight lines, not streets.
BUILT WITH
Like what you see?
Thanks for reading. There's more where this came from.