プログラミング

国土地理院の地図データをUnityで読みたい(願望)

投稿日:2019年9月29日 更新日:

なんかgoogleのAdSense通ったんで有益な情報載せないとなって。

国土地理院のオープンデータ

国土地理院が公開している(正確には国土地理院のデータを利用して国土交通省国土政策局国土情報課が公開している)「国土数値情報」っていうオープンデータが大量にあって、色々便利なのです。
で、せっかくなのでUnityで読んでみたいな、と。

今回は行政区域データってのを使ってみます。要は県とか市の境界情報がまとまっているデータです。

で、Unityで読んで表示したいな、と。

GeoJsonを読み込もう

このデータ、ダウンロードページに行くと簡単なアンケートに答えるだけで誰でも無料で利用できるんですけど、結構データ形式の情報が無い。そのわりに、割と色々な形式が入ってきます。
どうもメインはXML形式らしいんだけどJSON形式も付いてる。それもこれはGeoJsonっていうデファクトスタンダードっぽい仕様に沿ってるらしい。

データは大量(全国データで600MB)に入っているのだけど、例えば我が故郷伊丹市の部分を抜粋するとこんな感じ。


{
  "type": "FeatureCollection",
  "crs": {
    "type": "name",
    "properties": {
      "name": "urn:ogc:def:crs:EPSG::6668"
      }
    },
  "features": [
    {
      "type": "Feature",
      "properties": {
        "N03_001": "兵庫県",
        "N03_002": null,
        "N03_003": null,
        "N03_004": "伊丹市",
        "N03_007": "28207"
      },
      "geometry": {
        "type": "Polygon",
        "coordinates": [ 
          [ 
            [ 135.39466666703311, 34.805916386831825 ],
            [ 135.39479666673287, 34.805749162393965 ],
            (省略)
            [ 135.39447861159874, 34.806172774553943 ],
            [ 135.39466666703311, 34.805916386831825 ]
          ]
        ]
      } 
    },
    {(次の行政区域)}…
  ]
}

まぁまぁ意味が分かるかな、って感じのデータです。propertiesに謎のコードが入ってますが、これは「シェープファイルの属性について(xlsファイル)」っていうExcelに内容がまとめてあるのでそれを読むと何かわかる。それ以外の部分は、WikipediaのGeoJSONの項目がいい感じにまとめてあるので一読すると良いです。

Unityで処理しよう

せっかくなのでUnityで可視化したい。

JsonUtilityの挫折

UnityにはJsonUtilityっていうAPIが整備されていてJSONをいい感じにシリアライズできる。
が、
辞書のネストは読めても、配列のネストが読めない

具体的に言うと、こういうの(↓)は読める。


{
  "hoge" : 1,
  "piyo" : {
    "foo" : 2.1,
    "bar" : [2.21, 2.22, 2.23]
  }
}

こういうクラスを用意してあげれば良い。


[Serializable]
public class hogepiyo
{
    public int hoge;
    public foobar piyo;
}

[Serializable]
public class foobar
{
    public double foo;
    public double[] bar;
}

でもこういうの(↓)が読めない。


{
  "hoge" : [
    [1, 2],
    [3, 4],
    [5, 6]
  ]
}

配列の配列がダメらしい。どうもUnityシリアライザーが受け入れてくれないらしい。
(正確には、上みたいなのは”Vector2[]“として受け取ればイケるらしい…?)

今回のGeoJSONだとcoordinatesが三次元配列になっていて、JsonUtilityは諦めるしかない。

MiniJSON

というわけでMiniJSONを使う。
MiniJSONをUnityで使うサンプルコードはQiitaにいい感じの記事があるので、そっち見てもらった方が早い。
まぁザクっと読めば読める。

そのまま読むのがつらいので適当な構造体を用意して突っ込みます。
構造体の定義がこんな感じ。


public struct geoJsonFeatureCollection
{
    public string type;
    public List<geoJsonFeature> features;
}

public struct geoJsonFeature
{
    public string type;
    public geoJsonProperties properties;
    public geoJsonGeometry geometry;
}

public struct geoJsonProperties
{
    public string N03_001; /* 都道府県名 */
    public string N03_002; /* 支庁名 */
    public string N03_003; /* 郡政令都市 */
    public string N03_004; /* 市区町村名 */
    public string N03_005; /* 成立年月日 */
    public string N03_006; /* 消滅年月日 */
    public string N03_007; /* 行政区域コード */
}

public struct geoJsonGeometry
{
    public string type;
    public List<List<coordinate>> coordinates; /* 閉路ごとにリストが分けられている */
}

public struct coordinate
{
    public double latitude; /* 緯度 */
    public double longitude; /* 経度 */
}

で、この構造体にデータを突っ込む処理がこんな感じ。
testjsonって変数にjsonの文字列が入ってるもんだと思って。


Dictionary<string, object> featureCollection = Json.Deserialize(testjson) as Dictionary<string, object>;

