Velocidad de edición de atributos en QGIS desde un complemento de Python

9

Estoy tratando de editar el valor de un atributo para cada característica en una capa usando un complemento QGIS Python. Descubrí que hacer esto fuera del modo de edición es mucho más lento que durante la edición (incluso incluyendo la confirmación de las ediciones). Vea el código a continuación (líneas intercambiables en el mismo punto en un bucle). La diferencia de velocidad para mi conjunto de datos de muestra es de 2 segundos (modo de edición) frente a 72 segundos (no en modo de edición).

Modificación de un atributo en modo edición:

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

Modificación de un atributo fuera del modo de edición:

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

¿Es este un comportamiento esperado? No necesito que el usuario pueda deshacer los cambios, así que no creo que necesite usar el modo de edición.

Edición 1: vea el código completo a continuación con ambas versiones incluidas (pero comentadas):

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)

Ambos métodos producen el mismo resultado, pero la escritura a través del proveedor de datos lleva mucho más tiempo. La función clasifica la proximidad de las características del edificio a los campos cercanos (morado) utilizando buffers pre-creados (marrón-ish). Proximidad

Snorfalorpagus
fuente
1
Eso no parece correcto. ¿Puedes compartir más de tu código?
Nathan W
@NathanW He agregado la función completa. La idea es verificar las intersecciones en dos capas, luego actualizar una capa con el atributo de la otra capa cuando se encuentra una intersección.
Snorfalorpagus
¿Qué tipo de datos estás usando?
Nathan W
Ambas capas un ESRI Shapefiles (polígono). El layer_target tiene 905 características (edificios), el layer_buffered tiene 1155 características (espacios abiertos) con polígonos superpuestos que representan diferentes buffers (100m, 50m, 20m, 10m, 5m), de ahí el atributo 'distancia'.
Snorfalorpagus
1
¿Cómo se accede a sus datos? (es decir, a través de la red, disco tradicional, SSD)? ¿Es posible que la sobrecarga de E / S para una sola operación de escritura requiera mucho tiempo? Como prueba: ¿puede intentar almacenar en el búfer todos los atributos modificados en la memoria y luego llamar a dataProvider.changeAttributeValues ​​() una vez al final.
Matthias Kuhn el

Respuestas:

7

El problema era que cada llamada a QgsDataProvider.changeAttributeValues()iniciar una nueva transacción con todos los gastos generales relacionados (dependiendo del proveedor de datos y la configuración del sistema)

Cuando las características se cambian en la capa primero (como en QgsVectorLayer.changeAttributeValue()) todos los cambios se almacenan en la memoria caché, lo que es mucho más rápido y luego se confirman en una sola transacción al final.

Se puede lograr el mismo almacenamiento en búfer dentro del script (es decir, fuera del búfer de edición de la capa vectorial) y luego se confirma en una transacción llamando QgsDataProvider.changeAttributeValues()una vez, fuera del bucle.

También hay un atajo útil para esto en versiones recientes de QGIS:

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