Memparalelkan operasi GIS di PyQGIS?

15

Persyaratan umum dalam GIS adalah untuk menerapkan alat pemrosesan ke sejumlah file atau menerapkan proses untuk sejumlah fitur dalam satu file ke file lain.

Banyak dari operasi ini secara paralel memalukan karena hasil perhitungan sama sekali tidak memengaruhi operasi lainnya dalam loop. Tidak hanya itu tetapi seringkali file input masing-masing berbeda.

Kasus klasik yang dimaksud adalah pemasangan file bentuk terhadap file yang berisi poligon untuk klip mereka.

Berikut adalah metode prosedural klasik (teruji) untuk mencapai ini dalam skrip python untuk QGIS. (dari output file memori sementara ke file nyata lebih dari setengahnya waktu untuk memproses file pengujian saya)

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

Ini akan baik-baik saja kecuali bahwa file input saya adalah 2GB dan file kliping poligon berisi 400+ poligon. Proses yang dihasilkan memakan waktu lebih dari seminggu di mesin quad core saya. Sementara tiga core hanya menganggur.

Solusi yang saya miliki di kepala saya adalah untuk mengekspor proses ke file skrip dan menjalankannya secara asinkron menggunakan gnu parallel misalnya. Namun sepertinya memalukan harus keluar dari QGIS ke dalam solusi spesifik OS daripada menggunakan sesuatu yang asli dari python QGIS. Jadi pertanyaan saya adalah:

Dapatkah saya memparalelkan operasi geografis paralel memalukan secara native di dalam python QGIS?

Jika tidak, maka mungkin seseorang sudah memiliki kode untuk mengirim pekerjaan semacam ini ke skrip shell asinkron?

Tuan Ungu
sumber
Tidak terbiasa dengan multi-pemrosesan dalam QGIS, tetapi contoh spesifik ArcGIS ini mungkin bermanfaat: gis.stackexchange.com/a/20352/753
blah238
Terlihat menarik. Saya akan melihat apa yang bisa saya lakukan dengannya.
Tuan Ungu

Jawaban:

11

Jika Anda mengubah program Anda untuk membaca nama file dari baris perintah dan membagi file input Anda menjadi potongan-potongan yang lebih kecil, Anda dapat melakukan sesuatu seperti ini menggunakan GNU Parallel:

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

Ini akan menjalankan 1 pekerjaan per inti.

Semua komputer baru memiliki banyak inti, tetapi sebagian besar program bersifat serial dan karenanya tidak akan menggunakan banyak inti. Namun, banyak tugas yang sangat dapat diparalelkan:

  • Jalankan program yang sama pada banyak file
  • Jalankan program yang sama untuk setiap baris dalam file
  • Jalankan program yang sama untuk setiap blok dalam file

GNU Parallel adalah pemaralel umum dan membuatnya mudah untuk menjalankan pekerjaan secara paralel pada mesin yang sama atau pada beberapa mesin yang Anda miliki akses ssh.

Jika Anda memiliki 32 pekerjaan berbeda yang ingin Anda jalankan pada 4 CPU, cara langsung untuk memparalelkan adalah menjalankan 8 pekerjaan pada setiap CPU:

Penjadwalan sederhana

GNU Paralel sebagai gantinya memunculkan proses baru ketika proses selesai - menjaga CPU tetap aktif dan dengan demikian menghemat waktu:

Penjadwalan Paralel GNU

Instalasi

Jika GNU Parallel tidak dikemas untuk distribusi Anda, Anda dapat melakukan instalasi pribadi, yang tidak memerlukan akses root. Ini dapat dilakukan dalam 10 detik dengan melakukan ini:

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

Untuk opsi instalasi lain, lihat http://git.savannah.gnu.org/cgit/parallel.git/tree/README

Belajarlah lagi

Lihat lebih banyak contoh: http://www.gnu.org/software/parallel/man.html

Tonton video intro: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1

Ikuti tutorialnya: http://www.gnu.org/software/parallel/parallel_tutorial.html

Mendaftar untuk daftar email untuk mendapatkan dukungan: https://lists.gnu.org/mailman/listinfo/parallel

