Advertisements

Previously, we created layers by loaded existing datasets into our project. In this example, we will create a new layer from scratch.

To get started, let’s create a new QgsVectorLayer object for point geometries with the layer name “temp” and a memory data provider:

vl = QgsVectorLayer("Point", "temp", "memory")

Then we can add some attribute fields using the data provider’s addAttributes() function:

from qgis.PyQt.QtCore import QVariant
pr = vl.dataProvider()
pr.addAttributes([QgsField("name", QVariant.String),
                  QgsField("age",  QVariant.Int),
                  QgsField("size", QVariant.Double)])
vl.updateFields() 

A common pattern in this example is “update-after-change”. When making changes to a layer’s data provider, these changes are not automatically reflected by the layer. Therefore, we need to call the vector layer’s updateFields() after adding the attributes.

With the layer set up, we can add a point feature and add the layer to the project:

f = QgsFeature()
f.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(10,10)))
f.setAttributes(["Ada L.", 2, 0.3])
pr.addFeature(f)
vl.updateExtents() 
QgsProject.instance().addMapLayer(vl)

Similar to before, here we need to call updateExtents() after adding features to the data provider. Otherwise the layer’s extent will be incorrect.

Let’s have a look at some layer statistics now:

print("No. fields:", len(pr.fields()))
print("No. features:", pr.featureCount())
e = vl.extent()
print("Extent:", e.xMinimum(), e.yMinimum(), e.xMaximum(), e.yMaximum())

for f in vl.getFeatures():
    print("Feature:", f.id(), f.attributes(), f.geometry().asPoint())

This should print:

No. fields: 3
No. features: 1
Extent: 10.0 10.0 10.0 10.0
Feature: 1 ['Ada L.', 2, 0.3] <QgsPointXY: POINT(10 10)>

So far so good! But what if we decide that we want to add another attribute? Luckily QgsVectorLayers have a handy addAttribute() function. The documentation notes:

Calls to addAttribute() are only valid for layers in which edits have been enabled by a call to startEditing(). Changes made to features using this method are not committed to the underlying data provider until a commitChanges() call is made. Any uncommitted changes can be discarded by calling rollBack().

Ok, let’s do that! Pay attention to the combination of addAttribute() and updateFields().

vl.startEditing()

my_field_name = 'new field'
vl.addAttribute(QgsField(my_field_name, QVariant.String))
vl.updateFields()

for f in vl.getFeatures():
    print("Feature:", f.id(), f.attributes(), f.geometry().asPoint())

This should print:

Feature: 1 ['Ada L.', 2, 0.3, None] <QgsPointXY: POINT(10 10)>

So there is a new attribute field. We can populate it by assigning our desired value to the feature’s field. Again, we have to follow the update-after-changes pattern, this time using updateFeature():

my_field_value = 'Hello world!'
for f in vl.getFeatures():
    f[my_field_name] = my_field_value
    vl.updateFeature(f)

vl.commitChanges()

for f in vl.getFeatures():
    print("Feature:", f.id(), f.attributes(), f.geometry().asPoint())

Finally, this prints:

Feature: 1 ['Ada L.', 2, 0.3, 'Hello world!'] <QgsPointXY: POINT(10 10)>

Now, the only thing left to do is to stop the editing session:

iface.vectorLayerTools().stopEditing(vl) 

Excellent! … But wait!

There is an alternative to manually calling startEditing(), commitChanges(), stopEditing() and handling rollbacks. This alternative approach uses a context manager which will internally use vl.startEditing() and call vl.commitChanges() on success, and it will rollback if any exception occurs. This is how it’s done using with edit(vl):

my_field_name = 'new field'
my_field_value = 'Hello world!'

with edit(vl):
    vl.addAttribute(QgsField(my_field_name, QVariant.String))
    vl.updateFields()
    for f in vl.getFeatures():
        f[my_field_name] = my_field_value
        vl.updateFeature(f)

These are the basic steps to create a new layer from scratch. You saw how to define attribute fields and add features. Finally, we modified the layer and added another attribute field.

Next


PyQGIS 101 is a work in progress. I’d appreciate any feedback, particularly from beginners!

