QGIS API Documentation 3.43.0-Master (a93bf8b6462)
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 "qgssymbollayer.h"
20#include "qgssymbol.h"
21#include "qgscolorramp.h"
22#include "qgscolorrampimpl.h"
23#include "qgscolorutils.h"
24#include "qgsexpression.h"
25#include "qgsexpressionnode.h"
26#include "qgspainteffect.h"
28#include "qgsapplication.h"
29#include "qgspathresolver.h"
30#include "qgsogcutils.h"
31#include "qgslogger.h"
32#include "qgsreadwritecontext.h"
33#include "qgsrendercontext.h"
34#include "qgsunittypes.h"
37#include "qgsrenderer.h"
38#include "qgsxmlutils.h"
39#include "qgsfillsymbollayer.h"
40#include "qgslinesymbollayer.h"
41#include "qgslinesymbol.h"
42#include "qgsmarkersymbol.h"
43#include "qgsfillsymbol.h"
46#include "qgscurvepolygon.h"
47#include "qgsmasksymbollayer.h"
48#include "qgssldexportcontext.h"
49
50#include "qmath.h"
51#include <QColor>
52#include <QFont>
53#include <QDomDocument>
54#include <QDomNode>
55#include <QDomElement>
56#include <QIcon>
57#include <QPainter>
58#include <QSettings>
59#include <QPicture>
60#include <QUrl>
61#include <QUrlQuery>
62#include <QMimeData>
63#include <QRegularExpression>
64#include <QDir>
65
66#define POINTS_TO_MM 2.83464567
67
68QString QgsSymbolLayerUtils::encodeColor( const QColor &color )
69{
70 return QStringLiteral( "%1,%2,%3,%4" ).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 QStringLiteral( "normal" );
114 case QFont::StyleItalic:
115 return QStringLiteral( "italic" );
116 case QFont::StyleOblique:
117 return QStringLiteral( "oblique" );
118 default:
119 return QString();
120 }
121}
122
123QFont::Style QgsSymbolLayerUtils::decodeSldFontStyle( const QString &str )
124{
125 if ( str == QLatin1String( "normal" ) ) return QFont::StyleNormal;
126 if ( str == QLatin1String( "italic" ) ) return QFont::StyleItalic;
127 if ( str == QLatin1String( "oblique" ) ) return QFont::StyleOblique;
128 return QFont::StyleNormal;
129}
130
132{
133 if ( weight == 50 ) return QStringLiteral( "normal" );
134 if ( weight == 75 ) return QStringLiteral( "bold" );
135
136 // QFont::Weight is between 0 and 99
137 // CSS font-weight is between 100 and 900
138 if ( weight < 0 ) return QStringLiteral( "100" );
139 if ( weight > 99 ) return QStringLiteral( "900" );
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 QStringLiteral( "no" );
163 case Qt::SolidLine:
164 return QStringLiteral( "solid" );
165 case Qt::DashLine:
166 return QStringLiteral( "dash" );
167 case Qt::DotLine:
168 return QStringLiteral( "dot" );
169 case Qt::DashDotLine:
170 return QStringLiteral( "dash dot" );
171 case Qt::DashDotDotLine:
172 return QStringLiteral( "dash dot dot" );
173 default:
174 return QStringLiteral( "???" );
175 }
176}
177
178Qt::PenStyle QgsSymbolLayerUtils::decodePenStyle( const QString &str )
179{
180 if ( str == QLatin1String( "no" ) ) return Qt::NoPen;
181 if ( str == QLatin1String( "solid" ) ) return Qt::SolidLine;
182 if ( str == QLatin1String( "dash" ) ) return Qt::DashLine;
183 if ( str == QLatin1String( "dot" ) ) return Qt::DotLine;
184 if ( str == QLatin1String( "dash dot" ) ) return Qt::DashDotLine;
185 if ( str == QLatin1String( "dash dot dot" ) ) 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 QStringLiteral( "bevel" );
195 case Qt::MiterJoin:
196 return QStringLiteral( "miter" );
197 case Qt::RoundJoin:
198 return QStringLiteral( "round" );
199 default:
200 return QStringLiteral( "???" );
201 }
202}
203
204Qt::PenJoinStyle QgsSymbolLayerUtils::decodePenJoinStyle( const QString &str )
205{
206 const QString cleaned = str.toLower().trimmed();
207 if ( cleaned == QLatin1String( "bevel" ) )
208 return Qt::BevelJoin;
209 if ( cleaned == QLatin1String( "miter" ) )
210 return Qt::MiterJoin;
211 if ( cleaned == QLatin1String( "round" ) )
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 QStringLiteral( "bevel" );
222 case Qt::MiterJoin:
223 return QStringLiteral( "mitre" ); //#spellok
224 case Qt::RoundJoin:
225 return QStringLiteral( "round" );
226 default:
227 return QString();
228 }
229}
230
231Qt::PenJoinStyle QgsSymbolLayerUtils::decodeSldLineJoinStyle( const QString &str )
232{
233 if ( str == QLatin1String( "bevel" ) ) return Qt::BevelJoin;
234 if ( str == QLatin1String( "mitre" ) ) return Qt::MiterJoin; //#spellok
235 if ( str == QLatin1String( "round" ) ) 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 QStringLiteral( "square" );
245 case Qt::FlatCap:
246 return QStringLiteral( "flat" );
247 case Qt::RoundCap:
248 return QStringLiteral( "round" );
249 default:
250 return QStringLiteral( "???" );
251 }
252}
253
254Qt::PenCapStyle QgsSymbolLayerUtils::decodePenCapStyle( const QString &str )
255{
256 if ( str == QLatin1String( "square" ) ) return Qt::SquareCap;
257 if ( str == QLatin1String( "flat" ) ) return Qt::FlatCap;
258 if ( str == QLatin1String( "round" ) ) 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 QStringLiteral( "square" );
268 case Qt::FlatCap:
269 return QStringLiteral( "butt" );
270 case Qt::RoundCap:
271 return QStringLiteral( "round" );
272 default:
273 return QString();
274 }
275}
276
277Qt::PenCapStyle QgsSymbolLayerUtils::decodeSldLineCapStyle( const QString &str )
278{
279 if ( str == QLatin1String( "square" ) ) return Qt::SquareCap;
280 if ( str == QLatin1String( "butt" ) ) return Qt::FlatCap;
281 if ( str == QLatin1String( "round" ) ) 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 QStringLiteral( "solid" );
291 case Qt::HorPattern :
292 return QStringLiteral( "horizontal" );
293 case Qt::VerPattern :
294 return QStringLiteral( "vertical" );
295 case Qt::CrossPattern :
296 return QStringLiteral( "cross" );
297 case Qt::BDiagPattern :
298 return QStringLiteral( "b_diagonal" );
299 case Qt::FDiagPattern :
300 return QStringLiteral( "f_diagonal" );
301 case Qt::DiagCrossPattern :
302 return QStringLiteral( "diagonal_x" );
303 case Qt::Dense1Pattern :
304 return QStringLiteral( "dense1" );
305 case Qt::Dense2Pattern :
306 return QStringLiteral( "dense2" );
307 case Qt::Dense3Pattern :
308 return QStringLiteral( "dense3" );
309 case Qt::Dense4Pattern :
310 return QStringLiteral( "dense4" );
311 case Qt::Dense5Pattern :
312 return QStringLiteral( "dense5" );
313 case Qt::Dense6Pattern :
314 return QStringLiteral( "dense6" );
315 case Qt::Dense7Pattern :
316 return QStringLiteral( "dense7" );
317 case Qt::NoBrush :
318 return QStringLiteral( "no" );
319 default:
320 return QStringLiteral( "???" );
321 }
322}
323
324Qt::BrushStyle QgsSymbolLayerUtils::decodeBrushStyle( const QString &str )
325{
326 if ( str == QLatin1String( "solid" ) ) return Qt::SolidPattern;
327 if ( str == QLatin1String( "horizontal" ) ) return Qt::HorPattern;
328 if ( str == QLatin1String( "vertical" ) ) return Qt::VerPattern;
329 if ( str == QLatin1String( "cross" ) ) return Qt::CrossPattern;
330 if ( str == QLatin1String( "b_diagonal" ) ) return Qt::BDiagPattern;
331 if ( str == QLatin1String( "f_diagonal" ) ) return Qt::FDiagPattern;
332 if ( str == QLatin1String( "diagonal_x" ) ) return Qt::DiagCrossPattern;
333 if ( str == QLatin1String( "dense1" ) ) return Qt::Dense1Pattern;
334 if ( str == QLatin1String( "dense2" ) ) return Qt::Dense2Pattern;
335 if ( str == QLatin1String( "dense3" ) ) return Qt::Dense3Pattern;
336 if ( str == QLatin1String( "dense4" ) ) return Qt::Dense4Pattern;
337 if ( str == QLatin1String( "dense5" ) ) return Qt::Dense5Pattern;
338 if ( str == QLatin1String( "dense6" ) ) return Qt::Dense6Pattern;
339 if ( str == QLatin1String( "dense7" ) ) return Qt::Dense7Pattern;
340 if ( str == QLatin1String( "no" ) ) 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 QStringLiteral( "cross" );
350 case Qt::DiagCrossPattern:
351 return QStringLiteral( "x" );
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 QStringLiteral( "horline" );
359 case Qt::VerPattern:
360 return QStringLiteral( "line" );
361 case Qt::BDiagPattern:
362 return QStringLiteral( "slash" );
363 case Qt::FDiagPattern:
364 return QStringLiteral( "backslash" );
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 QStringLiteral( "brush://%1" ).arg( encodeBrushStyle( style ) );
375
376 default:
377 return QString();
378 }
379}
380
381Qt::BrushStyle QgsSymbolLayerUtils::decodeSldBrushStyle( const QString &str )
382{
383 if ( str == QLatin1String( "horline" ) ) return Qt::HorPattern;
384 if ( str == QLatin1String( "line" ) ) return Qt::VerPattern;
385 if ( str == QLatin1String( "cross" ) ) return Qt::CrossPattern;
386 if ( str == QLatin1String( "slash" ) ) return Qt::BDiagPattern;
387 if ( str == QLatin1String( "backshash" ) ) return Qt::FDiagPattern;
388 if ( str == QLatin1String( "x" ) ) return Qt::DiagCrossPattern;
389
390 if ( str.startsWith( QLatin1String( "brush://" ) ) )
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() == QLatin1String( "se:LineSymbolizer" )
439 || childElement.tagName() == QLatin1String( "se:PointSymbolizer" )
440 || childElement.tagName() == QLatin1String( "se:PolygonSymbolizer" ) )
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( QLatin1String( "feature" ), Qt::CaseInsensitive ) == 0 )
454 else if ( compareString.compare( QLatin1String( "viewport" ), Qt::CaseInsensitive ) == 0 )
456
457 if ( ok )
458 *ok = false;
460}
461
463{
464 switch ( coordinateReference )
465 {
467 return QStringLiteral( "feature" );
469 return QStringLiteral( "viewport" );
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 == QLatin1String( "single" ) )
483 else if ( s == QLatin1String( "reversed" ) )
485 else if ( s == QLatin1String( "double" ) )
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 == QLatin1String( "plain" ) )
508 else if ( s == QLatin1String( "lefthalf" ) )
510 else if ( s == QLatin1String( "righthalf" ) )
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( QLatin1String( "no" ), Qt::CaseInsensitive ) == 0 )
532 else if ( compareString.compare( QLatin1String( "shape" ), Qt::CaseInsensitive ) == 0 )
534 else if ( compareString.compare( QLatin1String( "centroid_within" ), Qt::CaseInsensitive ) == 0 )
536 else if ( compareString.compare( QLatin1String( "completely_within" ), Qt::CaseInsensitive ) == 0 )
538
539 if ( ok )
540 *ok = false;
542}
543
545{
546 switch ( mode )
547 {
549 return QStringLiteral( "no" );
551 return QStringLiteral( "shape" );
553 return QStringLiteral( "centroid_within" );
555 return QStringLiteral( "completely_within" );
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( QLatin1String( "no" ), Qt::CaseInsensitive ) == 0 )
568 else if ( compareString.compare( QLatin1String( "during_render" ), Qt::CaseInsensitive ) == 0 )
570 else if ( compareString.compare( QLatin1String( "before_render" ), Qt::CaseInsensitive ) == 0 )
572
573 if ( ok )
574 *ok = false;
576}
577
579{
580 switch ( mode )
581 {
583 return QStringLiteral( "no" );
585 return QStringLiteral( "during_render" );
587 return QStringLiteral( "before_render" );
588 }
589 return QString(); // no warnings
590}
591
592QString QgsSymbolLayerUtils::encodePoint( QPointF point )
593{
594 return QStringLiteral( "%1,%2" ).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 QStringLiteral( "%1,%2" ).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 QStringLiteral( "3x:%1,%2,%3,%4,%5,%6" ).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( QLatin1String( "3x:" ) ) )
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 QStringLiteral( "http://www.opengeospatial.org/se/units/metre" );
776
778 if ( scaleFactor )
779 *scaleFactor = 1.0; // from meters to meters
780 return QStringLiteral( "http://www.opengeospatial.org/se/units/metre" );
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 == QLatin1String( "http://www.opengeospatial.org/se/units/metre" ) )
797 {
798 if ( scaleFactor )
799 *scaleFactor = 1.0; // from meters to meters
801 }
802 else if ( str == QLatin1String( "http://www.opengeospatial.org/se/units/foot" ) )
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 = QStringLiteral( "diameter" );
884 break;
886 encodedValue = QStringLiteral( "area" );
887 break;
888 }
889 return encodedValue;
890}
891
893{
894 Qgis::ScaleMethod scaleMethod;
895
896 if ( str == QLatin1String( "diameter" ) )
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( QLatin1String( "Lighten" ), Qt::CaseInsensitive ) == 0 ) return QPainter::CompositionMode_Lighten;
911 if ( s.compare( QLatin1String( "Screen" ), Qt::CaseInsensitive ) == 0 ) return QPainter::CompositionMode_Screen;
912 if ( s.compare( QLatin1String( "Dodge" ), Qt::CaseInsensitive ) == 0 ) return QPainter::CompositionMode_ColorDodge;
913 if ( s.compare( QLatin1String( "Addition" ), Qt::CaseInsensitive ) == 0 ) return QPainter::CompositionMode_Plus;
914 if ( s.compare( QLatin1String( "Darken" ), Qt::CaseInsensitive ) == 0 ) return QPainter::CompositionMode_Darken;
915 if ( s.compare( QLatin1String( "Multiply" ), Qt::CaseInsensitive ) == 0 ) return QPainter::CompositionMode_Multiply;
916 if ( s.compare( QLatin1String( "Burn" ), Qt::CaseInsensitive ) == 0 ) return QPainter::CompositionMode_ColorBurn;
917 if ( s.compare( QLatin1String( "Overlay" ), Qt::CaseInsensitive ) == 0 ) return QPainter::CompositionMode_Overlay;
918 if ( s.compare( QLatin1String( "SoftLight" ), Qt::CaseInsensitive ) == 0 ) return QPainter::CompositionMode_SoftLight;
919 if ( s.compare( QLatin1String( "HardLight" ), Qt::CaseInsensitive ) == 0 ) return QPainter::CompositionMode_HardLight;
920 if ( s.compare( QLatin1String( "Difference" ), Qt::CaseInsensitive ) == 0 ) return QPainter::CompositionMode_Difference;
921 if ( s.compare( QLatin1String( "Subtract" ), 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() != QLatin1String( "data_defined_properties" ) && e.tagName() != QLatin1String( "buffer" ) )
1293 {
1294 if ( e.tagName() != QLatin1String( "layer" ) )
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( QStringLiteral( "symbol" ) );
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( QStringLiteral( "symbol layer refused subsymbol: " ) + 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( QStringLiteral( "no layers for symbol" ) );
1344 return nullptr;
1345 }
1346
1347 const QString symbolType = element.attribute( QStringLiteral( "type" ) );
1348
1349 std::unique_ptr< QgsSymbol > symbol;
1350 if ( symbolType == QLatin1String( "line" ) )
1351 symbol = std::make_unique< QgsLineSymbol >( layers );
1352 else if ( symbolType == QLatin1String( "fill" ) )
1353 symbol = std::make_unique< QgsFillSymbol >( layers );
1354 else if ( symbolType == QLatin1String( "marker" ) )
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( QStringLiteral( "outputUnit" ) ) )
1363 {
1364 symbol->setOutputUnit( QgsUnitTypes::decodeRenderUnit( element.attribute( QStringLiteral( "outputUnit" ) ) ) );
1365 }
1366 if ( element.hasAttribute( ( QStringLiteral( "mapUnitScale" ) ) ) )
1367 {
1368 QgsMapUnitScale mapUnitScale;
1369 const double oldMin = element.attribute( QStringLiteral( "mapUnitMinScale" ), QStringLiteral( "0.0" ) ).toDouble();
1370 mapUnitScale.minScale = oldMin != 0 ? 1.0 / oldMin : 0;
1371 const double oldMax = element.attribute( QStringLiteral( "mapUnitMaxScale" ), QStringLiteral( "0.0" ) ).toDouble();
1372 mapUnitScale.maxScale = oldMax != 0 ? 1.0 / oldMax : 0;
1373 symbol->setMapUnitScale( mapUnitScale );
1374 }
1375 symbol->setOpacity( element.attribute( QStringLiteral( "alpha" ), QStringLiteral( "1.0" ) ).toDouble() );
1376 symbol->setExtentBuffer( element.attribute( QStringLiteral( "extent_buffer" ), QStringLiteral( "0.0" ) ).toDouble() );
1377 symbol->setExtentBufferSizeUnit( QgsUnitTypes::decodeRenderUnit( element.attribute( QStringLiteral( "extent_buffer_unit" ), QStringLiteral( "MapUnit" ) ) ) );
1378 symbol->setClipFeaturesToExtent( element.attribute( QStringLiteral( "clip_to_extent" ), QStringLiteral( "1" ) ).toInt() );
1379 symbol->setForceRHR( element.attribute( QStringLiteral( "force_rhr" ), QStringLiteral( "0" ) ).toInt() );
1380 Qgis::SymbolFlags flags;
1381 if ( element.attribute( QStringLiteral( "renderer_should_use_levels" ), QStringLiteral( "0" ) ).toInt() )
1383 symbol->setFlags( flags );
1384
1385 symbol->animationSettings().setIsAnimated( element.attribute( QStringLiteral( "is_animated" ), QStringLiteral( "0" ) ).toInt() );
1386 symbol->animationSettings().setFrameRate( element.attribute( QStringLiteral( "frame_rate" ), QStringLiteral( "10" ) ).toDouble() );
1387
1388 if ( !element.firstChildElement( QStringLiteral( "buffer" ) ).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( QStringLiteral( "data_defined_properties" ) );
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( QStringLiteral( "class" ) );
1411 const bool locked = element.attribute( QStringLiteral( "locked" ) ).toInt();
1412 const bool enabled = element.attribute( QStringLiteral( "enabled" ), QStringLiteral( "1" ) ).toInt();
1413 const int pass = element.attribute( QStringLiteral( "pass" ) ).toInt();
1414 const QString id = element.attribute( QStringLiteral( "id" ) );
1415 const Qgis::SymbolLayerUserFlags userFlags = qgsFlagKeysToValue( element.attribute( QStringLiteral( "userFlags" ) ), 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( QStringLiteral( "effect" ) );
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( QStringLiteral( "data_defined_properties" ) );
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 QStringLiteral( "line" );
1479 return QStringLiteral( "marker" );
1481 return QStringLiteral( "fill" );
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( QStringLiteral( "symbol" ) );
1491 symEl.setAttribute( QStringLiteral( "type" ), _nameForSymbolType( symbol->type() ) );
1492 symEl.setAttribute( QStringLiteral( "name" ), name );
1493 symEl.setAttribute( QStringLiteral( "alpha" ), QString::number( symbol->opacity() ) );
1494 symEl.setAttribute( QStringLiteral( "clip_to_extent" ), symbol->clipFeaturesToExtent() ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
1495 if ( !qgsDoubleNear( symbol->extentBuffer(), 0 ) )
1496 {
1497 symEl.setAttribute( QStringLiteral( "extent_buffer" ), QString::number( symbol->extentBuffer() ) );
1498 symEl.setAttribute( QStringLiteral( "extent_buffer_unit" ), QgsUnitTypes::encodeUnit( symbol->extentBufferSizeUnit() ) );
1499 }
1500 symEl.setAttribute( QStringLiteral( "force_rhr" ), symbol->forceRHR() ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
1502 symEl.setAttribute( QStringLiteral( "renderer_should_use_levels" ), QStringLiteral( "1" ) );
1503
1504 symEl.setAttribute( QStringLiteral( "is_animated" ), symbol->animationSettings().isAnimated() ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
1505 symEl.setAttribute( QStringLiteral( "frame_rate" ), 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( QStringLiteral( "data_defined_properties" ) );
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( QStringLiteral( "layer" ) );
1521 layerEl.setAttribute( QStringLiteral( "class" ), layer->layerType() );
1522 layerEl.setAttribute( QStringLiteral( "enabled" ), layer->enabled() );
1523 layerEl.setAttribute( QStringLiteral( "locked" ), layer->isLocked() );
1524 layerEl.setAttribute( QStringLiteral( "pass" ), layer->renderingPass() );
1525 layerEl.setAttribute( QStringLiteral( "id" ), layer->id() );
1526 if ( layer->userFlags() != Qgis::SymbolLayerUserFlags() )
1527 layerEl.setAttribute( QStringLiteral( "userFlags" ), 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( QStringLiteral( "data_defined_properties" ) );
1541 layerEl.appendChild( ddProps );
1542
1543 if ( const QgsSymbol *subSymbol = const_cast< QgsSymbolLayer * >( layer )->subSymbol() )
1544 {
1545 const QString subname = QStringLiteral( "@%1@%2" ).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( QStringLiteral( "qgis-symbol-definition" ) );
1558 const QDomElement symbolElem = saveSymbol( QStringLiteral( "symbol" ), 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( QStringLiteral( "Entered." ), 4 );
1570
1571 if ( element.isNull() )
1572 return false;
1573
1574 const QString symbolizerName = element.localName();
1575
1576 if ( symbolizerName == QLatin1String( "PointSymbolizer" ) )
1577 {
1578 // first check for Graphic element, nothing will be rendered if not found
1579 const QDomElement graphicElem = element.firstChildElement( QStringLiteral( "Graphic" ) );
1580 if ( graphicElem.isNull() )
1581 {
1582 QgsDebugError( QStringLiteral( "Graphic element not found in PointSymbolizer" ) );
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( QStringLiteral( "CentroidFill" ), 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( QStringLiteral( "SimpleMarker" ), element );
1612 if ( l )
1613 layers.append( l.release() );
1614
1615 break;
1616 }
1617
1620 break;
1621 }
1622 }
1623 }
1624
1625 if ( symbolizerName == QLatin1String( "LineSymbolizer" ) )
1626 {
1627 // check for Stroke element, nothing will be rendered if not found
1628 const QDomElement strokeElem = element.firstChildElement( QStringLiteral( "Stroke" ) );
1629 if ( strokeElem.isNull() )
1630 {
1631 QgsDebugError( QStringLiteral( "Stroke element not found in LineSymbolizer" ) );
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( QStringLiteral( "MarkerLine" ), element );
1653 if ( l )
1654 layers.append( l.release() );
1655
1656 break;
1657 }
1658
1661 break;
1662 }
1663 }
1664 }
1665
1666 if ( symbolizerName == QLatin1String( "PolygonSymbolizer" ) )
1667 {
1668 // get Fill and Stroke elements, nothing will be rendered if both are missing
1669 const QDomElement fillElem = element.firstChildElement( QStringLiteral( "Fill" ) );
1670 const QDomElement strokeElem = element.firstChildElement( QStringLiteral( "Stroke" ) );
1671 if ( fillElem.isNull() && strokeElem.isNull() )
1672 {
1673 QgsDebugError( QStringLiteral( "neither Fill nor Stroke element not found in PolygonSymbolizer" ) );
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() == QLatin1String( "SimpleFill" ) || lastLayer->layerType() == QLatin1String( "SVGFill" ) )
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( QStringLiteral( "Fill" ) );
1733 if ( fillElem.isNull() )
1734 {
1735 QgsDebugError( QStringLiteral( "Fill element not found" ) );
1736 return nullptr;
1737 }
1738
1739 std::unique_ptr< QgsSymbolLayer > l;
1740
1741 if ( needLinePatternFill( element ) )
1742 l = QgsApplication::symbolLayerRegistry()->createSymbolLayerFromSld( QStringLiteral( "LinePatternFill" ), element );
1743 else if ( needPointPatternFill( element ) )
1744 l = QgsApplication::symbolLayerRegistry()->createSymbolLayerFromSld( QStringLiteral( "PointPatternFill" ), element );
1745 else if ( needSvgFill( element ) )
1746 l = QgsApplication::symbolLayerRegistry()->createSymbolLayerFromSld( QStringLiteral( "SVGFill" ), element );
1747 else if ( needRasterImageFill( element ) )
1748 l = QgsApplication::symbolLayerRegistry()->createSymbolLayerFromSld( QStringLiteral( "RasterFill" ), element );
1749 else
1750 l = QgsApplication::symbolLayerRegistry()->createSymbolLayerFromSld( QStringLiteral( "SimpleFill" ), element );
1751
1752 return l;
1753}
1754
1755std::unique_ptr< QgsSymbolLayer > QgsSymbolLayerUtils::createLineLayerFromSld( QDomElement &element )
1756{
1757 const QDomElement strokeElem = element.firstChildElement( QStringLiteral( "Stroke" ) );
1758 if ( strokeElem.isNull() )
1759 {
1760 QgsDebugError( QStringLiteral( "Stroke element not found" ) );
1761 return nullptr;
1762 }
1763
1764 std::unique_ptr< QgsSymbolLayer > l;
1765
1766 if ( needMarkerLine( element ) )
1767 l = QgsApplication::symbolLayerRegistry()->createSymbolLayerFromSld( QStringLiteral( "MarkerLine" ), element );
1768 else
1769 l = QgsApplication::symbolLayerRegistry()->createSymbolLayerFromSld( QStringLiteral( "SimpleLine" ), element );
1770
1771 return l;
1772}
1773
1774std::unique_ptr< QgsSymbolLayer > QgsSymbolLayerUtils::createMarkerLayerFromSld( QDomElement &element )
1775{
1776 const QDomElement graphicElem = element.firstChildElement( QStringLiteral( "Graphic" ) );
1777 if ( graphicElem.isNull() )
1778 {
1779 QgsDebugError( QStringLiteral( "Graphic element not found" ) );
1780 return nullptr;
1781 }
1782
1783 std::unique_ptr< QgsSymbolLayer > l;
1784
1785 if ( needFontMarker( element ) )
1786 l = QgsApplication::symbolLayerRegistry()->createSymbolLayerFromSld( QStringLiteral( "FontMarker" ), element );
1787 else if ( needSvgMarker( element ) )
1788 l = QgsApplication::symbolLayerRegistry()->createSymbolLayerFromSld( QStringLiteral( "SvgMarker" ), element );
1789 else if ( needEllipseMarker( element ) )
1790 l = QgsApplication::symbolLayerRegistry()->createSymbolLayerFromSld( QStringLiteral( "EllipseMarker" ), element );
1791 else if ( needRasterMarker( element ) )
1792 l = QgsApplication::symbolLayerRegistry()->createSymbolLayerFromSld( QStringLiteral( "RasterMarker" ), element );
1793 else
1794 l = QgsApplication::symbolLayerRegistry()->createSymbolLayerFromSld( QStringLiteral( "SimpleMarker" ), element );
1795
1796 return l;
1797}
1798
1800{
1801 return hasExternalGraphicV2( element, QStringLiteral( "image/svg+xml" ) );
1802}
1803
1804bool QgsSymbolLayerUtils::hasExternalGraphicV2( const QDomElement &element, const QString format )
1805{
1806 const QDomElement graphicElem = element.firstChildElement( QStringLiteral( "Graphic" ) );
1807 if ( graphicElem.isNull() )
1808 return false;
1809
1810 const QDomElement externalGraphicElem = graphicElem.firstChildElement( QStringLiteral( "ExternalGraphic" ) );
1811 if ( externalGraphicElem.isNull() )
1812 return false;
1813
1814 // check for format
1815 const QDomElement formatElem = externalGraphicElem.firstChildElement( QStringLiteral( "Format" ) );
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( QStringLiteral( "OnlineResource" ) );
1828 const QDomElement inlineContentElem = externalGraphicElem.firstChildElement( QStringLiteral( "InlineContent" ) );
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( QStringLiteral( "Graphic" ) );
1848 if ( graphicElem.isNull() )
1849 return false;
1850
1851 const QDomElement markElem = graphicElem.firstChildElement( QStringLiteral( "Mark" ) );
1852 if ( markElem.isNull() )
1853 return false;
1854
1855 const QDomElement wellKnownNameElem = markElem.firstChildElement( QStringLiteral( "WellKnownName" ) );
1856 return !wellKnownNameElem.isNull();
1857}
1858
1859
1860bool QgsSymbolLayerUtils::needFontMarker( QDomElement &element )
1861{
1862 const QDomElement graphicElem = element.firstChildElement( QStringLiteral( "Graphic" ) );
1863 if ( graphicElem.isNull() )
1864 return false;
1865
1866 const QDomElement markElem = graphicElem.firstChildElement( QStringLiteral( "Mark" ) );
1867 if ( markElem.isNull() )
1868 return false;
1869
1870 // check for format
1871 const QDomElement formatElem = markElem.firstChildElement( QStringLiteral( "Format" ) );
1872 if ( formatElem.isNull() )
1873 return false;
1874
1875 const QString format = formatElem.firstChild().nodeValue();
1876 if ( format != QLatin1String( "ttf" ) )
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( QStringLiteral( "OnlineResource" ) );
1884 const QDomElement inlineContentElem = markElem.firstChildElement( QStringLiteral( "InlineContent" ) );
1885 if ( !onlineResourceElem.isNull() )
1886 {
1887 // mark with ttf format has a markIndex element
1888 const QDomElement markIndexElem = markElem.firstChildElement( QStringLiteral( "MarkIndex" ) );
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, QStringLiteral( "image/svg+xml" ) );
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( QStringLiteral( "Graphic" ) );
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() == QLatin1String( "widthHeightFactor" ) )
1921 {
1922 return true;
1923 }
1924 }
1925
1926 return false;
1927}
1928
1929bool QgsSymbolLayerUtils::needMarkerLine( QDomElement &element )
1930{
1931 const QDomElement strokeElem = element.firstChildElement( QStringLiteral( "Stroke" ) );
1932 if ( strokeElem.isNull() )
1933 return false;
1934
1935 QDomElement graphicStrokeElem = strokeElem.firstChildElement( QStringLiteral( "GraphicStroke" ) );
1936 if ( graphicStrokeElem.isNull() )
1937 return false;
1938
1939 return hasWellKnownMark( graphicStrokeElem );
1940}
1941
1943{
1944 const QDomElement fillElem = element.firstChildElement( QStringLiteral( "Fill" ) );
1945 if ( fillElem.isNull() )
1946 return false;
1947
1948 const QDomElement graphicFillElem = fillElem.firstChildElement( QStringLiteral( "GraphicFill" ) );
1949 if ( graphicFillElem.isNull() )
1950 return false;
1951
1952 QDomElement graphicElem = graphicFillElem.firstChildElement( QStringLiteral( "Graphic" ) );
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 != QLatin1String( "horline" ) )
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( QStringLiteral( "Fill" ) );
1980 if ( fillElem.isNull() )
1981 return false;
1982
1983 const QDomElement graphicFillElem = fillElem.firstChildElement( QStringLiteral( "GraphicFill" ) );
1984 if ( graphicFillElem.isNull() )
1985 return false;
1986
1987 const QDomElement graphicElem = graphicFillElem.firstChildElement( QStringLiteral( "Graphic" ) );
1988 if ( graphicElem.isNull() )
1989 return false;
1990
1991 const QDomElement markElem = graphicElem.firstChildElement( QStringLiteral( "Mark" ) );
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( QStringLiteral( "Fill" ) );
2001 if ( fillElem.isNull() )
2002 return false;
2003
2004 QDomElement graphicFillElem = fillElem.firstChildElement( QStringLiteral( "GraphicFill" ) );
2005 if ( graphicFillElem.isNull() )
2006 return false;
2007
2008 return hasExternalGraphicV2( graphicFillElem, QStringLiteral( "image/svg+xml" ) );
2009}
2010
2012{
2013 const QDomElement fillElem = element.firstChildElement( QStringLiteral( "Fill" ) );
2014 if ( fillElem.isNull() )
2015 return false;
2016
2017 QDomElement graphicFillElem = fillElem.firstChildElement( QStringLiteral( "GraphicFill" ) );
2018 if ( graphicFillElem.isNull() )
2019 return false;
2020
2021 return hasExternalGraphicV2( graphicFillElem, QStringLiteral( "image/png" ) ) || hasExternalGraphicV2( graphicFillElem, QStringLiteral( "image/jpeg" ) ) || hasExternalGraphicV2( graphicFillElem, QStringLiteral( "image/gif" ) );
2022}
2023
2024
2025bool QgsSymbolLayerUtils::convertPolygonSymbolizerToPointMarker( QDomElement &element, QList<QgsSymbolLayer *> &layerList )
2026{
2027 QgsDebugMsgLevel( QStringLiteral( "Entered." ), 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( QStringLiteral( "Fill" ) );
2039 QDomElement strokeElem = element.firstChildElement( QStringLiteral( "Stroke" ) );
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[QStringLiteral( "name" )] = QStringLiteral( "square" );
2068 map[QStringLiteral( "color" )] = QgsColorUtils::colorToString( validFill ? fillColor : Qt::transparent );
2069 map[QStringLiteral( "color_border" )] = QgsColorUtils::colorToString( validStroke ? strokeColor : Qt::transparent );
2070 map[QStringLiteral( "size" )] = QString::number( 6 );
2071 map[QStringLiteral( "angle" )] = QString::number( 0 );
2072 map[QStringLiteral( "offset" )] = encodePoint( QPointF( 0, 0 ) );
2073 layers.append( QgsApplication::symbolLayerRegistry()->createSymbolLayer( QStringLiteral( "SimpleMarker" ), 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( QStringLiteral( "GraphicFill" ) );
2090 if ( !graphicFillElem.isNull() )
2091 {
2092 // GraphicFill element must contain a Graphic element
2093 QDomElement graphicElem = graphicFillElem.firstChildElement( QStringLiteral( "Graphic" ) );
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() == QLatin1String( "Mark" ) )
2104 {
2105 // check for a well known name
2106 const QDomElement wellKnownNameElem = graphicChildElem.firstChildElement( QStringLiteral( "WellKnownName" ) );
2107 if ( !wellKnownNameElem.isNull() )
2108 {
2109 name = wellKnownNameElem.firstChild().nodeValue();
2110 found = true;
2111 break;
2112 }
2113 }
2114
2115 if ( graphicChildElem.localName() == QLatin1String( "ExternalGraphic" ) || graphicChildElem.localName() == QLatin1String( "Mark" ) )
2116 {
2117 // check for external graphic format
2118 const QDomElement formatElem = graphicChildElem.firstChildElement( QStringLiteral( "Format" ) );
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() == QLatin1String( "ExternalGraphic" ) && format != QLatin1String( "image/svg+xml" ) )
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() == QLatin1String( "Mark" ) && format != QLatin1String( "ttf" ) )
2132 continue;
2133
2134 // check for a valid content
2135 const QDomElement onlineResourceElem = graphicChildElem.firstChildElement( QStringLiteral( "OnlineResource" ) );
2136 const QDomElement inlineContentElem = graphicChildElem.firstChildElement( QStringLiteral( "InlineContent" ) );
2137
2138 if ( !onlineResourceElem.isNull() )
2139 {
2140 name = onlineResourceElem.attributeNS( QStringLiteral( "http://www.w3.org/1999/xlink" ), QStringLiteral( "href" ) );
2141
2142 if ( graphicChildElem.localName() == QLatin1String( "Mark" ) && format == QLatin1String( "ttf" ) )
2143 {
2144 // mark with ttf format may have a name like ttf://fontFamily
2145 if ( name.startsWith( QLatin1String( "ttf://" ) ) )
2146 name = name.mid( 6 );
2147
2148 // mark with ttf format has a markIndex element
2149 const QDomElement markIndexElem = graphicChildElem.firstChildElement( QStringLiteral( "MarkIndex" ) );
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() == QLatin1String( "Mark" ) )
2176 {
2177 name = QStringLiteral( "square" );
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() == QLatin1String( "Mark" ) )
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( QStringLiteral( "Fill" ) );
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( QStringLiteral( "Stroke" ) );
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( QStringLiteral( "Opacity" ) );
2213 if ( !opacityElem.isNull() )
2214 fillColor.setAlpha( decodeSldAlpha( opacityElem.firstChild().nodeValue() ) );
2215
2216 const QDomElement sizeElem = graphicElem.firstChildElement( QStringLiteral( "Size" ) );
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 == QLatin1String( "image/svg+xml" ) )
2242 {
2243 QVariantMap map;
2244 map[QStringLiteral( "name" )] = name;
2245 map[QStringLiteral( "fill" )] = fillColor.name();
2246 map[QStringLiteral( "outline" )] = strokeColor.name();
2247 map[QStringLiteral( "outline-width" )] = QString::number( strokeWidth );
2248 if ( !qgsDoubleNear( size, 0.0 ) )
2249 map[QStringLiteral( "size" )] = QString::number( size );
2250 if ( !qgsDoubleNear( angle, 0.0 ) )
2251 map[QStringLiteral( "angle" )] = QString::number( angle );
2252 if ( !offset.isNull() )
2253 map[QStringLiteral( "offset" )] = encodePoint( offset );
2254 layers.append( QgsApplication::symbolLayerRegistry()->createSymbolLayer( QStringLiteral( "SvgMarker" ), map ).release() );
2255 }
2256 else if ( format == QLatin1String( "ttf" ) )
2257 {
2258 QVariantMap map;
2259 map[QStringLiteral( "font" )] = name;
2260 map[QStringLiteral( "chr" )] = markIndex;
2261 map[QStringLiteral( "color" )] = QgsColorUtils::colorToString( validFill ? fillColor : Qt::transparent );
2262 if ( size > 0 )
2263 map[QStringLiteral( "size" )] = QString::number( size );
2264 if ( !qgsDoubleNear( angle, 0.0 ) )
2265 map[QStringLiteral( "angle" )] = QString::number( angle );
2266 if ( !offset.isNull() )
2267 map[QStringLiteral( "offset" )] = encodePoint( offset );
2268 layers.append( QgsApplication::symbolLayerRegistry()->createSymbolLayer( QStringLiteral( "FontMarker" ), 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, QStringLiteral( "fill" ), color.name() ) );
2299 if ( color.alpha() < 255 )
2300 element.appendChild( createSvgParameterElement( doc, QStringLiteral( "fill-opacity" ), 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( QStringLiteral( "se:GraphicFill" ) );
2326 element.appendChild( graphicFillElem );
2327
2328 QDomElement graphicElem = doc.createElement( QStringLiteral( "se:Graphic" ) );
2329 graphicFillElem.appendChild( graphicElem );
2330
2331 const QColor fillColor = patternName.startsWith( QLatin1String( "brush://" ) ) ? color : QColor();
2332 const QColor strokeColor = !patternName.startsWith( QLatin1String( "brush://" ) ) ? 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( QStringLiteral( "Entered." ), 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( QStringLiteral( "GraphicFill" ) );
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( QStringLiteral( "found SvgParameter %1: %2" ).arg( it.key(), it.value() ), 2 );
2360
2361 if ( it.key() == QLatin1String( "fill" ) )
2362 color = QColor( it.value() );
2363 else if ( it.key() == QLatin1String( "fill-opacity" ) )
2364 color.setAlpha( decodeSldAlpha( it.value() ) );
2365 }
2366 }
2367 else // wellKnown marker
2368 {
2369 QDomElement graphicElem = graphicFillElem.firstChildElement( QStringLiteral( "Graphic" ) );
2370 if ( graphicElem.isNull() )
2371 return false; // Graphic is required within GraphicFill
2372
2373 QString patternName = QStringLiteral( "square" );
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( QLatin1String( "brush://" ) ) ? 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, QStringLiteral( "stroke" ), color.name() ) );
2450 if ( color.alpha() < 255 )
2451 element.appendChild( createSvgParameterElement( doc, QStringLiteral( "stroke-opacity" ), encodeSldAlpha( color.alpha() ) ) );
2452 }
2453 if ( width > 0 )
2454 {
2455 element.appendChild( createSvgParameterElement( doc, QStringLiteral( "stroke-width" ), 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, QStringLiteral( "stroke-width" ), QStringLiteral( "0.5" ) ) );
2461 }
2462 if ( penJoinStyle )
2463 element.appendChild( createSvgParameterElement( doc, QStringLiteral( "stroke-linejoin" ), encodeSldLineJoinStyle( *penJoinStyle ) ) );
2464 if ( penCapStyle )
2465 element.appendChild( createSvgParameterElement( doc, QStringLiteral( "stroke-linecap" ), encodeSldLineCapStyle( *penCapStyle ) ) );
2466
2467 if ( !pattern->isEmpty() )
2468 {
2469 element.appendChild( createSvgParameterElement( doc, QStringLiteral( "stroke-dasharray" ), encodeSldRealVector( *pattern ) ) );
2470 if ( !qgsDoubleNear( dashOffset, 0.0 ) )
2471 element.appendChild( createSvgParameterElement( doc, QStringLiteral( "stroke-dashoffset" ), 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( QStringLiteral( "Entered." ), 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( QStringLiteral( "found SvgParameter %1: %2" ).arg( it.key(), it.value() ), 2 );
2506
2507 if ( it.key() == QLatin1String( "stroke" ) )
2508 {
2509 color = QColor( it.value() );
2510 }
2511 else if ( it.key() == QLatin1String( "stroke-opacity" ) )
2512 {
2513 color.setAlpha( decodeSldAlpha( it.value() ) );
2514 }
2515 else if ( it.key() == QLatin1String( "stroke-width" ) )
2516 {
2517 bool ok;
2518 const double w = it.value().toDouble( &ok );
2519 if ( ok )
2520 width = w;
2521 }
2522 else if ( it.key() == QLatin1String( "stroke-linejoin" ) && penJoinStyle )
2523 {
2524 *penJoinStyle = decodeSldLineJoinStyle( it.value() );
2525 }
2526 else if ( it.key() == QLatin1String( "stroke-linecap" ) && penCapStyle )
2527 {
2528 *penCapStyle = decodeSldLineCapStyle( it.value() );
2529 }
2530 else if ( it.key() == QLatin1String( "stroke-dasharray" ) )
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( QStringLiteral( "custom dash pattern required but not provided. Using default dash pattern." ), 2 );
2590 penStyle = Qt::DashLine;
2591 }
2592 }
2593 }
2594 }
2595 else if ( it.key() == QLatin1String( "stroke-dashoffset" ) && 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( QStringLiteral( "se:ExternalGraphic" ) );
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( QStringLiteral( "se:Size" ) );
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( QStringLiteral( "Parametric SVG" ) ) );
2642 const QString parametricPath = getSvgParametricPath( path, fillColor, strokeColor, strokeWidth );
2643 QgsSymbolLayerUtils::externalGraphicToSld( doc, graphicElem, parametricPath, QStringLiteral( "image/svg+xml" ), fillColor, -1 );
2644 // also encode a fallback version without parameters, in case a renderer gets confused by the parameters
2645 graphicElem.appendChild( doc.createComment( QStringLiteral( "Plain SVG fallback, no parameters" ) ) );
2646 QgsSymbolLayerUtils::externalGraphicToSld( doc, graphicElem, path, QStringLiteral( "image/svg+xml" ), 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( QStringLiteral( "Well known marker fallback" ) ) );
2649 QgsSymbolLayerUtils::wellKnownMarkerToSld( doc, graphicElem, QStringLiteral( "square" ), 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( QStringLiteral( "se:Size" ) );
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( QStringLiteral( "fill" ), fillColor.name() );
2666 url.addQueryItem( QStringLiteral( "fill-opacity" ), encodeSldAlpha( fillColor.alpha() ) );
2667 }
2668 else
2669 {
2670 url.addQueryItem( QStringLiteral( "fill" ), QStringLiteral( "#000000" ) );
2671 url.addQueryItem( QStringLiteral( "fill-opacity" ), QStringLiteral( "1" ) );
2672 }
2673 if ( strokeColor.isValid() )
2674 {
2675 url.addQueryItem( QStringLiteral( "outline" ), strokeColor.name() );
2676 url.addQueryItem( QStringLiteral( "outline-opacity" ), encodeSldAlpha( strokeColor.alpha() ) );
2677 }
2678 else
2679 {
2680 url.addQueryItem( QStringLiteral( "outline" ), QStringLiteral( "#000000" ) );
2681 url.addQueryItem( QStringLiteral( "outline-opacity" ), QStringLiteral( "1" ) );
2682 }
2683 url.addQueryItem( QStringLiteral( "outline-width" ), 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( QStringLiteral( "Entered." ), 4 );
2700 Q_UNUSED( color )
2701
2702 QDomElement externalGraphicElem = element.firstChildElement( QStringLiteral( "ExternalGraphic" ) );
2703 if ( externalGraphicElem.isNull() )
2704 return false;
2705
2706 onlineResourceFromSldElement( externalGraphicElem, path, mime );
2707
2708 const QDomElement sizeElem = element.firstChildElement( QStringLiteral( "Size" ) );
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( QStringLiteral( "se:Mark" ) );
2731 element.appendChild( markElem );
2732
2733 createOnlineResourceElement( doc, markElem, path, format );
2734
2735 if ( markIndex )
2736 {
2737 QDomElement markIndexElem = doc.createElement( QStringLiteral( "se:MarkIndex" ) );
2738 markIndexElem.appendChild( doc.createTextNode( QString::number( *markIndex ) ) );
2739 markElem.appendChild( markIndexElem );
2740 }
2741
2742 // <Fill>
2743 QDomElement fillElem = doc.createElement( QStringLiteral( "se:Fill" ) );
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( QStringLiteral( "se:Size" ) );
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( QStringLiteral( "Entered." ), 4 );
2761
2762 color = QColor();
2763 markIndex = -1;
2764 size = -1;
2765
2766 QDomElement markElem = element.firstChildElement( QStringLiteral( "Mark" ) );
2767 if ( markElem.isNull() )
2768 return false;
2769
2770 onlineResourceFromSldElement( markElem, path, format );
2771
2772 const QDomElement markIndexElem = markElem.firstChildElement( QStringLiteral( "MarkIndex" ) );
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( QStringLiteral( "Fill" ) );
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( QStringLiteral( "Size" ) );
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( QStringLiteral( "se:Mark" ) );
2811 element.appendChild( markElem );
2812
2813 QDomElement wellKnownNameElem = doc.createElement( QStringLiteral( "se:WellKnownName" ) );
2814 wellKnownNameElem.appendChild( doc.createTextNode( name ) );
2815 markElem.appendChild( wellKnownNameElem );
2816
2817 // <Fill>
2818 if ( color.isValid() )
2819 {
2820 QDomElement fillElem = doc.createElement( QStringLiteral( "se:Fill" ) );
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( QStringLiteral( "se:Stroke" ) );
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( QStringLiteral( "se:Size" ) );
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( QStringLiteral( "Entered." ), 4 );
2847
2848 name = QStringLiteral( "square" );
2849 color = QColor();
2850 strokeColor = QColor( 0, 0, 0 );
2851 strokeWidth = 1;
2852 size = 6;
2853
2854 const QDomElement markElem = element.firstChildElement( QStringLiteral( "Mark" ) );
2855 if ( markElem.isNull() )
2856 return false;
2857
2858 const QDomElement wellKnownNameElem = markElem.firstChildElement( QStringLiteral( "WellKnownName" ) );
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( QStringLiteral( "Fill" ) );
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( QStringLiteral( "Stroke" ) );
2873 lineFromSld( strokeElem, strokeStyle, strokeColor, strokeWidth );
2874 // ignore stroke style, solid expected
2875
2876 // <Size>
2877 const QDomElement sizeElem = element.firstChildElement( QStringLiteral( "Size" ) );
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( QStringLiteral( "se:Rotation" ) );
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( QStringLiteral( "Rotation" ) );
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( QStringLiteral( "se:Opacity" ) );
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( QStringLiteral( "Opacity" ) );
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( QStringLiteral( "se:Displacement" ) );
2947 element.appendChild( displacementElem );
2948
2949 QDomElement dispXElem = doc.createElement( QStringLiteral( "se:DisplacementX" ) );
2950 dispXElem.appendChild( doc.createTextNode( qgsDoubleToString( offset.x(), 2 ) ) );
2951
2952 QDomElement dispYElem = doc.createElement( QStringLiteral( "se:DisplacementY" ) );
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( QStringLiteral( "se:AnchorPoint" ) );
2964 element.appendChild( anchorElem );
2965
2966 QDomElement anchorXElem = doc.createElement( QStringLiteral( "se:AnchorPointX" ) );
2967 anchorXElem.appendChild( doc.createTextNode( qgsDoubleToString( anchor.x() ) ) );
2968
2969 QDomElement anchorYElem = doc.createElement( QStringLiteral( "se:AnchorPointY" ) );
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( QStringLiteral( "Displacement" ) );
2981 if ( displacementElem.isNull() )
2982 return true;
2983
2984 const QDomElement dispXElem = displacementElem.firstChildElement( QStringLiteral( "DisplacementX" ) );
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( QStringLiteral( "DisplacementY" ) );
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( QStringLiteral( "se:Label" ) );
3016 labelElem.appendChild( doc.createTextNode( label ) );
3017 element.appendChild( labelElem );
3018
3019 QDomElement fontElem = doc.createElement( QStringLiteral( "se:Font" ) );
3020 element.appendChild( fontElem );
3021
3022 fontElem.appendChild( createSvgParameterElement( doc, QStringLiteral( "font-family" ), 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, QStringLiteral( "font-size" ), QString::number( size ) ) );
3028
3029 // <Fill>
3030 if ( color.isValid() )
3031 {
3032 QDomElement fillElem = doc.createElement( QStringLiteral( "Fill" ) );
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( QStringLiteral( "Geometry" ) );
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( QStringLiteral( "Geometry" ) );
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 == QLatin1String( "ELSE" ) )
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() != QLatin1String( "Filter" ) )
3228 {
3229 const QDomNodeList filterNodes = element.elementsByTagName( QStringLiteral( "Filter" ) );
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( QStringLiteral( "se:OnlineResource" ) );
3266 onlineResourceElem.setAttribute( QStringLiteral( "xlink:type" ), QStringLiteral( "simple" ) );
3267 onlineResourceElem.setAttribute( QStringLiteral( "xlink:href" ), url );
3268 element.appendChild( onlineResourceElem );
3269
3270 QDomElement formatElem = doc.createElement( QStringLiteral( "se:Format" ) );
3271 formatElem.appendChild( doc.createTextNode( format ) );
3272 element.appendChild( formatElem );
3273}
3274
3275bool QgsSymbolLayerUtils::onlineResourceFromSldElement( QDomElement &element, QString &path, QString &format )
3276{
3277 QgsDebugMsgLevel( QStringLiteral( "Entered." ), 4 );
3278
3279 const QDomElement onlineResourceElem = element.firstChildElement( QStringLiteral( "OnlineResource" ) );
3280 if ( onlineResourceElem.isNull() )
3281 return false;
3282
3283 path = QUrl::fromPercentEncoding( onlineResourceElem.attributeNS( QStringLiteral( "http://www.w3.org/1999/xlink" ), QStringLiteral( "href" ) ).toUtf8() );
3284
3285 const QDomElement formatElem = element.firstChildElement( QStringLiteral( "Format" ) );
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( QStringLiteral( "se:SvgParameter" ) );
3297 nodeElem.setAttribute( QStringLiteral( "name" ), 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() == QLatin1String( "SvgParameter" ) || paramElem.localName() == QLatin1String( "CssParameter" ) )
3311 {
3312 const QString name = paramElem.attribute( QStringLiteral( "name" ) );
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() == QLatin1String( "Literal" ) )
3321 {
3322 QgsDebugMsgLevel( paramElem.firstChild().localName(), 3 );
3323 value = paramElem.firstChild().firstChild().nodeValue();
3324 }
3325 else
3326 {
3327 QgsDebugError( QStringLiteral( "unexpected child of %1" ).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( QStringLiteral( "se:VendorOption" ) );
3344 nodeElem.setAttribute( QStringLiteral( "name" ), name );
3345 nodeElem.appendChild( doc.createTextNode( value ) );
3346 return nodeElem;
3347}
3348
3350{
3351 QgsStringMap params;
3352
3353 QDomElement paramElem = element.firstChildElement( QStringLiteral( "VendorOption" ) );
3354 while ( !paramElem.isNull() )
3355 {
3356 const QString name = paramElem.attribute( QStringLiteral( "name" ) );
3357 const QString value = paramElem.firstChild().nodeValue();
3358
3359 if ( !name.isEmpty() && !value.isEmpty() )
3360 params[ name ] = value;
3361
3362 paramElem = paramElem.nextSiblingElement( QStringLiteral( "VendorOption" ) );
3363 }
3364
3365 return params;
3366}
3367
3368
3369QVariantMap QgsSymbolLayerUtils::parseProperties( const QDomElement &element )
3370{
3371 const QVariant newSymbols = QgsXmlUtils::readVariant( element.firstChildElement( QStringLiteral( "Option" ) ) );
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() == QLatin1String( "prop" ) )
3385 {
3386 const QString propKey = e.attribute( QStringLiteral( "k" ) );
3387 const QString propValue = e.attribute( QStringLiteral( "v" ) );
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() == QLatin1String( "symbol" ) )
3412 {
3413 std::unique_ptr< QgsSymbol > symbol = QgsSymbolLayerUtils::loadSymbol( e, context );
3414 if ( symbol )
3415 symbols.insert( e.attribute( QStringLiteral( "name" ) ), 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( QStringLiteral( "symbol" ), 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() != QLatin1String( "symbol" ) )
3534 elem = elem.firstChildElement( QStringLiteral( "symbol" ) );
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( QStringLiteral( "type" ) );
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( QStringLiteral( "colorramp" ) );
3571 rampEl.setAttribute( QStringLiteral( "type" ), ramp->type() );
3572 rampEl.setAttribute( QStringLiteral( "name" ), 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( QStringLiteral( "type" ), ramp->type() );
3583 rampMap.insert( QStringLiteral( "name" ), 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( QStringLiteral( "properties" ), 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( QStringLiteral( "type" ) ).toString();
3602
3603 // parse properties
3604 const QVariantMap propertyMap = rampMap.value( QStringLiteral( "properties" ) ).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( QStringLiteral( "text/xml" ) ) )
3760 {
3761 //get XML doc
3762 const QByteArray encodedData = data->data( QStringLiteral( "text/xml" ) );
3763 QDomDocument xmlDoc;
3764 xmlDoc.setContent( encodedData );
3765
3766 const QDomElement dragDataElem = xmlDoc.documentElement();
3767 if ( dragDataElem.tagName() == QLatin1String( "ColorSchemeModelDragData" ) )
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( QStringLiteral( "color" ), QStringLiteral( "255,255,255,255" ) ) );
3783 namedColor.second = currentElem.attribute( QStringLiteral( "label" ), QString() );
3784
3785 mimeColors << namedColor;
3786 }
3787 }
3788 }
3789
3790 if ( mimeColors.length() == 0 && data->hasFormat( QStringLiteral( "application/x-colorobject-list" ) ) )
3791 {
3792 //get XML doc
3793 const QByteArray encodedData = data->data( QStringLiteral( "application/x-colorobject-list" ) );
3794 QDomDocument xmlDoc;
3795 xmlDoc.setContent( encodedData );
3796
3797 const QDomNodeList colorsNodes = xmlDoc.elementsByTagName( QStringLiteral( "colors" ) );
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( QStringLiteral( "color" ) );
3815 const QDomNodeList nameNodes = currentElem.elementsByTagName( QStringLiteral( "name" ) );
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( QStringLiteral( "ColorSchemeModelDragData" ) );
3873 xmlDoc.appendChild( xmlRootElement );
3874
3875 QgsNamedColorList::const_iterator colorIt = colorList.constBegin();
3876 for ( ; colorIt != colorList.constEnd(); ++colorIt )
3877 {
3878 QDomElement namedColor = xmlDoc.createElement( QStringLiteral( "NamedColor" ) );
3879 namedColor.setAttribute( QStringLiteral( "color" ), QgsColorUtils::colorToString( ( *colorIt ).first ) );
3880 namedColor.setAttribute( QStringLiteral( "label" ), ( *colorIt ).second );
3881 xmlRootElement.appendChild( namedColor );
3882 }
3883 mimeData->setData( QStringLiteral( "text/xml" ), 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#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
3917 stream.setCodec( "UTF-8" );
3918#endif
3919
3920 stream << "GIMP Palette" << Qt::endl;
3921 if ( paletteName.isEmpty() )
3922 {
3923 stream << "Name: QGIS Palette" << Qt::endl;
3924 }
3925 else
3926 {
3927 stream << "Name: " << paletteName << Qt::endl;
3928 }
3929 stream << "Columns: 4" << Qt::endl;
3930 stream << '#' << Qt::endl;
3931
3932 for ( QgsNamedColorList::ConstIterator colorIt = colors.constBegin(); colorIt != colors.constEnd(); ++colorIt )
3933 {
3934 const QColor color = ( *colorIt ).first;
3935 if ( !color.isValid() )
3936 {
3937 continue;
3938 }
3939 stream << QStringLiteral( "%1 %2 %3" ).arg( color.red(), 3 ).arg( color.green(), 3 ).arg( color.blue(), 3 );
3940 stream << "\t" << ( ( *colorIt ).second.isEmpty() ? color.name() : ( *colorIt ).second ) << Qt::endl;
3941 }
3942 file.close();
3943
3944 return true;
3945}
3946
3948{
3949 QgsNamedColorList importedColors;
3950
3951 if ( !file.open( QIODevice::ReadOnly ) )
3952 {
3953 ok = false;
3954 return importedColors;
3955 }
3956
3957 QTextStream in( &file );
3958#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
3959 in.setCodec( "UTF-8" );
3960#endif
3961
3962 QString line = in.readLine();
3963 if ( !line.startsWith( QLatin1String( "GIMP Palette" ) ) )
3964 {
3965 ok = false;
3966 return importedColors;
3967 }
3968
3969 //find name line
3970 while ( !in.atEnd() && !line.startsWith( QLatin1String( "Name:" ) ) && !line.startsWith( '#' ) )
3971 {
3972 line = in.readLine();
3973 }
3974 if ( line.startsWith( QLatin1String( "Name:" ) ) )
3975 {
3976 const thread_local QRegularExpression nameRx( "Name:\\s*(\\S.*)$" );
3977 const QRegularExpressionMatch match = nameRx.match( line );
3978 if ( match.hasMatch() )
3979 {
3980 name = match.captured( 1 );
3981 }
3982 }
3983
3984 //ignore lines until after "#"
3985 while ( !in.atEnd() && !line.startsWith( '#' ) )
3986 {
3987 line = in.readLine();
3988 }
3989 if ( in.atEnd() )
3990 {
3991 ok = false;
3992 return importedColors;
3993 }
3994
3995 //ready to start reading colors
3996 const thread_local QRegularExpression rx( "^\\s*(\\d+)\\s+(\\d+)\\s+(\\d+)(\\s.*)?$" );
3997 while ( !in.atEnd() )
3998 {
3999 line = in.readLine();
4000 const QRegularExpressionMatch match = rx.match( line );
4001 if ( !match.hasMatch() )
4002 {
4003 continue;
4004 }
4005 const int red = match.captured( 1 ).toInt();
4006 const int green = match.captured( 2 ).toInt();
4007 const int blue = match.captured( 3 ).toInt();
4008 const QColor color = QColor( red, green, blue );
4009 if ( !color.isValid() )
4010 {
4011 continue;
4012 }
4013
4014 //try to read color name
4015 QString label;
4016 if ( rx.captureCount() > 3 )
4017 {
4018 label = match.captured( 4 ).simplified();
4019 }
4020 else
4021 {
4022 label = colorToName( color );
4023 }
4024
4025 importedColors << qMakePair( color, label );
4026 }
4027
4028 file.close();
4029 ok = true;
4030 return importedColors;
4031}
4032
4033QColor QgsSymbolLayerUtils::parseColor( const QString &colorStr, bool strictEval )
4034{
4035 bool hasAlpha;
4036 return parseColorWithAlpha( colorStr, hasAlpha, strictEval );
4037}
4038
4039QColor QgsSymbolLayerUtils::parseColorWithAlpha( const QString &colorStr, bool &containsAlpha, bool strictEval )
4040{
4041 QColor parsedColor;
4042
4043 const thread_local QRegularExpression hexColorAlphaRx( "^\\s*#?([0-9a-fA-F]{6})([0-9a-fA-F]{2})\\s*$" );
4044 QRegularExpressionMatch match = hexColorAlphaRx.match( colorStr );
4045
4046 //color in hex format "#aabbcc", but not #aabbccdd
4047 if ( !match.hasMatch() && QColor::isValidColor( colorStr ) )
4048 {
4049 //string is a valid hex color string
4050 parsedColor.setNamedColor( colorStr );
4051 if ( parsedColor.isValid() )
4052 {
4053 containsAlpha = false;
4054 return parsedColor;
4055 }
4056 }
4057
4058 //color in hex format, with alpha
4059 if ( match.hasMatch() )
4060 {
4061 const QString hexColor = match.captured( 1 );
4062 parsedColor.setNamedColor( QStringLiteral( "#" ) + hexColor );
4063 bool alphaOk;
4064 const int alphaHex = match.captured( 2 ).toInt( &alphaOk, 16 );
4065
4066 if ( parsedColor.isValid() && alphaOk )
4067 {
4068 parsedColor.setAlpha( alphaHex );
4069 containsAlpha = true;
4070 return parsedColor;
4071 }
4072 }
4073
4074 if ( !strictEval )
4075 {
4076 //color in hex format, without #
4077 const thread_local QRegularExpression hexColorRx2( "^\\s*(?:[0-9a-fA-F]{3}){1,2}\\s*$" );
4078 if ( colorStr.indexOf( hexColorRx2 ) != -1 )
4079 {
4080 //add "#" and parse
4081 parsedColor.setNamedColor( QStringLiteral( "#" ) + colorStr );
4082 if ( parsedColor.isValid() )
4083 {
4084 containsAlpha = false;
4085 return parsedColor;
4086 }
4087 }
4088 }
4089
4090 //color in (rrr,ggg,bbb) format, brackets and rgb prefix optional
4091 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*$" );
4092 match = rgbFormatRx.match( colorStr );
4093 if ( match.hasMatch() )
4094 {
4095 bool rOk = false;
4096 bool gOk = false;
4097 bool bOk = false;
4098 const int r = match.captured( 1 ).toInt( &rOk );
4099 const int g = match.captured( 2 ).toInt( &gOk );
4100 const int b = match.captured( 3 ).toInt( &bOk );
4101
4102 if ( !rOk || !gOk || !bOk )
4103 {
4104 const float rFloat = match.captured( 1 ).toFloat();
4105 const float gFloat = match.captured( 2 ).toFloat();
4106 const float bFloat = match.captured( 3 ).toFloat();
4107 parsedColor.setRgbF( rFloat / 255.0, gFloat / 255.0, bFloat / 255.0 );
4108 }
4109 else
4110 {
4111 parsedColor.setRgb( r, g, b );
4112 }
4113
4114 if ( parsedColor.isValid() )
4115 {
4116 containsAlpha = false;
4117 return parsedColor;
4118 }
4119 }
4120
4121 //color in hsl(h,s,l) format, brackets optional
4122 const thread_local QRegularExpression hslFormatRx( "^\\s*hsl\\(?\\s*(\\d+(?:\\.\\d*)?)\\s*,\\s*(\\d+(?:\\.\\d*)?)\\s*%\\s*,\\s*(\\d+(?:\\.\\d*)?)\\s*%\\s*\\)?\\s*;?\\s*$" );
4123 match = hslFormatRx.match( colorStr );
4124 if ( match.hasMatch() )
4125 {
4126 bool hOk = false;
4127 bool sOk = false;
4128 bool lOk = false;
4129 const int h = match.captured( 1 ).toInt( &hOk );
4130 const int s = match.captured( 2 ).toInt( &sOk );
4131 const int l = match.captured( 3 ).toInt( &lOk );
4132
4133 if ( !hOk || !sOk || !lOk )
4134 {
4135 const float hFloat = match.captured( 1 ).toFloat();
4136 const float sFloat = match.captured( 2 ).toFloat();
4137 const float lFloat = match.captured( 3 ).toFloat();
4138 parsedColor.setHslF( hFloat / 360.0, sFloat / 100.0, lFloat / 100.0 );
4139 }
4140 else
4141 {
4142 parsedColor.setHsl( h, s / 100.0 * 255.0, l / 100.0 * 255.0 );
4143 }
4144 if ( parsedColor.isValid() )
4145 {
4146 containsAlpha = false;
4147 return parsedColor;
4148 }
4149 }
4150
4151 //color in (r%,g%,b%) format, brackets and rgb prefix optional
4152 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*$" );
4153 match = rgbPercentFormatRx.match( colorStr );
4154 if ( match.hasMatch() )
4155 {
4156 const double r = match.captured( 1 ).toDouble() / 100;
4157 const double g = match.captured( 2 ).toDouble() / 100;
4158 const double b = match.captured( 3 ).toDouble() / 100;
4159 parsedColor.setRgbF( r, g, b );
4160 if ( parsedColor.isValid() )
4161 {
4162 containsAlpha = false;
4163 return parsedColor;
4164 }
4165 }
4166
4167 //color in (r,g,b,a) format, brackets and rgba prefix optional
4168 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*$" );
4169 match = rgbaFormatRx.match( colorStr );
4170 if ( match.hasMatch() )
4171 {
4172 bool rOk = false;
4173 bool gOk = false;
4174 bool bOk = false;
4175 const int r = match.captured( 1 ).toInt( &rOk );
4176 const int g = match.captured( 2 ).toInt( &gOk );
4177 const int b = match.captured( 3 ).toInt( &bOk );
4178 const double aDouble = match.captured( 4 ).toDouble();
4179
4180 if ( !rOk || !gOk || !bOk )
4181 {
4182 const float rFloat = match.captured( 1 ).toFloat();
4183 const float gFloat = match.captured( 2 ).toFloat();
4184 const float bFloat = match.captured( 3 ).toFloat();
4185 parsedColor.setRgbF( rFloat / 255.0, gFloat / 255.0, bFloat / 255.0, aDouble );
4186 }
4187 else
4188 {
4189 const int a = static_cast< int >( std::round( match.captured( 4 ).toDouble() * 255.0 ) );
4190 parsedColor.setRgb( r, g, b, a );
4191 }
4192 if ( parsedColor.isValid() )
4193 {
4194 containsAlpha = true;
4195 return parsedColor;
4196 }
4197 }
4198
4199 //color in (r%,g%,b%,a) format, brackets and rgba prefix optional
4200 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*$" );
4201 match = rgbaPercentFormatRx.match( colorStr );
4202 if ( match.hasMatch() )
4203 {
4204 const double r = match.captured( 1 ).toDouble() / 100;
4205 const double g = match.captured( 2 ).toDouble() / 100;
4206 const double b = match.captured( 3 ).toDouble() / 100;
4207 const double a = match.captured( 4 ).toDouble();
4208 parsedColor.setRgbF( r, g, b, a );
4209 if ( parsedColor.isValid() )
4210 {
4211 containsAlpha = true;
4212 return parsedColor;
4213 }
4214 }
4215
4216 //color in hsla(h,s%,l%,a) format, brackets optional
4217 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*$" );
4218 match = hslaPercentFormatRx.match( colorStr );
4219 if ( match.hasMatch() )
4220 {
4221 bool hOk = false;
4222 bool sOk = false;
4223 bool lOk = false;
4224 const int h = match.captured( 1 ).toInt( &hOk );
4225 const int s = match.captured( 2 ).toInt( &sOk );
4226 const int l = match.captured( 3 ).toInt( &lOk );
4227 const double aDouble = match.captured( 4 ).toDouble();
4228
4229 if ( !hOk || !sOk || !lOk )
4230 {
4231 const float hFloat = match.captured( 1 ).toFloat();
4232 const float sFloat = match.captured( 2 ).toFloat();
4233 const float lFloat = match.captured( 3 ).toFloat();
4234 parsedColor.setHslF( hFloat / 360.0, sFloat / 100.0, lFloat / 100.0, aDouble );
4235 }
4236 else
4237 {
4238 const int a = std::round( aDouble * 255.0 );
4239 parsedColor.setHsl( h, s / 100.0 * 255.0, l / 100.0 * 255.0, a );
4240 }
4241
4242 if ( parsedColor.isValid() )
4243 {
4244 containsAlpha = true;
4245 return parsedColor;
4246 }
4247 }
4248
4249 //couldn't parse string as color
4250 return QColor();
4251}
4252
4253void QgsSymbolLayerUtils::multiplyImageOpacity( QImage *image, qreal opacity )
4254{
4255 if ( !image )
4256 {
4257 return;
4258 }
4259
4260 QRgb myRgb;
4261 const QImage::Format format = image->format();
4262 if ( format != QImage::Format_ARGB32_Premultiplied && format != QImage::Format_ARGB32 )
4263 {
4264 QgsDebugError( QStringLiteral( "no alpha channel." ) );
4265 return;
4266 }
4267
4268 //change the alpha component of every pixel
4269 for ( int heightIndex = 0; heightIndex < image->height(); ++heightIndex )
4270 {
4271 QRgb *scanLine = reinterpret_cast< QRgb * >( image->scanLine( heightIndex ) );
4272 for ( int widthIndex = 0; widthIndex < image->width(); ++widthIndex )
4273 {
4274 myRgb = scanLine[widthIndex];
4275 if ( format == QImage::Format_ARGB32_Premultiplied )
4276 scanLine[widthIndex] = qRgba( opacity * qRed( myRgb ), opacity * qGreen( myRgb ), opacity * qBlue( myRgb ), opacity * qAlpha( myRgb ) );
4277 else
4278 scanLine[widthIndex] = qRgba( qRed( myRgb ), qGreen( myRgb ), qBlue( myRgb ), opacity * qAlpha( myRgb ) );
4279 }
4280 }
4281}
4282
4283void QgsSymbolLayerUtils::blurImageInPlace( QImage &image, QRect rect, int radius, bool alphaOnly )
4284{
4285 // culled from Qt's qpixmapfilter.cpp, see: http://www.qtcentre.org/archive/index.php/t-26534.html
4286 const int tab[] = { 14, 10, 8, 6, 5, 5, 4, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2 };
4287 const int alpha = ( radius < 1 ) ? 16 : ( radius > 17 ) ? 1 : tab[radius - 1];
4288
4289 if ( image.format() != QImage::Format_ARGB32_Premultiplied
4290 && image.format() != QImage::Format_RGB32 )
4291 {
4292 image = image.convertToFormat( QImage::Format_ARGB32_Premultiplied );
4293 }
4294
4295 const int r1 = rect.top();
4296 const int r2 = rect.bottom();
4297 const int c1 = rect.left();
4298 const int c2 = rect.right();
4299
4300 const int bpl = image.bytesPerLine();
4301 int rgba[4];
4302 unsigned char *p;
4303
4304 int i1 = 0;
4305 int i2 = 3;
4306
4307 if ( alphaOnly ) // this seems to only work right for a black color
4308 i1 = i2 = ( QSysInfo::ByteOrder == QSysInfo::BigEndian ? 0 : 3 );
4309
4310 for ( int col = c1; col <= c2; col++ )
4311 {
4312 p = image.scanLine( r1 ) + col * 4;
4313 for ( int i = i1; i <= i2; i++ )
4314 rgba[i] = p[i] << 4;
4315
4316 p += bpl;
4317 for ( int j = r1; j < r2; j++, p += bpl )
4318 for ( int i = i1; i <= i2; i++ )
4319 p[i] = ( rgba[i] += ( ( p[i] << 4 ) - rgba[i] ) * alpha / 16 ) >> 4;
4320 }
4321
4322 for ( int row = r1; row <= r2; row++ )
4323 {
4324 p = image.scanLine( row ) + c1 * 4;
4325 for ( int i = i1; i <= i2; i++ )
4326 rgba[i] = p[i] << 4;
4327
4328 p += 4;
4329 for ( int j = c1; j < c2; j++, p += 4 )
4330 for ( int i = i1; i <= i2; i++ )
4331 p[i] = ( rgba[i] += ( ( p[i] << 4 ) - rgba[i] ) * alpha / 16 ) >> 4;
4332 }
4333
4334 for ( int col = c1; col <= c2; col++ )
4335 {
4336 p = image.scanLine( r2 ) + col * 4;
4337 for ( int i = i1; i <= i2; i++ )
4338 rgba[i] = p[i] << 4;
4339
4340 p -= bpl;
4341 for ( int j = r1; j < r2; j++, p -= bpl )
4342 for ( int i = i1; i <= i2; i++ )
4343 p[i] = ( rgba[i] += ( ( p[i] << 4 ) - rgba[i] ) * alpha / 16 ) >> 4;
4344 }
4345
4346 for ( int row = r1; row <= r2; row++ )
4347 {
4348 p = image.scanLine( row ) + c2 * 4;
4349 for ( int i = i1; i <= i2; i++ )
4350 rgba[i] = p[i] << 4;
4351
4352 p -= 4;
4353 for ( int j = c1; j < c2; j++, p -= 4 )
4354 for ( int i = i1; i <= i2; i++ )
4355 p[i] = ( rgba[i] += ( ( p[i] << 4 ) - rgba[i] ) * alpha / 16 ) >> 4;
4356 }
4357}
4358
4359void QgsSymbolLayerUtils::premultiplyColor( QColor &rgb, int alpha )
4360{
4361 if ( alpha != 255 && alpha > 0 )
4362 {
4363 // Semi-transparent pixel. We need to adjust the colors for ARGB32_Premultiplied images
4364 // where color values have to be premultiplied by alpha
4365 const double alphaFactor = alpha / 255.;
4366 int r = 0, g = 0, b = 0;
4367 rgb.getRgb( &r, &g, &b );
4368
4369 r *= alphaFactor;
4370 g *= alphaFactor;
4371 b *= alphaFactor;
4372 rgb.setRgb( r, g, b, alpha );
4373 }
4374 else if ( alpha == 0 )
4375 {
4376 rgb.setRgb( 0, 0, 0, 0 );
4377 }
4378}
4379
4381{
4382 QgsSimpleFillSymbolLayer *simpleFill = dynamic_cast< QgsSimpleFillSymbolLayer *>( fill );
4383 QgsSimpleLineSymbolLayer *simpleLine = dynamic_cast< QgsSimpleLineSymbolLayer *>( outline );
4384
4385 if ( !simpleFill || !simpleLine )
4386 return false;
4387
4388 if ( simpleLine->useCustomDashPattern() )
4389 return false;
4390
4391 if ( simpleLine->dashPatternOffset() )
4392 return false;
4393
4394 if ( simpleLine->alignDashPattern() )
4395 return false;
4396
4397 if ( simpleLine->tweakDashPatternOnCorners() )
4398 return false;
4399
4400 if ( simpleLine->trimDistanceStart() || simpleLine->trimDistanceEnd() )
4401 return false;
4402
4403 if ( simpleLine->drawInsidePolygon() )
4404 return false;
4405
4406 if ( simpleLine->ringFilter() != QgsSimpleLineSymbolLayer::AllRings )
4407 return false;
4408
4409 if ( simpleLine->offset() )
4410 return false;
4411
4412 if ( simpleLine->hasDataDefinedProperties() )
4413 return false;
4414
4415 // looks good!
4416 simpleFill->setStrokeColor( simpleLine->color() );
4417 simpleFill->setStrokeWidth( simpleLine->width() );
4418 simpleFill->setStrokeWidthUnit( simpleLine->widthUnit() );
4419 simpleFill->setStrokeWidthMapUnitScale( simpleLine->widthMapUnitScale() );
4420 simpleFill->setStrokeStyle( simpleLine->penStyle() );
4421 simpleFill->setPenJoinStyle( simpleLine->penJoinStyle() );
4422 return true;
4423}
4424
4425void QgsSymbolLayerUtils::sortVariantList( QList<QVariant> &list, Qt::SortOrder order )
4426{
4427 if ( order == Qt::AscendingOrder )
4428 {
4429 //std::sort( list.begin(), list.end(), _QVariantLessThan );
4430 std::sort( list.begin(), list.end(), qgsVariantLessThan );
4431 }
4432 else // Qt::DescendingOrder
4433 {
4434 //std::sort( list.begin(), list.end(), _QVariantGreaterThan );
4435 std::sort( list.begin(), list.end(), qgsVariantGreaterThan );
4436 }
4437}
4438
4439QPointF QgsSymbolLayerUtils::pointOnLineWithDistance( QPointF startPoint, QPointF directionPoint, double distance )
4440{
4441 const double dx = directionPoint.x() - startPoint.x();
4442 const double dy = directionPoint.y() - startPoint.y();
4443 const double length = std::sqrt( dx * dx + dy * dy );
4444 const double scaleFactor = distance / length;
4445 return QPointF( startPoint.x() + dx * scaleFactor, startPoint.y() + dy * scaleFactor );
4446}
4447
4448
4450{
4451 // copied from QgsMarkerCatalogue - TODO: unify //#spellok
4452 QStringList list;
4453 QStringList svgPaths = QgsApplication::svgPaths();
4454
4455 for ( int i = 0; i < svgPaths.size(); i++ )
4456 {
4457 const QDir dir( svgPaths[i] );
4458 const auto svgSubPaths = dir.entryList( QDir::Dirs | QDir::NoDotAndDotDot );
4459 for ( const QString &item : svgSubPaths )
4460 {
4461 svgPaths.insert( i + 1, dir.path() + '/' + item );
4462 }
4463
4464 const auto svgFiles = dir.entryList( QStringList( "*.svg" ), QDir::Files );
4465 for ( const QString &item : svgFiles )
4466 {
4467 // TODO test if it is correct SVG
4468 list.append( dir.path() + '/' + item );
4469 }
4470 }
4471 return list;
4472}
4473
4474// Stripped down version of listSvgFiles() for specified directory
4475QStringList QgsSymbolLayerUtils::listSvgFilesAt( const QString &directory )
4476{
4477 // TODO anything that applies for the listSvgFiles() applies this also
4478
4479 QStringList list;
4480 QStringList svgPaths;
4481 svgPaths.append( directory );
4482
4483 for ( int i = 0; i < svgPaths.size(); i++ )
4484 {
4485 const QDir dir( svgPaths[i] );
4486 const auto svgSubPaths = dir.entryList( QDir::Dirs | QDir::NoDotAndDotDot );
4487 for ( const QString &item : svgSubPaths )
4488 {
4489 svgPaths.insert( i + 1, dir.path() + '/' + item );
4490 }
4491
4492 const auto svgFiles = dir.entryList( QStringList( "*.svg" ), QDir::Files );
4493 for ( const QString &item : svgFiles )
4494 {
4495 list.append( dir.path() + '/' + item );
4496 }
4497 }
4498 return list;
4499
4500}
4501
4502QString QgsSymbolLayerUtils::svgSymbolNameToPath( const QString &n, const QgsPathResolver &pathResolver )
4503{
4504 if ( n.isEmpty() )
4505 return QString();
4506
4507 if ( n.startsWith( QLatin1String( "base64:" ) ) )
4508 return n;
4509
4510 // we might have a full path...
4511 if ( QFileInfo::exists( n ) )
4512 return QFileInfo( n ).canonicalFilePath();
4513
4514 QString name = n;
4515 // or it might be an url...
4516 if ( name.contains( QLatin1String( "://" ) ) )
4517 {
4518 const QUrl url( name );
4519 if ( url.isValid() && !url.scheme().isEmpty() )
4520 {
4521 if ( url.scheme().compare( QLatin1String( "file" ), Qt::CaseInsensitive ) == 0 )
4522 {
4523 // it's a url to a local file
4524 name = url.toLocalFile();
4525 if ( QFile( name ).exists() )
4526 {
4527 return QFileInfo( name ).canonicalFilePath();
4528 }
4529 }
4530 else
4531 {
4532 // it's a url pointing to a online resource
4533 return name;
4534 }
4535 }
4536 }
4537
4538 // SVG symbol not found - probably a relative path was used
4539
4540 QStringList svgPaths = QgsApplication::svgPaths();
4541 for ( int i = 0; i < svgPaths.size(); i++ )
4542 {
4543 QString svgPath = svgPaths[i];
4544 if ( svgPath.endsWith( QChar( '/' ) ) )
4545 {
4546 svgPath.chop( 1 );
4547 }
4548
4549 QgsDebugMsgLevel( "SvgPath: " + svgPath, 3 );
4550 // Not sure why to lowest dir was used instead of full relative path, it was causing #8664
4551 //QFileInfo myInfo( name );
4552 //QString myFileName = myInfo.fileName(); // foo.svg
4553 //QString myLowestDir = myInfo.dir().dirName();
4554 //QString myLocalPath = svgPath + QString( myLowestDir.isEmpty() ? "" : '/' + myLowestDir ) + '/' + myFileName;
4555 const QString myLocalPath = svgPath + QDir::separator() + name;
4556
4557 QgsDebugMsgLevel( "Alternative svg path: " + myLocalPath, 3 );
4558 if ( QFile( myLocalPath ).exists() )
4559 {
4560 QgsDebugMsgLevel( QStringLiteral( "Svg found in alternative path" ), 3 );
4561 return QFileInfo( myLocalPath ).canonicalFilePath();
4562 }
4563 }
4564
4565 return pathResolver.readPath( name );
4566}
4567
4568QString QgsSymbolLayerUtils::svgSymbolPathToName( const QString &p, const QgsPathResolver &pathResolver )
4569{
4570 if ( p.isEmpty() )
4571 return QString();
4572
4573 if ( p.startsWith( QLatin1String( "base64:" ) ) )
4574 return p;
4575
4576 if ( !QFileInfo::exists( p ) )
4577 return p;
4578
4579 QString path = QFileInfo( p ).canonicalFilePath();
4580
4581 QStringList svgPaths = QgsApplication::svgPaths();
4582
4583 bool isInSvgPaths = false;
4584 for ( int i = 0; i < svgPaths.size(); i++ )
4585 {
4586 const QString dir = QFileInfo( svgPaths[i] ).canonicalFilePath();
4587
4588 if ( !dir.isEmpty() && path.startsWith( dir ) )
4589 {
4590 path = path.mid( dir.size() + 1 );
4591 isInSvgPaths = true;
4592 break;
4593 }
4594 }
4595
4596 if ( isInSvgPaths )
4597 return path;
4598
4599 return pathResolver.writePath( path );
4600}
4601
4602QPolygonF lineStringToQPolygonF( const QgsLineString *line )
4603{
4604 const double *srcX = line->xData();
4605 const double *srcY = line->yData();
4606 const int count = line->numPoints();
4607 QPolygonF thisRes( count );
4608 QPointF *dest = thisRes.data();
4609 for ( int i = 0; i < count; ++i )
4610 {
4611 *dest++ = QPointF( *srcX++, *srcY++ );
4612 }
4613 return thisRes;
4614}
4615
4616QPolygonF curveToPolygonF( const QgsCurve *curve )
4617{
4618 if ( const QgsLineString *line = qgsgeometry_cast< const QgsLineString * >( curve ) )
4619 {
4620 return lineStringToQPolygonF( line );
4621 }
4622 else
4623 {
4624 const std::unique_ptr< QgsLineString > straightened( curve->curveToLine() );
4625 return lineStringToQPolygonF( straightened.get() );
4626 }
4627}
4628
4629QList<QList<QPolygonF> > QgsSymbolLayerUtils::toQPolygonF( const QgsGeometry &geometry, Qgis::SymbolType type )
4630{
4631 return toQPolygonF( geometry.constGet(), type );
4632}
4633
4634QList<QList<QPolygonF> > QgsSymbolLayerUtils::toQPolygonF( const QgsAbstractGeometry *geometry, Qgis::SymbolType type )
4635{
4636 if ( !geometry )
4637 return {};
4638
4639 switch ( type )
4640 {
4642 {
4643 QPolygonF points;
4644
4646 {
4647 for ( auto it = geometry->vertices_begin(); it != geometry->vertices_end(); ++it )
4648 points << QPointF( ( *it ).x(), ( *it ).y() );
4649 }
4650 else
4651 {
4652 points << QPointF( 0, 0 );
4653 }
4654 return QList< QList<QPolygonF> >() << ( QList< QPolygonF >() << points );
4655 }
4656
4658 {
4659 QList< QList<QPolygonF> > res;
4661 {
4662 for ( auto it = geometry->const_parts_begin(); it != geometry->const_parts_end(); ++it )
4663 {
4664 res << ( QList< QPolygonF >() << curveToPolygonF( qgsgeometry_cast< const QgsCurve * >( *it ) ) );
4665 }
4666 }
4667 return res;
4668 }
4669
4671 {
4672 QList< QList<QPolygonF> > res;
4673
4674 for ( auto it = geometry->const_parts_begin(); it != geometry->const_parts_end(); ++it )
4675 {
4676 QList<QPolygonF> thisPart;
4677 const QgsCurvePolygon *surface = qgsgeometry_cast< const QgsCurvePolygon * >( *it );
4678 if ( !surface )
4679 continue;
4680
4681 if ( !surface->exteriorRing() )
4682 continue;
4683
4684 thisPart << curveToPolygonF( surface->exteriorRing() );
4685
4686 for ( int i = 0; i < surface->numInteriorRings(); ++i )
4687 thisPart << curveToPolygonF( surface->interiorRing( i ) );
4688 res << thisPart;
4689 }
4690
4691 return res;
4692 }
4693
4695 return QList< QList<QPolygonF> >();
4696 }
4697
4698 return QList< QList<QPolygonF> >();
4699}
4700
4701
4702QPointF QgsSymbolLayerUtils::polygonCentroid( const QPolygonF &points )
4703{
4704 //Calculate the centroid of points
4705 double cx = 0, cy = 0;
4706 double area, sum = 0;
4707 for ( int i = points.count() - 1, j = 0; j < points.count(); i = j++ )
4708 {
4709 const QPointF &p1 = points[i];
4710 const QPointF &p2 = points[j];
4711 area = p1.x() * p2.y() - p1.y() * p2.x();
4712 sum += area;
4713 cx += ( p1.x() + p2.x() ) * area;
4714 cy += ( p1.y() + p2.y() ) * area;
4715 }
4716 sum *= 3.0;
4717 if ( qgsDoubleNear( sum, 0.0 ) )
4718 {
4719 // the linear ring is invalid - let's fall back to a solution that will still
4720 // allow us render at least something (instead of just returning point nan,nan)
4721 if ( points.count() >= 2 )
4722 return QPointF( ( points[0].x() + points[1].x() ) / 2, ( points[0].y() + points[1].y() ) / 2 );
4723 else if ( points.count() == 1 )
4724 return points[0];
4725 else
4726 return QPointF(); // hopefully we shouldn't ever get here
4727 }
4728 cx /= sum;
4729 cy /= sum;
4730
4731 return QPointF( cx, cy );
4732}
4733
4734QPointF QgsSymbolLayerUtils::polygonPointOnSurface( const QPolygonF &points, const QVector<QPolygonF> *rings )
4735{
4736 QPointF centroid = QgsSymbolLayerUtils::polygonCentroid( points );
4737
4738 if ( ( rings && rings->count() > 0 ) || !pointInPolygon( points, centroid ) )
4739 {
4740 unsigned int i, pointCount = points.count();
4741 QgsPolylineXY polyline( pointCount );
4742 for ( i = 0; i < pointCount; ++i ) polyline[i] = QgsPointXY( points[i].x(), points[i].y() );
4743 QgsGeometry geom = QgsGeometry::fromPolygonXY( QgsPolygonXY() << polyline );
4744 if ( !geom.isNull() )
4745 {
4746 if ( rings )
4747 {
4748 for ( auto ringIt = rings->constBegin(); ringIt != rings->constEnd(); ++ringIt )
4749 {
4750 pointCount = ( *ringIt ).count();
4751 QgsPolylineXY polyline( pointCount );
4752 for ( i = 0; i < pointCount; ++i ) polyline[i] = QgsPointXY( ( *ringIt )[i].x(), ( *ringIt )[i].y() );
4753 geom.addRing( polyline );
4754 }
4755 }
4756
4757 const QgsGeometry pointOnSurfaceGeom = geom.pointOnSurface();
4758 if ( !pointOnSurfaceGeom.isNull() )
4759 {
4760 const QgsPointXY point = pointOnSurfaceGeom.asPoint();
4761 centroid.setX( point.x() );
4762 centroid.setY( point.y() );
4763 }
4764 }
4765 }
4766
4767 return QPointF( centroid.x(), centroid.y() );
4768}
4769
4770bool QgsSymbolLayerUtils::pointInPolygon( const QPolygonF &points, QPointF point )
4771{
4772 bool inside = false;
4773
4774 const double x = point.x();
4775 const double y = point.y();
4776
4777 for ( int i = 0, j = points.count() - 1; i < points.count(); i++ )
4778 {
4779 const QPointF &p1 = points[i];
4780 const QPointF &p2 = points[j];
4781
4782 if ( qgsDoubleNear( p1.x(), x ) && qgsDoubleNear( p1.y(), y ) )
4783 return true;
4784
4785 if ( ( p1.y() < y && p2.y() >= y ) || ( p2.y() < y && p1.y() >= y ) )
4786 {
4787 if ( p1.x() + ( y - p1.y() ) / ( p2.y() - p1.y() ) * ( p2.x() - p1.x() ) <= x )
4788 inside = !inside;
4789 }
4790
4791 j = i;
4792 }
4793 return inside;
4794}
4795
4796double QgsSymbolLayerUtils::polylineLength( const QPolygonF &polyline )
4797{
4798 if ( polyline.size() < 2 )
4799 return 0;
4800
4801 double totalLength = 0;
4802 auto it = polyline.begin();
4803 QPointF p1 = *it++;
4804 for ( ; it != polyline.end(); ++it )
4805 {
4806 const QPointF p2 = *it;
4807 const double segmentLength = std::sqrt( std::pow( p1.x() - p2.x(), 2.0 ) + std::pow( p1.y() - p2.y(), 2.0 ) );
4808 totalLength += segmentLength;
4809 p1 = p2;
4810 }
4811 return totalLength;
4812}
4813
4814QPolygonF QgsSymbolLayerUtils::polylineSubstring( const QPolygonF &polyline, double startOffset, double endOffset )
4815{
4816 if ( polyline.size() < 2 )
4817 return QPolygonF();
4818
4819 double totalLength = 0;
4820 auto it = polyline.begin();
4821 QPointF p1 = *it++;
4822 std::vector< double > segmentLengths( polyline.size() - 1 );
4823 auto segmentLengthIt = segmentLengths.begin();
4824 for ( ; it != polyline.end(); ++it )
4825 {
4826 const QPointF p2 = *it;
4827 *segmentLengthIt = std::sqrt( std::pow( p1.x() - p2.x(), 2.0 ) + std::pow( p1.y() - p2.y(), 2.0 ) );
4828 totalLength += *segmentLengthIt;
4829
4830 segmentLengthIt++;
4831 p1 = p2;
4832 }
4833
4834 if ( startOffset >= 0 && totalLength <= startOffset )
4835 return QPolygonF();
4836 if ( endOffset < 0 && totalLength <= -endOffset )
4837 return QPolygonF();
4838
4839 const double startDistance = startOffset < 0 ? totalLength + startOffset : startOffset;
4840 const double endDistance = endOffset <= 0 ? totalLength + endOffset : endOffset;
4841 QPolygonF substringPoints;
4842 substringPoints.reserve( polyline.size() );
4843
4844 it = polyline.begin();
4845 segmentLengthIt = segmentLengths.begin();
4846
4847 p1 = *it++;
4848 bool foundStart = false;
4849 if ( qgsDoubleNear( startDistance, 0.0 ) || startDistance < 0 )
4850 {
4851 substringPoints << p1;
4852 foundStart = true;
4853 }
4854
4855 double distanceTraversed = 0;
4856 for ( ; it != polyline.end(); ++it )
4857 {
4858 const QPointF p2 = *it;
4859 if ( distanceTraversed < startDistance && distanceTraversed + *segmentLengthIt > startDistance )
4860 {
4861 // start point falls on this segment
4862 const double distanceToStart = startDistance - distanceTraversed;
4863 double startX, startY;
4864 QgsGeometryUtilsBase::pointOnLineWithDistance( p1.x(), p1.y(), p2.x(), p2.y(), distanceToStart, startX, startY );
4865 substringPoints << QPointF( startX, startY );
4866 foundStart = true;
4867 }
4868 if ( foundStart && ( distanceTraversed + *segmentLengthIt > endDistance ) )
4869 {
4870 // end point falls on this segment
4871 const double distanceToEnd = endDistance - distanceTraversed;
4872 double endX, endY;
4873 QgsGeometryUtilsBase::pointOnLineWithDistance( p1.x(), p1.y(), p2.x(), p2.y(), distanceToEnd, endX, endY );
4874 if ( substringPoints.last() != QPointF( endX, endY ) )
4875 substringPoints << QPointF( endX, endY );
4876 }
4877 else if ( foundStart )
4878 {
4879 if ( substringPoints.last() != QPointF( p2.x(), p2.y() ) )
4880 substringPoints << QPointF( p2.x(), p2.y() );
4881 }
4882
4883 distanceTraversed += *segmentLengthIt;
4884 if ( distanceTraversed > endDistance )
4885 break;
4886
4887 p1 = p2;
4888 segmentLengthIt++;
4889 }
4890
4891 if ( ( substringPoints.size() < 2 ) || ( substringPoints.size() == 2 && substringPoints.at( 0 ) == substringPoints.at( 1 ) ) )
4892 return QPolygonF();
4893
4894 return substringPoints;
4895}
4896
4897bool QgsSymbolLayerUtils::isSharpCorner( QPointF p1, QPointF p2, QPointF p3 )
4898{
4899 double vertexAngle = M_PI - ( std::atan2( p3.y() - p2.y(), p3.x() - p2.x() ) - std::atan2( p2.y() - p1.y(), p2.x() - p1.x() ) );
4900 vertexAngle = QgsGeometryUtilsBase::normalizedAngle( vertexAngle );
4901
4902 // extreme angles form more than 45 degree angle at a node
4903 return vertexAngle < M_PI * 135.0 / 180.0 || vertexAngle > M_PI * 225.0 / 180.0;
4904}
4905
4906void QgsSymbolLayerUtils::appendPolyline( QPolygonF &target, const QPolygonF &line )
4907{
4908 target.reserve( target.size() + line.size() );
4909 for ( const QPointF &pt : line )
4910 {
4911 if ( !target.empty() && target.last() == pt )
4912 continue;
4913
4914 target << pt;
4915 }
4916}
4917
4918std::unique_ptr< QgsExpression > QgsSymbolLayerUtils::fieldOrExpressionToExpression( const QString &fieldOrExpression )
4919{
4920 if ( fieldOrExpression.isEmpty() )
4921 return nullptr;
4922
4923 auto expr = std::make_unique< QgsExpression >( fieldOrExpression );
4924 if ( !expr->hasParserError() )
4925 return expr;
4926
4927 // now try with quoted field name
4928 expr = std::make_unique< QgsExpression >( QgsExpression::quotedColumnRef( fieldOrExpression ) );
4929 Q_ASSERT( !expr->hasParserError() );
4930 return expr;
4931}
4932
4934{
4935 const QgsExpressionNode *n = expression->rootNode();
4936
4937 if ( n && n->nodeType() == QgsExpressionNode::ntColumnRef )
4938 return static_cast<const QgsExpressionNodeColumnRef *>( n )->name();
4939
4940 return expression->expression();
4941}
4942
4943QList<double> QgsSymbolLayerUtils::prettyBreaks( double minimum, double maximum, int classes )
4944{
4945 // C++ implementation of R's pretty algorithm
4946 // Based on code for determining optimal tick placement for statistical graphics
4947 // from the R statistical programming language.
4948 // Code ported from R implementation from 'labeling' R package
4949 //
4950 // Computes a sequence of about 'classes' equally spaced round values
4951 // which cover the range of values from 'minimum' to 'maximum'.
4952 // The values are chosen so that they are 1, 2 or 5 times a power of 10.
4953
4954 QList<double> breaks;
4955 if ( classes < 1 )
4956 {
4957 breaks.append( maximum );
4958 return breaks;
4959 }
4960
4961 const int minimumCount = static_cast< int >( classes ) / 3;
4962 const double shrink = 0.75;
4963 const double highBias = 1.5;
4964 const double adjustBias = 0.5 + 1.5 * highBias;
4965 const int divisions = classes;
4966 const double h = highBias;
4967 double cell;
4968 bool small = false;
4969 const double dx = maximum - minimum;
4970
4971 if ( qgsDoubleNear( dx, 0.0 ) && qgsDoubleNear( maximum, 0.0 ) )
4972 {
4973 cell = 1.0;
4974 small = true;
4975 }
4976 else
4977 {
4978 int U = 1;
4979 cell = std::max( std::fabs( minimum ), std::fabs( maximum ) );
4980 if ( adjustBias >= 1.5 * h + 0.5 )
4981 {
4982 U = 1 + ( 1.0 / ( 1 + h ) );
4983 }
4984 else
4985 {
4986 U = 1 + ( 1.5 / ( 1 + adjustBias ) );
4987 }
4988 small = dx < ( cell * U * std::max( 1, divisions ) * 1e-07 * 3.0 );
4989 }
4990
4991 if ( small )
4992 {
4993 if ( cell > 10 )
4994 {
4995 cell = 9 + cell / 10;
4996 cell = cell * shrink;
4997 }
4998 if ( minimumCount > 1 )
4999 {
5000 cell = cell / minimumCount;
5001 }
5002 }
5003 else
5004 {
5005 cell = dx;
5006 if ( divisions > 1 )
5007 {
5008 cell = cell / divisions;
5009 }
5010 }
5011 if ( cell < 20 * 1e-07 )
5012 {
5013 cell = 20 * 1e-07;
5014 }
5015
5016 const double base = std::pow( 10.0, std::floor( std::log10( cell ) ) );
5017 double unit = base;
5018 if ( ( 2 * base ) - cell < h * ( cell - unit ) )
5019 {
5020 unit = 2.0 * base;
5021 if ( ( 5 * base ) - cell < adjustBias * ( cell - unit ) )
5022 {
5023 unit = 5.0 * base;
5024 if ( ( 10.0 * base ) - cell < h * ( cell - unit ) )
5025 {
5026 unit = 10.0 * base;
5027 }
5028 }
5029 }
5030 // Maybe used to correct for the epsilon here??
5031 int start = std::floor( minimum / unit + 1e-07 );
5032 int end = std::ceil( maximum / unit - 1e-07 );
5033
5034 // Extend the range out beyond the data. Does this ever happen??
5035 while ( start * unit > minimum + ( 1e-07 * unit ) )
5036 {
5037 start = start - 1;
5038 }
5039 while ( end * unit < maximum - ( 1e-07 * unit ) )
5040 {
5041 end = end + 1;
5042 }
5043 QgsDebugMsgLevel( QStringLiteral( "pretty classes: %1" ).arg( end ), 3 );
5044
5045 // If we don't have quite enough labels, extend the range out
5046 // to make more (these labels are beyond the data :()
5047 int k = std::floor( 0.5 + end - start );
5048 if ( k < minimumCount )
5049 {
5050 k = minimumCount - k;
5051 if ( start >= 0 )
5052 {
5053 end = end + k / 2;
5054 start = start - k / 2 + k % 2;
5055 }
5056 else
5057 {
5058 start = start - k / 2;
5059 end = end + k / 2 + k % 2;
5060 }
5061 }
5062 const double minimumBreak = start * unit;
5063 //double maximumBreak = end * unit;
5064 const int count = end - start;
5065
5066 breaks.reserve( count );
5067 for ( int i = 1; i < count + 1; i++ )
5068 {
5069 breaks.append( minimumBreak + i * unit );
5070 }
5071
5072 if ( breaks.isEmpty() )
5073 return breaks;
5074
5075 if ( breaks.first() < minimum )
5076 {
5077 breaks[0] = minimum;
5078 }
5079 if ( breaks.last() > maximum )
5080 {
5081 breaks[breaks.count() - 1] = maximum;
5082 }
5083
5084 // because sometimes when number of classes is big,
5085 // break supposed to be at zero is something like -2.22045e-16
5086 if ( minimum < 0.0 && maximum > 0.0 ) //then there should be a zero somewhere
5087 {
5088 QList<double> breaksMinusZero; // compute difference "each break - 0"
5089 for ( int i = 0; i < breaks.count(); i++ )
5090 {
5091 breaksMinusZero.append( breaks[i] - 0.0 );
5092 }
5093 int posOfMin = 0;
5094 for ( int i = 1; i < breaks.count(); i++ ) // find position of minimal difference
5095 {
5096 if ( std::abs( breaksMinusZero[i] ) < std::abs( breaksMinusZero[i - 1] ) )
5097 posOfMin = i;
5098 }
5099 breaks[posOfMin] = 0.0;
5100 }
5101
5102 return breaks;
5103}
5104
5105double QgsSymbolLayerUtils::rescaleUom( double size, Qgis::RenderUnit unit, const QVariantMap &props )
5106{
5107 double scale = 1;
5108 bool roundToUnit = false;
5109 if ( unit == Qgis::RenderUnit::Unknown )
5110 {
5111 if ( props.contains( QStringLiteral( "uomScale" ) ) )
5112 {
5113 bool ok;
5114 scale = props.value( QStringLiteral( "uomScale" ) ).toDouble( &ok );
5115 if ( !ok )
5116 {
5117 return size;
5118 }
5119 }
5120 }
5121 else
5122 {
5123 if ( props.value( QStringLiteral( "uom" ) ) == QLatin1String( "http://www.opengeospatial.org/se/units/metre" ) )
5124 {
5125 switch ( unit )
5126 {
5128 scale = 0.001;
5129 break;
5131 scale = 0.00028;
5132 roundToUnit = true;
5133 break;
5134 default:
5135 scale = 1;
5136 }
5137 }
5138 else
5139 {
5140 // target is pixels
5141 switch ( unit )
5142 {
5144 scale = 1 / 0.28;
5145 roundToUnit = true;
5146 break;
5148 scale = 1 / 0.28 * 25.4;
5149 roundToUnit = true;
5150 break;
5152 scale = 90. /* dots per inch according to OGC SLD */ / 72. /* points per inch */;
5153 roundToUnit = true;
5154 break;
5156 // pixel is pixel
5157 scale = 1;
5158 break;
5161 // already handed via uom
5162 scale = 1;
5163 break;
5166 // these do not make sense and should not really reach here
5167 scale = 1;
5168 }
5169 }
5170
5171 }
5172 double rescaled = size * scale;
5173 // round to unit if the result is pixels to avoid a weird looking SLD (people often think
5174 // of pixels as integers, even if SLD allows for float values in there
5175 if ( roundToUnit )
5176 {
5177 rescaled = std::round( rescaled );
5178 }
5179 return rescaled;
5180}
5181
5182QPointF QgsSymbolLayerUtils::rescaleUom( QPointF point, Qgis::RenderUnit unit, const QVariantMap &props )
5183{
5184 const double x = rescaleUom( point.x(), unit, props );
5185 const double y = rescaleUom( point.y(), unit, props );
5186 return QPointF( x, y );
5187}
5188
5189QVector<qreal> QgsSymbolLayerUtils::rescaleUom( const QVector<qreal> &array, Qgis::RenderUnit unit, const QVariantMap &props )
5190{
5191 QVector<qreal> result;
5192 QVector<qreal>::const_iterator it = array.constBegin();
5193 for ( ; it != array.constEnd(); ++it )
5194 {
5195 result.append( rescaleUom( *it, unit, props ) );
5196 }
5197 return result;
5198}
5199
5200void QgsSymbolLayerUtils::applyScaleDependency( QDomDocument &doc, QDomElement &ruleElem, QVariantMap &props )
5201{
5202 if ( !props.value( QStringLiteral( "scaleMinDenom" ), QString() ).toString().isEmpty() )
5203 {
5204 QDomElement scaleMinDenomElem = doc.createElement( QStringLiteral( "se:MinScaleDenominator" ) );
5205 scaleMinDenomElem.appendChild( doc.createTextNode( qgsDoubleToString( props.value( QStringLiteral( "scaleMinDenom" ) ).toString().toDouble() ) ) );
5206 ruleElem.appendChild( scaleMinDenomElem );
5207 }
5208
5209 if ( !props.value( QStringLiteral( "scaleMaxDenom" ), QString() ).toString().isEmpty() )
5210 {
5211 QDomElement scaleMaxDenomElem = doc.createElement( QStringLiteral( "se:MaxScaleDenominator" ) );
5212 scaleMaxDenomElem.appendChild( doc.createTextNode( qgsDoubleToString( props.value( QStringLiteral( "scaleMaxDenom" ) ).toString().toDouble() ) ) );
5213 ruleElem.appendChild( scaleMaxDenomElem );
5214 }
5215}
5216
5217void QgsSymbolLayerUtils::mergeScaleDependencies( double mScaleMinDenom, double mScaleMaxDenom, QVariantMap &props )
5218{
5219 if ( !qgsDoubleNear( mScaleMinDenom, 0 ) )
5220 {
5221 bool ok;
5222 const double parentScaleMinDenom = props.value( QStringLiteral( "scaleMinDenom" ), QStringLiteral( "0" ) ).toString().toDouble( &ok );
5223 if ( !ok || parentScaleMinDenom <= 0 )
5224 props[ QStringLiteral( "scaleMinDenom" )] = QString::number( mScaleMinDenom );
5225 else
5226 props[ QStringLiteral( "scaleMinDenom" )] = QString::number( std::max( parentScaleMinDenom, mScaleMinDenom ) );
5227 }
5228
5229 if ( !qgsDoubleNear( mScaleMaxDenom, 0 ) )
5230 {
5231 bool ok;
5232 const double parentScaleMaxDenom = props.value( QStringLiteral( "scaleMaxDenom" ), QStringLiteral( "0" ) ).toString().toDouble( &ok );
5233 if ( !ok || parentScaleMaxDenom <= 0 )
5234 props[ QStringLiteral( "scaleMaxDenom" )] = QString::number( mScaleMaxDenom );
5235 else
5236 props[ QStringLiteral( "scaleMaxDenom" )] = QString::number( std::min( parentScaleMaxDenom, mScaleMaxDenom ) );
5237 }
5238}
5239
5240double QgsSymbolLayerUtils::sizeInPixelsFromSldUom( const QString &uom, double size )
5241{
5242 double scale = 1.0;
5243
5244 if ( uom == QLatin1String( "http://www.opengeospatial.org/se/units/metre" ) )
5245 {
5246 scale = 1.0 / 0.00028; // from meters to pixels
5247 }
5248 else if ( uom == QLatin1String( "http://www.opengeospatial.org/se/units/foot" ) )
5249 {
5250 scale = 304.8 / 0.28; // from feet to pixels
5251 }
5252 else
5253 {
5254 scale = 1.0; // from pixels to pixels (default unit)
5255 }
5256
5257 return size * scale;
5258}
5259
5260QSet<const QgsSymbolLayer *> QgsSymbolLayerUtils::toSymbolLayerPointers( const QgsFeatureRenderer *renderer, const QSet<QgsSymbolLayerId> &symbolLayerIds )
5261{
5263 class SymbolLayerVisitor : public QgsStyleEntityVisitorInterface
5264 {
5265 public:
5266 SymbolLayerVisitor( const QSet<QgsSymbolLayerId> &layerIds )
5267 : mSymbolLayerIds( layerIds )
5268 {}
5269
5270 bool visitEnter( const QgsStyleEntityVisitorInterface::Node &node ) override
5271 {
5273 {
5274 mCurrentRuleKey = node.identifier;
5275 return true;
5276 }
5277 return false;
5278 }
5279
5280 void visitSymbol( const QgsSymbol *symbol, const QString &identifier, QVector<int> rootPath )
5281 {
5282 for ( int idx = 0; idx < symbol->symbolLayerCount(); idx++ )
5283 {
5284 QVector<int> indexPath = rootPath;
5285 indexPath.append( idx );
5286 const QgsSymbolLayer *sl = symbol->symbolLayer( idx );
5288 if ( mSymbolLayerIds.contains( QgsSymbolLayerId( mCurrentRuleKey + identifier, indexPath ) ) )
5289 {
5290 mSymbolLayers.insert( sl );
5291 }
5293
5294 const QgsSymbol *subSymbol = const_cast<QgsSymbolLayer *>( sl )->subSymbol();
5295 if ( subSymbol )
5296 visitSymbol( subSymbol, identifier, indexPath );
5297 }
5298 }
5299
5300 bool visit( const QgsStyleEntityVisitorInterface::StyleLeaf &leaf ) override
5301 {
5302 if ( leaf.entity && leaf.entity->type() == QgsStyle::SymbolEntity )
5303 {
5304 auto symbolEntity = static_cast<const QgsStyleSymbolEntity *>( leaf.entity );
5305 if ( symbolEntity->symbol() )
5306 {
5307 visitSymbol( symbolEntity->symbol(), leaf.identifier, {} );
5308 }
5309 }
5310 return true;
5311 }
5312
5313 QString mCurrentRuleKey;
5314 const QSet<QgsSymbolLayerId> &mSymbolLayerIds;
5315 QSet<const QgsSymbolLayer *> mSymbolLayers;
5316 };
5318
5319 SymbolLayerVisitor visitor( symbolLayerIds );
5320 renderer->accept( &visitor );
5321 return visitor.mSymbolLayers;
5322}
5323
5325{
5326 class SymbolRefreshRateVisitor : public QgsStyleEntityVisitorInterface
5327 {
5328 public:
5329 SymbolRefreshRateVisitor()
5330 {}
5331
5332 bool visitEnter( const QgsStyleEntityVisitorInterface::Node &node ) override
5333 {
5335 {
5336 return true;
5337 }
5338 return false;
5339 }
5340
5341 void visitSymbol( const QgsSymbol *symbol )
5342 {
5343 // symbol may be marked as animated on a symbol level (e.g. when it implements animation
5344 // via data defined properties)
5345 if ( symbol->animationSettings().isAnimated() )
5346 {
5347 if ( symbol->animationSettings().frameRate() > refreshRate )
5348 refreshRate = symbol->animationSettings().frameRate();
5349 }
5350 for ( int idx = 0; idx < symbol->symbolLayerCount(); idx++ )
5351 {
5352 const QgsSymbolLayer *sl = symbol->symbolLayer( idx );
5353 if ( const QgsAnimatedMarkerSymbolLayer *animatedMarker = dynamic_cast< const QgsAnimatedMarkerSymbolLayer *>( sl ) )
5354 {
5355 // this is a bit of a short cut -- if a symbol has multiple layers with different frame rates,
5356 // there's no guarantee that they will be even multiples of each other! But given we are looking for
5357 // a single frame rate for a whole renderer, it's an acceptable compromise...
5358 if ( refreshRate == -1 || ( animatedMarker->frameRate() > refreshRate ) )
5359 refreshRate = animatedMarker->frameRate();
5360 }
5361
5362 if ( const QgsSymbol *subSymbol = const_cast<QgsSymbolLayer *>( sl )->subSymbol() )
5363 visitSymbol( subSymbol );
5364 }
5365 }
5366
5367 bool visit( const QgsStyleEntityVisitorInterface::StyleLeaf &leaf ) override
5368 {
5369 if ( leaf.entity && leaf.entity->type() == QgsStyle::SymbolEntity )
5370 {
5371 if ( QgsSymbol *symbol = qgis::down_cast<const QgsStyleSymbolEntity *>( leaf.entity )->symbol() )
5372 {
5373 visitSymbol( symbol );
5374 }
5375 }
5376 return true;
5377 }
5378
5379 double refreshRate = -1;
5380 };
5381
5382 SymbolRefreshRateVisitor visitor;
5383 renderer->accept( &visitor );
5384 return visitor.refreshRate;
5385}
5386
5387QgsSymbol *QgsSymbolLayerUtils::restrictedSizeSymbol( const QgsSymbol *s, double minSize, double maxSize, QgsRenderContext *context, double &width, double &height, bool *ok )
5388{
5389 if ( !s || !context )
5390 {
5391 return nullptr;
5392 }
5393
5394 if ( ok )
5395 *ok = true;
5396
5397 const QgsSymbolLayerList sls = s->symbolLayers();
5398 for ( const QgsSymbolLayer *sl : std::as_const( sls ) )
5399 {
5400 // geometry generators involved, there is no way to get a restricted size symbol
5401 if ( sl->type() == Qgis::SymbolType::Hybrid )
5402 {
5403 if ( ok )
5404 *ok = false;
5405
5406 return nullptr;
5407 }
5408 }
5409
5410 double size;
5411 const QgsMarkerSymbol *markerSymbol = dynamic_cast<const QgsMarkerSymbol *>( s );
5412 const QgsLineSymbol *lineSymbol = dynamic_cast<const QgsLineSymbol *>( s );
5413 if ( markerSymbol )
5414 {
5415 size = markerSymbol->size( *context );
5416 }
5417 else if ( lineSymbol )
5418 {
5419 size = lineSymbol->width( *context );
5420 }
5421 else
5422 {
5423 // cannot return a size restricted symbol but we assume there is no need
5424 // for one as the rendering will be done in the given size (different from geometry
5425 // generator where rendering will bleed outside the given area
5426 return nullptr;
5427 }
5428
5429 size /= context->scaleFactor();
5430
5431 if ( minSize > 0 && size < minSize )
5432 {
5433 size = minSize;
5434 }
5435 else if ( maxSize > 0 && size > maxSize )
5436 {
5437 size = maxSize;
5438 }
5439 else
5440 {
5441 // no need to restricted size symbol
5442 return nullptr;
5443 }
5444
5445 if ( markerSymbol )
5446 {
5447 QgsMarkerSymbol *ms = dynamic_cast<QgsMarkerSymbol *>( s->clone() );
5448 ms->setSize( size );
5450 width = size;
5451 height = size;
5452 return ms;
5453 }
5454 else if ( lineSymbol )
5455 {
5456 QgsLineSymbol *ls = dynamic_cast<QgsLineSymbol *>( s->clone() );
5457 ls->setWidth( size );
5459 height = size;
5460 return ls;
5461 }
5462
5463 return nullptr;
5464}
5465
5466QgsStringMap QgsSymbolLayerUtils::evaluatePropertiesMap( const QMap<QString, QgsProperty> &propertiesMap, const QgsExpressionContext &context )
5467{
5468 QgsStringMap properties;
5469 QMap<QString, QgsProperty>::const_iterator paramIt = propertiesMap.constBegin();
5470 for ( ; paramIt != propertiesMap.constEnd(); ++paramIt )
5471 {
5472 properties.insert( paramIt.key(), paramIt.value().valueAsString( context ) );
5473 }
5474 return properties;
5475}
5476
5477QSize QgsSymbolLayerUtils::tileSize( int width, int height, double &angleRad )
5478{
5479
5480 angleRad = std::fmod( angleRad, M_PI * 2 );
5481
5482 if ( angleRad < 0 )
5483 {
5484 angleRad += M_PI * 2;
5485 }
5486
5487 // tan with rational sin/cos
5488 struct rationalTangent
5489 {
5490 int p; // numerator
5491 int q; // denominator
5492 double angle; // "good" angle
5493 };
5494
5495#if 0
5496
5497 // This list is more granular (approx 1 degree steps) but some
5498 // values can lead to huge tiles
5499 // List of "good" angles from 0 to PI/2
5500 static const QList<rationalTangent> __rationalTangents
5501 {
5502 { 1, 57, 0.01754206006 },
5503 { 3, 86, 0.03486958155 },
5504 { 1, 19, 0.05258306161 },
5505 { 3, 43, 0.06965457373 },
5506 { 7, 80, 0.08727771295 },
5507 { 2, 19, 0.1048769387 },
5508 { 7, 57, 0.1221951707 },
5509 { 9, 64, 0.1397088743 },
5510 { 13, 82, 0.157228051 },
5511 { 3, 17, 0.174672199 },
5512 { 7, 36, 0.1920480172 },
5513 { 17, 80, 0.209385393 },
5514 { 3, 13, 0.2267988481 },
5515 { 1, 4, 0.2449786631 },
5516 { 26, 97, 0.2618852647 },
5517 { 27, 94, 0.2797041525 },
5518 { 26, 85, 0.2968446734 },
5519 { 13, 40, 0.3142318991 },
5520 { 21, 61, 0.3315541619 },
5521 { 4, 11, 0.3487710036 },
5522 { 38, 99, 0.3664967859 },
5523 { 40, 99, 0.383984624 },
5524 { 31, 73, 0.4015805401 },
5525 { 41, 92, 0.4192323938 },
5526 { 7, 15, 0.4366271598 },
5527 { 20, 41, 0.4538440015 },
5528 { 27, 53, 0.4711662643 },
5529 { 42, 79, 0.4886424026 },
5530 { 51, 92, 0.5061751436 },
5531 { 56, 97, 0.5235757641 },
5532 { 3, 5, 0.5404195003 },
5533 { 5, 8, 0.5585993153 },
5534 { 50, 77, 0.5759185996 },
5535 { 29, 43, 0.5933501462 },
5536 { 7, 10, 0.6107259644 },
5537 { 69, 95, 0.6281701124 },
5538 { 52, 69, 0.6458159195 },
5539 { 25, 32, 0.6632029927 },
5540 { 17, 21, 0.6805212247 },
5541 { 73, 87, 0.6981204504 },
5542 { 73, 84, 0.7154487784 },
5543 { 9, 10, 0.7328151018 },
5544 { 83, 89, 0.7505285818 },
5545 { 28, 29, 0.7678561033 },
5546 { 1, 1, 0.7853981634 },
5547 { 29, 28, 0.8029402235 },
5548 { 89, 83, 0.820267745 },
5549 { 10, 9, 0.837981225 },
5550 { 107, 93, 0.855284165 },
5551 { 87, 73, 0.8726758763 },
5552 { 121, 98, 0.8900374031 },
5553 { 32, 25, 0.9075933341 },
5554 { 69, 52, 0.9249804073 },
5555 { 128, 93, 0.9424647244 },
5556 { 10, 7, 0.9600703624 },
5557 { 43, 29, 0.9774461806 },
5558 { 77, 50, 0.9948777272 },
5559 { 8, 5, 1.012197011 },
5560 { 163, 98, 1.029475114 },
5561 { 168, 97, 1.047174539 },
5562 { 175, 97, 1.064668696 },
5563 { 126, 67, 1.082075603 },
5564 { 157, 80, 1.099534652 },
5565 { 203, 99, 1.117049384 },
5566 { 193, 90, 1.134452855 },
5567 { 146, 65, 1.151936673 },
5568 { 139, 59, 1.169382787 },
5569 { 99, 40, 1.186811703 },
5570 { 211, 81, 1.204257817 },
5571 { 272, 99, 1.221730164 },
5572 { 273, 94, 1.239188479 },
5573 { 277, 90, 1.25664606 },
5574 { 157, 48, 1.274088705 },
5575 { 279, 80, 1.291550147 },
5576 { 362, 97, 1.308990773 },
5577 { 373, 93, 1.326448578 },
5578 { 420, 97, 1.343823596 },
5579 { 207, 44, 1.361353157 },
5580 { 427, 83, 1.378810994 },
5581 { 414, 73, 1.396261926 },
5582 { 322, 51, 1.413716057 },
5583 { 185, 26, 1.431170275 },
5584 { 790, 97, 1.448623034 },
5585 { 333, 35, 1.466075711 },
5586 { 1063, 93, 1.483530284 },
5587 { 1330, 93, 1.500985147 },
5588 { 706, 37, 1.518436297 },
5589 { 315, 11, 1.535889876 },
5590 { 3953, 69, 1.553343002 },
5591 };
5592#endif
5593
5594 // Optimized "good" angles list, it produces small tiles but
5595 // it has approximately 10 degrees steps
5596 static const QList<rationalTangent> rationalTangents
5597 {
5598 { 1, 10, qDegreesToRadians( 5.71059 ) },
5599 { 1, 5, qDegreesToRadians( 11.3099 ) },
5600 { 1, 4, qDegreesToRadians( 14.0362 ) },
5601 { 1, 4, qDegreesToRadians( 18.4349 ) },
5602 { 1, 2, qDegreesToRadians( 26.5651 ) },
5603 { 2, 3, qDegreesToRadians( 33.6901 ) },
5604 { 1, 1, qDegreesToRadians( 45.0 ) },
5605 { 3, 2, qDegreesToRadians( 56.3099 ) },
5606 { 2, 1, qDegreesToRadians( 63.4349 ) },
5607 { 3, 1, qDegreesToRadians( 71.5651 ) },
5608 { 4, 1, qDegreesToRadians( 75.9638 ) },
5609 { 10, 1, qDegreesToRadians( 84.2894 ) },
5610 };
5611
5612 const int quadrant { static_cast<int>( angleRad / M_PI_2 ) };
5613 Q_ASSERT( quadrant >= 0 && quadrant <= 3 );
5614
5615 QSize tileSize;
5616
5617 switch ( quadrant )
5618 {
5619 case 0:
5620 {
5621 break;
5622 }
5623 case 1:
5624 {
5625 angleRad -= M_PI / 2;
5626 break;
5627 }
5628 case 2:
5629 {
5630 angleRad -= M_PI;
5631 break;
5632 }
5633 case 3:
5634 {
5635 angleRad -= M_PI + M_PI_2;
5636 break;
5637 }
5638 }
5639
5640 if ( qgsDoubleNear( angleRad, 0, 10E-3 ) )
5641 {
5642 angleRad = 0;
5643 tileSize.setWidth( width );
5644 tileSize.setHeight( height );
5645 }
5646 else if ( qgsDoubleNear( angleRad, M_PI_2, 10E-3 ) )
5647 {
5648 angleRad = M_PI_2;
5649 tileSize.setWidth( height );
5650 tileSize.setHeight( width );
5651 }
5652 else
5653 {
5654
5655 int rTanIdx = 0;
5656
5657 for ( int idx = 0; idx < rationalTangents.count(); ++idx )
5658 {
5659 const auto item = rationalTangents.at( idx );
5660 if ( qgsDoubleNear( item.angle, angleRad, 10E-3 ) || item.angle > angleRad )
5661 {
5662 rTanIdx = idx;
5663 break;
5664 }
5665 }
5666
5667 const rationalTangent bTan { rationalTangents.at( rTanIdx ) };
5668 angleRad = bTan.angle;
5669 const double k { bTan.q *height *width / std::cos( angleRad ) };
5670 const int hcfH { std::gcd( bTan.p * height, bTan.q * width ) };
5671 const int hcfW { std::gcd( bTan.q * height, bTan.p * width ) };
5672 const int W1 { static_cast<int>( std::round( k / hcfW ) ) };
5673 const int H1 { static_cast<int>( std::round( k / hcfH ) ) };
5674 tileSize.setWidth( W1 );
5675 tileSize.setHeight( H1 );
5676 }
5677
5678 switch ( quadrant )
5679 {
5680 case 0:
5681 {
5682 break;
5683 }
5684 case 1:
5685 {
5686 angleRad += M_PI / 2;
5687 const int h { tileSize.height() };
5688 tileSize.setHeight( tileSize.width() );
5689 tileSize.setWidth( h );
5690 break;
5691 }
5692 case 2:
5693 {
5694 angleRad += M_PI;
5695 break;
5696 }
5697 case 3:
5698 {
5699 angleRad += M_PI + M_PI_2;
5700 const int h { tileSize.height() };
5701 tileSize.setHeight( tileSize.width() );
5702 tileSize.setWidth( h );
5703 break;
5704 }
5705 }
5706
5707 return tileSize;
5708}
5709
5710template <typename Functor>
5711void changeSymbolLayerIds( QgsSymbolLayer *sl, Functor &&generateId )
5712{
5713 sl->setId( generateId() );
5714
5715 // recurse over sub symbols
5716 QgsSymbol *subSymbol = sl->subSymbol();
5717 if ( subSymbol )
5718 changeSymbolLayerIds( subSymbol, generateId );
5719}
5720
5721template <typename Functor>
5722void changeSymbolLayerIds( QgsSymbol *symbol, Functor &&generateId )
5723{
5724 if ( !symbol )
5725 return;
5726
5727 for ( int idx = 0; idx < symbol->symbolLayerCount(); idx++ )
5728 changeSymbolLayerIds( symbol->symbolLayer( idx ), generateId );
5729}
5730
5732{
5733 changeSymbolLayerIds( symbol, []() { return QString(); } );
5734}
5735
5737{
5738 changeSymbolLayerIds( symbolLayer, []() { return QString(); } );
5739}
5740
5742{
5743 changeSymbolLayerIds( symbolLayer, []() { return QUuid::createUuid().toString(); } );
5744}
5745
5747{
5748 if ( !symbol )
5749 return;
5750
5751 for ( int idx = 0; idx < symbol->symbolLayerCount(); idx++ )
5752 {
5753 if ( QgsMaskMarkerSymbolLayer *maskSl = dynamic_cast<QgsMaskMarkerSymbolLayer *>( symbol->symbolLayer( idx ) ) )
5754 {
5755 maskSl->clearMasks();
5756
5757 // recurse over sub symbols
5758 if ( QgsSymbol *subSymbol = maskSl->subSymbol() )
5759 {
5760 clearSymbolLayerMasks( subSymbol );
5761 }
5762 }
5763 }
5764}
5765
5766QVector<QgsGeometry> QgsSymbolLayerUtils::collectSymbolLayerClipGeometries( const QgsRenderContext &context, const QString &symbolLayerId, const QRectF &bounds )
5767{
5768 QVector<QgsGeometry> clipGeometries = context.symbolLayerClipGeometries( symbolLayerId );
5769 if ( clipGeometries.empty() )
5770 return {};
5771
5772 if ( bounds.isNull() )
5773 return clipGeometries;
5774
5775 const QgsRectangle boundsRect = QgsRectangle( bounds );
5776
5777 clipGeometries.erase(
5778 std::remove_if( clipGeometries.begin(), clipGeometries.end(), [&boundsRect]( const QgsGeometry & geometry )
5779 {
5780 return !geometry.boundingBoxIntersects( boundsRect );
5781 } ), clipGeometries.end() );
5782
5783 return clipGeometries;
5784}
5785
5787{
5788 changeSymbolLayerIds( symbol, []() { return QUuid::createUuid().toString(); } );
5789}
@ PreferVector
Prefer vector-based rendering, when the result will still be visually near-identical to a raster-base...
MarkerClipMode
Marker clipping modes.
Definition qgis.h:3159
@ CompletelyWithin
Render complete markers wherever the completely fall within the polygon shape.
@ NoClipping
No clipping, render complete markers.
@ Shape
Clip to polygon shape.
@ CentroidWithin
Render complete markers wherever their centroid falls within the polygon shape.
LineClipMode
Line clipping modes.
Definition qgis.h:3173
@ NoClipping
Lines are not clipped, will extend to shape's bounding box.
@ ClipPainterOnly
Applying clipping on the painter only (i.e. line endpoints will coincide with polygon bounding box,...
@ ClipToIntersection
Clip lines to intersection with polygon shape (slower) (i.e. line endpoints will coincide with polygo...
ScaleMethod
Scale methods.
Definition qgis.h:588
@ ScaleDiameter
Calculate scale by the diameter.
@ ScaleArea
Calculate scale by the area.
QFlags< SymbolLayerUserFlag > SymbolLayerUserFlags
Symbol layer user flags.
Definition qgis.h:874
GeometryType
The geometry types are used to group Qgis::WkbType in a coarse way.
Definition qgis.h:337
@ Polygon
Polygons.
@ Unknown
Unknown types.
@ Null
No geometry.
JoinStyle
Join styles for buffers.
Definition qgis.h:2084
@ Bevel
Use beveled joins.
@ Round
Use rounded joins.
@ Miter
Use mitered joins.
RenderUnit
Rendering size units.
Definition qgis.h:5045
@ Percentage
Percentage of another measurement (e.g., canvas size, feature size)
@ Millimeters
Millimeters.
@ Points
Points (e.g., for font sizes)
@ Unknown
Mixed or unknown units.
@ MapUnits
Map units.
@ MetersInMapUnits
Meters value as Map units.
EndCapStyle
End cap styles for buffers.
Definition qgis.h:2071
@ Flat
Flat cap (in line with start/end of line)
@ Round
Round cap.
@ Square
Square cap (extends past start/end of line by buffer distance)
@ RenderSymbolPreview
The render is for a symbol preview only and map based properties may not be available,...
@ Antialiasing
Use antialiasing while drawing.
@ HighQualityImageTransforms
Enable high quality image transformations, which results in better appearance of scaled or rotated ra...
VertexMarkerType
Editing vertex markers, used for showing vertices during a edit operation.
Definition qgis.h:1796
@ SemiTransparentCircle
Semi-transparent circle marker.
@ Cross
Cross marker.
QFlags< SymbolRenderHint > SymbolRenderHints
Symbol render hints.
Definition qgis.h:741
QFlags< SymbolFlag > SymbolFlags
Symbol flags.
Definition qgis.h:817
SymbolType
Symbol types.
Definition qgis.h:574
@ Marker
Marker symbol.
@ Line
Line symbol.
@ Fill
Fill symbol.
@ Hybrid
Hybrid symbol.
@ RendererShouldUseSymbolLevels
If present, indicates that a QgsFeatureRenderer using the symbol should use symbol levels for best re...
@ LineString
LineString.
@ MultiPoint
MultiPoint.
@ Polygon
Polygon.
@ MultiPolygon
MultiPolygon.
@ MultiLineString
MultiLineString.
SymbolCoordinateReference
Symbol coordinate reference modes.
Definition qgis.h:3117
@ Feature
Relative to feature/shape being rendered.
@ Viewport
Relative to the whole viewport/output device.
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 readXml(const QDomElement &collectionElem, const QgsPropertiesDefinition &definitions)
Reads property collection state from an XML element.
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:35
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.
void setWidthUnit(Qgis::RenderUnit unit) const
Sets the width units for the whole symbol (including all symbol layers).
void setWidth(double width) const
Sets the width for the whole line 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:77
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.
void setSize(double size) const
Sets the size for the whole symbol.
double size() const
Returns the estimated size for the whole symbol, which is the maximum size of all marker symbol layer...
void setSizeUnit(Qgis::RenderUnit unit) const
Sets the size units for the whole symbol (including all symbol layers).
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:1397
@ SymbolEntity
Symbols.
Definition qgsstyle.h:205
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 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 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 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:799
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:644
bool clipFeaturesToExtent() const
Returns whether features drawn by the symbol will be clipped to the render context's extent.
Definition qgssymbol.h:699
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:910
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:721
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.
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:286
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:291
#define Q_NOWARN_DEPRECATED_POP
Definition qgis.h:6920
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
Definition qgis.h:6284
QString qgsFlagValueToKeys(const T &value, bool *returnOk=nullptr)
Returns the value for the given keys of a flag.
Definition qgis.h:6616
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:6638
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:6919
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition qgis.h:6367
QVector< QgsPolylineXY > QgsPolygonXY
Polygon: first item of the list is outer ring, inner rings (if any) start from second item.
Definition qgsgeometry.h:74
QVector< QgsPolylineXY > QgsMultiPolylineXY
A collection of QgsPolylines that share a common collection of attributes.
Definition qgsgeometry.h:84
QVector< QgsPointXY > QgsPolylineXY
Polyline as represented as a vector of two-dimensional points.
Definition qgsgeometry.h:62
QVector< QgsPolygonXY > QgsMultiPolygonXY
A collection of QgsPolygons that share a common collection of attributes.
Definition qgsgeometry.h:91
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:41
#define QgsDebugError(str)
Definition qgslogger.h:40
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)
QList< QPair< QColor, QString > > QgsNamedColorList
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.