はじめに
この記事は 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に紐付いて保存できるように修正します
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も削除してくれます
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を追加します
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に以下を追加します
<?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に追加します
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のイベントをそれぞれ実装します
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を指定します
<.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に移動すると以下のダイアログが表示されるのでアプリの使用中は許可
にします
GPSロギングはダミーがいくつかがあるので city runを選択します
startボタンをクリックすると xcodeのログ出力の部分に以下のようなログが出力されます
問題なく取得できているようです
最後に
本記事ではLocation APIを使用してGPSログを取得する箇所を実装しました
次はGPSログから地図で現在位置の表示について解説します
本記事は以上になりますありがとうごうざいました