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