Introducción
En publicaciones anteriores, bajo el título "Procesamiento por lotes en Python para ArcGIS" y "Procesamiento por lotes en Python para ArcGIS - Ejemplo de implementación", presentamos procesamiento por lotes con ArcPy y Python, mostrando ejemplos básicos y patrones de codificación.Esta vez, además de actualizar el patrón de codificación para Python 3.3 o superior, añadimos un ejemplo de integración con herramientas de geoprocesamiento de ArcGIS Pro.
Cabe destacar que el código explicado en este artículo ya está disponible como "Farmland polygon conversion tools", una herramienta de Python Toolbox pública que puede usarse inmediatamente. Por favor, consulte esa referencia.
Patrón de codificación
En la publicación anterior, asignamos tres puntos clave como patrón de codificación.Estos son:
1) Función worker: batch_convert
2) Wrapper para función worker: multi_run_batch_convert * No es necesario esta vez
3) Procesamiento por lotes: exec_batch_convert
Sin embargo, desde Python 3.3 se añadió starmap que permite llamar a Pool del módulo multiprocessing con múltiples argumentos. Por ello, al usarlo en el entorno actual de ArcGIS Pro, no es necesario el wrapper para la función worker y se puede simplificar el código.
A continuación, explicaremos los puntos clave del patrón de codificación basándonos en un ejemplo.
Código de ejemplo (conversión de polígonos agrícolas en formato GeoJSON)
Los datos a convertir son, al igual que en la publicación anterior, un ejemplo basado en los datos "Información sobre delimitación agrícola (polígonos)" publicados por el Ministerio de Agricultura, Silvicultura y Pesca.
* Debido a que la estructura del polígono ha cambiado respecto a la publicación anterior, el ejemplo subido al blog es una explicación basada en código que convierte los polígonos agrícolas más recientes (en formato GeoJSON).El ejemplo actual (MP_Farmland_JsonToFeatureClass.py) procesa uno de los "folders prefecturales" extraídos tras descomprimir el ZIP con los polígonos agrícolas.
En estado descomprimido, bajo cada "folder prefectural" hay múltiples archivos GeoJSON separados por ciudad/distrito/pueblo (la extensión del archivo es json).
Carpeta de entrada: carpeta prefectural que contiene archivos GeoJSON separados por ciudad/distrito/pueblo
Ejemplo)
|-2024_02
|-2024_022012.json # GeoJSON separado por ciudad/distrito/pueblo
|-2024_022021.json
|-2024_022039.json
.....
En el programa de ejemplo actual, esta carpeta prefectural se usa como carpeta de entrada y se realiza el siguiente procesamiento.
1. Crear bases de datos geográficas (FGDB) para cada archivo separado por ciudad/distrito/pueblo y convertir los polígonos agrícolas a clases de entidad (este procesamiento se realiza mediante procesamiento por lotes)
2. Crear una base de datos geográfica (FGDB) para la carpeta prefectural y consolidar las clases de entidad creadas en 1. como una clase unificada
3. Eliminar las clases creadas separadamente para cada ciudad/distrito/pueblo
Como resultado final, solo queda la clase unificada Farmland como clase de entidad (ver imagen abajo).
Carpeta de salida: ubicación donde se crea la base de datos geográfica unificada para la carpeta prefectural
Ejemplo)
|-2024_02_filegdb
|-2024_022012.gdb # FGDB separado por ciudad/distrito/pueblo será eliminado después de consolidar en carpeta prefectural
| c_2024_022012
|-2024_022021.gdb
| c_2024_022021
·····
|-2024_02_filegdb.gdb # Base de datos geográfica unificada para la carpeta prefectural que consolida las clases separadas por ciudad/distrito/pueblo
| Farmland # Nombre fijo para la clase (fijo dentro del código)
Ahora explicaremos los puntos clave del patrón de codificación relacionado con procesamiento por lotes.
1) Función worker : batch_convert
Como patrón de codificación, primero describimos la función worker similar al procesamiento habitual con ArcPy. Lo importante aquí es que para poder procesar correctamente lo obtenido con best practice, esta función worker debe corresponder al procesamiento en 1 proceso; si se accede desde múltiples procesos (múltiples funciones worker), es necesario utilizar enterprise geodatabase o evitar usar la misma clase dentro del mismo archivo geodatabase.
* Para evitar usar la misma clase dentro del mismo archivo geodatabase posteriormente, creamos nuevas bases geodatabase para archivos separados por ciudad/distrito/pueblo dentro de la función worker y realizamos el procesamiento dentro del archivo correspondiente.
def batch_convert(in_jsonfile :str, outws :str) -> str:
'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"','
Procesamiento ejecutado en 1 proceso:
1) Escritura a FGDB no puede hacerse desde múltiples procesos simultáneamente
Convierte archivo GeoJSON separado por ciudad/distrito/pueblo a clase entidad dentro del gdb correspondiente usando FarmlandGeojsonToFeaturesEx
','
in_jsonfile : ruta al archivo GeoJSON separado por ciudad/distrito/pueblo
outws : gdb donde se escribe la salida
'''
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) # Los nombres numéricos no pueden empezar directamente; se añade prefijo c_
# Conversión usando clase FarmlandGeojsonToFeaturesEx personalizada
#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" Conversión completada: {0}".format(outws)
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) Wrapper para función worker
Como se mencionó arriba, no es necesario definirlo actualmente para usarlo en el entorno ArcGIS Pro actual.
3) Procesamiento por lotes : exec_batch_convert
Se configura la ruta a pythonw.exe para cada proceso pythonw.exe usando multiprocessing.set_executable(), y se pasa una lista con dos argumentos (in_jsonfile, outws) a la función worker.'pool = multiprocessing.Pool(cpu_cnt)' asegura el pool creado con cpu_cnt procesos.'results = pool.starmap(batch_convert, params)' especifica el nombre función (batch_convert) y parámetros params.
'pool.close()'
'pool.join()'
se llaman después cuando termina el procesamiento en cada función worker.
def exec_batch_convert(infolder :str, outfolder :str, cpu_cnt :int):
'''
Procesamiento por lotes:
'''
try:
start = datetime.datetime.now()
arcpy.AddMessage(u"-- Inicio: MP_Farmland_JsonToFeatureClass --:{0}".format(start))
#a) Configurar pythonw.exe para cada proceso
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')) # Se usa 'pythonw.exe' porque CMD prompt abre ventana
#b) Convertir parámetros pasados a lista para cada proceso
arcpy.AddMessage(u" Convertir cada archivo GeoJSON : 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]))
ジオプロセシングツールへの組み込み
El procesamiento de conversión de polígonos agrícolas en formato GeoJSON termina con el código anterior, por lo que a continuación,Herramientas de Python Toolbox se centra en explicar la parte de llamada desde la herramienta de geoprocesamiento definida en Python Toolbox.
En las herramientas de Python Toolbox, como se indica en la ayuda de ¿Qué es una Python toolbox?, las herramientas de geoprocesamiento y las herramientas de geoprocesamiento están definidas como clases.
1) Definición y adición de herramientas
Dos herramientas de geoprocesamiento definidas como class AgrilandJsonConvTool y class AgrilandShpConvTool se añaden durante la inicialización de la clase Toolbox,
self.tools = [AgrilandJsonConvTool, AgrilandShpConvTool]
así se añaden.
# Importar módulos compatibles con multiproceso
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
# Definición de Python toolbox
class Toolbox:
def __init__(self):
self.label = "Conjunto de herramientas para conversión de polígonos agrícolas"
self.alias = ""
self.tools = [AgrilandJsonConvTool, AgrilandShpConvTool]
# Definición de cada herramienta de geoprocesamiento
class AgrilandJsonConvTool:
# ~省略~
class AgrilandShpConvTool:
# ~省略~
2) Definición de herramientas
Cada herramienta de geoprocesamiento debe definir al menos __init__, getParameterInfo y execute para funcionar como herramienta de geoprocesamiento en ArcGIS Pro.
El punto principal esta vez es que en execute se llama a exec_batch_convert definido dentro del archivo MP_Farmland_JsonToFeatureClass.py (se asigna un alias exec_batch_json_convert al importar para evitar conflictos con funciones del mismo nombre en formato shapefile).
class AgrilandJsonConvTool:
def __init__(self):
self.label = "01_Multiproceso ejemplo_conversión polígonos agrícolas (formato GeoJSON)_herramienta de conversión"
self.description = ""
def getParameterInfo(self):
param0 = arcpy.Parameter(
displayName="Carpeta de entrada (carpeta prefectural después del desempaquetado)",
name="input_folder",
datatype="DEFolder",
parameterType="Required",
direction="Input")
# ~省略~
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) Pantalla de ejecución
Esta es la pantalla real de ejecución de "Farmland polygon conversion tools".
Nota (conversión del archivo GeoJSON)
Para convertir archivos GeoJSON en ArcGIS Pro, generalmente se usa "JSON → Features (JSON To Features)", pero debido a ciertas limitaciones, esta vez respondemos con nuestra propia clase (class FarmlandGeojsonToFeaturesEx).JSON → Features (JSON To Features)