Szybkość edycji atrybutów w QGIS z wtyczki Python

9

Próbuję edytować wartość atrybutu dla każdej funkcji w warstwie przy użyciu wtyczki QGIS Python. Przekonałem się, że robienie tego poza trybem edycji jest znacznie wolniejsze niż podczas edycji (nawet włączając wprowadzanie zmian). Zobacz kod poniżej (linie wymienne w tym samym punkcie pętli). Różnica prędkości dla mojego przykładowego zestawu danych wynosi 2 sekundy (tryb edycji) w porównaniu do 72 sekund (nie tryb edycji).

Modyfikowanie atrybutu w trybie edycji:

layer.changeAttributeValue(feature.id(), 17, QtCore.QVariant(value))

Modyfikowanie atrybutu poza trybem edycji:

layer.dataProvider().changeAttributeValues({ feature.id() : { 17 : QtCore.QVariant(value) } })

Czy to jest oczekiwane zachowanie? Nie potrzebuję, aby użytkownik mógł cofnąć zmiany, więc nie sądzę, że muszę używać trybu edycji.

Edycja 1: Zobacz pełny kod poniżej z obiema wersjami dołączonymi (ale skomentowanymi):

def run(self):
    try:
        # create spatial index of buffered layer
        index = QgsSpatialIndex()
        self.layer_buffered.select()
        for feature in self.layer_buffered:
            index.insertFeature(feature)

        # enable editing
        #was_editing = self.layer_target.isEditable()
        #if was_editing is False:
        #    self.layer_target.startEditing()

        # check intersections
        self.layer_target.select()
        self.feature_count = self.layer_target.featureCount()
        for feature in self.layer_target:
            distance_min = None
            fids = index.intersects(feature.geometry().boundingBox())
            for fid in fids:
                # feature's bounding box and buffer bounding box intersect
                feature_buffered = QgsFeature()
                self.layer_buffered.featureAtId(fid, feature_buffered)
                if feature.geometry().intersects(feature_buffered.geometry()):
                    # feature intersects buffer
                    attrs = feature_buffered.attributeMap()
                    distance = attrs[0].toPyObject()
                    if distance_min is None or distance < distance_min:
                        distance_min = distance
                if self.abort is True: break
            if self.abort is True: break

            # update feature's distance attribute
            self.layer_target.dataProvider().changeAttributeValues({feature.id(): {self.field_index: QtCore.QVariant(distance_min)}})
            #self.layer_target.changeAttributeValue(feature.id(), self.field_index, QtCore.QVariant(distance_min))

            self.calculate_progress()

        # disable editing
        #if was_editing is False:
        #    self.layer_target.commitChanges()

    except:
        import traceback
        self.error.emit(traceback.format_exc())
    self.progress.emit(100)
    self.finished.emit(self.abort)

Obie metody dają ten sam wynik, ale pisanie za pośrednictwem dostawcy danych trwa znacznie dłużej. Funkcja klasyfikuje bliskość elementów budynku do pobliskich pól (fioletowa) za pomocą wstępnie utworzonych buforów (brązowo-brązowy). Bliskość

Snorfalorpagus
źródło
1
To nie wydaje się właściwe. Czy możesz udostępnić więcej swojego kodu.
Nathan W
@NathanW Dodałem pełną funkcję. Chodzi o to, aby sprawdzić dwie warstwy pod kątem przecięć, a następnie zaktualizować jedną warstwę atrybutem drugiej warstwy, gdy zostanie znalezione przecięcie.
Snorfalorpagus
Jakiego typu danych używasz?
Nathan W
Obie warstwy tworzą pliki kształtu ESRI (wielokąt). Warstwa_target ma 905 obiektów (budynki), warstwa_buforowana ma 1155 obiektów (otwarte przestrzenie) z nakładającymi się wielokątami reprezentującymi różne bufory (100 m, 50 m, 20 m, 10 m, 5 m) - stąd atrybut „odległość”.
Snorfalorpagus
1
W jaki sposób uzyskuje się dostęp do danych? (tj. przez sieć, tradycyjny dysk, SSD)? Czy to możliwe, że narzut we / wy dla pojedynczej operacji zapisu jest czasochłonny? Jako test: czy możesz spróbować buforować wszystkie zmienione atrybuty w pamięci, a następnie raz na końcu wywołać metodę dataProvider.changeAttributeValues ​​().
Matthias Kuhn

Odpowiedzi:

7

Problem polegał na tym, że każde wywołanie QgsDataProvider.changeAttributeValues()inicjuje nową transakcję ze wszystkimi powiązanymi kosztami ogólnymi (w zależności od dostawcy danych i konfiguracji systemu)

Kiedy funkcje są najpierw zmieniane na warstwie (jak w QgsVectorLayer.changeAttributeValue()), wszystkie zmiany są buforowane w pamięci, co jest znacznie szybsze, a następnie zostają ostatecznie zatwierdzone w jednej transakcji.

To samo buforowanie można osiągnąć w skrypcie (tj. Poza buforem edycji warstwy wektorowej), a następnie zatwierdzić w jednej transakcji, wywołując QgsDataProvider.changeAttributeValues()jeden raz poza pętlą.

Istnieje również przydatny skrót do tego w najnowszych wersjach QGIS:

with edit(layer):
    for fid in fids:
        layer.changeAttributeValue(fid, idx, value)
Matthias Kuhn
źródło