~ihabunek/triglav

ref: 1a8ba58407fc58b933347c9bec5bcb4358191cbb triglav/lib/triglav/zet/osmosis.ex -rw-r--r-- 5.3 KiB
1a8ba584Ivan Habunek Use derived data for route detail view 9 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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
defmodule Triglav.Zet.Osmosis do
  @moduledoc """
  Loads public relation data from the osmosis schema.
  """
  alias Triglav.Repo
  alias Triglav.Schemas.Osmosis.Node
  alias Triglav.Schemas.Osmosis.Relation
  alias Triglav.Schemas.Osmosis.RelationMember
  alias Triglav.Schemas.Osmosis.ReplicationChanges
  alias Triglav.Schemas.Osmosis.Way

  import Ecto.Query

  @doc """
  Returns a list of relations which are tagged with `type=route` or
  `type=route_master` and `network=ZET`.

  Options:

  * ref - filter by `ref` tag
  * type - filter by `type` tag
  * members - if set to true, will preload related RelationMember records
  """
  def list_public_transport_relations(opts \\ []) do
    pt_relations()
    |> maybe_filter_by_tag(:ref, opts)
    |> maybe_filter_by_tag(:type, opts)
    |> maybe_preload_members(opts)
    |> Repo.all()
  end

  def list_replication_changes(opts \\ []) do
    limit = Keyword.get(opts, :limit, 100)

    from(rc in ReplicationChanges, order_by: [desc: :tstamp], limit: ^limit)
    |> Repo.all()
  end

  @doc """
  For a given list of relations returns all included members of the :way variety
  which do not have a role (to skip stations entered as ways).
  """
  @spec list_ways([Relation.t()], Keyword.t()) :: Way.t()
  def list_ways(relations, opts \\ []) do
    geojson = Keyword.get(opts, :geojson, false)

    ids =
      relations
      |> Enum.map(fn r ->
        r.members
        |> Enum.filter(&(&1.member_type == "W" and &1.member_role == ""))
        |> Enum.map(& &1.member_id)
      end)
      |> List.flatten()

    if geojson do
      from(w in Way,
        select: %{w | geojson: fragment("st_asgeojson(?)", w.linestring)},
        where: w.id in ^ids
      )
      |> Repo.all()
    else
      from(w in Way, where: w.id in ^ids)
      |> Repo.all()
    end
  end

  @type stop_id :: String.t()

  @spec list_gtfs_stop_ids(Relation.id() | [Relation.id()]) :: %{Relation.id() => [stop_id]}

  def list_gtfs_stop_ids(relation_id) when is_integer(relation_id) do
    list_gtfs_stop_ids([relation_id])
    |> Map.get(relation_id)
  end

  def list_gtfs_stop_ids(relation_ids) do
    Repo.query!(
      """
        SELECT rm.relation_id,
               array_agg(
                 CASE
                   WHEN rm.member_type = 'W' THEN w.tags->'gtfs:stop_id'
                   WHEN rm.member_type = 'N' THEN n.tags->'gtfs:stop_id'
                 END
                 ORDER BY sequence_id
               ) as gtfs_stop_ids
        FROM osmosis.relation_members rm
        LEFT JOIN osmosis.ways w on rm.member_type = 'W' and w.id = rm.member_id
        LEFT JOIN osmosis.nodes n on rm.member_type = 'N' and n.id = rm.member_id
        WHERE rm.relation_id = ANY($1)
        AND rm.member_role ilike 'platform%'
        GROUP BY rm.relation_id
        ORDER BY rm.relation_id
      """,
      [relation_ids]
    )
    |> Map.get(:rows)
    |> Map.new(&List.to_tuple/1)
  end

  @doc """
  For a given relation id or list of ids returns a list of relation members with
  a platform role.
  """
  @spec list_platform_members(Relation.id() | [Relation.id()]) :: [RelationMember.t()]

  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
    from(m in RelationMember,
      left_join: n in Node,
      on: m.member_type == "N" and m.member_id == n.id,
      left_join: w in Way,
      on: m.member_type == "W" and m.member_id == w.id,
      where: m.relation_id in ^relation_ids and ilike(m.member_role, "platform%"),
      select: {m, n, w},
      order_by: m.sequence_id
    )
    |> Repo.all()
    |> Enum.map(fn {member, node, way} -> %{member | member: node || way} end)
  end

  @doc """
  Returns a list of all nodes with platform* roles which are members of any of
  the given relations.
  """
  @spec list_platform_nodes([Relation.t()]) :: [Node.t()]
  def list_platform_nodes(relations) do
    ids = platform_node_ids(relations)

    from(n in Node, where: n.id in ^ids, order_by: [asc: fragment("tags->?", "name")])
    |> Repo.all()
  end

  @spec list_platform_ways([Relation.t()]) :: [Way.t()]
  def list_platform_ways(relations) do
    ids = platform_way_ids(relations)

    from(w in Way, where: w.id in ^ids, order_by: [asc: fragment("tags->?", "name")])
    |> Repo.all()
  end

  defp platform_node_ids(relations), do: platform_member_ids(relations, "N")
  defp platform_way_ids(relations), do: platform_member_ids(relations, "W")

  defp platform_member_ids(relations, member_type) do
    relations
    |> Enum.map(fn r ->
      r.members
      |> Enum.filter(
        &(&1.member_type == member_type and String.starts_with?(&1.member_role, "platform"))
      )
      |> Enum.map(& &1.member_id)
    end)
    |> List.flatten()
  end

  defp pt_relations() do
    from(r in Relation,
      where:
        fragment("tags->?", "type") in ["route", "route_master"] and
          fragment("tags->?", "network") == "ZET"
    )
  end

  defp maybe_filter_by_tag(query, tag, opts) do
    value = Keyword.get(opts, tag)
    tag = to_string(tag)

    if value do
      where(query, [r], fragment("tags->?", ^tag) == ^value)
    else
      query
    end
  end

  defp maybe_preload_members(query, opts) do
    if Keyword.get(opts, :members, false) do
      preload(query, [r], :members)
    else
      query
    end
  end
end