11
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

この記事は Elixirアドベントカレンダーのシリーズ4の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に紐付いて保存できるように修正します

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も削除してくれます

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を使います

位置情報取得の許可

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

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はこちらを参考にしてください

JS Hookの作成

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

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のイベントをそれぞれ実装します

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を指定します

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

GPSロギングはダミーがいくつかがあるので city runを選択します
スクリーンショット 2023-12-24 1.39.31.png

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

スクリーンショット 2023-12-24 1.33.06.png

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

最後に

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

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

11
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?