Распараллеливание операций ГИС в PyQGIS?

15

Общим требованием в ГИС является применение инструмента обработки к нескольким файлам или применение процесса для ряда функций в одном файле к другому файлу.

Большая часть этих операций смущает параллель в том, что результаты вычислений никоим образом не влияют на другие операции в цикле. Не только это, но часто входные файлы различны.

Классический пример - разбиение файлов фигур на файлы, содержащие полигоны, для их ограничения.

Вот (проверенный) классический процедурный метод для достижения этого в скрипте Python для QGIS. (к сожалению, вывод временных файлов памяти в реальные файлы более чем вдвое сократил время обработки моих тестовых файлов)

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())

Это было бы хорошо, за исключением того, что мой входной файл имеет размер 2 ГБ, а файл обрезки полигонов содержит более 400 полигонов. Получающийся процесс занимает больше недели на моей четырехъядерной машине. Все это время три ядра просто бездействуют.

Решение, которое я имею в своей голове, состоит в том, чтобы экспортировать процесс в файлы сценариев и запускать их асинхронно с использованием, например, параллельной GNU. Тем не менее, стыдно бросить QGIS в решение для конкретной ОС, а не использовать нечто родное для Python QGIS. Итак, мой вопрос:

Могу ли я параллельно смущать параллельные географические операции внутри Python QGIS?

Если нет, то, возможно, у кого-то уже есть код для отправки такого рода работы асинхронным сценариям оболочки?

Мистер пурпурный
источник
Не знаком с многопроцессорностью в QGIS, но этот специфичный для ArcGIS пример может быть полезен
blah238
Выглядит интересно. Я посмотрю, что я могу с этим сделать.
Мистер Пурпур

Ответы:

11

Если вы измените свою программу на чтение имени файла из командной строки и разделите ваш входной файл на более мелкие куски, вы можете сделать что-то подобное, используя GNU Parallel:

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

Это запустит 1 задание на ядро.

Все новые компьютеры имеют несколько ядер, но большинство программ носят последовательный характер и поэтому не будут использовать несколько ядер. Однако многие задачи чрезвычайно распараллеливаемы:

  • Запустите одну и ту же программу на нескольких файлах
  • Запустите одну и ту же программу для каждой строки в файле
  • Запустите одну и ту же программу для каждого блока в файле

GNU Parallel - это общий параллелизатор, который позволяет легко запускать задания параллельно на одной и той же машине или на нескольких машинах, к которым у вас есть доступ по ssh.

Если у вас есть 32 различных задания, которые вы хотите запустить на 4 процессорах, прямой способ распараллеливания - запустить 8 заданий на каждом процессоре:

Простое планирование

GNU Parallel вместо этого порождает новый процесс после его завершения - поддерживая активные процессоры и, таким образом, экономя время:

Параллельное планирование GNU

Установка

Если GNU Parallel не упакован для вашего дистрибутива, вы можете выполнить личную установку, которая не требует root-доступа. Это можно сделать за 10 секунд, выполнив это:

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

Для других вариантов установки см. Http://git.savannah.gnu.org/cgit/parallel.git/tree/README.

Учить больше

Смотрите больше примеров: http://www.gnu.org/software/parallel/man.html

Посмотрите вступительные видеоролики: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1

Просмотрите руководство: http://www.gnu.org/software/parallel/parallel_tutorial.html.

Зарегистрируйтесь для получения списка поддержки по электронной почте: https://lists.gnu.org/mailman/listinfo/parallel

