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