Skip to content

arjohnsonn/austin-incident-map

Repository files navigation

Austin Fire & Incident Map πŸš’

Real-time emergency incident monitoring and visualization for Austin and Travis County, Texas. This application combines public incident data with live radio dispatch audio to provide comprehensive situational awareness for fire, medical, and traffic incidents.

✨ Features

Core Functionality

  • Real-time Incident Tracking - Live updates from Austin Open Data Portal and Broadcastify radio calls
  • Interactive Map - MapLibre GL-powered map with incident markers, clustering, and user location tracking
  • Live Dispatch Audio - Integration with Broadcastify Calls API for live radio communications
  • AI-Powered Transcript Processing - Deepgram transcribes audio, GPT-4o-mini extracts structured data (addresses, units, call types)
  • Smart Geocoding - Intelligent address parsing with multiple fallback providers to handle transcription errors
  • Concurrent Processing - Parallel batch transcription (20 requests at once) for ultra-fast incident loading

User Experience

  • Split-Panel Interface - Resizable sidebar with incident list and full-screen map view
  • Advanced Filtering - Filter by status, date range, agency, and search terms
  • Dark/Light Theme - Automatic theme detection with manual toggle
  • Audio Playback - Built-in player for dispatch call recordings
  • Incident Banners - Animated notifications for new incidents
  • Incident Replay - Re-inject past incidents to review and test alerts

Data Intelligence

  • Resolution Time Estimation - AI-powered predictions for incident resolution based on type and severity
  • Unit Tracking - Automatic extraction of responding units (Engine 5, Medic 12, etc.)
  • Channel Identification - Parse radio channels and tactical frequencies
  • Address Normalization - Handle common OCR/transcription errors in dispatch audio
  • Priority Sorting - High-priority incidents processed first for faster display

πŸ—οΈ Tech Stack

Frontend

Backend

Data Sources

πŸ“‹ Prerequisites

  • Node.js 20+ and pnpm 9+
  • Supabase account (free tier is sufficient)
  • Supabase CLI (npm install -g supabase)
  • Broadcastify API credentials (API Key ID, Secret, App ID)
  • Deepgram API key (free tier: 45,000 minutes/month)
  • OpenAI API key (for GPT-4o-mini transcript parsing)
  • Broadcastify account (username/password for authenticated API access)

πŸš€ Getting Started

1. Clone the Repository

git clone https://github.com/yourusername/austin-fire-map.git
cd austin-fire-map

2. Install Dependencies

pnpm install

3. Set Up Supabase

# Login to Supabase
supabase login

# Link to your project (or create new one)
supabase link --project-ref YOUR_PROJECT_REF

# Run database migrations
supabase db push

See SUPABASE_DEPLOYMENT.md for detailed setup instructions.

4. Configure Environment Variables

Create a .env.local file in the project root:

# Supabase Configuration
# Obtain from: https://supabase.com/dashboard/project/_/settings/api
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_anon_key
SUPABASE_SERVICE_ROLE_KEY=your_service_role_key

# Broadcastify API Credentials
# Obtain from: https://www.broadcastify.com/developer
BROADCASTIFY_API_KEY_ID=your_api_key_id
BROADCASTIFY_API_KEY_SECRET=your_api_key_secret
BROADCASTIFY_APP_ID=your_app_id

# Deepgram API Key (Free tier available)
# Obtain from: https://console.deepgram.com/
DEEPGRAM_API_KEY=your_deepgram_api_key

# OpenAI API Key (for transcript parsing)
# Obtain from: https://platform.openai.com/api-keys
OPENAI_API_KEY=sk-your-openai-api-key

# Optional: Geocoding API Keys (for fallback geocoding)
GEOCODING_API_KEY=your_mapsco_api_key
GEOCODING_API_KEY_2=your_mapsco_api_key_2

5. Run Development Server

pnpm run dev

Open http://localhost:3000 to view the application.