Оле Танге
источник
Это что-то вроде того, что я собирался попробовать, но мне нужно, чтобы все оставалось внутри питона. Строка нуждается в переписывании, чтобы использовать, например, скажем Popen ... Что-то вроде: из подпроцесса import Popen, PIPE p = Popen (["parallel", "ogr2ogr", "- clipsrc", "clip_file * .shp", "output *" .shp "input.shp"], stdin = PIPE, stdout = PIPE, stderr = PIPE) Проблема в том, что я пока не знаю, как правильно подготовить синтаксис
Мистер Пурпур,
Потрясающий ответ. Раньше я не сталкивался с тройными (или четырехкратными) операторами двоеточия (хотя в настоящее время я делаю mooc на Haskell для edX, так что, несомненно, произойдет нечто подобное). Я согласен с вами о Санте, призраках, феях и богах, но определенно не о гоблинах: D
Джон Пауэлл
@MrPurple Я думаю, что комментарий заслуживает вопроса сам по себе. Ответ определенно слишком длинный, чтобы добавить комментарий.
Оле Танге
ОК, спасибо за ссылки. Если я сформулирую ответ, используя параллель gnu, я выложу его здесь.
Мистер Пурпур,
Хорошая формулировка для вас my_processing.pyможет быть найдена в gis.stackexchange.com/a/130337/26897
Мистер Пурпур
4

Вместо использования метода GNU Parallel вы можете использовать модуль python mutliprocess для создания пула задач и их выполнения. У меня нет доступа к настройке QGIS, чтобы протестировать ее, но в Python 2.6 был добавлен многопроцессорный режим, поэтому при использовании версии 2.6 или более поздней она должна быть доступна. В Интернете есть много примеров использования этого модуля.

Стив Барнс
источник
2
Я попробовал многопроцессорность, но до сих пор не увидел, как она успешно реализована во встроенном питоне QGIS. Я столкнулся с рядом проблем, когда попробовал. Я могу опубликовать их как отдельные вопросы. Насколько я могу судить, нет открытых примеров для тех, кто начинает с этого.
Мистер Пурпур,
Это настоящий позор. Если бы кто-то мог написать пример многопроцессорного модуля, заключающего в себе одну функцию pyQGIS, как я делал с параллелью gnu, тогда мы могли бы все уйти и распараллелить все, что мы выбрали.
Мистер Пурпур,
Я согласен, но, как я уже сказал, в данный момент у меня нет доступа к QGIS.
Стив Барнс
Этот вопрос и ответ могут быть полезны,
Стив Барнс,
@MrPurple и этот gis.stackexchange.com/questions/114260/… приводит пример
Стив Барнс,
3

Вот параллельное решение GNU. С некоторой тщательностью можно было бы создать большинство изумительно параллельных алгоритмов ogr или saga на основе linux, чтобы они работали с ним внутри вашей установки QGIS.

Очевидно, что это решение требует установки GNU параллельно. Например, чтобы установить GNU параллельно в Ubuntu, перейдите в свой терминал и введите

sudo apt-get -y install parallel

NB: Я не мог заставить команду параллельной оболочки работать в Popen или подпроцессе, что я бы предпочел, поэтому я взломал экспорт в скрипт bash и вместо этого запустил его с Popen.

Вот конкретная команда оболочки, использующая параллель, которую я обернул в python

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

Каждое {1} заменяется числом из диапазона {1..400}, а затем gnu параллельно управляет четырьмя сотнями команд оболочки для одновременного использования всех ядер моего i7 :).

Вот фактический код Python, который я написал, чтобы решить примерную проблему, которую я разместил. Можно вставить его сразу после конца кода в вопросе.

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

Позвольте мне сказать вам, что это действительно что-то, когда вы видите, что все ядра работают на полную мощность :). Отдельное спасибо Оле и команде, которая построила Gnu Parallel.

Было бы неплохо иметь кроссплатформенное решение, и было бы неплохо, если бы я мог найти модуль многопроцессорного Python для встроенного в Python qgis, но, увы, этого не произошло.

Независимо от этого решение будет служить мне и, возможно, вам приятно.

Мистер пурпурный
источник
Очевидно, что нужно закомментировать строку «processing.runalg» в первом фрагменте кода, чтобы клип не запускался сначала последовательно, а потом параллельно. Кроме этого, это просто вопрос копирования и вставки кода из ответа под кодом в вопросе.
Мистер Пурпур
Если вы просто хотите запустить множество команд обработки, таких как набор «qgis: dissolved», применяемых параллельно к разным файлам, вы можете увидеть мой процесс для этого на purplelinux.co.nz/?p=190
Mr Purple