Skip to content

Commit

Permalink
Fix geometry generator when used as symbol for annotation item
Browse files Browse the repository at this point in the history
  • Loading branch information
nyalldawson committed Sep 22, 2021
1 parent 05778aa commit 0593a7b
Show file tree
Hide file tree
Showing 10 changed files with 93 additions and 40 deletions.
Expand Up @@ -120,7 +120,7 @@ This is a hybrid layer, it constructs its own geometry so it does not
care about the geometry of its parents.
%End

void render( QgsSymbolRenderContext &context, const QPolygonF *points = 0, const QVector<QPolygonF> *rings = 0 );
void render( QgsSymbolRenderContext &context, QgsWkbTypes::GeometryType geometryType = QgsWkbTypes::GeometryType::UnknownGeometry, const QPolygonF *points = 0, const QVector<QPolygonF> *rings = 0 );
%Docstring
Will render this symbol layer using the context.
In comparison to other symbols there is no geometry passed in, since
Expand All @@ -129,14 +129,14 @@ which contains a :py:class:`QgsRenderContext` which in turn contains an expressi
context which is available to the evaluated expression.

:param context: The rendering context which will be used to render and to construct a geometry.
:param geometryType: type of original geometry being rendered by the parent symbol (since QGIS 3.22)
:param points: optional list of original points which are being rendered by the parent symbol (since QGIS 3.22)
:param rings: optional list of original rings which are being rendered by the parent symbol (since QGIS 3.22)
%End

virtual void setColor( const QColor &color );



private:
QgsGeometryGeneratorSymbolLayer( const QgsGeometryGeneratorSymbolLayer &copy );
};
Expand Down
6 changes: 3 additions & 3 deletions python/core/auto_generated/symbology/qgssymbol.sip.in
Expand Up @@ -677,7 +677,7 @@ Retrieve a cloned list of all layers that make up this symbol.
Ownership is transferred to the caller.
%End

void renderUsingLayer( QgsSymbolLayer *layer, QgsSymbolRenderContext &context, const QPolygonF *points = 0, const QVector<QPolygonF> *rings = 0 );
void renderUsingLayer( QgsSymbolLayer *layer, QgsSymbolRenderContext &context, QgsWkbTypes::GeometryType geometryType = QgsWkbTypes::GeometryType::UnknownGeometry, const QPolygonF *points = 0, const QVector<QPolygonF> *rings = 0 );
%Docstring
Renders a context using a particular symbol layer without passing in a
geometry. This is used as fallback, if the symbol being rendered is not
Expand All @@ -687,8 +687,8 @@ geometry passed to the layer will be empty.
This is required for layers that generate their own geometry from other
information in the rendering context.

Since QGIS 3.22, the optional ``points`` and ``rings`` arguments can specify the original
points and rings in which are being rendered by the parent symbol.
Since QGIS 3.22, the optional ``geometryType``, ``points`` and ``rings`` arguments can specify the original
geometry type, points and rings in which are being rendered by the parent symbol.
%End

void renderVertexMarker( QPointF pt, QgsRenderContext &context, Qgis::VertexMarkerType currentVertexMarkerType, double currentVertexMarkerSize );
Expand Down
4 changes: 2 additions & 2 deletions src/core/symbology/qgsfillsymbol.cpp
Expand Up @@ -53,7 +53,7 @@ void QgsFillSymbol::renderPolygon( const QPolygonF &points, const QVector<QPolyg
if ( symbolLayer->type() == Qgis::SymbolType::Fill || symbolLayer->type() == Qgis::SymbolType::Line )
renderPolygonUsingLayer( symbolLayer, points, rings, symbolContext );
else
renderUsingLayer( symbolLayer, symbolContext, &points, rings );
renderUsingLayer( symbolLayer, symbolContext, QgsWkbTypes::PolygonGeometry, &points, rings );
}
return;
}
Expand All @@ -70,7 +70,7 @@ void QgsFillSymbol::renderPolygon( const QPolygonF &points, const QVector<QPolyg
if ( symbolLayer->type() == Qgis::SymbolType::Fill || symbolLayer->type() == Qgis::SymbolType::Line )
renderPolygonUsingLayer( symbolLayer, points, rings, symbolContext );
else
renderUsingLayer( symbolLayer, symbolContext, &points, rings );
renderUsingLayer( symbolLayer, symbolContext, QgsWkbTypes::PolygonGeometry, &points, rings );
}
}

