An interactive, single-file web map for visualizing traffic crash data. Built with Mapbox GL JS and PapaParse. No build step required — just a browser and a web server.
- Interactive map with crash point markers colored by severity
- Sidebar filters: severity, involvement flags, date range, time of day, day of week
- Click any crash marker to open a popup with full crash details
- Time-of-day bar chart and day-of-week heatmap, both clickable as filters
- All filters are independent and composable
- Responsive layout (mobile-friendly)
Sign up at mapbox.com — the free tier covers up to 50,000 map loads per month.
In your Mapbox account, restrict the token to your deployment domain to prevent unauthorized use.
Edit config.js:
window.ENV = {
MAPBOX_TOKEN: 'pk.eyJ1...' // add your own Mapbox token
};Open index.html and find the CONFIG block near the top of the <script> section. Update the values for your deployment:
const CONFIG = {
mapboxToken: (window.ENV && window.ENV.MAPBOX_TOKEN) || 'YOUR_MAPBOX_TOKEN_HERE',
// URL to your publicly accessible CSV file
csvUrl: 'https://storage.googleapis.com/your-bucket/your-data.csv',
// Map your CSV column names to the expected internal names
columns: {
lat: 'lat',
lng: 'lng',
severity: 'severity',
date: 'date',
numFatalities: 'NumFatalities',
numInjured: 'NumInjured',
crashType: 'VehCrashType',
vehicleFactors: 'vehicle_factors',
crashNum: 'CrashNum',
hitAndRun: 'HitandRun',
alcohol: 'AlcoholInvolved',
pedestrian: 'PedestrianInvolved',
motorcycle: 'MotorcycleInvolved',
extras: [] // optional extra fields to show in the popup
},
// Map raw severity values from your CSV to internal categories
severityMap: {
'Fatal': 'fatal',
'Serious Injury': 'serious',
},
// Starting map view
initialCenter: [-115.1398, 36.1699],
initialZoom: 10,
// Hard pan/zoom limits — set to null to allow unrestricted panning
maxBounds: [
[-116.790925, 34.277257], // [minLon, minLat]
[-113.148819, 37.578262], // [maxLon, maxLat]
],
// UI text — update for your region
ui: {
eyebrow: 'Clark County, NV',
title: 'Crash Data Viewer',
attribution: 'Data from <a href="proxy.php?url=https%3A%2F%2Fgithub.com.%2F...">Source</a> — Visualization by Your Name',
fatalLabel: 'Fatalities',
seriousLabel: 'Serious Injury',
},
};The browser fetches the CSV directly, so it must be publicly hosted somewhere. I have used Google Cloud Storage, but an AWS S3 bucket would also work.
You will likely need to configure CORS headers for your bucket
The file must be served over HTTP (not opened as file://). Any of these options work to spin it up locally:
# Python (no install needed)
python3 -m http.server 8080
# Node
npx serve .
# VS Code
Install the "Live Server" extension, then right-click index.html → Open with Live ServerThen open http://localhost:8080.
All column names are configurable in CONFIG.columns. The table below shows the default expected names and their definitions.
| Column | Default name | Type | Description |
|---|---|---|---|
| Latitude | lat |
Float | Decimal latitude of the crash location (WGS84) |
| Longitude | lng |
Float | Decimal longitude of the crash location (WGS84) |
| Severity | severity |
String | Crash severity category. Must match a key in CONFIG.severityMap. Default values: Fatal, Serious Injury |
| Date | date |
Datetime | Date and time of the crash. Any format parseable by JavaScript's Date() constructor is accepted, e.g. 2023-06-15 14:32:00 |
| Column | Default name | Type | Description |
|---|---|---|---|
| Crash number | CrashNum |
String | Unique identifier for the crash record |
| Crash type | VehCrashType |
String | Collision type description, e.g. Rear End, Angle, Head On |
| Fatalities | NumFatalities |
Integer | Number of people killed in the crash. Used to compute the total fatalities count in the sidebar |
| Injuries | NumInjured |
Integer | Number of people injured in the crash |
| Vehicle factors | vehicle_factors |
String | Semicolon-separated list of contributing factors, e.g. Speeding;Failure to Yield. Rendered as a bulleted list in the popup. Leave blank if none |
These columns drive the Involvement filter buttons in the sidebar. Each should contain True or False (case-insensitive).
| Column | Default name | Description |
|---|---|---|
| Alcohol involved | AlcoholInvolved |
Whether alcohol was a contributing factor |
| Pedestrian involved | PedestrianInvolved |
Whether a pedestrian was involved |
| Motorcycle involved | MotorcycleInvolved |
Whether a motorcycle was involved |
| Hit and run | HitandRun |
Whether the crash was a hit-and-run |
You can add any number of additional columns to display in the popup by adding entries to CONFIG.columns.extras:
extras: [
{ label: 'Road Name', col: 'road_name' },
{ label: 'Weather', col: 'weather_condition' },
]To deploy this viewer for a different area:
- Update
CONFIG.csvUrlto point to your hosted crash data - Update
CONFIG.columnsto match your CSV headers (or just match the schema provided) - Update
CONFIG.severityMapto match your severity values - Update
CONFIG.initialCenterandCONFIG.initialZoomfor your geography - Update
CONFIG.maxBoundsto restrict panning to your region ([minLon, minLat], [maxLon, maxLat]) — or set tonullto remove the restriction - Update
CONFIG.uiwith your region name, title, and attribution text
Everything else (filters, charts, popups, map styling) works automatically from those values.
- Add a column entry to
CONFIG.columnspointing to your boolean CSV column - Add an entry to the
FLAGSarray inindex.html:
{ id: 'your-id', label: 'Your Label', faClass: 'fa-solid fa-icon-name', col: 'your-column-key' }faClass must be a FontAwesome 6 free icon class string.
- Add a mapping to
CONFIG.severityMap - Add an entry to the
SEVERITIESarray:
{ id: 'your-id', label: 'Your Label', color: '#hexcolor', radius: 6 }Edit the CSS custom properties at the top of the <style> block:
:root {
--accent: #2563eb; /* filter highlight, links */
--fatal: #FE343B; /* fatal crash marker color */
--serious: #FEBC40; /* serious injury marker color */
}All loaded from public CDNs — no npm install required.
| Library | Version | Purpose |
|---|---|---|
| Mapbox GL JS | 3.3.0 | Map rendering |
| PapaParse | 5.4.1 | CSV parsing |
| Font Awesome | 6.5.1 | Involvement filter icons |
I supplied the Jupyter notebook I used to process the Clark County data I have received from the Nevada Department of Transportation. This is going to be highly tailored to that particular dataset which may or may not be helpful if you are looking to use this on a different agency's data.
As a derivative of the City of Austin's Vision Zero Viewer, this project is also released into the public domain.