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