πŸ“¦ Available Scripts

Command Description
pnpm run dev Start development server with Turbopack hot reload
pnpm run build Create production build
pnpm run start Start production server (requires build first)
pnpm run lint Run ESLint code quality checks

🎯 Usage

Monitoring Live Incidents

  1. View Map - Incidents appear as colored markers (red=fire, blue=medical, yellow=traffic)
  2. Filter Incidents - Use sidebar filters for status, date range, or search
  3. Click Marker - View detailed incident information
  4. Play Audio - Click audio button to hear dispatch call recording
  5. Track Location - Enable location tracking to see your position

Settings Configuration

Click the settings icon to configure:

  • Data Source - Toggle between Austin Open Data and Broadcastify Calls
  • Auto-refresh Interval - Set polling frequency (15s to 5min)
  • Show Banner - Enable/disable new incident notifications
  • Active Groups - Select which radio channels to monitor

Incident Replay

For testing or review:

  1. Open incident details in sidebar
  2. Click "Replay" button
  3. Incident will be re-injected as new with banner animation

πŸ›οΈ Architecture

Data Flow

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚           Supabase Infrastructure               β”‚
β”‚                                                 β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚  pg_cron (runs every 60 seconds)         β”‚  β”‚
β”‚  β”‚              ↓                            β”‚  β”‚
β”‚  β”‚  Edge Function: process-calls            β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                      ↓                          β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                      β”‚
         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
         β”‚                         β”‚
         β–Ό                         β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Broadcastify    β”‚      β”‚ Next.js API     β”‚
β”‚ Calls API       β”‚      β”‚ Route (SSE)     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚                        β”‚
         β”œβ”€β–Ί JWT Auth             β”‚
         β”œβ”€β–Ί Fetch Live Calls     β”‚
         β”‚                        β”‚
         β–Ό                        β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Deepgram API    │◄────── Process Audio   β”‚
β”‚ (Transcription) β”‚      β”‚ & Transcripts   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚                        β”‚
         β”œβ”€β–Ί Parallel Processing  β”‚
         β”‚   (20 concurrent)      β”‚
         β”‚                        β”‚
         β–Ό                        β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ OpenAI API      │◄────── Parse Data:     β”‚
β”‚ (GPT-4o-mini)   β”‚      β”‚ - Call types    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β”‚ - Units         β”‚
         β”‚               β”‚ - Addresses     β”‚
         β”‚               β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Geocoding       β”‚
β”‚ (Multi-source)  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚
         β”œβ”€β–Ί Nominatim (primary)
         β”œβ”€β–Ί Maps.co (fallback)
         β”‚
         β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚           Supabase PostgreSQL                   β”‚
β”‚                                                 β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚  incidents table (with PostGIS)          β”‚  β”‚
β”‚  β”‚  worker_state table (track position)     β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                      ↓                          β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚  Realtime (WebSocket subscriptions)      β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                      β”‚
                      β–Ό
         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
         β”‚  Next.js Frontend      β”‚
         β”‚  - Map rendering       β”‚
         β”‚  - Filtering           β”‚
         β”‚  - Realtime updates    β”‚
         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Project Structure

