An original, animated map of New York City and its subway network. It shows where trains would be right now if they followed the MTA's published static schedule.
This is not a live train tracker or an official MTA map.
The app has two parts:
- A build script turns MTA GTFS and NYC geographic data into small, browser-ready files.
- A Canvas renderer samples the current New York time and draws every scheduled train at its estimated position.
MTA GTFS + NYC GeoJSON
|
v
data compiler
|
+--> map.<hash>.json
+--> schedule-<service>.<hash>.json
+--> manifest.json
|
v
two Canvas layers
map + moving trains
The interface uses Next.js 16.3, React 19.2, TypeScript, and the Canvas 2D API. The static map and animated trains live on separate canvases, so the city only needs to redraw after a resize, theme change, route filter, or committed camera move. Train positions are sampled outside React state on each animation frame.
GTFS gives us stop times and route shapes, but it does not give each stop's distance along a shape. The compiler calculates that distance first.
For a stop point
If
Stops are processed in trip order. The segment search only moves forward, and
the final distance is clamped so that
Each trip becomes a list of keyframes
The smoothstep function
The renderer finds the shape vertices whose cumulative distances contain
That point becomes the center of the train roundel. A short direction stem is traced through the same shape vertices, so it stays attached to curved tracks.
Several subway families use the same corridor. Drawing them on one centerline hides services according to paint order, so the compiler finds nearby route families that run in roughly the same direction and assigns each one a centered lane factor.
For a family with ordered lane index
The compiler smooths each shape's factors three times with a local weighted average:
If
Dividing by
The compiler reads these MTA GTFS files:
feed_info.txt,calendar.txt, andcalendar_dates.txtroutes.txt,trips.txt, andstop_times.txtstops.txtandshapes.txt
It also reads NYC borough, street-centerline, and park GeoJSON. Coordinates are
placed in a local projection centered near New York, rotated by 0.06 tolerance for clean curves at high zoom.
The compiler then:
- Creates the borough, street, park, landmark, and route geometry.
- Detects shared corridors and compiles a lane factor for every route vertex.
- Projects every trip stop onto its route shape.
- Stores arrival, departure, and shape-distance keyframes for each trip.
- Groups trips by GTFS service calendar.
- Writes content-hashed map and schedule chunks.
- Writes
manifest.jsonlast, with feed dates and SHA-256 hashes for every source file.
The browser selects service with the MTA calendar and exception tables. It
loads both the current service day and the previous one, which preserves GTFS
times such as 25:10:00 after midnight. The display clock always uses
America/New_York. If today falls outside the bundled feed window, the app
replays the same time of day on the last covered date and labels it as replay.
To compile a new snapshot:
MTA_GTFS_DIRECTORY=/path/to/google_transit \
NYC_BOROUGHS_FILE=/path/to/boroughs.geojson \
NYC_MAJOR_STREETS_FILE=/path/to/major-streets.geojson \
NYC_MANHATTAN_STREETS_FILE=/path/to/manhattan-streets.geojson \
NYC_PARKS_FILE=/path/to/parks.geojson \
pnpm data:buildSee data/subway/README.md for the required source
files. Generated assets live in public/data/subway.
This project uses pnpm.
pnpm install
pnpm devOpen http://localhost:3000.
Run the release checks with:
pnpm test
pnpm typecheck
pnpm lint
pnpm buildThe committed manifest identifies the exact feed version, coverage dates, and source hashes used for the current build. Regular static GTFS describes the planned schedule and omits most temporary service changes.
Schedule data comes from MTA Developer Resources. Geography comes from NYC Open Data. The interface and cartography are original.