Ole Tange
sumber
Ini adalah sesuatu yang saya akan coba dan coba, tetapi saya membutuhkannya untuk tetap di dalam python. Baris perlu ditulis ulang untuk menggunakan katakan Popen misalnya ... Sesuatu seperti: dari impor subproses Popen, PIPE p = Popen (["paralel", "ogr2ogr", "- clipsrc", "clip_file * .shp", "output * .shp "input.shp"], stdin = PIPE, stdout = PIPE, stderr = PIPE) Masalahnya adalah saya belum tahu bagaimana mempersiapkan sintaks dengan benar
Mr Purple
Jawaban yang luar biasa. Saya belum pernah menemukan tiga (atau empat kali lipat) operator usus besar sebelumnya (walaupun saya saat ini sedang melakukan Haskell mooc di edX, jadi tidak ada keraguan sesuatu yang serupa akan datang). Saya setuju dengan Anda tentang santa, hantu, peri, dan dewa, tetapi jelas bukan goblin: D
John Powell
@ MrPurple Saya pikir komentar menjamin pertanyaan sendiri. Jawabannya pasti terlalu lama untuk dimasukkan dalam komentar.
Ole Tange
OK, terima kasih atas tautannya. Jika saya merumuskan jawaban menggunakan gnu paralel saya akan mempostingnya di sini.
Tuan Ungu
Formulasi yang baik untuk Anda my_processing.pydapat ditemukan di gis.stackexchange.com/a/130337/26897
Mr Purple
4

Daripada menggunakan metode Paralel GNU Anda bisa menggunakan modul python mutliprocess untuk membuat kumpulan tugas dan menjalankannya. Saya tidak memiliki akses ke pengaturan QGIS untuk mengujinya tetapi multiproses ditambahkan dalam Python 2.6 jadi asalkan Anda menggunakan 2.6 atau yang lebih baru itu harus tersedia. Ada banyak contoh online tentang penggunaan modul ini.

Steve Barnes
sumber
2
Saya mencoba multiproses, tetapi saya belum melihatnya berhasil dipuji dalam python tertanam QGIS. Saya mengalami sejumlah masalah saat saya mencobanya. Saya dapat mempostingnya sebagai pertanyaan terpisah. Sejauh yang saya tahu tidak ada contoh publik yang dapat diakses oleh seseorang yang memulai dengan ini.
Tuan Ungu
Sangat memalukan. Jika seseorang dapat menulis contoh modul multiproses yang membungkus fungsi pyQGIS tunggal seperti yang saya lakukan dengan paralel gnu maka kita semua bisa mematikan dan memparalelkan apa pun yang kita pilih.
Tn. Ungu
Saya setuju tetapi seperti yang saya katakan saya tidak memiliki akses ke QGIS saat ini.
Steve Barnes
Pertanyaan & jawaban ini mungkin bisa membantu jika Anda menjalankannya di bawah windows, gis.stackexchange.com/questions/35279/…
Steve Barnes
@MrPurple dan yang ini gis.stackexchange.com/questions/114260/... memberikan contoh
Steve Barnes
3

Berikut adalah solusi paralel gnu. Dengan beberapa kehati-hatian, sebagian besar algoritma ogr atau saga berbasis linux paralel dapat dibuat untuk dijalankan di dalam instalasi QGIS Anda.

Jelas solusi ini membutuhkan instalasi paralel gnu. Untuk menginstal gnu parallel di Ubuntu, misalnya, buka terminal Anda dan ketik

sudo apt-get -y install parallel

NB: Saya tidak bisa mendapatkan perintah shell paralel untuk bekerja di Popen atau subproses, yang saya lebih suka, jadi saya meretas ekspor bersama ke skrip bash dan menjalankannya dengan Popen sebagai gantinya.

Berikut adalah perintah shell khusus menggunakan paralel yang saya bungkus dengan python

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

Setiap {1} akan ditukar dengan angka dari rentang {1..400} dan kemudian empat ratus perintah shell dikelola oleh gnu parallel untuk secara bersamaan menggunakan semua core i7 saya :).

Berikut ini adalah kode python aktual yang saya tulis untuk memecahkan contoh masalah yang saya posting. Orang dapat menempelkannya langsung setelah akhir kode dalam pertanyaan.

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

Biarkan saya memberi tahu Anda itu benar-benar sesuatu ketika Anda melihat semua inti menyala dengan suara penuh :). Terima kasih khusus kepada Ole dan tim yang membangun Gnu Parallel.

Akan menyenangkan untuk memiliki solusi lintas platform dan alangkah baiknya jika saya bisa menemukan modul python multiprosesing untuk python tertanam qgis tetapi sayangnya tidak.

Apapun solusi ini akan membantu saya dan mungkin Anda baik.

Tuan Ungu
sumber
Jelas seseorang harus mengomentari baris "processing.runalg" di bagian pertama kode sehingga klip tidak berjalan secara berurutan terlebih dahulu sebelum dijalankan secara paralel. Selain itu, ini hanya masalah menyalin dan menempelkan kode dari jawaban di bawah kode dalam pertanyaan.
Tuan Ungu
Jika Anda hanya ingin menjalankan banyak perintah pemrosesan seperti seperangkat "qgis: larut" yang diterapkan ke file yang berbeda secara paralel, maka Anda dapat melihat proses saya untuk ini di purplelinux.co.nz/?p=190
Mr Purple