A terminal-based 911 incident viewer. Enter a US zip code and see live fire, EMS, and police incidents overlaid on an OpenStreetMap tile map — rendered entirely in your terminal using Unicode half-block characters and 24-bit color.
╔══════════════════════════════════════════════════════════╗
║ [map tiles rendered with ▀ half-block characters] ║ Incidents (12)
║ ║ ─────────────
║ ● Fire - Structure Fire ║ 🔴 Structure Fire
║ ║ 🔵 Theft Report
║ ◉ EMS - Medical Emergency ║ ⚪ Medical Emergency
║ ║ ...
║ ● Police - Traffic Stop ║
║ ║
╚══════════════════════════════════════════════════════════╝
Downtown Austin, TX | Zoom 14 | 12 incidents [?] help
For people who live near emergency activity and want to know what's happening around them — without a browser, ads, or a smartphone. TermCity gives you a calm, accurate picture of nearby emergency activity. No sensationalism, no surveillance.
- Live map — OpenStreetMap tiles rendered with half-block Unicode, pan and zoom freely
- Real-time incidents — Fire, EMS, and police incidents fetched from public APIs
- Pulsing markers — Animated incident dots with color-coded type (red/white/blue)
- Time-based fading — Older incidents fade automatically (new → recent → stale → old)
- Marker clustering — When zoomed out, nearby incidents group into numbered clusters
- Incident type filters — Toggle Fire (
f), Police (p), EMS (e) on/off - Incident sidebar — Scrollable list with address, type, and timestamp
- Detail overlay — Select any incident for responding unit details
- Recent zips — Quick access to last 5 locations on startup
- Disk tile cache — Tiles cached at
~/.cache/termcity/tiles/to reduce network load - Rate-limited — Respects OSM and Nominatim ToS out of the box
- Go 1.24+
- A terminal with 24-bit color and Unicode support (iTerm2, kitty, Alacritty, Windows Terminal, etc.)
git clone https://github.com/your-username/termcity
cd termcity
go run .Or build a binary:
go build -o termcity .
./termcityOn launch, enter any US zip code to center the map on that location.
A browser-based server with the same functionality (geocode by ZIP, OSM map, incidents, sidebar, detail modal). It uses the same data layer and is optimized for desktop and mobile. Includes SQLite-backed incident history (7-day retention).
go build -o termcity-web ./cmd/termcity-web
./termcity-web -port 8911 -foreground 10001Then open http://localhost:8911 in your browser.
| Flag | Default | Description |
|---|---|---|
-port N |
8911 | HTTP server port |
-foreground |
false | Run in foreground (logs to stdout) |
1. Install the binary:
go build -o termcity-web ./cmd/termcity-web
sudo cp termcity-web /usr/local/bin/
sudo chmod +x /usr/local/bin/termcity-web2. Create system user:
sudo useradd --system --home /var/lib/termcity --create-home --shell /usr/sbin/nologin termcity3. Create systemd service: /etc/systemd/system/termcity-web.service
[Unit]
Description=TermCity Web — 911 incident map viewer
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=termcity
Group=termcity
WorkingDirectory=/var/lib/termcity
Environment="TERMCITY_ZIP=10001"
Environment="TERMCITY_PORT=8911"
ExecStart=/usr/local/bin/termcity-web -port ${TERMCITY_PORT} ${TERMCITY_ZIP}
Restart=on-failure
RestartSec=10
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/termcity
PrivateTmp=true
[Install]
WantedBy=multi-user.target4. Enable and start:
sudo systemctl daemon-reload
sudo systemctl enable termcity-web
sudo systemctl start termcity-webManagement:
sudo systemctl status termcity-web # Check status
sudo journalctl -u termcity-web -f # View logs
sudo systemctl restart termcity-web # RestartChange the zip code by editing Environment="TERMCITY_ZIP=..." in the service file, then systemctl restart termcity-web.
./scripts/build-deb-web.sh
sudo dpkg -i termcity-web_1.0.0_amd64.deb
sudo systemctl enable --now termcity-web| Endpoint | Description |
|---|---|
GET / |
Web UI (HTML) |
GET /api/incidents |
Live incidents with metadata |
GET /api/history?days=N |
Historical incidents (1, 3, or 7 days) |
POST /api/history/clear |
Clear history database |
GET /api/status |
Server status and history counts |
| Key | Action |
|---|---|
+ / - |
Zoom in / out |
| Mouse wheel | Zoom in / out |
| Arrow keys | Pan map |
j / k |
Scroll incident list |
Tab |
Toggle sidebar focus |
Enter |
Show incident detail / select |
1–9 |
Jump to incident # and show detail |
f |
Toggle Fire incidents filter |
p |
Toggle Police incidents filter |
e |
Toggle EMS incidents filter |
m |
Cycle map style (OSM / Dark / Light) |
r |
Refresh incidents |
? |
Toggle help overlay |
Esc |
Return focus to map / close overlay |
q / Ctrl+C |
Quit |
| Source | Data | Notes |
|---|---|---|
| OpenStreetMap | Map tiles | Max 2 req/s per ToS |
| Nominatim | Zip → lat/lng geocoding | Max 1 req/s per ToS |
| PulsePoint | Fire & EMS incidents | Unofficial API |
| Socrata Open Data | Police incidents | City-specific datasets |
Incident data is sourced from public safety APIs and open government data portals. Coverage depends on your city's data sharing agreements.
Built in Go with the Bubbletea TUI framework (MVU pattern).
termcity/
├── main.go # TUI entry point
├── cmd/termcity-web/ # Web server: API (geocode, incidents) + embedded static frontend
│ ├── main.go
│ └── static/ # HTML, CSS, JS (Leaflet map, Bootstrap UI)
└── internal/
├── model/ # Bubbletea screen models (zip input → loading → map view)
├── data/ # API clients: geocoding, PulsePoint, Socrata, aggregator (shared by TUI and web)
├── tilemap/ # Tile fetching, disk cache, Web Mercator math, PNG → half-block render (TUI only)
└── ui/ # Lipgloss styles, sidebar, status bar (TUI only)
Map tiles (256×256 PNG) are decoded pixel-by-pixel and converted to terminal strings using ▀ (U+2580): each cell encodes two vertical pixels via foreground and background 24-bit colors. Incident data is fetched in parallel from all sources and merged before display.
go test ./internal/tilemap/... # Coordinate math unit tests
go vet ./... # Static analysis (must pass clean)TermCity displays public safety data from publicly available APIs. It is intended for informational awareness only — not for emergency response coordination. Always call 911 in an emergency.
PulsePoint integration uses an unofficial API and may break without notice. Police incident coverage varies by city.