M assets/css/app.scss => assets/css/app.scss +12 -1
@@ 165,6 165,17 @@ a.josm-remote {
display: inline-block;
}
+ul.breadcrumbs {
+ padding: 0;
+ list-style-type: none;
+ display: flex;
+
+ li:not(:first-child):before {
+ content: ">";
+ padding: 0 .75rem;
+ }
+}
+
// Utils
.tag { color: DimGray; white-space: nowrap; }
@@ 176,7 187,7 @@ a.josm-remote {
.w-full { width: 100%; }
.bg-red { background-color: rgba($red, 0.3); }
-.bg-green { background-color: rbga($green, 0.3); }
+.bg-green { background-color: rgba($green, 0.3); }
.text-bold { font-weight: bold; }
.text-smaller { font-size: 0.9rem; }
M lib/triglav/osm.ex => lib/triglav/osm.ex +4 -0
@@ 6,6 6,10 @@ defmodule Triglav.Osm do
import Ecto.Query
+ def get_relation(relation_id) do
+ Repo.get(Relation, relation_id)
+ end
+
@doc """
Returns a list of nodes with given ids, ordered the same as the given ids.
"""
M lib/triglav/zet/osmosis.ex => lib/triglav/zet/osmosis.ex +1 -1
@@ 94,7 94,7 @@ defmodule Triglav.Zet.Osmosis do
"""
@spec list_platform_members(Relation.id() | [Relation.id()]) :: [RelationMember.t()]
- def list_platform_members(relation_id) when is_integer(relation_id),
+ def list_platform_members(relation_id) when is_integer(relation_id) or is_binary(relation_id),
do: list_platform_members([relation_id])
def list_platform_members(relation_ids) when is_list(relation_ids) do
M lib/triglav_web/controllers/zet/routes_controller.ex => lib/triglav_web/controllers/zet/routes_controller.ex +73 -30
@@ 45,36 45,6 @@ defmodule TriglavWeb.Zet.RoutesController do
)
end
- defp annotate_routes(routes, relations) do
- osm_stop_ids = relations |> Enum.map(& &1.id) |> Osmosis.list_gtfs_stop_ids()
- 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))
-
- osm_variants =
- route_relations
- |> Enum.map(&Map.get(osm_stop_ids, &1.id))
- |> Enum.filter(& &1)
- |> MapSet.new()
-
- zet_variants =
- gtfs_distinct_trips
- |> Map.get(route.id)
- |> MapSet.new(&Map.get(&1, :stops))
-
- counts = %{
- total: Enum.count(zet_variants),
- correct: MapSet.intersection(osm_variants, zet_variants) |> Enum.count(),
- incorrect: MapSet.difference(osm_variants, zet_variants) |> Enum.count()
- }
-
- hierarchy = make_hierarchy(route_relations)
-
- {route, hierarchy, counts}
- end
- end
-
def detail(conn, %{"id" => id}) do
route = Gtfs.get_route(id)
relations = Osmosis.list_public_transport_relations(ref: id, members: true)
@@ 145,6 115,79 @@ defmodule TriglavWeb.Zet.RoutesController do
)
end
+ def match(conn, %{"id" => id, "relation_id" => relation_id}) do
+ trips = Gtfs.list_distinct_trips(route_id: id)
+ relation = Osm.get_relation(relation_id)
+
+ platform_members = Osmosis.list_platform_members(relation_id)
+
+ platforms =
+ platform_members
+ |> Enum.map(& &1.member)
+ |> Map.new(&{&1.tags["gtfs:stop_id"], &1})
+
+ stops =
+ trips
+ |> Enum.map(& &1.stops)
+ |> Enum.concat()
+ |> Enum.uniq()
+ |> Gtfs.list_stops_by_id()
+ |> Map.new(&{&1.id, &1})
+
+ relation_stop_ids = platform_members |> Enum.map(& &1.member.tags["gtfs:stop_id"])
+
+ diffs =
+ for trip <- trips do
+ List.myers_difference(relation_stop_ids, trip.stops)
+ end
+ |> Enum.sort_by(&count_eq/1, &>=/2)
+
+ render(conn, "match.html",
+ diffs: diffs,
+ platforms: platforms,
+ relation: relation,
+ route_id: id,
+ stops: stops
+ )
+ end
+
+ defp count_eq(diffs) do
+ diffs
+ |> Enum.filter(fn {k, _} -> k == :eq end)
+ |> Enum.map(fn {_, v} -> length(v) end)
+ |> Enum.sum()
+ end
+
+ defp annotate_routes(routes, relations) do
+ osm_stop_ids = relations |> Enum.map(& &1.id) |> Osmosis.list_gtfs_stop_ids()
+ 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))
+
+ osm_variants =
+ route_relations
+ |> Enum.map(&Map.get(osm_stop_ids, &1.id))
+ |> Enum.filter(& &1)
+ |> MapSet.new()
+
+ zet_variants =
+ gtfs_distinct_trips
+ |> Map.get(route.id)
+ |> MapSet.new(&Map.get(&1, :stops))
+
+ counts = %{
+ total: Enum.count(zet_variants),
+ correct: MapSet.intersection(osm_variants, zet_variants) |> Enum.count(),
+ incorrect: MapSet.difference(osm_variants, zet_variants) |> Enum.count()
+ }
+
+ hierarchy = make_hierarchy(route_relations)
+
+ {route, hierarchy, counts}
+ end
+ end
+
defp routes_feature_collecton(relations, platform_members) do
colors = @route_colors |> Enum.map(fn {_, v} -> v end) |> Stream.cycle()
M lib/triglav_web/router.ex => lib/triglav_web/router.ex +1 -0
@@ 22,6 22,7 @@ defmodule TriglavWeb.Router do
scope "/zet", alias: Zet, as: :zet do
get "/routes", RoutesController, :index
get "/routes/:id", RoutesController, :detail
+ get "/routes/:id/match/:relation_id", RoutesController, :match
get "/stops", StopsController, :index
get "/errors/history", ErrorsController, :index
get "/errors/history/atom", ErrorsController, :atom
M lib/triglav_web/templates/zet/routes/detail.html.eex => lib/triglav_web/templates/zet/routes/detail.html.eex +8 -0
@@ 17,6 17,11 @@
</script>
<main role="main" class="container-wide">
+ <ul class="breadcrumbs">
+ <li><a href="<%= Routes.zet_routes_path(@conn, :index) %>">Rotues</a></li>
+ <li>#<%= @route.id %></li>
+ </ul>
+
<h1>#<%= @route.id %>: <%= @route.long_name %></h1>
<div id="routes-map"></div>
@@ 106,6 111,9 @@
<% else %>
<div class="callout error">
✖ No GTFS trip matched.
+ <a href="<%= Routes.zet_routes_path(TriglavWeb.Endpoint, :match, @route.id, relation.id) %>">
+ Find a match
+ </a>
</div>
<% end %>
</div>
A lib/triglav_web/templates/zet/routes/match.html.eex => lib/triglav_web/templates/zet/routes/match.html.eex +52 -0
@@ 0,0 1,52 @@
+<main role="main" class="container-wide">
+ <ul class="breadcrumbs">
+ <li><a href="<%= Routes.zet_routes_path(@conn, :index) %>">Rotues</a></li>
+ <li><a href="<%= Routes.zet_routes_path(@conn, :detail, @route_id) %>">#<%= @route_id %></a></li>
+ <li>Match relation #<%= @relation.id %></li>
+ </ul>
+
+ <h1>Find a matching trip for relation #<%= @relation.id %></h1>
+ <p>Sorted by most to least probable.</p>
+
+ <%= for trip_diffs <- @diffs do %>
+ <table>
+ <thead>
+ <tr>
+ <th colspan="2">OSM platforms</th>
+ <th colspan="2">GTFS stops</th>
+ </tr>
+ </thead>
+ <tbody>
+ <%= for {op, ids} <- trip_diffs do %>
+ <%= for id <- ids do %>
+ <% platform = Map.get(@platforms, id) %>
+ <% stop = Map.get(@stops, id) %>
+ <tr>
+ <%= if op == :eq do %>
+ <td><%= id %></td>
+ <td><%= platform.tags["name"] %></td>
+ <td><%= id %></td>
+ <td><%= stop.name %></td>
+ <% end %>
+
+ <%= if op == :ins do %>
+ <td></td>
+ <td></td>
+ <td class="bg-green"><%= id %></td>
+ <td class="bg-green"><%= stop.name %></td>
+ <% end %>
+
+ <%= if op == :del do %>
+ <td class="bg-red"><%= id %></td>
+ <td class="bg-red"><%= platform.tags["name"] %></td>
+ <td></td>
+ <td></td>
+ <% end %>
+ </tr>
+ <% end %>
+ <% end %>
+ </tbody>
+ </table>
+ <hr style="margin: 2rem 0" />
+ <% end %>
+</main>
M lib/triglav_web/views/zet/routes_view.ex => lib/triglav_web/views/zet/routes_view.ex +1 -0
@@ 5,6 5,7 @@ defmodule TriglavWeb.Zet.RoutesView do
def title("index.html", _), do: "Routes"
def title("detail.html", assigns), do: "Route ##{assigns.route.id}"
+ def title("match.html", assigns), do: "Matching ##{assigns.relation.id}"
def render_relation_hierarchy(hierarchy) do
~E"""