Unityで距離と向きだけのナビアプリっぽいものを作る

# こんなやつ
<img width="200" alt="IMG_2201.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/193550/1d8513c4-6116-9c87-4f37-5456c1a8d2ef.png">

目的地の緯度経度と端末のセンサ情報をもとに、目的地までの**距離**と**向き**を表示する某アプリみたいな奴です。
後ろにカメラ画像を置いておくとなんだかヘンテコAR感ありますね。

# 動作確認した環境
Unity2017.3.0f3
iPhone7(iOS11.2.1)

# 目的地までの距離を測る
## 緯度経度を構造体にまとめる
緯度経度をまとめた構造体"Location"を定義しておきましょう！

```c#:Location.cs
[System.Serializable]
public struct Location
{
    public double Latitude;
    public double Longitude;

    public Location(double latitude, double longitude)
    {
        Latitude = latitude;
        Longitude = longitude;
    }
}
```



## 2点の緯度経度から距離を測る
緯度経度を用いた計算を行う場合は**double**を使います。
**UnityEngine.Mathf**では**float**で計算を行ってしまうので、**double**で計算を行う**System.Math**を今回は利用します。
緯度経度による距離の計算方法にはいくつか種類があるようですが、計算が簡単な**球面三角法**を用いてます。

```c#:NaviMath.cs
using System;

public static class NaviMath
{
    private const double EARTH_RADIUS = 6378.137d; //km

    public static double Deg2Rad { get { return Math.PI / 180.0d; } }

    public static double LatlngDistance(Location a, Location b)
    {
        double dlat1 = a.Latitude * Deg2Rad;
        double dlng1 = a.Longitude * Deg2Rad;
        double dlat2 = b.Latitude * Deg2Rad;
        double dlng2 = b.Longitude * Deg2Rad;

        double d1 = Math.Sin(dlat1) * Math.Sin(dlat2);
        double d2 = Math.Cos(dlat1) * Math.Cos(dlat2) * Math.Cos(dlng2 - dlng1);
        double distance = EARTH_RADIUS * Math.Acos(d1 + d2);
        return distance;
    }
}
```

試しに渋谷駅と博多駅間の距離を計算してみます。

```c#:Test.cs
using UnityEngine;

public class Test : MonoBehaviour
{
    Location shibuya = new Location(35.658126d, 139.701616d);
    Location hakata = new Location(33.590322d, 130.420675d);

    void Start()
    {
        Debug.Log("渋谷駅と博多駅の距離は" + NaviMath.LatlngDistance(shibuya, hakata) + "kmだよ");
    }
}
```

<img width="777" alt="スクリーンショット 2018-02-05 12.10.17.png" src="https://qiita-image-store.s3.amazonaws.com/0/193550/05019701-cae6-1650-6b0c-d3a32eed044d.png">
それっぽい数値が出てきた！

