Skip to content

Commit

Permalink
Ensure geometry generator symbol layer works correctly outside
Browse files Browse the repository at this point in the history
of vector layer renderers

Fixes #39159
  • Loading branch information
nyalldawson committed Sep 22, 2021
1 parent eed24cb commit 72d238c
Show file tree
Hide file tree
Showing 9 changed files with 98 additions and 23 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

virtual void render( QgsSymbolRenderContext &context );
void render( QgsSymbolRenderContext &context, 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 @@ -130,6 +130,9 @@ context which is available to the evaluated expression.

:param context: The rendering context which will be used to render and to
construct a geometry.

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.
%End

virtual void setColor( const QColor &color );
Expand Down
5 changes: 4 additions & 1 deletion 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 );
void renderUsingLayer( QgsSymbolLayer *layer, QgsSymbolRenderContext &context, 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 @@ -686,6 +686,9 @@ called and will call the layer's rendering method anyway but the
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.
%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 );
renderUsingLayer( symbolLayer, symbolContext, &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 );
renderUsingLayer( symbolLayer, symbolContext, &points, rings );
}
}

Expand Down
73 changes: 63 additions & 10 deletions src/core/symbology/qgsgeometrygeneratorsymbollayer.cpp
Expand Up @@ -18,6 +18,8 @@
#include "qgsmarkersymbol.h"
#include "qgslinesymbol.h"
#include "qgsfillsymbol.h"
#include "qgspolygon.h"

#include "qgsexpressioncontextutils.h"

QgsGeometryGeneratorSymbolLayer::~QgsGeometryGeneratorSymbolLayer() = default;
Expand Down Expand Up @@ -254,17 +256,68 @@ bool QgsGeometryGeneratorSymbolLayer::isCompatibleWithSymbol( QgsSymbol *symbol
Q_UNUSED( symbol )
return true;
}
void QgsGeometryGeneratorSymbolLayer::render( QgsSymbolRenderContext &context )
void QgsGeometryGeneratorSymbolLayer::render( QgsSymbolRenderContext &context, const QPolygonF *points, const QVector<QPolygonF> *rings )
{
if ( mRenderingFeature && mHasRenderedFeature )
return;

if ( context.feature() )
QgsExpressionContext &expressionContext = context.renderContext().expressionContext();
QgsFeature f = expressionContext.feature();

if ( !context.feature() && points )
{
QgsExpressionContext &expressionContext = context.renderContext().expressionContext();
// oh dear, we don't have a feature to work from... but that's ok, we are probably being rendered as a plain old symbol!
// in this case we need to build up a feature which represents the points being rendered
QgsGeometry drawGeometry;

QgsFeature f = expressionContext.feature();
// step 1 - convert points and rings to geometry
if ( points->size() == 1 )
{
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 )
{
polygon->addInteriorRing( QgsLineString::fromQPolygonF( ring ) );
}
drawGeometry = QgsGeometry( std::move( polygon ) );
}
else
{
// line
std::unique_ptr < QgsLineString > ring( QgsLineString::fromQPolygonF( *points ) );
drawGeometry = QgsGeometry( std::move( ring ) );
}

// step 2 - scale the draw geometry from PAINTER units to target units (e.g. millimeters)
const double scale = 1 / context.renderContext().convertToPainterUnits( 1, mUnits );
const QTransform painterToTargetUnits = QTransform::fromScale( scale, scale );
drawGeometry.transform( painterToTargetUnits );

// step 3 - set the feature to use the new scaled geometry, and inject it into the expression context
f.setGeometry( drawGeometry );
QgsExpressionContextScope *generatorScope = new QgsExpressionContextScope();
QgsExpressionContextScopePopper popper( expressionContext, generatorScope );
generatorScope->setFeature( f );

// step 4 - evaluate the new generated geometry.
QgsGeometry geom = mExpression->evaluate( &expressionContext ).value<QgsGeometry>();

// 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
QTransform mapToPixel = context.renderContext().mapToPixel().transform();
mapToPixel.scale( scale, scale );
geom.transform( mapToPixel.inverted() );
f.setGeometry( geom );
}
else if ( context.feature() )
{
switch ( mUnits )
{
case QgsUnitTypes::RenderMapUnits:
Expand Down Expand Up @@ -308,16 +361,16 @@ void QgsGeometryGeneratorSymbolLayer::render( QgsSymbolRenderContext &context )
break;
}
}
}

