1 /***************************************************************************
2  qgsgeometrygeneratorsymbollayer.cpp
3  ---------------------
4  begin                : November 2015
5  copyright            : (C) 2015 by Matthias Kuhn
6  email                : matthias at opengis dot ch
7  ***************************************************************************
8  *                                                                         *
9  *   This program is free software; you can redistribute it and/or modify  *
10  *   it under the terms of the GNU General Public License as published by  *
11  *   the Free Software Foundation; either version 2 of the License, or     *
12  *   (at your option) any later version.                                   *
13  *                                                                         *
14  ***************************************************************************/
15 
16 #include "qgsgeometrygeneratorsymbollayer.h"
17 #include "qgsgeometry.h"
18 #include "qgsmarkersymbol.h"
19 #include "qgslinesymbol.h"
20 #include "qgsfillsymbol.h"
21 #include "qgspolygon.h"
22 #include "qgslegendpatchshape.h"
23 #include "qgsstyle.h"
24 
25 #include "qgsexpressioncontextutils.h"
26 
27 QgsGeometryGeneratorSymbolLayer::~QgsGeometryGeneratorSymbolLayer() = default;
28 
create(const QVariantMap & properties)29 QgsSymbolLayer *QgsGeometryGeneratorSymbolLayer::create( const QVariantMap &properties )
30 {
31   QString expression = properties.value( QStringLiteral( "geometryModifier" ) ).toString();
32   if ( expression.isEmpty() )
33   {
34     expression = QStringLiteral( "$geometry" );
35   }
36   QgsGeometryGeneratorSymbolLayer *symbolLayer = new QgsGeometryGeneratorSymbolLayer( expression );
37 
38   if ( properties.value( QStringLiteral( "SymbolType" ) ) == QLatin1String( "Marker" ) )
39   {
40     symbolLayer->setSubSymbol( QgsMarkerSymbol::createSimple( properties ) );
41   }
42   else if ( properties.value( QStringLiteral( "SymbolType" ) ) == QLatin1String( "Line" ) )
43   {
44     symbolLayer->setSubSymbol( QgsLineSymbol::createSimple( properties ) );
45   }
46   else
47   {
48     symbolLayer->setSubSymbol( QgsFillSymbol::createSimple( properties ) );
49   }
50   symbolLayer->setUnits( QgsUnitTypes::decodeRenderUnit( properties.value( QStringLiteral( "units" ), QStringLiteral( "mapunits" ) ).toString() ) );
51 
52   symbolLayer->restoreOldDataDefinedProperties( properties );
53 
54   return symbolLayer;
55 }
56 
QgsGeometryGeneratorSymbolLayer(const QString & expression)57 QgsGeometryGeneratorSymbolLayer::QgsGeometryGeneratorSymbolLayer( const QString &expression )
58   : QgsSymbolLayer( Qgis::SymbolType::Hybrid )
59   , mExpression( new QgsExpression( expression ) )
60   , mSymbolType( Qgis::SymbolType::Marker )
61 {
62 
63 }
64 
layerType() const65 QString QgsGeometryGeneratorSymbolLayer::layerType() const
66 {
67   return QStringLiteral( "GeometryGenerator" );
68 }
69 
setSymbolType(Qgis::SymbolType symbolType)70 void QgsGeometryGeneratorSymbolLayer::setSymbolType( Qgis::SymbolType symbolType )
71 {
72   if ( symbolType == Qgis::SymbolType::Fill )
73   {
74     if ( !mFillSymbol )
75       mFillSymbol.reset( QgsFillSymbol::createSimple( QVariantMap() ) );
76     mSymbol = mFillSymbol.get();
77   }
78   else if ( symbolType == Qgis::SymbolType::Line )
79   {
80     if ( !mLineSymbol )
81       mLineSymbol.reset( QgsLineSymbol::createSimple( QVariantMap() ) );
82     mSymbol = mLineSymbol.get();
83   }
84   else if ( symbolType == Qgis::SymbolType::Marker )
85   {
86     if ( !mMarkerSymbol )
87       mMarkerSymbol.reset( QgsMarkerSymbol::createSimple( QVariantMap() ) );
88     mSymbol = mMarkerSymbol.get();
89   }
90   else
91     Q_ASSERT( false );
92 
93   mSymbolType = symbolType;
94 }
95 
startRender(QgsSymbolRenderContext & context)96 void QgsGeometryGeneratorSymbolLayer::startRender( QgsSymbolRenderContext &context )
97 {
98   mExpression->prepare( &context.renderContext().expressionContext() );
99 
100   subSymbol()->startRender( context.renderContext() );
101 }
102 
stopRender(QgsSymbolRenderContext & context)103 void QgsGeometryGeneratorSymbolLayer::stopRender( QgsSymbolRenderContext &context )
104 {
105   if ( mSymbol )
106     mSymbol->stopRender( context.renderContext() );
107 }
108 
startFeatureRender(const QgsFeature &,QgsRenderContext & context)109 void QgsGeometryGeneratorSymbolLayer::startFeatureRender( const QgsFeature &, QgsRenderContext &context )
110 {
111   if ( context.flags() & Qgis::RenderContextFlag::RenderingSubSymbol )
112     return;
113 
114   mRenderingFeature = true;
115   mHasRenderedFeature = false;
116 }
117 
stopFeatureRender(const QgsFeature &,QgsRenderContext &)118 void QgsGeometryGeneratorSymbolLayer::stopFeatureRender( const QgsFeature &, QgsRenderContext & )
119 {
120   mRenderingFeature = false;
121 }
122 
usesMapUnits() const123 bool QgsGeometryGeneratorSymbolLayer::usesMapUnits() const
124 {
125   if ( mFillSymbol )
126     return mFillSymbol->usesMapUnits();
127   else if ( mLineSymbol )
128     return mLineSymbol->usesMapUnits();
129   else if ( mMarkerSymbol )
130     return mMarkerSymbol->usesMapUnits();
131   return false;
132 }
133 
color() const134 QColor QgsGeometryGeneratorSymbolLayer::color() const
135 {
136   if ( mFillSymbol )
137     return mFillSymbol->color();
138   else if ( mLineSymbol )
139     return mLineSymbol->color();
140   else if ( mMarkerSymbol )
141     return mMarkerSymbol->color();
142   return QColor();
143 }
144 
outputUnit() const145 QgsUnitTypes::RenderUnit QgsGeometryGeneratorSymbolLayer::outputUnit() const
146 {
147   if ( mFillSymbol )
148     return mFillSymbol->outputUnit();
149   else if ( mLineSymbol )
150     return mLineSymbol->outputUnit();
151   else if ( mMarkerSymbol )
152     return mMarkerSymbol->outputUnit();
153   return QgsUnitTypes::RenderUnknownUnit;
154 }
155 
mapUnitScale() const156 QgsMapUnitScale QgsGeometryGeneratorSymbolLayer::mapUnitScale() const
157 {
158   if ( mFillSymbol )
159     return mFillSymbol->mapUnitScale();
160   else if ( mLineSymbol )
161     return mLineSymbol->mapUnitScale();
162   else if ( mMarkerSymbol )
163     return mMarkerSymbol->mapUnitScale();
164   return QgsMapUnitScale();
165 }
166 
clone() const167 QgsSymbolLayer *QgsGeometryGeneratorSymbolLayer::clone() const
168 {
169   QgsGeometryGeneratorSymbolLayer *clone = new QgsGeometryGeneratorSymbolLayer( mExpression->expression() );
170 
171   if ( mFillSymbol )
172     clone->mFillSymbol.reset( mFillSymbol->clone() );
173   if ( mLineSymbol )
174     clone->mLineSymbol.reset( mLineSymbol->clone() );
175   if ( mMarkerSymbol )
176     clone->mMarkerSymbol.reset( mMarkerSymbol->clone() );
177 
178   clone->setSymbolType( mSymbolType );
179   clone->setUnits( mUnits );
180 
181   copyDataDefinedProperties( clone );
182   copyPaintEffect( clone );
183 
184   return clone;
185 }
186 
properties() const187 QVariantMap QgsGeometryGeneratorSymbolLayer::properties() const
188 {
189   QVariantMap props;
190   props.insert( QStringLiteral( "geometryModifier" ), mExpression->expression() );
191   switch ( mSymbolType )
192   {
193     case Qgis::SymbolType::Marker:
194       props.insert( QStringLiteral( "SymbolType" ), QStringLiteral( "Marker" ) );
195       break;
196     case Qgis::SymbolType::Line:
197       props.insert( QStringLiteral( "SymbolType" ), QStringLiteral( "Line" ) );
198       break;
199     default:
200       props.insert( QStringLiteral( "SymbolType" ), QStringLiteral( "Fill" ) );
201       break;
202   }
203   props.insert( QStringLiteral( "units" ), QgsUnitTypes::encodeUnit( mUnits ) );
204 
205   return props;
206 }
207 
drawPreviewIcon(QgsSymbolRenderContext & context,QSize size)208 void QgsGeometryGeneratorSymbolLayer::drawPreviewIcon( QgsSymbolRenderContext &context, QSize size )
209 {
210   if ( mSymbol )
211   {
212     // evaluate expression
213     QgsGeometry patchShapeGeometry;
214 
215     if ( context.patchShape() && !context.patchShape()->isNull() )
216     {
217       patchShapeGeometry = context.patchShape()->scaledGeometry( size );
218     }
219     if ( patchShapeGeometry.isEmpty() )
220     {
221       Qgis::SymbolType originalSymbolType;
222       switch ( context.originalGeometryType() )
223       {
224         case QgsWkbTypes::PointGeometry:
225           originalSymbolType = Qgis::SymbolType::Marker;
226           break;
227         case QgsWkbTypes::LineGeometry:
228           originalSymbolType = Qgis::SymbolType::Line;
229           break;
230         case QgsWkbTypes::PolygonGeometry:
231           originalSymbolType = Qgis::SymbolType::Fill;
232           break;
233         case QgsWkbTypes::UnknownGeometry:
234         case QgsWkbTypes::NullGeometry:
235           originalSymbolType = mSymbol->type();
236           break;
237       }
238       patchShapeGeometry = QgsStyle::defaultStyle()->defaultPatch( originalSymbolType, size ).scaledGeometry( size );
239     }
240 
241     // evaluate geometry expression
242     QgsFeature feature;
243     if ( context.feature() )
244       feature = *context.feature();
245     else
246       feature.setGeometry( patchShapeGeometry );
247     const QgsGeometry iconGeometry = evaluateGeometryInPainterUnits( patchShapeGeometry, feature, context.renderContext(), context.renderContext().expressionContext() );
248 
249     QgsLegendPatchShape evaluatedPatchShape( mSymbol->type(), coerceToExpectedType( iconGeometry ) );
250     // we don't want to rescale the patch shape to fit the legend symbol size -- we've already considered that here,
251     // and we don't want to undo the effects of a geometry generator which modifies the symbol bounds
252     evaluatedPatchShape.setScaleToOutputSize( false );
253     mSymbol->drawPreviewIcon( context.renderContext().painter(), size, &context.renderContext(), false, &context.renderContext().expressionContext(), &evaluatedPatchShape );
254   }
255 }
256 
setGeometryExpression(const QString & exp)257 void QgsGeometryGeneratorSymbolLayer::setGeometryExpression( const QString &exp )
258 {
259   mExpression.reset( new QgsExpression( exp ) );
260 }
261 
setSubSymbol(QgsSymbol * symbol)262 bool QgsGeometryGeneratorSymbolLayer::setSubSymbol( QgsSymbol *symbol )
263 {
264   switch ( symbol->type() )
265   {
266     case Qgis::SymbolType::Marker:
267       mMarkerSymbol.reset( static_cast<QgsMarkerSymbol *>( symbol ) );
268       break;
269 
270     case Qgis::SymbolType::Line:
271       mLineSymbol.reset( static_cast<QgsLineSymbol *>( symbol ) );
272       break;
273 
274     case Qgis::SymbolType::Fill:
275       mFillSymbol.reset( static_cast<QgsFillSymbol *>( symbol ) );
276       break;
277 
278     default:
279       break;
280   }
281 
282   setSymbolType( symbol->type() );
283 
284   return true;
285 }
286 
usedAttributes(const QgsRenderContext & context) const287 QSet<QString> QgsGeometryGeneratorSymbolLayer::usedAttributes( const QgsRenderContext &context ) const
288 {
289   return QgsSymbolLayer::usedAttributes( context )
290          + mSymbol->usedAttributes( context )
291          + mExpression->referencedColumns();
292 }
293 
hasDataDefinedProperties() const294 bool QgsGeometryGeneratorSymbolLayer::hasDataDefinedProperties() const
295 {
296   // we treat geometry generator layers like they have data defined properties,
297   // since the WHOLE layer is based on expressions and requires the full expression
298   // context
299   return true;
300 }
301 
isCompatibleWithSymbol(QgsSymbol * symbol) const302 bool QgsGeometryGeneratorSymbolLayer::isCompatibleWithSymbol( QgsSymbol *symbol ) const
303 {
304   Q_UNUSED( symbol )
305   return true;
306 }
307 
evaluateGeometryInPainterUnits(const QgsGeometry & input,const QgsFeature & feature,const QgsRenderContext & renderContext,QgsExpressionContext & expressionContext) const308 QgsGeometry QgsGeometryGeneratorSymbolLayer::evaluateGeometryInPainterUnits( const QgsGeometry &input, const QgsFeature &feature, const QgsRenderContext &renderContext, QgsExpressionContext &expressionContext ) const
309 {
310   QgsGeometry drawGeometry( input );
311   // step 1 - scale the draw geometry from PAINTER units to target units (e.g. millimeters)
312   const double scale = 1 / renderContext.convertToPainterUnits( 1, mUnits );
313   const QTransform painterToTargetUnits = QTransform::fromScale( scale, scale );
314   drawGeometry.transform( painterToTargetUnits );
315 
316   // step 2 - set the feature to use the new scaled geometry, and inject it into the expression context
317   QgsFeature f( feature );
318   f.setGeometry( drawGeometry );
319   QgsExpressionContextScope *generatorScope = new QgsExpressionContextScope();
320   QgsExpressionContextScopePopper popper( expressionContext, generatorScope );
321   generatorScope->setFeature( f );
322 
323   // step 3 - evaluate the new generated geometry.
324   QgsGeometry geom = mExpression->evaluate( &expressionContext ).value<QgsGeometry>();
325 
326   // step 4 - transform geometry back from target units to painter units
327   geom.transform( painterToTargetUnits.inverted( ) );
328 
329   return geom;
330 }
331 
coerceToExpectedType(const QgsGeometry & geometry) const332 QgsGeometry QgsGeometryGeneratorSymbolLayer::coerceToExpectedType( const QgsGeometry &geometry ) const
333 {
334   switch ( mSymbolType )
335   {
336     case Qgis::SymbolType::Marker:
337       if ( geometry.type() != QgsWkbTypes::PointGeometry )
338       {
339         QVector< QgsGeometry > geoms = geometry.coerceToType( QgsWkbTypes::MultiPoint );
340         if ( !geoms.empty() )
341           return geoms.at( 0 );
342       }
343       break;
344     case Qgis::SymbolType::Line:
345       if ( geometry.type() != QgsWkbTypes::LineGeometry )
346       {
347         QVector< QgsGeometry > geoms = geometry.coerceToType( QgsWkbTypes::MultiLineString );
348         if ( !geoms.empty() )
349           return geoms.at( 0 );
350       }
351       break;
352     case Qgis::SymbolType::Fill:
353       if ( geometry.type() != QgsWkbTypes::PolygonGeometry )
354       {
355         QVector< QgsGeometry > geoms = geometry.coerceToType( QgsWkbTypes::MultiPolygon );
356         if ( !geoms.empty() )
357           return geoms.at( 0 );
358       }
359       break;
360     case Qgis::SymbolType::Hybrid:
361       break;
362   }
363   return geometry;
364 }
365 
render(QgsSymbolRenderContext & context,QgsWkbTypes::GeometryType geometryType,const QPolygonF * points,const QVector<QPolygonF> * rings)366 void QgsGeometryGeneratorSymbolLayer::render( QgsSymbolRenderContext &context, QgsWkbTypes::GeometryType geometryType, const QPolygonF *points, const QVector<QPolygonF> *rings )
367 {
368   if ( mRenderingFeature && mHasRenderedFeature )
369     return;
370 
371   QgsExpressionContext &expressionContext = context.renderContext().expressionContext();
372   QgsFeature f = expressionContext.feature();
373 
374   if ( ( !context.feature() || context.renderContext().flags() & Qgis::RenderContextFlag::RenderingSubSymbol ) && points )
375   {
376     // 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!
377     // in this case we need to build up a feature which represents the points being rendered.
378     // note that we also do this same logic when we are rendering a subsymbol. In that case the $geometry part of the
379     // expression should refer to the shape of the subsymbol being rendered, NOT the feature's original geometry
380     QgsGeometry drawGeometry;
381 
382     // step 1 - convert points and rings to geometry
383     switch ( geometryType )
384     {
385       case QgsWkbTypes::PointGeometry:
386       {
387         Q_ASSERT( points->size() == 1 );
388         drawGeometry = QgsGeometry::fromPointXY( points->at( 0 ) );
389         break;
390       }
391       case QgsWkbTypes::LineGeometry:
392       {
393         Q_ASSERT( !rings );
394         std::unique_ptr < QgsLineString > ring( QgsLineString::fromQPolygonF( *points ) );
395         drawGeometry = QgsGeometry( std::move( ring ) );
396         break;
397       }
398       case QgsWkbTypes::PolygonGeometry:
399       {
400         std::unique_ptr < QgsLineString > exterior( QgsLineString::fromQPolygonF( *points ) );
401         std::unique_ptr< QgsPolygon > polygon = std::make_unique< QgsPolygon >();
402         polygon->setExteriorRing( exterior.release() );
403         if ( rings )
404         {
405           for ( const QPolygonF &ring : *rings )
406           {
407             polygon->addInteriorRing( QgsLineString::fromQPolygonF( ring ) );
408           }
409         }
410         drawGeometry = QgsGeometry( std::move( polygon ) );
411         break;
412       }
413 
414       case QgsWkbTypes::UnknownGeometry:
415       case QgsWkbTypes::NullGeometry:
416         return; // unreachable
417     }
418 
419     // step 2 - evaluate the result
420     QgsGeometry result = evaluateGeometryInPainterUnits( drawGeometry, f, context.renderContext(), expressionContext );
421 
422     // We transform back to map units here (from painter units)
423     // as we'll ultimately be calling renderFeature, which excepts the feature has a geometry in map units.
424     // Here we also scale the transform by the target unit to painter units factor to reverse that conversion
425     QTransform mapToPixel = context.renderContext().mapToPixel().transform();
426     result.transform( mapToPixel.inverted() );
427     // also need to apply the coordinate transform from the render context
428     try
429     {
430       result.transform( context.renderContext().coordinateTransform(), Qgis::TransformDirection::Reverse );
431     }
432     catch ( QgsCsException & )
433     {
434       QgsDebugMsg( QStringLiteral( "Could no transform generated geometry to layer CRS" ) );
435     }
436 
437     f.setGeometry( coerceToExpectedType( result ) );
438   }
439   else if ( context.feature() )
440   {
441     switch ( mUnits )
442     {
443       case QgsUnitTypes::RenderMapUnits:
444       case QgsUnitTypes::RenderUnknownUnit: // unsupported, not exposed as an option
445       case QgsUnitTypes::RenderMetersInMapUnits: // unsupported, not exposed as an option
446       case QgsUnitTypes::RenderPercentage: // unsupported, not exposed as an option
447       {
448         QgsGeometry geom = mExpression->evaluate( &expressionContext ).value<QgsGeometry>();
449         f.setGeometry( coerceToExpectedType( geom ) );
450         break;
451       }
452 
453       case QgsUnitTypes::RenderMillimeters:
454       case QgsUnitTypes::RenderPixels:
455       case QgsUnitTypes::RenderPoints:
456       case QgsUnitTypes::RenderInches:
457       {
458         // convert feature geometry to painter units
459         QgsGeometry transformed = f.geometry();
460         transformed.transform( context.renderContext().coordinateTransform() );
461         const QTransform mapToPixel = context.renderContext().mapToPixel().transform();
462         transformed.transform( mapToPixel );
463 
464         QgsGeometry result = evaluateGeometryInPainterUnits( transformed, f, context.renderContext(), expressionContext );
465 
466         // We transform back to map units here (from painter units)
467         // as we'll ultimately be calling renderFeature, which excepts the feature has a geometry in map units.
468         // Here we also scale the transform by the target unit to painter units factor to reverse that conversion
469         result.transform( mapToPixel.inverted() );
470         // also need to apply the coordinate transform from the render context
471         try
472         {
473           result.transform( context.renderContext().coordinateTransform(), Qgis::TransformDirection::Reverse );
474         }
475         catch ( QgsCsException & )
476         {
477           QgsDebugMsg( QStringLiteral( "Could no transform generated geometry to layer CRS" ) );
478         }
479         f.setGeometry( coerceToExpectedType( result ) );
480         break;
481       }
482     }
483   }
484 
485   QgsExpressionContextScope *subSymbolExpressionContextScope = mSymbol->symbolRenderContext()->expressionContextScope();
486 
487   subSymbolExpressionContextScope->setFeature( f );
488 
489   const bool prevIsSubsymbol = context.renderContext().flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
490   context.renderContext().setFlag( Qgis::RenderContextFlag::RenderingSubSymbol );
491 
492   mSymbol->renderFeature( f, context.renderContext(), -1, context.selected() );
493 
494   context.renderContext().setFlag( Qgis::RenderContextFlag::RenderingSubSymbol, prevIsSubsymbol );
495 
496   if ( mRenderingFeature )
497     mHasRenderedFeature = true;
498 }
499 
setColor(const QColor & color)500 void QgsGeometryGeneratorSymbolLayer::setColor( const QColor &color )
501 {
502   mSymbol->setColor( color );
503 }
504