これが正しい数値か[国土地理院のサイト](http://vldb.gsi.go.jp/sokuchi/surveycalc/surveycalc/bl2stf.html)で確認してみます。
![スクリーンショット 2018-02-06 13.07.53.png](https://qiita-image-store.s3.amazonaws.com/0/193550/5b1ac0da-4405-c45e-684f-819b3303ed78.png)
880km程の距離で誤差は1km未満とそこそこの精度で距離を算出する事が出来ました。
国土地理院では、この距離の計算を行う計算式も[公開](http://vldb.gsi.go.jp/sokuchi/surveycalc/surveycalc/algorithm/bl2st/bl2st.htm)されています。より高精度な計算を行う場合はこれを参考に実装するといいかも。

# 目的地の向きを取得する
次に緯度経度から目的地への向きをUnityで表示します。現実の向きと合わせる為に今回は2ステップに分けて実装を進めます。

## まずは現実の北向きを取得する
パパっとソースだけぶん投げます。

```c#:LookAtNorth.cs
using UnityEngine;

public class LookAtNorth : MonoBehaviour
{
    void Start()
    {
        Input.location.Start();
    }

    void Update()
    {
        transform.localEulerAngles = Vector3.up * -Input.compass.trueHeading;
    }
}
```

なんとこれだけ！（すごい）
**Input.compass.trueHeading**は端末の向きが**真北**からどれだけ回転しているかを表しています。
これを適当なゲームオブジェクトに貼り付ければ、Unityの中で**Z-Frontを現実の真北に向けるY回転**を行ってくれます。

## 緯度経度から向きを取得する
NaviMath.csに以下の関数を追加してあげます。
**北を0度**として時計回りで方位角を計算してくれます。

**2019年1月30日追記: 方角の計算を修正しました、国土地理院とほぼ同じ結果を得られます。**

```c#:NaviMath.cs
        public static double LatlngDirection(Location from, Location to)
        {
            var dlat1 = to.Latitude * Deg2Rad;
            var dlng1 = to.Longitude * Deg2Rad;
            var dlat2 = from.Latitude * Deg2Rad;
            var dlng2 = from.Longitude * Deg2Rad;

            var deltaX = dlng2 - dlng1;
            var y = Math.Sin(deltaX);
            var x = Math.Cos(dlat1) * Math.Tan(dlat2) - Math.Sin(dlat1) * Math.Cos(deltaX);
            var dir = Math.Atan2(y, x) * 180 / Math.PI;
            if (dir < 0)
            {
                return 360 + dir;
            }
            return dir;
        }
```


方位角の計算も追加して実験してみます。

```c#:Test.cs
using UnityEngine;

public class Test : MonoBehaviour
{
    Location shibuya = new Location(35.658126d, 139.701616d);
    Location hakata = new Location(33.590322d, 130.420675d);

    void Start()
    {
        Debug.Log("渋谷駅と博多駅の距離は" + NaviMath.LatlngDistance(shibuya, hakata) + "kmだよ");
        Debug.Log("渋谷駅から博多駅の方位角は" + NaviMath.LatlngDirection(shibuya, hakata) + "だよ");
    }
}

```

<img width="767" alt="スクリーンショット 2018-02-08 18.50.50.png" src="https://qiita-image-store.s3.amazonaws.com/0/193550/c1ddf00c-3596-8c2c-73f5-a2c3093d60dd.png">

~~先ほどの国土地理院のサイトで計算した結果より**3度**くらいのズレが起きていますが、今回は良しとします。~~
**2019年1月30日追記: 方角の計算を修正した結果、1度未満のズレに収まるようになりました。**

方位角の計算についても同じく国土地理院のサイトで[公開](http://vldb.gsi.go.jp/sokuchi/surveycalc/surveycalc/algorithm/bl2st/bl2st.htm)されていますのでより精密な計算にはそちらを実装してみてください！

# Unity内で現実の目的地を指す
これまでの説明に加えて、**端末の緯度経度**を使って**現在地→渋谷駅**を案内する某アプリっぽいものを作ってみます。（スマホの縦持ちのみの対応です！）



```c#:LookAtDestination.cs
using UnityEngine;

public class LookAtDestination : MonoBehaviour
{

    Location shibuya = new Location(35.658126d, 139.701616d);

    public UnityEngine.UI.Text text;

    void Start()
    {
        Input.location.Start();
    }

    void Update()
    {
        //端末の緯度経度
        LocationInfo locationInfo = Input.location.lastData;
        Location deviceLocation = new Location(locationInfo.latitude, locationInfo.longitude);

        //距離(メートル)
        int distance = (int)NaviMath.LatlngDistance(deviceLocation, shibuya) * 1000;

        //向き
        Vector3 angle = Vector3.up * -Input.compass.trueHeading;
        double direction = NaviMath.LatlngDirection(deviceLocation, shibuya);
        angle.y += (float)direction;

        //更新
        text.text = string.Format("渋谷駅まで{0}mだよ", distance);
        transform.localEulerAngles = angle;
    }
}
```

↑のスクリプトを
<img width="318" alt="スクリーンショット 2018-02-08 19.19.51.png" src="https://qiita-image-store.s3.amazonaws.com/0/193550/97bae17a-3e15-f3f7-71e9-120be38461cb.png">
これに

<img width="341" alt="スクリーンショット 2018-02-09 12.23.10.png" src="https://qiita-image-store.s3.amazonaws.com/0/193550/8498c0fa-4171-83c3-02d4-bab523486aba.png">

こうじゃ！（Textは適当に作って突っ込んでください）
後は実機へビルドして渋谷まで案内されてください。