ElixirDesktop でGPSログを取得（Location API)

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

今回はLocation APIでGPSログを記録します

# GSPログを保存するPostionモデルの作成
すでにあるコンテキストにモデルを追加するときはphx.gen.schemaを使いschemaファイルとmigrationファイルを作成します

```
mix phx.gen.schema Loggers.Position positions lat:float lng:float route_id:references:routes
```

refrencesのオプションでrouteが削除された時に関連するpositionも消すようにします


マイグレーションを実行します

```
mix ecto.migrate
```

schemaファイルをrouteに紐付いて保存できるように修正します

```diff_elixir:lib/spotter/loggers/position.ex
defmodule Spotter.Loggers.Position do
  use Ecto.Schema
  import Ecto.Changeset

  schema "positions" do
    field(:lat, :float)
    field(:lng, :float)
-   field(:route_id, :integer)
+   belongs_to :route, Spotter.Loggers.Route

    timestamps(type: :utc_datetime)
  end

  @doc false
  def changeset(position, attrs) do
    position
-    |> cast(attrs, [:lat, :lng])
-    |> validate_required([:lat, :lng])
+    |> cast(attrs, [:lat, :lng, :route_id])
+    |> validate_required([:lat, :lng, :route_id])
  end
end
```

routeにもリレーションの設定をしておきます
リレーションに`on_delete: :deelte_all`オプションを追加するとRouteを削除した際に配下のpositionも削除してくれます

```diff_elixir:lib/spotter/loggers/route.ex
defmodule Spotter.Loggers.Route do
  use Ecto.Schema
  import Ecto.Changeset

  schema "routes" do
    field :name, :string
    belongs_to(:user, Spotter.Accounts.User)
+   has_many :positions, Spotter.Loggers.Position, on_delete: :delete_all

    timestamps(type: :utc_datetime)
  end
  ...
end
```

loggersコンテキストにlist_positionsとcreate_positionを追加します

```lib/spotter/loggers.ex
defmodule Spotter.Loggers do
  ...  
  alias Spotter.Loggers.Position

  def list_positions(route_id) do
    from(
      pos in Position,
      where: pos.route_id == ^route_id
    )
    |> Repo.all()
  end

  def create_position(attrs \\ %{}) do
    %Position{}
    |> Position.changeset(attrs)
    |> Repo.insert()
  end
end
```

これでモデルの実装が完了しました、次はLiveViewを実装していきます

# GPSログの取得
今回はJSのGeolocation APIを使います

https://developer.mozilla.org/ja/docs/Web/API/Geolocation_API

## 位置情報取得の許可
iOSアプリで位置情報を使う場合は許可が必要なので　info.plistに以下を追加します

```diff_xml:native/ios/todoapp/Info.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
+	<key>NSLocationWhenInUseUsageDescription</key>
+   <string>use GPS logging</string>
    ...
</dict>
```

Androidはこちらを参考にしてください

https://qiita.com/the_haigo/items/fd0282821c4a942cafda#android%E3%81%AEwebview%E3%81%A7%E4%BD%8D%E7%BD%AE%E6%83%85%E5%A0%B1%E3%82%92%E5%8F%96%E5%BE%97%E3%81%A7%E3%81%8D%E3%82%8B%E3%82%88%E3%81%86%E3%81%AB%E3%81%99%E3%82%8B

## JS Hookの作成
GPSデータを取得して、LiveView側にイベントを通知するイベントをすでに作成したMapLibere Hookに追加します

```diff_javascript:assets/js/hooks.js
Hooks.MapLibere = {
  mounted() {
    const map = new maplibregl.Map({
      container: "map",
      style: "https://tile2.openstreetmap.jp/styles/osm-bright/style.json",
      center: { lat: 33.30639, lng: 130.41806 },
      zoom: 8,
    });

    window.map = map;

   // 以下追加
   this.handleEvent("start_logging", () => {
     const watchID = navigator.geolocation.watchPosition((position) => {
       const coords = position.coords;
       this.pushEvent("update", {
          heading: coords.heading,
          lat: coords.latitude,
          lng: coords.longitude,
          speed: coords.speed,
          timestamp: position.timestamp,
        });
      });
      window.watchID = watchID;
    });

    this.handleEvent("stop_logging", () => {
      navigator.geolocation.clearWatch(window.watchID);
    });
  },
};
export default Hooks;
```

`start_logging`はLiveViewから`push_event`で呼ばれた時に現在位置を取得して結果を`this.pushEvent`でliveView側に送信して`update`イベントを発火します

`stop_logging`はLiveViewから`push_event`で呼ばれた時に現在位置を取得し続けているのを解除します


次はLiveViewの方を作成していきます

## GPSログデータの出力
起動フラグとしてrecordを初期値falseでassignします
start,stop,updateのイベントをそれぞれ実装します

```diff_elixir:lib/spotter_web/live/route_live/show.ex
defmodule SpotterWeb.RouteLive.Show do
  use SpotterWeb, :live_view
  
  ... 
  @impl true
  def handle_params(%{"id" => id}, _, socket) do
    {:noreply,
     socket
     |> assign(:page_title, page_title(socket.assigns.live_action))
-    |> assign(:route, Loggers.get_route!(id))}
+    |> assign(:route, Loggers.get_route!(id))
+    |> assign(:record, false)}
  end

  # 以下追加

  @impl true
  def handle_event("start", _, socket) do
    socket
    |> assign(:record, true)
    |> push_event("start_logging", %{})
    |> then(&{:noreply, &1})
  end

  def handle_event("stop", _, socket) do
    socket
    |> assign(:record, false)
    |> push_event("stop_logging", %{})
    |> then(&{:noreply, &1})    
  end

  def handle_event("update", params, socket) do
    IO.inspect(params)
    {:noreply, socket}
  end  
end
```

gheaderコンポーネントの右側に配置します

JS Hooksを使うときの注意ですが idをつけたタグにphx-hookで使用するhooksを指定します

```diff_elixir:elixir:phoenix/lib/spotties_web/live/route_live/show.html.heex
<.gheader title={@route.name}>
  <:back>
   <.link navigate={~p"/routes"}>
     <.icon name="hero-chevron-left-solid" class="h-6 w-6"/>
   </.link>
 </:back>
 <:actions>
-   <.link patch={~p"/routes/#{@route}/show/edit"} phx-click={JS.push_focus()}>
-     <.icon name="hero-pencil-square-solid" class="h-6 w-6 mr-4" />
-   </.link>
+   <span :if={!@record} phx-click="start" class="btn btn-sm">Start</span>
+   <span :if={@record} phx-click="stop" class="btn btn-sm">Stop</span>
 </:actions>
</.gheader>
...
```

# iOS Simulatorで確認
動作確認を行います
run_mixでビルドしたら Xcodeから起動します

Logger.Showに移動すると以下のダイアログが表示されるので`アプリの使用中は許可`にします
![スクリーンショット 2023-12-24 1.32.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64198/f6db4f4d-0561-a500-95a5-5b339910c6bf.png)

GPSロギングはダミーがいくつかがあるので city runを選択します
![スクリーンショット 2023-12-24 1.39.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64198/8938087b-3059-8fd2-6358-db893b73f3fb.png)

startボタンをクリックすると xcodeのログ出力の部分に以下のようなログが出力されます

![スクリーンショット 2023-12-24 1.33.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64198/f0dc62cf-eeaa-e7c8-5517-e7f20542b3f1.png)

問題なく取得できているようです

# 最後に
本記事ではLocation APIを使用してGPSログを取得する箇所を実装しました
次はGPSログから地図で現在位置の表示について解説します

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