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