QGIS API Documentation 3.41.0-Master (88383c3d16f)
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 stream << "GIMP Palette" << Qt::endl;
3830 if ( paletteName.isEmpty() )
3831 {
3832 stream << "Name: QGIS Palette" << Qt::endl;
3833 }
3834 else
3835 {
3836 stream << "Name: " << paletteName << Qt::endl;
3837 }
3838 stream << "Columns: 4" << Qt::endl;
3839 stream << '#' << Qt::endl;
3840
3841 for ( QgsNamedColorList::ConstIterator colorIt = colors.constBegin(); colorIt != colors.constEnd(); ++colorIt )
3842 {
3843 const QColor color = ( *colorIt ).first;
3844 if ( !color.isValid() )
3845 {
3846 continue;
3847 }
3848 stream << QStringLiteral( "%1 %2 %3" ).arg( color.red(), 3 ).arg( color.green(), 3 ).arg( color.blue(), 3 );
3849 stream << "\t" << ( ( *colorIt ).second.isEmpty() ? color.name() : ( *colorIt ).second ) << Qt::endl;
3850 }
3851 file.close();
3852
3853 return true;
3854}
3855
3857{
3858 QgsNamedColorList importedColors;
3859
3860 if ( !file.open( QIODevice::ReadOnly ) )
3861 {
3862 ok = false;
3863 return importedColors;
3864 }
3865
3866 QTextStream in( &file );
3867
3868 QString line = in.readLine();
3869 if ( !line.startsWith( QLatin1String( "GIMP Palette" ) ) )
3870 {
3871 ok = false;
3872 return importedColors;
3873 }
3874
3875 //find name line
3876 while ( !in.atEnd() && !line.startsWith( QLatin1String( "Name:" ) ) && !line.startsWith( '#' ) )
3877 {
3878 line = in.readLine();
3879 }
3880 if ( line.startsWith( QLatin1String( "Name:" ) ) )
3881 {
3882 const thread_local QRegularExpression nameRx( "Name:\\s*(\\S.*)$" );
3883 const QRegularExpressionMatch match = nameRx.match( line );
3884 if ( match.hasMatch() )
3885 {
3886 name = match.captured( 1 );
3887 }
3888 }
3889
3890 //ignore lines until after "#"
3891 while ( !in.atEnd() && !line.startsWith( '#' ) )
3892 {
3893 line = in.readLine();
3894 }
3895 if ( in.atEnd() )
3896 {
3897 ok = false;
3898 return importedColors;
3899 }
3900
3901 //ready to start reading colors
3902 const thread_local QRegularExpression rx( "^\\s*(\\d+)\\s+(\\d+)\\s+(\\d+)(\\s.*)?$" );
3903 while ( !in.atEnd() )
3904 {
3905 line = in.readLine();
3906 const QRegularExpressionMatch match = rx.match( line );
3907 if ( !match.hasMatch() )
3908 {
3909 continue;
3910 }
3911 const int red = match.captured( 1 ).toInt();
3912 const int green = match.captured( 2 ).toInt();
3913 const int blue = match.captured( 3 ).toInt();
3914 const QColor color = QColor( red, green, blue );
3915 if ( !color.isValid() )
3916 {
3917 continue;
3918 }
3919
3920 //try to read color name
3921 QString label;
3922 if ( rx.captureCount() > 3 )
3923 {
3924 label = match.captured( 4 ).simplified();
3925 }
3926 else
3927 {
3928 label = colorToName( color );
3929 }
3930
3931 importedColors << qMakePair( color, label );
3932 }
3933
3934 file.close();
3935 ok = true;
3936 return importedColors;
3937}
3938
3939QColor QgsSymbolLayerUtils::parseColor( const QString &colorStr, bool strictEval )
3940{
3941 bool hasAlpha;
3942 return parseColorWithAlpha( colorStr, hasAlpha, strictEval );
3943}
3944
3945QColor QgsSymbolLayerUtils::parseColorWithAlpha( const QString &colorStr, bool &containsAlpha, bool strictEval )
3946{
3947 QColor parsedColor;
3948
3949 const thread_local QRegularExpression hexColorAlphaRx( "^\\s*#?([0-9a-fA-F]{6})([0-9a-fA-F]{2})\\s*$" );
3950 QRegularExpressionMatch match = hexColorAlphaRx.match( colorStr );
3951
3952 //color in hex format "#aabbcc", but not #aabbccdd
3953 if ( !match.hasMatch() && QColor::isValidColor( colorStr ) )
3954 {
3955 //string is a valid hex color string
3956 parsedColor.setNamedColor( colorStr );
3957 if ( parsedColor.isValid() )
3958 {
3959 containsAlpha = false;
3960 return parsedColor;
3961 }
3962 }
3963
3964 //color in hex format, with alpha
3965 if ( match.hasMatch() )
3966 {
3967 const QString hexColor = match.captured( 1 );
3968 parsedColor.setNamedColor( QStringLiteral( "#" ) + hexColor );
3969 bool alphaOk;
3970 const int alphaHex = match.captured( 2 ).toInt( &alphaOk, 16 );
3971
3972 if ( parsedColor.isValid() && alphaOk )
3973 {
3974 parsedColor.setAlpha( alphaHex );
3975 containsAlpha = true;
3976 return parsedColor;
3977 }
3978 }
3979
3980 if ( !strictEval )
3981 {
3982 //color in hex format, without #
3983 const thread_local QRegularExpression hexColorRx2( "^\\s*(?:[0-9a-fA-F]{3}){1,2}\\s*$" );
3984 if ( colorStr.indexOf( hexColorRx2 ) != -1 )
3985 {
3986 //add "#" and parse
3987 parsedColor.setNamedColor( QStringLiteral( "#" ) + colorStr );
3988 if ( parsedColor.isValid() )
3989 {
3990 containsAlpha = false;
3991 return parsedColor;
3992 }
3993 }
3994 }
3995
3996 //color in (rrr,ggg,bbb) format, brackets and rgb prefix optional
3997 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*$" );
3998 match = rgbFormatRx.match( colorStr );
3999 if ( match.hasMatch() )
4000 {
4001 bool rOk = false;
4002 bool gOk = false;
4003 bool bOk = false;
4004 const int r = match.captured( 1 ).toInt( &rOk );
4005 const int g = match.captured( 2 ).toInt( &gOk );
4006 const int b = match.captured( 3 ).toInt( &bOk );
4007
4008 if ( !rOk || !gOk || !bOk )
4009 {
4010 const float rFloat = match.captured( 1 ).toFloat();
4011 const float gFloat = match.captured( 2 ).toFloat();
4012 const float bFloat = match.captured( 3 ).toFloat();
4013 parsedColor.setRgbF( rFloat / 255.0, gFloat / 255.0, bFloat / 255.0 );
4014 }
4015 else
4016 {
4017 parsedColor.setRgb( r, g, b );
4018 }
4019
4020 if ( parsedColor.isValid() )
4021 {
4022 containsAlpha = false;
4023 return parsedColor;
4024 }
4025 }
4026
4027 //color in hsl(h,s,l) format, brackets optional
4028 const thread_local QRegularExpression hslFormatRx( "^\\s*hsl\\(?\\s*(\\d+(?:\\.\\d*)?)\\s*,\\s*(\\d+(?:\\.\\d*)?)\\s*%\\s*,\\s*(\\d+(?:\\.\\d*)?)\\s*%\\s*\\)?\\s*;?\\s*$" );
4029 match = hslFormatRx.match( colorStr );
4030 if ( match.hasMatch() )
4031 {
4032 bool hOk = false;
4033 bool sOk = false;
4034 bool lOk = false;
4035 const int h = match.captured( 1 ).toInt( &hOk );
4036 const int s = match.captured( 2 ).toInt( &sOk );
4037 const int l = match.captured( 3 ).toInt( &lOk );
4038
4039 if ( !hOk || !sOk || !lOk )
4040 {
4041 const float hFloat = match.captured( 1 ).toFloat();
4042 const float sFloat = match.captured( 2 ).toFloat();
4043 const float lFloat = match.captured( 3 ).toFloat();
4044 parsedColor.setHslF( hFloat / 360.0, sFloat / 100.0, lFloat / 100.0 );
4045 }
4046 else
4047 {
4048 parsedColor.setHsl( h, s / 100.0 * 255.0, l / 100.0 * 255.0 );
4049 }
4050 if ( parsedColor.isValid() )
4051 {
4052 containsAlpha = false;
4053 return parsedColor;
4054 }
4055 }
4056
4057 //color in (r%,g%,b%) format, brackets and rgb prefix optional
4058 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*$" );
4059 match = rgbPercentFormatRx.match( colorStr );
4060 if ( match.hasMatch() )
4061 {
4062 const double r = match.captured( 1 ).toDouble() / 100;
4063 const double g = match.captured( 2 ).toDouble() / 100;
4064 const double b = match.captured( 3 ).toDouble() / 100;
4065 parsedColor.setRgbF( r, g, b );
4066 if ( parsedColor.isValid() )
4067 {
4068 containsAlpha = false;
4069 return parsedColor;
4070 }
4071 }
4072
4073 //color in (r,g,b,a) format, brackets and rgba prefix optional
4074 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*$" );
4075 match = rgbaFormatRx.match( colorStr );
4076 if ( match.hasMatch() )
4077 {
4078 bool rOk = false;
4079 bool gOk = false;
4080 bool bOk = false;
4081 const int r = match.captured( 1 ).toInt( &rOk );
4082 const int g = match.captured( 2 ).toInt( &gOk );
4083 const int b = match.captured( 3 ).toInt( &bOk );
4084 const double aDouble = match.captured( 4 ).toDouble();
4085
4086 if ( !rOk || !gOk || !bOk )
4087 {
4088 const float rFloat = match.captured( 1 ).toFloat();
4089 const float gFloat = match.captured( 2 ).toFloat();
4090 const float bFloat = match.captured( 3 ).toFloat();
4091 parsedColor.setRgbF( rFloat / 255.0, gFloat / 255.0, bFloat / 255.0, aDouble );
4092 }
4093 else
4094 {
4095 const int a = static_cast< int >( std::round( match.captured( 4 ).toDouble() * 255.0 ) );
4096 parsedColor.setRgb( r, g, b, a );
4097 }
4098 if ( parsedColor.isValid() )
4099 {
4100 containsAlpha = true;
4101 return parsedColor;
4102 }
4103 }
4104
4105 //color in (r%,g%,b%,a) format, brackets and rgba prefix optional
4106 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*$" );
4107 match = rgbaPercentFormatRx.match( colorStr );
4108 if ( match.hasMatch() )
4109 {
4110 const double r = match.captured( 1 ).toDouble() / 100;
4111 const double g = match.captured( 2 ).toDouble() / 100;
4112 const double b = match.captured( 3 ).toDouble() / 100;
4113 const double a = match.captured( 4 ).toDouble();
4114 parsedColor.setRgbF( r, g, b, a );
4115 if ( parsedColor.isValid() )
4116 {
4117 containsAlpha = true;
4118 return parsedColor;
4119 }
4120 }
4121
4122 //color in hsla(h,s%,l%,a) format, brackets optional
4123 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*$" );
4124 match = hslaPercentFormatRx.match( colorStr );
4125 if ( match.hasMatch() )
4126 {
4127 bool hOk = false;
4128 bool sOk = false;
4129 bool lOk = false;
4130 const int h = match.captured( 1 ).toInt( &hOk );
4131 const int s = match.captured( 2 ).toInt( &sOk );
4132 const int l = match.captured( 3 ).toInt( &lOk );
4133 const double aDouble = match.captured( 4 ).toDouble();
4134
4135 if ( !hOk || !sOk || !lOk )
4136 {
4137 const float hFloat = match.captured( 1 ).toFloat();
4138 const float sFloat = match.captured( 2 ).toFloat();
4139 const float lFloat = match.captured( 3 ).toFloat();
4140 parsedColor.setHslF( hFloat / 360.0, sFloat / 100.0, lFloat / 100.0, aDouble );
4141 }
4142 else
4143 {
4144 const int a = std::round( aDouble * 255.0 );
4145 parsedColor.setHsl( h, s / 100.0 * 255.0, l / 100.0 * 255.0, a );
4146 }
4147
4148 if ( parsedColor.isValid() )
4149 {
4150 containsAlpha = true;
4151 return parsedColor;
4152 }
4153 }
4154
4155 //couldn't parse string as color
4156 return QColor();
4157}
4158
4159void QgsSymbolLayerUtils::multiplyImageOpacity( QImage *image, qreal opacity )
4160{
4161 if ( !image )
4162 {
4163 return;
4164 }
4165
4166 QRgb myRgb;
4167 const QImage::Format format = image->format();
4168 if ( format != QImage::Format_ARGB32_Premultiplied && format != QImage::Format_ARGB32 )
4169 {
4170 QgsDebugError( QStringLiteral( "no alpha channel." ) );
4171 return;
4172 }
4173
4174 //change the alpha component of every pixel
4175 for ( int heightIndex = 0; heightIndex < image->height(); ++heightIndex )
4176 {
4177 QRgb *scanLine = reinterpret_cast< QRgb * >( image->scanLine( heightIndex ) );
4178 for ( int widthIndex = 0; widthIndex < image->width(); ++widthIndex )
4179 {
4180 myRgb = scanLine[widthIndex];
4181 if ( format == QImage::Format_ARGB32_Premultiplied )
4182 scanLine[widthIndex] = qRgba( opacity * qRed( myRgb ), opacity * qGreen( myRgb ), opacity * qBlue( myRgb ), opacity * qAlpha( myRgb ) );
4183 else
4184 scanLine[widthIndex] = qRgba( qRed( myRgb ), qGreen( myRgb ), qBlue( myRgb ), opacity * qAlpha( myRgb ) );
4185 }
4186 }
4187}
4188
4189void QgsSymbolLayerUtils::blurImageInPlace( QImage &image, QRect rect, int radius, bool alphaOnly )
4190{
4191 // culled from Qt's qpixmapfilter.cpp, see: http://www.qtcentre.org/archive/index.php/t-26534.html
4192 const int tab[] = { 14, 10, 8, 6, 5, 5, 4, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2 };
4193 const int alpha = ( radius < 1 ) ? 16 : ( radius > 17 ) ? 1 : tab[radius - 1];
4194
4195 if ( image.format() != QImage::Format_ARGB32_Premultiplied
4196 && image.format() != QImage::Format_RGB32 )
4197 {
4198 image = image.convertToFormat( QImage::Format_ARGB32_Premultiplied );
4199 }
4200
4201 const int r1 = rect.top();
4202 const int r2 = rect.bottom();
4203 const int c1 = rect.left();
4204 const int c2 = rect.right();
4205
4206 const int bpl = image.bytesPerLine();
4207 int rgba[4];
4208 unsigned char *p;
4209
4210 int i1 = 0;
4211 int i2 = 3;
4212
4213 if ( alphaOnly ) // this seems to only work right for a black color
4214 i1 = i2 = ( QSysInfo::ByteOrder == QSysInfo::BigEndian ? 0 : 3 );
4215
4216 for ( int col = c1; col <= c2; col++ )
4217 {
4218 p = image.scanLine( r1 ) + col * 4;
4219 for ( int i = i1; i <= i2; i++ )
4220 rgba[i] = p[i] << 4;
4221
4222 p += bpl;
4223 for ( int j = r1; j < r2; j++, p += bpl )
4224 for ( int i = i1; i <= i2; i++ )
4225 p[i] = ( rgba[i] += ( ( p[i] << 4 ) - rgba[i] ) * alpha / 16 ) >> 4;
4226 }
4227
4228 for ( int row = r1; row <= r2; row++ )
4229 {
4230 p = image.scanLine( row ) + c1 * 4;
4231 for ( int i = i1; i <= i2; i++ )
4232 rgba[i] = p[i] << 4;
4233
4234 p += 4;
4235 for ( int j = c1; j < c2; j++, p += 4 )
4236 for ( int i = i1; i <= i2; i++ )
4237 p[i] = ( rgba[i] += ( ( p[i] << 4 ) - rgba[i] ) * alpha / 16 ) >> 4;
4238 }
4239
4240 for ( int col = c1; col <= c2; col++ )
4241 {
4242 p = image.scanLine( r2 ) + col * 4;
4243 for ( int i = i1; i <= i2; i++ )
4244 rgba[i] = p[i] << 4;
4245
4246 p -= bpl;
4247 for ( int j = r1; j < r2; j++, p -= bpl )
4248 for ( int i = i1; i <= i2; i++ )
4249 p[i] = ( rgba[i] += ( ( p[i] << 4 ) - rgba[i] ) * alpha / 16 ) >> 4;
4250 }
4251
4252 for ( int row = r1; row <= r2; row++ )
4253 {
4254 p = image.scanLine( row ) + c2 * 4;
4255 for ( int i = i1; i <= i2; i++ )
4256 rgba[i] = p[i] << 4;
4257
4258 p -= 4;
4259 for ( int j = c1; j < c2; j++, p -= 4 )
4260 for ( int i = i1; i <= i2; i++ )
4261 p[i] = ( rgba[i] += ( ( p[i] << 4 ) - rgba[i] ) * alpha / 16 ) >> 4;
4262 }
4263}
4264
4265void QgsSymbolLayerUtils::premultiplyColor( QColor &rgb, int alpha )
4266{
4267 if ( alpha != 255 && alpha > 0 )
4268 {
4269 // Semi-transparent pixel. We need to adjust the colors for ARGB32_Premultiplied images
4270 // where color values have to be premultiplied by alpha
4271 const double alphaFactor = alpha / 255.;
4272 int r = 0, g = 0, b = 0;
4273 rgb.getRgb( &r, &g, &b );
4274
4275 r *= alphaFactor;
4276 g *= alphaFactor;
4277 b *= alphaFactor;
4278 rgb.setRgb( r, g, b, alpha );
4279 }
4280 else if ( alpha == 0 )
4281 {
4282 rgb.setRgb( 0, 0, 0, 0 );
4283 }
4284}
4285
4287{
4288 QgsSimpleFillSymbolLayer *simpleFill = dynamic_cast< QgsSimpleFillSymbolLayer *>( fill );
4289 QgsSimpleLineSymbolLayer *simpleLine = dynamic_cast< QgsSimpleLineSymbolLayer *>( outline );
4290
4291 if ( !simpleFill || !simpleLine )
4292 return false;
4293
4294 if ( simpleLine->useCustomDashPattern() )
4295 return false;
4296
4297 if ( simpleLine->dashPatternOffset() )
4298 return false;
4299
4300 if ( simpleLine->alignDashPattern() )
4301 return false;
4302
4303 if ( simpleLine->tweakDashPatternOnCorners() )
4304 return false;
4305
4306 if ( simpleLine->trimDistanceStart() || simpleLine->trimDistanceEnd() )
4307 return false;
4308
4309 if ( simpleLine->drawInsidePolygon() )
4310 return false;
4311
4312 if ( simpleLine->ringFilter() != QgsSimpleLineSymbolLayer::AllRings )
4313 return false;
4314
4315 if ( simpleLine->offset() )
4316 return false;
4317
4318 if ( simpleLine->hasDataDefinedProperties() )
4319 return false;
4320
4321 // looks good!
4322 simpleFill->setStrokeColor( simpleLine->color() );
4323 simpleFill->setStrokeWidth( simpleLine->width() );
4324 simpleFill->setStrokeWidthUnit( simpleLine->widthUnit() );
4325 simpleFill->setStrokeWidthMapUnitScale( simpleLine->widthMapUnitScale() );
4326 simpleFill->setStrokeStyle( simpleLine->penStyle() );
4327 simpleFill->setPenJoinStyle( simpleLine->penJoinStyle() );
4328 return true;
4329}
4330
4331void QgsSymbolLayerUtils::sortVariantList( QList<QVariant> &list, Qt::SortOrder order )
4332{
4333 if ( order == Qt::AscendingOrder )
4334 {
4335 //std::sort( list.begin(), list.end(), _QVariantLessThan );
4336 std::sort( list.begin(), list.end(), qgsVariantLessThan );
4337 }
4338 else // Qt::DescendingOrder
4339 {
4340 //std::sort( list.begin(), list.end(), _QVariantGreaterThan );
4341 std::sort( list.begin(), list.end(), qgsVariantGreaterThan );
4342 }
4343}
4344
4345QPointF QgsSymbolLayerUtils::pointOnLineWithDistance( QPointF startPoint, QPointF directionPoint, double distance )
4346{
4347 const double dx = directionPoint.x() - startPoint.x();
4348 const double dy = directionPoint.y() - startPoint.y();
4349 const double length = std::sqrt( dx * dx + dy * dy );
4350 const double scaleFactor = distance / length;
4351 return QPointF( startPoint.x() + dx * scaleFactor, startPoint.y() + dy * scaleFactor );
4352}
4353
4354
4356{
4357 // copied from QgsMarkerCatalogue - TODO: unify //#spellok
4358 QStringList list;
4359 QStringList svgPaths = QgsApplication::svgPaths();
4360
4361 for ( int i = 0; i < svgPaths.size(); i++ )
4362 {
4363 const QDir dir( svgPaths[i] );
4364 const auto svgSubPaths = dir.entryList( QDir::Dirs | QDir::NoDotAndDotDot );
4365 for ( const QString &item : svgSubPaths )
4366 {
4367 svgPaths.insert( i + 1, dir.path() + '/' + item );
4368 }
4369
4370 const auto svgFiles = dir.entryList( QStringList( "*.svg" ), QDir::Files );
4371 for ( const QString &item : svgFiles )
4372 {
4373 // TODO test if it is correct SVG
4374 list.append( dir.path() + '/' + item );
4375 }
4376 }
4377 return list;
4378}
4379
4380// Stripped down version of listSvgFiles() for specified directory
4381QStringList QgsSymbolLayerUtils::listSvgFilesAt( const QString &directory )
4382{
4383 // TODO anything that applies for the listSvgFiles() applies this also
4384
4385 QStringList list;
4386 QStringList svgPaths;
4387 svgPaths.append( directory );
4388
4389 for ( int i = 0; i < svgPaths.size(); i++ )
4390 {
4391 const QDir dir( svgPaths[i] );
4392 const auto svgSubPaths = dir.entryList( QDir::Dirs | QDir::NoDotAndDotDot );
4393 for ( const QString &item : svgSubPaths )
4394 {
4395 svgPaths.insert( i + 1, dir.path() + '/' + item );
4396 }
4397
4398 const auto svgFiles = dir.entryList( QStringList( "*.svg" ), QDir::Files );
4399 for ( const QString &item : svgFiles )
4400 {
4401 list.append( dir.path() + '/' + item );
4402 }
4403 }
4404 return list;
4405
4406}
4407
4408QString QgsSymbolLayerUtils::svgSymbolNameToPath( const QString &n, const QgsPathResolver &pathResolver )
4409{
4410 if ( n.isEmpty() )
4411 return QString();
4412
4413 if ( n.startsWith( QLatin1String( "base64:" ) ) )
4414 return n;
4415
4416 // we might have a full path...
4417 if ( QFileInfo::exists( n ) )
4418 return QFileInfo( n ).canonicalFilePath();
4419
4420 QString name = n;
4421 // or it might be an url...
4422 if ( name.contains( QLatin1String( "://" ) ) )
4423 {
4424 const QUrl url( name );
4425 if ( url.isValid() && !url.scheme().isEmpty() )
4426 {
4427 if ( url.scheme().compare( QLatin1String( "file" ), Qt::CaseInsensitive ) == 0 )
4428 {
4429 // it's a url to a local file
4430 name = url.toLocalFile();
4431 if ( QFile( name ).exists() )
4432 {
4433 return QFileInfo( name ).canonicalFilePath();
4434 }
4435 }
4436 else
4437 {
4438 // it's a url pointing to a online resource
4439 return name;
4440 }
4441 }
4442 }
4443
4444 // SVG symbol not found - probably a relative path was used
4445
4446 QStringList svgPaths = QgsApplication::svgPaths();
4447 for ( int i = 0; i < svgPaths.size(); i++ )
4448 {
4449 QString svgPath = svgPaths[i];
4450 if ( svgPath.endsWith( QChar( '/' ) ) )
4451 {
4452 svgPath.chop( 1 );
4453 }
4454
4455 QgsDebugMsgLevel( "SvgPath: " + svgPath, 3 );
4456 // Not sure why to lowest dir was used instead of full relative path, it was causing #8664
4457 //QFileInfo myInfo( name );
4458 //QString myFileName = myInfo.fileName(); // foo.svg
4459 //QString myLowestDir = myInfo.dir().dirName();
4460 //QString myLocalPath = svgPath + QString( myLowestDir.isEmpty() ? "" : '/' + myLowestDir ) + '/' + myFileName;
4461 const QString myLocalPath = svgPath + QDir::separator() + name;
4462
4463 QgsDebugMsgLevel( "Alternative svg path: " + myLocalPath, 3 );
4464 if ( QFile( myLocalPath ).exists() )
4465 {
4466 QgsDebugMsgLevel( QStringLiteral( "Svg found in alternative path" ), 3 );
4467 return QFileInfo( myLocalPath ).canonicalFilePath();
4468 }
4469 }
4470
4471 return pathResolver.readPath( name );
4472}
4473
4474QString QgsSymbolLayerUtils::svgSymbolPathToName( const QString &p, const QgsPathResolver &pathResolver )
4475{
4476 if ( p.isEmpty() )
4477 return QString();
4478
4479 if ( p.startsWith( QLatin1String( "base64:" ) ) )
4480 return p;
4481
4482 if ( !QFileInfo::exists( p ) )
4483 return p;
4484
4485 QString path = QFileInfo( p ).canonicalFilePath();
4486
4487 QStringList svgPaths = QgsApplication::svgPaths();
4488
4489 bool isInSvgPaths = false;
4490 for ( int i = 0; i < svgPaths.size(); i++ )
4491 {
4492 const QString dir = QFileInfo( svgPaths[i] ).canonicalFilePath();
4493
4494 if ( !dir.isEmpty() && path.startsWith( dir ) )
4495 {
4496 path = path.mid( dir.size() + 1 );
4497 isInSvgPaths = true;
4498 break;
4499 }
4500 }
4501
4502 if ( isInSvgPaths )
4503 return path;
4504
4505 return pathResolver.writePath( path );
4506}
4507
4508QPolygonF lineStringToQPolygonF( const QgsLineString *line )
4509{
4510 const double *srcX = line->xData();
4511 const double *srcY = line->yData();
4512 const int count = line->numPoints();
4513 QPolygonF thisRes( count );
4514 QPointF *dest = thisRes.data();
4515 for ( int i = 0; i < count; ++i )
4516 {
4517 *dest++ = QPointF( *srcX++, *srcY++ );
4518 }
4519 return thisRes;
4520}
4521
4522QPolygonF curveToPolygonF( const QgsCurve *curve )
4523{
4524 if ( const QgsLineString *line = qgsgeometry_cast< const QgsLineString * >( curve ) )
4525 {
4526 return lineStringToQPolygonF( line );
4527 }
4528 else
4529 {
4530 const std::unique_ptr< QgsLineString > straightened( curve->curveToLine() );
4531 return lineStringToQPolygonF( straightened.get() );
4532 }
4533}
4534
4535QList<QList<QPolygonF> > QgsSymbolLayerUtils::toQPolygonF( const QgsGeometry &geometry, Qgis::SymbolType type )
4536{
4537 return toQPolygonF( geometry.constGet(), type );
4538}
4539
4540QList<QList<QPolygonF> > QgsSymbolLayerUtils::toQPolygonF( const QgsAbstractGeometry *geometry, Qgis::SymbolType type )
4541{
4542 if ( !geometry )
4543 return {};
4544
4545 switch ( type )
4546 {
4548 {
4549 QPolygonF points;
4550
4552 {
4553 for ( auto it = geometry->vertices_begin(); it != geometry->vertices_end(); ++it )
4554 points << QPointF( ( *it ).x(), ( *it ).y() );
4555 }
4556 else
4557 {
4558 points << QPointF( 0, 0 );
4559 }
4560 return QList< QList<QPolygonF> >() << ( QList< QPolygonF >() << points );
4561 }
4562
4564 {
4565 QList< QList<QPolygonF> > res;
4567 {
4568 for ( auto it = geometry->const_parts_begin(); it != geometry->const_parts_end(); ++it )
4569 {
4570 res << ( QList< QPolygonF >() << curveToPolygonF( qgsgeometry_cast< const QgsCurve * >( *it ) ) );
4571 }
4572 }
4573 return res;
4574 }
4575
4577 {
4578 QList< QList<QPolygonF> > res;
4579
4580 for ( auto it = geometry->const_parts_begin(); it != geometry->const_parts_end(); ++it )
4581 {
4582 QList<QPolygonF> thisPart;
4583 const QgsCurvePolygon *surface = qgsgeometry_cast< const QgsCurvePolygon * >( *it );
4584 if ( !surface )
4585 continue;
4586
4587 if ( !surface->exteriorRing() )
4588 continue;
4589
4590 thisPart << curveToPolygonF( surface->exteriorRing() );
4591
4592 for ( int i = 0; i < surface->numInteriorRings(); ++i )
4593 thisPart << curveToPolygonF( surface->interiorRing( i ) );
4594 res << thisPart;
4595 }
4596
4597 return res;
4598 }
4599
4601 return QList< QList<QPolygonF> >();
4602 }
4603
4604 return QList< QList<QPolygonF> >();
4605}
4606
4607
4608QPointF QgsSymbolLayerUtils::polygonCentroid( const QPolygonF &points )
4609{
4610 //Calculate the centroid of points
4611 double cx = 0, cy = 0;
4612 double area, sum = 0;
4613 for ( int i = points.count() - 1, j = 0; j < points.count(); i = j++ )
4614 {
4615 const QPointF &p1 = points[i];
4616 const QPointF &p2 = points[j];
4617 area = p1.x() * p2.y() - p1.y() * p2.x();
4618 sum += area;
4619 cx += ( p1.x() + p2.x() ) * area;
4620 cy += ( p1.y() + p2.y() ) * area;
4621 }
4622 sum *= 3.0;
4623 if ( qgsDoubleNear( sum, 0.0 ) )
4624 {
4625 // the linear ring is invalid - let's fall back to a solution that will still
4626 // allow us render at least something (instead of just returning point nan,nan)
4627 if ( points.count() >= 2 )
4628 return QPointF( ( points[0].x() + points[1].x() ) / 2, ( points[0].y() + points[1].y() ) / 2 );
4629 else if ( points.count() == 1 )
4630 return points[0];
4631 else
4632 return QPointF(); // hopefully we shouldn't ever get here
4633 }
4634 cx /= sum;
4635 cy /= sum;
4636
4637 return QPointF( cx, cy );
4638}
4639
4640QPointF QgsSymbolLayerUtils::polygonPointOnSurface( const QPolygonF &points, const QVector<QPolygonF> *rings )
4641{
4642 QPointF centroid = QgsSymbolLayerUtils::polygonCentroid( points );
4643
4644 if ( ( rings && rings->count() > 0 ) || !pointInPolygon( points, centroid ) )
4645 {
4646 unsigned int i, pointCount = points.count();
4647 QgsPolylineXY polyline( pointCount );
4648 for ( i = 0; i < pointCount; ++i ) polyline[i] = QgsPointXY( points[i].x(), points[i].y() );
4649 QgsGeometry geom = QgsGeometry::fromPolygonXY( QgsPolygonXY() << polyline );
4650 if ( !geom.isNull() )
4651 {
4652 if ( rings )
4653 {
4654 for ( auto ringIt = rings->constBegin(); ringIt != rings->constEnd(); ++ringIt )
4655 {
4656 pointCount = ( *ringIt ).count();
4657 QgsPolylineXY polyline( pointCount );
4658 for ( i = 0; i < pointCount; ++i ) polyline[i] = QgsPointXY( ( *ringIt )[i].x(), ( *ringIt )[i].y() );
4659 geom.addRing( polyline );
4660 }
4661 }
4662
4663 const QgsGeometry pointOnSurfaceGeom = geom.pointOnSurface();
4664 if ( !pointOnSurfaceGeom.isNull() )
4665 {
4666 const QgsPointXY point = pointOnSurfaceGeom.asPoint();
4667 centroid.setX( point.x() );
4668 centroid.setY( point.y() );
4669 }
4670 }
4671 }
4672
4673 return QPointF( centroid.x(), centroid.y() );
4674}
4675
4676bool QgsSymbolLayerUtils::pointInPolygon( const QPolygonF &points, QPointF point )
4677{
4678 bool inside = false;
4679
4680 const double x = point.x();
4681 const double y = point.y();
4682
4683 for ( int i = 0, j = points.count() - 1; i < points.count(); i++ )
4684 {
4685 const QPointF &p1 = points[i];
4686 const QPointF &p2 = points[j];
4687
4688 if ( qgsDoubleNear( p1.x(), x ) && qgsDoubleNear( p1.y(), y ) )
4689 return true;
4690
4691 if ( ( p1.y() < y && p2.y() >= y ) || ( p2.y() < y && p1.y() >= y ) )
4692 {
4693 if ( p1.x() + ( y - p1.y() ) / ( p2.y() - p1.y() ) * ( p2.x() - p1.x() ) <= x )
4694 inside = !inside;
4695 }
4696
4697 j = i;
4698 }
4699 return inside;
4700}
4701
4702double QgsSymbolLayerUtils::polylineLength( const QPolygonF &polyline )
4703{
4704 if ( polyline.size() < 2 )
4705 return 0;
4706
4707 double totalLength = 0;
4708 auto it = polyline.begin();
4709 QPointF p1 = *it++;
4710 for ( ; it != polyline.end(); ++it )
4711 {
4712 const QPointF p2 = *it;
4713 const double segmentLength = std::sqrt( std::pow( p1.x() - p2.x(), 2.0 ) + std::pow( p1.y() - p2.y(), 2.0 ) );
4714 totalLength += segmentLength;
4715 p1 = p2;
4716 }
4717 return totalLength;
4718}
4719
4720QPolygonF QgsSymbolLayerUtils::polylineSubstring( const QPolygonF &polyline, double startOffset, double endOffset )
4721{
4722 if ( polyline.size() < 2 )
4723 return QPolygonF();
4724
4725 double totalLength = 0;
4726 auto it = polyline.begin();
4727 QPointF p1 = *it++;
4728 std::vector< double > segmentLengths( polyline.size() - 1 );
4729 auto segmentLengthIt = segmentLengths.begin();
4730 for ( ; it != polyline.end(); ++it )
4731 {
4732 const QPointF p2 = *it;
4733 *segmentLengthIt = std::sqrt( std::pow( p1.x() - p2.x(), 2.0 ) + std::pow( p1.y() - p2.y(), 2.0 ) );
4734 totalLength += *segmentLengthIt;
4735
4736 segmentLengthIt++;
4737 p1 = p2;
4738 }
4739
4740 if ( startOffset >= 0 && totalLength <= startOffset )
4741 return QPolygonF();
4742 if ( endOffset < 0 && totalLength <= -endOffset )
4743 return QPolygonF();
4744
4745 const double startDistance = startOffset < 0 ? totalLength + startOffset : startOffset;
4746 const double endDistance = endOffset <= 0 ? totalLength + endOffset : endOffset;
4747 QPolygonF substringPoints;
4748 substringPoints.reserve( polyline.size() );
4749
4750 it = polyline.begin();
4751 segmentLengthIt = segmentLengths.begin();
4752
4753 p1 = *it++;
4754 bool foundStart = false;
4755 if ( qgsDoubleNear( startDistance, 0.0 ) || startDistance < 0 )
4756 {
4757 substringPoints << p1;
4758 foundStart = true;
4759 }
4760
4761 double distanceTraversed = 0;
4762 for ( ; it != polyline.end(); ++it )
4763 {
4764 const QPointF p2 = *it;
4765 if ( distanceTraversed < startDistance && distanceTraversed + *segmentLengthIt > startDistance )
4766 {
4767 // start point falls on this segment
4768 const double distanceToStart = startDistance - distanceTraversed;
4769 double startX, startY;
4770 QgsGeometryUtilsBase::pointOnLineWithDistance( p1.x(), p1.y(), p2.x(), p2.y(), distanceToStart, startX, startY );
4771 substringPoints << QPointF( startX, startY );
4772 foundStart = true;
4773 }
4774 if ( foundStart && ( distanceTraversed + *segmentLengthIt > endDistance ) )
4775 {
4776 // end point falls on this segment
4777 const double distanceToEnd = endDistance - distanceTraversed;
4778 double endX, endY;
4779 QgsGeometryUtilsBase::pointOnLineWithDistance( p1.x(), p1.y(), p2.x(), p2.y(), distanceToEnd, endX, endY );
4780 if ( substringPoints.last() != QPointF( endX, endY ) )
4781 substringPoints << QPointF( endX, endY );
4782 }
4783 else if ( foundStart )
4784 {
4785 if ( substringPoints.last() != QPointF( p2.x(), p2.y() ) )
4786 substringPoints << QPointF( p2.x(), p2.y() );
4787 }
4788
4789 distanceTraversed += *segmentLengthIt;
4790 if ( distanceTraversed > endDistance )
4791 break;
4792
4793 p1 = p2;
4794 segmentLengthIt++;
4795 }
4796
4797 if ( ( substringPoints.size() < 2 ) || ( substringPoints.size() == 2 && substringPoints.at( 0 ) == substringPoints.at( 1 ) ) )
4798 return QPolygonF();
4799
4800 return substringPoints;
4801}
4802
4803bool QgsSymbolLayerUtils::isSharpCorner( QPointF p1, QPointF p2, QPointF p3 )
4804{
4805 double vertexAngle = M_PI - ( std::atan2( p3.y() - p2.y(), p3.x() - p2.x() ) - std::atan2( p2.y() - p1.y(), p2.x() - p1.x() ) );
4806 vertexAngle = QgsGeometryUtilsBase::normalizedAngle( vertexAngle );
4807
4808 // extreme angles form more than 45 degree angle at a node
4809 return vertexAngle < M_PI * 135.0 / 180.0 || vertexAngle > M_PI * 225.0 / 180.0;
4810}
4811
4812void QgsSymbolLayerUtils::appendPolyline( QPolygonF &target, const QPolygonF &line )
4813{
4814 target.reserve( target.size() + line.size() );
4815 for ( const QPointF &pt : line )
4816 {
4817 if ( !target.empty() && target.last() == pt )
4818 continue;
4819
4820 target << pt;
4821 }
4822}
4823
4825{
4826 if ( fieldOrExpression.isEmpty() )
4827 return nullptr;
4828
4829 QgsExpression *expr = new QgsExpression( fieldOrExpression );
4830 if ( !expr->hasParserError() )
4831 return expr;
4832
4833 // now try with quoted field name
4834 delete expr;
4835 QgsExpression *expr2 = new QgsExpression( QgsExpression::quotedColumnRef( fieldOrExpression ) );
4836 Q_ASSERT( !expr2->hasParserError() );
4837 return expr2;
4838}
4839
4841{
4842 const QgsExpressionNode *n = expression->rootNode();
4843
4844 if ( n && n->nodeType() == QgsExpressionNode::ntColumnRef )
4845 return static_cast<const QgsExpressionNodeColumnRef *>( n )->name();
4846
4847 return expression->expression();
4848}
4849
4850QList<double> QgsSymbolLayerUtils::prettyBreaks( double minimum, double maximum, int classes )
4851{
4852 // C++ implementation of R's pretty algorithm
4853 // Based on code for determining optimal tick placement for statistical graphics
4854 // from the R statistical programming language.
4855 // Code ported from R implementation from 'labeling' R package
4856 //
4857 // Computes a sequence of about 'classes' equally spaced round values
4858 // which cover the range of values from 'minimum' to 'maximum'.
4859 // The values are chosen so that they are 1, 2 or 5 times a power of 10.
4860
4861 QList<double> breaks;
4862 if ( classes < 1 )
4863 {
4864 breaks.append( maximum );
4865 return breaks;
4866 }
4867
4868 const int minimumCount = static_cast< int >( classes ) / 3;
4869 const double shrink = 0.75;
4870 const double highBias = 1.5;
4871 const double adjustBias = 0.5 + 1.5 * highBias;
4872 const int divisions = classes;
4873 const double h = highBias;
4874 double cell;
4875 bool small = false;
4876 const double dx = maximum - minimum;
4877
4878 if ( qgsDoubleNear( dx, 0.0 ) && qgsDoubleNear( maximum, 0.0 ) )
4879 {
4880 cell = 1.0;
4881 small = true;
4882 }
4883 else
4884 {
4885 int U = 1;
4886 cell = std::max( std::fabs( minimum ), std::fabs( maximum ) );
4887 if ( adjustBias >= 1.5 * h + 0.5 )
4888 {
4889 U = 1 + ( 1.0 / ( 1 + h ) );
4890 }
4891 else
4892 {
4893 U = 1 + ( 1.5 / ( 1 + adjustBias ) );
4894 }
4895 small = dx < ( cell * U * std::max( 1, divisions ) * 1e-07 * 3.0 );
4896 }
4897
4898 if ( small )
4899 {
4900 if ( cell > 10 )
4901 {
4902 cell = 9 + cell / 10;
4903 cell = cell * shrink;
4904 }
4905 if ( minimumCount > 1 )
4906 {
4907 cell = cell / minimumCount;
4908 }
4909 }
4910 else
4911 {
4912 cell = dx;
4913 if ( divisions > 1 )
4914 {
4915 cell = cell / divisions;
4916 }
4917 }
4918 if ( cell < 20 * 1e-07 )
4919 {
4920 cell = 20 * 1e-07;
4921 }
4922
4923 const double base = std::pow( 10.0, std::floor( std::log10( cell ) ) );
4924 double unit = base;
4925 if ( ( 2 * base ) - cell < h * ( cell - unit ) )
4926 {
4927 unit = 2.0 * base;
4928 if ( ( 5 * base ) - cell < adjustBias * ( cell - unit ) )
4929 {
4930 unit = 5.0 * base;
4931 if ( ( 10.0 * base ) - cell < h * ( cell - unit ) )
4932 {
4933 unit = 10.0 * base;
4934 }
4935 }
4936 }
4937 // Maybe used to correct for the epsilon here??
4938 int start = std::floor( minimum / unit + 1e-07 );
4939 int end = std::ceil( maximum / unit - 1e-07 );
4940
4941 // Extend the range out beyond the data. Does this ever happen??
4942 while ( start * unit > minimum + ( 1e-07 * unit ) )
4943 {
4944 start = start - 1;
4945 }
4946 while ( end * unit < maximum - ( 1e-07 * unit ) )
4947 {
4948 end = end + 1;
4949 }
4950 QgsDebugMsgLevel( QStringLiteral( "pretty classes: %1" ).arg( end ), 3 );
4951
4952 // If we don't have quite enough labels, extend the range out
4953 // to make more (these labels are beyond the data :()
4954 int k = std::floor( 0.5 + end - start );
4955 if ( k < minimumCount )
4956 {
4957 k = minimumCount - k;
4958 if ( start >= 0 )
4959 {
4960 end = end + k / 2;
4961 start = start - k / 2 + k % 2;
4962 }
4963 else
4964 {
4965 start = start - k / 2;
4966 end = end + k / 2 + k % 2;
4967 }
4968 }
4969 const double minimumBreak = start * unit;
4970 //double maximumBreak = end * unit;
4971 const int count = end - start;
4972
4973 breaks.reserve( count );
4974 for ( int i = 1; i < count + 1; i++ )
4975 {
4976 breaks.append( minimumBreak + i * unit );
4977 }
4978
4979 if ( breaks.isEmpty() )
4980 return breaks;
4981
4982 if ( breaks.first() < minimum )
4983 {
4984 breaks[0] = minimum;
4985 }
4986 if ( breaks.last() > maximum )
4987 {
4988 breaks[breaks.count() - 1] = maximum;
4989 }
4990
4991 // because sometimes when number of classes is big,
4992 // break supposed to be at zero is something like -2.22045e-16
4993 if ( minimum < 0.0 && maximum > 0.0 ) //then there should be a zero somewhere
4994 {
4995 QList<double> breaksMinusZero; // compute difference "each break - 0"
4996 for ( int i = 0; i < breaks.count(); i++ )
4997 {
4998 breaksMinusZero.append( breaks[i] - 0.0 );
4999 }
5000 int posOfMin = 0;
5001 for ( int i = 1; i < breaks.count(); i++ ) // find position of minimal difference
5002 {
5003 if ( std::abs( breaksMinusZero[i] ) < std::abs( breaksMinusZero[i - 1] ) )
5004 posOfMin = i;
5005 }
5006 breaks[posOfMin] = 0.0;
5007 }
5008
5009 return breaks;
5010}
5011
5012double QgsSymbolLayerUtils::rescaleUom( double size, Qgis::RenderUnit unit, const QVariantMap &props )
5013{
5014 double scale = 1;
5015 bool roundToUnit = false;
5016 if ( unit == Qgis::RenderUnit::Unknown )
5017 {
5018 if ( props.contains( QStringLiteral( "uomScale" ) ) )
5019 {
5020 bool ok;
5021 scale = props.value( QStringLiteral( "uomScale" ) ).toDouble( &ok );
5022 if ( !ok )
5023 {
5024 return size;
5025 }
5026 }
5027 }
5028 else
5029 {
5030 if ( props.value( QStringLiteral( "uom" ) ) == QLatin1String( "http://www.opengeospatial.org/se/units/metre" ) )
5031 {
5032 switch ( unit )
5033 {
5035 scale = 0.001;
5036 break;
5038 scale = 0.00028;
5039 roundToUnit = true;
5040 break;
5041 default:
5042 scale = 1;
5043 }
5044 }
5045 else
5046 {
5047 // target is pixels
5048 switch ( unit )
5049 {
5051 scale = 1 / 0.28;
5052 roundToUnit = true;
5053 break;
5055 scale = 1 / 0.28 * 25.4;
5056 roundToUnit = true;
5057 break;
5059 scale = 90. /* dots per inch according to OGC SLD */ / 72. /* points per inch */;
5060 roundToUnit = true;
5061 break;
5063 // pixel is pixel
5064 scale = 1;
5065 break;
5068 // already handed via uom
5069 scale = 1;
5070 break;
5073 // these do not make sense and should not really reach here
5074 scale = 1;
5075 }
5076 }
5077
5078 }
5079 double rescaled = size * scale;
5080 // round to unit if the result is pixels to avoid a weird looking SLD (people often think
5081 // of pixels as integers, even if SLD allows for float values in there
5082 if ( roundToUnit )
5083 {
5084 rescaled = std::round( rescaled );
5085 }
5086 return rescaled;
5087}
5088
5089QPointF QgsSymbolLayerUtils::rescaleUom( QPointF point, Qgis::RenderUnit unit, const QVariantMap &props )
5090{
5091 const double x = rescaleUom( point.x(), unit, props );
5092 const double y = rescaleUom( point.y(), unit, props );
5093 return QPointF( x, y );
5094}
5095
5096QVector<qreal> QgsSymbolLayerUtils::rescaleUom( const QVector<qreal> &array, Qgis::RenderUnit unit, const QVariantMap &props )
5097{
5098 QVector<qreal> result;
5099 QVector<qreal>::const_iterator it = array.constBegin();
5100 for ( ; it != array.constEnd(); ++it )
5101 {
5102 result.append( rescaleUom( *it, unit, props ) );
5103 }
5104 return result;
5105}
5106
5107void QgsSymbolLayerUtils::applyScaleDependency( QDomDocument &doc, QDomElement &ruleElem, QVariantMap &props )
5108{
5109 if ( !props.value( QStringLiteral( "scaleMinDenom" ), QString() ).toString().isEmpty() )
5110 {
5111 QDomElement scaleMinDenomElem = doc.createElement( QStringLiteral( "se:MinScaleDenominator" ) );
5112 scaleMinDenomElem.appendChild( doc.createTextNode( qgsDoubleToString( props.value( QStringLiteral( "scaleMinDenom" ) ).toString().toDouble() ) ) );
5113 ruleElem.appendChild( scaleMinDenomElem );
5114 }
5115
5116 if ( !props.value( QStringLiteral( "scaleMaxDenom" ), QString() ).toString().isEmpty() )
5117 {
5118 QDomElement scaleMaxDenomElem = doc.createElement( QStringLiteral( "se:MaxScaleDenominator" ) );
5119 scaleMaxDenomElem.appendChild( doc.createTextNode( qgsDoubleToString( props.value( QStringLiteral( "scaleMaxDenom" ) ).toString().toDouble() ) ) );
5120 ruleElem.appendChild( scaleMaxDenomElem );
5121 }
5122}
5123
5124void QgsSymbolLayerUtils::mergeScaleDependencies( double mScaleMinDenom, double mScaleMaxDenom, QVariantMap &props )
5125{
5126 if ( !qgsDoubleNear( mScaleMinDenom, 0 ) )
5127 {
5128 bool ok;
5129 const double parentScaleMinDenom = props.value( QStringLiteral( "scaleMinDenom" ), QStringLiteral( "0" ) ).toString().toDouble( &ok );
5130 if ( !ok || parentScaleMinDenom <= 0 )
5131 props[ QStringLiteral( "scaleMinDenom" )] = QString::number( mScaleMinDenom );
5132 else
5133 props[ QStringLiteral( "scaleMinDenom" )] = QString::number( std::max( parentScaleMinDenom, mScaleMinDenom ) );
5134 }
5135
5136 if ( !qgsDoubleNear( mScaleMaxDenom, 0 ) )
5137 {
5138 bool ok;
5139 const double parentScaleMaxDenom = props.value( QStringLiteral( "scaleMaxDenom" ), QStringLiteral( "0" ) ).toString().toDouble( &ok );
5140 if ( !ok || parentScaleMaxDenom <= 0 )
5141 props[ QStringLiteral( "scaleMaxDenom" )] = QString::number( mScaleMaxDenom );
5142 else
5143 props[ QStringLiteral( "scaleMaxDenom" )] = QString::number( std::min( parentScaleMaxDenom, mScaleMaxDenom ) );
5144 }
5145}
5146
5147double QgsSymbolLayerUtils::sizeInPixelsFromSldUom( const QString &uom, double size )
5148{
5149 double scale = 1.0;
5150
5151 if ( uom == QLatin1String( "http://www.opengeospatial.org/se/units/metre" ) )
5152 {
5153 scale = 1.0 / 0.00028; // from meters to pixels
5154 }
5155 else if ( uom == QLatin1String( "http://www.opengeospatial.org/se/units/foot" ) )
5156 {
5157 scale = 304.8 / 0.28; // from feet to pixels
5158 }
5159 else
5160 {
5161 scale = 1.0; // from pixels to pixels (default unit)
5162 }
5163
5164 return size * scale;
5165}
5166
5167QSet<const QgsSymbolLayer *> QgsSymbolLayerUtils::toSymbolLayerPointers( const QgsFeatureRenderer *renderer, const QSet<QgsSymbolLayerId> &symbolLayerIds )
5168{
5170 class SymbolLayerVisitor : public QgsStyleEntityVisitorInterface
5171 {
5172 public:
5173 SymbolLayerVisitor( const QSet<QgsSymbolLayerId> &layerIds )
5174 : mSymbolLayerIds( layerIds )
5175 {}
5176
5177 bool visitEnter( const QgsStyleEntityVisitorInterface::Node &node ) override
5178 {
5180 {
5181 mCurrentRuleKey = node.identifier;
5182 return true;
5183 }
5184 return false;
5185 }
5186
5187 void visitSymbol( const QgsSymbol *symbol, const QString &identifier, QVector<int> rootPath )
5188 {
5189 for ( int idx = 0; idx < symbol->symbolLayerCount(); idx++ )
5190 {
5191 QVector<int> indexPath = rootPath;
5192 indexPath.append( idx );
5193 const QgsSymbolLayer *sl = symbol->symbolLayer( idx );
5195 if ( mSymbolLayerIds.contains( QgsSymbolLayerId( mCurrentRuleKey + identifier, indexPath ) ) )
5196 {
5197 mSymbolLayers.insert( sl );
5198 }
5200
5201 const QgsSymbol *subSymbol = const_cast<QgsSymbolLayer *>( sl )->subSymbol();
5202 if ( subSymbol )
5203 visitSymbol( subSymbol, identifier, indexPath );
5204 }
5205 }
5206
5207 bool visit( const QgsStyleEntityVisitorInterface::StyleLeaf &leaf ) override
5208 {
5209 if ( leaf.entity && leaf.entity->type() == QgsStyle::SymbolEntity )
5210 {
5211 auto symbolEntity = static_cast<const QgsStyleSymbolEntity *>( leaf.entity );
5212 if ( symbolEntity->symbol() )
5213 {
5214 visitSymbol( symbolEntity->symbol(), leaf.identifier, {} );
5215 }
5216 }
5217 return true;
5218 }
5219
5220 QString mCurrentRuleKey;
5221 const QSet<QgsSymbolLayerId> &mSymbolLayerIds;
5222 QSet<const QgsSymbolLayer *> mSymbolLayers;
5223 };
5225
5226 SymbolLayerVisitor visitor( symbolLayerIds );
5227 renderer->accept( &visitor );
5228 return visitor.mSymbolLayers;
5229}
5230
5232{
5233 class SymbolRefreshRateVisitor : public QgsStyleEntityVisitorInterface
5234 {
5235 public:
5236 SymbolRefreshRateVisitor()
5237 {}
5238
5239 bool visitEnter( const QgsStyleEntityVisitorInterface::Node &node ) override
5240 {
5242 {
5243 return true;
5244 }
5245 return false;
5246 }
5247
5248 void visitSymbol( const QgsSymbol *symbol )
5249 {
5250 // symbol may be marked as animated on a symbol level (e.g. when it implements animation
5251 // via data defined properties)
5252 if ( symbol->animationSettings().isAnimated() )
5253 {
5254 if ( symbol->animationSettings().frameRate() > refreshRate )
5255 refreshRate = symbol->animationSettings().frameRate();
5256 }
5257 for ( int idx = 0; idx < symbol->symbolLayerCount(); idx++ )
5258 {
5259 const QgsSymbolLayer *sl = symbol->symbolLayer( idx );
5260 if ( const QgsAnimatedMarkerSymbolLayer *animatedMarker = dynamic_cast< const QgsAnimatedMarkerSymbolLayer *>( sl ) )
5261 {
5262 // this is a bit of a short cut -- if a symbol has multiple layers with different frame rates,
5263 // there's no guarantee that they will be even multiples of each other! But given we are looking for
5264 // a single frame rate for a whole renderer, it's an acceptable compromise...
5265 if ( refreshRate == -1 || ( animatedMarker->frameRate() > refreshRate ) )
5266 refreshRate = animatedMarker->frameRate();
5267 }
5268
5269 if ( const QgsSymbol *subSymbol = const_cast<QgsSymbolLayer *>( sl )->subSymbol() )
5270 visitSymbol( subSymbol );
5271 }
5272 }
5273
5274 bool visit( const QgsStyleEntityVisitorInterface::StyleLeaf &leaf ) override
5275 {
5276 if ( leaf.entity && leaf.entity->type() == QgsStyle::SymbolEntity )
5277 {
5278 if ( QgsSymbol *symbol = qgis::down_cast<const QgsStyleSymbolEntity *>( leaf.entity )->symbol() )
5279 {
5280 visitSymbol( symbol );
5281 }
5282 }
5283 return true;
5284 }
5285
5286 double refreshRate = -1;
5287 };
5288
5289 SymbolRefreshRateVisitor visitor;
5290 renderer->accept( &visitor );
5291 return visitor.refreshRate;
5292}
5293
5294QgsSymbol *QgsSymbolLayerUtils::restrictedSizeSymbol( const QgsSymbol *s, double minSize, double maxSize, QgsRenderContext *context, double &width, double &height, bool *ok )
5295{
5296 if ( !s || !context )
5297 {
5298 return nullptr;
5299 }
5300
5301 if ( ok )
5302 *ok = true;
5303
5304 const QgsSymbolLayerList sls = s->symbolLayers();
5305 for ( const QgsSymbolLayer *sl : std::as_const( sls ) )
5306 {
5307 // geometry generators involved, there is no way to get a restricted size symbol
5308 if ( sl->type() == Qgis::SymbolType::Hybrid )
5309 {
5310 if ( ok )
5311 *ok = false;
5312
5313 return nullptr;
5314 }
5315 }
5316
5317 double size;
5318 const QgsMarkerSymbol *markerSymbol = dynamic_cast<const QgsMarkerSymbol *>( s );
5319 const QgsLineSymbol *lineSymbol = dynamic_cast<const QgsLineSymbol *>( s );
5320 if ( markerSymbol )
5321 {
5322 size = markerSymbol->size( *context );
5323 }
5324 else if ( lineSymbol )
5325 {
5326 size = lineSymbol->width( *context );
5327 }
5328 else
5329 {
5330 // cannot return a size restricted symbol but we assume there is no need
5331 // for one as the rendering will be done in the given size (different from geometry
5332 // generator where rendering will bleed outside the given area
5333 return nullptr;
5334 }
5335
5336 size /= context->scaleFactor();
5337
5338 if ( minSize > 0 && size < minSize )
5339 {
5340 size = minSize;
5341 }
5342 else if ( maxSize > 0 && size > maxSize )
5343 {
5344 size = maxSize;
5345 }
5346 else
5347 {
5348 // no need to restricted size symbol
5349 return nullptr;
5350 }
5351
5352 if ( markerSymbol )
5353 {
5354 QgsMarkerSymbol *ms = dynamic_cast<QgsMarkerSymbol *>( s->clone() );
5355 ms->setSize( size );
5357 width = size;
5358 height = size;
5359 return ms;
5360 }
5361 else if ( lineSymbol )
5362 {
5363 QgsLineSymbol *ls = dynamic_cast<QgsLineSymbol *>( s->clone() );
5364 ls->setWidth( size );
5366 height = size;
5367 return ls;
5368 }
5369
5370 return nullptr;
5371}
5372
5373QgsStringMap QgsSymbolLayerUtils::evaluatePropertiesMap( const QMap<QString, QgsProperty> &propertiesMap, const QgsExpressionContext &context )
5374{
5375 QgsStringMap properties;
5376 QMap<QString, QgsProperty>::const_iterator paramIt = propertiesMap.constBegin();
5377 for ( ; paramIt != propertiesMap.constEnd(); ++paramIt )
5378 {
5379 properties.insert( paramIt.key(), paramIt.value().valueAsString( context ) );
5380 }
5381 return properties;
5382}
5383
5384QSize QgsSymbolLayerUtils::tileSize( int width, int height, double &angleRad )
5385{
5386
5387 angleRad = std::fmod( angleRad, M_PI * 2 );
5388
5389 if ( angleRad < 0 )
5390 {
5391 angleRad += M_PI * 2;
5392 }
5393
5394 // tan with rational sin/cos
5395 struct rationalTangent
5396 {
5397 int p; // numerator
5398 int q; // denominator
5399 double angle; // "good" angle
5400 };
5401
5402#if 0
5403
5404 // This list is more granular (approx 1 degree steps) but some
5405 // values can lead to huge tiles
5406 // List of "good" angles from 0 to PI/2
5407 static const QList<rationalTangent> __rationalTangents
5408 {
5409 { 1, 57, 0.01754206006 },
5410 { 3, 86, 0.03486958155 },
5411 { 1, 19, 0.05258306161 },
5412 { 3, 43, 0.06965457373 },
5413 { 7, 80, 0.08727771295 },
5414 { 2, 19, 0.1048769387 },
5415 { 7, 57, 0.1221951707 },
5416 { 9, 64, 0.1397088743 },
5417 { 13, 82, 0.157228051 },
5418 { 3, 17, 0.174672199 },
5419 { 7, 36, 0.1920480172 },
5420 { 17, 80, 0.209385393 },
5421 { 3, 13, 0.2267988481 },
5422 { 1, 4, 0.2449786631 },
5423 { 26, 97, 0.2618852647 },
5424 { 27, 94, 0.2797041525 },
5425 { 26, 85, 0.2968446734 },
5426 { 13, 40, 0.3142318991 },
5427 { 21, 61, 0.3315541619 },
5428 { 4, 11, 0.3487710036 },
5429 { 38, 99, 0.3664967859 },
5430 { 40, 99, 0.383984624 },
5431 { 31, 73, 0.4015805401 },
5432 { 41, 92, 0.4192323938 },
5433 { 7, 15, 0.4366271598 },
5434 { 20, 41, 0.4538440015 },
5435 { 27, 53, 0.4711662643 },
5436 { 42, 79, 0.4886424026 },
5437 { 51, 92, 0.5061751436 },
5438 { 56, 97, 0.5235757641 },
5439 { 3, 5, 0.5404195003 },
5440 { 5, 8, 0.5585993153 },
5441 { 50, 77, 0.5759185996 },
5442 { 29, 43, 0.5933501462 },
5443 { 7, 10, 0.6107259644 },
5444 { 69, 95, 0.6281701124 },
5445 { 52, 69, 0.6458159195 },
5446 { 25, 32, 0.6632029927 },
5447 { 17, 21, 0.6805212247 },
5448 { 73, 87, 0.6981204504 },
5449 { 73, 84, 0.7154487784 },
5450 { 9, 10, 0.7328151018 },
5451 { 83, 89, 0.7505285818 },
5452 { 28, 29, 0.7678561033 },
5453 { 1, 1, 0.7853981634 },
5454 { 29, 28, 0.8029402235 },
5455 { 89, 83, 0.820267745 },
5456 { 10, 9, 0.837981225 },
5457 { 107, 93, 0.855284165 },
5458 { 87, 73, 0.8726758763 },
5459 { 121, 98, 0.8900374031 },
5460 { 32, 25, 0.9075933341 },
5461 { 69, 52, 0.9249804073 },
5462 { 128, 93, 0.9424647244 },
5463 { 10, 7, 0.9600703624 },
5464 { 43, 29, 0.9774461806 },
5465 { 77, 50, 0.9948777272 },
5466 { 8, 5, 1.012197011 },
5467 { 163, 98, 1.029475114 },
5468 { 168, 97, 1.047174539 },
5469 { 175, 97, 1.064668696 },
5470 { 126, 67, 1.082075603 },
5471 { 157, 80, 1.099534652 },
5472 { 203, 99, 1.117049384 },
5473 { 193, 90, 1.134452855 },
5474 { 146, 65, 1.151936673 },
5475 { 139, 59, 1.169382787 },
5476 { 99, 40, 1.186811703 },
5477 { 211, 81, 1.204257817 },
5478 { 272, 99, 1.221730164 },
5479 { 273, 94, 1.239188479 },
5480 { 277, 90, 1.25664606 },
5481 { 157, 48, 1.274088705 },
5482 { 279, 80, 1.291550147 },
5483 { 362, 97, 1.308990773 },
5484 { 373, 93, 1.326448578 },
5485 { 420, 97, 1.343823596 },
5486 { 207, 44, 1.361353157 },
5487 { 427, 83, 1.378810994 },
5488 { 414, 73, 1.396261926 },
5489 { 322, 51, 1.413716057 },
5490 { 185, 26, 1.431170275 },
5491 { 790, 97, 1.448623034 },
5492 { 333, 35, 1.466075711 },
5493 { 1063, 93, 1.483530284 },
5494 { 1330, 93, 1.500985147 },
5495 { 706, 37, 1.518436297 },
5496 { 315, 11, 1.535889876 },
5497 { 3953, 69, 1.553343002 },
5498 };
5499#endif
5500
5501 // Optimized "good" angles list, it produces small tiles but
5502 // it has approximately 10 degrees steps
5503 static const QList<rationalTangent> rationalTangents
5504 {
5505 { 1, 10, qDegreesToRadians( 5.71059 ) },
5506 { 1, 5, qDegreesToRadians( 11.3099 ) },
5507 { 1, 4, qDegreesToRadians( 14.0362 ) },
5508 { 1, 4, qDegreesToRadians( 18.4349 ) },
5509 { 1, 2, qDegreesToRadians( 26.5651 ) },
5510 { 2, 3, qDegreesToRadians( 33.6901 ) },
5511 { 1, 1, qDegreesToRadians( 45.0 ) },
5512 { 3, 2, qDegreesToRadians( 56.3099 ) },
5513 { 2, 1, qDegreesToRadians( 63.4349 ) },
5514 { 3, 1, qDegreesToRadians( 71.5651 ) },
5515 { 4, 1, qDegreesToRadians( 75.9638 ) },
5516 { 10, 1, qDegreesToRadians( 84.2894 ) },
5517 };
5518
5519 const int quadrant { static_cast<int>( angleRad / M_PI_2 ) };
5520 Q_ASSERT( quadrant >= 0 && quadrant <= 3 );
5521
5522 QSize tileSize;
5523
5524 switch ( quadrant )
5525 {
5526 case 0:
5527 {
5528 break;
5529 }
5530 case 1:
5531 {
5532 angleRad -= M_PI / 2;
5533 break;
5534 }
5535 case 2:
5536 {
5537 angleRad -= M_PI;
5538 break;
5539 }
5540 case 3:
5541 {
5542 angleRad -= M_PI + M_PI_2;
5543 break;
5544 }
5545 }
5546
5547 if ( qgsDoubleNear( angleRad, 0, 10E-3 ) )
5548 {
5549 angleRad = 0;
5550 tileSize.setWidth( width );
5551 tileSize.setHeight( height );
5552 }
5553 else if ( qgsDoubleNear( angleRad, M_PI_2, 10E-3 ) )
5554 {
5555 angleRad = M_PI_2;
5556 tileSize.setWidth( height );
5557 tileSize.setHeight( width );
5558 }
5559 else
5560 {
5561
5562 int rTanIdx = 0;
5563
5564 for ( int idx = 0; idx < rationalTangents.count(); ++idx )
5565 {
5566 const auto item = rationalTangents.at( idx );
5567 if ( qgsDoubleNear( item.angle, angleRad, 10E-3 ) || item.angle > angleRad )
5568 {
5569 rTanIdx = idx;
5570 break;
5571 }
5572 }
5573
5574 const rationalTangent bTan { rationalTangents.at( rTanIdx ) };
5575 angleRad = bTan.angle;
5576 const double k { bTan.q *height *width / std::cos( angleRad ) };
5577 const int hcfH { std::gcd( bTan.p * height, bTan.q * width ) };
5578 const int hcfW { std::gcd( bTan.q * height, bTan.p * width ) };
5579 const int W1 { static_cast<int>( std::round( k / hcfW ) ) };
5580 const int H1 { static_cast<int>( std::round( k / hcfH ) ) };
5581 tileSize.setWidth( W1 );
5582 tileSize.setHeight( H1 );
5583 }
5584
5585 switch ( quadrant )
5586 {
5587 case 0:
5588 {
5589 break;
5590 }
5591 case 1:
5592 {
5593 angleRad += M_PI / 2;
5594 const int h { tileSize.height() };
5595 tileSize.setHeight( tileSize.width() );
5596 tileSize.setWidth( h );
5597 break;
5598 }
5599 case 2:
5600 {
5601 angleRad += M_PI;
5602 break;
5603 }
5604 case 3:
5605 {
5606 angleRad += M_PI + M_PI_2;
5607 const int h { tileSize.height() };
5608 tileSize.setHeight( tileSize.width() );
5609 tileSize.setWidth( h );
5610 break;
5611 }
5612 }
5613
5614 return tileSize;
5615}
5616
5617template <typename Functor>
5618void changeSymbolLayerIds( QgsSymbolLayer *sl, Functor &&generateId )
5619{
5620 sl->setId( generateId() );
5621
5622 // recurse over sub symbols
5623 QgsSymbol *subSymbol = sl->subSymbol();
5624 if ( subSymbol )
5625 changeSymbolLayerIds( subSymbol, generateId );
5626}
5627
5628template <typename Functor>
5629void changeSymbolLayerIds( QgsSymbol *symbol, Functor &&generateId )
5630{
5631 if ( !symbol )
5632 return;
5633
5634 for ( int idx = 0; idx < symbol->symbolLayerCount(); idx++ )
5635 changeSymbolLayerIds( symbol->symbolLayer( idx ), generateId );
5636}
5637
5639{
5640 changeSymbolLayerIds( symbol, []() { return QString(); } );
5641}
5642
5644{
5645 changeSymbolLayerIds( symbolLayer, []() { return QString(); } );
5646}
5647
5649{
5650 changeSymbolLayerIds( symbolLayer, []() { return QUuid::createUuid().toString(); } );
5651}
5652
5654{
5655 if ( !symbol )
5656 return;
5657
5658 for ( int idx = 0; idx < symbol->symbolLayerCount(); idx++ )
5659 {
5660 if ( QgsMaskMarkerSymbolLayer *maskSl = dynamic_cast<QgsMaskMarkerSymbolLayer *>( symbol->symbolLayer( idx ) ) )
5661 {
5662 maskSl->clearMasks();
5663
5664 // recurse over sub symbols
5665 if ( QgsSymbol *subSymbol = maskSl->subSymbol() )
5666 {
5667 clearSymbolLayerMasks( subSymbol );
5668 }
5669 }
5670 }
5671}
5672
5673QVector<QgsGeometry> QgsSymbolLayerUtils::collectSymbolLayerClipGeometries( const QgsRenderContext &context, const QString &symbolLayerId, const QRectF &bounds )
5674{
5675 QVector<QgsGeometry> clipGeometries = context.symbolLayerClipGeometries( symbolLayerId );
5676 if ( clipGeometries.empty() )
5677 return {};
5678
5679 if ( bounds.isNull() )
5680 return clipGeometries;
5681
5682 const QgsRectangle boundsRect = QgsRectangle( bounds );
5683
5684 clipGeometries.erase(
5685 std::remove_if( clipGeometries.begin(), clipGeometries.end(), [&boundsRect]( const QgsGeometry & geometry )
5686 {
5687 return !geometry.boundingBoxIntersects( boundsRect );
5688 } ), clipGeometries.end() );
5689
5690 return clipGeometries;
5691}
5692
5694{
5695 changeSymbolLayerIds( symbol, []() { return QUuid::createUuid().toString(); } );
5696}
MarkerClipMode
Marker clipping modes.
Definition qgis.h:3109
@ 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:3123
@ 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:844
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:2051
@ Bevel
Use beveled joins.
@ Round
Use rounded joins.
@ Miter
Use mitered joins.
RenderUnit
Rendering size units.
Definition qgis.h:4930
@ 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:2038
@ 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:1763
@ 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:787
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:3067
@ 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:343
void setX(double x)
Sets the point's x-coordinate.
Definition qgspoint.h:332
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:6702
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
Definition qgis.h:6042
QString qgsFlagValueToKeys(const T &value, bool *returnOk=nullptr)
Returns the value for the given keys of a flag.
Definition qgis.h:6374
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:6396
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:6701
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition qgis.h:6125
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.