LiveViewとGoogleMapAPIで作る GPS Logger short ver

本記事ではPhoenixのLiveViewでGoogleMapAPIを使用してGPS Loggerを作成していきます

今回は認証周りをすべて省いてLiveViewによるGoogleMapの更新のみに焦点を当てています

[LiveViewとGoogleMapAPIで作る GPS Logger 1](https://qiita.com/the_haigo/items/c1eaf7fde3f88eadb4be)
[LiveViewとGoogleMapAPIで作る GPS Logger 2](https://qiita.com/the_haigo/items/addc4e0e5c4ad398ca5c)
[LiveViewとGoogleMapAPIで作る GPS Logger 3](https://qiita.com/the_haigo/items/bae2203ffdb7f6963f72)
[LiveViewとGoogleMapAPIで作る GPS Logger short ver](https://qiita.com/the_haigo/items/9cb8d6446492c50d690c)
[LiveViewとGoogleMapAPIで作る GPS Logger Client watchOS APP + SwiftUI](https://qiita.com/the_haigo/items/6a8ddf84434cfa47a129)

# プロジェクト作成、DB,GoogleMapセットアップ

```
mix phx.new google_map --live
cd google_map
cd assets
npm install @googlemaps/js-api-loader
cd ..
mix phx.gen.schema Loggers.Point points lat:decimal lng:decimal
mix ecto.create
mix ecto.migrate
```

```assets/js/app.js
import "phoenix_html"
import {Socket} from "phoenix"
import topbar from "topbar"
import {LiveSocket} from "phoenix_live_view"
import Hooks from './hooks.js' // add this

let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
// modify below
let liveSocket = new LiveSocket("/live", Socket, {
  hooks: Hooks,
  params: {_csrf_token: csrfToken}
})

...
```

```assets/js/hooks.js
import { Loader } from "@googlemaps/js-api-loader";
let Hooks = {};

Hooks.Map = {
  mounted() {
    this.handleEvent("init_map", ({ points }) => {
      const loader = new Loader({
        apiKey: "your API key",
        version: "weekly",
      });

      loader.load().then(() => {
        const center = points.length == 0 ?
          { lat: 33.30639, lng: 130.41806 }
          :
          { lat: parseFloat(points[0].lat), lng: parseFloat(points[0].lng) }

        const map = new google.maps.Map(document.getElementById("map"), {
          center: center,
          zoom: 9,
        });
        window.map = map;

        points.forEach((point) => {
          this.addMarker(point)
        });
      });
    });
    this.handleEvent("created_point", ({ point }) => {
      this.addMarker(point)
    });
  },
  addMarker(point) {
    const marker = new google.maps.Marker({
      position: { lat: parseFloat(point.lat), lng: parseFloat(point.lng)},
      animation: google.maps.Animation.DROP,
      icon: {
          path: google.maps.SymbolPath.CIRCLE,
          fillColor: '#00F',
          fillOpacity: 0.6,
          strokeColor: '#00A',
          strokeOpacity: 0.9,
          strokeWeight: 1,
          scale: 2
      }
   })
   marker.setMap(window.map)
  }
};

export default Hooks;
```
# List, Create, Subscribe, Broadcast

```lib/google_map/loggers.ex
defmodule GoogleMap.Loggers do
  import Ecto.Query, warn: false
  alias GoogleMap.Repo

  alias GoogleMap.Loggers.Point

  def list_points do
      Repo.all(Point)
  end

  def create_point(attrs \\ %{}) do
    %Point{}
    |> Point.changeset(attrs)
    |> Repo.insert()
    |> broadcast(:created_point)
  end

  def subscribe(:map) do
    Phoenix.PubSub.subscribe(GoogleMap.PubSub, "map")
  end

  def broadcast({:ok, point}, :created_point = event) do
    Phoenix.PubSub.broadcast(GoogleMap.PubSub, "map", {event, point})
    {:ok, point}
  end
end
```
# mount時、座標登録時の処理

```lib/google_map_web/live/page_live.ex
defmodule GoogleMapWeb.PageLive do
  use GoogleMapWeb, :live_view
  alias GoogleMap.Loggers

  @impl true
  def mount(_params, _session, socket) do
    Loggers.subscribe(:map)
    points = Loggers.list_points |> Enum.map(fn p -> %{lat: p.lat, lng: p.lng} end)
    {
      :ok,
      socket
      |> push_event("init_map", %{points: points})
    }
  end

  @impl true
  def handle_info({:created_point, point}, socket) do
    {
      :noreply,
      socket
      |> push_event("created_point", %{ point: %{ lat: point.lat, lng: point.lng} })
    }
  end
end
```

```[lib/google_map_web/live/page_live.html.leex].html
<h1>Show Map</h1>
<section id="googleMap" phx-update="ignore" phx-hook="Map">
  <div id="map" style="height: 400px; border-radius:2em;"></div>
</section>
```
# 座標登録API

```lib/google_map_web/controllers/point_controller.ex
defmodule GoogleMapWeb.PointController do
  use GoogleMapWeb, :controller

  alias GoogleMap.Loggers
  alias GoogleMap.Loggers.Point

  def create(conn, point_params) do
    with {:ok, %Point{}} <- Loggers.create_point(point_params) do
      send_resp(conn, 200, "ok")
    end
  end
end
```

```lib/google_map_web/router.ex
defmodule GoogleMapWeb.Router do
...
  scope "/api", GoogleMapWeb do
    pipe_through :api

    resources "/points", PointController, only: [:create]
  end
...
end
```

# 最後に
いかがでしたでしょうか？
認証周りを除外すると少ないコードでGoogleMapのリアルタイム更新が実現できているのがわかります
GoogleMapAPIにはマーカーの描画だけではなく、scrollやハイライトなどいろいろあるので是非試してみてください
本記事は以上になります

次はGPSの送信側の記事をapple watch app, ReactNative辺りで書けたらと思います

# コード
https://github.com/thehaigo/live_map_short