All posts by phborba

About phborba

Developer of DsgTools, a python plugins for QGIS.

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

Have you ever heard of pgModeler?

Someone has given you a logical model with over 500 tables to implement it in PostgreSQL, where to begin? Should I cry first?  Should I just start typing create table statements like there is no tomorrow? No! I present you pgModeler!

pgmodeler

pgModeler is a modelling open software developed by Raphael Araújo e Silva and it allows you to build a database just using a nice graphical interface and after you finish your design, you can just deploy it and there you go! Your database is ready!

For instance, suppose you have a table called road with an text attribute “name”, geometry column “geom” with type MultiLinestring, with epsg 4326. To implement this table you just have to click around and there you go, you have the following table:

new_database

To deploy this, just go to export and choose between .png, sql or direct deploy into postgres. The sql for the table above is:

CREATE TABLE public.road(
id serial NOT NULL,
name text,
geom geometry(MULTILINESTRING, 4326) NOT NULL,
CONSTRAINT road_pk PRIMARY KEY (id)

);

Another great feature of pgModeler is reverse engineering! Just define a database connection and it shows you all tables and relationships.

By the way, I was the guy that was given the huge database to implement and if wasn’t by pgModeler, I think I’d still be crying… =]

Deaggregate geometries with pyqgis

Have you ever needed to explode multi geometry layer into a single geometry layer, using in each new geometry the attributes of the original multi one?  If you were working with FME, for instance, you basically would just use the transformer Deaggregator. Let’s learn how to solve this problem with python and QGIS!

The following code snippet teaches you how to work with QgsVectorLayers, it’s attributes and how to manipulate geometries.

from qgis.core import QgsVectorLayer, QgsFeature, QgsMapLayerRegistry

#fill in your input layer name. In this example, our inputLyrName is input_layer
inputLyrName = 'input_layer'
inputLyr = QgsMapLayerRegistry.instance().mapLayersByName(inputLyrName)[0]

#fill in your output layer name. In this example, our outputLyrName is output_layer
outputName = 'output_layer'
outputLyr = QgsMapLayerRegistry.instance().mapLayersByName(outputLyrName)[0]

#tests type of output: if it is a multi parted geometry or
#a single parted geometry
if outputLyr.wkbType() in [QGis.WKBPoint, QGis.WKBLineString, QGis.WKBPolygon]:
     isMulti = False
else:
     isMulti = True

outputLyr.startEditing()
addList = []
for feat in inputLyr.getFeatures():
     #gets all parts of geometry as an individual single geometry
     parts = feat.geometry().asGeometryCollection()
     #checks if it isMulti, if it is, convert each
     #part in geometryCollection to multi
     if isMulti:
          for part in parts:
               part.convertToMultiType()

     #for each part, get original set of attribute and create a new feat
     #with this set
     for i in range(0,len(parts)):
          #new feature constructor. newFeat has all atributes of feat
          newFeat = QgsFeature(feat)
          #set geometry with part
          newFeat.setGeometry(parts[i])
          #get field id and get defaultValue from provider
          idx = newFeat.fieldNameIndex('id')
          newFeat.setAttribute(idx,provider.defaultValue(idx))
          addList.append(newFeat)
outputLyr.addFeatures(addList,True)
outputLyr.commitChanges()

Hope you guys like it!