引き続き、AROWを使ったアプリ開発です。
今回は、主に"サーバからのマップ取得"と"AR表示"について。
前回:AROWオープンテストバージョンでUnityに3Dマップを表示するまで - じゅころぐAR
サーバからマップを配信する
マップデータダウンロードにて地方毎(北海道地方、東北地方・・・)のarowmap
が配布されています。
関東地方のzipファイルを解凍するとこんな感じです。
osm_[経度(度)]_[緯度(度)]
のディレクトリにblock_[緯度]_[経度].arowmap
のファイルが格納されています。osmはOpenStreetMapのデータであることを示すプレフィックス、経度・緯度はそのままマップの範囲を表していると思われます。
ファイル名の経緯度ですが、4-5桁目が00-60
ではなく00-99
になっているため、度分秒ではなく度(degree)形式の経緯度を文字列にした値のようです。また、末尾5桁は全て00000
になっているので、1/100度単位でファイルが区切られています。
例:
139.13度 = 1391300000
35.74度 = 0357400000
とりあえずのテストとしてローカルPC(Mac)にファイルを置いて、http-serverで配信できるようにしました。
$ cd ./Kanto $ http-server Starting up http-server, serving ./ Available on: http://127.0.0.1:8080 http://192.168.0.2:8080
上記例ではhttp://192.168.0.2:8080でローカルサーバが起動したので、http://192.168.0.2:8080/osm_139_035/block_1390000000_0350000000.arowmapのようにリクエストすればファイルが取得できます。
ライセンスを読むと、"本コンテンツ(含むマップデータ)"を"単独"で"バイナリ形式での頒布"が許可されているため、publicなWebサーバからの配信もOKな気がします。
マップをサーバから取得する
マップデータが緯度経度に基いて分割されていることがわかったので、緯度経度指定で周辺のマップを取得する形に書き換えます。
元の処理はAssets/ArowSample/Scripts/Demo/ArowDemoMain.cs
が参考になります。
private string mapSerrver = "http://192.168.0.2:8080"; //ローカルサーバ //マップの初期化(緯度経度指定) public void Initialize (float lon, float lat) { //経緯度(lon, lat)から周辺マップデータのパスを作成 string directory = "/osm_" + Math.Truncate(lon).ToString() + "_" + Math.Truncate(lat).ToString().PadLeft(3, '0'); string filePath = "/block_" + Math.Truncate(lon * 100).ToString() + "00000_" + Math.Truncate(lat * 100).ToString().PadLeft(5, '0') + "00000.arowmap"; //サーバにリクエストを投げる StartCoroutine(GetMap(mapSerrver + directory + filePath)); }
UnityWebRequestでGETリクエストを投げ、レスポンスのバイナリデータを使ってArowMapObjectModel
を生成します。
ローカルファイルから読みこむ場合との違いは、mapDataBytes
をFile.ReadAllBytes()
で読み込んでいるか、webRequest.downloadHandler.data
で取得しているかだけです。
IEnumerator GetMap(string uri) { using (UnityWebRequest webRequest = UnityWebRequest.Get(uri)) { yield return webRequest.SendWebRequest(); if (!webRequest.isNetworkError) { //webRequestで取得したdataからArowMapObjectModelを生成 byte[] mapDataBytes = webRequest.downloadHandler.data; arowMapObjectModel = ArowMapObjectModel.LoadByData(mapDataBytes); var mapRect = MapRectInt.FromDictionary(arowMapObjectModel.InfoList.FullInfoList); worldCenter = MapUtility.CalculateWorldCenter(mapRect.East, mapRect.North, mapRect.West, mapRect.South); CreateConfig config = ScriptableObject.CreateInstance<CreateConfig>(); CreateRuntimeBuilder.SetupConfigForSampleSlice(config); config.SetupSharedMaterials = CreateRuntimeBuilder.GetFixTextureSuiteCallbackForSampleSlice(config); //Mesh Colliderの設定 config.BuildingColliderElement = CreateConfig.BuildingCollider.Mesh; CreateBuildings(config); //建物の生成 CreateRoads(); //道路の生成 } } }
Tips
CreateConfigクラス
建物にMesh Colliderを設定するため、CreateConfig
クラスのプロパティを変更しています。
リファレンスを読むと、Colliderの設定だけでなく、オブジェクトに設定するマテリアルや生成するメッシュのパターンなども変更できるので、本クラスのリファレンスは要チェックです。
ArowMain.Runtime.CreateConfig クラス
ARで表示する
AROWをARKitと組み合わせてみます。
ただARで表示させるだけなら難しくはなく、Unity-ARKit-Pluginをセットアップして、マップを表示させればOKです。
GPSの緯度経度から周辺の3Dマップをサーバ取得して、AR表示するとこまでできました。
— jyuko (@jyuko49) 2019年4月24日
現実との位置合わせはできてない。#AROW pic.twitter.com/Gs8PyjzRzC
建物にMesh Colliderを付けているので、画面タップでHitTestを行って対象のオブジェクトをスクリプトで取得することもできます。
取得したオブジェクトをDestroy()
すれば消える。
@Query_chan が笑いながら建物を消し去るだけ😃#AROW #クエリちゃん pic.twitter.com/gtpGOE2F6n
— jyuko (@jyuko49) 2019年5月1日
無音でDestroyするだけでは面白くないので、クエリちゃんに消していただきました🍊
自分で作っておいてなんですが、笑い声がリアルで怖いです...
query-chan.com
ARでマップを空間と合わせたい
3DマップをARで使うにあたって現実の空間とできるだけ位置を合わせた方が実用的です。
そのためには、マップのスケール、位置(緯度経度)、方位、高さを現実と合わせる必要があります。
スケール
1/100度で分割されたarowmap
ファイルをUnityで表示させてみます。
worldScaleはMapUtility.WorldScale
を指定しており、値はVector2.one * 0.01f
になっています。
スケールの確認のため、x方向とz方向を1,000倍に引き伸ばしたCube(白い四角)を配置してみました。
マップはCubeと同じサイズで、Unity単位で1,000 x 1,000の範囲内に表示されています。一部Cubeからはみ出て見えますが、範囲内に少しでもかかっているオブジェクト(建物、道路)はマップに含まれる仕様と思われます。
1Unity単位は1メートルなので、1/100度 x 1/100度のマップを1,000m x 1,000mで表示していることになります。
じゃあ1/100度は何メートルかと言うと、緯度経度の1度あたりの距離は、経度が約90km(緯度35度付近)、緯度が約111kmなので、1/100度は900m x 1,110mになります。
デフォルトでも大体合っているのですが、より正確なスケールで表示したいので、worldScaleにnew Vector2(0.009f, 0.0111f)
を設定してみます。
マップの左側に表示されている楕円形の建物は等々力陸上競技場なのですが、OpenStreetMapと比べると、スケール変更前は緯度方向に若干潰れたような表示になっており、スケール変更後の方が正しい比率に見えます。
位置(緯度経度)
マップの表示位置を合わせるには、worldCenterを変更する方法が利用できます。
サンプルのスクリプトでは以下のコードで、読み込んだマップデータの中心をworldCenterに設定しています。
var mapRect = MapRectInt.FromDictionary(arowMapObjectModel.InfoList.FullInfoList); worldCenter = MapUtility.CalculateWorldCenter(mapRect.East, mapRect.North, mapRect.West, mapRect.South);
設定値の確認のため、"マップデータ(arowmap)のファイルパス"、"mapRect"、"worldCenter"をDebug.Log()
で表示させてみます。
mapRect.West
、mapRect.South
がファイルパスの経度・緯度と一致し、mapRect.East
、mapRect.North
は1/100度にあたる100000をそれぞれ足した値、worldCenterはmapRect
の中心という関係性になっています。
worldCenterの変更でマップの中心が変わることを確認したいので、試しにworldCenter.xをmapRect.West
に変更してみましょう。
worldCenter.x = mapRect.West; worldCenter.y = (mapRect.South + mapRect.North) / 2;
Unity上でのマップの中心がworldCenterで指定した緯度経度=マップデータの左端(西端)に移動していることがわかります。
よって、現在地の経度(lon)と緯度(lat)からworldCenterを計算することで、現在地を中心にしたマップが表示できます。
worldCenter.x = (int)Math.Truncate(lon * 10000000); worldCenter.y = (int)Math.Truncate(lat * 10000000);
現在地の緯度経度はInput.location.lastdata
から取得できます。
Unity - Scripting API: LocationService.Start
方位
ARでマップを使う場合、方位=マップの向きはかなり重要です。
UnityではInput.location.Start()
で位置情報の取得を開始し、Input.compass.enabled
をtrue
にすることで、デバイスに内蔵されたコンパスに基づく方位が取得できます。
Input.location.Start(); Input.compass.enabled = true;
現在デバイスが向いている方位はInput.compass.headingAccuracy
で取得できます。
ただし、ARセッションを既に開始している場合、コンパスから値を取得した時点でデバイスが回転している可能性があります。その場合、ARカメラ(例として、Camera.main)の回転角Camera.main.transform.eulerAngles.y
を引くことでARセッション開始時の方位角=z軸に合わせたい方位が得られます。
//ARセッション開始時の方位角を得る azimuth = Input.compass.headingAccuracy - Camera.main.transform.eulerAngles.y;
Compass-trueHeading - Unity スクリプトリファレンス
この方位がz方向になるようマップの向きを合わせたいのですが、建物のメッシュ生成を行うCreateBuildingMeshScripts クラスに方位角を指定する引数はなく、デフォルトでは北が正面(Unity座標系でのz軸方向)で表示されます。
これを調整する方法として、メッシュを生成し終わった後に、親オブジェクトのtransformを回転させています。
あえて"メッシュを生成し終わった後に"と書いたのは、メッシュ生成前に親オブジェクトを回転させてもメッシュ生成時に北がz方向になるよう相対的な回転が入ってしまい、期待した結果にならなかったためです。
ArowMain.Runtime.BuildingCreator.Builder クラスに、メッシュ生成完了時のcallbackを指定するSetOnCompletedCreateMeshCallBack()
があるので、callbackの処理内でマップを回転させます。
buildingMeshCreator.SetOnCompletedCreateMeshCallBack((List<BuildingDataModelWithMesh> list) => { //メッシュ生成後に方位角(azimuth)に合わせて回転 buildingParent.transform.Rotate(new Vector3(0, -azimuth, 0)); });
azimuthを負の値にしているのは、方位角に対して、その方位をUnity座標系での正面に向けたいからです。
デバイスの方位角が90°(東を向いている場合)を例にすると、Input.compass.trueHeading
から取得できる値は90fとなり、マップをy軸で-90f回転させると東(方位角90°)がUnity座標系でのz方向(y軸での回転が0°)に向きます。
実際に表示させてみると、わかりやすいです。
上記でマップの向きを方位に合わせたものの、コンパスから取得できる方位はかなり精度が低いので、初期表示ではコンパスを使い、精度の高い情報が得られたらマップの方位を補正する実装にしておくと良さそうです。
デバイスがある程度移動する前提でGPSが移動した軌跡から現在のデバイスの向きを推定した方が精度が良さそうですし、予め決められた場所での利用ならマーカー等で位置合わせするのも手です。
高さ
最後に、高さの調整です。
デバイスからはInput.location.altitude
でGPSの高度が取れるのですが、これは地面からの高さではないため使い物になりません(なお、精度も良くない)
せっかくのARなので水平面の検知を行い、平面の高さで補正する方法が良さそうです。
ARKitで試した際の実装例として、以下のようなコードになります。
using UnityEngine.XR.iOS; public class ArowTest : MonoBehaviour { private UnityARAnchorManager unityARAnchorManager; [SerializeField] private GameObject ground; //地面として表示するPlaneObject void Start() { unityARAnchorManager = new UnityARAnchorManager(); } void Update() { if (unityARAnchorManager.GetCurrentPlaneAnchors().Count != 0) { //最後に検知した平面を取得 ARPlaneAnchorGameObject plane = unityARAnchorManager.GetCurrentPlaneAnchors().Last.Value; //建物と道路の親オブジェクトを取得 GameObject buildingParent = arowTestMain.buildingParent; GameObject roadParent = arowTestMain.roadParent; //補正前のposition Vector3 currentPosition = buildingParent.transform.position; //建物のposition補正 buildingParent.transform.position = new Vector3( currentPosition.x, plane.gameObject.transform.position.y, currentPosition.z ); //道路のposition補正 roadParent.transform.position = new Vector3( currentPosition.x, plane.gameObject.transform.position.y - 1.5f, currentPosition.z ); //地面の高さを合わせる ground.transform.position = new Vector3(0, plane.gameObject.transform.position.y, 0); } } }
道路メッシュのみ-1.5f
しているのは、デフォルトが地面から若干浮いているためです。-1.5f
すると道路メッシュの上面が地面の高さと一致します。
上記例では簡単のために最後に検知した平面の高さを使ってますが、このままだと地面以外の水平面を検知してしまったときに表示がずれます。再度地面を検知すれば直るので、良しとするスタンス。
より正確性を求めるなら、"取得した平面をforeach
で回して、高さが最も低い平面を使う"、"カメラからの距離が1m未満の平面は無視する"などの工夫が必要です。もしくは、UIとしてユーザがボタンを押したときだけ補正するとか。
また、ユーザが地面より少し高い場所でアプリ起動するケースも悩みどころです。ただ、マップ自体も起伏や傾斜、段差が考慮されている訳ではないので、ある程度割り切るしかないかなと思います。
まとめ
AROWのマップをAR表示させてみました。多分、大体合ってると思う。
ARKitのみでARCoreは試していませんが、特別な実装はしていないので同じ感じになるはず。
また、AROWの使用感として、若干仕様に癖があるかなとは思いつつ、慣れればスクリプトで色々できるので割と使いやすいです。
今後の予定は未定ですけど、SDKに付属していたサンプル(↓)で指定した二地点の経路を求める処理があるので、簡単なARナビゲーションなら作れそう(作るとは言ってない)
AROWのサンプル動かしてみました。とりあえず、スマホでもサクサクなのでよい。#AROW pic.twitter.com/LpMJWu63ga
— jyuko (@jyuko49) 2019年4月23日
クレジット
本記事内のコンテンツでは、"AROW"、"OpenStreetMap"が使用されております。
© 2019 「AROW」Drecom Co.,Ltd. All rights reserved.
© OpenStreetMap contributors