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