Website: https://e-kotov.github.io/gtfsrt2static/
Experimental — APIs will change without deprecation.
Turn archived GTFS-Realtime feeds (and GPS-derived observations) into standard-compliant static GTFS snapshots of the service that actually ran, ready for comparison against the planned schedule.
How it fits together
live endpoints ──rt2s_collect()──► daily .pb ZIPs
│
Trip Updates ──(gtfsrealtime)──┴──► rt2s_events_from_trip_updates() ──┐
├─► observed stop events ─► rt2s_assemble() ─► realized GTFS zip
Vehicle Positions / raw GPS ──(gps2gtfs)─► rt2s_events_from_stop_times() ─┘ (baseline feed optional)
-
gtfsrealtimeparses GTFS-RT protobuf into data frames (not this package’s job). -
gps2gtfsinfers trips and stop times from raw coordinates (not this package’s job either). - This package does everything that is neither protobuf nor spatial: collecting feeds, converging both input paths on a canonical observed stop events table, and assembling compliant feeds.
Modules
| Module | Functions | Job |
|---|---|---|
| collect |
rt2s_collect(), rt2s_archive_rotate(), rt2s_archive_coverage(), rt2s_service_template()
|
archive ephemeral GTFS-RT endpoints as daily .pb ZIPs (never parses; raw bytes only) |
| events |
rt2s_events_from_trip_updates(), rt2s_events_from_stop_times(), rt2s_events_validate()
|
reduce observations to one row per trip × stop × service date, with provenance |
| summarise |
rt2s_obs_headways(), rt2s_obs_travel_times(), rt2s_obs_stop_order(), rt2s_time_window()
|
reduce many observed runs to headway / travel-time / dwell quantiles and a cross-trip canonical stop order |
| baseline |
rt2s_baseline_patterns(), rt2s_baseline_headways(), rt2s_baseline_service_dates()
|
reduce a planned static feed to one canonical stop pattern per route-direction, to its own scheduled headways, and to the dates a named service runs |
| assemble |
rt2s_assemble(), rt2s_scaffold(), rt2s_frequencies(), rt2s_resolved_grid()
|
merge events into a baseline feed (official IDs preserved), scaffold a compliant feed from scratch, or collapse many runs into a frequency-based feed — and read back which headway groups were emitted, with what, and why the rest were dropped |
Quick example
library(gtfsrt2static)
# Trip Updates archive -> realized feed for one day
updates <- gtfsrealtime::read_gtfsrt_trip_updates("tu/20260714.zip", "America/New_York")
events <- rt2s_events_from_trip_updates(updates)
feed <- rt2s_assemble(events, baseline = "planned_gtfs.zip")
gtfsio::export_gtfs(feed, "realized_20260714.zip")
# Vehicle Positions (no Trip Updates, no baseline)
positions <- gtfsrealtime::read_gtfsrt_positions("vp/20260714.zip", "America/New_York")
result <- gps2gtfs::g2g_extract_trips_and_stop_times(positions, terminals, stops, ...)
events <- rt2s_events_from_stop_times(result$stop_times)
feed <- rt2s_assemble(
events,
agency = list(name = "My Agency", url = "https://...", timezone = "America/New_York"),
stops = gps2gtfs::g2g_stops_from_positions(positions)
)
# Collapse many runs into a frequency-based feed at three reliability quantiles
feeds <- rt2s_frequencies(
events,
windows = list(am_peak = c("06:00", "09:00"), midday = c("09:00", "16:00")),
stops = stops_with_coords,
agency = list(name = "My Agency", url = "https://...", timezone = "America/New_York")
)
feeds$structural; feeds$median; feeds$reliable # p05 / p50 / p95 feeds
# If trip_ref is not per-run, estimate headways from reference-stop passages
feeds <- rt2s_frequencies(
events,
windows = list(am_peak = c("06:00", "09:00"), midday = c("09:00", "16:00")),
stops = stops_with_coords,
agency = list(name = "My Agency", url = "https://...", timezone = "America/New_York"),
headway_method = "passage"
)Supplying reference_stops restricts passage-headway output to route-directions that serve one of those stops. The same choice is available one level down as rt2s_obs_headways(method = "passage"), which is where reference_stops and min_revisit_gap_s are documented.
Anchoring the frequency feeds on a planned feed (pattern_source = "baseline", scaling=, headways=, headway_groups=, service_dates=) and mixing in individually-timed trips (extra_trips=) are covered in vignette("frequency-feeds").
Learn more
-
Get started: GTFS-Realtime Trip Updates to observed stop events to a scaffolded feed, read back with
gtfstools. -
From raw GPS to a realized GTFS feed: the end-to-end walkthrough across
gtfsrealtime,gps2gtfs, this package andgtfstools, in scaffold and baseline mode. - Frequency-based feeds from observed service: structural, median and reliable feeds; anchoring on a planned feed; individually-timed trips; the resolved-grid audit.
- Function reference.
Installation
# install.packages("pak")
pak::pak("e-kotov/gtfsrt2static")Design notes
- Both input paths converge on the observed stop events schema (
?"observed-stop-events"): trip × stop × service date × actual times × provenance (observed/predicted-last/propagated/skipped/canceled). - Times are absolute
POSIXctinternally;stop_times.txtclock strings (including>24:00:00for post-midnight stops) are derived only at assembly. - The collector never parses protobuf: archive fidelity must not depend on parser versions, and malformed responses are evidence worth keeping.
- Output follows the gtfsio convention, so gtfstools and tidytransit work on it directly.