さて、本日は GeoPandas、Fiona、Shapely を 使ってファイルジオデータベース内のデータを Shape 出力してみようと思います。どれも GIS 界では有名でとても優秀なライブラリですね。これらについては過去のエントリーで紹介しているので、詳しくはそちらを読んでみてください。
実行環境
実は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
出力するデータ
このようなデータをフィーチャクラス単位で出力してみようと思います。フィーチャデータセットも含めてみました。また、アノテーションが入っていますが、アノテーションは出力対象外になるように制御をかけようと思います。
一部ではありますが、データはこのように日本地図のポリゴン、各県の外郭線のライン、横須賀市内に作成したポイントなどがあります。
属性には日本語データが入っています。
今回直面した問題
最初はこんなにたくさんのライブラリを使うつもりはなく、単純に 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 もきちんと出力されましたね。
Shape を ArcGIS Pro で読み込むと図形データのみ出力されていることがわかります。
CSV はこのようになっています。左端の列がキーです。
色々苦労はしたものの、プログラム自体はけっこうあっさりしてますね。本当に便利なライブラリ達です。次は GDAL で同じことを実現させてみたいですね。やり方がごっそりと変わると思いますが面白そうです。
今回は出力するファイルを Shape と CSV に分ける方法をとったのですが、もし Shape ファイルを出力するだけで問題ないやり方をご存知の方がいましたらコメントいただけるととても助かります。よろしくお願いします。本日は以上です。