geojson

既存のライブラリが重厚長大なものが多いのだけど、 OpenGL や SVG のような二次元のベクター描画で簡単に済ませたい。 その方向で調査。

format

unknown: blockquote => {"type":"blockquote","children":[{"type":"paragraph","children":[{"type":"text","value":"GeoJSON is a geospatial data","position":{"start":{"line":14,"column":3,"offset":231},"end":{"line":14,"column":31,"offset":259}}}],"position":{"start":{"line":14,"column":3,"offset":231},"end":{"line":14,"column":31,"offset":259}}}],"position":{"start":{"line":14,"column":1,"offset":229},"end":{"line":14,"column":31,"offset":259}}}

以下のような様式。 Feature の中に Geometry が入っている。

{
    "type": "FeatureCollection",
    "features": [
        {
            "type": "Feature",
            "properties": {},
            "geometry": {}
        }
    ]
}
unknown: table => {"type":"table","align":[null],"children":[{"type":"tableRow","children":[{"type":"tableCell","children":[{"type":"text","value":"Geometry Object","position":{"start":{"line":32,"column":3,"offset":489},"end":{"line":32,"column":18,"offset":504}}}],"position":{"start":{"line":32,"column":1,"offset":487},"end":{"line":32,"column":23,"offset":509}}}],"position":{"start":{"line":32,"column":1,"offset":487},"end":{"line":32,"column":23,"offset":509}}},{"type":"tableRow","children":[{"type":"tableCell","children":[{"type":"text","value":"Point","position":{"start":{"line":34,"column":3,"offset":535},"end":{"line":34,"column":8,"offset":540}}}],"position":{"start":{"line":34,"column":1,"offset":533},"end":{"line":34,"column":23,"offset":555}}}],"position":{"start":{"line":34,"column":1,"offset":533},"end":{"line":34,"column":23,"offset":555}}},{"type":"tableRow","children":[{"type":"tableCell","children":[{"type":"text","value":"MultiPoint","position":{"start":{"line":35,"column":3,"offset":558},"end":{"line":35,"column":13,"offset":568}}}],"position":{"start":{"line":35,"column":1,"offset":556},"end":{"line":35,"column":23,"offset":578}}}],"position":{"start":{"line":35,"column":1,"offset":556},"end":{"line":35,"column":23,"offset":578}}},{"type":"tableRow","children":[{"type":"tableCell","children":[{"type":"text","value":"LineString","position":{"start":{"line":36,"column":3,"offset":581},"end":{"line":36,"column":13,"offset":591}}}],"position":{"start":{"line":36,"column":1,"offset":579},"end":{"line":36,"column":23,"offset":601}}}],"position":{"start":{"line":36,"column":1,"offset":579},"end":{"line":36,"column":23,"offset":601}}},{"type":"tableRow","children":[{"type":"tableCell","children":[{"type":"text","value":"MultiLineString","position":{"start":{"line":37,"column":3,"offset":604},"end":{"line":37,"column":18,"offset":619}}}],"position":{"start":{"line":37,"column":1,"offset":602},"end":{"line":37,"column":23,"offset":624}}}],"position":{"start":{"line":37,"column":1,"offset":602},"end":{"line":37,"column":23,"offset":624}}},{"type":"tableRow","children":[{"type":"tableCell","children":[{"type":"text","value":"Polygon","position":{"start":{"line":38,"column":3,"offset":627},"end":{"line":38,"column":10,"offset":634}}}],"position":{"start":{"line":38,"column":1,"offset":625},"end":{"line":38,"column":23,"offset":647}}}],"position":{"start":{"line":38,"column":1,"offset":625},"end":{"line":38,"column":23,"offset":647}}},{"type":"tableRow","children":[{"type":"tableCell","children":[{"type":"text","value":"MultiPolygon","position":{"start":{"line":39,"column":3,"offset":650},"end":{"line":39,"column":15,"offset":662}}}],"position":{"start":{"line":39,"column":1,"offset":648},"end":{"line":39,"column":23,"offset":670}}}],"position":{"start":{"line":39,"column":1,"offset":648},"end":{"line":39,"column":23,"offset":670}}},{"type":"tableRow","children":[{"type":"tableCell","children":[{"type":"text","value":"GeometryCollection","position":{"start":{"line":40,"column":3,"offset":673},"end":{"line":40,"column":21,"offset":691}}}],"position":{"start":{"line":40,"column":1,"offset":671},"end":{"line":40,"column":23,"offset":693}}}],"position":{"start":{"line":40,"column":1,"offset":671},"end":{"line":40,"column":23,"offset":693}}}],"position":{"start":{"line":32,"column":1,"offset":487},"end":{"line":40,"column":23,"offset":693}}}

