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