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