2

I want to use PyQGIS in QGIS 2.18 to add a new field to a vector (line) layer and calculate the bearings for each feature as i have been able to do easily in the field calculator using the expression

concat(floor(degrees(azimuth(start_point($geometry), end_point($geometry)))), '° ', 
floor(degrees(azimuth(start_point($geometry), end_point($geometry)))*60 % 60), ''' ',
degrees(azimuth(start_point($geometry), end_point($geometry)))*3600 % 60, '''')

The result of this expression is a string that displays the degrees, minute, seconds value

After a little research, i tried to carry out the task with this script which produced an error.(Actually i was able to use this script to create and update a field that shows the length of lines of the layer, using QVariant.Double and $length for the expression)

from PyQt4.QtCore import QVariant
from qgis.core import QgsField, QgsExpression, QgsFeature

vl = iface.activeLayer()
vl.startEditing()

myField = QgsField( 'Bearing', QVariant.String)
vl.dataProvider().addAttributes([myField])
vl.updateFields()
idx = vl.fieldNameIndex('Bearing')

#next step
exp = QgsExpression('
concat(floor(degrees(azimuth(start_point($geometry), end_point($geometry)))), '° ', 
floor(degrees(azimuth(start_point($geometry), end_point($geometry)))*60 % 60), ''' ',
degrees(azimuth(start_point($geometry), end_point($geometry)))*3600 % 60, '''')') 
exp.prepare( vl.pendingFields() )

for f in vl.getFeatures():
    f[idx] = exp.evaluate( f )
    vl.updateFeature( f )

vl.commitChanges()

Where am I going wrong?

  • You don't need this kind of expression if you want to work directly with PyQGIS. Please, see my answer. – xunilk Dec 31 '18 at 17:17
1

You don't need this kind of expression if you want to work directly with PyQGIS. Following code does the same and it is more legible.

from PyQt4.QtCore import QVariant
import math

layer = iface.activeLayer()

myField = QgsField( 'Bearing', QVariant.String)

layer.dataProvider().addAttributes([myField])
layer.updateFields()

idx = layer.fieldNameIndex('Bearing')

layer.startEditing()

for feat in layer.getFeatures():
    pt1 = feat.geometry().asPolyline()[0] 
    pt2 = feat.geometry().asPolyline()[1]
    az = pt1.azimuth(pt2)
    if az < 0:
        az += 360
    minutes = az%1.0*60
    seconds = minutes%1.0*60
    string = str(int(math.floor(az))) + "\xb0 " + str(int(math.floor(minutes))) + "' " + str(seconds) + "''"

    feat[idx] = string
    layer.updateFeature( feat )

layer.commitChanges()

I tried it out with shapefile of following image where it can be observed, after running above script, that 'with_form' field (calculated with your field expression) and 'Bearing' field are identical.

enter image description here

  • thanks a lot, i find that this script works perfectly – GeomaSmart Jan 1 at 2:24
  • You're welcome. – xunilk Jan 1 at 3:09
1

Doesn't look like your QgsExpression is a finished string....I suspect Python isn't parsing your characters for deg, min, sec as you expect. This is likely indicated in the code you posted in that the color-coding is all the same after your exp line? (On the exp.prepare line, shouldn't color revert back to black?)

I cannot try this with QGIS, but think your QgsExpression (within the parens) should contain the following text demonstrated by completeExpression shown below:

>>> part1 = "concat(floor(degrees(azimuth(start_point($geometry), end_point($geometry)))), '° ',"
>>> part2 = "floor(degrees(azimuth(start_point($geometry), end_point($geometry)))*60 % 60), '\' ',"
>>> part3 = "degrees(azimuth(start_point($geometry), end_point($geometry)))*3600 % 60, '\"')"
>>> completeExpression = part1 + part2 + part3
>>> print(completeExpression)
concat(floor(degrees(azimuth(start_point($geometry), end_point($geometry)))), '° ',floor(degrees(azimuth(start_point($geometry), end_point($geometry)))*60 % 60), '' ',degrees(azimuth(start_point($geometry), end_point($geometry)))*3600 % 60, '"')

To be a little more clear, you have to use the escape sequence for certain chars. The deg min sec symbols could be written as text in Python with (I'm using the Python console):

>>> degMinSec = "° \' \""
>>> print(degMinSec)
° ' "
>>>

Also, as shown below, you may have problems with Unicode - your degree symbol is such a character, \xc2\xb0, (but the above advice still holds for escape sequencing in Python):

>>> theExpression = "{}{}{}".format(part1, part2, part3)
>>> theExpression
'concat(floor(degrees(azimuth(start_point($geometry), end_point($geometry)))), \'\xc2\xb0 \',floor(degrees(azimuth(start_point($geometry), end_point($geometry)))*60 % 60), \'\' \',degrees(azimuth(start_point($geometry), end_point($geometry)))*3600 % 60, \'"\')'

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.