An interactive web dashboard for exploring crime incidents and personal safety routing in Philadelphia. Built with vanilla JavaScript, MapLibre GL JS, and Chart.js.
- Interactive map with police district and census tract choropleths
- Buffer analysis with customizable radius (400m - 3.2km)
- Time-series charts comparing buffer vs citywide trends
- 7x24 heatmap showing temporal crime patterns
- Per-capita rates using 2023 ACS population data
- Live route demo with safety gradient coloring and optional safer alternative overlay
- Route rating modal (overall stars, tags, optional notes + segment overrides)
- Segment popup “community feedback” card with top issues and quick actions
- Views: Live Route, My Routes (history list), Community (radius + high-concern list), Insights panel (trend, tags, heatmap)
- Community taps: “Agree” / “Feels safer” votes with session throttling
Status: Diary frontend (M1/M3) is complete for demo data; ready for backend integration and real submissions.
- Node.js 20+ (tested on v22.18.0)
- npm 10+
# Install dependencies
npm install
# Start development server (Crime + Diary)
npm run dev
# Open http://localhost:5173/?mode=diary (or /diary-demo.html) for the Route Safety Diarynpm run build # Build to dist/
npm run preview # Preview production build at http://localhost:4173What it is: A Diary mode that lets users explore safer routes in Philadelphia, rate trips, and see community perceptions of street safety. Demo data is generated from the Philly street network; submissions are currently local-only.
Major pieces
- UI views: Live Route (default), My Routes (history list), Community (area focus + concern list), right-side Insights card, segment popups, and the route rating modal.
- Map & style: MapLibre GL with MapTiler Positron light style when
VITE_MAPTILER_API_KEYis set; muted OSM raster fallback with a gray network grid beneath safety overlays. - Routing + data: Road network fetched from Overpass, segmented, then demo routes built via Dijkstra/SegmentGraph; safety colors use time-decayed, shrinkage-adjusted scores.
- State & modules: Central store, modular UI panels under
src/routes_diary/, shared IDs (map_ids.js), normalization helpers (data_normalization.js), and label helpers (labels.js).
Run the Diary
npm install
# Optional quickstart scripts may exist (diary:qs:*); otherwise:
npm run dev
# then open http://localhost:5173/?mode=diary or /diary-demo.htmlConfigure MapTiler
Create .env.local with:
VITE_MAPTILER_API_KEY=YOUR_KEY
Without a key, the app falls back to muted OSM with the network grid still visible.
Regenerate demo data
npm run data:fetch:streets # Fetch OSM streets (or STREETS_PHL_URL if set)
npm run data:segment:streets # Segment streets and build the graph
npm run data:gen # Generate demo routes + demo segment set
npm run data:check # Validate demo GeoJSONsrc/routes_diary/— Diary orchestrator (index.js), modular panels (ui_*_panel.js), rating modal (form_submit.js), shared helpers (map_ids.js,data_normalization.js,labels.js).src/map/— MapLibre layers: segments layer + popups, routing overlays, network grid.src/charts/diary_insights.js— Insights rendering.scripts/— Road network ETL (fetch, segment, demo data generation).data/— Demo GeoJSON outputs (segments_phl.demo.geojson,routes_phl.demo.geojson,segments_phl.network.geojson).
| Technology | Version | Purpose |
|---|---|---|
| MapLibre GL JS | ^4.5.0 | Vector map rendering |
| Chart.js | ^4.4.0 | Data visualizations |
| Vite | ^5.0.0 | Build tooling & dev server |
| Ajv | ^8.12.0 | JSON schema validation |
No framework: Vanilla JavaScript (no React/Vue/Angular).
The diary feature is controlled by an environment variable:
# .env.local
VITE_FEATURE_DIARY=1To disable the diary, set VITE_FEATURE_DIARY=0 or remove the variable.
All data is fetched from public APIs at runtime:
| Source | API Endpoint | Update Frequency |
|---|---|---|
| Crime incidents | CARTO SQL API | Daily |
| Police districts | PhillyGIS ArcGIS | Static |
| Census tracts | Esri ArcGIS | Static |
| ACS 2023 demographics | Census Bureau API | Annual |
Note: Crime data is rounded to the hundred block for privacy. Incident locations are approximate.
- Clustering: Automatically enabled when >20,000 points would be rendered
- Bbox filtering: All crime queries are constrained to the current map viewport
- Caching: Boundary GeoJSON files cached in
public/data/when available - Rate limiting: API requests throttled to avoid overloading city servers
- Control Specifications - Detailed state model for all UI controls
- Known Issues - Current blockers and workarounds
- M2 Specifications - Five detailed spec documents for Agent-I implementation
- DIARY_SPEC_M2.md - Visual encoding, UI copy, accessibility
- CHARTS_SPEC_M2.md - Three chart specifications with JSON schemas
- API_BACKEND_DIARY_M2.md - REST API contracts (future backend)
- SQL_SCHEMA_DIARY_M2.md - Postgres + PostGIS schema
- TEST_PLAN_M2.md - 60 testable acceptance criteria
- File Map - Codebase structure and module relationships
- Deployment Guide - Vite setup, build troubleshooting, hosting
- Diary Audit Checks - M1 verification checklist (all passed)
- Audit Logs - Timestamped audit reports with screenshots
We welcome contributions! Please see CONTRIBUTING.md for:
- Code style guidelines
- Branch naming conventions
- Pull request process
- Testing requirements
Key rules:
- All features must be feature-flagged (see
src/config.js) - Maintain vanilla JavaScript (no JSX, no frameworks)
- All map operations must use MapLibre GL APIs
- Performance: map refreshes <5s, routing <2s
npm run test:unit # Jest unit tests
npm run test:integration # Mocha + Supertest integration tests
npm run test:e2e # Playwright end-to-end tests
npm run test:perf # Artillery performance tests
npm test # Run all testsnpm run test:coverageTarget: 80% line coverage for all source files.
- Start dev server:
npm run dev - Open feature flag: Set
VITE_FEATURE_DIARY=1in.env.local - Follow test scenarios in TEST_PLAN_M2.md
npm run build
# Upload dist/ to GitHub PagesImportant: Configure base in vite.config.js if deploying to a subdirectory:
export default {
base: '/engagement-project/', // Match your repo name
}Serve the dist/ folder with any static file server:
npm install -g serve
serve -s dist -p 8080- Live route demo with safety gradients + alt route overlay
- Rating modal (overall + tags + optional segment overrides)
- Segment popup community card
- My Routes / Community views + Insights panel
- Wire Diary to real backend submissions + persistence
- Extend My Routes / Community with live data
- Promote demo routes to user-submitted routes
- Expand analytics (interactive insights, filterable trends)
Error: "Cannot resolve module 'maplibre-gl'"
- Cause: Missing dependencies
- Fix:
npm install
Error: "HTML proxy error"
- Cause:
index.htmlnot at project root - Fix: Ensure
index.htmlis in the root directory (notpublic/)
Error: "Network request failed"
- Cause: CARTO API or ArcGIS endpoints unreachable
- Fix: Check internet connection, verify API URLs are correct
Error: "Feature flag not working"
- Cause:
.env.localnot read by Vite - Fix: Restart dev server (
npm run dev) after editing.env.local
Map rendering slow:
- Reduce cluster radius (see
src/map/points.js) - Limit time window (use "Last 3mo" instead of "Last 12mo")
Chart.js lagging:
- Enable dataset decimation in
src/charts/line_monthly.js - Reduce data points on mobile devices
This project is licensed under the MIT License. See LICENSE for details.
- Data providers: City of Philadelphia Open Data, US Census Bureau
- Mapping: OpenStreetMap contributors, MapLibre GL JS
- Charts: Chart.js community
- Inspiration: Safe Streets Philadelphia, Walk Score
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Email: your.email@example.com
Last Updated: 2025-12-xx Branch: chore/diary-docs-and-copy-polish Status: Route Safety Diary frontend milestone complete; ready for backend integration.
Happy mapping! 🗺️✨