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