[Unity] Android/iOSで位置情報を取得する

既に `Unity 位置情報` 等で検索すると取得方法が出てくるが、自分向けにまとめる

## Input
- https://docs.unity3d.com/ja/current/ScriptReference/Input.html
- `UnityEngine.Input` クラスにてデバイスからの入力を検知することが出来る

## Input.location
- https://docs.unity3d.com/ja/current/ScriptReference/Input-location.html
- `Input.Location` にてLocationServiceを呼び出すことが出来る

## LocationService
- https://docs.unity3d.com/ja/current/ScriptReference/LocationService.html
- 携帯端末が保持する位置情報を取得するためのクラス

### LocationService.status
- https://docs.unity3d.com/ja/current/ScriptReference/LocationService-status.html
- https://docs.unity3d.com/ja/current/ScriptReference/LocationServiceStatus.html
- 現在位置の取得状況について、`Input.location.status` で取得可能

|status||
|---|---|
|Initializing|`Input.location.Start()`を実行すると始めはこの状態になる <br/>この状態ではまだ位置情報は取得できない|
|Running|`Input.location.Start()`を実行して位置情報を取得出来るようになるとこの状態になる<br/> `Input.location.lastData`に取得した位置情報が適宜格納される|
|Stopped|`Input.location.Stop()`を実行するとこの状態になる<br/>(初期状態もこれっぽい)<br />この状態では位置情報は更新されない|
|Failed|ユーザーがロケーションサービスへのアクセスを拒否した状態(未検証)|

### LocationService.isEnabledByUser
- https://docs.unity3d.com/ja/current/ScriptReference/LocationService-isEnabledByUser.html
- `Input.location.isEnabledByUser` で端末の設定でロケーション機能を有効にしているかどうかを返す
- この値を位置情報を使用するタイミングでチェックし、 `false`であれば有効にするよう促すのが良い
  - iOSの場合は、この値が`false`である場合に `Input.location.Start()`を実行すると設定を促すダイアログが表示される(未検証)

### LocationService.lastData
- https://docs.unity3d.com/ja/current/ScriptReference/LocationService-lastData.html
- https://docs.unity3d.com/ja/current/ScriptReference/LocationInfo.html
- `Input.location.status == LocationServiceStatus.Running` である場合、端末側の最新の位置情報を格納する
- 取得できる情報は `LocationInfo` の構造体を参照
  - Accuracyは実際の位置情報とどのくらいの誤差がありうるかを返す
  - 位置飛びを許容しない場合は、特定のAccuracy以上の場合を省くと良さそう
    - `LocationService.Start()`の引数で設定可能

|LocationInfo.*||
|---|---|
|latitude|緯度|
|longitude|軽度|
|altitude|高度|
|horizontalAccuracy|水平方向の精度(メートル単位)|
|verticalAccuracy|垂直方向の精度(メートル単位)|
|timestamp|タイムスタンプ<br> UTC基準で1970年1月1日の0時0分0秒からの経過秒数|

- 余談: timestampから現在の日時を出す

```csharp
double timestamp = Input.location.lastData.timestamp;
DateTime datetime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(timestamp).ToLocalTime();
Debug.Log(timestamp.ToString("yyyy-MM-dd HH:mm:ss"));
```

### LocationService.Start()
- https://docs.unity3d.com/ja/current/ScriptReference/LocationService.Start.html
- 位置情報の取得を開始する
- `LocationService.Start()` 後、直ぐに位置情報の取得は開始されない
  - initializeが走るので、`LocationService.status` も合わせて見ておくと良い
- 第一引数: `desiredAccuracyInMeters` を指定することで指定されたAccuracy以上の位置情報が 取得されなくなる
  - (大体) 500mより高い値に設定することでGPSチップをオンにしておく必要がなくなるためバッテリー容量を節約できる(未検証)
- 第二引数: `updateDistanceInMeters ` を指定することで LocationService.lastData の更新が少なくなり、オーバーヘッドが軽減される(未検証)

### LocationService.Stop()
- https://docs.unity3d.com/ja/current/ScriptReference/LocationService.Stop.html
- 位置情報の更新を停止することで更新処理分のオーバーヘッドを抑えることができ、バッテリー節約に繋がる(未検証/ベンチ取りたい)
- ゲーム性によって Stop するべきかしないべきか決めると良さそう
  - ポケモンGO等、アクションにフックさせず、常に現在地を必要とする場合はStopさせなくて良い
  - [こちネコ](https://www.mapfan.com/kochineko/) 等、アクションにフックして現在地を取得する場合、Stopさせた方が良さそう

## SampleCode
- https://docs.unity3d.com/ja/current/ScriptReference/LocationService.Start.html
- 上記にも位置情報取得のためのサンプルコード記載されている (C#/UnityScript)
- 上記を参考にStopを実行しないケースのコードを記載してみる

```LocationUpdater.cs
using UnityEngine;
using System.Collections;

public class LocationUpdater : MonoBehaviour
{
    public float IntervalSeconds = 1.0f;
    public LocationServiceStatus Status;
    public LocationInfo Location;

    IEnumerator Start()
    {
        while (true)
        {
            this.Status = Input.location.status;
            if (Input.location.isEnabledByUser)
            {
                switch(this.Status)
                {
                    case LocationServiceStatus.Stopped:
                        Input.location.Start();
                        break;
                    case LocationServiceStatus.Running:
                        this.Location = Input.location.lastData;
                        break;
                    default:
                        break;
                }
            }
            else
            {
                // FIXME 位置情報を有効にして!! 的なダイアログの表示処理を入れると良さそう
                Debug.Log("location is disabled by user");
            }

            // 指定した秒数後に再度判定を走らせる
            yield return new WaitForSeconds(IntervalSeconds);
        }
    }
}
```

- 確認用スクリプト

```LocationRenderer.cs
using UnityEngine;
using UnityEngine.UI;

public class LocationRenderer : MonoBehaviour
{
    public LocationUpdater updater;
    public Text text;

    void Update()
    {
        text.text = updater.Status.ToString()
                  + "\n" + "lat:" + updater.Location.latitude.ToString()
                  + "\n" + "lng:" + updater.Location.longitude.ToString();
    }
}
```

- 以下の様な感じで位置情報の取得を確認できる  
<img src=https://qiita-image-store.s3.amazonaws.com/0/102664/a76d69ae-b2cf-497b-5d83-cd5cc7a8b957.png width=200px>