Advertisements
17 comments
  1. Dear Anita,

    thanks for sharing your code.

    I’m trying to add two fields to an existing shapefile and populate it. Here below the chunk of code:

    # receiver_feat_new_fields is a dict containing the data to populate
    receiver_path='/home/pierluigi/Scrivania/dataset_for_testing/ricevitori.shp'
    receiver_layer=QgsVectorLayer(receiver_path,'ricevitore',"ogr")
    
    fields_number = int(receiver_layer.fields().count())
    
    level_field_index = {}
    print("No. fields before edit:", len(receiver_layer.fields()))
    
    #### Here I have 2 field ####
    
    receiver_layer.startEditing()
    
    #add fields
    receiver_layer.addAttribute(QgsField('gen', QVariant.Double, len=5, prec=1))
    level_field_index['gen'] = fields_number
    fields_number = fields_number + 1
    
    receiver_layer.addAttribute((QgsField('day', QVariant.Double, len=5, prec=1)))
    level_field_index['day'] = fields_number
    fields_number = fields_number + 1
    
    #updatefields
    receiver_layer.updateFields()
    
    #insert attribute data in table
    for f in receiver_layer.getFeatures():
        if 'gen' in level_field_index:
            f['gen'] = receiver_feat_new_fields[f.id()][level_field_index['gen']]
        if 'day' in level_field_index:
            f['day'] = receiver_feat_new_fields[f.id()][level_field_index['day']]
        receiver_layer.updateFeature(f)
    receiver_layer.updateExtents() 
    
    #print some info for debug
    print("No. fields:", len(receiver_layer.fields()))
    print("No. features:", receiver_layer.featureCount())
    e = receiver_layer.extent()
    print("Extent:", e.xMinimum(), e.yMinimum(), e.xMaximum(), e.yMaximum())
    
    ## Here I have 4 field and it seems working correctly  ###
    
    receiver_layer.commitChanges()
    
    for f in receiver_layer.getFeatures():
        print("Feature:", f.id(), f.attributes(), f.geometry().asPoint())
    
    ## I can see all my data correcly -- It looks nice ###
    
    iface.vectorLayerTools().stopEditing(receiver_layer)
    

    Here the software ask me to stop edit.

    Is it possible to avoid such popup but directly accept the edits?

    Now the problem is that even if I look at the table, it seem that is not changed but simply pressing the “pencil” edit button and closing edit and the table magically appear correctly.

    Where I’m wrong?

    Thanks
    Pierluigi

    • Hi Pierluigi,

      Which QGIS version are you using?
      I tried to reproduce your issue in 3.5. I don’t get the popup, even though it is mentioned in the documentation https://qgis.org/pyqgis/master/core/Vector/QgsVectorLayerTools.html?highlight=stopediting#qgis.core.QgsVectorLayerTools.stopEditing

      The following code works for me:

      QgsVectorFileWriter.writeAsVectorFormat(vl, "C:/temp/pt.gpkg", "CP1250", vl.crs())
      tmp=QgsVectorLayer("C:/temp/pt.gpkg",'tmp',"ogr")
      QgsProject.instance().addMapLayer(tmp)
      
      tmp.startEditing()
      
      tmp.addAttribute(QgsField('gen', QVariant.Double, len=5, prec=1))
      tmp.addAttribute(QgsField('day', QVariant.Double, len=5, prec=1))
      tmp.updateFields()
      
      for f in tmp.getFeatures():
          f['gen'] = 1.1
          f['day'] = 5.2
          tmp.updateFeature(f)
          
      tmp.commitChanges()
      
      for f in tmp.getFeatures():
          print("Feature:", f.id(), f.attributes(), f.geometry().asPoint())
          
      iface.vectorLayerTools().stopEditing(tmp)
      
  2. Shiva said:

    ‘With edit(layer)’ example would be good addition. Relating ‘with open(file) ‘ like usage in python

  3. Hi Anita!

    Thank you for very useful tutorial! I hope you continue writing it by practical topics using in common tasks with QGIS. I’m newcomer both in this software and Python use.

    Thanks!

  4. soli said:

    Hello Anita!
    Thanks for your effort doing this. I do work with lots of csv files exported from many different databases that I have no access to, just only to export csv files. This csv files needs to be working with and lots of the tasks-processes are very similar so to have a Phyton script doing this would be a life saver. For example, load a csv point data layer and query on two fields, process the selected points using random process with percentage value and output a temporary layer with name.., next do a new query on the same point data layer and use random process again with another percentage value and output to a temporary layer. Normally it is about 30 querys on the same point layer and random percentage to run thru and output to temporary layers that in the end is merged together to 5 layers and saved. Very boring time consuming process that is easy to do errors. I would love to do this more automatic with a Phyton solution if that is possible…

    Thanks!

      • soli004 said:

        Thanks, no I have not tried, but after been giving this tasks three times I got frustrated as it is to easy doing an error and then you have to start all over again. I never been using python so I should have a look into the processing tools doing it manually and if I understand correctly you should be able to copy those lines and edit and reuse it at later moments… doing it manually takes me hours doing…..

      • soli004 said:

        Hi Anita, So far I have been able to load the csv and run the “random extract”, but when I use filter data I get errors “Unable to execute algorithm”, so I’m a bit lost here… and I need to add another 7 steps filtering and random extract on top of the below example. Also is there a way to name the ‘OUTPUT’ layers to a specific name as when I try without filtering all gets the same layer name, hard to see what each layer holds as I need to merged them from specific randoms settings … thanks


        uri = "file:///Volumes/Lacie2000/QGIS/csv/data.csv?encoding=windows-1258&type=csv&delimiter=;&detectTypes=yes&xField=koord_X&yField=koord_Y&crs=EPSG:3006&spatialIndex=no&subsetIndex=no&watchFile=no"

        vlayer = iface.addVectorLayer(uri, "data_csv", "delimitedtext")

        #Filter the data step 1
        vlayer.setSubsetString("field_1 = 2 AND field_7 + field_8 = 1")
        for feature in vlayer.getFeatures():

        #Get random result from the filtered data step 1
        result= processing.runAndLoadResults("qgis:randomextract", {'INPUT':uri,'METHOD':1,'NUMBER':53,'OUTPUT':'memory:'})

        #Reset active filter and do a new filtering
        vlayer.setSubsetString("")
        for feature in vlayer.getFeatures():

        #Filter the data step 2
        vlayer.setSubsetString("field_1 = 5 AND field_7 + field_8 = 2")
        for feature in vlayer.getFeatures():

        #Get random result from the filtered data step 2
        result= processing.runAndLoadResults("qgis:randomextract", {'INPUT':uri,'METHOD':1,'NUMBER':78,'OUTPUT':'memory:'})

      • I recommend to not use vlayer.setSubsetString() because the subsetString is not used by Processing in the current code. There are many different solutions for this problem but the following seems to be the most straightforward to me: First use Extract by expression. Then apply Random extract on the result.

        Instead of ‘OUTPUT’:’memory:’, you can specify a file name of your choice.

  5. soli004 said:

    Bare in mind that I’m not been using python before, I searched for “extract by expression” but did not find any understandable solution, I was able to get a selection by using “selectByExpression” but the “Random extract” will not process the active selection it use all data and that is not what I want, and if I change “OUTPUT” or ‘memory’ or both to any other name I get errors so something is missing. My suggestion that in future PyQGIS101 perhaps can go in to more detail about processing csv in different ways… at least I have many of this type of processes to be done in a better way. Thanks

  6. soli004 said:

    EDIT: a little error… ‘INPUT’: uri, should be ‘INPUT’:vlayer,

  7. Tyler Tresslar said:

    Hello Anita (and budding pyQgis community),

    This tutoral was a huge help in writing a script, which I would like to post as open source. Soo, voila:

    “`
    #This script scrapes data on the location of powerlines
    #from http://www.minigrids.go.tz/en/GIS/ and turns it into a QGIS vector

    #Import the required libraries
    import json
    import urllib.request
    from qgis.PyQt.QtCore import QVariant

    #set the link to get the existing powerlines, then load the data
    link = ‘http://www.minigrids.go.tz/en/GIS/getNetworkLines?network=1’
    l = urllib.request.urlopen(link)
    data = json.load(l)

    #create a vector layer that has the points
    vl = QgsVectorLayer(“LineString”, “Powerlines”, “memory”)
    pr = vl.dataProvider()

    #loop through the data to create the points for the line
    for item in data:
    pointlist = []
    for point in item[“points”]:
    thing = QgsPoint(point[“y”],point[“x”])
    pointlist.append(thing)
    f = QgsFeature()
    f.setGeometry(QgsGeometry.fromPolyline(pointlist))
    vl.startEditing()
    vl.addFeature(f)
    vl.commitChanges()

    #add the lines to the map layer
    QgsProject.instance().addMapLayer(vl)
    “`
    Disclaimer: NOT an expert in python, QGIS, or pyQGIS for that matter, so the code isn’t “professional”. It does, however, work

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: