Równoległe operacje GIS w PyQGIS?

15

Częstym wymogiem w GIS jest zastosowanie narzędzia przetwarzania do wielu plików lub zastosowanie procesu dla wielu funkcji w jednym pliku do innego pliku.

Wiele z tych operacji jest krępująco równoległych, ponieważ wyniki obliczeń w żaden sposób nie wpływają na żadną inną operację w pętli. Nie tylko to, ale często pliki wejściowe są różne.

Klasycznym przykładem jest kafelkowanie plików kształtów w stosunku do plików zawierających wielokąty w celu przycięcia ich.

Oto (przetestowana) klasyczna metoda proceduralna umożliwiająca osiągnięcie tego w skrypcie Python dla QGIS. (fyi wyjściowe pliki pamięci tymczasowej do prawdziwych plików ponad połowę czasu na przetwarzanie moich plików testowych)

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

Byłoby dobrze, gdyby mój plik wejściowy miał 2 GB, a plik przycinający wielokąt zawiera ponad 400 wielokątów. Wynikowy proces trwa ponad tydzień na mojej czterordzeniowej maszynie. Przez cały czas trzy rdzenie są na biegu jałowym.

Rozwiązaniem, które mam w głowie, jest wyeksportowanie procesu do plików skryptów i uruchomienie ich asynchronicznie, na przykład przy użyciu równoległego GNU. Jednak szkoda, że ​​trzeba zrezygnować z QGIS w rozwiązaniu specyficznym dla systemu operacyjnego, zamiast używać czegoś natywnego dla Pythona QGIS. Więc moje pytanie brzmi:

Czy mogę równolegle zawstydzać równoległe operacje geograficzne natywnie w Pythonie QGIS?

Jeśli nie, to może ktoś już ma kod, aby wysłać tego rodzaju pracę do asynchronicznych skryptów powłoki?

Mr Purple
źródło
Nie zaznajomiony z wieloprocesowością w QGIS, ale ten przykład specyficzny dla ArcGIS może się przydać
blah238
Wygląda ciekawie. Zobaczę, co mogę z tym zrobić.
Mr Purple,

Odpowiedzi:

11

Jeśli zmienisz program tak, aby czytał nazwę pliku z wiersza poleceń i podzieliłeś plik wejściowy na mniejsze części, możesz zrobić coś takiego za pomocą GNU Parallel:

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

Spowoduje to uruchomienie 1 zadania na rdzeń.

Wszystkie nowe komputery mają wiele rdzeni, ale większość programów ma charakter szeregowy i dlatego nie będzie używać wielu rdzeni. Jednak wiele zadań można bardzo zrównoleglać:

  • Uruchom ten sam program na wielu plikach
  • Uruchom ten sam program dla każdej linii w pliku
  • Uruchom ten sam program dla każdego bloku w pliku

GNU Parallel to ogólny paralelizator, który ułatwia równoległe uruchamianie zadań na tej samej maszynie lub na wielu maszynach, do których masz dostęp ssh.

Jeśli masz 32 różne zadania, które chcesz uruchomić na 4 procesorach, prostym sposobem na zrównoleglenie jest uruchomienie 8 zadań na każdym procesorze:

Proste planowanie

Zamiast tego GNU Parallel odradza nowy proces po zakończeniu - utrzymując procesory aktywne, a tym samym oszczędzając czas:

Planowanie równoległe GNU

Instalacja

Jeśli GNU Parallel nie jest spakowany dla twojej dystrybucji, możesz wykonać osobistą instalację, która nie wymaga dostępu do roota. Można to zrobić w 10 sekund, wykonując następujące czynności:

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

Inne opcje instalacji można znaleźć na stronie http://git.savannah.gnu.org/cgit/parallel.git/tree/README

Ucz się więcej

Zobacz więcej przykładów: http://www.gnu.org/software/parallel/man.html

Obejrzyj filmy wprowadzające: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1

Przejrzyj samouczek: http://www.gnu.org/software/parallel/parallel_tutorial.html

Zarejestruj się, aby otrzymać listę e-mail, aby uzyskać pomoc: https://lists.gnu.org/mailman/listinfo/parallel

