自由研究

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

投稿日:

なんか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メッシュとして表示させたりしてみたいですね。

-自由研究

執筆者:


  1. […] 前回の続きで、国土地理院の「国土数値情報」っていうオープンデータで遊んでいます。独立した記事なので前回のは読まなくても良いです。 […]

comment

メールアドレスが公開されることはありません。

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください

関連記事

no image

あみだくじはどれぐらいランダムか

あみだくじ。 紙とペンがあればすぐに作ることができるお手軽な抽選方法として知られています。 なにか物事を決めたいとき、あみだくじを使うという人も多いのではないでしょうか。 ふと思ったんですが、あみだく …

no image

MagicaVoxelとDMM.makeでオリジナルフィギュアを作ろう

もくじ1 読まなくても良い前書き2 概要3 MagicaVoxel3.1 フィギュアの自立の有無3.1.1 基本的に自立しません3.1.2 どうしても自立させたいなら3.2 折れる可能性を考慮する3. …

no image

NHKを従量課金にすると1時間いくらになるの?

もくじ1 はじめに2 前提条件3 試算に使うデータを集める3.1 NHKの収入3.2 世帯数3.3 NHKの視聴率4 実際に試算する4.1 各世帯の年間視聴時間4.2 全世帯の年間視聴時間4.3 受信 …

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

GeoJSONのPolygonをマージしたい第2回です。 前回、純粋な多角形の統合ではなくて、領域が被らない多角形の統合になるのでグラフ問題として解くことができるという説明をしました。 今回はどうやっ …

no image

Unityでメッシュをさわるノウハウ

ヒマなので覚書。ウラを取っていない経験則なので話半分で読んでください。 あと、3Dの基本概念とUnity固有の話の区別が付いていないのでごめんなさい。 もくじ1 Meshクラスの基本2 ポリゴンの読み …