QGIS API Documentation 3.41.0-Master (fda2aa46e9a)
Loading...
Searching...
No Matches
qgssymbol.cpp
Go to the documentation of this file.
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#include <QPicture>
22
23#include <cmath>
24#include <map>
25#include <random>
26
27#include "qgssymbol.h"
29#include "qgssymbollayer.h"
30
33#include "qgslogger.h"
34#include "qgsrendercontext.h" // for bigSymbolPreview
35#include "qgsproject.h"
37#include "qgsstyle.h"
38#include "qgspainteffect.h"
39#include "qgsvectorlayer.h"
40#include "qgsfeature.h"
41#include "qgsgeometry.h"
42#include "qgsmultipoint.h"
44#include "qgslinestring.h"
45#include "qgspolygon.h"
46#include "qgsclipper.h"
47#include "qgsproperty.h"
49#include "qgsapplication.h"
52#include "qgslegendpatchshape.h"
53#include "qgsgeos.h"
54#include "qgsmarkersymbol.h"
55#include "qgslinesymbol.h"
56#include "qgsfillsymbol.h"
57#include "qgsfillsymbollayer.h"
58#include "qgscolorutils.h"
59#include "qgsunittypes.h"
61#include "qgspainting.h"
62
63QgsPropertiesDefinition QgsSymbol::sPropertyDefinitions;
64
65
66//
67// QgsSymbolBufferSettings
68//
69
71{
72 mFillSymbol = std::make_unique< QgsFillSymbol >( QgsSymbolLayerList() << new QgsSimpleFillSymbolLayer( QColor( 255, 255, 255 ), Qt::SolidPattern, QColor( 200, 200, 200 ), Qt::NoPen ) );
73}
74
76 : mEnabled( other.mEnabled )
77 , mSize( other.mSize )
78 , mSizeUnit( other.mSizeUnit )
79 , mSizeMapUnitScale( other.mSizeMapUnitScale )
80 , mJoinStyle( other.mJoinStyle )
81 , mFillSymbol( other.mFillSymbol ? other.mFillSymbol->clone() : nullptr )
82{
83
84}
85
87{
88 mEnabled = other.mEnabled;
89 mSize = other.mSize;
90 mSizeUnit = other.mSizeUnit;
91 mSizeMapUnitScale = other.mSizeMapUnitScale;
92 mJoinStyle = other.mJoinStyle;
93 mFillSymbol.reset( other.mFillSymbol ? other.mFillSymbol->clone() : nullptr );
94 return *this;
95}
96
98{
99 return mFillSymbol.get();
100}
101
103{
104 mFillSymbol.reset( symbol );
105}
106
108
109void QgsSymbolBufferSettings::writeXml( QDomElement &element, const QgsReadWriteContext &context ) const
110{
111 QDomElement symbolBufferElem = element.ownerDocument().createElement( QStringLiteral( "buffer" ) );
112 symbolBufferElem.setAttribute( QStringLiteral( "enabled" ), mEnabled );
113 symbolBufferElem.setAttribute( QStringLiteral( "size" ), mSize );
114 symbolBufferElem.setAttribute( QStringLiteral( "sizeUnits" ), QgsUnitTypes::encodeUnit( mSizeUnit ) );
115 symbolBufferElem.setAttribute( QStringLiteral( "sizeMapUnitScale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mSizeMapUnitScale ) );
116 symbolBufferElem.setAttribute( QStringLiteral( "joinStyle" ), static_cast< unsigned int >( mJoinStyle ) );
117
118 if ( mFillSymbol )
119 {
120 QDomDocument document = element.ownerDocument();
121 const QDomElement fillElem = QgsSymbolLayerUtils::saveSymbol( QString(), mFillSymbol.get(), document, context );
122 symbolBufferElem.appendChild( fillElem );
123 }
124
125 element.appendChild( symbolBufferElem );
126}
127
128void QgsSymbolBufferSettings::readXml( const QDomElement &element, const QgsReadWriteContext &context )
129{
130 const QDomElement symbolBufferElem = element.firstChildElement( QStringLiteral( "buffer" ) );
131 mEnabled = symbolBufferElem.attribute( QStringLiteral( "enabled" ), QStringLiteral( "0" ) ).toInt();
132 mSize = symbolBufferElem.attribute( QStringLiteral( "size" ), QStringLiteral( "1" ) ).toDouble();
133 mSizeUnit = QgsUnitTypes::decodeRenderUnit( symbolBufferElem.attribute( QStringLiteral( "sizeUnits" ) ) );
134 mSizeMapUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( symbolBufferElem.attribute( QStringLiteral( "sizeMapUnitScale" ) ) );
135 mJoinStyle = static_cast< Qt::PenJoinStyle >( symbolBufferElem.attribute( QStringLiteral( "joinStyle" ), QString::number( Qt::RoundJoin ) ).toUInt() );
136
137 const QDomElement fillSymbolElem = symbolBufferElem.firstChildElement( QStringLiteral( "symbol" ) );
138 if ( !fillSymbolElem.isNull() )
139 {
140 mFillSymbol.reset( QgsSymbolLayerUtils::loadSymbol<QgsFillSymbol>( fillSymbolElem, context ) );
141 }
142 else
143 {
144 mFillSymbol = std::make_unique< QgsFillSymbol >( QgsSymbolLayerList() << new QgsSimpleFillSymbolLayer( QColor( 255, 255, 255 ), Qt::SolidPattern, QColor( 200, 200, 200 ), Qt::NoPen ) );
145 }
146}
147
148
149//
150// QgsSymbol
151//
152
153Q_NOWARN_DEPRECATED_PUSH // because of deprecated mLayer
155 : mType( type )
156 , mLayers( layers )
157{
158
159 // check they're all correct symbol layers
160 for ( int i = 0; i < mLayers.count(); i++ )
161 {
162 if ( !mLayers.at( i ) )
163 {
164 mLayers.removeAt( i-- );
165 }
166 else if ( !mLayers.at( i )->isCompatibleWithSymbol( this ) )
167 {
168 delete mLayers.at( i );
169 mLayers.removeAt( i-- );
170 }
171 }
172}
174
175QPolygonF QgsSymbol::_getLineString( QgsRenderContext &context, const QgsCurve &curve, bool clipToExtent )
176{
177 if ( curve.is3D() )
178 return _getLineString3d( context, curve, clipToExtent );
179 else
180 return _getLineString2d( context, curve, clipToExtent );
181}
182
183QPolygonF QgsSymbol::_getLineString3d( QgsRenderContext &context, const QgsCurve &curve, bool clipToExtent )
184{
185 const unsigned int nPoints = curve.numPoints();
186
188 const QgsMapToPixel &mtp = context.mapToPixel();
189 QVector< double > pointsX;
190 QVector< double > pointsY;
191 QVector< double > pointsZ;
192
193 // apply clipping for large lines to achieve a better rendering performance
194 if ( clipToExtent && nPoints > 1 && !( context.flags() & Qgis::RenderContextFlag::ApplyClipAfterReprojection ) )
195 {
196 const QgsRectangle e = context.extent();
197 const double cw = e.width() / 10;
198 const double ch = e.height() / 10;
199 const QgsBox3D clipRect( e.xMinimum() - cw, e.yMinimum() - ch, -HUGE_VAL, e.xMaximum() + cw, e.yMaximum() + ch, HUGE_VAL ); // TODO also need to be clipped according to z axis
200
201 const QgsLineString *lineString = nullptr;
202 std::unique_ptr< QgsLineString > segmentized;
203 if ( const QgsLineString *ls = qgsgeometry_cast< const QgsLineString * >( &curve ) )
204 {
205 lineString = ls;
206 }
207 else
208 {
209 segmentized.reset( qgsgeometry_cast< QgsLineString * >( curve.segmentize( ) ) );
210 lineString = segmentized.get();
211 }
212
213 QgsClipper::clipped3dLine( lineString->xVector(), lineString->yVector(), lineString->zVector(), pointsX, pointsY, pointsZ, clipRect );
214 }
215 else
216 {
217 // clone...
218 if ( const QgsLineString *ls = qgsgeometry_cast<const QgsLineString *>( &curve ) )
219 {
220 pointsX = ls->xVector();
221 pointsY = ls->yVector();
222 pointsZ = ls->zVector();
223 }
224 else
225 {
226 std::unique_ptr< QgsLineString > segmentized;
227 segmentized.reset( qgsgeometry_cast< QgsLineString * >( curve.segmentize( ) ) );
228
229 pointsX = segmentized->xVector();
230 pointsY = segmentized->yVector();
231 pointsZ = segmentized->zVector();
232 }
233 }
234
235 // transform the points to screen coordinates
236 const QVector< double > preTransformPointsZ = pointsZ;
237 bool wasTransformed = false;
238 if ( ct.isValid() )
239 {
240 //create x, y arrays
241 const int nVertices = pointsX.size();
242 wasTransformed = true;
243
244 try
245 {
246 ct.transformCoords( nVertices, pointsX.data(), pointsY.data(), pointsZ.data(), Qgis::TransformDirection::Forward );
247 }
248 catch ( QgsCsException & )
249 {
250 // we don't abort the rendering here, instead we remove any invalid points and just plot those which ARE valid
251 }
252 }
253
254 // remove non-finite points, e.g. infinite or NaN points caused by reprojecting errors
255 {
256 const int size = pointsX.size();
257
258 const double *xIn = pointsX.data();
259 const double *yIn = pointsY.data();
260 const double *zIn = pointsZ.data();
261
262 const double *preTransformZIn = wasTransformed ? preTransformPointsZ.constData() : nullptr;
263
264 double *xOut = pointsX.data();
265 double *yOut = pointsY.data();
266 double *zOut = pointsZ.data();
267 int outSize = 0;
268 for ( int i = 0; i < size; ++i )
269 {
270 bool pointOk = std::isfinite( *xIn ) && std::isfinite( *yIn );
271
272 // skip z points which have been made non-finite during transformations only. Ie if:
273 // - we did no transformation, then always render even if non-finite z
274 // - we did transformation and z is finite then render
275 // - we did transformation and z is non-finite BUT input z was also non finite then render
276 // - we did transformation and z is non-finite AND input z WAS finite then skip
277 pointOk &= !wasTransformed || std::isfinite( *zIn ) || !std::isfinite( *preTransformZIn );
278
279 if ( pointOk )
280 {
281 *xOut++ = *xIn++;
282 *yOut++ = *yIn++;
283 *zOut++ = *zIn++;
284 outSize++;
285 }
286 else
287 {
288 xIn++;
289 yIn++;
290 zIn++;
291 }
292
293 if ( preTransformZIn )
294 preTransformZIn++;
295 }
296 pointsX.resize( outSize );
297 pointsY.resize( outSize );
298 pointsZ.resize( outSize );
299 }
300
301 if ( clipToExtent && nPoints > 1 && context.flags() & Qgis::RenderContextFlag::ApplyClipAfterReprojection )
302 {
303 // early clipping was not possible, so we have to apply it here after transformation
304 const QgsRectangle e = context.mapExtent();
305 const double cw = e.width() / 10;
306 const double ch = e.height() / 10;
307 const QgsBox3D clipRect( e.xMinimum() - cw, e.yMinimum() - ch, -HUGE_VAL, e.xMaximum() + cw, e.yMaximum() + ch, HUGE_VAL ); // TODO also need to be clipped according to z axis
308
309 QVector< double > tempX;
310 QVector< double > tempY;
311 QVector< double > tempZ;
312 QgsClipper::clipped3dLine( pointsX, pointsY, pointsZ, tempX, tempY, tempZ, clipRect );
313 pointsX = tempX;
314 pointsY = tempY;
315 pointsZ = tempZ;
316 }
317
318 const int polygonSize = pointsX.size();
319 QPolygonF out( polygonSize );
320 const double *x = pointsX.constData();
321 const double *y = pointsY.constData();
322 QPointF *dest = out.data();
323 for ( int i = 0; i < polygonSize; ++i )
324 {
325 double screenX = *x++;
326 double screenY = *y++;
327 mtp.transformInPlace( screenX, screenY );
328 *dest++ = QPointF( screenX, screenY );
329 }
330
331 return out;
332}
333
334QPolygonF QgsSymbol::_getLineString2d( QgsRenderContext &context, const QgsCurve &curve, bool clipToExtent )
335{
336 const unsigned int nPoints = curve.numPoints();
337
339 const QgsMapToPixel &mtp = context.mapToPixel();
340 QPolygonF pts;
341
342 // apply clipping for large lines to achieve a better rendering performance
343 if ( clipToExtent && nPoints > 1 && !( context.flags() & Qgis::RenderContextFlag::ApplyClipAfterReprojection ) )
344 {
345 const QgsRectangle e = context.extent();
346 const double cw = e.width() / 10;
347 const double ch = e.height() / 10;
348 const QgsRectangle clipRect( e.xMinimum() - cw, e.yMinimum() - ch, e.xMaximum() + cw, e.yMaximum() + ch );
349 pts = QgsClipper::clippedLine( curve, clipRect );
350 }
351 else
352 {
353 pts = curve.asQPolygonF();
354 }
355
356 // transform the QPolygonF to screen coordinates
357 if ( ct.isValid() )
358 {
359 try
360 {
361 ct.transformPolygon( pts );
362 }
363 catch ( QgsCsException & )
364 {
365 // we don't abort the rendering here, instead we remove any invalid points and just plot those which ARE valid
366 }
367 }
368
369 // remove non-finite points, e.g. infinite or NaN points caused by reprojecting errors
370 pts.erase( std::remove_if( pts.begin(), pts.end(),
371 []( const QPointF point )
372 {
373 return !std::isfinite( point.x() ) || !std::isfinite( point.y() );
374 } ), pts.end() );
375
376 if ( clipToExtent && nPoints > 1 && context.flags() & Qgis::RenderContextFlag::ApplyClipAfterReprojection )
377 {
378 // early clipping was not possible, so we have to apply it here after transformation
379 const QgsRectangle e = context.mapExtent();
380 const double cw = e.width() / 10;
381 const double ch = e.height() / 10;
382 const QgsRectangle clipRect( e.xMinimum() - cw, e.yMinimum() - ch, e.xMaximum() + cw, e.yMaximum() + ch );
383 pts = QgsClipper::clippedLine( pts, clipRect );
384 }
385
386 QPointF *ptr = pts.data();
387 for ( int i = 0; i < pts.size(); ++i, ++ptr )
388 {
389 mtp.transformInPlace( ptr->rx(), ptr->ry() );
390 }
391
392 return pts;
393}
394
395
396QPolygonF QgsSymbol::_getPolygonRing( QgsRenderContext &context, const QgsCurve &curve, const bool clipToExtent, const bool isExteriorRing, const bool correctRingOrientation )
397{
398 if ( curve.is3D() )
399 return _getPolygonRing3d( context, curve, clipToExtent, isExteriorRing, correctRingOrientation );
400 else
401 return _getPolygonRing2d( context, curve, clipToExtent, isExteriorRing, correctRingOrientation );
402}
403
404QPolygonF QgsSymbol::_getPolygonRing3d( QgsRenderContext &context, const QgsCurve &curve, const bool clipToExtent, const bool isExteriorRing, const bool correctRingOrientation )
405{
406 const QgsCoordinateTransform ct = context.coordinateTransform();
407 const QgsMapToPixel &mtp = context.mapToPixel();
408
409 QVector< double > pointsX;
410 QVector< double > pointsY;
411 QVector< double > pointsZ;
412
413 if ( curve.numPoints() < 1 )
414 return QPolygonF();
415
416 bool reverseRing = false;
417 if ( correctRingOrientation )
418 {
419 // ensure consistent polygon ring orientation
420 if ( ( isExteriorRing && curve.orientation() != Qgis::AngularDirection::Clockwise ) || ( !isExteriorRing && curve.orientation() != Qgis::AngularDirection::CounterClockwise ) )
421 {
422 reverseRing = true;
423 }
424 }
425
426 //clip close to view extent, if needed
427 if ( clipToExtent && !( context.flags() & Qgis::RenderContextFlag::ApplyClipAfterReprojection ) && !context.extent().contains( curve.boundingBox() ) )
428 {
429 const QgsRectangle e = context.extent();
430 const double cw = e.width() / 10;
431 const double ch = e.height() / 10;
432 const QgsBox3D clipRect( e.xMinimum() - cw, e.yMinimum() - ch, -HUGE_VAL, e.xMaximum() + cw, e.yMaximum() + ch, HUGE_VAL ); // TODO also need to be clipped according to z axis
433
434 const QgsLineString *lineString = nullptr;
435 std::unique_ptr< QgsLineString > segmentized;
436 if ( const QgsLineString *ls = qgsgeometry_cast< const QgsLineString * >( &curve ) )
437 {
438 lineString = ls;
439 }
440 else
441 {
442 segmentized.reset( qgsgeometry_cast< QgsLineString * >( curve.segmentize( ) ) );
443 lineString = segmentized.get();
444 }
445
446 pointsX = lineString->xVector();
447 pointsY = lineString->yVector();
448 pointsZ = lineString->zVector();
449
450 QgsClipper::trimPolygon( pointsX, pointsY, pointsZ, clipRect );
451 }
452 else
453 {
454 // clone...
455 if ( const QgsLineString *ls = qgsgeometry_cast<const QgsLineString *>( &curve ) )
456 {
457 pointsX = ls->xVector();
458 pointsY = ls->yVector();
459 pointsZ = ls->zVector();
460 }
461 else
462 {
463 std::unique_ptr< QgsLineString > segmentized;
464 segmentized.reset( qgsgeometry_cast< QgsLineString * >( curve.segmentize( ) ) );
465
466 pointsX = segmentized->xVector();
467 pointsY = segmentized->yVector();
468 pointsZ = segmentized->zVector();
469 }
470 }
471
472 if ( reverseRing )
473 {
474 std::reverse( pointsX.begin(), pointsX.end() );
475 std::reverse( pointsY.begin(), pointsY.end() );
476 std::reverse( pointsZ.begin(), pointsZ.end() );
477 }
478
479 //transform the QPolygonF to screen coordinates
480 const QVector< double > preTransformPointsZ = pointsZ;
481 bool wasTransformed = false;
482 if ( ct.isValid() )
483 {
484 const int nVertices = pointsX.size();
485 wasTransformed = true;
486 try
487 {
488 ct.transformCoords( nVertices, pointsX.data(), pointsY.data(), pointsZ.data(), Qgis::TransformDirection::Forward );
489 }
490 catch ( QgsCsException & )
491 {
492 // we don't abort the rendering here, instead we remove any invalid points and just plot those which ARE valid
493 }
494 }
495
496 // remove non-finite points, e.g. infinite or NaN points caused by reprojecting errors
497 {
498 const int size = pointsX.size();
499
500 const double *xIn = pointsX.data();
501 const double *yIn = pointsY.data();
502 const double *zIn = pointsZ.data();
503
504 const double *preTransformZIn = wasTransformed ? preTransformPointsZ.constData() : nullptr;
505
506 double *xOut = pointsX.data();
507 double *yOut = pointsY.data();
508 double *zOut = pointsZ.data();
509 int outSize = 0;
510 for ( int i = 0; i < size; ++i )
511 {
512 bool pointOk = std::isfinite( *xIn ) && std::isfinite( *yIn );
513 // skip z points which have been made non-finite during transformations only. Ie if:
514 // - we did no transformation, then always render even if non-finite z
515 // - we did transformation and z is finite then render
516 // - we did transformation and z is non-finite BUT input z was also non finite then render
517 // - we did transformation and z is non-finite AND input z WAS finite then skip
518 pointOk &= !wasTransformed || std::isfinite( *zIn ) || !std::isfinite( *preTransformZIn );
519
520 if ( pointOk )
521 {
522 *xOut++ = *xIn++;
523 *yOut++ = *yIn++;
524 *zOut++ = *zIn++;
525 outSize++;
526 }
527 else
528 {
529 xIn++;
530 yIn++;
531 zIn++;
532 }
533
534 if ( preTransformZIn )
535 preTransformZIn++;
536 }
537 pointsX.resize( outSize );
538 pointsY.resize( outSize );
539 pointsZ.resize( outSize );
540 }
541
542 if ( clipToExtent && context.flags() & Qgis::RenderContextFlag::ApplyClipAfterReprojection && !context.mapExtent().contains( curve.boundingBox() ) )
543 {
544 // early clipping was not possible, so we have to apply it here after transformation
545 const QgsRectangle e = context.mapExtent();
546 const double cw = e.width() / 10;
547 const double ch = e.height() / 10;
548 const QgsBox3D clipRect( e.xMinimum() - cw, e.yMinimum() - ch, -HUGE_VAL, e.xMaximum() + cw, e.yMaximum() + ch, HUGE_VAL ); // TODO also need to be clipped according to z axis
549
550 QgsClipper::trimPolygon( pointsX, pointsY, pointsZ, clipRect );
551 }
552
553 const int polygonSize = pointsX.size();
554 QPolygonF out( polygonSize );
555 const double *x = pointsX.constData();
556 const double *y = pointsY.constData();
557 QPointF *dest = out.data();
558 for ( int i = 0; i < polygonSize; ++i )
559 {
560 double screenX = *x++;
561 double screenY = *y++;
562 mtp.transformInPlace( screenX, screenY );
563 *dest++ = QPointF( screenX, screenY );
564 }
565
566 if ( !out.empty() && !out.isClosed() )
567 out << out.at( 0 );
568
569 return out;
570}
571
572
573QPolygonF QgsSymbol::_getPolygonRing2d( QgsRenderContext &context, const QgsCurve &curve, const bool clipToExtent, const bool isExteriorRing, const bool correctRingOrientation )
574{
575 const QgsCoordinateTransform ct = context.coordinateTransform();
576 const QgsMapToPixel &mtp = context.mapToPixel();
577
578 QPolygonF poly = curve.asQPolygonF();
579
580 if ( curve.numPoints() < 1 )
581 return QPolygonF();
582
583 if ( correctRingOrientation )
584 {
585 // ensure consistent polygon ring orientation
586 if ( isExteriorRing && curve.orientation() != Qgis::AngularDirection::Clockwise )
587 std::reverse( poly.begin(), poly.end() );
588 else if ( !isExteriorRing && curve.orientation() != Qgis::AngularDirection::CounterClockwise )
589 std::reverse( poly.begin(), poly.end() );
590 }
591
592 //clip close to view extent, if needed
593 if ( clipToExtent && !( context.flags() & Qgis::RenderContextFlag::ApplyClipAfterReprojection ) && !context.extent().contains( poly.boundingRect() ) )
594 {
595 const QgsRectangle e = context.extent();
596 const double cw = e.width() / 10;
597 const double ch = e.height() / 10;
598 const QgsRectangle clipRect( e.xMinimum() - cw, e.yMinimum() - ch, e.xMaximum() + cw, e.yMaximum() + ch );
599 QgsClipper::trimPolygon( poly, clipRect );
600 }
601
602 //transform the QPolygonF to screen coordinates
603 if ( ct.isValid() )
604 {
605 try
606 {
607 ct.transformPolygon( poly );
608 }
609 catch ( QgsCsException & )
610 {
611 // we don't abort the rendering here, instead we remove any invalid points and just plot those which ARE valid
612 }
613 }
614
615 // remove non-finite points, e.g. infinite or NaN points caused by reprojecting errors
616 poly.erase( std::remove_if( poly.begin(), poly.end(),
617 []( const QPointF point )
618 {
619 return !std::isfinite( point.x() ) || !std::isfinite( point.y() );
620 } ), poly.end() );
621
622 if ( clipToExtent && context.flags() & Qgis::RenderContextFlag::ApplyClipAfterReprojection && !context.mapExtent().contains( poly.boundingRect() ) )
623 {
624 // early clipping was not possible, so we have to apply it here after transformation
625 const QgsRectangle e = context.mapExtent();
626 const double cw = e.width() / 10;
627 const double ch = e.height() / 10;
628 const QgsRectangle clipRect( e.xMinimum() - cw, e.yMinimum() - ch, e.xMaximum() + cw, e.yMaximum() + ch );
629 QgsClipper::trimPolygon( poly, clipRect );
630 }
631
632 QPointF *ptr = poly.data();
633 for ( int i = 0; i < poly.size(); ++i, ++ptr )
634 {
635 mtp.transformInPlace( ptr->rx(), ptr->ry() );
636 }
637
638 if ( !poly.empty() && !poly.isClosed() )
639 poly << poly.at( 0 );
640
641 return poly;
642}
643
644void QgsSymbol::_getPolygon( QPolygonF &pts, QVector<QPolygonF> &holes, QgsRenderContext &context, const QgsPolygon &polygon, const bool clipToExtent, const bool correctRingOrientation )
645{
646 holes.clear();
647
648 pts = _getPolygonRing( context, *polygon.exteriorRing(), clipToExtent, true, correctRingOrientation );
649 const int ringCount = polygon.numInteriorRings();
650 holes.reserve( ringCount );
651 for ( int idx = 0; idx < ringCount; idx++ )
652 {
653 const QPolygonF hole = _getPolygonRing( context, *( polygon.interiorRing( idx ) ), clipToExtent, false, correctRingOrientation );
654 if ( !hole.isEmpty() )
655 holes.append( hole );
656 }
657}
658
660{
661 switch ( type )
662 {
664 return QObject::tr( "Marker" );
666 return QObject::tr( "Line" );
668 return QObject::tr( "Fill" );
670 return QObject::tr( "Hybrid" );
671 }
672 return QString();
673}
674
691
693{
694 QgsSymbol::initPropertyDefinitions();
695 return sPropertyDefinitions;
696}
697
699{
700 // delete all symbol layers (we own them, so it's okay)
701 qDeleteAll( mLayers );
702}
703
705{
706 if ( mLayers.empty() )
707 {
709 }
710
711 QgsSymbolLayerList::const_iterator it = mLayers.constBegin();
712
713 Qgis::RenderUnit unit = ( *it )->outputUnit();
714
715 for ( ; it != mLayers.constEnd(); ++it )
716 {
717 if ( ( *it )->outputUnit() != unit )
718 {
720 }
721 }
722 return unit;
723}
724
726{
727 if ( mLayers.empty() )
728 {
729 return false;
730 }
731
732 for ( const QgsSymbolLayer *layer : mLayers )
733 {
734 if ( layer->usesMapUnits() )
735 {
736 return true;
737 }
738 }
739 return false;
740}
741
743{
744 if ( mLayers.empty() )
745 {
746 return QgsMapUnitScale();
747 }
748
749 QgsSymbolLayerList::const_iterator it = mLayers.constBegin();
750 if ( it == mLayers.constEnd() )
751 return QgsMapUnitScale();
752
753 QgsMapUnitScale scale = ( *it )->mapUnitScale();
754 ++it;
755
756 for ( ; it != mLayers.constEnd(); ++it )
757 {
758 if ( ( *it )->mapUnitScale() != scale )
759 {
760 return QgsMapUnitScale();
761 }
762 }
763 return scale;
764}
765
767{
768 const auto constMLayers = mLayers;
769 for ( QgsSymbolLayer *layer : constMLayers )
770 {
771 layer->setOutputUnit( u );
772 }
773}
774
776{
777 const auto constMLayers = mLayers;
778 for ( QgsSymbolLayer *layer : constMLayers )
779 {
780 layer->setMapUnitScale( scale );
781 }
782}
783
788
790{
791 return mBufferSettings.get();
792}
793
795{
796 if ( mBufferSettings.get() == settings )
797 return;
798 mBufferSettings.reset( settings );
799}
800
805
810
812{
813 mAnimationSettings = settings;
814}
815
817{
818 std::unique_ptr< QgsSymbol > s;
819
820 // override global default if project has a default for this type
821 switch ( geomType )
822 {
824 s.reset( QgsProject::instance()->styleSettings()->defaultSymbol( Qgis::SymbolType::Marker ) ); // skip-keyword-check
825 break;
827 s.reset( QgsProject::instance()->styleSettings()->defaultSymbol( Qgis::SymbolType::Line ) ); // skip-keyword-check
828 break;
830 s.reset( QgsProject::instance()->styleSettings()->defaultSymbol( Qgis::SymbolType::Fill ) ); // skip-keyword-check
831 break;
832 default:
833 break;
834 }
835
836 // if no default found for this type, get global default (as previously)
837 if ( !s )
838 {
839 switch ( geomType )
840 {
842 s = std::make_unique< QgsMarkerSymbol >();
843 break;
845 s = std::make_unique< QgsLineSymbol >();
846 break;
848 s = std::make_unique< QgsFillSymbol >();
849 break;
850 default:
851 QgsDebugError( QStringLiteral( "unknown layer's geometry type" ) );
852 break;
853 }
854 }
855
856 if ( !s )
857 return nullptr;
858
859 // set opacity
860 s->setOpacity( QgsProject::instance()->styleSettings()->defaultSymbolOpacity() ); // skip-keyword-check
861
862 // set random color, it project prefs allow
863 if ( QgsProject::instance()->styleSettings()->randomizeDefaultSymbolColor() ) // skip-keyword-check
864 {
865 s->setColor( QgsApplication::colorSchemeRegistry()->fetchRandomStyleColor() );
866 }
867
868 const bool isCmyk = QgsProject::instance()->styleSettings() && QgsProject::instance()->styleSettings()->colorModel() == Qgis::ColorModel::Cmyk; // skip-keyword-check
869 if ( s->color().spec() == QColor::Spec::Rgb && isCmyk )
870 {
871 s->setColor( s->color().toCmyk() );
872 }
873 else if ( s->color().spec() == QColor::Spec::Cmyk && !isCmyk )
874 {
875 s->setColor( s->color().toRgb() );
876 }
877
878 return s.release();
879}
880
882{
883 return mLayers.value( layer );
884}
885
886const QgsSymbolLayer *QgsSymbol::symbolLayer( int layer ) const
887{
888 return mLayers.value( layer );
889}
890
892{
893 if ( index < 0 || index > mLayers.count() ) // can be added also after the last index
894 return false;
895
896 if ( !layer || !layer->isCompatibleWithSymbol( this ) )
897 return false;
898
899 mLayers.insert( index, layer );
900 return true;
901}
902
903
905{
906 if ( !layer || !layer->isCompatibleWithSymbol( this ) )
907 return false;
908
909 mLayers.append( layer );
910 return true;
911}
912
913
915{
916 if ( index < 0 || index >= mLayers.count() )
917 return false;
918
919 delete mLayers.at( index );
920 mLayers.removeAt( index );
921 return true;
922}
923
924
926{
927 if ( index < 0 || index >= mLayers.count() )
928 return nullptr;
929
930 return mLayers.takeAt( index );
931}
932
933
935{
936 QgsSymbolLayer *oldLayer = mLayers.value( index );
937
938 if ( oldLayer == layer )
939 return false;
940
941 if ( !layer || !layer->isCompatibleWithSymbol( this ) )
942 return false;
943
944 delete oldLayer; // first delete the original layer
945 mLayers[index] = layer; // set new layer
946 return true;
947}
948
949
950void QgsSymbol::startRender( QgsRenderContext &context, const QgsFields &fields )
951{
952 Q_ASSERT_X( !mStarted, "startRender", "Rendering has already been started for this symbol instance!" );
953 mStarted = true;
954
956
957 mSymbolRenderContext.reset( new QgsSymbolRenderContext( context, Qgis::RenderUnit::Unknown, mOpacity, false, renderHints, nullptr, fields ) );
958
959 // Why do we need a copy here ? Is it to make sure the symbol layer rendering does not mess with the symbol render context ?
960 // Or is there another profound reason ?
961 QgsSymbolRenderContext symbolContext( context, Qgis::RenderUnit::Unknown, mOpacity, false, renderHints, nullptr, fields );
962
963 std::unique_ptr< QgsExpressionContextScope > scope( QgsExpressionContextUtils::updateSymbolScope( this, new QgsExpressionContextScope() ) );
964
966 {
967 const long long mapFrameNumber = context.currentFrame();
968 double animationTimeSeconds = 0;
969 if ( mapFrameNumber >= 0 && context.frameRate() > 0 )
970 {
971 // render is part of an animation, so we base the calculated frame on that
972 animationTimeSeconds = mapFrameNumber / context.frameRate();
973 }
974 else
975 {
976 // render is outside of animation, so base the calculated frame on the current epoch
977 animationTimeSeconds = QDateTime::currentMSecsSinceEpoch() / 1000.0;
978 }
979
980 const long long symbolFrame = static_cast< long long >( std::floor( animationTimeSeconds * mAnimationSettings.frameRate() ) );
981 scope->setVariable( QStringLiteral( "symbol_frame" ), symbolFrame, true );
982 }
983
984 mSymbolRenderContext->setExpressionContextScope( scope.release() );
985
986 mDataDefinedProperties.prepare( context.expressionContext() );
987
988 if ( mBufferSettings && mBufferSettings->enabled() && mBufferSettings->fillSymbol() )
989 {
990 mBufferSettings->fillSymbol()->startRender( context, fields );
991 }
992
993 for ( QgsSymbolLayer *layer : std::as_const( mLayers ) )
994 {
995 if ( !layer->enabled() || !context.isSymbolLayerEnabled( layer ) )
996 continue;
997
998 layer->prepareExpressions( symbolContext );
999
1000 // We prepare "entire map" clip masks in advance only in certain circumstances. These are non-optimal,
1001 // because the entire map mask will be applied once for every feature rendered, resulting in overly complex
1002 // clipping paths with paths which fall well outside of the map area that is actually being drawn on for the
1003 // feature. These circumstances are:
1004 // 1. If we are rendering a sub symbol. The current logic relating to calculating per-feature masks
1005 // is not designed to handle sub symbol rendering where layers from the subsymbol have their own set of
1006 // clipping paths, so we just fallback to the non-optimal approach always for these cases.
1007 // TODO:
1008 // - we could add another special condition here to check whether the subsymbol actually does have unique
1009 // clipping paths in its symbol layers, or whether they are identical to the parent symbol layer's clipping paths.
1010 // 2. When the symbol layer type doesn't explicitly state that it's compatible with per-feature mask geometries
1011 // 3. When per feature mask geometry is explicitly disabled for the render context
1012 // In other circumstances we do NOT prepare masks in advance, and instead calculate them in renderFeature().
1016 layer->prepareMasks( symbolContext );
1017 layer->startRender( symbolContext );
1018 }
1019}
1020
1022{
1023 Q_ASSERT_X( mStarted, "startRender", "startRender was not called for this symbol instance!" );
1024 mStarted = false;
1025
1026 Q_UNUSED( context )
1027 if ( mSymbolRenderContext )
1028 {
1029 const auto constMLayers = mLayers;
1030 for ( QgsSymbolLayer *layer : constMLayers )
1031 {
1032 if ( !layer->enabled() || !context.isSymbolLayerEnabled( layer ) )
1033 continue;
1034
1035 layer->stopRender( *mSymbolRenderContext );
1036 }
1037 }
1038
1039 if ( mBufferSettings && mBufferSettings->enabled() && mBufferSettings->fillSymbol() )
1040 {
1041 mBufferSettings->fillSymbol()->stopRender( context );
1042 }
1043
1044 mSymbolRenderContext.reset( nullptr );
1045
1047 mLayer = nullptr;
1049}
1050
1051void QgsSymbol::setColor( const QColor &color ) const
1052{
1053 const auto constMLayers = mLayers;
1054 for ( QgsSymbolLayer *layer : constMLayers )
1055 {
1056 if ( !layer->isLocked() )
1057 layer->setColor( color );
1058 }
1059}
1060
1061QColor QgsSymbol::color() const
1062{
1063 for ( const QgsSymbolLayer *layer : mLayers )
1064 {
1065 // return color of the first unlocked layer
1066 if ( !layer->isLocked() )
1067 {
1068 const QColor layerColor = layer->color();
1069 if ( layerColor.isValid() )
1070 return layerColor;
1071 }
1072 }
1073 return QColor( 0, 0, 0 );
1074}
1075
1076void QgsSymbol::drawPreviewIcon( QPainter *painter, QSize size, QgsRenderContext *customContext, bool selected, const QgsExpressionContext *expressionContext, const QgsLegendPatchShape *patchShape, const QgsScreenProperties &screen )
1077{
1078 QgsRenderContext *context = customContext;
1079 std::unique_ptr< QgsRenderContext > tempContext;
1080 if ( !context )
1081 {
1082 tempContext.reset( new QgsRenderContext( QgsRenderContext::fromQPainter( painter ) ) );
1083 context = tempContext.get();
1085 }
1086
1087 if ( screen.isValid() )
1088 {
1089 screen.updateRenderContextForScreen( *context );
1090 }
1091
1092 const bool prevForceVector = context->forceVectorOutput();
1093 context->setForceVectorOutput( true );
1094
1095 const double opacity = expressionContext ? dataDefinedProperties().valueAsDouble( QgsSymbol::Property::Opacity, *expressionContext, mOpacity * 100 ) * 0.01 : mOpacity;
1096
1097 QgsSymbolRenderContext symbolContext( *context, Qgis::RenderUnit::Unknown, opacity, false, renderHints(), nullptr );
1098 symbolContext.setSelected( selected );
1099 switch ( mType )
1100 {
1103 break;
1106 break;
1109 break;
1112 break;
1113 }
1114
1115 if ( patchShape )
1116 symbolContext.setPatchShape( *patchShape );
1117
1118 if ( !customContext && expressionContext )
1119 {
1120 context->setExpressionContext( *expressionContext );
1121 }
1122 else if ( !customContext )
1123 {
1124 // if no render context was passed, build a minimal expression context
1125 QgsExpressionContext expContext;
1127 context->setExpressionContext( expContext );
1128 }
1129
1130 const bool usingBuffer = mBufferSettings && mBufferSettings->enabled() && mBufferSettings->fillSymbol();
1131 // handle symbol buffers -- we do this by deferring the rendering of the symbol and redirecting
1132 // to QPictures, and then using the actual rendered shape from the QPictures to determine the buffer shape.
1133 QPainter *originalTargetPainter = nullptr;
1134 // this is an array, we need to separate out the symbol layers if we're drawing only one symbol level
1135 std::unique_ptr< QPicture > pictureForDeferredRendering;
1136 std::unique_ptr< QPainter > deferredRenderingPainter;
1137 if ( usingBuffer )
1138 {
1139 originalTargetPainter = context->painter();
1140 pictureForDeferredRendering = std::make_unique< QPicture >();
1141 deferredRenderingPainter = std::make_unique< QPainter >( pictureForDeferredRendering.get() );
1142 context->setPainter( deferredRenderingPainter.get() );
1143 }
1144
1145 for ( QgsSymbolLayer *layer : std::as_const( mLayers ) )
1146 {
1147 if ( !layer->enabled() || ( customContext && !customContext->isSymbolLayerEnabled( layer ) ) )
1148 continue;
1149
1151 {
1152 // line symbol layer would normally draw just a line
1153 // so we override this case to force it to draw a polygon stroke
1154 QgsLineSymbolLayer *lsl = dynamic_cast<QgsLineSymbolLayer *>( layer );
1155 if ( lsl )
1156 {
1157 // from QgsFillSymbolLayer::drawPreviewIcon() -- would be nicer to add the
1158 // symbol type to QgsSymbolLayer::drawPreviewIcon so this logic could be avoided!
1159
1160 // hmm... why was this using size -1 ??
1161 const QSizeF targetSize = QSizeF( size.width() - 1, size.height() - 1 );
1162
1163 const QList< QList< QPolygonF > > polys = patchShape ? patchShape->toQPolygonF( Qgis::SymbolType::Fill, targetSize )
1165
1166 lsl->startRender( symbolContext );
1167 QgsPaintEffect *effect = lsl->paintEffect();
1168
1169 std::unique_ptr< QgsEffectPainter > effectPainter;
1170 if ( effect && effect->enabled() )
1171 effectPainter = std::make_unique< QgsEffectPainter >( symbolContext.renderContext(), effect );
1172
1173 for ( const QList< QPolygonF > &poly : polys )
1174 {
1175 QVector< QPolygonF > rings;
1176 rings.reserve( poly.size() );
1177 for ( int i = 1; i < poly.size(); ++i )
1178 rings << poly.at( i );
1179 lsl->renderPolygonStroke( poly.value( 0 ), &rings, symbolContext );
1180 }
1181
1182 effectPainter.reset();
1183 lsl->stopRender( symbolContext );
1184 }
1185 }
1186 else
1187 layer->drawPreviewIcon( symbolContext, size );
1188 }
1189
1190 // if required, render the calculated buffer below the symbol
1191 if ( usingBuffer )
1192 {
1193 deferredRenderingPainter->end();
1194 deferredRenderingPainter.reset();
1195
1196 QgsGeometryPaintDevice geometryPaintDevice;
1197 QPainter geometryPainter( &geometryPaintDevice );
1198 QgsPainting::drawPicture( &geometryPainter, QPointF( 0, 0 ), *pictureForDeferredRendering );
1199 geometryPainter.end();
1200
1201 // retrieve the shape of the rendered symbol
1202 const QgsGeometry renderedShape( geometryPaintDevice.geometry().clone() );
1203
1204 context->setPainter( originalTargetPainter );
1205
1206 // next, buffer out the rendered shape, and draw!
1207 const double bufferSize = context->convertToPainterUnits( mBufferSettings->size(), mBufferSettings->sizeUnit(), mBufferSettings->sizeMapUnitScale() );
1209 switch ( mBufferSettings->joinStyle() )
1210 {
1211 case Qt::MiterJoin:
1212 case Qt::SvgMiterJoin:
1213 joinStyle = Qgis::JoinStyle::Miter;
1214 break;
1215 case Qt::BevelJoin:
1216 joinStyle = Qgis::JoinStyle::Bevel;
1217 break;
1218 case Qt::RoundJoin:
1219 joinStyle = Qgis::JoinStyle::Round;
1220 break;
1221
1222 case Qt::MPenJoinStyle:
1223 break;
1224 }
1225
1226 const QgsGeometry bufferedGeometry = renderedShape.buffer( bufferSize, 8, Qgis::EndCapStyle::Round, joinStyle, 2 );
1227 const QList<QList<QPolygonF> > polygons = QgsSymbolLayerUtils::toQPolygonF( bufferedGeometry, Qgis::SymbolType::Fill );
1228
1229 mBufferSettings->fillSymbol()->startRender( *context );
1230 for ( const QList< QPolygonF > &polygon : polygons )
1231 {
1232 QVector< QPolygonF > rings;
1233 for ( int i = 1; i < polygon.size(); ++i )
1234 rings << polygon.at( i );
1235 mBufferSettings->fillSymbol()->renderPolygon( polygon.value( 0 ), &rings, nullptr, *context );
1236 }
1237 mBufferSettings->fillSymbol()->stopRender( *context );
1238
1239 // finally, draw the actual rendered symbol on top
1240 QgsPainting::drawPicture( context->painter(), QPointF( 0, 0 ), *pictureForDeferredRendering );
1241 }
1242
1243 context->setForceVectorOutput( prevForceVector );
1244}
1245
1246void QgsSymbol::exportImage( const QString &path, const QString &format, QSize size )
1247{
1248 if ( format.compare( QLatin1String( "svg" ), Qt::CaseInsensitive ) == 0 )
1249 {
1250 QSvgGenerator generator;
1251 generator.setFileName( path );
1252 generator.setSize( size );
1253 generator.setViewBox( QRect( 0, 0, size.height(), size.height() ) );
1254
1255 QPainter painter( &generator );
1256 drawPreviewIcon( &painter, size );
1257 painter.end();
1258 }
1259 else
1260 {
1261 QImage image = asImage( size );
1262 image.save( path );
1263 }
1264}
1265
1266QImage QgsSymbol::asImage( QSize size, QgsRenderContext *customContext )
1267{
1268 QImage image( size, QImage::Format_ARGB32_Premultiplied );
1269 image.fill( 0 );
1270
1271 QPainter p( &image );
1272 p.setRenderHint( QPainter::Antialiasing );
1273 p.setRenderHint( QPainter::SmoothPixmapTransform );
1274
1275 drawPreviewIcon( &p, size, customContext );
1276
1277 return image;
1278}
1279
1280
1282{
1283 const double devicePixelRatio = screen.isValid() ? screen.devicePixelRatio() : 1;
1284 QImage preview( QSize( 100, 100 ) * devicePixelRatio, QImage::Format_ARGB32_Premultiplied );
1285 preview.fill( 0 );
1286 preview.setDevicePixelRatio( devicePixelRatio );
1287
1288 QPainter p( &preview );
1289 p.setRenderHint( QPainter::Antialiasing );
1290 p.translate( 0.5, 0.5 ); // shift by half a pixel to avoid blurring due antialiasing
1291
1293 {
1294 p.setPen( QPen( Qt::gray ) );
1295 p.drawLine( QLineF( 0, 50, 100, 50 ) );
1296 p.drawLine( QLineF( 50, 0, 50, 100 ) );
1297 }
1298
1303 context.setPainterFlagsUsingContext( &p );
1304
1305 if ( screen.isValid() )
1306 {
1307 screen.updateRenderContextForScreen( context );
1308 }
1309
1310 if ( expressionContext )
1311 context.setExpressionContext( *expressionContext );
1312
1313 context.setIsGuiPreview( true );
1314 startRender( context );
1315
1317 {
1318 QPolygonF poly;
1319 poly << QPointF( 0, 50 ) << QPointF( 99, 50 );
1320 static_cast<QgsLineSymbol *>( this )->renderPolyline( poly, nullptr, context );
1321 }
1322 else if ( mType == Qgis::SymbolType::Fill )
1323 {
1324 QPolygonF polygon;
1325 polygon << QPointF( 20, 20 ) << QPointF( 80, 20 ) << QPointF( 80, 80 ) << QPointF( 20, 80 ) << QPointF( 20, 20 );
1326 static_cast<QgsFillSymbol *>( this )->renderPolygon( polygon, nullptr, nullptr, context );
1327 }
1328 else // marker
1329 {
1330 static_cast<QgsMarkerSymbol *>( this )->renderPoint( QPointF( 50, 50 ), nullptr, context );
1331 }
1332
1333 stopRender( context );
1334 return preview;
1335}
1336
1337QImage QgsSymbol::bigSymbolPreviewImage( QgsExpressionContext *expressionContext, int flags )
1338{
1339 return bigSymbolPreviewImage( expressionContext, static_cast< Qgis::SymbolPreviewFlags >( flags ) );
1340}
1341
1342QString QgsSymbol::dump() const
1343{
1344 QString t;
1345 switch ( type() )
1346 {
1348 t = QStringLiteral( "MARKER" );
1349 break;
1351 t = QStringLiteral( "LINE" );
1352 break;
1354 t = QStringLiteral( "FILL" );
1355 break;
1356 default:
1357 Q_ASSERT( false && "unknown symbol type" );
1358 }
1359 QString s = QStringLiteral( "%1 SYMBOL (%2 layers) color %3" ).arg( t ).arg( mLayers.count() ).arg( QgsColorUtils::colorToString( color() ) );
1360
1361 for ( QgsSymbolLayerList::const_iterator it = mLayers.begin(); it != mLayers.end(); ++it )
1362 {
1363 // TODO:
1364 }
1365 return s;
1366}
1367
1368void QgsSymbol::toSld( QDomDocument &doc, QDomElement &element, QVariantMap props ) const
1369{
1370 props[ QStringLiteral( "alpha" )] = QString::number( opacity() );
1371 double scaleFactor = 1.0;
1372 props[ QStringLiteral( "uom" )] = QgsSymbolLayerUtils::encodeSldUom( outputUnit(), &scaleFactor );
1373 props[ QStringLiteral( "uomScale" )] = ( !qgsDoubleNear( scaleFactor, 1.0 ) ? qgsDoubleToString( scaleFactor ) : QString() );
1374
1375 for ( QgsSymbolLayerList::const_iterator it = mLayers.begin(); it != mLayers.end(); ++it )
1376 {
1377 ( *it )->toSld( doc, element, props );
1378 }
1379}
1380
1382{
1384 for ( QgsSymbolLayerList::const_iterator it = mLayers.begin(); it != mLayers.end(); ++it )
1385 {
1386 QgsSymbolLayer *layer = ( *it )->clone();
1387 layer->setLocked( ( *it )->isLocked() );
1388 layer->setRenderingPass( ( *it )->renderingPass() );
1389 layer->setEnabled( ( *it )->enabled() );
1390 layer->setId( ( *it )->id() );
1391 layer->setUserFlags( ( *it )->userFlags() );
1392 lst.append( layer );
1393 }
1394 return lst;
1395}
1396
1397void QgsSymbol::renderUsingLayer( QgsSymbolLayer *layer, QgsSymbolRenderContext &context, Qgis::GeometryType geometryType, const QPolygonF *points, const QVector<QPolygonF> *rings )
1398{
1399 Q_ASSERT( layer->type() == Qgis::SymbolType::Hybrid );
1400
1401 if ( layer->dataDefinedProperties().hasActiveProperties() && !layer->dataDefinedProperties().valueAsBool( QgsSymbolLayer::Property::LayerEnabled, context.renderContext().expressionContext(), true ) )
1402 return;
1403
1404 QgsGeometryGeneratorSymbolLayer *generatorLayer = static_cast<QgsGeometryGeneratorSymbolLayer *>( layer );
1405
1406 QgsPaintEffect *effect = generatorLayer->paintEffect();
1407 if ( effect && effect->enabled() )
1408 {
1409 QgsEffectPainter p( context.renderContext(), effect );
1410 generatorLayer->render( context, geometryType, points, rings );
1411 }
1412 else
1413 {
1414 generatorLayer->render( context, geometryType, points, rings );
1415 }
1416}
1417
1418QSet<QString> QgsSymbol::usedAttributes( const QgsRenderContext &context ) const
1419{
1420 // calling referencedFields() with ignoreContext=true because in our expression context
1421 // we do not have valid QgsFields yet - because of that the field names from expressions
1422 // wouldn't get reported
1423 QSet<QString> attributes = mDataDefinedProperties.referencedFields( context.expressionContext(), true );
1424 QgsSymbolLayerList::const_iterator sIt = mLayers.constBegin();
1425 for ( ; sIt != mLayers.constEnd(); ++sIt )
1426 {
1427 if ( *sIt )
1428 {
1429 attributes.unite( ( *sIt )->usedAttributes( context ) );
1430 }
1431 }
1432 if ( mBufferSettings && mBufferSettings->enabled() && mBufferSettings->fillSymbol() )
1433 {
1434 attributes.unite( mBufferSettings->fillSymbol()->usedAttributes( context ) );
1435 }
1436 return attributes;
1437}
1438
1440{
1441 mDataDefinedProperties.setProperty( key, property );
1442}
1443
1445{
1446 if ( mDataDefinedProperties.hasActiveProperties() )
1447 return true;
1448
1449 for ( QgsSymbolLayer *layer : mLayers )
1450 {
1451 if ( layer->hasDataDefinedProperties() )
1452 return true;
1453 }
1454 return false;
1455}
1456
1458{
1459 for ( QgsSymbolLayer *layer : mLayers )
1460 {
1461 if ( layer->canCauseArtifactsBetweenAdjacentTiles() )
1462 return true;
1463 }
1464 return false;
1465}
1466
1473
1480
1482
1486class ExpressionContextScopePopper
1487{
1488 public:
1489
1490 ExpressionContextScopePopper() = default;
1491
1492 ~ExpressionContextScopePopper()
1493 {
1494 if ( context )
1495 context->popScope();
1496 }
1497
1498 QgsExpressionContext *context = nullptr;
1499};
1500
1504class GeometryRestorer
1505{
1506 public:
1507 GeometryRestorer( QgsRenderContext &context )
1508 : mContext( context ),
1509 mGeometry( context.geometry() )
1510 {}
1511
1512 ~GeometryRestorer()
1513 {
1514 mContext.setGeometry( mGeometry );
1515 }
1516
1517 private:
1518 QgsRenderContext &mContext;
1519 const QgsAbstractGeometry *mGeometry;
1520};
1522
1523void QgsSymbol::renderFeature( const QgsFeature &feature, QgsRenderContext &context, int layer, bool selected, bool drawVertexMarker, Qgis::VertexMarkerType currentVertexMarkerType, double currentVertexMarkerSize )
1524{
1525 if ( context.renderingStopped() )
1526 return;
1527
1528 const QgsGeometry geom = feature.geometry();
1529 if ( geom.isNull() )
1530 {
1531 return;
1532 }
1533
1534 GeometryRestorer geomRestorer( context );
1535
1536 bool usingSegmentizedGeometry = false;
1537 context.setGeometry( geom.constGet() );
1538
1539 if ( geom.type() != Qgis::GeometryType::Point && !geom.boundingBox().isNull() )
1540 {
1541 try
1542 {
1543 const QPointF boundsOrigin = _getPoint( context, QgsPoint( geom.boundingBox().xMinimum(), geom.boundingBox().yMinimum() ) );
1544 if ( std::isfinite( boundsOrigin.x() ) && std::isfinite( boundsOrigin.y() ) )
1545 context.setTextureOrigin( boundsOrigin );
1546 }
1547 catch ( QgsCsException & )
1548 {
1549
1550 }
1551 }
1552
1553 bool clippingEnabled = clipFeaturesToExtent();
1554 // do any symbol layers prevent feature clipping?
1555 for ( QgsSymbolLayer *layer : std::as_const( mLayers ) )
1556 {
1558 {
1559 clippingEnabled = false;
1560 break;
1561 }
1562 }
1564 if ( clippingEnabled && context.testFlag( Qgis::RenderContextFlag::RenderMapTile ) )
1565 {
1566 // If the "avoid artifacts between adjacent tiles" flag is set (RenderMapTile), then we'll force disable
1567 // the geometry clipping IF (and only if) this symbol can potentially have rendering artifacts when rendered as map tiles.
1568 // If the symbol won't have any artifacts anyway, then it's pointless and incredibly expensive to skip the clipping!
1570 {
1571 clippingEnabled = false;
1572 }
1573 }
1574 if ( context.extent().isEmpty() )
1575 clippingEnabled = false;
1576
1577 mSymbolRenderContext->setGeometryPartCount( geom.constGet()->partCount() );
1578 mSymbolRenderContext->setGeometryPartNum( 1 );
1579
1580 const bool needsExpressionContext = hasDataDefinedProperties();
1581 ExpressionContextScopePopper scopePopper;
1582 if ( mSymbolRenderContext->expressionContextScope() )
1583 {
1584 if ( needsExpressionContext )
1585 {
1586 // this is somewhat nasty - by appending this scope here it's now owned
1587 // by both mSymbolRenderContext AND context.expressionContext()
1588 // the RAII scopePopper is required to make sure it always has ownership transferred back
1589 // from context.expressionContext(), even if exceptions of other early exits occur in this
1590 // function
1591 context.expressionContext().appendScope( mSymbolRenderContext->expressionContextScope() );
1592 scopePopper.context = &context.expressionContext();
1593
1594 QgsExpressionContextUtils::updateSymbolScope( this, mSymbolRenderContext->expressionContextScope() );
1595 mSymbolRenderContext->expressionContextScope()->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_PART_COUNT, mSymbolRenderContext->geometryPartCount(), true ) );
1596 mSymbolRenderContext->expressionContextScope()->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_PART_NUM, 1, true ) );
1597 }
1598 }
1599
1600 // Collection of markers to paint, only used for no curve types.
1601 QPolygonF markers;
1602
1603 QgsGeometry renderedBoundsGeom;
1604
1605 // Step 1 - collect the set of painter coordinate geometries to render.
1606 // We do this upfront, because we only want to ever do this once, regardless how many symbol layers we need to render.
1607
1608 struct PointInfo
1609 {
1610 QPointF renderPoint;
1611 const QgsPoint *originalGeometry = nullptr;
1612 };
1613 QVector< PointInfo > pointsToRender;
1614
1615 struct LineInfo
1616 {
1617 QPolygonF renderLine;
1618 const QgsCurve *originalGeometry = nullptr;
1619 };
1620 QVector< LineInfo > linesToRender;
1621
1622 struct PolygonInfo
1623 {
1624 QPolygonF renderExterior;
1625 QVector< QPolygonF > renderRings;
1626 const QgsCurvePolygon *originalGeometry = nullptr;
1627 int originalPartIndex = 0;
1628 };
1629 QVector< PolygonInfo > polygonsToRender;
1630
1631 std::function< void ( const QgsAbstractGeometry *, int partIndex )> getPartGeometry;
1632 getPartGeometry = [&pointsToRender, &linesToRender, &polygonsToRender, &getPartGeometry, &context, &clippingEnabled, &markers, &feature, &usingSegmentizedGeometry, this]( const QgsAbstractGeometry * part, int partIndex = 0 )
1633 {
1634 Q_UNUSED( feature )
1635
1636 if ( !part )
1637 return;
1638
1639 // geometry preprocessing
1640 QgsGeometry temporaryGeometryContainer;
1641 const QgsAbstractGeometry *processedGeometry = nullptr;
1642
1643 const bool isMultiPart = qgsgeometry_cast< const QgsGeometryCollection * >( part ) && qgsgeometry_cast< const QgsGeometryCollection * >( part )->numGeometries() > 1;
1644
1645 if ( !isMultiPart )
1646 {
1647 // segmentize curved geometries
1648 const bool needsSegmentizing = QgsWkbTypes::isCurvedType( part->wkbType() ) || part->hasCurvedSegments();
1649 if ( needsSegmentizing )
1650 {
1651 std::unique_ptr< QgsAbstractGeometry > segmentizedPart( part->segmentize( context.segmentationTolerance(), context.segmentationToleranceType() ) );
1652 if ( !segmentizedPart )
1653 {
1654 return;
1655 }
1656 temporaryGeometryContainer.set( segmentizedPart.release() );
1657 processedGeometry = temporaryGeometryContainer.constGet();
1658 usingSegmentizedGeometry = true;
1659 }
1660 else
1661 {
1662 // no segmentation required
1663 processedGeometry = part;
1664 }
1665
1666 // Simplify the geometry, if needed.
1668 {
1669 const int simplifyHints = context.vectorSimplifyMethod().simplifyHints();
1670 const QgsMapToPixelSimplifier simplifier( simplifyHints, context.vectorSimplifyMethod().tolerance(),
1672
1673 std::unique_ptr< QgsAbstractGeometry > simplified( simplifier.simplify( processedGeometry ) );
1674 if ( simplified )
1675 {
1676 temporaryGeometryContainer.set( simplified.release() );
1677 processedGeometry = temporaryGeometryContainer.constGet();
1678 }
1679 }
1680
1681 // clip geometry to render context clipping regions
1682 if ( !context.featureClipGeometry().isEmpty() )
1683 {
1684 // apply feature clipping from context to the rendered geometry only -- just like the render time simplification,
1685 // we should NEVER apply this to the geometry attached to the feature itself. Doing so causes issues with certain
1686 // renderer settings, e.g. if polygons are being rendered using a rule based renderer based on the feature's area,
1687 // then we need to ensure that the original feature area is used instead of the clipped area..
1688 QgsGeos geos( processedGeometry );
1689 std::unique_ptr< QgsAbstractGeometry > clippedGeom( geos.intersection( context.featureClipGeometry().constGet() ) );
1690 if ( clippedGeom )
1691 {
1692 temporaryGeometryContainer.set( clippedGeom.release() );
1693 processedGeometry = temporaryGeometryContainer.constGet();
1694 }
1695 }
1696 }
1697 else
1698 {
1699 // for multipart geometries, the processing is deferred till we're rendering the actual part...
1700 processedGeometry = part;
1701 }
1702
1703 if ( !processedGeometry )
1704 {
1705 // shouldn't happen!
1706 QgsDebugError( QStringLiteral( "No processed geometry to render for part!" ) );
1707 return;
1708 }
1709
1710 switch ( QgsWkbTypes::flatType( processedGeometry->wkbType() ) )
1711 {
1713 {
1715 {
1716 QgsDebugMsgLevel( QStringLiteral( "point can be drawn only with marker symbol!" ), 2 );
1717 break;
1718 }
1719
1720 PointInfo info;
1721 info.originalGeometry = qgsgeometry_cast< const QgsPoint * >( part );
1722 info.renderPoint = _getPoint( context, *info.originalGeometry );
1723 pointsToRender << info;
1724 break;
1725 }
1726
1728 {
1730 {
1731 QgsDebugMsgLevel( QStringLiteral( "linestring can be drawn only with line symbol!" ), 2 );
1732 break;
1733 }
1734
1735 LineInfo info;
1736 info.originalGeometry = qgsgeometry_cast<const QgsCurve *>( part );
1737 info.renderLine = _getLineString( context, *qgsgeometry_cast<const QgsCurve *>( processedGeometry ), clippingEnabled );
1738 linesToRender << info;
1739 break;
1740 }
1741
1744 {
1745 QPolygonF pts;
1747 {
1748 QgsDebugMsgLevel( QStringLiteral( "polygon can be drawn only with fill symbol!" ), 2 );
1749 break;
1750 }
1751
1752 PolygonInfo info;
1753 info.originalGeometry = qgsgeometry_cast<const QgsCurvePolygon *>( part );
1754 info.originalPartIndex = partIndex;
1755 if ( !qgsgeometry_cast<const QgsPolygon *>( processedGeometry )->exteriorRing() )
1756 {
1757 QgsDebugError( QStringLiteral( "cannot render polygon with no exterior ring" ) );
1758 break;
1759 }
1760
1761 _getPolygon( info.renderExterior, info.renderRings, context, *qgsgeometry_cast<const QgsPolygon *>( processedGeometry ), clippingEnabled, mForceRHR );
1762 polygonsToRender << info;
1763 break;
1764 }
1765
1767 {
1768 const QgsMultiPoint *mp = qgsgeometry_cast< const QgsMultiPoint * >( processedGeometry );
1769 markers.reserve( mp->numGeometries() );
1770 }
1771 [[fallthrough]];
1775 {
1776 const QgsGeometryCollection *geomCollection = qgsgeometry_cast<const QgsGeometryCollection *>( processedGeometry );
1777
1778 const unsigned int num = geomCollection->numGeometries();
1779 for ( unsigned int i = 0; i < num; ++i )
1780 {
1781 if ( context.renderingStopped() )
1782 break;
1783
1784 getPartGeometry( geomCollection->geometryN( i ), i );
1785 }
1786 break;
1787 }
1788
1791 {
1793 {
1794 QgsDebugMsgLevel( QStringLiteral( "multi-polygon can be drawn only with fill symbol!" ), 2 );
1795 break;
1796 }
1797
1798 QPolygonF pts;
1799
1800 const QgsGeometryCollection *geomCollection = dynamic_cast<const QgsGeometryCollection *>( processedGeometry );
1801 const unsigned int num = geomCollection->numGeometries();
1802
1803 // Sort components by approximate area (probably a bit faster than using
1804 // area() )
1805 std::map<double, QList<unsigned int> > thisAreaToPartNum;
1806 for ( unsigned int i = 0; i < num; ++i )
1807 {
1808 const QgsRectangle r( geomCollection->geometryN( i )->boundingBox() );
1809 thisAreaToPartNum[ r.width() * r.height()] << i;
1810 }
1811
1812 // Draw starting with larger parts down to smaller parts, so that in
1813 // case of a part being incorrectly inside another part, it is drawn
1814 // on top of it (#15419)
1815 std::map<double, QList<unsigned int> >::const_reverse_iterator iter = thisAreaToPartNum.rbegin();
1816 for ( ; iter != thisAreaToPartNum.rend(); ++iter )
1817 {
1818 const QList<unsigned int> &listPartIndex = iter->second;
1819 for ( int idx = 0; idx < listPartIndex.size(); ++idx )
1820 {
1821 const unsigned i = listPartIndex[idx];
1822 getPartGeometry( geomCollection->geometryN( i ), i );
1823 }
1824 }
1825 break;
1826 }
1827
1829 case Qgis::WkbType::TIN:
1830 {
1831 const QgsPolyhedralSurface *polySurface = qgsgeometry_cast<const QgsPolyhedralSurface *>( processedGeometry );
1832
1833 const int num = polySurface->numPatches();
1834 for ( int i = 0; i < num; ++i )
1835 {
1836 if ( context.renderingStopped() )
1837 break;
1838
1839 getPartGeometry( polySurface->patchN( i ), i );
1840 }
1841 break;
1842 }
1843
1844 default:
1845 QgsDebugError( QStringLiteral( "feature %1: unsupported wkb type %2/%3 for rendering" )
1846 .arg( feature.id() )
1847 .arg( QgsWkbTypes::displayString( part->wkbType() ) )
1848 .arg( static_cast< quint32>( part->wkbType() ), 0, 16 ) );
1849 }
1850 };
1851
1852 // Use the simplified type ref when rendering -- this avoids some unnecessary cloning/geometry modification
1853 // (e.g. if the original geometry is a compound curve containing only a linestring curve, we don't have
1854 // to segmentize the geometry before rendering)
1855 getPartGeometry( geom.constGet()->simplifiedTypeRef(), 0 );
1856
1857 // If we're drawing using symbol levels, we only draw buffers for the bottom most level
1858 const bool usingBuffer = ( layer == -1 || layer == 0 ) && mBufferSettings && mBufferSettings->enabled() && mBufferSettings->fillSymbol();
1859
1860 // step 2 - determine which layers to render
1861 std::vector< int > allLayers;
1862 allLayers.reserve( mLayers.count() );
1863 for ( int i = 0; i < mLayers.count(); ++i )
1864 allLayers.emplace_back( i );
1865
1866 std::vector< int > layerToRender;
1867 if ( layer == -1 )
1868 {
1869 layerToRender = allLayers;
1870 }
1871 else
1872 {
1873 // if we're rendering using a buffer, then we'll need to draw ALL symbol layers in order to calculate the
1874 // buffer shape, but then ultimately we'll ONLY draw the target layer on top.
1875 if ( usingBuffer )
1876 layerToRender = allLayers;
1877 else
1878 layerToRender.emplace_back( layer );
1879 }
1880
1881 // step 3 - render these geometries using the desired symbol layers.
1882
1883 if ( needsExpressionContext )
1884 mSymbolRenderContext->expressionContextScope()->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "symbol_layer_count" ), mLayers.count(), true ) );
1885
1886 const bool maskGeometriesDisabledForSymbol = context.testFlag( Qgis::RenderContextFlag::AlwaysUseGlobalMasks )
1888
1889 // handle symbol buffers -- we do this by deferring the rendering of the symbol and redirecting
1890 // to QPictures, and then using the actual rendered shape from the QPictures to determine the buffer shape.
1891 QPainter *originalTargetPainter = nullptr;
1892 // this is an array, we need to separate out the symbol layers if we're drawing only one symbol level
1893 std::vector< QPicture > picturesForDeferredRendering;
1894 std::unique_ptr< QPainter > deferredRenderingPainter;
1895 if ( usingBuffer )
1896 {
1897 originalTargetPainter = context.painter();
1898 picturesForDeferredRendering.emplace_back( QPicture() );
1899 deferredRenderingPainter = std::make_unique< QPainter >( &picturesForDeferredRendering.front() );
1900 context.setPainter( deferredRenderingPainter.get() );
1901 }
1902
1903 const bool prevExcludeBuffers = mSymbolRenderContext->renderHints().testFlag( Qgis::SymbolRenderHint::ExcludeSymbolBuffers );
1904 // disable buffers when calling subclass render methods -- we've already handled them here
1905 mSymbolRenderContext->setRenderHint( Qgis::SymbolRenderHint::ExcludeSymbolBuffers, true );
1906
1907 for ( const int symbolLayerIndex : layerToRender )
1908 {
1909 if ( deferredRenderingPainter && layer != -1 && symbolLayerIndex != layerToRender.front() )
1910 {
1911 // if we're using deferred rendering along with symbol level drawing, we
1912 // start a new picture for each symbol layer drawn
1913 deferredRenderingPainter->end();
1914 picturesForDeferredRendering.emplace_back( QPicture() );
1915 deferredRenderingPainter->begin( &picturesForDeferredRendering.back() );
1916 }
1917
1918 QgsSymbolLayer *symbolLayer = mLayers.value( symbolLayerIndex );
1919 if ( !symbolLayer || !symbolLayer->enabled() )
1920 continue;
1921
1922 if ( needsExpressionContext )
1923 mSymbolRenderContext->expressionContextScope()->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "symbol_layer_index" ), symbolLayerIndex + 1, true ) );
1924
1925 // if this symbol layer has associated clip masks, we need to render it to a QPicture first so that we can
1926 // determine the actual rendered bounds of the symbol. We'll then use that to retrieve the clip masks we need
1927 // to apply when painting the symbol via this QPicture.
1928 const bool hasClipGeometries = !maskGeometriesDisabledForSymbol
1931 QPainter *previousPainter = nullptr;
1932 std::unique_ptr< QPicture > renderedPicture;
1933 std::unique_ptr< QPainter > picturePainter;
1934 if ( hasClipGeometries )
1935 {
1936 previousPainter = context.painter();
1937 renderedPicture = std::make_unique< QPicture >();
1938 picturePainter = std::make_unique< QPainter >( renderedPicture.get() );
1939 context.setPainter( picturePainter.get() );
1940 }
1941
1942 symbolLayer->startFeatureRender( feature, context );
1943
1944 switch ( mType )
1945 {
1947 {
1948 int geometryPartNumber = 0;
1949 for ( const PointInfo &point : std::as_const( pointsToRender ) )
1950 {
1951 if ( context.renderingStopped() )
1952 break;
1953
1954 mSymbolRenderContext->setGeometryPartNum( geometryPartNumber + 1 );
1955 if ( needsExpressionContext )
1956 mSymbolRenderContext->expressionContextScope()->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_PART_NUM, geometryPartNumber + 1, true ) );
1957
1958 static_cast<QgsMarkerSymbol *>( this )->renderPoint( point.renderPoint, &feature, context, symbolLayerIndex, selected );
1959 geometryPartNumber++;
1960 }
1961
1962 break;
1963 }
1964
1966 {
1967 if ( linesToRender.empty() )
1968 break;
1969
1970 int geometryPartNumber = 0;
1971 for ( const LineInfo &line : std::as_const( linesToRender ) )
1972 {
1973 if ( context.renderingStopped() )
1974 break;
1975
1976 mSymbolRenderContext->setGeometryPartNum( geometryPartNumber + 1 );
1977 if ( needsExpressionContext )
1978 mSymbolRenderContext->expressionContextScope()->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_PART_NUM, geometryPartNumber + 1, true ) );
1979
1980 context.setGeometry( line.originalGeometry );
1981 static_cast<QgsLineSymbol *>( this )->renderPolyline( line.renderLine, &feature, context, symbolLayerIndex, selected );
1982 geometryPartNumber++;
1983 }
1984 break;
1985 }
1986
1988 {
1989 for ( const PolygonInfo &info : std::as_const( polygonsToRender ) )
1990 {
1991 if ( context.renderingStopped() )
1992 break;
1993
1994 mSymbolRenderContext->setGeometryPartNum( info.originalPartIndex + 1 );
1995 if ( needsExpressionContext )
1996 mSymbolRenderContext->expressionContextScope()->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_PART_NUM, info.originalPartIndex + 1, true ) );
1997
1998 context.setGeometry( info.originalGeometry );
1999 static_cast<QgsFillSymbol *>( this )->renderPolygon( info.renderExterior, ( !info.renderRings.isEmpty() ? &info.renderRings : nullptr ), &feature, context, symbolLayerIndex, selected );
2000 }
2001
2002 break;
2003 }
2004
2006 break;
2007 }
2008
2009 symbolLayer->stopFeatureRender( feature, context );
2010
2011 if ( hasClipGeometries )
2012 {
2013 // restore previous painter
2014 context.setPainter( previousPainter );
2015 picturePainter->end();
2016 picturePainter.reset();
2017
2018 // determine actual rendered bounds of symbol layer, and then buffer out a little to be safe
2019 QRectF maximalBounds = renderedPicture->boundingRect();
2020 constexpr double BOUNDS_MARGIN = 0.05;
2021 maximalBounds.adjust( -maximalBounds.width() * BOUNDS_MARGIN, -maximalBounds.height() * BOUNDS_MARGIN, maximalBounds.width() * BOUNDS_MARGIN, maximalBounds.height() * BOUNDS_MARGIN );
2022
2023 const bool hadClipping = context.painter()->hasClipping();
2024 const QPainterPath oldClipPath = hadClipping ? context.painter()->clipPath() : QPainterPath();
2025
2026 const bool isMasked = symbolLayer->installMasks( context, false, maximalBounds );
2027
2028 context.painter()->drawPicture( QPointF( 0, 0 ), *renderedPicture );
2029
2030 if ( isMasked )
2031 {
2032 context.painter()->setClipPath( oldClipPath );
2033 context.painter()->setClipping( hadClipping );
2034 }
2035 }
2036 }
2037
2038 // step 4 - if required, render the calculated buffer below the symbol
2039 if ( usingBuffer )
2040 {
2041 deferredRenderingPainter->end();
2042 deferredRenderingPainter.reset();
2043
2044 QgsGeometryPaintDevice geometryPaintDevice;
2045 QPainter geometryPainter( &geometryPaintDevice );
2046 // render all the symbol layers onto the geometry painter, so we can calculate a single
2047 // buffer for ALL of them
2048 for ( const auto &deferredPicture : picturesForDeferredRendering )
2049 {
2050 QgsPainting::drawPicture( &geometryPainter, QPointF( 0, 0 ), deferredPicture );
2051 }
2052 geometryPainter.end();
2053
2054 // retrieve the shape of the rendered symbol
2055 const QgsGeometry renderedShape( geometryPaintDevice.geometry().clone() );
2056
2057 context.setPainter( originalTargetPainter );
2058
2059 // next, buffer out the rendered shape, and draw!
2060 const double bufferSize = context.convertToPainterUnits( mBufferSettings->size(), mBufferSettings->sizeUnit(), mBufferSettings->sizeMapUnitScale() );
2062 switch ( mBufferSettings->joinStyle() )
2063 {
2064 case Qt::MiterJoin:
2065 case Qt::SvgMiterJoin:
2066 joinStyle = Qgis::JoinStyle::Miter;
2067 break;
2068 case Qt::BevelJoin:
2069 joinStyle = Qgis::JoinStyle::Bevel;
2070 break;
2071 case Qt::RoundJoin:
2072 joinStyle = Qgis::JoinStyle::Round;
2073 break;
2074
2075 case Qt::MPenJoinStyle:
2076 break;
2077 }
2078
2079 const QgsGeometry bufferedGeometry = renderedShape.buffer( bufferSize, 8, Qgis::EndCapStyle::Round, joinStyle, 2 );
2080 const QList<QList<QPolygonF> > polygons = QgsSymbolLayerUtils::toQPolygonF( bufferedGeometry, Qgis::SymbolType::Fill );
2081 for ( const QList< QPolygonF > &polygon : polygons )
2082 {
2083 QVector< QPolygonF > rings;
2084 for ( int i = 1; i < polygon.size(); ++i )
2085 rings << polygon.at( i );
2086 mBufferSettings->fillSymbol()->renderPolygon( polygon.value( 0 ), &rings, nullptr, context );
2087 }
2088
2089 // finally, draw the actual rendered symbol on top. If symbol levels are at play then this will ONLY
2090 // be the target symbol level, not all of them.
2091 QgsPainting::drawPicture( context.painter(), QPointF( 0, 0 ), picturesForDeferredRendering.front() );
2092 }
2093
2094 // step 5 - handle post processing steps
2095 switch ( mType )
2096 {
2098 {
2099 markers.reserve( pointsToRender.size() );
2100 for ( const PointInfo &info : std::as_const( pointsToRender ) )
2101 {
2103 {
2104 const QRectF bounds = static_cast<QgsMarkerSymbol *>( this )->bounds( info.renderPoint, context, feature );
2105 if ( context.hasRenderedFeatureHandlers() )
2106 {
2107 renderedBoundsGeom = renderedBoundsGeom.isNull() ? QgsGeometry::fromRect( bounds )
2108 : QgsGeometry::collectGeometry( QVector< QgsGeometry>() << QgsGeometry::fromRect( QgsRectangle( bounds ) ) << renderedBoundsGeom );
2109 }
2111 {
2112 //draw debugging rect
2113 context.painter()->setPen( Qt::red );
2114 context.painter()->setBrush( QColor( 255, 0, 0, 100 ) );
2115 context.painter()->drawRect( bounds );
2116 }
2117 }
2118
2119 if ( drawVertexMarker && !usingSegmentizedGeometry )
2120 {
2121 markers.append( info.renderPoint );
2122 }
2123 }
2124 break;
2125 }
2126
2128 {
2129 for ( const LineInfo &info : std::as_const( linesToRender ) )
2130 {
2131 if ( context.hasRenderedFeatureHandlers() && !info.renderLine.empty() )
2132 {
2133 renderedBoundsGeom = renderedBoundsGeom.isNull() ? QgsGeometry::fromQPolygonF( info.renderLine )
2134 : QgsGeometry::collectGeometry( QVector< QgsGeometry>() << QgsGeometry::fromQPolygonF( info.renderLine ) << renderedBoundsGeom );
2135 }
2136
2137 if ( drawVertexMarker && !usingSegmentizedGeometry )
2138 {
2139 markers << info.renderLine;
2140 }
2141 }
2142 break;
2143 }
2144
2146 {
2147 for ( const PolygonInfo &info : std::as_const( polygonsToRender ) )
2148 {
2149 if ( context.hasRenderedFeatureHandlers() && !info.renderExterior.empty() )
2150 {
2151 renderedBoundsGeom = renderedBoundsGeom.isNull() ? QgsGeometry::fromQPolygonF( info.renderExterior )
2152 : QgsGeometry::collectGeometry( QVector< QgsGeometry>() << QgsGeometry::fromQPolygonF( info.renderExterior ) << renderedBoundsGeom );
2153 // TODO: consider holes?
2154 }
2155
2156 if ( drawVertexMarker && !usingSegmentizedGeometry )
2157 {
2158 markers << info.renderExterior;
2159
2160 for ( const QPolygonF &hole : info.renderRings )
2161 {
2162 markers << hole;
2163 }
2164 }
2165 }
2166 break;
2167 }
2168
2170 break;
2171 }
2172
2173 mSymbolRenderContext->setRenderHint( Qgis::SymbolRenderHint::ExcludeSymbolBuffers, prevExcludeBuffers );
2174
2175 if ( context.hasRenderedFeatureHandlers() && !renderedBoundsGeom.isNull() )
2176 {
2178 const QList< QgsRenderedFeatureHandlerInterface * > handlers = context.renderedFeatureHandlers();
2179 for ( QgsRenderedFeatureHandlerInterface *handler : handlers )
2180 handler->handleRenderedFeature( feature, renderedBoundsGeom, featureContext );
2181 }
2182
2183 if ( drawVertexMarker )
2184 {
2185 if ( !markers.isEmpty() && !context.renderingStopped() )
2186 {
2187 const auto constMarkers = markers;
2188 for ( QPointF marker : constMarkers )
2189 {
2190 renderVertexMarker( marker, context, currentVertexMarkerType, currentVertexMarkerSize );
2191 }
2192 }
2193 else
2194 {
2196 const QgsMapToPixel &mtp = context.mapToPixel();
2197
2198 QgsPoint vertexPoint;
2199 QgsVertexId vertexId;
2200 double x, y, z;
2201 QPointF mapPoint;
2202 while ( geom.constGet()->nextVertex( vertexId, vertexPoint ) )
2203 {
2204 //transform
2205 x = vertexPoint.x();
2206 y = vertexPoint.y();
2207 z = 0.0;
2208 if ( ct.isValid() )
2209 {
2210 ct.transformInPlace( x, y, z );
2211 }
2212 mapPoint.setX( x );
2213 mapPoint.setY( y );
2214 mtp.transformInPlace( mapPoint.rx(), mapPoint.ry() );
2215 renderVertexMarker( mapPoint, context, currentVertexMarkerType, currentVertexMarkerSize );
2216 }
2217 }
2218 }
2219}
2220
2222{
2223 return mSymbolRenderContext.get();
2224}
2225
2226void QgsSymbol::renderVertexMarker( QPointF pt, QgsRenderContext &context, Qgis::VertexMarkerType currentVertexMarkerType, double currentVertexMarkerSize )
2227{
2228 int markerSize = context.convertToPainterUnits( currentVertexMarkerSize, Qgis::RenderUnit::Millimeters );
2229 QgsSymbolLayerUtils::drawVertexMarker( pt.x(), pt.y(), *context.painter(), currentVertexMarkerType, markerSize );
2230}
2231
2232void QgsSymbol::initPropertyDefinitions()
2233{
2234 if ( !sPropertyDefinitions.isEmpty() )
2235 return;
2236
2237 QString origin = QStringLiteral( "symbol" );
2238
2239 sPropertyDefinitions = QgsPropertiesDefinition
2240 {
2241 { static_cast< int >( QgsSymbol::Property::Opacity ), QgsPropertyDefinition( "alpha", QObject::tr( "Opacity" ), QgsPropertyDefinition::Opacity, origin )},
2242 };
2243}
2244
2245void QgsSymbol::startFeatureRender( const QgsFeature &feature, QgsRenderContext &context, const int layer )
2246{
2247 if ( layer != -1 )
2248 {
2250 if ( symbolLayer && symbolLayer->enabled() )
2251 {
2252 symbolLayer->startFeatureRender( feature, context );
2253 }
2254 return;
2255 }
2256 else
2257 {
2258 const QList< QgsSymbolLayer * > layers = mLayers;
2259 for ( QgsSymbolLayer *symbolLayer : layers )
2260 {
2261 if ( !symbolLayer->enabled() )
2262 continue;
2263
2264 symbolLayer->startFeatureRender( feature, context );
2265 }
2266 }
2267}
2268
2269void QgsSymbol::stopFeatureRender( const QgsFeature &feature, QgsRenderContext &context, int layer )
2270{
2271 if ( layer != -1 )
2272 {
2274 if ( symbolLayer && symbolLayer->enabled() )
2275 {
2276 symbolLayer->stopFeatureRender( feature, context );
2277 }
2278 return;
2279 }
2280 else
2281 {
2282 const QList< QgsSymbolLayer * > layers = mLayers;
2283 for ( QgsSymbolLayer *symbolLayer : layers )
2284 {
2285 if ( !symbolLayer->enabled() )
2286 continue;
2287
2288 symbolLayer->stopFeatureRender( feature, context );
2289 }
2290 }
2291}
2292
2294{
2295 mOpacity = other->mOpacity;
2297 mForceRHR = other->mForceRHR;
2298 mDataDefinedProperties = other->mDataDefinedProperties;
2299 mSymbolFlags = other->mSymbolFlags;
2301 if ( other->mBufferSettings )
2302 mBufferSettings = std::make_unique< QgsSymbolBufferSettings >( *other->mBufferSettings );
2303 else
2304 mBufferSettings.reset();
2305
2307 mLayer = other->mLayer;
2309}
2310
2312{
2314 if ( mBufferSettings && mBufferSettings->enabled() )
2315 {
2316 hints.setFlag( Qgis::SymbolRenderHint::ForceVectorRendering, true );
2317 }
2318 return hints;
2319
2320}
2321
2323{
2325 for ( const QgsSymbolLayer *layer : mLayers )
2326 {
2328 {
2329 res.setFlag( Qgis::SymbolFlag::AffectsLabeling );
2330 }
2331 }
2332 return res;
2333}
@ CounterClockwise
Counter-clockwise direction.
@ Clockwise
Clockwise direction.
@ ExcludeSymbolBuffers
Do not render symbol buffers.
@ IsSymbolLayerSubSymbol
Symbol is being rendered as a sub-symbol of a QgsSymbolLayer.
@ ForceVectorRendering
Symbol must be rendered using vector methods, and optimisations like pre-rendered images must be disa...
@ DisableFeatureClipping
If present, indicates that features should never be clipped to the map extent during rendering.
@ AffectsLabeling
If present, indicates that the symbol layer will participate in the map labeling problem.
@ CanCalculateMaskGeometryPerFeature
If present, indicates that mask geometry can safely be calculated per feature for the symbol layer....
QFlags< SymbolPreviewFlag > SymbolPreviewFlags
Symbol preview flags.
Definition qgis.h:801
GeometryType
The geometry types are used to group Qgis::WkbType in a coarse way.
Definition qgis.h:337
@ Polygon
Polygons.
@ Unknown
Unknown types.
@ Null
No geometry.
JoinStyle
Join styles for buffers.
Definition qgis.h:1968
@ Bevel
Use beveled joins.
@ Round
Use rounded joins.
@ Miter
Use mitered joins.
RenderUnit
Rendering size units.
Definition qgis.h:4839
@ Millimeters
Millimeters.
@ Unknown
Mixed or unknown units.
@ Round
Round cap.
@ DisableSymbolClippingToExtent
Force symbol clipping to map extent to be disabled in all situations. This will result in slower rend...
@ RenderSymbolPreview
The render is for a symbol preview only and map based properties may not be available,...
@ ApplyClipAfterReprojection
Feature geometry clipping to mapExtent() must be performed after the geometries are transformed using...
@ DrawSymbolBounds
Draw bounds of symbols (for debugging/testing)
@ RenderMapTile
Draw map such that there are no problems between adjacent tiles.
@ AlwaysUseGlobalMasks
When applying clipping paths for selective masking, always use global ("entire map") paths,...
@ Antialiasing
Use antialiasing while drawing.
@ HighQualityImageTransforms
Enable high quality image transformations, which results in better appearance of scaled or rotated ra...
@ FlagIncludeCrosshairsForMarkerSymbols
Include a crosshairs reference image in the background of marker symbol previews.
VertexMarkerType
Editing vertex markers, used for showing vertices during a edit operation.
Definition qgis.h:1680
QFlags< SymbolRenderHint > SymbolRenderHints
Symbol render hints.
Definition qgis.h:741
QFlags< SymbolFlag > SymbolFlags
Symbol flags.
Definition qgis.h:787
SymbolType
Symbol types.
Definition qgis.h:574
@ Marker
Marker symbol.
@ Line
Line symbol.
@ Fill
Fill symbol.
@ Hybrid
Hybrid symbol.
@ Cmyk
CMYK color model.
@ AffectsLabeling
If present, indicates that the symbol will participate in the map labeling problem.
@ LineString
LineString.
@ MultiPoint
MultiPoint.
@ Polygon
Polygon.
@ MultiPolygon
MultiPolygon.
@ Triangle
Triangle.
@ MultiLineString
MultiLineString.
@ GeometryCollection
GeometryCollection.
@ MultiCurve
MultiCurve.
@ PolyhedralSurface
PolyhedralSurface.
@ MultiSurface
MultiSurface.
@ Forward
Forward transform (from source to destination)
Abstract base class for all geometries.
virtual const QgsAbstractGeometry * simplifiedTypeRef() const
Returns a reference to the simplest lossless representation of this geometry, e.g.
virtual QgsRectangle boundingBox() const
Returns the minimal bounding box for the geometry.
bool is3D() const
Returns true if the geometry is 3D and contains a z-value.
Qgis::WkbType wkbType() const
Returns the WKB type of the geometry.
virtual int partCount() const =0
Returns count of parts contained in the geometry.
virtual bool hasCurvedSegments() const
Returns true if the geometry contains curved segments.
virtual bool nextVertex(QgsVertexId &id, QgsPoint &vertex) const =0
Returns next vertex id and coordinates.
virtual QgsAbstractGeometry * clone() const =0
Clones the geometry by performing a deep copy.
double valueAsDouble(int key, const QgsExpressionContext &context, double defaultValue=0.0, bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as a double.
static QgsColorSchemeRegistry * colorSchemeRegistry()
Returns the application's color scheme registry, used for managing color schemes.
A 3-dimensional box composed of x, y, z coordinates.
Definition qgsbox3d.h:43
static void trimPolygon(QPolygonF &pts, const QgsRectangle &clipRect)
Trims the given polygon to a rectangular box, by modifying the given polygon in place.
Definition qgsclipper.h:281
static QPolygonF clippedLine(const QgsCurve &curve, const QgsRectangle &clipExtent)
Takes a linestring and clips it to clipExtent.
static void clipped3dLine(const QVector< double > &xIn, const QVector< double > &yIn, const QVector< double > &zIn, QVector< double > &x, QVector< double > &y, QVector< double > &z, const QgsBox3D &clipExtent)
Takes a line with 3D coordinates and clips it to clipExtent.
static QString colorToString(const QColor &color)
Encodes a color into a string value.
Class for doing transforms between two map coordinate systems.
void transformCoords(int numPoint, double *x, double *y, double *z, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward) const
Transform an array of coordinates to the destination CRS.
void transformInPlace(double &x, double &y, double &z, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward) const
Transforms an array of x, y and z double coordinates in place, from the source CRS to the destination...
void transformPolygon(QPolygonF &polygon, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward) const
Transforms a polygon to the destination coordinate system.
bool isValid() const
Returns true if the coordinate transform is valid, ie both the source and destination CRS have been s...
Custom exception class for Coordinate Reference System related exceptions.
Curve polygon geometry type.
int numInteriorRings() const
Returns the number of interior rings contained with the curve polygon.
const QgsCurve * exteriorRing() const
Returns the curve polygon's exterior ring.
const QgsCurve * interiorRing(int i) const
Retrieves an interior ring from the curve polygon.
Abstract base class for curved geometry type.
Definition qgscurve.h:35
Qgis::AngularDirection orientation() const
Returns the curve's orientation, e.g.
Definition qgscurve.cpp:286
virtual int numPoints() const =0
Returns the number of points in the curve.
QgsCurve * segmentize(double tolerance=M_PI_2/90, SegmentationToleranceType toleranceType=MaximumAngle) const override
Returns a geometry without curves.
Definition qgscurve.cpp:175
virtual QPolygonF asQPolygonF() const
Returns a QPolygonF representing the points.
Definition qgscurve.cpp:266
A class to manager painter saving and restoring required for effect drawing.
Single scope for storing variables and functions for use within a QgsExpressionContext.
static QgsExpressionContextScope * updateSymbolScope(const QgsSymbol *symbol, QgsExpressionContextScope *symbolScope=nullptr)
Updates a symbol scope related to a QgsSymbol to an expression context.
static QList< QgsExpressionContextScope * > globalProjectLayerScopes(const QgsMapLayer *layer)
Creates a list of three scopes: global, layer's project and layer.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
static const QString EXPR_GEOMETRY_PART_COUNT
Inbuilt variable name for geometry part count variable.
static const QString EXPR_GEOMETRY_PART_NUM
Inbuilt variable name for geometry part number variable.
void appendScope(QgsExpressionContextScope *scope)
Appends a scope to the end of the context.
void appendScopes(const QList< QgsExpressionContextScope * > &scopes)
Appends a list of scopes to the end of the context.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition qgsfeature.h:58
QgsFeatureId id
Definition qgsfeature.h:66
QgsGeometry geometry
Definition qgsfeature.h:69
Container of fields for a vector layer.
Definition qgsfields.h:46
A fill symbol type, for rendering Polygon and MultiPolygon geometries.
int numGeometries() const
Returns the number of geometries within the collection.
const QgsAbstractGeometry * geometryN(int n) const
Returns a const reference to a geometry from within the collection.
A symbol layer subclass which alters rendered feature shapes through the use of QGIS expressions.
void render(QgsSymbolRenderContext &context, Qgis::GeometryType geometryType=Qgis::GeometryType::Unknown, const QPolygonF *points=nullptr, const QVector< QPolygonF > *rings=nullptr)
Will render this symbol layer using the context.
A paint device which converts everything renderer to a QgsGeometry representation of the rendered sha...
const QgsAbstractGeometry & geometry() const
Returns the rendered geometry.
A geometry is the spatial representation of a feature.
static QgsGeometry fromRect(const QgsRectangle &rect)
Creates a new geometry from a QgsRectangle.
static QgsGeometry collectGeometry(const QVector< QgsGeometry > &geometries)
Creates a new multipart geometry from a list of QgsGeometry objects.
static QgsGeometry fromQPolygonF(const QPolygonF &polygon)
Construct geometry from a QPolygonF.
const QgsAbstractGeometry * constGet() const
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
Qgis::GeometryType type
void set(QgsAbstractGeometry *geometry)
Sets the underlying geometry store.
QgsGeometry buffer(double distance, int segments) const
Returns a buffer region around this geometry having the given width and with a specified number of se...
bool isEmpty() const
Returns true if the geometry is empty (eg a linestring with no vertices, or a collection with no geom...
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
Does vector analysis using the geos library and handles import, export, exception handling*.
Definition qgsgeos.h:137
Represents a patch shape for use in map legends.
QList< QList< QPolygonF > > toQPolygonF(Qgis::SymbolType type, QSizeF size) const
Converts the patch shape to a set of QPolygonF objects representing how the patch should be drawn for...
Line string geometry type, with support for z-dimension and m-values.
QVector< double > xVector() const
Returns the x vertex values as a vector.
QVector< double > yVector() const
Returns the y vertex values as a vector.
QVector< double > zVector() const
Returns the z vertex values as a vector.
Abstract base class for line symbol layers.
virtual void renderPolygonStroke(const QPolygonF &points, const QVector< QPolygonF > *rings, QgsSymbolRenderContext &context)
Renders the line symbol layer along the outline of polygon, using the given render context.
A line symbol type, for rendering LineString and MultiLineString geometries.
bool setId(const QString &id)
Sets the layer's id.
QgsMapLayer::LayerFlags flags() const
Returns the flags for this layer.
Qgis::LayerType type
Definition qgsmaplayer.h:86
Implementation of GeometrySimplifier using the "MapToPixel" algorithm.
QgsGeometry simplify(const QgsGeometry &geometry) const override
Returns a simplified version the specified geometry.
Perform transforms between map coordinates and device coordinates.
void transformInPlace(double &x, double &y) const
Transforms map coordinates to device coordinates.
Struct for storing maximum and minimum scales for measurements in map units.
A marker symbol type, for rendering Point and MultiPoint geometries.
Multi point geometry collection.
Base class for visual effects which can be applied to QPicture drawings.
bool enabled() const
Returns whether the effect is enabled.
static void drawPicture(QPainter *painter, const QPointF &point, const QPicture &picture)
Draws a picture onto a painter, correctly applying workarounds to avoid issues with incorrect scaling...
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:49
double x
Definition qgspoint.h:52
double y
Definition qgspoint.h:53
Polygon geometry type.
Definition qgspolygon.h:33
Polyhedral surface geometry type.
int numPatches() const
Returns the number of patches contained with the polyhedral surface.
const QgsPolygon * patchN(int i) const
Retrieves a patch from the polyhedral surface.
Qgis::ColorModel colorModel() const
Returns the project's color model.
static QgsProject * instance()
Returns the QgsProject singleton instance.
const QgsProjectStyleSettings * styleSettings() const
Returns the project's style settings, which contains settings and properties relating to how a QgsPro...
void setProperty(int key, const QgsProperty &property)
Adds a property to the collection and takes ownership of it.
bool prepare(const QgsExpressionContext &context=QgsExpressionContext()) const final
Prepares the collection against a specified expression context.
bool hasActiveProperties() const final
Returns true if the collection has any active properties, or false if all properties within the colle...
QSet< QString > referencedFields(const QgsExpressionContext &context=QgsExpressionContext(), bool ignoreContext=false) const final
Returns the set of any fields referenced by the active properties from the collection.
Definition for a property.
Definition qgsproperty.h:45
@ Opacity
Opacity (0-100)
Definition qgsproperty.h:60
A store for object properties.
The class is used as a container of context for various read/write operations on other objects.
A rectangle specified with double values.
bool contains(const QgsRectangle &rect) const
Returns true when rectangle contains other rectangle.
double xMinimum() const
Returns the x minimum value (left side of rectangle).
double yMinimum() const
Returns the y minimum value (bottom side of rectangle).
double width() const
Returns the width of the rectangle.
double xMaximum() const
Returns the x maximum value (right side of rectangle).
bool isNull() const
Test if the rectangle is null (holding no spatial information).
double yMaximum() const
Returns the y maximum value (top side of rectangle).
bool isEmpty() const
Returns true if the rectangle has no area.
double height() const
Returns the height of the rectangle.
Contains information about the context of a rendering operation.
void setForceVectorOutput(bool force)
Sets whether rendering operations should use vector operations instead of any faster raster shortcuts...
void setTextureOrigin(const QPointF &origin)
Sets the texture origin, which should be used as a brush transform when rendering using QBrush object...
bool symbolLayerHasClipGeometries(const QString &symbolLayerId) const
Returns true if the symbol layer with matching ID has any associated clip geometries.
bool hasRenderedFeatureHandlers() const
Returns true if the context has any rendered feature handlers.
QgsVectorSimplifyMethod & vectorSimplifyMethod()
Returns the simplification settings to use when rendering vector layers.
double segmentationTolerance() const
Gets the segmentation tolerance applied when rendering curved geometries.
double convertToPainterUnits(double size, Qgis::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale(), Qgis::RenderSubcomponentProperty property=Qgis::RenderSubcomponentProperty::Generic) const
Converts a size from the specified units to painter units (pixels).
QPainter * painter()
Returns the destination QPainter for the render operation.
void setPainterFlagsUsingContext(QPainter *painter=nullptr) const
Sets relevant flags on a destination painter, using the flags and settings currently defined for the ...
QgsExpressionContext & expressionContext()
Gets the expression context.
void setGeometry(const QgsAbstractGeometry *geometry)
Sets pointer to original (unsegmentized) geometry.
QgsGeometry featureClipGeometry() const
Returns the geometry to use to clip features at render time.
const QgsRectangle & extent() const
When rendering a map layer, calling this method returns the "clipping" extent for the layer (in the l...
bool testFlag(Qgis::RenderContextFlag flag) const
Check whether a particular flag is enabled.
bool forceVectorOutput() const
Returns true if rendering operations should use vector operations instead of any faster raster shortc...
long long currentFrame() const
Returns the current frame number of the map (in frames per second), for maps which are part of an ani...
void setIsGuiPreview(bool preview)
Sets GUI preview mode.
QgsRectangle mapExtent() const
Returns the original extent of the map being rendered.
QList< QgsRenderedFeatureHandlerInterface * > renderedFeatureHandlers() const
Returns the list of rendered feature handlers to use while rendering map layers.
void setFlag(Qgis::RenderContextFlag flag, bool on=true)
Enable or disable a particular flag (other flags are not affected)
double frameRate() const
Returns the frame rate of the map, for maps which are part of an animation.
const QgsMapToPixel & mapToPixel() const
Returns the context's map to pixel transform, which transforms between map coordinates and device coo...
void setPainter(QPainter *p)
Sets the destination QPainter for the render operation.
bool isSymbolLayerEnabled(const QgsSymbolLayer *layer) const
When rendering a map layer in a second pass (for selective masking), some symbol layers may be disabl...
bool renderingStopped() const
Returns true if the rendering operation has been stopped and any ongoing rendering should be canceled...
static QgsRenderContext fromQPainter(QPainter *painter)
Creates a default render context given a pixel based QPainter destination.
void setExpressionContext(const QgsExpressionContext &context)
Sets the expression context.
QgsCoordinateTransform coordinateTransform() const
Returns the current coordinate transform for the context.
Qgis::RenderContextFlags flags() const
Returns combination of flags used for rendering.
QgsAbstractGeometry::SegmentationToleranceType segmentationToleranceType() const
Gets segmentation tolerance type (maximum angle or maximum difference between curve and approximation...
An interface for classes which provider custom handlers for features rendered as part of a map render...
Stores properties relating to a screen.
double devicePixelRatio() const
Returns the ratio between physical pixels and device-independent pixels for the screen.
bool isValid() const
Returns true if the properties are valid.
void updateRenderContextForScreen(QgsRenderContext &context) const
Updates the settings in a render context to match the screen settings.
Renders polygons using a single fill and stroke color.
static QgsStyle * defaultStyle(bool initialize=true)
Returns the default application-wide style.
Definition qgsstyle.cpp:146
QList< QList< QPolygonF > > defaultPatchAsQPolygonF(Qgis::SymbolType type, QSizeF size) const
Returns the default patch geometry for the given symbol type and size as a set of QPolygonF objects (...
Contains settings relating to symbol animation.
Definition qgssymbol.h:41
bool isAnimated() const
Returns true if the symbol is animated.
Definition qgssymbol.h:64
double frameRate() const
Returns the symbol animation frame rate (in frames per second).
Definition qgssymbol.h:78
Contains settings relating to symbol buffers, which draw a "halo" effect around the symbol.
Definition qgssymbol.h:97
QgsFillSymbol * fillSymbol() const
Returns the fill symbol used to render the buffer.
Definition qgssymbol.cpp:97
void setFillSymbol(QgsFillSymbol *symbol)
Sets the fill symbol used to render the buffer.
void writeXml(QDomElement &element, const QgsReadWriteContext &context) const
Writes the buffer settings to an XML element.
QgsSymbolBufferSettings & operator=(const QgsSymbolBufferSettings &)
Definition qgssymbol.cpp:86
void readXml(const QDomElement &element, const QgsReadWriteContext &context)
Reads the buffer settings from an XML element.
static QString encodeMapUnitScale(const QgsMapUnitScale &mapUnitScale)
static void drawVertexMarker(double x, double y, QPainter &p, Qgis::VertexMarkerType type, int markerSize)
Draws a vertex symbol at (painter) coordinates x, y.
static QgsMapUnitScale decodeMapUnitScale(const QString &str)
static QString encodeSldUom(Qgis::RenderUnit unit, double *scaleFactor)
Encodes a render unit into an SLD unit of measure string.
static QList< QList< QPolygonF > > toQPolygonF(const QgsGeometry &geometry, Qgis::SymbolType type)
Converts a geometry to a set of QPolygonF objects representing how the geometry should be drawn for a...
static QDomElement saveSymbol(const QString &symbolName, const QgsSymbol *symbol, QDomDocument &doc, const QgsReadWriteContext &context)
Writes a symbol definition to XML.
virtual void startFeatureRender(const QgsFeature &feature, QgsRenderContext &context)
Called before the layer will be rendered for a particular feature.
bool installMasks(QgsRenderContext &context, bool recursive, const QRectF &rect=QRectF())
When rendering, install masks on context painter.
@ LayerEnabled
Whether symbol layer is enabled.
QgsPaintEffect * paintEffect() const
Returns the current paint effect for the layer.
virtual void startRender(QgsSymbolRenderContext &context)=0
Called before a set of rendering operations commences on the supplied render context.
bool enabled() const
Returns true if symbol layer is enabled and will be drawn.
virtual void stopRender(QgsSymbolRenderContext &context)=0
Called after a set of rendering operations has finished on the supplied render context.
QString id() const
Returns symbol layer identifier This id is unique in the whole project.
virtual void stopFeatureRender(const QgsFeature &feature, QgsRenderContext &context)
Called after the layer has been rendered for a particular feature.
virtual Qgis::SymbolLayerFlags flags() const
Returns flags which control the symbol layer's behavior.
void setSelected(bool selected)
Sets whether symbols should be rendered using the selected symbol coloring and style.
void setOriginalGeometryType(Qgis::GeometryType type)
Sets the geometry type for the original feature geometry being rendered.
void setPatchShape(const QgsLegendPatchShape &shape)
Sets the symbol patch shape, to use if rendering symbol preview icons.
QgsRenderContext & renderContext()
Returns a reference to the context's render context.
Abstract base class for all rendered symbols.
Definition qgssymbol.h:231
void renderUsingLayer(QgsSymbolLayer *layer, QgsSymbolRenderContext &context, Qgis::GeometryType geometryType=Qgis::GeometryType::Unknown, const QPolygonF *points=nullptr, const QVector< QPolygonF > *rings=nullptr)
Renders a context using a particular symbol layer without passing in a geometry.
Qgis::SymbolFlags mSymbolFlags
Symbol flags.
Definition qgssymbol.h:968
QgsSymbolLayerList cloneLayers() const
Retrieve a cloned list of all layers that make up this symbol.
void setOutputUnit(Qgis::RenderUnit unit) const
Sets the units to use for sizes and widths within the symbol.
Property
Data definable properties.
Definition qgssymbol.h:270
QgsSymbolRenderContext * symbolRenderContext()
Returns the symbol render context.
QgsSymbolLayer * symbolLayer(int layer)
Returns the symbol layer at the specified index.
Qgis::SymbolRenderHints renderHints() const
Returns the rendering hint flags for the symbol.
void copyCommonProperties(const QgsSymbol *other)
Copies common properties from an other symbol to this symbol.
void setDataDefinedProperty(Property key, const QgsProperty &property)
Sets a data defined property for the symbol.
void renderFeature(const QgsFeature &feature, QgsRenderContext &context, int layer=-1, bool selected=false, bool drawVertexMarker=false, Qgis::VertexMarkerType currentVertexMarkerType=Qgis::VertexMarkerType::SemiTransparentCircle, double currentVertexMarkerSize=0.0)
Render a feature.
QgsPropertyCollection & dataDefinedProperties()
Returns a reference to the symbol's property collection, used for data defined overrides.
Definition qgssymbol.h:787
static QPolygonF _getLineString(QgsRenderContext &context, const QgsCurve &curve, bool clipToExtent=true)
Creates a line string in screen coordinates from a QgsCurve in map coordinates.
void stopRender(QgsRenderContext &context)
Ends the rendering process.
qreal mOpacity
Symbol opacity (in the range 0 - 1)
Definition qgssymbol.h:959
Q_DECL_DEPRECATED const QgsVectorLayer * mLayer
Definition qgssymbol.h:976
static QPolygonF _getPolygonRing(QgsRenderContext &context, const QgsCurve &curve, bool clipToExtent, bool isExteriorRing=false, bool correctRingOrientation=false)
Creates a polygon ring in screen coordinates from a QgsCurve in map coordinates.
QgsSymbolAnimationSettings & animationSettings()
Returns a reference to the symbol animation settings.
void renderVertexMarker(QPointF pt, QgsRenderContext &context, Qgis::VertexMarkerType currentVertexMarkerType, double currentVertexMarkerSize)
Render editing vertex marker at specified point.
static QPointF _getPoint(QgsRenderContext &context, const QgsPoint &point)
Creates a point in screen coordinates from a QgsPoint in map coordinates.
Definition qgssymbol.h:882
static const QgsPropertiesDefinition & propertyDefinitions()
Returns the symbol property definitions.
bool appendSymbolLayer(QgsSymbolLayer *layer)
Appends a symbol layer at the end of the current symbol layer list.
bool mClipFeaturesToExtent
Definition qgssymbol.h:970
bool usesMapUnits() const
Returns true if the symbol has any components which use map unit based sizes.
Qgis::SymbolFlags flags() const
Returns flags for the symbol.
void toSld(QDomDocument &doc, QDomElement &element, QVariantMap props) const
Converts the symbol to a SLD representation.
void setColor(const QColor &color) const
Sets the color for the symbol.
bool insertSymbolLayer(int index, QgsSymbolLayer *layer)
Inserts a symbol layer to specified index.
QgsMapUnitScale mapUnitScale() const
Returns the map unit scale for the symbol.
static QString symbolTypeToString(Qgis::SymbolType type)
Returns a translated string version of the specified symbol type.
qreal opacity() const
Returns the opacity for the symbol.
Definition qgssymbol.h:632
bool canCauseArtifactsBetweenAdjacentTiles() const
Returns true if the symbol rendering can cause visible artifacts across a single feature when the fea...
static Qgis::SymbolType symbolTypeForGeometryType(Qgis::GeometryType type)
Returns the default symbol type required for the specified geometry type.
void setMapUnitScale(const QgsMapUnitScale &scale) const
Sets the map unit scale for the symbol.
bool clipFeaturesToExtent() const
Returns whether features drawn by the symbol will be clipped to the render context's extent.
Definition qgssymbol.h:687
QImage bigSymbolPreviewImage(QgsExpressionContext *expressionContext=nullptr, Qgis::SymbolPreviewFlags flags=Qgis::SymbolPreviewFlag::FlagIncludeCrosshairsForMarkerSymbols, const QgsScreenProperties &screen=QgsScreenProperties())
Returns a large (roughly 100x100 pixel) preview image for the symbol.
QgsSymbolBufferSettings * bufferSettings()
Returns the symbol buffer settings, which control an optional "halo" effect around the symbol.
QImage asImage(QSize size, QgsRenderContext *customContext=nullptr)
Returns an image of the symbol at the specified size.
static void _getPolygon(QPolygonF &pts, QVector< QPolygonF > &holes, QgsRenderContext &context, const QgsPolygon &polygon, bool clipToExtent=true, bool correctRingOrientation=false)
Creates a polygon in screen coordinates from a QgsPolygonXYin map coordinates.
QString dump() const
Returns a string dump of the symbol's properties.
bool hasDataDefinedProperties() const
Returns whether the symbol utilizes any data defined properties.
bool deleteSymbolLayer(int index)
Removes and deletes the symbol layer at the specified index.
virtual ~QgsSymbol()
QSet< QString > usedAttributes(const QgsRenderContext &context) const
Returns a list of attributes required to render this feature.
std::unique_ptr< QgsSymbolBufferSettings > mBufferSettings
Definition qgssymbol.h:973
Qgis::SymbolType mType
Definition qgssymbol.h:955
bool changeSymbolLayer(int index, QgsSymbolLayer *layer)
Deletes the current layer at the specified index and replaces it with layer.
QgsSymbolLayer * takeSymbolLayer(int index)
Removes a symbol layer from the list and returns a pointer to it.
Qgis::SymbolRenderHints mRenderHints
Definition qgssymbol.h:961
bool mForceRHR
Definition qgssymbol.h:971
QgsSymbolLayerList mLayers
Definition qgssymbol.h:956
void drawPreviewIcon(QPainter *painter, QSize size, QgsRenderContext *customContext=nullptr, bool selected=false, const QgsExpressionContext *expressionContext=nullptr, const QgsLegendPatchShape *patchShape=nullptr, const QgsScreenProperties &screen=QgsScreenProperties())
Draws an icon of the symbol that occupies an area given by size using the specified painter.
Q_DECL_DEPRECATED const QgsVectorLayer * layer() const
QgsSymbolAnimationSettings mAnimationSettings
Definition qgssymbol.h:974
void startFeatureRender(const QgsFeature &feature, QgsRenderContext &context, int layer=-1)
Called before symbol layers will be rendered for a particular feature.
QColor color() const
Returns the symbol's color.
Qgis::RenderUnit outputUnit() const
Returns the units to use for sizes and widths within the symbol.
Qgis::SymbolType type() const
Returns the symbol's type.
Definition qgssymbol.h:293
QgsSymbol(Qgis::SymbolType type, const QgsSymbolLayerList &layers)
Constructor for a QgsSymbol of the specified type.
void setAnimationSettings(const QgsSymbolAnimationSettings &settings)
Sets a the symbol animation settings.
void startRender(QgsRenderContext &context, const QgsFields &fields=QgsFields())
Begins the rendering process for the symbol.
void setBufferSettings(QgsSymbolBufferSettings *settings)
Sets a the symbol buffer settings, which control an optional "halo" effect around the symbol.
Q_DECL_DEPRECATED void setLayer(const QgsVectorLayer *layer)
void exportImage(const QString &path, const QString &format, QSize size)
Export the symbol as an image format, to the specified path and with the given size.
void stopFeatureRender(const QgsFeature &feature, QgsRenderContext &context, int layer=-1)
Called after symbol layers have been rendered for a particular feature.
static QgsSymbol * defaultSymbol(Qgis::GeometryType geomType)
Returns a new default symbol for the specified geometry type.
static Q_INVOKABLE Qgis::RenderUnit decodeRenderUnit(const QString &string, bool *ok=nullptr)
Decodes a render unit from a string.
static Q_INVOKABLE QString encodeUnit(Qgis::DistanceUnit unit)
Encodes a distance unit to a string.
Represents a vector layer which manages a vector based data sets.
QgsVectorLayer * clone() const override
Returns a new instance equivalent to this one.
double tolerance() const
Gets the tolerance of simplification in map units. Represents the maximum distance in map units betwe...
bool forceLocalOptimization() const
Gets where the simplification executes, after fetch the geometries from provider, or when supported,...
Qgis::VectorRenderingSimplificationFlags simplifyHints() const
Gets the simplification hints of the vector layer managed.
Qgis::VectorSimplificationAlgorithm simplifyAlgorithm() const
Gets the local simplification algorithm of the vector layer managed.
static QString displayString(Qgis::WkbType type)
Returns a non-translated display string type for a WKB type, e.g., the geometry name used in WKT geom...
static bool isCurvedType(Qgis::WkbType type)
Returns true if the WKB type is a curved type or can contain curved geometries.
static Qgis::WkbType flatType(Qgis::WkbType type)
Returns the flat type for a WKB type.
Contains geos related utilities and functions.
Definition qgsgeos.h:75
#define Q_NOWARN_DEPRECATED_POP
Definition qgis.h:6494
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
Definition qgis.h:5834
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:6493
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition qgis.h:5917
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:39
#define QgsDebugError(str)
Definition qgslogger.h:38
QMap< int, QgsPropertyDefinition > QgsPropertiesDefinition
Definition of available properties.
QList< QgsSymbolLayer * > QgsSymbolLayerList
Definition qgssymbol.h:30
Single variable definition for use within a QgsExpressionContextScope.
Utility class for identifying a unique vertex within a geometry.
Definition qgsvertexid.h:30