QGIS API Documentation 3.99.0-Master (d270888f95f)
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 Qgis::SymbolLayerUserFlags userFlags = qgsFlagKeysToValue( element.attribute( u"userFlags"_s ), Qgis::SymbolLayerUserFlags() );
1419
1420 // parse properties
1421 QVariantMap props = parseProperties( element );
1422
1423 // if there are any paths stored in properties, convert them from relative to absolute
1424 QgsApplication::symbolLayerRegistry()->resolvePaths( layerClass, props, context.pathResolver(), false );
1425
1426 QgsApplication::symbolLayerRegistry()->resolveFonts( layerClass, props, context );
1427
1428 std::unique_ptr< QgsSymbolLayer > layer = QgsApplication::symbolLayerRegistry()->createSymbolLayer( layerClass, props );
1429 if ( layer )
1430 {
1431 layer->setLocked( locked );
1432 layer->setRenderingPass( pass );
1433 layer->setEnabled( enabled );
1434 layer->setUserFlags( userFlags );
1435
1436 // old project format, empty is missing, keep the actual layer one
1437 if ( !id.isEmpty() )
1438 layer->setId( id );
1439
1440 //restore layer effect
1441 const QDomElement effectElem = element.firstChildElement( u"effect"_s );
1442 if ( !effectElem.isNull() )
1443 {
1444 std::unique_ptr< QgsPaintEffect > effect( QgsApplication::paintEffectRegistry()->createEffect( effectElem ) );
1445 if ( effect && !QgsPaintEffectRegistry::isDefaultStack( effect.get() ) )
1446 layer->setPaintEffect( effect.release() );
1447 }
1448
1449 // restore data defined properties
1450 const QDomElement ddProps = element.firstChildElement( u"data_defined_properties"_s );
1451 if ( !ddProps.isNull() )
1452 {
1453 const QgsPropertyCollection prevProperties = layer->dataDefinedProperties();
1454 layer->dataDefinedProperties().readXml( ddProps, QgsSymbolLayer::propertyDefinitions() );
1455
1456 // some symbol layers will be created with data defined properties by default -- we want to retain
1457 // these if they weren't restored from the xml
1458 const QSet< int > oldKeys = prevProperties.propertyKeys();
1459 for ( int key : oldKeys )
1460 {
1461 if ( !layer->dataDefinedProperties().propertyKeys().contains( key ) )
1462 layer->setDataDefinedProperty( static_cast< QgsSymbolLayer::Property >( key ), prevProperties.property( key ) );
1463 }
1464 }
1465
1466 return layer;
1467 }
1468 else
1469 {
1470 QgsDebugError( "unknown class " + layerClass );
1471 return nullptr;
1472 }
1473}
1474
1475static QString _nameForSymbolType( Qgis::SymbolType type )
1476{
1477 switch ( type )
1478 {
1480 return u"line"_s;
1482 return u"marker"_s;
1484 return u"fill"_s;
1485 default:
1486 return QString();
1487 }
1488}
1489
1490QDomElement QgsSymbolLayerUtils::saveSymbol( const QString &name, const QgsSymbol *symbol, QDomDocument &doc, const QgsReadWriteContext &context )
1491{
1492 Q_ASSERT( symbol );
1493 QDomElement symEl = doc.createElement( u"symbol"_s );
1494 symEl.setAttribute( u"type"_s, _nameForSymbolType( symbol->type() ) );
1495 symEl.setAttribute( u"name"_s, name );
1496 symEl.setAttribute( u"alpha"_s, QString::number( symbol->opacity() ) );
1497 symEl.setAttribute( u"clip_to_extent"_s, symbol->clipFeaturesToExtent() ? u"1"_s : u"0"_s );
1498 if ( !qgsDoubleNear( symbol->extentBuffer(), 0 ) )
1499 {
1500 symEl.setAttribute( u"extent_buffer"_s, QString::number( symbol->extentBuffer() ) );
1501 symEl.setAttribute( u"extent_buffer_unit"_s, QgsUnitTypes::encodeUnit( symbol->extentBufferSizeUnit() ) );
1502 }
1503 symEl.setAttribute( u"force_rhr"_s, symbol->forceRHR() ? u"1"_s : u"0"_s );
1505 symEl.setAttribute( u"renderer_should_use_levels"_s, u"1"_s );
1506
1507 symEl.setAttribute( u"is_animated"_s, symbol->animationSettings().isAnimated() ? u"1"_s : u"0"_s );
1508 symEl.setAttribute( u"frame_rate"_s, qgsDoubleToString( symbol->animationSettings().frameRate() ) );
1509
1510 if ( const QgsSymbolBufferSettings *bufferSettings = symbol->bufferSettings() )
1511 bufferSettings->writeXml( symEl, context );
1512
1513 //QgsDebugMsgLevel( "num layers " + QString::number( symbol->symbolLayerCount() ), 2 );
1514
1515 QDomElement ddProps = doc.createElement( u"data_defined_properties"_s );
1517 symEl.appendChild( ddProps );
1518
1519 for ( int i = 0; i < symbol->symbolLayerCount(); i++ )
1520 {
1521 const QgsSymbolLayer *layer = symbol->symbolLayer( i );
1522
1523 QDomElement layerEl = doc.createElement( u"layer"_s );
1524 layerEl.setAttribute( u"class"_s, layer->layerType() );
1525 layerEl.setAttribute( u"enabled"_s, layer->enabled() );
1526 layerEl.setAttribute( u"locked"_s, layer->isLocked() );
1527 layerEl.setAttribute( u"pass"_s, layer->renderingPass() );
1528 layerEl.setAttribute( u"id"_s, layer->id() );
1529 if ( layer->userFlags() != Qgis::SymbolLayerUserFlags() )
1530 layerEl.setAttribute( u"userFlags"_s, qgsFlagValueToKeys( layer->userFlags() ) );
1531
1532 QVariantMap props = layer->properties();
1533
1534 // if there are any paths in properties, convert them from absolute to relative
1535 QgsApplication::symbolLayerRegistry()->resolvePaths( layer->layerType(), props, context.pathResolver(), true );
1536
1537 saveProperties( props, doc, layerEl );
1538
1539 if ( layer->paintEffect() && !QgsPaintEffectRegistry::isDefaultStack( layer->paintEffect() ) )
1540 layer->paintEffect()->saveProperties( doc, layerEl );
1541
1542 QDomElement ddProps = doc.createElement( u"data_defined_properties"_s );
1544 layerEl.appendChild( ddProps );
1545
1546 if ( const QgsSymbol *subSymbol = const_cast< QgsSymbolLayer * >( layer )->subSymbol() )
1547 {
1548 const QString subname = u"@%1@%2"_s.arg( name ).arg( i );
1549 const QDomElement subEl = saveSymbol( subname, subSymbol, doc, context );
1550 layerEl.appendChild( subEl );
1551 }
1552 symEl.appendChild( layerEl );
1553 }
1554
1555 return symEl;
1556}
1557
1559{
1560 QDomDocument doc( u"qgis-symbol-definition"_s );
1561 const QDomElement symbolElem = saveSymbol( u"symbol"_s, symbol, doc, QgsReadWriteContext() );
1562 QString props;
1563 QTextStream stream( &props );
1564 symbolElem.save( stream, -1 );
1565 return props;
1566}
1567
1569 Qgis::GeometryType geomType,
1570 QList<QgsSymbolLayer *> &layers )
1571{
1572 QgsDebugMsgLevel( u"Entered."_s, 4 );
1573
1574 if ( element.isNull() )
1575 return false;
1576
1577 const QString symbolizerName = element.localName();
1578
1579 if ( symbolizerName == "PointSymbolizer"_L1 )
1580 {
1581 // first check for Graphic element, nothing will be rendered if not found
1582 const QDomElement graphicElem = element.firstChildElement( u"Graphic"_s );
1583 if ( graphicElem.isNull() )
1584 {
1585 QgsDebugError( u"Graphic element not found in PointSymbolizer"_s );
1586 }
1587 else
1588 {
1589 switch ( geomType )
1590 {
1592 {
1593 // polygon layer and point symbolizer: draw polygon centroid
1594 std::unique_ptr< QgsSymbolLayer> l = QgsApplication::symbolLayerRegistry()->createSymbolLayerFromSld( u"CentroidFill"_s, element );
1595 if ( l )
1596 layers.append( l.release() );
1597
1598 break;
1599 }
1600
1602 {
1603 // point layer and point symbolizer: use markers
1604 std::unique_ptr< QgsSymbolLayer> l( createMarkerLayerFromSld( element ) );
1605 if ( l )
1606 layers.append( l.release() );
1607
1608 break;
1609 }
1610
1612 {
1613 // line layer and point symbolizer: draw central point
1614 std::unique_ptr< QgsSymbolLayer> l = QgsApplication::symbolLayerRegistry()->createSymbolLayerFromSld( u"SimpleMarker"_s, element );
1615 if ( l )
1616 layers.append( l.release() );
1617
1618 break;
1619 }
1620
1623 break;
1624 }
1625 }
1626 }
1627
1628 if ( symbolizerName == "LineSymbolizer"_L1 )
1629 {
1630 // check for Stroke element, nothing will be rendered if not found
1631 const QDomElement strokeElem = element.firstChildElement( u"Stroke"_s );
1632 if ( strokeElem.isNull() )
1633 {
1634 QgsDebugError( u"Stroke element not found in LineSymbolizer"_s );
1635 }
1636 else
1637 {
1638 switch ( geomType )
1639 {
1642 {
1643 // polygon layer and line symbolizer: draw polygon stroke
1644 // line layer and line symbolizer: draw line
1645 std::unique_ptr< QgsSymbolLayer> l = createLineLayerFromSld( element );
1646 if ( l )
1647 layers.append( l.release() );
1648
1649 break;
1650 }
1651
1653 {
1654 // point layer and line symbolizer: draw a little line marker
1655 std::unique_ptr< QgsSymbolLayer> l = QgsApplication::symbolLayerRegistry()->createSymbolLayerFromSld( u"MarkerLine"_s, element );
1656 if ( l )
1657 layers.append( l.release() );
1658
1659 break;
1660 }
1661
1664 break;
1665 }
1666 }
1667 }
1668
1669 if ( symbolizerName == "PolygonSymbolizer"_L1 )
1670 {
1671 // get Fill and Stroke elements, nothing will be rendered if both are missing
1672 const QDomElement fillElem = element.firstChildElement( u"Fill"_s );
1673 const QDomElement strokeElem = element.firstChildElement( u"Stroke"_s );
1674 if ( fillElem.isNull() && strokeElem.isNull() )
1675 {
1676 QgsDebugError( u"neither Fill nor Stroke element not found in PolygonSymbolizer"_s );
1677 }
1678 else
1679 {
1680 switch ( geomType )
1681 {
1683 {
1684 // polygon layer and polygon symbolizer: draw fill
1685
1686 std::unique_ptr< QgsSymbolLayer > l = createFillLayerFromSld( element );
1687 if ( l )
1688 {
1689 QgsSymbolLayer *lastLayer = l.get();
1690 layers.append( l.release() );
1691
1692 // SVGFill and SimpleFill symbolLayerV2 supports stroke internally,
1693 // so don't go forward to create a different symbolLayerV2 for stroke
1694 if ( lastLayer->layerType() == "SimpleFill"_L1 || lastLayer->layerType() == "SVGFill"_L1 )
1695 break;
1696 }
1697
1698 // now create polygon stroke
1699 // polygon layer and polygon symbolizer: draw polygon stroke
1700 l = createLineLayerFromSld( element );
1701 if ( l )
1702 layers.append( l.release() );
1703
1704 break;
1705 }
1706
1708 {
1709 // line layer and polygon symbolizer: draw line
1710 std::unique_ptr< QgsSymbolLayer > l = createLineLayerFromSld( element );
1711 if ( l )
1712 layers.append( l.release() );
1713
1714 break;
1715 }
1716
1718 {
1719 // point layer and polygon symbolizer: draw a square marker
1720 convertPolygonSymbolizerToPointMarker( element, layers );
1721 break;
1722 }
1725 break;
1726 }
1727 }
1728 }
1729
1730 return true;
1731}
1732
1733std::unique_ptr< QgsSymbolLayer > QgsSymbolLayerUtils::createFillLayerFromSld( QDomElement &element )
1734{
1735 const QDomElement fillElem = element.firstChildElement( u"Fill"_s );
1736 if ( fillElem.isNull() )
1737 {
1738 QgsDebugError( u"Fill element not found"_s );
1739 return nullptr;
1740 }
1741
1742 std::unique_ptr< QgsSymbolLayer > l;
1743
1744 if ( needLinePatternFill( element ) )
1745 l = QgsApplication::symbolLayerRegistry()->createSymbolLayerFromSld( u"LinePatternFill"_s, element );
1746 else if ( needPointPatternFill( element ) )
1747 l = QgsApplication::symbolLayerRegistry()->createSymbolLayerFromSld( u"PointPatternFill"_s, element );
1748 else if ( needSvgFill( element ) )
1749 l = QgsApplication::symbolLayerRegistry()->createSymbolLayerFromSld( u"SVGFill"_s, element );
1750 else if ( needRasterImageFill( element ) )
1751 l = QgsApplication::symbolLayerRegistry()->createSymbolLayerFromSld( u"RasterFill"_s, element );
1752 else
1753 l = QgsApplication::symbolLayerRegistry()->createSymbolLayerFromSld( u"SimpleFill"_s, element );
1754
1755 return l;
1756}
1757
1758std::unique_ptr< QgsSymbolLayer > QgsSymbolLayerUtils::createLineLayerFromSld( QDomElement &element )
1759{
1760 const QDomElement strokeElem = element.firstChildElement( u"Stroke"_s );
1761 if ( strokeElem.isNull() )
1762 {
1763 QgsDebugError( u"Stroke element not found"_s );
1764 return nullptr;
1765 }
1766
1767 std::unique_ptr< QgsSymbolLayer > l;
1768
1769 if ( needMarkerLine( element ) )
1770 l = QgsApplication::symbolLayerRegistry()->createSymbolLayerFromSld( u"MarkerLine"_s, element );
1771 else
1772 l = QgsApplication::symbolLayerRegistry()->createSymbolLayerFromSld( u"SimpleLine"_s, element );
1773
1774 return l;
1775}
1776
1777std::unique_ptr< QgsSymbolLayer > QgsSymbolLayerUtils::createMarkerLayerFromSld( QDomElement &element )
1778{
1779 const QDomElement graphicElem = element.firstChildElement( u"Graphic"_s );
1780 if ( graphicElem.isNull() )
1781 {
1782 QgsDebugError( u"Graphic element not found"_s );
1783 return nullptr;
1784 }
1785
1786 std::unique_ptr< QgsSymbolLayer > l;
1787
1788 if ( needFontMarker( element ) )
1789 l = QgsApplication::symbolLayerRegistry()->createSymbolLayerFromSld( u"FontMarker"_s, element );
1790 else if ( needSvgMarker( element ) )
1791 l = QgsApplication::symbolLayerRegistry()->createSymbolLayerFromSld( u"SvgMarker"_s, element );
1792 else if ( needEllipseMarker( element ) )
1793 l = QgsApplication::symbolLayerRegistry()->createSymbolLayerFromSld( u"EllipseMarker"_s, element );
1794 else if ( needRasterMarker( element ) )
1795 l = QgsApplication::symbolLayerRegistry()->createSymbolLayerFromSld( u"RasterMarker"_s, element );
1796 else
1797 l = QgsApplication::symbolLayerRegistry()->createSymbolLayerFromSld( u"SimpleMarker"_s, element );
1798
1799 return l;
1800}
1801
1803{
1804 return hasExternalGraphicV2( element, u"image/svg+xml"_s );
1805}
1806
1807bool QgsSymbolLayerUtils::hasExternalGraphicV2( const QDomElement &element, const QString format )
1808{
1809 const QDomElement graphicElem = element.firstChildElement( u"Graphic"_s );
1810 if ( graphicElem.isNull() )
1811 return false;
1812
1813 const QDomElement externalGraphicElem = graphicElem.firstChildElement( u"ExternalGraphic"_s );
1814 if ( externalGraphicElem.isNull() )
1815 return false;
1816
1817 // check for format
1818 const QDomElement formatElem = externalGraphicElem.firstChildElement( u"Format"_s );
1819 if ( formatElem.isNull() )
1820 return false;
1821
1822 const QString elementFormat = formatElem.firstChild().nodeValue();
1823 if ( ! format.isEmpty() && elementFormat != format )
1824 {
1825 QgsDebugMsgLevel( "unsupported External Graphic format found: " + elementFormat, 4 );
1826 return false;
1827 }
1828
1829 // check for a valid content
1830 const QDomElement onlineResourceElem = externalGraphicElem.firstChildElement( u"OnlineResource"_s );
1831 const QDomElement inlineContentElem = externalGraphicElem.firstChildElement( u"InlineContent"_s );
1832 // NOLINTBEGIN(bugprone-branch-clone)
1833 if ( !onlineResourceElem.isNull() )
1834 {
1835 return true;
1836 }
1837 else if ( !inlineContentElem.isNull() )
1838 {
1839 return true;
1840 }
1841 else
1842 {
1843 return false;
1844 }
1845 // NOLINTEND(bugprone-branch-clone)
1846}
1847
1848bool QgsSymbolLayerUtils::hasWellKnownMark( QDomElement &element )
1849{
1850 const QDomElement graphicElem = element.firstChildElement( u"Graphic"_s );
1851 if ( graphicElem.isNull() )
1852 return false;
1853
1854 const QDomElement markElem = graphicElem.firstChildElement( u"Mark"_s );
1855 if ( markElem.isNull() )
1856 return false;
1857
1858 const QDomElement wellKnownNameElem = markElem.firstChildElement( u"WellKnownName"_s );
1859 return !wellKnownNameElem.isNull();
1860}
1861
1862
1863bool QgsSymbolLayerUtils::needFontMarker( QDomElement &element )
1864{
1865 const QDomElement graphicElem = element.firstChildElement( u"Graphic"_s );
1866 if ( graphicElem.isNull() )
1867 return false;
1868
1869 const QDomElement markElem = graphicElem.firstChildElement( u"Mark"_s );
1870 if ( markElem.isNull() )
1871 return false;
1872
1873 // check for format
1874 const QDomElement formatElem = markElem.firstChildElement( u"Format"_s );
1875 if ( formatElem.isNull() )
1876 return false;
1877
1878 const QString format = formatElem.firstChild().nodeValue();
1879 if ( format != "ttf"_L1 )
1880 {
1881 QgsDebugError( "unsupported Graphic Mark format found: " + format );
1882 return false;
1883 }
1884
1885 // check for a valid content
1886 const QDomElement onlineResourceElem = markElem.firstChildElement( u"OnlineResource"_s );
1887 const QDomElement inlineContentElem = markElem.firstChildElement( u"InlineContent"_s );
1888 if ( !onlineResourceElem.isNull() )
1889 {
1890 // mark with ttf format has a markIndex element
1891 const QDomElement markIndexElem = markElem.firstChildElement( u"MarkIndex"_s );
1892 if ( !markIndexElem.isNull() )
1893 return true;
1894 }
1895 else if ( !inlineContentElem.isNull() )
1896 {
1897 return false; // not implemented yet
1898 }
1899
1900 return false;
1901}
1902
1903bool QgsSymbolLayerUtils::needSvgMarker( const QDomElement &element )
1904{
1905 return hasExternalGraphicV2( element, u"image/svg+xml"_s );
1906}
1907
1908bool QgsSymbolLayerUtils::needRasterMarker( const QDomElement &element )
1909{
1910 // any external graphic except SVGs are considered rasters
1911 return hasExternalGraphicV2( element, QString() ) && !needSvgMarker( element );
1912}
1913
1914bool QgsSymbolLayerUtils::needEllipseMarker( QDomElement &element )
1915{
1916 QDomElement graphicElem = element.firstChildElement( u"Graphic"_s );
1917 if ( graphicElem.isNull() )
1918 return false;
1919
1920 QgsStringMap vendorOptions = QgsSymbolLayerUtils::getVendorOptionList( graphicElem );
1921 for ( QgsStringMap::iterator it = vendorOptions.begin(); it != vendorOptions.end(); ++it )
1922 {
1923 if ( it.key() == "widthHeightFactor"_L1 )
1924 {
1925 return true;
1926 }
1927 }
1928
1929 return false;
1930}
1931
1932bool QgsSymbolLayerUtils::needMarkerLine( QDomElement &element )
1933{
1934 const QDomElement strokeElem = element.firstChildElement( u"Stroke"_s );
1935 if ( strokeElem.isNull() )
1936 return false;
1937
1938 QDomElement graphicStrokeElem = strokeElem.firstChildElement( u"GraphicStroke"_s );
1939 if ( graphicStrokeElem.isNull() )
1940 return false;
1941
1942 return hasWellKnownMark( graphicStrokeElem );
1943}
1944
1946{
1947 const QDomElement fillElem = element.firstChildElement( u"Fill"_s );
1948 if ( fillElem.isNull() )
1949 return false;
1950
1951 const QDomElement graphicFillElem = fillElem.firstChildElement( u"GraphicFill"_s );
1952 if ( graphicFillElem.isNull() )
1953 return false;
1954
1955 QDomElement graphicElem = graphicFillElem.firstChildElement( u"Graphic"_s );
1956 if ( graphicElem.isNull() )
1957 return false;
1958
1959 // line pattern fill uses horline wellknown marker with an angle
1960
1961 QString name;
1962 QColor fillColor, strokeColor;
1963 double size, strokeWidth;
1964 Qt::PenStyle strokeStyle;
1965 if ( !wellKnownMarkerFromSld( graphicElem, name, fillColor, strokeColor, strokeStyle, strokeWidth, size ) )
1966 return false;
1967
1968 if ( name != "horline"_L1 )
1969 return false;
1970
1971 QString angleFunc;
1972 if ( !rotationFromSldElement( graphicElem, angleFunc ) )
1973 return false;
1974
1975 bool ok;
1976 const double angle = angleFunc.toDouble( &ok );
1977 return !( !ok || qgsDoubleNear( angle, 0.0 ) );
1978}
1979
1981{
1982 const QDomElement fillElem = element.firstChildElement( u"Fill"_s );
1983 if ( fillElem.isNull() )
1984 return false;
1985
1986 const QDomElement graphicFillElem = fillElem.firstChildElement( u"GraphicFill"_s );
1987 if ( graphicFillElem.isNull() )
1988 return false;
1989
1990 const QDomElement graphicElem = graphicFillElem.firstChildElement( u"Graphic"_s );
1991 if ( graphicElem.isNull() )
1992 return false;
1993
1994 const QDomElement markElem = graphicElem.firstChildElement( u"Mark"_s );
1995 if ( markElem.isNull() )
1996 return false;
1997
1998 return true;
1999}
2000
2001bool QgsSymbolLayerUtils::needSvgFill( QDomElement &element )
2002{
2003 const QDomElement fillElem = element.firstChildElement( u"Fill"_s );
2004 if ( fillElem.isNull() )
2005 return false;
2006
2007 QDomElement graphicFillElem = fillElem.firstChildElement( u"GraphicFill"_s );
2008 if ( graphicFillElem.isNull() )
2009 return false;
2010
2011 return hasExternalGraphicV2( graphicFillElem, u"image/svg+xml"_s );
2012}
2013
2015{
2016 const QDomElement fillElem = element.firstChildElement( u"Fill"_s );
2017 if ( fillElem.isNull() )
2018 return false;
2019
2020 QDomElement graphicFillElem = fillElem.firstChildElement( u"GraphicFill"_s );
2021 if ( graphicFillElem.isNull() )
2022 return false;
2023
2024 return hasExternalGraphicV2( graphicFillElem, u"image/png"_s ) || hasExternalGraphicV2( graphicFillElem, u"image/jpeg"_s ) || hasExternalGraphicV2( graphicFillElem, u"image/gif"_s );
2025}
2026
2027
2028bool QgsSymbolLayerUtils::convertPolygonSymbolizerToPointMarker( QDomElement &element, QList<QgsSymbolLayer *> &layerList )
2029{
2030 QgsDebugMsgLevel( u"Entered."_s, 4 );
2031
2032 /* SE 1.1 says about PolygonSymbolizer:
2033 if a point geometry is referenced instead of a polygon,
2034 then a small, square, ortho-normal polygon should be
2035 constructed for rendering.
2036 */
2037
2038 QgsSymbolLayerList layers;
2039
2040 // retrieve both Fill and Stroke elements
2041 QDomElement fillElem = element.firstChildElement( u"Fill"_s );
2042 QDomElement strokeElem = element.firstChildElement( u"Stroke"_s );
2043
2044 // first symbol layer
2045 {
2046 bool validFill = false, validStroke = false;
2047
2048 // check for simple fill
2049 // Fill element can contain some SvgParameter elements
2050 QColor fillColor;
2051 Qt::BrushStyle fillStyle;
2052
2053 if ( fillFromSld( fillElem, fillStyle, fillColor ) )
2054 validFill = true;
2055
2056 // check for simple stroke
2057 // Stroke element can contain some SvgParameter elements
2058 QColor strokeColor;
2059 Qt::PenStyle strokeStyle;
2060 double strokeWidth = 1.0, dashOffset = 0.0;
2061 QVector<qreal> customDashPattern;
2062
2063 if ( lineFromSld( strokeElem, strokeStyle, strokeColor, strokeWidth,
2064 nullptr, nullptr, &customDashPattern, &dashOffset ) )
2065 validStroke = true;
2066
2067 if ( validFill || validStroke )
2068 {
2069 QVariantMap map;
2070 map[u"name"_s] = u"square"_s;
2071 map[u"color"_s] = QgsColorUtils::colorToString( validFill ? fillColor : Qt::transparent );
2072 map[u"color_border"_s] = QgsColorUtils::colorToString( validStroke ? strokeColor : Qt::transparent );
2073 map[u"size"_s] = QString::number( 6 );
2074 map[u"angle"_s] = QString::number( 0 );
2075 map[u"offset"_s] = encodePoint( QPointF( 0, 0 ) );
2076 layers.append( QgsApplication::symbolLayerRegistry()->createSymbolLayer( u"SimpleMarker"_s, map ).release() );
2077 }
2078 }
2079
2080 // second symbol layer
2081 {
2082 bool validFill = false, validStroke = false;
2083
2084 // check for graphic fill
2085 QString name, format;
2086 int markIndex = -1;
2087 QColor fillColor, strokeColor;
2088 double strokeWidth = 1.0, size = 0.0, angle = 0.0;
2089 QPointF offset;
2090
2091 // Fill element can contain a GraphicFill element
2092 const QDomElement graphicFillElem = fillElem.firstChildElement( u"GraphicFill"_s );
2093 if ( !graphicFillElem.isNull() )
2094 {
2095 // GraphicFill element must contain a Graphic element
2096 QDomElement graphicElem = graphicFillElem.firstChildElement( u"Graphic"_s );
2097 if ( !graphicElem.isNull() )
2098 {
2099 // Graphic element can contains some ExternalGraphic and Mark element
2100 // search for the first supported one and use it
2101 bool found = false;
2102
2103 const QDomElement graphicChildElem = graphicElem.firstChildElement();
2104 while ( !graphicChildElem.isNull() )
2105 {
2106 if ( graphicChildElem.localName() == "Mark"_L1 )
2107 {
2108 // check for a well known name
2109 const QDomElement wellKnownNameElem = graphicChildElem.firstChildElement( u"WellKnownName"_s );
2110 if ( !wellKnownNameElem.isNull() )
2111 {
2112 name = wellKnownNameElem.firstChild().nodeValue();
2113 found = true;
2114 break;
2115 }
2116 }
2117
2118 if ( graphicChildElem.localName() == "ExternalGraphic"_L1 || graphicChildElem.localName() == "Mark"_L1 )
2119 {
2120 // check for external graphic format
2121 const QDomElement formatElem = graphicChildElem.firstChildElement( u"Format"_s );
2122 if ( formatElem.isNull() )
2123 continue;
2124
2125 format = formatElem.firstChild().nodeValue();
2126
2127 // TODO: remove this check when more formats will be supported
2128 // only SVG external graphics are supported in this moment
2129 if ( graphicChildElem.localName() == "ExternalGraphic"_L1 && format != "image/svg+xml"_L1 )
2130 continue;
2131
2132 // TODO: remove this check when more formats will be supported
2133 // only ttf marks are supported in this moment
2134 if ( graphicChildElem.localName() == "Mark"_L1 && format != "ttf"_L1 )
2135 continue;
2136
2137 // check for a valid content
2138 const QDomElement onlineResourceElem = graphicChildElem.firstChildElement( u"OnlineResource"_s );
2139 const QDomElement inlineContentElem = graphicChildElem.firstChildElement( u"InlineContent"_s );
2140
2141 if ( !onlineResourceElem.isNull() )
2142 {
2143 name = onlineResourceElem.attributeNS( u"http://www.w3.org/1999/xlink"_s, u"href"_s );
2144
2145 if ( graphicChildElem.localName() == "Mark"_L1 && format == "ttf"_L1 )
2146 {
2147 // mark with ttf format may have a name like ttf://fontFamily
2148 if ( name.startsWith( "ttf://"_L1 ) )
2149 name = name.mid( 6 );
2150
2151 // mark with ttf format has a markIndex element
2152 const QDomElement markIndexElem = graphicChildElem.firstChildElement( u"MarkIndex"_s );
2153 if ( markIndexElem.isNull() )
2154 continue;
2155
2156 bool ok;
2157 const int v = markIndexElem.firstChild().nodeValue().toInt( &ok );
2158 if ( !ok || v < 0 )
2159 continue;
2160
2161 markIndex = v;
2162 }
2163
2164 found = true;
2165 break;
2166 }
2167#if 0
2168 else if ( !inlineContentElem.isNull() )
2169 continue; // TODO: not implemented yet
2170#endif
2171 else
2172 continue;
2173 }
2174
2175 // if Mark element is present but it doesn't contains neither
2176 // WellKnownName nor OnlineResource nor InlineContent,
2177 // use the default mark (square)
2178 if ( graphicChildElem.localName() == "Mark"_L1 )
2179 {
2180 name = u"square"_s;
2181 found = true;
2182 break;
2183 }
2184 }
2185
2186 // if found a valid Mark, check for its Fill and Stroke element
2187 if ( found && graphicChildElem.localName() == "Mark"_L1 )
2188 {
2189 // XXX: recursive definition!?! couldn't be dangerous???
2190 // to avoid recursion we handle only simple fill and simple stroke
2191
2192 // check for simple fill
2193 // Fill element can contain some SvgParameter elements
2194 Qt::BrushStyle markFillStyle;
2195
2196 QDomElement markFillElem = graphicChildElem.firstChildElement( u"Fill"_s );
2197 if ( fillFromSld( markFillElem, markFillStyle, fillColor ) )
2198 validFill = true;
2199
2200 // check for simple stroke
2201 // Stroke element can contain some SvgParameter elements
2202 Qt::PenStyle strokeStyle;
2203 double strokeWidth = 1.0, dashOffset = 0.0;
2204 QVector<qreal> customDashPattern;
2205
2206 QDomElement markStrokeElem = graphicChildElem.firstChildElement( u"Stroke"_s );
2207 if ( lineFromSld( markStrokeElem, strokeStyle, strokeColor, strokeWidth,
2208 nullptr, nullptr, &customDashPattern, &dashOffset ) )
2209 validStroke = true;
2210 }
2211
2212 if ( found )
2213 {
2214 // check for Opacity, Size, Rotation, AnchorPoint, Displacement
2215 const QDomElement opacityElem = graphicElem.firstChildElement( u"Opacity"_s );
2216 if ( !opacityElem.isNull() )
2217 fillColor.setAlpha( decodeSldAlpha( opacityElem.firstChild().nodeValue() ) );
2218
2219 const QDomElement sizeElem = graphicElem.firstChildElement( u"Size"_s );
2220 if ( !sizeElem.isNull() )
2221 {
2222 bool ok;
2223 const double v = sizeElem.firstChild().nodeValue().toDouble( &ok );
2224 if ( ok && v > 0 )
2225 size = v;
2226 }
2227
2228 QString angleFunc;
2229 if ( rotationFromSldElement( graphicElem, angleFunc ) && !angleFunc.isEmpty() )
2230 {
2231 bool ok;
2232 const double v = angleFunc.toDouble( &ok );
2233 if ( ok )
2234 angle = v;
2235 }
2236
2237 displacementFromSldElement( graphicElem, offset );
2238 }
2239 }
2240 }
2241
2242 if ( validFill || validStroke )
2243 {
2244 if ( format == "image/svg+xml"_L1 )
2245 {
2246 QVariantMap map;
2247 map[u"name"_s] = name;
2248 map[u"fill"_s] = fillColor.name();
2249 map[u"outline"_s] = strokeColor.name();
2250 map[u"outline-width"_s] = QString::number( strokeWidth );
2251 if ( !qgsDoubleNear( size, 0.0 ) )
2252 map[u"size"_s] = QString::number( size );
2253 if ( !qgsDoubleNear( angle, 0.0 ) )
2254 map[u"angle"_s] = QString::number( angle );
2255 if ( !offset.isNull() )
2256 map[u"offset"_s] = encodePoint( offset );
2257 layers.append( QgsApplication::symbolLayerRegistry()->createSymbolLayer( u"SvgMarker"_s, map ).release() );
2258 }
2259 else if ( format == "ttf"_L1 )
2260 {
2261 QVariantMap map;
2262 map[u"font"_s] = name;
2263 map[u"chr"_s] = markIndex;
2264 map[u"color"_s] = QgsColorUtils::colorToString( validFill ? fillColor : Qt::transparent );
2265 if ( size > 0 )
2266 map[u"size"_s] = QString::number( size );
2267 if ( !qgsDoubleNear( angle, 0.0 ) )
2268 map[u"angle"_s] = QString::number( angle );
2269 if ( !offset.isNull() )
2270 map[u"offset"_s] = encodePoint( offset );
2271 layers.append( QgsApplication::symbolLayerRegistry()->createSymbolLayer( u"FontMarker"_s, map ).release() );
2272 }
2273 }
2274 }
2275
2276 if ( layers.isEmpty() )
2277 return false;
2278
2279 layerList << layers;
2280 layers.clear();
2281 return true;
2282}
2283
2284void QgsSymbolLayerUtils::fillToSld( QDomDocument &doc, QDomElement &element, Qt::BrushStyle brushStyle, const QColor &color )
2285{
2286 QgsSldExportContext context;
2287 fillToSld( doc, element, context, brushStyle, color );
2288}
2289
2290void QgsSymbolLayerUtils::fillToSld( QDomDocument &doc, QDomElement &element, QgsSldExportContext &context, Qt::BrushStyle brushStyle, const QColor &color )
2291{
2292 QString patternName;
2293 switch ( brushStyle )
2294 {
2295 case Qt::NoBrush:
2296 return;
2297
2298 case Qt::SolidPattern:
2299 if ( color.isValid() )
2300 {
2301 element.appendChild( createSvgParameterElement( doc, u"fill"_s, color.name() ) );
2302 if ( color.alpha() < 255 )
2303 element.appendChild( createSvgParameterElement( doc, u"fill-opacity"_s, encodeSldAlpha( color.alpha() ) ) );
2304 }
2305 return;
2306
2307 case Qt::CrossPattern:
2308 case Qt::DiagCrossPattern:
2309 case Qt::HorPattern:
2310 case Qt::VerPattern:
2311 case Qt::BDiagPattern:
2312 case Qt::FDiagPattern:
2313 case Qt::Dense1Pattern:
2314 case Qt::Dense2Pattern:
2315 case Qt::Dense3Pattern:
2316 case Qt::Dense4Pattern:
2317 case Qt::Dense5Pattern:
2318 case Qt::Dense6Pattern:
2319 case Qt::Dense7Pattern:
2320 patternName = encodeSldBrushStyle( brushStyle );
2321 break;
2322
2323 default:
2324 context.pushWarning( QObject::tr( "Brush style '%1' is not supported for SLD" ).arg( brushStyle ) );
2325 return;
2326 }
2327
2328 QDomElement graphicFillElem = doc.createElement( u"se:GraphicFill"_s );
2329 element.appendChild( graphicFillElem );
2330
2331 QDomElement graphicElem = doc.createElement( u"se:Graphic"_s );
2332 graphicFillElem.appendChild( graphicElem );
2333
2334 const QColor fillColor = patternName.startsWith( "brush://"_L1 ) ? color : QColor();
2335 const QColor strokeColor = !patternName.startsWith( "brush://"_L1 ) ? color : QColor();
2336
2337 /* Use WellKnownName tag to handle QT brush styles. */
2338 wellKnownMarkerToSld( doc, graphicElem, patternName, fillColor, strokeColor, Qt::SolidLine, context, -1, -1 );
2339}
2340
2341bool QgsSymbolLayerUtils::fillFromSld( QDomElement &element, Qt::BrushStyle &brushStyle, QColor &color )
2342{
2343 QgsDebugMsgLevel( u"Entered."_s, 4 );
2344
2345 brushStyle = Qt::SolidPattern;
2346 color = QColor( 128, 128, 128 );
2347
2348 if ( element.isNull() )
2349 {
2350 brushStyle = Qt::NoBrush;
2351 color = QColor();
2352 return true;
2353 }
2354
2355 const QDomElement graphicFillElem = element.firstChildElement( u"GraphicFill"_s );
2356 // if no GraphicFill element is found, it's a solid fill
2357 if ( graphicFillElem.isNull() )
2358 {
2359 QgsStringMap svgParams = getSvgParameterList( element );
2360 for ( QgsStringMap::iterator it = svgParams.begin(); it != svgParams.end(); ++it )
2361 {
2362 QgsDebugMsgLevel( u"found SvgParameter %1: %2"_s.arg( it.key(), it.value() ), 2 );
2363
2364 if ( it.key() == "fill"_L1 )
2365 color = QColor( it.value() );
2366 else if ( it.key() == "fill-opacity"_L1 )
2367 color.setAlpha( decodeSldAlpha( it.value() ) );
2368 }
2369 }
2370 else // wellKnown marker
2371 {
2372 QDomElement graphicElem = graphicFillElem.firstChildElement( u"Graphic"_s );
2373 if ( graphicElem.isNull() )
2374 return false; // Graphic is required within GraphicFill
2375
2376 QString patternName = u"square"_s;
2377 QColor fillColor, strokeColor;
2378 double strokeWidth, size;
2379 Qt::PenStyle strokeStyle;
2380 if ( !wellKnownMarkerFromSld( graphicElem, patternName, fillColor, strokeColor, strokeStyle, strokeWidth, size ) )
2381 return false;
2382
2383 brushStyle = decodeSldBrushStyle( patternName );
2384 if ( brushStyle == Qt::NoBrush )
2385 return false; // unable to decode brush style
2386
2387 const QColor c = patternName.startsWith( "brush://"_L1 ) ? fillColor : strokeColor;
2388 if ( c.isValid() )
2389 color = c;
2390 }
2391
2392 return true;
2393}
2394
2395void QgsSymbolLayerUtils::lineToSld( QDomDocument &doc, QDomElement &element,
2396 Qt::PenStyle penStyle, const QColor &color, QgsSldExportContext &context, double width,
2397 const Qt::PenJoinStyle *penJoinStyle, const Qt::PenCapStyle *penCapStyle,
2398 const QVector<qreal> *customDashPattern, double dashOffset )
2399{
2400 QVector<qreal> dashPattern;
2401 const QVector<qreal> *pattern = &dashPattern;
2402
2403 if ( penStyle == Qt::CustomDashLine && !customDashPattern )
2404 {
2405 context.pushWarning( QObject::tr( "WARNING: Custom dash pattern required but not provided. Using default dash pattern." ) );
2406 penStyle = Qt::DashLine;
2407 }
2408
2409 switch ( penStyle )
2410 {
2411 case Qt::NoPen:
2412 return;
2413
2414 case Qt::SolidLine:
2415 break;
2416
2417 case Qt::DashLine:
2418 dashPattern.push_back( 4.0 );
2419 dashPattern.push_back( 2.0 );
2420 break;
2421 case Qt::DotLine:
2422 dashPattern.push_back( 1.0 );
2423 dashPattern.push_back( 2.0 );
2424 break;
2425 case Qt::DashDotLine:
2426 dashPattern.push_back( 4.0 );
2427 dashPattern.push_back( 2.0 );
2428 dashPattern.push_back( 1.0 );
2429 dashPattern.push_back( 2.0 );
2430 break;
2431 case Qt::DashDotDotLine:
2432 dashPattern.push_back( 4.0 );
2433 dashPattern.push_back( 2.0 );
2434 dashPattern.push_back( 1.0 );
2435 dashPattern.push_back( 2.0 );
2436 dashPattern.push_back( 1.0 );
2437 dashPattern.push_back( 2.0 );
2438 break;
2439
2440 case Qt::CustomDashLine:
2441 Q_ASSERT( customDashPattern );
2442 pattern = customDashPattern;
2443 break;
2444
2445 default:
2446 context.pushWarning( QObject::tr( "Pen style '%1' is not supported for SLD" ).arg( penStyle ) );
2447 return;
2448 }
2449
2450 if ( color.isValid() )
2451 {
2452 element.appendChild( createSvgParameterElement( doc, u"stroke"_s, color.name() ) );
2453 if ( color.alpha() < 255 )
2454 element.appendChild( createSvgParameterElement( doc, u"stroke-opacity"_s, encodeSldAlpha( color.alpha() ) ) );
2455 }
2456 if ( width > 0 )
2457 {
2458 element.appendChild( createSvgParameterElement( doc, u"stroke-width"_s, qgsDoubleToString( width ) ) );
2459 }
2460 else if ( width == 0 )
2461 {
2462 // hairline, yet not zero. it's actually painted in qgis
2463 element.appendChild( createSvgParameterElement( doc, u"stroke-width"_s, u"0.5"_s ) );
2464 }
2465 if ( penJoinStyle )
2466 element.appendChild( createSvgParameterElement( doc, u"stroke-linejoin"_s, encodeSldLineJoinStyle( *penJoinStyle ) ) );
2467 if ( penCapStyle )
2468 element.appendChild( createSvgParameterElement( doc, u"stroke-linecap"_s, encodeSldLineCapStyle( *penCapStyle ) ) );
2469
2470 if ( !pattern->isEmpty() )
2471 {
2472 element.appendChild( createSvgParameterElement( doc, u"stroke-dasharray"_s, encodeSldRealVector( *pattern ) ) );
2473 if ( !qgsDoubleNear( dashOffset, 0.0 ) )
2474 element.appendChild( createSvgParameterElement( doc, u"stroke-dashoffset"_s, qgsDoubleToString( dashOffset ) ) );
2475 }
2476}
2477
2478
2479bool QgsSymbolLayerUtils::lineFromSld( QDomElement &element,
2480 Qt::PenStyle &penStyle, QColor &color, double &width,
2481 Qt::PenJoinStyle *penJoinStyle, Qt::PenCapStyle *penCapStyle,
2482 QVector<qreal> *customDashPattern, double *dashOffset )
2483{
2484 QgsDebugMsgLevel( u"Entered."_s, 4 );
2485
2486 penStyle = Qt::SolidLine;
2487 color = QColor( 0, 0, 0 );
2488 width = 1;
2489 if ( penJoinStyle )
2490 *penJoinStyle = Qt::BevelJoin;
2491 if ( penCapStyle )
2492 *penCapStyle = Qt::SquareCap;
2493 if ( customDashPattern )
2494 customDashPattern->clear();
2495 if ( dashOffset )
2496 *dashOffset = 0;
2497
2498 if ( element.isNull() )
2499 {
2500 penStyle = Qt::NoPen;
2501 color = QColor();
2502 return true;
2503 }
2504
2505 QgsStringMap svgParams = getSvgParameterList( element );
2506 for ( QgsStringMap::iterator it = svgParams.begin(); it != svgParams.end(); ++it )
2507 {
2508 QgsDebugMsgLevel( u"found SvgParameter %1: %2"_s.arg( it.key(), it.value() ), 2 );
2509
2510 if ( it.key() == "stroke"_L1 )
2511 {
2512 color = QColor( it.value() );
2513 }
2514 else if ( it.key() == "stroke-opacity"_L1 )
2515 {
2516 color.setAlpha( decodeSldAlpha( it.value() ) );
2517 }
2518 else if ( it.key() == "stroke-width"_L1 )
2519 {
2520 bool ok;
2521 const double w = it.value().toDouble( &ok );
2522 if ( ok )
2523 width = w;
2524 }
2525 else if ( it.key() == "stroke-linejoin"_L1 && penJoinStyle )
2526 {
2527 *penJoinStyle = decodeSldLineJoinStyle( it.value() );
2528 }
2529 else if ( it.key() == "stroke-linecap"_L1 && penCapStyle )
2530 {
2531 *penCapStyle = decodeSldLineCapStyle( it.value() );
2532 }
2533 else if ( it.key() == "stroke-dasharray"_L1 )
2534 {
2535 const QVector<qreal> dashPattern = decodeSldRealVector( it.value() );
2536 if ( !dashPattern.isEmpty() )
2537 {
2538 // convert the dasharray to one of the QT pen style,
2539 // if no match is found then set pen style to CustomDashLine
2540 bool dashPatternFound = false;
2541
2542 if ( dashPattern.count() == 2 )
2543 {
2544 if ( dashPattern.at( 0 ) == 4.0 &&
2545 dashPattern.at( 1 ) == 2.0 )
2546 {
2547 penStyle = Qt::DashLine;
2548 dashPatternFound = true;
2549 }
2550 else if ( dashPattern.at( 0 ) == 1.0 &&
2551 dashPattern.at( 1 ) == 2.0 )
2552 {
2553 penStyle = Qt::DotLine;
2554 dashPatternFound = true;
2555 }
2556 }
2557 else if ( dashPattern.count() == 4 )
2558 {
2559 if ( dashPattern.at( 0 ) == 4.0 &&
2560 dashPattern.at( 1 ) == 2.0 &&
2561 dashPattern.at( 2 ) == 1.0 &&
2562 dashPattern.at( 3 ) == 2.0 )
2563 {
2564 penStyle = Qt::DashDotLine;
2565 dashPatternFound = true;
2566 }
2567 }
2568 else if ( dashPattern.count() == 6 )
2569 {
2570 if ( dashPattern.at( 0 ) == 4.0 &&
2571 dashPattern.at( 1 ) == 2.0 &&
2572 dashPattern.at( 2 ) == 1.0 &&
2573 dashPattern.at( 3 ) == 2.0 &&
2574 dashPattern.at( 4 ) == 1.0 &&
2575 dashPattern.at( 5 ) == 2.0 )
2576 {
2577 penStyle = Qt::DashDotDotLine;
2578 dashPatternFound = true;
2579 }
2580 }
2581
2582 // default case: set pen style to CustomDashLine
2583 if ( !dashPatternFound )
2584 {
2585 if ( customDashPattern )
2586 {
2587 penStyle = Qt::CustomDashLine;
2588 *customDashPattern = dashPattern;
2589 }
2590 else
2591 {
2592 QgsDebugMsgLevel( u"custom dash pattern required but not provided. Using default dash pattern."_s, 2 );
2593 penStyle = Qt::DashLine;
2594 }
2595 }
2596 }
2597 }
2598 else if ( it.key() == "stroke-dashoffset"_L1 && dashOffset )
2599 {
2600 bool ok;
2601 const double d = it.value().toDouble( &ok );
2602 if ( ok )
2603 *dashOffset = d;
2604 }
2605 }
2606
2607 return true;
2608}
2609
2610void QgsSymbolLayerUtils::externalGraphicToSld( QDomDocument &doc, QDomElement &element,
2611 const QString &path, const QString &mime,
2612 const QColor &color, double size )
2613{
2614 QDomElement externalGraphicElem = doc.createElement( u"se:ExternalGraphic"_s );
2615 element.appendChild( externalGraphicElem );
2616
2617 createOnlineResourceElement( doc, externalGraphicElem, path, mime );
2618
2619 //TODO: missing a way to handle svg color. Should use <se:ColorReplacement>
2620 Q_UNUSED( color )
2621
2622 if ( size >= 0 )
2623 {
2624 QDomElement sizeElem = doc.createElement( u"se:Size"_s );
2625 sizeElem.appendChild( doc.createTextNode( qgsDoubleToString( size ) ) );
2626 element.appendChild( sizeElem );
2627 }
2628}
2629
2630void QgsSymbolLayerUtils::parametricSvgToSld( QDomDocument &doc, QDomElement &graphicElem,
2631 const QString &path, const QColor &fillColor, double size, const QColor &strokeColor, double strokeWidth )
2632{
2633 QgsSldExportContext context;
2634 parametricSvgToSld( doc, graphicElem, path, fillColor, size, strokeColor, strokeWidth, context );
2635}
2636
2637void QgsSymbolLayerUtils::parametricSvgToSld( QDomDocument &doc, QDomElement &graphicElem, const QString &path, const QColor &fillColor, double size, const QColor &strokeColor, double strokeWidth, QgsSldExportContext &context )
2638{
2639 // Parametric SVG paths are an extension that few systems will understand, but se:Graphic allows for fallback
2640 // symbols, this encodes the full parametric path first, the pure shape second, and a mark with the right colors as
2641 // a last resort for systems that cannot do SVG at all
2642
2643 // encode parametric version with all coloring details (size is going to be encoded by the last fallback)
2644 graphicElem.appendChild( doc.createComment( u"Parametric SVG"_s ) );
2645 const QString parametricPath = getSvgParametricPath( path, fillColor, strokeColor, strokeWidth );
2646 QgsSymbolLayerUtils::externalGraphicToSld( doc, graphicElem, parametricPath, u"image/svg+xml"_s, fillColor, -1 );
2647 // also encode a fallback version without parameters, in case a renderer gets confused by the parameters
2648 graphicElem.appendChild( doc.createComment( u"Plain SVG fallback, no parameters"_s ) );
2649 QgsSymbolLayerUtils::externalGraphicToSld( doc, graphicElem, path, u"image/svg+xml"_s, fillColor, -1 );
2650 // finally encode a simple mark with the right colors/outlines for renderers that cannot do SVG at all
2651 graphicElem.appendChild( doc.createComment( u"Well known marker fallback"_s ) );
2652 QgsSymbolLayerUtils::wellKnownMarkerToSld( doc, graphicElem, u"square"_s, fillColor, strokeColor, Qt::PenStyle::SolidLine, context, strokeWidth, -1 );
2653
2654 // size is encoded here, it's part of se:Graphic, not attached to the single symbol
2655 if ( size >= 0 )
2656 {
2657 QDomElement sizeElem = doc.createElement( u"se:Size"_s );
2658 sizeElem.appendChild( doc.createTextNode( qgsDoubleToString( size ) ) );
2659 graphicElem.appendChild( sizeElem );
2660 }
2661}
2662
2663QString QgsSymbolLayerUtils::getSvgParametricPath( const QString &basePath, const QColor &fillColor, const QColor &strokeColor, double strokeWidth )
2664{
2665 QUrlQuery url;
2666 if ( fillColor.isValid() )
2667 {
2668 url.addQueryItem( u"fill"_s, fillColor.name() );
2669 url.addQueryItem( u"fill-opacity"_s, encodeSldAlpha( fillColor.alpha() ) );
2670 }
2671 else
2672 {
2673 url.addQueryItem( u"fill"_s, u"#000000"_s );
2674 url.addQueryItem( u"fill-opacity"_s, u"1"_s );
2675 }
2676 if ( strokeColor.isValid() )
2677 {
2678 url.addQueryItem( u"outline"_s, strokeColor.name() );
2679 url.addQueryItem( u"outline-opacity"_s, encodeSldAlpha( strokeColor.alpha() ) );
2680 }
2681 else
2682 {
2683 url.addQueryItem( u"outline"_s, u"#000000"_s );
2684 url.addQueryItem( u"outline-opacity"_s, u"1"_s );
2685 }
2686 url.addQueryItem( u"outline-width"_s, QString::number( strokeWidth ) );
2687 const QString params = url.toString( QUrl::FullyEncoded );
2688 if ( params.isEmpty() )
2689 {
2690 return basePath;
2691 }
2692 else
2693 {
2694 return basePath + "?" + params;
2695 }
2696}
2697
2699 QString &path, QString &mime,
2700 QColor &color, double &size )
2701{
2702 QgsDebugMsgLevel( u"Entered."_s, 4 );
2703 Q_UNUSED( color )
2704
2705 QDomElement externalGraphicElem = element.firstChildElement( u"ExternalGraphic"_s );
2706 if ( externalGraphicElem.isNull() )
2707 return false;
2708
2709 onlineResourceFromSldElement( externalGraphicElem, path, mime );
2710
2711 const QDomElement sizeElem = element.firstChildElement( u"Size"_s );
2712 if ( !sizeElem.isNull() )
2713 {
2714 bool ok;
2715 const double s = sizeElem.firstChild().nodeValue().toDouble( &ok );
2716 if ( ok )
2717 size = s;
2718 }
2719
2720 return true;
2721}
2722
2723void QgsSymbolLayerUtils::externalMarkerToSld( QDomDocument &doc, QDomElement &element,
2724 const QString &path, const QString &format, int *markIndex,
2725 const QColor &color, double size )
2726{
2727 QgsSldExportContext context;
2728 externalMarkerToSld( doc, element, path, format, context, markIndex, color, size );
2729}
2730
2731void QgsSymbolLayerUtils::externalMarkerToSld( QDomDocument &doc, QDomElement &element, const QString &path, const QString &format, QgsSldExportContext &context, int *markIndex, const QColor &color, double size )
2732{
2733 QDomElement markElem = doc.createElement( u"se:Mark"_s );
2734 element.appendChild( markElem );
2735
2736 createOnlineResourceElement( doc, markElem, path, format );
2737
2738 if ( markIndex )
2739 {
2740 QDomElement markIndexElem = doc.createElement( u"se:MarkIndex"_s );
2741 markIndexElem.appendChild( doc.createTextNode( QString::number( *markIndex ) ) );
2742 markElem.appendChild( markIndexElem );
2743 }
2744
2745 // <Fill>
2746 QDomElement fillElem = doc.createElement( u"se:Fill"_s );
2747 fillToSld( doc, fillElem, context, Qt::SolidPattern, color );
2748 markElem.appendChild( fillElem );
2749
2750 // <Size>
2751 if ( !qgsDoubleNear( size, 0.0 ) && size > 0 )
2752 {
2753 QDomElement sizeElem = doc.createElement( u"se:Size"_s );
2754 sizeElem.appendChild( doc.createTextNode( qgsDoubleToString( size ) ) );
2755 element.appendChild( sizeElem );
2756 }
2757}
2758
2760 QString &path, QString &format, int &markIndex,
2761 QColor &color, double &size )
2762{
2763 QgsDebugMsgLevel( u"Entered."_s, 4 );
2764
2765 color = QColor();
2766 markIndex = -1;
2767 size = -1;
2768
2769 QDomElement markElem = element.firstChildElement( u"Mark"_s );
2770 if ( markElem.isNull() )
2771 return false;
2772
2773 onlineResourceFromSldElement( markElem, path, format );
2774
2775 const QDomElement markIndexElem = markElem.firstChildElement( u"MarkIndex"_s );
2776 if ( !markIndexElem.isNull() )
2777 {
2778 bool ok;
2779 const int i = markIndexElem.firstChild().nodeValue().toInt( &ok );
2780 if ( ok )
2781 markIndex = i;
2782 }
2783
2784 // <Fill>
2785 QDomElement fillElem = markElem.firstChildElement( u"Fill"_s );
2786 Qt::BrushStyle b = Qt::SolidPattern;
2787 fillFromSld( fillElem, b, color );
2788 // ignore brush style, solid expected
2789
2790 // <Size>
2791 const QDomElement sizeElem = element.firstChildElement( u"Size"_s );
2792 if ( !sizeElem.isNull() )
2793 {
2794 bool ok;
2795 const double s = sizeElem.firstChild().nodeValue().toDouble( &ok );
2796 if ( ok )
2797 size = s;
2798 }
2799
2800 return true;
2801}
2802
2803void QgsSymbolLayerUtils::wellKnownMarkerToSld( QDomDocument &doc, QDomElement &element,
2804 const QString &name, const QColor &color, const QColor &strokeColor, Qt::PenStyle strokeStyle,
2805 double strokeWidth, double size )
2806{
2807 QgsSldExportContext context;
2808 wellKnownMarkerToSld( doc, element, name, color, strokeColor, strokeStyle, context, strokeWidth, size );
2809}
2810
2811void QgsSymbolLayerUtils::wellKnownMarkerToSld( QDomDocument &doc, QDomElement &element, const QString &name, const QColor &color, const QColor &strokeColor, Qt::PenStyle strokeStyle, QgsSldExportContext &context, double strokeWidth, double size )
2812{
2813 QDomElement markElem = doc.createElement( u"se:Mark"_s );
2814 element.appendChild( markElem );
2815
2816 QDomElement wellKnownNameElem = doc.createElement( u"se:WellKnownName"_s );
2817 wellKnownNameElem.appendChild( doc.createTextNode( name ) );
2818 markElem.appendChild( wellKnownNameElem );
2819
2820 // <Fill>
2821 if ( color.isValid() )
2822 {
2823 QDomElement fillElem = doc.createElement( u"se:Fill"_s );
2824 fillToSld( doc, fillElem, context, Qt::SolidPattern, color );
2825 markElem.appendChild( fillElem );
2826 }
2827
2828 // <Stroke>
2829 if ( strokeColor.isValid() )
2830 {
2831 QDomElement strokeElem = doc.createElement( u"se:Stroke"_s );
2832 lineToSld( doc, strokeElem, strokeStyle, strokeColor, context, strokeWidth );
2833 markElem.appendChild( strokeElem );
2834 }
2835
2836 // <Size>
2837 if ( !qgsDoubleNear( size, 0.0 ) && size > 0 )
2838 {
2839 QDomElement sizeElem = doc.createElement( u"se:Size"_s );
2840 sizeElem.appendChild( doc.createTextNode( qgsDoubleToString( size ) ) );
2841 element.appendChild( sizeElem );
2842 }
2843}
2844
2846 QString &name, QColor &color, QColor &strokeColor, Qt::PenStyle &strokeStyle,
2847 double &strokeWidth, double &size )
2848{
2849 QgsDebugMsgLevel( u"Entered."_s, 4 );
2850
2851 name = u"square"_s;
2852 color = QColor();
2853 strokeColor = QColor( 0, 0, 0 );
2854 strokeWidth = 1;
2855 size = 6;
2856
2857 const QDomElement markElem = element.firstChildElement( u"Mark"_s );
2858 if ( markElem.isNull() )
2859 return false;
2860
2861 const QDomElement wellKnownNameElem = markElem.firstChildElement( u"WellKnownName"_s );
2862 if ( !wellKnownNameElem.isNull() )
2863 {
2864 name = wellKnownNameElem.firstChild().nodeValue();
2865 QgsDebugMsgLevel( "found Mark with well known name: " + name, 2 );
2866 }
2867
2868 // <Fill>
2869 QDomElement fillElem = markElem.firstChildElement( u"Fill"_s );
2870 Qt::BrushStyle b = Qt::SolidPattern;
2871 fillFromSld( fillElem, b, color );
2872 // ignore brush style, solid expected
2873
2874 // <Stroke>
2875 QDomElement strokeElem = markElem.firstChildElement( u"Stroke"_s );
2876 lineFromSld( strokeElem, strokeStyle, strokeColor, strokeWidth );
2877 // ignore stroke style, solid expected
2878
2879 // <Size>
2880 const QDomElement sizeElem = element.firstChildElement( u"Size"_s );
2881 if ( !sizeElem.isNull() )
2882 {
2883 bool ok;
2884 const double s = sizeElem.firstChild().nodeValue().toDouble( &ok );
2885 if ( ok )
2886 size = s;
2887 }
2888
2889 return true;
2890}
2891
2892void QgsSymbolLayerUtils::createRotationElement( QDomDocument &doc, QDomElement &element, const QString &rotationFunc )
2893{
2894 QgsSldExportContext context;
2895 createRotationElement( doc, element, rotationFunc, context );
2896}
2897
2898void QgsSymbolLayerUtils::createRotationElement( QDomDocument &doc, QDomElement &element, const QString &rotationFunc, QgsSldExportContext &context )
2899{
2900 if ( !rotationFunc.isEmpty() )
2901 {
2902 QDomElement rotationElem = doc.createElement( u"se:Rotation"_s );
2903 createExpressionElement( doc, rotationElem, rotationFunc, context );
2904 element.appendChild( rotationElem );
2905 }
2906}
2907
2908bool QgsSymbolLayerUtils::rotationFromSldElement( QDomElement &element, QString &rotationFunc )
2909{
2910 QDomElement rotationElem = element.firstChildElement( u"Rotation"_s );
2911 if ( !rotationElem.isNull() )
2912 {
2913 return functionFromSldElement( rotationElem, rotationFunc );
2914 }
2915 return true;
2916}
2917
2918void QgsSymbolLayerUtils::createOpacityElement( QDomDocument &doc, QDomElement &element, const QString &alphaFunc )
2919{
2920 QgsSldExportContext context;
2921 createOpacityElement( doc, element, alphaFunc, context );
2922}
2923
2924void QgsSymbolLayerUtils::createOpacityElement( QDomDocument &doc, QDomElement &element, const QString &alphaFunc, QgsSldExportContext &context )
2925{
2926 if ( !alphaFunc.isEmpty() )
2927 {
2928 QDomElement opacityElem = doc.createElement( u"se:Opacity"_s );
2929 createExpressionElement( doc, opacityElem, alphaFunc, context );
2930 element.appendChild( opacityElem );
2931 }
2932}
2933
2934bool QgsSymbolLayerUtils::opacityFromSldElement( QDomElement &element, QString &alphaFunc )
2935{
2936 QDomElement opacityElem = element.firstChildElement( u"Opacity"_s );
2937 if ( !opacityElem.isNull() )
2938 {
2939 return functionFromSldElement( opacityElem, alphaFunc );
2940 }
2941 return true;
2942}
2943
2944void QgsSymbolLayerUtils::createDisplacementElement( QDomDocument &doc, QDomElement &element, QPointF offset )
2945{
2946 if ( offset.isNull() )
2947 return;
2948
2949 QDomElement displacementElem = doc.createElement( u"se:Displacement"_s );
2950 element.appendChild( displacementElem );
2951
2952 QDomElement dispXElem = doc.createElement( u"se:DisplacementX"_s );
2953 dispXElem.appendChild( doc.createTextNode( qgsDoubleToString( offset.x(), 2 ) ) );
2954
2955 QDomElement dispYElem = doc.createElement( u"se:DisplacementY"_s );
2956 dispYElem.appendChild( doc.createTextNode( qgsDoubleToString( offset.y(), 2 ) ) );
2957
2958 displacementElem.appendChild( dispXElem );
2959 displacementElem.appendChild( dispYElem );
2960}
2961
2962void QgsSymbolLayerUtils::createAnchorPointElement( QDomDocument &doc, QDomElement &element, QPointF anchor )
2963{
2964 // anchor is not tested for null, (0,0) is _not_ the default value (0.5, 0) is.
2965
2966 QDomElement anchorElem = doc.createElement( u"se:AnchorPoint"_s );
2967 element.appendChild( anchorElem );
2968
2969 QDomElement anchorXElem = doc.createElement( u"se:AnchorPointX"_s );
2970 anchorXElem.appendChild( doc.createTextNode( qgsDoubleToString( anchor.x() ) ) );
2971
2972 QDomElement anchorYElem = doc.createElement( u"se:AnchorPointY"_s );
2973 anchorYElem.appendChild( doc.createTextNode( qgsDoubleToString( anchor.y() ) ) );
2974
2975 anchorElem.appendChild( anchorXElem );
2976 anchorElem.appendChild( anchorYElem );
2977}
2978
2979bool QgsSymbolLayerUtils::displacementFromSldElement( QDomElement &element, QPointF &offset )
2980{
2981 offset = QPointF( 0, 0 );
2982
2983 const QDomElement displacementElem = element.firstChildElement( u"Displacement"_s );
2984 if ( displacementElem.isNull() )
2985 return true;
2986
2987 const QDomElement dispXElem = displacementElem.firstChildElement( u"DisplacementX"_s );
2988 if ( !dispXElem.isNull() )
2989 {
2990 bool ok;
2991 const double offsetX = dispXElem.firstChild().nodeValue().toDouble( &ok );
2992 if ( ok )
2993 offset.setX( offsetX );
2994 }
2995
2996 const QDomElement dispYElem = displacementElem.firstChildElement( u"DisplacementY"_s );
2997 if ( !dispYElem.isNull() )
2998 {
2999 bool ok;
3000 const double offsetY = dispYElem.firstChild().nodeValue().toDouble( &ok );
3001 if ( ok )
3002 offset.setY( offsetY );
3003 }
3004
3005 return true;
3006}
3007
3008void QgsSymbolLayerUtils::labelTextToSld( QDomDocument &doc, QDomElement &element,
3009 const QString &label, const QFont &font,
3010 const QColor &color, double size )
3011{
3012 QgsSldExportContext context;
3013 labelTextToSld( doc, element, label, font, context, color, size );
3014}
3015
3016void QgsSymbolLayerUtils::labelTextToSld( QDomDocument &doc, QDomElement &element, const QString &label, const QFont &font, QgsSldExportContext &context, const QColor &color, double size )
3017{
3018 QDomElement labelElem = doc.createElement( u"se:Label"_s );
3019 labelElem.appendChild( doc.createTextNode( label ) );
3020 element.appendChild( labelElem );
3021
3022 QDomElement fontElem = doc.createElement( u"se:Font"_s );
3023 element.appendChild( fontElem );
3024
3025 fontElem.appendChild( createSvgParameterElement( doc, u"font-family"_s, font.family() ) );
3026#if 0
3027 fontElem.appendChild( createSldParameterElement( doc, "font-style", encodeSldFontStyle( font.style() ) ) );
3028 fontElem.appendChild( createSldParameterElement( doc, "font-weight", encodeSldFontWeight( font.weight() ) ) );
3029#endif
3030 fontElem.appendChild( createSvgParameterElement( doc, u"font-size"_s, QString::number( size ) ) );
3031
3032 // <Fill>
3033 if ( color.isValid() )
3034 {
3035 QDomElement fillElem = doc.createElement( u"Fill"_s );
3036 fillToSld( doc, fillElem, context, Qt::SolidPattern, color );
3037 element.appendChild( fillElem );
3038 }
3039}
3040
3041QString QgsSymbolLayerUtils::ogrFeatureStylePen( double width, double mmScaleFactor, double mapUnitScaleFactor, const QColor &c,
3042 Qt::PenJoinStyle joinStyle,
3043 Qt::PenCapStyle capStyle,
3044 double offset,
3045 const QVector<qreal> *dashPattern )
3046{
3047 QString penStyle;
3048 penStyle.append( "PEN(" );
3049 penStyle.append( "c:" );
3050 penStyle.append( c.name() );
3051 penStyle.append( ",w:" );
3052 //dxf driver writes ground units as mm? Should probably be changed in ogr
3053 penStyle.append( QString::number( width * mmScaleFactor ) );
3054 penStyle.append( "mm" );
3055
3056 //dash dot vector
3057 if ( dashPattern && !dashPattern->isEmpty() )
3058 {
3059 penStyle.append( ",p:\"" );
3060 QVector<qreal>::const_iterator pIt = dashPattern->constBegin();
3061 for ( ; pIt != dashPattern->constEnd(); ++pIt )
3062 {
3063 if ( pIt != dashPattern->constBegin() )
3064 {
3065 penStyle.append( ' ' );
3066 }
3067 penStyle.append( QString::number( *pIt * mapUnitScaleFactor ) );
3068 penStyle.append( 'g' );
3069 }
3070 penStyle.append( '\"' );
3071 }
3072
3073 //cap
3074 penStyle.append( ",cap:" );
3075 switch ( capStyle )
3076 {
3077 case Qt::SquareCap:
3078 penStyle.append( 'p' );
3079 break;
3080 case Qt::RoundCap:
3081 penStyle.append( 'r' );
3082 break;
3083 case Qt::FlatCap:
3084 default:
3085 penStyle.append( 'b' );
3086 }
3087
3088 //join
3089 penStyle.append( ",j:" );
3090 switch ( joinStyle )
3091 {
3092 case Qt::BevelJoin:
3093 penStyle.append( 'b' );
3094 break;
3095 case Qt::RoundJoin:
3096 penStyle.append( 'r' );
3097 break;
3098 case Qt::MiterJoin:
3099 default:
3100 penStyle.append( 'm' );
3101 }
3102
3103 //offset
3104 if ( !qgsDoubleNear( offset, 0.0 ) )
3105 {
3106 penStyle.append( ",dp:" );
3107 penStyle.append( QString::number( offset * mapUnitScaleFactor ) );
3108 penStyle.append( 'g' );
3109 }
3110
3111 penStyle.append( ')' );
3112 return penStyle;
3113}
3114
3115QString QgsSymbolLayerUtils::ogrFeatureStyleBrush( const QColor &fillColor )
3116{
3117 QString brushStyle;
3118 brushStyle.append( "BRUSH(" );
3119 brushStyle.append( "fc:" );
3120 brushStyle.append( fillColor.name() );
3121 brushStyle.append( ')' );
3122 return brushStyle;
3123}
3124
3125void QgsSymbolLayerUtils::createGeometryElement( QDomDocument &doc, QDomElement &element, const QString &geomFunc )
3126{
3127 QgsSldExportContext context;
3128 createGeometryElement( doc, element, geomFunc, context );
3129}
3130
3131void QgsSymbolLayerUtils::createGeometryElement( QDomDocument &doc, QDomElement &element, const QString &geomFunc, QgsSldExportContext &context )
3132{
3133 if ( geomFunc.isEmpty() )
3134 return;
3135
3136 QDomElement geometryElem = doc.createElement( u"Geometry"_s );
3137 element.appendChild( geometryElem );
3138
3139 /* About using a function within the Geometry tag.
3140 *
3141 * The SLD specification <= 1.1 is vague:
3142 * "In principle, a fixed geometry could be defined using GML or
3143 * operators could be defined for computing the geometry from
3144 * references or literals. However, using a feature property directly
3145 * is by far the most commonly useful method."
3146 *
3147 * Even if it seems that specs should take care all the possible cases,
3148 * looking at the XML schema fragment that encodes the Geometry element,
3149 * it has to be a PropertyName element:
3150 * <xsd:element name="Geometry">
3151 * <xsd:complexType>
3152 * <xsd:sequence>
3153 * <xsd:element ref="ogc:PropertyName"/>
3154 * </xsd:sequence>
3155 * </xsd:complexType>
3156 * </xsd:element>
3157 *
3158 * Anyway we will use a ogc:Function to handle geometry transformations
3159 * like offset, centroid, ...
3160 */
3161
3162 createExpressionElement( doc, geometryElem, geomFunc, context );
3163}
3164
3165bool QgsSymbolLayerUtils::geometryFromSldElement( QDomElement &element, QString &geomFunc )
3166{
3167 QDomElement geometryElem = element.firstChildElement( u"Geometry"_s );
3168 if ( geometryElem.isNull() )
3169 return true;
3170
3171 return functionFromSldElement( geometryElem, geomFunc );
3172}
3173
3174bool QgsSymbolLayerUtils::createExpressionElement( QDomDocument &doc, QDomElement &element, const QString &function )
3175{
3176 QgsSldExportContext context;
3177 return createExpressionElement( doc, element, function, context );
3178}
3179
3180bool QgsSymbolLayerUtils::createExpressionElement( QDomDocument &doc, QDomElement &element, const QString &function, QgsSldExportContext &context )
3181{
3182 // let's use QgsExpression to generate the SLD for the function
3183 const QgsExpression expr( function );
3184 if ( expr.hasParserError() )
3185 {
3186 context.pushError( QObject::tr( "Parser error encountered when converting expression to SLD: %1 - Expression was: %2" ).arg( expr.parserErrorString(), function ) );
3187 return false;
3188 }
3189 const QDomElement filterElem = QgsOgcUtils::expressionToOgcExpression( expr, doc );
3190 if ( !filterElem.isNull() )
3191 element.appendChild( filterElem );
3192 return true;
3193}
3194
3195bool QgsSymbolLayerUtils::createFunctionElement( QDomDocument &doc, QDomElement &element, const QString &function )
3196{
3197 QgsSldExportContext context;
3198 return createFunctionElement( doc, element, function, context );
3199}
3200
3201bool QgsSymbolLayerUtils::createFunctionElement( QDomDocument &doc, QDomElement &element, const QString &function, QgsSldExportContext &context )
3202{
3203 // else rule is not a valid expression
3204 if ( function == "ELSE"_L1 )
3205 {
3206 const QDomElement filterElem = QgsOgcUtils::elseFilterExpression( doc );
3207 element.appendChild( filterElem );
3208 return true;
3209 }
3210 else
3211 {
3212 // let's use QgsExpression to generate the SLD for the function
3213 const QgsExpression expr( function );
3214 if ( expr.hasParserError() )
3215 {
3216 context.pushError( QObject::tr( "Parser error encountered when converting expression to SLD: %1 - Expression was: %2" ).arg( expr.parserErrorString(), function ) );
3217 return false;
3218 }
3219 const QDomElement filterElem = QgsOgcUtils::expressionToOgcFilter( expr, doc );
3220 if ( !filterElem.isNull() )
3221 element.appendChild( filterElem );
3222 return true;
3223 }
3224}
3225
3226bool QgsSymbolLayerUtils::functionFromSldElement( QDomElement &element, QString &function )
3227{
3228 // check if ogc:Filter or contains ogc:Filters
3229 QDomElement elem = element;
3230 if ( element.tagName() != "Filter"_L1 )
3231 {
3232 const QDomNodeList filterNodes = element.elementsByTagName( u"Filter"_s );
3233 if ( !filterNodes.isEmpty() )
3234 {
3235 elem = filterNodes.at( 0 ).toElement();
3236 }
3237 }
3238
3239 if ( elem.isNull() )
3240 {
3241 return false;
3242 }
3243
3244 // parse ogc:Filter
3246 if ( !expr )
3247 return false;
3248
3249 const bool valid = !expr->hasParserError();
3250 if ( !valid )
3251 {
3252 QgsDebugError( "parser error: " + expr->parserErrorString() );
3253 }
3254 else
3255 {
3256 function = expr->expression();
3257 }
3258
3259 delete expr;
3260 return valid;
3261}
3262
3263void QgsSymbolLayerUtils::createOnlineResourceElement( QDomDocument &doc, QDomElement &element,
3264 const QString &path, const QString &format )
3265{
3266 // get resource url or relative path
3267 const QString url = svgSymbolPathToName( path, QgsPathResolver() );
3268 QDomElement onlineResourceElem = doc.createElement( u"se:OnlineResource"_s );
3269 onlineResourceElem.setAttribute( u"xlink:type"_s, u"simple"_s );
3270 onlineResourceElem.setAttribute( u"xlink:href"_s, url );
3271 element.appendChild( onlineResourceElem );
3272
3273 QDomElement formatElem = doc.createElement( u"se:Format"_s );
3274 formatElem.appendChild( doc.createTextNode( format ) );
3275 element.appendChild( formatElem );
3276}
3277
3278bool QgsSymbolLayerUtils::onlineResourceFromSldElement( QDomElement &element, QString &path, QString &format )
3279{
3280 QgsDebugMsgLevel( u"Entered."_s, 4 );
3281
3282 const QDomElement onlineResourceElem = element.firstChildElement( u"OnlineResource"_s );
3283 if ( onlineResourceElem.isNull() )
3284 return false;
3285
3286 path = QUrl::fromPercentEncoding( onlineResourceElem.attributeNS( u"http://www.w3.org/1999/xlink"_s, u"href"_s ).toUtf8() );
3287
3288 const QDomElement formatElem = element.firstChildElement( u"Format"_s );
3289 if ( formatElem.isNull() )
3290 return false; // OnlineResource requires a Format sibling element
3291
3292 format = formatElem.firstChild().nodeValue();
3293 return true;
3294}
3295
3296
3297QDomElement QgsSymbolLayerUtils::createSvgParameterElement( QDomDocument &doc, const QString &name, const QString &value )
3298{
3299 QDomElement nodeElem = doc.createElement( u"se:SvgParameter"_s );
3300 nodeElem.setAttribute( u"name"_s, name );
3301 nodeElem.appendChild( doc.createTextNode( value ) );
3302 return nodeElem;
3303}
3304
3306{
3307 QgsStringMap params;
3308 QString value;
3309
3310 QDomElement paramElem = element.firstChildElement();
3311 while ( !paramElem.isNull() )
3312 {
3313 if ( paramElem.localName() == "SvgParameter"_L1 || paramElem.localName() == "CssParameter"_L1 )
3314 {
3315 const QString name = paramElem.attribute( u"name"_s );
3316 if ( paramElem.firstChild().nodeType() == QDomNode::TextNode )
3317 {
3318 value = paramElem.firstChild().nodeValue();
3319 }
3320 else
3321 {
3322 if ( paramElem.firstChild().nodeType() == QDomNode::ElementNode &&
3323 paramElem.firstChild().localName() == "Literal"_L1 )
3324 {
3325 QgsDebugMsgLevel( paramElem.firstChild().localName(), 3 );
3326 value = paramElem.firstChild().firstChild().nodeValue();
3327 }
3328 else
3329 {
3330 QgsDebugError( u"unexpected child of %1"_s.arg( paramElem.localName() ) );
3331 }
3332 }
3333
3334 if ( !name.isEmpty() && !value.isEmpty() )
3335 params[ name ] = value;
3336 }
3337
3338 paramElem = paramElem.nextSiblingElement();
3339 }
3340
3341 return params;
3342}
3343
3344QDomElement QgsSymbolLayerUtils::createVendorOptionElement( QDomDocument &doc, const QString &name, const QString &value )
3345{
3346 QDomElement nodeElem = doc.createElement( u"se:VendorOption"_s );
3347 nodeElem.setAttribute( u"name"_s, name );
3348 nodeElem.appendChild( doc.createTextNode( value ) );
3349 return nodeElem;
3350}
3351
3353{
3354 QgsStringMap params;
3355
3356 QDomElement paramElem = element.firstChildElement( u"VendorOption"_s );
3357 while ( !paramElem.isNull() )
3358 {
3359 const QString name = paramElem.attribute( u"name"_s );
3360 const QString value = paramElem.firstChild().nodeValue();
3361
3362 if ( !name.isEmpty() && !value.isEmpty() )
3363 params[ name ] = value;
3364
3365 paramElem = paramElem.nextSiblingElement( u"VendorOption"_s );
3366 }
3367
3368 return params;
3369}
3370
3371
3372QVariantMap QgsSymbolLayerUtils::parseProperties( const QDomElement &element )
3373{
3374 const QVariant newSymbols = QgsXmlUtils::readVariant( element.firstChildElement( u"Option"_s ) );
3375 if ( newSymbols.userType() == QMetaType::Type::QVariantMap )
3376 {
3377 return newSymbols.toMap();
3378 }
3379 else
3380 {
3381 // read old style of writing properties
3382 // backward compatibility with project saved in <= 3.16
3383 QVariantMap props;
3384 QDomElement e = element.firstChildElement();
3385 while ( !e.isNull() )
3386 {
3387 if ( e.tagName() == "prop"_L1 )
3388 {
3389 const QString propKey = e.attribute( u"k"_s );
3390 const QString propValue = e.attribute( u"v"_s );
3391 props[propKey] = propValue;
3392 }
3393 e = e.nextSiblingElement();
3394 }
3395 return props;
3396 }
3397}
3398
3399
3400void QgsSymbolLayerUtils::saveProperties( QVariantMap props, QDomDocument &doc, QDomElement &element )
3401{
3402 element.appendChild( QgsXmlUtils::writeVariant( props, doc ) );
3403}
3404
3406{
3407 // go through symbols one-by-one and load them
3408
3409 QgsSymbolMap symbols;
3410 QDomElement e = element.firstChildElement();
3411
3412 while ( !e.isNull() )
3413 {
3414 if ( e.tagName() == "symbol"_L1 )
3415 {
3416 std::unique_ptr< QgsSymbol > symbol = QgsSymbolLayerUtils::loadSymbol( e, context );
3417 if ( symbol )
3418 symbols.insert( e.attribute( u"name"_s ), symbol.release() );
3419 }
3420 else
3421 {
3422 QgsDebugError( "unknown tag: " + e.tagName() );
3423 }
3424 e = e.nextSiblingElement();
3425 }
3426
3427
3428 // now walk through the list of symbols and find those prefixed with @
3429 // these symbols are sub-symbols of some other symbol layers
3430 // e.g. symbol named "@foo@1" is sub-symbol of layer 1 in symbol "foo"
3431 QStringList subsymbols;
3432
3433 for ( QMap<QString, QgsSymbol *>::iterator it = symbols.begin(); it != symbols.end(); ++it )
3434 {
3435 if ( it.key()[0] != '@' )
3436 continue;
3437
3438 // add to array (for deletion)
3439 subsymbols.append( it.key() );
3440
3441 QStringList parts = it.key().split( '@' );
3442 if ( parts.count() < 3 )
3443 {
3444 QgsDebugError( "found subsymbol with invalid name: " + it.key() );
3445 delete it.value(); // we must delete it
3446 continue; // some invalid syntax
3447 }
3448 const QString symname = parts[1];
3449 const int symlayer = parts[2].toInt();
3450
3451 if ( !symbols.contains( symname ) )
3452 {
3453 QgsDebugError( "subsymbol references invalid symbol: " + symname );
3454 delete it.value(); // we must delete it
3455 continue;
3456 }
3457
3458 QgsSymbol *sym = symbols[symname];
3459 if ( symlayer < 0 || symlayer >= sym->symbolLayerCount() )
3460 {
3461 QgsDebugError( "subsymbol references invalid symbol layer: " + QString::number( symlayer ) );
3462 delete it.value(); // we must delete it
3463 continue;
3464 }
3465
3466 // set subsymbol takes ownership
3467 const bool res = sym->symbolLayer( symlayer )->setSubSymbol( it.value() );
3468 if ( !res )
3469 {
3470 QgsDebugError( "symbol layer refused subsymbol: " + it.key() );
3471 }
3472
3473
3474 }
3475
3476 // now safely remove sub-symbol entries (they have been already deleted or the ownership was taken away)
3477 for ( int i = 0; i < subsymbols.count(); i++ )
3478 symbols.take( subsymbols[i] );
3479
3480 return symbols;
3481}
3482
3483QDomElement QgsSymbolLayerUtils::saveSymbols( QgsSymbolMap &symbols, const QString &tagName, QDomDocument &doc, const QgsReadWriteContext &context )
3484{
3485 QDomElement symbolsElem = doc.createElement( tagName );
3486
3487 // save symbols
3488 for ( QMap<QString, QgsSymbol *>::iterator its = symbols.begin(); its != symbols.end(); ++its )
3489 {
3490 const QDomElement symEl = saveSymbol( its.key(), its.value(), doc, context );
3491 symbolsElem.appendChild( symEl );
3492 }
3493
3494 return symbolsElem;
3495}
3496
3498{
3499 qDeleteAll( symbols );
3500 symbols.clear();
3501}
3502
3504{
3505 if ( !symbol )
3506 return nullptr;
3507
3508 std::unique_ptr< QMimeData >mimeData( new QMimeData );
3509
3510 QDomDocument symbolDoc;
3511 const QDomElement symbolElem = saveSymbol( u"symbol"_s, symbol, symbolDoc, QgsReadWriteContext() );
3512 symbolDoc.appendChild( symbolElem );
3513 mimeData->setText( symbolDoc.toString() );
3514
3515 mimeData->setImageData( symbolPreviewPixmap( symbol, QSize( 100, 100 ), 18 ).toImage() );
3516 mimeData->setColorData( symbol->color() );
3517
3518 return mimeData.release();
3519}
3520
3521std::unique_ptr< QgsSymbol > QgsSymbolLayerUtils::symbolFromMimeData( const QMimeData *data )
3522{
3523 if ( !data )
3524 return nullptr;
3525
3526 const QString text = data->text();
3527 if ( !text.isEmpty() )
3528 {
3529 QDomDocument doc;
3530 QDomElement elem;
3531
3532 if ( doc.setContent( text ) )
3533 {
3534 elem = doc.documentElement();
3535
3536 if ( elem.nodeName() != "symbol"_L1 )
3537 elem = elem.firstChildElement( u"symbol"_s );
3538
3539 return loadSymbol( elem, QgsReadWriteContext() );
3540 }
3541 }
3542 return nullptr;
3543}
3544
3545
3546std::unique_ptr< QgsColorRamp > QgsSymbolLayerUtils::loadColorRamp( QDomElement &element )
3547{
3548 const QString rampType = element.attribute( u"type"_s );
3549
3550 // parse properties
3551 const QVariantMap props = QgsSymbolLayerUtils::parseProperties( element );
3552
3553 if ( rampType == QgsGradientColorRamp::typeString() )
3554 return std::unique_ptr< QgsColorRamp >( QgsGradientColorRamp::create( props ) );
3555 else if ( rampType == QgsLimitedRandomColorRamp::typeString() )
3556 return std::unique_ptr< QgsColorRamp >( QgsLimitedRandomColorRamp::create( props ) );
3557 else if ( rampType == QgsColorBrewerColorRamp::typeString() )
3558 return std::unique_ptr< QgsColorRamp >( QgsColorBrewerColorRamp::create( props ) );
3559 else if ( rampType == QgsCptCityColorRamp::typeString() )
3560 return std::unique_ptr< QgsColorRamp >( QgsCptCityColorRamp::create( props ) );
3561 else if ( rampType == QgsPresetSchemeColorRamp::typeString() )
3562 return std::unique_ptr< QgsColorRamp >( QgsPresetSchemeColorRamp::create( props ) );
3563 else
3564 {
3565 QgsDebugError( "unknown colorramp type " + rampType );
3566 return nullptr;
3567 }
3568}
3569
3570
3571QDomElement QgsSymbolLayerUtils::saveColorRamp( const QString &name, const QgsColorRamp *ramp, QDomDocument &doc )
3572{
3573 QDomElement rampEl = doc.createElement( u"colorramp"_s );
3574 rampEl.setAttribute( u"type"_s, ramp->type() );
3575 rampEl.setAttribute( u"name"_s, name );
3576
3577 QgsSymbolLayerUtils::saveProperties( ramp->properties(), doc, rampEl );
3578 return rampEl;
3579}
3580
3581QVariant QgsSymbolLayerUtils::colorRampToVariant( const QString &name, QgsColorRamp *ramp )
3582{
3583 QVariantMap rampMap;
3584
3585 rampMap.insert( u"type"_s, ramp->type() );
3586 rampMap.insert( u"name"_s, name );
3587
3588 const QVariantMap properties = ramp->properties();
3589
3590 QVariantMap propertyMap;
3591 for ( auto property = properties.constBegin(); property != properties.constEnd(); ++property )
3592 {
3593 propertyMap.insert( property.key(), property.value() );
3594 }
3595
3596 rampMap.insert( u"properties"_s, propertyMap );
3597 return rampMap;
3598}
3599
3600std::unique_ptr< QgsColorRamp > QgsSymbolLayerUtils::loadColorRamp( const QVariant &value )
3601{
3602 const QVariantMap rampMap = value.toMap();
3603
3604 const QString rampType = rampMap.value( u"type"_s ).toString();
3605
3606 // parse properties
3607 const QVariantMap propertyMap = rampMap.value( u"properties"_s ).toMap();
3608 QVariantMap props;
3609
3610 for ( auto property = propertyMap.constBegin(); property != propertyMap.constEnd(); ++property )
3611 {
3612 props.insert( property.key(), property.value().toString() );
3613 }
3614
3615 if ( rampType == QgsGradientColorRamp::typeString() )
3616 return std::unique_ptr< QgsColorRamp >( QgsGradientColorRamp::create( props ) );
3617 else if ( rampType == QgsLimitedRandomColorRamp::typeString() )
3618 return std::unique_ptr< QgsColorRamp >( QgsLimitedRandomColorRamp::create( props ) );
3619 else if ( rampType == QgsColorBrewerColorRamp::typeString() )
3620 return std::unique_ptr< QgsColorRamp >( QgsColorBrewerColorRamp::create( props ) );
3621 else if ( rampType == QgsCptCityColorRamp::typeString() )
3622 return std::unique_ptr< QgsColorRamp >( QgsCptCityColorRamp::create( props ) );
3623 else if ( rampType == QgsPresetSchemeColorRamp::typeString() )
3624 return std::unique_ptr< QgsColorRamp >( QgsPresetSchemeColorRamp::create( props ) );
3625 else
3626 {
3627 QgsDebugError( "unknown colorramp type " + rampType );
3628 return nullptr;
3629 }
3630}
3631
3632QString QgsSymbolLayerUtils::colorToName( const QColor &color )
3633{
3634 if ( !color.isValid() )
3635 {
3636 return QString();
3637 }
3638
3639 //TODO - utilize a color names database (such as X11) to return nicer names
3640 //for now, just return hex codes
3641 return color.name();
3642}
3643
3644QList<QColor> QgsSymbolLayerUtils::parseColorList( const QString &colorStr )
3645{
3646 QList<QColor> colors;
3647
3648 //try splitting string at commas, spaces or newlines
3649 const thread_local QRegularExpression sepCommaSpaceRegExp( "(,|\\s)" );
3650 QStringList components = colorStr.simplified().split( sepCommaSpaceRegExp );
3651 QStringList::iterator it = components.begin();
3652 for ( ; it != components.end(); ++it )
3653 {
3654 const QColor result = parseColor( *it, true );
3655 if ( result.isValid() )
3656 {
3657 colors << result;
3658 }
3659 }
3660 if ( colors.length() > 0 )
3661 {
3662 return colors;
3663 }
3664
3665 //try splitting string at commas or newlines
3666 const thread_local QRegularExpression sepCommaRegExp( "(,|\n)" );
3667 components = colorStr.split( sepCommaRegExp );
3668 it = components.begin();
3669 for ( ; it != components.end(); ++it )
3670 {
3671 const QColor result = parseColor( *it, true );
3672 if ( result.isValid() )
3673 {
3674 colors << result;
3675 }
3676 }
3677 if ( colors.length() > 0 )
3678 {
3679 return colors;
3680 }
3681
3682 //try splitting string at whitespace or newlines
3683 components = colorStr.simplified().split( QString( ' ' ) );
3684 it = components.begin();
3685 for ( ; it != components.end(); ++it )
3686 {
3687 const QColor result = parseColor( *it, true );
3688 if ( result.isValid() )
3689 {
3690 colors << result;
3691 }
3692 }
3693 if ( colors.length() > 0 )
3694 {
3695 return colors;
3696 }
3697
3698 //try splitting string just at newlines
3699 components = colorStr.split( '\n' );
3700 it = components.begin();
3701 for ( ; it != components.end(); ++it )
3702 {
3703 const QColor result = parseColor( *it, true );
3704 if ( result.isValid() )
3705 {
3706 colors << result;
3707 }
3708 }
3709
3710 return colors;
3711}
3712
3713QMimeData *QgsSymbolLayerUtils::colorToMimeData( const QColor &color )
3714{
3715 //set both the mime color data (which includes alpha channel), and the text (which is the color's hex
3716 //value, and can be used when pasting colors outside of QGIS).
3717 QMimeData *mimeData = new QMimeData;
3718 mimeData->setColorData( QVariant( color ) );
3719 mimeData->setText( color.name() );
3720 return mimeData;
3721}
3722
3723QColor QgsSymbolLayerUtils::colorFromMimeData( const QMimeData *mimeData, bool &hasAlpha )
3724{
3725 if ( !mimeData )
3726 return QColor();
3727
3728 //attempt to read color data directly from mime
3729 if ( mimeData->hasColor() )
3730 {
3731 QColor mimeColor = mimeData->colorData().value<QColor>();
3732 if ( mimeColor.isValid() )
3733 {
3734 hasAlpha = true;
3735 return mimeColor;
3736 }
3737 }
3738
3739 //attempt to intrepret a color from mime text data
3740 if ( mimeData->hasText() )
3741 {
3742 hasAlpha = false;
3743 QColor textColor = QgsSymbolLayerUtils::parseColorWithAlpha( mimeData->text(), hasAlpha );
3744 if ( textColor.isValid() )
3745 {
3746 return textColor;
3747 }
3748 }
3749
3750 //could not get color from mime data
3751 return QColor();
3752}
3753
3755{
3756 if ( !data )
3757 return {};
3758
3759 QgsNamedColorList mimeColors;
3760
3761 //prefer xml format
3762 if ( data->hasFormat( u"text/xml"_s ) )
3763 {
3764 //get XML doc
3765 const QByteArray encodedData = data->data( u"text/xml"_s );
3766 QDomDocument xmlDoc;
3767 xmlDoc.setContent( encodedData );
3768
3769 const QDomElement dragDataElem = xmlDoc.documentElement();
3770 if ( dragDataElem.tagName() == "ColorSchemeModelDragData"_L1 )
3771 {
3772 const QDomNodeList nodeList = dragDataElem.childNodes();
3773 const int nChildNodes = nodeList.size();
3774 QDomElement currentElem;
3775
3776 for ( int i = 0; i < nChildNodes; ++i )
3777 {
3778 currentElem = nodeList.at( i ).toElement();
3779 if ( currentElem.isNull() )
3780 {
3781 continue;
3782 }
3783
3784 QPair< QColor, QString> namedColor;
3785 namedColor.first = QgsColorUtils::colorFromString( currentElem.attribute( u"color"_s, u"255,255,255,255"_s ) );
3786 namedColor.second = currentElem.attribute( u"label"_s, QString() );
3787
3788 mimeColors << namedColor;
3789 }
3790 }
3791 }
3792
3793 if ( mimeColors.length() == 0 && data->hasFormat( u"application/x-colorobject-list"_s ) )
3794 {
3795 //get XML doc
3796 const QByteArray encodedData = data->data( u"application/x-colorobject-list"_s );
3797 QDomDocument xmlDoc;
3798 xmlDoc.setContent( encodedData );
3799
3800 const QDomNodeList colorsNodes = xmlDoc.elementsByTagName( u"colors"_s );
3801 if ( colorsNodes.length() > 0 )
3802 {
3803 const QDomElement colorsElem = colorsNodes.at( 0 ).toElement();
3804 const QDomNodeList colorNodeList = colorsElem.childNodes();
3805 const int nChildNodes = colorNodeList.size();
3806 QDomElement currentElem;
3807
3808 for ( int i = 0; i < nChildNodes; ++i )
3809 {
3810 //li element
3811 currentElem = colorNodeList.at( i ).toElement();
3812 if ( currentElem.isNull() )
3813 {
3814 continue;
3815 }
3816
3817 const QDomNodeList colorNodes = currentElem.elementsByTagName( u"color"_s );
3818 const QDomNodeList nameNodes = currentElem.elementsByTagName( u"name"_s );
3819
3820 if ( colorNodes.length() > 0 )
3821 {
3822 const QDomElement colorElem = colorNodes.at( 0 ).toElement();
3823
3824 const QStringList colorParts = colorElem.text().simplified().split( ' ' );
3825 if ( colorParts.length() < 3 )
3826 {
3827 continue;
3828 }
3829
3830 const int red = colorParts.at( 0 ).toDouble() * 255;
3831 const int green = colorParts.at( 1 ).toDouble() * 255;
3832 const int blue = colorParts.at( 2 ).toDouble() * 255;
3833 QPair< QColor, QString> namedColor;
3834 namedColor.first = QColor( red, green, blue );
3835 if ( nameNodes.length() > 0 )
3836 {
3837 const QDomElement nameElem = nameNodes.at( 0 ).toElement();
3838 namedColor.second = nameElem.text();
3839 }
3840 mimeColors << namedColor;
3841 }
3842 }
3843 }
3844 }
3845
3846 if ( mimeColors.length() == 0 && data->hasText() )
3847 {
3848 //attempt to read color data from mime text
3849 QList< QColor > parsedColors = QgsSymbolLayerUtils::parseColorList( data->text() );
3850 QList< QColor >::iterator it = parsedColors.begin();
3851 for ( ; it != parsedColors.end(); ++it )
3852 {
3853 mimeColors << qMakePair( *it, QString() );
3854 }
3855 }
3856
3857 if ( mimeColors.length() == 0 && data->hasColor() )
3858 {
3859 //attempt to read color data directly from mime
3860 const QColor mimeColor = data->colorData().value<QColor>();
3861 if ( mimeColor.isValid() )
3862 {
3863 mimeColors << qMakePair( mimeColor, QString() );
3864 }
3865 }
3866
3867 return mimeColors;
3868}
3869
3870QMimeData *QgsSymbolLayerUtils::colorListToMimeData( const QgsNamedColorList &colorList, const bool allFormats )
3871{
3872 //native format
3873 QMimeData *mimeData = new QMimeData();
3874 QDomDocument xmlDoc;
3875 QDomElement xmlRootElement = xmlDoc.createElement( u"ColorSchemeModelDragData"_s );
3876 xmlDoc.appendChild( xmlRootElement );
3877
3878 QgsNamedColorList::const_iterator colorIt = colorList.constBegin();
3879 for ( ; colorIt != colorList.constEnd(); ++colorIt )
3880 {
3881 QDomElement namedColor = xmlDoc.createElement( u"NamedColor"_s );
3882 namedColor.setAttribute( u"color"_s, QgsColorUtils::colorToString( ( *colorIt ).first ) );
3883 namedColor.setAttribute( u"label"_s, ( *colorIt ).second );
3884 xmlRootElement.appendChild( namedColor );
3885 }
3886 mimeData->setData( u"text/xml"_s, xmlDoc.toByteArray() );
3887
3888 if ( !allFormats )
3889 {
3890 return mimeData;
3891 }
3892
3893 //set mime text to list of hex values
3894 colorIt = colorList.constBegin();
3895 QStringList colorListString;
3896 for ( ; colorIt != colorList.constEnd(); ++colorIt )
3897 {
3898 colorListString << ( *colorIt ).first.name();
3899 }
3900 mimeData->setText( colorListString.join( QLatin1Char( '\n' ) ) );
3901
3902 //set mime color data to first color
3903 if ( colorList.length() > 0 )
3904 {
3905 mimeData->setColorData( QVariant( colorList.at( 0 ).first ) );
3906 }
3907
3908 return mimeData;
3909}
3910
3911bool QgsSymbolLayerUtils::saveColorsToGpl( QFile &file, const QString &paletteName, const QgsNamedColorList &colors )
3912{
3913 if ( !file.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
3914 {
3915 return false;
3916 }
3917
3918 QTextStream stream( &file );
3919
3920 stream << "GIMP Palette" << Qt::endl;
3921 if ( paletteName.isEmpty() )
3922 {
3923 stream << "Name: QGIS Palette" << Qt::endl;
3924 }
3925 else
3926 {
3927 stream << "Name: " << paletteName << Qt::endl;
3928 }
3929 stream << "Columns: 4" << Qt::endl;
3930 stream << '#' << Qt::endl;
3931
3932 for ( QgsNamedColorList::ConstIterator colorIt = colors.constBegin(); colorIt != colors.constEnd(); ++colorIt )
3933 {
3934 const QColor color = ( *colorIt ).first;
3935 if ( !color.isValid() )
3936 {
3937 continue;
3938 }
3939 stream << u"%1 %2 %3"_s.arg( color.red(), 3 ).arg( color.green(), 3 ).arg( color.blue(), 3 );
3940 stream << "\t" << ( ( *colorIt ).second.isEmpty() ? color.name() : ( *colorIt ).second ) << Qt::endl;
3941 }
3942 file.close();
3943
3944 return true;
3945}
3946
3948{
3949 QgsNamedColorList importedColors;
3950
3951 if ( !file.open( QIODevice::ReadOnly ) )
3952 {
3953 ok = false;
3954 return importedColors;
3955 }
3956
3957 QTextStream in( &file );
3958
3959 QString line = in.readLine();
3960 if ( !line.startsWith( "GIMP Palette"_L1 ) )
3961 {
3962 ok = false;
3963 return importedColors;
3964 }
3965
3966 //find name line
3967 while ( !in.atEnd() && !line.startsWith( "Name:"_L1 ) && !line.startsWith( '#' ) )
3968 {
3969 line = in.readLine();
3970 }
3971 if ( line.startsWith( "Name:"_L1 ) )
3972 {
3973 const thread_local QRegularExpression nameRx( "Name:\\s*(\\S.*)$" );
3974 const QRegularExpressionMatch match = nameRx.match( line );
3975 if ( match.hasMatch() )
3976 {
3977 name = match.captured( 1 );
3978 }
3979 }
3980
3981 //ignore lines until after "#"
3982 while ( !in.atEnd() && !line.startsWith( '#' ) )
3983 {
3984 line = in.readLine();
3985 }
3986 if ( in.atEnd() )
3987 {
3988 ok = false;
3989 return importedColors;
3990 }
3991
3992 //ready to start reading colors
3993 const thread_local QRegularExpression rx( "^\\s*(\\d+)\\s+(\\d+)\\s+(\\d+)(\\s.*)?$" );
3994 while ( !in.atEnd() )
3995 {
3996 line = in.readLine();
3997 const QRegularExpressionMatch match = rx.match( line );
3998 if ( !match.hasMatch() )
3999 {
4000 continue;
4001 }
4002 const int red = match.captured( 1 ).toInt();
4003 const int green = match.captured( 2 ).toInt();
4004 const int blue = match.captured( 3 ).toInt();
4005 const QColor color = QColor( red, green, blue );
4006 if ( !color.isValid() )
4007 {
4008 continue;
4009 }
4010
4011 //try to read color name
4012 QString label;
4013 if ( rx.captureCount() > 3 )
4014 {
4015 label = match.captured( 4 ).simplified();
4016 }
4017 else
4018 {
4019 label = colorToName( color );
4020 }
4021
4022 importedColors << qMakePair( color, label );
4023 }
4024
4025 file.close();
4026 ok = true;
4027 return importedColors;
4028}
4029
4030QColor QgsSymbolLayerUtils::parseColor( const QString &colorStr, bool strictEval )
4031{
4032 bool hasAlpha;
4033 return parseColorWithAlpha( colorStr, hasAlpha, strictEval );
4034}
4035
4036QColor QgsSymbolLayerUtils::parseColorWithAlpha( const QString &colorStr, bool &containsAlpha, bool strictEval )
4037{
4038 QColor parsedColor;
4039
4040 const thread_local QRegularExpression hexColorAlphaRx( "^\\s*#?([0-9a-fA-F]{6})([0-9a-fA-F]{2})\\s*$" );
4041 QRegularExpressionMatch match = hexColorAlphaRx.match( colorStr );
4042
4043 //color in hex format "#aabbcc", but not #aabbccdd
4044 if ( !match.hasMatch() && QColor::isValidColor( colorStr ) )
4045 {
4046 //string is a valid hex color string
4047 parsedColor.setNamedColor( colorStr );
4048 if ( parsedColor.isValid() )
4049 {
4050 containsAlpha = false;
4051 return parsedColor;
4052 }
4053 }
4054
4055 //color in hex format, with alpha
4056 if ( match.hasMatch() )
4057 {
4058 const QString hexColor = match.captured( 1 );
4059 parsedColor.setNamedColor( u"#"_s + hexColor );
4060 bool alphaOk;
4061 const int alphaHex = match.captured( 2 ).toInt( &alphaOk, 16 );
4062
4063 if ( parsedColor.isValid() && alphaOk )
4064 {
4065 parsedColor.setAlpha( alphaHex );
4066 containsAlpha = true;
4067 return parsedColor;
4068 }
4069 }
4070
4071 if ( !strictEval )
4072 {
4073 //color in hex format, without #
4074 const thread_local QRegularExpression hexColorRx2( "^\\s*(?:[0-9a-fA-F]{3}){1,2}\\s*$" );
4075 if ( colorStr.indexOf( hexColorRx2 ) != -1 )
4076 {
4077 //add "#" and parse
4078 parsedColor.setNamedColor( u"#"_s + colorStr );
4079 if ( parsedColor.isValid() )
4080 {
4081 containsAlpha = false;
4082 return parsedColor;
4083 }
4084 }
4085 }
4086
4087 //color in (rrr,ggg,bbb) format, brackets and rgb prefix optional
4088 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*$" );
4089 match = rgbFormatRx.match( colorStr );
4090 if ( match.hasMatch() )
4091 {
4092 bool rOk = false;
4093 bool gOk = false;
4094 bool bOk = false;
4095 const int r = match.captured( 1 ).toInt( &rOk );
4096 const int g = match.captured( 2 ).toInt( &gOk );
4097 const int b = match.captured( 3 ).toInt( &bOk );
4098
4099 if ( !rOk || !gOk || !bOk )
4100 {
4101 const float rFloat = match.captured( 1 ).toFloat();
4102 const float gFloat = match.captured( 2 ).toFloat();
4103 const float bFloat = match.captured( 3 ).toFloat();
4104 parsedColor.setRgbF( rFloat / 255.0, gFloat / 255.0, bFloat / 255.0 );
4105 }
4106 else
4107 {
4108 parsedColor.setRgb( r, g, b );
4109 }
4110
4111 if ( parsedColor.isValid() )
4112 {
4113 containsAlpha = false;
4114 return parsedColor;
4115 }
4116 }
4117
4118 //color in hsl(h,s,l) format, brackets optional
4119 const thread_local QRegularExpression hslFormatRx( "^\\s*hsl\\(?\\s*(\\d+(?:\\.\\d*)?)\\s*,\\s*(\\d+(?:\\.\\d*)?)\\s*%\\s*,\\s*(\\d+(?:\\.\\d*)?)\\s*%\\s*\\)?\\s*;?\\s*$" );
4120 match = hslFormatRx.match( colorStr );
4121 if ( match.hasMatch() )
4122 {
4123 bool hOk = false;
4124 bool sOk = false;
4125 bool lOk = false;
4126 const int h = match.captured( 1 ).toInt( &hOk );
4127 const int s = match.captured( 2 ).toInt( &sOk );
4128 const int l = match.captured( 3 ).toInt( &lOk );
4129
4130 if ( !hOk || !sOk || !lOk )
4131 {
4132 const float hFloat = match.captured( 1 ).toFloat();
4133 const float sFloat = match.captured( 2 ).toFloat();
4134 const float lFloat = match.captured( 3 ).toFloat();
4135 parsedColor.setHslF( hFloat / 360.0, sFloat / 100.0, lFloat / 100.0 );
4136 }
4137 else
4138 {
4139 parsedColor.setHsl( h, s / 100.0 * 255.0, l / 100.0 * 255.0 );
4140 }
4141 if ( parsedColor.isValid() )
4142 {
4143 containsAlpha = false;
4144 return parsedColor;
4145 }
4146 }
4147
4148 //color in (r%,g%,b%) format, brackets and rgb prefix optional
4149 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*$" );
4150 match = rgbPercentFormatRx.match( colorStr );
4151 if ( match.hasMatch() )
4152 {
4153 const double r = match.captured( 1 ).toDouble() / 100;
4154 const double g = match.captured( 2 ).toDouble() / 100;
4155 const double b = match.captured( 3 ).toDouble() / 100;
4156 parsedColor.setRgbF( r, g, b );
4157 if ( parsedColor.isValid() )
4158 {
4159 containsAlpha = false;
4160 return parsedColor;
4161 }
4162 }
4163
4164 //color in (r,g,b,a) format, brackets and rgba prefix optional
4165 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*$" );
4166 match = rgbaFormatRx.match( colorStr );
4167 if ( match.hasMatch() )
4168 {
4169 bool rOk = false;
4170 bool gOk = false;
4171 bool bOk = false;
4172 const int r = match.captured( 1 ).toInt( &rOk );
4173 const int g = match.captured( 2 ).toInt( &gOk );
4174 const int b = match.captured( 3 ).toInt( &bOk );
4175 const double aDouble = match.captured( 4 ).toDouble();
4176
4177 if ( !rOk || !gOk || !bOk )
4178 {
4179 const float rFloat = match.captured( 1 ).toFloat();
4180 const float gFloat = match.captured( 2 ).toFloat();
4181 const float bFloat = match.captured( 3 ).toFloat();
4182 parsedColor.setRgbF( rFloat / 255.0, gFloat / 255.0, bFloat / 255.0, aDouble );
4183 }
4184 else
4185 {
4186 const int a = static_cast< int >( std::round( match.captured( 4 ).toDouble() * 255.0 ) );
4187 parsedColor.setRgb( r, g, b, a );
4188 }
4189 if ( parsedColor.isValid() )
4190 {
4191 containsAlpha = true;
4192 return parsedColor;
4193 }
4194 }
4195
4196 //color in (r%,g%,b%,a) format, brackets and rgba prefix optional
4197 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*$" );
4198 match = rgbaPercentFormatRx.match( colorStr );
4199 if ( match.hasMatch() )
4200 {
4201 const double r = match.captured( 1 ).toDouble() / 100;
4202 const double g = match.captured( 2 ).toDouble() / 100;
4203 const double b = match.captured( 3 ).toDouble() / 100;
4204 const double a = match.captured( 4 ).toDouble();
4205 parsedColor.setRgbF( r, g, b, a );
4206 if ( parsedColor.isValid() )
4207 {
4208 containsAlpha = true;
4209 return parsedColor;
4210 }
4211 }
4212
4213 //color in hsla(h,s%,l%,a) format, brackets optional
4214 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*$" );
4215 match = hslaPercentFormatRx.match( colorStr );
4216 if ( match.hasMatch() )
4217 {
4218 bool hOk = false;
4219 bool sOk = false;
4220 bool lOk = false;
4221 const int h = match.captured( 1 ).toInt( &hOk );
4222 const int s = match.captured( 2 ).toInt( &sOk );
4223 const int l = match.captured( 3 ).toInt( &lOk );
4224 const double aDouble = match.captured( 4 ).toDouble();
4225
4226 if ( !hOk || !sOk || !lOk )
4227 {
4228 const float hFloat = match.captured( 1 ).toFloat();
4229 const float sFloat = match.captured( 2 ).toFloat();
4230 const float lFloat = match.captured( 3 ).toFloat();
4231 parsedColor.setHslF( hFloat / 360.0, sFloat / 100.0, lFloat / 100.0, aDouble );
4232 }
4233 else
4234 {
4235 const int a = std::round( aDouble * 255.0 );
4236 parsedColor.setHsl( h, s / 100.0 * 255.0, l / 100.0 * 255.0, a );
4237 }
4238
4239 if ( parsedColor.isValid() )
4240 {
4241 containsAlpha = true;
4242 return parsedColor;
4243 }
4244 }
4245
4246 //couldn't parse string as color
4247 return QColor();
4248}
4249
4250void QgsSymbolLayerUtils::multiplyImageOpacity( QImage *image, qreal opacity )
4251{
4252 if ( !image )
4253 {
4254 return;
4255 }
4256
4257 QRgb myRgb;
4258 const QImage::Format format = image->format();
4259 if ( format != QImage::Format_ARGB32_Premultiplied && format != QImage::Format_ARGB32 )
4260 {
4261 QgsDebugError( u"no alpha channel."_s );
4262 return;
4263 }
4264
4265 //change the alpha component of every pixel
4266 for ( int heightIndex = 0; heightIndex < image->height(); ++heightIndex )
4267 {
4268 QRgb *scanLine = reinterpret_cast< QRgb * >( image->scanLine( heightIndex ) );
4269 for ( int widthIndex = 0; widthIndex < image->width(); ++widthIndex )
4270 {
4271 myRgb = scanLine[widthIndex];
4272 if ( format == QImage::Format_ARGB32_Premultiplied )
4273 scanLine[widthIndex] = qRgba( opacity * qRed( myRgb ), opacity * qGreen( myRgb ), opacity * qBlue( myRgb ), opacity * qAlpha( myRgb ) );
4274 else
4275 scanLine[widthIndex] = qRgba( qRed( myRgb ), qGreen( myRgb ), qBlue( myRgb ), opacity * qAlpha( myRgb ) );
4276 }
4277 }
4278}
4279
4280void QgsSymbolLayerUtils::blurImageInPlace( QImage &image, QRect rect, int radius, bool alphaOnly )
4281{
4282 // culled from Qt's qpixmapfilter.cpp, see: http://www.qtcentre.org/archive/index.php/t-26534.html
4283 const int tab[] = { 14, 10, 8, 6, 5, 5, 4, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2 };
4284 const int alpha = ( radius < 1 ) ? 16 : ( radius > 17 ) ? 1 : tab[radius - 1];
4285
4286 if ( image.format() != QImage::Format_ARGB32_Premultiplied
4287 && image.format() != QImage::Format_RGB32 )
4288 {
4289 image = image.convertToFormat( QImage::Format_ARGB32_Premultiplied );
4290 }
4291
4292 const int r1 = rect.top();
4293 const int r2 = rect.bottom();
4294 const int c1 = rect.left();
4295 const int c2 = rect.right();
4296
4297 const int bpl = image.bytesPerLine();
4298 int rgba[4];
4299 unsigned char *p;
4300
4301 int i1 = 0;
4302 int i2 = 3;
4303
4304 if ( alphaOnly ) // this seems to only work right for a black color
4305 i1 = i2 = ( QSysInfo::ByteOrder == QSysInfo::BigEndian ? 0 : 3 );
4306
4307 for ( int col = c1; col <= c2; col++ )
4308 {
4309 p = image.scanLine( r1 ) + col * 4;
4310 for ( int i = i1; i <= i2; i++ )
4311 rgba[i] = p[i] << 4;
4312
4313 p += bpl;
4314 for ( int j = r1; j < r2; j++, p += bpl )
4315 for ( int i = i1; i <= i2; i++ )
4316 p[i] = ( rgba[i] += ( ( p[i] << 4 ) - rgba[i] ) * alpha / 16 ) >> 4;
4317 }
4318
4319 for ( int row = r1; row <= r2; row++ )
4320 {
4321 p = image.scanLine( row ) + c1 * 4;
4322 for ( int i = i1; i <= i2; i++ )
4323 rgba[i] = p[i] << 4;
4324
4325 p += 4;
4326 for ( int j = c1; j < c2; j++, p += 4 )
4327 for ( int i = i1; i <= i2; i++ )
4328 p[i] = ( rgba[i] += ( ( p[i] << 4 ) - rgba[i] ) * alpha / 16 ) >> 4;
4329 }
4330
4331 for ( int col = c1; col <= c2; col++ )
4332 {
4333 p = image.scanLine( r2 ) + col * 4;
4334 for ( int i = i1; i <= i2; i++ )
4335 rgba[i] = p[i] << 4;
4336
4337 p -= bpl;
4338 for ( int j = r1; j < r2; j++, p -= bpl )
4339 for ( int i = i1; i <= i2; i++ )
4340 p[i] = ( rgba[i] += ( ( p[i] << 4 ) - rgba[i] ) * alpha / 16 ) >> 4;
4341 }
4342
4343 for ( int row = r1; row <= r2; row++ )
4344 {
4345 p = image.scanLine( row ) + c2 * 4;
4346 for ( int i = i1; i <= i2; i++ )
4347 rgba[i] = p[i] << 4;
4348
4349 p -= 4;
4350 for ( int j = c1; j < c2; j++, p -= 4 )
4351 for ( int i = i1; i <= i2; i++ )
4352 p[i] = ( rgba[i] += ( ( p[i] << 4 ) - rgba[i] ) * alpha / 16 ) >> 4;
4353 }
4354}
4355
4356void QgsSymbolLayerUtils::premultiplyColor( QColor &rgb, int alpha )
4357{
4358 if ( alpha != 255 && alpha > 0 )
4359 {
4360 // Semi-transparent pixel. We need to adjust the colors for ARGB32_Premultiplied images
4361 // where color values have to be premultiplied by alpha
4362 const double alphaFactor = alpha / 255.;
4363 int r = 0, g = 0, b = 0;
4364 rgb.getRgb( &r, &g, &b );
4365
4366 r *= alphaFactor;
4367 g *= alphaFactor;
4368 b *= alphaFactor;
4369 rgb.setRgb( r, g, b, alpha );
4370 }
4371 else if ( alpha == 0 )
4372 {
4373 rgb.setRgb( 0, 0, 0, 0 );
4374 }
4375}
4376
4378{
4379 QgsSimpleFillSymbolLayer *simpleFill = dynamic_cast< QgsSimpleFillSymbolLayer *>( fill );
4380 QgsSimpleLineSymbolLayer *simpleLine = dynamic_cast< QgsSimpleLineSymbolLayer *>( outline );
4381
4382 if ( !simpleFill || !simpleLine )
4383 return false;
4384
4385 if ( simpleLine->useCustomDashPattern() )
4386 return false;
4387
4388 if ( simpleLine->dashPatternOffset() )
4389 return false;
4390
4391 if ( simpleLine->alignDashPattern() )
4392 return false;
4393
4394 if ( simpleLine->tweakDashPatternOnCorners() )
4395 return false;
4396
4397 if ( simpleLine->trimDistanceStart() || simpleLine->trimDistanceEnd() )
4398 return false;
4399
4400 if ( simpleLine->drawInsidePolygon() )
4401 return false;
4402
4403 if ( simpleLine->ringFilter() != QgsSimpleLineSymbolLayer::AllRings )
4404 return false;
4405
4406 if ( simpleLine->offset() )
4407 return false;
4408
4409 if ( simpleLine->hasDataDefinedProperties() )
4410 return false;
4411
4412 // looks good!
4413 simpleFill->setStrokeColor( simpleLine->color() );
4414 simpleFill->setStrokeWidth( simpleLine->width() );
4415 simpleFill->setStrokeWidthUnit( simpleLine->widthUnit() );
4416 simpleFill->setStrokeWidthMapUnitScale( simpleLine->widthMapUnitScale() );
4417 simpleFill->setStrokeStyle( simpleLine->penStyle() );
4418 simpleFill->setPenJoinStyle( simpleLine->penJoinStyle() );
4419 return true;
4420}
4421
4422void QgsSymbolLayerUtils::sortVariantList( QList<QVariant> &list, Qt::SortOrder order )
4423{
4424 if ( order == Qt::AscendingOrder )
4425 {
4426 //std::sort( list.begin(), list.end(), _QVariantLessThan );
4427 std::sort( list.begin(), list.end(), qgsVariantLessThan );
4428 }
4429 else // Qt::DescendingOrder
4430 {
4431 //std::sort( list.begin(), list.end(), _QVariantGreaterThan );
4432 std::sort( list.begin(), list.end(), qgsVariantGreaterThan );
4433 }
4434}
4435
4436QPointF QgsSymbolLayerUtils::pointOnLineWithDistance( QPointF startPoint, QPointF directionPoint, double distance )
4437{
4438 const double dx = directionPoint.x() - startPoint.x();
4439 const double dy = directionPoint.y() - startPoint.y();
4440 const double length = std::sqrt( dx * dx + dy * dy );
4441 const double scaleFactor = distance / length;
4442 return QPointF( startPoint.x() + dx * scaleFactor, startPoint.y() + dy * scaleFactor );
4443}
4444
4445
4447{
4448 // copied from QgsMarkerCatalogue - TODO: unify //#spellok
4449 QStringList list;
4450 QStringList svgPaths = QgsApplication::svgPaths();
4451
4452 for ( int i = 0; i < svgPaths.size(); i++ )
4453 {
4454 const QDir dir( svgPaths[i] );
4455 const auto svgSubPaths = dir.entryList( QDir::Dirs | QDir::NoDotAndDotDot );
4456 for ( const QString &item : svgSubPaths )
4457 {
4458 svgPaths.insert( i + 1, dir.path() + '/' + item );
4459 }
4460
4461 const auto svgFiles = dir.entryList( QStringList( "*.svg" ), QDir::Files );
4462 for ( const QString &item : svgFiles )
4463 {
4464 // TODO test if it is correct SVG
4465 list.append( dir.path() + '/' + item );
4466 }
4467 }
4468 return list;
4469}
4470
4471// Stripped down version of listSvgFiles() for specified directory
4472QStringList QgsSymbolLayerUtils::listSvgFilesAt( const QString &directory )
4473{
4474 // TODO anything that applies for the listSvgFiles() applies this also
4475
4476 QStringList list;
4477 QStringList svgPaths;
4478 svgPaths.append( directory );
4479
4480 for ( int i = 0; i < svgPaths.size(); i++ )
4481 {
4482 const QDir dir( svgPaths[i] );
4483 const auto svgSubPaths = dir.entryList( QDir::Dirs | QDir::NoDotAndDotDot );
4484 for ( const QString &item : svgSubPaths )
4485 {
4486 svgPaths.insert( i + 1, dir.path() + '/' + item );
4487 }
4488
4489 const auto svgFiles = dir.entryList( QStringList( "*.svg" ), QDir::Files );
4490 for ( const QString &item : svgFiles )
4491 {
4492 list.append( dir.path() + '/' + item );
4493 }
4494 }
4495 return list;
4496
4497}
4498
4499QString QgsSymbolLayerUtils::svgSymbolNameToPath( const QString &n, const QgsPathResolver &pathResolver )
4500{
4501 if ( n.isEmpty() )
4502 return QString();
4503
4504 if ( n.startsWith( "base64:"_L1 ) )
4505 return n;
4506
4507 // we might have a full path...
4508 if ( QFileInfo::exists( n ) )
4509 return QFileInfo( n ).canonicalFilePath();
4510
4511 QString name = n;
4512 // or it might be an url...
4513 if ( name.contains( "://"_L1 ) )
4514 {
4515 const QUrl url( name );
4516 if ( url.isValid() && !url.scheme().isEmpty() )
4517 {
4518 if ( url.scheme().compare( "file"_L1, Qt::CaseInsensitive ) == 0 )
4519 {
4520 // it's a url to a local file
4521 name = url.toLocalFile();
4522 if ( QFile( name ).exists() )
4523 {
4524 return QFileInfo( name ).canonicalFilePath();
4525 }
4526 }
4527 else
4528 {
4529 // it's a url pointing to a online resource
4530 return name;
4531 }
4532 }
4533 }
4534
4535 // SVG symbol not found - probably a relative path was used
4536
4537 QStringList svgPaths = QgsApplication::svgPaths();
4538 for ( int i = 0; i < svgPaths.size(); i++ )
4539 {
4540 QString svgPath = svgPaths[i];
4541 if ( svgPath.endsWith( QChar( '/' ) ) )
4542 {
4543 svgPath.chop( 1 );
4544 }
4545
4546 QgsDebugMsgLevel( "SvgPath: " + svgPath, 3 );
4547 // Not sure why to lowest dir was used instead of full relative path, it was causing #8664
4548 //QFileInfo myInfo( name );
4549 //QString myFileName = myInfo.fileName(); // foo.svg
4550 //QString myLowestDir = myInfo.dir().dirName();
4551 //QString myLocalPath = svgPath + QString( myLowestDir.isEmpty() ? "" : '/' + myLowestDir ) + '/' + myFileName;
4552 const QString myLocalPath = svgPath + QDir::separator() + name;
4553
4554 QgsDebugMsgLevel( "Alternative svg path: " + myLocalPath, 3 );
4555 if ( QFile( myLocalPath ).exists() )
4556 {
4557 QgsDebugMsgLevel( u"Svg found in alternative path"_s, 3 );
4558 return QFileInfo( myLocalPath ).canonicalFilePath();
4559 }
4560 }
4561
4562 return pathResolver.readPath( name );
4563}
4564
4565QString QgsSymbolLayerUtils::svgSymbolPathToName( const QString &p, const QgsPathResolver &pathResolver )
4566{
4567 if ( p.isEmpty() )
4568 return QString();
4569
4570 if ( p.startsWith( "base64:"_L1 ) )
4571 return p;
4572
4573 if ( !QFileInfo::exists( p ) )
4574 return p;
4575
4576 QString path = QFileInfo( p ).canonicalFilePath();
4577
4578 QStringList svgPaths = QgsApplication::svgPaths();
4579
4580 bool isInSvgPaths = false;
4581 for ( int i = 0; i < svgPaths.size(); i++ )
4582 {
4583 const QString dir = QFileInfo( svgPaths[i] ).canonicalFilePath();
4584
4585 if ( !dir.isEmpty() && path.startsWith( dir ) )
4586 {
4587 path = path.mid( dir.size() + 1 );
4588 isInSvgPaths = true;
4589 break;
4590 }
4591 }
4592
4593 if ( isInSvgPaths )
4594 return path;
4595
4596 return pathResolver.writePath( path );
4597}
4598
4599QPolygonF lineStringToQPolygonF( const QgsLineString *line )
4600{
4601 const double *srcX = line->xData();
4602 const double *srcY = line->yData();
4603 const int count = line->numPoints();
4604 QPolygonF thisRes( count );
4605 QPointF *dest = thisRes.data();
4606 for ( int i = 0; i < count; ++i )
4607 {
4608 *dest++ = QPointF( *srcX++, *srcY++ );
4609 }
4610 return thisRes;
4611}
4612
4613QPolygonF curveToPolygonF( const QgsCurve *curve )
4614{
4615 if ( const QgsLineString *line = qgsgeometry_cast< const QgsLineString * >( curve ) )
4616 {
4617 return lineStringToQPolygonF( line );
4618 }
4619 else
4620 {
4621 const std::unique_ptr< QgsLineString > straightened( curve->curveToLine() );
4622 return lineStringToQPolygonF( straightened.get() );
4623 }
4624}
4625
4626QList<QList<QPolygonF> > QgsSymbolLayerUtils::toQPolygonF( const QgsGeometry &geometry, Qgis::SymbolType type )
4627{
4628 return toQPolygonF( geometry.constGet(), type );
4629}
4630
4631QList<QList<QPolygonF> > QgsSymbolLayerUtils::toQPolygonF( const QgsAbstractGeometry *geometry, Qgis::SymbolType type )
4632{
4633 if ( !geometry )
4634 return {};
4635
4636 switch ( type )
4637 {
4639 {
4640 QPolygonF points;
4641
4643 {
4644 for ( auto it = geometry->vertices_begin(); it != geometry->vertices_end(); ++it )
4645 points << QPointF( ( *it ).x(), ( *it ).y() );
4646 }
4647 else
4648 {
4649 points << QPointF( 0, 0 );
4650 }
4651 return QList< QList<QPolygonF> >() << ( QList< QPolygonF >() << points );
4652 }
4653
4655 {
4656 QList< QList<QPolygonF> > res;
4658 {
4659 for ( auto it = geometry->const_parts_begin(); it != geometry->const_parts_end(); ++it )
4660 {
4661 res << ( QList< QPolygonF >() << curveToPolygonF( qgsgeometry_cast< const QgsCurve * >( *it ) ) );
4662 }
4663 }
4664 return res;
4665 }
4666
4668 {
4669 QList< QList<QPolygonF> > res;
4670
4671 for ( auto it = geometry->const_parts_begin(); it != geometry->const_parts_end(); ++it )
4672 {
4673 QList<QPolygonF> thisPart;
4675 if ( !surface )
4676 continue;
4677
4678 if ( !surface->exteriorRing() )
4679 continue;
4680
4681 thisPart << curveToPolygonF( surface->exteriorRing() );
4682
4683 for ( int i = 0; i < surface->numInteriorRings(); ++i )
4684 thisPart << curveToPolygonF( surface->interiorRing( i ) );
4685 res << thisPart;
4686 }
4687
4688 return res;
4689 }
4690
4692 return QList< QList<QPolygonF> >();
4693 }
4694
4695 return QList< QList<QPolygonF> >();
4696}
4697
4698
4699QPointF QgsSymbolLayerUtils::polygonCentroid( const QPolygonF &points )
4700{
4701 //Calculate the centroid of points
4702 double cx = 0, cy = 0;
4703 double area, sum = 0;
4704 for ( int i = points.count() - 1, j = 0; j < points.count(); i = j++ )
4705 {
4706 const QPointF &p1 = points[i];
4707 const QPointF &p2 = points[j];
4708 area = p1.x() * p2.y() - p1.y() * p2.x();
4709 sum += area;
4710 cx += ( p1.x() + p2.x() ) * area;
4711 cy += ( p1.y() + p2.y() ) * area;
4712 }
4713 sum *= 3.0;
4714 if ( qgsDoubleNear( sum, 0.0 ) )
4715 {
4716 // the linear ring is invalid - let's fall back to a solution that will still
4717 // allow us render at least something (instead of just returning point nan,nan)
4718 if ( points.count() >= 2 )
4719 return QPointF( ( points[0].x() + points[1].x() ) / 2, ( points[0].y() + points[1].y() ) / 2 );
4720 else if ( points.count() == 1 )
4721 return points[0];
4722 else
4723 return QPointF(); // hopefully we shouldn't ever get here
4724 }
4725 cx /= sum;
4726 cy /= sum;
4727
4728 return QPointF( cx, cy );
4729}
4730
4731QPointF QgsSymbolLayerUtils::polygonPointOnSurface( const QPolygonF &points, const QVector<QPolygonF> *rings )
4732{
4733 QPointF centroid = QgsSymbolLayerUtils::polygonCentroid( points );
4734
4735 if ( ( rings && rings->count() > 0 ) || !pointInPolygon( points, centroid ) )
4736 {
4737 unsigned int i, pointCount = points.count();
4738 QgsPolylineXY polyline( pointCount );
4739 for ( i = 0; i < pointCount; ++i ) polyline[i] = QgsPointXY( points[i].x(), points[i].y() );
4740 QgsGeometry geom = QgsGeometry::fromPolygonXY( QgsPolygonXY() << polyline );
4741 if ( !geom.isNull() )
4742 {
4743 if ( rings )
4744 {
4745 for ( auto ringIt = rings->constBegin(); ringIt != rings->constEnd(); ++ringIt )
4746 {
4747 pointCount = ( *ringIt ).count();
4748 QgsPolylineXY polyline( pointCount );
4749 for ( i = 0; i < pointCount; ++i ) polyline[i] = QgsPointXY( ( *ringIt )[i].x(), ( *ringIt )[i].y() );
4750 geom.addRing( polyline );
4751 }
4752 }
4753
4754 const QgsGeometry pointOnSurfaceGeom = geom.pointOnSurface();
4755 if ( !pointOnSurfaceGeom.isNull() )
4756 {
4757 const QgsPointXY point = pointOnSurfaceGeom.asPoint();
4758 centroid.setX( point.x() );
4759 centroid.setY( point.y() );
4760 }
4761 }
4762 }
4763
4764 return QPointF( centroid.x(), centroid.y() );
4765}
4766
4767bool QgsSymbolLayerUtils::pointInPolygon( const QPolygonF &points, QPointF point )
4768{
4769 bool inside = false;
4770
4771 const double x = point.x();
4772 const double y = point.y();
4773
4774 for ( int i = 0, j = points.count() - 1; i < points.count(); i++ )
4775 {
4776 const QPointF &p1 = points[i];
4777 const QPointF &p2 = points[j];
4778
4779 if ( qgsDoubleNear( p1.x(), x ) && qgsDoubleNear( p1.y(), y ) )
4780 return true;
4781
4782 if ( ( p1.y() < y && p2.y() >= y ) || ( p2.y() < y && p1.y() >= y ) )
4783 {
4784 if ( p1.x() + ( y - p1.y() ) / ( p2.y() - p1.y() ) * ( p2.x() - p1.x() ) <= x )
4785 inside = !inside;
4786 }
4787
4788 j = i;
4789 }
4790 return inside;
4791}
4792
4793double QgsSymbolLayerUtils::polylineLength( const QPolygonF &polyline )
4794{
4795 if ( polyline.size() < 2 )
4796 return 0;
4797
4798 double totalLength = 0;
4799 auto it = polyline.begin();
4800 QPointF p1 = *it++;
4801 for ( ; it != polyline.end(); ++it )
4802 {
4803 const QPointF p2 = *it;
4804 const double segmentLength = std::sqrt( std::pow( p1.x() - p2.x(), 2.0 ) + std::pow( p1.y() - p2.y(), 2.0 ) );
4805 totalLength += segmentLength;
4806 p1 = p2;
4807 }
4808 return totalLength;
4809}
4810
4811QPolygonF QgsSymbolLayerUtils::polylineSubstring( const QPolygonF &polyline, double startOffset, double endOffset )
4812{
4813 if ( polyline.size() < 2 )
4814 return QPolygonF();
4815
4816 double totalLength = 0;
4817 auto it = polyline.begin();
4818 QPointF p1 = *it++;
4819 std::vector< double > segmentLengths( polyline.size() - 1 );
4820 auto segmentLengthIt = segmentLengths.begin();
4821 for ( ; it != polyline.end(); ++it )
4822 {
4823 const QPointF p2 = *it;
4824 *segmentLengthIt = std::sqrt( std::pow( p1.x() - p2.x(), 2.0 ) + std::pow( p1.y() - p2.y(), 2.0 ) );
4825 totalLength += *segmentLengthIt;
4826
4827 segmentLengthIt++;
4828 p1 = p2;
4829 }
4830
4831 if ( startOffset >= 0 && totalLength <= startOffset )
4832 return QPolygonF();
4833 if ( endOffset < 0 && totalLength <= -endOffset )
4834 return QPolygonF();
4835
4836 const double startDistance = startOffset < 0 ? totalLength + startOffset : startOffset;
4837 const double endDistance = endOffset <= 0 ? totalLength + endOffset : endOffset;
4838 QPolygonF substringPoints;
4839 substringPoints.reserve( polyline.size() );
4840
4841 it = polyline.begin();
4842 segmentLengthIt = segmentLengths.begin();
4843
4844 p1 = *it++;
4845 bool foundStart = false;
4846 if ( qgsDoubleNear( startDistance, 0.0 ) || startDistance < 0 )
4847 {
4848 substringPoints << p1;
4849 foundStart = true;
4850 }
4851
4852 double distanceTraversed = 0;
4853 for ( ; it != polyline.end(); ++it )
4854 {
4855 const QPointF p2 = *it;
4856 if ( distanceTraversed < startDistance && distanceTraversed + *segmentLengthIt > startDistance )
4857 {
4858 // start point falls on this segment
4859 const double distanceToStart = startDistance - distanceTraversed;
4860 double startX, startY;
4861 QgsGeometryUtilsBase::pointOnLineWithDistance( p1.x(), p1.y(), p2.x(), p2.y(), distanceToStart, startX, startY );
4862 substringPoints << QPointF( startX, startY );
4863 foundStart = true;
4864 }
4865 if ( foundStart && ( distanceTraversed + *segmentLengthIt > endDistance ) )
4866 {
4867 // end point falls on this segment
4868 const double distanceToEnd = endDistance - distanceTraversed;
4869 double endX, endY;
4870 QgsGeometryUtilsBase::pointOnLineWithDistance( p1.x(), p1.y(), p2.x(), p2.y(), distanceToEnd, endX, endY );
4871 if ( substringPoints.last() != QPointF( endX, endY ) )
4872 substringPoints << QPointF( endX, endY );
4873 }
4874 else if ( foundStart )
4875 {
4876 if ( substringPoints.last() != QPointF( p2.x(), p2.y() ) )
4877 substringPoints << QPointF( p2.x(), p2.y() );
4878 }
4879
4880 distanceTraversed += *segmentLengthIt;
4881 if ( distanceTraversed > endDistance )
4882 break;
4883
4884 p1 = p2;
4885 segmentLengthIt++;
4886 }
4887
4888 if ( ( substringPoints.size() < 2 ) || ( substringPoints.size() == 2 && substringPoints.at( 0 ) == substringPoints.at( 1 ) ) )
4889 return QPolygonF();
4890
4891 return substringPoints;
4892}
4893
4894bool QgsSymbolLayerUtils::isSharpCorner( QPointF p1, QPointF p2, QPointF p3 )
4895{
4896 double vertexAngle = M_PI - ( std::atan2( p3.y() - p2.y(), p3.x() - p2.x() ) - std::atan2( p2.y() - p1.y(), p2.x() - p1.x() ) );
4897 vertexAngle = QgsGeometryUtilsBase::normalizedAngle( vertexAngle );
4898
4899 // extreme angles form more than 45 degree angle at a node
4900 return vertexAngle < M_PI * 135.0 / 180.0 || vertexAngle > M_PI * 225.0 / 180.0;
4901}
4902
4903void QgsSymbolLayerUtils::appendPolyline( QPolygonF &target, const QPolygonF &line )
4904{
4905 target.reserve( target.size() + line.size() );
4906 for ( const QPointF &pt : line )
4907 {
4908 if ( !target.empty() && target.last() == pt )
4909 continue;
4910
4911 target << pt;
4912 }
4913}
4914
4915std::unique_ptr< QgsExpression > QgsSymbolLayerUtils::fieldOrExpressionToExpression( const QString &fieldOrExpression )
4916{
4917 if ( fieldOrExpression.isEmpty() )
4918 return nullptr;
4919
4920 auto expr = std::make_unique< QgsExpression >( fieldOrExpression );
4921 if ( !expr->hasParserError() )
4922 return expr;
4923
4924 // now try with quoted field name
4925 expr = std::make_unique< QgsExpression >( QgsExpression::quotedColumnRef( fieldOrExpression ) );
4926 Q_ASSERT( !expr->hasParserError() );
4927 return expr;
4928}
4929
4931{
4932 const QgsExpressionNode *n = expression->rootNode();
4933
4934 if ( n && n->nodeType() == QgsExpressionNode::ntColumnRef )
4935 return static_cast<const QgsExpressionNodeColumnRef *>( n )->name();
4936
4937 return expression->expression();
4938}
4939
4940QList<double> QgsSymbolLayerUtils::prettyBreaks( double minimum, double maximum, int classes )
4941{
4942 // C++ implementation of R's pretty algorithm
4943 // Based on code for determining optimal tick placement for statistical graphics
4944 // from the R statistical programming language.
4945 // Code ported from R implementation from 'labeling' R package
4946 //
4947 // Computes a sequence of about 'classes' equally spaced round values
4948 // which cover the range of values from 'minimum' to 'maximum'.
4949 // The values are chosen so that they are 1, 2 or 5 times a power of 10.
4950
4951 QList<double> breaks;
4952 if ( classes < 1 )
4953 {
4954 breaks.append( maximum );
4955 return breaks;
4956 }
4957
4958 const int minimumCount = static_cast< int >( classes ) / 3;
4959 const double shrink = 0.75;
4960 const double highBias = 1.5;
4961 const double adjustBias = 0.5 + 1.5 * highBias;
4962 const int divisions = classes;
4963 const double h = highBias;
4964 double cell;
4965 bool small = false;
4966 const double dx = maximum - minimum;
4967
4968 if ( qgsDoubleNear( dx, 0.0 ) && qgsDoubleNear( maximum, 0.0 ) )
4969 {
4970 cell = 1.0;
4971 small = true;
4972 }
4973 else
4974 {
4975 int U = 1;
4976 cell = std::max( std::fabs( minimum ), std::fabs( maximum ) );
4977 if ( adjustBias >= 1.5 * h + 0.5 )
4978 {
4979 U = 1 + ( 1.0 / ( 1 + h ) );
4980 }
4981 else
4982 {
4983 U = 1 + ( 1.5 / ( 1 + adjustBias ) );
4984 }
4985 small = dx < ( cell * U * std::max( 1, divisions ) * 1e-07 * 3.0 );
4986 }
4987
4988 if ( small )
4989 {
4990 if ( cell > 10 )
4991 {
4992 cell = 9 + cell / 10;
4993 cell = cell * shrink;
4994 }
4995 if ( minimumCount > 1 )
4996 {
4997 cell = cell / minimumCount;
4998 }
4999 }
5000 else
5001 {
5002 cell = dx;
5003 if ( divisions > 1 )
5004 {
5005 cell = cell / divisions;
5006 }
5007 }
5008 if ( cell < 20 * 1e-07 )
5009 {
5010 cell = 20 * 1e-07;
5011 }
5012
5013 const double base = std::pow( 10.0, std::floor( std::log10( cell ) ) );
5014 double unit = base;
5015 if ( ( 2 * base ) - cell < h * ( cell - unit ) )
5016 {
5017 unit = 2.0 * base;
5018 if ( ( 5 * base ) - cell < adjustBias * ( cell - unit ) )
5019 {
5020 unit = 5.0 * base;
5021 if ( ( 10.0 * base ) - cell < h * ( cell - unit ) )
5022 {
5023 unit = 10.0 * base;
5024 }
5025 }
5026 }
5027 // Maybe used to correct for the epsilon here??
5028 int start = std::floor( minimum / unit + 1e-07 );
5029 int end = std::ceil( maximum / unit - 1e-07 );
5030
5031 // Extend the range out beyond the data. Does this ever happen??
5032 while ( start * unit > minimum + ( 1e-07 * unit ) )
5033 {
5034 start = start - 1;
5035 }
5036 while ( end * unit < maximum - ( 1e-07 * unit ) )
5037 {
5038 end = end + 1;
5039 }
5040 QgsDebugMsgLevel( u"pretty classes: %1"_s.arg( end ), 3 );
5041
5042 // If we don't have quite enough labels, extend the range out
5043 // to make more (these labels are beyond the data :()
5044 int k = std::floor( 0.5 + end - start );
5045 if ( k < minimumCount )
5046 {
5047 k = minimumCount - k;
5048 if ( start >= 0 )
5049 {
5050 end = end + k / 2;
5051 start = start - k / 2 + k % 2;
5052 }
5053 else
5054 {
5055 start = start - k / 2;
5056 end = end + k / 2 + k % 2;
5057 }
5058 }
5059 const double minimumBreak = start * unit;
5060 //double maximumBreak = end * unit;
5061 const int count = end - start;
5062
5063 breaks.reserve( count );
5064 for ( int i = 1; i < count + 1; i++ )
5065 {
5066 breaks.append( minimumBreak + i * unit );
5067 }
5068
5069 if ( breaks.isEmpty() )
5070 return breaks;
5071
5072 if ( breaks.first() < minimum )
5073 {
5074 breaks[0] = minimum;
5075 }
5076 if ( breaks.last() > maximum )
5077 {
5078 breaks[breaks.count() - 1] = maximum;
5079 }
5080
5081 // because sometimes when number of classes is big,
5082 // break supposed to be at zero is something like -2.22045e-16
5083 if ( minimum < 0.0 && maximum > 0.0 ) //then there should be a zero somewhere
5084 {
5085 QList<double> breaksMinusZero; // compute difference "each break - 0"
5086 for ( int i = 0; i < breaks.count(); i++ )
5087 {
5088 breaksMinusZero.append( breaks[i] - 0.0 );
5089 }
5090 int posOfMin = 0;
5091 for ( int i = 1; i < breaks.count(); i++ ) // find position of minimal difference
5092 {
5093 if ( std::abs( breaksMinusZero[i] ) < std::abs( breaksMinusZero[i - 1] ) )
5094 posOfMin = i;
5095 }
5096 breaks[posOfMin] = 0.0;
5097 }
5098
5099 return breaks;
5100}
5101
5102double QgsSymbolLayerUtils::rescaleUom( double size, Qgis::RenderUnit unit, const QVariantMap &props )
5103{
5104 double scale = 1;
5105 bool roundToUnit = false;
5106 if ( unit == Qgis::RenderUnit::Unknown )
5107 {
5108 if ( props.contains( u"uomScale"_s ) )
5109 {
5110 bool ok;
5111 scale = props.value( u"uomScale"_s ).toDouble( &ok );
5112 if ( !ok )
5113 {
5114 return size;
5115 }
5116 }
5117 }
5118 else
5119 {
5120 if ( props.value( u"uom"_s ) == "http://www.opengeospatial.org/se/units/metre"_L1 )
5121 {
5122 switch ( unit )
5123 {
5125 scale = 0.001;
5126 break;
5128 scale = 0.00028;
5129 roundToUnit = true;
5130 break;
5131 default:
5132 scale = 1;
5133 }
5134 }
5135 else
5136 {
5137 // target is pixels
5138 switch ( unit )
5139 {
5141 scale = 1 / 0.28;
5142 roundToUnit = true;
5143 break;
5145 scale = 1 / 0.28 * 25.4;
5146 roundToUnit = true;
5147 break;
5149 scale = 90. /* dots per inch according to OGC SLD */ / 72. /* points per inch */;
5150 roundToUnit = true;
5151 break;
5153 // pixel is pixel
5154 scale = 1;
5155 break;
5158 // already handed via uom
5159 scale = 1;
5160 break;
5163 // these do not make sense and should not really reach here
5164 scale = 1;
5165 }
5166 }
5167
5168 }
5169 double rescaled = size * scale;
5170 // round to unit if the result is pixels to avoid a weird looking SLD (people often think
5171 // of pixels as integers, even if SLD allows for float values in there
5172 if ( roundToUnit )
5173 {
5174 rescaled = std::round( rescaled );
5175 }
5176 return rescaled;
5177}
5178
5179QPointF QgsSymbolLayerUtils::rescaleUom( QPointF point, Qgis::RenderUnit unit, const QVariantMap &props )
5180{
5181 const double x = rescaleUom( point.x(), unit, props );
5182 const double y = rescaleUom( point.y(), unit, props );
5183 return QPointF( x, y );
5184}
5185
5186QVector<qreal> QgsSymbolLayerUtils::rescaleUom( const QVector<qreal> &array, Qgis::RenderUnit unit, const QVariantMap &props )
5187{
5188 QVector<qreal> result;
5189 QVector<qreal>::const_iterator it = array.constBegin();
5190 for ( ; it != array.constEnd(); ++it )
5191 {
5192 result.append( rescaleUom( *it, unit, props ) );
5193 }
5194 return result;
5195}
5196
5197void QgsSymbolLayerUtils::applyScaleDependency( QDomDocument &doc, QDomElement &ruleElem, QVariantMap &props )
5198{
5199 if ( !props.value( u"scaleMinDenom"_s, QString() ).toString().isEmpty() )
5200 {
5201 QDomElement scaleMinDenomElem = doc.createElement( u"se:MinScaleDenominator"_s );
5202 scaleMinDenomElem.appendChild( doc.createTextNode( qgsDoubleToString( props.value( u"scaleMinDenom"_s ).toString().toDouble() ) ) );
5203 ruleElem.appendChild( scaleMinDenomElem );
5204 }
5205
5206 if ( !props.value( u"scaleMaxDenom"_s, QString() ).toString().isEmpty() )
5207 {
5208 QDomElement scaleMaxDenomElem = doc.createElement( u"se:MaxScaleDenominator"_s );
5209 scaleMaxDenomElem.appendChild( doc.createTextNode( qgsDoubleToString( props.value( u"scaleMaxDenom"_s ).toString().toDouble() ) ) );
5210 ruleElem.appendChild( scaleMaxDenomElem );
5211 }
5212}
5213
5214void QgsSymbolLayerUtils::mergeScaleDependencies( double mScaleMinDenom, double mScaleMaxDenom, QVariantMap &props )
5215{
5216 if ( !qgsDoubleNear( mScaleMinDenom, 0 ) )
5217 {
5218 bool ok;
5219 const double parentScaleMinDenom = props.value( u"scaleMinDenom"_s, u"0"_s ).toString().toDouble( &ok );
5220 if ( !ok || parentScaleMinDenom <= 0 )
5221 props[ u"scaleMinDenom"_s] = QString::number( mScaleMinDenom );
5222 else
5223 props[ u"scaleMinDenom"_s] = QString::number( std::max( parentScaleMinDenom, mScaleMinDenom ) );
5224 }
5225
5226 if ( !qgsDoubleNear( mScaleMaxDenom, 0 ) )
5227 {
5228 bool ok;
5229 const double parentScaleMaxDenom = props.value( u"scaleMaxDenom"_s, u"0"_s ).toString().toDouble( &ok );
5230 if ( !ok || parentScaleMaxDenom <= 0 )
5231 props[ u"scaleMaxDenom"_s] = QString::number( mScaleMaxDenom );
5232 else
5233 props[ u"scaleMaxDenom"_s] = QString::number( std::min( parentScaleMaxDenom, mScaleMaxDenom ) );
5234 }
5235}
5236
5237double QgsSymbolLayerUtils::sizeInPixelsFromSldUom( const QString &uom, double size )
5238{
5239 double scale = 1.0;
5240
5241 if ( uom == "http://www.opengeospatial.org/se/units/metre"_L1 )
5242 {
5243 scale = 1.0 / 0.00028; // from meters to pixels
5244 }
5245 else if ( uom == "http://www.opengeospatial.org/se/units/foot"_L1 )
5246 {
5247 scale = 304.8 / 0.28; // from feet to pixels
5248 }
5249 else
5250 {
5251 scale = 1.0; // from pixels to pixels (default unit)
5252 }
5253
5254 return size * scale;
5255}
5256
5257QSet<const QgsSymbolLayer *> QgsSymbolLayerUtils::toSymbolLayerPointers( const QgsFeatureRenderer *renderer, const QSet<QgsSymbolLayerId> &symbolLayerIds )
5258{
5260 class SymbolLayerVisitor : public QgsStyleEntityVisitorInterface
5261 {
5262 public:
5263 SymbolLayerVisitor( const QSet<QgsSymbolLayerId> &layerIds )
5264 : mSymbolLayerIds( layerIds )
5265 {}
5266
5267 bool visitEnter( const QgsStyleEntityVisitorInterface::Node &node ) override
5268 {
5270 {
5271 mCurrentRuleKey = node.identifier;
5272 return true;
5273 }
5274 return false;
5275 }
5276
5277 void visitSymbol( const QgsSymbol *symbol, const QString &identifier, QVector<int> rootPath )
5278 {
5279 for ( int idx = 0; idx < symbol->symbolLayerCount(); idx++ )
5280 {
5281 QVector<int> indexPath = rootPath;
5282 indexPath.append( idx );
5283 const QgsSymbolLayer *sl = symbol->symbolLayer( idx );
5285 if ( mSymbolLayerIds.contains( QgsSymbolLayerId( mCurrentRuleKey + identifier, indexPath ) ) )
5286 {
5287 mSymbolLayers.insert( sl );
5288 }
5290
5291 const QgsSymbol *subSymbol = const_cast<QgsSymbolLayer *>( sl )->subSymbol();
5292 if ( subSymbol )
5293 visitSymbol( subSymbol, identifier, indexPath );
5294 }
5295 }
5296
5297 bool visit( const QgsStyleEntityVisitorInterface::StyleLeaf &leaf ) override
5298 {
5299 if ( leaf.entity && leaf.entity->type() == QgsStyle::SymbolEntity )
5300 {
5301 auto symbolEntity = static_cast<const QgsStyleSymbolEntity *>( leaf.entity );
5302 if ( symbolEntity->symbol() )
5303 {
5304 visitSymbol( symbolEntity->symbol(), leaf.identifier, {} );
5305 }
5306 }
5307 return true;
5308 }
5309
5310 QString mCurrentRuleKey;
5311 const QSet<QgsSymbolLayerId> &mSymbolLayerIds;
5312 QSet<const QgsSymbolLayer *> mSymbolLayers;
5313 };
5315
5316 SymbolLayerVisitor visitor( symbolLayerIds );
5317 renderer->accept( &visitor );
5318 return visitor.mSymbolLayers;
5319}
5320
5322{
5323 class SymbolRefreshRateVisitor : public QgsStyleEntityVisitorInterface
5324 {
5325 public:
5326 SymbolRefreshRateVisitor()
5327 {}
5328
5329 bool visitEnter( const QgsStyleEntityVisitorInterface::Node &node ) override
5330 {
5332 {
5333 return true;
5334 }
5335 return false;
5336 }
5337
5338 void visitSymbol( const QgsSymbol *symbol )
5339 {
5340 // symbol may be marked as animated on a symbol level (e.g. when it implements animation
5341 // via data defined properties)
5342 if ( symbol->animationSettings().isAnimated() )
5343 {
5344 if ( symbol->animationSettings().frameRate() > refreshRate )
5345 refreshRate = symbol->animationSettings().frameRate();
5346 }
5347 for ( int idx = 0; idx < symbol->symbolLayerCount(); idx++ )
5348 {
5349 const QgsSymbolLayer *sl = symbol->symbolLayer( idx );
5350 if ( const QgsAnimatedMarkerSymbolLayer *animatedMarker = dynamic_cast< const QgsAnimatedMarkerSymbolLayer *>( sl ) )
5351 {
5352 // this is a bit of a short cut -- if a symbol has multiple layers with different frame rates,
5353 // there's no guarantee that they will be even multiples of each other! But given we are looking for
5354 // a single frame rate for a whole renderer, it's an acceptable compromise...
5355 if ( refreshRate == -1 || ( animatedMarker->frameRate() > refreshRate ) )
5356 refreshRate = animatedMarker->frameRate();
5357 }
5358
5359 if ( const QgsSymbol *subSymbol = const_cast<QgsSymbolLayer *>( sl )->subSymbol() )
5360 visitSymbol( subSymbol );
5361 }
5362 }
5363
5364 bool visit( const QgsStyleEntityVisitorInterface::StyleLeaf &leaf ) override
5365 {
5366 if ( leaf.entity && leaf.entity->type() == QgsStyle::SymbolEntity )
5367 {
5368 if ( QgsSymbol *symbol = qgis::down_cast<const QgsStyleSymbolEntity *>( leaf.entity )->symbol() )
5369 {
5370 visitSymbol( symbol );
5371 }
5372 }
5373 return true;
5374 }
5375
5376 double refreshRate = -1;
5377 };
5378
5379 SymbolRefreshRateVisitor visitor;
5380 renderer->accept( &visitor );
5381 return visitor.refreshRate;
5382}
5383
5384std::unique_ptr< QgsSymbol > QgsSymbolLayerUtils::restrictedSizeSymbol( const QgsSymbol *s, double minSize, double maxSize, QgsRenderContext *context, double &width, double &height, bool *ok )
5385{
5386 if ( !s || !context )
5387 {
5388 return nullptr;
5389 }
5390
5391 if ( ok )
5392 *ok = true;
5393
5394 const QgsSymbolLayerList sls = s->symbolLayers();
5395 for ( const QgsSymbolLayer *sl : std::as_const( sls ) )
5396 {
5397 // geometry generators involved, there is no way to get a restricted size symbol
5398 if ( sl->type() == Qgis::SymbolType::Hybrid )
5399 {
5400 if ( ok )
5401 *ok = false;
5402
5403 return nullptr;
5404 }
5405 }
5406
5407 double size;
5408 const QgsMarkerSymbol *markerSymbol = dynamic_cast<const QgsMarkerSymbol *>( s );
5409 const QgsLineSymbol *lineSymbol = dynamic_cast<const QgsLineSymbol *>( s );
5410 if ( markerSymbol )
5411 {
5412 size = markerSymbol->size( *context );
5413 }
5414 else if ( lineSymbol )
5415 {
5416 size = lineSymbol->width( *context );
5417 }
5418 else
5419 {
5420 // cannot return a size restricted symbol but we assume there is no need
5421 // for one as the rendering will be done in the given size (different from geometry
5422 // generator where rendering will bleed outside the given area
5423 return nullptr;
5424 }
5425
5426 size /= context->scaleFactor();
5427
5428 if ( minSize > 0 && size < minSize )
5429 {
5430 size = minSize;
5431 }
5432 else if ( maxSize > 0 && size > maxSize )
5433 {
5434 size = maxSize;
5435 }
5436 else
5437 {
5438 // no need to restricted size symbol
5439 return nullptr;
5440 }
5441
5442 if ( markerSymbol )
5443 {
5444 std::unique_ptr< QgsMarkerSymbol > ms( markerSymbol->clone() );
5445 ms->setSize( size );
5446 ms->setSizeUnit( Qgis::RenderUnit::Millimeters );
5447 width = size;
5448 height = size;
5449 return ms;
5450 }
5451 else if ( lineSymbol )
5452 {
5453 std::unique_ptr< QgsLineSymbol > ls( lineSymbol->clone() );
5454 ls->setWidth( size );
5455 ls->setWidthUnit( Qgis::RenderUnit::Millimeters );
5456 height = size;
5457 return ls;
5458 }
5459
5460 return nullptr;
5461}
5462
5463QgsStringMap QgsSymbolLayerUtils::evaluatePropertiesMap( const QMap<QString, QgsProperty> &propertiesMap, const QgsExpressionContext &context )
5464{
5465 QgsStringMap properties;
5466 QMap<QString, QgsProperty>::const_iterator paramIt = propertiesMap.constBegin();
5467 for ( ; paramIt != propertiesMap.constEnd(); ++paramIt )
5468 {
5469 properties.insert( paramIt.key(), paramIt.value().valueAsString( context ) );
5470 }
5471 return properties;
5472}
5473
5474QSize QgsSymbolLayerUtils::tileSize( int width, int height, double &angleRad )
5475{
5476
5477 angleRad = std::fmod( angleRad, M_PI * 2 );
5478
5479 if ( angleRad < 0 )
5480 {
5481 angleRad += M_PI * 2;
5482 }
5483
5484 // tan with rational sin/cos
5485 struct rationalTangent
5486 {
5487 int p; // numerator
5488 int q; // denominator
5489 double angle; // "good" angle
5490 };
5491
5492#if 0
5493
5494 // This list is more granular (approx 1 degree steps) but some
5495 // values can lead to huge tiles
5496 // List of "good" angles from 0 to PI/2
5497 static const QList<rationalTangent> __rationalTangents
5498 {
5499 { 1, 57, 0.01754206006 },
5500 { 3, 86, 0.03486958155 },
5501 { 1, 19, 0.05258306161 },
5502 { 3, 43, 0.06965457373 },
5503 { 7, 80, 0.08727771295 },
5504 { 2, 19, 0.1048769387 },
5505 { 7, 57, 0.1221951707 },
5506 { 9, 64, 0.1397088743 },
5507 { 13, 82, 0.157228051 },
5508 { 3, 17, 0.174672199 },
5509 { 7, 36, 0.1920480172 },
5510 { 17, 80, 0.209385393 },
5511 { 3, 13, 0.2267988481 },
5512 { 1, 4, 0.2449786631 },
5513 { 26, 97, 0.2618852647 },
5514 { 27, 94, 0.2797041525 },
5515 { 26, 85, 0.2968446734 },
5516 { 13, 40, 0.3142318991 },
5517 { 21, 61, 0.3315541619 },
5518 { 4, 11, 0.3487710036 },
5519 { 38, 99, 0.3664967859 },
5520 { 40, 99, 0.383984624 },
5521 { 31, 73, 0.4015805401 },
5522 { 41, 92, 0.4192323938 },
5523 { 7, 15, 0.4366271598 },
5524 { 20, 41, 0.4538440015 },
5525 { 27, 53, 0.4711662643 },
5526 { 42, 79, 0.4886424026 },
5527 { 51, 92, 0.5061751436 },
5528 { 56, 97, 0.5235757641 },
5529 { 3, 5, 0.5404195003 },
5530 { 5, 8, 0.5585993153 },
5531 { 50, 77, 0.5759185996 },
5532 { 29, 43, 0.5933501462 },
5533 { 7, 10, 0.6107259644 },
5534 { 69, 95, 0.6281701124 },
5535 { 52, 69, 0.6458159195 },
5536 { 25, 32, 0.6632029927 },
5537 { 17, 21, 0.6805212247 },
5538 { 73, 87, 0.6981204504 },
5539 { 73, 84, 0.7154487784 },
5540 { 9, 10, 0.7328151018 },
5541 { 83, 89, 0.7505285818 },
5542 { 28, 29, 0.7678561033 },
5543 { 1, 1, 0.7853981634 },
5544 { 29, 28, 0.8029402235 },
5545 { 89, 83, 0.820267745 },
5546 { 10, 9, 0.837981225 },
5547 { 107, 93, 0.855284165 },
5548 { 87, 73, 0.8726758763 },
5549 { 121, 98, 0.8900374031 },
5550 { 32, 25, 0.9075933341 },
5551 { 69, 52, 0.9249804073 },
5552 { 128, 93, 0.9424647244 },
5553 { 10, 7, 0.9600703624 },
5554 { 43, 29, 0.9774461806 },
5555 { 77, 50, 0.9948777272 },
5556 { 8, 5, 1.012197011 },
5557 { 163, 98, 1.029475114 },
5558 { 168, 97, 1.047174539 },
5559 { 175, 97, 1.064668696 },
5560 { 126, 67, 1.082075603 },
5561 { 157, 80, 1.099534652 },
5562 { 203, 99, 1.117049384 },
5563 { 193, 90, 1.134452855 },
5564 { 146, 65, 1.151936673 },
5565 { 139, 59, 1.169382787 },
5566 { 99, 40, 1.186811703 },
5567 { 211, 81, 1.204257817 },
5568 { 272, 99, 1.221730164 },
5569 { 273, 94, 1.239188479 },
5570 { 277, 90, 1.25664606 },
5571 { 157, 48, 1.274088705 },
5572 { 279, 80, 1.291550147 },
5573 { 362, 97, 1.308990773 },
5574 { 373, 93, 1.326448578 },
5575 { 420, 97, 1.343823596 },
5576 { 207, 44, 1.361353157 },
5577 { 427, 83, 1.378810994 },
5578 { 414, 73, 1.396261926 },
5579 { 322, 51, 1.413716057 },
5580 { 185, 26, 1.431170275 },
5581 { 790, 97, 1.448623034 },
5582 { 333, 35, 1.466075711 },
5583 { 1063, 93, 1.483530284 },
5584 { 1330, 93, 1.500985147 },
5585 { 706, 37, 1.518436297 },
5586 { 315, 11, 1.535889876 },
5587 { 3953, 69, 1.553343002 },
5588 };
5589#endif
5590
5591 // Optimized "good" angles list, it produces small tiles but
5592 // it has approximately 10 degrees steps
5593 static const QList<rationalTangent> rationalTangents
5594 {
5595 { 1, 10, qDegreesToRadians( 5.71059 ) },
5596 { 1, 5, qDegreesToRadians( 11.3099 ) },
5597 { 1, 4, qDegreesToRadians( 14.0362 ) },
5598 { 1, 4, qDegreesToRadians( 18.4349 ) },
5599 { 1, 2, qDegreesToRadians( 26.5651 ) },
5600 { 2, 3, qDegreesToRadians( 33.6901 ) },
5601 { 1, 1, qDegreesToRadians( 45.0 ) },
5602 { 3, 2, qDegreesToRadians( 56.3099 ) },
5603 { 2, 1, qDegreesToRadians( 63.4349 ) },
5604 { 3, 1, qDegreesToRadians( 71.5651 ) },
5605 { 4, 1, qDegreesToRadians( 75.9638 ) },
5606 { 10, 1, qDegreesToRadians( 84.2894 ) },
5607 };
5608
5609 const int quadrant { static_cast<int>( angleRad / M_PI_2 ) };
5610 Q_ASSERT( quadrant >= 0 && quadrant <= 3 );
5611
5612 QSize tileSize;
5613
5614 switch ( quadrant )
5615 {
5616 case 0:
5617 {
5618 break;
5619 }
5620 case 1:
5621 {
5622 angleRad -= M_PI / 2;
5623 break;
5624 }
5625 case 2:
5626 {
5627 angleRad -= M_PI;
5628 break;
5629 }
5630 case 3:
5631 {
5632 angleRad -= M_PI + M_PI_2;
5633 break;
5634 }
5635 }
5636
5637 if ( qgsDoubleNear( angleRad, 0, 10E-3 ) )
5638 {
5639 angleRad = 0;
5640 tileSize.setWidth( width );
5641 tileSize.setHeight( height );
5642 }
5643 else if ( qgsDoubleNear( angleRad, M_PI_2, 10E-3 ) )
5644 {
5645 angleRad = M_PI_2;
5646 tileSize.setWidth( height );
5647 tileSize.setHeight( width );
5648 }
5649 else
5650 {
5651
5652 int rTanIdx = 0;
5653
5654 for ( int idx = 0; idx < rationalTangents.count(); ++idx )
5655 {
5656 const auto item = rationalTangents.at( idx );
5657 if ( qgsDoubleNear( item.angle, angleRad, 10E-3 ) || item.angle > angleRad )
5658 {
5659 rTanIdx = idx;
5660 break;
5661 }
5662 }
5663
5664 const rationalTangent bTan { rationalTangents.at( rTanIdx ) };
5665 angleRad = bTan.angle;
5666 const double k { bTan.q *height *width / std::cos( angleRad ) };
5667 const int hcfH { std::gcd( bTan.p * height, bTan.q * width ) };
5668 const int hcfW { std::gcd( bTan.q * height, bTan.p * width ) };
5669 const int W1 { static_cast<int>( std::round( k / hcfW ) ) };
5670 const int H1 { static_cast<int>( std::round( k / hcfH ) ) };
5671 tileSize.setWidth( W1 );
5672 tileSize.setHeight( H1 );
5673 }
5674
5675 switch ( quadrant )
5676 {
5677 case 0:
5678 {
5679 break;
5680 }
5681 case 1:
5682 {
5683 angleRad += M_PI / 2;
5684 const int h { tileSize.height() };
5685 tileSize.setHeight( tileSize.width() );
5686 tileSize.setWidth( h );
5687 break;
5688 }
5689 case 2:
5690 {
5691 angleRad += M_PI;
5692 break;
5693 }
5694 case 3:
5695 {
5696 angleRad += M_PI + M_PI_2;
5697 const int h { tileSize.height() };
5698 tileSize.setHeight( tileSize.width() );
5699 tileSize.setWidth( h );
5700 break;
5701 }
5702 }
5703
5704 return tileSize;
5705}
5706
5707template <typename Functor>
5708void changeSymbolLayerIds( QgsSymbolLayer *sl, Functor &&generateId )
5709{
5710 sl->setId( generateId() );
5711
5712 // recurse over sub symbols
5713 QgsSymbol *subSymbol = sl->subSymbol();
5714 if ( subSymbol )
5715 changeSymbolLayerIds( subSymbol, generateId );
5716}
5717
5718template <typename Functor>
5719void changeSymbolLayerIds( QgsSymbol *symbol, Functor &&generateId )
5720{
5721 if ( !symbol )
5722 return;
5723
5724 for ( int idx = 0; idx < symbol->symbolLayerCount(); idx++ )
5725 changeSymbolLayerIds( symbol->symbolLayer( idx ), generateId );
5726}
5727
5729{
5730 changeSymbolLayerIds( symbol, []() { return QString(); } );
5731}
5732
5734{
5735 changeSymbolLayerIds( symbolLayer, []() { return QString(); } );
5736}
5737
5739{
5740 changeSymbolLayerIds( symbolLayer, []() { return QUuid::createUuid().toString(); } );
5741}
5742
5744{
5745 if ( !symbol )
5746 return;
5747
5748 for ( int idx = 0; idx < symbol->symbolLayerCount(); idx++ )
5749 {
5750 if ( QgsMaskMarkerSymbolLayer *maskSl = dynamic_cast<QgsMaskMarkerSymbolLayer *>( symbol->symbolLayer( idx ) ) )
5751 {
5752 maskSl->clearMasks();
5753
5754 // recurse over sub symbols
5755 if ( QgsSymbol *subSymbol = maskSl->subSymbol() )
5756 {
5757 clearSymbolLayerMasks( subSymbol );
5758 }
5759 }
5760 }
5761}
5762
5763QVector<QgsGeometry> QgsSymbolLayerUtils::collectSymbolLayerClipGeometries( const QgsRenderContext &context, const QString &symbolLayerId, const QRectF &bounds )
5764{
5765 QVector<QgsGeometry> clipGeometries = context.symbolLayerClipGeometries( symbolLayerId );
5766 if ( clipGeometries.empty() )
5767 return {};
5768
5769 if ( bounds.isNull() )
5770 return clipGeometries;
5771
5772 const QgsRectangle boundsRect = QgsRectangle( bounds );
5773
5774 clipGeometries.erase(
5775 std::remove_if( clipGeometries.begin(), clipGeometries.end(), [&boundsRect]( const QgsGeometry & geometry )
5776 {
5777 return !geometry.boundingBoxIntersects( boundsRect );
5778 } ), clipGeometries.end() );
5779
5780 return clipGeometries;
5781}
5782
5784{
5785 changeSymbolLayerIds( symbol, []() { return QUuid::createUuid().toString(); } );
5786}
@ 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:3308
@ CompletelyWithin
Render complete markers wherever the completely fall within the polygon shape.
Definition qgis.h:3312
@ NoClipping
No clipping, render complete markers.
Definition qgis.h:3309
@ Shape
Clip to polygon shape.
Definition qgis.h:3310
@ CentroidWithin
Render complete markers wherever their centroid falls within the polygon shape.
Definition qgis.h:3311
LineClipMode
Line clipping modes.
Definition qgis.h:3322
@ NoClipping
Lines are not clipped, will extend to shape's bounding box.
Definition qgis.h:3325
@ ClipPainterOnly
Applying clipping on the painter only (i.e. line endpoints will coincide with polygon bounding box,...
Definition qgis.h:3323
@ ClipToIntersection
Clip lines to intersection with polygon shape (slower) (i.e. line endpoints will coincide with polygo...
Definition qgis.h:3324
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:5255
@ Percentage
Percentage of another measurement (e.g., canvas size, feature size).
Definition qgis.h:5259
@ Millimeters
Millimeters.
Definition qgis.h:5256
@ Points
Points (e.g., for font sizes).
Definition qgis.h:5260
@ Unknown
Mixed or unknown units.
Definition qgis.h:5262
@ MapUnits
Map units.
Definition qgis.h:5257
@ Pixels
Pixels.
Definition qgis.h:5258
@ Inches
Inches.
Definition qgis.h:5261
@ MetersInMapUnits
Meters value as Map units.
Definition qgis.h:5263
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:3266
@ Feature
Relative to feature/shape being rendered.
Definition qgis.h:3267
@ Viewport
Relative to the whole viewport/output device.
Definition qgis.h:3268
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.
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:7451
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
Definition qgis.h:6817
QString qgsFlagValueToKeys(const T &value, bool *returnOk=nullptr)
Returns the value for the given keys of a flag.
Definition qgis.h:7149
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:7171
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:7450
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:6900
T qgsgeometry_cast(QgsAbstractGeometry *geom)
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.