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.

Matt
  • 16,843
  • 3
  • 21
  • 52
nerdsconsider
  • 379
  • 1
  • 8

1 Answers1

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)

Ben W
  • 21,426
  • 3
  • 15
  • 39