LiveViewとGoogleMapAPIで作る GPS Logger 3

本記事ではPhoenixのLiveViewでGoogleMapAPIを使用してGPS Loggerを作成していきます
その３ではマーカーの描画、リアルタイムアップデートを実装していきます

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

# Marker描画
描画するためにの座標(point)を
preload(:points)で関連先を取得します

```lib/live_map/loggers.ex
defmodule LiveMap.Loggers do
...
  def get_map_with_points!(id) do
    Map
    |> preload(:points)
    |> Repo.get!(id)
  end
...
end
```
jsで扱いやすいようにEnum.mapで変換してからpush_eventに渡していきます

```lib/live_map_web/live/map_live/show.ex
defmodule LiveMapWeb.MapLive.Show do
...
  @impl true
  def handle_params(%{"id" => id}, _, socket) do
    map = Loggers.get_map_with_points!(id)
    points = Enum.map(route.points, fn p -> %{lat: p.lat, lng: p.lng} end)
    {
      :noreply,
      socket
      |> assign(:page_title, page_title(socket.assigns.live_action))
      |> assign(:map, map)
      |> assign(:points, points)
      |> push_event("init_map", %{points: points})
    }
  end
...
end
```
init_map時にpointsがあった場合１つめのpointの座標をcenterにセットします
次にpointsをforEachでマーカーを描画していきます
通常のマーカーはでかいので青いドットに変更しています

```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(() => {
        // change below
        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;
        // add below
        points.forEach((point) => {
          this.addMarker(point)
        });
      });
    });
  },
  // add below
  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;
```
# real-time update
最後にリアルタイムアップデートの実装になります
PhoenixではデフォルトでPubSubが起動してるので、subscribeとbroadcastを追加するだけです
apiからcreate_pointが実行された後にbroadcastでLiveView側の関数を実行させます

```lib/live_map/loggers.ex
defmodule LiveMap.Loggers do
...
  def create_point(attrs \\ %{}) do
    %Point{}
    |> Point.changeset(attrs)
    |> Repo.insert()
    |> broadcast(:created_point) # add this
  end

  # add below
  def subscribe({:map, map_id}) do
    Phoenix.PubSub.subscribe(LiveMap.PubSub, "map:#{map_id}")
  end

  def broadcast({:ok, point}, :created_point = event) do
    Phoenix.PubSub.broadcast(LiveMap.PubSub, "map:#{point.map_id}", {event, point})
    {:ok, point}
  end
...
end
```
LiveView側ではmount時にsubscribeを実行し
handle_infoでcreate_pointのbroadcastで呼ばれたcreated_pointイベントを実行します
created_pointではassignsのpointsの更新とpush_eventで新しいマーカーを描画するJSの関数を実行します

```lib/live_map_web/live/map_live/show.ex
defmodule LiveMapWeb.MapLive.Show do
...
  @impl true
  def mount(%{"id" => id}, _session, socket) do
    Loggers.subscribe({:map, id}) # add this
    {:ok, socket}
  end

  # add below
  @impl true
  def handle_info({:created_point, point}, socket) do
    {
      :noreply,
      socket
      |> update(:points, fn points -> [point | points] |> Enum.reverse end)
      |> push_event("created_point", %{ point: %{ lat: point.lat, lng: point.lng} })
    }
  end
...
end

```
マーカーの描画は先程実装したので、handleEventで created_pointイベントを追加するだけで完了です

```assets/js/hooks.js
Hooks.Map = {
  mounted() {
...
    this.handleEvent("created_point", ({ point }) => {
      this.addMarker(point)
    });
  },
...
}
```
# 最後に
こちらが最終的な成果物になります
GPSと3Gのモジュールは流石に持ってなかったので、
apple watchのアプリをswift uiで作成しました
watchOSのシミュレーターにはGPSのテストで一定間隔でApple本社あたりをぐるぐる回る物があるので今回はそちらを使用しています

[![Image from Gyazo](https://i.gyazo.com/b51935eb035cf01b184135a50abc9d8f.gif)](https://gyazo.com/b51935eb035cf01b184135a50abc9d8f)

記事３つでだいぶ長くなってしまいましたがLiveViewとGoogleMapAPIでGPS Loggerを作成できました
これを発展させれば、複数のドローンにGPSと3Gモジュールを載せてリアルタイムに状況を確認できますし、
スマートウォッチやスマホアプリでお互いの位置を確認しながらドライブなどいろいろできそうです

次は認証周りを色々詰め込んで長くなったのでLiveViewとGoogleMapAPIのみのショートバージョンになります

# コード
https://github.com/thehaigo/live_map/tree/qiita
https://github.com/thehaigo/live_map/tree/render_marker