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