Exploring Bay Area bike-share patterns with Bay Wheel open data
Baywheel.ing visualizes BayWheels trip patterns using anonymized historical system data published by Lyft.
Click any station to view stats. Browse different months to spot seasonal patterns.
Rides Data:
- New archives are reduced to station/month summaries and origin→destination counts in KV; the app does not need individual ride rows to draw the map or show its stats
- Existing monthly
rides_YYYYMMtables in D1 remain available as a source for rebuilding summaries during the migration
Route Polylines:
- Cycling paths are fetched from the OSRM API as needed and cached once per station pair in KV
- Monthly ride counts are grouped by origin→destination pair, and busy lines are drawn thicker
Station Stats:
- Station/month totals, average and longest ride duration, breakdowns, and route counts are precomputed in KV, so selecting a station reads a compact summary instead of loading every ride
OSRM enforces a 1 request/second limit per IP. To respect this:
- Client initiates requests with a 2 concurrent limit
- Requests are staggered by 500ms (queueIndex * 500) to maintain spacing
- Combined with Cloudflare's caching, this prevents bandwidth blocks
Route lines are computed using OSRM, the only free open-source routing engine I could find.
It only supports driving and walking modes. Cycling is used here.
If you have another free and simple option, feel free to open an issue.
- Next.js with shadcn/ui and Tailwind
- MapLibre GL (via mapcn and carto) for mapping
- Deck.gl for route rendering
- Dither Kit for dithered charts and visual primitives
- Cloudflare Workers — Edge compute for OSRM routing requests and API proxying
- Cloudflare D1 — SQLite database for caching computed route polylines, reducing API calls to OSRM
- Cloudflare KV — Key-value store for rapid access to station metadata and frequently-requested aggregated trip statistics
- Cloudflare via opennext for hosting
- Node.js 18+ and pnpm
- Cloudflare account with D1 and KV enabled
-
Clone and install:
git clone https://github.com/samwarnick/baywheeling cd baywheeling pnpm install pnpm run init -
Run the development server:
pnpm dev
Open http://localhost:3000 in your browser.
-
Deploying to Cloudflare (optional):
pnpm deploy
This builds and deploys to Cloudflare Pages, Workers, D1, and KV using the bindings configured in
wrangler.jsonc.
Note
The app relies on historical trip data from Lyft's Bay Wheels system. This data is not included in the repository due to size, but you can easily load it yourself using the steps below.
Make you run the init script before loading data, as it sets up the D1 database. KV should just work without initialization, but D1 needs the schema to be created first.
The importer runs outside the Worker and writes compact monthly summaries to the existing KV namespace. It does not insert individual rides into D1.
-
Download system data: Download CSV files from Lyft's Bay Wheels system data and place them in
utils/data/:utils/data/202512-baywheels-tripdata.csv utils/data/202601-baywheels-tripdata.csv # etc. -
Import a month from the Bay Wheels S3 archive:
python3 utils/import-month.py 2026-02 python3 utils/import-month.py 2017
Use a year for a yearly archive such as 2017. Omit the period to process all available archives. Each CSV is reduced to monthly station stats and route counts, then uploaded to KV; raw ride rows are discarded. Legacy Ford GoBike columns are normalized automatically.
-
Backfill compact summaries for months already in D1:
pnpm cache:month-stats
Pass a month such as
2026-02to refresh only that month. The no-argument command discovers each existingrides_YYYYMMtable in remote D1 and writes station stats and route counts to KV. -
Backfill the local pair cache when needed:
python3 utils/fetch_routes.py