Branch data Line data Source code
1 : : /***************************************************************************
2 : : qgsrulebasedrenderer.cpp - Rule-based renderer (symbology)
3 : : ---------------------
4 : : begin : May 2010
5 : : copyright : (C) 2010 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 "qgsrulebasedrenderer.h"
17 : : #include "qgssymbollayer.h"
18 : : #include "qgsexpression.h"
19 : : #include "qgssymbollayerutils.h"
20 : : #include "qgsrendercontext.h"
21 : : #include "qgsvectorlayer.h"
22 : : #include "qgslogger.h"
23 : : #include "qgsogcutils.h"
24 : : #include "qgssinglesymbolrenderer.h"
25 : : #include "qgspointdisplacementrenderer.h"
26 : : #include "qgsinvertedpolygonrenderer.h"
27 : : #include "qgspainteffect.h"
28 : : #include "qgspainteffectregistry.h"
29 : : #include "qgsproperty.h"
30 : : #include "qgsstyleentityvisitor.h"
31 : : #include "qgsembeddedsymbolrenderer.h"
32 : : #include <QSet>
33 : :
34 : : #include <QDomDocument>
35 : : #include <QDomElement>
36 : : #include <QUuid>
37 : :
38 : :
39 : 0 : QgsRuleBasedRenderer::Rule::Rule( QgsSymbol *symbol, int scaleMinDenom, int scaleMaxDenom, const QString &filterExp, const QString &label, const QString &description, bool elseRule )
40 : 0 : : mParent( nullptr )
41 : 0 : , mSymbol( symbol )
42 : 0 : , mMaximumScale( scaleMinDenom )
43 : 0 : , mMinimumScale( scaleMaxDenom )
44 : 0 : , mFilterExp( filterExp )
45 : 0 : , mLabel( label )
46 : 0 : , mDescription( description )
47 : 0 : , mElseRule( elseRule )
48 : : {
49 : 0 : if ( mElseRule )
50 : 0 : mFilterExp = QStringLiteral( "ELSE" );
51 : :
52 : 0 : mRuleKey = QUuid::createUuid().toString();
53 : 0 : initFilter();
54 : 0 : }
55 : :
56 : 0 : QgsRuleBasedRenderer::Rule::~Rule()
57 : : {
58 : 0 : qDeleteAll( mChildren );
59 : : // do NOT delete parent
60 : 0 : }
61 : :
62 : 0 : void QgsRuleBasedRenderer::Rule::initFilter()
63 : : {
64 : 0 : if ( mFilterExp.trimmed().compare( QLatin1String( "ELSE" ), Qt::CaseInsensitive ) == 0 )
65 : : {
66 : 0 : mElseRule = true;
67 : 0 : mFilter.reset();
68 : 0 : }
69 : 0 : else if ( mFilterExp.trimmed().isEmpty() )
70 : : {
71 : 0 : mElseRule = false;
72 : 0 : mFilter.reset();
73 : 0 : }
74 : : else
75 : : {
76 : 0 : mElseRule = false;
77 : 0 : mFilter = std::make_unique< QgsExpression >( mFilterExp );
78 : : }
79 : 0 : }
80 : :
81 : 0 : void QgsRuleBasedRenderer::Rule::appendChild( Rule *rule )
82 : : {
83 : 0 : mChildren.append( rule );
84 : 0 : rule->mParent = this;
85 : 0 : updateElseRules();
86 : 0 : }
87 : :
88 : 0 : void QgsRuleBasedRenderer::Rule::insertChild( int i, Rule *rule )
89 : : {
90 : 0 : mChildren.insert( i, rule );
91 : 0 : rule->mParent = this;
92 : 0 : updateElseRules();
93 : 0 : }
94 : :
95 : 0 : void QgsRuleBasedRenderer::Rule::removeChild( Rule *rule )
96 : : {
97 : 0 : mChildren.removeAll( rule );
98 : 0 : delete rule;
99 : 0 : updateElseRules();
100 : 0 : }
101 : :
102 : 0 : void QgsRuleBasedRenderer::Rule::removeChildAt( int i )
103 : : {
104 : 0 : delete mChildren.takeAt( i );
105 : 0 : updateElseRules();
106 : 0 : }
107 : :
108 : 0 : QgsRuleBasedRenderer::Rule *QgsRuleBasedRenderer::Rule::takeChild( Rule *rule )
109 : : {
110 : 0 : mChildren.removeAll( rule );
111 : 0 : rule->mParent = nullptr;
112 : 0 : updateElseRules();
113 : 0 : return rule;
114 : : }
115 : :
116 : 0 : QgsRuleBasedRenderer::Rule *QgsRuleBasedRenderer::Rule::takeChildAt( int i )
117 : : {
118 : 0 : Rule *rule = mChildren.takeAt( i );
119 : 0 : rule->mParent = nullptr;
120 : 0 : updateElseRules();
121 : 0 : return rule;
122 : : }
123 : :
124 : 0 : QgsRuleBasedRenderer::Rule *QgsRuleBasedRenderer::Rule::findRuleByKey( const QString &key )
125 : : {
126 : : // we could use a hash / map for search if this will be slow...
127 : :
128 : 0 : if ( key == mRuleKey )
129 : 0 : return this;
130 : :
131 : 0 : const auto constMChildren = mChildren;
132 : 0 : for ( Rule *rule : constMChildren )
133 : : {
134 : 0 : Rule *r = rule->findRuleByKey( key );
135 : 0 : if ( r )
136 : 0 : return r;
137 : : }
138 : 0 : return nullptr;
139 : 0 : }
140 : :
141 : 0 : void QgsRuleBasedRenderer::Rule::updateElseRules()
142 : : {
143 : 0 : mElseRules.clear();
144 : 0 : const auto constMChildren = mChildren;
145 : 0 : for ( Rule *rule : constMChildren )
146 : : {
147 : 0 : if ( rule->isElse() )
148 : 0 : mElseRules << rule;
149 : : }
150 : 0 : }
151 : :
152 : 0 : void QgsRuleBasedRenderer::Rule::setIsElse( bool iselse )
153 : : {
154 : 0 : mFilterExp = QStringLiteral( "ELSE" );
155 : 0 : mElseRule = iselse;
156 : 0 : mFilter.reset();
157 : 0 : }
158 : :
159 : 0 : bool QgsRuleBasedRenderer::Rule::accept( QgsStyleEntityVisitorInterface *visitor ) const
160 : : {
161 : : // NOTE: if visitEnter returns false it means "don't visit the rule", not "abort all further visitations"
162 : 0 : if ( mParent && !visitor->visitEnter( QgsStyleEntityVisitorInterface::Node( QgsStyleEntityVisitorInterface::NodeType::SymbolRule, mRuleKey, mLabel ) ) )
163 : 0 : return true;
164 : :
165 : 0 : if ( mSymbol )
166 : : {
167 : 0 : QgsStyleSymbolEntity entity( mSymbol.get() );
168 : 0 : if ( !visitor->visit( QgsStyleEntityVisitorInterface::StyleLeaf( &entity ) ) )
169 : 0 : return false;
170 : 0 : }
171 : :
172 : 0 : if ( !mChildren.empty() )
173 : : {
174 : 0 : for ( const Rule *rule : mChildren )
175 : : {
176 : :
177 : 0 : if ( !rule->accept( visitor ) )
178 : 0 : return false;
179 : : }
180 : 0 : }
181 : :
182 : 0 : if ( mParent && !visitor->visitExit( QgsStyleEntityVisitorInterface::Node( QgsStyleEntityVisitorInterface::NodeType::SymbolRule, mRuleKey, mLabel ) ) )
183 : 0 : return false;
184 : :
185 : 0 : return true;
186 : 0 : }
187 : :
188 : 0 : QString QgsRuleBasedRenderer::Rule::dump( int indent ) const
189 : : {
190 : 0 : QString off;
191 : 0 : off.fill( QChar( ' ' ), indent );
192 : 0 : QString symbolDump = ( mSymbol ? mSymbol->dump() : QStringLiteral( "[]" ) );
193 : 0 : QString msg = off + QStringLiteral( "RULE %1 - scale [%2,%3] - filter %4 - symbol %5\n" )
194 : 0 : .arg( mLabel ).arg( mMaximumScale ).arg( mMinimumScale )
195 : 0 : .arg( mFilterExp, symbolDump );
196 : :
197 : 0 : QStringList lst;
198 : 0 : const auto constMChildren = mChildren;
199 : 0 : for ( Rule *rule : constMChildren )
200 : : {
201 : 0 : lst.append( rule->dump( indent + 2 ) );
202 : : }
203 : 0 : msg += lst.join( QLatin1Char( '\n' ) );
204 : 0 : return msg;
205 : 0 : }
206 : :
207 : 0 : QSet<QString> QgsRuleBasedRenderer::Rule::usedAttributes( const QgsRenderContext &context ) const
208 : : {
209 : : // attributes needed by this rule
210 : 0 : QSet<QString> attrs;
211 : 0 : if ( mFilter )
212 : 0 : attrs.unite( mFilter->referencedColumns() );
213 : 0 : if ( mSymbol )
214 : 0 : attrs.unite( mSymbol->usedAttributes( context ) );
215 : :
216 : : // attributes needed by child rules
217 : 0 : const auto constMChildren = mChildren;
218 : 0 : for ( Rule *rule : constMChildren )
219 : : {
220 : 0 : attrs.unite( rule->usedAttributes( context ) );
221 : : }
222 : 0 : return attrs;
223 : 0 : }
224 : :
225 : 0 : bool QgsRuleBasedRenderer::Rule::needsGeometry() const
226 : : {
227 : 0 : if ( mFilter && mFilter->needsGeometry() )
228 : 0 : return true;
229 : :
230 : 0 : const auto constMChildren = mChildren;
231 : 0 : for ( Rule *rule : constMChildren )
232 : : {
233 : 0 : if ( rule->needsGeometry() )
234 : 0 : return true;
235 : : }
236 : :
237 : 0 : return false;
238 : 0 : }
239 : :
240 : 0 : QgsSymbolList QgsRuleBasedRenderer::Rule::symbols( const QgsRenderContext &context ) const
241 : : {
242 : 0 : QgsSymbolList lst;
243 : 0 : if ( mSymbol )
244 : 0 : lst.append( mSymbol.get() );
245 : :
246 : 0 : const auto constMChildren = mChildren;
247 : 0 : for ( Rule *rule : constMChildren )
248 : : {
249 : 0 : lst += rule->symbols( context );
250 : : }
251 : 0 : return lst;
252 : 0 : }
253 : :
254 : 0 : void QgsRuleBasedRenderer::Rule::setSymbol( QgsSymbol *sym )
255 : : {
256 : 0 : mSymbol.reset( sym );
257 : 0 : }
258 : :
259 : 0 : void QgsRuleBasedRenderer::Rule::setFilterExpression( const QString &filterExp )
260 : : {
261 : 0 : mFilterExp = filterExp;
262 : 0 : initFilter();
263 : 0 : }
264 : :
265 : 0 : QgsLegendSymbolList QgsRuleBasedRenderer::Rule::legendSymbolItems( int currentLevel ) const
266 : : {
267 : 0 : QgsLegendSymbolList lst;
268 : 0 : if ( currentLevel != -1 ) // root rule should not be shown
269 : : {
270 : 0 : lst << QgsLegendSymbolItem( mSymbol.get(), mLabel, mRuleKey, true, mMaximumScale, mMinimumScale, currentLevel, mParent ? mParent->mRuleKey : QString() );
271 : 0 : }
272 : :
273 : 0 : for ( RuleList::const_iterator it = mChildren.constBegin(); it != mChildren.constEnd(); ++it )
274 : : {
275 : 0 : Rule *rule = *it;
276 : 0 : lst << rule->legendSymbolItems( currentLevel + 1 );
277 : 0 : }
278 : 0 : return lst;
279 : 0 : }
280 : :
281 : :
282 : 0 : bool QgsRuleBasedRenderer::Rule::isFilterOK( const QgsFeature &f, QgsRenderContext *context ) const
283 : : {
284 : 0 : if ( ! mFilter || mElseRule || ! context )
285 : 0 : return true;
286 : :
287 : 0 : context->expressionContext().setFeature( f );
288 : 0 : QVariant res = mFilter->evaluate( &context->expressionContext() );
289 : 0 : return res.toBool();
290 : 0 : }
291 : :
292 : 0 : bool QgsRuleBasedRenderer::Rule::isScaleOK( double scale ) const
293 : : {
294 : 0 : if ( qgsDoubleNear( scale, 0.0 ) ) // so that we can count features in classes without scale context
295 : 0 : return true;
296 : 0 : if ( qgsDoubleNear( mMaximumScale, 0.0 ) && qgsDoubleNear( mMinimumScale, 0.0 ) )
297 : 0 : return true;
298 : 0 : if ( !qgsDoubleNear( mMaximumScale, 0.0 ) && mMaximumScale > scale )
299 : 0 : return false;
300 : 0 : if ( !qgsDoubleNear( mMinimumScale, 0.0 ) && mMinimumScale < scale )
301 : 0 : return false;
302 : 0 : return true;
303 : 0 : }
304 : :
305 : 0 : QgsRuleBasedRenderer::Rule *QgsRuleBasedRenderer::Rule::clone() const
306 : : {
307 : 0 : QgsSymbol *sym = mSymbol ? mSymbol->clone() : nullptr;
308 : 0 : Rule *newrule = new Rule( sym, mMaximumScale, mMinimumScale, mFilterExp, mLabel, mDescription );
309 : 0 : newrule->setActive( mIsActive );
310 : : // clone children
311 : 0 : const auto constMChildren = mChildren;
312 : 0 : for ( Rule *rule : constMChildren )
313 : 0 : newrule->appendChild( rule->clone() );
314 : 0 : return newrule;
315 : 0 : }
316 : :
317 : 0 : QDomElement QgsRuleBasedRenderer::Rule::save( QDomDocument &doc, QgsSymbolMap &symbolMap ) const
318 : : {
319 : 0 : QDomElement ruleElem = doc.createElement( QStringLiteral( "rule" ) );
320 : :
321 : 0 : if ( mSymbol )
322 : : {
323 : 0 : int symbolIndex = symbolMap.size();
324 : 0 : symbolMap[QString::number( symbolIndex )] = mSymbol.get();
325 : 0 : ruleElem.setAttribute( QStringLiteral( "symbol" ), symbolIndex );
326 : 0 : }
327 : 0 : if ( !mFilterExp.isEmpty() )
328 : 0 : ruleElem.setAttribute( QStringLiteral( "filter" ), mFilterExp );
329 : 0 : if ( mMaximumScale != 0 )
330 : 0 : ruleElem.setAttribute( QStringLiteral( "scalemindenom" ), mMaximumScale );
331 : 0 : if ( mMinimumScale != 0 )
332 : 0 : ruleElem.setAttribute( QStringLiteral( "scalemaxdenom" ), mMinimumScale );
333 : 0 : if ( !mLabel.isEmpty() )
334 : 0 : ruleElem.setAttribute( QStringLiteral( "label" ), mLabel );
335 : 0 : if ( !mDescription.isEmpty() )
336 : 0 : ruleElem.setAttribute( QStringLiteral( "description" ), mDescription );
337 : 0 : if ( !mIsActive )
338 : 0 : ruleElem.setAttribute( QStringLiteral( "checkstate" ), 0 );
339 : 0 : ruleElem.setAttribute( QStringLiteral( "key" ), mRuleKey );
340 : :
341 : 0 : const auto constMChildren = mChildren;
342 : 0 : for ( Rule *rule : constMChildren )
343 : : {
344 : 0 : ruleElem.appendChild( rule->save( doc, symbolMap ) );
345 : : }
346 : 0 : return ruleElem;
347 : 0 : }
348 : :
349 : 0 : void QgsRuleBasedRenderer::Rule::toSld( QDomDocument &doc, QDomElement &element, QVariantMap props ) const
350 : : {
351 : : // do not convert this rule if there are no symbols
352 : 0 : QgsRenderContext context;
353 : 0 : if ( symbols( context ).isEmpty() )
354 : 0 : return;
355 : :
356 : 0 : if ( !mFilterExp.isEmpty() )
357 : : {
358 : 0 : QString filter = props.value( QStringLiteral( "filter" ), QString() ).toString();
359 : 0 : if ( !filter.isEmpty() )
360 : 0 : filter += QLatin1String( " AND " );
361 : 0 : filter += mFilterExp;
362 : 0 : props[ QStringLiteral( "filter" )] = filter;
363 : 0 : }
364 : :
365 : 0 : QgsSymbolLayerUtils::mergeScaleDependencies( mMaximumScale, mMinimumScale, props );
366 : :
367 : 0 : if ( mSymbol )
368 : : {
369 : 0 : QDomElement ruleElem = doc.createElement( QStringLiteral( "se:Rule" ) );
370 : 0 : element.appendChild( ruleElem );
371 : :
372 : : //XXX: <se:Name> is the rule identifier, but our the Rule objects
373 : : // have no properties could be used as identifier. Use the label.
374 : 0 : QDomElement nameElem = doc.createElement( QStringLiteral( "se:Name" ) );
375 : 0 : nameElem.appendChild( doc.createTextNode( mLabel ) );
376 : 0 : ruleElem.appendChild( nameElem );
377 : :
378 : 0 : if ( !mLabel.isEmpty() || !mDescription.isEmpty() )
379 : : {
380 : 0 : QDomElement descrElem = doc.createElement( QStringLiteral( "se:Description" ) );
381 : 0 : if ( !mLabel.isEmpty() )
382 : : {
383 : 0 : QDomElement titleElem = doc.createElement( QStringLiteral( "se:Title" ) );
384 : 0 : titleElem.appendChild( doc.createTextNode( mLabel ) );
385 : 0 : descrElem.appendChild( titleElem );
386 : 0 : }
387 : 0 : if ( !mDescription.isEmpty() )
388 : : {
389 : 0 : QDomElement abstractElem = doc.createElement( QStringLiteral( "se:Abstract" ) );
390 : 0 : abstractElem.appendChild( doc.createTextNode( mDescription ) );
391 : 0 : descrElem.appendChild( abstractElem );
392 : 0 : }
393 : 0 : ruleElem.appendChild( descrElem );
394 : 0 : }
395 : :
396 : 0 : if ( !props.value( QStringLiteral( "filter" ), QString() ).toString().isEmpty() )
397 : : {
398 : 0 : QgsSymbolLayerUtils::createFunctionElement( doc, ruleElem, props.value( QStringLiteral( "filter" ), QString() ).toString() );
399 : 0 : }
400 : :
401 : 0 : QgsSymbolLayerUtils::applyScaleDependency( doc, ruleElem, props );
402 : :
403 : 0 : mSymbol->toSld( doc, ruleElem, props );
404 : 0 : }
405 : :
406 : : // loop into children rule list
407 : 0 : const auto constMChildren = mChildren;
408 : 0 : for ( Rule *rule : constMChildren )
409 : : {
410 : 0 : rule->toSld( doc, element, props );
411 : : }
412 : 0 : }
413 : :
414 : 0 : bool QgsRuleBasedRenderer::Rule::startRender( QgsRenderContext &context, const QgsFields &fields, QString &filter )
415 : : {
416 : 0 : mActiveChildren.clear();
417 : :
418 : 0 : if ( ! mIsActive )
419 : 0 : return false;
420 : :
421 : : // filter out rules which are not compatible with this scale
422 : 0 : if ( !isScaleOK( context.rendererScale() ) )
423 : 0 : return false;
424 : :
425 : : // init this rule
426 : 0 : if ( mFilter )
427 : 0 : mFilter->prepare( &context.expressionContext() );
428 : 0 : if ( mSymbol )
429 : 0 : mSymbol->startRender( context, fields );
430 : :
431 : : // init children
432 : : // build temporary list of active rules (usable with this scale)
433 : 0 : QStringList subfilters;
434 : 0 : const auto constMChildren = mChildren;
435 : 0 : for ( Rule *rule : constMChildren )
436 : : {
437 : 0 : QString subfilter;
438 : 0 : if ( rule->startRender( context, fields, subfilter ) )
439 : : {
440 : : // only add those which are active with current scale
441 : 0 : mActiveChildren.append( rule );
442 : 0 : subfilters.append( subfilter );
443 : 0 : }
444 : 0 : }
445 : :
446 : : // subfilters (on the same level) are joined with OR
447 : : // Finally they are joined with their parent (this) with AND
448 : 0 : QString sf;
449 : : // If there are subfilters present (and it's not a single empty one), group them and join them with OR
450 : 0 : if ( subfilters.length() > 1 || !subfilters.value( 0 ).isEmpty() )
451 : : {
452 : 0 : if ( subfilters.contains( QStringLiteral( "TRUE" ) ) )
453 : : {
454 : 0 : sf = QStringLiteral( "TRUE" );
455 : 0 : }
456 : : else
457 : : {
458 : : // test for a common case -- all subfilters can be combined into a single "field in (...)" expression
459 : 0 : if ( QgsExpression::attemptReduceToInClause( subfilters, sf ) )
460 : : {
461 : : // success! we can use a simple "field IN (...)" list!
462 : 0 : }
463 : : // If we have more than 50 rules (to stay on the safe side) make a binary tree or SQLITE will fail,
464 : : // see: https://github.com/qgis/QGIS/issues/27269
465 : 0 : else if ( subfilters.count() > 50 )
466 : : {
467 : 0 : std::function<QString( const QStringList & )>bt = [ &bt ]( const QStringList & subf )
468 : : {
469 : 0 : if ( subf.count( ) == 1 )
470 : : {
471 : 0 : return subf.at( 0 );
472 : : }
473 : 0 : else if ( subf.count( ) == 2 )
474 : : {
475 : 0 : return subf.join( QLatin1String( ") OR (" ) ).prepend( '(' ).append( ')' );
476 : : }
477 : : else
478 : : {
479 : 0 : int midpos = static_cast<int>( subf.length() / 2 );
480 : 0 : return QStringLiteral( "(%1) OR (%2)" ).arg( bt( subf.mid( 0, midpos ) ), bt( subf.mid( midpos ) ) );
481 : : }
482 : 0 : };
483 : 0 : sf = bt( subfilters );
484 : 0 : }
485 : : else
486 : : {
487 : 0 : sf = subfilters.join( QLatin1String( ") OR (" ) ).prepend( '(' ).append( ')' );
488 : : }
489 : : }
490 : 0 : }
491 : :
492 : : // Now join the subfilters with their parent (this) based on if
493 : : // * The parent is an else rule
494 : : // * The existence of parent filter and subfilters
495 : :
496 : : // No filter expression: ELSE rule or catchall rule
497 : 0 : if ( !mFilter )
498 : : {
499 : 0 : if ( mSymbol || sf.isEmpty() )
500 : 0 : filter = QStringLiteral( "TRUE" );
501 : : else
502 : 0 : filter = sf;
503 : 0 : }
504 : 0 : else if ( mSymbol )
505 : 0 : filter = mFilterExp;
506 : 0 : else if ( !mFilterExp.trimmed().isEmpty() && !sf.isEmpty() )
507 : 0 : filter = QStringLiteral( "(%1) AND (%2)" ).arg( mFilterExp, sf );
508 : 0 : else if ( !mFilterExp.trimmed().isEmpty() )
509 : 0 : filter = mFilterExp;
510 : 0 : else if ( sf.isEmpty() )
511 : 0 : filter = QStringLiteral( "TRUE" );
512 : : else
513 : 0 : filter = sf;
514 : :
515 : 0 : filter = filter.trimmed();
516 : :
517 : 0 : return true;
518 : 0 : }
519 : :
520 : 0 : QSet<int> QgsRuleBasedRenderer::Rule::collectZLevels()
521 : : {
522 : 0 : QSet<int> symbolZLevelsSet;
523 : :
524 : : // process this rule
525 : 0 : if ( mSymbol )
526 : : {
527 : : // find out which Z-levels are used
528 : 0 : for ( int i = 0; i < mSymbol->symbolLayerCount(); i++ )
529 : : {
530 : 0 : symbolZLevelsSet.insert( mSymbol->symbolLayer( i )->renderingPass() );
531 : 0 : }
532 : 0 : }
533 : :
534 : : // process children
535 : 0 : QList<Rule *>::iterator it;
536 : 0 : for ( it = mActiveChildren.begin(); it != mActiveChildren.end(); ++it )
537 : : {
538 : 0 : Rule *rule = *it;
539 : 0 : symbolZLevelsSet.unite( rule->collectZLevels() );
540 : 0 : }
541 : 0 : return symbolZLevelsSet;
542 : 0 : }
543 : :
544 : 0 : void QgsRuleBasedRenderer::Rule::setNormZLevels( const QMap<int, int> &zLevelsToNormLevels )
545 : : {
546 : 0 : if ( mSymbol )
547 : : {
548 : 0 : for ( int i = 0; i < mSymbol->symbolLayerCount(); i++ )
549 : : {
550 : 0 : int normLevel = zLevelsToNormLevels.value( mSymbol->symbolLayer( i )->renderingPass() );
551 : 0 : mSymbolNormZLevels.insert( normLevel );
552 : 0 : }
553 : 0 : }
554 : :
555 : : // prepare list of normalized levels for each rule
556 : 0 : const auto constMActiveChildren = mActiveChildren;
557 : 0 : for ( Rule *rule : constMActiveChildren )
558 : : {
559 : 0 : rule->setNormZLevels( zLevelsToNormLevels );
560 : : }
561 : 0 : }
562 : 0 :
563 : :
564 : 0 : QgsRuleBasedRenderer::Rule::RenderResult QgsRuleBasedRenderer::Rule::renderFeature( QgsRuleBasedRenderer::FeatureToRender &featToRender, QgsRenderContext &context, QgsRuleBasedRenderer::RenderQueue &renderQueue )
565 : : {
566 : 0 : if ( !isFilterOK( featToRender.feat, &context ) )
567 : 0 : return Filtered;
568 : :
569 : 0 : bool rendered = false;
570 : :
571 : : // create job for this feature and this symbol, add to list of jobs
572 : 0 : if ( mSymbol && mIsActive )
573 : : {
574 : : // add job to the queue: each symbol's zLevel must be added
575 : 0 : const auto constMSymbolNormZLevels = mSymbolNormZLevels;
576 : 0 : for ( int normZLevel : constMSymbolNormZLevels )
577 : : {
578 : : //QgsDebugMsg(QString("add job at level %1").arg(normZLevel));
579 : 0 : renderQueue[normZLevel].jobs.append( new RenderJob( featToRender, mSymbol.get() ) );
580 : 0 : rendered = true;
581 : : }
582 : 0 : }
583 : :
584 : 0 : bool willrendersomething = false;
585 : :
586 : : // process children
587 : 0 : const auto constMChildren = mChildren;
588 : 0 : for ( Rule *rule : constMChildren )
589 : : {
590 : : // Don't process else rules yet
591 : 0 : if ( !rule->isElse() )
592 : : {
593 : 0 : RenderResult res = rule->renderFeature( featToRender, context, renderQueue );
594 : : // consider inactive items as "rendered" so the else rule will ignore them
595 : 0 : willrendersomething |= ( res == Rendered || res == Inactive );
596 : 0 : rendered |= ( res == Rendered );
597 : 0 : }
598 : : }
599 : :
600 : : // If none of the rules passed then we jump into the else rules and process them.
601 : 0 : if ( !willrendersomething )
602 : : {
603 : 0 : const auto constMElseRules = mElseRules;
604 : 0 : for ( Rule *rule : constMElseRules )
605 : : {
606 : 0 : rendered |= rule->renderFeature( featToRender, context, renderQueue ) == Rendered;
607 : : }
608 : 0 : }
609 : 0 : if ( !mIsActive || ( mSymbol && !rendered ) )
610 : 0 : return Inactive;
611 : 0 : else if ( rendered )
612 : 0 : return Rendered;
613 : : else
614 : 0 : return Filtered;
615 : 0 : }
616 : :
617 : 0 : bool QgsRuleBasedRenderer::Rule::willRenderFeature( const QgsFeature &feature, QgsRenderContext *context )
618 : : {
619 : 0 : if ( !isFilterOK( feature, context ) )
620 : 0 : return false;
621 : :
622 : 0 : if ( mSymbol )
623 : 0 : return true;
624 : :
625 : 0 : const auto constMActiveChildren = mActiveChildren;
626 : 0 : for ( Rule *rule : constMActiveChildren )
627 : : {
628 : 0 : if ( rule->isElse() )
629 : : {
630 : 0 : if ( rule->children().isEmpty() )
631 : : {
632 : 0 : RuleList lst = rulesForFeature( feature, context, false );
633 : 0 : lst.removeOne( rule );
634 : :
635 : 0 : if ( lst.empty() )
636 : : {
637 : 0 : return true;
638 : : }
639 : 0 : }
640 : : else
641 : : {
642 : 0 : return rule->willRenderFeature( feature, context );
643 : : }
644 : 0 : }
645 : 0 : else if ( rule->willRenderFeature( feature, context ) )
646 : : {
647 : 0 : return true;
648 : : }
649 : : }
650 : 0 : return false;
651 : 0 : }
652 : :
653 : 0 : QgsSymbolList QgsRuleBasedRenderer::Rule::symbolsForFeature( const QgsFeature &feature, QgsRenderContext *context )
654 : : {
655 : 0 : QgsSymbolList lst;
656 : 0 : if ( !isFilterOK( feature, context ) )
657 : 0 : return lst;
658 : 0 : if ( mSymbol )
659 : 0 : lst.append( mSymbol.get() );
660 : :
661 : 0 : const auto constMActiveChildren = mActiveChildren;
662 : 0 : for ( Rule *rule : constMActiveChildren )
663 : : {
664 : 0 : lst += rule->symbolsForFeature( feature, context );
665 : : }
666 : 0 : return lst;
667 : 0 : }
668 : :
669 : 0 : QSet<QString> QgsRuleBasedRenderer::Rule::legendKeysForFeature( const QgsFeature &feature, QgsRenderContext *context )
670 : : {
671 : 0 : QSet< QString> lst;
672 : 0 : if ( !isFilterOK( feature, context ) )
673 : 0 : return lst;
674 : 0 : lst.insert( mRuleKey );
675 : :
676 : 0 : const auto constMActiveChildren = mActiveChildren;
677 : 0 : for ( Rule *rule : constMActiveChildren )
678 : : {
679 : 0 : bool validKey = false;
680 : 0 : if ( rule->isElse() )
681 : : {
682 : 0 : RuleList lst = rulesForFeature( feature, context, false );
683 : 0 : lst.removeOne( rule );
684 : :
685 : 0 : if ( lst.empty() )
686 : : {
687 : 0 : validKey = true;
688 : 0 : }
689 : 0 : }
690 : 0 : else if ( !rule->isElse( ) && rule->willRenderFeature( feature, context ) )
691 : : {
692 : 0 : validKey = true;
693 : 0 : }
694 : :
695 : 0 : if ( validKey )
696 : : {
697 : 0 : lst.unite( rule->legendKeysForFeature( feature, context ) );
698 : 0 : }
699 : : }
700 : 0 : return lst;
701 : 0 : }
702 : :
703 : 0 : QgsRuleBasedRenderer::RuleList QgsRuleBasedRenderer::Rule::rulesForFeature( const QgsFeature &feature, QgsRenderContext *context, bool onlyActive )
704 : : {
705 : 0 : RuleList lst;
706 : 0 : if ( ! isFilterOK( feature, context ) || ( context && ! isScaleOK( context->rendererScale() ) ) )
707 : 0 : return lst;
708 : :
709 : 0 : if ( mSymbol )
710 : 0 : lst.append( this );
711 : :
712 : 0 : RuleList listChildren = children();
713 : 0 : if ( onlyActive )
714 : 0 : listChildren = mActiveChildren;
715 : :
716 : 0 : const auto constListChildren = listChildren;
717 : 0 : for ( Rule *rule : constListChildren )
718 : : {
719 : 0 : lst += rule->rulesForFeature( feature, context, onlyActive );
720 : : }
721 : 0 : return lst;
722 : 0 : }
723 : :
724 : 0 : void QgsRuleBasedRenderer::Rule::stopRender( QgsRenderContext &context )
725 : : {
726 : 0 : if ( mSymbol )
727 : 0 : mSymbol->stopRender( context );
728 : :
729 : 0 : const auto constMActiveChildren = mActiveChildren;
730 : 0 : for ( Rule *rule : constMActiveChildren )
731 : : {
732 : 0 : rule->stopRender( context );
733 : : }
734 : :
735 : 0 : mActiveChildren.clear();
736 : 0 : mSymbolNormZLevels.clear();
737 : 0 : }
738 : :
739 : 0 : QgsRuleBasedRenderer::Rule *QgsRuleBasedRenderer::Rule::create( QDomElement &ruleElem, QgsSymbolMap &symbolMap )
740 : : {
741 : 0 : QString symbolIdx = ruleElem.attribute( QStringLiteral( "symbol" ) );
742 : 0 : QgsSymbol *symbol = nullptr;
743 : 0 : if ( !symbolIdx.isEmpty() )
744 : : {
745 : 0 : if ( symbolMap.contains( symbolIdx ) )
746 : : {
747 : 0 : symbol = symbolMap.take( symbolIdx );
748 : 0 : }
749 : : else
750 : : {
751 : 0 : QgsDebugMsg( "symbol for rule " + symbolIdx + " not found!" );
752 : : }
753 : 0 : }
754 : :
755 : 0 : QString filterExp = ruleElem.attribute( QStringLiteral( "filter" ) );
756 : 0 : QString label = ruleElem.attribute( QStringLiteral( "label" ) );
757 : 0 : QString description = ruleElem.attribute( QStringLiteral( "description" ) );
758 : 0 : int scaleMinDenom = ruleElem.attribute( QStringLiteral( "scalemindenom" ), QStringLiteral( "0" ) ).toInt();
759 : 0 : int scaleMaxDenom = ruleElem.attribute( QStringLiteral( "scalemaxdenom" ), QStringLiteral( "0" ) ).toInt();
760 : 0 : QString ruleKey = ruleElem.attribute( QStringLiteral( "key" ) );
761 : 0 : Rule *rule = new Rule( symbol, scaleMinDenom, scaleMaxDenom, filterExp, label, description );
762 : :
763 : 0 : if ( !ruleKey.isEmpty() )
764 : 0 : rule->mRuleKey = ruleKey;
765 : :
766 : 0 : rule->setActive( ruleElem.attribute( QStringLiteral( "checkstate" ), QStringLiteral( "1" ) ).toInt() );
767 : :
768 : 0 : QDomElement childRuleElem = ruleElem.firstChildElement( QStringLiteral( "rule" ) );
769 : 0 : while ( !childRuleElem.isNull() )
770 : : {
771 : 0 : Rule *childRule = create( childRuleElem, symbolMap );
772 : 0 : if ( childRule )
773 : : {
774 : 0 : rule->appendChild( childRule );
775 : 0 : }
776 : : else
777 : : {
778 : 0 : QgsDebugMsg( QStringLiteral( "failed to init a child rule!" ) );
779 : : }
780 : 0 : childRuleElem = childRuleElem.nextSiblingElement( QStringLiteral( "rule" ) );
781 : : }
782 : :
783 : 0 : return rule;
784 : 0 : }
785 : :
786 : 0 : QgsRuleBasedRenderer::RuleList QgsRuleBasedRenderer::Rule::descendants() const
787 : : {
788 : 0 : RuleList l;
789 : 0 : for ( QgsRuleBasedRenderer::Rule *c : mChildren )
790 : : {
791 : 0 : l += c;
792 : 0 : l += c->descendants();
793 : : }
794 : 0 : return l;
795 : 0 : }
796 : :
797 : 0 : QgsRuleBasedRenderer::Rule *QgsRuleBasedRenderer::Rule::createFromSld( QDomElement &ruleElem, QgsWkbTypes::GeometryType geomType )
798 : : {
799 : 0 : if ( ruleElem.localName() != QLatin1String( "Rule" ) )
800 : : {
801 : 0 : QgsDebugMsg( QStringLiteral( "invalid element: Rule element expected, %1 found!" ).arg( ruleElem.tagName() ) );
802 : 0 : return nullptr;
803 : : }
804 : :
805 : 0 : QString label, description, filterExp;
806 : 0 : int scaleMinDenom = 0, scaleMaxDenom = 0;
807 : 0 : QgsSymbolLayerList layers;
808 : :
809 : : // retrieve the Rule element child nodes
810 : 0 : QDomElement childElem = ruleElem.firstChildElement();
811 : 0 : while ( !childElem.isNull() )
812 : : {
813 : 0 : if ( childElem.localName() == QLatin1String( "Name" ) )
814 : : {
815 : : // <se:Name> tag contains the rule identifier,
816 : : // so prefer title tag for the label property value
817 : 0 : if ( label.isEmpty() )
818 : 0 : label = childElem.firstChild().nodeValue();
819 : 0 : }
820 : 0 : else if ( childElem.localName() == QLatin1String( "Description" ) )
821 : : {
822 : : // <se:Description> can contains a title and an abstract
823 : 0 : QDomElement titleElem = childElem.firstChildElement( QStringLiteral( "Title" ) );
824 : 0 : if ( !titleElem.isNull() )
825 : : {
826 : 0 : label = titleElem.firstChild().nodeValue();
827 : 0 : }
828 : :
829 : 0 : QDomElement abstractElem = childElem.firstChildElement( QStringLiteral( "Abstract" ) );
830 : 0 : if ( !abstractElem.isNull() )
831 : : {
832 : 0 : description = abstractElem.firstChild().nodeValue();
833 : 0 : }
834 : 0 : }
835 : 0 : else if ( childElem.localName() == QLatin1String( "Abstract" ) )
836 : : {
837 : : // <sld:Abstract> (v1.0)
838 : 0 : description = childElem.firstChild().nodeValue();
839 : 0 : }
840 : 0 : else if ( childElem.localName() == QLatin1String( "Title" ) )
841 : : {
842 : : // <sld:Title> (v1.0)
843 : 0 : label = childElem.firstChild().nodeValue();
844 : 0 : }
845 : 0 : else if ( childElem.localName() == QLatin1String( "Filter" ) )
846 : : {
847 : 0 : QgsExpression *filter = QgsOgcUtils::expressionFromOgcFilter( childElem );
848 : 0 : if ( filter )
849 : : {
850 : 0 : if ( filter->hasParserError() )
851 : : {
852 : 0 : QgsDebugMsg( "parser error: " + filter->parserErrorString() );
853 : 0 : }
854 : : else
855 : : {
856 : 0 : filterExp = filter->expression();
857 : : }
858 : 0 : delete filter;
859 : 0 : }
860 : 0 : }
861 : 0 : else if ( childElem.localName() == QLatin1String( "MinScaleDenominator" ) )
862 : : {
863 : : bool ok;
864 : 0 : int v = childElem.firstChild().nodeValue().toInt( &ok );
865 : 0 : if ( ok )
866 : 0 : scaleMinDenom = v;
867 : 0 : }
868 : 0 : else if ( childElem.localName() == QLatin1String( "MaxScaleDenominator" ) )
869 : : {
870 : : bool ok;
871 : 0 : int v = childElem.firstChild().nodeValue().toInt( &ok );
872 : 0 : if ( ok )
873 : 0 : scaleMaxDenom = v;
874 : 0 : }
875 : 0 : else if ( childElem.localName().endsWith( QLatin1String( "Symbolizer" ) ) )
876 : : {
877 : : // create symbol layers for this symbolizer
878 : 0 : QgsSymbolLayerUtils::createSymbolLayerListFromSld( childElem, geomType, layers );
879 : 0 : }
880 : :
881 : 0 : childElem = childElem.nextSiblingElement();
882 : : }
883 : :
884 : : // now create the symbol
885 : 0 : QgsSymbol *symbol = nullptr;
886 : 0 : if ( !layers.isEmpty() )
887 : : {
888 : 0 : switch ( geomType )
889 : : {
890 : : case QgsWkbTypes::LineGeometry:
891 : 0 : symbol = new QgsLineSymbol( layers );
892 : 0 : break;
893 : :
894 : : case QgsWkbTypes::PolygonGeometry:
895 : 0 : symbol = new QgsFillSymbol( layers );
896 : 0 : break;
897 : :
898 : : case QgsWkbTypes::PointGeometry:
899 : 0 : symbol = new QgsMarkerSymbol( layers );
900 : 0 : break;
901 : :
902 : : default:
903 : 0 : QgsDebugMsg( QStringLiteral( "invalid geometry type: found %1" ).arg( geomType ) );
904 : 0 : return nullptr;
905 : : }
906 : 0 : }
907 : :
908 : : // and then create and return the new rule
909 : 0 : return new Rule( symbol, scaleMinDenom, scaleMaxDenom, filterExp, label, description );
910 : 0 : }
911 : :
912 : :
913 : : /////////////////////
914 : :
915 : 0 : QgsRuleBasedRenderer::QgsRuleBasedRenderer( QgsRuleBasedRenderer::Rule *root )
916 : 0 : : QgsFeatureRenderer( QStringLiteral( "RuleRenderer" ) )
917 : 0 : , mRootRule( root )
918 : 0 : {
919 : 0 : }
920 : :
921 : 0 : QgsRuleBasedRenderer::QgsRuleBasedRenderer( QgsSymbol *defaultSymbol )
922 : 0 : : QgsFeatureRenderer( QStringLiteral( "RuleRenderer" ) )
923 : 0 : {
924 : 0 : mRootRule = new Rule( nullptr ); // root has no symbol, no filter etc - just a container
925 : 0 : mRootRule->appendChild( new Rule( defaultSymbol ) );
926 : 0 : }
927 : :
928 : 0 : QgsRuleBasedRenderer::~QgsRuleBasedRenderer()
929 : 0 : {
930 : 0 : delete mRootRule;
931 : 0 : }
932 : :
933 : :
934 : 0 : QgsSymbol *QgsRuleBasedRenderer::symbolForFeature( const QgsFeature &, QgsRenderContext & ) const
935 : : {
936 : : // not used at all
937 : 0 : return nullptr;
938 : : }
939 : :
940 : 0 : bool QgsRuleBasedRenderer::renderFeature( const QgsFeature &feature,
941 : : QgsRenderContext &context,
942 : : int layer,
943 : : bool selected,
944 : : bool drawVertexMarker )
945 : : {
946 : : Q_UNUSED( layer )
947 : :
948 : 0 : int flags = ( selected ? FeatIsSelected : 0 ) | ( drawVertexMarker ? FeatDrawMarkers : 0 );
949 : 0 : mCurrentFeatures.append( FeatureToRender( feature, flags ) );
950 : :
951 : : // check each active rule
952 : 0 : return mRootRule->renderFeature( mCurrentFeatures.last(), context, mRenderQueue ) == Rule::Rendered;
953 : 0 : }
954 : :
955 : :
956 : 0 : void QgsRuleBasedRenderer::startRender( QgsRenderContext &context, const QgsFields &fields )
957 : : {
958 : 0 : QgsFeatureRenderer::startRender( context, fields );
959 : :
960 : : // prepare active children
961 : 0 : mRootRule->startRender( context, fields, mFilter );
962 : :
963 : 0 : QSet<int> symbolZLevelsSet = mRootRule->collectZLevels();
964 : 0 : QList<int> symbolZLevels = qgis::setToList( symbolZLevelsSet );
965 : 0 : std::sort( symbolZLevels.begin(), symbolZLevels.end() );
966 : :
967 : : // create mapping from unnormalized levels [unlimited range] to normalized levels [0..N-1]
968 : : // and prepare rendering queue
969 : 0 : QMap<int, int> zLevelsToNormLevels;
970 : 0 : int maxNormLevel = -1;
971 : 0 : const auto constSymbolZLevels = symbolZLevels;
972 : 0 : for ( int zLevel : constSymbolZLevels )
973 : : {
974 : 0 : zLevelsToNormLevels[zLevel] = ++maxNormLevel;
975 : 0 : mRenderQueue.append( RenderLevel( zLevel ) );
976 : 0 : QgsDebugMsgLevel( QStringLiteral( "zLevel %1 -> %2" ).arg( zLevel ).arg( maxNormLevel ), 4 );
977 : : }
978 : :
979 : 0 : mRootRule->setNormZLevels( zLevelsToNormLevels );
980 : 0 : }
981 : :
982 : 0 : void QgsRuleBasedRenderer::stopRender( QgsRenderContext &context )
983 : : {
984 : 0 : QgsFeatureRenderer::stopRender( context );
985 : :
986 : : //
987 : : // do the actual rendering
988 : : //
989 : :
990 : : // go through all levels
991 : 0 : if ( !context.renderingStopped() )
992 : : {
993 : 0 : const auto constMRenderQueue = mRenderQueue;
994 : 0 : for ( const RenderLevel &level : constMRenderQueue )
995 : : {
996 : : //QgsDebugMsg(QString("level %1").arg(level.zIndex));
997 : : // go through all jobs at the level
998 : 0 : for ( const RenderJob *job : std::as_const( level.jobs ) )
999 : : {
1000 : 0 : context.expressionContext().setFeature( job->ftr.feat );
1001 : : //QgsDebugMsg(QString("job fid %1").arg(job->f->id()));
1002 : : // render feature - but only with symbol layers with specified zIndex
1003 : 0 : QgsSymbol *s = job->symbol;
1004 : 0 : int count = s->symbolLayerCount();
1005 : 0 : for ( int i = 0; i < count; i++ )
1006 : : {
1007 : : // TODO: better solution for this
1008 : : // renderFeatureWithSymbol asks which symbol layer to draw
1009 : : // but there are multiple transforms going on!
1010 : 0 : if ( s->symbolLayer( i )->renderingPass() == level.zIndex )
1011 : : {
1012 : 0 : int flags = job->ftr.flags;
1013 : 0 : renderFeatureWithSymbol( job->ftr.feat, job->symbol, context, i, flags & FeatIsSelected, flags & FeatDrawMarkers );
1014 : 0 : }
1015 : 0 : }
1016 : : }
1017 : : }
1018 : 0 : }
1019 : :
1020 : : // clean current features
1021 : 0 : mCurrentFeatures.clear();
1022 : :
1023 : : // clean render queue
1024 : 0 : mRenderQueue.clear();
1025 : :
1026 : : // clean up rules from temporary stuff
1027 : 0 : mRootRule->stopRender( context );
1028 : 0 : }
1029 : :
1030 : 0 : QString QgsRuleBasedRenderer::filter( const QgsFields & )
1031 : : {
1032 : 0 : return mFilter;
1033 : : }
1034 : :
1035 : 0 : QSet<QString> QgsRuleBasedRenderer::usedAttributes( const QgsRenderContext &context ) const
1036 : : {
1037 : 0 : return mRootRule->usedAttributes( context );
1038 : : }
1039 : :
1040 : 0 : bool QgsRuleBasedRenderer::filterNeedsGeometry() const
1041 : : {
1042 : 0 : return mRootRule->needsGeometry();
1043 : : }
1044 : :
1045 : 0 : QgsRuleBasedRenderer *QgsRuleBasedRenderer::clone() const
1046 : : {
1047 : 0 : QgsRuleBasedRenderer::Rule *clonedRoot = mRootRule->clone();
1048 : :
1049 : : // normally with clone() the individual rules get new keys (UUID), but here we want to keep
1050 : : // the tree of rules intact, so that other components that may use the rule keys work nicely (e.g. map themes)
1051 : 0 : clonedRoot->setRuleKey( mRootRule->ruleKey() );
1052 : 0 : RuleList origDescendants = mRootRule->descendants();
1053 : 0 : RuleList clonedDescendants = clonedRoot->descendants();
1054 : : Q_ASSERT( origDescendants.count() == clonedDescendants.count() );
1055 : 0 : for ( int i = 0; i < origDescendants.count(); ++i )
1056 : 0 : clonedDescendants[i]->setRuleKey( origDescendants[i]->ruleKey() );
1057 : :
1058 : 0 : QgsRuleBasedRenderer *r = new QgsRuleBasedRenderer( clonedRoot );
1059 : :
1060 : 0 : r->setUsingSymbolLevels( usingSymbolLevels() );
1061 : 0 : copyRendererData( r );
1062 : 0 : return r;
1063 : 0 : }
1064 : :
1065 : 0 : void QgsRuleBasedRenderer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
1066 : : {
1067 : 0 : mRootRule->toSld( doc, element, props );
1068 : 0 : }
1069 : :
1070 : : // TODO: ideally this function should be removed in favor of legendSymbol(ogy)Items
1071 : 0 : QgsSymbolList QgsRuleBasedRenderer::symbols( QgsRenderContext &context ) const
1072 : : {
1073 : 0 : return mRootRule->symbols( context );
1074 : : }
1075 : :
1076 : 0 : QDomElement QgsRuleBasedRenderer::save( QDomDocument &doc, const QgsReadWriteContext &context )
1077 : : {
1078 : 0 : QDomElement rendererElem = doc.createElement( RENDERER_TAG_NAME );
1079 : 0 : rendererElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "RuleRenderer" ) );
1080 : 0 : rendererElem.setAttribute( QStringLiteral( "symbollevels" ), ( mUsingSymbolLevels ? QStringLiteral( "1" ) : QStringLiteral( "0" ) ) );
1081 : 0 : rendererElem.setAttribute( QStringLiteral( "forceraster" ), ( mForceRaster ? QStringLiteral( "1" ) : QStringLiteral( "0" ) ) );
1082 : :
1083 : 0 : QgsSymbolMap symbols;
1084 : :
1085 : 0 : QDomElement rulesElem = mRootRule->save( doc, symbols );
1086 : 0 : rulesElem.setTagName( QStringLiteral( "rules" ) ); // instead of just "rule"
1087 : 0 : rendererElem.appendChild( rulesElem );
1088 : :
1089 : 0 : QDomElement symbolsElem = QgsSymbolLayerUtils::saveSymbols( symbols, QStringLiteral( "symbols" ), doc, context );
1090 : 0 : rendererElem.appendChild( symbolsElem );
1091 : :
1092 : 0 : if ( mPaintEffect && !QgsPaintEffectRegistry::isDefaultStack( mPaintEffect ) )
1093 : 0 : mPaintEffect->saveProperties( doc, rendererElem );
1094 : :
1095 : 0 : if ( !mOrderBy.isEmpty() )
1096 : : {
1097 : 0 : QDomElement orderBy = doc.createElement( QStringLiteral( "orderby" ) );
1098 : 0 : mOrderBy.save( orderBy );
1099 : 0 : rendererElem.appendChild( orderBy );
1100 : 0 : }
1101 : 0 : rendererElem.setAttribute( QStringLiteral( "enableorderby" ), ( mOrderByEnabled ? QStringLiteral( "1" ) : QStringLiteral( "0" ) ) );
1102 : :
1103 : 0 : return rendererElem;
1104 : 0 : }
1105 : :
1106 : 0 : bool QgsRuleBasedRenderer::legendSymbolItemsCheckable() const
1107 : : {
1108 : 0 : return true;
1109 : : }
1110 : :
1111 : 0 : bool QgsRuleBasedRenderer::legendSymbolItemChecked( const QString &key )
1112 : : {
1113 : 0 : Rule *rule = mRootRule->findRuleByKey( key );
1114 : 0 : return rule ? rule->active() : true;
1115 : : }
1116 : :
1117 : 0 : void QgsRuleBasedRenderer::checkLegendSymbolItem( const QString &key, bool state )
1118 : : {
1119 : 0 : Rule *rule = mRootRule->findRuleByKey( key );
1120 : 0 : if ( rule )
1121 : 0 : rule->setActive( state );
1122 : 0 : }
1123 : :
1124 : 0 : void QgsRuleBasedRenderer::setLegendSymbolItem( const QString &key, QgsSymbol *symbol )
1125 : : {
1126 : 0 : Rule *rule = mRootRule->findRuleByKey( key );
1127 : 0 : if ( rule )
1128 : 0 : rule->setSymbol( symbol );
1129 : : else
1130 : 0 : delete symbol;
1131 : 0 : }
1132 : :
1133 : 0 : QgsLegendSymbolList QgsRuleBasedRenderer::legendSymbolItems() const
1134 : : {
1135 : 0 : return mRootRule->legendSymbolItems();
1136 : : }
1137 : :
1138 : :
1139 : 0 : QgsFeatureRenderer *QgsRuleBasedRenderer::create( QDomElement &element, const QgsReadWriteContext &context )
1140 : : {
1141 : : // load symbols
1142 : 0 : QDomElement symbolsElem = element.firstChildElement( QStringLiteral( "symbols" ) );
1143 : 0 : if ( symbolsElem.isNull() )
1144 : 0 : return nullptr;
1145 : :
1146 : 0 : QgsSymbolMap symbolMap = QgsSymbolLayerUtils::loadSymbols( symbolsElem, context );
1147 : :
1148 : 0 : QDomElement rulesElem = element.firstChildElement( QStringLiteral( "rules" ) );
1149 : :
1150 : 0 : Rule *root = Rule::create( rulesElem, symbolMap );
1151 : 0 : if ( !root )
1152 : 0 : return nullptr;
1153 : :
1154 : 0 : QgsRuleBasedRenderer *r = new QgsRuleBasedRenderer( root );
1155 : :
1156 : : // delete symbols if there are any more
1157 : 0 : QgsSymbolLayerUtils::clearSymbolMap( symbolMap );
1158 : :
1159 : 0 : return r;
1160 : 0 : }
1161 : :
1162 : 0 : QgsFeatureRenderer *QgsRuleBasedRenderer::createFromSld( QDomElement &element, QgsWkbTypes::GeometryType geomType )
1163 : : {
1164 : : // retrieve child rules
1165 : 0 : Rule *root = nullptr;
1166 : :
1167 : 0 : QDomElement ruleElem = element.firstChildElement( QStringLiteral( "Rule" ) );
1168 : 0 : while ( !ruleElem.isNull() )
1169 : : {
1170 : 0 : Rule *child = Rule::createFromSld( ruleElem, geomType );
1171 : 0 : if ( child )
1172 : : {
1173 : : // create the root rule if not done before
1174 : 0 : if ( !root )
1175 : 0 : root = new Rule( nullptr );
1176 : :
1177 : 0 : root->appendChild( child );
1178 : 0 : }
1179 : :
1180 : 0 : ruleElem = ruleElem.nextSiblingElement( QStringLiteral( "Rule" ) );
1181 : : }
1182 : :
1183 : 0 : if ( !root )
1184 : : {
1185 : : // no valid rules was found
1186 : 0 : return nullptr;
1187 : : }
1188 : :
1189 : : // create and return the new renderer
1190 : 0 : return new QgsRuleBasedRenderer( root );
1191 : 0 : }
1192 : :
1193 : : #include "qgscategorizedsymbolrenderer.h"
1194 : : #include "qgsgraduatedsymbolrenderer.h"
1195 : :
1196 : 0 : void QgsRuleBasedRenderer::refineRuleCategories( QgsRuleBasedRenderer::Rule *initialRule, QgsCategorizedSymbolRenderer *r )
1197 : : {
1198 : 0 : QString attr = r->classAttribute();
1199 : : // categorizedAttr could be either an attribute name or an expression.
1200 : : // the only way to differentiate is to test it as an expression...
1201 : 0 : QgsExpression testExpr( attr );
1202 : 0 : if ( testExpr.hasParserError() || ( testExpr.isField() && !attr.startsWith( '\"' ) ) )
1203 : : {
1204 : : //not an expression, so need to quote column name
1205 : 0 : attr = QgsExpression::quotedColumnRef( attr );
1206 : 0 : }
1207 : :
1208 : 0 : const auto constCategories = r->categories();
1209 : 0 : for ( const QgsRendererCategory &cat : constCategories )
1210 : : {
1211 : 0 : QString value;
1212 : : // not quoting numbers saves a type cast
1213 : 0 : if ( cat.value().type() == QVariant::Int )
1214 : 0 : value = cat.value().toString();
1215 : 0 : else if ( cat.value().type() == QVariant::Double )
1216 : : // we loose precision here - so we may miss some categories :-(
1217 : : // TODO: have a possibility to construct expressions directly as a parse tree to avoid loss of precision
1218 : 0 : value = QString::number( cat.value().toDouble(), 'f', 4 );
1219 : : else
1220 : 0 : value = QgsExpression::quotedString( cat.value().toString() );
1221 : 0 : QString filter = QStringLiteral( "%1 = %2" ).arg( attr, value );
1222 : 0 : QString label = filter;
1223 : 0 : initialRule->appendChild( new Rule( cat.symbol()->clone(), 0, 0, filter, label ) );
1224 : 0 : }
1225 : 0 : }
1226 : :
1227 : 0 : void QgsRuleBasedRenderer::refineRuleRanges( QgsRuleBasedRenderer::Rule *initialRule, QgsGraduatedSymbolRenderer *r )
1228 : : {
1229 : 0 : QString attr = r->classAttribute();
1230 : : // categorizedAttr could be either an attribute name or an expression.
1231 : : // the only way to differentiate is to test it as an expression...
1232 : 0 : QgsExpression testExpr( attr );
1233 : 0 : if ( testExpr.hasParserError() || ( testExpr.isField() && !attr.startsWith( '\"' ) ) )
1234 : : {
1235 : : //not an expression, so need to quote column name
1236 : 0 : attr = QgsExpression::quotedColumnRef( attr );
1237 : 0 : }
1238 : 0 : else if ( !testExpr.isField() )
1239 : : {
1240 : : //otherwise wrap expression in brackets
1241 : 0 : attr = QStringLiteral( "(%1)" ).arg( attr );
1242 : 0 : }
1243 : :
1244 : 0 : bool firstRange = true;
1245 : 0 : const auto constRanges = r->ranges();
1246 : 0 : for ( const QgsRendererRange &rng : constRanges )
1247 : : {
1248 : : // due to the loss of precision in double->string conversion we may miss out values at the limit of the range
1249 : : // TODO: have a possibility to construct expressions directly as a parse tree to avoid loss of precision
1250 : 0 : QString filter = QStringLiteral( "%1 %2 %3 AND %1 <= %4" ).arg( attr, firstRange ? QStringLiteral( ">=" ) : QStringLiteral( ">" ),
1251 : 0 : QString::number( rng.lowerValue(), 'f', 4 ),
1252 : 0 : QString::number( rng.upperValue(), 'f', 4 ) );
1253 : 0 : firstRange = false;
1254 : 0 : QString label = rng.label().isEmpty() ? filter : rng.label();
1255 : 0 : initialRule->appendChild( new Rule( rng.symbol()->clone(), 0, 0, filter, label ) );
1256 : 0 : }
1257 : 0 : }
1258 : :
1259 : 0 : void QgsRuleBasedRenderer::refineRuleScales( QgsRuleBasedRenderer::Rule *initialRule, QList<int> scales )
1260 : : {
1261 : 0 : std::sort( scales.begin(), scales.end() ); // make sure the scales are in ascending order
1262 : 0 : double oldScale = initialRule->maximumScale();
1263 : 0 : double maxDenom = initialRule->minimumScale();
1264 : 0 : QgsSymbol *symbol = initialRule->symbol();
1265 : 0 : const auto constScales = scales;
1266 : 0 : for ( int scale : constScales )
1267 : : {
1268 : 0 : if ( initialRule->maximumScale() >= scale )
1269 : 0 : continue; // jump over the first scales out of the interval
1270 : 0 : if ( maxDenom != 0 && maxDenom <= scale )
1271 : 0 : break; // ignore the latter scales out of the interval
1272 : 0 : initialRule->appendChild( new Rule( symbol->clone(), oldScale, scale, QString(), QStringLiteral( "%1 - %2" ).arg( oldScale ).arg( scale ) ) );
1273 : 0 : oldScale = scale;
1274 : : }
1275 : : // last rule
1276 : 0 : initialRule->appendChild( new Rule( symbol->clone(), oldScale, maxDenom, QString(), QStringLiteral( "%1 - %2" ).arg( oldScale ).arg( maxDenom ) ) );
1277 : 0 : }
1278 : :
1279 : 0 : QString QgsRuleBasedRenderer::dump() const
1280 : : {
1281 : 0 : QString msg( QStringLiteral( "Rule-based renderer:\n" ) );
1282 : 0 : msg += mRootRule->dump();
1283 : 0 : return msg;
1284 : 0 : }
1285 : :
1286 : 0 : bool QgsRuleBasedRenderer::willRenderFeature( const QgsFeature &feature, QgsRenderContext &context ) const
1287 : : {
1288 : 0 : return mRootRule->willRenderFeature( feature, &context );
1289 : : }
1290 : :
1291 : 0 : QgsSymbolList QgsRuleBasedRenderer::symbolsForFeature( const QgsFeature &feature, QgsRenderContext &context ) const
1292 : : {
1293 : 0 : return mRootRule->symbolsForFeature( feature, &context );
1294 : : }
1295 : :
1296 : 0 : QgsSymbolList QgsRuleBasedRenderer::originalSymbolsForFeature( const QgsFeature &feature, QgsRenderContext &context ) const
1297 : : {
1298 : 0 : return mRootRule->symbolsForFeature( feature, &context );
1299 : : }
1300 : :
1301 : 0 : QSet< QString > QgsRuleBasedRenderer::legendKeysForFeature( const QgsFeature &feature, QgsRenderContext &context ) const
1302 : : {
1303 : 0 : return mRootRule->legendKeysForFeature( feature, &context );
1304 : : }
1305 : :
1306 : 0 : bool QgsRuleBasedRenderer::accept( QgsStyleEntityVisitorInterface *visitor ) const
1307 : : {
1308 : 0 : return mRootRule->accept( visitor );
1309 : : }
1310 : :
1311 : 0 : QgsRuleBasedRenderer *QgsRuleBasedRenderer::convertFromRenderer( const QgsFeatureRenderer *renderer, QgsVectorLayer *layer )
1312 : : {
1313 : 0 : std::unique_ptr< QgsRuleBasedRenderer > r;
1314 : 0 : if ( renderer->type() == QLatin1String( "RuleRenderer" ) )
1315 : : {
1316 : 0 : r.reset( dynamic_cast<QgsRuleBasedRenderer *>( renderer->clone() ) );
1317 : 0 : }
1318 : 0 : else if ( renderer->type() == QLatin1String( "singleSymbol" ) )
1319 : : {
1320 : 0 : const QgsSingleSymbolRenderer *singleSymbolRenderer = dynamic_cast<const QgsSingleSymbolRenderer *>( renderer );
1321 : 0 : if ( !singleSymbolRenderer )
1322 : 0 : return nullptr;
1323 : :
1324 : 0 : std::unique_ptr< QgsSymbol > origSymbol( singleSymbolRenderer->symbol()->clone() );
1325 : 0 : r = std::make_unique< QgsRuleBasedRenderer >( origSymbol.release() );
1326 : 0 : }
1327 : 0 : else if ( renderer->type() == QLatin1String( "categorizedSymbol" ) )
1328 : : {
1329 : 0 : const QgsCategorizedSymbolRenderer *categorizedRenderer = dynamic_cast<const QgsCategorizedSymbolRenderer *>( renderer );
1330 : 0 : if ( !categorizedRenderer )
1331 : 0 : return nullptr;
1332 : :
1333 : 0 : QString attr = categorizedRenderer->classAttribute();
1334 : : // categorizedAttr could be either an attribute name or an expression.
1335 : : // the only way to differentiate is to test it as an expression...
1336 : 0 : QgsExpression testExpr( attr );
1337 : 0 : if ( testExpr.hasParserError() || ( testExpr.isField() && !attr.startsWith( '\"' ) ) )
1338 : : {
1339 : : //not an expression, so need to quote column name
1340 : 0 : attr = QgsExpression::quotedColumnRef( attr );
1341 : 0 : }
1342 : :
1343 : 0 : std::unique_ptr< QgsRuleBasedRenderer::Rule > rootrule = std::make_unique< QgsRuleBasedRenderer::Rule >( nullptr );
1344 : :
1345 : 0 : QString expression;
1346 : 0 : QString value;
1347 : 0 : QgsRendererCategory category;
1348 : 0 : for ( const QgsRendererCategory &category : categorizedRenderer->categories() )
1349 : : {
1350 : 0 : std::unique_ptr< QgsRuleBasedRenderer::Rule > rule = std::make_unique< QgsRuleBasedRenderer::Rule >( nullptr );
1351 : :
1352 : 0 : rule->setLabel( category.label() );
1353 : :
1354 : : //We first define the rule corresponding to the category
1355 : 0 : if ( category.value().type() == QVariant::List )
1356 : : {
1357 : 0 : QStringList values;
1358 : 0 : const QVariantList list = category.value().toList();
1359 : 0 : for ( const QVariant &v : list )
1360 : : {
1361 : : //If the value is a number, we can use it directly, otherwise we need to quote it in the rule
1362 : 0 : if ( QVariant( v ).convert( QVariant::Double ) )
1363 : : {
1364 : 0 : values << v.toString();
1365 : 0 : }
1366 : : else
1367 : : {
1368 : 0 : values << QgsExpression::quotedString( v.toString() );
1369 : : }
1370 : : }
1371 : :
1372 : 0 : if ( values.empty() )
1373 : : {
1374 : 0 : expression = QStringLiteral( "ELSE" );
1375 : 0 : }
1376 : : else
1377 : : {
1378 : 0 : expression = QStringLiteral( "%1 IN (%2)" ).arg( attr, values.join( ',' ) );
1379 : : }
1380 : 0 : }
1381 : : else
1382 : : {
1383 : : //If the value is a number, we can use it directly, otherwise we need to quote it in the rule
1384 : 0 : if ( category.value().convert( QVariant::Double ) )
1385 : : {
1386 : 0 : value = category.value().toString();
1387 : 0 : }
1388 : : else
1389 : : {
1390 : 0 : value = QgsExpression::quotedString( category.value().toString() );
1391 : : }
1392 : :
1393 : : //An empty category is equivalent to the ELSE keyword
1394 : 0 : if ( value == QLatin1String( "''" ) )
1395 : : {
1396 : 0 : expression = QStringLiteral( "ELSE" );
1397 : 0 : }
1398 : : else
1399 : : {
1400 : 0 : expression = QStringLiteral( "%1 = %2" ).arg( attr, value );
1401 : : }
1402 : : }
1403 : 0 : rule->setFilterExpression( expression );
1404 : :
1405 : : //Then we construct an equivalent symbol.
1406 : : //Ideally we could simply copy the symbol, but the categorized renderer allows a separate interface to specify
1407 : : //data dependent area and rotation, so we need to convert these to obtain the same rendering
1408 : :
1409 : 0 : std::unique_ptr< QgsSymbol > origSymbol( category.symbol()->clone() );
1410 : 0 : rule->setSymbol( origSymbol.release() );
1411 : :
1412 : 0 : rootrule->appendChild( rule.release() );
1413 : 0 : }
1414 : :
1415 : 0 : r = std::make_unique< QgsRuleBasedRenderer >( rootrule.release() );
1416 : 0 : }
1417 : 0 : else if ( renderer->type() == QLatin1String( "graduatedSymbol" ) )
1418 : : {
1419 : 0 : const QgsGraduatedSymbolRenderer *graduatedRenderer = dynamic_cast<const QgsGraduatedSymbolRenderer *>( renderer );
1420 : 0 : if ( !graduatedRenderer )
1421 : 0 : return nullptr;
1422 : :
1423 : 0 : QString attr = graduatedRenderer->classAttribute();
1424 : : // categorizedAttr could be either an attribute name or an expression.
1425 : : // the only way to differentiate is to test it as an expression...
1426 : 0 : QgsExpression testExpr( attr );
1427 : 0 : if ( testExpr.hasParserError() || ( testExpr.isField() && !attr.startsWith( '\"' ) ) )
1428 : : {
1429 : : //not an expression, so need to quote column name
1430 : 0 : attr = QgsExpression::quotedColumnRef( attr );
1431 : 0 : }
1432 : 0 : else if ( !testExpr.isField() )
1433 : : {
1434 : : //otherwise wrap expression in brackets
1435 : 0 : attr = QStringLiteral( "(%1)" ).arg( attr );
1436 : 0 : }
1437 : :
1438 : 0 : std::unique_ptr< QgsRuleBasedRenderer::Rule > rootrule = std::make_unique< QgsRuleBasedRenderer::Rule >( nullptr );
1439 : :
1440 : 0 : QString expression;
1441 : 0 : QgsRendererRange range;
1442 : 0 : for ( int i = 0; i < graduatedRenderer->ranges().size(); ++i )
1443 : : {
1444 : 0 : range = graduatedRenderer->ranges().value( i );
1445 : 0 : std::unique_ptr< QgsRuleBasedRenderer::Rule > rule = std::make_unique< QgsRuleBasedRenderer::Rule >( nullptr );
1446 : 0 : rule->setLabel( range.label() );
1447 : 0 : if ( i == 0 )//The lower boundary of the first range is included, while it is excluded for the others
1448 : : {
1449 : 0 : expression = attr + " >= " + QString::number( range.lowerValue(), 'f' ) + " AND " + \
1450 : 0 : attr + " <= " + QString::number( range.upperValue(), 'f' );
1451 : 0 : }
1452 : : else
1453 : : {
1454 : 0 : expression = attr + " > " + QString::number( range.lowerValue(), 'f' ) + " AND " + \
1455 : 0 : attr + " <= " + QString::number( range.upperValue(), 'f' );
1456 : : }
1457 : 0 : rule->setFilterExpression( expression );
1458 : :
1459 : : //Then we construct an equivalent symbol.
1460 : : //Ideally we could simply copy the symbol, but the graduated renderer allows a separate interface to specify
1461 : : //data dependent area and rotation, so we need to convert these to obtain the same rendering
1462 : :
1463 : 0 : std::unique_ptr< QgsSymbol > symbol( range.symbol()->clone() );
1464 : 0 : rule->setSymbol( symbol.release() );
1465 : :
1466 : 0 : rootrule->appendChild( rule.release() );
1467 : 0 : }
1468 : :
1469 : 0 : r = std::make_unique< QgsRuleBasedRenderer >( rootrule.release() );
1470 : 0 : }
1471 : 0 : else if ( renderer->type() == QLatin1String( "pointDisplacement" ) || renderer->type() == QLatin1String( "pointCluster" ) )
1472 : : {
1473 : 0 : if ( const QgsPointDistanceRenderer *pointDistanceRenderer = dynamic_cast<const QgsPointDistanceRenderer *>( renderer ) )
1474 : 0 : return convertFromRenderer( pointDistanceRenderer->embeddedRenderer() );
1475 : 0 : }
1476 : 0 : else if ( renderer->type() == QLatin1String( "invertedPolygonRenderer" ) )
1477 : : {
1478 : 0 : if ( const QgsInvertedPolygonRenderer *invertedPolygonRenderer = dynamic_cast<const QgsInvertedPolygonRenderer *>( renderer ) )
1479 : 0 : r.reset( convertFromRenderer( invertedPolygonRenderer->embeddedRenderer() ) );
1480 : 0 : }
1481 : 0 : else if ( renderer->type() == QLatin1String( "mergedFeatureRenderer" ) )
1482 : : {
1483 : 0 : if ( const QgsMergedFeatureRenderer *mergedRenderer = dynamic_cast<const QgsMergedFeatureRenderer *>( renderer ) )
1484 : 0 : r.reset( convertFromRenderer( mergedRenderer->embeddedRenderer() ) );
1485 : 0 : }
1486 : 0 : else if ( renderer->type() == QLatin1String( "embeddedSymbol" ) && layer )
1487 : : {
1488 : 0 : const QgsEmbeddedSymbolRenderer *embeddedRenderer = dynamic_cast<const QgsEmbeddedSymbolRenderer *>( renderer );
1489 : :
1490 : 0 : std::unique_ptr< QgsRuleBasedRenderer::Rule > rootrule = std::make_unique< QgsRuleBasedRenderer::Rule >( nullptr );
1491 : :
1492 : 0 : QgsFeatureRequest req;
1493 : 0 : req.setFlags( QgsFeatureRequest::EmbeddedSymbols | QgsFeatureRequest::NoGeometry );
1494 : 0 : req.setNoAttributes();
1495 : 0 : QgsFeatureIterator it = layer->getFeatures( req );
1496 : 0 : QgsFeature feature;
1497 : 0 : while ( it.nextFeature( feature ) && rootrule->children().size() < 500 )
1498 : : {
1499 : 0 : if ( feature.embeddedSymbol() )
1500 : : {
1501 : 0 : std::unique_ptr< QgsRuleBasedRenderer::Rule > rule = std::make_unique< QgsRuleBasedRenderer::Rule >( nullptr );
1502 : 0 : rule->setFilterExpression( QStringLiteral( "$id=%1" ).arg( feature.id() ) );
1503 : 0 : rule->setLabel( QString::number( feature.id() ) );
1504 : 0 : rule->setSymbol( feature.embeddedSymbol()->clone() );
1505 : 0 : rootrule->appendChild( rule.release() );
1506 : 0 : }
1507 : : }
1508 : :
1509 : 0 : std::unique_ptr< QgsRuleBasedRenderer::Rule > rule = std::make_unique< QgsRuleBasedRenderer::Rule >( nullptr );
1510 : 0 : rule->setFilterExpression( QStringLiteral( "ELSE" ) );
1511 : 0 : rule->setLabel( QObject::tr( "All other features" ) );
1512 : 0 : rule->setSymbol( embeddedRenderer->defaultSymbol()->clone() );
1513 : 0 : rootrule->appendChild( rule.release() );
1514 : :
1515 : 0 : r = std::make_unique< QgsRuleBasedRenderer >( rootrule.release() );
1516 : 0 : }
1517 : :
1518 : 0 : if ( r )
1519 : : {
1520 : 0 : r->setOrderBy( renderer->orderBy() );
1521 : 0 : r->setOrderByEnabled( renderer->orderByEnabled() );
1522 : 0 : }
1523 : :
1524 : 0 : return r.release();
1525 : 0 : }
1526 : :
1527 : 0 : void QgsRuleBasedRenderer::convertToDataDefinedSymbology( QgsSymbol *symbol, const QString &sizeScaleField, const QString &rotationField )
1528 : : {
1529 : 0 : QString sizeExpression;
1530 : 0 : switch ( symbol->type() )
1531 : : {
1532 : : case QgsSymbol::Marker:
1533 : 0 : for ( int j = 0; j < symbol->symbolLayerCount(); ++j )
1534 : : {
1535 : 0 : QgsMarkerSymbolLayer *msl = static_cast<QgsMarkerSymbolLayer *>( symbol->symbolLayer( j ) );
1536 : 0 : if ( ! sizeScaleField.isEmpty() )
1537 : : {
1538 : 0 : sizeExpression = QStringLiteral( "%1*(%2)" ).arg( msl->size() ).arg( sizeScaleField );
1539 : 0 : msl->setDataDefinedProperty( QgsSymbolLayer::PropertySize, QgsProperty::fromExpression( sizeExpression ) );
1540 : 0 : }
1541 : 0 : if ( ! rotationField.isEmpty() )
1542 : : {
1543 : 0 : msl->setDataDefinedProperty( QgsSymbolLayer::PropertyAngle, QgsProperty::fromField( rotationField ) );
1544 : 0 : }
1545 : 0 : }
1546 : 0 : break;
1547 : : case QgsSymbol::Line:
1548 : 0 : if ( ! sizeScaleField.isEmpty() )
1549 : : {
1550 : 0 : for ( int j = 0; j < symbol->symbolLayerCount(); ++j )
1551 : : {
1552 : 0 : if ( symbol->symbolLayer( j )->layerType() == QLatin1String( "SimpleLine" ) )
1553 : : {
1554 : 0 : QgsLineSymbolLayer *lsl = static_cast<QgsLineSymbolLayer *>( symbol->symbolLayer( j ) );
1555 : 0 : sizeExpression = QStringLiteral( "%1*(%2)" ).arg( lsl->width() ).arg( sizeScaleField );
1556 : 0 : lsl->setDataDefinedProperty( QgsSymbolLayer::PropertyStrokeWidth, QgsProperty::fromExpression( sizeExpression ) );
1557 : 0 : }
1558 : 0 : if ( symbol->symbolLayer( j )->layerType() == QLatin1String( "MarkerLine" ) )
1559 : : {
1560 : 0 : QgsSymbol *marker = symbol->symbolLayer( j )->subSymbol();
1561 : 0 : for ( int k = 0; k < marker->symbolLayerCount(); ++k )
1562 : : {
1563 : 0 : QgsMarkerSymbolLayer *msl = static_cast<QgsMarkerSymbolLayer *>( marker->symbolLayer( k ) );
1564 : 0 : sizeExpression = QStringLiteral( "%1*(%2)" ).arg( msl->size() ).arg( sizeScaleField );
1565 : 0 : msl->setDataDefinedProperty( QgsSymbolLayer::PropertySize, QgsProperty::fromExpression( sizeExpression ) );
1566 : 0 : }
1567 : 0 : }
1568 : 0 : }
1569 : 0 : }
1570 : 0 : break;
1571 : : default:
1572 : 0 : break;
1573 : : }
1574 : 0 : }
|