A GenServer that downloads and handles updating GTFS files.
Add it to your application and it'll load the files to disk on initialization.
children = [
RiderUtils.Gtfs
]And you can get the values and do things with them:
all_inbound_destinations = case RiderUtils.Gtfs.get("directions.txt") do
{:ok, directions} ->
directions
|> Enum.filter(& &1["direction_id"] == "1")
|> Enum.map(& {&1["route_id"], &1["direction_destination"]})
other ->
other
endYou probably shouldn't do it, but technically can have more than one of these:
children = [
{RiderUtils.Gtfs, name: Green, gtfs_url: "https://cdn.mbta.com/MBTA_GTFS-dev-green.zip"},
{RiderUtils.Gtfs, name: Blue, gtfs_url: "https://cdn.mbta.com/MBTA_GTFS-dev-blue.zip"}
]Which'll let you handle multiple sets of files, I guess?
# true???
RiderUtils.Gtfs.get("agency.txt", Green) != RiderUtils.Gtfs.get("agency.txt", Blue)Options
All fields are optional.
:gtfs_url- The HTTP endpoint from which to download the GTFS archive. Defaults to"https://cdn.mbta.com/MBTA_GTFS.zip".:on_update- An MFA tuple that will be invoked after successful GTFS file download.:refresh_interval- How frequently to check for GTFS file changes. Defaults to:timer.minutes(5).
What refreshing means
After the initial download, the Last-Modified response header is saved for
future calls. Subsequent calls leverage the If-Modified-Since request header.
If the file on the :gtfs_url server hasn't changed since, we get a 304
response and don't need to update our own data. So, the :refresh_interval
merely defines how frequently we are checking, not how often we're downloading
the .zip file.
Summary
Functions
Get the parsed contents of a GTFS file.
Functions
@spec get(String.t(), GenServer.server()) :: {:ok, [map()]} | {:error, term()}
Get the parsed contents of a GTFS file.
Expects a valid GTFS filename such as "calendar.txt",
"trips_properties.txt", and such. Returns a map per row.
Because the files can be quite large, and the file contents do not change often, it is recommended to run this prudently.
Usage
{:ok, [%{"feed_version" => version} | _]} = RiderUtils.Gtfs.get("feed_info.txt")
version # "Summer 2026, 2026-08-10T18:56:26+00:00, version D"If using a named GenServer,
{:ok, routes} = RiderUtils.Gtfs.get("routes.txt", CustomGtfsServer)
ferry_routes = Enum.find(routes, & &1["route_type"] == "4")