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