2

I have a code that works well in the GUI for the field calculator but I am trying to make it work in the python console of QGIS. Firstly I start off with a roads then calculate the azimuth of the road with this PyQGIS code:

fp = "C:/Users/my/file/path/roads.geojson"
layer = QgsVectorLayer(fp, "roads", "ogr")
QgsProject.instance().addMapLayer(layer)
pv = layer.dataProvider()
pv.addAttributes([QgsField('azimuth', QVariant.Double)])
layer.updateFields()
print (layer.fields().names())

The field "azimuth" is successfully created. Next is to calculate the azimuth using the method answered in the question below. Adding Direction and Distance into Attribute table

expression1 = QgsExpression('CASE WHEN ((yat(-1)-yat(0)) = 0 and (xat(-1) - xat(0)) >0) THEN 90 WHEN ((yat(-1)-yat(0)) = 0 and (xat(-1) - xat(0)) <0) THEN 270 ELSE (atan((xat(-1)-xat(0))/(yat(-1)-yat(0)))) * 180/pi() + (180 * (((yat(-1)-yat(0)) < 0) + (((xat(-1)-xat(0)) < 0 AND (yat(-1) - yat(0)) > 0)*2))) END')
context = QgsExpressionContext()
context.appendScopes(QgsExpressionContextUtils.globalProjectLayerScopes(layer))
with edit(layer):
    for f in layer.getFeatures():
        context.setFeature(f)
        f['azimuth'] = expression1.evaluate(context)
        layer.updateFeature(f)

My goal is to categorize the roads by quadrant. In the field calculator GUI of QGIS the following code works perfectly.

CASE 
WHEN  (0.000 <= "azimuth" AND "azimuth" <= 90.000 )THEN 'quad-1'
WHEN  (90.000 < "azimuth" AND "azimuth"  <= 180.000 )THEN 'quad-2'
WHEN  (180.000 < "azimuth" AND "azimuth" <= 270.000 )THEN 'quad-3'
WHEN  (270.000 < "azimuth" AND "azimuth" <= 360.000 )THEN 'quad-4'
END

I have not been able to find documentation for PyQGIS where the expression references values from an existing column to add a text string to a new column. The expression from the documentation is very simple. 11. Expressions, Filtering and Calculating Values I know that the field (column) name must be in a double quote "azimuth" was the previously created column where I calculated the az as a decimal. My attempt looks like:

pv = layer.dataProvider()
pv.addAttributes([QgsField('az_quad', QVariant.String)])

layer.updateFields()

print (layer.fields().names())
## or
for field in layer.fields():
    print(field.name(), field.typeName())

e = QgsExpression('CASE WHEN  (0.000 <= "azimuth" AND "azimuth" <= 90.000 )THEN "quad-1" WHEN  (90.000 < "azimuth" AND "azimuth"  <= 180.000 )THEN "quad-2" WHEN  (180.000 < "azimuth" AND "azimuth" <= 270.000 )THEN "quad-3" WHEN  (270.000 < "azimuth" AND "azimuth" <= 360.000 )THEN "quad-4" END')

context = QgsExpressionContext()
context.appendScopes(QgsExpressionContextUtils.globalProjectLayerScopes(layer))

with edit(layer):
    for f in layer.getFeatures():
        context.setFeature(f)
        f['az_quad'] = e.evaluate(context)
        layer.updateFeature(f)

The only difference between the python equation and the GUI field calculator was that I changed the single quote to double quote to get rid of the syntax error. It does not add any values to the az_cat column.

1 Answer 1

3

In QGIS expressions, double quotes are strictly for field names, single quotes are string literals, so I guess that in your Python attempt, the expression evaluation is looking for fields called "quad-1", "quad-2" etc. Therefore, you should remove the double quotes from your string values.

I have found that parsing QGIS expressions into Python syntax can be tricky. I have also found in the past that wrapping the entire expression in triple double quotes often works. I tested this with your expression and it worked for me.

Modified code:

pv = layer.dataProvider()
pv.addAttributes([QgsField('az_quad', QVariant.String)])

layer.updateFields()

e = QgsExpression("""CASE
                WHEN (0.000 <= "azimuth" AND "azimuth" <= 90.000 )THEN 'quad-1' 
                WHEN (90.000 < "azimuth" AND "azimuth"  <= 180.000 )THEN 'quad-2'
                WHEN (180.000 < "azimuth" AND "azimuth" <= 270.000 )THEN 'quad-3'
                WHEN (270.000 < "azimuth" AND "azimuth" <= 360.000 )THEN 'quad-4'
                END""")

context = QgsExpressionContext()
context.appendScopes(QgsExpressionContextUtils.globalProjectLayerScopes(layer))

with edit(layer):
    for f in layer.getFeatures():
        context.setFeature(f)
        f['az_quad'] = e.evaluate(context)
        layer.updateFeature(f)

Example result:

enter image description here

I played around some more and this also worked (no quotes around field names and single quotes around strings; whole expression wrapped in double quotes).

e = QgsExpression("CASE WHEN 0.000 <= azimuth AND azimuth <= 90.000 THEN 'quad-1' WHEN 90.000 < azimuth AND azimuth  <= 180.000 THEN 'quad-2' WHEN 180.000 < azimuth AND azimuth <= 270.000 THEN 'quad-3' WHEN 270.000 < azimuth AND azimuth <= 360.000 THEN 'quad-4' END")

Another option is to calculate and update the new values entirely with PyQGIS. An example of that (which produced the identical result) looks like this:

pv = layer.dataProvider()
pv.addAttributes([QgsField('az_quad', QVariant.String)])

layer.updateFields()

# retrieve index of 'az_quad' field
fld_idx = layer.fields().lookupField('az_quad')

# a dictionary which will be populated with feature id keys and
# value dictionaries composed of field index and attribute value
att_map = {}

# loop through features and populate attribute map with values calculated
# from value in 'azimuth' field
for f in layer.getFeatures():
    az = f['azimuth']
    if az == NULL:
        continue
    if 0.000 <= az <= 90.000:
        att_map[f.id()]={fld_idx: 'quad-1'}
    if 90.000 < az <= 180.000:
        att_map[f.id()]={fld_idx: 'quad-2'}
    if 180.000 < az <= 270.000:
        att_map[f.id()]={fld_idx: 'quad-3'}
    if 270.000 < az <= 360.000:
        att_map[f.id()]={fld_idx: 'quad-4'}

# finally, write attributes to layer provider
pv.changeAttributeValues(att_map)

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.