Velocidade de edição de atributos no QGIS a partir de um plugin Python

9

Estou tentando editar o valor de um atributo para cada recurso em uma camada usando um plug-in QGIS Python. Descobri que fazer isso fora do modo de edição é muito mais lento do que durante a edição (mesmo incluindo a confirmação das edições). Veja o código abaixo (linhas intercambiáveis ​​no mesmo ponto em um loop). A diferença na velocidade do meu conjunto de dados de amostra é de 2 segundos (modo de edição) vs 72 segundos (não no modo de edição).

Modificando um atributo no modo de edição:

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

Modificando um atributo fora do modo de edição:

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

Esse é um comportamento esperado? Não preciso que o usuário consiga desfazer as alterações; portanto, acho que não preciso usar o modo de edição.

Edição 1: Veja o código completo abaixo com as duas versões incluídas (mas 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 os métodos produzem o mesmo resultado, mas a gravação via provedor de dados leva muito mais tempo. A função classifica a proximidade dos recursos de construção aos campos próximos (roxo) usando buffers pré-criados (marrom-ish). Proximidade

Snorfalorpagus
fonte
11
Isso não parece certo. Você pode compartilhar mais do seu código.
Nathan W
@NathanW Adicionei a função completa. A idéia é verificar duas camadas em busca de interseções e atualizar uma camada com o atributo da outra camada quando uma interseção for encontrada.
Snorphelorpagus
Que tipo de dados você está usando?
Nathan W
Ambas as camadas são arquivos de forma ESRI (polígono). O layer_target possui 905 recursos (edifícios), o layer_buffered possui 1155 recursos (espaços abertos) com polígonos sobrepostos que representam diferentes buffers (100m, 50m, 20m, 10m, 5m) - daí o atributo 'distance'.
Snorkalorpagus
11
Como você acessa os dados? (ou seja, através da rede, disco tradicional, SSD)? É possível que a sobrecarga de E / S para uma única operação de gravação consuma tempo? Como teste: você pode tentar armazenar em buffer todos os seus atributos alterados na memória e chamar dataProvider.changeAttributeValues ​​() uma vez no final.
Matthias Kuhn

Respostas:

7

O problema era que cada chamada QgsDataProvider.changeAttributeValues()inicia uma nova transação com toda a sobrecarga relacionada (dependendo do provedor de dados e da configuração do sistema)

Quando os recursos são alterados na camada primeiro (como em QgsVectorLayer.changeAttributeValue()) todas as alterações são armazenadas em cache na memória, o que é muito mais rápido e, em seguida, são confirmadas em uma única transação no final.

O mesmo buffer pode ser alcançado dentro do script (ou seja, fora do buffer de edição da camada vetorial) e, em seguida, confirmado em uma transação, chamando QgsDataProvider.changeAttributeValues()uma vez, fora do loop.

Há também um atalho útil para isso nas versões recentes do QGIS:

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