M assets/js/routes.js => assets/js/routes.js +3 -3
@@ 18,7 18,7 @@ const tiles = new TileLayer(tilesUrl, { attribution })
tiles.addTo(map)
-const ways = JSON.parse(document.getElementById("ways-geojson").innerHTML)
+const routes = JSON.parse(document.getElementById("routes-geojson").innerHTML)
const stops = JSON.parse(document.getElementById("stops-json").innerHTML)
const icon = new Icon({ iconUrl: "/images/stop.svg", iconSize: [14, 14] })
@@ 32,8 32,8 @@ const zetStops = new FeatureGroup(markers).addTo(map)
const layers = { "ZET Stops": zetStops }
-for (const name of Object.keys(ways)) {
- const way = L.geoJSON(ways[name], {
+for (const name of Object.keys(routes)) {
+ const way = L.geoJSON(routes[name], {
style: feature => ({
color: feature.properties.color
}),
M lib/triglav/zet/gtfs.ex => lib/triglav/zet/gtfs.ex +11 -30
@@ 19,42 19,23 @@ defmodule Triglav.Zet.Gtfs do
|> Repo.one!()
end
- @spec list_gtfs_stop_ids() :: [Zet.DistinctTrip.t()]
- def list_gtfs_stop_ids() do
- from(t in Zet.DistinctTrip)
+ def list_stops_by_id(stop_ids) do
+ Zet.Stop
+ |> where([s], s.id in ^stop_ids)
|> Repo.all()
end
- @spec list_gtfs_stop_ids(Zet.Trip.id()) :: [Zet.DistinctTrip.t()]
- def list_gtfs_stop_ids(trip_id) do
- from(t in Zet.DistinctTrip, where: t.trip_id == ^trip_id)
+ @spec list_distinct_trips([{:route_id, binary()}]) :: [Zet.DistinctTrip.t()]
+ def list_distinct_trips(opts \\ []) do
+ route_id = Keyword.get(opts, :route_id)
+
+ from(t in Zet.DistinctTrip)
+ |> maybe_filter_by_route_id(route_id)
|> Repo.all()
end
- def fetch_distinct_trips(%Zet.Route{} = route) do
- Repo.select!(
- """
- WITH trips AS (
- SELECT t.trip_id,
- t.direction_id,
- array_agg(
- row(s.stop_id, s.stop_name, s.stop_lat, s.stop_lon, st.stop_sequence::text)
- ORDER BY st.stop_sequence
- ) AS stops
- FROM zet.stop_times st
- JOIN zet.trips t ON st.trip_id = t.trip_id
- JOIN zet.stops s ON s.stop_id = st.stop_id
- WHERE t.route_id = $1
- GROUP BY 1, 2
- )
- SELECT direction_id, stops, count(*) AS trip_count
- FROM trips
- GROUP BY 1, 2
- ORDER BY 1 ASC, 3 DESC;
- """,
- [route.id]
- )
- end
+ defp maybe_filter_by_route_id(query, nil), do: query
+ defp maybe_filter_by_route_id(query, route_id), do: where(query, [t], t.route_id == ^route_id)
@doc """
Returns a list of all stops contained in any trip in the given route.
M lib/triglav_web/controllers/zet/routes_controller.ex => lib/triglav_web/controllers/zet/routes_controller.ex +31 -12
@@ 47,7 47,7 @@ defmodule TriglavWeb.Zet.RoutesController do
defp annotate_routes(routes, relations) do
osm_stop_ids = relations |> Enum.map(& &1.id) |> Osmosis.list_gtfs_stop_ids()
- zet_stop_ids = Gtfs.list_gtfs_stop_ids() |> Enum.group_by(& &1.route_id)
+ gtfs_distinct_trips = Gtfs.list_distinct_trips() |> Enum.group_by(& &1.route_id)
for route <- routes do
route_relations = Enum.filter(relations, &Relation.has_tag?(&1, "ref", route.id))
@@ 59,7 59,7 @@ defmodule TriglavWeb.Zet.RoutesController do
|> MapSet.new()
zet_variants =
- zet_stop_ids
+ gtfs_distinct_trips
|> Map.get(route.id)
|> MapSet.new(&Map.get(&1, :stops))
@@ 90,18 90,34 @@ defmodule TriglavWeb.Zet.RoutesController do
|> Osmosis.list_platform_members()
|> Enum.group_by(& &1.relation_id)
+ trips = Gtfs.list_distinct_trips(route_id: route.id)
+ trips_by_stop_ids = trips |> Map.new(&{&1.stops, &1})
+
relations_by_stop_ids =
for relation <- relations, Relation.is_route(relation), into: %{} do
{Osmosis.list_gtfs_stop_ids(relation.id), relation}
end
- trips =
- for trip <- Gtfs.fetch_distinct_trips(route) do
- stop_ids = Enum.map(trip["stops"], &elem(&1, 0))
- {trip, Map.get(relations_by_stop_ids, stop_ids)}
- end
+ annotated_trips =
+ trips
+ |> Enum.map(fn trip ->
+ Map.put(trip, :matched_relation, Map.get(relations_by_stop_ids, trip.stops))
+ end)
+ |> Enum.group_by(& &1.direction_id)
- grouped_trips = Enum.group_by(trips, fn {trip, _} -> trip["direction_id"] end)
+ annotated_relations =
+ Enum.map(relations_by_stop_ids, fn {stops, relation} ->
+ Map.put(relation, :matched_trip, Map.get(trips_by_stop_ids, stops))
+ end)
+ |> IO.inspect()
+
+ stops =
+ trips
+ |> Enum.map(& &1.stops)
+ |> Enum.concat()
+ |> Enum.uniq()
+ |> Gtfs.list_stops_by_id()
+ |> Map.new(&{&1.id, &1})
# JSON encode stops for drawing on the map
stops_json =
@@ 110,19 126,22 @@ defmodule TriglavWeb.Zet.RoutesController do
|> Enum.map(&Map.take(&1, [:id, :name, :lat, :lon]))
|> Jason.encode!()
- ways_geojson = routes_feature_collecton(relations, platform_members) |> Jason.encode!()
+ routes_geojson =
+ routes_feature_collecton(relations, platform_members)
+ |> Jason.encode!()
render(conn, "detail.html",
conn: conn,
hierarchy: hierarchy,
platform_members: platform_members,
relation_errors: relation_errors,
- relations: relations,
+ relations: annotated_relations,
route: route,
route_errors: route_errors,
+ routes_geojson: routes_geojson,
+ stops: stops,
stops_json: stops_json,
- trips: grouped_trips,
- ways_geojson: ways_geojson
+ trips: annotated_trips
)
end
M lib/triglav_web/templates/zet/routes/detail.html.eex => lib/triglav_web/templates/zet/routes/detail.html.eex +28 -16
@@ 4,6 4,7 @@
#routes-map {
height: 600px;
max-width: 1024px;
+ margin: 1rem 0;
}
</style>
@@ 11,8 12,8 @@
<%= raw(@stops_json) %>
</script>
-<script id="ways-geojson" type="application/json">
- <%= raw(@ways_geojson) %>
+<script id="routes-geojson" type="application/json">
+ <%= raw(@routes_geojson) %>
</script>
<main role="main" class="container-wide">
@@ 62,7 63,7 @@
<section>
<h3>Route relations</h3>
- <div style="display: flex; flex-direction: row;">
+ <div style="display: flex; flex-direction: row; flex-wrap: wrap;">
<%= for relation <- @relations, Relation.is_route(relation) do %>
<% members = @platform_members[relation.id] || [] %>
<div style="margin-left: 1rem;">
@@ 97,6 98,16 @@
<% end %>
</tbody>
</table>
+
+ <%= if relation.matched_trip do %>
+ <div class="callout">
+ <span class="text-green">✔ Matches a GTFS trip</span>
+ </div>
+ <% else %>
+ <div class="callout error">
+ ✖ No GTFS trip matched.
+ </div>
+ <% end %>
</div>
<% end %>
</div>
@@ 104,18 115,18 @@
<hr />
- <h2>ZET GTFS routes</h2>
+ <h2>ZET GTFS trips</h2>
- <p>Routes extracted from GTFS data published by ZET.
- All distinct routes are shown along with the number of trips along this route. </p>
+ <p>Trips extracted from GTFS data published by ZET.<br/>
+ All distinct trips are shown along with the number of departures along this route. </p>
<%= for {direction_id, trips} <- @trips do %>
<h3>Direction #<%= direction_id %></h3>
- <div style="display: flex; flex-direction: row;">
- <%= for {trip, matched_relation} <- trips do %>
+ <div style="display: flex; flex-direction: row; flex-wrap: wrap">
+ <%= for trip <- trips do %>
<div style="margin-left: 1rem;">
- <p><b><%= trip["trip_count"] %> departures</b></p>
+ <p><b><%= trip.trip_count %> departures</b></p>
<table>
<thead>
<tr>
@@ 126,21 137,22 @@
</tr>
</thead>
<tbody>
- <%= for {id, name, lat, lon, ord} <- trip["stops"] do %>
+ <%= for {stop_id, index} <- Enum.with_index(trip.stops) do %>
+ <% stop = Map.get(@stops, stop_id) %>
<tr>
- <td><%= ord %>.</td>
- <td><%= id %></td>
- <td><%= name %></td>
- <td><%= josm_zoom(lat, lon) %></td>
+ <td><%= index %>.</td>
+ <td><%= stop.id %></td>
+ <td><%= stop.name %></td>
+ <td><%= josm_zoom(stop.lat, stop.lon) %></td>
</tr>
<% end %>
</tbody>
</table>
- <%= if matched_relation do %>
+ <%= if trip.matched_relation do %>
<div class="callout">
<span class="text-green">✔ Matched relation</span><br />
- <%= osm_link(matched_relation, [josm: true]) %>
+ <%= osm_link(trip.matched_relation, [josm: true]) %>
</div>
<% else %>
<div class="callout error">