Ole Tange
źródło
To jest coś, co zamierzałem spróbować, ale potrzebuję tego, aby wszyscy pozostali w Pythonie. Linia wymaga przepisania, aby użyć powiedzmy na przykład Popen ... Coś w stylu: z importu podprocesu Popen, PIPE p = Popen ([„równoległy”, „ogr2ogr”, „- clipsrc”, „plik_pliku * .shp”, „wyjście * .shp "input.shp"], stdin = PIPE, stdout = PIPE, stderr = PIPE) Problem w tym, że jeszcze nie wiem, jak prawidłowo przygotować składnię
Mr Purple
Świetna odpowiedź. Nie spotkałem wcześniej potrójnych (lub poczwórnych) operatorów jelita grubego (chociaż obecnie robię mooc Haskell na edX, więc bez wątpienia coś podobnego przyjdzie). Zgadzam się z tobą w sprawie Świętego Mikołaja, duchów, wróżek i bogów, ale zdecydowanie nie gobliny: D
John Powell
@MrPurple Myślę, że ten komentarz sam w sobie uzasadnia pytanie. Odpowiedź jest zdecydowanie za długa, aby dodać komentarz.
Ole Tange
OK, dzięki za linki. Jeśli sformułuję odpowiedź przy użyciu GNU równoległego, opublikuję ją tutaj.
Mr Purple
Dobre sformułowanie my_processing.pymożna znaleźć na stronie gis.stackexchange.com/a/130337/26897
Mr Purple
4

Zamiast korzystać z metody GNU Parallel, możesz użyć modułu mutliprocess Pythona , aby utworzyć pulę zadań i wykonać je. Nie mam dostępu do konfiguracji QGIS, aby ją przetestować, ale w Pythonie 2.6 dodano proces wieloprocesowy, więc jeśli używasz wersji 2.6 lub nowszej, powinna być dostępna. Istnieje wiele przykładów online dotyczących korzystania z tego modułu.

Steve Barnes
źródło
2
Uruchomiłem proces wieloprocesowy, ale jeszcze nie widziałem, aby został skutecznie zaimplementowany w osadzonym pythonie QGIS. Podczas wypróbowania natrafiłem na wiele problemów. Mogę opublikować je jako osobne pytania. O ile mogę stwierdzić, nie ma publicznych przykładów dostępnych dla kogoś, kto zaczyna z tym.
Mr Purple
Szkoda. Gdyby ktoś mógł napisać przykład modułu wieloprocesowego owijającego pojedynczą funkcję pyQGIS, tak jak to zrobiłem z GNU równoległym, wszyscy moglibyśmy odejść i zrównoleglić wszystko, co wybraliśmy.
Mr Purple
Zgadzam się, ale jak powiedziałem, w tej chwili nie mam dostępu do QGIS.
Steve Barnes,
To pytanie i odpowiedź mogą być pomocne, jeśli działasz w
Steve Barnes,
@MrPurple i ten gis.stackexchange.com/questions/114260/... podaje przykład
Steve Barnes
3

Oto równoległe rozwiązanie GNU. Z pewną ostrożnością można wprowadzić najbardziej zawstydzająco równoległe algorytmy ogr lub saga oparte na systemie Linux do pracy z nim w instalacji QGIS.

Oczywiście to rozwiązanie wymaga instalacji GNU równolegle. Aby na przykład zainstalować GNU równolegle w Ubuntu, przejdź do terminala i wpisz

sudo apt-get -y install parallel

NB: Nie mogłem uruchomić polecenia powłoki równoległej w Popen lub podprocesie, co wolałbym, więc zhakowałem eksport do skryptu bash i zamiast tego uruchomiłem to z Popenem.

Oto specyficzne polecenie powłoki przy użyciu równoległego, które zawinąłem w python

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

Każde {1} zostaje zamienione na liczbę z zakresu {1..400}, a następnie czterysta poleceń powłoki jest zarządzanych przez GNU równolegle, aby jednocześnie używać wszystkich rdzeni mojego i7 :).

Oto kod napisany przeze mnie, aby rozwiązać przykładowy problem, który opublikowałem. Można go wkleić bezpośrednio po końcu kodu w pytaniu.

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

Pozwól mi powiedzieć, że to naprawdę coś, gdy zobaczysz, że wszystkie rdzenie zapalają się do pełnego hałasu :). Specjalne podziękowania dla Ole i zespołu, który zbudował Gnu Parallel.

Byłoby miło mieć rozwiązanie wieloplatformowe i byłoby miło, gdybym mógł wymyślić wieloprocesowy moduł python dla osadzonego pytona qgis, ale niestety tak nie było.

Niezależnie od tego to rozwiązanie będzie dla mnie przydatne, a może i dla Ciebie.

Mr Purple
źródło
Oczywiście należy skomentować wiersz „processing.runalg” w pierwszym fragmencie kodu, aby klip nie był uruchamiany sekwencyjnie jako pierwszy przed równoległym uruchomieniem. Poza tym jest to po prostu kwestia skopiowania i wklejenia kodu z odpowiedzi pod kodem w pytaniu.
Mr Purple,
Jeśli chcesz po prostu uruchomić wiele poleceń przetwarzania, takich jak zestaw „qgis: rozwiązanie” zastosowanych równolegle do różnych plików, możesz zobaczyć mój proces na purplelinux.co.nz/?p=190
Mr Purple