austin-fire-map/
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ app/
β”‚   β”‚   β”œβ”€β”€ page.tsx                      # Main application page
β”‚   β”‚   β”œβ”€β”€ layout.tsx                    # Root layout with theme provider
β”‚   β”‚   └── api/
β”‚   β”‚       └── broadcastify/
β”‚   β”‚           └── live-calls/
β”‚   β”‚               └── route.ts          # SSE endpoint with parallel processing
β”‚   β”œβ”€β”€ components/
β”‚   β”‚   β”œβ”€β”€ IncidentMap.tsx               # MapLibre GL map component
β”‚   β”‚   β”œβ”€β”€ IncidentsList.tsx             # Sidebar with filtering
β”‚   β”‚   β”œβ”€β”€ CallBanner.tsx                # New incident notifications
β”‚   β”‚   β”œβ”€β”€ SettingsDialog.tsx            # Configuration UI
β”‚   β”‚   β”œβ”€β”€ LoadingScreen.tsx             # Initial load animation
β”‚   β”‚   β”œβ”€β”€ ThemeToggle.tsx               # Dark/light mode switcher
β”‚   β”‚   └── ui/                           # shadcn/ui components
β”‚   β”œβ”€β”€ lib/
β”‚   β”‚   β”œβ”€β”€ api.ts                        # useFireIncidents hook
β”‚   β”‚   β”œβ”€β”€ dispatch-parser.ts            # GPT-4o-mini parsing logic
β”‚   β”‚   β”œβ”€β”€ broadcastify-jwt.ts           # JWT token generation
β”‚   β”‚   β”œβ”€β”€ supabase.ts                   # Supabase client
β”‚   β”‚   β”œβ”€β”€ settings.ts                   # Settings persistence
β”‚   β”‚   └── utils.ts                      # Utility functions
β”‚   └── types/
β”‚       β”œβ”€β”€ incident.ts                   # Incident data types
β”‚       └── broadcastify.ts               # API response types
β”œβ”€β”€ supabase/
β”‚   β”œβ”€β”€ functions/
β”‚   β”‚   β”œβ”€β”€ process-calls/                # Edge Function for background processing
β”‚   β”‚   β”‚   β”œβ”€β”€ index.ts                  # Main function handler
β”‚   β”‚   β”‚   └── deno.json                 # Deno configuration
β”‚   β”‚   └── _shared/                      # Shared utilities
β”‚   β”‚       β”œβ”€β”€ broadcastify-jwt.ts       # JWT generation
β”‚   β”‚       └── dispatch-parser.ts        # Transcript parsing
β”‚   β”œβ”€β”€ migrations/
β”‚   β”‚   β”œβ”€β”€ 001_initial_schema.sql        # Tables, indexes, RLS policies
β”‚   β”‚   └── 002_setup_cron.sql            # pg_cron job configuration
β”‚   └── config.toml                       # Supabase project configuration
β”œβ”€β”€ public/                               # Static assets
β”œβ”€β”€ .env.local                            # Environment variables (create this)
β”œβ”€β”€ package.json                          # Dependencies
β”œβ”€β”€ tsconfig.json                         # TypeScript configuration
β”œβ”€β”€ tailwind.config.js                    # Tailwind CSS configuration
└── SUPABASE_DEPLOYMENT.md                # Deployment guide

πŸ”§ Configuration

Broadcastify Group IDs

Austin/Travis County radio channels (configured in settings):

  • Fire Dispatch A1: 2-3416
  • Fire Dispatch A2: 2-3417
  • Fire Dispatch A3: 2-3418
  • Medical Dispatch M1: 2-3419
  • Medical Dispatch M2: 2-3420

Transcription Processing

The application uses a two-stage AI pipeline:

  1. Deepgram Nova-2 - Converts MP3 audio to text

    • Real-time speed (~10x faster than Whisper)
    • Smart formatting and punctuation
    • Processes directly from URL (no download needed)
    • Batch processing: 20 concurrent requests
  2. OpenAI GPT-4o-mini - Extracts structured data from transcript

    • Call types with proper title case
    • Unit identification (Engine 5, Medic 12)
    • Address parsing with error correction
    • Channel extraction (F-TAC-203)
    • Address variant generation for geocoding

Geocoding Strategy

Multi-provider fallback with rate limiting:

  1. Nominatim - Primary free service (1 req/sec)
  2. Maps.co Key 1 - First fallback (1 req/sec)
  3. Maps.co Key 2 - Second fallback (1 req/sec)

Each provider tries all address variants before moving to next provider.