Point

{
    "type": "Point",
    "coordinates": [100.0, 0.0]
}

Polygon

穴が空いている場合は、複数の頂点リストを保持する。

// No holes:

{
    "type": "Polygon",
    "coordinates": [
        [
            [100.0, 0.0],
            [101.0, 0.0],
            [101.0, 1.0],
            [100.0, 1.0],
            [100.0, 0.0]
        ]
    ]
}

// with holes:

{
    "type": "Polygon",
    "coordinates": [
        [
            [100.0, 0.0],
            [101.0, 0.0],
            [101.0, 1.0],
            [100.0, 1.0],
            [100.0, 0.0]
        ],
        [
            [100.8, 0.8],
            [100.8, 0.2],
            [100.2, 0.2],
            [100.2, 0.8],
            [100.8, 0.8]
        ]
    ]
}

read

python で素直に読んでみた。

import pathlib
import json


def process_geometry(geometry: dict):
    match geometry:
        case {"type": "MultiPolygon", "coordinates": coordinates}:
            print(f'{len(coordinates)} polygon')
            for coord in coordinates:
                print(f'  {len(coord)} rings')
                for x in coord:
                    print(f'    {len(x)} points')


def process_feature(feature: dict):
    match feature:
        case {"type": "Feature", "properties": props, "geometry": geometry}:
            print(props)
            process_geometry(geometry)

        case _:
            raise NotImplementedError()


def main(path: pathlib.Path):
    data = json.loads(path.read_bytes())
    match data:
        case {"type": "FeatureCollection", "features": features}:
            for feature in features:
                process_feature(feature)


if __name__ == '__main__':
    main(pathlib.Path('japan.geo.json'))

jpan.geo.json は、 https://github.com/dataofjapan/land です。 実行結果。

{'nam': 'Kyoto Fu', 'nam_ja': '京都府', 'id': 26}
4 polygon
  1 rings
    1235 points
  1 rings
    6 points
  1 rings
    8 points
  1 rings
    6 points

なるほど。

GL_LINE_LOOP

単純に GL_LINE_LOOP で描画できそうとわかった。

def process_geometry(geometry: dict) -> Polygon:
    match geometry:
        case {"type": "Polygon", "coordinates": polygon}:
            assert len(polygon) == 1
            array = (float2 * len(polygon[0]))()
            for i, (x, y) in enumerate(polygon[0]):
                array[i] = float2(x, y)
            return Polygon(array, [SubMesh(0, len(array))])

        case {"type": "MultiPolygon", "coordinates": polygons}:
            array = (float2 * sum(len(polygon[0]) for polygon in polygons))()
            i = 0
            submeshes = []
            for polygon in polygons:
                assert len(polygon) == 1
                submeshes.append(SubMesh(i, len(polygon[0])))
                for (x, y) in polygon[0]:
                    array[i] = float2(x, y)
                    i += 1
            return Polygon(array, submeshes)

        case _:
            raise NotImplementedError()

orthogonal の方で適当にビューポートを (140, 35) というような適当な経度緯度に調整してやればよさそう。

data

参考