QGIS API Documentation 4.3.0-Master (c8cdfe19d7b)
Loading...
Searching...
No Matches
qgssymbollayerutils.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgssymbollayerutils.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 "qgssymbollayerutils.h"
17
18#include "qgsapplication.h"
19#include "qgscolorramp.h"
20#include "qgscolorrampimpl.h"
21#include "qgscolorutils.h"
22#include "qgscurvepolygon.h"
23#include "qgsdatadefinedutils.h"
24#include "qgsexpression.h"
26#include "qgsexpressionnode.h"
27#include "qgsfillsymbol.h"
28#include "qgsfillsymbollayer.h"
29#include "qgslinesymbol.h"
30#include "qgslinesymbollayer.h"
31#include "qgslogger.h"
32#include "qgsmarkersymbol.h"
34#include "qgsmasksymbollayer.h"
35#include "qgsogcutils.h"
36#include "qgspainteffect.h"
38#include "qgspathresolver.h"
39#include "qgsreadwritecontext.h"
40#include "qgsrendercontext.h"
41#include "qgsrenderer.h"
42#include "qgssldexportcontext.h"
44#include "qgssymbol.h"
45#include "qgssymbollayer.h"
48#include "qgsunittypes.h"
49#include "qgsxmlutils.h"
50
51#include <QColor>
52#include <QDir>
53#include <QDomDocument>
54#include <QDomElement>
55#include <QDomNode>
56#include <QFont>
57#include <QIcon>
58#include <QMimeData>
59#include <QPainter>
60#include <QPicture>
61#include <QRegularExpression>
62#include <QSettings>
63#include <QString>
64#include <QUrl>
65#include <QUrlQuery>
66#include <qmath.h>
67
68using namespace Qt::StringLiterals;
69
70#define POINTS_TO_MM 2.83464567
71
72QString QgsSymbolLayerUtils::encodeColor( const QColor &color )
73{
74 return u"%1,%2,%3,%4"_s.arg( color.red() ).arg( color.green() ).arg( color.blue() ).arg( color.alpha() );
75}
76
77QColor QgsSymbolLayerUtils::decodeColor( const QString &str )
78{
79 const QStringList lst = str.split( ',' );
80 if ( lst.count() < 3 )
81 {
82 return QColor( str );
83 }
84 int red, green, blue, alpha;
85 red = lst[0].toInt();
86 green = lst[1].toInt();
87 blue = lst[2].toInt();
88 alpha = 255;
89 if ( lst.count() > 3 )
90 {
91 alpha = lst[3].toInt();
92 }
93 return QColor( red, green, blue, alpha );
94}
95
97{
98 return QString::number( alpha / 255.0, 'g', 2 );
99}
100
101int QgsSymbolLayerUtils::decodeSldAlpha( const QString &str )
102{
103 bool ok;
104 double alpha = str.toDouble( &ok );
105 if ( !ok || alpha > 1 )
106 alpha = 255;
107 else if ( alpha < 0 )
108 alpha = 0;
109 return alpha * 255;
110}
111
112QString QgsSymbolLayerUtils::encodeSldFontStyle( QFont::Style style )
113{
114 switch ( style )
115 {
116 case QFont::StyleNormal:
117 return u"normal"_s;
118 case QFont::StyleItalic:
119 return u"italic"_s;
120 case QFont::StyleOblique:
121 return u"oblique"_s;
122 default:
123 return QString();
124 }
125}
126
127QFont::Style QgsSymbolLayerUtils::decodeSldFontStyle( const QString &str )
128{
129 if ( str == "normal"_L1 )
130 return QFont::StyleNormal;
131 if ( str == "italic"_L1 )
132 return QFont::StyleItalic;
133 if ( str == "oblique"_L1 )
134 return QFont::StyleOblique;
135 return QFont::StyleNormal;
136}
137
139{
140 if ( weight == 50 )
141 return u"normal"_s;
142 if ( weight == 75 )
143 return u"bold"_s;
144
145 // QFont::Weight is between 0 and 99
146 // CSS font-weight is between 100 and 900
147 if ( weight < 0 )
148 return u"100"_s;
149 if ( weight > 99 )
150 return u"900"_s;
151 return QString::number( weight * 800 / 99 + 100 );
152}
153
155{
156 bool ok;
157 const int weight = str.toInt( &ok );
158 if ( !ok )
159 return static_cast< int >( QFont::Normal );
160
161 // CSS font-weight is between 100 and 900
162 // QFont::Weight is between 0 and 99
163 if ( weight > 900 )
164 return 99;
165 if ( weight < 100 )
166 return 0;
167 return ( weight - 100 ) * 99 / 800;
168}
169
170QString QgsSymbolLayerUtils::encodePenStyle( Qt::PenStyle style )
171{
172 switch ( style )
173 {
174 case Qt::NoPen:
175 return u"no"_s;
176 case Qt::SolidLine:
177 return u"solid"_s;
178 case Qt::DashLine:
179 return u"dash"_s;
180 case Qt::DotLine:
181 return u"dot"_s;
182 case Qt::DashDotLine:
183 return u"dash dot"_s;
184 case Qt::DashDotDotLine:
185 return u"dash dot dot"_s;
186 default:
187 return u"???"_s;
188 }
189}
190
191Qt::PenStyle QgsSymbolLayerUtils::decodePenStyle( const QString &str )
192{
193 if ( str == "no"_L1 )
194 return Qt::NoPen;
195 if ( str == "solid"_L1 )
196 return Qt::SolidLine;
197 if ( str == "dash"_L1 )
198 return Qt::DashLine;
199 if ( str == "dot"_L1 )
200 return Qt::DotLine;
201 if ( str == "dash dot"_L1 )
202 return Qt::DashDotLine;
203 if ( str == "dash dot dot"_L1 )
204 return Qt::DashDotDotLine;
205 return Qt::SolidLine;
206}
207
208QString QgsSymbolLayerUtils::encodePenJoinStyle( Qt::PenJoinStyle style )
209{
210 switch ( style )
211 {
212 case Qt::BevelJoin:
213 return u"bevel"_s;
214 case Qt::MiterJoin:
215 return u"miter"_s;
216 case Qt::RoundJoin:
217 return u"round"_s;
218 default:
219 return u"???"_s;
220 }
221}
222
223Qt::PenJoinStyle QgsSymbolLayerUtils::decodePenJoinStyle( const QString &str )
224{
225 const QString cleaned = str.toLower().trimmed();
226 if ( cleaned == "bevel"_L1 )
227 return Qt::BevelJoin;
228 if ( cleaned == "miter"_L1 )
229 return Qt::MiterJoin;
230 if ( cleaned == "round"_L1 )
231 return Qt::RoundJoin;
232 return Qt::BevelJoin;
233}
234
235QString QgsSymbolLayerUtils::encodeSldLineJoinStyle( Qt::PenJoinStyle style )
236{
237 switch ( style )
238 {
239 case Qt::BevelJoin:
240 return u"bevel"_s;
241 case Qt::MiterJoin:
242 return u"mitre"_s; //#spellok
243 case Qt::RoundJoin:
244 return u"round"_s;
245 default:
246 return QString();
247 }
248}
249
250Qt::PenJoinStyle QgsSymbolLayerUtils::decodeSldLineJoinStyle( const QString &str )
251{
252 if ( str == "bevel"_L1 )
253 return Qt::BevelJoin;
254 if ( str == "mitre"_L1 ) //#spellok
255 return Qt::MiterJoin;
256 if ( str == "round"_L1 )
257 return Qt::RoundJoin;
258 return Qt::BevelJoin;
259}
260
261QString QgsSymbolLayerUtils::encodePenCapStyle( Qt::PenCapStyle style )
262{
263 switch ( style )
264 {
265 case Qt::SquareCap:
266 return u"square"_s;
267 case Qt::FlatCap:
268 return u"flat"_s;
269 case Qt::RoundCap:
270 return u"round"_s;
271 default:
272 return u"???"_s;
273 }
274}
275
276Qt::PenCapStyle QgsSymbolLayerUtils::decodePenCapStyle( const QString &str )
277{
278 if ( str == "square"_L1 )
279 return Qt::SquareCap;
280 if ( str == "flat"_L1 )
281 return Qt::FlatCap;
282 if ( str == "round"_L1 )
283 return Qt::RoundCap;
284 return Qt::SquareCap;
285}
286
287QString QgsSymbolLayerUtils::encodeSldLineCapStyle( Qt::PenCapStyle style )
288{
289 switch ( style )
290 {
291 case Qt::SquareCap:
292 return u"square"_s;
293 case Qt::FlatCap:
294 return u"butt"_s;
295 case Qt::RoundCap:
296 return u"round"_s;
297 default:
298 return QString();
299 }
300}
301
302Qt::PenCapStyle QgsSymbolLayerUtils::decodeSldLineCapStyle( const QString &str )
303{
304 if ( str == "square"_L1 )
305 return Qt::SquareCap;
306 if ( str == "butt"_L1 )
307 return Qt::FlatCap;
308 if ( str == "round"_L1 )
309 return Qt::RoundCap;
310 return Qt::SquareCap;
311}
312
313QString QgsSymbolLayerUtils::encodeBrushStyle( Qt::BrushStyle style )
314{
315 switch ( style )
316 {
317 case Qt::SolidPattern:
318 return u"solid"_s;
319 case Qt::HorPattern:
320 return u"horizontal"_s;
321 case Qt::VerPattern:
322 return u"vertical"_s;
323 case Qt::CrossPattern:
324 return u"cross"_s;
325 case Qt::BDiagPattern:
326 return u"b_diagonal"_s;
327 case Qt::FDiagPattern:
328 return u"f_diagonal"_s;
329 case Qt::DiagCrossPattern:
330 return u"diagonal_x"_s;
331 case Qt::Dense1Pattern:
332 return u"dense1"_s;
333 case Qt::Dense2Pattern:
334 return u"dense2"_s;
335 case Qt::Dense3Pattern:
336 return u"dense3"_s;
337 case Qt::Dense4Pattern:
338 return u"dense4"_s;
339 case Qt::Dense5Pattern:
340 return u"dense5"_s;
341 case Qt::Dense6Pattern:
342 return u"dense6"_s;
343 case Qt::Dense7Pattern:
344 return u"dense7"_s;
345 case Qt::NoBrush:
346 return u"no"_s;
347 default:
348 return u"???"_s;
349 }
350}
351
352Qt::BrushStyle QgsSymbolLayerUtils::decodeBrushStyle( const QString &str )
353{
354 if ( str == "solid"_L1 )
355 return Qt::SolidPattern;
356 if ( str == "horizontal"_L1 )
357 return Qt::HorPattern;
358 if ( str == "vertical"_L1 )
359 return Qt::VerPattern;
360 if ( str == "cross"_L1 )
361 return Qt::CrossPattern;
362 if ( str == "b_diagonal"_L1 )
363 return Qt::BDiagPattern;
364 if ( str == "f_diagonal"_L1 )
365 return Qt::FDiagPattern;
366 if ( str == "diagonal_x"_L1 )
367 return Qt::DiagCrossPattern;
368 if ( str == "dense1"_L1 )
369 return Qt::Dense1Pattern;
370 if ( str == "dense2"_L1 )
371 return Qt::Dense2Pattern;
372 if ( str == "dense3"_L1 )
373 return Qt::Dense3Pattern;
374 if ( str == "dense4"_L1 )
375 return Qt::Dense4Pattern;
376 if ( str == "dense5"_L1 )
377 return Qt::Dense5Pattern;
378 if ( str == "dense6"_L1 )
379 return Qt::Dense6Pattern;
380 if ( str == "dense7"_L1 )
381 return Qt::Dense7Pattern;
382 if ( str == "no"_L1 )
383 return Qt::NoBrush;
384 return Qt::SolidPattern;
385}
386
387QString QgsSymbolLayerUtils::encodeSldBrushStyle( Qt::BrushStyle style )
388{
389 switch ( style )
390 {
391 case Qt::CrossPattern:
392 return u"cross"_s;
393 case Qt::DiagCrossPattern:
394 return u"x"_s;
395
396 /* The following names are taken from the presentation "GeoServer
397 * Cartographic Rendering" by Andrea Aime at the FOSS4G 2010.
398 * (see http://2010.foss4g.org/presentations/3588.pdf)
399 */
400 case Qt::HorPattern:
401 return u"horline"_s;
402 case Qt::VerPattern:
403 return u"line"_s;
404 case Qt::BDiagPattern:
405 return u"slash"_s;
406 case Qt::FDiagPattern:
407 return u"backslash"_s;
408
409 /* define the other names following the same pattern used above */
410 case Qt::Dense1Pattern:
411 case Qt::Dense2Pattern:
412 case Qt::Dense3Pattern:
413 case Qt::Dense4Pattern:
414 case Qt::Dense5Pattern:
415 case Qt::Dense6Pattern:
416 case Qt::Dense7Pattern:
417 return u"brush://%1"_s.arg( encodeBrushStyle( style ) );
418
419 default:
420 return QString();
421 }
422}
423
424Qt::BrushStyle QgsSymbolLayerUtils::decodeSldBrushStyle( const QString &str )
425{
426 if ( str == "horline"_L1 )
427 return Qt::HorPattern;
428 if ( str == "line"_L1 )
429 return Qt::VerPattern;
430 if ( str == "cross"_L1 )
431 return Qt::CrossPattern;
432 if ( str == "slash"_L1 )
433 return Qt::BDiagPattern;
434 if ( str == "backshash"_L1 )
435 return Qt::FDiagPattern;
436 if ( str == "x"_L1 )
437 return Qt::DiagCrossPattern;
438
439 if ( str.startsWith( "brush://"_L1 ) )
440 return decodeBrushStyle( str.mid( 8 ) );
441
442 return Qt::NoBrush;
443}
444
446{
447 switch ( style )
448 {
449 case Qt::FlatCap:
451 case Qt::SquareCap:
453 case Qt::RoundCap:
455 case Qt::MPenCapStyle:
456 // undocumented?
457 break;
458 }
459
461}
462
464{
465 switch ( style )
466 {
467 case Qt::MiterJoin:
468 case Qt::SvgMiterJoin:
470 case Qt::BevelJoin:
472 case Qt::RoundJoin:
474 case Qt::MPenJoinStyle:
475 // undocumented?
476 break;
477 }
479}
480
481bool QgsSymbolLayerUtils::hasSldSymbolizer( const QDomElement &element )
482{
483 const QDomNodeList children = element.childNodes();
484 for ( int i = 0; i < children.size(); ++i )
485 {
486 const QDomElement childElement = children.at( i ).toElement();
487 if ( childElement.tagName() == "se:LineSymbolizer"_L1 || childElement.tagName() == "se:PointSymbolizer"_L1 || childElement.tagName() == "se:PolygonSymbolizer"_L1 )
488 return true;
489 }
490 return false;
491}
492
494{
495 const QString compareString = string.trimmed();
496 if ( ok )
497 *ok = true;
498
499 if ( compareString.compare( "feature"_L1, Qt::CaseInsensitive ) == 0 )
501 else if ( compareString.compare( "viewport"_L1, Qt::CaseInsensitive ) == 0 )
503
504 if ( ok )
505 *ok = false;
507}
508
510{
511 switch ( coordinateReference )
512 {
514 return u"feature"_s;
516 return u"viewport"_s;
517 }
518 return QString(); // no warnings
519}
520
522{
523 if ( ok )
524 *ok = true;
525
526 static constexpr auto ARROW_HEAD_MAP = makeDataDefinedStringDecoder<QgsArrowSymbolLayer::HeadType>( {
527 { u"single", QgsArrowSymbolLayer::HeadSingle },
528 { u"reversed", QgsArrowSymbolLayer::HeadReversed },
529 { u"double", QgsArrowSymbolLayer::HeadDouble },
530 } );
531
532 auto evaluatedArrowHead = ARROW_HEAD_MAP.lookup( value.toString() );
533 if ( evaluatedArrowHead )
534 {
535 return *evaluatedArrowHead;
536 }
537
538 bool intOk = false;
539 if ( value.toInt() == 1 )
541 else if ( value.toInt() == 2 )
543 else if ( value.toInt( &intOk ) == 0 && intOk )
545
546 if ( ok )
547 *ok = false;
549}
550
552{
553 if ( ok )
554 *ok = true;
555
556 static constexpr auto ARROW_TYPE_MAP = makeDataDefinedStringDecoder<QgsArrowSymbolLayer::ArrowType>( {
558 { u"lefthalf", QgsArrowSymbolLayer::ArrowLeftHalf },
559 { u"righthalf", QgsArrowSymbolLayer::ArrowRightHalf },
560 } );
561
562 auto evaluatedArrowType = ARROW_TYPE_MAP.lookup( value.toString() );
563 if ( evaluatedArrowType )
564 {
565 return *evaluatedArrowType;
566 }
567
568 bool intOk = false;
569 if ( value.toInt() == 1 )
571 else if ( value.toInt() == 2 )
573 else if ( value.toInt( &intOk ) == 0 && intOk )
575
576 if ( ok )
577 *ok = false;
579}
580
582{
583 if ( ok )
584 *ok = true;
585
586 static constexpr auto MARKER_CLIP_MODE_MAP = makeDataDefinedStringDecoder<Qgis::MarkerClipMode>( {
588 { u"shape", Qgis::MarkerClipMode::Shape },
589 { u"centroidwithin", Qgis::MarkerClipMode::CentroidWithin },
590 { u"completelywithin", Qgis::MarkerClipMode::CompletelyWithin },
591 } );
592
593 auto evaluatedMode = MARKER_CLIP_MODE_MAP.lookup( string );
594 if ( evaluatedMode )
595 {
596 return *evaluatedMode;
597 }
598
599 if ( ok )
600 *ok = false;
602}
603
605{
606 switch ( mode )
607 {
609 return u"no"_s;
611 return u"shape"_s;
613 return u"centroid_within"_s;
615 return u"completely_within"_s;
616 }
617 return QString(); // no warnings
618}
619
621{
622 if ( ok )
623 *ok = true;
624
625 static constexpr auto LINE_CLIP_MODE_MAP = makeDataDefinedStringDecoder<Qgis::LineClipMode>( {
627 { u"duringrender", Qgis::LineClipMode::ClipPainterOnly },
628 { u"beforerender", Qgis::LineClipMode::ClipToIntersection },
629 } );
630
631 auto evaluatedMode = LINE_CLIP_MODE_MAP.lookup( string );
632 if ( evaluatedMode )
633 {
634 return *evaluatedMode;
635 }
636
637 if ( ok )
638 *ok = false;
640}
641
643{
644 switch ( mode )
645 {
647 return u"no"_s;
649 return u"during_render"_s;
651 return u"before_render"_s;
652 }
653 return QString(); // no warnings
654}
655
656QString QgsSymbolLayerUtils::encodePoint( QPointF point )
657{
658 return u"%1,%2"_s.arg( qgsDoubleToString( point.x() ), qgsDoubleToString( point.y() ) );
659}
660
661QPointF QgsSymbolLayerUtils::decodePoint( const QString &str )
662{
663 QStringList lst = str.split( ',' );
664 if ( lst.count() != 2 )
665 return QPointF( 0, 0 );
666 return QPointF( lst[0].toDouble(), lst[1].toDouble() );
667}
668
669QPointF QgsSymbolLayerUtils::toPoint( const QVariant &value, bool *ok )
670{
671 if ( ok )
672 *ok = false;
673
674 if ( QgsVariantUtils::isNull( value ) )
675 return QPoint();
676
677 if ( value.userType() == QMetaType::Type::QVariantList )
678 {
679 const QVariantList list = value.toList();
680 if ( list.size() != 2 )
681 {
682 return QPointF();
683 }
684 bool convertOk = false;
685 const double x = list.at( 0 ).toDouble( &convertOk );
686 if ( convertOk )
687 {
688 const double y = list.at( 1 ).toDouble( &convertOk );
689 if ( convertOk )
690 {
691 if ( ok )
692 *ok = true;
693 return QPointF( x, y );
694 }
695 }
696 return QPointF();
697 }
698 else
699 {
700 // can't use decodePoint here -- has no OK handling
701 const QStringList list = value.toString().trimmed().split( ',' );
702 if ( list.count() != 2 )
703 return QPointF();
704 bool convertOk = false;
705 const double x = list.at( 0 ).toDouble( &convertOk );
706 if ( convertOk )
707 {
708 const double y = list.at( 1 ).toDouble( &convertOk );
709 if ( convertOk )
710 {
711 if ( ok )
712 *ok = true;
713 return QPointF( x, y );
714 }
715 }
716 return QPointF();
717 }
718}
719
721{
722 return u"%1,%2"_s.arg( qgsDoubleToString( size.width() ), qgsDoubleToString( size.height() ) );
723}
724
725QSizeF QgsSymbolLayerUtils::decodeSize( const QString &string )
726{
727 QStringList lst = string.split( ',' );
728 if ( lst.count() != 2 )
729 return QSizeF( 0, 0 );
730 return QSizeF( lst[0].toDouble(), lst[1].toDouble() );
731}
732
733QSizeF QgsSymbolLayerUtils::toSize( const QVariant &value, bool *ok )
734{
735 if ( ok )
736 *ok = false;
737
738 if ( QgsVariantUtils::isNull( value ) )
739 return QSizeF();
740
741 if ( value.userType() == QMetaType::Type::QVariantList )
742 {
743 const QVariantList list = value.toList();
744 if ( list.size() != 2 )
745 {
746 return QSizeF();
747 }
748 bool convertOk = false;
749 const double x = list.at( 0 ).toDouble( &convertOk );
750 if ( convertOk )
751 {
752 const double y = list.at( 1 ).toDouble( &convertOk );
753 if ( convertOk )
754 {
755 if ( ok )
756 *ok = true;
757 return QSizeF( x, y );
758 }
759 }
760 return QSizeF();
761 }
762 else
763 {
764 // can't use decodePoint here -- has no OK handling
765 const QStringList list = value.toString().trimmed().split( ',' );
766 if ( list.count() != 2 )
767 return QSizeF();
768 bool convertOk = false;
769 const double x = list.at( 0 ).toDouble( &convertOk );
770 if ( convertOk )
771 {
772 const double y = list.at( 1 ).toDouble( &convertOk );
773 if ( convertOk )
774 {
775 if ( ok )
776 *ok = true;
777 return QSizeF( x, y );
778 }
779 }
780 return QSizeF();
781 }
782}
783
785{
786 return u"3x:%1,%2,%3,%4,%5,%6"_s.arg( qgsDoubleToString( mapUnitScale.minScale ), qgsDoubleToString( mapUnitScale.maxScale ) )
787 .arg( mapUnitScale.minSizeMMEnabled ? 1 : 0 )
788 .arg( mapUnitScale.minSizeMM )
789 .arg( mapUnitScale.maxSizeMMEnabled ? 1 : 0 )
790 .arg( mapUnitScale.maxSizeMM );
791}
792
794{
795 QStringList lst;
796 bool v3 = false;
797 if ( str.startsWith( "3x:"_L1 ) )
798 {
799 v3 = true;
800 const QString chopped = str.mid( 3 );
801 lst = chopped.split( ',' );
802 }
803 else
804 {
805 lst = str.split( ',' );
806 }
807 if ( lst.count() < 2 )
808 return QgsMapUnitScale();
809
810 double minScale = lst[0].toDouble();
811 if ( !v3 )
812 minScale = minScale != 0 ? 1.0 / minScale : 0;
813 double maxScale = lst[1].toDouble();
814 if ( !v3 )
815 maxScale = maxScale != 0 ? 1.0 / maxScale : 0;
816
817 if ( lst.count() < 6 )
818 {
819 // old format
820 return QgsMapUnitScale( minScale, maxScale );
821 }
822
823 QgsMapUnitScale s( minScale, maxScale );
824 s.minSizeMMEnabled = lst[2].toInt();
825 s.minSizeMM = lst[3].toDouble();
826 s.maxSizeMMEnabled = lst[4].toInt();
827 s.maxSizeMM = lst[5].toDouble();
828 return s;
829}
830
831QString QgsSymbolLayerUtils::encodeSldUom( Qgis::RenderUnit unit, double *scaleFactor )
832{
833 switch ( unit )
834 {
836 if ( scaleFactor )
837 *scaleFactor = 0.001; // from millimeters to meters
838 return u"http://www.opengeospatial.org/se/units/metre"_s;
839
841 if ( scaleFactor )
842 *scaleFactor = 1.0; // from meters to meters
843 return u"http://www.opengeospatial.org/se/units/metre"_s;
844
846 default:
847 // pixel is the SLD default uom. The "standardized rendering pixel
848 // size" is defined to be 0.28mm × 0.28mm (millimeters).
849 if ( scaleFactor )
850 *scaleFactor = 1 / 0.28; // from millimeters to pixels
851
852 // http://www.opengeospatial.org/sld/units/pixel
853 return QString();
854 }
855}
856
857Qgis::RenderUnit QgsSymbolLayerUtils::decodeSldUom( const QString &str, double *scaleFactor )
858{
859 if ( str == "http://www.opengeospatial.org/se/units/metre"_L1 )
860 {
861 if ( scaleFactor )
862 *scaleFactor = 1.0; // from meters to meters
864 }
865 else if ( str == "http://www.opengeospatial.org/se/units/foot"_L1 )
866 {
867 if ( scaleFactor )
868 *scaleFactor = 0.3048; // from feet to meters
870 }
871 // pixel is the SLD default uom so it's used if no uom attribute is available or
872 // if uom="http://www.opengeospatial.org/se/units/pixel"
873 else
874 {
875 if ( scaleFactor )
876 *scaleFactor = 1.0; // from pixels to pixels
878 }
879}
880
881QString QgsSymbolLayerUtils::encodeRealVector( const QVector<qreal> &v )
882{
883 QString vectorString;
884 QVector<qreal>::const_iterator it = v.constBegin();
885 for ( ; it != v.constEnd(); ++it )
886 {
887 if ( it != v.constBegin() )
888 {
889 vectorString.append( ';' );
890 }
891 vectorString.append( QString::number( *it ) );
892 }
893 return vectorString;
894}
895
896QVector<qreal> QgsSymbolLayerUtils::decodeRealVector( const QString &s )
897{
898 QVector<qreal> resultVector;
899
900 const QStringList realList = s.split( ';' );
901 QStringList::const_iterator it = realList.constBegin();
902 for ( ; it != realList.constEnd(); ++it )
903 {
904 resultVector.append( it->toDouble() );
905 }
906
907 return resultVector;
908}
909
910QString QgsSymbolLayerUtils::encodeSldRealVector( const QVector<qreal> &v )
911{
912 QString vectorString;
913 QVector<qreal>::const_iterator it = v.constBegin();
914 for ( ; it != v.constEnd(); ++it )
915 {
916 if ( it != v.constBegin() )
917 {
918 vectorString.append( ' ' );
919 }
920 vectorString.append( QString::number( *it ) );
921 }
922 return vectorString;
923}
924
925QVector<qreal> QgsSymbolLayerUtils::decodeSldRealVector( const QString &s )
926{
927 QVector<qreal> resultVector;
928
929 const QStringList realList = s.split( ' ' );
930 QStringList::const_iterator it = realList.constBegin();
931 for ( ; it != realList.constEnd(); ++it )
932 {
933 resultVector.append( it->toDouble() );
934 }
935
936 return resultVector;
937}
938
940{
941 QString encodedValue;
942
943 switch ( scaleMethod )
944 {
946 encodedValue = u"diameter"_s;
947 break;
949 encodedValue = u"area"_s;
950 break;
951 }
952 return encodedValue;
953}
954
956{
957 Qgis::ScaleMethod scaleMethod;
958
959 if ( str == "diameter"_L1 )
960 {
962 }
963 else
964 {
965 scaleMethod = Qgis::ScaleMethod::ScaleArea;
966 }
967
968 return scaleMethod;
969}
970
971QPainter::CompositionMode QgsSymbolLayerUtils::decodeBlendMode( const QString &s )
972{
973 if ( s.compare( "Lighten"_L1, Qt::CaseInsensitive ) == 0 )
974 return QPainter::CompositionMode_Lighten;
975 if ( s.compare( "Screen"_L1, Qt::CaseInsensitive ) == 0 )
976 return QPainter::CompositionMode_Screen;
977 if ( s.compare( "Dodge"_L1, Qt::CaseInsensitive ) == 0 )
978 return QPainter::CompositionMode_ColorDodge;
979 if ( s.compare( "Addition"_L1, Qt::CaseInsensitive ) == 0 )
980 return QPainter::CompositionMode_Plus;
981 if ( s.compare( "Darken"_L1, Qt::CaseInsensitive ) == 0 )
982 return QPainter::CompositionMode_Darken;
983 if ( s.compare( "Multiply"_L1, Qt::CaseInsensitive ) == 0 )
984 return QPainter::CompositionMode_Multiply;
985 if ( s.compare( "Burn"_L1, Qt::CaseInsensitive ) == 0 )
986 return QPainter::CompositionMode_ColorBurn;
987 if ( s.compare( "Overlay"_L1, Qt::CaseInsensitive ) == 0 )
988 return QPainter::CompositionMode_Overlay;
989 if ( s.compare( "SoftLight"_L1, Qt::CaseInsensitive ) == 0 )
990 return QPainter::CompositionMode_SoftLight;
991 if ( s.compare( "HardLight"_L1, Qt::CaseInsensitive ) == 0 )
992 return QPainter::CompositionMode_HardLight;
993 if ( s.compare( "Difference"_L1, Qt::CaseInsensitive ) == 0 )
994 return QPainter::CompositionMode_Difference;
995 if ( s.compare( "Subtract"_L1, Qt::CaseInsensitive ) == 0 )
996 return QPainter::CompositionMode_Exclusion;
997 return QPainter::CompositionMode_SourceOver; // "Normal"
998}
999
1000QIcon QgsSymbolLayerUtils::symbolPreviewIcon( const QgsSymbol *symbol, QSize size, int padding, QgsLegendPatchShape *shape, const QgsScreenProperties &screen )
1001{
1002 return QIcon( symbolPreviewPixmap( symbol, size, padding, nullptr, false, nullptr, shape, screen ) );
1003}
1004
1006 const QgsSymbol *symbol, QSize size, int padding, QgsRenderContext *customContext, bool selected, const QgsExpressionContext *expressionContext, const QgsLegendPatchShape *shape, const QgsScreenProperties &screen
1007)
1008{
1009 Q_ASSERT( symbol );
1010
1011 const double devicePixelRatio = screen.isValid() ? screen.devicePixelRatio() : 1;
1012 QPixmap pixmap( size * devicePixelRatio );
1013 pixmap.setDevicePixelRatio( devicePixelRatio );
1014
1015 pixmap.fill( Qt::transparent );
1016 QPainter painter;
1017 painter.begin( &pixmap );
1018 if ( customContext )
1019 customContext->setPainterFlagsUsingContext( &painter );
1020 else
1021 {
1022 painter.setRenderHint( QPainter::Antialiasing );
1023 painter.setRenderHint( QPainter::SmoothPixmapTransform );
1024 }
1025
1026 if ( customContext )
1027 {
1028 customContext->setPainter( &painter );
1029 }
1030
1031 if ( padding > 0 )
1032 {
1033 size.setWidth( size.rwidth() - ( padding * 2 ) );
1034 size.setHeight( size.rheight() - ( padding * 2 ) );
1035 painter.translate( padding, padding );
1036 }
1037
1038 // If the context has no feature and there are DD properties,
1039 // use a clone and clear some DDs: see issue #19096
1040 // Applying a data defined size to a categorized layer hides its category symbol in the layers panel and legend
1041 if ( symbol->hasDataDefinedProperties() && !( customContext && customContext->expressionContext().hasFeature() ) )
1042 {
1043 std::unique_ptr<QgsSymbol> symbol_noDD( symbol->clone() );
1044 const QgsSymbolLayerList layers( symbol_noDD->symbolLayers() );
1045 for ( const auto &layer : layers )
1046 {
1047 for ( int i = 0; i < layer->dataDefinedProperties().count(); ++i )
1048 {
1049 QgsProperty &prop = layer->dataDefinedProperties().property( i );
1050 // don't clear project color properties -- we want to show them in symbol previews
1051 if ( prop.isActive() && !prop.isProjectColor() )
1052 prop.setActive( false );
1053 }
1054 }
1055 symbol_noDD->drawPreviewIcon( &painter, size, customContext, selected, expressionContext, shape, screen );
1056 }
1057 else
1058 {
1059 std::unique_ptr<QgsSymbol> symbolClone( symbol->clone() );
1060 symbolClone->drawPreviewIcon( &painter, size, customContext, selected, expressionContext, shape, screen );
1061 }
1062
1063 painter.end();
1064 return pixmap;
1065}
1066
1068{
1069 double maxBleed = 0;
1070 for ( int i = 0; i < symbol->symbolLayerCount(); i++ )
1071 {
1072 QgsSymbolLayer *layer = symbol->symbolLayer( i );
1073 const double layerMaxBleed = layer->estimateMaxBleed( context );
1074 maxBleed = layerMaxBleed > maxBleed ? layerMaxBleed : maxBleed;
1075 }
1076
1077 return maxBleed;
1078}
1079
1080QPicture QgsSymbolLayerUtils::symbolLayerPreviewPicture( const QgsSymbolLayer *layer, Qgis::RenderUnit units, QSize size, const QgsMapUnitScale &, Qgis::SymbolType parentSymbolType )
1081{
1082 QPicture picture;
1083 QPainter painter;
1084 painter.begin( &picture );
1085 painter.setRenderHint( QPainter::Antialiasing );
1086 QgsRenderContext renderContext = QgsRenderContext::fromQPainter( &painter );
1089 renderContext.setFlag( Qgis::RenderContextFlag::Antialiasing, true );
1091 renderContext.setPainterFlagsUsingContext( &painter );
1092
1093 QgsSymbolRenderContext symbolContext( renderContext, units, 1.0, false, Qgis::SymbolRenderHints(), nullptr );
1094
1095 switch ( parentSymbolType )
1096 {
1099 break;
1102 break;
1105 break;
1107 break;
1108 }
1109
1110 std::unique_ptr< QgsSymbolLayer > layerClone( layer->clone() );
1111 layerClone->drawPreviewIcon( symbolContext, size );
1112 painter.end();
1113 return picture;
1114}
1115
1117 const QgsSymbolLayer *layer, Qgis::RenderUnit u, QSize size, const QgsMapUnitScale &, Qgis::SymbolType parentSymbolType, QgsMapLayer *mapLayer, const QgsScreenProperties &screen
1118)
1119{
1120 const double devicePixelRatio = screen.isValid() ? screen.devicePixelRatio() : 1;
1121 QPixmap pixmap( size * devicePixelRatio );
1122 pixmap.setDevicePixelRatio( devicePixelRatio );
1123 pixmap.fill( Qt::transparent );
1124 QPainter painter;
1125 painter.begin( &pixmap );
1126 painter.setRenderHint( QPainter::Antialiasing );
1127 QgsRenderContext renderContext = QgsRenderContext::fromQPainter( &painter );
1128
1129 if ( screen.isValid() )
1130 {
1131 screen.updateRenderContextForScreen( renderContext );
1132 }
1133
1136 renderContext.setDevicePixelRatio( devicePixelRatio );
1137 // build a minimal expression context
1138 QgsExpressionContext expContext;
1140 renderContext.setExpressionContext( expContext );
1141
1142 QgsSymbolRenderContext symbolContext( renderContext, u, 1.0, false, Qgis::SymbolRenderHints(), nullptr );
1143
1144 switch ( parentSymbolType )
1145 {
1148 break;
1151 break;
1154 break;
1156 break;
1157 }
1158
1159 std::unique_ptr< QgsSymbolLayer > layerClone( layer->clone() );
1160 layerClone->drawPreviewIcon( symbolContext, size );
1161 painter.end();
1162 return QIcon( pixmap );
1163}
1164
1165QIcon QgsSymbolLayerUtils::colorRampPreviewIcon( QgsColorRamp *ramp, QSize size, int padding )
1166{
1167 return QIcon( colorRampPreviewPixmap( ramp, size, padding ) );
1168}
1169
1170QPixmap QgsSymbolLayerUtils::colorRampPreviewPixmap( QgsColorRamp *ramp, QSize size, int padding, Qt::Orientation direction, bool flipDirection, bool drawTransparentBackground )
1171{
1172 QPixmap pixmap( size );
1173 pixmap.fill( Qt::transparent );
1174 // pixmap.fill( Qt::white ); // this makes the background white instead of transparent
1175 QPainter painter;
1176 painter.begin( &pixmap );
1177
1178 //draw stippled background, for transparent images
1179 if ( drawTransparentBackground )
1180 drawStippledBackground( &painter, QRect( padding, padding, size.width() - padding * 2, size.height() - padding * 2 ) );
1181
1182 // antialiasing makes the colors duller, and no point in antialiasing a color ramp
1183 // painter.setRenderHint( QPainter::Antialiasing );
1184 switch ( direction )
1185 {
1186 case Qt::Horizontal:
1187 {
1188 for ( int i = 0; i < size.width(); i++ )
1189 {
1190 const QPen pen( ramp->color( static_cast< double >( i ) / size.width() ) );
1191 painter.setPen( pen );
1192 const int x = flipDirection ? size.width() - i - 1 : i;
1193 painter.drawLine( QLineF( x, 0 + padding, x, size.height() - 1 - padding ) );
1194 }
1195 break;
1196 }
1197
1198 case Qt::Vertical:
1199 {
1200 for ( int i = 0; i < size.height(); i++ )
1201 {
1202 const QPen pen( ramp->color( static_cast< double >( i ) / size.height() ) );
1203 painter.setPen( pen );
1204 const int y = flipDirection ? size.height() - i - 1 : i;
1205 painter.drawLine( QLineF( 0 + padding, y, size.width() - 1 - padding, y ) );
1206 }
1207 break;
1208 }
1209 }
1210
1211 painter.end();
1212 return pixmap;
1213}
1214
1215void QgsSymbolLayerUtils::drawStippledBackground( QPainter *painter, QRect rect )
1216{
1217 // create a 2x2 checker-board image
1218 uchar pixDataRGB[] = { 255, 255, 255, 255, 127, 127, 127, 255, 127, 127, 127, 255, 255, 255, 255, 255 };
1219 const QImage img( pixDataRGB, 2, 2, 8, QImage::Format_ARGB32 );
1220 // scale it to rect so at least 5 patterns are shown
1221 const int width = ( rect.width() < rect.height() ) ? rect.width() / 2.5 : rect.height() / 2.5;
1222 const QPixmap pix = QPixmap::fromImage( img.scaled( width, width ) );
1223 // fill rect with texture
1224 QBrush brush;
1225 brush.setTexture( pix );
1226 painter->fillRect( rect, brush );
1227}
1228
1229void QgsSymbolLayerUtils::drawVertexMarker( double x, double y, QPainter &p, Qgis::VertexMarkerType type, int markerSize )
1230{
1231 const qreal s = ( markerSize - 1 ) / 2.0;
1232
1233 switch ( type )
1234 {
1236 p.setPen( QColor( 50, 100, 120, 200 ) );
1237 p.setBrush( QColor( 200, 200, 210, 120 ) );
1238 p.drawEllipse( x - s, y - s, s * 2, s * 2 );
1239 break;
1241 p.setPen( QColor( 255, 0, 0 ) );
1242 p.drawLine( x - s, y + s, x + s, y - s );
1243 p.drawLine( x - s, y - s, x + s, y + s );
1244 break;
1246 break;
1247 }
1248}
1249
1250#include <QPolygonF>
1251
1252#include <cmath>
1253#include <cfloat>
1254
1255static QPolygonF makeOffsetGeometry( const QgsPolylineXY &polyline )
1256{
1257 int i, pointCount = polyline.count();
1258
1259 QPolygonF resultLine;
1260 resultLine.resize( pointCount );
1261
1262 const QgsPointXY *tempPtr = polyline.data();
1263
1264 for ( i = 0; i < pointCount; ++i, tempPtr++ )
1265 resultLine[i] = QPointF( tempPtr->x(), tempPtr->y() );
1266
1267 return resultLine;
1268}
1269static QList<QPolygonF> makeOffsetGeometry( const QgsPolygonXY &polygon )
1270{
1271 QList<QPolygonF> resultGeom;
1272 resultGeom.reserve( polygon.size() );
1273 for ( int ring = 0; ring < polygon.size(); ++ring )
1274 resultGeom.append( makeOffsetGeometry( polygon[ring] ) );
1275 return resultGeom;
1276}
1277
1278QList<QPolygonF> offsetLine( QPolygonF polyline, double dist, Qgis::GeometryType geometryType )
1279{
1280 QList<QPolygonF> resultLine;
1281
1282 if ( polyline.count() < 2 )
1283 {
1284 resultLine.append( polyline );
1285 return resultLine;
1286 }
1287
1288 unsigned int i, pointCount = polyline.count();
1289
1290 QgsPolylineXY tempPolyline( pointCount );
1291 QPointF *tempPtr = polyline.data();
1292 for ( i = 0; i < pointCount; ++i, tempPtr++ )
1293 tempPolyline[i] = QgsPointXY( tempPtr->rx(), tempPtr->ry() );
1294
1295 QgsGeometry tempGeometry = geometryType == Qgis::GeometryType::Polygon ? QgsGeometry::fromPolygonXY( QgsPolygonXY() << tempPolyline ) : QgsGeometry::fromPolylineXY( tempPolyline );
1296 if ( !tempGeometry.isNull() )
1297 {
1298 const int quadSegments = 0; // we want miter joins, not round joins
1299 const double miterLimit = 2.0; // the default value in GEOS (5.0) allows for fairly sharp endings
1300 QgsGeometry offsetGeom;
1301 if ( geometryType == Qgis::GeometryType::Polygon )
1302 offsetGeom = tempGeometry.buffer( -dist, quadSegments, Qgis::EndCapStyle::Flat, Qgis::JoinStyle::Miter, miterLimit );
1303 else
1304 offsetGeom = tempGeometry.offsetCurve( dist, quadSegments, Qgis::JoinStyle::Miter, miterLimit );
1305
1306 if ( !offsetGeom.isNull() )
1307 {
1308 tempGeometry = offsetGeom;
1309
1310 if ( QgsWkbTypes::flatType( tempGeometry.wkbType() ) == Qgis::WkbType::LineString )
1311 {
1312 const QgsPolylineXY line = tempGeometry.asPolyline();
1313 resultLine.append( makeOffsetGeometry( line ) );
1314 return resultLine;
1315 }
1316 else if ( QgsWkbTypes::flatType( tempGeometry.wkbType() ) == Qgis::WkbType::Polygon )
1317 {
1318 resultLine.append( makeOffsetGeometry( tempGeometry.asPolygon() ) );
1319 return resultLine;
1320 }
1321 else if ( QgsWkbTypes::flatType( tempGeometry.wkbType() ) == Qgis::WkbType::MultiLineString )
1322 {
1323 QgsMultiPolylineXY tempMPolyline = tempGeometry.asMultiPolyline();
1324 resultLine.reserve( tempMPolyline.count() );
1325 for ( int part = 0; part < tempMPolyline.count(); ++part )
1326 {
1327 resultLine.append( makeOffsetGeometry( tempMPolyline[part] ) );
1328 }
1329 return resultLine;
1330 }
1331 else if ( QgsWkbTypes::flatType( tempGeometry.wkbType() ) == Qgis::WkbType::MultiPolygon )
1332 {
1333 QgsMultiPolygonXY tempMPolygon = tempGeometry.asMultiPolygon();
1334 resultLine.reserve( tempMPolygon.count() );
1335 for ( int part = 0; part < tempMPolygon.count(); ++part )
1336 {
1337 resultLine.append( makeOffsetGeometry( tempMPolygon[part] ) );
1338 }
1339 return resultLine;
1340 }
1341 }
1342 }
1343
1344 // returns original polyline when 'GEOSOffsetCurve' fails!
1345 resultLine.append( polyline );
1346 return resultLine;
1347}
1348
1350
1351
1352std::unique_ptr< QgsSymbol > QgsSymbolLayerUtils::loadSymbol( const QDomElement &element, const QgsReadWriteContext &context )
1353{
1354 if ( element.isNull() )
1355 return nullptr;
1356
1357 QgsSymbolLayerList layers;
1358 QDomNode layerNode = element.firstChild();
1359
1360 while ( !layerNode.isNull() )
1361 {
1362 QDomElement e = layerNode.toElement();
1363 if ( !e.isNull() && e.tagName() != "data_defined_properties"_L1 && e.tagName() != "buffer"_L1 )
1364 {
1365 if ( e.tagName() != "layer"_L1 )
1366 {
1367 QgsDebugError( "unknown tag " + e.tagName() );
1368 }
1369 else
1370 {
1371 std::unique_ptr< QgsSymbolLayer > layer = loadSymbolLayer( e, context );
1372 if ( layer )
1373 {
1374 // Dealing with sub-symbols nested into a layer
1375 const QDomElement s = e.firstChildElement( u"symbol"_s );
1376 if ( !s.isNull() )
1377 {
1378 std::unique_ptr< QgsSymbol > subSymbol( loadSymbol( s, context ) );
1379 // special handling for SVG fill symbol layer -- upgrade the subsymbol which
1380 // was historically used for the fill stroke to be dedicated symbol layer instead
1381 // in order to match the behavior of all other fill symbol layer types
1382 if ( dynamic_cast< QgsSVGFillSymbolLayer * >( layer.get() ) )
1383 {
1384 // add the SVG fill first
1385 layers.append( layer.release() );
1386 // then add the layers from the subsymbol stroke outline on top
1387 for ( int i = 0; i < subSymbol->symbolLayerCount(); ++i )
1388 {
1389 layers.append( subSymbol->symbolLayer( i )->clone() );
1390 }
1391 }
1392 else
1393 {
1394 const bool res = layer->setSubSymbol( subSymbol.release() );
1395 if ( !res )
1396 {
1397 QgsDebugError( u"symbol layer refused subsymbol: "_s + s.attribute( "name" ) );
1398 }
1399 layers.append( layer.release() );
1400 }
1401 }
1402 else
1403 {
1404 layers.append( layer.release() );
1405 }
1406 }
1407 }
1408 }
1409 layerNode = layerNode.nextSibling();
1410 }
1411
1412 if ( layers.isEmpty() )
1413 {
1414 QgsDebugError( u"no layers for symbol"_s );
1415 return nullptr;
1416 }
1417
1418 const QString symbolType = element.attribute( u"type"_s );
1419
1420 std::unique_ptr< QgsSymbol > symbol;
1421 if ( symbolType == "line"_L1 )
1422 symbol = std::make_unique< QgsLineSymbol >( layers );
1423 else if ( symbolType == "fill"_L1 )
1424 symbol = std::make_unique< QgsFillSymbol >( layers );
1425 else if ( symbolType == "marker"_L1 )
1426 symbol = std::make_unique< QgsMarkerSymbol >( layers );
1427 else
1428 {
1429 QgsDebugError( "unknown symbol type " + symbolType );
1430 return nullptr;
1431 }
1432
1433 if ( element.hasAttribute( u"outputUnit"_s ) )
1434 {
1435 symbol->setOutputUnit( QgsUnitTypes::decodeRenderUnit( element.attribute( u"outputUnit"_s ) ) );
1436 }
1437 if ( element.hasAttribute( ( u"mapUnitScale"_s ) ) )
1438 {
1439 QgsMapUnitScale mapUnitScale;
1440 const double oldMin = element.attribute( u"mapUnitMinScale"_s, u"0.0"_s ).toDouble();
1441 mapUnitScale.minScale = oldMin != 0 ? 1.0 / oldMin : 0;
1442 const double oldMax = element.attribute( u"mapUnitMaxScale"_s, u"0.0"_s ).toDouble();
1443 mapUnitScale.maxScale = oldMax != 0 ? 1.0 / oldMax : 0;
1444 symbol->setMapUnitScale( mapUnitScale );
1445 }
1446 symbol->setOpacity( element.attribute( u"alpha"_s, u"1.0"_s ).toDouble() );
1447 symbol->setExtentBuffer( element.attribute( u"extent_buffer"_s, u"0.0"_s ).toDouble() );
1448 symbol->setExtentBufferSizeUnit( QgsUnitTypes::decodeRenderUnit( element.attribute( u"extent_buffer_unit"_s, u"MapUnit"_s ) ) );
1449 symbol->setClipFeaturesToExtent( element.attribute( u"clip_to_extent"_s, u"1"_s ).toInt() );
1450 symbol->setForceRHR( element.attribute( u"force_rhr"_s, u"0"_s ).toInt() );
1451 Qgis::SymbolFlags flags;
1452 if ( element.attribute( u"renderer_should_use_levels"_s, u"0"_s ).toInt() )
1454 symbol->setFlags( flags );
1455
1456 symbol->animationSettings().setIsAnimated( element.attribute( u"is_animated"_s, u"0"_s ).toInt() );
1457 symbol->animationSettings().setFrameRate( element.attribute( u"frame_rate"_s, u"10"_s ).toDouble() );
1458
1459 if ( !element.firstChildElement( u"buffer"_s ).isNull() )
1460 {
1461 auto bufferSettings = std::make_unique< QgsSymbolBufferSettings >();
1462 bufferSettings->readXml( element, context );
1463 symbol->setBufferSettings( bufferSettings.release() );
1464 }
1465 else
1466 {
1467 symbol->setBufferSettings( nullptr );
1468 }
1469
1470 const QDomElement ddProps = element.firstChildElement( u"data_defined_properties"_s );
1471 if ( !ddProps.isNull() )
1472 {
1473 symbol->dataDefinedProperties().readXml( ddProps, QgsSymbol::propertyDefinitions() );
1474 }
1475
1476 return symbol;
1477}
1478
1479std::unique_ptr< QgsSymbolLayer > QgsSymbolLayerUtils::loadSymbolLayer( QDomElement &element, const QgsReadWriteContext &context )
1480{
1481 const QString layerClass = element.attribute( u"class"_s );
1482 const bool locked = element.attribute( u"locked"_s ).toInt();
1483 const bool enabled = element.attribute( u"enabled"_s, u"1"_s ).toInt();
1484 const int pass = element.attribute( u"pass"_s ).toInt();
1485 const QString id = element.attribute( u"id"_s );
1486 const QString selectiveMaskingSetId = element.attribute( u"selectiveMaskingSet"_s );
1487 const Qgis::SymbolLayerUserFlags userFlags = qgsFlagKeysToValue( element.attribute( u"userFlags"_s ), Qgis::SymbolLayerUserFlags() );
1488
1489 // parse properties
1490 QVariantMap props = parseProperties( element );
1491
1492 // if there are any paths stored in properties, convert them from relative to absolute
1493 QgsApplication::symbolLayerRegistry()->resolvePaths( layerClass, props, context.pathResolver(), false );
1494
1495 QgsApplication::symbolLayerRegistry()->resolveFonts( layerClass, props, context );
1496
1497 std::unique_ptr< QgsSymbolLayer > layer = QgsApplication::symbolLayerRegistry()->createSymbolLayer( layerClass, props );
1498 if ( layer )
1499 {
1500 layer->setLocked( locked );
1501 layer->setRenderingPass( pass );
1502 layer->setEnabled( enabled );
1503 layer->setUserFlags( userFlags );
1504 layer->setSelectiveMaskingSourceSetId( selectiveMaskingSetId );
1505
1506 // old project format, empty is missing, keep the actual layer one
1507 if ( !id.isEmpty() )
1508 layer->setId( id );
1509
1510 //restore layer effect
1511 const QDomElement effectElem = element.firstChildElement( u"effect"_s );
1512 if ( !effectElem.isNull() )
1513 {
1514 std::unique_ptr< QgsPaintEffect > effect( QgsApplication::paintEffectRegistry()->createEffect( effectElem ) );
1515 if ( effect && !QgsPaintEffectRegistry::isDefaultStack( effect.get() ) )
1516 layer->setPaintEffect( effect.release() );
1517 }
1518
1519 // restore data defined properties
1520 const QDomElement ddProps = element.firstChildElement( u"data_defined_properties"_s );
1521 if ( !ddProps.isNull() )
1522 {
1523 const QgsPropertyCollection prevProperties = layer->dataDefinedProperties();
1524 layer->dataDefinedProperties().readXml( ddProps, QgsSymbolLayer::propertyDefinitions() );
1525
1526 // some symbol layers will be created with data defined properties by default -- we want to retain
1527 // these if they weren't restored from the xml
1528 const QSet< int > oldKeys = prevProperties.propertyKeys();
1529 for ( int key : oldKeys )
1530 {
1531 if ( !layer->dataDefinedProperties().propertyKeys().contains( key ) )
1532 layer->setDataDefinedProperty( static_cast< QgsSymbolLayer::Property >( key ), prevProperties.property( key ) );
1533 }
1534 }
1535
1536 return layer;
1537 }
1538 else
1539 {
1540 QgsDebugError( "unknown class " + layerClass );
1541 return nullptr;
1542 }
1543}
1544
1545static QString _nameForSymbolType( Qgis::SymbolType type )
1546{
1547 switch ( type )
1548 {
1550 return u"line"_s;
1552 return u"marker"_s;
1554 return u"fill"_s;
1555 default:
1556 return QString();
1557 }
1558}
1559
1560QDomElement QgsSymbolLayerUtils::saveSymbol( const QString &name, const QgsSymbol *symbol, QDomDocument &doc, const QgsReadWriteContext &context )
1561{
1562 Q_ASSERT( symbol );
1563 QDomElement symEl = doc.createElement( u"symbol"_s );
1564 symEl.setAttribute( u"type"_s, _nameForSymbolType( symbol->type() ) );
1565 symEl.setAttribute( u"name"_s, name );
1566 symEl.setAttribute( u"alpha"_s, QString::number( symbol->opacity() ) );
1567 symEl.setAttribute( u"clip_to_extent"_s, symbol->clipFeaturesToExtent() ? u"1"_s : u"0"_s );
1568 if ( !qgsDoubleNear( symbol->extentBuffer(), 0 ) )
1569 {
1570 symEl.setAttribute( u"extent_buffer"_s, QString::number( symbol->extentBuffer() ) );
1571 symEl.setAttribute( u"extent_buffer_unit"_s, QgsUnitTypes::encodeUnit( symbol->extentBufferSizeUnit() ) );
1572 }
1573 symEl.setAttribute( u"force_rhr"_s, symbol->forceRHR() ? u"1"_s : u"0"_s );
1575 symEl.setAttribute( u"renderer_should_use_levels"_s, u"1"_s );
1576
1577 symEl.setAttribute( u"is_animated"_s, symbol->animationSettings().isAnimated() ? u"1"_s : u"0"_s );
1578 symEl.setAttribute( u"frame_rate"_s, qgsDoubleToString( symbol->animationSettings().frameRate() ) );
1579
1580 if ( const QgsSymbolBufferSettings *bufferSettings = symbol->bufferSettings() )
1581 bufferSettings->writeXml( symEl, context );
1582
1583 //QgsDebugMsgLevel( "num layers " + QString::number( symbol->symbolLayerCount() ), 2 );
1584
1585 QDomElement ddProps = doc.createElement( u"data_defined_properties"_s );
1587 symEl.appendChild( ddProps );
1588
1589 for ( int i = 0; i < symbol->symbolLayerCount(); i++ )
1590 {
1591 const QgsSymbolLayer *layer = symbol->symbolLayer( i );
1592
1593 QDomElement layerEl = doc.createElement( u"layer"_s );
1594 layerEl.setAttribute( u"class"_s, layer->layerType() );
1595 layerEl.setAttribute( u"enabled"_s, layer->enabled() );
1596 layerEl.setAttribute( u"locked"_s, layer->isLocked() );
1597 layerEl.setAttribute( u"pass"_s, layer->renderingPass() );
1598 layerEl.setAttribute( u"id"_s, layer->id() );
1599 if ( layer->userFlags() != Qgis::SymbolLayerUserFlags() )
1600 layerEl.setAttribute( u"userFlags"_s, qgsFlagValueToKeys( layer->userFlags() ) );
1601 if ( !layer->selectiveMaskingSourceSetId().isEmpty() )
1602 layerEl.setAttribute( u"selectiveMaskingSet"_s, layer->selectiveMaskingSourceSetId() );
1603
1604 QVariantMap props = layer->properties();
1605
1606 // if there are any paths in properties, convert them from absolute to relative
1607 QgsApplication::symbolLayerRegistry()->resolvePaths( layer->layerType(), props, context.pathResolver(), true );
1608
1609 saveProperties( props, doc, layerEl );
1610
1611 if ( layer->paintEffect() && !QgsPaintEffectRegistry::isDefaultStack( layer->paintEffect() ) )
1612 layer->paintEffect()->saveProperties( doc, layerEl );
1613
1614 QDomElement ddProps = doc.createElement( u"data_defined_properties"_s );
1616 layerEl.appendChild( ddProps );
1617
1618 if ( const QgsSymbol *subSymbol = const_cast< QgsSymbolLayer * >( layer )->subSymbol() )
1619 {
1620 const QString subname = u"@%1@%2"_s.arg( name ).arg( i );
1621 const QDomElement subEl = saveSymbol( subname, subSymbol, doc, context );
1622 layerEl.appendChild( subEl );
1623 }
1624 symEl.appendChild( layerEl );
1625 }
1626
1627 return symEl;
1628}
1629
1631{
1632 QDomDocument doc( u"qgis-symbol-definition"_s );
1633 const QDomElement symbolElem = saveSymbol( u"symbol"_s, symbol, doc, QgsReadWriteContext() );
1634 QString props;
1635 QTextStream stream( &props );
1636 symbolElem.save( stream, -1 );
1637 return props;
1638}
1639
1640bool QgsSymbolLayerUtils::createSymbolLayerListFromSld( QDomElement &element, Qgis::GeometryType geomType, QList<QgsSymbolLayer *> &layers )
1641{
1642 QgsDebugMsgLevel( u"Entered."_s, 4 );
1643
1644 if ( element.isNull() )
1645 return false;
1646
1647 const QString symbolizerName = element.localName();
1648
1649 if ( symbolizerName == "PointSymbolizer"_L1 )
1650 {
1651 // first check for Graphic element, nothing will be rendered if not found
1652 const QDomElement graphicElem = element.firstChildElement( u"Graphic"_s );
1653 if ( graphicElem.isNull() )
1654 {
1655 QgsDebugError( u"Graphic element not found in PointSymbolizer"_s );
1656 }
1657 else
1658 {
1659 switch ( geomType )
1660 {
1662 {
1663 // polygon layer and point symbolizer: draw polygon centroid
1664 std::unique_ptr< QgsSymbolLayer> l = QgsApplication::symbolLayerRegistry()->createSymbolLayerFromSld( u"CentroidFill"_s, element );
1665 if ( l )
1666 layers.append( l.release() );
1667
1668 break;
1669 }
1670
1672 {
1673 // point layer and point symbolizer: use markers
1674 std::unique_ptr< QgsSymbolLayer> l( createMarkerLayerFromSld( element ) );
1675 if ( l )
1676 layers.append( l.release() );
1677
1678 break;
1679 }
1680
1682 {
1683 // line layer and point symbolizer: draw central point
1684 std::unique_ptr< QgsSymbolLayer> l = QgsApplication::symbolLayerRegistry()->createSymbolLayerFromSld( u"SimpleMarker"_s, element );
1685 if ( l )
1686 layers.append( l.release() );
1687
1688 break;
1689 }
1690
1693 break;
1694 }
1695 }
1696 }
1697
1698 if ( symbolizerName == "LineSymbolizer"_L1 )
1699 {
1700 // check for Stroke element, nothing will be rendered if not found
1701 const QDomElement strokeElem = element.firstChildElement( u"Stroke"_s );
1702 if ( strokeElem.isNull() )
1703 {
1704 QgsDebugError( u"Stroke element not found in LineSymbolizer"_s );
1705 }
1706 else
1707 {
1708 switch ( geomType )
1709 {
1712 {
1713 // polygon layer and line symbolizer: draw polygon stroke
1714 // line layer and line symbolizer: draw line
1715 std::unique_ptr< QgsSymbolLayer> l = createLineLayerFromSld( element );
1716 if ( l )
1717 layers.append( l.release() );
1718
1719 break;
1720 }
1721
1723 {
1724 // point layer and line symbolizer: draw a little line marker
1725 std::unique_ptr< QgsSymbolLayer> l = QgsApplication::symbolLayerRegistry()->createSymbolLayerFromSld( u"MarkerLine"_s, element );
1726 if ( l )
1727 layers.append( l.release() );
1728
1729 break;
1730 }
1731
1734 break;
1735 }
1736 }
1737 }
1738
1739 if ( symbolizerName == "PolygonSymbolizer"_L1 )
1740 {
1741 // get Fill and Stroke elements, nothing will be rendered if both are missing
1742 const QDomElement fillElem = element.firstChildElement( u"Fill"_s );
1743 const QDomElement strokeElem = element.firstChildElement( u"Stroke"_s );
1744 if ( fillElem.isNull() && strokeElem.isNull() )
1745 {
1746 QgsDebugError( u"neither Fill nor Stroke element not found in PolygonSymbolizer"_s );
1747 }
1748 else
1749 {
1750 switch ( geomType )
1751 {
1753 {
1754 // polygon layer and polygon symbolizer: draw fill
1755
1756 std::unique_ptr< QgsSymbolLayer > l = createFillLayerFromSld( element );
1757 if ( l )
1758 {
1759 QgsSymbolLayer *lastLayer = l.get();
1760 layers.append( l.release() );
1761
1762 // SVGFill and SimpleFill symbolLayerV2 supports stroke internally,
1763 // so don't go forward to create a different symbolLayerV2 for stroke
1764 if ( lastLayer->layerType() == "SimpleFill"_L1 || lastLayer->layerType() == "SVGFill"_L1 )
1765 break;
1766 }
1767
1768 // now create polygon stroke
1769 // polygon layer and polygon symbolizer: draw polygon stroke
1770 l = createLineLayerFromSld( element );
1771 if ( l )
1772 layers.append( l.release() );
1773
1774 break;
1775 }
1776
1778 {
1779 // line layer and polygon symbolizer: draw line
1780 std::unique_ptr< QgsSymbolLayer > l = createLineLayerFromSld( element );
1781 if ( l )
1782 layers.append( l.release() );
1783
1784 break;
1785 }
1786
1788 {
1789 // point layer and polygon symbolizer: draw a square marker
1790 convertPolygonSymbolizerToPointMarker( element, layers );
1791 break;
1792 }
1795 break;
1796 }
1797 }
1798 }
1799
1800 return true;
1801}
1802
1803std::unique_ptr< QgsSymbolLayer > QgsSymbolLayerUtils::createFillLayerFromSld( QDomElement &element )
1804{
1805 const QDomElement fillElem = element.firstChildElement( u"Fill"_s );
1806 if ( fillElem.isNull() )
1807 {
1808 QgsDebugError( u"Fill element not found"_s );
1809 return nullptr;
1810 }
1811
1812 std::unique_ptr< QgsSymbolLayer > l;
1813
1814 if ( needLinePatternFill( element ) )
1815 l = QgsApplication::symbolLayerRegistry()->createSymbolLayerFromSld( u"LinePatternFill"_s, element );
1816 else if ( needPointPatternFill( element ) )
1817 l = QgsApplication::symbolLayerRegistry()->createSymbolLayerFromSld( u"PointPatternFill"_s, element );
1818 else if ( needSvgFill( element ) )
1819 l = QgsApplication::symbolLayerRegistry()->createSymbolLayerFromSld( u"SVGFill"_s, element );
1820 else if ( needRasterImageFill( element ) )
1821 l = QgsApplication::symbolLayerRegistry()->createSymbolLayerFromSld( u"RasterFill"_s, element );
1822 else
1823 l = QgsApplication::symbolLayerRegistry()->createSymbolLayerFromSld( u"SimpleFill"_s, element );
1824
1825 return l;
1826}
1827
1828std::unique_ptr< QgsSymbolLayer > QgsSymbolLayerUtils::createLineLayerFromSld( QDomElement &element )
1829{
1830 const QDomElement strokeElem = element.firstChildElement( u"Stroke"_s );
1831 if ( strokeElem.isNull() )
1832 {
1833 QgsDebugError( u"Stroke element not found"_s );
1834 return nullptr;
1835 }
1836
1837 std::unique_ptr< QgsSymbolLayer > l;
1838
1839 if ( needMarkerLine( element ) )
1840 l = QgsApplication::symbolLayerRegistry()->createSymbolLayerFromSld( u"MarkerLine"_s, element );
1841 else
1842 l = QgsApplication::symbolLayerRegistry()->createSymbolLayerFromSld( u"SimpleLine"_s, element );
1843
1844 return l;
1845}
1846
1847std::unique_ptr< QgsSymbolLayer > QgsSymbolLayerUtils::createMarkerLayerFromSld( QDomElement &element )
1848{
1849 const QDomElement graphicElem = element.firstChildElement( u"Graphic"_s );
1850 if ( graphicElem.isNull() )
1851 {
1852 QgsDebugError( u"Graphic element not found"_s );
1853 return nullptr;
1854 }
1855
1856 std::unique_ptr< QgsSymbolLayer > l;
1857
1858 if ( needFontMarker( element ) )
1859 l = QgsApplication::symbolLayerRegistry()->createSymbolLayerFromSld( u"FontMarker"_s, element );
1860 else if ( needSvgMarker( element ) )
1861 l = QgsApplication::symbolLayerRegistry()->createSymbolLayerFromSld( u"SvgMarker"_s, element );
1862 else if ( needEllipseMarker( element ) )
1863 l = QgsApplication::symbolLayerRegistry()->createSymbolLayerFromSld( u"EllipseMarker"_s, element );
1864 else if ( needRasterMarker( element ) )
1865 l = QgsApplication::symbolLayerRegistry()->createSymbolLayerFromSld( u"RasterMarker"_s, element );
1866 else
1867 l = QgsApplication::symbolLayerRegistry()->createSymbolLayerFromSld( u"SimpleMarker"_s, element );
1868
1869 return l;
1870}
1871
1873{
1874 return hasExternalGraphicV2( element, u"image/svg+xml"_s );
1875}
1876
1877bool QgsSymbolLayerUtils::hasExternalGraphicV2( const QDomElement &element, const QString format )
1878{
1879 const QDomElement graphicElem = element.firstChildElement( u"Graphic"_s );
1880 if ( graphicElem.isNull() )
1881 return false;
1882
1883 const QDomElement externalGraphicElem = graphicElem.firstChildElement( u"ExternalGraphic"_s );
1884 if ( externalGraphicElem.isNull() )
1885 return false;
1886
1887 // check for format
1888 const QDomElement formatElem = externalGraphicElem.firstChildElement( u"Format"_s );
1889 if ( formatElem.isNull() )
1890 return false;
1891
1892 const QString elementFormat = formatElem.firstChild().nodeValue();
1893 if ( !format.isEmpty() && elementFormat != format )
1894 {
1895 QgsDebugMsgLevel( "unsupported External Graphic format found: " + elementFormat, 4 );
1896 return false;
1897 }
1898
1899 // check for a valid content
1900 const QDomElement onlineResourceElem = externalGraphicElem.firstChildElement( u"OnlineResource"_s );
1901 const QDomElement inlineContentElem = externalGraphicElem.firstChildElement( u"InlineContent"_s );
1902 // NOLINTBEGIN(bugprone-branch-clone)
1903 if ( !onlineResourceElem.isNull() )
1904 {
1905 return true;
1906 }
1907 else if ( !inlineContentElem.isNull() )
1908 {
1909 return true;
1910 }
1911 else
1912 {
1913 return false;
1914 }
1915 // NOLINTEND(bugprone-branch-clone)
1916}
1917
1918bool QgsSymbolLayerUtils::hasWellKnownMark( QDomElement &element )
1919{
1920 const QDomElement graphicElem = element.firstChildElement( u"Graphic"_s );
1921 if ( graphicElem.isNull() )
1922 return false;
1923
1924 const QDomElement markElem = graphicElem.firstChildElement( u"Mark"_s );
1925 if ( markElem.isNull() )
1926 return false;
1927
1928 const QDomElement wellKnownNameElem = markElem.firstChildElement( u"WellKnownName"_s );
1929 return !wellKnownNameElem.isNull();
1930}
1931
1932
1933bool QgsSymbolLayerUtils::needFontMarker( QDomElement &element )
1934{
1935 const QDomElement graphicElem = element.firstChildElement( u"Graphic"_s );
1936 if ( graphicElem.isNull() )
1937 return false;
1938
1939 const QDomElement markElem = graphicElem.firstChildElement( u"Mark"_s );
1940 if ( markElem.isNull() )
1941 return false;
1942
1943 // check for format
1944 const QDomElement formatElem = markElem.firstChildElement( u"Format"_s );
1945 if ( formatElem.isNull() )
1946 return false;
1947
1948 const QString format = formatElem.firstChild().nodeValue();
1949 if ( format != "ttf"_L1 )
1950 {
1951 QgsDebugError( "unsupported Graphic Mark format found: " + format );
1952 return false;
1953 }
1954
1955 // check for a valid content
1956 const QDomElement onlineResourceElem = markElem.firstChildElement( u"OnlineResource"_s );
1957 const QDomElement inlineContentElem = markElem.firstChildElement( u"InlineContent"_s );
1958 if ( !onlineResourceElem.isNull() )
1959 {
1960 // mark with ttf format has a markIndex element
1961 const QDomElement markIndexElem = markElem.firstChildElement( u"MarkIndex"_s );
1962 if ( !markIndexElem.isNull() )
1963 return true;
1964 }
1965 else if ( !inlineContentElem.isNull() )
1966 {
1967 return false; // not implemented yet
1968 }
1969
1970 return false;
1971}
1972
1973bool QgsSymbolLayerUtils::needSvgMarker( const QDomElement &element )
1974{
1975 return hasExternalGraphicV2( element, u"image/svg+xml"_s );
1976}
1977
1978bool QgsSymbolLayerUtils::needRasterMarker( const QDomElement &element )
1979{
1980 // any external graphic except SVGs are considered rasters
1981 return hasExternalGraphicV2( element, QString() ) && !needSvgMarker( element );
1982}
1983
1984bool QgsSymbolLayerUtils::needEllipseMarker( QDomElement &element )
1985{
1986 QDomElement graphicElem = element.firstChildElement( u"Graphic"_s );
1987 if ( graphicElem.isNull() )
1988 return false;
1989
1990 QgsStringMap vendorOptions = QgsSymbolLayerUtils::getVendorOptionList( graphicElem );
1991 for ( QgsStringMap::iterator it = vendorOptions.begin(); it != vendorOptions.end(); ++it )
1992 {
1993 if ( it.key() == "widthHeightFactor"_L1 )
1994 {
1995 return true;
1996 }
1997 }
1998
1999 return false;
2000}
2001
2002bool QgsSymbolLayerUtils::needMarkerLine( QDomElement &element )
2003{
2004 const QDomElement strokeElem = element.firstChildElement( u"Stroke"_s );
2005 if ( strokeElem.isNull() )
2006 return false;
2007
2008 QDomElement graphicStrokeElem = strokeElem.firstChildElement( u"GraphicStroke"_s );
2009 if ( graphicStrokeElem.isNull() )
2010 return false;
2011
2012 return hasWellKnownMark( graphicStrokeElem );
2013}
2014
2016{
2017 const QDomElement fillElem = element.firstChildElement( u"Fill"_s );
2018 if ( fillElem.isNull() )
2019 return false;
2020
2021 const QDomElement graphicFillElem = fillElem.firstChildElement( u"GraphicFill"_s );
2022 if ( graphicFillElem.isNull() )
2023 return false;
2024
2025 QDomElement graphicElem = graphicFillElem.firstChildElement( u"Graphic"_s );
2026 if ( graphicElem.isNull() )
2027 return false;
2028
2029 // line pattern fill uses horline wellknown marker with an angle
2030
2031 QString name;
2032 QColor fillColor, strokeColor;
2033 double size, strokeWidth;
2034 Qt::PenStyle strokeStyle;
2035 if ( !wellKnownMarkerFromSld( graphicElem, name, fillColor, strokeColor, strokeStyle, strokeWidth, size ) )
2036 return false;
2037
2038 if ( name != "horline"_L1 )
2039 return false;
2040
2041 QString angleFunc;
2042 if ( !rotationFromSldElement( graphicElem, angleFunc ) )
2043 return false;
2044
2045 bool ok;
2046 const double angle = angleFunc.toDouble( &ok );
2047 return !( !ok || qgsDoubleNear( angle, 0.0 ) );
2048}
2049
2051{
2052 const QDomElement fillElem = element.firstChildElement( u"Fill"_s );
2053 if ( fillElem.isNull() )
2054 return false;
2055
2056 const QDomElement graphicFillElem = fillElem.firstChildElement( u"GraphicFill"_s );
2057 if ( graphicFillElem.isNull() )
2058 return false;
2059
2060 const QDomElement graphicElem = graphicFillElem.firstChildElement( u"Graphic"_s );
2061 if ( graphicElem.isNull() )
2062 return false;
2063
2064 const QDomElement markElem = graphicElem.firstChildElement( u"Mark"_s );
2065 if ( markElem.isNull() )
2066 return false;
2067
2068 return true;
2069}
2070
2071bool QgsSymbolLayerUtils::needSvgFill( QDomElement &element )
2072{
2073 const QDomElement fillElem = element.firstChildElement( u"Fill"_s );
2074 if ( fillElem.isNull() )
2075 return false;
2076
2077 QDomElement graphicFillElem = fillElem.firstChildElement( u"GraphicFill"_s );
2078 if ( graphicFillElem.isNull() )
2079 return false;
2080
2081 return hasExternalGraphicV2( graphicFillElem, u"image/svg+xml"_s );
2082}
2083
2085{
2086 const QDomElement fillElem = element.firstChildElement( u"Fill"_s );
2087 if ( fillElem.isNull() )
2088 return false;
2089
2090 QDomElement graphicFillElem = fillElem.firstChildElement( u"GraphicFill"_s );
2091 if ( graphicFillElem.isNull() )
2092 return false;
2093
2094 return hasExternalGraphicV2( graphicFillElem, u"image/png"_s ) || hasExternalGraphicV2( graphicFillElem, u"image/jpeg"_s ) || hasExternalGraphicV2( graphicFillElem, u"image/gif"_s );
2095}
2096
2097
2098bool QgsSymbolLayerUtils::convertPolygonSymbolizerToPointMarker( QDomElement &element, QList<QgsSymbolLayer *> &layerList )
2099{
2100 QgsDebugMsgLevel( u"Entered."_s, 4 );
2101
2102 /* SE 1.1 says about PolygonSymbolizer:
2103 if a point geometry is referenced instead of a polygon,
2104 then a small, square, ortho-normal polygon should be
2105 constructed for rendering.
2106 */
2107
2108 QgsSymbolLayerList layers;
2109
2110 // retrieve both Fill and Stroke elements
2111 QDomElement fillElem = element.firstChildElement( u"Fill"_s );
2112 QDomElement strokeElem = element.firstChildElement( u"Stroke"_s );
2113
2114 // first symbol layer
2115 {
2116 bool validFill = false, validStroke = false;
2117
2118 // check for simple fill
2119 // Fill element can contain some SvgParameter elements
2120 QColor fillColor;
2121 Qt::BrushStyle fillStyle;
2122
2123 if ( fillFromSld( fillElem, fillStyle, fillColor ) )
2124 validFill = true;
2125
2126 // check for simple stroke
2127 // Stroke element can contain some SvgParameter elements
2128 QColor strokeColor;
2129 Qt::PenStyle strokeStyle;
2130 double strokeWidth = 1.0, dashOffset = 0.0;
2131 QVector<qreal> customDashPattern;
2132
2133 if ( lineFromSld( strokeElem, strokeStyle, strokeColor, strokeWidth, nullptr, nullptr, &customDashPattern, &dashOffset ) )
2134 validStroke = true;
2135
2136 if ( validFill || validStroke )
2137 {
2138 QVariantMap map;
2139 map[u"name"_s] = u"square"_s;
2140 map[u"color"_s] = QgsColorUtils::colorToString( validFill ? fillColor : Qt::transparent );
2141 map[u"color_border"_s] = QgsColorUtils::colorToString( validStroke ? strokeColor : Qt::transparent );
2142 map[u"size"_s] = QString::number( 6 );
2143 map[u"angle"_s] = QString::number( 0 );
2144 map[u"offset"_s] = encodePoint( QPointF( 0, 0 ) );
2145 layers.append( QgsApplication::symbolLayerRegistry()->createSymbolLayer( u"SimpleMarker"_s, map ).release() );
2146 }
2147 }
2148
2149 // second symbol layer
2150 {
2151 bool validFill = false, validStroke = false;
2152
2153 // check for graphic fill
2154 QString name, format;
2155 int markIndex = -1;
2156 QColor fillColor, strokeColor;
2157 double strokeWidth = 1.0, size = 0.0, angle = 0.0;
2158 QPointF offset;
2159
2160 // Fill element can contain a GraphicFill element
2161 const QDomElement graphicFillElem = fillElem.firstChildElement( u"GraphicFill"_s );
2162 if ( !graphicFillElem.isNull() )
2163 {
2164 // GraphicFill element must contain a Graphic element
2165 QDomElement graphicElem = graphicFillElem.firstChildElement( u"Graphic"_s );
2166 if ( !graphicElem.isNull() )
2167 {
2168 // Graphic element can contains some ExternalGraphic and Mark element
2169 // search for the first supported one and use it
2170 bool found = false;
2171
2172 const QDomElement graphicChildElem = graphicElem.firstChildElement();
2173 while ( !graphicChildElem.isNull() )
2174 {
2175 if ( graphicChildElem.localName() == "Mark"_L1 )
2176 {
2177 // check for a well known name
2178 const QDomElement wellKnownNameElem = graphicChildElem.firstChildElement( u"WellKnownName"_s );
2179 if ( !wellKnownNameElem.isNull() )
2180 {
2181 name = wellKnownNameElem.firstChild().nodeValue();
2182 found = true;
2183 break;
2184 }
2185 }
2186
2187 if ( graphicChildElem.localName() == "ExternalGraphic"_L1 || graphicChildElem.localName() == "Mark"_L1 )
2188 {
2189 // check for external graphic format
2190 const QDomElement formatElem = graphicChildElem.firstChildElement( u"Format"_s );
2191 if ( formatElem.isNull() )
2192 continue;
2193
2194 format = formatElem.firstChild().nodeValue();
2195
2196 // TODO: remove this check when more formats will be supported
2197 // only SVG external graphics are supported in this moment
2198 if ( graphicChildElem.localName() == "ExternalGraphic"_L1 && format != "image/svg+xml"_L1 )
2199 continue;
2200
2201 // TODO: remove this check when more formats will be supported
2202 // only ttf marks are supported in this moment
2203 if ( graphicChildElem.localName() == "Mark"_L1 && format != "ttf"_L1 )
2204 continue;
2205
2206 // check for a valid content
2207 const QDomElement onlineResourceElem = graphicChildElem.firstChildElement( u"OnlineResource"_s );
2208 const QDomElement inlineContentElem = graphicChildElem.firstChildElement( u"InlineContent"_s );
2209
2210 if ( !onlineResourceElem.isNull() )
2211 {
2212 name = onlineResourceElem.attributeNS( u"http://www.w3.org/1999/xlink"_s, u"href"_s );
2213
2214 if ( graphicChildElem.localName() == "Mark"_L1 && format == "ttf"_L1 )
2215 {
2216 // mark with ttf format may have a name like ttf://fontFamily
2217 if ( name.startsWith( "ttf://"_L1 ) )
2218 name = name.mid( 6 );
2219
2220 // mark with ttf format has a markIndex element
2221 const QDomElement markIndexElem = graphicChildElem.firstChildElement( u"MarkIndex"_s );
2222 if ( markIndexElem.isNull() )
2223 continue;
2224
2225 bool ok;
2226 const int v = markIndexElem.firstChild().nodeValue().toInt( &ok );
2227 if ( !ok || v < 0 )
2228 continue;
2229
2230 markIndex = v;
2231 }
2232
2233 found = true;
2234 break;
2235 }
2236#if 0
2237 else if ( !inlineContentElem.isNull() )
2238 continue; // TODO: not implemented yet
2239#endif
2240 else
2241 continue;
2242 }
2243
2244 // if Mark element is present but it doesn't contains neither
2245 // WellKnownName nor OnlineResource nor InlineContent,
2246 // use the default mark (square)
2247 if ( graphicChildElem.localName() == "Mark"_L1 )
2248 {
2249 name = u"square"_s;
2250 found = true;
2251 break;
2252 }
2253 }
2254
2255 // if found a valid Mark, check for its Fill and Stroke element
2256 if ( found && graphicChildElem.localName() == "Mark"_L1 )
2257 {
2258 // XXX: recursive definition!?! couldn't be dangerous???
2259 // to avoid recursion we handle only simple fill and simple stroke
2260
2261 // check for simple fill
2262 // Fill element can contain some SvgParameter elements
2263 Qt::BrushStyle markFillStyle;
2264
2265 QDomElement markFillElem = graphicChildElem.firstChildElement( u"Fill"_s );
2266 if ( fillFromSld( markFillElem, markFillStyle, fillColor ) )
2267 validFill = true;
2268
2269 // check for simple stroke
2270 // Stroke element can contain some SvgParameter elements
2271 Qt::PenStyle strokeStyle;
2272 double strokeWidth = 1.0, dashOffset = 0.0;
2273 QVector<qreal> customDashPattern;
2274
2275 QDomElement markStrokeElem = graphicChildElem.firstChildElement( u"Stroke"_s );
2276 if ( lineFromSld( markStrokeElem, strokeStyle, strokeColor, strokeWidth, nullptr, nullptr, &customDashPattern, &dashOffset ) )
2277 validStroke = true;
2278 }
2279
2280 if ( found )
2281 {
2282 // check for Opacity, Size, Rotation, AnchorPoint, Displacement
2283 const QDomElement opacityElem = graphicElem.firstChildElement( u"Opacity"_s );
2284 if ( !opacityElem.isNull() )
2285 fillColor.setAlpha( decodeSldAlpha( opacityElem.firstChild().nodeValue() ) );
2286
2287 const QDomElement sizeElem = graphicElem.firstChildElement( u"Size"_s );
2288 if ( !sizeElem.isNull() )
2289 {
2290 bool ok;
2291 const double v = sizeElem.firstChild().nodeValue().toDouble( &ok );
2292 if ( ok && v > 0 )
2293 size = v;
2294 }
2295
2296 QString angleFunc;
2297 if ( rotationFromSldElement( graphicElem, angleFunc ) && !angleFunc.isEmpty() )
2298 {
2299 bool ok;
2300 const double v = angleFunc.toDouble( &ok );
2301 if ( ok )
2302 angle = v;
2303 }
2304
2305 displacementFromSldElement( graphicElem, offset );
2306 }
2307 }
2308 }
2309
2310 if ( validFill || validStroke )
2311 {
2312 if ( format == "image/svg+xml"_L1 )
2313 {
2314 QVariantMap map;
2315 map[u"name"_s] = name;
2316 map[u"fill"_s] = fillColor.name();
2317 map[u"outline"_s] = strokeColor.name();
2318 map[u"outline-width"_s] = QString::number( strokeWidth );
2319 if ( !qgsDoubleNear( size, 0.0 ) )
2320 map[u"size"_s] = QString::number( size );
2321 if ( !qgsDoubleNear( angle, 0.0 ) )
2322 map[u"angle"_s] = QString::number( angle );
2323 if ( !offset.isNull() )
2324 map[u"offset"_s] = encodePoint( offset );
2325 layers.append( QgsApplication::symbolLayerRegistry()->createSymbolLayer( u"SvgMarker"_s, map ).release() );
2326 }
2327 else if ( format == "ttf"_L1 )
2328 {
2329 QVariantMap map;
2330 map[u"font"_s] = name;
2331 map[u"chr"_s] = markIndex;
2332 map[u"color"_s] = QgsColorUtils::colorToString( validFill ? fillColor : Qt::transparent );
2333 if ( size > 0 )
2334 map[u"size"_s] = QString::number( size );
2335 if ( !qgsDoubleNear( angle, 0.0 ) )
2336 map[u"angle"_s] = QString::number( angle );
2337 if ( !offset.isNull() )
2338 map[u"offset"_s] = encodePoint( offset );
2339 layers.append( QgsApplication::symbolLayerRegistry()->createSymbolLayer( u"FontMarker"_s, map ).release() );
2340 }
2341 }
2342 }
2343
2344 if ( layers.isEmpty() )
2345 return false;
2346
2347 layerList << layers;
2348 layers.clear();
2349 return true;
2350}
2351
2352void QgsSymbolLayerUtils::fillToSld( QDomDocument &doc, QDomElement &element, Qt::BrushStyle brushStyle, const QColor &color )
2353{
2354 QgsSldExportContext context;
2355 fillToSld( doc, element, context, brushStyle, color );
2356}
2357
2358void QgsSymbolLayerUtils::fillToSld( QDomDocument &doc, QDomElement &element, QgsSldExportContext &context, Qt::BrushStyle brushStyle, const QColor &color )
2359{
2360 QString patternName;
2361 switch ( brushStyle )
2362 {
2363 case Qt::NoBrush:
2364 return;
2365
2366 case Qt::SolidPattern:
2367 if ( color.isValid() )
2368 {
2369 element.appendChild( createSvgParameterElement( doc, u"fill"_s, color.name() ) );
2370 if ( color.alpha() < 255 )
2371 element.appendChild( createSvgParameterElement( doc, u"fill-opacity"_s, encodeSldAlpha( color.alpha() ) ) );
2372 }
2373 return;
2374
2375 case Qt::CrossPattern:
2376 case Qt::DiagCrossPattern:
2377 case Qt::HorPattern:
2378 case Qt::VerPattern:
2379 case Qt::BDiagPattern:
2380 case Qt::FDiagPattern:
2381 case Qt::Dense1Pattern:
2382 case Qt::Dense2Pattern:
2383 case Qt::Dense3Pattern:
2384 case Qt::Dense4Pattern:
2385 case Qt::Dense5Pattern:
2386 case Qt::Dense6Pattern:
2387 case Qt::Dense7Pattern:
2388 patternName = encodeSldBrushStyle( brushStyle );
2389 break;
2390
2391 default:
2392 context.pushWarning( QObject::tr( "Brush style '%1' is not supported for SLD" ).arg( brushStyle ) );
2393 return;
2394 }
2395
2396 QDomElement graphicFillElem = doc.createElement( u"se:GraphicFill"_s );
2397 element.appendChild( graphicFillElem );
2398
2399 QDomElement graphicElem = doc.createElement( u"se:Graphic"_s );
2400 graphicFillElem.appendChild( graphicElem );
2401
2402 const QColor fillColor = patternName.startsWith( "brush://"_L1 ) ? color : QColor();
2403 const QColor strokeColor = !patternName.startsWith( "brush://"_L1 ) ? color : QColor();
2404
2405 /* Use WellKnownName tag to handle QT brush styles. */
2406 wellKnownMarkerToSld( doc, graphicElem, patternName, fillColor, strokeColor, Qt::SolidLine, context, -1, -1 );
2407}
2408
2409bool QgsSymbolLayerUtils::fillFromSld( QDomElement &element, Qt::BrushStyle &brushStyle, QColor &color )
2410{
2411 QgsDebugMsgLevel( u"Entered."_s, 4 );
2412
2413 brushStyle = Qt::SolidPattern;
2414 color = QColor( 128, 128, 128 );
2415
2416 if ( element.isNull() )
2417 {
2418 brushStyle = Qt::NoBrush;
2419 color = QColor();
2420 return true;
2421 }
2422
2423 const QDomElement graphicFillElem = element.firstChildElement( u"GraphicFill"_s );
2424 // if no GraphicFill element is found, it's a solid fill
2425 if ( graphicFillElem.isNull() )
2426 {
2427 QgsStringMap svgParams = getSvgParameterList( element );
2428 for ( QgsStringMap::iterator it = svgParams.begin(); it != svgParams.end(); ++it )
2429 {
2430 QgsDebugMsgLevel( u"found SvgParameter %1: %2"_s.arg( it.key(), it.value() ), 2 );
2431
2432 if ( it.key() == "fill"_L1 )
2433 color = QColor( it.value() );
2434 else if ( it.key() == "fill-opacity"_L1 )
2435 color.setAlpha( decodeSldAlpha( it.value() ) );
2436 }
2437 }
2438 else // wellKnown marker
2439 {
2440 QDomElement graphicElem = graphicFillElem.firstChildElement( u"Graphic"_s );
2441 if ( graphicElem.isNull() )
2442 return false; // Graphic is required within GraphicFill
2443
2444 QString patternName = u"square"_s;
2445 QColor fillColor, strokeColor;
2446 double strokeWidth, size;
2447 Qt::PenStyle strokeStyle;
2448 if ( !wellKnownMarkerFromSld( graphicElem, patternName, fillColor, strokeColor, strokeStyle, strokeWidth, size ) )
2449 return false;
2450
2451 brushStyle = decodeSldBrushStyle( patternName );
2452 if ( brushStyle == Qt::NoBrush )
2453 return false; // unable to decode brush style
2454
2455 const QColor c = patternName.startsWith( "brush://"_L1 ) ? fillColor : strokeColor;
2456 if ( c.isValid() )
2457 color = c;
2458 }
2459
2460 return true;
2461}
2462
2464 QDomDocument &doc,
2465 QDomElement &element,
2466 Qt::PenStyle penStyle,
2467 const QColor &color,
2468 QgsSldExportContext &context,
2469 double width,
2470 const Qt::PenJoinStyle *penJoinStyle,
2471 const Qt::PenCapStyle *penCapStyle,
2472 const QVector<qreal> *customDashPattern,
2473 double dashOffset
2474)
2475{
2476 QVector<qreal> dashPattern;
2477 const QVector<qreal> *pattern = &dashPattern;
2478
2479 if ( penStyle == Qt::CustomDashLine && !customDashPattern )
2480 {
2481 context.pushWarning( QObject::tr( "WARNING: Custom dash pattern required but not provided. Using default dash pattern." ) );
2482 penStyle = Qt::DashLine;
2483 }
2484
2485 switch ( penStyle )
2486 {
2487 case Qt::NoPen:
2488 return;
2489
2490 case Qt::SolidLine:
2491 break;
2492
2493 case Qt::DashLine:
2494 dashPattern.push_back( 4.0 );
2495 dashPattern.push_back( 2.0 );
2496 break;
2497 case Qt::DotLine:
2498 dashPattern.push_back( 1.0 );
2499 dashPattern.push_back( 2.0 );
2500 break;
2501 case Qt::DashDotLine:
2502 dashPattern.push_back( 4.0 );
2503 dashPattern.push_back( 2.0 );
2504 dashPattern.push_back( 1.0 );
2505 dashPattern.push_back( 2.0 );
2506 break;
2507 case Qt::DashDotDotLine:
2508 dashPattern.push_back( 4.0 );
2509 dashPattern.push_back( 2.0 );
2510 dashPattern.push_back( 1.0 );
2511 dashPattern.push_back( 2.0 );
2512 dashPattern.push_back( 1.0 );
2513 dashPattern.push_back( 2.0 );
2514 break;
2515
2516 case Qt::CustomDashLine:
2517 Q_ASSERT( customDashPattern );
2518 pattern = customDashPattern;
2519 break;
2520
2521 default:
2522 context.pushWarning( QObject::tr( "Pen style '%1' is not supported for SLD" ).arg( penStyle ) );
2523 return;
2524 }
2525
2526 if ( color.isValid() )
2527 {
2528 element.appendChild( createSvgParameterElement( doc, u"stroke"_s, color.name() ) );
2529 if ( color.alpha() < 255 )
2530 element.appendChild( createSvgParameterElement( doc, u"stroke-opacity"_s, encodeSldAlpha( color.alpha() ) ) );
2531 }
2532 if ( width > 0 )
2533 {
2534 element.appendChild( createSvgParameterElement( doc, u"stroke-width"_s, qgsDoubleToString( width ) ) );
2535 }
2536 else if ( width == 0 )
2537 {
2538 // hairline, yet not zero. it's actually painted in qgis
2539 element.appendChild( createSvgParameterElement( doc, u"stroke-width"_s, u"0.5"_s ) );
2540 }
2541 if ( penJoinStyle )
2542 element.appendChild( createSvgParameterElement( doc, u"stroke-linejoin"_s, encodeSldLineJoinStyle( *penJoinStyle ) ) );
2543 if ( penCapStyle )
2544 element.appendChild( createSvgParameterElement( doc, u"stroke-linecap"_s, encodeSldLineCapStyle( *penCapStyle ) ) );
2545
2546 if ( !pattern->isEmpty() )
2547 {
2548 element.appendChild( createSvgParameterElement( doc, u"stroke-dasharray"_s, encodeSldRealVector( *pattern ) ) );
2549 if ( !qgsDoubleNear( dashOffset, 0.0 ) )
2550 element.appendChild( createSvgParameterElement( doc, u"stroke-dashoffset"_s, qgsDoubleToString( dashOffset ) ) );
2551 }
2552}
2553
2554
2556 QDomElement &element, Qt::PenStyle &penStyle, QColor &color, double &width, Qt::PenJoinStyle *penJoinStyle, Qt::PenCapStyle *penCapStyle, QVector<qreal> *customDashPattern, double *dashOffset
2557)
2558{
2559 QgsDebugMsgLevel( u"Entered."_s, 4 );
2560
2561 penStyle = Qt::SolidLine;
2562 color = QColor( 0, 0, 0 );
2563 width = 1;
2564 if ( penJoinStyle )
2565 *penJoinStyle = Qt::BevelJoin;
2566 if ( penCapStyle )
2567 *penCapStyle = Qt::SquareCap;
2568 if ( customDashPattern )
2569 customDashPattern->clear();
2570 if ( dashOffset )
2571 *dashOffset = 0;
2572
2573 if ( element.isNull() )
2574 {
2575 penStyle = Qt::NoPen;
2576 color = QColor();
2577 return true;
2578 }
2579
2580 QgsStringMap svgParams = getSvgParameterList( element );
2581 for ( QgsStringMap::iterator it = svgParams.begin(); it != svgParams.end(); ++it )
2582 {
2583 QgsDebugMsgLevel( u"found SvgParameter %1: %2"_s.arg( it.key(), it.value() ), 2 );
2584
2585 if ( it.key() == "stroke"_L1 )
2586 {
2587 color = QColor( it.value() );
2588 }
2589 else if ( it.key() == "stroke-opacity"_L1 )
2590 {
2591 color.setAlpha( decodeSldAlpha( it.value() ) );
2592 }
2593 else if ( it.key() == "stroke-width"_L1 )
2594 {
2595 bool ok;
2596 const double w = it.value().toDouble( &ok );
2597 if ( ok )
2598 width = w;
2599 }
2600 else if ( it.key() == "stroke-linejoin"_L1 && penJoinStyle )
2601 {
2602 *penJoinStyle = decodeSldLineJoinStyle( it.value() );
2603 }
2604 else if ( it.key() == "stroke-linecap"_L1 && penCapStyle )
2605 {
2606 *penCapStyle = decodeSldLineCapStyle( it.value() );
2607 }
2608 else if ( it.key() == "stroke-dasharray"_L1 )
2609 {
2610 const QVector<qreal> dashPattern = decodeSldRealVector( it.value() );
2611 if ( !dashPattern.isEmpty() )
2612 {
2613 // convert the dasharray to one of the QT pen style,
2614 // if no match is found then set pen style to CustomDashLine
2615 bool dashPatternFound = false;
2616
2617 if ( dashPattern.count() == 2 )
2618 {
2619 if ( dashPattern.at( 0 ) == 4.0 && dashPattern.at( 1 ) == 2.0 )
2620 {
2621 penStyle = Qt::DashLine;
2622 dashPatternFound = true;
2623 }
2624 else if ( dashPattern.at( 0 ) == 1.0 && dashPattern.at( 1 ) == 2.0 )
2625 {
2626 penStyle = Qt::DotLine;
2627 dashPatternFound = true;
2628 }
2629 }
2630 else if ( dashPattern.count() == 4 )
2631 {
2632 if ( dashPattern.at( 0 ) == 4.0 && dashPattern.at( 1 ) == 2.0 && dashPattern.at( 2 ) == 1.0 && dashPattern.at( 3 ) == 2.0 )
2633 {
2634 penStyle = Qt::DashDotLine;
2635 dashPatternFound = true;
2636 }
2637 }
2638 else if ( dashPattern.count() == 6 )
2639 {
2640 if ( dashPattern.at( 0 ) == 4.0 && dashPattern.at( 1 ) == 2.0 && dashPattern.at( 2 ) == 1.0 && dashPattern.at( 3 ) == 2.0 && dashPattern.at( 4 ) == 1.0 && dashPattern.at( 5 ) == 2.0 )
2641 {
2642 penStyle = Qt::DashDotDotLine;
2643 dashPatternFound = true;
2644 }
2645 }
2646
2647 // default case: set pen style to CustomDashLine
2648 if ( !dashPatternFound )
2649 {
2650 if ( customDashPattern )
2651 {
2652 penStyle = Qt::CustomDashLine;
2653 *customDashPattern = dashPattern;
2654 }
2655 else
2656 {
2657 QgsDebugMsgLevel( u"custom dash pattern required but not provided. Using default dash pattern."_s, 2 );
2658 penStyle = Qt::DashLine;
2659 }
2660 }
2661 }
2662 }
2663 else if ( it.key() == "stroke-dashoffset"_L1 && dashOffset )
2664 {
2665 bool ok;
2666 const double d = it.value().toDouble( &ok );
2667 if ( ok )
2668 *dashOffset = d;
2669 }
2670 }
2671
2672 return true;
2673}
2674
2675void QgsSymbolLayerUtils::externalGraphicToSld( QDomDocument &doc, QDomElement &element, const QString &path, const QString &mime, const QColor &color, double size )
2676{
2677 QDomElement externalGraphicElem = doc.createElement( u"se:ExternalGraphic"_s );
2678 element.appendChild( externalGraphicElem );
2679
2680 createOnlineResourceElement( doc, externalGraphicElem, path, mime );
2681
2682 //TODO: missing a way to handle svg color. Should use <se:ColorReplacement>
2683 Q_UNUSED( color )
2684
2685 if ( size >= 0 )
2686 {
2687 QDomElement sizeElem = doc.createElement( u"se:Size"_s );
2688 sizeElem.appendChild( doc.createTextNode( qgsDoubleToString( size ) ) );
2689 element.appendChild( sizeElem );
2690 }
2691}
2692
2693void QgsSymbolLayerUtils::parametricSvgToSld( QDomDocument &doc, QDomElement &graphicElem, const QString &path, const QColor &fillColor, double size, const QColor &strokeColor, double strokeWidth )
2694{
2695 QgsSldExportContext context;
2696 parametricSvgToSld( doc, graphicElem, path, fillColor, size, strokeColor, strokeWidth, context );
2697}
2698
2700 QDomDocument &doc, QDomElement &graphicElem, const QString &path, const QColor &fillColor, double size, const QColor &strokeColor, double strokeWidth, QgsSldExportContext &context
2701)
2702{
2703 // Parametric SVG paths are an extension that few systems will understand, but se:Graphic allows for fallback
2704 // symbols, this encodes the full parametric path first, the pure shape second, and a mark with the right colors as
2705 // a last resort for systems that cannot do SVG at all
2706
2707 // encode parametric version with all coloring details (size is going to be encoded by the last fallback)
2708 graphicElem.appendChild( doc.createComment( u"Parametric SVG"_s ) );
2709 const QString parametricPath = getSvgParametricPath( path, fillColor, strokeColor, strokeWidth );
2710 QgsSymbolLayerUtils::externalGraphicToSld( doc, graphicElem, parametricPath, u"image/svg+xml"_s, fillColor, -1 );
2711 // also encode a fallback version without parameters, in case a renderer gets confused by the parameters
2712 graphicElem.appendChild( doc.createComment( u"Plain SVG fallback, no parameters"_s ) );
2713 QgsSymbolLayerUtils::externalGraphicToSld( doc, graphicElem, path, u"image/svg+xml"_s, fillColor, -1 );
2714 // finally encode a simple mark with the right colors/outlines for renderers that cannot do SVG at all
2715 graphicElem.appendChild( doc.createComment( u"Well known marker fallback"_s ) );
2716 QgsSymbolLayerUtils::wellKnownMarkerToSld( doc, graphicElem, u"square"_s, fillColor, strokeColor, Qt::PenStyle::SolidLine, context, strokeWidth, -1 );
2717
2718 // size is encoded here, it's part of se:Graphic, not attached to the single symbol
2719 if ( size >= 0 )
2720 {
2721 QDomElement sizeElem = doc.createElement( u"se:Size"_s );
2722 sizeElem.appendChild( doc.createTextNode( qgsDoubleToString( size ) ) );
2723 graphicElem.appendChild( sizeElem );
2724 }
2725}
2726
2727QString QgsSymbolLayerUtils::getSvgParametricPath( const QString &basePath, const QColor &fillColor, const QColor &strokeColor, double strokeWidth )
2728{
2729 QUrlQuery url;
2730 if ( fillColor.isValid() )
2731 {
2732 url.addQueryItem( u"fill"_s, fillColor.name() );
2733 url.addQueryItem( u"fill-opacity"_s, encodeSldAlpha( fillColor.alpha() ) );
2734 }
2735 else
2736 {
2737 url.addQueryItem( u"fill"_s, u"#000000"_s );
2738 url.addQueryItem( u"fill-opacity"_s, u"1"_s );
2739 }
2740 if ( strokeColor.isValid() )
2741 {
2742 url.addQueryItem( u"outline"_s, strokeColor.name() );
2743 url.addQueryItem( u"outline-opacity"_s, encodeSldAlpha( strokeColor.alpha() ) );
2744 }
2745 else
2746 {
2747 url.addQueryItem( u"outline"_s, u"#000000"_s );
2748 url.addQueryItem( u"outline-opacity"_s, u"1"_s );
2749 }
2750 url.addQueryItem( u"outline-width"_s, QString::number( strokeWidth ) );
2751 const QString params = url.toString( QUrl::FullyEncoded );
2752 if ( params.isEmpty() )
2753 {
2754 return basePath;
2755 }
2756 else
2757 {
2758 return basePath + "?" + params;
2759 }
2760}
2761
2762bool QgsSymbolLayerUtils::externalGraphicFromSld( QDomElement &element, QString &path, QString &mime, QColor &color, double &size )
2763{
2764 QgsDebugMsgLevel( u"Entered."_s, 4 );
2765 Q_UNUSED( color )
2766
2767 QDomElement externalGraphicElem = element.firstChildElement( u"ExternalGraphic"_s );
2768 if ( externalGraphicElem.isNull() )
2769 return false;
2770
2771 onlineResourceFromSldElement( externalGraphicElem, path, mime );
2772
2773 const QDomElement sizeElem = element.firstChildElement( u"Size"_s );
2774 if ( !sizeElem.isNull() )
2775 {
2776 bool ok;
2777 const double s = sizeElem.firstChild().nodeValue().toDouble( &ok );
2778 if ( ok )
2779 size = s;
2780 }
2781
2782 return true;
2783}
2784
2785void QgsSymbolLayerUtils::externalMarkerToSld( QDomDocument &doc, QDomElement &element, const QString &path, const QString &format, int *markIndex, const QColor &color, double size )
2786{
2787 QgsSldExportContext context;
2788 externalMarkerToSld( doc, element, path, format, context, markIndex, color, size );
2789}
2790
2792 QDomDocument &doc, QDomElement &element, const QString &path, const QString &format, QgsSldExportContext &context, int *markIndex, const QColor &color, double size
2793)
2794{
2795 QDomElement markElem = doc.createElement( u"se:Mark"_s );
2796 element.appendChild( markElem );
2797
2798 createOnlineResourceElement( doc, markElem, path, format );
2799
2800 if ( markIndex )
2801 {
2802 QDomElement markIndexElem = doc.createElement( u"se:MarkIndex"_s );
2803 markIndexElem.appendChild( doc.createTextNode( QString::number( *markIndex ) ) );
2804 markElem.appendChild( markIndexElem );
2805 }
2806
2807 // <Fill>
2808 QDomElement fillElem = doc.createElement( u"se:Fill"_s );
2809 fillToSld( doc, fillElem, context, Qt::SolidPattern, color );
2810 markElem.appendChild( fillElem );
2811
2812 // <Size>
2813 if ( !qgsDoubleNear( size, 0.0 ) && size > 0 )
2814 {
2815 QDomElement sizeElem = doc.createElement( u"se:Size"_s );
2816 sizeElem.appendChild( doc.createTextNode( qgsDoubleToString( size ) ) );
2817 element.appendChild( sizeElem );
2818 }
2819}
2820
2821bool QgsSymbolLayerUtils::externalMarkerFromSld( QDomElement &element, QString &path, QString &format, int &markIndex, QColor &color, double &size )
2822{
2823 QgsDebugMsgLevel( u"Entered."_s, 4 );
2824
2825 color = QColor();
2826 markIndex = -1;
2827 size = -1;
2828
2829 QDomElement markElem = element.firstChildElement( u"Mark"_s );
2830 if ( markElem.isNull() )
2831 return false;
2832
2833 onlineResourceFromSldElement( markElem, path, format );
2834
2835 const QDomElement markIndexElem = markElem.firstChildElement( u"MarkIndex"_s );
2836 if ( !markIndexElem.isNull() )
2837 {
2838 bool ok;
2839 const int i = markIndexElem.firstChild().nodeValue().toInt( &ok );
2840 if ( ok )
2841 markIndex = i;
2842 }
2843
2844 // <Fill>
2845 QDomElement fillElem = markElem.firstChildElement( u"Fill"_s );
2846 Qt::BrushStyle b = Qt::SolidPattern;
2847 fillFromSld( fillElem, b, color );
2848 // ignore brush style, solid expected
2849
2850 // <Size>
2851 const QDomElement sizeElem = element.firstChildElement( u"Size"_s );
2852 if ( !sizeElem.isNull() )
2853 {
2854 bool ok;
2855 const double s = sizeElem.firstChild().nodeValue().toDouble( &ok );
2856 if ( ok )
2857 size = s;
2858 }
2859
2860 return true;
2861}
2862
2864 QDomDocument &doc, QDomElement &element, const QString &name, const QColor &color, const QColor &strokeColor, Qt::PenStyle strokeStyle, double strokeWidth, double size
2865)
2866{
2867 QgsSldExportContext context;
2868 wellKnownMarkerToSld( doc, element, name, color, strokeColor, strokeStyle, context, strokeWidth, size );
2869}
2870
2872 QDomDocument &doc, QDomElement &element, const QString &name, const QColor &color, const QColor &strokeColor, Qt::PenStyle strokeStyle, QgsSldExportContext &context, double strokeWidth, double size
2873)
2874{
2875 QDomElement markElem = doc.createElement( u"se:Mark"_s );
2876 element.appendChild( markElem );
2877
2878 QDomElement wellKnownNameElem = doc.createElement( u"se:WellKnownName"_s );
2879 wellKnownNameElem.appendChild( doc.createTextNode( name ) );
2880 markElem.appendChild( wellKnownNameElem );
2881
2882 // <Fill>
2883 if ( color.isValid() )
2884 {
2885 QDomElement fillElem = doc.createElement( u"se:Fill"_s );
2886 fillToSld( doc, fillElem, context, Qt::SolidPattern, color );
2887 markElem.appendChild( fillElem );
2888 }
2889
2890 // <Stroke>
2891 if ( strokeColor.isValid() )
2892 {
2893 QDomElement strokeElem = doc.createElement( u"se:Stroke"_s );
2894 lineToSld( doc, strokeElem, strokeStyle, strokeColor, context, strokeWidth );
2895 markElem.appendChild( strokeElem );
2896 }
2897
2898 // <Size>
2899 if ( !qgsDoubleNear( size, 0.0 ) && size > 0 )
2900 {
2901 QDomElement sizeElem = doc.createElement( u"se:Size"_s );
2902 sizeElem.appendChild( doc.createTextNode( qgsDoubleToString( size ) ) );
2903 element.appendChild( sizeElem );
2904 }
2905}
2906
2907bool QgsSymbolLayerUtils::wellKnownMarkerFromSld( QDomElement &element, QString &name, QColor &color, QColor &strokeColor, Qt::PenStyle &strokeStyle, double &strokeWidth, double &size )
2908{
2909 QgsDebugMsgLevel( u"Entered."_s, 4 );
2910
2911 name = u"square"_s;
2912 color = QColor();
2913 strokeColor = QColor( 0, 0, 0 );
2914 strokeWidth = 1;
2915 size = 6;
2916
2917 const QDomElement markElem = element.firstChildElement( u"Mark"_s );
2918 if ( markElem.isNull() )
2919 return false;
2920
2921 const QDomElement wellKnownNameElem = markElem.firstChildElement( u"WellKnownName"_s );
2922 if ( !wellKnownNameElem.isNull() )
2923 {
2924 name = wellKnownNameElem.firstChild().nodeValue();
2925 QgsDebugMsgLevel( "found Mark with well known name: " + name, 2 );
2926 }
2927
2928 // <Fill>
2929 QDomElement fillElem = markElem.firstChildElement( u"Fill"_s );
2930 Qt::BrushStyle b = Qt::SolidPattern;
2931 fillFromSld( fillElem, b, color );
2932 // ignore brush style, solid expected
2933
2934 // <Stroke>
2935 QDomElement strokeElem = markElem.firstChildElement( u"Stroke"_s );
2936 lineFromSld( strokeElem, strokeStyle, strokeColor, strokeWidth );
2937 // ignore stroke style, solid expected
2938
2939 // <Size>
2940 const QDomElement sizeElem = element.firstChildElement( u"Size"_s );
2941 if ( !sizeElem.isNull() )
2942 {
2943 bool ok;
2944 const double s = sizeElem.firstChild().nodeValue().toDouble( &ok );
2945 if ( ok )
2946 size = s;
2947 }
2948
2949 return true;
2950}
2951
2952void QgsSymbolLayerUtils::createRotationElement( QDomDocument &doc, QDomElement &element, const QString &rotationFunc )
2953{
2954 QgsSldExportContext context;
2955 createRotationElement( doc, element, rotationFunc, context );
2956}
2957
2958void QgsSymbolLayerUtils::createRotationElement( QDomDocument &doc, QDomElement &element, const QString &rotationFunc, QgsSldExportContext &context )
2959{
2960 if ( !rotationFunc.isEmpty() )
2961 {
2962 QDomElement rotationElem = doc.createElement( u"se:Rotation"_s );
2963 createExpressionElement( doc, rotationElem, rotationFunc, context );
2964 element.appendChild( rotationElem );
2965 }
2966}
2967
2968bool QgsSymbolLayerUtils::rotationFromSldElement( QDomElement &element, QString &rotationFunc )
2969{
2970 QDomElement rotationElem = element.firstChildElement( u"Rotation"_s );
2971 if ( !rotationElem.isNull() )
2972 {
2973 return functionFromSldElement( rotationElem, rotationFunc );
2974 }
2975 return true;
2976}
2977
2978void QgsSymbolLayerUtils::createOpacityElement( QDomDocument &doc, QDomElement &element, const QString &alphaFunc )
2979{
2980 QgsSldExportContext context;
2981 createOpacityElement( doc, element, alphaFunc, context );
2982}
2983
2984void QgsSymbolLayerUtils::createOpacityElement( QDomDocument &doc, QDomElement &element, const QString &alphaFunc, QgsSldExportContext &context )
2985{
2986 if ( !alphaFunc.isEmpty() )
2987 {
2988 QDomElement opacityElem = doc.createElement( u"se:Opacity"_s );
2989 createExpressionElement( doc, opacityElem, alphaFunc, context );
2990 element.appendChild( opacityElem );
2991 }
2992}
2993
2994bool QgsSymbolLayerUtils::opacityFromSldElement( QDomElement &element, QString &alphaFunc )
2995{
2996 QDomElement opacityElem = element.firstChildElement( u"Opacity"_s );
2997 if ( !opacityElem.isNull() )
2998 {
2999 return functionFromSldElement( opacityElem, alphaFunc );
3000 }
3001 return true;
3002}
3003
3004void QgsSymbolLayerUtils::createDisplacementElement( QDomDocument &doc, QDomElement &element, QPointF offset )
3005{
3006 if ( offset.isNull() )
3007 return;
3008
3009 QDomElement displacementElem = doc.createElement( u"se:Displacement"_s );
3010 element.appendChild( displacementElem );
3011
3012 QDomElement dispXElem = doc.createElement( u"se:DisplacementX"_s );
3013 dispXElem.appendChild( doc.createTextNode( qgsDoubleToString( offset.x(), 2 ) ) );
3014
3015 QDomElement dispYElem = doc.createElement( u"se:DisplacementY"_s );
3016 dispYElem.appendChild( doc.createTextNode( qgsDoubleToString( offset.y(), 2 ) ) );
3017
3018 displacementElem.appendChild( dispXElem );
3019 displacementElem.appendChild( dispYElem );
3020}
3021
3022void QgsSymbolLayerUtils::createAnchorPointElement( QDomDocument &doc, QDomElement &element, QPointF anchor )
3023{
3024 // anchor is not tested for null, (0,0) is _not_ the default value (0.5, 0) is.
3025
3026 QDomElement anchorElem = doc.createElement( u"se:AnchorPoint"_s );
3027 element.appendChild( anchorElem );
3028
3029 QDomElement anchorXElem = doc.createElement( u"se:AnchorPointX"_s );
3030 anchorXElem.appendChild( doc.createTextNode( qgsDoubleToString( anchor.x() ) ) );
3031
3032 QDomElement anchorYElem = doc.createElement( u"se:AnchorPointY"_s );
3033 anchorYElem.appendChild( doc.createTextNode( qgsDoubleToString( anchor.y() ) ) );
3034
3035 anchorElem.appendChild( anchorXElem );
3036 anchorElem.appendChild( anchorYElem );
3037}
3038
3039bool QgsSymbolLayerUtils::displacementFromSldElement( QDomElement &element, QPointF &offset )
3040{
3041 offset = QPointF( 0, 0 );
3042
3043 const QDomElement displacementElem = element.firstChildElement( u"Displacement"_s );
3044 if ( displacementElem.isNull() )
3045 return true;
3046
3047 const QDomElement dispXElem = displacementElem.firstChildElement( u"DisplacementX"_s );
3048 if ( !dispXElem.isNull() )
3049 {
3050 bool ok;
3051 const double offsetX = dispXElem.firstChild().nodeValue().toDouble( &ok );
3052 if ( ok )
3053 offset.setX( offsetX );
3054 }
3055
3056 const QDomElement dispYElem = displacementElem.firstChildElement( u"DisplacementY"_s );
3057 if ( !dispYElem.isNull() )
3058 {
3059 bool ok;
3060 const double offsetY = dispYElem.firstChild().nodeValue().toDouble( &ok );
3061 if ( ok )
3062 offset.setY( offsetY );
3063 }
3064
3065 return true;
3066}
3067
3068void QgsSymbolLayerUtils::labelTextToSld( QDomDocument &doc, QDomElement &element, const QString &label, const QFont &font, const QColor &color, double size )
3069{
3070 QgsSldExportContext context;
3071 labelTextToSld( doc, element, label, font, context, color, size );
3072}
3073
3074void QgsSymbolLayerUtils::labelTextToSld( QDomDocument &doc, QDomElement &element, const QString &label, const QFont &font, QgsSldExportContext &context, const QColor &color, double size )
3075{
3076 QDomElement labelElem = doc.createElement( u"se:Label"_s );
3077 labelElem.appendChild( doc.createTextNode( label ) );
3078 element.appendChild( labelElem );
3079
3080 QDomElement fontElem = doc.createElement( u"se:Font"_s );
3081 element.appendChild( fontElem );
3082
3083 fontElem.appendChild( createSvgParameterElement( doc, u"font-family"_s, font.family() ) );
3084#if 0
3085 fontElem.appendChild( createSldParameterElement( doc, "font-style", encodeSldFontStyle( font.style() ) ) );
3086 fontElem.appendChild( createSldParameterElement( doc, "font-weight", encodeSldFontWeight( font.weight() ) ) );
3087#endif
3088 fontElem.appendChild( createSvgParameterElement( doc, u"font-size"_s, QString::number( size ) ) );
3089
3090 // <Fill>
3091 if ( color.isValid() )
3092 {
3093 QDomElement fillElem = doc.createElement( u"Fill"_s );
3094 fillToSld( doc, fillElem, context, Qt::SolidPattern, color );
3095 element.appendChild( fillElem );
3096 }
3097}
3098
3100 double width, double mmScaleFactor, double mapUnitScaleFactor, const QColor &c, Qt::PenJoinStyle joinStyle, Qt::PenCapStyle capStyle, double offset, const QVector<qreal> *dashPattern
3101)
3102{
3103 QString penStyle;
3104 penStyle.append( "PEN(" );
3105 penStyle.append( "c:" );
3106 penStyle.append( c.name() );
3107 penStyle.append( ",w:" );
3108 //dxf driver writes ground units as mm? Should probably be changed in ogr
3109 penStyle.append( QString::number( width * mmScaleFactor ) );
3110 penStyle.append( "mm" );
3111
3112 //dash dot vector
3113 if ( dashPattern && !dashPattern->isEmpty() )
3114 {
3115 penStyle.append( ",p:\"" );
3116 QVector<qreal>::const_iterator pIt = dashPattern->constBegin();
3117 for ( ; pIt != dashPattern->constEnd(); ++pIt )
3118 {
3119 if ( pIt != dashPattern->constBegin() )
3120 {
3121 penStyle.append( ' ' );
3122 }
3123 penStyle.append( QString::number( *pIt * mapUnitScaleFactor ) );
3124 penStyle.append( 'g' );
3125 }
3126 penStyle.append( '\"' );
3127 }
3128
3129 //cap
3130 penStyle.append( ",cap:" );
3131 switch ( capStyle )
3132 {
3133 case Qt::SquareCap:
3134 penStyle.append( 'p' );
3135 break;
3136 case Qt::RoundCap:
3137 penStyle.append( 'r' );
3138 break;
3139 case Qt::FlatCap:
3140 default:
3141 penStyle.append( 'b' );
3142 }
3143
3144 //join
3145 penStyle.append( ",j:" );
3146 switch ( joinStyle )
3147 {
3148 case Qt::BevelJoin:
3149 penStyle.append( 'b' );
3150 break;
3151 case Qt::RoundJoin:
3152 penStyle.append( 'r' );
3153 break;
3154 case Qt::MiterJoin:
3155 default:
3156 penStyle.append( 'm' );
3157 }
3158
3159 //offset
3160 if ( !qgsDoubleNear( offset, 0.0 ) )
3161 {
3162 penStyle.append( ",dp:" );
3163 penStyle.append( QString::number( offset * mapUnitScaleFactor ) );
3164 penStyle.append( 'g' );
3165 }
3166
3167 penStyle.append( ')' );
3168 return penStyle;
3169}
3170
3171QString QgsSymbolLayerUtils::ogrFeatureStyleBrush( const QColor &fillColor )
3172{
3173 QString brushStyle;
3174 brushStyle.append( "BRUSH(" );
3175 brushStyle.append( "fc:" );
3176 brushStyle.append( fillColor.name() );
3177 brushStyle.append( ')' );
3178 return brushStyle;
3179}
3180
3181void QgsSymbolLayerUtils::createGeometryElement( QDomDocument &doc, QDomElement &element, const QString &geomFunc )
3182{
3183 QgsSldExportContext context;
3184 createGeometryElement( doc, element, geomFunc, context );
3185}
3186
3187void QgsSymbolLayerUtils::createGeometryElement( QDomDocument &doc, QDomElement &element, const QString &geomFunc, QgsSldExportContext &context )
3188{
3189 if ( geomFunc.isEmpty() )
3190 return;
3191
3192 QDomElement geometryElem = doc.createElement( u"Geometry"_s );
3193 element.appendChild( geometryElem );
3194
3195 /* About using a function within the Geometry tag.
3196 *
3197 * The SLD specification <= 1.1 is vague:
3198 * "In principle, a fixed geometry could be defined using GML or
3199 * operators could be defined for computing the geometry from
3200 * references or literals. However, using a feature property directly
3201 * is by far the most commonly useful method."
3202 *
3203 * Even if it seems that specs should take care all the possible cases,
3204 * looking at the XML schema fragment that encodes the Geometry element,
3205 * it has to be a PropertyName element:
3206 * <xsd:element name="Geometry">
3207 * <xsd:complexType>
3208 * <xsd:sequence>
3209 * <xsd:element ref="ogc:PropertyName"/>
3210 * </xsd:sequence>
3211 * </xsd:complexType>
3212 * </xsd:element>
3213 *
3214 * Anyway we will use a ogc:Function to handle geometry transformations
3215 * like offset, centroid, ...
3216 */
3217
3218 createExpressionElement( doc, geometryElem, geomFunc, context );
3219}
3220
3221bool QgsSymbolLayerUtils::geometryFromSldElement( QDomElement &element, QString &geomFunc )
3222{
3223 QDomElement geometryElem = element.firstChildElement( u"Geometry"_s );
3224 if ( geometryElem.isNull() )
3225 return true;
3226
3227 return functionFromSldElement( geometryElem, geomFunc );
3228}
3229
3230bool QgsSymbolLayerUtils::createExpressionElement( QDomDocument &doc, QDomElement &element, const QString &function )
3231{
3232 QgsSldExportContext context;
3233 return createExpressionElement( doc, element, function, context );
3234}
3235
3236bool QgsSymbolLayerUtils::createExpressionElement( QDomDocument &doc, QDomElement &element, const QString &function, QgsSldExportContext &context )
3237{
3238 // let's use QgsExpression to generate the SLD for the function
3239 const QgsExpression expr( function );
3240 if ( expr.hasParserError() )
3241 {
3242 context.pushError( QObject::tr( "Parser error encountered when converting expression to SLD: %1 - Expression was: %2" ).arg( expr.parserErrorString(), function ) );
3243 return false;
3244 }
3245 const QDomElement filterElem = QgsOgcUtils::expressionToOgcExpression( expr, doc );
3246 if ( !filterElem.isNull() )
3247 element.appendChild( filterElem );
3248 return true;
3249}
3250
3251bool QgsSymbolLayerUtils::createFunctionElement( QDomDocument &doc, QDomElement &element, const QString &function )
3252{
3253 QgsSldExportContext context;
3254 return createFunctionElement( doc, element, function, context );
3255}
3256
3257bool QgsSymbolLayerUtils::createFunctionElement( QDomDocument &doc, QDomElement &element, const QString &function, QgsSldExportContext &context )
3258{
3259 // else rule is not a valid expression
3260 if ( function == "ELSE"_L1 )
3261 {
3262 const QDomElement filterElem = QgsOgcUtils::elseFilterExpression( doc );
3263 element.appendChild( filterElem );
3264 return true;
3265 }
3266 else
3267 {
3268 // let's use QgsExpression to generate the SLD for the function
3269 const QgsExpression expr( function );
3270 if ( expr.hasParserError() )
3271 {
3272 context.pushError( QObject::tr( "Parser error encountered when converting expression to SLD: %1 - Expression was: %2" ).arg( expr.parserErrorString(), function ) );
3273 return false;
3274 }
3275 const QDomElement filterElem = QgsOgcUtils::expressionToOgcFilter( expr, doc );
3276 if ( !filterElem.isNull() )
3277 element.appendChild( filterElem );
3278 return true;
3279 }
3280}
3281
3282bool QgsSymbolLayerUtils::functionFromSldElement( QDomElement &element, QString &function )
3283{
3284 // check if ogc:Filter or contains ogc:Filters
3285 QDomElement elem = element;
3286 if ( element.tagName() != "Filter"_L1 )
3287 {
3288 const QDomNodeList filterNodes = element.elementsByTagName( u"Filter"_s );
3289 if ( !filterNodes.isEmpty() )
3290 {
3291 elem = filterNodes.at( 0 ).toElement();
3292 }
3293 }
3294
3295 if ( elem.isNull() )
3296 {
3297 return false;
3298 }
3299
3300 // parse ogc:Filter
3302 if ( !expr )
3303 return false;
3304
3305 const bool valid = !expr->hasParserError();
3306 if ( !valid )
3307 {
3308 QgsDebugError( "parser error: " + expr->parserErrorString() );
3309 }
3310 else
3311 {
3312 function = expr->expression();
3313 }
3314
3315 delete expr;
3316 return valid;
3317}
3318
3319void QgsSymbolLayerUtils::createOnlineResourceElement( QDomDocument &doc, QDomElement &element, const QString &path, const QString &format )
3320{
3321 // get resource url or relative path
3322 const QString url = svgSymbolPathToName( path, QgsPathResolver() );
3323 QDomElement onlineResourceElem = doc.createElement( u"se:OnlineResource"_s );
3324 onlineResourceElem.setAttribute( u"xlink:type"_s, u"simple"_s );
3325 onlineResourceElem.setAttribute( u"xlink:href"_s, url );
3326 element.appendChild( onlineResourceElem );
3327
3328 QDomElement formatElem = doc.createElement( u"se:Format"_s );
3329 formatElem.appendChild( doc.createTextNode( format ) );
3330 element.appendChild( formatElem );
3331}
3332
3333bool QgsSymbolLayerUtils::onlineResourceFromSldElement( QDomElement &element, QString &path, QString &format )
3334{
3335 QgsDebugMsgLevel( u"Entered."_s, 4 );
3336
3337 const QDomElement onlineResourceElem = element.firstChildElement( u"OnlineResource"_s );
3338 if ( onlineResourceElem.isNull() )
3339 return false;
3340
3341 path = QUrl::fromPercentEncoding( onlineResourceElem.attributeNS( u"http://www.w3.org/1999/xlink"_s, u"href"_s ).toUtf8() );
3342
3343 const QDomElement formatElem = element.firstChildElement( u"Format"_s );
3344 if ( formatElem.isNull() )
3345 return false; // OnlineResource requires a Format sibling element
3346
3347 format = formatElem.firstChild().nodeValue();
3348 return true;
3349}
3350
3351
3352QDomElement QgsSymbolLayerUtils::createSvgParameterElement( QDomDocument &doc, const QString &name, const QString &value )
3353{
3354 QDomElement nodeElem = doc.createElement( u"se:SvgParameter"_s );
3355 nodeElem.setAttribute( u"name"_s, name );
3356 nodeElem.appendChild( doc.createTextNode( value ) );
3357 return nodeElem;
3358}
3359
3361{
3362 QgsStringMap params;
3363 QString value;
3364
3365 QDomElement paramElem = element.firstChildElement();
3366 while ( !paramElem.isNull() )
3367 {
3368 if ( paramElem.localName() == "SvgParameter"_L1 || paramElem.localName() == "CssParameter"_L1 )
3369 {
3370 const QString name = paramElem.attribute( u"name"_s );
3371 if ( paramElem.firstChild().nodeType() == QDomNode::TextNode )
3372 {
3373 value = paramElem.firstChild().nodeValue();
3374 }
3375 else
3376 {
3377 if ( paramElem.firstChild().nodeType() == QDomNode::ElementNode && paramElem.firstChild().localName() == "Literal"_L1 )
3378 {
3379 QgsDebugMsgLevel( paramElem.firstChild().localName(), 3 );
3380 value = paramElem.firstChild().firstChild().nodeValue();
3381 }
3382 else
3383 {
3384 QgsDebugError( u"unexpected child of %1"_s.arg( paramElem.localName() ) );
3385 }
3386 }
3387
3388 if ( !name.isEmpty() && !value.isEmpty() )
3389 params[name] = value;
3390 }
3391
3392 paramElem = paramElem.nextSiblingElement();
3393 }
3394
3395 return params;
3396}
3397
3398QDomElement QgsSymbolLayerUtils::createVendorOptionElement( QDomDocument &doc, const QString &name, const QString &value )
3399{
3400 QDomElement nodeElem = doc.createElement( u"se:VendorOption"_s );
3401 nodeElem.setAttribute( u"name"_s, name );
3402 nodeElem.appendChild( doc.createTextNode( value ) );
3403 return nodeElem;
3404}
3405
3407{
3408 QgsStringMap params;
3409
3410 QDomElement paramElem = element.firstChildElement( u"VendorOption"_s );
3411 while ( !paramElem.isNull() )
3412 {
3413 const QString name = paramElem.attribute( u"name"_s );
3414 const QString value = paramElem.firstChild().nodeValue();
3415
3416 if ( !name.isEmpty() && !value.isEmpty() )
3417 params[name] = value;
3418
3419 paramElem = paramElem.nextSiblingElement( u"VendorOption"_s );
3420 }
3421
3422 return params;
3423}
3424
3425
3426QVariantMap QgsSymbolLayerUtils::parseProperties( const QDomElement &element )
3427{
3428 const QVariant newSymbols = QgsXmlUtils::readVariant( element.firstChildElement( u"Option"_s ) );
3429 if ( newSymbols.userType() == QMetaType::Type::QVariantMap )
3430 {
3431 return newSymbols.toMap();
3432 }
3433 else
3434 {
3435 // read old style of writing properties
3436 // backward compatibility with project saved in <= 3.16
3437 QVariantMap props;
3438 QDomElement e = element.firstChildElement();
3439 while ( !e.isNull() )
3440 {
3441 if ( e.tagName() == "prop"_L1 )
3442 {
3443 const QString propKey = e.attribute( u"k"_s );
3444 const QString propValue = e.attribute( u"v"_s );
3445 props[propKey] = propValue;
3446 }
3447 e = e.nextSiblingElement();
3448 }
3449 return props;
3450 }
3451}
3452
3453
3454void QgsSymbolLayerUtils::saveProperties( QVariantMap props, QDomDocument &doc, QDomElement &element )
3455{
3456 element.appendChild( QgsXmlUtils::writeVariant( props, doc ) );
3457}
3458
3460{
3461 // go through symbols one-by-one and load them
3462
3463 QgsSymbolMap symbols;
3464 QDomElement e = element.firstChildElement();
3465
3466 while ( !e.isNull() )
3467 {
3468 if ( e.tagName() == "symbol"_L1 )
3469 {
3470 std::unique_ptr< QgsSymbol > symbol = QgsSymbolLayerUtils::loadSymbol( e, context );
3471 if ( symbol )
3472 symbols.insert( e.attribute( u"name"_s ), symbol.release() );
3473 }
3474 else
3475 {
3476 QgsDebugError( "unknown tag: " + e.tagName() );
3477 }
3478 e = e.nextSiblingElement();
3479 }
3480
3481
3482 // now walk through the list of symbols and find those prefixed with @
3483 // these symbols are sub-symbols of some other symbol layers
3484 // e.g. symbol named "@foo@1" is sub-symbol of layer 1 in symbol "foo"
3485 QStringList subsymbols;
3486
3487 for ( QMap<QString, QgsSymbol *>::iterator it = symbols.begin(); it != symbols.end(); ++it )
3488 {
3489 if ( it.key()[0] != '@' )
3490 continue;
3491
3492 // add to array (for deletion)
3493 subsymbols.append( it.key() );
3494
3495 QStringList parts = it.key().split( '@' );
3496 if ( parts.count() < 3 )
3497 {
3498 QgsDebugError( "found subsymbol with invalid name: " + it.key() );
3499 delete it.value(); // we must delete it
3500 continue; // some invalid syntax
3501 }
3502 const QString symname = parts[1];
3503 const int symlayer = parts[2].toInt();
3504
3505 if ( !symbols.contains( symname ) )
3506 {
3507 QgsDebugError( "subsymbol references invalid symbol: " + symname );
3508 delete it.value(); // we must delete it
3509 continue;
3510 }
3511
3512 QgsSymbol *sym = symbols[symname];
3513 if ( symlayer < 0 || symlayer >= sym->symbolLayerCount() )
3514 {
3515 QgsDebugError( "subsymbol references invalid symbol layer: " + QString::number( symlayer ) );
3516 delete it.value(); // we must delete it
3517 continue;
3518 }
3519
3520 // set subsymbol takes ownership
3521 const bool res = sym->symbolLayer( symlayer )->setSubSymbol( it.value() );
3522 if ( !res )
3523 {
3524 QgsDebugError( "symbol layer refused subsymbol: " + it.key() );
3525 }
3526 }
3527
3528 // now safely remove sub-symbol entries (they have been already deleted or the ownership was taken away)
3529 for ( int i = 0; i < subsymbols.count(); i++ )
3530 symbols.take( subsymbols[i] );
3531
3532 return symbols;
3533}
3534
3535QDomElement QgsSymbolLayerUtils::saveSymbols( QgsSymbolMap &symbols, const QString &tagName, QDomDocument &doc, const QgsReadWriteContext &context )
3536{
3537 QDomElement symbolsElem = doc.createElement( tagName );
3538
3539 // save symbols
3540 for ( QMap<QString, QgsSymbol *>::iterator its = symbols.begin(); its != symbols.end(); ++its )
3541 {
3542 const QDomElement symEl = saveSymbol( its.key(), its.value(), doc, context );
3543 symbolsElem.appendChild( symEl );
3544 }
3545
3546 return symbolsElem;
3547}
3548
3550{
3551 qDeleteAll( symbols );
3552 symbols.clear();
3553}
3554
3555std::unique_ptr<QMimeData> QgsSymbolLayerUtils::symbolToMimeData( const QgsSymbol *symbol )
3556{
3557 if ( !symbol )
3558 return nullptr;
3559
3560 std::unique_ptr< QMimeData > mimeData( new QMimeData );
3561
3562 QDomDocument symbolDoc;
3563 const QDomElement symbolElem = saveSymbol( u"symbol"_s, symbol, symbolDoc, QgsReadWriteContext() );
3564 symbolDoc.appendChild( symbolElem );
3565 mimeData->setText( symbolDoc.toString() );
3566
3567 mimeData->setImageData( symbolPreviewPixmap( symbol, QSize( 100, 100 ), 18 ).toImage() );
3568 mimeData->setColorData( symbol->color() );
3569
3570 return mimeData;
3571}
3572
3573std::unique_ptr< QgsSymbol > QgsSymbolLayerUtils::symbolFromMimeData( const QMimeData *data )
3574{
3575 if ( !data )
3576 return nullptr;
3577
3578 const QString text = data->text();
3579 if ( !text.isEmpty() )
3580 {
3581 QDomDocument doc;
3582 QDomElement elem;
3583
3584 if ( doc.setContent( text ) )
3585 {
3586 elem = doc.documentElement();
3587
3588 if ( elem.nodeName() != "symbol"_L1 )
3589 elem = elem.firstChildElement( u"symbol"_s );
3590
3591 return loadSymbol( elem, QgsReadWriteContext() );
3592 }
3593 }
3594 return nullptr;
3595}
3596
3597
3598std::unique_ptr< QgsColorRamp > QgsSymbolLayerUtils::loadColorRamp( QDomElement &element )
3599{
3600 const QString rampType = element.attribute( u"type"_s );
3601
3602 // parse properties
3603 const QVariantMap props = QgsSymbolLayerUtils::parseProperties( element );
3604
3605 if ( rampType == QgsGradientColorRamp::typeString() )
3606 return std::unique_ptr< QgsColorRamp >( QgsGradientColorRamp::create( props ) );
3607 else if ( rampType == QgsLimitedRandomColorRamp::typeString() )
3608 return std::unique_ptr< QgsColorRamp >( QgsLimitedRandomColorRamp::create( props ) );
3609 else if ( rampType == QgsColorBrewerColorRamp::typeString() )
3610 return std::unique_ptr< QgsColorRamp >( QgsColorBrewerColorRamp::create( props ) );
3611 else if ( rampType == QgsCptCityColorRamp::typeString() )
3612 return std::unique_ptr< QgsColorRamp >( QgsCptCityColorRamp::create( props ) );
3613 else if ( rampType == QgsPresetSchemeColorRamp::typeString() )
3614 return std::unique_ptr< QgsColorRamp >( QgsPresetSchemeColorRamp::create( props ) );
3615 else
3616 {
3617 QgsDebugError( "unknown colorramp type " + rampType );
3618 return nullptr;
3619 }
3620}
3621
3622
3623QDomElement QgsSymbolLayerUtils::saveColorRamp( const QString &name, const QgsColorRamp *ramp, QDomDocument &doc )
3624{
3625 QDomElement rampEl = doc.createElement( u"colorramp"_s );
3626 rampEl.setAttribute( u"type"_s, ramp->type() );
3627 rampEl.setAttribute( u"name"_s, name );
3628
3629 QgsSymbolLayerUtils::saveProperties( ramp->properties(), doc, rampEl );
3630 return rampEl;
3631}
3632
3633QVariant QgsSymbolLayerUtils::colorRampToVariant( const QString &name, QgsColorRamp *ramp )
3634{
3635 QVariantMap rampMap;
3636
3637 rampMap.insert( u"type"_s, ramp->type() );
3638 rampMap.insert( u"name"_s, name );
3639
3640 const QVariantMap properties = ramp->properties();
3641
3642 QVariantMap propertyMap;
3643 for ( auto property = properties.constBegin(); property != properties.constEnd(); ++property )
3644 {
3645 propertyMap.insert( property.key(), property.value() );
3646 }
3647
3648 rampMap.insert( u"properties"_s, propertyMap );
3649 return rampMap;
3650}
3651
3652std::unique_ptr< QgsColorRamp > QgsSymbolLayerUtils::loadColorRamp( const QVariant &value )
3653{
3654 const QVariantMap rampMap = value.toMap();
3655
3656 const QString rampType = rampMap.value( u"type"_s ).toString();
3657
3658 // parse properties
3659 const QVariantMap propertyMap = rampMap.value( u"properties"_s ).toMap();
3660 QVariantMap props;
3661
3662 for ( auto property = propertyMap.constBegin(); property != propertyMap.constEnd(); ++property )
3663 {
3664 props.insert( property.key(), property.value().toString() );
3665 }
3666
3667 if ( rampType == QgsGradientColorRamp::typeString() )
3668 return std::unique_ptr< QgsColorRamp >( QgsGradientColorRamp::create( props ) );
3669 else if ( rampType == QgsLimitedRandomColorRamp::typeString() )
3670 return std::unique_ptr< QgsColorRamp >( QgsLimitedRandomColorRamp::create( props ) );
3671 else if ( rampType == QgsColorBrewerColorRamp::typeString() )
3672 return std::unique_ptr< QgsColorRamp >( QgsColorBrewerColorRamp::create( props ) );
3673 else if ( rampType == QgsCptCityColorRamp::typeString() )
3674 return std::unique_ptr< QgsColorRamp >( QgsCptCityColorRamp::create( props ) );
3675 else if ( rampType == QgsPresetSchemeColorRamp::typeString() )
3676 return std::unique_ptr< QgsColorRamp >( QgsPresetSchemeColorRamp::create( props ) );
3677 else
3678 {
3679 QgsDebugError( "unknown colorramp type " + rampType );
3680 return nullptr;
3681 }
3682}
3683
3684QString QgsSymbolLayerUtils::colorToName( const QColor &color )
3685{
3686 if ( !color.isValid() )
3687 {
3688 return QString();
3689 }
3690
3691 //TODO - utilize a color names database (such as X11) to return nicer names
3692 //for now, just return hex codes
3693 return color.name();
3694}
3695
3696QList<QColor> QgsSymbolLayerUtils::parseColorList( const QString &colorStr )
3697{
3698 QList<QColor> colors;
3699
3700 //try splitting string at commas, spaces or newlines
3701 const thread_local QRegularExpression sepCommaSpaceRegExp( "(,|\\s)" );
3702 QStringList components = colorStr.simplified().split( sepCommaSpaceRegExp );
3703 QStringList::iterator it = components.begin();
3704 for ( ; it != components.end(); ++it )
3705 {
3706 const QColor result = parseColor( *it, true );
3707 if ( result.isValid() )
3708 {
3709 colors << result;
3710 }
3711 }
3712 if ( colors.length() > 0 )
3713 {
3714 return colors;
3715 }
3716
3717 //try splitting string at commas or newlines
3718 const thread_local QRegularExpression sepCommaRegExp( "(,|\n)" );
3719 components = colorStr.split( sepCommaRegExp );
3720 it = components.begin();
3721 for ( ; it != components.end(); ++it )
3722 {
3723 const QColor result = parseColor( *it, true );
3724 if ( result.isValid() )
3725 {
3726 colors << result;
3727 }
3728 }
3729 if ( colors.length() > 0 )
3730 {
3731 return colors;
3732 }
3733
3734 //try splitting string at whitespace or newlines
3735 components = colorStr.simplified().split( QString( ' ' ) );
3736 it = components.begin();
3737 for ( ; it != components.end(); ++it )
3738 {
3739 const QColor result = parseColor( *it, true );
3740 if ( result.isValid() )
3741 {
3742 colors << result;
3743 }
3744 }
3745 if ( colors.length() > 0 )
3746 {
3747 return colors;
3748 }
3749
3750 //try splitting string just at newlines
3751 components = colorStr.split( '\n' );
3752 it = components.begin();
3753 for ( ; it != components.end(); ++it )
3754 {
3755 const QColor result = parseColor( *it, true );
3756 if ( result.isValid() )
3757 {
3758 colors << result;
3759 }
3760 }
3761
3762 return colors;
3763}
3764
3765std::unique_ptr<QMimeData> QgsSymbolLayerUtils::colorToMimeData( const QColor &color )
3766{
3767 //set both the mime color data (which includes alpha channel), and the text (which is the color's hex
3768 //value, and can be used when pasting colors outside of QGIS).
3769 auto mimeData = std::make_unique<QMimeData>();
3770 mimeData->setColorData( QVariant( color ) );
3771 mimeData->setText( color.name() );
3772 return mimeData;
3773}
3774
3775QColor QgsSymbolLayerUtils::colorFromMimeData( const QMimeData *mimeData, bool &hasAlpha )
3776{
3777 if ( !mimeData )
3778 return QColor();
3779
3780 //attempt to read color data directly from mime
3781 if ( mimeData->hasColor() )
3782 {
3783 QColor mimeColor = mimeData->colorData().value<QColor>();
3784 if ( mimeColor.isValid() )
3785 {
3786 hasAlpha = true;
3787 return mimeColor;
3788 }
3789 }
3790
3791 //attempt to intrepret a color from mime text data
3792 if ( mimeData->hasText() )
3793 {
3794 hasAlpha = false;
3795 QColor textColor = QgsSymbolLayerUtils::parseColorWithAlpha( mimeData->text(), hasAlpha );
3796 if ( textColor.isValid() )
3797 {
3798 return textColor;
3799 }
3800 }
3801
3802 //could not get color from mime data
3803 return QColor();
3804}
3805
3807{
3808 if ( !data )
3809 return {};
3810
3811 QgsNamedColorList mimeColors;
3812
3813 //prefer xml format
3814 if ( data->hasFormat( u"text/xml"_s ) )
3815 {
3816 //get XML doc
3817 const QByteArray encodedData = data->data( u"text/xml"_s );
3818 QDomDocument xmlDoc;
3819 xmlDoc.setContent( encodedData );
3820
3821 const QDomElement dragDataElem = xmlDoc.documentElement();
3822 if ( dragDataElem.tagName() == "ColorSchemeModelDragData"_L1 )
3823 {
3824 const QDomNodeList nodeList = dragDataElem.childNodes();
3825 const int nChildNodes = nodeList.size();
3826 QDomElement currentElem;
3827
3828 for ( int i = 0; i < nChildNodes; ++i )
3829 {
3830 currentElem = nodeList.at( i ).toElement();
3831 if ( currentElem.isNull() )
3832 {
3833 continue;
3834 }
3835
3836 QPair< QColor, QString> namedColor;
3837 namedColor.first = QgsColorUtils::colorFromString( currentElem.attribute( u"color"_s, u"255,255,255,255"_s ) );
3838 namedColor.second = currentElem.attribute( u"label"_s, QString() );
3839
3840 mimeColors << namedColor;
3841 }
3842 }
3843 }
3844
3845 if ( mimeColors.length() == 0 && data->hasFormat( u"application/x-colorobject-list"_s ) )
3846 {
3847 //get XML doc
3848 const QByteArray encodedData = data->data( u"application/x-colorobject-list"_s );
3849 QDomDocument xmlDoc;
3850 xmlDoc.setContent( encodedData );
3851
3852 const QDomNodeList colorsNodes = xmlDoc.elementsByTagName( u"colors"_s );
3853 if ( colorsNodes.length() > 0 )
3854 {
3855 const QDomElement colorsElem = colorsNodes.at( 0 ).toElement();
3856 const QDomNodeList colorNodeList = colorsElem.childNodes();
3857 const int nChildNodes = colorNodeList.size();
3858 QDomElement currentElem;
3859
3860 for ( int i = 0; i < nChildNodes; ++i )
3861 {
3862 //li element
3863 currentElem = colorNodeList.at( i ).toElement();
3864 if ( currentElem.isNull() )
3865 {
3866 continue;
3867 }
3868
3869 const QDomNodeList colorNodes = currentElem.elementsByTagName( u"color"_s );
3870 const QDomNodeList nameNodes = currentElem.elementsByTagName( u"name"_s );
3871
3872 if ( colorNodes.length() > 0 )
3873 {
3874 const QDomElement colorElem = colorNodes.at( 0 ).toElement();
3875
3876 const QStringList colorParts = colorElem.text().simplified().split( ' ' );
3877 if ( colorParts.length() < 3 )
3878 {
3879 continue;
3880 }
3881
3882 const int red = colorParts.at( 0 ).toDouble() * 255;
3883 const int green = colorParts.at( 1 ).toDouble() * 255;
3884 const int blue = colorParts.at( 2 ).toDouble() * 255;
3885 QPair< QColor, QString> namedColor;
3886 namedColor.first = QColor( red, green, blue );
3887 if ( nameNodes.length() > 0 )
3888 {
3889 const QDomElement nameElem = nameNodes.at( 0 ).toElement();
3890 namedColor.second = nameElem.text();
3891 }
3892 mimeColors << namedColor;
3893 }
3894 }
3895 }
3896 }
3897
3898 if ( mimeColors.length() == 0 && data->hasText() )
3899 {
3900 //attempt to read color data from mime text
3901 QList< QColor > parsedColors = QgsSymbolLayerUtils::parseColorList( data->text() );
3902 QList< QColor >::iterator it = parsedColors.begin();
3903 for ( ; it != parsedColors.end(); ++it )
3904 {
3905 mimeColors << qMakePair( *it, QString() );
3906 }
3907 }
3908
3909 if ( mimeColors.length() == 0 && data->hasColor() )
3910 {
3911 //attempt to read color data directly from mime
3912 const QColor mimeColor = data->colorData().value<QColor>();
3913 if ( mimeColor.isValid() )
3914 {
3915 mimeColors << qMakePair( mimeColor, QString() );
3916 }
3917 }
3918
3919 return mimeColors;
3920}
3921
3922std::unique_ptr<QMimeData> QgsSymbolLayerUtils::colorListToMimeData( const QgsNamedColorList &colorList, const bool allFormats )
3923{
3924 //native format
3925 auto mimeData = std::make_unique<QMimeData>();
3926 QDomDocument xmlDoc;
3927 QDomElement xmlRootElement = xmlDoc.createElement( u"ColorSchemeModelDragData"_s );
3928 xmlDoc.appendChild( xmlRootElement );
3929
3930 QgsNamedColorList::const_iterator colorIt = colorList.constBegin();
3931 for ( ; colorIt != colorList.constEnd(); ++colorIt )
3932 {
3933 QDomElement namedColor = xmlDoc.createElement( u"NamedColor"_s );
3934 namedColor.setAttribute( u"color"_s, QgsColorUtils::colorToString( ( *colorIt ).first ) );
3935 namedColor.setAttribute( u"label"_s, ( *colorIt ).second );
3936 xmlRootElement.appendChild( namedColor );
3937 }
3938 mimeData->setData( u"text/xml"_s, xmlDoc.toByteArray() );
3939
3940 if ( !allFormats )
3941 {
3942 return mimeData;
3943 }
3944
3945 //set mime text to list of hex values
3946 colorIt = colorList.constBegin();
3947 QStringList colorListString;
3948 for ( ; colorIt != colorList.constEnd(); ++colorIt )
3949 {
3950 colorListString << ( *colorIt ).first.name();
3951 }
3952 mimeData->setText( colorListString.join( QLatin1Char( '\n' ) ) );
3953
3954 //set mime color data to first color
3955 if ( colorList.length() > 0 )
3956 {
3957 mimeData->setColorData( QVariant( colorList.at( 0 ).first ) );
3958 }
3959
3960 return mimeData;
3961}
3962
3963bool QgsSymbolLayerUtils::saveColorsToGpl( QFile &file, const QString &paletteName, const QgsNamedColorList &colors )
3964{
3965 if ( !file.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
3966 {
3967 return false;
3968 }
3969
3970 QTextStream stream( &file );
3971
3972 stream << "GIMP Palette" << Qt::endl;
3973 if ( paletteName.isEmpty() )
3974 {
3975 stream << "Name: QGIS Palette" << Qt::endl;
3976 }
3977 else
3978 {
3979 stream << "Name: " << paletteName << Qt::endl;
3980 }
3981 stream << "Columns: 4" << Qt::endl;
3982 stream << '#' << Qt::endl;
3983
3984 for ( QgsNamedColorList::ConstIterator colorIt = colors.constBegin(); colorIt != colors.constEnd(); ++colorIt )
3985 {
3986 const QColor color = ( *colorIt ).first;
3987 if ( !color.isValid() )
3988 {
3989 continue;
3990 }
3991 stream << u"%1 %2 %3"_s.arg( color.red(), 3 ).arg( color.green(), 3 ).arg( color.blue(), 3 );
3992 stream << "\t" << ( ( *colorIt ).second.isEmpty() ? color.name() : ( *colorIt ).second ) << Qt::endl;
3993 }
3994 file.close();
3995
3996 return true;
3997}
3998
4000{
4001 QgsNamedColorList importedColors;
4002
4003 if ( !file.open( QIODevice::ReadOnly ) )
4004 {
4005 ok = false;
4006 return importedColors;
4007 }
4008
4009 QTextStream in( &file );
4010
4011 QString line = in.readLine();
4012 if ( !line.startsWith( "GIMP Palette"_L1 ) )
4013 {
4014 ok = false;
4015 return importedColors;
4016 }
4017
4018 //find name line
4019 while ( !in.atEnd() && !line.startsWith( "Name:"_L1 ) && !line.startsWith( '#' ) )
4020 {
4021 line = in.readLine();
4022 }
4023 if ( line.startsWith( "Name:"_L1 ) )
4024 {
4025 const thread_local QRegularExpression nameRx( "Name:\\s*(\\S.*)$" );
4026 const QRegularExpressionMatch match = nameRx.match( line );
4027 if ( match.hasMatch() )
4028 {
4029 name = match.captured( 1 );
4030 }
4031 }
4032
4033 //ignore lines until after "#"
4034 while ( !in.atEnd() && !line.startsWith( '#' ) )
4035 {
4036 line = in.readLine();
4037 }
4038 if ( in.atEnd() )
4039 {
4040 ok = false;
4041 return importedColors;
4042 }
4043
4044 //ready to start reading colors
4045 const thread_local QRegularExpression rx( "^\\s*(\\d+)\\s+(\\d+)\\s+(\\d+)(\\s.*)?$" );
4046 while ( !in.atEnd() )
4047 {
4048 line = in.readLine();
4049 const QRegularExpressionMatch match = rx.match( line );
4050 if ( !match.hasMatch() )
4051 {
4052 continue;
4053 }
4054 const int red = match.captured( 1 ).toInt();
4055 const int green = match.captured( 2 ).toInt();
4056 const int blue = match.captured( 3 ).toInt();
4057 const QColor color = QColor( red, green, blue );
4058 if ( !color.isValid() )
4059 {
4060 continue;
4061 }
4062
4063 //try to read color name
4064 QString label;
4065 if ( rx.captureCount() > 3 )
4066 {
4067 label = match.captured( 4 ).simplified();
4068 }
4069 else
4070 {
4071 label = colorToName( color );
4072 }
4073
4074 importedColors << qMakePair( color, label );
4075 }
4076
4077 file.close();
4078 ok = true;
4079 return importedColors;
4080}
4081
4082QColor QgsSymbolLayerUtils::parseColor( const QString &colorStr, bool strictEval )
4083{
4084 bool hasAlpha;
4085 return parseColorWithAlpha( colorStr, hasAlpha, strictEval );
4086}
4087
4088QColor QgsSymbolLayerUtils::parseColorWithAlpha( const QString &colorStr, bool &containsAlpha, bool strictEval )
4089{
4090 QColor parsedColor;
4091
4092 const thread_local QRegularExpression hexColorAlphaRx( "^\\s*#?([0-9a-fA-F]{6})([0-9a-fA-F]{2})\\s*$" );
4093 QRegularExpressionMatch match = hexColorAlphaRx.match( colorStr );
4094
4095 //color in hex format "#aabbcc", but not #aabbccdd
4096 if ( !match.hasMatch() && QColor::isValidColor( colorStr ) )
4097 {
4098 //string is a valid hex color string
4099 parsedColor.setNamedColor( colorStr );
4100 if ( parsedColor.isValid() )
4101 {
4102 containsAlpha = false;
4103 return parsedColor;
4104 }
4105 }
4106
4107 //color in hex format, with alpha
4108 if ( match.hasMatch() )
4109 {
4110 const QString hexColor = match.captured( 1 );
4111 parsedColor.setNamedColor( u"#"_s + hexColor );
4112 bool alphaOk;
4113 const int alphaHex = match.captured( 2 ).toInt( &alphaOk, 16 );
4114
4115 if ( parsedColor.isValid() && alphaOk )
4116 {
4117 parsedColor.setAlpha( alphaHex );
4118 containsAlpha = true;
4119 return parsedColor;
4120 }
4121 }
4122
4123 if ( !strictEval )
4124 {
4125 //color in hex format, without #
4126 const thread_local QRegularExpression hexColorRx2( "^\\s*(?:[0-9a-fA-F]{3}){1,2}\\s*$" );
4127 if ( colorStr.indexOf( hexColorRx2 ) != -1 )
4128 {
4129 //add "#" and parse
4130 parsedColor.setNamedColor( u"#"_s + colorStr );
4131 if ( parsedColor.isValid() )
4132 {
4133 containsAlpha = false;
4134 return parsedColor;
4135 }
4136 }
4137 }
4138
4139 //color in (rrr,ggg,bbb) format, brackets and rgb prefix optional
4140 const thread_local QRegularExpression rgbFormatRx(
4141 "^\\s*(?:rgb)?\\(?\\s*((?:[01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])(?:\\.\\d*)?)\\s*,\\s*((?:[01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])(?:\\.\\d*)?)\\s*,\\s*((?:[01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])(?:\\."
4142 "\\d*)?)\\s*\\)?\\s*;?\\s*$"
4143 );
4144 match = rgbFormatRx.match( colorStr );
4145 if ( match.hasMatch() )
4146 {
4147 bool rOk = false;
4148 bool gOk = false;
4149 bool bOk = false;
4150 const int r = match.captured( 1 ).toInt( &rOk );
4151 const int g = match.captured( 2 ).toInt( &gOk );
4152 const int b = match.captured( 3 ).toInt( &bOk );
4153
4154 if ( !rOk || !gOk || !bOk )
4155 {
4156 const float rFloat = match.captured( 1 ).toFloat();
4157 const float gFloat = match.captured( 2 ).toFloat();
4158 const float bFloat = match.captured( 3 ).toFloat();
4159 parsedColor.setRgbF( rFloat / 255.0, gFloat / 255.0, bFloat / 255.0 );
4160 }
4161 else
4162 {
4163 parsedColor.setRgb( r, g, b );
4164 }
4165
4166 if ( parsedColor.isValid() )
4167 {
4168 containsAlpha = false;
4169 return parsedColor;
4170 }
4171 }
4172
4173 //color in hsl(h,s,l) format, brackets optional
4174 const thread_local QRegularExpression hslFormatRx( "^\\s*hsl\\(?\\s*(\\d+(?:\\.\\d*)?)\\s*,\\s*(\\d+(?:\\.\\d*)?)\\s*%\\s*,\\s*(\\d+(?:\\.\\d*)?)\\s*%\\s*\\)?\\s*;?\\s*$" );
4175 match = hslFormatRx.match( colorStr );
4176 if ( match.hasMatch() )
4177 {
4178 bool hOk = false;
4179 bool sOk = false;
4180 bool lOk = false;
4181 const int h = match.captured( 1 ).toInt( &hOk );
4182 const int s = match.captured( 2 ).toInt( &sOk );
4183 const int l = match.captured( 3 ).toInt( &lOk );
4184
4185 if ( !hOk || !sOk || !lOk )
4186 {
4187 const float hFloat = match.captured( 1 ).toFloat();
4188 const float sFloat = match.captured( 2 ).toFloat();
4189 const float lFloat = match.captured( 3 ).toFloat();
4190 parsedColor.setHslF( hFloat / 360.0, sFloat / 100.0, lFloat / 100.0 );
4191 }
4192 else
4193 {
4194 parsedColor.setHsl( h, s / 100.0 * 255.0, l / 100.0 * 255.0 );
4195 }
4196 if ( parsedColor.isValid() )
4197 {
4198 containsAlpha = false;
4199 return parsedColor;
4200 }
4201 }
4202
4203 //color in (r%,g%,b%) format, brackets and rgb prefix optional
4204 const thread_local QRegularExpression rgbPercentFormatRx(
4205 "^\\s*(?:rgb)?\\(?\\s*(100|0*\\d{1,2}(?:\\.\\d*)?)\\s*%\\s*,\\s*(100|0*\\d{1,2}(?:\\.\\d*)?)\\s*%\\s*,\\s*(100|0*\\d{1,2}(?:\\.\\d*)?)\\s*%\\s*\\)?\\s*;?\\s*$"
4206 );
4207 match = rgbPercentFormatRx.match( colorStr );
4208 if ( match.hasMatch() )
4209 {
4210 const double r = match.captured( 1 ).toDouble() / 100;
4211 const double g = match.captured( 2 ).toDouble() / 100;
4212 const double b = match.captured( 3 ).toDouble() / 100;
4213 parsedColor.setRgbF( r, g, b );
4214 if ( parsedColor.isValid() )
4215 {
4216 containsAlpha = false;
4217 return parsedColor;
4218 }
4219 }
4220
4221 //color in (r,g,b,a) format, brackets and rgba prefix optional
4222 const thread_local QRegularExpression rgbaFormatRx(
4223 "^\\s*(?:rgba)?\\(?\\s*((?:[01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])(?:\\.\\d*)?)\\s*,\\s*((?:[01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])(?:\\.\\d*)?)\\s*,\\s*((?:[01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])(?:"
4224 "\\.\\d*)?)\\s*,\\s*(0|0?\\.\\d*|1(?:\\.0*)?)\\s*\\)?\\s*;?\\s*$"
4225 );
4226 match = rgbaFormatRx.match( colorStr );
4227 if ( match.hasMatch() )
4228 {
4229 bool rOk = false;
4230 bool gOk = false;
4231 bool bOk = false;
4232 const int r = match.captured( 1 ).toInt( &rOk );
4233 const int g = match.captured( 2 ).toInt( &gOk );
4234 const int b = match.captured( 3 ).toInt( &bOk );
4235 const double aDouble = match.captured( 4 ).toDouble();
4236
4237 if ( !rOk || !gOk || !bOk )
4238 {
4239 const float rFloat = match.captured( 1 ).toFloat();
4240 const float gFloat = match.captured( 2 ).toFloat();
4241 const float bFloat = match.captured( 3 ).toFloat();
4242 parsedColor.setRgbF( rFloat / 255.0, gFloat / 255.0, bFloat / 255.0, aDouble );
4243 }
4244 else
4245 {
4246 const int a = static_cast< int >( std::round( match.captured( 4 ).toDouble() * 255.0 ) );
4247 parsedColor.setRgb( r, g, b, a );
4248 }
4249 if ( parsedColor.isValid() )
4250 {
4251 containsAlpha = true;
4252 return parsedColor;
4253 }
4254 }
4255
4256 //color in (r%,g%,b%,a) format, brackets and rgba prefix optional
4257 const thread_local QRegularExpression rgbaPercentFormatRx(
4258 "^\\s*(?:rgba)?\\(?\\s*(100|0*\\d{1,2}(?:\\.\\d*)?)\\s*%\\s*,\\s*(100|0*\\d{1,2}(?:\\.\\d*)?)\\s*%\\s*,\\s*(100|0*\\d{1,2}(?:\\.\\d*)?)\\s*%\\s*,\\s*(0|0?\\.\\d*|1(?:\\.0*)?)\\s*\\)?\\s*;?\\s*$"
4259 );
4260 match = rgbaPercentFormatRx.match( colorStr );
4261 if ( match.hasMatch() )
4262 {
4263 const double r = match.captured( 1 ).toDouble() / 100;
4264 const double g = match.captured( 2 ).toDouble() / 100;
4265 const double b = match.captured( 3 ).toDouble() / 100;
4266 const double a = match.captured( 4 ).toDouble();
4267 parsedColor.setRgbF( r, g, b, a );
4268 if ( parsedColor.isValid() )
4269 {
4270 containsAlpha = true;
4271 return parsedColor;
4272 }
4273 }
4274
4275 //color in hsla(h,s%,l%,a) format, brackets optional
4276 const thread_local QRegularExpression hslaPercentFormatRx( "^\\s*hsla\\(?\\s*(\\d+(?:\\.\\d*)?)\\s*,\\s*(\\d+(?:\\.\\d*)?)\\s*%\\s*,\\s*(\\d+(?:\\.\\d*)?)\\s*%\\s*,\\s*([\\d\\.]+)\\s*\\)?\\s*;?\\s*$" );
4277 match = hslaPercentFormatRx.match( colorStr );
4278 if ( match.hasMatch() )
4279 {
4280 bool hOk = false;
4281 bool sOk = false;
4282 bool lOk = false;
4283 const int h = match.captured( 1 ).toInt( &hOk );
4284 const int s = match.captured( 2 ).toInt( &sOk );
4285 const int l = match.captured( 3 ).toInt( &lOk );
4286 const double aDouble = match.captured( 4 ).toDouble();
4287
4288 if ( !hOk || !sOk || !lOk )
4289 {
4290 const float hFloat = match.captured( 1 ).toFloat();
4291 const float sFloat = match.captured( 2 ).toFloat();
4292 const float lFloat = match.captured( 3 ).toFloat();
4293 parsedColor.setHslF( hFloat / 360.0, sFloat / 100.0, lFloat / 100.0, aDouble );
4294 }
4295 else
4296 {
4297 const int a = std::round( aDouble * 255.0 );
4298 parsedColor.setHsl( h, s / 100.0 * 255.0, l / 100.0 * 255.0, a );
4299 }
4300
4301 if ( parsedColor.isValid() )
4302 {
4303 containsAlpha = true;
4304 return parsedColor;
4305 }
4306 }
4307
4308 //couldn't parse string as color
4309 return QColor();
4310}
4311
4312void QgsSymbolLayerUtils::multiplyImageOpacity( QImage *image, qreal opacity )
4313{
4314 if ( !image )
4315 {
4316 return;
4317 }
4318
4319 QRgb myRgb;
4320 const QImage::Format format = image->format();
4321 if ( format != QImage::Format_ARGB32_Premultiplied && format != QImage::Format_ARGB32 )
4322 {
4323 QgsDebugError( u"no alpha channel."_s );
4324 return;
4325 }
4326
4327 //change the alpha component of every pixel
4328 for ( int heightIndex = 0; heightIndex < image->height(); ++heightIndex )
4329 {
4330 QRgb *scanLine = reinterpret_cast< QRgb * >( image->scanLine( heightIndex ) );
4331 for ( int widthIndex = 0; widthIndex < image->width(); ++widthIndex )
4332 {
4333 myRgb = scanLine[widthIndex];
4334 if ( format == QImage::Format_ARGB32_Premultiplied )
4335 scanLine[widthIndex] = qRgba( opacity * qRed( myRgb ), opacity * qGreen( myRgb ), opacity * qBlue( myRgb ), opacity * qAlpha( myRgb ) );
4336 else
4337 scanLine[widthIndex] = qRgba( qRed( myRgb ), qGreen( myRgb ), qBlue( myRgb ), opacity * qAlpha( myRgb ) );
4338 }
4339 }
4340}
4341
4342void QgsSymbolLayerUtils::blurImageInPlace( QImage &image, QRect rect, int radius, bool alphaOnly )
4343{
4344 // culled from Qt's qpixmapfilter.cpp, see: http://www.qtcentre.org/archive/index.php/t-26534.html
4345 const int tab[] = { 14, 10, 8, 6, 5, 5, 4, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2 };
4346 const int alpha = ( radius < 1 ) ? 16 : ( radius > 17 ) ? 1 : tab[radius - 1];
4347
4348 if ( image.format() != QImage::Format_ARGB32_Premultiplied && image.format() != QImage::Format_RGB32 )
4349 {
4350 image = image.convertToFormat( QImage::Format_ARGB32_Premultiplied );
4351 }
4352
4353 const int r1 = rect.top();
4354 const int r2 = rect.bottom();
4355 const int c1 = rect.left();
4356 const int c2 = rect.right();
4357
4358 const int bpl = image.bytesPerLine();
4359 int rgba[4];
4360 unsigned char *p;
4361
4362 int i1 = 0;
4363 int i2 = 3;
4364
4365 if ( alphaOnly ) // this seems to only work right for a black color
4366 i1 = i2 = ( QSysInfo::ByteOrder == QSysInfo::BigEndian ? 0 : 3 );
4367
4368 for ( int col = c1; col <= c2; col++ )
4369 {
4370 p = image.scanLine( r1 ) + col * 4;
4371 for ( int i = i1; i <= i2; i++ )
4372 rgba[i] = p[i] << 4;
4373
4374 p += bpl;
4375 for ( int j = r1; j < r2; j++, p += bpl )
4376 for ( int i = i1; i <= i2; i++ )
4377 p[i] = ( rgba[i] += ( ( p[i] << 4 ) - rgba[i] ) * alpha / 16 ) >> 4;
4378 }
4379
4380 for ( int row = r1; row <= r2; row++ )
4381 {
4382 p = image.scanLine( row ) + c1 * 4;
4383 for ( int i = i1; i <= i2; i++ )
4384 rgba[i] = p[i] << 4;
4385
4386 p += 4;
4387 for ( int j = c1; j < c2; j++, p += 4 )
4388 for ( int i = i1; i <= i2; i++ )
4389 p[i] = ( rgba[i] += ( ( p[i] << 4 ) - rgba[i] ) * alpha / 16 ) >> 4;
4390 }
4391
4392 for ( int col = c1; col <= c2; col++ )
4393 {
4394 p = image.scanLine( r2 ) + col * 4;
4395 for ( int i = i1; i <= i2; i++ )
4396 rgba[i] = p[i] << 4;
4397
4398 p -= bpl;
4399 for ( int j = r1; j < r2; j++, p -= bpl )
4400 for ( int i = i1; i <= i2; i++ )
4401 p[i] = ( rgba[i] += ( ( p[i] << 4 ) - rgba[i] ) * alpha / 16 ) >> 4;
4402 }
4403
4404 for ( int row = r1; row <= r2; row++ )
4405 {
4406 p = image.scanLine( row ) + c2 * 4;
4407 for ( int i = i1; i <= i2; i++ )
4408 rgba[i] = p[i] << 4;
4409
4410 p -= 4;
4411 for ( int j = c1; j < c2; j++, p -= 4 )
4412 for ( int i = i1; i <= i2; i++ )
4413 p[i] = ( rgba[i] += ( ( p[i] << 4 ) - rgba[i] ) * alpha / 16 ) >> 4;
4414 }
4415}
4416
4417void QgsSymbolLayerUtils::premultiplyColor( QColor &rgb, int alpha )
4418{
4419 if ( alpha != 255 && alpha > 0 )
4420 {
4421 // Semi-transparent pixel. We need to adjust the colors for ARGB32_Premultiplied images
4422 // where color values have to be premultiplied by alpha
4423 const double alphaFactor = alpha / 255.;
4424 int r = 0, g = 0, b = 0;
4425 rgb.getRgb( &r, &g, &b );
4426
4427 r *= alphaFactor;
4428 g *= alphaFactor;
4429 b *= alphaFactor;
4430 rgb.setRgb( r, g, b, alpha );
4431 }
4432 else if ( alpha == 0 )
4433 {
4434 rgb.setRgb( 0, 0, 0, 0 );
4435 }
4436}
4437
4439{
4440 QgsSimpleFillSymbolLayer *simpleFill = dynamic_cast< QgsSimpleFillSymbolLayer *>( fill );
4441 QgsSimpleLineSymbolLayer *simpleLine = dynamic_cast< QgsSimpleLineSymbolLayer *>( outline );
4442
4443 if ( !simpleFill || !simpleLine )
4444 return false;
4445
4446 if ( simpleLine->useCustomDashPattern() )
4447 return false;
4448
4449 if ( simpleLine->dashPatternOffset() )
4450 return false;
4451
4452 if ( simpleLine->alignDashPattern() )
4453 return false;
4454
4455 if ( simpleLine->tweakDashPatternOnCorners() )
4456 return false;
4457
4458 if ( simpleLine->trimDistanceStart() || simpleLine->trimDistanceEnd() )
4459 return false;
4460
4461 if ( simpleLine->drawInsidePolygon() )
4462 return false;
4463
4464 if ( simpleLine->ringFilter() != QgsSimpleLineSymbolLayer::AllRings )
4465 return false;
4466
4467 if ( simpleLine->offset() )
4468 return false;
4469
4470 if ( simpleLine->hasDataDefinedProperties() )
4471 return false;
4472
4473 // looks good!
4474 simpleFill->setStrokeColor( simpleLine->color() );
4475 simpleFill->setStrokeWidth( simpleLine->width() );
4476 simpleFill->setStrokeWidthUnit( simpleLine->widthUnit() );
4477 simpleFill->setStrokeWidthMapUnitScale( simpleLine->widthMapUnitScale() );
4478 simpleFill->setStrokeStyle( simpleLine->penStyle() );
4479 simpleFill->setPenJoinStyle( simpleLine->penJoinStyle() );
4480 return true;
4481}
4482
4483void QgsSymbolLayerUtils::sortVariantList( QList<QVariant> &list, Qt::SortOrder order )
4484{
4485 if ( order == Qt::AscendingOrder )
4486 {
4487 //std::sort( list.begin(), list.end(), _QVariantLessThan );
4488 std::sort( list.begin(), list.end(), qgsVariantLessThan );
4489 }
4490 else // Qt::DescendingOrder
4491 {
4492 //std::sort( list.begin(), list.end(), _QVariantGreaterThan );
4493 std::sort( list.begin(), list.end(), qgsVariantGreaterThan );
4494 }
4495}
4496
4497QPointF QgsSymbolLayerUtils::pointOnLineWithDistance( QPointF startPoint, QPointF directionPoint, double distance )
4498{
4499 const double dx = directionPoint.x() - startPoint.x();
4500 const double dy = directionPoint.y() - startPoint.y();
4501 const double length = std::sqrt( dx * dx + dy * dy );
4502 const double scaleFactor = distance / length;
4503 return QPointF( startPoint.x() + dx * scaleFactor, startPoint.y() + dy * scaleFactor );
4504}
4505
4506
4508{
4509 // copied from QgsMarkerCatalogue - TODO: unify //#spellok
4510 QStringList list;
4511 QStringList svgPaths = QgsApplication::svgPaths();
4512
4513 for ( int i = 0; i < svgPaths.size(); i++ )
4514 {
4515 const QDir dir( svgPaths[i] );
4516 const auto svgSubPaths = dir.entryList( QDir::Dirs | QDir::NoDotAndDotDot );
4517 for ( const QString &item : svgSubPaths )
4518 {
4519 svgPaths.insert( i + 1, dir.path() + '/' + item );
4520 }
4521
4522 const auto svgFiles = dir.entryList( QStringList( "*.svg" ), QDir::Files );
4523 for ( const QString &item : svgFiles )
4524 {
4525 // TODO test if it is correct SVG
4526 list.append( dir.path() + '/' + item );
4527 }
4528 }
4529 return list;
4530}
4531
4532// Stripped down version of listSvgFiles() for specified directory
4533QStringList QgsSymbolLayerUtils::listSvgFilesAt( const QString &directory )
4534{
4535 // TODO anything that applies for the listSvgFiles() applies this also
4536
4537 QStringList list;
4538 QStringList svgPaths;
4539 svgPaths.append( directory );
4540
4541 for ( int i = 0; i < svgPaths.size(); i++ )
4542 {
4543 const QDir dir( svgPaths[i] );
4544 const auto svgSubPaths = dir.entryList( QDir::Dirs | QDir::NoDotAndDotDot );
4545 for ( const QString &item : svgSubPaths )
4546 {
4547 svgPaths.insert( i + 1, dir.path() + '/' + item );
4548 }
4549
4550 const auto svgFiles = dir.entryList( QStringList( "*.svg" ), QDir::Files );
4551 for ( const QString &item : svgFiles )
4552 {
4553 list.append( dir.path() + '/' + item );
4554 }
4555 }
4556 return list;
4557}
4558
4559QString QgsSymbolLayerUtils::svgSymbolNameToPath( const QString &n, const QgsPathResolver &pathResolver )
4560{
4561 if ( n.isEmpty() )
4562 return QString();
4563
4564 if ( n.startsWith( "base64:"_L1 ) )
4565 return n;
4566
4567 // we might have a full path...
4568 if ( QFileInfo::exists( n ) )
4569 return QFileInfo( n ).canonicalFilePath();
4570
4571 QString name = n;
4572 // or it might be an url...
4573 if ( name.contains( "://"_L1 ) )
4574 {
4575 const QUrl url( name );
4576 if ( url.isValid() && !url.scheme().isEmpty() )
4577 {
4578 if ( url.scheme().compare( "file"_L1, Qt::CaseInsensitive ) == 0 )
4579 {
4580 // it's a url to a local file
4581 name = url.toLocalFile();
4582 if ( QFile( name ).exists() )
4583 {
4584 return QFileInfo( name ).canonicalFilePath();
4585 }
4586 }
4587 else
4588 {
4589 // it's a url pointing to a online resource
4590 return name;
4591 }
4592 }
4593 }
4594
4595 // SVG symbol not found - probably a relative path was used
4596
4597 QStringList svgPaths = QgsApplication::svgPaths();
4598 for ( int i = 0; i < svgPaths.size(); i++ )
4599 {
4600 QString svgPath = svgPaths[i];
4601 if ( svgPath.endsWith( QChar( '/' ) ) )
4602 {
4603 svgPath.chop( 1 );
4604 }
4605
4606 QgsDebugMsgLevel( "SvgPath: " + svgPath, 3 );
4607 // Not sure why to lowest dir was used instead of full relative path, it was causing #8664
4608 //QFileInfo myInfo( name );
4609 //QString myFileName = myInfo.fileName(); // foo.svg
4610 //QString myLowestDir = myInfo.dir().dirName();
4611 //QString myLocalPath = svgPath + QString( myLowestDir.isEmpty() ? "" : '/' + myLowestDir ) + '/' + myFileName;
4612 const QString myLocalPath = svgPath + QDir::separator() + name;
4613
4614 QgsDebugMsgLevel( "Alternative svg path: " + myLocalPath, 3 );
4615 if ( QFile( myLocalPath ).exists() )
4616 {
4617 QgsDebugMsgLevel( u"Svg found in alternative path"_s, 3 );
4618 return QFileInfo( myLocalPath ).canonicalFilePath();
4619 }
4620 }
4621
4622 return pathResolver.readPath( name );
4623}
4624
4625QString QgsSymbolLayerUtils::svgSymbolPathToName( const QString &p, const QgsPathResolver &pathResolver )
4626{
4627 if ( p.isEmpty() )
4628 return QString();
4629
4630 if ( p.startsWith( "base64:"_L1 ) )
4631 return p;
4632
4633 if ( !QFileInfo::exists( p ) )
4634 return p;
4635
4636 QString path = QFileInfo( p ).canonicalFilePath();
4637
4638 QStringList svgPaths = QgsApplication::svgPaths();
4639
4640 bool isInSvgPaths = false;
4641 for ( int i = 0; i < svgPaths.size(); i++ )
4642 {
4643 const QString dir = QFileInfo( svgPaths[i] ).canonicalFilePath();
4644
4645 if ( !dir.isEmpty() && path.startsWith( dir ) )
4646 {
4647 path = path.mid( dir.size() + 1 );
4648 isInSvgPaths = true;
4649 break;
4650 }
4651 }
4652
4653 if ( isInSvgPaths )
4654 return path;
4655
4656 return pathResolver.writePath( p );
4657}
4658
4659QPolygonF lineStringToQPolygonF( const QgsLineString *line )
4660{
4661 const double *srcX = line->xData();
4662 const double *srcY = line->yData();
4663 const int count = line->numPoints();
4664 QPolygonF thisRes( count );
4665 QPointF *dest = thisRes.data();
4666 for ( int i = 0; i < count; ++i )
4667 {
4668 *dest++ = QPointF( *srcX++, *srcY++ );
4669 }
4670 return thisRes;
4671}
4672
4673QPolygonF curveToPolygonF( const QgsCurve *curve )
4674{
4675 if ( const QgsLineString *line = qgsgeometry_cast< const QgsLineString * >( curve ) )
4676 {
4677 return lineStringToQPolygonF( line );
4678 }
4679 else
4680 {
4681 const std::unique_ptr< QgsLineString > straightened( curve->curveToLine() );
4682 return lineStringToQPolygonF( straightened.get() );
4683 }
4684}
4685
4686QList<QList<QPolygonF> > QgsSymbolLayerUtils::toQPolygonF( const QgsGeometry &geometry, Qgis::SymbolType type )
4687{
4688 return toQPolygonF( geometry.constGet(), type );
4689}
4690
4691QList<QList<QPolygonF> > QgsSymbolLayerUtils::toQPolygonF( const QgsAbstractGeometry *geometry, Qgis::SymbolType type )
4692{
4693 if ( !geometry )
4694 return {};
4695
4696 switch ( type )
4697 {
4699 {
4700 QPolygonF points;
4701
4703 {
4704 for ( auto it = geometry->vertices_begin(); it != geometry->vertices_end(); ++it )
4705 points << QPointF( ( *it ).x(), ( *it ).y() );
4706 }
4707 else
4708 {
4709 points << QPointF( 0, 0 );
4710 }
4711 return QList< QList<QPolygonF> >() << ( QList< QPolygonF >() << points );
4712 }
4713
4715 {
4716 QList< QList<QPolygonF> > res;
4718 {
4719 for ( auto it = geometry->const_parts_begin(); it != geometry->const_parts_end(); ++it )
4720 {
4721 res << ( QList< QPolygonF >() << curveToPolygonF( qgsgeometry_cast< const QgsCurve * >( *it ) ) );
4722 }
4723 }
4724 return res;
4725 }
4726
4728 {
4729 QList< QList<QPolygonF> > res;
4730
4731 for ( auto it = geometry->const_parts_begin(); it != geometry->const_parts_end(); ++it )
4732 {
4733 QList<QPolygonF> thisPart;
4735 if ( !surface )
4736 continue;
4737
4738 if ( !surface->exteriorRing() )
4739 continue;
4740
4741 thisPart << curveToPolygonF( surface->exteriorRing() );
4742
4743 for ( int i = 0; i < surface->numInteriorRings(); ++i )
4744 thisPart << curveToPolygonF( surface->interiorRing( i ) );
4745 res << thisPart;
4746 }
4747
4748 return res;
4749 }
4750
4752 return QList< QList<QPolygonF> >();
4753 }
4754
4755 return QList< QList<QPolygonF> >();
4756}
4757
4758
4759QPointF QgsSymbolLayerUtils::polygonCentroid( const QPolygonF &points )
4760{
4761 //Calculate the centroid of points
4762 double cx = 0, cy = 0;
4763 double area, sum = 0;
4764 for ( int i = points.count() - 1, j = 0; j < points.count(); i = j++ )
4765 {
4766 const QPointF &p1 = points[i];
4767 const QPointF &p2 = points[j];
4768 area = p1.x() * p2.y() - p1.y() * p2.x();
4769 sum += area;
4770 cx += ( p1.x() + p2.x() ) * area;
4771 cy += ( p1.y() + p2.y() ) * area;
4772 }
4773 sum *= 3.0;
4774 if ( qgsDoubleNear( sum, 0.0 ) )
4775 {
4776 // the linear ring is invalid - let's fall back to a solution that will still
4777 // allow us render at least something (instead of just returning point nan,nan)
4778 if ( points.count() >= 2 )
4779 return QPointF( ( points[0].x() + points[1].x() ) / 2, ( points[0].y() + points[1].y() ) / 2 );
4780 else if ( points.count() == 1 )
4781 return points[0];
4782 else
4783 return QPointF(); // hopefully we shouldn't ever get here
4784 }
4785 cx /= sum;
4786 cy /= sum;
4787
4788 return QPointF( cx, cy );
4789}
4790
4791QPointF QgsSymbolLayerUtils::polygonPointOnSurface( const QPolygonF &points, const QVector<QPolygonF> *rings )
4792{
4793 QPointF centroid = QgsSymbolLayerUtils::polygonCentroid( points );
4794
4795 if ( ( rings && rings->count() > 0 ) || !pointInPolygon( points, centroid ) )
4796 {
4797 unsigned int i, pointCount = points.count();
4798 QgsPolylineXY polyline( pointCount );
4799 for ( i = 0; i < pointCount; ++i )
4800 polyline[i] = QgsPointXY( points[i].x(), points[i].y() );
4801 QgsGeometry geom = QgsGeometry::fromPolygonXY( QgsPolygonXY() << polyline );
4802 if ( !geom.isNull() )
4803 {
4804 if ( rings )
4805 {
4806 for ( auto ringIt = rings->constBegin(); ringIt != rings->constEnd(); ++ringIt )
4807 {
4808 pointCount = ( *ringIt ).count();
4809 QgsPolylineXY polyline( pointCount );
4810 for ( i = 0; i < pointCount; ++i )
4811 polyline[i] = QgsPointXY( ( *ringIt )[i].x(), ( *ringIt )[i].y() );
4812 geom.addRing( polyline );
4813 }
4814 }
4815
4816 const QgsGeometry pointOnSurfaceGeom = geom.pointOnSurface();
4817 if ( !pointOnSurfaceGeom.isNull() )
4818 {
4819 const QgsPointXY point = pointOnSurfaceGeom.asPoint();
4820 centroid.setX( point.x() );
4821 centroid.setY( point.y() );
4822 }
4823 }
4824 }
4825
4826 return QPointF( centroid.x(), centroid.y() );
4827}
4828
4829bool QgsSymbolLayerUtils::pointInPolygon( const QPolygonF &points, QPointF point )
4830{
4831 bool inside = false;
4832
4833 const double x = point.x();
4834 const double y = point.y();
4835
4836 for ( int i = 0, j = points.count() - 1; i < points.count(); i++ )
4837 {
4838 const QPointF &p1 = points[i];
4839 const QPointF &p2 = points[j];
4840
4841 if ( qgsDoubleNear( p1.x(), x ) && qgsDoubleNear( p1.y(), y ) )
4842 return true;
4843
4844 if ( ( p1.y() < y && p2.y() >= y ) || ( p2.y() < y && p1.y() >= y ) )
4845 {
4846 if ( p1.x() + ( y - p1.y() ) / ( p2.y() - p1.y() ) * ( p2.x() - p1.x() ) <= x )
4847 inside = !inside;
4848 }
4849
4850 j = i;
4851 }
4852 return inside;
4853}
4854
4855double QgsSymbolLayerUtils::polylineLength( const QPolygonF &polyline )
4856{
4857 if ( polyline.size() < 2 )
4858 return 0;
4859
4860 double totalLength = 0;
4861 auto it = polyline.begin();
4862 QPointF p1 = *it++;
4863 for ( ; it != polyline.end(); ++it )
4864 {
4865 const QPointF p2 = *it;
4866 const double segmentLength = std::sqrt( std::pow( p1.x() - p2.x(), 2.0 ) + std::pow( p1.y() - p2.y(), 2.0 ) );
4867 totalLength += segmentLength;
4868 p1 = p2;
4869 }
4870 return totalLength;
4871}
4872
4873QPolygonF QgsSymbolLayerUtils::polylineSubstring( const QPolygonF &polyline, double startOffset, double endOffset )
4874{
4875 if ( polyline.size() < 2 )
4876 return QPolygonF();
4877
4878 double totalLength = 0;
4879 auto it = polyline.begin();
4880 QPointF p1 = *it++;
4881 std::vector< double > segmentLengths( polyline.size() - 1 );
4882 auto segmentLengthIt = segmentLengths.begin();
4883 for ( ; it != polyline.end(); ++it )
4884 {
4885 const QPointF p2 = *it;
4886 *segmentLengthIt = std::sqrt( std::pow( p1.x() - p2.x(), 2.0 ) + std::pow( p1.y() - p2.y(), 2.0 ) );
4887 totalLength += *segmentLengthIt;
4888
4889 segmentLengthIt++;
4890 p1 = p2;
4891 }
4892
4893 if ( startOffset >= 0 && totalLength <= startOffset )
4894 return QPolygonF();
4895 if ( endOffset < 0 && totalLength <= -endOffset )
4896 return QPolygonF();
4897
4898 const double startDistance = startOffset < 0 ? totalLength + startOffset : startOffset;
4899 const double endDistance = endOffset <= 0 ? totalLength + endOffset : endOffset;
4900 QPolygonF substringPoints;
4901 substringPoints.reserve( polyline.size() );
4902
4903 it = polyline.begin();
4904 segmentLengthIt = segmentLengths.begin();
4905
4906 p1 = *it++;
4907 bool foundStart = false;
4908 if ( qgsDoubleNear( startDistance, 0.0 ) || startDistance < 0 )
4909 {
4910 substringPoints << p1;
4911 foundStart = true;
4912 }
4913
4914 double distanceTraversed = 0;
4915 for ( ; it != polyline.end(); ++it )
4916 {
4917 const QPointF p2 = *it;
4918 if ( distanceTraversed < startDistance && distanceTraversed + *segmentLengthIt > startDistance )
4919 {
4920 // start point falls on this segment
4921 const double distanceToStart = startDistance - distanceTraversed;
4922 double startX, startY;
4923 QgsGeometryUtilsBase::pointOnLineWithDistance( p1.x(), p1.y(), p2.x(), p2.y(), distanceToStart, startX, startY );
4924 substringPoints << QPointF( startX, startY );
4925 foundStart = true;
4926 }
4927 if ( foundStart && ( distanceTraversed + *segmentLengthIt > endDistance ) )
4928 {
4929 // end point falls on this segment
4930 const double distanceToEnd = endDistance - distanceTraversed;
4931 double endX, endY;
4932 QgsGeometryUtilsBase::pointOnLineWithDistance( p1.x(), p1.y(), p2.x(), p2.y(), distanceToEnd, endX, endY );
4933 if ( substringPoints.last() != QPointF( endX, endY ) )
4934 substringPoints << QPointF( endX, endY );
4935 }
4936 else if ( foundStart )
4937 {
4938 if ( substringPoints.last() != QPointF( p2.x(), p2.y() ) )
4939 substringPoints << QPointF( p2.x(), p2.y() );
4940 }
4941
4942 distanceTraversed += *segmentLengthIt;
4943 if ( distanceTraversed > endDistance )
4944 break;
4945
4946 p1 = p2;
4947 segmentLengthIt++;
4948 }
4949
4950 if ( ( substringPoints.size() < 2 ) || ( substringPoints.size() == 2 && substringPoints.at( 0 ) == substringPoints.at( 1 ) ) )
4951 return QPolygonF();
4952
4953 return substringPoints;
4954}
4955
4956bool QgsSymbolLayerUtils::isSharpCorner( QPointF p1, QPointF p2, QPointF p3 )
4957{
4958 double vertexAngle = M_PI - ( std::atan2( p3.y() - p2.y(), p3.x() - p2.x() ) - std::atan2( p2.y() - p1.y(), p2.x() - p1.x() ) );
4959 vertexAngle = QgsGeometryUtilsBase::normalizedAngle( vertexAngle );
4960
4961 // extreme angles form more than 45 degree angle at a node
4962 return vertexAngle < M_PI * 135.0 / 180.0 || vertexAngle > M_PI * 225.0 / 180.0;
4963}
4964
4965void QgsSymbolLayerUtils::appendPolyline( QPolygonF &target, const QPolygonF &line )
4966{
4967 target.reserve( target.size() + line.size() );
4968 for ( const QPointF &pt : line )
4969 {
4970 if ( !target.empty() && target.last() == pt )
4971 continue;
4972
4973 target << pt;
4974 }
4975}
4976
4977std::unique_ptr< QgsExpression > QgsSymbolLayerUtils::fieldOrExpressionToExpression( const QString &fieldOrExpression )
4978{
4979 if ( fieldOrExpression.isEmpty() )
4980 return nullptr;
4981
4982 auto expr = std::make_unique< QgsExpression >( fieldOrExpression );
4983 if ( !expr->hasParserError() )
4984 return expr;
4985
4986 // now try with quoted field name
4987 expr = std::make_unique< QgsExpression >( QgsExpression::quotedColumnRef( fieldOrExpression ) );
4988 Q_ASSERT( !expr->hasParserError() );
4989 return expr;
4990}
4991
4993{
4994 const QgsExpressionNode *n = expression->rootNode();
4995
4996 if ( n && n->nodeType() == QgsExpressionNode::ntColumnRef )
4997 return static_cast<const QgsExpressionNodeColumnRef *>( n )->name();
4998
4999 return expression->expression();
5000}
5001
5002QList<double> QgsSymbolLayerUtils::prettyBreaks( double minimum, double maximum, int classes )
5003{
5004 // C++ implementation of R's pretty algorithm
5005 // Based on code for determining optimal tick placement for statistical graphics
5006 // from the R statistical programming language.
5007 // Code ported from R implementation from 'labeling' R package
5008 //
5009 // Computes a sequence of about 'classes' equally spaced round values
5010 // which cover the range of values from 'minimum' to 'maximum'.
5011 // The values are chosen so that they are 1, 2 or 5 times a power of 10.
5012
5013 QList<double> breaks;
5014 if ( classes < 1 )
5015 {
5016 breaks.append( maximum );
5017 return breaks;
5018 }
5019
5020 const int minimumCount = static_cast< int >( classes ) / 3;
5021 const double shrink = 0.75;
5022 const double highBias = 1.5;
5023 const double adjustBias = 0.5 + 1.5 * highBias;
5024 const int divisions = classes;
5025 const double h = highBias;
5026 double cell;
5027 bool small = false;
5028 const double dx = maximum - minimum;
5029
5030 if ( qgsDoubleNear( dx, 0.0 ) && qgsDoubleNear( maximum, 0.0 ) )
5031 {
5032 cell = 1.0;
5033 small = true;
5034 }
5035 else
5036 {
5037 int U = 1;
5038 cell = std::max( std::fabs( minimum ), std::fabs( maximum ) );
5039 if ( adjustBias >= 1.5 * h + 0.5 )
5040 {
5041 U = 1 + ( 1.0 / ( 1 + h ) );
5042 }
5043 else
5044 {
5045 U = 1 + ( 1.5 / ( 1 + adjustBias ) );
5046 }
5047 small = dx < ( cell * U * std::max( 1, divisions ) * 1e-07 * 3.0 );
5048 }
5049
5050 if ( small )
5051 {
5052 if ( cell > 10 )
5053 {
5054 cell = 9 + cell / 10;
5055 cell = cell * shrink;
5056 }
5057 if ( minimumCount > 1 )
5058 {
5059 cell = cell / minimumCount;
5060 }
5061 }
5062 else
5063 {
5064 cell = dx;
5065 if ( divisions > 1 )
5066 {
5067 cell = cell / divisions;
5068 }
5069 }
5070 if ( cell < 20 * 1e-07 )
5071 {
5072 cell = 20 * 1e-07;
5073 }
5074
5075 const double base = std::pow( 10.0, std::floor( std::log10( cell ) ) );
5076 double unit = base;
5077 if ( ( 2 * base ) - cell < h * ( cell - unit ) )
5078 {
5079 unit = 2.0 * base;
5080 if ( ( 5 * base ) - cell < adjustBias * ( cell - unit ) )
5081 {
5082 unit = 5.0 * base;
5083 if ( ( 10.0 * base ) - cell < h * ( cell - unit ) )
5084 {
5085 unit = 10.0 * base;
5086 }
5087 }
5088 }
5089 // Maybe used to correct for the epsilon here??
5090 int start = std::floor( minimum / unit + 1e-07 );
5091 int end = std::ceil( maximum / unit - 1e-07 );
5092
5093 // Extend the range out beyond the data. Does this ever happen??
5094 while ( start * unit > minimum + ( 1e-07 * unit ) )
5095 {
5096 start = start - 1;
5097 }
5098 while ( end * unit < maximum - ( 1e-07 * unit ) )
5099 {
5100 end = end + 1;
5101 }
5102 QgsDebugMsgLevel( u"pretty classes: %1"_s.arg( end ), 3 );
5103
5104 // If we don't have quite enough labels, extend the range out
5105 // to make more (these labels are beyond the data :()
5106 int k = std::floor( 0.5 + end - start );
5107 if ( k < minimumCount )
5108 {
5109 k = minimumCount - k;
5110 if ( start >= 0 )
5111 {
5112 end = end + k / 2;
5113 start = start - k / 2 + k % 2;
5114 }
5115 else
5116 {
5117 start = start - k / 2;
5118 end = end + k / 2 + k % 2;
5119 }
5120 }
5121 const double minimumBreak = start * unit;
5122 //double maximumBreak = end * unit;
5123 const int count = end - start;
5124
5125 breaks.reserve( count );
5126 for ( int i = 1; i < count + 1; i++ )
5127 {
5128 breaks.append( minimumBreak + i * unit );
5129 }
5130
5131 if ( breaks.isEmpty() )
5132 return breaks;
5133
5134 if ( breaks.first() < minimum )
5135 {
5136 breaks[0] = minimum;
5137 }
5138 if ( breaks.last() > maximum )
5139 {
5140 breaks[breaks.count() - 1] = maximum;
5141 }
5142
5143 // because sometimes when number of classes is big,
5144 // break supposed to be at zero is something like -2.22045e-16
5145 if ( minimum < 0.0 && maximum > 0.0 ) //then there should be a zero somewhere
5146 {
5147 QList<double> breaksMinusZero; // compute difference "each break - 0"
5148 for ( int i = 0; i < breaks.count(); i++ )
5149 {
5150 breaksMinusZero.append( breaks[i] - 0.0 );
5151 }
5152 int posOfMin = 0;
5153 for ( int i = 1; i < breaks.count(); i++ ) // find position of minimal difference
5154 {
5155 if ( std::abs( breaksMinusZero[i] ) < std::abs( breaksMinusZero[i - 1] ) )
5156 posOfMin = i;
5157 }
5158 breaks[posOfMin] = 0.0;
5159 }
5160
5161 return breaks;
5162}
5163
5164double QgsSymbolLayerUtils::rescaleUom( double size, Qgis::RenderUnit unit, const QVariantMap &props )
5165{
5166 double scale = 1;
5167 bool roundToUnit = false;
5168 if ( unit == Qgis::RenderUnit::Unknown )
5169 {
5170 if ( props.contains( u"uomScale"_s ) )
5171 {
5172 bool ok;
5173 scale = props.value( u"uomScale"_s ).toDouble( &ok );
5174 if ( !ok )
5175 {
5176 return size;
5177 }
5178 }
5179 }
5180 else
5181 {
5182 if ( props.value( u"uom"_s ) == "http://www.opengeospatial.org/se/units/metre"_L1 )
5183 {
5184 switch ( unit )
5185 {
5187 scale = 0.001;
5188 break;
5190 scale = 0.00028;
5191 roundToUnit = true;
5192 break;
5193 default:
5194 scale = 1;
5195 }
5196 }
5197 else
5198 {
5199 // target is pixels
5200 switch ( unit )
5201 {
5203 scale = 1 / 0.28;
5204 roundToUnit = true;
5205 break;
5207 scale = 1 / 0.28 * 25.4;
5208 roundToUnit = true;
5209 break;
5211 scale = 90. /* dots per inch according to OGC SLD */ / 72. /* points per inch */;
5212 roundToUnit = true;
5213 break;
5215 // pixel is pixel
5216 scale = 1;
5217 break;
5220 // already handed via uom
5221 scale = 1;
5222 break;
5225 // these do not make sense and should not really reach here
5226 scale = 1;
5227 }
5228 }
5229 }
5230 double rescaled = size * scale;
5231 // round to unit if the result is pixels to avoid a weird looking SLD (people often think
5232 // of pixels as integers, even if SLD allows for float values in there
5233 if ( roundToUnit )
5234 {
5235 rescaled = std::round( rescaled );
5236 }
5237 return rescaled;
5238}
5239
5240QPointF QgsSymbolLayerUtils::rescaleUom( QPointF point, Qgis::RenderUnit unit, const QVariantMap &props )
5241{
5242 const double x = rescaleUom( point.x(), unit, props );
5243 const double y = rescaleUom( point.y(), unit, props );
5244 return QPointF( x, y );
5245}
5246
5247QVector<qreal> QgsSymbolLayerUtils::rescaleUom( const QVector<qreal> &array, Qgis::RenderUnit unit, const QVariantMap &props )
5248{
5249 QVector<qreal> result;
5250 QVector<qreal>::const_iterator it = array.constBegin();
5251 for ( ; it != array.constEnd(); ++it )
5252 {
5253 result.append( rescaleUom( *it, unit, props ) );
5254 }
5255 return result;
5256}
5257
5258void QgsSymbolLayerUtils::applyScaleDependency( QDomDocument &doc, QDomElement &ruleElem, QVariantMap &props )
5259{
5260 if ( !props.value( u"scaleMinDenom"_s, QString() ).toString().isEmpty() )
5261 {
5262 QDomElement scaleMinDenomElem = doc.createElement( u"se:MinScaleDenominator"_s );
5263 scaleMinDenomElem.appendChild( doc.createTextNode( qgsDoubleToString( props.value( u"scaleMinDenom"_s ).toString().toDouble() ) ) );
5264 ruleElem.appendChild( scaleMinDenomElem );
5265 }
5266
5267 if ( !props.value( u"scaleMaxDenom"_s, QString() ).toString().isEmpty() )
5268 {
5269 QDomElement scaleMaxDenomElem = doc.createElement( u"se:MaxScaleDenominator"_s );
5270 scaleMaxDenomElem.appendChild( doc.createTextNode( qgsDoubleToString( props.value( u"scaleMaxDenom"_s ).toString().toDouble() ) ) );
5271 ruleElem.appendChild( scaleMaxDenomElem );
5272 }
5273}
5274
5275void QgsSymbolLayerUtils::mergeScaleDependencies( double mScaleMinDenom, double mScaleMaxDenom, QVariantMap &props )
5276{
5277 if ( !qgsDoubleNear( mScaleMinDenom, 0 ) )
5278 {
5279 bool ok;
5280 const double parentScaleMinDenom = props.value( u"scaleMinDenom"_s, u"0"_s ).toString().toDouble( &ok );
5281 if ( !ok || parentScaleMinDenom <= 0 )
5282 props[u"scaleMinDenom"_s] = QString::number( mScaleMinDenom );
5283 else
5284 props[u"scaleMinDenom"_s] = QString::number( std::max( parentScaleMinDenom, mScaleMinDenom ) );
5285 }
5286
5287 if ( !qgsDoubleNear( mScaleMaxDenom, 0 ) )
5288 {
5289 bool ok;
5290 const double parentScaleMaxDenom = props.value( u"scaleMaxDenom"_s, u"0"_s ).toString().toDouble( &ok );
5291 if ( !ok || parentScaleMaxDenom <= 0 )
5292 props[u"scaleMaxDenom"_s] = QString::number( mScaleMaxDenom );
5293 else
5294 props[u"scaleMaxDenom"_s] = QString::number( std::min( parentScaleMaxDenom, mScaleMaxDenom ) );
5295 }
5296}
5297
5298double QgsSymbolLayerUtils::sizeInPixelsFromSldUom( const QString &uom, double size )
5299{
5300 double scale = 1.0;
5301
5302 if ( uom == "http://www.opengeospatial.org/se/units/metre"_L1 )
5303 {
5304 scale = 1.0 / 0.00028; // from meters to pixels
5305 }
5306 else if ( uom == "http://www.opengeospatial.org/se/units/foot"_L1 )
5307 {
5308 scale = 304.8 / 0.28; // from feet to pixels
5309 }
5310 else
5311 {
5312 scale = 1.0; // from pixels to pixels (default unit)
5313 }
5314
5315 return size * scale;
5316}
5317
5318QSet<const QgsSymbolLayer *> QgsSymbolLayerUtils::toSymbolLayerPointers( const QgsFeatureRenderer *renderer, const QSet<QgsSymbolLayerId> &symbolLayerIds )
5319{
5321 class SymbolLayerVisitor : public QgsStyleEntityVisitorInterface
5322 {
5323 public:
5324 SymbolLayerVisitor( const QSet<QgsSymbolLayerId> &layerIds )
5325 : mSymbolLayerIds( layerIds )
5326 {}
5327
5328 bool visitEnter( const QgsStyleEntityVisitorInterface::Node &node ) override
5329 {
5331 {
5332 mCurrentRuleKey = node.identifier;
5333 return true;
5334 }
5335 return false;
5336 }
5337
5338 void visitSymbol( const QgsSymbol *symbol, const QString &identifier, QVector<int> rootPath )
5339 {
5340 for ( int idx = 0; idx < symbol->symbolLayerCount(); idx++ )
5341 {
5342 QVector<int> indexPath = rootPath;
5343 indexPath.append( idx );
5344 const QgsSymbolLayer *sl = symbol->symbolLayer( idx );
5346 if ( mSymbolLayerIds.contains( QgsSymbolLayerId( mCurrentRuleKey + identifier, indexPath ) ) )
5347 {
5348 mSymbolLayers.insert( sl );
5349 }
5351
5352 const QgsSymbol *subSymbol = const_cast<QgsSymbolLayer *>( sl )->subSymbol();
5353 if ( subSymbol )
5354 visitSymbol( subSymbol, identifier, indexPath );
5355 }
5356 }
5357
5358 bool visit( const QgsStyleEntityVisitorInterface::StyleLeaf &leaf ) override
5359 {
5360 if ( leaf.entity && leaf.entity->type() == QgsStyle::SymbolEntity )
5361 {
5362 auto symbolEntity = static_cast<const QgsStyleSymbolEntity *>( leaf.entity );
5363 if ( symbolEntity->symbol() )
5364 {
5365 visitSymbol( symbolEntity->symbol(), leaf.identifier, {} );
5366 }
5367 }
5368 return true;
5369 }
5370
5371 QString mCurrentRuleKey;
5372 const QSet<QgsSymbolLayerId> &mSymbolLayerIds;
5373 QSet<const QgsSymbolLayer *> mSymbolLayers;
5374 };
5376
5377 SymbolLayerVisitor visitor( symbolLayerIds );
5378 renderer->accept( &visitor );
5379 return visitor.mSymbolLayers;
5380}
5381
5383{
5384 class SymbolRefreshRateVisitor : public QgsStyleEntityVisitorInterface
5385 {
5386 public:
5387 SymbolRefreshRateVisitor() {}
5388
5389 bool visitEnter( const QgsStyleEntityVisitorInterface::Node &node ) override
5390 {
5392 {
5393 return true;
5394 }
5395 return false;
5396 }
5397
5398 void visitSymbol( const QgsSymbol *symbol )
5399 {
5400 // symbol may be marked as animated on a symbol level (e.g. when it implements animation
5401 // via data defined properties)
5402 if ( symbol->animationSettings().isAnimated() )
5403 {
5404 if ( symbol->animationSettings().frameRate() > refreshRate )
5405 refreshRate = symbol->animationSettings().frameRate();
5406 }
5407 for ( int idx = 0; idx < symbol->symbolLayerCount(); idx++ )
5408 {
5409 const QgsSymbolLayer *sl = symbol->symbolLayer( idx );
5410 if ( const QgsAnimatedMarkerSymbolLayer *animatedMarker = dynamic_cast< const QgsAnimatedMarkerSymbolLayer *>( sl ) )
5411 {
5412 // this is a bit of a short cut -- if a symbol has multiple layers with different frame rates,
5413 // there's no guarantee that they will be even multiples of each other! But given we are looking for
5414 // a single frame rate for a whole renderer, it's an acceptable compromise...
5415 if ( refreshRate == -1 || ( animatedMarker->frameRate() > refreshRate ) )
5416 refreshRate = animatedMarker->frameRate();
5417 }
5418
5419 if ( const QgsSymbol *subSymbol = const_cast<QgsSymbolLayer *>( sl )->subSymbol() )
5420 visitSymbol( subSymbol );
5421 }
5422 }
5423
5424 bool visit( const QgsStyleEntityVisitorInterface::StyleLeaf &leaf ) override
5425 {
5426 if ( leaf.entity && leaf.entity->type() == QgsStyle::SymbolEntity )
5427 {
5428 if ( QgsSymbol *symbol = qgis::down_cast<const QgsStyleSymbolEntity *>( leaf.entity )->symbol() )
5429 {
5430 visitSymbol( symbol );
5431 }
5432 }
5433 return true;
5434 }
5435
5436 double refreshRate = -1;
5437 };
5438
5439 SymbolRefreshRateVisitor visitor;
5440 renderer->accept( &visitor );
5441 return visitor.refreshRate;
5442}
5443
5444std::unique_ptr< QgsSymbol > QgsSymbolLayerUtils::restrictedSizeSymbol( const QgsSymbol *s, double minSize, double maxSize, QgsRenderContext *context, double &width, double &height, bool *ok )
5445{
5446 if ( !s || !context )
5447 {
5448 return nullptr;
5449 }
5450
5451 if ( ok )
5452 *ok = true;
5453
5454 const QgsSymbolLayerList sls = s->symbolLayers();
5455 for ( const QgsSymbolLayer *sl : std::as_const( sls ) )
5456 {
5457 // geometry generators involved, there is no way to get a restricted size symbol
5458 if ( sl->type() == Qgis::SymbolType::Hybrid )
5459 {
5460 if ( ok )
5461 *ok = false;
5462
5463 return nullptr;
5464 }
5465 }
5466
5467 double size;
5468 const QgsMarkerSymbol *markerSymbol = dynamic_cast<const QgsMarkerSymbol *>( s );
5469 const QgsLineSymbol *lineSymbol = dynamic_cast<const QgsLineSymbol *>( s );
5470 if ( markerSymbol )
5471 {
5472 size = markerSymbol->size( *context );
5473 }
5474 else if ( lineSymbol )
5475 {
5476 size = lineSymbol->width( *context );
5477 }
5478 else
5479 {
5480 // cannot return a size restricted symbol but we assume there is no need
5481 // for one as the rendering will be done in the given size (different from geometry
5482 // generator where rendering will bleed outside the given area
5483 return nullptr;
5484 }
5485
5486 size /= context->scaleFactor();
5487
5488 if ( minSize > 0 && size < minSize )
5489 {
5490 size = minSize;
5491 }
5492 else if ( maxSize > 0 && size > maxSize )
5493 {
5494 size = maxSize;
5495 }
5496 else
5497 {
5498 // no need to restricted size symbol
5499 return nullptr;
5500 }
5501
5502 if ( markerSymbol )
5503 {
5504 std::unique_ptr< QgsMarkerSymbol > ms( markerSymbol->clone() );
5505 ms->setSize( size );
5506 ms->setSizeUnit( Qgis::RenderUnit::Millimeters );
5507 width = size;
5508 height = size;
5509 return ms;
5510 }
5511 else if ( lineSymbol )
5512 {
5513 std::unique_ptr< QgsLineSymbol > ls( lineSymbol->clone() );
5514 ls->setWidth( size );
5515 ls->setWidthUnit( Qgis::RenderUnit::Millimeters );
5516 height = size;
5517 return ls;
5518 }
5519
5520 return nullptr;
5521}
5522
5523QgsStringMap QgsSymbolLayerUtils::evaluatePropertiesMap( const QMap<QString, QgsProperty> &propertiesMap, const QgsExpressionContext &context )
5524{
5525 QgsStringMap properties;
5526 QMap<QString, QgsProperty>::const_iterator paramIt = propertiesMap.constBegin();
5527 for ( ; paramIt != propertiesMap.constEnd(); ++paramIt )
5528 {
5529 properties.insert( paramIt.key(), paramIt.value().valueAsString( context ) );
5530 }
5531 return properties;
5532}
5533
5534QSize QgsSymbolLayerUtils::tileSize( int width, int height, double &angleRad )
5535{
5536 angleRad = std::fmod( angleRad, M_PI * 2 );
5537
5538 if ( angleRad < 0 )
5539 {
5540 angleRad += M_PI * 2;
5541 }
5542
5543 // tan with rational sin/cos
5544 struct rationalTangent
5545 {
5546 int p; // numerator
5547 int q; // denominator
5548 double angle; // "good" angle
5549 };
5550
5551#if 0
5552
5553 // This list is more granular (approx 1 degree steps) but some
5554 // values can lead to huge tiles
5555 // List of "good" angles from 0 to PI/2
5556 static const QList<rationalTangent> __rationalTangents
5557 {
5558 { 1, 57, 0.01754206006 },
5559 { 3, 86, 0.03486958155 },
5560 { 1, 19, 0.05258306161 },
5561 { 3, 43, 0.06965457373 },
5562 { 7, 80, 0.08727771295 },
5563 { 2, 19, 0.1048769387 },
5564 { 7, 57, 0.1221951707 },
5565 { 9, 64, 0.1397088743 },
5566 { 13, 82, 0.157228051 },
5567 { 3, 17, 0.174672199 },
5568 { 7, 36, 0.1920480172 },
5569 { 17, 80, 0.209385393 },
5570 { 3, 13, 0.2267988481 },
5571 { 1, 4, 0.2449786631 },
5572 { 26, 97, 0.2618852647 },
5573 { 27, 94, 0.2797041525 },
5574 { 26, 85, 0.2968446734 },
5575 { 13, 40, 0.3142318991 },
5576 { 21, 61, 0.3315541619 },
5577 { 4, 11, 0.3487710036 },
5578 { 38, 99, 0.3664967859 },
5579 { 40, 99, 0.383984624 },
5580 { 31, 73, 0.4015805401 },
5581 { 41, 92, 0.4192323938 },
5582 { 7, 15, 0.4366271598 },
5583 { 20, 41, 0.4538440015 },
5584 { 27, 53, 0.4711662643 },
5585 { 42, 79, 0.4886424026 },
5586 { 51, 92, 0.5061751436 },
5587 { 56, 97, 0.5235757641 },
5588 { 3, 5, 0.5404195003 },
5589 { 5, 8, 0.5585993153 },
5590 { 50, 77, 0.5759185996 },
5591 { 29, 43, 0.5933501462 },
5592 { 7, 10, 0.6107259644 },
5593 { 69, 95, 0.6281701124 },
5594 { 52, 69, 0.6458159195 },
5595 { 25, 32, 0.6632029927 },
5596 { 17, 21, 0.6805212247 },
5597 { 73, 87, 0.6981204504 },
5598 { 73, 84, 0.7154487784 },
5599 { 9, 10, 0.7328151018 },
5600 { 83, 89, 0.7505285818 },
5601 { 28, 29, 0.7678561033 },
5602 { 1, 1, 0.7853981634 },
5603 { 29, 28, 0.8029402235 },
5604 { 89, 83, 0.820267745 },
5605 { 10, 9, 0.837981225 },
5606 { 107, 93, 0.855284165 },
5607 { 87, 73, 0.8726758763 },
5608 { 121, 98, 0.8900374031 },
5609 { 32, 25, 0.9075933341 },
5610 { 69, 52, 0.9249804073 },
5611 { 128, 93, 0.9424647244 },
5612 { 10, 7, 0.9600703624 },
5613 { 43, 29, 0.9774461806 },
5614 { 77, 50, 0.9948777272 },
5615 { 8, 5, 1.012197011 },
5616 { 163, 98, 1.029475114 },
5617 { 168, 97, 1.047174539 },
5618 { 175, 97, 1.064668696 },
5619 { 126, 67, 1.082075603 },
5620 { 157, 80, 1.099534652 },
5621 { 203, 99, 1.117049384 },
5622 { 193, 90, 1.134452855 },
5623 { 146, 65, 1.151936673 },
5624 { 139, 59, 1.169382787 },
5625 { 99, 40, 1.186811703 },
5626 { 211, 81, 1.204257817 },
5627 { 272, 99, 1.221730164 },
5628 { 273, 94, 1.239188479 },
5629 { 277, 90, 1.25664606 },
5630 { 157, 48, 1.274088705 },
5631 { 279, 80, 1.291550147 },
5632 { 362, 97, 1.308990773 },
5633 { 373, 93, 1.326448578 },
5634 { 420, 97, 1.343823596 },
5635 { 207, 44, 1.361353157 },
5636 { 427, 83, 1.378810994 },
5637 { 414, 73, 1.396261926 },
5638 { 322, 51, 1.413716057 },
5639 { 185, 26, 1.431170275 },
5640 { 790, 97, 1.448623034 },
5641 { 333, 35, 1.466075711 },
5642 { 1063, 93, 1.483530284 },
5643 { 1330, 93, 1.500985147 },
5644 { 706, 37, 1.518436297 },
5645 { 315, 11, 1.535889876 },
5646 { 3953, 69, 1.553343002 },
5647 };
5648#endif
5649
5650 // Optimized "good" angles list, it produces small tiles but
5651 // it has approximately 10 degrees steps
5652 static const QList<rationalTangent> rationalTangents {
5653 { 1, 10, qDegreesToRadians( 5.71059 ) },
5654 { 1, 5, qDegreesToRadians( 11.3099 ) },
5655 { 1, 4, qDegreesToRadians( 14.0362 ) },
5656 { 1, 4, qDegreesToRadians( 18.4349 ) },
5657 { 1, 2, qDegreesToRadians( 26.5651 ) },
5658 { 2, 3, qDegreesToRadians( 33.6901 ) },
5659 { 1, 1, qDegreesToRadians( 45.0 ) },
5660 { 3, 2, qDegreesToRadians( 56.3099 ) },
5661 { 2, 1, qDegreesToRadians( 63.4349 ) },
5662 { 3, 1, qDegreesToRadians( 71.5651 ) },
5663 { 4, 1, qDegreesToRadians( 75.9638 ) },
5664 { 10, 1, qDegreesToRadians( 84.2894 ) },
5665 };
5666
5667 const int quadrant { static_cast<int>( angleRad / M_PI_2 ) };
5668 Q_ASSERT( quadrant >= 0 && quadrant <= 3 );
5669
5670 QSize tileSize;
5671
5672 switch ( quadrant )
5673 {
5674 case 0:
5675 {
5676 break;
5677 }
5678 case 1:
5679 {
5680 angleRad -= M_PI / 2;
5681 break;
5682 }
5683 case 2:
5684 {
5685 angleRad -= M_PI;
5686 break;
5687 }
5688 case 3:
5689 {
5690 angleRad -= M_PI + M_PI_2;
5691 break;
5692 }
5693 }
5694
5695 if ( qgsDoubleNear( angleRad, 0, 10E-3 ) )
5696 {
5697 angleRad = 0;
5698 tileSize.setWidth( width );
5699 tileSize.setHeight( height );
5700 }
5701 else if ( qgsDoubleNear( angleRad, M_PI_2, 10E-3 ) )
5702 {
5703 angleRad = M_PI_2;
5704 tileSize.setWidth( height );
5705 tileSize.setHeight( width );
5706 }
5707 else
5708 {
5709 int rTanIdx = 0;
5710
5711 for ( int idx = 0; idx < rationalTangents.count(); ++idx )
5712 {
5713 const auto item = rationalTangents.at( idx );
5714 if ( qgsDoubleNear( item.angle, angleRad, 10E-3 ) || item.angle > angleRad )
5715 {
5716 rTanIdx = idx;
5717 break;
5718 }
5719 }
5720
5721 const rationalTangent bTan { rationalTangents.at( rTanIdx ) };
5722 angleRad = bTan.angle;
5723 const double k { bTan.q * height * width / std::cos( angleRad ) };
5724 const int hcfH { std::gcd( bTan.p * height, bTan.q * width ) };
5725 const int hcfW { std::gcd( bTan.q * height, bTan.p * width ) };
5726 const int W1 { static_cast<int>( std::round( k / hcfW ) ) };
5727 const int H1 { static_cast<int>( std::round( k / hcfH ) ) };
5728 tileSize.setWidth( W1 );
5729 tileSize.setHeight( H1 );
5730 }
5731
5732 switch ( quadrant )
5733 {
5734 case 0:
5735 {
5736 break;
5737 }
5738 case 1:
5739 {
5740 angleRad += M_PI / 2;
5741 const int h { tileSize.height() };
5742 tileSize.setHeight( tileSize.width() );
5743 tileSize.setWidth( h );
5744 break;
5745 }
5746 case 2:
5747 {
5748 angleRad += M_PI;
5749 break;
5750 }
5751 case 3:
5752 {
5753 angleRad += M_PI + M_PI_2;
5754 const int h { tileSize.height() };
5755 tileSize.setHeight( tileSize.width() );
5756 tileSize.setWidth( h );
5757 break;
5758 }
5759 }
5760
5761 return tileSize;
5762}
5763
5764template<typename Functor> void changeSymbolLayerIds( QgsSymbolLayer *sl, Functor &&generateId )
5765{
5766 sl->setId( generateId() );
5767
5768 // recurse over sub symbols
5769 QgsSymbol *subSymbol = sl->subSymbol();
5770 if ( subSymbol )
5771 changeSymbolLayerIds( subSymbol, generateId );
5772}
5773
5774template<typename Functor> void changeSymbolLayerIds( QgsSymbol *symbol, Functor &&generateId )
5775{
5776 if ( !symbol )
5777 return;
5778
5779 for ( int idx = 0; idx < symbol->symbolLayerCount(); idx++ )
5780 changeSymbolLayerIds( symbol->symbolLayer( idx ), generateId );
5781}
5782
5784{
5785 changeSymbolLayerIds( symbol, []() { return QString(); } );
5786}
5787
5789{
5790 changeSymbolLayerIds( symbolLayer, []() { return QString(); } );
5791}
5792
5794{
5795 changeSymbolLayerIds( symbolLayer, []() { return QUuid::createUuid().toString(); } );
5796}
5797
5799{
5800 if ( !symbol )
5801 return;
5802
5803 for ( int idx = 0; idx < symbol->symbolLayerCount(); idx++ )
5804 {
5805 if ( QgsMaskMarkerSymbolLayer *maskSl = dynamic_cast<QgsMaskMarkerSymbolLayer *>( symbol->symbolLayer( idx ) ) )
5806 {
5807 maskSl->clearMasks();
5808
5809 // recurse over sub symbols
5810 if ( QgsSymbol *subSymbol = maskSl->subSymbol() )
5811 {
5812 clearSymbolLayerMasks( subSymbol );
5813 }
5814 }
5815 }
5816}
5817
5818QVector<QgsGeometry> QgsSymbolLayerUtils::collectSymbolLayerClipGeometries( const QgsRenderContext &context, const QString &symbolLayerId, const QRectF &bounds )
5819{
5820 QVector<QgsGeometry> clipGeometries = context.symbolLayerClipGeometries( symbolLayerId );
5821 if ( clipGeometries.empty() )
5822 return {};
5823
5824 if ( bounds.isNull() )
5825 return clipGeometries;
5826
5827 const QgsRectangle boundsRect = QgsRectangle( bounds );
5828
5829 clipGeometries
5830 .erase( std::remove_if( clipGeometries.begin(), clipGeometries.end(), [&boundsRect]( const QgsGeometry &geometry ) { return !geometry.boundingBoxIntersects( boundsRect ); } ), clipGeometries.end() );
5831
5832 return clipGeometries;
5833}
5834
5836{
5837 changeSymbolLayerIds( symbol, []() { return QUuid::createUuid().toString(); } );
5838}
5839
5841
5842QList<QList<QgsSymbolLayerUtils::BlankSegments>> QgsSymbolLayerUtils::parseBlankSegments( const QString &strBlankSegments, const QgsRenderContext &renderContext, Qgis::RenderUnit unit, QString &error )
5843{
5844 QString currentNumber;
5845 QList<QList<BlankSegments>> blankSegments;
5846 constexpr char internalError[] = "Internal error while processing blank segments";
5847
5848 auto appendLevel = [&blankSegments, &internalError]( int level ) -> void {
5849 if ( level == 0 )
5850 {
5851 blankSegments.append( QList<BlankSegments>() );
5852 }
5853 else if ( level == 1 )
5854 {
5855 if ( blankSegments.isEmpty() )
5856 throw std::runtime_error( internalError ); // should not happen
5857 blankSegments.back().append( BlankSegments() );
5858 }
5859 else if ( level == 2 )
5860 {
5861 if ( blankSegments.isEmpty() || blankSegments.back().isEmpty() )
5862 throw std::runtime_error( internalError ); // should not happen
5863 blankSegments.back().back().append( QPair<double, double>( -1, -1 ) );
5864 }
5865 else
5866 throw std::runtime_error( internalError ); // should not happen
5867 };
5868
5869 auto addNumber = [&blankSegments, &internalError, &currentNumber]( const QChar &c ) -> void {
5870 if ( blankSegments.isEmpty() || blankSegments.back().isEmpty() || blankSegments.back().back().isEmpty() )
5871 {
5872 throw std::runtime_error( internalError ); // should not happen
5873 }
5874
5875 if ( ( c == ')' || c == ',' ) && blankSegments.back().back().back().first == -1 )
5876 {
5877 throw std::runtime_error( "Missing number" );
5878 }
5879
5880 if ( blankSegments.back().back().back().second != -1 )
5881 {
5882 throw std::runtime_error( "Too many number" );
5883 }
5884
5885 bool ok;
5886 const double number = currentNumber.toDouble( &ok );
5887 if ( !ok )
5888 {
5889 throw std::runtime_error( u"bad formatted number '%1'"_s.arg( currentNumber ).toStdString() );
5890 }
5891
5892 BlankSegments &segments = blankSegments.back().back();
5893 if ( segments.back().first == -1 )
5894 {
5895 if ( segments.count() > 1 && segments.at( segments.count() - 2 ).second > number )
5896 throw std::runtime_error( u"Wrong blank segments distances, start (%1) < previous end (%2)"_s.arg( number ).arg( segments.at( segments.count() - 2 ).second ).toStdString() );
5897
5898 blankSegments.back().back().back().first = number;
5899 }
5900 else if ( blankSegments.back().back().back().first > number )
5901 {
5902 throw std::runtime_error( u"Wrong blank segments distances, start (%1) > end (%2)"_s.arg( blankSegments.back().back().back().first ).arg( number ).toStdString() );
5903 }
5904 else
5905 {
5906 blankSegments.back().back().back().second = number;
5907 }
5908 currentNumber.clear();
5909 };
5910
5911 int level = -1;
5912 int iChar = 0;
5913 try
5914 {
5915 for ( const QChar &c : strBlankSegments )
5916 {
5917 if ( !currentNumber.isEmpty() && ( c.isSpace() || c == ')' || c == ',' ) )
5918 {
5919 if ( level < 2 )
5920 throw std::runtime_error( "Missing '('" );
5921 addNumber( c );
5922 }
5923
5924 if ( c == '(' )
5925 {
5926 if ( level >= 2 )
5927 throw std::runtime_error( "Extraneous '('" );
5928 appendLevel( ++level );
5929 }
5930 else if ( c == ')' )
5931 {
5932 if ( level < 0 )
5933 throw std::runtime_error( "Extraneous ')'" );
5934 if ( level == 2 && !blankSegments.isEmpty() && !blankSegments.back().isEmpty() && blankSegments.back().back().count() == 1 && blankSegments.back().back().back() == QPair<double, double>( -1, -1 ) )
5935 {
5936 blankSegments.back().back().pop_back();
5937 }
5938 level--;
5939 }
5940 else if ( c == ',' )
5941 {
5942 if ( ( level == 0 && blankSegments.count() == 0 )
5943 || ( level == 1 && !blankSegments.isEmpty() && blankSegments.back().count() == 0 )
5944 || ( level == 2 && !blankSegments.isEmpty() && !blankSegments.back().isEmpty() && blankSegments.back().back().count() == 0 ) )
5945 throw std::runtime_error( "No elements, Not expecting ','" );
5946
5947 appendLevel( level );
5948 }
5949 else if ( c.isNumber() || c == '.' )
5950 {
5951 currentNumber.append( c );
5952 }
5953 else if ( !c.isSpace() )
5954 {
5955 throw std::runtime_error( u"Invalid character '%1'"_s.arg( c ).toStdString() );
5956 }
5957 iChar++;
5958 }
5959 if ( level != -1 )
5960 {
5961 throw std::runtime_error( "Missing ')'" );
5962 }
5963 }
5964 catch ( const std::exception &e )
5965 {
5966 blankSegments.clear();
5967 error = u"%1 (column: %2)"_s.arg( e.what() ).arg( iChar );
5968 }
5969
5970 // convert in pixels
5971 std::for_each( blankSegments.begin(), blankSegments.end(), [&renderContext, &unit]( QList<BlankSegments> &rings ) {
5972 std::for_each( rings.begin(), rings.end(), [&renderContext, &unit]( BlankSegments &blankSegments ) {
5973 std::for_each( blankSegments.begin(), blankSegments.end(), [&renderContext, &unit]( QPair<double, double> &blankSegment ) {
5974 blankSegment.first = renderContext.convertToPainterUnits( blankSegment.first, unit );
5975 blankSegment.second = renderContext.convertToPainterUnits( blankSegment.second, unit );
5976 } );
5977 } );
5978 } );
5979
5980 return blankSegments;
5981}
5982
5983QgsSymbolLayerUtils::ExtraItems QgsSymbolLayerUtils::parseExtraItems( const QString &strExtraItems, QString &error )
5984{
5985 QString currentNumber;
5986 ExtraItems extraItems;
5987 int nbNumbers = 0;
5988
5989 auto addNumber = [&extraItems, &currentNumber, &nbNumbers]( const QChar &c ) -> QString {
5990 if ( c == ',' && nbNumbers == 0 )
5991 {
5992 return u"Missing number"_s;
5993 }
5994 else if ( nbNumbers >= 3 )
5995 {
5996 return u"Too many number"_s;
5997 }
5998 else
5999 {
6000 bool ok;
6001 const double number = currentNumber.toDouble( &ok );
6002 if ( !ok )
6003 {
6004 return u"bad formatted number '%1'"_s.arg( currentNumber );
6005 }
6006 else
6007 {
6008 switch ( nbNumbers )
6009 {
6010 case 0:
6011 extraItems.append( { { number, 0 }, 0 } );
6012 break;
6013
6014 case 1:
6015 extraItems.back().first.setY( number );
6016 break;
6017
6018 case 2:
6019 if ( number < 0 || number > 360 )
6020 {
6021 return u"Angle must be between 0° and 360°"_s;
6022 }
6023
6024 extraItems.back().second = number;
6025 break;
6026
6027 default:
6028 return u"Too many number"_s;
6029 }
6030
6031 nbNumbers++;
6032 currentNumber.clear();
6033 }
6034 }
6035
6036 return QString();
6037 };
6038
6039 int iChar = 0;
6040 for ( const QChar &c : strExtraItems )
6041 {
6042 if ( !currentNumber.isEmpty() && ( c.isSpace() || c == ',' ) )
6043 {
6044 error = addNumber( c );
6045 }
6046
6047 if ( !error.isEmpty() )
6048 {
6049 break;
6050 }
6051
6052 if ( c == ',' )
6053 {
6054 if ( extraItems.count() == 0 )
6055 {
6056 error = u"No elements, Not expecting ','"_s;
6057 }
6058 nbNumbers = 0;
6059 }
6060 else if ( c.isNumber() || c == '.' || c == '-' )
6061 {
6062 currentNumber.append( c );
6063 }
6064 else if ( !c.isSpace() )
6065 {
6066 error = u"Invalid character '%1'"_s.arg( c );
6067 }
6068
6069 if ( !error.isEmpty() )
6070 {
6071 break;
6072 }
6073
6074 iChar++;
6075 }
6076
6077 // This is the end but there may have still a number to add
6078 if ( error.isEmpty() && !currentNumber.isEmpty() )
6079 {
6080 error = addNumber( ' ' );
6081 }
6082
6083 if ( error.isEmpty() && nbNumbers != 0 && nbNumbers != 3 )
6084 {
6085 error = u"Missing number"_s;
6086 }
6087
6088 if ( !error.isEmpty() )
6089 {
6090 extraItems.clear();
6091 error += u" (column: %1)"_s.arg( iChar );
6092 }
6093
6094 return extraItems;
6095}
6096
@ PreferVector
Prefer vector-based rendering, when the result will still be visually near-identical to a raster-base...
Definition qgis.h:2918
MarkerClipMode
Marker clipping modes.
Definition qgis.h:3499
@ CompletelyWithin
Render complete markers wherever the completely fall within the polygon shape.
Definition qgis.h:3503
@ NoClipping
No clipping, render complete markers.
Definition qgis.h:3500
@ Shape
Clip to polygon shape.
Definition qgis.h:3501
@ CentroidWithin
Render complete markers wherever their centroid falls within the polygon shape.
Definition qgis.h:3502
LineClipMode
Line clipping modes.
Definition qgis.h:3513
@ NoClipping
Lines are not clipped, will extend to shape's bounding box.
Definition qgis.h:3516
@ ClipPainterOnly
Applying clipping on the painter only (i.e. line endpoints will coincide with polygon bounding box,...
Definition qgis.h:3514
@ ClipToIntersection
Clip lines to intersection with polygon shape (slower) (i.e. line endpoints will coincide with polygo...
Definition qgis.h:3515
ScaleMethod
Scale methods.
Definition qgis.h:685
@ ScaleDiameter
Calculate scale by the diameter.
Definition qgis.h:687
@ ScaleArea
Calculate scale by the area.
Definition qgis.h:686
QFlags< SymbolLayerUserFlag > SymbolLayerUserFlags
Symbol layer user flags.
Definition qgis.h:992
GeometryType
The geometry types are used to group Qgis::WkbType in a coarse way.
Definition qgis.h:399
@ Point
Points.
Definition qgis.h:400
@ Line
Lines.
Definition qgis.h:401
@ Polygon
Polygons.
Definition qgis.h:402
@ Unknown
Unknown types.
Definition qgis.h:403
@ Null
No geometry.
Definition qgis.h:404
JoinStyle
Join styles for buffers.
Definition qgis.h:2293
@ Bevel
Use beveled joins.
Definition qgis.h:2296
@ Round
Use rounded joins.
Definition qgis.h:2294
@ Miter
Use mitered joins.
Definition qgis.h:2295
RenderUnit
Rendering size units.
Definition qgis.h:5770
@ Percentage
Percentage of another measurement (e.g., canvas size, feature size).
Definition qgis.h:5774
@ Millimeters
Millimeters.
Definition qgis.h:5771
@ Points
Points (e.g., for font sizes).
Definition qgis.h:5775
@ Unknown
Mixed or unknown units.
Definition qgis.h:5777
@ MapUnits
Map units.
Definition qgis.h:5772
@ Pixels
Pixels.
Definition qgis.h:5773
@ Inches
Inches.
Definition qgis.h:5776
@ MetersInMapUnits
Meters value as Map units.
Definition qgis.h:5778
EndCapStyle
End cap styles for buffers.
Definition qgis.h:2280
@ Flat
Flat cap (in line with start/end of line).
Definition qgis.h:2282
@ Round
Round cap.
Definition qgis.h:2281
@ Square
Square cap (extends past start/end of line by buffer distance).
Definition qgis.h:2283
@ RenderSymbolPreview
The render is for a symbol preview only and map based properties may not be available,...
Definition qgis.h:2988
@ Antialiasing
Use antialiasing while drawing.
Definition qgis.h:2984
@ HighQualityImageTransforms
Enable high quality image transformations, which results in better appearance of scaled or rotated ra...
Definition qgis.h:2994
VertexMarkerType
Editing vertex markers, used for showing vertices during a edit operation.
Definition qgis.h:2003
@ NoMarker
No marker.
Definition qgis.h:2006
@ SemiTransparentCircle
Semi-transparent circle marker.
Definition qgis.h:2004
@ Cross
Cross marker.
Definition qgis.h:2005
QFlags< SymbolRenderHint > SymbolRenderHints
Symbol render hints.
Definition qgis.h:838
QFlags< SymbolFlag > SymbolFlags
Symbol flags.
Definition qgis.h:935
SymbolType
Symbol types.
Definition qgis.h:671
@ Marker
Marker symbol.
Definition qgis.h:672
@ Line
Line symbol.
Definition qgis.h:673
@ Fill
Fill symbol.
Definition qgis.h:674
@ Hybrid
Hybrid symbol.
Definition qgis.h:675
@ RendererShouldUseSymbolLevels
If present, indicates that a QgsFeatureRenderer using the symbol should use symbol levels for best re...
Definition qgis.h:930
@ LineString
LineString.
Definition qgis.h:317
@ MultiPoint
MultiPoint.
Definition qgis.h:320
@ Polygon
Polygon.
Definition qgis.h:318
@ MultiPolygon
MultiPolygon.
Definition qgis.h:322
@ MultiLineString
MultiLineString.
Definition qgis.h:321
SymbolCoordinateReference
Symbol coordinate reference modes.
Definition qgis.h:3457
@ Feature
Relative to feature/shape being rendered.
Definition qgis.h:3458
@ Viewport
Relative to the whole viewport/output device.
Definition qgis.h:3459
Abstract base class for all geometries.
vertex_iterator vertices_end() const
Returns STL-style iterator pointing to the imaginary vertex after the last vertex of the geometry.
Qgis::WkbType wkbType() const
Returns the WKB type of the geometry.
const_part_iterator const_parts_end() const
Returns STL-style iterator pointing to the imaginary const part after the last part of the geometry.
vertex_iterator vertices_begin() const
Returns STL-style iterator pointing to the first vertex of the geometry.
const_part_iterator const_parts_begin() const
Returns STL-style iterator pointing to the const first part of the geometry.
virtual bool writeXml(QDomElement &collectionElem, const QgsPropertiesDefinition &definitions) const
Writes the current state of the property collection into an XML element.
Animated marker symbol layer class.
static QgsPaintEffectRegistry * paintEffectRegistry()
Returns the application's paint effect registry, used for managing paint effects.
static QgsSymbolLayerRegistry * symbolLayerRegistry()
Returns the application's symbol layer registry, used for managing symbol layers.
static QStringList svgPaths()
Returns the paths to svg directories.
HeadType
Possible head types.
@ HeadSingle
One single head at the end.
@ HeadReversed
One single head at the beginning.
ArrowType
Possible arrow types.
@ ArrowRightHalf
Halved arrow, only the right side of the arrow is rendered (for straight arrows) or the side toward t...
@ ArrowLeftHalf
Halved arrow, only the left side of the arrow is rendered (for straight arrows) or the side toward th...
static QString typeString()
Returns the string identifier for QgsColorBrewerColorRamp.
static QgsColorRamp * create(const QVariantMap &properties=QVariantMap())
Returns a new QgsColorBrewerColorRamp color ramp created using the properties encoded in a string map...
Abstract base class for color ramps.
virtual QColor color(double value) const =0
Returns the color corresponding to a specified value.
virtual QVariantMap properties() const =0
Returns a string map containing all the color ramp's properties.
virtual QString type() const =0
Returns a string representing the color ramp type.
static QColor colorFromString(const QString &string)
Decodes a string into a color value.
static QString colorToString(const QColor &color)
Encodes a color into a string value.
static QString typeString()
Returns the string identifier for QgsCptCityColorRamp.
static std::unique_ptr< QgsColorRamp > create(const QVariantMap &properties=QVariantMap())
Creates the symbol layer.
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
virtual QgsLineString * curveToLine(double tolerance=M_PI_2/90, SegmentationToleranceType toleranceType=MaximumAngle) const =0
Returns a new line string geometry corresponding to a segmentized approximation of the curve.
static std::vector< std::unique_ptr< 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...
void appendScopes(const QList< QgsExpressionContextScope * > &scopes)
Appends a list of scopes to the end of the context.
bool hasFeature() const
Returns true if the context has a feature associated with it.
An expression node which takes its value from a feature's field.
Abstract base class for all nodes that can appear in an expression.
virtual QgsExpressionNode::NodeType nodeType() const =0
Gets the type of this node.
Handles parsing and evaluation of expressions (formerly called "search strings").
QString expression() const
Returns the original, unmodified expression string.
bool hasParserError() const
Returns true if an error occurred when parsing the input expression.
QString parserErrorString() const
Returns parser error.
static QString quotedColumnRef(QString name)
Returns a quoted column reference (in double quotes).
const QgsExpressionNode * rootNode() const
Returns the root node of the expression.
Abstract base class for all 2D vector feature renderers.
virtual bool accept(QgsStyleEntityVisitorInterface *visitor) const
Accepts the specified symbology visitor, causing it to visit all symbols associated with the renderer...
Abstract base class for fill symbol layers.
static void pointOnLineWithDistance(double x1, double y1, double x2, double y2, double distance, double &x, double &y, double *z1=nullptr, double *z2=nullptr, double *z=nullptr, double *m1=nullptr, double *m2=nullptr, double *m=nullptr)
Calculates the point a specified distance from (x1, y1) toward a second point (x2,...
static double normalizedAngle(double angle)
Ensures that an angle is in the range 0 <= angle < 2 pi.
A geometry is the spatial representation of a feature.
QgsMultiPolygonXY asMultiPolygon() const
Returns the contents of the geometry as a multi-polygon.
QgsGeometry offsetCurve(double distance, int segments, Qgis::JoinStyle joinStyle, double miterLimit) const
Returns an offset line at a given distance and side from an input line.
QgsGeometry pointOnSurface() const
Returns a point guaranteed to lie on the surface of a geometry.
static QgsGeometry fromPolylineXY(const QgsPolylineXY &polyline)
Creates a new LineString geometry from a list of QgsPointXY points.
QgsPolygonXY asPolygon() const
Returns the contents of the geometry as a polygon.
const QgsAbstractGeometry * constGet() const
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
QgsPolylineXY asPolyline() const
Returns the contents of the geometry as a polyline.
QgsPointXY asPoint() const
Returns the contents of the geometry as a 2-dimensional point.
Qgis::GeometryOperationResult addRing(const QVector< QgsPointXY > &ring)
Adds a new ring to this geometry.
QgsMultiPolylineXY asMultiPolyline() const
Returns the contents of the geometry as a multi-linestring.
static QgsGeometry fromPolygonXY(const QgsPolygonXY &polygon)
Creates a new geometry from a QgsPolygonXY.
QgsGeometry buffer(double distance, int segments, QgsFeedback *feedback=nullptr) const
Returns a buffer region around this geometry having the given width and with a specified number of se...
Qgis::WkbType wkbType() const
Returns type of the geometry as a WKB type (point / linestring / polygon etc.).
static std::unique_ptr< QgsColorRamp > create(const QVariantMap &properties=QVariantMap())
Creates a new QgsColorRamp from a map of properties.
static QString typeString()
Returns the string identifier for QgsGradientColorRamp.
Represents a patch shape for use in map legends.
static QString typeString()
Returns the string identifier for QgsLimitedRandomColorRamp.
static QgsColorRamp * create(const QVariantMap &properties=QVariantMap())
Returns a new QgsLimitedRandomColorRamp color ramp created using the properties encoded in a string m...
Line string geometry type, with support for z-dimension and m-values.
Abstract base class for line symbol layers.
const QgsMapUnitScale & widthMapUnitScale() const
@ AllRings
Render both exterior and interior rings.
RenderRingFilter ringFilter() const
Returns the line symbol layer's ring filter, which controls which rings are rendered when the line sy...
virtual double width() const
Returns the estimated width for the line symbol layer.
double offset() const
Returns the line's offset.
Qgis::RenderUnit widthUnit() const
Returns the units for the line's width.
A line symbol type, for rendering LineString and MultiLineString geometries.
QgsLineSymbol * clone() const override
Returns a deep copy of this symbol.
double width() const
Returns the estimated width for the whole symbol, which is the maximum width of all marker symbol lay...
Base class for all map layer types.
Definition qgsmaplayer.h:83
Struct for storing maximum and minimum scales for measurements in map units.
bool minSizeMMEnabled
Whether the minimum size in mm should be respected.
double maxScale
The maximum scale, or 0.0 if unset.
double minScale
The minimum scale, or 0.0 if unset.
double maxSizeMM
The maximum size in millimeters, or 0.0 if unset.
bool maxSizeMMEnabled
Whether the maximum size in mm should be respected.
double minSizeMM
The minimum size in millimeters, or 0.0 if unset.
A marker symbol type, for rendering Point and MultiPoint geometries.
double size() const
Returns the estimated size for the whole symbol, which is the maximum size of all marker symbol layer...
QgsMarkerSymbol * clone() const override
Returns a deep copy of this symbol.
Special symbol layer that uses its sub symbol as a selective mask.
static QDomElement elseFilterExpression(QDomDocument &doc)
Creates an ElseFilter from doc.
static QDomElement expressionToOgcExpression(const QgsExpression &exp, QDomDocument &doc, QString *errorMessage=nullptr, bool requiresFilterElement=false)
Creates an OGC expression XML element from the exp expression with default values for the geometry na...
static QDomElement expressionToOgcFilter(const QgsExpression &exp, QDomDocument &doc, QString *errorMessage=nullptr)
Creates OGC filter XML element.
static QgsExpression * expressionFromOgcFilter(const QDomElement &element, QgsVectorLayer *layer=nullptr)
Parse XML with OGC filter into QGIS expression.
static bool isDefaultStack(QgsPaintEffect *effect)
Tests whether a paint effect matches the default effects stack.
virtual bool saveProperties(QDomDocument &doc, QDomElement &element) const
Saves the current state of the effect to a DOM element.
Resolves relative paths into absolute paths and vice versa.
QString writePath(const QString &filename) const
Prepare a filename to save it to the project file.
QString readPath(const QString &filename) const
Turn filename read from the project file to an absolute path.
Represents a 2D point.
Definition qgspointxy.h:62
double y
Definition qgspointxy.h:66
double x
Definition qgspointxy.h:65
void setX(double x)
Sets the x value of the point.
Definition qgspointxy.h:122
static QString typeString()
Returns the string identifier for QgsPresetSchemeColorRamp.
static QgsColorRamp * create(const QVariantMap &properties=QVariantMap())
Returns a new QgsPresetSchemeColorRamp color ramp created using the properties encoded in a string ma...
A grouped map of multiple QgsProperty objects, each referenced by an integer key value.
QSet< int > propertyKeys() const final
Returns a list of property keys contained within the collection.
QgsProperty property(int key) const final
Returns a matching property from the collection, if one exists.
A store for object properties.
bool isProjectColor() const
Returns true if the property is set to a linked project color.
bool isActive() const
Returns whether the property is currently active.
void setActive(bool active)
Sets whether the property is currently active.
A container for the context for various read/write operations on objects.
const QgsPathResolver & pathResolver() const
Returns path resolver for conversion between relative and absolute paths.
A rectangle specified with double values.
Contains information about the context of a rendering operation.
double scaleFactor() const
Returns the scaling factor for the render to convert painter units to physical sizes.
void setDevicePixelRatio(float ratio)
Sets the device pixel ratio.
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 setRasterizedRenderingPolicy(Qgis::RasterizedRenderingPolicy policy)
Sets the policy controlling when rasterisation of content during renders is permitted.
void setFlag(Qgis::RenderContextFlag flag, bool on=true)
Enable or disable a particular flag (other flags are not affected).
void setPainter(QPainter *p)
Sets the destination QPainter for the render operation.
QVector< QgsGeometry > symbolLayerClipGeometries(const QString &symbolLayerId) const
Returns clipping geometries to be applied to the symbolLayer before rendering.
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.
A fill symbol layer which fills polygons with a repeated SVG file.
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.
int numPoints() const override
Returns the number of points in the curve.
const double * yData() const
Returns a const pointer to the y vertex data.
const double * xData() const
Returns a const pointer to the x vertex data.
Renders polygons using a single fill and stroke color.
void setStrokeWidthMapUnitScale(const QgsMapUnitScale &scale)
void setStrokeWidthUnit(Qgis::RenderUnit unit)
Sets the units for the width of the fill's stroke.
void setPenJoinStyle(Qt::PenJoinStyle style)
void setStrokeWidth(double strokeWidth)
void setStrokeStyle(Qt::PenStyle strokeStyle)
void setStrokeColor(const QColor &strokeColor) override
Sets the stroke color for the symbol layer.
A simple line symbol layer, which renders lines using a line in a variety of styles (e....
bool tweakDashPatternOnCorners() const
Returns true if dash patterns tweaks should be applied on sharp corners, to ensure that a double-leng...
Qt::PenJoinStyle penJoinStyle() const
Returns the pen join style used to render the line (e.g.
double trimDistanceStart() const
Returns the trim distance for the start of the line, which dictates a length from the start of the li...
double trimDistanceEnd() const
Returns the trim distance for the end of the line, which dictates a length from the end of the line a...
bool useCustomDashPattern() const
Returns true if the line uses a custom dash pattern.
Qt::PenStyle penStyle() const
Returns the pen style used to render the line (e.g.
double dashPatternOffset() const
Returns the dash pattern offset, which dictates how far along the dash pattern the pattern should sta...
bool drawInsidePolygon() const
Returns true if the line should only be drawn inside polygons, and any portion of the line which fall...
bool alignDashPattern() const
Returns true if dash patterns should be aligned to the start and end of lines, by applying subtle twe...
Holds SLD export options and other information related to SLD export of a QGIS layer style.
void pushWarning(const QString &warning)
Pushes a warning message generated during the conversion.
void pushError(const QString &error)
Pushes a error message generated during the conversion.
virtual QgsStyle::StyleEntity type() const =0
Returns the type of style entity.
An interface for classes which can visit style entity (e.g.
@ SymbolRule
Rule based symbology or label child rule.
A symbol entity for QgsStyle databases.
Definition qgsstyle.h:1462
@ SymbolEntity
Symbols.
Definition qgsstyle.h:207
bool isAnimated() const
Returns true if the symbol is animated.
Definition qgssymbol.h:63
double frameRate() const
Returns the symbol animation frame rate (in frames per second).
Definition qgssymbol.h:77
Contains settings relating to symbol buffers, which draw a "halo" effect around the symbol.
Definition qgssymbol.h:94
We may need stable references to symbol layers, when pointers to symbol layers are not usable (when a...
std::unique_ptr< QgsSymbolLayer > createSymbolLayerFromSld(const QString &name, QDomElement &element) const
create a new instance of symbol layer given symbol layer name and SLD
void resolvePaths(const QString &name, QVariantMap &properties, const QgsPathResolver &pathResolver, bool saving) const
Resolve paths in properties of a particular symbol layer.
void resolveFonts(const QString &name, QVariantMap &properties, const QgsReadWriteContext &context) const
Resolve fonts from the properties of a particular symbol layer.
std::unique_ptr< QgsSymbolLayer > createSymbolLayer(const QString &name, const QVariantMap &properties=QVariantMap()) const
create a new instance of symbol layer given symbol layer name and properties
static bool externalMarkerFromSld(QDomElement &element, QString &path, QString &format, int &markIndex, QColor &color, double &size)
static QColor parseColor(const QString &colorStr, bool strictEval=false)
Attempts to parse a string as a color using a variety of common formats, including hex codes,...
static bool rotationFromSldElement(QDomElement &element, QString &rotationFunc)
static void createAnchorPointElement(QDomDocument &doc, QDomElement &element, QPointF anchor)
Creates a SE 1.1 anchor point element as a child of the specified element.
static void sortVariantList(QList< QVariant > &list, Qt::SortOrder order)
Sorts the passed list in requested order.
static Qgis::MarkerClipMode decodeMarkerClipMode(const QString &string, bool *ok=nullptr)
Decodes a string representing a marker clip mode.
static QPicture symbolLayerPreviewPicture(const QgsSymbolLayer *layer, Qgis::RenderUnit units, QSize size, const QgsMapUnitScale &scale=QgsMapUnitScale(), Qgis::SymbolType parentSymbolType=Qgis::SymbolType::Hybrid)
Draws a symbol layer preview to a QPicture.
static bool hasExternalGraphic(QDomElement &element)
Checks if element contains an ExternalGraphic element with format "image/svg+xml".
static QString encodePenStyle(Qt::PenStyle style)
static bool needMarkerLine(QDomElement &element)
static QVector< qreal > decodeSldRealVector(const QString &s)
static bool needLinePatternFill(QDomElement &element)
static std::unique_ptr< QgsSymbol > symbolFromMimeData(const QMimeData *data)
Attempts to parse mime data as a symbol.
static void clearSymbolLayerIds(QgsSymbol *symbol)
Remove recursively unique id from all symbol symbol layers and set an empty string instead.
static QString encodeSldBrushStyle(Qt::BrushStyle style)
static Qt::PenJoinStyle decodePenJoinStyle(const QString &str)
static QgsArrowSymbolLayer::HeadType decodeArrowHeadType(const QVariant &value, bool *ok=nullptr)
Decodes a value representing an arrow head type.
static QString encodeMapUnitScale(const QgsMapUnitScale &mapUnitScale)
static Q_DECL_DEPRECATED QSet< const QgsSymbolLayer * > toSymbolLayerPointers(const QgsFeatureRenderer *renderer, const QSet< QgsSymbolLayerId > &symbolLayerIds)
Converts a set of symbol layer id to a set of pointers to actual symbol layers carried by the feature...
static QVariant colorRampToVariant(const QString &name, QgsColorRamp *ramp)
Saves a color ramp to a QVariantMap, wrapped in a QVariant.
static void applyScaleDependency(QDomDocument &doc, QDomElement &ruleElem, QVariantMap &props)
Checks if the properties contain scaleMinDenom and scaleMaxDenom, if available, they are added into t...
static QgsStringMap evaluatePropertiesMap(const QMap< QString, QgsProperty > &propertiesMap, const QgsExpressionContext &context)
Evaluates a map of properties using the given context and returns a variant map with evaluated expres...
static void drawVertexMarker(double x, double y, QPainter &p, Qgis::VertexMarkerType type, int markerSize)
Draws a vertex symbol at (painter) coordinates x, y.
static Q_DECL_DEPRECATED bool createExpressionElement(QDomDocument &doc, QDomElement &element, const QString &function)
Creates a OGC Expression element based on the provided function expression.
static bool displacementFromSldElement(QDomElement &element, QPointF &offset)
static bool hasWellKnownMark(QDomElement &element)
static std::unique_ptr< QgsColorRamp > loadColorRamp(QDomElement &element)
Creates a color ramp from the settings encoded in an XML element.
static QString getSvgParametricPath(const QString &basePath, const QColor &fillColor, const QColor &strokeColor, double strokeWidth)
Encodes a reference to a parametric SVG into a path with parameters according to the SVG Parameters s...
static Q_DECL_DEPRECATED bool createFunctionElement(QDomDocument &doc, QDomElement &element, const QString &function)
Creates an OGC function element.
static std::unique_ptr< QgsSymbol > restrictedSizeSymbol(const QgsSymbol *s, double minSize, double maxSize, QgsRenderContext *context, double &width, double &height, bool *ok=nullptr)
Creates a new symbol with size restricted to min/max size if original size is out of min/max range.
static QColor decodeColor(const QString &str)
Decodes a string to a color.
static std::unique_ptr< QgsSymbolLayer > loadSymbolLayer(QDomElement &element, const QgsReadWriteContext &context)
Reads and returns symbol layer from XML. Caller is responsible for deleting the returned object.
static bool onlineResourceFromSldElement(QDomElement &element, QString &path, QString &format)
static QPointF polygonCentroid(const QPolygonF &points)
Calculate the centroid point of a QPolygonF.
static QIcon colorRampPreviewIcon(QgsColorRamp *ramp, QSize size, int padding=0)
Returns an icon preview for a color ramp.
static QString encodeBrushStyle(Qt::BrushStyle style)
static QString svgSymbolPathToName(const QString &path, const QgsPathResolver &pathResolver)
Determines an SVG symbol's name from its path.
static bool needRasterMarker(const QDomElement &element)
Checks if element contains an ExternalGraphic element that should translate to a raster marker.
static QPixmap colorRampPreviewPixmap(QgsColorRamp *ramp, QSize size, int padding=0, Qt::Orientation direction=Qt::Horizontal, bool flipDirection=false, bool drawTransparentBackground=true)
Returns a pixmap preview for a color ramp.
static QString encodeSldAlpha(int alpha)
static void externalGraphicToSld(QDomDocument &doc, QDomElement &element, const QString &path, const QString &mime, const QColor &color, double size=-1)
static QPointF polygonPointOnSurface(const QPolygonF &points, const QVector< QPolygonF > *rings=nullptr)
Calculate a point on the surface of a QPolygonF.
static void blurImageInPlace(QImage &image, QRect rect, int radius, bool alphaOnly)
Blurs an image in place, e.g. creating Qt-independent drop shadows.
static QList< double > prettyBreaks(double minimum, double maximum, int classes)
Computes a sequence of about 'classes' equally spaced round values which cover the range of values fr...
static QPointF toPoint(const QVariant &value, bool *ok=nullptr)
Converts a value to a point.
static void premultiplyColor(QColor &rgb, int alpha)
Converts a QColor into a premultiplied ARGB QColor value using a specified alpha value.
static void saveProperties(QVariantMap props, QDomDocument &doc, QDomElement &element)
Saves the map of properties to XML.
static void multiplyImageOpacity(QImage *image, qreal opacity)
Multiplies opacity of image pixel values with a (global) transparency value.
static bool functionFromSldElement(QDomElement &element, QString &function)
static bool saveColorsToGpl(QFile &file, const QString &paletteName, const QgsNamedColorList &colors)
Exports colors to a gpl GIMP palette file.
static std::unique_ptr< QMimeData > colorListToMimeData(const QgsNamedColorList &colorList, bool allFormats=true)
Creates mime data from a list of named colors.
static std::unique_ptr< QgsSymbolLayer > createFillLayerFromSld(QDomElement &element)
Creates a new fill layer from a SLD DOM element.
static QColor parseColorWithAlpha(const QString &colorStr, bool &containsAlpha, bool strictEval=false)
Attempts to parse a string as a color using a variety of common formats, including hex codes,...
static bool hasSldSymbolizer(const QDomElement &element)
Returns true if a DOM element contains an SLD Symbolizer element.
static QgsMapUnitScale decodeMapUnitScale(const QString &str)
static QSizeF toSize(const QVariant &value, bool *ok=nullptr)
Converts a value to a size.
static double rescaleUom(double size, Qgis::RenderUnit unit, const QVariantMap &props)
Rescales the given size based on the uomScale found in the props, if any is found,...
static bool needEllipseMarker(QDomElement &element)
static std::unique_ptr< QgsSymbolLayer > createMarkerLayerFromSld(QDomElement &element)
Creates a new marker layer from a SLD DOM element.
static QgsNamedColorList colorListFromMimeData(const QMimeData *data)
Attempts to parse mime data as a list of named colors.
static void clearSymbolLayerMasks(QgsSymbol *symbol)
Remove recursively masks from all symbol symbol layers.
static bool isSharpCorner(QPointF p1, QPointF p2, QPointF p3)
Returns true if the angle formed by the line p1 - p2 - p3 forms a "sharp" corner.
static QString ogrFeatureStylePen(double width, double mmScaleFactor, double mapUnitsScaleFactor, const QColor &c, Qt::PenJoinStyle joinStyle=Qt::MiterJoin, Qt::PenCapStyle capStyle=Qt::FlatCap, double offset=0.0, const QVector< qreal > *dashPattern=nullptr)
Create ogr feature style string for pen.
static Qt::PenCapStyle decodePenCapStyle(const QString &str)
static double rendererFrameRate(const QgsFeatureRenderer *renderer)
Calculates the frame rate (in frames per second) at which the given renderer must be redrawn.
static QgsStringMap getSvgParameterList(QDomElement &element)
static bool needSvgFill(QDomElement &element)
static bool createSymbolLayerListFromSld(QDomElement &element, Qgis::GeometryType geomType, QList< QgsSymbolLayer * > &layers)
Creates a symbol layer list from a DOM element.
static bool externalGraphicFromSld(QDomElement &element, QString &path, QString &mime, QColor &color, double &size)
static Q_DECL_DEPRECATED void parametricSvgToSld(QDomDocument &doc, QDomElement &graphicElem, const QString &path, const QColor &fillColor, double size, const QColor &strokeColor, double strokeWidth)
Encodes a reference to a parametric SVG into SLD, as a succession of parametric SVG using URL paramet...
static QIcon symbolLayerPreviewIcon(const QgsSymbolLayer *layer, Qgis::RenderUnit u, QSize size, const QgsMapUnitScale &scale=QgsMapUnitScale(), Qgis::SymbolType parentSymbolType=Qgis::SymbolType::Hybrid, QgsMapLayer *mapLayer=nullptr, const QgsScreenProperties &screen=QgsScreenProperties())
Draws a symbol layer preview to an icon.
static QString encodeSldLineCapStyle(Qt::PenCapStyle style)
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 QVector< qreal > decodeRealVector(const QString &s)
static bool lineFromSld(QDomElement &element, Qt::PenStyle &penStyle, QColor &color, double &width, Qt::PenJoinStyle *penJoinStyle=nullptr, Qt::PenCapStyle *penCapStyle=nullptr, QVector< qreal > *customDashPattern=nullptr, double *dashOffset=nullptr)
static QPainter::CompositionMode decodeBlendMode(const QString &s)
static Qgis::ScaleMethod decodeScaleMethod(const QString &str)
Decodes a symbol scale method from a string.
static Q_DECL_DEPRECATED void createOpacityElement(QDomDocument &doc, QDomElement &element, const QString &alphaFunc)
Creates SLD opacity element.
static std::unique_ptr< QgsSymbolLayer > createLineLayerFromSld(QDomElement &element)
Creates a new line layer from a SLD DOM element.
static QString ogrFeatureStyleBrush(const QColor &fillColr)
Create ogr feature style string for brush.
static bool pointInPolygon(const QPolygonF &points, QPointF point)
Calculate whether a point is within of a QPolygonF.
static QStringList listSvgFiles()
Returns a list of all available svg files.
static QString encodeLineClipMode(Qgis::LineClipMode mode)
Encodes a line clip mode to a string.
static QPixmap symbolPreviewPixmap(const QgsSymbol *symbol, QSize size, int padding=0, QgsRenderContext *customContext=nullptr, bool selected=false, const QgsExpressionContext *expressionContext=nullptr, const QgsLegendPatchShape *shape=nullptr, const QgsScreenProperties &screen=QgsScreenProperties())
Returns a pixmap preview for a color ramp.
static bool convertPolygonSymbolizerToPointMarker(QDomElement &element, QList< QgsSymbolLayer * > &layerList)
Converts a polygon symbolizer element to a list of marker symbol layers.
static Qgis::LineClipMode decodeLineClipMode(const QString &string, bool *ok=nullptr)
Decodes a string representing a line clip mode.
static bool needSvgMarker(const QDomElement &element)
Checks if element contains an ExternalGraphic element that should translate to an SVG marker.
static QStringList listSvgFilesAt(const QString &directory)
Returns a list of svg files at the specified directory.
static bool needFontMarker(QDomElement &element)
static QString encodePenCapStyle(Qt::PenCapStyle style)
static QPointF pointOnLineWithDistance(QPointF startPoint, QPointF directionPoint, double distance)
Returns a point on the line from startPoint to directionPoint that is a certain distance away from th...
static QFont::Style decodeSldFontStyle(const QString &str)
static QSize tileSize(int width, int height, double &angleRad)
Calculate the minimum size in pixels of a symbol tile given the symbol width and height and the symbo...
static QString fieldOrExpressionFromExpression(QgsExpression *expression)
Returns a field name if the whole expression is just a name of the field .
static bool opacityFromSldElement(QDomElement &element, QString &alphaFunc)
static QString encodeSldFontWeight(int weight)
static Q_DECL_DEPRECATED void externalMarkerToSld(QDomDocument &doc, QDomElement &element, const QString &path, const QString &format, int *markIndex=nullptr, const QColor &color=QColor(), double size=-1)
Exports a marker to an SLD definition.
static Qt::BrushStyle decodeBrushStyle(const QString &str)
static QVector< QgsGeometry > collectSymbolLayerClipGeometries(const QgsRenderContext &context, const QString &symbolLayerId, const QRectF &bounds)
Returns a list of the symbol layer clip geometries to be used for the symbol layer with the specified...
static Qt::PenCapStyle decodeSldLineCapStyle(const QString &str)
static QgsNamedColorList importColorsFromGpl(QFile &file, bool &ok, QString &name)
Imports colors from a gpl GIMP palette file.
static QString encodeSize(QSizeF size)
Encodes a QSizeF to a string.
static QDomElement createVendorOptionElement(QDomDocument &doc, const QString &name, const QString &value)
static double sizeInPixelsFromSldUom(const QString &uom, double size)
Returns the size scaled in pixels according to the uom attribute.
static void appendPolyline(QPolygonF &target, const QPolygonF &line)
Appends a polyline line to an existing target polyline.
static bool wellKnownMarkerFromSld(QDomElement &element, QString &name, QColor &color, QColor &strokeColor, Qt::PenStyle &strokeStyle, double &strokeWidth, double &size)
Extracts properties from an SLD marker definition.
static void mergeScaleDependencies(double mScaleMinDenom, double mScaleMaxDenom, QVariantMap &props)
Merges the local scale limits, if any, with the ones already in the map, if any.
static std::unique_ptr< QMimeData > symbolToMimeData(const QgsSymbol *symbol)
Creates new mime data from a symbol.
static QString colorToName(const QColor &color)
Returns a friendly display name for a color.
static int decodeSldAlpha(const QString &str)
static QString encodeSldLineJoinStyle(Qt::PenJoinStyle style)
static void createDisplacementElement(QDomDocument &doc, QDomElement &element, QPointF offset)
static QString svgSymbolNameToPath(const QString &name, const QgsPathResolver &pathResolver)
Determines an SVG symbol's path from its name.
static void drawStippledBackground(QPainter *painter, QRect rect)
static QList< QColor > parseColorList(const QString &colorStr)
Attempts to parse a string as a list of colors using a variety of common formats, including hex codes...
static QString encodeColor(const QColor &color)
Lossy-encodes a color to a string.
static Qt::PenJoinStyle decodeSldLineJoinStyle(const QString &str)
static QVariantMap parseProperties(const QDomElement &element)
Parses the properties from XML and returns a map.
static bool fillFromSld(QDomElement &element, Qt::BrushStyle &brushStyle, QColor &color)
static Qgis::EndCapStyle penCapStyleToEndCapStyle(Qt::PenCapStyle style)
Converts a Qt pen cap style to a QGIS end cap style.
static QString encodeSldFontStyle(QFont::Style style)
static QColor colorFromMimeData(const QMimeData *data, bool &hasAlpha)
Attempts to parse mime data as a color.
static int decodeSldFontWeight(const QString &str)
static QDomElement saveSymbol(const QString &symbolName, const QgsSymbol *symbol, QDomDocument &doc, const QgsReadWriteContext &context)
Writes a symbol definition to XML.
static Q_DECL_DEPRECATED void fillToSld(QDomDocument &doc, QDomElement &element, Qt::BrushStyle brushStyle, const QColor &color=QColor())
Exports fill details to an SLD element.
static double polylineLength(const QPolygonF &polyline)
Returns the total length of a polyline.
static QDomElement saveColorRamp(const QString &name, const QgsColorRamp *ramp, QDomDocument &doc)
Encodes a color ramp's settings to an XML element.
static Qgis::RenderUnit decodeSldUom(const QString &str, double *scaleFactor=nullptr)
Decodes a SLD unit of measure string to a render unit.
static Q_DECL_DEPRECATED void createGeometryElement(QDomDocument &doc, QDomElement &element, const QString &geomFunc)
Creates an SLD geometry element.
static QgsArrowSymbolLayer::ArrowType decodeArrowType(const QVariant &value, bool *ok=nullptr)
Decodes a value representing an arrow type.
static void clearSymbolMap(QgsSymbolMap &symbols)
static Qt::BrushStyle decodeSldBrushStyle(const QString &str)
static void resetSymbolLayerIds(QgsSymbol *symbol)
Regenerate recursively unique id from all symbol symbol layers.
static double estimateMaxSymbolBleed(QgsSymbol *symbol, const QgsRenderContext &context)
Returns the maximum estimated bleed for the symbol.
static Q_DECL_DEPRECATED void wellKnownMarkerToSld(QDomDocument &doc, QDomElement &element, const QString &name, const QColor &color, const QColor &strokeColor, Qt::PenStyle strokeStyle, double strokeWidth=-1, double size=-1)
Exports a marker to SLD.
static QString symbolProperties(QgsSymbol *symbol)
Returns a string representing the symbol.
static bool geometryFromSldElement(QDomElement &element, QString &geomFunc)
static QString encodeScaleMethod(Qgis::ScaleMethod scaleMethod)
Encodes a symbol scale method to a string.
static void createOnlineResourceElement(QDomDocument &doc, QDomElement &element, const QString &path, const QString &format)
static Qt::PenStyle decodePenStyle(const QString &str)
static Q_DECL_DEPRECATED void createRotationElement(QDomDocument &doc, QDomElement &element, const QString &rotationFunc)
Creates SLD rotation element.
static Qgis::SymbolCoordinateReference decodeCoordinateReference(const QString &string, bool *ok=nullptr)
Decodes a string representing a symbol coordinate reference mode.
static QgsSymbolMap loadSymbols(QDomElement &element, const QgsReadWriteContext &context)
Reads a collection of symbols from XML and returns them in a map. Caller is responsible for deleting ...
static QString encodePoint(QPointF point)
Encodes a QPointF to a string.
static Q_DECL_DEPRECATED void labelTextToSld(QDomDocument &doc, QDomElement &element, const QString &label, const QFont &font, const QColor &color=QColor(), double size=-1)
Exports label text to SLD.
static QDomElement saveSymbols(QgsSymbolMap &symbols, const QString &tagName, QDomDocument &doc, const QgsReadWriteContext &context)
Writes a collection of symbols to XML with specified tagName for the top-level element.
static std::unique_ptr< QgsExpression > fieldOrExpressionToExpression(const QString &fieldOrExpression)
Returns a new valid expression instance for given field or expression string.
static QString encodePenJoinStyle(Qt::PenJoinStyle style)
static QgsStringMap getVendorOptionList(QDomElement &element)
static Qgis::JoinStyle penJoinStyleToJoinStyle(Qt::PenJoinStyle style)
Converts a Qt pen joinstyle to a QGIS join style.
static QIcon symbolPreviewIcon(const QgsSymbol *symbol, QSize size, int padding=0, QgsLegendPatchShape *shape=nullptr, const QgsScreenProperties &screen=QgsScreenProperties())
Returns an icon preview for a color ramp.
static bool condenseFillAndOutline(QgsFillSymbolLayer *fill, QgsLineSymbolLayer *outline)
Attempts to condense a fill and outline layer, by moving the outline layer to the fill symbol's strok...
static QPointF decodePoint(const QString &string)
Decodes a QSizeF from a string.
static QPolygonF polylineSubstring(const QPolygonF &polyline, double startOffset, double endOffset)
Returns the substring of a polyline which starts at startOffset from the beginning of the line and en...
static bool needPointPatternFill(QDomElement &element)
static QString encodeSldRealVector(const QVector< qreal > &v)
static QString encodeCoordinateReference(Qgis::SymbolCoordinateReference coordinateReference)
Encodes a symbol coordinate reference mode to a string.
static bool needRasterImageFill(QDomElement &element)
Checks if element contains a graphic fill with a raster image of type PNG, JPEG or GIF.
static QDomElement createSvgParameterElement(QDomDocument &doc, const QString &name, const QString &value)
static QString encodeMarkerClipMode(Qgis::MarkerClipMode mode)
Encodes a marker clip mode to a string.
static void lineToSld(QDomDocument &doc, QDomElement &element, Qt::PenStyle penStyle, const QColor &color, QgsSldExportContext &context, double width=-1, const Qt::PenJoinStyle *penJoinStyle=nullptr, const Qt::PenCapStyle *penCapStyle=nullptr, const QVector< qreal > *customDashPattern=nullptr, double dashOffset=0.0)
static QSizeF decodeSize(const QString &string)
Decodes a QSizeF from a string.
static bool hasExternalGraphicV2(const QDomElement &element, const QString format=QString())
Checks if element contains an ExternalGraphic element, if the optional format is specified it will al...
static QString encodeRealVector(const QVector< qreal > &v)
static std::unique_ptr< QMimeData > colorToMimeData(const QColor &color)
Creates mime data from a color.
Abstract base class for symbol layers.
virtual QgsSymbolLayer * clone() const =0
Shall be reimplemented by subclasses to create a deep copy of the instance.
virtual bool setSubSymbol(QgsSymbol *symbol)
Sets layer's subsymbol. takes ownership of the passed symbol.
void setId(const QString &id)
Set symbol layer identifier This id has to be unique in the whole project.
bool isLocked() const
Returns true if the symbol layer colors are locked and the layer will ignore any symbol-level color c...
Property
Data definable properties.
virtual double estimateMaxBleed(const QgsRenderContext &context) const
Returns the estimated maximum distance which the layer style will bleed outside the drawn shape when ...
QgsPaintEffect * paintEffect() const
Returns the current paint effect for the layer.
QString selectiveMaskingSourceSetId() const
Returns the selective masking source set ID for this symbol layer.
virtual QVariantMap properties() const =0
Should be reimplemented by subclasses to return a string map that contains the configuration informat...
bool enabled() const
Returns true if symbol layer is enabled and will be drawn.
virtual QString layerType() const =0
Returns a string that represents this layer type.
int renderingPass() const
Specifies the rendering pass in which this symbol layer should be rendered.
QString id() const
Returns symbol layer identifier This id is unique in the whole project.
virtual QgsSymbol * subSymbol()
Returns the symbol's sub symbol, if present.
Qgis::SymbolLayerUserFlags userFlags() const
Returns user-controlled flags which control the symbol layer's behavior.
virtual QColor color() const
Returns the "representative" color of the symbol layer.
static const QgsPropertiesDefinition & propertyDefinitions()
Returns the symbol layer property definitions.
QgsPropertyCollection & dataDefinedProperties()
Returns a reference to the symbol layer's property collection, used for data defined overrides.
virtual bool hasDataDefinedProperties() const
Returns true if the symbol layer (or any of its sub-symbols) contains data defined properties.
Encapsulates the context in which a symbol is being rendered.
void setOriginalGeometryType(Qgis::GeometryType type)
Sets the geometry type for the original feature geometry being rendered.
Abstract base class for all rendered symbols.
Definition qgssymbol.h:227
QgsSymbolLayer * symbolLayer(int layer)
Returns the symbol layer at the specified index.
QgsPropertyCollection & dataDefinedProperties()
Returns a reference to the symbol's property collection, used for data defined overrides.
Definition qgssymbol.h:832
QgsSymbolAnimationSettings & animationSettings()
Returns a reference to the symbol animation settings.
static const QgsPropertiesDefinition & propertyDefinitions()
Returns the symbol property definitions.
Qgis::SymbolFlags flags() const
Returns flags for the symbol.
qreal opacity() const
Returns the opacity for the symbol.
Definition qgssymbol.h:677
bool clipFeaturesToExtent() const
Returns whether features drawn by the symbol will be clipped to the render context's extent.
Definition qgssymbol.h:732
QgsSymbolBufferSettings * bufferSettings()
Returns the symbol buffer settings, which control an optional "halo" effect around the symbol.
bool hasDataDefinedProperties() const
Returns whether the symbol utilizes any data defined properties.
QgsSymbolLayerList symbolLayers() const
Returns the list of symbol layers contained in the symbol.
Definition qgssymbol.h:306
Qgis::RenderUnit extentBufferSizeUnit() const
Returns the units for the buffer size.
Definition qgssymbol.h:951
virtual QgsSymbol * clone() const =0
Returns a deep copy of this symbol.
int symbolLayerCount() const
Returns the total number of symbol layers contained in the symbol.
Definition qgssymbol.h:357
QColor color() const
Returns the symbol's color.
Qgis::SymbolType type() const
Returns the symbol's type.
Definition qgssymbol.h:296
double extentBuffer() const
Returns the symbol's extent buffer.
bool forceRHR() const
Returns true if polygon features drawn by the symbol will be reoriented to follow the standard right-...
Definition qgssymbol.h:754
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.
static bool isNull(const QVariant &variant, bool silenceNullWarnings=false)
Returns true if the specified variant should be considered a NULL value.
static Qgis::GeometryType geometryType(Qgis::WkbType type)
Returns the geometry type for a WKB type, e.g., both MultiPolygon and CurvePolygon would have a Polyg...
static Qgis::WkbType flatType(Qgis::WkbType type)
Returns the flat type for a WKB type.
static QDomElement writeVariant(const QVariant &value, QDomDocument &doc)
Write a QVariant to a QDomElement.
static QVariant readVariant(const QDomElement &element)
Read a QVariant from a QDomElement.
QList< QPair< QColor, QString > > QgsNamedColorList
List of colors paired with a friendly display name identifying the color.
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
bool qgsVariantLessThan(const QVariant &lhs, const QVariant &rhs)
Compares two QVariant values and returns whether the first is less than the second.
Definition qgis.cpp:596
bool qgsVariantGreaterThan(const QVariant &lhs, const QVariant &rhs)
Compares two QVariant values and returns whether the first is greater than the second.
Definition qgis.cpp:601
#define Q_NOWARN_DEPRECATED_POP
Definition qgis.h:8431
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
Definition qgis.h:7702
QString qgsFlagValueToKeys(const T &value, bool *returnOk=nullptr)
Returns the value for the given keys of a flag.
Definition qgis.h:8127
T qgsFlagKeysToValue(const QString &keys, const T &defaultValue, bool tryValueAsKey=true, bool *returnOk=nullptr)
Returns the value corresponding to the given keys of a flag.
Definition qgis.h:8156
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:8430
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:7795
T qgsgeometry_cast(QgsAbstractGeometry *geom)
constexpr QgsDataDefinedStringDecoder< V, N > makeDataDefinedStringDecoder(const DataDefinedDecoderEntry< V >(&values)[N])
Creates a QgsDataDefinedStringDecoder from a hardcoded lookup table.
QVector< QgsPolylineXY > QgsPolygonXY
Polygon: first item of the list is outer ring, inner rings (if any) start from second item.
Definition qgsgeometry.h:92
QVector< QgsPolylineXY > QgsMultiPolylineXY
A collection of QgsPolylines that share a common collection of attributes.
QVector< QgsPointXY > QgsPolylineXY
Polyline as represented as a vector of two-dimensional points.
Definition qgsgeometry.h:63
QVector< QgsPolygonXY > QgsMultiPolygonXY
A collection of QgsPolygons that share a common collection of attributes.
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:80
#define QgsDebugError(str)
Definition qgslogger.h:71
QList< QgsSymbolLayer * > QgsSymbolLayerList
Definition qgssymbol.h:30
QList< QPolygonF > offsetLine(QPolygonF polyline, double dist, Qgis::GeometryType geometryType)
calculate geometry shifted by a specified distance
QPolygonF lineStringToQPolygonF(const QgsLineString *line)
QPolygonF curveToPolygonF(const QgsCurve *curve)
void changeSymbolLayerIds(QgsSymbolLayer *sl, Functor &&generateId)
QMap< QString, QgsSymbol * > QgsSymbolMap
QMap< QString, QString > QgsStringMap
Contains information relating to a node (i.e.
QString identifier
A string identifying the node.
QgsStyleEntityVisitorInterface::NodeType type
Node type.
Contains information relating to the style entity currently being visited.
const QgsStyleEntityInterface * entity
Reference to style entity being visited.
QString identifier
A string identifying the style entity.