QGIS API Documentation 3.39.0-Master (52f98f8c831)
Loading...
Searching...
No Matches
qgssymbol.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgssymbol.cpp
3 ---------------------
4 begin : November 2009
5 copyright : (C) 2009 by Martin Dobias
6 email : wonder dot sk at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16#include <QColor>
17#include <QImage>
18#include <QPainter>
19#include <QSize>
20#include <QSvgGenerator>
21#include <QPicture>
22
23#include <cmath>
24#include <map>
25#include <random>
26
27#include "qgssymbol.h"
28#include "qgssymbollayer.h"
29
32#include "qgslogger.h"
33#include "qgsrendercontext.h" // for bigSymbolPreview
34#include "qgsproject.h"
36#include "qgsstyle.h"
37#include "qgspainteffect.h"
38#include "qgsvectorlayer.h"
39#include "qgsfeature.h"
40#include "qgsgeometry.h"
41#include "qgsmultipoint.h"
43#include "qgslinestring.h"
44#include "qgspolygon.h"
45#include "qgsclipper.h"
46#include "qgsproperty.h"
48#include "qgsapplication.h"
51#include "qgslegendpatchshape.h"
52#include "qgsgeos.h"
53#include "qgsmarkersymbol.h"
54#include "qgslinesymbol.h"
55#include "qgsfillsymbol.h"
56#include "qgscolorutils.h"
57
58QgsPropertiesDefinition QgsSymbol::sPropertyDefinitions;
59
60Q_NOWARN_DEPRECATED_PUSH // because of deprecated mLayer
62 : mType( type )
63 , mLayers( layers )
64{
65
66 // check they're all correct symbol layers
67 for ( int i = 0; i < mLayers.count(); i++ )
68 {
69 if ( !mLayers.at( i ) )
70 {
71 mLayers.removeAt( i-- );
72 }
73 else if ( !mLayers.at( i )->isCompatibleWithSymbol( this ) )
74 {
75 delete mLayers.at( i );
76 mLayers.removeAt( i-- );
77 }
78 }
79}
81
82QPolygonF QgsSymbol::_getLineString( QgsRenderContext &context, const QgsCurve &curve, bool clipToExtent )
83{
84 if ( curve.is3D() )
85 return _getLineString3d( context, curve, clipToExtent );
86 else
87 return _getLineString2d( context, curve, clipToExtent );
88}
89
90QPolygonF QgsSymbol::_getLineString3d( QgsRenderContext &context, const QgsCurve &curve, bool clipToExtent )
91{
92 const unsigned int nPoints = curve.numPoints();
93
95 const QgsMapToPixel &mtp = context.mapToPixel();
96 QVector< double > pointsX;
97 QVector< double > pointsY;
98 QVector< double > pointsZ;
99
100 // apply clipping for large lines to achieve a better rendering performance
101 if ( clipToExtent && nPoints > 1 && !( context.flags() & Qgis::RenderContextFlag::ApplyClipAfterReprojection ) )
102 {
103 const QgsRectangle e = context.extent();
104 const double cw = e.width() / 10;
105 const double ch = e.height() / 10;
106 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
107
108 const QgsLineString *lineString = nullptr;
109 std::unique_ptr< QgsLineString > segmentized;
110 if ( const QgsLineString *ls = qgsgeometry_cast< const QgsLineString * >( &curve ) )
111 {
112 lineString = ls;
113 }
114 else
115 {
116 segmentized.reset( qgsgeometry_cast< QgsLineString * >( curve.segmentize( ) ) );
117 lineString = segmentized.get();
118 }
119
120 QgsClipper::clipped3dLine( lineString->xVector(), lineString->yVector(), lineString->zVector(), pointsX, pointsY, pointsZ, clipRect );
121 }
122 else
123 {
124 // clone...
125 if ( const QgsLineString *ls = qgsgeometry_cast<const QgsLineString *>( &curve ) )
126 {
127 pointsX = ls->xVector();
128 pointsY = ls->yVector();
129 pointsZ = ls->zVector();
130 }
131 else
132 {
133 std::unique_ptr< QgsLineString > segmentized;
134 segmentized.reset( qgsgeometry_cast< QgsLineString * >( curve.segmentize( ) ) );
135
136 pointsX = segmentized->xVector();
137 pointsY = segmentized->yVector();
138 pointsZ = segmentized->zVector();
139 }
140 }
141
142 // transform the points to screen coordinates
143 const QVector< double > preTransformPointsZ = pointsZ;
144 bool wasTransformed = false;
145 if ( ct.isValid() )
146 {
147 //create x, y arrays
148 const int nVertices = pointsX.size();
149 wasTransformed = true;
150
151 try
152 {
153 ct.transformCoords( nVertices, pointsX.data(), pointsY.data(), pointsZ.data(), Qgis::TransformDirection::Forward );
154 }
155 catch ( QgsCsException & )
156 {
157 // we don't abort the rendering here, instead we remove any invalid points and just plot those which ARE valid
158 }
159 }
160
161 // remove non-finite points, e.g. infinite or NaN points caused by reprojecting errors
162 {
163 const int size = pointsX.size();
164
165 const double *xIn = pointsX.data();
166 const double *yIn = pointsY.data();
167 const double *zIn = pointsZ.data();
168
169 const double *preTransformZIn = wasTransformed ? preTransformPointsZ.constData() : nullptr;
170
171 double *xOut = pointsX.data();
172 double *yOut = pointsY.data();
173 double *zOut = pointsZ.data();
174 int outSize = 0;
175 for ( int i = 0; i < size; ++i )
176 {
177 bool pointOk = std::isfinite( *xIn ) && std::isfinite( *yIn );
178
179 // skip z points which have been made non-finite during transformations only. Ie if:
180 // - we did no transformation, then always render even if non-finite z
181 // - we did transformation and z is finite then render
182 // - we did transformation and z is non-finite BUT input z was also non finite then render
183 // - we did transformation and z is non-finite AND input z WAS finite then skip
184 pointOk &= !wasTransformed || std::isfinite( *zIn ) || !std::isfinite( *preTransformZIn );
185
186 if ( pointOk )
187 {
188 *xOut++ = *xIn++;
189 *yOut++ = *yIn++;
190 *zOut++ = *zIn++;
191 outSize++;
192 }
193 else
194 {
195 xIn++;
196 yIn++;
197 zIn++;
198 }
199
200 if ( preTransformZIn )
201 preTransformZIn++;
202 }
203 pointsX.resize( outSize );
204 pointsY.resize( outSize );
205 pointsZ.resize( outSize );
206 }
207
208 if ( clipToExtent && nPoints > 1 && context.flags() & Qgis::RenderContextFlag::ApplyClipAfterReprojection )
209 {
210 // early clipping was not possible, so we have to apply it here after transformation
211 const QgsRectangle e = context.mapExtent();
212 const double cw = e.width() / 10;
213 const double ch = e.height() / 10;
214 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
215
216 QVector< double > tempX;
217 QVector< double > tempY;
218 QVector< double > tempZ;
219 QgsClipper::clipped3dLine( pointsX, pointsY, pointsZ, tempX, tempY, tempZ, clipRect );
220 pointsX = tempX;
221 pointsY = tempY;
222 pointsZ = tempZ;
223 }
224
225 const int polygonSize = pointsX.size();
226 QPolygonF out( polygonSize );
227 const double *x = pointsX.constData();
228 const double *y = pointsY.constData();
229 QPointF *dest = out.data();
230 for ( int i = 0; i < polygonSize; ++i )
231 {
232 double screenX = *x++;
233 double screenY = *y++;
234 mtp.transformInPlace( screenX, screenY );
235 *dest++ = QPointF( screenX, screenY );
236 }
237
238 return out;
239}
240
241QPolygonF QgsSymbol::_getLineString2d( QgsRenderContext &context, const QgsCurve &curve, bool clipToExtent )
242{
243 const unsigned int nPoints = curve.numPoints();
244
246 const QgsMapToPixel &mtp = context.mapToPixel();
247 QPolygonF pts;
248
249 // apply clipping for large lines to achieve a better rendering performance
250 if ( clipToExtent && nPoints > 1 && !( context.flags() & Qgis::RenderContextFlag::ApplyClipAfterReprojection ) )
251 {
252 const QgsRectangle e = context.extent();
253 const double cw = e.width() / 10;
254 const double ch = e.height() / 10;
255 const QgsRectangle clipRect( e.xMinimum() - cw, e.yMinimum() - ch, e.xMaximum() + cw, e.yMaximum() + ch );
256 pts = QgsClipper::clippedLine( curve, clipRect );
257 }
258 else
259 {
260 pts = curve.asQPolygonF();
261 }
262
263 // transform the QPolygonF to screen coordinates
264 if ( ct.isValid() )
265 {
266 try
267 {
268 ct.transformPolygon( pts );
269 }
270 catch ( QgsCsException & )
271 {
272 // we don't abort the rendering here, instead we remove any invalid points and just plot those which ARE valid
273 }
274 }
275
276 // remove non-finite points, e.g. infinite or NaN points caused by reprojecting errors
277 pts.erase( std::remove_if( pts.begin(), pts.end(),
278 []( const QPointF point )
279 {
280 return !std::isfinite( point.x() ) || !std::isfinite( point.y() );
281 } ), pts.end() );
282
283 if ( clipToExtent && nPoints > 1 && context.flags() & Qgis::RenderContextFlag::ApplyClipAfterReprojection )
284 {
285 // early clipping was not possible, so we have to apply it here after transformation
286 const QgsRectangle e = context.mapExtent();
287 const double cw = e.width() / 10;
288 const double ch = e.height() / 10;
289 const QgsRectangle clipRect( e.xMinimum() - cw, e.yMinimum() - ch, e.xMaximum() + cw, e.yMaximum() + ch );
290 pts = QgsClipper::clippedLine( pts, clipRect );
291 }
292
293 QPointF *ptr = pts.data();
294 for ( int i = 0; i < pts.size(); ++i, ++ptr )
295 {
296 mtp.transformInPlace( ptr->rx(), ptr->ry() );
297 }
298
299 return pts;
300}
301
302
303QPolygonF QgsSymbol::_getPolygonRing( QgsRenderContext &context, const QgsCurve &curve, const bool clipToExtent, const bool isExteriorRing, const bool correctRingOrientation )
304{
305 if ( curve.is3D() )
306 return _getPolygonRing3d( context, curve, clipToExtent, isExteriorRing, correctRingOrientation );
307 else
308 return _getPolygonRing2d( context, curve, clipToExtent, isExteriorRing, correctRingOrientation );
309}
310
311QPolygonF QgsSymbol::_getPolygonRing3d( QgsRenderContext &context, const QgsCurve &curve, const bool clipToExtent, const bool isExteriorRing, const bool correctRingOrientation )
312{
313 const QgsCoordinateTransform ct = context.coordinateTransform();
314 const QgsMapToPixel &mtp = context.mapToPixel();
315
316 QVector< double > pointsX;
317 QVector< double > pointsY;
318 QVector< double > pointsZ;
319
320 if ( curve.numPoints() < 1 )
321 return QPolygonF();
322
323 bool reverseRing = false;
324 if ( correctRingOrientation )
325 {
326 // ensure consistent polygon ring orientation
327 if ( ( isExteriorRing && curve.orientation() != Qgis::AngularDirection::Clockwise ) || ( !isExteriorRing && curve.orientation() != Qgis::AngularDirection::CounterClockwise ) )
328 {
329 reverseRing = true;
330 }
331 }
332
333 //clip close to view extent, if needed
334 if ( clipToExtent && !( context.flags() & Qgis::RenderContextFlag::ApplyClipAfterReprojection ) && !context.extent().contains( curve.boundingBox() ) )
335 {
336 const QgsRectangle e = context.extent();
337 const double cw = e.width() / 10;
338 const double ch = e.height() / 10;
339 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
340
341 const QgsLineString *lineString = nullptr;
342 std::unique_ptr< QgsLineString > segmentized;
343 if ( const QgsLineString *ls = qgsgeometry_cast< const QgsLineString * >( &curve ) )
344 {
345 lineString = ls;
346 }
347 else
348 {
349 segmentized.reset( qgsgeometry_cast< QgsLineString * >( curve.segmentize( ) ) );
350 lineString = segmentized.get();
351 }
352
353 pointsX = lineString->xVector();
354 pointsY = lineString->yVector();
355 pointsZ = lineString->zVector();
356
357 QgsClipper::trimPolygon( pointsX, pointsY, pointsZ, clipRect );
358 }
359 else
360 {
361 // clone...
362 if ( const QgsLineString *ls = qgsgeometry_cast<const QgsLineString *>( &curve ) )
363 {
364 pointsX = ls->xVector();
365 pointsY = ls->yVector();
366 pointsZ = ls->zVector();
367 }
368 else
369 {
370 std::unique_ptr< QgsLineString > segmentized;
371 segmentized.reset( qgsgeometry_cast< QgsLineString * >( curve.segmentize( ) ) );
372
373 pointsX = segmentized->xVector();
374 pointsY = segmentized->yVector();
375 pointsZ = segmentized->zVector();
376 }
377 }
378
379 if ( reverseRing )
380 {
381 std::reverse( pointsX.begin(), pointsX.end() );
382 std::reverse( pointsY.begin(), pointsY.end() );
383 std::reverse( pointsZ.begin(), pointsZ.end() );
384 }
385
386 //transform the QPolygonF to screen coordinates
387 const QVector< double > preTransformPointsZ = pointsZ;
388 bool wasTransformed = false;
389 if ( ct.isValid() )
390 {
391 const int nVertices = pointsX.size();
392 wasTransformed = true;
393 try
394 {
395 ct.transformCoords( nVertices, pointsX.data(), pointsY.data(), pointsZ.data(), Qgis::TransformDirection::Forward );
396 }
397 catch ( QgsCsException & )
398 {
399 // we don't abort the rendering here, instead we remove any invalid points and just plot those which ARE valid
400 }
401 }
402
403 // remove non-finite points, e.g. infinite or NaN points caused by reprojecting errors
404 {
405 const int size = pointsX.size();
406
407 const double *xIn = pointsX.data();
408 const double *yIn = pointsY.data();
409 const double *zIn = pointsZ.data();
410
411 const double *preTransformZIn = wasTransformed ? preTransformPointsZ.constData() : nullptr;
412
413 double *xOut = pointsX.data();
414 double *yOut = pointsY.data();
415 double *zOut = pointsZ.data();
416 int outSize = 0;
417 for ( int i = 0; i < size; ++i )
418 {
419 bool pointOk = std::isfinite( *xIn ) && std::isfinite( *yIn );
420 // skip z points which have been made non-finite during transformations only. Ie if:
421 // - we did no transformation, then always render even if non-finite z
422 // - we did transformation and z is finite then render
423 // - we did transformation and z is non-finite BUT input z was also non finite then render
424 // - we did transformation and z is non-finite AND input z WAS finite then skip
425 pointOk &= !wasTransformed || std::isfinite( *zIn ) || !std::isfinite( *preTransformZIn );
426
427 if ( pointOk )
428 {
429 *xOut++ = *xIn++;
430 *yOut++ = *yIn++;
431 *zOut++ = *zIn++;
432 outSize++;
433 }
434 else
435 {
436 xIn++;
437 yIn++;
438 zIn++;
439 }
440
441 if ( preTransformZIn )
442 preTransformZIn++;
443 }
444 pointsX.resize( outSize );
445 pointsY.resize( outSize );
446 pointsZ.resize( outSize );
447 }
448
449 if ( clipToExtent && context.flags() & Qgis::RenderContextFlag::ApplyClipAfterReprojection && !context.mapExtent().contains( curve.boundingBox() ) )
450 {
451 // early clipping was not possible, so we have to apply it here after transformation
452 const QgsRectangle e = context.mapExtent();
453 const double cw = e.width() / 10;
454 const double ch = e.height() / 10;
455 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
456
457 QgsClipper::trimPolygon( pointsX, pointsY, pointsZ, clipRect );
458 }
459
460 const int polygonSize = pointsX.size();
461 QPolygonF out( polygonSize );
462 const double *x = pointsX.constData();
463 const double *y = pointsY.constData();
464 QPointF *dest = out.data();
465 for ( int i = 0; i < polygonSize; ++i )
466 {
467 double screenX = *x++;
468 double screenY = *y++;
469 mtp.transformInPlace( screenX, screenY );
470 *dest++ = QPointF( screenX, screenY );
471 }
472
473 if ( !out.empty() && !out.isClosed() )
474 out << out.at( 0 );
475
476 return out;
477}
478
479
480QPolygonF QgsSymbol::_getPolygonRing2d( QgsRenderContext &context, const QgsCurve &curve, const bool clipToExtent, const bool isExteriorRing, const bool correctRingOrientation )
481{
482 const QgsCoordinateTransform ct = context.coordinateTransform();
483 const QgsMapToPixel &mtp = context.mapToPixel();
484
485 QPolygonF poly = curve.asQPolygonF();
486
487 if ( curve.numPoints() < 1 )
488 return QPolygonF();
489
490 if ( correctRingOrientation )
491 {
492 // ensure consistent polygon ring orientation
493 if ( isExteriorRing && curve.orientation() != Qgis::AngularDirection::Clockwise )
494 std::reverse( poly.begin(), poly.end() );
495 else if ( !isExteriorRing && curve.orientation() != Qgis::AngularDirection::CounterClockwise )
496 std::reverse( poly.begin(), poly.end() );
497 }
498
499 //clip close to view extent, if needed
500 if ( clipToExtent && !( context.flags() & Qgis::RenderContextFlag::ApplyClipAfterReprojection ) && !context.extent().contains( poly.boundingRect() ) )
501 {
502 const QgsRectangle e = context.extent();
503 const double cw = e.width() / 10;
504 const double ch = e.height() / 10;
505 const QgsRectangle clipRect( e.xMinimum() - cw, e.yMinimum() - ch, e.xMaximum() + cw, e.yMaximum() + ch );
506 QgsClipper::trimPolygon( poly, clipRect );
507 }
508
509 //transform the QPolygonF to screen coordinates
510 if ( ct.isValid() )
511 {
512 try
513 {
514 ct.transformPolygon( poly );
515 }
516 catch ( QgsCsException & )
517 {
518 // we don't abort the rendering here, instead we remove any invalid points and just plot those which ARE valid
519 }
520 }
521
522 // remove non-finite points, e.g. infinite or NaN points caused by reprojecting errors
523 poly.erase( std::remove_if( poly.begin(), poly.end(),
524 []( const QPointF point )
525 {
526 return !std::isfinite( point.x() ) || !std::isfinite( point.y() );
527 } ), poly.end() );
528
529 if ( clipToExtent && context.flags() & Qgis::RenderContextFlag::ApplyClipAfterReprojection && !context.mapExtent().contains( poly.boundingRect() ) )
530 {
531 // early clipping was not possible, so we have to apply it here after transformation
532 const QgsRectangle e = context.mapExtent();
533 const double cw = e.width() / 10;
534 const double ch = e.height() / 10;
535 const QgsRectangle clipRect( e.xMinimum() - cw, e.yMinimum() - ch, e.xMaximum() + cw, e.yMaximum() + ch );
536 QgsClipper::trimPolygon( poly, clipRect );
537 }
538
539 QPointF *ptr = poly.data();
540 for ( int i = 0; i < poly.size(); ++i, ++ptr )
541 {
542 mtp.transformInPlace( ptr->rx(), ptr->ry() );
543 }
544
545 if ( !poly.empty() && !poly.isClosed() )
546 poly << poly.at( 0 );
547
548 return poly;
549}
550
551void QgsSymbol::_getPolygon( QPolygonF &pts, QVector<QPolygonF> &holes, QgsRenderContext &context, const QgsPolygon &polygon, const bool clipToExtent, const bool correctRingOrientation )
552{
553 holes.clear();
554
555 pts = _getPolygonRing( context, *polygon.exteriorRing(), clipToExtent, true, correctRingOrientation );
556 const int ringCount = polygon.numInteriorRings();
557 holes.reserve( ringCount );
558 for ( int idx = 0; idx < ringCount; idx++ )
559 {
560 const QPolygonF hole = _getPolygonRing( context, *( polygon.interiorRing( idx ) ), clipToExtent, false, correctRingOrientation );
561 if ( !hole.isEmpty() )
562 holes.append( hole );
563 }
564}
565
567{
568 switch ( type )
569 {
571 return QObject::tr( "Marker" );
573 return QObject::tr( "Line" );
575 return QObject::tr( "Fill" );
577 return QObject::tr( "Hybrid" );
578 }
579 return QString();
580}
581
598
600{
601 QgsSymbol::initPropertyDefinitions();
602 return sPropertyDefinitions;
603}
604
606{
607 // delete all symbol layers (we own them, so it's okay)
608 qDeleteAll( mLayers );
609}
610
612{
613 if ( mLayers.empty() )
614 {
616 }
617
618 QgsSymbolLayerList::const_iterator it = mLayers.constBegin();
619
620 Qgis::RenderUnit unit = ( *it )->outputUnit();
621
622 for ( ; it != mLayers.constEnd(); ++it )
623 {
624 if ( ( *it )->outputUnit() != unit )
625 {
627 }
628 }
629 return unit;
630}
631
633{
634 if ( mLayers.empty() )
635 {
636 return false;
637 }
638
639 for ( const QgsSymbolLayer *layer : mLayers )
640 {
641 if ( layer->usesMapUnits() )
642 {
643 return true;
644 }
645 }
646 return false;
647}
648
650{
651 if ( mLayers.empty() )
652 {
653 return QgsMapUnitScale();
654 }
655
656 QgsSymbolLayerList::const_iterator it = mLayers.constBegin();
657 if ( it == mLayers.constEnd() )
658 return QgsMapUnitScale();
659
660 QgsMapUnitScale scale = ( *it )->mapUnitScale();
661 ++it;
662
663 for ( ; it != mLayers.constEnd(); ++it )
664 {
665 if ( ( *it )->mapUnitScale() != scale )
666 {
667 return QgsMapUnitScale();
668 }
669 }
670 return scale;
671}
672
674{
675 const auto constMLayers = mLayers;
676 for ( QgsSymbolLayer *layer : constMLayers )
677 {
678 layer->setOutputUnit( u );
679 }
680}
681
683{
684 const auto constMLayers = mLayers;
685 for ( QgsSymbolLayer *layer : constMLayers )
686 {
687 layer->setMapUnitScale( scale );
688 }
689}
690
695
700
702{
703 mAnimationSettings = settings;
704}
705
707{
708 std::unique_ptr< QgsSymbol > s;
709
710 // override global default if project has a default for this type
711 switch ( geomType )
712 {
714 s.reset( QgsProject::instance()->styleSettings()->defaultSymbol( Qgis::SymbolType::Marker ) );
715 break;
717 s.reset( QgsProject::instance()->styleSettings()->defaultSymbol( Qgis::SymbolType::Line ) );
718 break;
720 s.reset( QgsProject::instance()->styleSettings()->defaultSymbol( Qgis::SymbolType::Fill ) );
721 break;
722 default:
723 break;
724 }
725
726 // if no default found for this type, get global default (as previously)
727 if ( !s )
728 {
729 switch ( geomType )
730 {
732 s = std::make_unique< QgsMarkerSymbol >();
733 break;
735 s = std::make_unique< QgsLineSymbol >();
736 break;
738 s = std::make_unique< QgsFillSymbol >();
739 break;
740 default:
741 QgsDebugError( QStringLiteral( "unknown layer's geometry type" ) );
742 break;
743 }
744 }
745
746 if ( !s )
747 return nullptr;
748
749 // set opacity
750 s->setOpacity( QgsProject::instance()->styleSettings()->defaultSymbolOpacity() );
751
752 // set random color, it project prefs allow
753 if ( QgsProject::instance()->styleSettings()->randomizeDefaultSymbolColor() )
754 {
755 s->setColor( QgsApplication::colorSchemeRegistry()->fetchRandomStyleColor() );
756 }
757
758 return s.release();
759}
760
762{
763 return mLayers.value( layer );
764}
765
766const QgsSymbolLayer *QgsSymbol::symbolLayer( int layer ) const
767{
768 return mLayers.value( layer );
769}
770
772{
773 if ( index < 0 || index > mLayers.count() ) // can be added also after the last index
774 return false;
775
776 if ( !layer || !layer->isCompatibleWithSymbol( this ) )
777 return false;
778
779 mLayers.insert( index, layer );
780 return true;
781}
782
783
785{
786 if ( !layer || !layer->isCompatibleWithSymbol( this ) )
787 return false;
788
789 mLayers.append( layer );
790 return true;
791}
792
793
795{
796 if ( index < 0 || index >= mLayers.count() )
797 return false;
798
799 delete mLayers.at( index );
800 mLayers.removeAt( index );
801 return true;
802}
803
804
806{
807 if ( index < 0 || index >= mLayers.count() )
808 return nullptr;
809
810 return mLayers.takeAt( index );
811}
812
813
815{
816 QgsSymbolLayer *oldLayer = mLayers.value( index );
817
818 if ( oldLayer == layer )
819 return false;
820
821 if ( !layer || !layer->isCompatibleWithSymbol( this ) )
822 return false;
823
824 delete oldLayer; // first delete the original layer
825 mLayers[index] = layer; // set new layer
826 return true;
827}
828
829
830void QgsSymbol::startRender( QgsRenderContext &context, const QgsFields &fields )
831{
832 Q_ASSERT_X( !mStarted, "startRender", "Rendering has already been started for this symbol instance!" );
833 mStarted = true;
834
835 mSymbolRenderContext.reset( new QgsSymbolRenderContext( context, Qgis::RenderUnit::Unknown, mOpacity, false, mRenderHints, nullptr, fields ) );
836
837 // Why do we need a copy here ? Is it to make sure the symbol layer rendering does not mess with the symbol render context ?
838 // Or is there another profound reason ?
839 QgsSymbolRenderContext symbolContext( context, Qgis::RenderUnit::Unknown, mOpacity, false, mRenderHints, nullptr, fields );
840
841 std::unique_ptr< QgsExpressionContextScope > scope( QgsExpressionContextUtils::updateSymbolScope( this, new QgsExpressionContextScope() ) );
842
844 {
845 const long long mapFrameNumber = context.currentFrame();
846 double animationTimeSeconds = 0;
847 if ( mapFrameNumber >= 0 && context.frameRate() > 0 )
848 {
849 // render is part of an animation, so we base the calculated frame on that
850 animationTimeSeconds = mapFrameNumber / context.frameRate();
851 }
852 else
853 {
854 // render is outside of animation, so base the calculated frame on the current epoch
855 animationTimeSeconds = QDateTime::currentMSecsSinceEpoch() / 1000.0;
856 }
857
858 const long long symbolFrame = static_cast< long long >( std::floor( animationTimeSeconds * mAnimationSettings.frameRate() ) );
859 scope->setVariable( QStringLiteral( "symbol_frame" ), symbolFrame, true );
860 }
861
862 mSymbolRenderContext->setExpressionContextScope( scope.release() );
863
864 mDataDefinedProperties.prepare( context.expressionContext() );
865
866 for ( QgsSymbolLayer *layer : std::as_const( mLayers ) )
867 {
868 if ( !layer->enabled() || !context.isSymbolLayerEnabled( layer ) )
869 continue;
870
871 layer->prepareExpressions( symbolContext );
872
873 // We prepare "entire map" clip masks in advance only in certain circumstances. These are non-optimal,
874 // because the entire map mask will be applied once for every feature rendered, resulting in overly complex
875 // clipping paths with paths which fall well outside of the map area that is actually being drawn on for the
876 // feature. These circumstances are:
877 // 1. If we are rendering a sub symbol. The current logic relating to calculating per-feature masks
878 // is not designed to handle sub symbol rendering where layers from the subsymbol have their own set of
879 // clipping paths, so we just fallback to the non-optimal approach always for these cases.
880 // TODO:
881 // - we could add another special condition here to check whether the subsymbol actually does have unique
882 // clipping paths in its symbol layers, or whether they are identical to the parent symbol layer's clipping paths.
883 // 2. When the symbol layer type doesn't explicitly state that it's compatible with per-feature mask geometries
884 // 3. When per feature mask geometry is explicitly disabled for the render context
885 // In other circumstances we do NOT prepare masks in advance, and instead calculate them in renderFeature().
889 layer->prepareMasks( symbolContext );
890 layer->startRender( symbolContext );
891 }
892}
893
895{
896 Q_ASSERT_X( mStarted, "startRender", "startRender was not called for this symbol instance!" );
897 mStarted = false;
898
899 Q_UNUSED( context )
900 if ( mSymbolRenderContext )
901 {
902 const auto constMLayers = mLayers;
903 for ( QgsSymbolLayer *layer : constMLayers )
904 {
905 if ( !layer->enabled() || !context.isSymbolLayerEnabled( layer ) )
906 continue;
907
908 layer->stopRender( *mSymbolRenderContext );
909 }
910 }
911
912 mSymbolRenderContext.reset( nullptr );
913
915 mLayer = nullptr;
917}
918
919void QgsSymbol::setColor( const QColor &color ) const
920{
921 const auto constMLayers = mLayers;
922 for ( QgsSymbolLayer *layer : constMLayers )
923 {
924 if ( !layer->isLocked() )
925 layer->setColor( color );
926 }
927}
928
929QColor QgsSymbol::color() const
930{
931 for ( const QgsSymbolLayer *layer : mLayers )
932 {
933 // return color of the first unlocked layer
934 if ( !layer->isLocked() )
935 {
936 const QColor layerColor = layer->color();
937 if ( layerColor.isValid() )
938 return layerColor;
939 }
940 }
941 return QColor( 0, 0, 0 );
942}
943
944void QgsSymbol::drawPreviewIcon( QPainter *painter, QSize size, QgsRenderContext *customContext, bool selected, const QgsExpressionContext *expressionContext, const QgsLegendPatchShape *patchShape, const QgsScreenProperties &screen )
945{
946 QgsRenderContext *context = customContext;
947 std::unique_ptr< QgsRenderContext > tempContext;
948 if ( !context )
949 {
950 tempContext.reset( new QgsRenderContext( QgsRenderContext::fromQPainter( painter ) ) );
951 context = tempContext.get();
953 }
954
955 if ( screen.isValid() )
956 {
957 screen.updateRenderContextForScreen( *context );
958 }
959
960 const bool prevForceVector = context->forceVectorOutput();
961 context->setForceVectorOutput( true );
962
963 const double opacity = expressionContext ? dataDefinedProperties().valueAsDouble( QgsSymbol::Property::Opacity, *expressionContext, mOpacity * 100 ) * 0.01 : mOpacity;
964
965 QgsSymbolRenderContext symbolContext( *context, Qgis::RenderUnit::Unknown, opacity, false, mRenderHints, nullptr );
966 symbolContext.setSelected( selected );
967 switch ( mType )
968 {
971 break;
974 break;
977 break;
980 break;
981 }
982
983 if ( patchShape )
984 symbolContext.setPatchShape( *patchShape );
985
986 if ( !customContext && expressionContext )
987 {
988 context->setExpressionContext( *expressionContext );
989 }
990 else if ( !customContext )
991 {
992 // if no render context was passed, build a minimal expression context
993 QgsExpressionContext expContext;
995 context->setExpressionContext( expContext );
996 }
997
998 for ( QgsSymbolLayer *layer : std::as_const( mLayers ) )
999 {
1000 if ( !layer->enabled() || ( customContext && !customContext->isSymbolLayerEnabled( layer ) ) )
1001 continue;
1002
1004 {
1005 // line symbol layer would normally draw just a line
1006 // so we override this case to force it to draw a polygon stroke
1007 QgsLineSymbolLayer *lsl = dynamic_cast<QgsLineSymbolLayer *>( layer );
1008 if ( lsl )
1009 {
1010 // from QgsFillSymbolLayer::drawPreviewIcon() -- would be nicer to add the
1011 // symbol type to QgsSymbolLayer::drawPreviewIcon so this logic could be avoided!
1012
1013 // hmm... why was this using size -1 ??
1014 const QSizeF targetSize = QSizeF( size.width() - 1, size.height() - 1 );
1015
1016 const QList< QList< QPolygonF > > polys = patchShape ? patchShape->toQPolygonF( Qgis::SymbolType::Fill, targetSize )
1018
1019 lsl->startRender( symbolContext );
1020 QgsPaintEffect *effect = lsl->paintEffect();
1021
1022 std::unique_ptr< QgsEffectPainter > effectPainter;
1023 if ( effect && effect->enabled() )
1024 effectPainter = std::make_unique< QgsEffectPainter >( symbolContext.renderContext(), effect );
1025
1026 for ( const QList< QPolygonF > &poly : polys )
1027 {
1028 QVector< QPolygonF > rings;
1029 rings.reserve( poly.size() );
1030 for ( int i = 1; i < poly.size(); ++i )
1031 rings << poly.at( i );
1032 lsl->renderPolygonStroke( poly.value( 0 ), &rings, symbolContext );
1033 }
1034
1035 effectPainter.reset();
1036 lsl->stopRender( symbolContext );
1037 }
1038 }
1039 else
1040 layer->drawPreviewIcon( symbolContext, size );
1041 }
1042
1043 context->setForceVectorOutput( prevForceVector );
1044}
1045
1046void QgsSymbol::exportImage( const QString &path, const QString &format, QSize size )
1047{
1048 if ( format.compare( QLatin1String( "svg" ), Qt::CaseInsensitive ) == 0 )
1049 {
1050 QSvgGenerator generator;
1051 generator.setFileName( path );
1052 generator.setSize( size );
1053 generator.setViewBox( QRect( 0, 0, size.height(), size.height() ) );
1054
1055 QPainter painter( &generator );
1056 drawPreviewIcon( &painter, size );
1057 painter.end();
1058 }
1059 else
1060 {
1061 QImage image = asImage( size );
1062 image.save( path );
1063 }
1064}
1065
1066QImage QgsSymbol::asImage( QSize size, QgsRenderContext *customContext )
1067{
1068 QImage image( size, QImage::Format_ARGB32_Premultiplied );
1069 image.fill( 0 );
1070
1071 QPainter p( &image );
1072 p.setRenderHint( QPainter::Antialiasing );
1073 p.setRenderHint( QPainter::SmoothPixmapTransform );
1074
1075 drawPreviewIcon( &p, size, customContext );
1076
1077 return image;
1078}
1079
1080
1082{
1083 const double devicePixelRatio = screen.isValid() ? screen.devicePixelRatio() : 1;
1084 QImage preview( QSize( 100, 100 ) * devicePixelRatio, QImage::Format_ARGB32_Premultiplied );
1085 preview.fill( 0 );
1086 preview.setDevicePixelRatio( devicePixelRatio );
1087
1088 QPainter p( &preview );
1089 p.setRenderHint( QPainter::Antialiasing );
1090 p.translate( 0.5, 0.5 ); // shift by half a pixel to avoid blurring due antialiasing
1091
1093 {
1094 p.setPen( QPen( Qt::gray ) );
1095 p.drawLine( QLineF( 0, 50, 100, 50 ) );
1096 p.drawLine( QLineF( 50, 0, 50, 100 ) );
1097 }
1098
1103 context.setPainterFlagsUsingContext( &p );
1104
1105 if ( screen.isValid() )
1106 {
1107 screen.updateRenderContextForScreen( context );
1108 }
1109
1110 if ( expressionContext )
1111 context.setExpressionContext( *expressionContext );
1112
1113 context.setIsGuiPreview( true );
1114 startRender( context );
1115
1117 {
1118 QPolygonF poly;
1119 poly << QPointF( 0, 50 ) << QPointF( 99, 50 );
1120 static_cast<QgsLineSymbol *>( this )->renderPolyline( poly, nullptr, context );
1121 }
1122 else if ( mType == Qgis::SymbolType::Fill )
1123 {
1124 QPolygonF polygon;
1125 polygon << QPointF( 20, 20 ) << QPointF( 80, 20 ) << QPointF( 80, 80 ) << QPointF( 20, 80 ) << QPointF( 20, 20 );
1126 static_cast<QgsFillSymbol *>( this )->renderPolygon( polygon, nullptr, nullptr, context );
1127 }
1128 else // marker
1129 {
1130 static_cast<QgsMarkerSymbol *>( this )->renderPoint( QPointF( 50, 50 ), nullptr, context );
1131 }
1132
1133 stopRender( context );
1134 return preview;
1135}
1136
1137QImage QgsSymbol::bigSymbolPreviewImage( QgsExpressionContext *expressionContext, int flags )
1138{
1139 return bigSymbolPreviewImage( expressionContext, static_cast< Qgis::SymbolPreviewFlags >( flags ) );
1140}
1141
1142QString QgsSymbol::dump() const
1143{
1144 QString t;
1145 switch ( type() )
1146 {
1148 t = QStringLiteral( "MARKER" );
1149 break;
1151 t = QStringLiteral( "LINE" );
1152 break;
1154 t = QStringLiteral( "FILL" );
1155 break;
1156 default:
1157 Q_ASSERT( false && "unknown symbol type" );
1158 }
1159 QString s = QStringLiteral( "%1 SYMBOL (%2 layers) color %3" ).arg( t ).arg( mLayers.count() ).arg( QgsColorUtils::colorToString( color() ) );
1160
1161 for ( QgsSymbolLayerList::const_iterator it = mLayers.begin(); it != mLayers.end(); ++it )
1162 {
1163 // TODO:
1164 }
1165 return s;
1166}
1167
1168void QgsSymbol::toSld( QDomDocument &doc, QDomElement &element, QVariantMap props ) const
1169{
1170 props[ QStringLiteral( "alpha" )] = QString::number( opacity() );
1171 double scaleFactor = 1.0;
1172 props[ QStringLiteral( "uom" )] = QgsSymbolLayerUtils::encodeSldUom( outputUnit(), &scaleFactor );
1173 props[ QStringLiteral( "uomScale" )] = ( !qgsDoubleNear( scaleFactor, 1.0 ) ? qgsDoubleToString( scaleFactor ) : QString() );
1174
1175 for ( QgsSymbolLayerList::const_iterator it = mLayers.begin(); it != mLayers.end(); ++it )
1176 {
1177 ( *it )->toSld( doc, element, props );
1178 }
1179}
1180
1182{
1184 for ( QgsSymbolLayerList::const_iterator it = mLayers.begin(); it != mLayers.end(); ++it )
1185 {
1186 QgsSymbolLayer *layer = ( *it )->clone();
1187 layer->setLocked( ( *it )->isLocked() );
1188 layer->setRenderingPass( ( *it )->renderingPass() );
1189 layer->setEnabled( ( *it )->enabled() );
1190 layer->setId( ( *it )->id() );
1191 layer->setUserFlags( ( *it )->userFlags() );
1192 lst.append( layer );
1193 }
1194 return lst;
1195}
1196
1197void QgsSymbol::renderUsingLayer( QgsSymbolLayer *layer, QgsSymbolRenderContext &context, Qgis::GeometryType geometryType, const QPolygonF *points, const QVector<QPolygonF> *rings )
1198{
1199 Q_ASSERT( layer->type() == Qgis::SymbolType::Hybrid );
1200
1201 if ( layer->dataDefinedProperties().hasActiveProperties() && !layer->dataDefinedProperties().valueAsBool( QgsSymbolLayer::Property::LayerEnabled, context.renderContext().expressionContext(), true ) )
1202 return;
1203
1204 QgsGeometryGeneratorSymbolLayer *generatorLayer = static_cast<QgsGeometryGeneratorSymbolLayer *>( layer );
1205
1206 QgsPaintEffect *effect = generatorLayer->paintEffect();
1207 if ( effect && effect->enabled() )
1208 {
1209 QgsEffectPainter p( context.renderContext(), effect );
1210 generatorLayer->render( context, geometryType, points, rings );
1211 }
1212 else
1213 {
1214 generatorLayer->render( context, geometryType, points, rings );
1215 }
1216}
1217
1218QSet<QString> QgsSymbol::usedAttributes( const QgsRenderContext &context ) const
1219{
1220 // calling referencedFields() with ignoreContext=true because in our expression context
1221 // we do not have valid QgsFields yet - because of that the field names from expressions
1222 // wouldn't get reported
1223 QSet<QString> attributes = mDataDefinedProperties.referencedFields( context.expressionContext(), true );
1224 QgsSymbolLayerList::const_iterator sIt = mLayers.constBegin();
1225 for ( ; sIt != mLayers.constEnd(); ++sIt )
1226 {
1227 if ( *sIt )
1228 {
1229 attributes.unite( ( *sIt )->usedAttributes( context ) );
1230 }
1231 }
1232 return attributes;
1233}
1234
1236{
1237 mDataDefinedProperties.setProperty( key, property );
1238}
1239
1241{
1242 if ( mDataDefinedProperties.hasActiveProperties() )
1243 return true;
1244
1245 for ( QgsSymbolLayer *layer : mLayers )
1246 {
1247 if ( layer->hasDataDefinedProperties() )
1248 return true;
1249 }
1250 return false;
1251}
1252
1254{
1255 for ( QgsSymbolLayer *layer : mLayers )
1256 {
1257 if ( layer->canCauseArtifactsBetweenAdjacentTiles() )
1258 return true;
1259 }
1260 return false;
1261}
1262
1269
1276
1278
1282class ExpressionContextScopePopper
1283{
1284 public:
1285
1286 ExpressionContextScopePopper() = default;
1287
1288 ~ExpressionContextScopePopper()
1289 {
1290 if ( context )
1291 context->popScope();
1292 }
1293
1294 QgsExpressionContext *context = nullptr;
1295};
1296
1300class GeometryRestorer
1301{
1302 public:
1303 GeometryRestorer( QgsRenderContext &context )
1304 : mContext( context ),
1305 mGeometry( context.geometry() )
1306 {}
1307
1308 ~GeometryRestorer()
1309 {
1310 mContext.setGeometry( mGeometry );
1311 }
1312
1313 private:
1314 QgsRenderContext &mContext;
1315 const QgsAbstractGeometry *mGeometry;
1316};
1318
1319void QgsSymbol::renderFeature( const QgsFeature &feature, QgsRenderContext &context, int layer, bool selected, bool drawVertexMarker, Qgis::VertexMarkerType currentVertexMarkerType, double currentVertexMarkerSize )
1320{
1321 if ( context.renderingStopped() )
1322 return;
1323
1324 const QgsGeometry geom = feature.geometry();
1325 if ( geom.isNull() )
1326 {
1327 return;
1328 }
1329
1330 GeometryRestorer geomRestorer( context );
1331
1332 bool usingSegmentizedGeometry = false;
1333 context.setGeometry( geom.constGet() );
1334
1335 if ( geom.type() != Qgis::GeometryType::Point && !geom.boundingBox().isNull() )
1336 {
1337 try
1338 {
1339 const QPointF boundsOrigin = _getPoint( context, QgsPoint( geom.boundingBox().xMinimum(), geom.boundingBox().yMinimum() ) );
1340 if ( std::isfinite( boundsOrigin.x() ) && std::isfinite( boundsOrigin.y() ) )
1341 context.setTextureOrigin( boundsOrigin );
1342 }
1343 catch ( QgsCsException & )
1344 {
1345
1346 }
1347 }
1348
1349 bool clippingEnabled = clipFeaturesToExtent();
1350 // do any symbol layers prevent feature clipping?
1351 for ( QgsSymbolLayer *layer : std::as_const( mLayers ) )
1352 {
1354 {
1355 clippingEnabled = false;
1356 break;
1357 }
1358 }
1359 if ( clippingEnabled && context.testFlag( Qgis::RenderContextFlag::RenderMapTile ) )
1360 {
1361 // If the "avoid artifacts between adjacent tiles" flag is set (RenderMapTile), then we'll force disable
1362 // the geometry clipping IF (and only if) this symbol can potentially have rendering artifacts when rendered as map tiles.
1363 // If the symbol won't have any artifacts anyway, then it's pointless and incredibly expensive to skip the clipping!
1365 {
1366 clippingEnabled = false;
1367 }
1368 }
1369 if ( context.extent().isEmpty() )
1370 clippingEnabled = false;
1371
1372 mSymbolRenderContext->setGeometryPartCount( geom.constGet()->partCount() );
1373 mSymbolRenderContext->setGeometryPartNum( 1 );
1374
1375 const bool needsExpressionContext = hasDataDefinedProperties();
1376 ExpressionContextScopePopper scopePopper;
1377 if ( mSymbolRenderContext->expressionContextScope() )
1378 {
1379 if ( needsExpressionContext )
1380 {
1381 // this is somewhat nasty - by appending this scope here it's now owned
1382 // by both mSymbolRenderContext AND context.expressionContext()
1383 // the RAII scopePopper is required to make sure it always has ownership transferred back
1384 // from context.expressionContext(), even if exceptions of other early exits occur in this
1385 // function
1386 context.expressionContext().appendScope( mSymbolRenderContext->expressionContextScope() );
1387 scopePopper.context = &context.expressionContext();
1388
1389 QgsExpressionContextUtils::updateSymbolScope( this, mSymbolRenderContext->expressionContextScope() );
1390 mSymbolRenderContext->expressionContextScope()->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_PART_COUNT, mSymbolRenderContext->geometryPartCount(), true ) );
1391 mSymbolRenderContext->expressionContextScope()->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_PART_NUM, 1, true ) );
1392 }
1393 }
1394
1395 // Collection of markers to paint, only used for no curve types.
1396 QPolygonF markers;
1397
1398 QgsGeometry renderedBoundsGeom;
1399
1400 // Step 1 - collect the set of painter coordinate geometries to render.
1401 // We do this upfront, because we only want to ever do this once, regardless how many symbol layers we need to render.
1402
1403 struct PointInfo
1404 {
1405 QPointF renderPoint;
1406 const QgsPoint *originalGeometry = nullptr;
1407 };
1408 QVector< PointInfo > pointsToRender;
1409
1410 struct LineInfo
1411 {
1412 QPolygonF renderLine;
1413 const QgsCurve *originalGeometry = nullptr;
1414 };
1415 QVector< LineInfo > linesToRender;
1416
1417 struct PolygonInfo
1418 {
1419 QPolygonF renderExterior;
1420 QVector< QPolygonF > renderRings;
1421 const QgsCurvePolygon *originalGeometry = nullptr;
1422 int originalPartIndex = 0;
1423 };
1424 QVector< PolygonInfo > polygonsToRender;
1425
1426 std::function< void ( const QgsAbstractGeometry *, int partIndex )> getPartGeometry;
1427 getPartGeometry = [&pointsToRender, &linesToRender, &polygonsToRender, &getPartGeometry, &context, &clippingEnabled, &markers, &feature, &usingSegmentizedGeometry, this]( const QgsAbstractGeometry * part, int partIndex = 0 )
1428 {
1429 Q_UNUSED( feature )
1430
1431 if ( !part )
1432 return;
1433
1434 // geometry preprocessing
1435 QgsGeometry temporaryGeometryContainer;
1436 const QgsAbstractGeometry *processedGeometry = nullptr;
1437
1438 const bool isMultiPart = qgsgeometry_cast< const QgsGeometryCollection * >( part ) && qgsgeometry_cast< const QgsGeometryCollection * >( part )->numGeometries() > 1;
1439
1440 if ( !isMultiPart )
1441 {
1442 // segmentize curved geometries
1443 const bool needsSegmentizing = QgsWkbTypes::isCurvedType( part->wkbType() ) || part->hasCurvedSegments();
1444 if ( needsSegmentizing )
1445 {
1446 std::unique_ptr< QgsAbstractGeometry > segmentizedPart( part->segmentize( context.segmentationTolerance(), context.segmentationToleranceType() ) );
1447 if ( !segmentizedPart )
1448 {
1449 return;
1450 }
1451 temporaryGeometryContainer.set( segmentizedPart.release() );
1452 processedGeometry = temporaryGeometryContainer.constGet();
1453 usingSegmentizedGeometry = true;
1454 }
1455 else
1456 {
1457 // no segmentation required
1458 processedGeometry = part;
1459 }
1460
1461 // Simplify the geometry, if needed.
1463 {
1464 const int simplifyHints = context.vectorSimplifyMethod().simplifyHints();
1465 const QgsMapToPixelSimplifier simplifier( simplifyHints, context.vectorSimplifyMethod().tolerance(),
1467
1468 std::unique_ptr< QgsAbstractGeometry > simplified( simplifier.simplify( processedGeometry ) );
1469 if ( simplified )
1470 {
1471 temporaryGeometryContainer.set( simplified.release() );
1472 processedGeometry = temporaryGeometryContainer.constGet();
1473 }
1474 }
1475
1476 // clip geometry to render context clipping regions
1477 if ( !context.featureClipGeometry().isEmpty() )
1478 {
1479 // apply feature clipping from context to the rendered geometry only -- just like the render time simplification,
1480 // we should NEVER apply this to the geometry attached to the feature itself. Doing so causes issues with certain
1481 // renderer settings, e.g. if polygons are being rendered using a rule based renderer based on the feature's area,
1482 // then we need to ensure that the original feature area is used instead of the clipped area..
1483 QgsGeos geos( processedGeometry );
1484 std::unique_ptr< QgsAbstractGeometry > clippedGeom( geos.intersection( context.featureClipGeometry().constGet() ) );
1485 if ( clippedGeom )
1486 {
1487 temporaryGeometryContainer.set( clippedGeom.release() );
1488 processedGeometry = temporaryGeometryContainer.constGet();
1489 }
1490 }
1491 }
1492 else
1493 {
1494 // for multipart geometries, the processing is deferred till we're rendering the actual part...
1495 processedGeometry = part;
1496 }
1497
1498 if ( !processedGeometry )
1499 {
1500 // shouldn't happen!
1501 QgsDebugError( QStringLiteral( "No processed geometry to render for part!" ) );
1502 return;
1503 }
1504
1505 switch ( QgsWkbTypes::flatType( processedGeometry->wkbType() ) )
1506 {
1508 {
1510 {
1511 QgsDebugMsgLevel( QStringLiteral( "point can be drawn only with marker symbol!" ), 2 );
1512 break;
1513 }
1514
1515 PointInfo info;
1516 info.originalGeometry = qgsgeometry_cast< const QgsPoint * >( part );
1517 info.renderPoint = _getPoint( context, *info.originalGeometry );
1518 pointsToRender << info;
1519 break;
1520 }
1521
1523 {
1525 {
1526 QgsDebugMsgLevel( QStringLiteral( "linestring can be drawn only with line symbol!" ), 2 );
1527 break;
1528 }
1529
1530 LineInfo info;
1531 info.originalGeometry = qgsgeometry_cast<const QgsCurve *>( part );
1532 info.renderLine = _getLineString( context, *qgsgeometry_cast<const QgsCurve *>( processedGeometry ), clippingEnabled );
1533 linesToRender << info;
1534 break;
1535 }
1536
1539 {
1540 QPolygonF pts;
1542 {
1543 QgsDebugMsgLevel( QStringLiteral( "polygon can be drawn only with fill symbol!" ), 2 );
1544 break;
1545 }
1546
1547 PolygonInfo info;
1548 info.originalGeometry = qgsgeometry_cast<const QgsCurvePolygon *>( part );
1549 info.originalPartIndex = partIndex;
1550 if ( !qgsgeometry_cast<const QgsPolygon *>( processedGeometry )->exteriorRing() )
1551 {
1552 QgsDebugError( QStringLiteral( "cannot render polygon with no exterior ring" ) );
1553 break;
1554 }
1555
1556 _getPolygon( info.renderExterior, info.renderRings, context, *qgsgeometry_cast<const QgsPolygon *>( processedGeometry ), clippingEnabled, mForceRHR );
1557 polygonsToRender << info;
1558 break;
1559 }
1560
1562 {
1563 const QgsMultiPoint *mp = qgsgeometry_cast< const QgsMultiPoint * >( processedGeometry );
1564 markers.reserve( mp->numGeometries() );
1565 }
1566 [[fallthrough]];
1570 {
1571 const QgsGeometryCollection *geomCollection = qgsgeometry_cast<const QgsGeometryCollection *>( processedGeometry );
1572
1573 const unsigned int num = geomCollection->numGeometries();
1574 for ( unsigned int i = 0; i < num; ++i )
1575 {
1576 if ( context.renderingStopped() )
1577 break;
1578
1579 getPartGeometry( geomCollection->geometryN( i ), i );
1580 }
1581 break;
1582 }
1583
1586 {
1588 {
1589 QgsDebugMsgLevel( QStringLiteral( "multi-polygon can be drawn only with fill symbol!" ), 2 );
1590 break;
1591 }
1592
1593 QPolygonF pts;
1594
1595 const QgsGeometryCollection *geomCollection = dynamic_cast<const QgsGeometryCollection *>( processedGeometry );
1596 const unsigned int num = geomCollection->numGeometries();
1597
1598 // Sort components by approximate area (probably a bit faster than using
1599 // area() )
1600 std::map<double, QList<unsigned int> > thisAreaToPartNum;
1601 for ( unsigned int i = 0; i < num; ++i )
1602 {
1603 const QgsRectangle r( geomCollection->geometryN( i )->boundingBox() );
1604 thisAreaToPartNum[ r.width() * r.height()] << i;
1605 }
1606
1607 // Draw starting with larger parts down to smaller parts, so that in
1608 // case of a part being incorrectly inside another part, it is drawn
1609 // on top of it (#15419)
1610 std::map<double, QList<unsigned int> >::const_reverse_iterator iter = thisAreaToPartNum.rbegin();
1611 for ( ; iter != thisAreaToPartNum.rend(); ++iter )
1612 {
1613 const QList<unsigned int> &listPartIndex = iter->second;
1614 for ( int idx = 0; idx < listPartIndex.size(); ++idx )
1615 {
1616 const unsigned i = listPartIndex[idx];
1617 getPartGeometry( geomCollection->geometryN( i ), i );
1618 }
1619 }
1620 break;
1621 }
1622
1623 default:
1624 QgsDebugError( QStringLiteral( "feature %1: unsupported wkb type %2/%3 for rendering" )
1625 .arg( feature.id() )
1626 .arg( QgsWkbTypes::displayString( part->wkbType() ) )
1627 .arg( static_cast< quint32>( part->wkbType() ), 0, 16 ) );
1628 }
1629 };
1630
1631 // Use the simplified type ref when rendering -- this avoids some unnecessary cloning/geometry modification
1632 // (e.g. if the original geometry is a compound curve containing only a linestring curve, we don't have
1633 // to segmentize the geometry before rendering)
1634 getPartGeometry( geom.constGet()->simplifiedTypeRef(), 0 );
1635
1636 // step 2 - determine which layers to render
1637 std::vector< int > layers;
1638 if ( layer == -1 )
1639 {
1640 layers.reserve( mLayers.count() );
1641 for ( int i = 0; i < mLayers.count(); ++i )
1642 layers.emplace_back( i );
1643 }
1644 else
1645 {
1646 layers.emplace_back( layer );
1647 }
1648
1649 // step 3 - render these geometries using the desired symbol layers.
1650
1651 if ( needsExpressionContext )
1652 mSymbolRenderContext->expressionContextScope()->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "symbol_layer_count" ), mLayers.count(), true ) );
1653
1654 const bool maskGeometriesDisabledForSymbol = context.testFlag( Qgis::RenderContextFlag::AlwaysUseGlobalMasks )
1656
1657 for ( const int symbolLayerIndex : layers )
1658 {
1659 QgsSymbolLayer *symbolLayer = mLayers.value( symbolLayerIndex );
1660 if ( !symbolLayer || !symbolLayer->enabled() )
1661 continue;
1662
1663 if ( needsExpressionContext )
1664 mSymbolRenderContext->expressionContextScope()->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "symbol_layer_index" ), symbolLayerIndex + 1, true ) );
1665
1666 // if this symbol layer has associated clip masks, we need to render it to a QPicture first so that we can
1667 // determine the actual rendered bounds of the symbol. We'll then use that to retrieve the clip masks we need
1668 // to apply when painting the symbol via this QPicture.
1669 const bool hasClipGeometries = !maskGeometriesDisabledForSymbol
1672 QPainter *previousPainter = nullptr;
1673 std::unique_ptr< QPicture > renderedPicture;
1674 std::unique_ptr< QPainter > picturePainter;
1675 if ( hasClipGeometries )
1676 {
1677 previousPainter = context.painter();
1678 renderedPicture = std::make_unique< QPicture >();
1679 picturePainter = std::make_unique< QPainter >( renderedPicture.get() );
1680 context.setPainter( picturePainter.get() );
1681 }
1682
1683 symbolLayer->startFeatureRender( feature, context );
1684
1685 switch ( mType )
1686 {
1688 {
1689 int geometryPartNumber = 0;
1690 for ( const PointInfo &point : std::as_const( pointsToRender ) )
1691 {
1692 if ( context.renderingStopped() )
1693 break;
1694
1695 mSymbolRenderContext->setGeometryPartNum( geometryPartNumber + 1 );
1696 if ( needsExpressionContext )
1697 mSymbolRenderContext->expressionContextScope()->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_PART_NUM, geometryPartNumber + 1, true ) );
1698
1699 static_cast<QgsMarkerSymbol *>( this )->renderPoint( point.renderPoint, &feature, context, symbolLayerIndex, selected );
1700 geometryPartNumber++;
1701 }
1702
1703 break;
1704 }
1705
1707 {
1708 if ( linesToRender.empty() )
1709 break;
1710
1711 int geometryPartNumber = 0;
1712 for ( const LineInfo &line : std::as_const( linesToRender ) )
1713 {
1714 if ( context.renderingStopped() )
1715 break;
1716
1717 mSymbolRenderContext->setGeometryPartNum( geometryPartNumber + 1 );
1718 if ( needsExpressionContext )
1719 mSymbolRenderContext->expressionContextScope()->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_PART_NUM, geometryPartNumber + 1, true ) );
1720
1721 context.setGeometry( line.originalGeometry );
1722 static_cast<QgsLineSymbol *>( this )->renderPolyline( line.renderLine, &feature, context, symbolLayerIndex, selected );
1723 geometryPartNumber++;
1724 }
1725 break;
1726 }
1727
1729 {
1730 for ( const PolygonInfo &info : std::as_const( polygonsToRender ) )
1731 {
1732 if ( context.renderingStopped() )
1733 break;
1734
1735 mSymbolRenderContext->setGeometryPartNum( info.originalPartIndex + 1 );
1736 if ( needsExpressionContext )
1737 mSymbolRenderContext->expressionContextScope()->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_PART_NUM, info.originalPartIndex + 1, true ) );
1738
1739 context.setGeometry( info.originalGeometry );
1740 static_cast<QgsFillSymbol *>( this )->renderPolygon( info.renderExterior, ( !info.renderRings.isEmpty() ? &info.renderRings : nullptr ), &feature, context, symbolLayerIndex, selected );
1741 }
1742
1743 break;
1744 }
1745
1747 break;
1748 }
1749
1750 symbolLayer->stopFeatureRender( feature, context );
1751
1752 if ( hasClipGeometries )
1753 {
1754 // restore previous painter
1755 context.setPainter( previousPainter );
1756 picturePainter->end();
1757 picturePainter.reset();
1758
1759 // determine actual rendered bounds of symbol layer, and then buffer out a little to be safe
1760 QRectF maximalBounds = renderedPicture->boundingRect();
1761 constexpr double BOUNDS_MARGIN = 0.05;
1762 maximalBounds.adjust( -maximalBounds.width() * BOUNDS_MARGIN, -maximalBounds.height() * BOUNDS_MARGIN, maximalBounds.width() * BOUNDS_MARGIN, maximalBounds.height() * BOUNDS_MARGIN );
1763
1764 const bool hadClipping = context.painter()->hasClipping();
1765 const QPainterPath oldClipPath = hadClipping ? context.painter()->clipPath() : QPainterPath();
1766
1767 const bool isMasked = symbolLayer->installMasks( context, false, maximalBounds );
1768
1769 context.painter()->drawPicture( QPointF( 0, 0 ), *renderedPicture );
1770
1771 if ( isMasked )
1772 {
1773 context.painter()->setClipPath( oldClipPath );
1774 context.painter()->setClipping( hadClipping );
1775 }
1776 }
1777 }
1778
1779 // step 4 - handle post processing steps
1780 switch ( mType )
1781 {
1783 {
1784 markers.reserve( pointsToRender.size() );
1785 for ( const PointInfo &info : std::as_const( pointsToRender ) )
1786 {
1788 {
1789 const QRectF bounds = static_cast<QgsMarkerSymbol *>( this )->bounds( info.renderPoint, context, feature );
1790 if ( context.hasRenderedFeatureHandlers() )
1791 {
1792 renderedBoundsGeom = renderedBoundsGeom.isNull() ? QgsGeometry::fromRect( bounds )
1793 : QgsGeometry::collectGeometry( QVector< QgsGeometry>() << QgsGeometry::fromRect( QgsRectangle( bounds ) ) << renderedBoundsGeom );
1794 }
1796 {
1797 //draw debugging rect
1798 context.painter()->setPen( Qt::red );
1799 context.painter()->setBrush( QColor( 255, 0, 0, 100 ) );
1800 context.painter()->drawRect( bounds );
1801 }
1802 }
1803
1804 if ( drawVertexMarker && !usingSegmentizedGeometry )
1805 {
1806 markers.append( info.renderPoint );
1807 }
1808 }
1809 break;
1810 }
1811
1813 {
1814 for ( const LineInfo &info : std::as_const( linesToRender ) )
1815 {
1816 if ( context.hasRenderedFeatureHandlers() && !info.renderLine.empty() )
1817 {
1818 renderedBoundsGeom = renderedBoundsGeom.isNull() ? QgsGeometry::fromQPolygonF( info.renderLine )
1819 : QgsGeometry::collectGeometry( QVector< QgsGeometry>() << QgsGeometry::fromQPolygonF( info.renderLine ) << renderedBoundsGeom );
1820 }
1821
1822 if ( drawVertexMarker && !usingSegmentizedGeometry )
1823 {
1824 markers << info.renderLine;
1825 }
1826 }
1827 break;
1828 }
1829
1831 {
1832 for ( const PolygonInfo &info : std::as_const( polygonsToRender ) )
1833 {
1834 if ( context.hasRenderedFeatureHandlers() && !info.renderExterior.empty() )
1835 {
1836 renderedBoundsGeom = renderedBoundsGeom.isNull() ? QgsGeometry::fromQPolygonF( info.renderExterior )
1837 : QgsGeometry::collectGeometry( QVector< QgsGeometry>() << QgsGeometry::fromQPolygonF( info.renderExterior ) << renderedBoundsGeom );
1838 // TODO: consider holes?
1839 }
1840
1841 if ( drawVertexMarker && !usingSegmentizedGeometry )
1842 {
1843 markers << info.renderExterior;
1844
1845 for ( const QPolygonF &hole : info.renderRings )
1846 {
1847 markers << hole;
1848 }
1849 }
1850 }
1851 break;
1852 }
1853
1855 break;
1856 }
1857
1858 if ( context.hasRenderedFeatureHandlers() && !renderedBoundsGeom.isNull() )
1859 {
1861 const QList< QgsRenderedFeatureHandlerInterface * > handlers = context.renderedFeatureHandlers();
1862 for ( QgsRenderedFeatureHandlerInterface *handler : handlers )
1863 handler->handleRenderedFeature( feature, renderedBoundsGeom, featureContext );
1864 }
1865
1866 if ( drawVertexMarker )
1867 {
1868 if ( !markers.isEmpty() && !context.renderingStopped() )
1869 {
1870 const auto constMarkers = markers;
1871 for ( QPointF marker : constMarkers )
1872 {
1873 renderVertexMarker( marker, context, currentVertexMarkerType, currentVertexMarkerSize );
1874 }
1875 }
1876 else
1877 {
1879 const QgsMapToPixel &mtp = context.mapToPixel();
1880
1881 QgsPoint vertexPoint;
1882 QgsVertexId vertexId;
1883 double x, y, z;
1884 QPointF mapPoint;
1885 while ( geom.constGet()->nextVertex( vertexId, vertexPoint ) )
1886 {
1887 //transform
1888 x = vertexPoint.x();
1889 y = vertexPoint.y();
1890 z = 0.0;
1891 if ( ct.isValid() )
1892 {
1893 ct.transformInPlace( x, y, z );
1894 }
1895 mapPoint.setX( x );
1896 mapPoint.setY( y );
1897 mtp.transformInPlace( mapPoint.rx(), mapPoint.ry() );
1898 renderVertexMarker( mapPoint, context, currentVertexMarkerType, currentVertexMarkerSize );
1899 }
1900 }
1901 }
1902}
1903
1905{
1906 return mSymbolRenderContext.get();
1907}
1908
1909void QgsSymbol::renderVertexMarker( QPointF pt, QgsRenderContext &context, Qgis::VertexMarkerType currentVertexMarkerType, double currentVertexMarkerSize )
1910{
1911 int markerSize = context.convertToPainterUnits( currentVertexMarkerSize, Qgis::RenderUnit::Millimeters );
1912 QgsSymbolLayerUtils::drawVertexMarker( pt.x(), pt.y(), *context.painter(), currentVertexMarkerType, markerSize );
1913}
1914
1915void QgsSymbol::initPropertyDefinitions()
1916{
1917 if ( !sPropertyDefinitions.isEmpty() )
1918 return;
1919
1920 QString origin = QStringLiteral( "symbol" );
1921
1922 sPropertyDefinitions = QgsPropertiesDefinition
1923 {
1924 { static_cast< int >( QgsSymbol::Property::Opacity ), QgsPropertyDefinition( "alpha", QObject::tr( "Opacity" ), QgsPropertyDefinition::Opacity, origin )},
1925 };
1926}
1927
1928void QgsSymbol::startFeatureRender( const QgsFeature &feature, QgsRenderContext &context, const int layer )
1929{
1930 if ( layer != -1 )
1931 {
1933 if ( symbolLayer && symbolLayer->enabled() )
1934 {
1935 symbolLayer->startFeatureRender( feature, context );
1936 }
1937 return;
1938 }
1939 else
1940 {
1941 const QList< QgsSymbolLayer * > layers = mLayers;
1942 for ( QgsSymbolLayer *symbolLayer : layers )
1943 {
1944 if ( !symbolLayer->enabled() )
1945 continue;
1946
1947 symbolLayer->startFeatureRender( feature, context );
1948 }
1949 }
1950}
1951
1952void QgsSymbol::stopFeatureRender( const QgsFeature &feature, QgsRenderContext &context, int layer )
1953{
1954 if ( layer != -1 )
1955 {
1957 if ( symbolLayer && symbolLayer->enabled() )
1958 {
1959 symbolLayer->stopFeatureRender( feature, context );
1960 }
1961 return;
1962 }
1963 else
1964 {
1965 const QList< QgsSymbolLayer * > layers = mLayers;
1966 for ( QgsSymbolLayer *symbolLayer : layers )
1967 {
1968 if ( !symbolLayer->enabled() )
1969 continue;
1970
1971 symbolLayer->stopFeatureRender( feature, context );
1972 }
1973 }
1974}
@ CounterClockwise
Counter-clockwise direction.
@ Clockwise
Clockwise direction.
@ IsSymbolLayerSubSymbol
Symbol is being rendered as a sub-symbol of a QgsSymbolLayer (since QGIS 3.38)
@ DisableFeatureClipping
If present, indicates that features should never be clipped to the map extent during rendering.
@ CanCalculateMaskGeometryPerFeature
If present, indicates that mask geometry can safely be calculated per feature for the symbol layer....
QFlags< SymbolPreviewFlag > SymbolPreviewFlags
Symbol preview flags.
Definition qgis.h:625
GeometryType
The geometry types are used to group Qgis::WkbType in a coarse way.
Definition qgis.h:274
@ Polygon
Polygons.
@ Unknown
Unknown types.
@ Null
No geometry.
RenderUnit
Rendering size units.
Definition qgis.h:4494
@ Millimeters
Millimeters.
@ Unknown
Mixed or unknown units.
@ RenderSymbolPreview
The render is for a symbol preview only and map based properties may not be available,...
@ ApplyClipAfterReprojection
Feature geometry clipping to mapExtent() must be performed after the geometries are transformed using...
@ DrawSymbolBounds
Draw bounds of symbols (for debugging/testing)
@ RenderMapTile
Draw map such that there are no problems between adjacent tiles.
@ AlwaysUseGlobalMasks
When applying clipping paths for selective masking, always use global ("entire map") paths,...
@ Antialiasing
Use antialiasing while drawing.
@ HighQualityImageTransforms
Enable high quality image transformations, which results in better appearance of scaled or rotated ra...
@ FlagIncludeCrosshairsForMarkerSymbols
Include a crosshairs reference image in the background of marker symbol previews.
VertexMarkerType
Editing vertex markers, used for showing vertices during a edit operation.
Definition qgis.h:1503
SymbolType
Symbol types.
Definition qgis.h:420
@ Marker
Marker symbol.
@ Line
Line symbol.
@ Fill
Fill symbol.
@ Hybrid
Hybrid symbol.
@ LineString
LineString.
@ MultiPoint
MultiPoint.
@ Polygon
Polygon.
@ MultiPolygon
MultiPolygon.
@ Triangle
Triangle.
@ MultiLineString
MultiLineString.
@ GeometryCollection
GeometryCollection.
@ MultiCurve
MultiCurve.
@ MultiSurface
MultiSurface.
@ Forward
Forward transform (from source to destination)
Abstract base class for all geometries.
virtual const QgsAbstractGeometry * simplifiedTypeRef() const
Returns a reference to the simplest lossless representation of this geometry, e.g.
virtual QgsRectangle boundingBox() const
Returns the minimal bounding box for the geometry.
bool is3D() const
Returns true if the geometry is 3D and contains a z-value.
Qgis::WkbType wkbType() const
Returns the WKB type of the geometry.
virtual int partCount() const =0
Returns count of parts contained in the geometry.
virtual bool hasCurvedSegments() const
Returns true if the geometry contains curved segments.
virtual bool nextVertex(QgsVertexId &id, QgsPoint &vertex) const =0
Returns next vertex id and coordinates.
double valueAsDouble(int key, const QgsExpressionContext &context, double defaultValue=0.0, bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as a double.
static QgsColorSchemeRegistry * colorSchemeRegistry()
Returns the application's color scheme registry, used for managing color schemes.
A 3-dimensional box composed of x, y, z coordinates.
Definition qgsbox3d.h:43
static void trimPolygon(QPolygonF &pts, const QgsRectangle &clipRect)
Trims the given polygon to a rectangular box, by modifying the given polygon in place.
Definition qgsclipper.h:285
static QPolygonF clippedLine(const QgsCurve &curve, const QgsRectangle &clipExtent)
Takes a linestring and clips it to clipExtent.
static void clipped3dLine(const QVector< double > &xIn, const QVector< double > &yIn, const QVector< double > &zIn, QVector< double > &x, QVector< double > &y, QVector< double > &z, const QgsBox3D &clipExtent)
Takes a line with 3D coordinates and clips it to clipExtent.
static QString colorToString(const QColor &color)
Encodes a color into a string value.
Class for doing transforms between two map coordinate systems.
void transformCoords(int numPoint, double *x, double *y, double *z, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward) const
Transform an array of coordinates to the destination CRS.
void transformInPlace(double &x, double &y, double &z, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward) const
Transforms an array of x, y and z double coordinates in place, from the source CRS to the destination...
void transformPolygon(QPolygonF &polygon, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward) const
Transforms a polygon to the destination coordinate system.
bool isValid() const
Returns true if the coordinate transform is valid, ie both the source and destination CRS have been s...
Custom exception class for Coordinate Reference System related exceptions.
Curve polygon geometry type.
int numInteriorRings() const
Returns the number of interior rings contained with the curve polygon.
const QgsCurve * exteriorRing() const
Returns the curve polygon's exterior ring.
const QgsCurve * interiorRing(int i) const
Retrieves an interior ring from the curve polygon.
Abstract base class for curved geometry type.
Definition qgscurve.h:35
Qgis::AngularDirection orientation() const
Returns the curve's orientation, e.g.
Definition qgscurve.cpp:286
virtual int numPoints() const =0
Returns the number of points in the curve.
QgsCurve * segmentize(double tolerance=M_PI_2/90, SegmentationToleranceType toleranceType=MaximumAngle) const override
Returns a geometry without curves.
Definition qgscurve.cpp:175
virtual QPolygonF asQPolygonF() const
Returns a QPolygonF representing the points.
Definition qgscurve.cpp:266
A class to manager painter saving and restoring required for effect drawing.
Single scope for storing variables and functions for use within a QgsExpressionContext.
static QgsExpressionContextScope * updateSymbolScope(const QgsSymbol *symbol, QgsExpressionContextScope *symbolScope=nullptr)
Updates a symbol scope related to a QgsSymbol to an expression context.
static QList< QgsExpressionContextScope * > globalProjectLayerScopes(const QgsMapLayer *layer)
Creates a list of three scopes: global, layer's project and layer.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
static const QString EXPR_GEOMETRY_PART_COUNT
Inbuilt variable name for geometry part count variable.
static const QString EXPR_GEOMETRY_PART_NUM
Inbuilt variable name for geometry part number variable.
void appendScope(QgsExpressionContextScope *scope)
Appends a scope to the end of the context.
void appendScopes(const QList< QgsExpressionContextScope * > &scopes)
Appends a list of scopes to the end of the context.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition qgsfeature.h:58
QgsFeatureId id
Definition qgsfeature.h:66
QgsGeometry geometry
Definition qgsfeature.h:69
Container of fields for a vector layer.
Definition qgsfields.h:46
A fill symbol type, for rendering Polygon and MultiPolygon geometries.
int numGeometries() const
Returns the number of geometries within the collection.
const QgsAbstractGeometry * geometryN(int n) const
Returns a const reference to a geometry from within the collection.
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 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.
bool isEmpty() const
Returns true if the geometry is empty (eg a linestring with no vertices, or a collection with no geom...
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
Does vector analysis using the geos library and handles import, export, exception handling*.
Definition qgsgeos.h:137
Represents a patch shape for use in map legends.
QList< QList< QPolygonF > > toQPolygonF(Qgis::SymbolType type, QSizeF size) const
Converts the patch shape to a set of QPolygonF objects representing how the patch should be drawn for...
Line string geometry type, with support for z-dimension and m-values.
QVector< double > xVector() const
Returns the x vertex values as a vector.
QVector< double > yVector() const
Returns the y vertex values as a vector.
QVector< double > zVector() const
Returns the z vertex values as a vector.
virtual void renderPolygonStroke(const QPolygonF &points, const QVector< QPolygonF > *rings, QgsSymbolRenderContext &context)
Renders the line symbol layer along the outline of polygon, using the given render context.
A line symbol type, for rendering LineString and MultiLineString geometries.
bool setId(const QString &id)
Sets the layer's id.
QgsMapLayer::LayerFlags flags() const
Returns the flags for this layer.
Qgis::LayerType type
Definition qgsmaplayer.h:85
Implementation of GeometrySimplifier using the "MapToPixel" algorithm.
QgsGeometry simplify(const QgsGeometry &geometry) const override
Returns a simplified version the specified geometry.
Perform transforms between map coordinates and device coordinates.
void transformInPlace(double &x, double &y) const
Transforms device coordinates to map 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.
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
static QgsProject * instance()
Returns the QgsProject singleton instance.
void setProperty(int key, const QgsProperty &property)
Adds a property to the collection and takes ownership of it.
bool prepare(const QgsExpressionContext &context=QgsExpressionContext()) const final
Prepares the collection against a specified expression context.
bool hasActiveProperties() const final
Returns true if the collection has any active properties, or false if all properties within the colle...
QSet< QString > referencedFields(const QgsExpressionContext &context=QgsExpressionContext(), bool ignoreContext=false) const final
Returns the set of any fields referenced by the active properties from the collection.
Definition for a property.
Definition qgsproperty.h:45
@ Opacity
Opacity (0-100)
Definition qgsproperty.h:60
A store for object properties.
A rectangle specified with double values.
bool contains(const QgsRectangle &rect) const
Returns true when rectangle contains other rectangle.
double xMinimum() const
Returns the x minimum value (left side of rectangle).
double yMinimum() const
Returns the y minimum value (bottom side of rectangle).
double width() const
Returns the width of the rectangle.
double xMaximum() const
Returns the x maximum value (right side of rectangle).
bool isNull() const
Test if the rectangle is null (holding no spatial information).
double yMaximum() const
Returns the y maximum value (top side of rectangle).
bool isEmpty() const
Returns true if the rectangle has no area.
double height() const
Returns the height of the rectangle.
Contains information about the context of a rendering operation.
void setForceVectorOutput(bool force)
Sets whether rendering operations should use vector operations instead of any faster raster shortcuts...
void setTextureOrigin(const QPointF &origin)
Sets the texture origin, which should be used as a brush transform when rendering using QBrush object...
bool symbolLayerHasClipGeometries(const QString &symbolLayerId) const
Returns true if the symbol layer with matching ID has any associated clip geometries.
bool hasRenderedFeatureHandlers() const
Returns true if the context has any rendered feature handlers.
double segmentationTolerance() const
Gets the segmentation tolerance applied when rendering curved geometries.
double convertToPainterUnits(double size, Qgis::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale(), Qgis::RenderSubcomponentProperty property=Qgis::RenderSubcomponentProperty::Generic) const
Converts a size from the specified units to painter units (pixels).
QPainter * painter()
Returns the destination QPainter for the render operation.
void setPainterFlagsUsingContext(QPainter *painter=nullptr) const
Sets relevant flags on a destination painter, using the flags and settings currently defined for the ...
QgsExpressionContext & expressionContext()
Gets the expression context.
void setGeometry(const QgsAbstractGeometry *geometry)
Sets pointer to original (unsegmentized) geometry.
QgsGeometry featureClipGeometry() const
Returns the geometry to use to clip features at render time.
const QgsRectangle & extent() const
When rendering a map layer, calling this method returns the "clipping" extent for the layer (in the l...
bool testFlag(Qgis::RenderContextFlag flag) const
Check whether a particular flag is enabled.
bool forceVectorOutput() const
Returns true if rendering operations should use vector operations instead of any faster raster shortc...
long long currentFrame() const
Returns the current frame number of the map (in frames per second), for maps which are part of an ani...
void setIsGuiPreview(bool preview)
Sets GUI preview mode.
QgsRectangle mapExtent() const
Returns the original extent of the map being rendered.
QList< QgsRenderedFeatureHandlerInterface * > renderedFeatureHandlers() const
Returns the list of rendered feature handlers to use while rendering map layers.
void setFlag(Qgis::RenderContextFlag flag, bool on=true)
Enable or disable a particular flag (other flags are not affected)
double frameRate() const
Returns the frame rate of the map, for maps which are part of an animation.
const QgsVectorSimplifyMethod & vectorSimplifyMethod() const
Returns the simplification settings to use when rendering vector layers.
const QgsMapToPixel & mapToPixel() const
Returns the context's map to pixel transform, which transforms between map coordinates and device coo...
void setPainter(QPainter *p)
Sets the destination QPainter for the render operation.
bool isSymbolLayerEnabled(const QgsSymbolLayer *layer) const
When rendering a map layer in a second pass (for selective masking), some symbol layers may be disabl...
bool renderingStopped() const
Returns true if the rendering operation has been stopped and any ongoing rendering should be canceled...
static QgsRenderContext fromQPainter(QPainter *painter)
Creates a default render context given a pixel based QPainter destination.
void setExpressionContext(const QgsExpressionContext &context)
Sets the expression context.
QgsCoordinateTransform coordinateTransform() const
Returns the current coordinate transform for the context.
Qgis::RenderContextFlags flags() const
Returns combination of flags used for rendering.
QgsAbstractGeometry::SegmentationToleranceType segmentationToleranceType() const
Gets segmentation tolerance type (maximum angle or maximum difference between curve and approximation...
An interface for classes which provider custom handlers for features rendered as part of a map render...
Stores properties relating to a screen.
double devicePixelRatio() const
Returns the ratio between physical pixels and device-independent pixels for the screen.
bool isValid() const
Returns true if the properties are valid.
void updateRenderContextForScreen(QgsRenderContext &context) const
Updates the settings in a render context to match the screen settings.
static QgsStyle * defaultStyle(bool initialize=true)
Returns the default application-wide style.
Definition qgsstyle.cpp:145
QList< QList< QPolygonF > > defaultPatchAsQPolygonF(Qgis::SymbolType type, QSizeF size) const
Returns the default patch geometry for the given symbol type and size as a set of QPolygonF objects (...
Contains settings relating to symbol animation.
Definition qgssymbol.h:41
bool isAnimated() const
Returns true if the symbol is animated.
Definition qgssymbol.h:64
double frameRate() const
Returns the symbol animation frame rate (in frames per second).
Definition qgssymbol.h:78
static void drawVertexMarker(double x, double y, QPainter &p, Qgis::VertexMarkerType type, int markerSize)
Draws a vertex symbol at (painter) coordinates x, y.
static QString encodeSldUom(Qgis::RenderUnit unit, double *scaleFactor)
Encodes a render unit into an SLD unit of measure string.
virtual void startFeatureRender(const QgsFeature &feature, QgsRenderContext &context)
Called before the layer will be rendered for a particular feature.
bool installMasks(QgsRenderContext &context, bool recursive, const QRectF &rect=QRectF())
When rendering, install masks on context painter.
@ LayerEnabled
Whether symbol layer is enabled.
QgsPaintEffect * paintEffect() const
Returns the current paint effect for the layer.
virtual void startRender(QgsSymbolRenderContext &context)=0
Called before a set of rendering operations commences on the supplied render context.
bool enabled() const
Returns true if symbol layer is enabled and will be drawn.
virtual void stopRender(QgsSymbolRenderContext &context)=0
Called after a set of rendering operations has finished on the supplied render context.
QString id() const
Returns symbol layer identifier This id is unique in the whole project.
virtual void stopFeatureRender(const QgsFeature &feature, QgsRenderContext &context)
Called after the layer has been rendered for a particular feature.
virtual Qgis::SymbolLayerFlags flags() const
Returns flags which control the symbol layer's behavior.
void setSelected(bool selected)
Sets whether symbols should be rendered using the selected symbol coloring and style.
void setOriginalGeometryType(Qgis::GeometryType type)
Sets the geometry type for the original feature geometry being rendered.
void setPatchShape(const QgsLegendPatchShape &shape)
Sets the symbol patch shape, to use if rendering symbol preview icons.
QgsRenderContext & renderContext()
Returns a reference to the context's render context.
Abstract base class for all rendered symbols.
Definition qgssymbol.h:94
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.
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:133
QgsSymbolRenderContext * symbolRenderContext()
Returns the symbol render context.
QgsSymbolLayer * symbolLayer(int layer)
Returns the symbol layer at the specified index.
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:620
static QPolygonF _getLineString(QgsRenderContext &context, const QgsCurve &curve, bool clipToExtent=true)
Creates a line string in screen coordinates from a QgsCurve in map coordinates.
Definition qgssymbol.cpp:82
void stopRender(QgsRenderContext &context)
Ends the rendering process.
qreal mOpacity
Symbol opacity (in the range 0 - 1)
Definition qgssymbol.h:785
Q_DECL_DEPRECATED const QgsVectorLayer * mLayer
Definition qgssymbol.h:801
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:715
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 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.
Definition qgssymbol.h:530
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:495
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:550
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.
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.
Qgis::SymbolType mType
Definition qgssymbol.h:781
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:787
bool mForceRHR
Definition qgssymbol.h:797
QgsSymbolLayerList mLayers
Definition qgssymbol.h:782
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:799
void startFeatureRender(const QgsFeature &feature, QgsRenderContext &context, int layer=-1)
Called before symbol layers will be rendered for a particular feature.
QColor color() const
Returns the symbol's color.
Qgis::RenderUnit outputUnit() const
Returns the units to use for sizes and widths within the symbol.
Qgis::SymbolType type() const
Returns the symbol's type.
Definition qgssymbol.h:156
QgsSymbol(Qgis::SymbolType type, const QgsSymbolLayerList &layers)
Constructor for a QgsSymbol of the specified type.
Definition qgssymbol.cpp:61
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.
Q_DECL_DEPRECATED void setLayer(const QgsVectorLayer *layer)
void exportImage(const QString &path, const QString &format, QSize size)
Export the symbol as an image format, to the specified path and with the given size.
void stopFeatureRender(const QgsFeature &feature, QgsRenderContext &context, int layer=-1)
Called after symbol layers have been rendered for a particular feature.
static QgsSymbol * defaultSymbol(Qgis::GeometryType geomType)
Returns a new default symbol for the specified geometry type.
Represents a vector layer which manages a vector based data sets.
QgsVectorLayer * clone() const override
Returns a new instance equivalent to this one.
double tolerance() const
Gets the tolerance of simplification in map units. Represents the maximum distance in map units betwe...
bool forceLocalOptimization() const
Gets where the simplification executes, after fetch the geometries from provider, or when supported,...
Qgis::VectorRenderingSimplificationFlags simplifyHints() const
Gets the simplification hints of the vector layer managed.
Qgis::VectorSimplificationAlgorithm simplifyAlgorithm() const
Gets the local simplification algorithm of the vector layer managed.
static QString displayString(Qgis::WkbType type)
Returns a non-translated display string type for a WKB type, e.g., the geometry name used in WKT geom...
static bool isCurvedType(Qgis::WkbType type)
Returns true if the WKB type is a curved type or can contain curved geometries.
static Qgis::WkbType flatType(Qgis::WkbType type)
Returns the flat type for a WKB type.
Contains geos related utilities and functions.
Definition qgsgeos.h:75
#define Q_NOWARN_DEPRECATED_POP
Definition qgis.h:6042
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
Definition qgis.h:5382
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:6041
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition qgis.h:5465
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:39
#define QgsDebugError(str)
Definition qgslogger.h:38
QMap< int, QgsPropertyDefinition > QgsPropertiesDefinition
Definition of available properties.
QList< QgsSymbolLayer * > QgsSymbolLayerList
Definition qgssymbol.h:30
Single variable definition for use within a QgsExpressionContextScope.
Utility class for identifying a unique vertex within a geometry.
Definition qgsvertexid.h:30