~ihabunek/triglav

ref: 7bdb88d4d88886e08c456f7ee012d7a343a73731 triglav/lib/triglav/derived/public_transport.ex -rw-r--r-- 3.8 KiB
7bdb88d4Ivan Habunek Split validation from persisting errors 5 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
defmodule Triglav.Derived.PublicTransport do
  @moduledoc """
  Generates derived data for public transport.
  """

  alias Ecto.Multi
  alias Triglav.GeoJSON
  alias Triglav.Osm.Router
  alias Triglav.Repo
  alias Triglav.Schemas.Osmosis.Relation
  alias Triglav.Schemas.PublicTransport.{Trip, Platform}
  alias Triglav.Zet.Gtfs
  alias Triglav.Zet.Osmosis

  def generate() do
    relations = Osmosis.list_public_transport_relations(members: true, type: "route")
    relations_map = Map.new(relations, &{&1.id, &1})
    ways_map = Osmosis.list_ways(relations) |> Map.new(&{&1.id, &1})
    routes_map = Gtfs.list_routes() |> Map.new(&{&1.id, &1})
    platform_members = relations |> Enum.map(& &1.id) |> Osmosis.list_platform_members()
    platform_member_map = Map.new(platform_members, &{{&1.relation_id, &1.sequence_id}, &1})

    trips = Enum.map(relations, &generate_trip(&1, ways_map, routes_map, platform_member_map))

    stops_map =
      trips
      |> Enum.map(& &1.stop_ids)
      |> Enum.concat()
      |> Gtfs.list_stops_by_id()
      |> Map.new(&{&1.id, &1})

    platforms =
      platform_members
      |> Enum.sort_by(&{&1.relation_id, &1.sequence_id})
      |> Enum.map(&generate_platform(&1, stops_map, routes_map, relations_map))

    persist(trips, platforms)
    nil
  end

  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)
    |> 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)
    end)
  end

  defp generate_trip(relation, ways_map, routes_map, platform_member_map) 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))

    %Trip{
      relation: relation,
      route: route,
      geometry: geometry,
      exact: exact,
      stop_ids: stop_ids
    }
  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
    stop = Map.get(stops_map, platform_member.member.tags["gtfs:stop_id"])

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

    geometry =
      case platform_member.member_type do
        "N" -> platform_member.member.geom
        "W" -> platform_member.member.linestring
      end

    %Platform{
      relation_id: platform_member.relation_id,
      node: node,
      way: way,
      stop: stop,
      route: route,
      sequence_id: platform_member.sequence_id,
      geometry: geometry
    }
  end

  defp trip_stop_ids(relation, platform_member_map) do
    relation.members
    |> Enum.filter(&String.starts_with?(&1.member_role, "platform"))
    |> Enum.map(&Map.get(platform_member_map, {&1.relation_id, &1.sequence_id}))
    |> Enum.map(& &1.member.tags["gtfs:stop_id"])
  end

  defp trip_geometry(relation, ways_map) do
    ordered_ways =
      relation.members
      |> Enum.filter(&(&1.member_type == "W" and &1.member_role == ""))
      |> Enum.map(fn member -> Map.fetch!(ways_map, member.member_id) end)

    case Router.route_ways(ordered_ways) do
      {:ok, geometry} ->
        {geometry, true}

      {:error, _} ->
        geometry = GeoJSON.ways_to_multilinestring(ordered_ways)
        {geometry, false}
    end
  end
end