QgsExpressionContextScope *subSymbolExpressionContextScope = mSymbol->symbolRenderContext()->expressionContextScope();
QgsExpressionContextScope *subSymbolExpressionContextScope = mSymbol->symbolRenderContext()->expressionContextScope();

subSymbolExpressionContextScope->setFeature( f );
subSymbolExpressionContextScope->setFeature( f );

mSymbol->renderFeature( f, context.renderContext(), -1, context.selected() );
mSymbol->renderFeature( f, context.renderContext(), -1, context.selected() );

if ( mRenderingFeature )
mHasRenderedFeature = true;
}
if ( mRenderingFeature )
mHasRenderedFeature = true;
}

void QgsGeometryGeneratorSymbolLayer::setColor( const QColor &color )
Expand Down
5 changes: 4 additions & 1 deletion src/core/symbology/qgsgeometrygeneratorsymbollayer.h
Expand Up @@ -124,8 +124,11 @@ class CORE_EXPORT QgsGeometryGeneratorSymbolLayer : public QgsSymbolLayer
*
* \param context The rendering context which will be used to render and to
* construct a geometry.
*
* 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.
*/
virtual void render( QgsSymbolRenderContext &context );
void render( QgsSymbolRenderContext &context, const QPolygonF *points = nullptr, const QVector<QPolygonF> *rings = nullptr );

void setColor( const QColor &color ) override;

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 );
renderUsingLayer( symbolLayer, symbolContext, &points );
}
return;
}
Expand All @@ -244,7 +244,7 @@ void QgsLineSymbol::renderPolyline( const QPolygonF &points, const QgsFeature *f
}
else
{
renderUsingLayer( symbolLayer, symbolContext );
renderUsingLayer( symbolLayer, symbolContext, &points );
}
}

Expand Down
12 changes: 10 additions & 2 deletions src/core/symbology/qgsmarkersymbol.cpp
Expand Up @@ -430,7 +430,11 @@ void QgsMarkerSymbol::renderPoint( QPointF point, const QgsFeature *f, QgsRender
renderPointUsingLayer( markerLayer, point, symbolContext );
}
else
renderUsingLayer( symbolLayer, symbolContext );
{
QPolygonF points;
points.append( point );
renderUsingLayer( symbolLayer, symbolContext, &points );
}
}
return;
}
Expand All @@ -450,7 +454,11 @@ void QgsMarkerSymbol::renderPoint( QPointF point, const QgsFeature *f, QgsRender
renderPointUsingLayer( markerLayer, point, symbolContext );
}
else
renderUsingLayer( symbolLayer, symbolContext );
{
QPolygonF points;
points.append( point );
renderUsingLayer( symbolLayer, symbolContext, &points );
}
}
}

Expand Down
8 changes: 5 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 )
void QgsSymbol::renderUsingLayer( QgsSymbolLayer *layer, QgsSymbolRenderContext &context, 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 );
generatorLayer->render( context, points, rings );
}
else
{
generatorLayer->render( context );
generatorLayer->render( context, points, rings );
}
}

Expand Down Expand Up @@ -950,6 +950,8 @@ void QgsSymbol::renderFeature( const QgsFeature &feature, QgsRenderContext &cont
clippingEnabled = false;
}
}
if ( context.extent().isEmpty() )
clippingEnabled = false;

mSymbolRenderContext->setGeometryPartCount( geom.constGet()->partCount() );
mSymbolRenderContext->setGeometryPartNum( 1 );
Expand Down
5 changes: 4 additions & 1 deletion src/core/symbology/qgssymbol.h
Expand Up @@ -691,8 +691,11 @@ class CORE_EXPORT QgsSymbol
* 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 \a points and \a rings arguments can specify the original
* points and rings in which are being rendered by the parent symbol.
*/
void renderUsingLayer( QgsSymbolLayer *layer, QgsSymbolRenderContext &context );
void renderUsingLayer( QgsSymbolLayer *layer, QgsSymbolRenderContext &context, const QPolygonF *points = nullptr, const QVector<QPolygonF> *rings = nullptr );

/**
* Render editing vertex marker at specified point
Expand Down

0 comments on commit 72d238c

Please sign in to comment.