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).
Réponses:
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:
la source