2

I’m looking for a way to do the following in a Python script in QGIS. I have multiple shapefiles (shape_x.shp, shape_y.shp, shape_z.shp) and a CSV table which is structured like the following example:

csv table

Now I want the values value_a, value_b and value_c to be written to a new column in the respective shapefile. This is not particular a classical table join, because all the features from the shape should get the same value. So, there is no need for a common column.

The attribute table of the shape_x.shp might look like this before the data is added:

attribute table of the example shape

…and a knew column, based on the value of the CSV, should automatically be added, so that the attribute table of the shape_x.shp afterwards looks like this:

attribute table of the example shapefile after the data is added

The different shapefiles have a varying number of features, so no matter how many features, the value from the CSV should be added as a new column to every feature.

I hope I made the problem clear. I searched for automatically adding data to shapefiles and any kind of join (like this example), but no success so far.

1

Here's a code that should achieve what you described in PyQGIS. Note that this is for QGIS 2.18:

import os
import csv
from PyQt4.QtCore import QVariant
source_dir = "C:\\GIS\\Shapefiles\\" #directory containing shapefiles and data csv

#Function to write line from CSV to layer
def addField(vl, fieldData):
    vl.startEditing()
    for i,j in enumerate(range(len(fieldData))):
        myField = QgsField( fieldData[i][0], QVariant.String,'',10,2) #Note here the field type!
        vl.dataProvider().addAttributes([myField])
        vl.updateFields()
        idx = vl.fieldNameIndex( fieldData[i][0] )
        for f in vl.getFeatures():
            f[idx] = fieldData[i][1]
            vl.updateFeature( f )
    vl.commitChanges()

#Read CSV and create Python dictionary of each entry
f = open(source_dir + "Data.csv", 'rb')
reader = csv.reader(f, delimiter = ";")
first=True
d1={}
for row in reader:
    if first==True:
        header=row
        first=False
    else:
        #print nrow
        d1[row[0]]=row
f.close()
print d1



for files in os.listdir(source_dir):
    if files.endswith(".shp"):
        # create vector layer object
        vlayer  = QgsVectorLayer(source_dir + "/" + files, files, "ogr")
        print(files)
        try:
            fieldData= zip(header,d1[files])
            addField(vlayer, fieldData)
            # add the layer to the registry
            QgsMapLayerRegistry.instance().addMapLayer(vlayer )
        except KeyError:
            print "Shapefile %s"  % (files) + " does not match CSV"

Just note that it creates all the fields as type 'String', as your some of your CSV entries contain text ('no data'). If you need that to be integer or float, you should modify your CSV and then the code to QVariant.Integer. Furthermore, the code assumes that the first line in the CSV is a header. Lastly, it doesn't matter if your there's redundant entries in the CSV (Shapefiles that don't exist in the folder), but other way around will throw the exception I created.

  • Did this work for you @Miron ? – 15Step Jul 9 '18 at 12:10
  • 1
    Thanks for the answer! I couldn’t make it work so fare but I am still looking in to it to identify what could be the problem. I will report back as soon as I hade the time to figure out what’s wrong. – Miron Jul 11 '18 at 9:33
  • So far, I could not get your script to work for the test data that I have created. The script returns a “shapefile does not match CSV” message. The for loop that creates a python dictionary for each entry of the csv file returns a dictionary where the key is equal to the respective value. So print d1 returns {'shape_x.shp;1;no data;2;1;3;4;45;no data;3': ['shape_x.shp;1;no data;2;1;3;4;45;no data;3'], 'shape_y.shp;2;5;no data;4;no data;5;43;3;111': ['shape_y.shp;2;5;no data;4;no data;5;43;3;111'], 'shape_z.shp;3;4;1;4;12;98;no data;no data;8': ['shape_z.shp;3;4;1;4;12;98;no data;no data;8']} – Miron Jul 11 '18 at 14:11
  • Does this look correct? To my understanding, when the header is zipped with the entry of the python dictionary of the respective shapefile later on in your script, no connection between the actual shapefile and the key of the dictionary can be established. – Miron Jul 11 '18 at 14:11
  • 1
    Yes you are right, a KeyError occurs when there's an entry in the SHAPEFILE that does not match any of the CSV entries. In your case, I suspect it is because your particular CSV needs a ";" seperator as an argument. Change line 22 to: reader = csv.reader(f, delimiter=";"). See updated code above too – 15Step Jul 11 '18 at 19:11

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.