こんなやつ
目的地の緯度経度と端末のセンサ情報をもとに、目的地までの距離と向きを表示する某アプリみたいな奴です。
後ろにカメラ画像を置いておくとなんだかヘンテコAR感ありますね。
動作確認した環境
Unity2017.3.0f3
iPhone7(iOS11.2.1)
目的地までの距離を測る
緯度経度を構造体にまとめる
緯度経度をまとめた構造体"Location"を定義しておきましょう!
[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を今回は利用します。
緯度経度による距離の計算方法にはいくつか種類があるようですが、計算が簡単な球面三角法を用いてます。
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;
}
}
試しに渋谷駅と博多駅間の距離を計算してみます。
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だよ");
}
}
これが正しい数値か国土地理院のサイトで確認してみます。
880km程の距離で誤差は1km未満とそこそこの精度で距離を算出する事が出来ました。
国土地理院では、この距離の計算を行う計算式も公開されています。より高精度な計算を行う場合はこれを参考に実装するといいかも。
目的地の向きを取得する
次に緯度経度から目的地への向きをUnityで表示します。現実の向きと合わせる為に今回は2ステップに分けて実装を進めます。
まずは現実の北向きを取得する
パパっとソースだけぶん投げます。
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日追記: 方角の計算を修正しました、国土地理院とほぼ同じ結果を得られます。
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;
}
方位角の計算も追加して実験してみます。
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) + "だよ");
}
}
先ほどの国土地理院のサイトで計算した結果より3度くらいのズレが起きていますが、今回は良しとします。
2019年1月30日追記: 方角の計算を修正した結果、1度未満のズレに収まるようになりました。
方位角の計算についても同じく国土地理院のサイトで公開されていますのでより精密な計算にはそちらを実装してみてください!
Unity内で現実の目的地を指す
これまでの説明に加えて、端末の緯度経度を使って現在地→渋谷駅を案内する某アプリっぽいものを作ってみます。(スマホの縦持ちのみの対応です!)
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;
}
}
こうじゃ!(Textは適当に作って突っ込んでください)
後は実機へビルドして渋谷まで案内されてください。