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.
PyQGIS 101 is a work in progress. I’d appreciate any feedback, particularly from beginners!
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:
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:
‘With edit(layer)’ example would be good addition. Relating ‘with open(file) ‘ like usage in python
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!
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!
Hi Soli, That’s certainly possible. Have you given it a try yet? Where are you stuck? Reading CSVs is documented in https://docs.qgis.org/testing/en/docs/pyqgis_developer_cookbook/loadlayer.html. The rest should be covered by running Processing tools https://anitagraser.com/pyqgis-101-introduction-to-qgis-python-programming-for-non-programmers/pyqgis-101-running-processing-tools/
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…..
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.
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
Remember what I wrote in https://anitagraser.com/pyqgis-101-introduction-to-qgis-python-programming-for-non-programmers/pyqgis-101-running-processing-tools/: you can run Processing tools from the GUI and then get the corresponding code from the Processing history. That way, you can find the correct syntax to make Processing use only the selected layers. You can also see how the syntax looks like if an output file path is specified. The issues you are encountering are not specific to the input file format.
I’ve written a new blog post on chaining Processing tools that will hopefully help: https://anitagraser.com/pyqgis-101-introduction-to-qgis-python-programming-for-non-programmers/pyqgis101-chaining-processing-tools/
Exellent! Thank you, I will have a look into this one :)
Great, let me know how it goes.
EDIT: a little error… ‘INPUT’: uri, should be ‘INPUT’:vlayer,
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
Thanks for sharing, Tyler!
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.
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
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
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 !^^
I managed to solve it !!!
I just needed to add v.startEditing() at the beginning of my script to make my vector editable