geoJsonFeatureCollection geodata = new geoJsonFeatureCollection();

geodata.type = (string)featureCollection["type"];

geodata.features = new List<geoJsonFeature>();

foreach (Dictionary<string, object> feature in (List<object>)featureCollection["features"])
{
    geoJsonFeature tmpfeature = new geoJsonFeature();

    tmpfeature.type = (string)feature["type"];

    Dictionary<string, object> properties = (Dictionary<string, object>)feature["properties"];
    if (properties.ContainsKey("N03_001")) tmpfeature.properties.N03_001 = (string)properties["N03_001"];
    if (properties.ContainsKey("N03_002")) tmpfeature.properties.N03_002 = (string)properties["N03_002"];
    if (properties.ContainsKey("N03_003")) tmpfeature.properties.N03_003 = (string)properties["N03_003"];
    if (properties.ContainsKey("N03_004")) tmpfeature.properties.N03_004 = (string)properties["N03_004"];
    if (properties.ContainsKey("N03_005")) tmpfeature.properties.N03_005 = (string)properties["N03_005"];
    if (properties.ContainsKey("N03_006")) tmpfeature.properties.N03_006 = (string)properties["N03_006"];
    if (properties.ContainsKey("N03_007")) tmpfeature.properties.N03_007 = (string)properties["N03_007"];


    Dictionary<string, object> geometry = (Dictionary<string, object>)feature["geometry"];
    tmpfeature.geometry.type = (string)geometry["type"];
    tmpfeature.geometry.coordinates = new List<List<coordinate>>();
    foreach (List<object> closedLoop in (List<object>)geometry["coordinates"])
    {
        List<coordinate> tmpcloop = new List<coordinate>();
        foreach (List<object> position in closedLoop)
        {
            coordinate tmppos = new coordinate();
            tmppos.latitude = (double)position[0];
            tmppos.longitude = (double)position[1];
            tmpcloop.Add(tmppos);
        }
        tmpfeature.geometry.coordinates.Add(tmpcloop);

    }
    geodata.features.Add(tmpfeature);
}

ただ問題は、日本の市町村の大きさが地球に対して小さすぎるので、緯度経度の数値をfloatに入れると使い物にならない。
なので、floatに入れても値が丸まらないように気をつけつつ処理しないといけない。

たとえばとりあえずDebug.DrawLineに流し込む例はこんな感じ。配列の最初のデータを原点に置いて、緯度・経度を1万倍して表示しています。


void Update()
{
    List<coordinate> tmp = geodata.features[0].geometry.coordinates[0];
    for (int i = 1; i < tmp.Count; i++)
    {
        Debug.DrawLine(
            new Vector3((float)(tmp[i-1].latitude * 10000 - tmp[0].latitude * 10000), (float)(tmp[i - 1].longitude * 10000 - tmp[0].longitude * 10000)),
            new Vector3((float)(tmp[i].latitude * 10000 - tmp[0].latitude * 10000), (float)(tmp[i].longitude * 10000 - tmp[0].longitude * 10000))
            );
    }
}

これの実行結果がこう。

緯度経度を何も考えずにx,y座標に流しているので縦横比がメチャメチャですが、とりあえずデータが取れているのが分かります。

本当はきちんと座標変換すべきだし、今後このデータを3Dメッシュとして表示させたりしてみたいですね。

-プログラミング

執筆者:

関連記事

【Unity】GPUを使ってパンツを隠すスクリプトができた

かわいいスカートを履きたい!でもパンチラしたくない! それは誰もが抱く夢です。もちろん、3Dモデルだってパンチラしたくないと思っているハズです。 というわけで偉大なる先人がいます。 Unityでパンツ …

SHOWROOMのガチイベをGoogleスプレッドシートでグラフにしよう

まえがき SHOWROOMのガチイベ、と言えば配信者が獲得ポイント数でランキングされ、最終的にファンの札束で殴り合って勝者を競う札束タワーバトルなわけです。 数日~数週間にわたって配信者同士でランキン …

no image

Google Apps Script(GAS)で自宅サーバーの死活監視をする

前置き 自宅サーバー使ってますか? 最近もう流行らないかな、と思ったらRaspberry Piの流行で機器も電気代も安価に組めるようになったりして、地味に持っている人も多いんじゃないかな、と思ってます …

no image

UnityのCompute Shaderに線分と三角形の衝突判定をさせる

はじめてCompute Shader使ってみた。でもあまり性能は出ないのでただの失敗の記録です。 まず元のアルゴリズムはコレ。 線分と三角形の当たり判定 – 富士見研究所 で、これをまず三角形の表裏問 …

YouTube Data APIをGoogle Apps Script(GAS)から使おう

YouTubeってAPIから色々な情報を取ることができるんですよ。 APIの情報はリファレンスにまとまってるんですが、APIキーだのOAuth2.0だの、使い始めるまでがまぁまぁ面倒なんですね。 で、 …