Geschwindigkeit der Bearbeitung von Attributen in QGIS über ein Python-Plugin

9

Ich versuche, den Wert eines Attributs für jedes Feature in einer Ebene mithilfe eines QGIS Python-Plugins zu bearbeiten. Ich habe festgestellt, dass dies außerhalb des Bearbeitungsmodus viel langsamer ist als beim Bearbeiten (einschließlich des Festschreibens der Änderungen). Siehe Code unten (Linien, die an derselben Stelle in einer Schleife austauschbar sind). Der Geschwindigkeitsunterschied für meinen Beispieldatensatz beträgt 2 Sekunden (Bearbeitungsmodus) gegenüber 72 Sekunden (kein Bearbeitungsmodus).

Ändern eines Attributs im Bearbeitungsmodus:

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

Ändern eines Attributs außerhalb des Bearbeitungsmodus:

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

Ist das ein erwartetes Verhalten? Der Benutzer muss die Änderungen nicht rückgängig machen können, daher muss ich den Bearbeitungsmodus nicht verwenden.

Bearbeiten 1: Siehe vollständigen Code unten mit beiden Versionen enthalten (aber auskommentiert):

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)

Beide Methoden führen zum gleichen Ergebnis, das Schreiben über den Datenprovider dauert jedoch viel länger. Die Funktion klassifiziert die Nähe von Gebäudeelementen zu nahe gelegenen Feldern (lila) mithilfe vorgefertigter Puffer (braun). Nähe

Snorfalorpagus
quelle
1
Das scheint nicht richtig zu sein. Können Sie noch mehr von Ihrem Code teilen?
Nathan W
@ NathanW Ich habe die komplette Funktion hinzugefügt. Die Idee ist, zwei Ebenen auf Schnittpunkte zu überprüfen und dann eine Ebene mit dem Attribut der anderen Ebene zu aktualisieren, wenn eine Kreuzung gefunden wird.
Snorfalorpagus
Welchen Datentyp verwenden Sie?
Nathan W
Beide Ebenen sind ESRI-Shapefiles (Polygon). Das Layer-Ziel hat 905 Features (Gebäude), das Layer-Puffer hat 1155 Features (offene Räume) mit überlappenden Polygonen, die verschiedene Puffer darstellen (100 m, 50 m, 20 m, 10 m, 5 m) - daher das Attribut 'Entfernung'.
Snorfalorpagus
1
Wie wird auf Ihre Daten zugegriffen? (dh über Netzwerk, herkömmliche Festplatte, SSD)? Ist es möglich, dass der E / A-Overhead für einen einzelnen Schreibvorgang zeitaufwändig ist? Als Test: Können Sie versuchen, alle geänderten Attribute im Speicher zu puffern und dann am Ende einmal dataProvider.changeAttributeValues ​​() aufrufen?
Matthias Kuhn

Antworten:

7

Das Problem war, dass jeder Aufruf zum QgsDataProvider.changeAttributeValues()Initiieren einer neuen Transaktion mit dem gesamten damit verbundenen Overhead (abhängig vom Datenprovider und der Systemkonfiguration)

Wenn die Features zuerst (wie in QgsVectorLayer.changeAttributeValue()) auf der Ebene geändert werden, werden alle Änderungen im Speicher zwischengespeichert, was viel schneller ist und am Ende in einer einzigen Transaktion festgeschrieben wird.

Die gleiche Pufferung kann innerhalb des Skripts erreicht werden (dh außerhalb des Bearbeitungspuffers der Vektorebene) und dann in einer Transaktion durch QgsDataProvider.changeAttributeValues()einmaliges Aufrufen außerhalb der Schleife festgeschrieben werden .

In den neuesten QGIS-Versionen gibt es dafür auch eine praktische Verknüpfung:

with edit(layer):
    for fid in fids:
        layer.changeAttributeValue(fid, idx, value)
Matthias Kuhn
quelle