diff --git a/python/core/auto_generated/symbology/qgsgeometrygeneratorsymbollayer.sip.in b/python/core/auto_generated/symbology/qgsgeometrygeneratorsymbollayer.sip.in index 9de6e8960e58..5ff0a1ed10c8 100644 --- a/python/core/auto_generated/symbology/qgsgeometrygeneratorsymbollayer.sip.in +++ b/python/core/auto_generated/symbology/qgsgeometrygeneratorsymbollayer.sip.in @@ -114,7 +114,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 *rings = 0 ); %Docstring Will render this symbol layer using the context. In comparison to other symbols there is no geometry passed in, since @@ -124,6 +124,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 ); diff --git a/python/core/auto_generated/symbology/qgssymbol.sip.in b/python/core/auto_generated/symbology/qgssymbol.sip.in index 30cf381adb05..9e87c93e7c25 100644 --- a/python/core/auto_generated/symbology/qgssymbol.sip.in +++ b/python/core/auto_generated/symbology/qgssymbol.sip.in @@ -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 *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 @@ -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 ); diff --git a/src/core/symbology/qgsfillsymbol.cpp b/src/core/symbology/qgsfillsymbol.cpp index 4db09f1f7f84..c44596c616c5 100644 --- a/src/core/symbology/qgsfillsymbol.cpp +++ b/src/core/symbology/qgsfillsymbol.cpp @@ -53,7 +53,7 @@ void QgsFillSymbol::renderPolygon( const QPolygonF &points, const QVectortype() == Qgis::SymbolType::Fill || symbolLayer->type() == Qgis::SymbolType::Line ) renderPolygonUsingLayer( symbolLayer, points, rings, symbolContext ); else - renderUsingLayer( symbolLayer, symbolContext ); + renderUsingLayer( symbolLayer, symbolContext, &points, rings ); } return; } @@ -70,7 +70,7 @@ void QgsFillSymbol::renderPolygon( const QPolygonF &points, const QVectortype() == Qgis::SymbolType::Fill || symbolLayer->type() == Qgis::SymbolType::Line ) renderPolygonUsingLayer( symbolLayer, points, rings, symbolContext ); else - renderUsingLayer( symbolLayer, symbolContext ); + renderUsingLayer( symbolLayer, symbolContext, &points, rings ); } } diff --git a/src/core/symbology/qgsgeometrygeneratorsymbollayer.cpp b/src/core/symbology/qgsgeometrygeneratorsymbollayer.cpp index 87248de6771b..e34954a6a05b 100644 --- a/src/core/symbology/qgsgeometrygeneratorsymbollayer.cpp +++ b/src/core/symbology/qgsgeometrygeneratorsymbollayer.cpp @@ -18,6 +18,8 @@ #include "qgsmarkersymbol.h" #include "qgslinesymbol.h" #include "qgsfillsymbol.h" +#include "qgspolygon.h" + #include "qgsexpressioncontextutils.h" QgsGeometryGeneratorSymbolLayer::~QgsGeometryGeneratorSymbolLayer() = default; @@ -221,17 +223,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 *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(); + + // 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: @@ -275,16 +328,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 ) diff --git a/src/core/symbology/qgsgeometrygeneratorsymbollayer.h b/src/core/symbology/qgsgeometrygeneratorsymbollayer.h index 5cc3174999ac..614226d57b16 100644 --- a/src/core/symbology/qgsgeometrygeneratorsymbollayer.h +++ b/src/core/symbology/qgsgeometrygeneratorsymbollayer.h @@ -121,8 +121,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 *rings = nullptr ); void setColor( const QColor &color ) override; diff --git a/src/core/symbology/qgslinesymbol.cpp b/src/core/symbology/qgslinesymbol.cpp index 5b96dce373d0..4e5975b732b5 100644 --- a/src/core/symbology/qgslinesymbol.cpp +++ b/src/core/symbology/qgslinesymbol.cpp @@ -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; } @@ -244,7 +244,7 @@ void QgsLineSymbol::renderPolyline( const QPolygonF &points, const QgsFeature *f } else { - renderUsingLayer( symbolLayer, symbolContext ); + renderUsingLayer( symbolLayer, symbolContext, &points ); } } diff --git a/src/core/symbology/qgsmarkersymbol.cpp b/src/core/symbology/qgsmarkersymbol.cpp index 0f43e9325681..2716b052c134 100644 --- a/src/core/symbology/qgsmarkersymbol.cpp +++ b/src/core/symbology/qgsmarkersymbol.cpp @@ -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; } @@ -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 ); + } } } diff --git a/src/core/symbology/qgssymbol.cpp b/src/core/symbology/qgssymbol.cpp index b7b899918fdf..e5be90f2a9b4 100644 --- a/src/core/symbology/qgssymbol.cpp +++ b/src/core/symbology/qgssymbol.cpp @@ -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 *rings ) { Q_ASSERT( layer->type() == Qgis::SymbolType::Hybrid ); @@ -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 ); } } @@ -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 ); diff --git a/src/core/symbology/qgssymbol.h b/src/core/symbology/qgssymbol.h index 92f2528e8796..198f3718094d 100644 --- a/src/core/symbology/qgssymbol.h +++ b/src/core/symbology/qgssymbol.h @@ -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 *rings = nullptr ); /** * Render editing vertex marker at specified point