プログラミング

国土地理院の地図データを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メッシュとして表示させたりしてみたいですね。

-プログラミング

執筆者:

関連記事

no image

M5StickC Plusの明るさ(M5.Axp.ScreenBreath)、0~100かもしれない

M5StickC Plusで画面の明るさを調整するM5.Axp.ScreenBreath()という関数があります。 日本語リファレンスでは指定値7~12ということになっています。 また、公式ドキュメン …

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

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

GeoJSONで市町村境界をマージして都道府県境界にしたい(その1)

前回の続きで、国土地理院の「国土数値情報」っていうオープンデータで遊んでいます。独立した記事なので前回のは読まなくても良いです。 やりたいこと 国土数値情報で提供されている「行政区域データ」は、市町村 …

no image

Google Apps Script (GAS)でBlueskyのbotを作る

Blueskyで何かやりたくなったので、PRTimesのプレスリリースを自動投稿するbotを作ってみました。 Google Apps Scriptを使うと、無料でbotが作れるので便利です。 せっかく …

no image

[python]Windows環境でsubprocessするときは文字コードに気をつけて

pythonは内部の文字コードと実行環境の文字コードが色々絡み合っていて、いろんなところで悪さをする。 特にWindows環境だと、内部はUTF-8で動いているのに実行環境はShift-JIS(正確に …