9

I'm trying to edit the value of an attribute for each feature in a layer using a QGIS Python plugin. I've found that doing this outside of editing mode is much slower than while editing (even including committing the edits). See code below (lines interchangeable at the same point in a loop). The difference in speed for my sample dataset is 2 seconds (edit mode) vs 72 seconds (not edit mode).

Modifying an attribute in edit mode:

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

Modifying an attribute outside of edit mode:

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

Is this an expected behavior? I don't need the user to be able to undo the changes, so I don't think I need to use edit mode.

Edit 1: See full code below with both versions included (but commented out):

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)

Both methods produce the same result, but writing via the data provider takes much longer. The function classifies proximity of building features to nearby fields (purple) using pre-created buffers (brown-ish). Proximity

8
  • 1
    That doesn't seem right. Can you share any more of your code.
    – Nathan W
    Commented Apr 21, 2013 at 12:40
  • @NathanW I've added the complete function. The idea is to check two layers for intersections, then update one layer with the attribute of the other layer when an intersection is found. Commented Apr 22, 2013 at 13:42
  • What data type are you using?
    – Nathan W
    Commented Apr 22, 2013 at 13:48
  • Both layers a ESRI Shapefiles (polygon). The layer_target has 905 features (buildings), the layer_buffered has 1155 features (open spaces) with overlapping polygons representing different buffers (100m, 50m, 20m, 10m, 5m) - hence the 'distance' attribute. Commented Apr 22, 2013 at 13:54
  • 1
    How is you data accessed? (i.e. over network, traditional disk, SSD)? Is it possible, that the I/O overhead for a single write operation is time-consuming? As a test: can you try buffering all your changed attributes in memory and then call dataProvider.changeAttributeValues() once at the end. Commented Apr 23, 2013 at 8:03

1 Answer 1

7

The problem was, that each call to QgsDataProvider.changeAttributeValues() initiates a new transaction with all the related overhead (depending on the data provider and system configuration)

When the features are changed on the layer first (as in QgsVectorLayer.changeAttributeValue() ) all the changes are cached in memory, what is much faster and are then committed in one single transaction in the end.

The same buffering can be achieved within the script, (i.e. outside of the vector layer edit buffer) and then committed in one transaction by calling QgsDataProvider.changeAttributeValues() once, outside of the loop.

There is also a handy shortcut for this in recent QGIS versions:

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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.