ElixirDesktop MapLibre GPSログデータを描画、更新

# はじめに
この記事は Elixirアドベントカレンダーのシリーズ４の23日目の記事です

今回は保存したGPSログを地図上に描画していきます

# Hooksの改修

Hooksを以下のように修正しています

mounted直下
- init イベントに変更

initイベント
- 引数のlogを addSourceでgeojson形式でログデータを入れる
- addLayerでログデータをどのように描画するかを指定
- startボタンを押すまで自分の位置を取得しなかったので、自分の位置だけを更新する watchを追加

render_logイベント
- 引数のlogでsourceを更新し、再描画する

start_loggingイベント

- 自己位置だけを更新するwatchをクリアして、センターとGPSをLiveView側に送信する watchを追加

stop_loggingイベント

- センターとGPSをLiveView側に送信するwatchをクリアして、 自己位置だけを更新するwatchを追加

genGeoJson関数

- ログデータをGeoJson形式に変換

```diff_javascript:phoenix/assets/js/hooks.js
Hooks.MapLibere = {
  mounted() {
-   navigator.geolocation.getCurrentPosition((pos) => {
-     const init = [pos.coords.longitude, pos.coords.latitude];
+   this.handleEvent("init", ({ log }) => {
+     navigator.geolocation.getCurrentPosition((pos) => {
+       const init = [pos.coords.longitude, pos.coords.latitude]; 
        const map = new maplibregl.Map({
          container: "map",
          style: "https://tile2.openstreetmap.jp/styles/osm-bright/style.json",
          center: init,
          zoom: 12,
        });

        map.on("load", () => {
          map.addControl(new maplibregl.NavigationControl());
+         map.addSource("map_log", {
+            type: "geojson",
+            data: {
+              type: "FeatureCollection",
+              features: genGeoJSON(log),
+            },
+          });
+          map.addLayer({
+            id: "points",
+            type: "circle",
+            source: "map_log",
+            layout: {},
+            paint: {
+              "circle-color": "#0000FF",
+              "circle-radius": 4,
+            },
+         });        
        });

        const user = new maplibregl.Marker().setLngLat(init).addTo(map);
+       const watchID = navigator.geolocation.watchPosition((position) => {
+          const coords = position.coords;
+          window.user.setLngLat([coords.longitude, coords.latitude]);
+       });
+       window.watchID = watchID;        
        window.user = user;
        window.map = map;
      });
    });

+  this.handleEvent("render_log", ({ log }) => {
+     window.map.getSource("map_log").setData({
+       type: "FeatureCollection",
+       features: genGeoJSON(log),
+     });
+   });

    this.handleEvent("start_logging", () => {
+     navigator.geolocation.clearWatch(window.watchID);
      const watchID = navigator.geolocation.watchPosition((position) => {
        const coords = position.coords;
        window.user.setLngLat([coords.longitude, coords.latitude]);
        window.map.setCenter([coords.longitude, coords.latitude]);
        this.pushEvent("update", {
          lat: coords.latitude,
          lng: coords.longitude,
        });
      });
      window.watchID = watchID;
    });

    this.handleEvent("stop_logging", () => {
      navigator.geolocation.clearWatch(window.watchID);
+     const watchID = navigator.geolocation.watchPosition((position) => {
+       const coords = position.coords;
+       window.user.setLngLat([coords.longitude, coords.latitude]);
+     });
+     window.watchID = watchID;     
    });
  },
};

+ const genGeoJSON = (log) => {
+  return log.map((latlng) => {
+    return {
+      type: "Feature",
+      properties: {},
+      geometry: {
+        type: "Point",
+        coordinates: latlng,
+      },
+    };
+  });
+ };


export default Hooks
```

# ログデータを取得する list_position/1 を改修
route_idに紐づくpositionを取得していましたが、ログの表示に使うlat,lngだけが欲しいので selectで返すデータを加工します

```diff_elixir:lib/spotter/loggers.ex
  ...
  def list_positions(route_id) do
    from(
      pos in Position,
-     where: pos.route_id == ^route_id
+     where: pos.route_id == ^route_id,
+     select: [pos.lng, pos.lat]
    )
    |> Repo.all()
  end

end
```

# LiveView側の改修
mount時の既存ログを描画する処理と、新規ログ登録時の処理を追加します


```diff_elixir:lib/spotter_web/live/route_live/show.ex
defmodule SpotterWeb.RouteLive.Show do
  ...
  @impl true
  def handle_params(%{"id" => id}, _, socket) do
+   log = Loggers.list_positions(id)

    socket
    |> assign(:page_title, page_title(socket.assigns.live_action))
    |> assign(:route, Loggers.get_route!(id))
+   |> assign(:log, log)
    |> assign(:pos, [0, 0])
    |> assign(:record, false)
+   |> push_event("init", %{log: log})
    |> then(&{:noreply, &1})
  end

  ...

  def handle_event("update", %{"lat" => lat, "lng" => lng}, socket) do
    if Geocalc.distance_between(socket.assigns.pos, [lat, lng]) > 100 do
      Loggers.create_position(%{lat: lat, lng: lng, route_id: socket.assigns.route.id})
+     new_log = [[lng, lat] | socket.assigns.log]

      socket
      |> assign(:pos, [lat, lng])
+     |> assign(:log, new_log)
+     |> push_event("render_log", %{log: new_log})
      |> then(&{:noreply, &1})
    else
      {:noreply, socket}
    end
  end
end

```

# 動作確認
追従モードの切り替えと、ログがMap上に描画されるのを確認できました


![692eacface4bc0abc5461e065ec5dc3c.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64198/d3067ea6-e6f1-1fba-2506-18eebeae024e.gif)

# 最後に
本記事ではGPSログデータのMap上への描画と、追従ロジックの改修を行いました
　GPSロガーについてはこれで一旦完了になります

 次はおまけでネイティブ機能の呼び出しについて解説します

 本記事は以上になりますありがとうございました