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