~ihabunek/triglav

867bf42a0dceba22e320c93792249890dea21659 — Ivan Habunek 10 months ago ae13c4c
Add poi index and nodes pages
M assets/css/app.css => assets/css/app.css +3 -0
@@ 24,6 24,8 @@
  section + section { @apply mt-4 }

  ul { @apply list-disc ml-4 }
  summary { cursor: pointer; text-decoration: underline }
  details[open] > summary { margin-bottom: 4px }
}

@layer components {


@@ 44,6 46,7 @@
  }
  .button:hover { background-color: Gainsboro }
  .button[disabled] { cursor: not-allowed }
  .button.small { @apply h-6 px-2 }

  .box {
    border: 1px solid black;

A lib/triglav_web/components/poi.ex => lib/triglav_web/components/poi.ex +118 -0
@@ 0,0 1,118 @@
defmodule TriglavWeb.Components.Poi do
  use Phoenix.Component

  def tag_diff(assigns) do
    poi_node = assigns.node
    osm_element = if poi_node.mapping, do: poi_node.mapping.osm_node || poi_node.mapping.osm_way

    if osm_element do
      diffs = calc_diffs(assigns.node, osm_element)

      summary =
        diffs
        |> Enum.frequencies_by(fn {_, _, _, cmd} -> cmd end)
        |> Map.drop([:delete])
        |> Enum.map(fn {cmd, count} ->
          case cmd do
            :insert -> "#{count} new"
            :diff -> "#{count} different"
            :same -> "#{count} same"
            _ -> nil
          end
        end)
        |> Enum.join(", ")

      ~H"""
      <details>
        <summary><%= summary %></summary>

        <table class="border border-gray-300">
          <thead>
            <tr>
              <th>Key</th>
              <th>Source value</th>
              <th>OSM value</th>
            </tr>
          </thead>
          <tbody>
            <%= for {key, poi_value, osm_value, cmd} <- diffs do %>
              <tr>
                  <td><%= key %></td>
                <%= if cmd == :same do %>
                  <td class="text-gray-600"><%= poi_value %></td>
                  <td class="text-gray-600"><%= osm_value %></td>
                  <td></td>
                <% end %>
                <%= if cmd == :diff do %>
                  <td class="text-red-900"><%= poi_value %></td>
                  <td class="text-red-900"><%= osm_value %></td>
                  <td></td>
                <% end %>
                <%= if cmd == :insert do %>
                  <td class="text-green-900"><%= poi_value %></td>
                  <td><%= osm_value %></td>
                  <td></td>
                <% end %>
                <%= if cmd == :delete do %>
                  <td><%= poi_value %></td>
                  <td class="text-gray-600"><%= osm_value %></td>
                  <td></td>
                <% end %>
              </tr>
            <% end %>
          </tbody>
        </table>
      </details>

      """
    else
      ~H"""
      <details>
        <summary>Show tags</summary>
        <table>
          <thead>
            <tr>
              <th>Key</th>
              <th>Value</th>
            </tr>
          </thead>
          <tbody>
            <%= for {key, value} <- poi_node.tags do %>
              <tr>
                <td><%= key %></td>
                <td><%= value %></td>
              </tr>
            <% end %>
          </tbody>
        </table>
      </details>
      """
    end
  end

  def calc_diffs(poi_node, osm_element) do
    poi_tags = poi_node.tags
    osm_tags = osm_element.tags

    keys =
      Map.keys(poi_tags)
      |> Enum.concat(Map.keys(osm_tags))
      |> Enum.uniq()
      |> Enum.sort()

    for key <- keys do
      poi_value = Map.get(poi_tags, key)
      osm_value = Map.get(osm_tags, key)

      cmd =
        cond do
          is_nil(osm_value) -> :insert
          is_nil(poi_value) -> :delete
          poi_value == osm_value -> :same
          true -> :diff
        end

      {key, poi_value, osm_value, cmd}
    end
  end
end

A lib/triglav_web/controllers/poi_controller.ex => lib/triglav_web/controllers/poi_controller.ex +21 -0
@@ 0,0 1,21 @@
defmodule TriglavWeb.PoiController do
  use TriglavWeb, :controller

  action_fallback TriglavWeb.FallbackController

  alias Triglav.Repo
  alias Triglav.Poi
  alias Triglav.Poi.Source

  def index(conn, _params) do
    sources = Triglav.Poi.Sources.all()
    render(conn, "index.html", sources: sources)
  end

  def nodes(conn, %{"source" => source}) do
    with {:ok, source} <- Repo.fetch_by(Source, slug: source) do
      nodes = Poi.nodes(source) |> Repo.preload(mapping: [:osm_node, :osm_way])
      render(conn, "nodes.html", source: source, nodes: nodes)
    end
  end
end

M lib/triglav_web/router.ex => lib/triglav_web/router.ex +3 -0
@@ 26,6 26,9 @@ defmodule TriglavWeb.Router do
    get "/public_transport/:operator/routes/:ref/match/:relation_id", PublicTransportController, :match
    get "/public_transport/:operator/stops", PublicTransportController, :stops

    get "/poi/", PoiController, :index
    get "/poi/:source", PoiController, :nodes

    scope "/hps", alias: Hps, as: :hps do
      get "/tracks", TracksController, :index
      get "/tracks/:id", TracksController, :detail

M lib/triglav_web/templates/layout/app.html.heex => lib/triglav_web/templates/layout/app.html.heex +5 -0
@@ 20,6 20,11 @@
          <% else %>
            <a class="text-base text-white px-5 py-5" href="/public_transport">Public Transport</a>
          <% end %>
          <%= if active?(@conn, "/poi") do %>
            <a class="text-base text-white px-5 py-5 font-bold bg-purple" href="/poi">POI</a>
          <% else %>
            <a class="text-base text-white px-5 py-5" href="/poi">POI</a>
          <% end %>
        </div>
      </nav>
    </header>

A lib/triglav_web/templates/poi/index.html.heex => lib/triglav_web/templates/poi/index.html.heex +34 -0
@@ 0,0 1,34 @@
<main role="main" class="container">
  <h1>Points of Interest</h1>

  <%= if length(@sources) > 0 do %>
    <table>
      <tbody>
        <tr>
          <th>Source</th>
          <th class="text-right">POIs</th>
          <th class="text-right">Mapped</th>
          <th class="text-right">Mapped %</th>
          <th class="text-right">Last Updated</th>
        </tr>
        <%= for source <- @sources do %>
          <tr>
            <td><a href={Routes.poi_path(@conn, :nodes, source.slug)}><%= source.name %></a></td>
            <td class="text-right"><%= source.node_count %></td>
            <td class="text-right"><%= source.mapping_count %></td>
            <td class="text-right">
              <%= if source.node_count > 0 do %>
                <%= round(100 * source.mapping_count / source.node_count) %>%
              <% else %>
                0%
              <% end %>
            </td>
            <td><%= source.updated_at %></td>
          </tr>
        <% end %>
      </tbody>
    </table>
  <% else %>
    <p>No data loaded. Run an import script.</p>
  <% end %>
</main>

A lib/triglav_web/templates/poi/nodes.html.heex => lib/triglav_web/templates/poi/nodes.html.heex +65 -0
@@ 0,0 1,65 @@
<main role="main" class="container-wide">
  <h1><%= @source.name %> POI</h1>

  <%= if length(@nodes) > 0 do %>
    <div class="flex space-x-4 mb-4">
      <button id="expand-all" class="button small">Expand all</button>
      <button id="collapse-all" class="button small">Collapse all</button>
    </div>

    <table>
      <tr>
        <th>Ref</th>
        <th>Name</th>
        <th>OSM element</th>
        <th>Distance</th>
        <th>Tags</th>
        <th>JOSM</th>
      </tr>
      <%= for node <- @nodes do %>
        <% {lon, lat} = node.geometry.coordinates %>
        <% osm_element = if node.mapping, do: node.mapping.osm_node || node.mapping.osm_way %>
        <tr>
          <td><%= node.ref %></td>
          <td><%= node.name %></td>
          <%= if node.mapping do %>
            <td><%= osm_link(osm_element) %></td>
            <td class="text-right"><%= round(node.mapping.distance) %>m</td>
          <% else %>
            <td colspan="2">
              <%= josm_add_node(lat, lon, node.tags) %>
              <%= josm_add_square_way(lat, lon, node.tags) %>
            </td>
          <% end %>
          <td>
            <.tag_diff node={node} />
          </td>
          <td class="whitespace-nowrap">
            <%= josm_zoom(lat, lon) %>
            <%= if osm_element do %>
              <%= josm_load_objects([osm_element]) %>
            <% end %>
          </td>
        </tr>
      <% end %>
    </table>
  <% else %>
    <p>No nodes loaded for this source.</p>
  <% end %>
</main>

<script>
document.getElementById("expand-all").addEventListener("click", () => {
  const elements = document.getElementsByTagName("details")
  for (const element of elements) {
    element.setAttribute("open", true)
  }
})

document.getElementById("collapse-all").addEventListener("click", () => {
  const elements = document.getElementsByTagName("details")
  for (const element of elements) {
    element.removeAttribute("open")
  }
})
</script>
\ No newline at end of file

A lib/triglav_web/views/poi_view.ex => lib/triglav_web/views/poi_view.ex +8 -0
@@ 0,0 1,8 @@
defmodule TriglavWeb.PoiView do
  use TriglavWeb, :view

  import TriglavWeb.Components.Poi

  def title("index.html", _), do: "POI"
  def title("source.html", %{"source" => source}), do: "POI #{source}"
end