Rate Limiting

  • Broadcastify API: Maximum 1 request per 5 seconds (enforced by API)
  • Deepgram API: 100 concurrent requests (using batch size of 20)
  • Client Polling: Configurable interval (default 30 seconds)
  • OpenAI API: Standard quota limits
  • Nominatim: 1 request per second (enforced by client)

πŸ› οΈ Development

Adding New Features

See CLAUDE.md for detailed development patterns including:

  • Adding new incident fields
  • Modifying dispatch parsing logic
  • Changing map behavior
  • Extending filter capabilities

Testing Broadcastify Integration

Use test credentials provided in Broadcastify API docs:

# Standard User
Username: spook42069
Password: df7a0nqagl

# Premium Subscriber
Username: motion42069
Password: ef7a0n5a5ml

Test JWT generation:

pnpm tsx src/lib/test-jwt.ts

Common Development Tasks

Debug SSE Connection:

# Terminal 1: Start dev server with logging
pnpm run dev

# Terminal 2: Monitor SSE endpoint
curl -N http://localhost:3000/api/broadcastify/live-calls?stream=1&init=1

Test Geocoding:

// In browser console
fetch('/api/geocode?address=100 Main St, Austin, TX')
  .then(r => r.json())
  .then(console.log)

Clear Local Storage:

// In browser console
localStorage.clear()
location.reload()

🚒 Deployment

Supabase Backend

Deploy the Edge Function and database:

# Deploy Edge Function
supabase functions deploy process-calls

# Configure Edge Function secrets
supabase secrets set BROADCASTIFY_API_KEY_ID=your_key
# ... (see SUPABASE_DEPLOYMENT.md for complete list)

Then set up the cron job in the Supabase SQL Editor (see SUPABASE_DEPLOYMENT.md).

Vercel Frontend (Recommended)

  1. Push code to GitHub
  2. Import project in Vercel Dashboard
  3. Add environment variables in project settings:
    • Supabase URL and keys (3 variables)
    • Broadcastify credentials (3 variables)
    • Deepgram API key
    • OpenAI API key
    • Optional: Maps.co geocoding keys
  4. Deploy

For detailed instructions, see SUPABASE_DEPLOYMENT.md.

Performance Considerations

  • SSE Connections: Browser limit of 6 concurrent EventSource connections
  • Map Tiles: Consider MapTiler or Mapbox for production tile serving
  • Geocoding: Multi-provider fallback reduces dependency on single service
  • Transcription: Deepgram free tier provides 45k minutes/month
  • Parsing: OpenAI GPT-4o-mini costs ~$0.0001 per call
  • Batch Processing: 20 concurrent requests = 10-15x faster initial loads

πŸ’° Cost Estimates

For typical usage (monitoring 1-2 channels):

  • Supabase: FREE (500MB database, 500k Edge Function invocations/month)
  • Deepgram: FREE (45k min/month tier covers ~1500 incidents/day)
  • OpenAI: ~$3-5/month (GPT-4o-mini parsing)
  • Nominatim: FREE (community service)
  • Maps.co: FREE tier or $10/month for higher limits
  • Vercel: FREE (Hobby tier sufficient)

Total: $0-15/month depending on traffic and geocoding needs

The cron job running every 60 seconds = 43,200 invocations/month, well within Supabase's free tier.

πŸ“ License

MIT License - See LICENSE file for details

🀝 Contributing

Contributions are welcome! Please:

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

πŸ“§ Support

For issues and questions:

  • Open an issue on GitHub
  • Check CLAUDE.md for architecture details
  • Review Broadcastify API documentation

πŸ™ Acknowledgments

  • City of Austin - Open Data Portal
  • Broadcastify/RadioReference - Live radio call data
  • Deepgram - Fast, accurate speech-to-text transcription
  • OpenAI - GPT-4o-mini for intelligent transcript parsing
  • Austin Fire Department - Emergency response data

Built with ❀️ for Austin/Travis County emergency services

About

Real-time fire, rescue, hazmat, and traffic incidents map for Austin and Travis County, Texas.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published