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