1 /***************************************************************************
2 qgssymbol.cpp
3 ---------------------
4 begin : November 2009
5 copyright : (C) 2009 by Martin Dobias
6 email : wonder dot sk at gmail dot com
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 <QColor>
17 #include <QImage>
18 #include <QPainter>
19 #include <QSize>
20 #include <QSvgGenerator>
21
22 #include <cmath>
23 #include <map>
24 #include <random>
25
26 #include "qgssymbol.h"
27 #include "qgssymbollayer.h"
28
29 #include "qgslinesymbollayer.h"
30 #include "qgsmarkersymbollayer.h"
31 #include "qgsfillsymbollayer.h"
32 #include "qgsgeometrygeneratorsymbollayer.h"
33 #include "qgsmaptopixelgeometrysimplifier.h"
34 #include "qgslogger.h"
35 #include "qgsrendercontext.h" // for bigSymbolPreview
36 #include "qgsproject.h"
37 #include "qgsstyle.h"
38 #include "qgspainteffect.h"
39 #include "qgseffectstack.h"
40 #include "qgsvectorlayer.h"
41 #include "qgsfeature.h"
42 #include "qgsgeometry.h"
43 #include "qgsmultipoint.h"
44 #include "qgsgeometrycollection.h"
45 #include "qgslinestring.h"
46 #include "qgspolygon.h"
47 #include "qgsclipper.h"
48 #include "qgsproperty.h"
49 #include "qgscolorschemeregistry.h"
50 #include "qgsapplication.h"
51 #include "qgsexpressioncontextutils.h"
52 #include "qgsrenderedfeaturehandlerinterface.h"
53 #include "qgslegendpatchshape.h"
54 #include "qgsgeos.h"
55 #include "qgsmarkersymbol.h"
56 #include "qgslinesymbol.h"
57 #include "qgsfillsymbol.h"
58
59 QgsPropertiesDefinition QgsSymbol::sPropertyDefinitions;
60
61 Q_NOWARN_DEPRECATED_PUSH // because of deprecated mLayer
QgsSymbol(Qgis::SymbolType type,const QgsSymbolLayerList & layers)62 QgsSymbol::QgsSymbol( Qgis::SymbolType type, const QgsSymbolLayerList &layers )
63 : mType( type )
64 , mLayers( layers )
65 {
66
67 // check they're all correct symbol layers
68 for ( int i = 0; i < mLayers.count(); i++ )
69 {
70 if ( !mLayers.at( i ) )
71 {
72 mLayers.removeAt( i-- );
73 }
74 else if ( !mLayers.at( i )->isCompatibleWithSymbol( this ) )
75 {
76 delete mLayers.at( i );
77 mLayers.removeAt( i-- );
78 }
79 }
80 }
81 Q_NOWARN_DEPRECATED_POP
82
_getLineString(QgsRenderContext & context,const QgsCurve & curve,bool clipToExtent)83 QPolygonF QgsSymbol::_getLineString( QgsRenderContext &context, const QgsCurve &curve, bool clipToExtent )
84 {
85 const unsigned int nPoints = curve.numPoints();
86
87 QgsCoordinateTransform ct = context.coordinateTransform();
88 const QgsMapToPixel &mtp = context.mapToPixel();
89 QPolygonF pts;
90
91 //apply clipping for large lines to achieve a better rendering performance
92 if ( clipToExtent && nPoints > 1 && !( context.flags() & Qgis::RenderContextFlag::ApplyClipAfterReprojection ) )
93 {
94 const QgsRectangle e = context.extent();
95 const double cw = e.width() / 10;
96 const double ch = e.height() / 10;
97 const QgsRectangle clipRect( e.xMinimum() - cw, e.yMinimum() - ch, e.xMaximum() + cw, e.yMaximum() + ch );
98 pts = QgsClipper::clippedLine( curve, clipRect );
99 }
100 else
101 {
102 pts = curve.asQPolygonF();
103 }
104
105 //transform the QPolygonF to screen coordinates
106 if ( ct.isValid() )
107 {
108 try
109 {
110 ct.transformPolygon( pts );
111 }
112 catch ( QgsCsException & )
113 {
114 // we don't abort the rendering here, instead we remove any invalid points and just plot those which ARE valid
115 }
116 }
117
118 // remove non-finite points, e.g. infinite or NaN points caused by reprojecting errors
119 pts.erase( std::remove_if( pts.begin(), pts.end(),
120 []( const QPointF point )
121 {
122 return !std::isfinite( point.x() ) || !std::isfinite( point.y() );
123 } ), pts.end() );
124
125 if ( clipToExtent && nPoints > 1 && context.flags() & Qgis::RenderContextFlag::ApplyClipAfterReprojection )
126 {
127 // early clipping was not possible, so we have to apply it here after transformation
128 const QgsRectangle e = context.mapExtent();
129 const double cw = e.width() / 10;
130 const double ch = e.height() / 10;
131 const QgsRectangle clipRect( e.xMinimum() - cw, e.yMinimum() - ch, e.xMaximum() + cw, e.yMaximum() + ch );
132 pts = QgsClipper::clippedLine( pts, clipRect );
133 }
134
135 QPointF *ptr = pts.data();
136 for ( int i = 0; i < pts.size(); ++i, ++ptr )
137 {
138 mtp.transformInPlace( ptr->rx(), ptr->ry() );
139 }
140
141 return pts;
142 }
143
_getPolygonRing(QgsRenderContext & context,const QgsCurve & curve,const bool clipToExtent,const bool isExteriorRing,const bool correctRingOrientation)144 QPolygonF QgsSymbol::_getPolygonRing( QgsRenderContext &context, const QgsCurve &curve, const bool clipToExtent, const bool isExteriorRing, const bool correctRingOrientation )
145 {
146 const QgsCoordinateTransform ct = context.coordinateTransform();
147 const QgsMapToPixel &mtp = context.mapToPixel();
148
149 QPolygonF poly = curve.asQPolygonF();
150
151 if ( curve.numPoints() < 1 )
152 return QPolygonF();
153
154 if ( correctRingOrientation )
155 {
156 // ensure consistent polygon ring orientation
157 if ( isExteriorRing && curve.orientation() != QgsCurve::Clockwise )
158 std::reverse( poly.begin(), poly.end() );
159 else if ( !isExteriorRing && curve.orientation() != QgsCurve::CounterClockwise )
160 std::reverse( poly.begin(), poly.end() );
161 }
162
163 //clip close to view extent, if needed
164 if ( clipToExtent && !( context.flags() & Qgis::RenderContextFlag::ApplyClipAfterReprojection ) && !context.extent().contains( poly.boundingRect() ) )
165 {
166 const QgsRectangle e = context.extent();
167 const double cw = e.width() / 10;
168 const double ch = e.height() / 10;
169 const QgsRectangle clipRect( e.xMinimum() - cw, e.yMinimum() - ch, e.xMaximum() + cw, e.yMaximum() + ch );
170 QgsClipper::trimPolygon( poly, clipRect );
171 }
172
173 //transform the QPolygonF to screen coordinates
174 if ( ct.isValid() )
175 {
176 try
177 {
178 ct.transformPolygon( poly );
179 }
180 catch ( QgsCsException & )
181 {
182 // we don't abort the rendering here, instead we remove any invalid points and just plot those which ARE valid
183 }
184 }
185
186 // remove non-finite points, e.g. infinite or NaN points caused by reprojecting errors
187 poly.erase( std::remove_if( poly.begin(), poly.end(),
188 []( const QPointF point )
189 {
190 return !std::isfinite( point.x() ) || !std::isfinite( point.y() );
191 } ), poly.end() );
192
193 if ( clipToExtent && context.flags() & Qgis::RenderContextFlag::ApplyClipAfterReprojection && !context.mapExtent().contains( poly.boundingRect() ) )
194 {
195 // early clipping was not possible, so we have to apply it here after transformation
196 const QgsRectangle e = context.mapExtent();
197 const double cw = e.width() / 10;
198 const double ch = e.height() / 10;
199 const QgsRectangle clipRect( e.xMinimum() - cw, e.yMinimum() - ch, e.xMaximum() + cw, e.yMaximum() + ch );
200 QgsClipper::trimPolygon( poly, clipRect );
201 }
202
203 QPointF *ptr = poly.data();
204 for ( int i = 0; i < poly.size(); ++i, ++ptr )
205 {
206 mtp.transformInPlace( ptr->rx(), ptr->ry() );
207 }
208
209 if ( !poly.empty() && !poly.isClosed() )
210 poly << poly.at( 0 );
211
212 return poly;
213 }
214
_getPolygon(QPolygonF & pts,QVector<QPolygonF> & holes,QgsRenderContext & context,const QgsPolygon & polygon,const bool clipToExtent,const bool correctRingOrientation)215 void QgsSymbol::_getPolygon( QPolygonF &pts, QVector<QPolygonF> &holes, QgsRenderContext &context, const QgsPolygon &polygon, const bool clipToExtent, const bool correctRingOrientation )
216 {
217 holes.clear();
218
219 pts = _getPolygonRing( context, *polygon.exteriorRing(), clipToExtent, true, correctRingOrientation );
220 const int ringCount = polygon.numInteriorRings();
221 holes.reserve( ringCount );
222 for ( int idx = 0; idx < ringCount; idx++ )
223 {
224 const QPolygonF hole = _getPolygonRing( context, *( polygon.interiorRing( idx ) ), clipToExtent, false, correctRingOrientation );
225 if ( !hole.isEmpty() )
226 holes.append( hole );
227 }
228 }
229
symbolTypeToString(Qgis::SymbolType type)230 QString QgsSymbol::symbolTypeToString( Qgis::SymbolType type )
231 {
232 switch ( type )
233 {
234 case Qgis::SymbolType::Marker:
235 return QObject::tr( "Marker" );
236 case Qgis::SymbolType::Line:
237 return QObject::tr( "Line" );
238 case Qgis::SymbolType::Fill:
239 return QObject::tr( "Fill" );
240 case Qgis::SymbolType::Hybrid:
241 return QObject::tr( "Hybrid" );
242 }
243 return QString();
244 }
245
symbolTypeForGeometryType(QgsWkbTypes::GeometryType type)246 Qgis::SymbolType QgsSymbol::symbolTypeForGeometryType( QgsWkbTypes::GeometryType type )
247 {
248 switch ( type )
249 {
250 case QgsWkbTypes::PointGeometry:
251 return Qgis::SymbolType::Marker;
252 case QgsWkbTypes::LineGeometry:
253 return Qgis::SymbolType::Line;
254 case QgsWkbTypes::PolygonGeometry:
255 return Qgis::SymbolType::Fill;
256 case QgsWkbTypes::UnknownGeometry:
257 case QgsWkbTypes::NullGeometry:
258 return Qgis::SymbolType::Hybrid;
259 }
260 return Qgis::SymbolType::Hybrid;
261 }
262
propertyDefinitions()263 const QgsPropertiesDefinition &QgsSymbol::propertyDefinitions()
264 {
265 QgsSymbol::initPropertyDefinitions();
266 return sPropertyDefinitions;
267 }
268
~QgsSymbol()269 QgsSymbol::~QgsSymbol()
270 {
271 // delete all symbol layers (we own them, so it's okay)
272 qDeleteAll( mLayers );
273 }
274
outputUnit() const275 QgsUnitTypes::RenderUnit QgsSymbol::outputUnit() const
276 {
277 if ( mLayers.empty() )
278 {
279 return QgsUnitTypes::RenderUnknownUnit;
280 }
281
282 QgsSymbolLayerList::const_iterator it = mLayers.constBegin();
283
284 QgsUnitTypes::RenderUnit unit = ( *it )->outputUnit();
285
286 for ( ; it != mLayers.constEnd(); ++it )
287 {
288 if ( ( *it )->outputUnit() != unit )
289 {
290 return QgsUnitTypes::RenderUnknownUnit;
291 }
292 }
293 return unit;
294 }
295
usesMapUnits() const296 bool QgsSymbol::usesMapUnits() const
297 {
298 if ( mLayers.empty() )
299 {
300 return false;
301 }
302
303 for ( const QgsSymbolLayer *layer : mLayers )
304 {
305 if ( layer->usesMapUnits() )
306 {
307 return true;
308 }
309 }
310 return false;
311 }
312
mapUnitScale() const313 QgsMapUnitScale QgsSymbol::mapUnitScale() const
314 {
315 if ( mLayers.empty() )
316 {
317 return QgsMapUnitScale();
318 }
319
320 QgsSymbolLayerList::const_iterator it = mLayers.constBegin();
321 if ( it == mLayers.constEnd() )
322 return QgsMapUnitScale();
323
324 QgsMapUnitScale scale = ( *it )->mapUnitScale();
325 ++it;
326
327 for ( ; it != mLayers.constEnd(); ++it )
328 {
329 if ( ( *it )->mapUnitScale() != scale )
330 {
331 return QgsMapUnitScale();
332 }
333 }
334 return scale;
335 }
336
setOutputUnit(QgsUnitTypes::RenderUnit u)337 void QgsSymbol::setOutputUnit( QgsUnitTypes::RenderUnit u )
338 {
339 const auto constMLayers = mLayers;
340 for ( QgsSymbolLayer *layer : constMLayers )
341 {
342 layer->setOutputUnit( u );
343 }
344 }
345
setMapUnitScale(const QgsMapUnitScale & scale)346 void QgsSymbol::setMapUnitScale( const QgsMapUnitScale &scale )
347 {
348 const auto constMLayers = mLayers;
349 for ( QgsSymbolLayer *layer : constMLayers )
350 {
351 layer->setMapUnitScale( scale );
352 }
353 }
354
defaultSymbol(QgsWkbTypes::GeometryType geomType)355 QgsSymbol *QgsSymbol::defaultSymbol( QgsWkbTypes::GeometryType geomType )
356 {
357 std::unique_ptr< QgsSymbol > s;
358
359 // override global default if project has a default for this type
360 QString defaultSymbol;
361 switch ( geomType )
362 {
363 case QgsWkbTypes::PointGeometry :
364 defaultSymbol = QgsProject::instance()->readEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/Marker" ) );
365 break;
366 case QgsWkbTypes::LineGeometry :
367 defaultSymbol = QgsProject::instance()->readEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/Line" ) );
368 break;
369 case QgsWkbTypes::PolygonGeometry :
370 defaultSymbol = QgsProject::instance()->readEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/Fill" ) );
371 break;
372 default:
373 break;
374 }
375 if ( !defaultSymbol.isEmpty() )
376 s.reset( QgsStyle::defaultStyle()->symbol( defaultSymbol ) );
377
378 // if no default found for this type, get global default (as previously)
379 if ( !s )
380 {
381 switch ( geomType )
382 {
383 case QgsWkbTypes::PointGeometry:
384 s = std::make_unique< QgsMarkerSymbol >();
385 break;
386 case QgsWkbTypes::LineGeometry:
387 s = std::make_unique< QgsLineSymbol >();
388 break;
389 case QgsWkbTypes::PolygonGeometry:
390 s = std::make_unique< QgsFillSymbol >();
391 break;
392 default:
393 QgsDebugMsg( QStringLiteral( "unknown layer's geometry type" ) );
394 return nullptr;
395 }
396 }
397
398 // set opacity
399 double opacity = 1.0;
400 bool ok = false;
401 // upgrade old setting
402 double alpha = QgsProject::instance()->readDoubleEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/AlphaInt" ), 255, &ok );
403 if ( ok )
404 opacity = alpha / 255.0;
405 double newOpacity = QgsProject::instance()->readDoubleEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/Opacity" ), 1.0, &ok );
406 if ( ok )
407 opacity = newOpacity;
408 s->setOpacity( opacity );
409
410 // set random color, it project prefs allow
411 if ( defaultSymbol.isEmpty() ||
412 QgsProject::instance()->readBoolEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/RandomColors" ), true ) )
413 {
414 s->setColor( QgsApplication::colorSchemeRegistry()->fetchRandomStyleColor() );
415 }
416
417 return s.release();
418 }
419
symbolLayer(int layer)420 QgsSymbolLayer *QgsSymbol::symbolLayer( int layer )
421 {
422 return mLayers.value( layer );
423 }
424
symbolLayer(int layer) const425 const QgsSymbolLayer *QgsSymbol::symbolLayer( int layer ) const
426 {
427 return mLayers.value( layer );
428 }
429
insertSymbolLayer(int index,QgsSymbolLayer * layer)430 bool QgsSymbol::insertSymbolLayer( int index, QgsSymbolLayer *layer )
431 {
432 if ( index < 0 || index > mLayers.count() ) // can be added also after the last index
433 return false;
434
435 if ( !layer || !layer->isCompatibleWithSymbol( this ) )
436 return false;
437
438 mLayers.insert( index, layer );
439 return true;
440 }
441
442
appendSymbolLayer(QgsSymbolLayer * layer)443 bool QgsSymbol::appendSymbolLayer( QgsSymbolLayer *layer )
444 {
445 if ( !layer || !layer->isCompatibleWithSymbol( this ) )
446 return false;
447
448 mLayers.append( layer );
449 return true;
450 }
451
452
deleteSymbolLayer(int index)453 bool QgsSymbol::deleteSymbolLayer( int index )
454 {
455 if ( index < 0 || index >= mLayers.count() )
456 return false;
457
458 delete mLayers.at( index );
459 mLayers.removeAt( index );
460 return true;
461 }
462
463
takeSymbolLayer(int index)464 QgsSymbolLayer *QgsSymbol::takeSymbolLayer( int index )
465 {
466 if ( index < 0 || index >= mLayers.count() )
467 return nullptr;
468
469 return mLayers.takeAt( index );
470 }
471
472
changeSymbolLayer(int index,QgsSymbolLayer * layer)473 bool QgsSymbol::changeSymbolLayer( int index, QgsSymbolLayer *layer )
474 {
475 QgsSymbolLayer *oldLayer = mLayers.value( index );
476
477 if ( oldLayer == layer )
478 return false;
479
480 if ( !layer || !layer->isCompatibleWithSymbol( this ) )
481 return false;
482
483 delete oldLayer; // first delete the original layer
484 mLayers[index] = layer; // set new layer
485 return true;
486 }
487
488
startRender(QgsRenderContext & context,const QgsFields & fields)489 void QgsSymbol::startRender( QgsRenderContext &context, const QgsFields &fields )
490 {
491 Q_ASSERT_X( !mStarted, "startRender", "Rendering has already been started for this symbol instance!" );
492 mStarted = true;
493
494 mSymbolRenderContext.reset( new QgsSymbolRenderContext( context, QgsUnitTypes::RenderUnknownUnit, mOpacity, false, mRenderHints, nullptr, fields ) );
495
496 // Why do we need a copy here ? Is it to make sure the symbol layer rendering does not mess with the symbol render context ?
497 // Or is there another profound reason ?
498 QgsSymbolRenderContext symbolContext( context, QgsUnitTypes::RenderUnknownUnit, mOpacity, false, mRenderHints, nullptr, fields );
499
500 std::unique_ptr< QgsExpressionContextScope > scope( QgsExpressionContextUtils::updateSymbolScope( this, new QgsExpressionContextScope() ) );
501 mSymbolRenderContext->setExpressionContextScope( scope.release() );
502
503 mDataDefinedProperties.prepare( context.expressionContext() );
504
505 const auto constMLayers = mLayers;
506 for ( QgsSymbolLayer *layer : constMLayers )
507 {
508 if ( !layer->enabled() || !context.isSymbolLayerEnabled( layer ) )
509 continue;
510
511 layer->prepareExpressions( symbolContext );
512 layer->startRender( symbolContext );
513 }
514 }
515
stopRender(QgsRenderContext & context)516 void QgsSymbol::stopRender( QgsRenderContext &context )
517 {
518 Q_ASSERT_X( mStarted, "startRender", "startRender was not called for this symbol instance!" );
519 mStarted = false;
520
521 Q_UNUSED( context )
522 if ( mSymbolRenderContext )
523 {
524 const auto constMLayers = mLayers;
525 for ( QgsSymbolLayer *layer : constMLayers )
526 {
527 if ( !layer->enabled() || !context.isSymbolLayerEnabled( layer ) )
528 continue;
529
530 layer->stopRender( *mSymbolRenderContext );
531 }
532 }
533
534 mSymbolRenderContext.reset( nullptr );
535
536 Q_NOWARN_DEPRECATED_PUSH
537 mLayer = nullptr;
538 Q_NOWARN_DEPRECATED_POP
539 }
540
setColor(const QColor & color)541 void QgsSymbol::setColor( const QColor &color )
542 {
543 const auto constMLayers = mLayers;
544 for ( QgsSymbolLayer *layer : constMLayers )
545 {
546 if ( !layer->isLocked() )
547 layer->setColor( color );
548 }
549 }
550
color() const551 QColor QgsSymbol::color() const
552 {
553 for ( QgsSymbolLayerList::const_iterator it = mLayers.begin(); it != mLayers.end(); ++it )
554 {
555 // return color of the first unlocked layer
556 if ( !( *it )->isLocked() )
557 return ( *it )->color();
558 }
559 return QColor( 0, 0, 0 );
560 }
561
drawPreviewIcon(QPainter * painter,QSize size,QgsRenderContext * customContext,bool selected,const QgsExpressionContext * expressionContext,const QgsLegendPatchShape * patchShape)562 void QgsSymbol::drawPreviewIcon( QPainter *painter, QSize size, QgsRenderContext *customContext, bool selected, const QgsExpressionContext *expressionContext, const QgsLegendPatchShape *patchShape )
563 {
564 QgsRenderContext *context = customContext;
565 std::unique_ptr< QgsRenderContext > tempContext;
566 if ( !context )
567 {
568 tempContext.reset( new QgsRenderContext( QgsRenderContext::fromQPainter( painter ) ) );
569 context = tempContext.get();
570 context->setFlag( Qgis::RenderContextFlag::RenderSymbolPreview, true );
571 }
572
573 const bool prevForceVector = context->forceVectorOutput();
574 context->setForceVectorOutput( true );
575
576 const double opacity = expressionContext ? dataDefinedProperties().valueAsDouble( QgsSymbol::PropertyOpacity, *expressionContext, mOpacity ) : mOpacity;
577
578 QgsSymbolRenderContext symbolContext( *context, QgsUnitTypes::RenderUnknownUnit, opacity, false, mRenderHints, nullptr );
579 symbolContext.setSelected( selected );
580 switch ( mType )
581 {
582 case Qgis::SymbolType::Marker:
583 symbolContext.setOriginalGeometryType( QgsWkbTypes::PointGeometry );
584 break;
585 case Qgis::SymbolType::Line:
586 symbolContext.setOriginalGeometryType( QgsWkbTypes::LineGeometry );
587 break;
588 case Qgis::SymbolType::Fill:
589 symbolContext.setOriginalGeometryType( QgsWkbTypes::PolygonGeometry );
590 break;
591 case Qgis::SymbolType::Hybrid:
592 symbolContext.setOriginalGeometryType( QgsWkbTypes::UnknownGeometry );
593 break;
594 }
595
596 if ( patchShape )
597 symbolContext.setPatchShape( *patchShape );
598
599 if ( !customContext && expressionContext )
600 {
601 context->setExpressionContext( *expressionContext );
602 }
603 else if ( !customContext )
604 {
605 // if no render context was passed, build a minimal expression context
606 QgsExpressionContext expContext;
607 expContext.appendScopes( QgsExpressionContextUtils::globalProjectLayerScopes( nullptr ) );
608 context->setExpressionContext( expContext );
609 }
610
611 for ( QgsSymbolLayer *layer : std::as_const( mLayers ) )
612 {
613 if ( !layer->enabled() || ( customContext && !customContext->isSymbolLayerEnabled( layer ) ) )
614 continue;
615
616 if ( mType == Qgis::SymbolType::Fill && layer->type() == Qgis::SymbolType::Line )
617 {
618 // line symbol layer would normally draw just a line
619 // so we override this case to force it to draw a polygon stroke
620 QgsLineSymbolLayer *lsl = dynamic_cast<QgsLineSymbolLayer *>( layer );
621 if ( lsl )
622 {
623 // from QgsFillSymbolLayer::drawPreviewIcon() -- would be nicer to add the
624 // symbol type to QgsSymbolLayer::drawPreviewIcon so this logic could be avoided!
625
626 // hmm... why was this using size -1 ??
627 const QSizeF targetSize = QSizeF( size.width() - 1, size.height() - 1 );
628
629 const QList< QList< QPolygonF > > polys = patchShape ? patchShape->toQPolygonF( Qgis::SymbolType::Fill, targetSize )
630 : QgsStyle::defaultStyle()->defaultPatchAsQPolygonF( Qgis::SymbolType::Fill, targetSize );
631
632 lsl->startRender( symbolContext );
633 QgsPaintEffect *effect = lsl->paintEffect();
634
635 std::unique_ptr< QgsEffectPainter > effectPainter;
636 if ( effect && effect->enabled() )
637 effectPainter = std::make_unique< QgsEffectPainter >( symbolContext.renderContext(), effect );
638
639 for ( const QList< QPolygonF > &poly : polys )
640 {
641 QVector< QPolygonF > rings;
642 rings.reserve( poly.size() );
643 for ( int i = 1; i < poly.size(); ++i )
644 rings << poly.at( i );
645 lsl->renderPolygonStroke( poly.value( 0 ), &rings, symbolContext );
646 }
647
648 effectPainter.reset();
649 lsl->stopRender( symbolContext );
650 }
651 }
652 else
653 layer->drawPreviewIcon( symbolContext, size );
654 }
655
656 context->setForceVectorOutput( prevForceVector );
657 }
658
exportImage(const QString & path,const QString & format,QSize size)659 void QgsSymbol::exportImage( const QString &path, const QString &format, QSize size )
660 {
661 if ( format.compare( QLatin1String( "svg" ), Qt::CaseInsensitive ) == 0 )
662 {
663 QSvgGenerator generator;
664 generator.setFileName( path );
665 generator.setSize( size );
666 generator.setViewBox( QRect( 0, 0, size.height(), size.height() ) );
667
668 QPainter painter( &generator );
669 drawPreviewIcon( &painter, size );
670 painter.end();
671 }
672 else
673 {
674 QImage image = asImage( size );
675 image.save( path );
676 }
677 }
678
asImage(QSize size,QgsRenderContext * customContext)679 QImage QgsSymbol::asImage( QSize size, QgsRenderContext *customContext )
680 {
681 QImage image( size, QImage::Format_ARGB32_Premultiplied );
682 image.fill( 0 );
683
684 QPainter p( &image );
685 p.setRenderHint( QPainter::Antialiasing );
686
687 drawPreviewIcon( &p, size, customContext );
688
689 return image;
690 }
691
692
bigSymbolPreviewImage(QgsExpressionContext * expressionContext,Qgis::SymbolPreviewFlags flags)693 QImage QgsSymbol::bigSymbolPreviewImage( QgsExpressionContext *expressionContext, Qgis::SymbolPreviewFlags flags )
694 {
695 QImage preview( QSize( 100, 100 ), QImage::Format_ARGB32_Premultiplied );
696 preview.fill( 0 );
697
698 QPainter p( &preview );
699 p.setRenderHint( QPainter::Antialiasing );
700 p.translate( 0.5, 0.5 ); // shift by half a pixel to avoid blurring due antialiasing
701
702 if ( mType == Qgis::SymbolType::Marker && flags & Qgis::SymbolPreviewFlag::FlagIncludeCrosshairsForMarkerSymbols )
703 {
704 p.setPen( QPen( Qt::gray ) );
705 p.drawLine( 0, 50, 100, 50 );
706 p.drawLine( 50, 0, 50, 100 );
707 }
708
709 QgsRenderContext context = QgsRenderContext::fromQPainter( &p );
710 context.setFlag( Qgis::RenderContextFlag::RenderSymbolPreview );
711 if ( expressionContext )
712 context.setExpressionContext( *expressionContext );
713
714 context.setIsGuiPreview( true );
715 startRender( context );
716
717 if ( mType == Qgis::SymbolType::Line )
718 {
719 QPolygonF poly;
720 poly << QPointF( 0, 50 ) << QPointF( 99, 50 );
721 static_cast<QgsLineSymbol *>( this )->renderPolyline( poly, nullptr, context );
722 }
723 else if ( mType == Qgis::SymbolType::Fill )
724 {
725 QPolygonF polygon;
726 polygon << QPointF( 20, 20 ) << QPointF( 80, 20 ) << QPointF( 80, 80 ) << QPointF( 20, 80 ) << QPointF( 20, 20 );
727 static_cast<QgsFillSymbol *>( this )->renderPolygon( polygon, nullptr, nullptr, context );
728 }
729 else // marker
730 {
731 static_cast<QgsMarkerSymbol *>( this )->renderPoint( QPointF( 50, 50 ), nullptr, context );
732 }
733
734 stopRender( context );
735 return preview;
736 }
737
bigSymbolPreviewImage(QgsExpressionContext * expressionContext,int flags)738 QImage QgsSymbol::bigSymbolPreviewImage( QgsExpressionContext *expressionContext, int flags )
739 {
740 return bigSymbolPreviewImage( expressionContext, static_cast< Qgis::SymbolPreviewFlags >( flags ) );
741 }
742
dump() const743 QString QgsSymbol::dump() const
744 {
745 QString t;
746 switch ( type() )
747 {
748 case Qgis::SymbolType::Marker:
749 t = QStringLiteral( "MARKER" );
750 break;
751 case Qgis::SymbolType::Line:
752 t = QStringLiteral( "LINE" );
753 break;
754 case Qgis::SymbolType::Fill:
755 t = QStringLiteral( "FILL" );
756 break;
757 default:
758 Q_ASSERT( false && "unknown symbol type" );
759 }
760 QString s = QStringLiteral( "%1 SYMBOL (%2 layers) color %3" ).arg( t ).arg( mLayers.count() ).arg( QgsSymbolLayerUtils::encodeColor( color() ) );
761
762 for ( QgsSymbolLayerList::const_iterator it = mLayers.begin(); it != mLayers.end(); ++it )
763 {
764 // TODO:
765 }
766 return s;
767 }
768
toSld(QDomDocument & doc,QDomElement & element,QVariantMap props) const769 void QgsSymbol::toSld( QDomDocument &doc, QDomElement &element, QVariantMap props ) const
770 {
771 props[ QStringLiteral( "alpha" )] = QString::number( opacity() );
772 double scaleFactor = 1.0;
773 props[ QStringLiteral( "uom" )] = QgsSymbolLayerUtils::encodeSldUom( outputUnit(), &scaleFactor );
774 props[ QStringLiteral( "uomScale" )] = ( !qgsDoubleNear( scaleFactor, 1.0 ) ? qgsDoubleToString( scaleFactor ) : QString() );
775
776 for ( QgsSymbolLayerList::const_iterator it = mLayers.begin(); it != mLayers.end(); ++it )
777 {
778 ( *it )->toSld( doc, element, props );
779 }
780 }
781
cloneLayers() const782 QgsSymbolLayerList QgsSymbol::cloneLayers() const
783 {
784 QgsSymbolLayerList lst;
785 for ( QgsSymbolLayerList::const_iterator it = mLayers.begin(); it != mLayers.end(); ++it )
786 {
787 QgsSymbolLayer *layer = ( *it )->clone();
788 layer->setLocked( ( *it )->isLocked() );
789 layer->setRenderingPass( ( *it )->renderingPass() );
790 layer->setEnabled( ( *it )->enabled() );
791 lst.append( layer );
792 }
793 return lst;
794 }
795
renderUsingLayer(QgsSymbolLayer * layer,QgsSymbolRenderContext & context,QgsWkbTypes::GeometryType geometryType,const QPolygonF * points,const QVector<QPolygonF> * rings)796 void QgsSymbol::renderUsingLayer( QgsSymbolLayer *layer, QgsSymbolRenderContext &context, QgsWkbTypes::GeometryType geometryType, const QPolygonF *points, const QVector<QPolygonF> *rings )
797 {
798 Q_ASSERT( layer->type() == Qgis::SymbolType::Hybrid );
799
800 if ( layer->dataDefinedProperties().hasActiveProperties() && !layer->dataDefinedProperties().valueAsBool( QgsSymbolLayer::PropertyLayerEnabled, context.renderContext().expressionContext(), true ) )
801 return;
802
803 QgsGeometryGeneratorSymbolLayer *generatorLayer = static_cast<QgsGeometryGeneratorSymbolLayer *>( layer );
804
805 QgsPaintEffect *effect = generatorLayer->paintEffect();
806 if ( effect && effect->enabled() )
807 {
808 QgsEffectPainter p( context.renderContext(), effect );
809 generatorLayer->render( context, geometryType, points, rings );
810 }
811 else
812 {
813 generatorLayer->render( context, geometryType, points, rings );
814 }
815 }
816
usedAttributes(const QgsRenderContext & context) const817 QSet<QString> QgsSymbol::usedAttributes( const QgsRenderContext &context ) const
818 {
819 // calling referencedFields() with ignoreContext=true because in our expression context
820 // we do not have valid QgsFields yet - because of that the field names from expressions
821 // wouldn't get reported
822 QSet<QString> attributes = mDataDefinedProperties.referencedFields( context.expressionContext(), true );
823 QgsSymbolLayerList::const_iterator sIt = mLayers.constBegin();
824 for ( ; sIt != mLayers.constEnd(); ++sIt )
825 {
826 if ( *sIt )
827 {
828 attributes.unite( ( *sIt )->usedAttributes( context ) );
829 }
830 }
831 return attributes;
832 }
833
setDataDefinedProperty(QgsSymbol::Property key,const QgsProperty & property)834 void QgsSymbol::setDataDefinedProperty( QgsSymbol::Property key, const QgsProperty &property )
835 {
836 mDataDefinedProperties.setProperty( key, property );
837 }
838
hasDataDefinedProperties() const839 bool QgsSymbol::hasDataDefinedProperties() const
840 {
841 if ( mDataDefinedProperties.hasActiveProperties() )
842 return true;
843
844 for ( QgsSymbolLayer *layer : mLayers )
845 {
846 if ( layer->hasDataDefinedProperties() )
847 return true;
848 }
849 return false;
850 }
851
canCauseArtifactsBetweenAdjacentTiles() const852 bool QgsSymbol::canCauseArtifactsBetweenAdjacentTiles() const
853 {
854 for ( QgsSymbolLayer *layer : mLayers )
855 {
856 if ( layer->canCauseArtifactsBetweenAdjacentTiles() )
857 return true;
858 }
859 return false;
860 }
861
setLayer(const QgsVectorLayer * layer)862 void QgsSymbol::setLayer( const QgsVectorLayer *layer )
863 {
864 Q_NOWARN_DEPRECATED_PUSH
865 mLayer = layer;
866 Q_NOWARN_DEPRECATED_POP
867 }
868
layer() const869 const QgsVectorLayer *QgsSymbol::layer() const
870 {
871 Q_NOWARN_DEPRECATED_PUSH
872 return mLayer;
873 Q_NOWARN_DEPRECATED_POP
874 }
875
876 ///@cond PRIVATE
877
878 /**
879 * RAII class to pop scope from an expression context on destruction
880 */
881 class ExpressionContextScopePopper
882 {
883 public:
884
885 ExpressionContextScopePopper() = default;
886
~ExpressionContextScopePopper()887 ~ExpressionContextScopePopper()
888 {
889 if ( context )
890 context->popScope();
891 }
892
893 QgsExpressionContext *context = nullptr;
894 };
895
896 /**
897 * RAII class to restore original geometry on a render context on destruction
898 */
899 class GeometryRestorer
900 {
901 public:
GeometryRestorer(QgsRenderContext & context)902 GeometryRestorer( QgsRenderContext &context )
903 : mContext( context ),
904 mGeometry( context.geometry() )
905 {}
906
~GeometryRestorer()907 ~GeometryRestorer()
908 {
909 mContext.setGeometry( mGeometry );
910 }
911
912 private:
913 QgsRenderContext &mContext;
914 const QgsAbstractGeometry *mGeometry;
915 };
916 ///@endcond PRIVATE
917
renderFeature(const QgsFeature & feature,QgsRenderContext & context,int layer,bool selected,bool drawVertexMarker,Qgis::VertexMarkerType currentVertexMarkerType,double currentVertexMarkerSize)918 void QgsSymbol::renderFeature( const QgsFeature &feature, QgsRenderContext &context, int layer, bool selected, bool drawVertexMarker, Qgis::VertexMarkerType currentVertexMarkerType, double currentVertexMarkerSize )
919 {
920 if ( context.renderingStopped() )
921 return;
922
923 const QgsGeometry geom = feature.geometry();
924 if ( geom.isNull() )
925 {
926 return;
927 }
928
929 GeometryRestorer geomRestorer( context );
930
931 bool usingSegmentizedGeometry = false;
932 context.setGeometry( geom.constGet() );
933
934 if ( geom.type() != QgsWkbTypes::PointGeometry && !geom.boundingBox().isNull() )
935 {
936 try
937 {
938 const QPointF boundsOrigin = _getPoint( context, QgsPoint( geom.boundingBox().xMinimum(), geom.boundingBox().yMinimum() ) );
939 if ( std::isfinite( boundsOrigin.x() ) && std::isfinite( boundsOrigin.y() ) )
940 context.setTextureOrigin( boundsOrigin );
941 }
942 catch ( QgsCsException & )
943 {
944
945 }
946 }
947
948 bool clippingEnabled = clipFeaturesToExtent();
949 // do any symbol layers prevent feature clipping?
950 for ( QgsSymbolLayer *layer : std::as_const( mLayers ) )
951 {
952 if ( layer->flags() & Qgis::SymbolLayerFlag::DisableFeatureClipping )
953 {
954 clippingEnabled = false;
955 break;
956 }
957 }
958 if ( clippingEnabled && context.testFlag( Qgis::RenderContextFlag::RenderMapTile ) )
959 {
960 // If the "avoid artifacts between adjacent tiles" flag is set (RenderMapTile), then we'll force disable
961 // the geometry clipping IF (and only if) this symbol can potentially have rendering artifacts when rendered as map tiles.
962 // If the symbol won't have any artifacts anyway, then it's pointless and incredibly expensive to skip the clipping!
963 if ( canCauseArtifactsBetweenAdjacentTiles() )
964 {
965 clippingEnabled = false;
966 }
967 }
968 if ( context.extent().isEmpty() )
969 clippingEnabled = false;
970
971 mSymbolRenderContext->setGeometryPartCount( geom.constGet()->partCount() );
972 mSymbolRenderContext->setGeometryPartNum( 1 );
973
974 const bool needsExpressionContext = hasDataDefinedProperties();
975 ExpressionContextScopePopper scopePopper;
976 if ( mSymbolRenderContext->expressionContextScope() )
977 {
978 if ( needsExpressionContext )
979 {
980 // this is somewhat nasty - by appending this scope here it's now owned
981 // by both mSymbolRenderContext AND context.expressionContext()
982 // the RAII scopePopper is required to make sure it always has ownership transferred back
983 // from context.expressionContext(), even if exceptions of other early exits occur in this
984 // function
985 context.expressionContext().appendScope( mSymbolRenderContext->expressionContextScope() );
986 scopePopper.context = &context.expressionContext();
987
988 QgsExpressionContextUtils::updateSymbolScope( this, mSymbolRenderContext->expressionContextScope() );
989 mSymbolRenderContext->expressionContextScope()->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_PART_COUNT, mSymbolRenderContext->geometryPartCount(), true ) );
990 mSymbolRenderContext->expressionContextScope()->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_PART_NUM, 1, true ) );
991 }
992 }
993
994 // Collection of markers to paint, only used for no curve types.
995 QPolygonF markers;
996
997 QgsGeometry renderedBoundsGeom;
998
999 // Step 1 - collect the set of painter coordinate geometries to render.
1000 // We do this upfront, because we only want to ever do this once, regardless how many symbol layers we need to render.
1001
1002 struct PointInfo
1003 {
1004 QPointF renderPoint;
1005 const QgsPoint *originalGeometry = nullptr;
1006 };
1007 QVector< PointInfo > pointsToRender;
1008
1009 struct LineInfo
1010 {
1011 QPolygonF renderLine;
1012 const QgsCurve *originalGeometry = nullptr;
1013 };
1014 QVector< LineInfo > linesToRender;
1015
1016 struct PolygonInfo
1017 {
1018 QPolygonF renderExterior;
1019 QVector< QPolygonF > renderRings;
1020 const QgsCurvePolygon *originalGeometry = nullptr;
1021 int originalPartIndex = 0;
1022 };
1023 QVector< PolygonInfo > polygonsToRender;
1024
1025 std::function< void ( const QgsAbstractGeometry *, int partIndex )> getPartGeometry;
1026 getPartGeometry = [&pointsToRender, &linesToRender, &polygonsToRender, &getPartGeometry, &context, &clippingEnabled, &markers, &feature, &usingSegmentizedGeometry, this]( const QgsAbstractGeometry * part, int partIndex = 0 )
1027 {
1028 Q_UNUSED( feature )
1029
1030 if ( !part )
1031 return;
1032
1033 // geometry preprocessing
1034 QgsGeometry temporaryGeometryContainer;
1035 const QgsAbstractGeometry *processedGeometry = nullptr;
1036
1037 const bool isMultiPart = qgsgeometry_cast< const QgsGeometryCollection * >( part ) && qgsgeometry_cast< const QgsGeometryCollection * >( part )->numGeometries() > 1;
1038
1039 if ( !isMultiPart )
1040 {
1041 // segmentize curved geometries
1042 const bool needsSegmentizing = QgsWkbTypes::isCurvedType( part->wkbType() ) || part->hasCurvedSegments();
1043 if ( needsSegmentizing )
1044 {
1045 std::unique_ptr< QgsAbstractGeometry > segmentizedPart( part->segmentize( context.segmentationTolerance(), context.segmentationToleranceType() ) );
1046 if ( !segmentizedPart )
1047 {
1048 return;
1049 }
1050 temporaryGeometryContainer.set( segmentizedPart.release() );
1051 processedGeometry = temporaryGeometryContainer.constGet();
1052 usingSegmentizedGeometry = true;
1053 }
1054 else
1055 {
1056 // no segmentation required
1057 processedGeometry = part;
1058 }
1059
1060 // Simplify the geometry, if needed.
1061 if ( context.vectorSimplifyMethod().forceLocalOptimization() )
1062 {
1063 const int simplifyHints = context.vectorSimplifyMethod().simplifyHints();
1064 const QgsMapToPixelSimplifier simplifier( simplifyHints, context.vectorSimplifyMethod().tolerance(),
1065 static_cast< QgsMapToPixelSimplifier::SimplifyAlgorithm >( context.vectorSimplifyMethod().simplifyAlgorithm() ) );
1066
1067 std::unique_ptr< QgsAbstractGeometry > simplified( simplifier.simplify( processedGeometry ) );
1068 if ( simplified )
1069 {
1070 temporaryGeometryContainer.set( simplified.release() );
1071 processedGeometry = temporaryGeometryContainer.constGet();
1072 }
1073 }
1074
1075 // clip geometry to render context clipping regions
1076 if ( !context.featureClipGeometry().isEmpty() )
1077 {
1078 // apply feature clipping from context to the rendered geometry only -- just like the render time simplification,
1079 // we should NEVER apply this to the geometry attached to the feature itself. Doing so causes issues with certain
1080 // renderer settings, e.g. if polygons are being rendered using a rule based renderer based on the feature's area,
1081 // then we need to ensure that the original feature area is used instead of the clipped area..
1082 QgsGeos geos( processedGeometry );
1083 std::unique_ptr< QgsAbstractGeometry > clippedGeom( geos.intersection( context.featureClipGeometry().constGet() ) );
1084 if ( clippedGeom )
1085 {
1086 temporaryGeometryContainer.set( clippedGeom.release() );
1087 processedGeometry = temporaryGeometryContainer.constGet();
1088 }
1089 }
1090 }
1091 else
1092 {
1093 // for multipart geometries, the processing is deferred till we're rendering the actual part...
1094 processedGeometry = part;
1095 }
1096
1097 if ( !processedGeometry )
1098 {
1099 // shouldn't happen!
1100 QgsDebugMsg( QStringLiteral( "No processed geometry to render for part!" ) );
1101 return;
1102 }
1103
1104 switch ( QgsWkbTypes::flatType( processedGeometry->wkbType() ) )
1105 {
1106 case QgsWkbTypes::Point:
1107 {
1108 if ( mType != Qgis::SymbolType::Marker )
1109 {
1110 QgsDebugMsgLevel( QStringLiteral( "point can be drawn only with marker symbol!" ), 2 );
1111 break;
1112 }
1113
1114 PointInfo info;
1115 info.originalGeometry = qgsgeometry_cast< const QgsPoint * >( part );
1116 info.renderPoint = _getPoint( context, *info.originalGeometry );
1117 pointsToRender << info;
1118 break;
1119 }
1120
1121 case QgsWkbTypes::LineString:
1122 {
1123 if ( mType != Qgis::SymbolType::Line )
1124 {
1125 QgsDebugMsgLevel( QStringLiteral( "linestring can be drawn only with line symbol!" ), 2 );
1126 break;
1127 }
1128
1129 LineInfo info;
1130 info.originalGeometry = qgsgeometry_cast<const QgsCurve *>( part );
1131 info.renderLine = _getLineString( context, *qgsgeometry_cast<const QgsCurve *>( processedGeometry ), clippingEnabled );
1132 linesToRender << info;
1133 break;
1134 }
1135
1136 case QgsWkbTypes::Polygon:
1137 case QgsWkbTypes::Triangle:
1138 {
1139 QPolygonF pts;
1140 if ( mType != Qgis::SymbolType::Fill )
1141 {
1142 QgsDebugMsgLevel( QStringLiteral( "polygon can be drawn only with fill symbol!" ), 2 );
1143 break;
1144 }
1145
1146 PolygonInfo info;
1147 info.originalGeometry = qgsgeometry_cast<const QgsCurvePolygon *>( part );
1148 info.originalPartIndex = partIndex;
1149 if ( !qgsgeometry_cast<const QgsPolygon *>( processedGeometry )->exteriorRing() )
1150 {
1151 QgsDebugMsg( QStringLiteral( "cannot render polygon with no exterior ring" ) );
1152 break;
1153 }
1154
1155 _getPolygon( info.renderExterior, info.renderRings, context, *qgsgeometry_cast<const QgsPolygon *>( processedGeometry ), clippingEnabled, mForceRHR );
1156 polygonsToRender << info;
1157 break;
1158 }
1159
1160 case QgsWkbTypes::MultiPoint:
1161 {
1162 const QgsMultiPoint *mp = qgsgeometry_cast< const QgsMultiPoint * >( processedGeometry );
1163 markers.reserve( mp->numGeometries() );
1164 }
1165 FALLTHROUGH
1166 case QgsWkbTypes::MultiCurve:
1167 case QgsWkbTypes::MultiLineString:
1168 case QgsWkbTypes::GeometryCollection:
1169 {
1170 const QgsGeometryCollection *geomCollection = qgsgeometry_cast<const QgsGeometryCollection *>( processedGeometry );
1171
1172 const unsigned int num = geomCollection->numGeometries();
1173 for ( unsigned int i = 0; i < num; ++i )
1174 {
1175 if ( context.renderingStopped() )
1176 break;
1177
1178 getPartGeometry( geomCollection->geometryN( i ), i );
1179 }
1180 break;
1181 }
1182
1183 case QgsWkbTypes::MultiSurface:
1184 case QgsWkbTypes::MultiPolygon:
1185 {
1186 if ( mType != Qgis::SymbolType::Fill )
1187 {
1188 QgsDebugMsgLevel( QStringLiteral( "multi-polygon can be drawn only with fill symbol!" ), 2 );
1189 break;
1190 }
1191
1192 QPolygonF pts;
1193
1194 const QgsGeometryCollection *geomCollection = dynamic_cast<const QgsGeometryCollection *>( processedGeometry );
1195 const unsigned int num = geomCollection->numGeometries();
1196
1197 // Sort components by approximate area (probably a bit faster than using
1198 // area() )
1199 std::map<double, QList<unsigned int> > thisAreaToPartNum;
1200 for ( unsigned int i = 0; i < num; ++i )
1201 {
1202 const QgsRectangle r( geomCollection->geometryN( i )->boundingBox() );
1203 thisAreaToPartNum[ r.width() * r.height()] << i;
1204 }
1205
1206 // Draw starting with larger parts down to smaller parts, so that in
1207 // case of a part being incorrectly inside another part, it is drawn
1208 // on top of it (#15419)
1209 std::map<double, QList<unsigned int> >::const_reverse_iterator iter = thisAreaToPartNum.rbegin();
1210 for ( ; iter != thisAreaToPartNum.rend(); ++iter )
1211 {
1212 const QList<unsigned int> &listPartIndex = iter->second;
1213 for ( int idx = 0; idx < listPartIndex.size(); ++idx )
1214 {
1215 const unsigned i = listPartIndex[idx];
1216 getPartGeometry( geomCollection->geometryN( i ), i );
1217 }
1218 }
1219 break;
1220 }
1221
1222 default:
1223 QgsDebugMsg( QStringLiteral( "feature %1: unsupported wkb type %2/%3 for rendering" )
1224 .arg( feature.id() )
1225 .arg( QgsWkbTypes::displayString( part->wkbType() ) )
1226 .arg( part->wkbType(), 0, 16 ) );
1227 }
1228 };
1229
1230 // Use the simplified type ref when rendering -- this avoids some unnecessary cloning/geometry modification
1231 // (e.g. if the original geometry is a compound curve containing only a linestring curve, we don't have
1232 // to segmentize the geometry before rendering)
1233 getPartGeometry( geom.constGet()->simplifiedTypeRef(), 0 );
1234
1235 // step 2 - determine which layers to render
1236 std::vector< int > layers;
1237 if ( layer == -1 )
1238 {
1239 layers.reserve( mLayers.count() );
1240 for ( int i = 0; i < mLayers.count(); ++i )
1241 layers.emplace_back( i );
1242 }
1243 else
1244 {
1245 layers.emplace_back( layer );
1246 }
1247
1248 // step 3 - render these geometries using the desired symbol layers.
1249
1250 if ( needsExpressionContext )
1251 mSymbolRenderContext->expressionContextScope()->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "symbol_layer_count" ), mLayers.count(), true ) );
1252
1253 for ( const int symbolLayerIndex : layers )
1254 {
1255 QgsSymbolLayer *symbolLayer = mLayers.value( symbolLayerIndex );
1256 if ( !symbolLayer || !symbolLayer->enabled() )
1257 continue;
1258
1259 if ( needsExpressionContext )
1260 mSymbolRenderContext->expressionContextScope()->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "symbol_layer_index" ), symbolLayerIndex + 1, true ) );
1261
1262 symbolLayer->startFeatureRender( feature, context );
1263
1264 switch ( mType )
1265 {
1266 case Qgis::SymbolType::Marker:
1267 {
1268 int geometryPartNumber = 0;
1269 for ( const PointInfo &point : std::as_const( pointsToRender ) )
1270 {
1271 if ( context.renderingStopped() )
1272 break;
1273
1274 mSymbolRenderContext->setGeometryPartNum( geometryPartNumber + 1 );
1275 if ( needsExpressionContext )
1276 mSymbolRenderContext->expressionContextScope()->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_PART_NUM, geometryPartNumber + 1, true ) );
1277
1278 static_cast<QgsMarkerSymbol *>( this )->renderPoint( point.renderPoint, &feature, context, symbolLayerIndex, selected );
1279 geometryPartNumber++;
1280 }
1281
1282 break;
1283 }
1284
1285 case Qgis::SymbolType::Line:
1286 {
1287 if ( linesToRender.empty() )
1288 break;
1289
1290 int geometryPartNumber = 0;
1291 for ( const LineInfo &line : std::as_const( linesToRender ) )
1292 {
1293 if ( context.renderingStopped() )
1294 break;
1295
1296 mSymbolRenderContext->setGeometryPartNum( geometryPartNumber + 1 );
1297 if ( needsExpressionContext )
1298 mSymbolRenderContext->expressionContextScope()->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_PART_NUM, geometryPartNumber + 1, true ) );
1299
1300 context.setGeometry( line.originalGeometry );
1301 static_cast<QgsLineSymbol *>( this )->renderPolyline( line.renderLine, &feature, context, symbolLayerIndex, selected );
1302 geometryPartNumber++;
1303 }
1304 break;
1305 }
1306
1307 case Qgis::SymbolType::Fill:
1308 {
1309 for ( const PolygonInfo &info : std::as_const( polygonsToRender ) )
1310 {
1311 if ( context.renderingStopped() )
1312 break;
1313
1314 mSymbolRenderContext->setGeometryPartNum( info.originalPartIndex + 1 );
1315 if ( needsExpressionContext )
1316 mSymbolRenderContext->expressionContextScope()->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_PART_NUM, info.originalPartIndex + 1, true ) );
1317
1318 context.setGeometry( info.originalGeometry );
1319 static_cast<QgsFillSymbol *>( this )->renderPolygon( info.renderExterior, ( !info.renderRings.isEmpty() ? &info.renderRings : nullptr ), &feature, context, symbolLayerIndex, selected );
1320 }
1321
1322 break;
1323 }
1324
1325 case Qgis::SymbolType::Hybrid:
1326 break;
1327 }
1328
1329 symbolLayer->stopFeatureRender( feature, context );
1330 }
1331
1332 // step 4 - handle post processing steps
1333 switch ( mType )
1334 {
1335 case Qgis::SymbolType::Marker:
1336 {
1337 markers.reserve( pointsToRender.size() );
1338 for ( const PointInfo &info : std::as_const( pointsToRender ) )
1339 {
1340 if ( context.hasRenderedFeatureHandlers() || context.testFlag( Qgis::RenderContextFlag::DrawSymbolBounds ) )
1341 {
1342 const QRectF bounds = static_cast<QgsMarkerSymbol *>( this )->bounds( info.renderPoint, context, feature );
1343 if ( context.hasRenderedFeatureHandlers() )
1344 {
1345 renderedBoundsGeom = renderedBoundsGeom.isNull() ? QgsGeometry::fromRect( bounds )
1346 : QgsGeometry::collectGeometry( QVector< QgsGeometry>() << QgsGeometry::fromRect( QgsRectangle( bounds ) ) << renderedBoundsGeom );
1347 }
1348 if ( context.testFlag( Qgis::RenderContextFlag::DrawSymbolBounds ) )
1349 {
1350 //draw debugging rect
1351 context.painter()->setPen( Qt::red );
1352 context.painter()->setBrush( QColor( 255, 0, 0, 100 ) );
1353 context.painter()->drawRect( bounds );
1354 }
1355 }
1356
1357 if ( drawVertexMarker && !usingSegmentizedGeometry )
1358 {
1359 markers.append( info.renderPoint );
1360 }
1361 }
1362 break;
1363 }
1364
1365 case Qgis::SymbolType::Line:
1366 {
1367 for ( const LineInfo &info : std::as_const( linesToRender ) )
1368 {
1369 if ( context.hasRenderedFeatureHandlers() && !info.renderLine.empty() )
1370 {
1371 renderedBoundsGeom = renderedBoundsGeom.isNull() ? QgsGeometry::fromQPolygonF( info.renderLine )
1372 : QgsGeometry::collectGeometry( QVector< QgsGeometry>() << QgsGeometry::fromQPolygonF( info.renderLine ) << renderedBoundsGeom );
1373 }
1374
1375 if ( drawVertexMarker && !usingSegmentizedGeometry )
1376 {
1377 markers << info.renderLine;
1378 }
1379 }
1380 break;
1381 }
1382
1383 case Qgis::SymbolType::Fill:
1384 {
1385 int i = 0;
1386 for ( const PolygonInfo &info : std::as_const( polygonsToRender ) )
1387 {
1388 if ( context.hasRenderedFeatureHandlers() && !info.renderExterior.empty() )
1389 {
1390 renderedBoundsGeom = renderedBoundsGeom.isNull() ? QgsGeometry::fromQPolygonF( info.renderExterior )
1391 : QgsGeometry::collectGeometry( QVector< QgsGeometry>() << QgsGeometry::fromQPolygonF( info.renderExterior ) << renderedBoundsGeom );
1392 // TODO: consider holes?
1393 }
1394
1395 if ( drawVertexMarker && !usingSegmentizedGeometry )
1396 {
1397 markers << info.renderExterior;
1398
1399 for ( const QPolygonF &hole : info.renderRings )
1400 {
1401 markers << hole;
1402 }
1403 }
1404 i++;
1405 }
1406 break;
1407 }
1408
1409 case Qgis::SymbolType::Hybrid:
1410 break;
1411 }
1412
1413 if ( context.hasRenderedFeatureHandlers() && !renderedBoundsGeom.isNull() )
1414 {
1415 QgsRenderedFeatureHandlerInterface::RenderedFeatureContext featureContext( context );
1416 const QList< QgsRenderedFeatureHandlerInterface * > handlers = context.renderedFeatureHandlers();
1417 for ( QgsRenderedFeatureHandlerInterface *handler : handlers )
1418 handler->handleRenderedFeature( feature, renderedBoundsGeom, featureContext );
1419 }
1420
1421 if ( drawVertexMarker )
1422 {
1423 if ( !markers.isEmpty() && !context.renderingStopped() )
1424 {
1425 const auto constMarkers = markers;
1426 for ( QPointF marker : constMarkers )
1427 {
1428 renderVertexMarker( marker, context, currentVertexMarkerType, currentVertexMarkerSize );
1429 }
1430 }
1431 else
1432 {
1433 QgsCoordinateTransform ct = context.coordinateTransform();
1434 const QgsMapToPixel &mtp = context.mapToPixel();
1435
1436 QgsPoint vertexPoint;
1437 QgsVertexId vertexId;
1438 double x, y, z;
1439 QPointF mapPoint;
1440 while ( geom.constGet()->nextVertex( vertexId, vertexPoint ) )
1441 {
1442 //transform
1443 x = vertexPoint.x();
1444 y = vertexPoint.y();
1445 z = 0.0;
1446 if ( ct.isValid() )
1447 {
1448 ct.transformInPlace( x, y, z );
1449 }
1450 mapPoint.setX( x );
1451 mapPoint.setY( y );
1452 mtp.transformInPlace( mapPoint.rx(), mapPoint.ry() );
1453 renderVertexMarker( mapPoint, context, currentVertexMarkerType, currentVertexMarkerSize );
1454 }
1455 }
1456 }
1457 }
1458
symbolRenderContext()1459 QgsSymbolRenderContext *QgsSymbol::symbolRenderContext()
1460 {
1461 return mSymbolRenderContext.get();
1462 }
1463
renderVertexMarker(QPointF pt,QgsRenderContext & context,Qgis::VertexMarkerType currentVertexMarkerType,double currentVertexMarkerSize)1464 void QgsSymbol::renderVertexMarker( QPointF pt, QgsRenderContext &context, Qgis::VertexMarkerType currentVertexMarkerType, double currentVertexMarkerSize )
1465 {
1466 int markerSize = context.convertToPainterUnits( currentVertexMarkerSize, QgsUnitTypes::RenderMillimeters );
1467 QgsSymbolLayerUtils::drawVertexMarker( pt.x(), pt.y(), *context.painter(), currentVertexMarkerType, markerSize );
1468 }
1469
initPropertyDefinitions()1470 void QgsSymbol::initPropertyDefinitions()
1471 {
1472 if ( !sPropertyDefinitions.isEmpty() )
1473 return;
1474
1475 QString origin = QStringLiteral( "symbol" );
1476
1477 sPropertyDefinitions = QgsPropertiesDefinition
1478 {
1479 { QgsSymbol::PropertyOpacity, QgsPropertyDefinition( "alpha", QObject::tr( "Opacity" ), QgsPropertyDefinition::Opacity, origin )},
1480 };
1481 }
1482
startFeatureRender(const QgsFeature & feature,QgsRenderContext & context,const int layer)1483 void QgsSymbol::startFeatureRender( const QgsFeature &feature, QgsRenderContext &context, const int layer )
1484 {
1485 if ( layer != -1 )
1486 {
1487 QgsSymbolLayer *symbolLayer = mLayers.value( layer );
1488 if ( symbolLayer && symbolLayer->enabled() )
1489 {
1490 symbolLayer->startFeatureRender( feature, context );
1491 }
1492 return;
1493 }
1494 else
1495 {
1496 const QList< QgsSymbolLayer * > layers = mLayers;
1497 for ( QgsSymbolLayer *symbolLayer : layers )
1498 {
1499 if ( !symbolLayer->enabled() )
1500 continue;
1501
1502 symbolLayer->startFeatureRender( feature, context );
1503 }
1504 }
1505 }
1506
stopFeatureRender(const QgsFeature & feature,QgsRenderContext & context,int layer)1507 void QgsSymbol::stopFeatureRender( const QgsFeature &feature, QgsRenderContext &context, int layer )
1508 {
1509 if ( layer != -1 )
1510 {
1511 QgsSymbolLayer *symbolLayer = mLayers.value( layer );
1512 if ( symbolLayer && symbolLayer->enabled() )
1513 {
1514 symbolLayer->stopFeatureRender( feature, context );
1515 }
1516 return;
1517 }
1518 else
1519 {
1520 const QList< QgsSymbolLayer * > layers = mLayers;
1521 for ( QgsSymbolLayer *symbolLayer : layers )
1522 {
1523 if ( !symbolLayer->enabled() )
1524 continue;
1525
1526 symbolLayer->stopFeatureRender( feature, context );
1527 }
1528 }
1529 }
1530