Category Archives: python

Working properly with pyqgqis edit buffer to enable undo commands

Today I wanna share a solution for a problem we faced during the implementation of a tool for our QGIS plugin, DsgTools. The tool in question captures the signal featureAdded to get access to the newly created features for a particular QgsVectorLayer and change their attributes.

But, during our code testing we found a major problem. During the edition, when the undo button was pressed QGIS crashed big time!

So, how can you solve this problem? Surfing the web, lcoandrade and I have come across this: http://gis.stackexchange.com/questions/183423/qgis-crashes-when-doing-a-rollback-after-modifying-values-of-an-user-added-featu

By studing the link above and some other related, we have discovered that our function connected to featureAdded was messing things up, but why? Sure, we have learned some “kludges” or like some might say “McGyver Programming Stunts” to solve the problem, but what is the magic that happens when a user finishes adding a feature in QGIS? We need to understand that in order to code in an elegant way!

First of all, featureAdded() is triggered:


self.iface.actionAddFeature().trigger()

This triggers QgsMapToolAddFeature::addFeature which does the following


QgsFeatureAction *action = new QgsFeatureAction( tr( "add feature" ), *f, vlayer, QString(), -1, this );
bool res = action->addFeature( QgsAttributeMap(), showModal );

Which calls QgsFeatureAction::addFeature


mLayer->beginEditCommand( text() );
mFeatureSaved = mLayer->addFeature( *mFeature );

if ( mFeatureSaved )
mLayer->endEditCommand();
else
mLayer->destroyEditCommand();

So, between:


mLayer->beginEditCommand( text() );

and:


if ( mFeatureSaved )
mLayer->endEditCommand();
else
mLayer->destroyEditCommand();

QgsVectorLayer::addFeature is called


mFeatureSaved = mLayer->addFeature( *mFeature );

Which calls QgsVectorLayerEditBuffer::addFeature. It is only inside this method that the undo stack is updated.


L->undoStack()->push( new QgsVectorLayerUndoCommandAddFeature( this, f ) );

But, until endEditCommand() is called it is not possible to perform undo operations (http://pyqt.sourceforge.net/Docs/PyQt4/qundostack.html#beginMacro). Ok?

So let’s go back to our former coding, keeping in mind that if you decide to perform an Undo operation QGIS will Crash big time (http://qgis-developer.osgeo.narkive.com/5wnziigA/wrapping-changeattributevalue-between-begin-and-end-editcommand#post2).

Ok, on one hand I want to manipulate feature’s attributes when they are added, but on the other hand, if I try to do so, I’ll mess up the undo stack. How should we proceed? What if I only peep while QGIS does its magic and then I barge in and do my own stuff? So I have to have one slot connected to featureAdded signal just to let me know which features should I visit on the editBuffer and another slot connected to editCommandEnded signal, so that I am sure that the undoStack is properly built allowing me to revisit the added features to change its attributes.

Something like this:

myLayer.featureAdded.connect(self.storeFeaturesIds)
mylayer.editCommandEnded.connect(self.updateAttributesAfterAdding)

@pyqtSlot(int)
def storeFeaturesIds(self, featId):
     """
     This method only stores featIds in a class variable
     """
     self.addedFeatures.append(featId)

def updateAttributesAfterAdding(self):
     layer = self.sender() #here I can get the layer that has sent the signal
     while self.addedFeatures:
          featureId = self.addedFeatures.pop()
          #begining the edit command
          layer.beginEditCommand('Your command message here'))
          #Do your stuff here
     layer.endEditCommand()

So there it is, how to access attributes on the fly and not explode your QGIS while doing it!

Advertisements

Using GDAL to get raster extent

Hi there guys!!!

Let’s suppose we want to determine the extent of a raster file and we want to use GDAL and Python. How can we do that?

Let’s start importing GDAL:

from osgeo import gdal

Then we need t0 open the raster file:

gdalSrc = gdal.Open('/foo/bar/filename')

To finish getting what we need, let’s get our affine transform coefficients with the following:

upx, xres, xskew, upy, yskew, yres = gdalSrc.GetGeotransform()

Where upx is the upper pixel x, xres is the pixel width and xskew is the shear in the x direction as we can see in the following picture (https://en.wikipedia.org/wiki/Affine_transformation):

xskew

Let’s go forward. The variable upy is the upper pixel y, yres is the pixel height and yskew is the shear in the y direction as we can see in the following picture (https://en.wikipedia.org/wiki/Affine_transformation):

yskew

Understanting the variables we can use the documentation obtained at http://www.gdal.org/gdal_datamodel.html to get the following relationship:

Xgeo = GT(0) + Xpixel*GT(1) + Yline*GT(2)
Ygeo = GT(3) + Xpixel*GT(4) + Yline*GT(5)

Where GT(2) is the xskew and GT(4) is the yskew.

With all of this together, we can make the following code snippet to accomplish our mission:

from osgeo import gdal, ogr
gdalSrc = gdal.Open('/foo/bar/filename')
upx, xres, xskew, upy, yskew, yres = gdalSrc.GetGeotransform()
cols = gdalSrc.RasterXSize
rows = gdalSrc.RasterYSize

ulx = upx + 0*xres + 0*xskew
uly = upy + 0*yskew + 0*yres

llx = upx + 0*xres + rows*xskew
lly = upy + 0*yskew + rows*yres

lrx = upx + cols*xres + rows*xskew
lry = upy + cols*yskew + rows*yres

urx = upx + cols*xres + 0*xskew
ury = upy + cols*yskew + 0*yres

I hope this can be useful to you guys.