Vitesse de modification des attributs dans QGIS à partir d'un plugin Python

9

J'essaie de modifier la valeur d'un attribut pour chaque entité d'une couche à l'aide d'un plugin QGIS Python. J'ai trouvé que faire cela en dehors du mode d'édition est beaucoup plus lent que lors de l'édition (même en validant les modifications). Voir le code ci-dessous (lignes interchangeables au même point dans une boucle). La différence de vitesse pour mon échantillon de données est de 2 secondes (mode édition) contre 72 secondes (pas en mode édition).

Modification d'un attribut en mode édition:

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

Modification d'un attribut en dehors du mode d'édition:

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

Est-ce un comportement attendu? Je n'ai pas besoin que l'utilisateur puisse annuler les modifications, donc je ne pense pas avoir besoin d'utiliser le mode d'édition.

Edit 1: Voir le code complet ci-dessous avec les deux versions incluses (mais commentées):

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)

Les deux méthodes produisent le même résultat, mais l'écriture via le fournisseur de données prend beaucoup plus de temps. La fonction classe la proximité des caractéristiques du bâtiment aux champs voisins (violet) à l'aide de tampons pré-créés (brun-ish). Proximité

Snorfalorpagus
la source
1
Cela ne semble pas correct. Pouvez-vous partager plus de votre code.
Nathan W
@NathanW J'ai ajouté la fonction complète. L'idée est de vérifier deux couches pour les intersections, puis de mettre à jour une couche avec l'attribut de l'autre couche lorsqu'une intersection est trouvée.
Snorfalorpagus
Quel type de données utilisez-vous?
Nathan W
Les deux couches ont un Shapefile ESRI (polygone). Le layer_target a 905 entités (bâtiments), le layer_buffered a 1155 entités (espaces ouverts) avec des polygones qui se chevauchent représentant différents tampons (100m, 50m, 20m, 10m, 5m) - d'où l'attribut 'distance'.
Snorfalorpagus
1
Comment vos données sont-elles accessibles? (c.-à-d. sur réseau, disque traditionnel, SSD)? Est-il possible que la surcharge d'E / S pour une seule opération d'écriture prenne du temps? À titre de test: pouvez-vous essayer de mettre en mémoire tampon tous vos attributs modifiés, puis appeler dataProvider.changeAttributeValues ​​() une fois à la fin.
Matthias Kuhn

Réponses:

7

Le problème était que chaque appel à QgsDataProvider.changeAttributeValues()initier une nouvelle transaction avec tous les frais généraux associés (en fonction du fournisseur de données et de la configuration du système)

Lorsque les fonctionnalités sont modifiées sur la couche en premier (comme dans QgsVectorLayer.changeAttributeValue()), toutes les modifications sont mises en cache en mémoire, ce qui est beaucoup plus rapide et sont ensuite validées en une seule transaction à la fin.

La même mise en mémoire tampon peut être obtenue dans le script (c'est-à-dire en dehors du tampon d'édition de la couche vectorielle) puis validée en une seule transaction en appelant QgsDataProvider.changeAttributeValues()une fois, en dehors de la boucle.

Il existe également un raccourci pratique pour cela dans les versions récentes de QGIS:

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