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