Скорость редактирования атрибутов в QGIS из плагина Python

9

Я пытаюсь изменить значение атрибута для каждого объекта в слое, используя плагин QGIS Python. Я обнаружил, что делать это вне режима редактирования намного медленнее, чем во время редактирования (даже включая фиксацию правок). Смотрите код ниже (строки взаимозаменяемы в одной и той же точке цикла). Разница в скорости для моего образца набора данных составляет 2 секунды (режим редактирования) по сравнению с 72 секундами (не режим редактирования).

Изменение атрибута в режиме редактирования:

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

Изменение атрибута вне режима редактирования:

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

Это ожидаемое поведение? Мне не нужно, чтобы пользователь мог отменить изменения, поэтому я не думаю, что мне нужно использовать режим редактирования.

Изменить 1: См. Полный код ниже с обеими версиями включены (но закомментированы):

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)

Оба метода дают одинаковый результат, но запись через поставщика данных занимает гораздо больше времени. Функция классифицирует близость элементов здания к близлежащим полям (фиолетовым цветом) с использованием предварительно созданных буферов (коричневого цвета). близость

Snorfalorpagus
источник
1
Это не кажется правильным. Можете ли вы поделиться больше вашего кода.
Натан W
@NathanW Я добавил полную функцию. Идея состоит в том, чтобы проверить два слоя на наличие пересечений, а затем обновить один слой атрибутом другого слоя, когда пересечение будет найдено.
Снорфалорпаг
Какой тип данных вы используете?
Натан W
Оба слоя представляют собой шейп-файлы ESRI (многоугольник). Слой layer_target имеет 905 объектов (зданий), а layer_buffered имеет 1155 объектов (открытых пространств) с перекрывающимися полигонами, представляющими различные буферы (100 м, 50 м, 20 м, 10 м, 5 м) - отсюда атрибут «расстояние».
Snorfalorpagus
1
Как вы получаете доступ к данным? (то есть по сети, традиционный диск, SSD)? Возможно ли, что накладные расходы ввода-вывода для одной операции записи отнимают много времени? В качестве теста: можете ли вы попробовать буферизовать все ваши измененные атрибуты в памяти и затем вызвать dataProvider.changeAttributeValues ​​() один раз в конце.
Матиас Кун

Ответы:

7

Проблема заключалась в том, что каждый вызов QgsDataProvider.changeAttributeValues()инициирует новую транзакцию со всеми связанными издержками (в зависимости от поставщика данных и конфигурации системы).

Когда объекты меняются на слое вначале (как в QgsVectorLayer.changeAttributeValue()), все изменения кэшируются в памяти, что происходит гораздо быстрее, а затем фиксируется в одной транзакции в конце.

Та же самая буферизация может быть достигнута внутри скрипта (то есть вне буфера редактирования векторного уровня) и затем зафиксирована в одной транзакции, вызвав QgsDataProvider.changeAttributeValues()один раз, вне цикла.

В последних версиях QGIS для этого есть удобный ярлык:

with edit(layer):
    for fid in fids:
        layer.changeAttributeValue(fid, idx, value)
Матиас Кун
источник