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