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– xunilk2018年12月31日 17:17:52 +00:00Commented Dec 31, 2018 at 17:17
2 Answers 2
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.
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, \'"\')'