Allow access to feature geometry in generator subsymbols via

geometry($currentfeature)

When used in a geometry generator subsymbol, the expression

    geometry($currentfeature)

should refer to the original feature's geometry, not the geometry
generated by the generator symbol (which is accessible through
$geometry)

Fixes #46455
This commit is contained in:
Nyall Dawson 2022-02-17 14:05:22 +10:00
parent 84f3b42861
commit 914dccbd5b
4 changed files with 81 additions and 7 deletions

View File

@ -305,7 +305,7 @@ bool QgsGeometryGeneratorSymbolLayer::isCompatibleWithSymbol( QgsSymbol *symbol
return true;
}
QgsGeometry QgsGeometryGeneratorSymbolLayer::evaluateGeometryInPainterUnits( const QgsGeometry &input, const QgsFeature &feature, const QgsRenderContext &renderContext, QgsExpressionContext &expressionContext ) const
QgsGeometry QgsGeometryGeneratorSymbolLayer::evaluateGeometryInPainterUnits( const QgsGeometry &input, const QgsFeature &, const QgsRenderContext &renderContext, QgsExpressionContext &expressionContext ) const
{
QgsGeometry drawGeometry( input );
// step 1 - scale the draw geometry from PAINTER units to target units (e.g. millimeters)
@ -314,11 +314,9 @@ QgsGeometry QgsGeometryGeneratorSymbolLayer::evaluateGeometryInPainterUnits( con
drawGeometry.transform( painterToTargetUnits );
// step 2 - set the feature to use the new scaled geometry, and inject it into the expression context
QgsFeature f( feature );
f.setGeometry( drawGeometry );
QgsExpressionContextScope *generatorScope = new QgsExpressionContextScope();
QgsExpressionContextScopePopper popper( expressionContext, generatorScope );
generatorScope->setFeature( f );
generatorScope->setGeometry( drawGeometry );
// step 3 - evaluate the new generated geometry.
QgsGeometry geom = mExpression->evaluate( &expressionContext ).value<QgsGeometry>();
@ -483,8 +481,8 @@ void QgsGeometryGeneratorSymbolLayer::render( QgsSymbolRenderContext &context, Q
}
QgsExpressionContextScope *subSymbolExpressionContextScope = mSymbol->symbolRenderContext()->expressionContextScope();
subSymbolExpressionContextScope->setFeature( f );
// override the $geometry value for all subsymbols -- this should be the generated geometry
subSymbolExpressionContextScope->setGeometry( f.geometry() );
const bool prevIsSubsymbol = context.renderContext().flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
context.renderContext().setFlag( Qgis::RenderContextFlag::RenderingSubSymbol );

View File

@ -47,7 +47,11 @@ from qgis.core import (
QgsCoordinateTransform,
QgsArrowSymbolLayer,
QgsFeature,
QgsGeometry
QgsGeometry,
QgsFontMarkerSymbolLayer,
QgsFontUtils,
QgsSymbolLayer,
QgsProperty
)
from qgis.testing import start_app, unittest
@ -410,6 +414,78 @@ class TestQgsGeometryGeneratorSymbolLayerV2(unittest.TestCase):
self.report += renderchecker.report()
self.assertTrue(res)
def test_geometry_function(self):
"""
The $geometry function used in a subsymbol should refer to the generated geometry
"""
points = QgsVectorLayer('Point?crs=epsg:4326', 'Points', 'memory')
self.assertTrue(points.isValid())
f = QgsFeature()
f.setGeometry(QgsGeometry.fromWkt('Point(1 2)'))
points.dataProvider().addFeature(f)
font = QgsFontUtils.getStandardTestFont('Bold')
font_marker = QgsFontMarkerSymbolLayer(font.family(), 'x', 16)
font_marker.setDataDefinedProperty(QgsSymbolLayer.PropertyCharacter, QgsProperty.fromExpression('geom_to_wkt($geometry)'))
subsymbol = QgsMarkerSymbol()
subsymbol.changeSymbolLayer(0, font_marker)
parent_generator = QgsGeometryGeneratorSymbolLayer.create({'geometryModifier': 'translate($geometry, 1, 2)'})
parent_generator.setSymbolType(QgsSymbol.Marker)
parent_generator.setSubSymbol(subsymbol)
geom_symbol = QgsMarkerSymbol()
geom_symbol.changeSymbolLayer(0, parent_generator)
points.renderer().setSymbol(geom_symbol)
mapsettings = QgsMapSettings(self.mapsettings)
mapsettings.setExtent(QgsRectangle(0, 0, 5, 5))
mapsettings.setLayers([points])
renderchecker = QgsMultiRenderChecker()
renderchecker.setMapSettings(mapsettings)
renderchecker.setControlName('expected_geometrygenerator_function_geometry')
res = renderchecker.runTest('geometrygenerator_function_geometry')
self.report += renderchecker.report()
self.assertTrue(res)
def test_feature_geometry(self):
"""
The geometry($currentfeature) expression used in a subsymbol should refer to the original FEATURE geometry
"""
points = QgsVectorLayer('Point?crs=epsg:4326', 'Points', 'memory')
self.assertTrue(points.isValid())
f = QgsFeature()
f.setGeometry(QgsGeometry.fromWkt('Point(1 2)'))
points.dataProvider().addFeature(f)
font = QgsFontUtils.getStandardTestFont('Bold')
font_marker = QgsFontMarkerSymbolLayer(font.family(), 'x', 16)
font_marker.setDataDefinedProperty(QgsSymbolLayer.PropertyCharacter, QgsProperty.fromExpression('geom_to_wkt(geometry($currentfeature))'))
subsymbol = QgsMarkerSymbol()
subsymbol.changeSymbolLayer(0, font_marker)
parent_generator = QgsGeometryGeneratorSymbolLayer.create({'geometryModifier': 'translate($geometry, 1, 2)'})
parent_generator.setSymbolType(QgsSymbol.Marker)
parent_generator.setSubSymbol(subsymbol)
geom_symbol = QgsMarkerSymbol()
geom_symbol.changeSymbolLayer(0, parent_generator)
points.renderer().setSymbol(geom_symbol)
mapsettings = QgsMapSettings(self.mapsettings)
mapsettings.setExtent(QgsRectangle(0, 0, 5, 5))
mapsettings.setLayers([points])
renderchecker = QgsMultiRenderChecker()
renderchecker.setMapSettings(mapsettings)
renderchecker.setControlName('expected_geometrygenerator_feature_geometry')
res = renderchecker.runTest('geometrygenerator_feature_geometry')
self.report += renderchecker.report()
self.assertTrue(res)
def imageCheck(self, name, reference_image, image):
self.report += "<h2>Render {}</h2>\n".format(name)
temp_dir = QDir.tempPath() + '/'

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 MiB