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