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