QGIS API Documentation  3.4.15-Madeira (e83d02e274)
qgscategorizedsymbolrenderer.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgscategorizedsymbolrenderer.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 #include <algorithm>
16 
18 
20 #include "qgssymbol.h"
21 #include "qgssymbollayerutils.h"
22 #include "qgscolorramp.h"
25 #include "qgspainteffect.h"
26 #include "qgspainteffectregistry.h"
27 #include "qgssymbollayer.h"
28 #include "qgsfeature.h"
29 #include "qgsvectorlayer.h"
30 #include "qgslogger.h"
31 #include "qgsproperty.h"
32 #include "qgsstyle.h"
33 
34 #include <QDomDocument>
35 #include <QDomElement>
36 #include <QSettings> // for legend
37 
38 QgsRendererCategory::QgsRendererCategory( const QVariant &value, QgsSymbol *symbol, const QString &label, bool render )
39  : mValue( value )
40  , mSymbol( symbol )
41  , mLabel( label )
42  , mRender( render )
43 {
44 }
45 
47  : mValue( cat.mValue )
48  , mSymbol( cat.mSymbol ? cat.mSymbol->clone() : nullptr )
49  , mLabel( cat.mLabel )
50  , mRender( cat.mRender )
51 {
52 }
53 
54 // copy+swap idion, the copy is done through the 'pass by value'
56 {
57  swap( cat );
58  return *this;
59 }
60 
62 {
63  std::swap( mValue, cat.mValue );
64  std::swap( mSymbol, cat.mSymbol );
65  std::swap( mLabel, cat.mLabel );
66 }
67 
69 {
70  return mValue;
71 }
72 
74 {
75  return mSymbol.get();
76 }
77 
79 {
80  return mLabel;
81 }
82 
84 {
85  return mRender;
86 }
87 
88 void QgsRendererCategory::setValue( const QVariant &value )
89 {
90  mValue = value;
91 }
92 
94 {
95  if ( mSymbol.get() != s ) mSymbol.reset( s );
96 }
97 
98 void QgsRendererCategory::setLabel( const QString &label )
99 {
100  mLabel = label;
101 }
102 
104 {
105  mRender = render;
106 }
107 
109 {
110  return QStringLiteral( "%1::%2::%3:%4\n" ).arg( mValue.toString(), mLabel, mSymbol->dump() ).arg( mRender );
111 }
112 
113 void QgsRendererCategory::toSld( QDomDocument &doc, QDomElement &element, QgsStringMap props ) const
114 {
115  if ( !mSymbol.get() || props.value( QStringLiteral( "attribute" ), QString() ).isEmpty() )
116  return;
117 
118  QString attrName = props[ QStringLiteral( "attribute" )];
119 
120  QDomElement ruleElem = doc.createElement( QStringLiteral( "se:Rule" ) );
121  element.appendChild( ruleElem );
122 
123  QDomElement nameElem = doc.createElement( QStringLiteral( "se:Name" ) );
124  nameElem.appendChild( doc.createTextNode( mLabel ) );
125  ruleElem.appendChild( nameElem );
126 
127  QDomElement descrElem = doc.createElement( QStringLiteral( "se:Description" ) );
128  QDomElement titleElem = doc.createElement( QStringLiteral( "se:Title" ) );
129  QString descrStr = QStringLiteral( "%1 is '%2'" ).arg( attrName, mValue.toString() );
130  titleElem.appendChild( doc.createTextNode( !mLabel.isEmpty() ? mLabel : descrStr ) );
131  descrElem.appendChild( titleElem );
132  ruleElem.appendChild( descrElem );
133 
134  // create the ogc:Filter for the range
135  QString filterFunc;
136  if ( mValue.isNull() || mValue.toString().isEmpty() )
137  {
138  filterFunc = QStringLiteral( "%1 = '%2' or %1 is null" )
139  .arg( attrName.replace( '\"', QLatin1String( "\"\"" ) ),
140  mValue.toString().replace( '\'', QLatin1String( "''" ) ) );
141  }
142  else
143  {
144  filterFunc = QStringLiteral( "%1 = '%2'" )
145  .arg( attrName.replace( '\"', QLatin1String( "\"\"" ) ),
146  mValue.toString().replace( '\'', QLatin1String( "''" ) ) );
147  }
148 
149  QgsSymbolLayerUtils::createFunctionElement( doc, ruleElem, filterFunc );
150 
151  // add the mix/max scale denoms if we got any from the callers
152  QgsSymbolLayerUtils::applyScaleDependency( doc, ruleElem, props );
153 
154  mSymbol->toSld( doc, ruleElem, props );
155 }
156 
158 
160  : QgsFeatureRenderer( QStringLiteral( "categorizedSymbol" ) )
161  , mAttrName( attrName )
162 {
163  //important - we need a deep copy of the categories list, not a shared copy. This is required because
164  //QgsRendererCategory::symbol() is marked const, and so retrieving the symbol via this method does not
165  //trigger a detachment and copy of mCategories BUT that same method CAN be used to modify a symbol in place
166  Q_FOREACH ( const QgsRendererCategory &cat, categories )
167  {
168  if ( !cat.symbol() )
169  {
170  QgsDebugMsg( QStringLiteral( "invalid symbol in a category! ignoring..." ) );
171  }
172  mCategories << cat;
173  }
174 }
175 
177 {
178  mSymbolHash.clear();
179 
180  for ( int i = 0; i < mCategories.size(); ++i )
181  {
182  const QgsRendererCategory &cat = mCategories.at( i );
183  mSymbolHash.insert( cat.value().toString(), ( cat.renderState() || mCounting ) ? cat.symbol() : nullptr );
184  }
185 }
186 
188 {
189  return nullptr;
190 }
191 
193 {
194  bool found = false;
195  return symbolForValue( value, found );
196 }
197 
198 QgsSymbol *QgsCategorizedSymbolRenderer::symbolForValue( const QVariant &value, bool &foundMatchingSymbol ) const
199 {
200  foundMatchingSymbol = false;
201 
202  // TODO: special case for int, double
203  QHash<QString, QgsSymbol *>::const_iterator it = mSymbolHash.constFind( value.isNull() ? QString() : value.toString() );
204  if ( it == mSymbolHash.constEnd() )
205  {
206  if ( mSymbolHash.isEmpty() )
207  {
208  QgsDebugMsg( QStringLiteral( "there are no hashed symbols!!!" ) );
209  }
210  else
211  {
212  QgsDebugMsgLevel( "attribute value not found: " + value.toString(), 3 );
213  }
214  return nullptr;
215  }
216 
217  foundMatchingSymbol = true;
218 
219  return *it;
220 }
221 
223 {
224  return originalSymbolForFeature( feature, context );
225 }
226 
227 QVariant QgsCategorizedSymbolRenderer::valueForFeature( const QgsFeature &feature, QgsRenderContext &context ) const
228 {
229  QgsAttributes attrs = feature.attributes();
230  QVariant value;
231  if ( mAttrNum == -1 )
232  {
233  Q_ASSERT( mExpression );
234 
235  value = mExpression->evaluate( &context.expressionContext() );
236  }
237  else
238  {
239  value = attrs.value( mAttrNum );
240  }
241 
242  return value;
243 }
244 
246 {
247  QVariant value = valueForFeature( feature, context );
248 
249  bool foundCategory = false;
250  // find the right symbol for the category
251  QgsSymbol *symbol = symbolForValue( value, foundCategory );
252 
253  if ( !foundCategory )
254  {
255  // if no symbol found, use default symbol
256  return symbolForValue( QVariant( "" ), foundCategory );
257  }
258 
259  return symbol;
260 }
261 
262 
264 {
265  for ( int i = 0; i < mCategories.count(); i++ )
266  {
267  if ( mCategories[i].value() == val )
268  return i;
269  }
270  return -1;
271 }
272 
274 {
275  int idx = -1;
276  for ( int i = 0; i < mCategories.count(); i++ )
277  {
278  if ( mCategories[i].label() == val )
279  {
280  if ( idx != -1 )
281  return -1;
282  else
283  idx = i;
284  }
285  }
286  return idx;
287 }
288 
289 bool QgsCategorizedSymbolRenderer::updateCategoryValue( int catIndex, const QVariant &value )
290 {
291  if ( catIndex < 0 || catIndex >= mCategories.size() )
292  return false;
293  mCategories[catIndex].setValue( value );
294  return true;
295 }
296 
298 {
299  if ( catIndex < 0 || catIndex >= mCategories.size() )
300  return false;
301  mCategories[catIndex].setSymbol( symbol );
302  return true;
303 }
304 
305 bool QgsCategorizedSymbolRenderer::updateCategoryLabel( int catIndex, const QString &label )
306 {
307  if ( catIndex < 0 || catIndex >= mCategories.size() )
308  return false;
309  mCategories[catIndex].setLabel( label );
310  return true;
311 }
312 
314 {
315  if ( catIndex < 0 || catIndex >= mCategories.size() )
316  return false;
317  mCategories[catIndex].setRenderState( render );
318  return true;
319 }
320 
322 {
323  if ( !cat.symbol() )
324  {
325  QgsDebugMsg( QStringLiteral( "invalid symbol in a category! ignoring..." ) );
326  return;
327  }
328 
329  mCategories.append( cat );
330 }
331 
333 {
334  if ( catIndex < 0 || catIndex >= mCategories.size() )
335  return false;
336 
337  mCategories.removeAt( catIndex );
338  return true;
339 }
340 
342 {
343  mCategories.clear();
344 }
345 
347 {
348  if ( from < 0 || from >= mCategories.size() || to < 0 || to >= mCategories.size() ) return;
349  mCategories.move( from, to );
350 }
351 
353 {
354  return qgsVariantLessThan( c1.value(), c2.value() );
355 }
357 {
358  return qgsVariantGreaterThan( c1.value(), c2.value() );
359 }
360 
361 void QgsCategorizedSymbolRenderer::sortByValue( Qt::SortOrder order )
362 {
363  if ( order == Qt::AscendingOrder )
364  {
365  std::sort( mCategories.begin(), mCategories.end(), valueLessThan );
366  }
367  else
368  {
369  std::sort( mCategories.begin(), mCategories.end(), valueGreaterThan );
370  }
371 }
372 
374 {
375  return QString::localeAwareCompare( c1.label(), c2.label() ) < 0;
376 }
377 
379 {
380  return !labelLessThan( c1, c2 );
381 }
382 
383 void QgsCategorizedSymbolRenderer::sortByLabel( Qt::SortOrder order )
384 {
385  if ( order == Qt::AscendingOrder )
386  {
387  std::sort( mCategories.begin(), mCategories.end(), labelLessThan );
388  }
389  else
390  {
391  std::sort( mCategories.begin(), mCategories.end(), labelGreaterThan );
392  }
393 }
394 
396 {
397  QgsFeatureRenderer::startRender( context, fields );
398 
399  mCounting = context.rendererScale() == 0.0;
400 
401  // make sure that the hash table is up to date
402  rebuildHash();
403 
404  // find out classification attribute index from name
405  mAttrNum = fields.lookupField( mAttrName );
406  if ( mAttrNum == -1 )
407  {
408  mExpression.reset( new QgsExpression( mAttrName ) );
409  mExpression->prepare( &context.expressionContext() );
410  }
411 
412  Q_FOREACH ( const QgsRendererCategory &cat, mCategories )
413  {
414  cat.symbol()->startRender( context, fields );
415  }
416 }
417 
419 {
421 
422  Q_FOREACH ( const QgsRendererCategory &cat, mCategories )
423  {
424  cat.symbol()->stopRender( context );
425  }
426  mExpression.reset();
427 }
428 
430 {
431  QSet<QString> attributes;
432 
433  // mAttrName can contain either attribute name or an expression.
434  // Sometimes it is not possible to distinguish between those two,
435  // e.g. "a - b" can be both a valid attribute name or expression.
436  // Since we do not have access to fields here, try both options.
437  attributes << mAttrName;
438 
439  QgsExpression testExpr( mAttrName );
440  if ( !testExpr.hasParserError() )
441  attributes.unite( testExpr.referencedColumns() );
442 
443  QgsCategoryList::const_iterator catIt = mCategories.constBegin();
444  for ( ; catIt != mCategories.constEnd(); ++catIt )
445  {
446  QgsSymbol *catSymbol = catIt->symbol();
447  if ( catSymbol )
448  {
449  attributes.unite( catSymbol->usedAttributes( context ) );
450  }
451  }
452  return attributes;
453 }
454 
456 {
457  QgsExpression testExpr( mAttrName );
458  if ( !testExpr.hasParserError() )
459  {
460  QgsExpressionContext context;
461  context.appendScopes( QgsExpressionContextUtils::globalProjectLayerScopes( nullptr ) ); // unfortunately no layer access available!
462  testExpr.prepare( &context );
463  return testExpr.needsGeometry();
464  }
465  return false;
466 }
467 
469 {
470  QString s = QStringLiteral( "CATEGORIZED: idx %1\n" ).arg( mAttrName );
471  for ( int i = 0; i < mCategories.count(); i++ )
472  s += mCategories[i].dump();
473  return s;
474 }
475 
477 {
479  if ( mSourceSymbol )
480  r->setSourceSymbol( mSourceSymbol->clone() );
481  if ( mSourceColorRamp )
482  {
483  r->setSourceColorRamp( mSourceColorRamp->clone() );
484  }
487 
488  copyRendererData( r );
489  return r;
490 }
491 
492 void QgsCategorizedSymbolRenderer::toSld( QDomDocument &doc, QDomElement &element, const QgsStringMap &props ) const
493 {
494  QgsStringMap newProps = props;
495  newProps[ QStringLiteral( "attribute" )] = mAttrName;
496 
497  // create a Rule for each range
498  for ( QgsCategoryList::const_iterator it = mCategories.constBegin(); it != mCategories.constEnd(); ++it )
499  {
500  it->toSld( doc, element, newProps );
501  }
502 }
503 
505 {
506  int attrNum = fields.lookupField( mAttrName );
507  bool isExpression = ( attrNum == -1 );
508 
509  bool hasDefault = false;
510  bool defaultActive = false;
511  bool allActive = true;
512  bool noneActive = true;
513 
514  //we need to build lists of both inactive and active values, as either list may be required
515  //depending on whether the default category is active or not
516  QString activeValues;
517  QString inactiveValues;
518 
519  Q_FOREACH ( const QgsRendererCategory &cat, mCategories )
520  {
521  if ( cat.value() == "" )
522  {
523  hasDefault = true;
524  defaultActive = cat.renderState();
525  }
526 
527  noneActive = noneActive && !cat.renderState();
528  allActive = allActive && cat.renderState();
529 
530  QVariant::Type valType = isExpression ? cat.value().type() : fields.at( attrNum ).type();
531  QString value = QgsExpression::quotedValue( cat.value(), valType );
532 
533  if ( !cat.renderState() )
534  {
535  if ( cat.value() != "" )
536  {
537  if ( !inactiveValues.isEmpty() )
538  inactiveValues.append( ',' );
539 
540  inactiveValues.append( value );
541  }
542  }
543  else
544  {
545  if ( cat.value() != "" )
546  {
547  if ( !activeValues.isEmpty() )
548  activeValues.append( ',' );
549 
550  activeValues.append( value );
551  }
552  }
553  }
554 
555  QString attr = isExpression ? mAttrName : QStringLiteral( "\"%1\"" ).arg( mAttrName );
556 
557  if ( allActive && hasDefault )
558  {
559  return QString();
560  }
561  else if ( noneActive )
562  {
563  return QStringLiteral( "FALSE" );
564  }
565  else if ( defaultActive )
566  {
567  return QStringLiteral( "(%1) NOT IN (%2) OR (%1) IS NULL" ).arg( attr, inactiveValues );
568  }
569  else
570  {
571  return QStringLiteral( "(%1) IN (%2)" ).arg( attr, activeValues );
572  }
573 }
574 
576 {
577  Q_UNUSED( context );
578  QgsSymbolList lst;
579  lst.reserve( mCategories.count() );
580  Q_FOREACH ( const QgsRendererCategory &cat, mCategories )
581  {
582  lst.append( cat.symbol() );
583  }
584  return lst;
585 }
586 
588 {
589  QDomElement symbolsElem = element.firstChildElement( QStringLiteral( "symbols" ) );
590  if ( symbolsElem.isNull() )
591  return nullptr;
592 
593  QDomElement catsElem = element.firstChildElement( QStringLiteral( "categories" ) );
594  if ( catsElem.isNull() )
595  return nullptr;
596 
597  QgsSymbolMap symbolMap = QgsSymbolLayerUtils::loadSymbols( symbolsElem, context );
598  QgsCategoryList cats;
599 
600  QDomElement catElem = catsElem.firstChildElement();
601  while ( !catElem.isNull() )
602  {
603  if ( catElem.tagName() == QLatin1String( "category" ) )
604  {
605  QVariant value = QVariant( catElem.attribute( QStringLiteral( "value" ) ) );
606  QString symbolName = catElem.attribute( QStringLiteral( "symbol" ) );
607  QString label = catElem.attribute( QStringLiteral( "label" ) );
608  bool render = catElem.attribute( QStringLiteral( "render" ) ) != QLatin1String( "false" );
609  if ( symbolMap.contains( symbolName ) )
610  {
611  QgsSymbol *symbol = symbolMap.take( symbolName );
612  cats.append( QgsRendererCategory( value, symbol, label, render ) );
613  }
614  }
615  catElem = catElem.nextSiblingElement();
616  }
617 
618  QString attrName = element.attribute( QStringLiteral( "attr" ) );
619 
621 
622  // delete symbols if there are any more
624 
625  // try to load source symbol (optional)
626  QDomElement sourceSymbolElem = element.firstChildElement( QStringLiteral( "source-symbol" ) );
627  if ( !sourceSymbolElem.isNull() )
628  {
629  QgsSymbolMap sourceSymbolMap = QgsSymbolLayerUtils::loadSymbols( sourceSymbolElem, context );
630  if ( sourceSymbolMap.contains( QStringLiteral( "0" ) ) )
631  {
632  r->setSourceSymbol( sourceSymbolMap.take( QStringLiteral( "0" ) ) );
633  }
634  QgsSymbolLayerUtils::clearSymbolMap( sourceSymbolMap );
635  }
636 
637  // try to load color ramp (optional)
638  QDomElement sourceColorRampElem = element.firstChildElement( QStringLiteral( "colorramp" ) );
639  if ( !sourceColorRampElem.isNull() && sourceColorRampElem.attribute( QStringLiteral( "name" ) ) == QLatin1String( "[source]" ) )
640  {
641  r->setSourceColorRamp( QgsSymbolLayerUtils::loadColorRamp( sourceColorRampElem ) );
642  }
643 
644  QDomElement rotationElem = element.firstChildElement( QStringLiteral( "rotation" ) );
645  if ( !rotationElem.isNull() && !rotationElem.attribute( QStringLiteral( "field" ) ).isEmpty() )
646  {
647  Q_FOREACH ( const QgsRendererCategory &cat, r->mCategories )
648  {
649  convertSymbolRotation( cat.symbol(), rotationElem.attribute( QStringLiteral( "field" ) ) );
650  }
651  if ( r->mSourceSymbol )
652  {
653  convertSymbolRotation( r->mSourceSymbol.get(), rotationElem.attribute( QStringLiteral( "field" ) ) );
654  }
655  }
656 
657  QDomElement sizeScaleElem = element.firstChildElement( QStringLiteral( "sizescale" ) );
658  if ( !sizeScaleElem.isNull() && !sizeScaleElem.attribute( QStringLiteral( "field" ) ).isEmpty() )
659  {
660  Q_FOREACH ( const QgsRendererCategory &cat, r->mCategories )
661  {
663  QgsSymbolLayerUtils::decodeScaleMethod( sizeScaleElem.attribute( QStringLiteral( "scalemethod" ) ) ),
664  sizeScaleElem.attribute( QStringLiteral( "field" ) ) );
665  }
666  if ( r->mSourceSymbol && r->mSourceSymbol->type() == QgsSymbol::Marker )
667  {
669  QgsSymbolLayerUtils::decodeScaleMethod( sizeScaleElem.attribute( QStringLiteral( "scalemethod" ) ) ),
670  sizeScaleElem.attribute( QStringLiteral( "field" ) ) );
671  }
672  }
673 
674  QDomElement ddsLegendSizeElem = element.firstChildElement( QStringLiteral( "data-defined-size-legend" ) );
675  if ( !ddsLegendSizeElem.isNull() )
676  {
677  r->mDataDefinedSizeLegend.reset( QgsDataDefinedSizeLegend::readXml( ddsLegendSizeElem, context ) );
678  }
679 
680  // TODO: symbol levels
681  return r;
682 }
683 
684 QDomElement QgsCategorizedSymbolRenderer::save( QDomDocument &doc, const QgsReadWriteContext &context )
685 {
686  // clazy:skip
687  QDomElement rendererElem = doc.createElement( RENDERER_TAG_NAME );
688  rendererElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "categorizedSymbol" ) );
689  rendererElem.setAttribute( QStringLiteral( "symbollevels" ), ( mUsingSymbolLevels ? QStringLiteral( "1" ) : QStringLiteral( "0" ) ) );
690  rendererElem.setAttribute( QStringLiteral( "forceraster" ), ( mForceRaster ? QStringLiteral( "1" ) : QStringLiteral( "0" ) ) );
691  rendererElem.setAttribute( QStringLiteral( "attr" ), mAttrName );
692 
693  // categories
694  if ( !mCategories.isEmpty() )
695  {
696  int i = 0;
698  QDomElement catsElem = doc.createElement( QStringLiteral( "categories" ) );
699  QgsCategoryList::const_iterator it = mCategories.constBegin();
700  for ( ; it != mCategories.constEnd(); ++it )
701  {
702  const QgsRendererCategory &cat = *it;
703  QString symbolName = QString::number( i );
704  symbols.insert( symbolName, cat.symbol() );
705 
706  QDomElement catElem = doc.createElement( QStringLiteral( "category" ) );
707  catElem.setAttribute( QStringLiteral( "value" ), cat.value().toString() );
708  catElem.setAttribute( QStringLiteral( "symbol" ), symbolName );
709  catElem.setAttribute( QStringLiteral( "label" ), cat.label() );
710  catElem.setAttribute( QStringLiteral( "render" ), cat.renderState() ? "true" : "false" );
711  catsElem.appendChild( catElem );
712  i++;
713  }
714  rendererElem.appendChild( catsElem );
715 
716  // save symbols
717  QDomElement symbolsElem = QgsSymbolLayerUtils::saveSymbols( symbols, QStringLiteral( "symbols" ), doc, context );
718  rendererElem.appendChild( symbolsElem );
719 
720  }
721 
722  // save source symbol
723  if ( mSourceSymbol )
724  {
725  QgsSymbolMap sourceSymbols;
726  sourceSymbols.insert( QStringLiteral( "0" ), mSourceSymbol.get() );
727  QDomElement sourceSymbolElem = QgsSymbolLayerUtils::saveSymbols( sourceSymbols, QStringLiteral( "source-symbol" ), doc, context );
728  rendererElem.appendChild( sourceSymbolElem );
729  }
730 
731  // save source color ramp
732  if ( mSourceColorRamp )
733  {
734  QDomElement colorRampElem = QgsSymbolLayerUtils::saveColorRamp( QStringLiteral( "[source]" ), mSourceColorRamp.get(), doc );
735  rendererElem.appendChild( colorRampElem );
736  }
737 
738  QDomElement rotationElem = doc.createElement( QStringLiteral( "rotation" ) );
739  rendererElem.appendChild( rotationElem );
740 
741  QDomElement sizeScaleElem = doc.createElement( QStringLiteral( "sizescale" ) );
742  rendererElem.appendChild( sizeScaleElem );
743 
745  mPaintEffect->saveProperties( doc, rendererElem );
746 
747  if ( !mOrderBy.isEmpty() )
748  {
749  QDomElement orderBy = doc.createElement( QStringLiteral( "orderby" ) );
750  mOrderBy.save( orderBy );
751  rendererElem.appendChild( orderBy );
752  }
753  rendererElem.setAttribute( QStringLiteral( "enableorderby" ), ( mOrderByEnabled ? QStringLiteral( "1" ) : QStringLiteral( "0" ) ) );
754 
756  {
757  QDomElement ddsLegendElem = doc.createElement( QStringLiteral( "data-defined-size-legend" ) );
758  mDataDefinedSizeLegend->writeXml( ddsLegendElem, context );
759  rendererElem.appendChild( ddsLegendElem );
760  }
761 
762  return rendererElem;
763 }
764 
765 
766 QgsLegendSymbolList QgsCategorizedSymbolRenderer::baseLegendSymbolItems() const
767 {
769  int i = 0;
770  Q_FOREACH ( const QgsRendererCategory &cat, mCategories )
771  {
772  lst << QgsLegendSymbolItem( cat.symbol(), cat.label(), QString::number( i++ ), true );
773  }
774  return lst;
775 }
776 
778 {
780  {
781  // check that all symbols that have the same size expression
782  QgsProperty ddSize;
783  Q_FOREACH ( const QgsRendererCategory &category, mCategories )
784  {
785  const QgsMarkerSymbol *symbol = static_cast<const QgsMarkerSymbol *>( category.symbol() );
786  if ( ddSize )
787  {
788  QgsProperty sSize( symbol->dataDefinedSize() );
789  if ( sSize != ddSize )
790  {
791  // no common size expression
792  return baseLegendSymbolItems();
793  }
794  }
795  else
796  {
797  ddSize = symbol->dataDefinedSize();
798  }
799  }
800 
801  if ( ddSize && ddSize.isActive() )
802  {
804 
806  ddSizeLegend.updateFromSymbolAndProperty( static_cast<const QgsMarkerSymbol *>( mSourceSymbol.get() ), ddSize );
807  lst += ddSizeLegend.legendSymbolList();
808 
809  lst += baseLegendSymbolItems();
810  return lst;
811  }
812  }
813 
814  return baseLegendSymbolItems();
815 }
816 
818 {
819  QString value = valueForFeature( feature, context ).toString();
820  int i = 0;
821 
822  Q_FOREACH ( const QgsRendererCategory &cat, mCategories )
823  {
824  if ( value == cat.value() )
825  {
826  if ( cat.renderState() || mCounting )
827  return QSet< QString >() << QString::number( i );
828  else
829  return QSet< QString >();
830  }
831  i++;
832  }
833 
834  return QSet< QString >();
835 }
836 
838 {
839  return mSourceSymbol.get();
840 }
842 {
843  mSourceSymbol.reset( sym );
844 }
845 
847 {
848  return mSourceColorRamp.get();
849 }
850 
852 {
853  mSourceColorRamp.reset( ramp );
854 }
855 
857 {
858  setSourceColorRamp( ramp );
859  double num = mCategories.count() - 1;
860  double count = 0;
861 
862  QgsRandomColorRamp *randomRamp = dynamic_cast<QgsRandomColorRamp *>( ramp );
863  if ( randomRamp )
864  {
865  //ramp is a random colors ramp, so inform it of the total number of required colors
866  //this allows the ramp to pregenerate a set of visually distinctive colors
867  randomRamp->setTotalColorCount( mCategories.count() );
868  }
869 
870  Q_FOREACH ( const QgsRendererCategory &cat, mCategories )
871  {
872  double value = count / num;
873  cat.symbol()->setColor( mSourceColorRamp->color( value ) );
874  count += 1;
875  }
876 }
877 
879 {
880  int i = 0;
881  Q_FOREACH ( const QgsRendererCategory &cat, mCategories )
882  {
883  QgsSymbol *symbol = sym->clone();
884  symbol->setColor( cat.symbol()->color() );
885  updateCategorySymbol( i, symbol );
886  ++i;
887  }
888  setSourceSymbol( sym->clone() );
889 }
890 
892 {
893  return true;
894 }
895 
897 {
898  bool ok;
899  int index = key.toInt( &ok );
900  if ( ok && index >= 0 && index < mCategories.size() )
901  return mCategories.at( index ).renderState();
902  else
903  return true;
904 }
905 
907 {
908  bool ok;
909  int index = key.toInt( &ok );
910  if ( ok )
911  updateCategorySymbol( index, symbol );
912  else
913  delete symbol;
914 }
915 
916 void QgsCategorizedSymbolRenderer::checkLegendSymbolItem( const QString &key, bool state )
917 {
918  bool ok;
919  int index = key.toInt( &ok );
920  if ( ok )
921  updateCategoryRenderState( index, state );
922 }
923 
925 {
926  QgsCategorizedSymbolRenderer *r = nullptr;
927  if ( renderer->type() == QLatin1String( "categorizedSymbol" ) )
928  {
929  r = dynamic_cast<QgsCategorizedSymbolRenderer *>( renderer->clone() );
930  }
931  else if ( renderer->type() == QLatin1String( "pointDisplacement" ) || renderer->type() == QLatin1String( "pointCluster" ) )
932  {
933  const QgsPointDistanceRenderer *pointDistanceRenderer = dynamic_cast<const QgsPointDistanceRenderer *>( renderer );
934  if ( pointDistanceRenderer )
935  r = convertFromRenderer( pointDistanceRenderer->embeddedRenderer() );
936  }
937  else if ( renderer->type() == QLatin1String( "invertedPolygonRenderer" ) )
938  {
939  const QgsInvertedPolygonRenderer *invertedPolygonRenderer = dynamic_cast<const QgsInvertedPolygonRenderer *>( renderer );
940  if ( invertedPolygonRenderer )
941  r = convertFromRenderer( invertedPolygonRenderer->embeddedRenderer() );
942  }
943 
944  // If not one of the specifically handled renderers, then just grab the symbol from the renderer
945  // Could have applied this to specific renderer types (singleSymbol, graduatedSymbol)
946 
947  if ( !r )
948  {
949  r = new QgsCategorizedSymbolRenderer( QString(), QgsCategoryList() );
950  QgsRenderContext context;
951  QgsSymbolList symbols = const_cast<QgsFeatureRenderer *>( renderer )->symbols( context );
952  if ( !symbols.isEmpty() )
953  {
954  r->setSourceSymbol( symbols.at( 0 )->clone() );
955  }
956  }
957 
958  r->setOrderBy( renderer->orderBy() );
959  r->setOrderByEnabled( renderer->orderByEnabled() );
960 
961  return r;
962 }
963 
965 {
966  mDataDefinedSizeLegend.reset( settings );
967 }
968 
970 {
971  return mDataDefinedSizeLegend.get();
972 }
973 
974 int QgsCategorizedSymbolRenderer::matchToSymbols( QgsStyle *style, const QgsSymbol::SymbolType type, QVariantList &unmatchedCategories, QStringList &unmatchedSymbols, const bool caseSensitive, const bool useTolerantMatch )
975 {
976  if ( !style )
977  return 0;
978 
979  int matched = 0;
980  unmatchedSymbols = style->symbolNames();
981  const QSet< QString > allSymbolNames = unmatchedSymbols.toSet();
982 
983  const QRegularExpression tolerantMatchRe( QStringLiteral( "[^\\w\\d ]" ), QRegularExpression::UseUnicodePropertiesOption );
984 
985  for ( int catIdx = 0; catIdx < mCategories.count(); ++catIdx )
986  {
987  const QVariant value = mCategories.at( catIdx ).value();
988  const QString val = value.toString().trimmed();
989  std::unique_ptr< QgsSymbol > symbol( style->symbol( val ) );
990  // case-sensitive match
991  if ( symbol && symbol->type() == type )
992  {
993  matched++;
994  unmatchedSymbols.removeAll( val );
995  updateCategorySymbol( catIdx, symbol.release() );
996  continue;
997  }
998 
999  if ( !caseSensitive || useTolerantMatch )
1000  {
1001  QString testVal = val;
1002  if ( useTolerantMatch )
1003  testVal.replace( tolerantMatchRe, QString() );
1004 
1005  bool foundMatch = false;
1006  for ( const QString &name : allSymbolNames )
1007  {
1008  QString testName = name.trimmed();
1009  if ( useTolerantMatch )
1010  testName.replace( tolerantMatchRe, QString() );
1011 
1012  if ( testName == testVal || ( !caseSensitive && testName.trimmed().compare( testVal, Qt::CaseInsensitive ) == 0 ) )
1013  {
1014  // found a case-insensitive match
1015  std::unique_ptr< QgsSymbol > symbol( style->symbol( name ) );
1016  if ( symbol && symbol->type() == type )
1017  {
1018  matched++;
1019  unmatchedSymbols.removeAll( name );
1020  updateCategorySymbol( catIdx, symbol.release() );
1021  foundMatch = true;
1022  break;
1023  }
1024  }
1025  }
1026  if ( foundMatch )
1027  continue;
1028  }
1029 
1030  unmatchedCategories << value;
1031  }
1032 
1033  return matched;
1034 }
An abstract base class for distance based point renderers (e.g., clusterer and displacement renderers...
Q_DECL_DEPRECATED QgsSymbol * skipRender()
Class for parsing and evaluation of expressions (formerly called "search strings").
void toSld(QDomDocument &doc, QDomElement &element, QgsStringMap props) const
Converts the category to a matching SLD rule, within the specified DOM document and element...
The class is used as a container of context for various read/write operations on other objects...
QString type() const
Definition: qgsrenderer.h:129
void CORE_EXPORT save(QDomElement &elem) const
Serialize to XML.
QgsSymbolList symbols(QgsRenderContext &context) const override
Returns list of symbols used by the renderer.
static QgsSymbol::ScaleMethod decodeScaleMethod(const QString &str)
bool hasParserError() const
Returns true if an error occurred when parsing the input expression.
QList< QgsLegendSymbolItem > QgsLegendSymbolList
QgsFeatureRequest::OrderBy mOrderBy
Definition: qgsrenderer.h:521
Represents an individual category (class) from a QgsCategorizedSymbolRenderer.
void updateColorRamp(QgsColorRamp *ramp)
Update the color ramp used and all symbols colors.
static bool createFunctionElement(QDomDocument &doc, QDomElement &element, const QString &function)
Abstract base class for all rendered symbols.
Definition: qgssymbol.h:61
Q_DECL_DEPRECATED QgsSymbol * symbolForValue(const QVariant &value) const
Returns the matching symbol corresponding to an attribute value.
QString dump() const
Returns a string representing the categories settings, used for debugging purposes only...
bool updateCategoryRenderState(int catIndex, bool render)
Changes the render state for the category with the specified index.
static void applyScaleDependency(QDomDocument &doc, QDomElement &ruleElem, QgsStringMap &props)
Checks if the properties contain scaleMinDenom and scaleMaxDenom, if available, they are added into t...
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
static QgsFeatureRenderer * create(QDomElement &element, const QgsReadWriteContext &context)
Creates a categorized renderer from an XML element.
bool isActive() const
Returns whether the property is currently active.
double rendererScale() const
Returns the renderer map scale.
QSet< QString > referencedColumns() const
Gets list of columns referenced by the expression.
bool filterNeedsGeometry() const override
Returns true if this renderer requires the geometry to apply the filter.
QgsCategorizedSymbolRenderer(const QString &attrName=QString(), const QgsCategoryList &categories=QgsCategoryList())
Constructor for QgsCategorizedSymbolRenderer.
Abstract base class for color ramps.
Definition: qgscolorramp.h:31
bool usingSymbolLevels() const
Definition: qgsrenderer.h:271
void moveCategory(int from, int to)
Moves an existing category at index position from to index position to.
static bool isDefaultStack(QgsPaintEffect *effect)
Tests whether a paint effect matches the default effects stack.
static QDomElement saveColorRamp(const QString &name, QgsColorRamp *ramp, QDomDocument &doc)
Encodes a color ramp&#39;s settings to an XML element.
QString filter(const QgsFields &fields=QgsFields()) override
If a renderer does not require all the features this method may be overridden and return an expressio...
Container of fields for a vector layer.
Definition: qgsfields.h:42
#define RENDERER_TAG_NAME
Definition: qgsrenderer.h:49
void setUsingSymbolLevels(bool usingSymbolLevels)
Definition: qgsrenderer.h:272
void checkLegendSymbolItem(const QString &key, bool state=true) override
item in symbology was checked
bool updateCategoryLabel(int catIndex, const QString &label)
Changes the label for the category with the specified index.
static void clearSymbolMap(QgsSymbolMap &symbols)
QgsLegendSymbolList legendSymbolItems() const override
Returns a list of symbology items for the legend.
QgsPaintEffect * mPaintEffect
Definition: qgsrenderer.h:505
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:55
std::unique_ptr< QgsSymbol > mSourceSymbol
int categoryIndexForLabel(const QString &val)
Returns the index of the category with the specified label (or -1 if the label was not found...
bool qgsVariantGreaterThan(const QVariant &lhs, const QVariant &rhs)
Compares two QVariant values and returns whether the first is greater than the second.
Definition: qgis.cpp:221
void swap(QgsRendererCategory &other)
QgsField at(int i) const
Gets field at particular index (must be in range 0..N-1)
Definition: qgsfields.cpp:163
QMap< QString, QString > QgsStringMap
Definition: qgis.h:577
A marker symbol type, for rendering Point and MultiPoint geometries.
Definition: qgssymbol.h:732
bool qgsVariantLessThan(const QVariant &lhs, const QVariant &rhs)
Compares two QVariant values and returns whether the first is less than the second.
Definition: qgis.cpp:153
bool valueGreaterThan(const QgsRendererCategory &c1, const QgsRendererCategory &c2)
void startRender(QgsRenderContext &context, const QgsFields &fields=QgsFields())
Begins the rendering process for the symbol.
Definition: qgssymbol.cpp:404
QList< QgsRendererCategory > QgsCategoryList
SymbolType
Type of the symbol.
Definition: qgssymbol.h:83
QgsCategorizedSymbolRenderer * clone() const override
Create a deep copy of this renderer.
QDomElement save(QDomDocument &doc, const QgsReadWriteContext &context) override
store renderer info to XML element
QList< QgsSymbol * > QgsSymbolList
Definition: qgsrenderer.h:43
QgsInvertedPolygonRenderer is a polygon-only feature renderer used to display features inverted...
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
QStringList symbolNames()
Returns a list of names of symbols.
Definition: qgsstyle.cpp:191
static QDomElement saveSymbols(QgsSymbolMap &symbols, const QString &tagName, QDomDocument &doc, const QgsReadWriteContext &context)
Writes a collection of symbols to XML with specified tagName for the top-level element.
bool labelGreaterThan(const QgsRendererCategory &c1, const QgsRendererCategory &c2)
std::unique_ptr< QgsSymbol > mSymbol
QgsLegendSymbolList legendSymbolList() const
Generates legend symbol items according to the configuration.
bool needsGeometry() const
Returns true if the expression uses feature geometry for some computation.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
QgsSymbol * originalSymbolForFeature(const QgsFeature &feature, QgsRenderContext &context) const override
Returns symbol for feature.
void sortByLabel(Qt::SortOrder order=Qt::AscendingOrder)
Sorts the existing categories by their label.
void startRender(QgsRenderContext &context, const QgsFields &fields) override
Must be called when a new render cycle is started.
void updateFromSymbolAndProperty(const QgsMarkerSymbol *symbol, const QgsProperty &ddSize)
Updates the list of classes, source symbol and title label from given symbol and property.
static QgsColorRamp * loadColorRamp(QDomElement &element)
Creates a color ramp from the settings encoded in an XML element.
static void convertSymbolSizeScale(QgsSymbol *symbol, QgsSymbol::ScaleMethod method, const QString &field)
bool legendSymbolItemChecked(const QString &key) override
items of symbology items in legend is checked
static QList< QgsExpressionContextScope * > globalProjectLayerScopes(const QgsMapLayer *layer)
Creates a list of three scopes: global, layer&#39;s project and layer.
void setLegendSymbolItem(const QString &key, QgsSymbol *symbol) override
Sets the symbol to be used for a legend symbol item.
int lookupField(const QString &fieldName) const
Looks up field&#39;s index from the field name.
Definition: qgsfields.cpp:320
QgsSymbol * sourceSymbol()
Returns the renderer&#39;s source symbol, which is the base symbol used for the each categories&#39; symbol b...
A store for object properties.
Definition: qgsproperty.h:229
void deleteAllCategories()
Deletes all existing categories from the renderer.
virtual void setTotalColorCount(int colorCount)
Sets the desired total number of unique colors for the resultant ramp.
static QgsSymbolMap loadSymbols(QDomElement &element, const QgsReadWriteContext &context)
Reads a collection of symbols from XML and returns them in a map. Caller is responsible for deleting ...
void stopRender(QgsRenderContext &context) override
Must be called when a render cycle has finished, to allow the renderer to clean up.
void setSymbol(QgsSymbol *s)
Sets the symbol which will be used to render this category.
bool labelLessThan(const QgsRendererCategory &c1, const QgsRendererCategory &c2)
bool orderByEnabled() const
Returns whether custom ordering will be applied before features are processed by this renderer...
void setRenderState(bool render)
Sets whether the category is currently enabled and should be rendered.
void setOrderBy(const QgsFeatureRequest::OrderBy &orderBy)
Define the order in which features shall be processed by this renderer.
Totally random color ramp.
Definition: qgscolorramp.h:427
int matchToSymbols(QgsStyle *style, QgsSymbol::SymbolType type, QVariantList &unmatchedCategories, QStringList &unmatchedSymbols, bool caseSensitive=true, bool useTolerantMatch=false)
Replaces category symbols with the symbols from a style that have a matching name and symbol type...
QString dump() const override
Returns debug information about this renderer.
QgsExpressionContext & expressionContext()
Gets the expression context.
void setDataDefinedSizeLegend(QgsDataDefinedSizeLegend *settings)
Configures appearance of legend when renderer is configured to use data-defined size for marker symbo...
bool legendSymbolItemsCheckable() const override
items of symbology items in legend should be checkable
static void convertSymbolRotation(QgsSymbol *symbol, const QString &field)
Marker symbol.
Definition: qgssymbol.h:85
const QgsFeatureRenderer * embeddedRenderer() const override
Returns the current embedded renderer (subrenderer) for this feature renderer.
virtual bool saveProperties(QDomDocument &doc, QDomElement &element) const
Saves the current state of the effect to a DOM element.
void addCategory(const QgsRendererCategory &category)
Adds a new category to the renderer.
The class stores information about one class/rule of a vector layer renderer in a unified way that ca...
Contains information about the context of a rendering operation.
void setSourceColorRamp(QgsColorRamp *ramp)
Sets the source color ramp.
QgsProperty dataDefinedSize() const
Returns data defined size for whole symbol (including all symbol layers).
Definition: qgssymbol.cpp:1430
QgsColorRamp * sourceColorRamp()
Returns the source color ramp, from which each categories&#39; color is derived.
QSet< QString > legendKeysForFeature(const QgsFeature &feature, QgsRenderContext &context) const override
Returns legend keys matching a specified feature.
QgsRendererCategory & operator=(QgsRendererCategory cat)
virtual void startRender(QgsRenderContext &context, const QgsFields &fields)
Must be called when a new render cycle is started.
Definition: qgsrenderer.cpp:92
bool prepare(const QgsExpressionContext *context)
Gets the expression ready for evaluation - find out column indexes.
virtual QgsSymbol * clone() const =0
Returns a deep copy of this symbol.
QgsSymbol * symbol() const
Returns the symbol which will be used to render this category.
QVariant value() const
Returns the value corresponding to this category.
std::unique_ptr< QgsColorRamp > mSourceColorRamp
void copyRendererData(QgsFeatureRenderer *destRenderer) const
Clones generic renderer data to another renderer.
Definition: qgsrenderer.cpp:48
int categoryIndexForValue(const QVariant &val)
Returns the index for the category with the specified value (or -1 if not found). ...
QString label() const
Returns the label for this category, which is used to represent the category within legends and the l...
bool updateCategoryValue(int catIndex, const QVariant &value)
Changes the value for the category with the specified index.
virtual void stopRender(QgsRenderContext &context)
Must be called when a render cycle has finished, to allow the renderer to clean up.
QMap< QString, QgsSymbol * > QgsSymbolMap
Definition: qgsrenderer.h:44
bool updateCategorySymbol(int catIndex, QgsSymbol *symbol)
Changes the symbol for the category with the specified index.
bool valueLessThan(const QgsRendererCategory &c1, const QgsRendererCategory &c2)
QgsSymbol * symbolForFeature(const QgsFeature &feature, QgsRenderContext &context) const override
To be overridden.
static QgsCategorizedSymbolRenderer * convertFromRenderer(const QgsFeatureRenderer *renderer)
creates a QgsCategorizedSymbolRenderer from an existing renderer.
QgsFeatureRequest::OrderBy orderBy() const
Gets the order in which features shall be processed by this renderer.
QHash< QString, QgsSymbol * > mSymbolHash
hashtable for faster access to symbols
void sortByValue(Qt::SortOrder order=Qt::AscendingOrder)
Sorts the existing categories by their value.
QgsSymbol * symbol(const QString &name)
Returns a NEW copy of symbol.
Definition: qgsstyle.cpp:175
void updateSymbols(QgsSymbol *sym)
Update all the symbols but leave categories and colors.
void appendScopes(const QList< QgsExpressionContextScope * > &scopes)
Appends a list of scopes to the end of the context.
static QString quotedValue(const QVariant &value)
Returns a string representation of a literal value, including appropriate quotations where required...
const QgsFeatureRenderer * embeddedRenderer() const override
Returns the current embedded renderer (subrenderer) for this feature renderer.
QSet< QString > usedAttributes(const QgsRenderContext &context) const
Returns a list of attributes required to render this feature.
Definition: qgssymbol.cpp:654
QgsRendererCategory()=default
Constructor for QgsRendererCategory.
A vector of attributes.
Definition: qgsattributes.h:57
QColor color() const
Returns the symbol&#39;s color.
Definition: qgssymbol.cpp:459
Object that keeps configuration of appearance of marker symbol&#39;s data-defined size in legend...
std::unique_ptr< QgsExpression > mExpression
int mAttrNum
attribute index (derived from attribute name in startRender)
void toSld(QDomDocument &doc, QDomElement &element, const QgsStringMap &props=QgsStringMap()) const override
used from subclasses to create SLD Rule elements following SLD v1.1 specs
void setSourceSymbol(QgsSymbol *sym)
Sets the source symbol for the renderer, which is the base symbol used for the each categories&#39; symbo...
void stopRender(QgsRenderContext &context)
Ends the rendering process.
Definition: qgssymbol.cpp:426
void setLabel(const QString &label)
Sets the label for this category, which is used to represent the category within legends and the laye...
static QgsDataDefinedSizeLegend * readXml(const QDomElement &elem, const QgsReadWriteContext &context) SIP_FACTORY
Creates instance from given element and returns it (caller takes ownership). Returns null on error...
bool deleteCategory(int catIndex)
Deletes the category with the specified index from the renderer.
bool renderState() const
Returns true if the category is currently enabled and should be rendered.
QSet< QString > usedAttributes(const QgsRenderContext &context) const override
Returns a list of attributes required by this renderer.
void setOrderByEnabled(bool enabled)
Sets whether custom ordering should be applied before features are processed by this renderer...
QVariant::Type type
Definition: qgsfield.h:55
void setValue(const QVariant &value)
Sets the value corresponding to this category.
virtual QgsFeatureRenderer * clone() const =0
Create a deep copy of this renderer.
QgsAttributes attributes
Definition: qgsfeature.h:65
QgsDataDefinedSizeLegend * dataDefinedSizeLegend() const
Returns configuration of appearance of legend when using data-defined size for marker symbols...
std::unique_ptr< QgsDataDefinedSizeLegend > mDataDefinedSizeLegend
void setColor(const QColor &color)
Sets the color for the symbol.
Definition: qgssymbol.cpp:450