【Unity】PLATEAUの都市モデルとGPSログの軌跡を重ねてみる
この記事はAkatsuki Games Advent Calendar 2023の17日目の記事です。
昨日の記事はHajime Sannoさんの「Shader の GUI を実装する」でした。 普通のInspector拡張を触ることが多い自分にとっても、ShaderGUIの話はかなり新鮮でした。 今後Shaderを実装した際にShaderGUIも書いてみたいなと思える記事でした!
他にもクライアントサイド、サーバサイドだけでなくゲーム以外の技術等々、たくさんの記事があります。よければ見ていってください〜
はじめに
去年あたりからドライブにドハマりしていて、その過程で溜まってきたGPSログの軌跡データを使って何かをしたかったので、今回はUnity空間上に描画してみました。 都合よく日本の都市の3DモデルがUnity向けに開発・提供されているので、今回はそれも使ってみました。 軽めの記事のつもりです。
本記事の内容を3行にまとめると
- 国土交通省主導の都市デジタルツインを実現するプロジェクト「Project PLATEAU」のUnity用のSDK「PLATEAU SDK for Unity」を使って、Unity空間上に日本の都市を召喚した
- Googleマップのタイムライン機能からKMLファイル形式でGPSログをエクスポートし、Unityプロジェクト側にインポート & パースした
- PLATEAUのモデルと、GPSログの軌跡を重ね合わせた
です。
目次
Unity空間上に日本の都市を召喚
GPSログの軌跡を見るだけなら、それこそ (エクスポートせずとも) Googleマップのタイムライン機能で見たり、(エクスポートするにしても) Google Earthで見ればいい話です。 が、Unityでの開発を生業にしている身なのでUnityの勉強がてらにUnityを選択しました。
Unityのシーン上に何も配置せずにGPSログの軌跡を描画しても味気ないので、PLATEAUの3Dモデルを配置して、そこに表示を重ねることにしました。
① Unityインストール
PLATEAU SDK for Unityのマニュアルによると、執筆時点で最新リリースのv2.2.1-alphaの推奨環境はUnity 2021.3.30f1以上
とのことなので、執筆時点で最新バージョンの2021.3.33f1をインストールします。
今回はEditor上で動けば十分なので、追加のモジュールは入れません。
② 空のUnityプロジェクト作成
特にひねりを入れずに、通常の「3D」テンプレートから作成します。
③ PLATEAU SDK導入
入手方法は3つありますが、今回は手間が少なそうな2を選びます。
- GitHubのReleasesから入手 (マニュアル)
- UnityのPackage Managerでgit URLを指定 (マニュアル)
- Asset Storeから入手
今回はv2.2.1-alpha
を使うので、UnityのPackage ManagerのAdd package from git URL...
にて
https://github.com/Project-PLATEAU/PLATEAU-SDK-for-Unity.git#v2.2.1-alpha
を指定します。
④ 都市3Dモデルのインポート & 配置
SDKにはモデルデータそのもののアセットは同梱されてないので、配置に先立ってモデルデータをインポートする必要があります。
まずメニュー/PLATEAU/PLATEAU SDK
からPLATEAU SDK
ビューを開きます。
モデルデータをインポートする際の参照元としてローカル
(ダウンロード済みのデータを使う) とサーバー
(新たにサーバーからダウンロードする) を選べますが、今回は何もダウンロードしてない状態なのでサーバー
を選びます。
データセットの選択
では「どの都市のモデルデータをインポートするか」を指定できますが、今回はアカツキゲームスがある東京都23区
を選びます。
基準座標系
では浮動小数点数由来の誤差を1小さくするために選択した都市に近い基準地を指定します。
今回は東京都23区
を選んだので、基準地には09: 東京(本州), 福島, 栃木, 茨城, 埼玉, 千葉, 群馬, 神奈川
を選びます。
マップ範囲選択
では、Sceneビュー上2で「データセットに含まれる範囲のうち、どの範囲をインポートするか」を選択します。
今回はアカツキゲームスのある目黒駅周辺の9マス分3を選びます。
地物別設定
では、モデルデータの他に都市計画決定情報等のデータをインポートするかを選べます。
今回はモデルデータ以外を使わないため、インポートする
の☑を外します。
ここまでやったらいざインポートを実行します。
今回は3分足らずでダウンロードからインポートまで完了しました。
インポート完了時に現在開いているシーンの原点とインポートしたモデルデータの中心が重なるよう自動的に配置される
ので注意しましょう (KMLからインポートした点群をどれだけオフセットさせればよいかに関わる) 。
なおモデルデータは原寸大なのでスケールは調整不要です。
GPSログの準備 & パーサ実装
⑤ KMLファイルへのエクスポート
まずはGPSログのデータを準備します。
前述の通り、Googleマップのタイムライン機能からKMLファイルをエクスポートします4。
今回は目黒駅周辺のモデルデータのみをインポートしたので、目黒駅周辺を通った2023-04-09のデータを用います。
タイムライン画面上で⚙ボタンをクリックすると出てくるこの日のデータを KML 形式でエクスポート
をすると、KMLファイルがダウンロードされます (今までの全期間の一括ダウンロードはできないように見える) 。
⑥ KMLファイルのインポート
次にUnityプロジェクトにKMLファイルをインポートします。
拡張子が*.kml
のままだとUnityにTextAssetとして認識されずにDefaultAssetとして扱われてしまって不都合が多いため、*.xml
にリネームします。
そしてAssets/Resources/
フォルダを作り、リネームしたファイルをその配下にインポートします5。
⑦ KMLファイルのパーサ実装
次にKMLファイルから緯度・経度のタプル列を抽出するパーサを実装します。
KMLファイルはXMLのフォーマットに従っていて、スキーマは公式リファレンスに記載されている通りです。
今回は移動軌跡を取れれば十分なので、KML全体のパースは試みず、移動軌跡にあたる部分のみをパースできれば十分です。
KML上のkml/Document
タグで囲われた部分は配列になっていて、様々な種類の子要素が入っています。
それらの子要素のうち、今回必要な座標の点群データはPlacemark
タグで囲われた部分にあります。
Placemark
には以下の2パターンがあるので、両方に対応します。
- 1点のデータのみを含むもの (訪問先を表すデータ):
Placemark/Point/coordinates
内に記述されている - 複数点のデータを含むもの (移動中の座標の履歴を表すデータ):
Placemark/LineString/coordinates
内にスペース区切りで記述されている
手抜き気味ですが、以下のコードで最低限動きます。
public static IEnumerable<(double latitude, double longitude)> ExtractGeodeticCoordinates(string kml) { // エラーハンドリング等は手抜き var xDoc = XDocument.Parse(kml); XNamespace xmlns = "http://www.opengis.net/kml/2.2"; var kmlElement = xDoc.Element(xmlns + "kml"); var documentElement = kmlElement!.Element(xmlns + "Document"); foreach (var placemarkElement in documentElement!.Elements(xmlns + "Placemark")) { var coordinatesElement = placemarkElement.Element(xmlns + "Point")?.Element(xmlns + "coordinates") // 1点のみ ?? placemarkElement.Element(xmlns + "LineString")?.Element(xmlns + "coordinates"); // 複数点 foreach (var coordinate in coordinatesElement!.Value.Split(' ')) { // 例: 139.7161781,35.633174499999996,0 if (coordinate.Split(',') is { Length: 3 } coordinates) { // KMLファイル中は経度,緯度,高度の順番になっているので逆にする。高度には0しか入ってないので省略する。 yield return (double.Parse(coordinates[1]), double.Parse(coordinates[0])); } } } }
軌跡を描画
最後に、緯度・経度のタプル列をワールド座標に変換してLineRendererで描画します。
緯度・経度 (測地座標系) から平面直角座標系に変換するコードは以下の記事を参考にしました (長いので省略) 。 yubeshicat.hatenablog.com
変換の際に必要となる基準地点は、インポートしたモデルの中心座標35.634375, 139.7171875とします。 この値は、モデルの親GameObjectについているPLATEAUInstancedCityModelコンポーネントのLatitudeプロパティ・Longitudeプロパティで簡単に得られます。
また、モデルをシーン上で動かしたい場合は、変換により得られたワールド座標をさらにオフセットさせる必要があります。
LatLon2Coordinate()
で得られる平面直角座標はZ-upの右手座標系なので、最後にUnity標準のY-upの左手座標系に変換します。
これまた手抜き気味ですが、以下のコンポーネントをシーン上に配置し、PLATEAUInstancedCityModelコンポーネントとLineRendererコンポーネントをアタッチすることで最低限動きます。
public class KmlRenderer : MonoBehaviour { [SerializeField] private PLATEAUInstancedCityModel cityModel; [SerializeField] private LineRenderer lineRenderer; private void Start() { var kml = Resources.Load<TextAsset>("history-2023-04-09"); var geodeticCoordinates = KmlParser.ExtractGeodeticCoordinates(kml.text); var worldPositions = geodeticCoordinates.Select(LatLon2WorldPosition).ToArray(); lineRenderer.positionCount = worldPositions.Length; lineRenderer.SetPositions(worldPositions); } private Vector3 LatLon2WorldPosition((double latitude, double longitude) geodeticCoordinates) { var cityModelPosition = cityModel.transform.position; var coordinate = GeoCoordinateConverter.LatLon2Coordinate(geodeticCoordinates.latitude, geodeticCoordinates.longitude, cityModel.Latitude, cityModel.Longitude); return new Vector3((float)(coordinate.X - cityModelPosition.x), (float)(50.0 - cityModelPosition.y), (float)(coordinate.Z - cityModelPosition.z)); } }
先程エクスポートした2023-04-09の移動経路を描画した結果はこちらです。 一応予想通り(?)の結果が得られました。
今後の発展
ひとまず技術検証はできたので、自分にとっての理想形を得るために続きをやるとしたらここらへんかな〜と思ってます。
- 目黒駅周辺の9マスだけでなく、23区全体、東京全体、日本全体をインポートして表示してみる
- Unityがどこまで耐えられるか次第
- 手作業で1日ずつエクスポートするのは大変なので、Googleマップのタイムライン機能から何らかの方法で過去のすべてのデータをKMLファイルにエクスポートしてみる
- KMLファイルには経度・緯度情報しか無いので代わりに決め打ちで標高50mとしていたが、固定値ではないそれっぽい値をでっち上げてPLATEAU空間上に表示してみる
- 地表のメッシュにRaycastしたときの交点のY座標を使う?
- 「どこで渋滞によくハマるかの可視化」等、面白そうなことに繋げられそうなので、軌跡上の通過速度を何らかの手法で取ってみる
おわりに
ひとまずできることは分かったので、ぼちぼちいじってみて何か面白いことが分かったらまた記事にしようと思います! (エクスポート面倒問題がネックですね……)
明日のAkatsuki Games Advent Calendar 2023の記事はぐんそうさんの何かだそうです! お楽しみに〜
-
日本のサイズは1000kmオーダーなので、Unityの単精度floatを用いた座標系ではcmオーダーの誤差が出る。今回は気にするほどのレベルではない (GPSの計測誤差の方がよっぽど大きい) が、わざわざ誤差が大きくなる基準地を選ぶ理由も無いので素直に
09
を選択。↩ - Sceneビュー上に地理院地図っぽい地図が出てきて、GUI操作で範囲選択できる。リアルタイムに地理院地図のデータをダウンロードしてきているのか、拡大縮小したりすると重たい。↩
- 1km弱四方。今回は動くようになるところまでを試したかったので、時間とストレージ容量の節約のためにかなり狭めの範囲とした。↩
- ロケーション履歴を有効にしたAndroid端末を普段から使っていて、運転時にGoogleマップのナビを使っているため、運転時のGPSログが (明示的に計測開始/終了せずとも) 自動的にここに集約されており、データソースとしてちょうどよかったためこれを選んだ。ちなみに、Android Autoでのナビ中はGPSを受信できないトンネル中でも最後の地点から速度・加速度・地磁気等のセンシングデータを基に推定した地点を記録してくれる (車側とスマホ側のどっちが推定しているかは分からない) 。↩
- Unityのベストプラクティス的にはResourceフォルダを使った実装方法は推奨されていないが、今回は「動けばいい」のでResourcesフォルダを使った↩