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