GIS奮闘記

元GISエンジニアの技術紹介ブログ。主にPythonを使用。

スポンサーリンク

GeoPandas、Fiona、Shapely を 使ってファイルジオデータベース内のデータを Shape 出力する方法

さて、本日は GeoPandas、Fiona、Shapely を 使ってファイルジオデータベース内のデータを Shape 出力してみようと思います。どれも GIS 界では有名でとても優秀なライブラリですね。これらについては過去のエントリーで紹介しているので、詳しくはそちらを読んでみてください。

www.gis-py.com

www.gis-py.com

www.gis-py.com

実行環境

実はGeoPandas、Fiona、Shapely 以外にも Pandas を使います。ただ、Pandas は Shape 出力には関係ないところで使用します。詳細は後述します。

Windows 10
Python 3.6.6
ArcGIS Pro 2.4
GeoPandas 0.6.1
Fiona 1.8.4
Shapely 1.6.4
Pandas 0.25.2

出力するデータ

このようなデータをフィーチャクラス単位で出力してみようと思います。フィーチャデータセットも含めてみました。また、アノテーションが入っていますが、アノテーションは出力対象外になるように制御をかけようと思います。

f:id:sanvarie:20191030174559p:plain

一部ではありますが、データはこのように日本地図のポリゴン、各県の外郭線のライン、横須賀市内に作成したポイントなどがあります。

f:id:sanvarie:20191030175020p:plain

f:id:sanvarie:20191030175047p:plain

属性には日本語データが入っています。 f:id:sanvarie:20191030175507p:plain

f:id:sanvarie:20191030175532p:plain

今回直面した問題

最初はこんなにたくさんのライブラリを使うつもりはなく、単純に GDAL とかで出力しようかと思ったのですが、日本語データが含まれているとうまく出力できず、また、GeoPandas や Fiona でも同じように試したのですが、日本語データがネックになり断念しました。もしかしたら何かやり方があるのかもしれないのですが、とりあえず今回は別のアプローチをとりました。ちなみに日本語データが含まれていなければ以下のような感じにすれば大丈夫かと思います(GeoPandas と Fiona を使用した例)。

import fiona
import geopandas as gpd
import os

gdb = r"D:\python\data\Sample.gdb"
layers = fiona.listlayers(gdb)
anno_flg = 0

for layer in layers:

    gdf = gpd.read_file(gdb, driver="FileGDB", layer=layer)

    for column in gdf.columns:

        if column == "AnnotationClassID":# アノテーションは対象外
            anno_flg = 1

        if column == "Shape_Length":
            gdf.drop('Shape_Length', axis=1, inplace=True)

        if column == "Shape_Area":
            gdf.drop('Shape_Area', axis=1, inplace=True)

    if anno_flg == 1:
        anno_flg = 0
        continue

    out = os.path.join(r"D:\python\data\shape", layer + ".shp")
    gdf.to_file(out)

直面した問題を回避するために

出力するファイルを以下の二つに分けました。
1.Shape・・・図形データを出力
2.CSV・・・属性データを出力

このようにファイルを分けて出力すれば日本語データが含まれていても問題ないかと思います。双方にキーとなるカラムも含めていますので、データ出力後はそのキーを使って図形と属性を紐づけるだけですね。Pandas は CSV 出力の処理で使用します。

サンプルコード

import os
import geopandas as gpd
import fiona
from shapely.geometry import Point, LineString, Polygon, MultiPolygon, MultiLineString, MultiPoint
import pandas as pd

def make_shape_file():
    gdb = r"D:\python\data\ToShapeFile.gdb" # 出力対象のGDB
    layers = fiona.listlayers(gdb)

    for layer in layers:
        new_data = gpd.GeoDataFrame()
        new_data['geometry'] = None

        gdf = gpd.read_file(gdb, driver="FileGDB", layer=layer)

        if 'AnnotationClassID' in gdf[0:1].columns: # アノテーションは処理対象外
            continue

        geometry_type = gdf[0:1]['geometry'].geom_type[0]

        for index, row in gdf.iterrows():

            if geometry_type == "MultiPolygon":
                new_data.loc[index, 'geometry'] = MultiPolygon(row.geometry)

            elif geometry_type == "Polygon": # 今回は不使用
                new_data.loc[index, 'geometry'] = Polygon(row.geometry)

            elif geometry_type == "LineString": # 今回は不使用
                new_data.loc[index, 'geometry'] = LineString(row.geometry)

            elif geometry_type == "MultiLineString":
                new_data.loc[index, 'geometry'] = MultiLineString(row.geometry)

            elif geometry_type == "Point":
                new_data.loc[index, 'geometry'] = Point(row.geometry)

            elif geometry_type == "MultiPoint": # 今回は不使用
                new_data.loc[index, 'geometry'] = MultiPoint(row.geometry)

        # Shapeファイル出力
        out = os.path.join(r"D:\python\data\shape", layer + ".shp")
        new_data.to_file(out)

        # CSVファイル出力
        df = pd.DataFrame(gdf)
        df.to_csv(os.path.join(r"D:\python\data\csv", layer + ".csv"))

make_shape_file()

結果

Shape も CSV もきちんと出力されましたね。

f:id:sanvarie:20191030181847p:plain

f:id:sanvarie:20191030181816p:plain

Shape を ArcGIS Pro で読み込むと図形データのみ出力されていることがわかります。

f:id:sanvarie:20191030182008p:plain

CSV はこのようになっています。左端の列がキーです。

f:id:sanvarie:20191030182422p:plain

色々苦労はしたものの、プログラム自体はけっこうあっさりしてますね。本当に便利なライブラリ達です。次は GDAL で同じことを実現させてみたいですね。やり方がごっそりと変わると思いますが面白そうです。

今回は出力するファイルを Shape と CSV に分ける方法をとったのですが、もし Shape ファイルを出力するだけで問題ないやり方をご存知の方がいましたらコメントいただけるととても助かります。よろしくお願いします。本日は以上です。