Expand Down
63 changes: 43 additions & 20 deletions src/core/symbology/qgsgeometrygeneratorsymbollayer.cpp
Expand Up @@ -256,7 +256,7 @@ bool QgsGeometryGeneratorSymbolLayer::isCompatibleWithSymbol( QgsSymbol *symbol
Q_UNUSED( symbol )
return true;
}
void QgsGeometryGeneratorSymbolLayer::render( QgsSymbolRenderContext &context, const QPolygonF *points, const QVector<QPolygonF> *rings )
void QgsGeometryGeneratorSymbolLayer::render( QgsSymbolRenderContext &context, QgsWkbTypes::GeometryType geometryType, const QPolygonF *points, const QVector<QPolygonF> *rings )
{
if ( mRenderingFeature && mHasRenderedFeature )
return;
Expand All @@ -271,27 +271,40 @@ void QgsGeometryGeneratorSymbolLayer::render( QgsSymbolRenderContext &context, c
QgsGeometry drawGeometry;

// step 1 - convert points and rings to geometry
if ( points->size() == 1 )
switch ( geometryType )
{
drawGeometry = QgsGeometry::fromPointXY( points->at( 0 ) );
}
else if ( rings )
{
// polygon
std::unique_ptr < QgsLineString > exterior( QgsLineString::fromQPolygonF( *points ) );
std::unique_ptr< QgsPolygon > polygon = std::make_unique< QgsPolygon >();
polygon->setExteriorRing( exterior.release() );
for ( const QPolygonF &ring : *rings )
case QgsWkbTypes::PointGeometry:
{
polygon->addInteriorRing( QgsLineString::fromQPolygonF( ring ) );
Q_ASSERT( points->size() == 1 );
drawGeometry = QgsGeometry::fromPointXY( points->at( 0 ) );
break;
}
drawGeometry = QgsGeometry( std::move( polygon ) );
}
else
{
// line
std::unique_ptr < QgsLineString > ring( QgsLineString::fromQPolygonF( *points ) );
drawGeometry = QgsGeometry( std::move( ring ) );
case QgsWkbTypes::LineGeometry:
{
Q_ASSERT( !rings );
std::unique_ptr < QgsLineString > ring( QgsLineString::fromQPolygonF( *points ) );
drawGeometry = QgsGeometry( std::move( ring ) );
break;
}
case QgsWkbTypes::PolygonGeometry:
{
std::unique_ptr < QgsLineString > exterior( QgsLineString::fromQPolygonF( *points ) );
std::unique_ptr< QgsPolygon > polygon = std::make_unique< QgsPolygon >();
polygon->setExteriorRing( exterior.release() );
if ( rings )
{
for ( const QPolygonF &ring : *rings )
{
polygon->addInteriorRing( QgsLineString::fromQPolygonF( ring ) );
}
}
drawGeometry = QgsGeometry( std::move( polygon ) );
break;
}

case QgsWkbTypes::UnknownGeometry:
case QgsWkbTypes::NullGeometry:
return; // unreachable
}

// step 2 - scale the draw geometry from PAINTER units to target units (e.g. millimeters)
Expand All @@ -311,9 +324,19 @@ void QgsGeometryGeneratorSymbolLayer::render( QgsSymbolRenderContext &context, c
// step 5 - transform geometry back from target units to MAP units. We transform to map units here
// as we'll ultimately be calling renderFeature, which excepts the feature has a geometry in map units.
// Here we also scale the transform by the target unit to painter units factor to reverse that conversion
geom.transform( painterToTargetUnits.inverted( ) );
QTransform mapToPixel = context.renderContext().mapToPixel().transform();
mapToPixel.scale( scale, scale );
geom.transform( mapToPixel.inverted() );
// also need to apply the coordinate transform from the render context
try
{
geom.transform( context.renderContext().coordinateTransform(), QgsCoordinateTransform::ReverseTransform );
}
catch ( QgsCsException & )
{
QgsDebugMsg( QStringLiteral( "Could no transform generated geometry to layer CRS" ) );
}

f.setGeometry( geom );
}
else if ( context.feature() )
Expand Down
4 changes: 2 additions & 2 deletions src/core/symbology/qgsgeometrygeneratorsymbollayer.h
Expand Up @@ -123,14 +123,14 @@ class CORE_EXPORT QgsGeometryGeneratorSymbolLayer : public QgsSymbolLayer
* context which is available to the evaluated expression.
*
* \param context The rendering context which will be used to render and to construct a geometry.
* \param geometryType type of original geometry being rendered by the parent symbol (since QGIS 3.22)
* \param points optional list of original points which are being rendered by the parent symbol (since QGIS 3.22)
* \param rings optional list of original rings which are being rendered by the parent symbol (since QGIS 3.22)
*/
void render( QgsSymbolRenderContext &context, const QPolygonF *points = nullptr, const QVector<QPolygonF> *rings = nullptr );
void render( QgsSymbolRenderContext &context, QgsWkbTypes::GeometryType geometryType = QgsWkbTypes::GeometryType::UnknownGeometry, const QPolygonF *points = nullptr, const QVector<QPolygonF> *rings = nullptr );

void setColor( const QColor &color ) override;


private:
QgsGeometryGeneratorSymbolLayer( const QString &expression );

Expand Down
4 changes: 2 additions & 2 deletions src/core/symbology/qgslinesymbol.cpp
Expand Up @@ -223,7 +223,7 @@ void QgsLineSymbol::renderPolyline( const QPolygonF &points, const QgsFeature *f
renderPolylineUsingLayer( lineLayer, points, symbolContext );
}
else
renderUsingLayer( symbolLayer, symbolContext, &points );
renderUsingLayer( symbolLayer, symbolContext, QgsWkbTypes::LineGeometry, &points );
}
return;
}
Expand All @@ -244,7 +244,7 @@ void QgsLineSymbol::renderPolyline( const QPolygonF &points, const QgsFeature *f
}
else
{
renderUsingLayer( symbolLayer, symbolContext, &points );
renderUsingLayer( symbolLayer, symbolContext, QgsWkbTypes::LineGeometry, &points );
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/core/symbology/qgsmarkersymbol.cpp
Expand Up @@ -433,7 +433,7 @@ void QgsMarkerSymbol::renderPoint( QPointF point, const QgsFeature *f, QgsRender
{
QPolygonF points;
points.append( point );
renderUsingLayer( symbolLayer, symbolContext, &points );
renderUsingLayer( symbolLayer, symbolContext, QgsWkbTypes::PointGeometry, &points );
}
}
return;
Expand All @@ -457,7 +457,7 @@ void QgsMarkerSymbol::renderPoint( QPointF point, const QgsFeature *f, QgsRender
{
QPolygonF points;
points.append( point );
renderUsingLayer( symbolLayer, symbolContext, &points );
renderUsingLayer( symbolLayer, symbolContext, QgsWkbTypes::PointGeometry, &points );
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/core/symbology/qgssymbol.cpp
Expand Up @@ -778,7 +778,7 @@ QgsSymbolLayerList QgsSymbol::cloneLayers() const
return lst;
}

void QgsSymbol::renderUsingLayer( QgsSymbolLayer *layer, QgsSymbolRenderContext &context, const QPolygonF *points, const QVector<QPolygonF> *rings )
void QgsSymbol::renderUsingLayer( QgsSymbolLayer *layer, QgsSymbolRenderContext &context, QgsWkbTypes::GeometryType geometryType, const QPolygonF *points, const QVector<QPolygonF> *rings )
{
Q_ASSERT( layer->type() == Qgis::SymbolType::Hybrid );

Expand All @@ -791,11 +791,11 @@ void QgsSymbol::renderUsingLayer( QgsSymbolLayer *layer, QgsSymbolRenderContext
if ( effect && effect->enabled() )
{
QgsEffectPainter p( context.renderContext(), effect );
generatorLayer->render( context, points, rings );
generatorLayer->render( context, geometryType, points, rings );
}
else
{
generatorLayer->render( context, points, rings );
generatorLayer->render( context, geometryType, points, rings );
}
}

Expand Down
6 changes: 3 additions & 3 deletions src/core/symbology/qgssymbol.h
Expand Up @@ -692,10 +692,10 @@ class CORE_EXPORT QgsSymbol
* This is required for layers that generate their own geometry from other
* information in the rendering context.
*
* Since QGIS 3.22, the optional \a points and \a rings arguments can specify the original
* points and rings in which are being rendered by the parent symbol.
* Since QGIS 3.22, the optional \a geometryType, \a points and \a rings arguments can specify the original
* geometry type, points and rings in which are being rendered by the parent symbol.
*/
void renderUsingLayer( QgsSymbolLayer *layer, QgsSymbolRenderContext &context, const QPolygonF *points = nullptr, const QVector<QPolygonF> *rings = nullptr );
void renderUsingLayer( QgsSymbolLayer *layer, QgsSymbolRenderContext &context, QgsWkbTypes::GeometryType geometryType = QgsWkbTypes::GeometryType::UnknownGeometry, const QPolygonF *points = nullptr, const QVector<QPolygonF> *rings = nullptr );

/**
* Render editing vertex marker at specified point
Expand Down
32 changes: 31 additions & 1 deletion tests/src/python/test_qgsgeometrygeneratorsymbollayer.py
Expand Up @@ -42,7 +42,9 @@
Qgis,
QgsUnitTypes,
QgsRenderContext,
QgsRenderChecker
QgsRenderChecker,
QgsCoordinateReferenceSystem,
QgsCoordinateTransform
)

from qgis.testing import start_app, unittest
Expand Down Expand Up @@ -293,6 +295,34 @@ def test_no_feature(self):

self.assertTrue(self.imageCheck('geometrygenerator_nofeature', 'geometrygenerator_nofeature', image))

def test_no_feature_coordinate_transform(self):
"""
Test rendering as a pure symbol, no feature associated, with coordinate transform
"""
buffer_layer = QgsGeometryGeneratorSymbolLayer.create({'geometryModifier': 'buffer($geometry, 5)'})
buffer_layer.setSymbolType(QgsSymbol.Fill)
buffer_layer.setUnits(QgsUnitTypes.RenderMillimeters)
self.assertIsNotNone(buffer_layer.subSymbol())

symbol = QgsLineSymbol()
symbol.changeSymbolLayer(0, buffer_layer)

image = QImage(400, 400, QImage.Format_RGB32)
image.fill(QColor(255, 255, 255))
painter = QPainter(image)

context = QgsRenderContext.fromQPainter(painter)
context.setCoordinateTransform(QgsCoordinateTransform(QgsCoordinateReferenceSystem('EPSG:4326'), QgsCoordinateReferenceSystem('EPSG:3857'), QgsProject.instance().transformContext()))

symbol.startRender(context)

symbol.renderPolyline(QPolygonF([QPointF(50, 200), QPointF(100, 170), QPointF(350, 270)]), None, context)

symbol.stopRender(context)
painter.end()

self.assertTrue(self.imageCheck('geometrygenerator_nofeature', 'geometrygenerator_nofeature', image))

def imageCheck(self, name, reference_image, image):
self.report += "<h2>Render {}</h2>\n".format(name)
temp_dir = QDir.tempPath() + '/'
Expand Down

0 comments on commit 0593a7b

Please sign in to comment.