5

I have a working script that builds rectangles from a CSV using @Kadir Sahbaz's answer and @Ben W's answer

I'm wanting to pass non-geometry attributes as well. This runs and creates the fields but with NULL values:

srcFile = "D:\\Data\\filename.csv"

layer = QgsVectorLayer("Polygon?crs=EPSG:2193", "Polygons", "memory")
QgsProject.instance().addMapLayer(layer)

provider = layer.dataProvider()
provider.addAttributes([QgsField('ViewName',QVariant.String, '', 50, 0),
                        QgsField('FigName',QVariant.String, '', 10, 0)])
layer.updateFields()

with open(srcFile,'r') as infile:
    rect = infile.readlines()
    
for b in rect[1:]:
    parts = b.split(',')

    #### Coordinates coming from CSV ####
    xMin = float(parts[2])
    yMin = float(parts[3])
    xMax = float(parts[4])
    yMax = float(parts[5])

    rect = QgsRectangle(xMin, yMin, xMax, yMax)
    polygon = QgsGeometry.fromRect(rect)
    
    feature = QgsFeature()
    feature.setGeometry(polygon)

    provider.addFeatures([feature]) 
 
    fields = QgsFields()
    feature.setFields(fields)
    feature['ViewName'] = parts[0]
    feature['FigName'] = parts[-2]

    provider.addAttributes(fields)
    layer.updateFields()

I'm still getting NULL values. FWIW...if I run @Ben W's code, change the with statement to:

with edit(layer):
    print(parts[0],parts[-2])
    provider.addFeatures([feature])
    t = provider.changeAttributeValues({feature.id(): {f1: parts[0], f2: parts[-2]}})
    print(t)

The values from the CSV are appearing and .changeAttributeValues appears to running properly:

enter image description here

3 Answers 3

10

You set feature fields to empty QgsFields.

fields = QgsFields() # fields is empty
feature.setFields(fields) # you set the empty fields

Instead, pass layer.fields() to QgsFeature.

feature = QgsFeature(layer.fields())

You also get NULL values, because you add the feature to the layer (provider.addFeatures([feature])) before you populate the fields.

Use this script:

srcFile = "D:\\Data\\filename.csv"

layer = QgsVectorLayer("Polygon?crs=EPSG:2193", "Polygons", "memory")
QgsProject.instance().addMapLayer(layer)

provider = layer.dataProvider()
provider.addAttributes([QgsField('ViewName',QVariant.String, '', 50, 0),
                        QgsField('FigName',QVariant.String, '', 10, 0)])
layer.updateFields()

with open(srcFile,'r') as infile:
    rows = infile.readlines()

for r in rows:
    parts = r.split(',')

    xMin = float(parts[2])
    yMin = float(parts[3])
    xMax = float(parts[4])
    yMax = float(parts[5])

    rect = QgsRectangle(xMin, yMin, xMax, yMax)
    polygon = QgsGeometry.fromRect(rect)
    
    feature = QgsFeature(layer.fields())
    feature.setGeometry(polygon)
    feature['ViewName'] = parts[0]
    feature['FigName'] = parts[-2]
    provider.addFeatures([feature]) 

Sample CSV:

1,0, 1804512.8556, 5453390.58088, 1813512.8556, 5461390.58088, yedi1, sekiz1, dokuz1
2,0, 1814512.8556, 5473390.58088, 1823512.8556, 5471390.58088, yedi2, sekiz2, dokuz2

Result:

enter image description here

1
  • I had to re-add the FOR loop slice to skip my header row, but this works...and with a minimal # of lines. Cheers
    – CreekGeek
    Commented May 14, 2021 at 2:00
8

Personally, I prefer to use the changeAttributeValues() method of QgsVectorDataProvider (see API docs here) as I find it more reliable. This method takes an attributes map which is basically a dictionary where the key is a feature id and the value is a second dictionary comprising field indexes as keys and the new attributes as values.

I have tested this starting with a csv file with the following structure:

enter image description here

After running the following code:

srcFile = 'D:\\Data\\filename.csv'

layer = QgsVectorLayer("Polygon?crs=EPSG:2193", "Polygons", "memory")
QgsProject.instance().addMapLayer(layer)
provider = layer.dataProvider()
provider.addAttributes([QgsField('ViewName',QVariant.String, '', 50, 0),
                        QgsField('FigName',QVariant.String, '', 10, 0)])
layer.updateFields()
f1 = layer.fields().lookupField('ViewName')
f2 = layer.fields().lookupField('FigName')
fid = 1

with open(srcFile,'r') as infile:
    csv_lines = infile.readlines()[1:]

for line in csv_lines:
    line = line.split(',')
    #### Coordinates coming from CSV ####
    xMin = float(line[2])
    yMin = float(line[3])
    xMax = float(line[4])
    yMax = float(line[5])

    rect = QgsRectangle(xMin, yMin, xMax, yMax)
    polygon = QgsGeometry.fromRect(rect)
    
    feature = QgsFeature()
    feature.setGeometry(polygon)
    
    with edit(layer):
        provider.addFeatures([feature])
        atts = {f1: line[0], f2: line[-2]}
        provider.changeAttributeValues({fid: atts})
        fid += 1

The resulting layer and attribute table looks like this:

enter image description here

3
  • cheers for reply. I'm still getting NULLs. I've updated your with statement in my post with results
    – CreekGeek
    Commented May 13, 2021 at 21:01
  • 1
    @CreekGeek, the problem was incrementing the feature id in the attributes map. I have thoroughly tested this and edited my answer. The other answers probably suit you better, but as a matter of pride I was compelled to provide an alternative working solution! ;-)
    – Ben W
    Commented May 14, 2021 at 3:48
  • 1
    Tested it on my end and totally good. I appreciate the effort (and pride in your work). Pretty awesome to get three different answers, all of which are viable. I'm new to QGIS, but already digging it way more than Arc/arcpy. Cheers
    – CreekGeek
    Commented May 14, 2021 at 6:09
5

I have had this problem too. You have:

vl = QgsVectorLayer("Polygon?crs=EPSG:2193", "Polygons", "memory")

Maybe add the attribute definitions into the uri.

uri = 'LineString?crs= EPSG:2193&field=id:integer&field=name:String(40)&field=age:double'
layer = QgsVectorLayer(uri, "Polygons", "memory")
pr = vl.dataProvider()
pr.addFeatures(features)
pr.updateExtents()
vl.updateExtents()

The features array would contain features that have both geometry and attributes defined.

In a loop:

feature = QgsFeature()
ID = 1
name = 'Dog'
age = 4.36
g = QgsGeometry.fromPolylineXY(points)
a = [int(ID), name, age]
feature.setGeometry(g)
feature.setAttributes(a)
features.add(feature)
  • This way works reliably for me.
  • if you get the uri syntax wrong, you usually get NULL attribute values
  • Every time I have a problem, it has always been with the uri syntax not matching the real attributes (make sure an int is actually an int, as shown above)
1
  • I like the idea of building it in early and just passing a list. Upvoting and will keep this one in my back pocket as the Kadir's answer required fewer mods. Cheers!
    – CreekGeek
    Commented May 14, 2021 at 2:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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