Real-time public data for Portland, OR on an interactive map. Aggregates police/fire dispatch, NWS weather alerts, TriMet transit alerts, bridge lifts, road closures, water advisories, and nine GIS safety overlays from City of Portland open data sources.

Project Kickoff and Tech specs
- Framework: Next.js 16 (App Router, TypeScript)
- Map: MapLibre GL JS
- Data fetching: SWR (client polling) + Next.js ISR (server-side overlay caching)
- Styling: Tailwind CSS v4
- Runtime: Bun (recommended) or Node.js 20+
- Bun v1.0+ or Node.js 20+
- Git
git clone https://github.com/codeforpdx/pdxhub.git
cd pdxHub
bun install # or: npm installCopy the example file:
cp .env.example .env.localThen fill in .env.local. See Environment Variables below for where to get each value.
bun dev # or: npm run devOpen http://localhost:3000.
All Docker workflows are managed through make. Run make help from the project root to see available targets.
make build # build the production image
make run # start the production container (foreground)
make up # build then start in one stepmake devStarts the dev container in Docker Compose watch mode. Source changes are synced into the container and Next.js hot-reloads. Changes to package.json trigger a full image rebuild.
Approximate image sizes:
pdx-hub:localproduction image ~288 MBpdx-hub:devdevelopment image ~1.3 GB (carries full deps + tooling)
Both modes publish the app on http://localhost:3000 and load .env.local automatically when that file exists.
| Command | What it does |
|---|---|
make stop |
Pause containers without removing them |
make down |
Remove project containers and network |
make clean |
Remove containers, network, named volumes, and local images |
make clobber |
Everything in clean, plus force-removes active BuildKit helper containers and their state volumes, then globally prunes unused Docker images, volumes, networks, and builder cache |
All variables are optional — the app runs without any of them, but will be missing data from APIs that require a key.
Required for the Transit Alerts feed. Without it the transit route throws a 403.
- Register at developer.trimet.org (free, instant)
- Copy your App ID into
.env.local
TRIMET_APP_ID=your-app-id-hereRequired for the Road Closures feed. Without it the road route returns an error.
- Request access at api.odot.state.or.us / TripCheck developer portal (free)
- Copy your subscription key into
.env.local
TRIPCHECK_PRIMARY_KEY=your-key-hereOptional. Without it the Bridge Lifts feed works but is rate-limited to ~1,000 requests/day across all anonymous callers.
- Register at data.portlandoregon.gov → Developer Settings (free)
- Create an app token and copy it into
.env.local
SOCRATA_APP_TOKEN=your-token-heresrc/
app/
page.tsx # Root page — renders HomeClient
layout.tsx # App shell, metadata, PWA manifest
globals.css # Tailwind + MapLibre CSS, design tokens
api/
police/ # Portland 911 dispatch (police)
fire/ # Portland 911 dispatch (fire)
weather/ # NWS alerts (api.weather.gov)
transit/ # TriMet service alerts
bridge/ # Portland bridge lift schedule (Socrata)
road/ # Oregon TripCheck road incidents
waterworks/ # Portland Water Bureau projects
advisories/ # Oregon drinking water advisories (ArcGIS)
health/ # Disabled — stub returns empty (no public API)
overlays/ # GIS overlay routes (ISR-cached, ArcGIS Feature Services)
airquality/ # EPA AirNow AQI contours — 30 min cache
potholes/ # PBOT active pothole reports — 24 h cache
streetwork/ # PBOT active permit jobs — 30 min cache
cip/ # City capital improvement projects — 24 h cache
flood/ # FEMA flood hazard areas — 24 h cache
beecn/ # Earthquake communication nodes — 24 h cache
emergencyroutes/ # Emergency transportation routes — 24 h cache
highcrash/ # PBOT high-crash intersections — 24 h cache
highcrashstreets/ # PBOT high-crash corridors — 24 h cache
components/
HomeClient.tsx # Root client component, all state lives here
map/
MapView.tsx # MapLibre GL map, markers, popups, overlay layers
FilterBar.tsx # Category toggle pills
LayerSwitcher.tsx # Tile layer switcher (Street / Satellite)
OverlaySwitcher.tsx # GIS overlay panel
LocationSearch.tsx # Nominatim geocoder
sidebar/
Sidebar.tsx # Event feed, date filter
FeedCard.tsx # Individual incident card
hooks/
useEvents.ts # SWR polling for all incident routes
lib/
constants.ts # Map defaults, category filters, overlay configs
normalizers.ts # Raw API data → IncidentEvent[]
arcgis.ts # Paginated ArcGIS FeatureServer fetcher
portland911.ts # Portland Maps Atom/XML dispatch feed parser
types/
index.ts # Shared TypeScript types
public/
manifest.json # PWA manifest
sw.js # Service worker (production only)
bun run build # Production build
bun run start # Run production build locally
bun run lint # ESLint
bun run clean-dev # Kill existing dev server, wipe .next cache, restart
make up # Build and start the Dockerized app
make dev # Start the live-reload dev container
make stop # Stop running containers without removing them
make clean # Remove project containers, volumes, and images
make clobber # Full Docker cleanup including BuildKit state (global, destructive)| Feed | Source | Requires key |
|---|---|---|
| Police / Fire dispatch | Portland Maps 911 feed | No |
| Weather alerts | api.weather.gov | No |
| Bridge lifts | Portland Open Data (Socrata) | Optional |
| Transit alerts | TriMet REST API | Yes |
| Road incidents | Oregon TripCheck | Yes |
| Water projects | Portland Water Bureau | No |
| Water advisories | Oregon DEQ ArcGIS | No |
| GIS overlays | Portland Maps Open Data | No |
| Air quality | EPA AirNow ArcGIS | No |
| Location search | Nominatim / OpenStreetMap | No |
The app ships a service worker (public/sw.js) that is only registered in production builds. In development the service worker is automatically unregistered to prevent stale chunk errors.
PDX Hub is an open-source Code PDX project and welcomes contributions of all sizes. See CONTRIBUTING.md for the full guide covering:
- Setting up a local or Docker development environment
- Branch naming conventions and the PR workflow
- Coding standards and data source rules
- How to file bug reports, feature requests, and enhancement requests
If you're new to the project, look for issues labeled good first issue on the issues page.