~ihabunek/triglav

1a8ba58407fc58b933347c9bec5bcb4358191cbb — Ivan Habunek 3 months ago f66c431
Use derived data for route detail view
M assets/css/app.scss => assets/css/app.scss +11 -5
@@ 44,6 44,12 @@ table {

  td.success {
    background-color: rgba($green, 0.2);
    border-bottom: 1px solid rgba($green, 0.4);
  }

  td.error {
    background-color: rgba($red, 0.2);
    border-bottom: 1px solid rgba($red, 0.4);
  }

  th {


@@ 51,7 57,7 @@ table {
    text-align: left;
  }

  tr:hover {
  tbody > tr:hover {
    background-color: WhiteSmoke;
  }
}


@@ 112,11 118,11 @@ summary {
  }

  &.error {
    background-color: rgba($red, 0.3);
    background-color: rgba($red, 0.2);
  }

  &.warning {
    background-color: rgba($orange, 0.3);
    background-color: rgba($orange, 0.2);
  }

  &.success {


@@ 186,8 192,8 @@ ul.breadcrumbs {
.hidden { visibility: hidden; }
.w-full { width: 100%; }

.bg-red { background-color: rgba($red, 0.3); }
.bg-green { background-color: rgba($green, 0.3); }
.bg-red { background-color: rgba($red, 0.2); }
.bg-green { background-color: rgba($green, 0.2); }

.text-bold { font-weight: bold; }
.text-smaller { font-size: 0.9rem; }

M assets/js/routes.js => assets/js/routes.js +13 -18
@@ 2,38 2,33 @@ import "leaflet/dist/leaflet.css"
import {
  CircleMarker,
  Control,
  FeatureGroup,
  GeoJSON,
  Icon,
  Map,
  Marker,
  TileLayer,
} from "leaflet"

const map = new Map("routes-map").setView([45.8, 16], 10)

const map = new Map("routes-map")
const tilesUrl = "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
const attribution =
  '&copy <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
const tiles = new TileLayer(tilesUrl, { attribution })

tiles.addTo(map)
const zetStopsGJS = JSON.parse(document.getElementById("zet-stops-geojson").innerHTML)
const trips = JSON.parse(document.getElementById("trips-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] })

const markers = []
for (const stop of stops) {
  const marker = new Marker(stop, { icon: icon, title: stop.name })
  markers.push(marker)
}

const zetStops = new FeatureGroup(markers).addTo(map)
const zetStops = L.geoJSON(zetStopsGJS, {
  pointToLayer: (feature, latlng) => {
    const title = `#${feature.properties.id}: ${feature.properties.name}`
    return new Marker(latlng, { icon, title })
  }
}).addTo(map)

const layers = { "ZET Stops": zetStops }

for (const name of Object.keys(routes)) {
  const way = L.geoJSON(routes[name], {
for (const name of Object.keys(trips)) {
  const way = L.geoJSON(trips[name], {
    style: feature => ({
      color: feature.properties.color
    }),


@@ 54,6 49,6 @@ for (const name of Object.keys(routes)) {
  layers[name] = way
}

new TileLayer(tilesUrl, { attribution }).addTo(map)
new Control.Layers({}, layers).addTo(map)

map.fitBounds(zetStops.getBounds())

M lib/triglav/derived/public_transport.ex => lib/triglav/derived/public_transport.ex +35 -22
@@ 20,6 20,14 @@ defmodule Triglav.Derived.PublicTransport do
    from(t in Trip) |> Repo.all()
  end

  def list_route_trips(route_id) do
    from(t in Trip,
      where: t.route_id == ^route_id,
      preload: [:relation, :route, platforms: [:node, :way, :stop]]
    )
    |> Repo.all()
  end

  def list_platforms() do
    from(p in Platform,
      join: t in Trip,


@@ 60,22 68,22 @@ defmodule Triglav.Derived.PublicTransport do

  defp persist(trips, platforms) do
    Multi.new()
    |> Multi.delete_all(:delete_trips, Trip)
    |> persist_trips(trips)
    |> Multi.delete_all(:delete_platforms, Platform)
    |> persist_platforms(platforms)
    |> Multi.delete_all(:delete_trips, Trip)
    |> Multi.insert_all(:insert_trips, Trip, trips)
    |> Multi.insert_all(:insert_platforms, Platform, platforms)
    |> add_distances()
    |> Repo.transaction()
  end

  defp persist_trips(multi, trips) do
    Enum.reduce(trips, multi, fn trip, multi ->
      Multi.insert(multi, {:trip, trip.relation.id}, trip)
    end)
  end

  defp persist_platforms(multi, platforms) do
    Enum.reduce(platforms, multi, fn platform, multi ->
      Multi.insert(multi, {:platform, platform.relation_id, platform.sequence_id}, platform)
  defp add_distances(multi) do
    Multi.run(multi, :add_distances, fn repo, _changes ->
      repo.query("""
        UPDATE public_transport_platforms p
        SET distance_from_route = st_distance(t.geometry::geography, p.geometry::geography)
        FROM public_transport_trips t
        WHERE t.id = p.trip_id;
      """)
    end)
  end



@@ 83,6 91,7 @@ defmodule Triglav.Derived.PublicTransport do
    {geometry, exact} = trip_geometry(relation, ways_map)
    stop_ids = trip_stop_ids(relation, platform_member_map)
    route = Map.get(routes_map, Relation.ref(relation))
    route_id = if route, do: route.id

    sample_trip_id =
      with %Route{} <- route,


@@ 90,9 99,10 @@ defmodule Triglav.Derived.PublicTransport do
        distinct_trip.sample_trip_id
      end

    %Trip{
      relation: relation,
      route: route,
    %{
      id: relation.id,
      relation_id: relation.id,
      route_id: route_id,
      geometry: geometry,
      exact: exact,
      stop_ids: stop_ids,


@@ 101,12 111,14 @@ defmodule Triglav.Derived.PublicTransport do
  end

  defp generate_platform(platform_member, stops_map, routes_map, relations_map) do
    node = if platform_member.member_type == "N", do: platform_member.member
    way = if platform_member.member_type == "W", do: platform_member.member
    node_id = if platform_member.member_type == "N", do: platform_member.member_id
    way_id = if platform_member.member_type == "W", do: platform_member.member_id
    stop = Map.get(stops_map, platform_member.member.tags["gtfs:stop_id"])
    stop_id = if stop, do: stop.id

    relation = Map.fetch!(relations_map, platform_member.relation_id)
    route = Map.get(routes_map, relation.tags["ref"])
    route_id = if route, do: route.id

    geometry =
      case platform_member.member_type do


@@ 114,12 126,13 @@ defmodule Triglav.Derived.PublicTransport do
        "W" -> platform_member.member.linestring
      end

    %Platform{
    %{
      relation_id: platform_member.relation_id,
      node: node,
      way: way,
      stop: stop,
      route: route,
      trip_id: platform_member.relation_id,
      node_id: node_id,
      way_id: way_id,
      stop_id: stop_id,
      route_id: route_id,
      sequence_id: platform_member.sequence_id,
      geometry: geometry
    }

M lib/triglav/schemas/public_transport/platform.ex => lib/triglav/schemas/public_transport/platform.ex +1 -4
@@ 24,9 24,6 @@ defmodule Triglav.Schemas.PublicTransport.Platform do

    field :sequence_id, :integer
    field :geometry, Geo.PostGIS.Geometry

    # TODO: maybe make this a real field, we'd need to import data first, to get
    # it indexed, and then populate this field
    field :distance_from_route, :float, virtual: true
    field :distance_from_route, :integer
  end
end

M lib/triglav/schemas/public_transport/trip.ex => lib/triglav/schemas/public_transport/trip.ex +2 -0
@@ 7,6 7,7 @@ defmodule Triglav.Schemas.PublicTransport.Trip do

  alias Triglav.Schemas.Osmosis
  alias Triglav.Schemas.Zet
  alias Triglav.Schemas.PublicTransport.Platform

  @derive {Inspect, only: [:id, :relation_id, :sample_trip_id]}



@@ 15,6 16,7 @@ defmodule Triglav.Schemas.PublicTransport.Trip do
  schema "public_transport_trips" do
    belongs_to :relation, Osmosis.Relation
    belongs_to :route, Zet.Route, type: :binary
    has_many :platforms, Platform
    field :geometry, Geo.PostGIS.Geometry
    field :exact, :boolean
    field :stop_ids, {:array, :string}

M lib/triglav/schemas/zet/distinct_trip.ex => lib/triglav/schemas/zet/distinct_trip.ex +1 -1
@@ 7,7 7,7 @@ defmodule Triglav.Schemas.Zet.DistinctTrip do
  schema "v_distinct_trips" do
    field :route_id, :string
    field :direction_id, :integer
    field :stops, {:array, :string}
    field :stop_ids, {:array, :string}
    field :trip_count, :integer
    field :sample_trip_id, :binary
  end

M lib/triglav/schemas/zet/stop.ex => lib/triglav/schemas/zet/stop.ex +5 -0
@@ 1,5 1,6 @@
defmodule Triglav.Schemas.Zet.Stop do
  use Ecto.Schema
  alias Triglav.GeoJSON

  @primary_key false
  @schema_prefix :zet


@@ 17,4 18,8 @@ defmodule Triglav.Schemas.Zet.Stop do
    field :parent_station, :string
    field :geom, Geo.PostGIS.Geometry
  end

  def to_feature(%__MODULE__{} = stop) do
    GeoJSON.feature(stop.geom, Map.take(stop, [:id, :name]))
  end
end

M lib/triglav_web/controllers/zet/routes_controller.ex => lib/triglav_web/controllers/zet/routes_controller.ex +53 -81
@@ 1,11 1,14 @@
defmodule TriglavWeb.Zet.RoutesController do
  use TriglavWeb, :controller

  alias Triglav.Derived.PublicTransport
  alias Triglav.GeoJSON
  alias Triglav.Import.Geofabrik
  alias Triglav.Osm
  alias Triglav.Osm.Router
  alias Triglav.Repo
  alias Triglav.Schemas.Osmosis.Relation
  alias Triglav.Schemas.PublicTransport.{Platform, Trip}
  alias Triglav.Schemas.Zet
  alias Triglav.Zet.Errors
  alias Triglav.Zet.Gtfs
  alias Triglav.Zet.Osmosis


@@ 47,71 50,44 @@ defmodule TriglavWeb.Zet.RoutesController do

  def detail(conn, %{"id" => id}) do
    route = Gtfs.get_route(id)
    relations = Osmosis.list_public_transport_relations(ref: id, members: true)
    relations = Osmosis.list_public_transport_relations(ref: id)
    hierarchy = make_hierarchy(relations)

    errors = Errors.list_unresolved(route_id: id)
    relation_errors = Enum.filter(errors, & &1.relation_id) |> Enum.group_by(& &1.relation_id)
    route_errors = Enum.filter(errors, &is_nil(&1.relation_id))

    platform_members =
      relations
      |> Enum.map(& &1.id)
      |> Osmosis.list_platform_members()
      |> Enum.group_by(& &1.relation_id)
    derived_trips = PublicTransport.list_route_trips(id)
    zet_stops = Gtfs.fetch_distinct_stops(route)
    zet_stops_map = Map.new(zet_stops, &{&1.id, &1})

    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

    annotated_trips =
      trips
    zet_trips =
      Gtfs.list_distinct_trips(route_id: route.id)
      |> Enum.map(fn trip ->
        Map.put(trip, :matched_relation, Map.get(relations_by_stop_ids, trip.stops))
      end)
      |> Enum.group_by(& &1.direction_id)
        matched_trip = Enum.find(derived_trips, &(&1.sample_trip_id == trip.sample_trip_id))
        matched_relation = if matched_trip, do: matched_trip.relation
        stops = Enum.map(trip.stop_ids, &Map.fetch!(zet_stops_map, &1))

    annotated_relations =
      Enum.map(relations_by_stop_ids, fn {stops, relation} ->
        Map.put(relation, :matched_trip, Map.get(trips_by_stop_ids, stops))
        trip
        |> Map.put(:matched_relation, matched_relation)
        |> Map.put(:stops, 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 =
      route
      |> Gtfs.fetch_distinct_stops()
      |> Enum.map(&Map.take(&1, [:id, :name, :lat, :lon]))
      |> Jason.encode!()
      |> Enum.group_by(& &1.direction_id)

    routes_geojson =
      routes_feature_collecton(relations, platform_members)
      |> Jason.encode!()
    trips_geojson = trips_feature_collecton(derived_trips) |> Jason.encode!()
    zet_stops_geojson = zet_stops_feature_collection(zet_stops) |> Jason.encode!()

    render(conn, "detail.html",
      conn: conn,
      derived_trips: derived_trips,
      hierarchy: hierarchy,
      platform_members: platform_members,
      relation_errors: relation_errors,
      relations: annotated_relations,
      relations: relations,
      route: route,
      route_errors: route_errors,
      routes_geojson: routes_geojson,
      stops: stops,
      stops_json: stops_json,
      trips: annotated_trips
      trips_geojson: trips_geojson,
      zet_stops_geojson: zet_stops_geojson,
      zet_trips: zet_trips
    )
  end



@@ 129,7 105,7 @@ defmodule TriglavWeb.Zet.RoutesController do

    stops =
      trips
      |> Enum.map(& &1.stops)
      |> Enum.map(& &1.stop_ids)
      |> Enum.concat()
      |> Enum.uniq()
      |> Gtfs.list_stops_by_id()


@@ 139,7 115,7 @@ defmodule TriglavWeb.Zet.RoutesController do

    diffs =
      for trip <- trips do
        diffs = List.myers_difference(relation_stop_ids, trip.stops)
        diffs = List.myers_difference(relation_stop_ids, trip.stop_ids)
        %{trip: trip, diffs: diffs, eq_count: count_eq(diffs)}
      end
      |> Enum.sort_by(& &1.eq_count, &>=/2)


@@ 190,50 166,44 @@ defmodule TriglavWeb.Zet.RoutesController do
    end
  end

  defp routes_feature_collecton(relations, platform_members) do
  defp zet_stops_feature_collection(zet_stops) do
    zet_stops
    |> Enum.map(&Zet.Stop.to_feature/1)
    |> GeoJSON.feature_collection()
  end

  defp trips_feature_collecton(trips) do
    colors = @route_colors |> Enum.map(fn {_, v} -> v end) |> Stream.cycle()

    relations
    |> Enum.filter(&Relation.is_route/1)
    trips
    |> Enum.zip(colors)
    |> Map.new(fn {relation, color} ->
      name = "#{relation.tags["name"]} (##{relation.id})"
      platforms = Map.get(platform_members, relation.id, [])

      route_feature = route_feature(relation, color)
      platform_features = Enum.map(platforms, &platform_feature(&1, color))
      collection = GeoJSON.feature_collection([route_feature | platform_features])
    |> Map.new(fn {trip, color} ->
      name = "#{trip.relation.tags["name"]} (##{trip.relation.id})"
      trip_feature = trip_feature(trip, color)
      platform_features = Enum.map(trip.platforms, &platform_feature(&1, color))
      collection = GeoJSON.feature_collection([trip_feature | platform_features])

      {name, collection}
    end)
  end

  defp platform_feature(member, color) do
    stop_id = member.member.tags["gtfs:stop_id"]
    platform_color = if stop_id, do: color, else: @error_color
  defp platform_feature(%Platform{} = platform, color) do
    platform_color = if platform.stop_id, do: color, else: @error_color
    member = platform.node || platform.way

    GeoJSON.to_feature(member.member, %{
      name: member.member.tags["name"],
      relation_id: member.relation_id,
      sequence_id: member.sequence_id,
      gtfs_stop_id: stop_id,
    GeoJSON.to_feature(member, %{
      name: member.tags["name"],
      relation_id: platform.relation_id,
      sequence_id: platform.sequence_id,
      gtfs_stop_id: platform.stop_id,
      color: platform_color
    })
  end

  defp route_feature(relation, color) do
    ordered_ways = Osm.list_ordered_ways(relation)

    # Attempt to get route as a single linestring, fall back to route segments
    route_geometry =
      case Router.route_ways(ordered_ways) do
        {:ok, linestring} -> linestring
        {:error, _} -> GeoJSON.ways_to_multilinestring(ordered_ways)
      end

    GeoJSON.feature(route_geometry, %{
      id: relation.tags["id"],
      type: relation.tags["type"],
  defp trip_feature(%Trip{} = trip, color) do
    GeoJSON.feature(trip.geometry, %{
      id: trip.relation.tags["id"],
      type: trip.relation.tags["type"],
      color: color
    })
  end


@@ 245,6 215,8 @@ defmodule TriglavWeb.Zet.RoutesController do
    route_masters =
      route_masters
      |> Enum.map(fn route_master ->
        route_master = Repo.preload(route_master, :members)

        contained_routes =
          Enum.map(route_master.members, fn member ->
            Enum.find(routes, &(&1.id == member.member_id))

M lib/triglav_web/templates/zet/routes/detail.html.eex => lib/triglav_web/templates/zet/routes/detail.html.eex +75 -54
@@ 6,14 6,16 @@
    max-width:  1024px;
    margin: 1rem 0;
  }

  .bl { border-left: 10px solid lightgray }
</style>

<script id="stops-json" type="application/json">
  <%= raw(@stops_json) %>
<script id="trips-geojson" type="application/json">
  <%= raw(@trips_geojson) %>
</script>

<script id="routes-geojson" type="application/json">
  <%= raw(@routes_geojson) %>
<script id="zet-stops-geojson" type="application/json">
  <%= raw(@zet_stops_geojson) %>
</script>

<main role="main" class="container-wide">


@@ 66,59 68,75 @@
  <% end %>

  <section>
    <h3>Route relations</h3>

    <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;">
    <%= for trip <- @derived_trips do %>
      <h3 style="margin-top: 1rem"><%= osm_link(trip.relation, name: true) %></h3>
      <div style="margin-left: 1rem;">
        <%= if length(trip.platforms) > 0 do %>
          <table>
            <thead>
              <tr>
                <th colspan="4" class="text-center">
                  <%= osm_link(relation, name: true) %>
                </th>
                <th colspan="6" class="text-center">OSM Platform</th>
                <th colspan="2" class="text-center bl">GTFS Stop</th>
              </tr>
              <tr>
                <th>#</th>
                <th>Platform</th>
                <th>Name</th>
                <th>gtfs:stop_id</th>
                <th>Stop ID</th>
                <th>From route</th>
                <th>JOSM</th>
                <th class="bl">ID</th>
                <th>Name</th>
              </tr>
            </thead>
            <tbody>
              <%= for m <- members do %>
              <%= for platform <- trip.platforms do %>
              <% member = platform.node || platform.way %>
              <tr>
                <td><%= osm_link(m.member) %></td>
                <td><%= m.member.tags["name"] %></td>
                <td class="<%= if !m.member.tags["gtfs:stop_id"], do: "bg-red" %>">
                  <%= if m.member.tags["gtfs:stop_id"] do %>
                    <%= m.member.tags["gtfs:stop_id"] %>
                  <% else %>
                    Missing
                  <% end %>
                <td><%= platform.sequence_id %></td>
                <td><%= osm_link(member) %></td>
                <td><%= member.tags["name"] %></td>
                <td class="<%= if !platform.stop_id, do: "error" %>">
                  <%= platform.stop_id || "Missing" %>
                </td>
                <td><%= josm_load_objects([m.member]) %></td>
                <td class="text-right <%= if platform.distance_from_route > 20, do: "error" %>">
                  <%= platform.distance_from_route %>m
                </td>
                <td><%= josm_load_objects([member]) %></td>
                <%= if platform.stop do %>
                  <td class="bl"><%= platform.stop.id %></td>
                  <td><%= platform.stop.name %></td>
                <% else %>
                  <td class="bl"></td>
                  <td></td>
                <% end %>
              </tr>
              <% end %>
            </tbody>
            <tfoot>
              <%= if trip.sample_trip_id do %>
                <tr>
                  <td colspan="8" class="success text-green text-center">
                    ✔ Matches ZET trip <b><%= trip.sample_trip_id %></b>
                  </td>
                </tr>
              <% else %>
                <tr>
                  <td class="error text-center" colspan="8">
                    ✖ No GTFS trip matched.
                    <a href="<%= Routes.zet_routes_path(TriglavWeb.Endpoint, :match, @route.id, trip.relation.id) %>">
                      Find a match
                    </a>
                  </td>
                </tr>
              <% end %>
            </tfoot>
          </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.
              <a href="<%= Routes.zet_routes_path(TriglavWeb.Endpoint, :match, @route.id, relation.id) %>">
                Find a match
              </a>
            </div>
          <% end %>
        </div>
      <% end %>
    </div>
        <% else %>
          <div class="callout error">This relation does not have any platforms</div>
        <% end %>
      </div>
    <% end %>
  </section>

  <hr />


@@ 128,13 146,13 @@
  <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 %>
  <%= for {direction_id, trips} <- @zet_trips do %>
    <h3>Direction #<%= direction_id %></h3>

    <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.sample_trip_id %></b> (<%= trip.trip_count %> departures)</p>
          <table>
            <thead>
              <tr>


@@ 145,8 163,7 @@
              </tr>
            </thead>
            <tbody>
              <%= for {stop_id, index} <- Enum.with_index(trip.stops) do %>
              <% stop = Map.get(@stops, stop_id) %>
              <%= for {stop, index} <- Enum.with_index(trip.stops) do %>
              <tr>
                <td><%= index %>.</td>
                <td><%= stop.id %></td>


@@ 155,18 172,22 @@
              </tr>
              <% end %>
            </tbody>
            <tfoot>
              <tr>
                <%= if trip.matched_relation do %>
                  <td class="success text-center" colspan="4">
                    <span class="text-green">✔ Matches relation</span>
                    <%= osm_link(trip.matched_relation) %>
                  </td>
                <% else %>
                  <td class="error text-center" colspan="4">
                    ✖ No relation matched.
                  </td>
                <% end %>
              </tr>
            </tfoot>
          </table>

          <%= if trip.matched_relation do %>
            <div class="callout">
              <span class="text-green">✔ Matched relation</span><br />
              <%= osm_link(trip.matched_relation, [josm: true]) %>
            </div>
          <% else %>
            <div class="callout error">
              ✖ No relation matched.
            </div>
          <% end %>
        </div>
      <% end %>
    </div>

M lib/triglav_web/templates/zet/routes/match.html.eex => lib/triglav_web/templates/zet/routes/match.html.eex +4 -4
@@ 43,13 43,13 @@
            <%= if op == :ins do %>
              <td></td>
              <td></td>
              <td class="bg-green"><%= id %></td>
              <td class="bg-green"><%= stop.name %></td>
              <td class="success"><%= id %></td>
              <td class="success"><%= stop.name %></td>
            <% end %>

            <%= if op == :del do %>
              <td class="bg-red"><%= id || "???" %></td>
              <td class="bg-red"><%= platform_name %></td>
              <td class="error"><%= id || "???" %></td>
              <td class="error"><%= platform_name %></td>
              <td></td>
              <td></td>
            <% end %>

M priv/gtfs/transform.sql => priv/gtfs/transform.sql +3 -3
@@ 9,7 9,7 @@ WITH trips AS (
  SELECT t.route_id,
         t.trip_id,
         t.direction_id,
         array_agg(s.stop_id ORDER BY st.stop_sequence) AS stops
         array_agg(s.stop_id ORDER BY st.stop_sequence) AS stop_ids
  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


@@ 17,9 17,9 @@ WITH trips AS (
)
  SELECT route_id,
         direction_id,
         stops,
         stop_ids,
         count(*) AS trip_count,
         min(trip_id) AS sample_trip_id
    FROM trips
GROUP BY route_id, direction_id, stops
GROUP BY route_id, direction_id, stop_ids
ORDER BY route_id::integer, direction_id, trip_count DESC;

M priv/repo/migrations/20210227082725_create_public_transport_tables.exs => priv/repo/migrations/20210227082725_create_public_transport_tables.exs +3 -2
@@ 12,7 12,7 @@ defmodule Triglav.Repo.Migrations.CreatePublicTransportTables do
    end

    create table("public_transport_platforms") do
      add :trip_id, references("public_transport_trips")
      add :trip_id, references("public_transport_trips"), null: false
      add :relation_id, :bigint, null: false
      add :route_id, :string
      add :stop_id, :string


@@ 20,6 20,7 @@ defmodule Triglav.Repo.Migrations.CreatePublicTransportTables do
      add :way_id, :bigint
      add :sequence_id, :integer, null: false
      add :geometry, :geometry, null: false
      add :distance_from_route, :integer
    end

    create index("public_transport_trips", [:sample_trip_id], unique: true)


@@ 30,7 31,7 @@ defmodule Triglav.Repo.Migrations.CreatePublicTransportTables do
  end

  def down do
    drop table("public_transport_trips")
    drop table("public_transport_platforms")
    drop table("public_transport_trips")
  end
end