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