¿Paralizando operaciones GIS en PyQGIS?

15

Un requisito común en GIS es aplicar una herramienta de procesamiento a un número de archivos o aplicar un proceso para un número de características en un archivo a otro archivo.

Muchas de estas operaciones son vergonzosamente paralelas en el sentido de que los resultados de los cálculos de ninguna manera influyen en ninguna otra operación en el bucle. No solo eso, sino que a menudo los archivos de entrada son distintos.

Un ejemplo clásico es el mosaico de archivos de formas contra archivos que contienen polígonos para recortarlos.

Aquí hay un método de procedimiento clásico (probado) para lograr esto en un script de python para QGIS. (para la salida de archivos de memoria temporal a archivos reales más de la mitad del tiempo para procesar mis archivos de prueba)

import processing
import os
input_file="/path/to/input_file.shp"
clip_polygons_file="/path/to/polygon_file.shp"
output_folder="/tmp/test/"
input_layer = QgsVectorLayer(input_file, "input file", "ogr")
QgsMapLayerRegistry.instance().addMapLayer(input_layer)
tile_layer  = QgsVectorLayer(clip_polygons_file, "clip_polys", "ogr")
QgsMapLayerRegistry.instance().addMapLayer(tile_layer)
tile_layer_dp=input_layer.dataProvider()
EPSG_code=int(tile_layer_dp.crs().authid().split(":")[1])
tile_no=0
clipping_polygons = tile_layer.getFeatures()
for clipping_polygon in clipping_polygons:
    print "Tile no: "+str(tile_no)
    tile_no+=1
    geom = clipping_polygon.geometry()
    clip_layer=QgsVectorLayer("Polygon?crs=epsg:"+str(EPSG_code)+\
    "&field=id:integer&index=yes","clip_polygon", "memory")
    clip_layer_dp = clip_layer.dataProvider()
    clip_layer.startEditing()
    clip_layer_feature = QgsFeature()
    clip_layer_feature.setGeometry(geom)
    (res, outFeats) = clip_layer_dp.addFeatures([clip_layer_feature])
    clip_layer.commitChanges()
    clip_file = os.path.join(output_folder,"tile_"+str(tile_no)+".shp")
    write_error = QgsVectorFileWriter.writeAsVectorFormat(clip_layer, \
    clip_file, "system", \
    QgsCoordinateReferenceSystem(EPSG_code), "ESRI Shapefile")
    QgsMapLayerRegistry.instance().addMapLayer(clip_layer)
    output_file = os.path.join(output_folder,str(tile_no)+".shp")
    processing.runalg("qgis:clip", input_file, clip_file, output_file)
    QgsMapLayerRegistry.instance().removeMapLayer(clip_layer.id())

Esto estaría bien, excepto que mi archivo de entrada es de 2 GB y el archivo de recorte de polígonos contiene más de 400 polígonos. El proceso resultante lleva más de una semana en mi máquina de cuatro núcleos. Mientras tanto, tres núcleos simplemente están inactivos.

La solución que tengo en mi cabeza es exportar el proceso a los archivos de script y ejecutarlos de forma asíncrona utilizando gnu paralelo, por ejemplo. Sin embargo, parece una pena tener que abandonar QGIS en una solución específica del sistema operativo en lugar de usar algo nativo de QGIS python. Así que mi pregunta es:

¿Puedo paralelizar operaciones geográficas vergonzosamente paralelas de forma nativa dentro de Python QGIS?

Si no, ¿quizás alguien ya tenga el código para enviar este tipo de trabajo a los scripts de shell asíncronos?

    
pregunta Mr Purple 28.10.2014 - 01:59

3 respuestas

3

Aquí está la solución paralela de GNU. Con un poco de cuidado, la mayoría de los algoritmos de sgr o saga basados en Linux paralelos se pueden hacer para que funcionen con él dentro de su instalación de QGIS.

Obviamente, esta solución requiere la instalación de gnu paralelo. Para instalar gnu parallel en Ubuntu, por ejemplo, vaya a su terminal y escriba

sudo apt-get -y install parallel

NB: no conseguí que el comando de shell paralelo funcionara en Popen o en el subproceso, que hubiera preferido, así que piraté una exportación a un script de bash y lo ejecuté con Popen en su lugar.

Aquí está el comando de shell específico usando paralelo que envolví en Python

parallel ogr2ogr -skipfailures -clipsrc tile_{1}.shp output_{1}.shp input.shp ::: {1..400}

Cada {1} se intercambia por un número del rango {1..400} y luego los cuatrocientos comandos de shell son administrados por gnu en paralelo para usar simultáneamente todos los núcleos de mi i7 :).

Aquí está el código Python real que escribí para resolver el problema de ejemplo que publiqué. Uno podría pegarlo directamente después del final del código en la pregunta.

