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