Thursday 19 April 2018

python - Why a QgsExpression does not evaluate a feature as expected in PyQGIS 3?


When I run following code in QGIS 2.18:



layer = iface.activeLayer()

expression = QgsExpression( u'"area" > 2e8 AND "area" < 3e8' )

feats = [ feat for feat in layer.getFeatures()
if expression.evaluate( feat )]

epsg = layer.crs().postgisSrid()

uri = "Polygon?crs=epsg:" + str(epsg) + "&field=id:integer""&index=yes"


mem_layer = QgsVectorLayer(uri,
'mem_layer',
'memory')

prov = mem_layer.dataProvider()

for i, feat in enumerate(feats):
feat.setAttributes([i])
mem_layer.addFeature(feat)


prov.addFeatures(feats)

QgsMapLayerRegistry.instance().addMapLayer(mem_layer)

I got the result of following image:


enter image description here


However, equivalent code in QGIS 3.x doesn't work.


layer = iface.activeLayer()


expression = QgsExpression( u'"area" > 2e8 AND "area" < 3e8' )

feats = [ feat for feat in layer.getFeatures()
if expression.evaluate( feat )]

epsg = layer.crs().postgisSrid()

uri = "Polygon?crs=epsg:" + str(epsg) + "&field=id:integer""&index=yes"

mem_layer = QgsVectorLayer(uri,

'mem_layer',
'memory')

prov = mem_layer.dataProvider()

for i, feat in enumerate(feats):
feat.setAttributes([i])
mem_layer.addFeature(feat)

prov.addFeatures(feats)


QgsProject.instance().addMapLayer(mem_layer)

Error message is:


Traceback (most recent call last):
File "C:\PROGRA~1\QGIS3~1.0\apps\Python36\lib\code.py", line 91, in runcode
exec(code, self.locals)
File "", line 1, in
File "", line 5, in
File "", line 6, in

TypeError: QgsExpression.evaluate(): arguments did not match any overloaded call:
overload 1: too many arguments
overload 2: argument 1 has unexpected type 'QgsFeature'

where it is said that overload 2: argument 1 has unexpected type 'QgsFeature'.


So, I did a 'help(QgsExpression.evaluate)' request at Python Console:


help(QgsExpression.evaluate)
Help on built-in function evaluate:

evaluate(...)

Evaluate the feature and return the result.

.. note::

this method does not expect that prepare() has been called on this instance

.. versionadded:: 2.12
Evaluate the expression against the specified context and return the result.

:param context: context for evaluating expression


.. note::

prepare() should be called before calling this method.

.. versionadded:: 2.12

where it can be observed, at first paragraph, that Evaluate the feature and return the result is completely factible and this method does not expect that prepare() has been called on this instance.


Searching other possible solutions, I found out this Joseph's answer where it is pointed out that "... you may need to use a combination of the QgsExpressionContext and QgsExpressionContextScope classes". When I use this approach, in a second run, QGIS 3.0 crashed indefectively.


So, Why a QgsExpression does not evaluate a feature as expected in PyQGIS 3? Is It possible to solve this issue without QgsExpressionContext and QgsExpressionContextScope classes?



Editing Note:


As I am a very big fan of Python List Comprehension, I modified Kadir's lines code to more concise form as:


project = QgsProject.instance()

layer = project.mapLayersByName('polygon8')

expression = QgsExpression( '"area" > 2e8 AND "area" < 3e8' )

context = QgsExpressionContext()
scope = QgsExpressionContextScope()

context.appendScope(scope)

eval = [ [scope.setFeature(feat), expression.evaluate(context)][1] for feat in layer[0].getFeatures() ]

feats = [ item[1] for item in enumerate(layer[0].getFeatures()) if eval[item[0]] == 1]

epsg = layer[0].crs().postgisSrid()

uri = "Polygon?crs=epsg:" + str(epsg) + "&field=id:integer""&index=yes"


mem_layer = QgsVectorLayer(uri,
'mem_layer',
'memory')

prov = mem_layer.dataProvider()

for i, feat in enumerate(feats):
feat.setAttributes([i])
mem_layer.addFeature(feat)


prov.addFeatures(feats)

QgsProject.instance().addMapLayer(mem_layer)

It works as expected.



Answer



By changing some lines, your code will work. In Joseph's answer, context.appendScope(scope) line have to moved outside the loop to solve crashing issue. @ndawson's answer explains why.


layer = iface.activeLayer()

expression = QgsExpression( u'"area" > 2e8 AND "area" < 3e8' )


# Added / changed lines ##########
context = QgsExpressionContext()
scope = QgsExpressionContextScope()
context.appendScope(scope)

feats=[]
for feat in layer.getFeatures():
scope.setFeature(feat)
result = expression.evaluate(context)

if result:
feats.append(feat)
################


epsg = layer.crs().postgisSrid()

uri = "Polygon?crs=epsg:" + str(epsg) + "&field=id:integer""&index=yes"

mem_layer = QgsVectorLayer(uri, 'mem_layer', 'memory')


prov = mem_layer.dataProvider()

for i, feat in enumerate(feats):
feat.setAttributes([i])
mem_layer.addFeature(feat)

prov.addFeatures(feats)

QgsProject.instance().addMapLayer(mem_layer)

No comments:

Post a Comment

arcpy - Changing output name when exporting data driven pages to JPG?

Is there a way to save the output JPG, changing the output file name to the page name, instead of page number? I mean changing the script fo...