import stat
from subprocess import Popen
from subprocess import PIPE
feature_count=tile_layer.dataProvider().featureCount()
subprocess_args=["parallel", \
"ogr2ogr","-skipfailures","-clipsrc",\
os.path.join(output_folder,"tile_"+"{1}"+".shp"),\
os.path.join(output_folder,"output_"+"{1}"+".shp"),\
input_file,\
" ::: ","{1.."+str(feature_count)+"}"]
#Hacky part where I write the shell command to a script file
temp_script=os.path.join(output_folder,"parallelclip.sh")
f = open(temp_script,'w')
f.write("#!/bin/bash\n")
f.write(" ".join(subprocess_args)+'\n')
f.close()
st = os.stat(temp_script)
os.chmod(temp_script, st.st_mode | stat.S_IEXEC)
#End of hacky bash script export
p = Popen([os.path.join(output_folder,"parallelclip.sh")],\
stdin=PIPE, stdout=PIPE, stderr=PIPE)
#Below is the commented out Popen line I couldn't get to work
#p = Popen(subprocess_args, stdin=PIPE, stdout=PIPE, stderr=PIPE)
output, err = p.communicate(b"input data that is passed to subprocess' stdin")
rc = p.returncode
print output
print err

#Delete script and old clip files
os.remove(os.path.join(output_folder,"parallelclip.sh"))
for i in range(feature_count):
    delete_file = os.path.join(output_folder,"tile_"+str(i+1)+".shp")
    nosuff=os.path.splitext(delete_file)[0]
    suffix_list=[]
    suffix_list.append('.shx')
    suffix_list.append('.dbf')
    suffix_list.append('.qpj')
    suffix_list.append('.prj')
    suffix_list.append('.shp')
    suffix_list.append('.cpg')
    for suffix in suffix_list:
        try:
            os.remove(nosuff+suffix)
        except:
            pass

Déjame decirte que realmente es algo cuando ves que todos los núcleos se activan hasta el máximo ruido :). Un agradecimiento especial a Ole y al equipo que construyó Gnu Parallel.

Sería bueno tener una solución multiplataforma y sería bueno si hubiera podido averiguar el módulo de multiproceso de python para el qgis python incrustado, pero lamentablemente no fue así.

A pesar de esto, esta solución me servirá y tal vez a usted muy bien.

    
respondido por el Mr Purple 30.10.2014 - 09:59
11

Si cambia su programa para leer el nombre del archivo desde la línea de comandos y divide su archivo de entrada en partes más pequeñas, puede hacer algo como esto utilizando GNU Parallel:

parallel my_processing.py {} /path/to/polygon_file.shp ::: input_files*.shp

Esto ejecutará 1 trabajo por núcleo.

Todas las computadoras nuevas tienen múltiples núcleos, pero la mayoría de los programas son de naturaleza serial y por lo tanto no usarán los múltiples núcleos. Sin embargo, muchas tareas son extremadamente paralelizables:

  • Ejecuta el mismo programa en muchos archivos
  • Ejecutar el mismo programa para cada línea en un archivo
  • Ejecuta el mismo programa para cada bloque en un archivo

GNU Parallel es un paralelizador general y facilita la ejecución de trabajos en paralelo en la misma máquina o en varias máquinas a las que tiene acceso ssh.

Si tiene 32 trabajos diferentes que desea ejecutar en 4 CPU, una forma directa de paralelizar es ejecutar 8 trabajos en cada CPU:

GNUParallelensulugargeneraunnuevoprocesocuandounotermina,manteniendolasCPUactivasy,porlotanto,ahorrandotiempo:

Instalación

Si GNU Parallel no está empaquetado para su distribución, puede hacer una instalación personal, que no requiere acceso de root. Se puede hacer en 10 segundos haciendo esto:

(wget -O - pi.dk/3 || curl pi.dk/3/ || fetch -o - http://pi.dk/3) | bash

Para otras opciones de instalación, consulte enlace

Más información

Vea más ejemplos: enlace

Vea los videos de introducción: enlace

Recorra el tutorial: enlace

Regístrese en la lista de correo electrónico para obtener asistencia: enlace

    
respondido por el Ole Tange 28.10.2014 - 08:18
4

En lugar de usar el método paralelo de GNU, puede usar el módulo pya mutliprocess para crear un grupo de Tareas y ejecuciones. No tengo acceso a una configuración de QGIS para probarlo, pero se agregó multiproceso en Python 2.6, por lo que siempre que esté usando 2.6 o posterior debería estar disponible. Hay muchos ejemplos en línea sobre el uso de este módulo.

    
respondido por el Steve Barnes 04.11.2014 - 12:13

Lea otras preguntas en las etiquetas