Current section
Files
Jump to
Current section
Files
lib/transit.ex
defmodule Transit do
require Logger
NimbleCSV.define(CSVParser, separator: ",", escape: "\"")
@files [
{"stops.txt", GTFS.Stop},
{"stop_times.txt", GTFS.StopTime},
{"routes.txt", GTFS.Route},
{"trips.txt", GTFS.Trip},
{"calendar.txt", GTFS.Calendar},
{"calendar_dates.txt", GTFS.CalendarDate}
]
@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, gtfs_stream_data, gtfs_tmp_dir} <- _extract_stream_data_from_gtfs_zip(gtfs_zip),
{:ok, mapped_feed_data} <- _convert_feed_stream_to_map(gtfs_stream_data) do
File.rm_rf!(gtfs_tmp_dir)
{: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}
res -> res
end
end
defp _extract_stream_data_from_gtfs_zip(zip) do
tmp_dir = '/tmp/transit/gtfs_' ++ to_charlist(:os.system_time)
case :zip.zip_open(zip, [{:cwd, tmp_dir}]) do
{:ok, zip_pid} ->
file_paths = @files |> Enum.map(fn {filename, module} ->
{:ok, path} = :zip.zip_get(to_charlist(filename), zip_pid)
{path, module}
end)
gtfs_streams_with_structs = file_paths |> _file_paths_to_stream
:zip.zip_close(zip_pid)
{:ok, gtfs_streams_with_structs, to_string(tmp_dir)}
{:error, reason} -> Logger.error(reason)
end
end
defp _file_paths_to_stream(file_paths) do
file_paths |> Enum.map(fn {path, module} ->
stream = to_string(path) |> File.stream!
{stream, module}
end)
end
defp _convert_feed_stream_to_map(streams) do
feed_map = streams |> Enum.reduce(%{}, fn {stream, module}, acc ->
gtfs_data_arr = stream |> _parse_gtfs_file_stream(module)
key = (Kernel.inspect(module) |> String.replace("GTFS.", "") |> Macro.underscore) <> "s" |> String.to_atom
acc |> Map.put(key, gtfs_data_arr)
end)
{:ok, feed_map}
end
defp _parse_gtfs_file_stream(file_stream, s) do
try do
csv_stream = file_stream |> CSVParser.parse_stream(headers: false)
headers = csv_stream |> Stream.take(1) |> Enum.to_list |> List.first |> Enum.map(&(String.to_atom(&1)))
csv_stream
|> Stream.drop(1)
|> Enum.map(fn row ->
map = Enum.zip(headers, row) |> Enum.into(%{})
struct(s, map)
end)
rescue
File.Error -> Logger.error("Error reading GTFS files. Ensure that all files are present.")
end
end
defp _redirect_location(headers) do
for {key, value} <- headers, String.downcase(key) == "location" do
value
end
end
end