Current section
Files
Jump to
Current section
Files
lib/transit.ex
defmodule Transit do
require Logger
NimbleCSV.define(CSVParser, separator: ",", escape: "\"")
@moduledoc """
GTFS tools for the time being for Elixir applications.
"""
@doc """
Parse url that resolves to a *.zip for GTFS.
"""
def parse_url(url) when is_binary(url) do
with {:ok, gtfs_zip} <- _fetch_gtfs_zip(url),
{:ok, csv_data} <- _extract_csv_data_from_gtfs_zip(gtfs_zip),
{:ok, mapped_feed_data} <- _convert_feed_csv_to_map(csv_data) do
{:ok, mapped_feed_data}
end
end
defp _fetch_gtfs_zip(url) when is_binary(url) do
case HTTPoison.get(url) do
{:ok, %HTTPoison.Response{status_code: status_code, headers: headers}}
when status_code > 300 and status_code < 400 ->
case _redirect_location(headers) do
[url] when is_binary(url) -> _fetch_gtfs_zip(url)
_ -> {:error, "There was an issue fetching fallback gtfs data. Error following redirects"}
end
{:ok, %HTTPoison.Response{body: gtfs_zip, status_code: 200}} -> {:ok, gtfs_zip}
reason -> {:error, reason}
end
end
defp _extract_csv_data_from_gtfs_zip(zip) do
case :zip.zip_open(zip, [:memory]) do
{:ok, zip_pid} ->
{:ok, {'stops.txt', stops_csv}} = :zip.zip_get('stops.txt', zip_pid)
{:ok, {'stop_times.txt', stop_times_csv}} = :zip.zip_get('stop_times.txt', zip_pid)
{:ok, {'routes.txt', routes_csv}} = :zip.zip_get('routes.txt', zip_pid)
{:ok, {'trips.txt', trips_csv}} = :zip.zip_get('trips.txt', zip_pid)
:zip.zip_close(zip_pid)
{:ok,
%{
stops_csv: stops_csv,
stop_times_csv: stop_times_csv,
routes_csv: routes_csv,
trips_csv: trips_csv
}}
{:error, reason} -> Logger.error(reason)
end
end
defp _convert_feed_csv_to_map(feed_data) do
# stops = CSVParser.parse_string(feed_data.stops_csv) |> IO.inspect
stops = _csv_to_maps(feed_data.stops_csv)
routes = _csv_to_maps(feed_data.routes_csv)
stop_times = _csv_to_maps(feed_data.stop_times_csv)
trips = _csv_to_maps(feed_data.trips_csv)
{:ok, %{ stops: stops, stop_times: stop_times, routes: routes, trips: trips }}
end
defp _csv_to_maps(csv) do
# headers
{hd_row, rows} = CSVParser.parse_string(csv, headers: false) |> List.pop_at(0)
rows |> Enum.map(fn row ->
_csv_row_to_map(hd_row, row)
end)
end
defp _csv_row_to_map(hd_row, row), do: Enum.zip(hd_row, row) |> Enum.into(%{})
defp _redirect_location(headers) do
for {key, value} <- headers, String.downcase(key) == "location" do
value
end
end
end