Take the 2-minute tour ×
Geographic Information Systems Stack Exchange is a question and answer site for cartographers, geographers and GIS professionals. It's 100% free, no registration required.

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

share|improve this question
1  
That doesn't seem right. Can you share any more of your code. –  Nathan W Apr 21 '13 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. –  Snorfalorpagus Apr 22 '13 at 13:42
    
What data type are you using? –  Nathan W Apr 22 '13 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. –  Snorfalorpagus Apr 22 '13 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. –  Matthias Kuhn Apr 23 '13 at 8:03

1 Answer 1

up vote 4 down vote accepted

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.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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