-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: optionally use OpenTripPlanner for nearby transit (#41)
* feat: optionally use OpenTripPlanner for nearby transit * fix: don't use OTP's wacky route pattern data * fix Nearby.parse/1 typespec * refactor: don't parse source as a single-use atom * feat: ignore massport stops * test: add OTP parsing parent stations
- Loading branch information
1 parent
fd5bedf
commit 73aff82
Showing
17 changed files
with
640 additions
and
38 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
defmodule OpenTripPlannerClient do | ||
@moduledoc """ | ||
Fetches data from the OpenTripPlanner API. | ||
## Configuration | ||
```elixir | ||
config :mobile_app_backend, | ||
otp_url: "http://localhost:8080" | ||
``` | ||
""" | ||
|
||
require Logger | ||
|
||
alias OpenTripPlannerClient.Nearby | ||
|
||
@doc """ | ||
Fetches stops within the given number of meters of the given position. | ||
""" | ||
@spec nearby(float(), float(), integer(), Keyword.t()) :: | ||
{:ok, [MBTAV3API.Stop.t()]} | {:error, term()} | ||
def nearby(latitude, longitude, radius, opts \\ []) do | ||
root_url = | ||
Keyword.get(opts, :root_url, Application.fetch_env!(:mobile_app_backend, :otp_url)) | ||
|
||
request = | ||
Nearby.request(latitude, longitude, radius) | ||
|> Req.update(base_url: root_url, url: "/otp/routers/default/index/graphql") | ||
|
||
case send_request(request) do | ||
{:ok, body} -> Nearby.parse(body) | ||
{:error, error} -> {:error, error} | ||
end | ||
end | ||
|
||
@spec send_request(Req.Request.t()) :: {:ok, term()} | {:error, term()} | ||
defp send_request(request) do | ||
with {:ok, response} <- log_response(request), | ||
%{status: 200, body: body} <- response do | ||
{:ok, body} | ||
else | ||
%{status: _} = response -> | ||
{:error, response} | ||
|
||
error -> | ||
error | ||
end | ||
end | ||
|
||
@spec log_response(Req.Request.t()) :: {:ok, Req.Response.t()} | {:error, term()} | ||
defp log_response(request) do | ||
{duration, response} = | ||
:timer.tc( | ||
MobileAppBackend.HTTP, | ||
:request, | ||
[request] | ||
) | ||
|
||
_ = | ||
Logger.info(fn -> | ||
"#{__MODULE__}.otp_response query=#{inspect(request.options[:graphql])} #{status_text(response)} duration=#{duration / :timer.seconds(1)}" | ||
end) | ||
|
||
response | ||
end | ||
|
||
defp status_text({:ok, %{status: code}}) do | ||
"status=#{code}" | ||
end | ||
|
||
defp status_text({:error, error}) do | ||
"status=error error=#{inspect(error)}" | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
defmodule OpenTripPlannerClient.Nearby do | ||
@spec request(float(), float(), integer()) :: Req.Request.t() | ||
def request(latitude, longitude, radius) do | ||
Req.new(method: :post) | ||
|> AbsintheClient.attach( | ||
graphql: {graphql_query(), %{latitude: latitude, longitude: longitude, radius: radius}} | ||
) | ||
end | ||
|
||
@spec parse(map()) :: {:ok, [MBTAV3API.Stop.t()]} | {:error, term()} | ||
def parse(data) do | ||
with {:ok, edges} <- get_edges(data), | ||
{:ok, stops} <- parse_edges(edges) do | ||
{:ok, stops} | ||
else | ||
{:error, error} -> {:error, error} | ||
end | ||
end | ||
|
||
defp graphql_query do | ||
""" | ||
query NearbyQuery($latitude: Float!, $longitude: Float!, $radius: Int!) { | ||
nearest(lat: $latitude, lon: $longitude, maxDistance: $radius, filterByPlaceTypes: [STOP]) { | ||
edges { | ||
node { | ||
place { | ||
... on Stop { | ||
...stopDetails | ||
parentStation { | ||
...stopDetails | ||
} | ||
} | ||
} | ||
distance | ||
} | ||
} | ||
} | ||
} | ||
fragment stopDetails on Stop { | ||
gtfsId | ||
lat | ||
lon | ||
name | ||
} | ||
""" | ||
end | ||
|
||
@spec get_edges(map()) :: {:ok, list(map())} | {:error, term()} | ||
defp get_edges(data) do | ||
case data do | ||
%{"data" => %{"nearest" => %{"edges" => edges}}} -> {:ok, edges} | ||
_ -> {:error, :bad_format} | ||
end | ||
end | ||
|
||
@spec parse_edges(list(map())) :: {:ok, list(MBTAV3API.Stop.t())} | {:error, term()} | ||
defp parse_edges(edges) do | ||
edges | ||
|> Enum.reduce({:ok, []}, fn | ||
edge, {:ok, reversed_stops} -> | ||
with {:ok, stop} <- get_stop(edge), | ||
{:ok, stop} <- parse_stop(stop) do | ||
{:ok, [stop | reversed_stops]} | ||
else | ||
:ignore -> {:ok, reversed_stops} | ||
{:error, error} -> {:error, error} | ||
end | ||
|
||
_edge, {:error, error} -> | ||
{:error, error} | ||
end) | ||
|> case do | ||
{:ok, reversed_stops} -> {:ok, Enum.reverse(reversed_stops)} | ||
{:error, error} -> {:error, error} | ||
end | ||
end | ||
|
||
@spec get_stop(map()) :: {:ok, map()} | {:error, term()} | ||
defp get_stop(edge) do | ||
case edge do | ||
%{"node" => %{"place" => stop}} -> {:ok, stop} | ||
_ -> {:error, :bad_format} | ||
end | ||
end | ||
|
||
@spec parse_stop(map()) :: {:ok, MBTAV3API.Stop.t()} | :ignore | {:error, term()} | ||
defp parse_stop(place) do | ||
case place do | ||
%{"lat" => latitude, "lon" => longitude, "gtfsId" => "mbta-ma-us:" <> id, "name" => name} -> | ||
{:ok, | ||
%MBTAV3API.Stop{ | ||
id: id, | ||
latitude: latitude, | ||
longitude: longitude, | ||
name: name, | ||
parent_station: | ||
with {:ok, parent_station} when not is_nil(parent_station) <- | ||
Map.fetch(place, "parentStation"), | ||
{:ok, parent_station} <- parse_stop(parent_station) do | ||
parent_station | ||
else | ||
_ -> nil | ||
end | ||
}} | ||
|
||
%{"gtfsId" => "2272_2274:" <> _} -> | ||
:ignore | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{"data":{"nearest":{"edges":[{"node":{"distance":354,"place":{"gtfsId":"mbta-ma-us:67120","lat":42.28101,"lon":-71.177035,"name":"Millennium Park","parentStation":null}}},{"node":{"distance":618,"place":{"gtfsId":"mbta-ma-us:129","lat":42.278959,"lon":-71.175667,"name":"Rivermoor St @ Charles Park Rd","parentStation":null}}},{"node":{"distance":627,"place":{"gtfsId":"mbta-ma-us:137","lat":42.278907,"lon":-71.175492,"name":"Charles Park Rd @ Rivermoor St","parentStation":null}}}]}}} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{"data":{"nearest":{"edges":[{"node":{"distance":44,"place":{"gtfsId":"mbta-ma-us:7097","lat":42.374518,"lon":-71.029532,"name":"Airport","parentStation":{"gtfsId":"mbta-ma-us:place-aport","lat":42.374262,"lon":-71.030395,"name":"Airport"}}}},{"node":{"distance":44,"place":{"gtfsId":"mbta-ma-us:7096","lat":42.374693,"lon":-71.029333,"name":"Airport","parentStation":{"gtfsId":"mbta-ma-us:place-aport","lat":42.374262,"lon":-71.030395,"name":"Airport"}}}},{"node":{"distance":44,"place":{"gtfsId":"mbta-ma-us:70048","lat":42.374262,"lon":-71.030395,"name":"Airport","parentStation":{"gtfsId":"mbta-ma-us:place-aport","lat":42.374262,"lon":-71.030395,"name":"Airport"}}}},{"node":{"distance":44,"place":{"gtfsId":"mbta-ma-us:70047","lat":42.374262,"lon":-71.030395,"name":"Airport","parentStation":{"gtfsId":"mbta-ma-us:place-aport","lat":42.374262,"lon":-71.030395,"name":"Airport"}}}},{"node":{"distance":69,"place":{"gtfsId":"2272_2274:52","lat":42.373908,"lon":-71.030087,"name":"Blue Line - MBTA","parentStation":null}}},{"node":{"distance":80,"place":{"gtfsId":"2272_2274:41","lat":42.37374,"lon":-71.03005,"name":"Blue Line - MBTA","parentStation":null}}}]}}} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.