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!

25 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

  8. Judith said:

    Is there a way to capture what you have just done in the GUI into pyqgis – similar to python snippets in arcgis – that really helps to learn the code and if you are stuck you can do it in the gui for one layer and then use the snippet as a template?

    • Dear Judith, I’m not familiar with how it works in ArcGIS but you can get the code snippets for all tools that you use from the Processing toolbox in the Processing history. The other parts of the QGIS GUI are not recorded by any log/history.

  9. Joao Folgado said:

    Hey! thank for this publication. I create the layer just fine. but the points that i add to the layer dont appear in qgis. can you help me whit this problem ? thanks

  10. Gustavo Ureña said:

    Excuse me I have a question:
    How can I do to add a greater number of data or rows to the field that we have created?
    I await your answer, thank you

  11. Alexandre said:

    Hello,
    I’m trying to add a feature like above but I encountered an error : NameError: name ‘QVariant’ is not defined
    After searching a bit, I tried to replace String, Int and Double by QVariant.String, Qvariant.Int and Qvariant.Double and then the error disapeared (and won’t show again if I take out the QVariant.* …. weird….)
    Though I am stuck at this : the feature f is not added by pr.addFeature(f) (when printing the no of features I have 0), though the attributes are well writenn in f by f.setAttributes([…]).
    Anyway this tutorial is great so far !^^

    • Alexandre said:

      I managed to solve it !!!
      I just needed to add v.startEditing() at the beginning of my script to make my vector editable

  12. Bonjour j’ai une préoccupation. j’aimerais pouvoir tracer grace à un script une ligne liant un point A à un autre point B.
    Cette action devra être répétée sur plusieurs points.
    Considérons qu’on a un tableau de couches vectorielles avec plusieurs points et q’on voudrais les relier indépendamment.
    Veuillez m’aider plus explicitement je débute vraiment en script.

Leave a reply to soli Cancel reply

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