はじめに
過去に本ブログで、「ArcGIS の Python でのマルチプロセス処理 」、「ArcGIS の Python でのマルチプロセス処理-実装例 」として、ArcPy と Python でのマルチプロセス処理をとりあげ、ベスト プラクティスや、コーディング パターンをサンプルを交えながら紹介させて頂きました。今回は、Python 3.3 以上でのコーディング パターンとしてアップデートするとともに、ArcGIS Pro のジオプロセシング ツールへの組み込み例を加えた解説記事になります。
なお、本記事で解説しているコードは、すでに「Farmland polygon conversion tools」として、すぐに利用可能な Python ツールボックス のサンプルとして公開したものを基にしております。併せてご参照ください。
コーディングパターン
前回の記事では、コーティング パターンのポイントとしてあげていたのは以下の3つです。
1) ワーカー関数:batch_convert
2) ワーカー関数のラッパー:multi_run_batch_convert ※今回は不要
3) マルチプロセスの処理:exec_batch_convert
しかしながら、Python 3.3 からmultiprocessing モジュールのPool クラスに複数引数での呼出が可能な starmap が追加されました。そのため、現在のArcGIS Pro の環境で利用する際には、ワーカー関数のラッパーは不要になり、コーディングもよりシンプルにすることが可能です。
それでは、サンプルをもとに、コーティングする際のポイントを中心に解説していきます。
サンプルコード(GeoJSON形式の農地筆ポリゴンの変換)
変換対象のデータは、前回までの記事と同様、農林水産省が公開している「農地の区画情報(筆ポリゴン)」のデータを例にしています。
※筆ポリゴンの仕様は、前回までの記事時点とは異なるため、本ブログで取り上げているサンプルは、最新の筆ポリゴン(GeoJSON形式)を変換するコードをもとにした紹介記事としています。今回のサンプル(MP_Farmland_JsonToFeatureClass.py)は、筆ポリゴンのZIP 解凍後の1つの「都道府県フォルダー」を処理するものです。
解凍した状態では、次のように「都道府県フォルダー」下に「各市区町村のGeoJSONファイル(ファイルの拡張子はjson)」が複数入っています。
入力フォルダー: 市区町村別のGeoJSONファイルが入った都道府県フォルダー
例)
|-2024_02
|-2024_022012.json # 市区町村別のGeoJSON
|-2024_022021.json
|-2024_022039.json
.....
今回のサンプルプログラムでは、この都道府県フォルダーを入力フォルダーとして、次のような処理を行います。
1.各市区町村のファイル ジオデータベース(FGDB) を作成し、農地の筆界ポリゴンをフィーチャクラスへ変換(※この処理をマルチプロセスで実行)
2.都道府県ファイル ジオデータベース(FGDB) を作成し、1.の各市区町村のフィーチャクラスを、都道府県のフィーチャクラスとして統合
3.各市区町村のファイル ジオデータベース(FGDB) を削除
結果として、最終的には都道府県ファイル ジオデータベース(FGDB) に、農地の筆界ポリゴン(フィーチャクラス)として、統合されたFarmland フィーチャクラスだけが残ります(下図参照)。
出力フォルダー: 統合された都道府県ファイル ジオデータベース の作成先
例)
|-2024_02_filegdb
|-2024_022012.gdb # 市区町村別のFGDBは都道府県にマージ後に削除される
| c_2024_022012
|-2024_022021.gdb
| c_2024_022021
・・・・・
|-2024_02_filegdb.gdb # 市区町村別のフィーチャクラスをマージしたフィーチャクラスを格納する都道府県のファイルジオデータベース
| Farmland # フィーチャクラス名(コード内で固定)
それでは、マルチプロセスに関連したコーディング パターンの実装ポイントを中心に解説します。
1)ワーカー関数 : batch_convert
コーディング パターンとして、まずはワーカー関数を通常の ArcPyでの処理と同様に記述します。このときに注意するべきなのは、ベストプラクティスで取り上げたように、このワーカー関数での処理が1プロセスでの処理に相当するため、複数プロセス(複数のワーカー関数)から書込み先としてアクセスする必要がある場合、エンタープライズ ジオデータベースを利用するか、同じファイル ジオデータベースを使用しないようにすることです。
※今回は後者の同じファイル ジオデータベースを使用しないようにするため、ワーカー関数内で新規の市区町村ファイル ジオデータベースを作成し、そのファイル ジオデータベース内で変換処理を行うようにしています。
def batch_convert(in_jsonfile :str, outws :str) -> str:
'''
1プロセスで実行する処理:
1) FGDBへの書込みは仕様で複数プロセスで書込みできないため
1市区町村のGeoJSONファイルを 独自のFarmlandGeojsonToFeaturesEx クラスで1市区町村.gdb内のフィーチャクラスに変換
in_jsonfile : 市区町村のGeoJSONファイルへのパス
outws : 出力するgdb
'''
if not arcpy.Exists(outws):
outfolder = u"{0}".format(os.path.dirname(outws))
foldername= u"{0}".format(os.path.basename(outws))
arcpy.management.CreateFileGDB(outfolder, foldername, "CURRENT")
filename = os.path.basename(in_jsonfile)
in_json_file = os.path.splitext(filename)[0]
newfc = u"c_{0}".format(in_json_file) #数値で始まるファイル名はそのまま変換できないので接頭にc_を入れる
#独自のFarmlandGeojsonToFeaturesEx クラスで変換
#arcpy.conversion.JSONToFeatures(in_jsonfile, os.path.join(outws, newfc))
convGeojson = FarmlandGeojsonToFeaturesEx()
convGeojson.geojson_to_features(in_jsonfile, os.path.join(outws, newfc))
del convGeojson
return u" 変換済:{0}".format(outws)
2)ワーカー関数のラッパー
上述のように、現在のArcGIS Pro の環境で利用する際は不要のため、定義していません。
3)マルチプロセスの処理 : exec_batch_convert
各プロセス用のpythonw.exe へのパスを、multiprocessing.set_executable() で設定し、ワーカー関数へ渡す2つの引数(in_jsonfile, outws)を params にリスト化して入れます。その後、pool = multiprocessing.Pool(cpu_cnt) で確保した pool にresults = pool.starmap(batch_convert, params) で関数名 (batch_convert) と params を指定します。また、各ワーカー関数での処理が終了したら、
pool.close()
pool.join()
を呼び出します。
def exec_batch_convert(infolder :str, outfolder :str, cpu_cnt :int):
'''
マルチプロセスでの処理:
'''
try:
start = datetime.datetime.now()
arcpy.AddMessage(u"-- Strat: MP_Farmland_JsonToFeatureClass --:{0}".format(start))
#a) 各プロセス用の pythonw.exe を設定
python_path = sys.exec_prefix
multiprocessing.set_executable(os.path.join(python_path,'pythonw.exe'))
#multiprocessing.set_executable(os.path.join(python_path,'python.exe')) #CMDプロンプトの画面が起動するので'pythonw.exe'を使う
#b) 各プロセスに渡すパラメータをリスト化
arcpy.AddMessage(u" Convert each GeoJSON files : multiprocessing")
arcpy.env.workspace = infolder
infiles = arcpy.ListFiles("*.json")
params=[]
for infile in infiles:
param1 = os.path.join(infolder,infile) #市区町村のGeoJSONファイル
filename = os.path.basename(infile)
gdbname = u"{0}.gdb".format(os.path.splitext(filename)[0])
param2 = os.path.join(outfolder,gdbname) # 出力する市区町村ファイルジオデータベース
params.append((param1, param2))
if len(infiles) < cpu_cnt: # 処理ファイル数がCPUコアより少ない場合無駄なプロセスを起動不要
cpu_cnt = len(infiles)
pool = multiprocessing.Pool(cpu_cnt) # cpu数分プロセス作成
results = pool.starmap(batch_convert, params) # 割り当てプロセスで順次実行される(Python3.3で追加されたstarmapは複数の引数に対応)
pool.close()
pool.join()
# 各プロセスでの処理結果を出力
for r in results:
arcpy.AddMessage(u"{0}".format(r))
#c) 各プロセスで変換されたフィーチャクラスを都道府県のFGDBへマージしたものを作成("Farmland")
#~省略~
# フィールドエイリアスを設定
#~省略~
# コード値ドメインを設定
#~省略~
#d) マージが終わったので後片付け 各市区町村のFGDBを削除
#~省略~
fin = datetime.datetime.now()
arcpy.AddMessage(u"-- Finish: MP_Farmland_JsonToFeatureClass --:{0}".format(fin))
arcpy.AddMessage(u" Elapsed time:{0}".format(fin-start))
except:
arcpy.AddError(u"Exception:{0}".format(sys.exc_info()[2]))
ジオプロセシングツールへの組み込み
The conversion process for farmland polygons in GeoJSON format ends with the code above, so next,Python toolbox is defined, and I will explain the calling part from the geoprocessing tool.
In Python toolbox, as described in the help for What is a Python toolbox?, geoprocessing toolbox and geoprocessing tool are defined as classes.
1) Definition of toolbox and addition of tools
Two geoprocessing tools defined as class AgrilandJsonConvTool and class AgrilandShpConvTool are added during the initialization of class Toolbox,
self.tools = [AgrilandJsonConvTool, AgrilandShpConvTool]
is added.
# Import modules for multi-process support
from MP_Farmland_JsonToFeatureClass import exec_batch_convert as exec_batch_json_convert
from MP_Farmland_ShapefileToFeatureClass import exec_batch_convert as exec_batch_shp_convert
# Definition of Python toolbox
class Toolbox:
def __init__(self):
self.label = "Farmland Polygon Conversion Sample Toolbox"
self.alias = ""
self.tools = [AgrilandJsonConvTool, AgrilandShpConvTool]
# Definition of each geoprocessing tool
class AgrilandJsonConvTool:
# ~Omitted~
class AgrilandShpConvTool:
# ~Omitted~
2) Definition of tools
Each geoprocessing tool must at least write __init__, getParameterInfo, and execute to work as a geoprocessing tool on ArcGIS Pro.
This point only calls exec_batch_convert defined inside the file MP_Farmland_JsonToFeatureClass.py in execute (since a function with the same name for shapefile format is also added, it is imported with an alias exec_batch_json_convert).
class AgrilandJsonConvTool:
def __init__(self):
self.label = "01_MultiProcessSample_FarmlandPolygon(GeoJSONFormat)_ConversionTool"
self.description = ""
def getParameterInfo(self):
param0 = arcpy.Parameter(
displayName="Input Folder (Folder of Prefectures after unzipping)",
name="input_folder",
datatype="DEFolder",
parameterType="Required",
direction="Input")
# ~Omitted~
params = [param0, param1, param2]
return params
def execute(self, parameters, messages):
infolder = parameters[0].valueAsText
outfolder = parameters[1].valueAsText
cpu_cnt = int(parameters[2].valueAsText)
exec_batch_json_convert(infolder, outfolder, cpu_cnt)
return
3) Execution screen
This is the actual execution screen of "Farmland polygon conversion tools."
Example (GeoJSON file conversion)
To convert GeoJSON files in ArcGIS Pro, it is common to convert using "JSON → Features (JSON To Features)" but due to the following limitations, this time we respond with our own class (class FarmlandGeojsonToFeaturesEx).
- The geometry is defined as PolygonZ
- The field type is automatically determined, so later type reconversion may be necessary
- The tool does not support defining CRS codes for farmland polygon geometries
Summary
Using past blog posts about multi-process processing with ArcPy and Python as material,
- Updated coding pattern for Python 3.3 or higher
- Added example of integration into ArcGIS Pro geoprocessing tool
Based on the sample of "Farmland polygon conversion tools", I explained mainly the points when coding.
Depending on data processing, multi-process may not always be necessary, but if needed, please refer to the best practices and coding patterns in this article.
Related links
Multi-process processing with ArcGIS Python
Multi-process processing with ArcGIS Python - Implementation example