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