QGIS API Documentation  2.2.0-Valmiera
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
qgscategorizedsymbolrendererv2.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgscategorizedsymbolrendererv2.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 
19 #include "qgssymbolv2.h"
20 #include "qgssymbollayerv2utils.h"
21 #include "qgsvectorcolorrampv2.h"
22 
23 #include "qgsfeature.h"
24 #include "qgsvectorlayer.h"
25 #include "qgslogger.h"
26 
27 #include <QDomDocument>
28 #include <QDomElement>
29 #include <QSettings> // for legend
30 
32 {
33 }
34 
35 QgsRendererCategoryV2::QgsRendererCategoryV2( QVariant value, QgsSymbolV2* symbol, QString label )
36  : mValue( value )
37  , mSymbol( symbol )
38  , mLabel( label )
39 {
40 }
41 
43  : mValue( cat.mValue )
44  , mSymbol( cat.mSymbol.data() ? cat.mSymbol->clone() : NULL )
45  , mLabel( cat.mLabel )
46 {
47 }
48 
49 // copy+swap idion, the copy is done through the 'pass by value'
51 {
52  swap( cat );
53  return *this;
54 }
55 
57 {
58  qSwap( mValue, cat.mValue );
59  qSwap( mSymbol, cat.mSymbol );
60  qSwap( mLabel, cat.mLabel );
61 }
62 
64 {
65  return mValue;
66 }
67 
69 {
70  return mSymbol.data();
71 }
72 
74 {
75  return mLabel;
76 }
77 
78 void QgsRendererCategoryV2::setValue( const QVariant &value )
79 {
80  mValue = value;
81 }
82 
84 {
85  if ( mSymbol.data() != s ) mSymbol.reset( s );
86 }
87 
88 void QgsRendererCategoryV2::setLabel( const QString &label )
89 {
90  mLabel = label;
91 }
92 
94 {
95  return QString( "%1::%2::%3\n" ).arg( mValue.toString() ).arg( mLabel ).arg( mSymbol->dump() );
96 }
97 
98 void QgsRendererCategoryV2::toSld( QDomDocument &doc, QDomElement &element, QgsStringMap props ) const
99 {
100  if ( !mSymbol.data() || props.value( "attribute", "" ).isEmpty() )
101  return;
102 
103  QString attrName = props[ "attribute" ];
104 
105  QDomElement ruleElem = doc.createElement( "se:Rule" );
106  element.appendChild( ruleElem );
107 
108  QDomElement nameElem = doc.createElement( "se:Name" );
109  nameElem.appendChild( doc.createTextNode( mLabel ) );
110  ruleElem.appendChild( nameElem );
111 
112  QDomElement descrElem = doc.createElement( "se:Description" );
113  QDomElement titleElem = doc.createElement( "se:Title" );
114  QString descrStr = QString( "%1 is '%2'" ).arg( attrName ).arg( mValue.toString() );
115  titleElem.appendChild( doc.createTextNode( !mLabel.isEmpty() ? mLabel : descrStr ) );
116  descrElem.appendChild( titleElem );
117  ruleElem.appendChild( descrElem );
118 
119  // create the ogc:Filter for the range
120  QString filterFunc = QString( "%1 = '%2'" )
121  .arg( attrName.replace( "\"", "\"\"" ) )
122  .arg( mValue.toString().replace( "'", "''" ) );
123  QgsSymbolLayerV2Utils::createFunctionElement( doc, ruleElem, filterFunc );
124 
125  mSymbol->toSld( doc, ruleElem, props );
126 }
127 
129 
131  : QgsFeatureRendererV2( "categorizedSymbol" )
132  , mAttrName( attrName )
133  , mCategories( categories )
134  , mInvertedColorRamp( false )
135  , mScaleMethod( DEFAULT_SCALE_METHOD )
136 {
137  for ( int i = 0; i < mCategories.count(); ++i )
138  {
140  if ( cat.symbol() == NULL )
141  {
142  QgsDebugMsg( "invalid symbol in a category! ignoring..." );
143  mCategories.removeAt( i-- );
144  }
145  //mCategories.insert(cat.value().toString(), cat);
146  }
147 }
148 
150 {
151 }
152 
154 {
155  mSymbolHash.clear();
156 
157  for ( int i = 0; i < mCategories.count(); ++i )
158  {
160  mSymbolHash.insert( cat.value().toString(), cat.symbol() );
161  }
162 }
163 
165 {
166  // TODO: special case for int, double
167  QHash<QString, QgsSymbolV2*>::iterator it = mSymbolHash.find( value.toString() );
168  if ( it == mSymbolHash.end() )
169  {
170  if ( mSymbolHash.count() == 0 )
171  {
172  QgsDebugMsg( "there are no hashed symbols!!!" );
173  }
174  else
175  {
176  QgsDebugMsgLevel( "attribute value not found: " + value.toString(), 3 );
177  }
178  return NULL;
179  }
180 
181  return *it;
182 }
183 
185 {
186  const QgsAttributes& attrs = feature.attributes();
187  QVariant value;
188  if ( mAttrNum == -1 )
189  {
190  Q_ASSERT( mExpression.data() );
191  value = mExpression->evaluate( &feature );
192  }
193  else
194  {
195  value = attrs[mAttrNum];
196  }
197 
198  // find the right symbol for the category
199  QgsSymbolV2* symbol = symbolForValue( value );
200  if ( symbol == NULL )
201  {
202  // if no symbol found use default one
203  return symbolForValue( QVariant( "" ) );
204  }
205 
206  if ( !mRotation.data() && !mSizeScale.data() )
207  return symbol; // no data-defined rotation/scaling - just return the symbol
208 
209  // find out rotation, size scale
210  const double rotation = mRotation.data() ? mRotation->evaluate( feature ).toDouble() : 0;
211  const double sizeScale = mSizeScale.data() ? mSizeScale->evaluate( feature ).toDouble() : 1.;
212 
213  // take a temporary symbol (or create it if doesn't exist)
214  QgsSymbolV2* tempSymbol = mTempSymbols[value.toString()];
215 
216  // modify the temporary symbol and return it
217  if ( tempSymbol->type() == QgsSymbolV2::Marker )
218  {
219  QgsMarkerSymbolV2* markerSymbol = static_cast<QgsMarkerSymbolV2*>( tempSymbol );
220  if ( mRotation.data() ) markerSymbol->setAngle( rotation );
221  markerSymbol->setSize( sizeScale * static_cast<QgsMarkerSymbolV2*>( symbol )->size() );
222  markerSymbol->setScaleMethod( mScaleMethod );
223  }
224  else if ( tempSymbol->type() == QgsSymbolV2::Line )
225  {
226  QgsLineSymbolV2* lineSymbol = static_cast<QgsLineSymbolV2*>( tempSymbol );
227  lineSymbol->setWidth( sizeScale * static_cast<QgsLineSymbolV2*>( symbol )->width() );
228  }
229 
230  return tempSymbol;
231 }
232 
234 {
235  for ( int i = 0; i < mCategories.count(); i++ )
236  {
237  if ( mCategories[i].value() == val )
238  return i;
239  }
240  return -1;
241 }
242 
243 bool QgsCategorizedSymbolRendererV2::updateCategoryValue( int catIndex, const QVariant &value )
244 {
245  if ( catIndex < 0 || catIndex >= mCategories.size() )
246  return false;
247  mCategories[catIndex].setValue( value );
248  return true;
249 }
250 
252 {
253  if ( catIndex < 0 || catIndex >= mCategories.size() )
254  return false;
255  mCategories[catIndex].setSymbol( symbol );
256  return true;
257 }
258 
259 bool QgsCategorizedSymbolRendererV2::updateCategoryLabel( int catIndex, QString label )
260 {
261  if ( catIndex < 0 || catIndex >= mCategories.size() )
262  return false;
263  mCategories[catIndex].setLabel( label );
264  return true;
265 }
266 
268 {
269  if ( cat.symbol() == NULL )
270  {
271  QgsDebugMsg( "invalid symbol in a category! ignoring..." );
272  }
273  else
274  {
275  mCategories.append( cat );
276  }
277 }
278 
280 {
281  if ( catIndex < 0 || catIndex >= mCategories.size() )
282  return false;
283 
284  mCategories.removeAt( catIndex );
285  return true;
286 }
287 
289 {
290  mCategories.clear();
291 }
292 
294 {
295  if ( from < 0 || from >= mCategories.size() || to < 0 || to >= mCategories.size() ) return;
296  mCategories.move( from, to );
297 }
298 
300 {
301  return qgsVariantLessThan( c1.value(), c2.value() );
302 }
304 {
305  return qgsVariantGreaterThan( c1.value(), c2.value() );
306 }
307 
309 {
310  if ( order == Qt::AscendingOrder )
311  {
312  qSort( mCategories.begin(), mCategories.end(), valueLessThan );
313  }
314  else
315  {
316  qSort( mCategories.begin(), mCategories.end(), valueGreaterThan );
317  }
318 }
319 
321 {
322  return QString::localeAwareCompare( c1.label(), c2.label() ) < 0;
323 }
324 
326 {
327  return !labelLessThan( c1, c2 );
328 }
329 
331 {
332  if ( order == Qt::AscendingOrder )
333  {
334  qSort( mCategories.begin(), mCategories.end(), labelLessThan );
335  }
336  else
337  {
338  qSort( mCategories.begin(), mCategories.end(), labelGreaterThan );
339  }
340 }
341 
343 {
344  // make sure that the hash table is up to date
345  rebuildHash();
346 
347  // find out classification attribute index from name
348  mAttrNum = vlayer ? vlayer->fieldNameIndex( mAttrName ) : -1;
349  if ( mAttrNum == -1 )
350  {
351  mExpression.reset( new QgsExpression( mAttrName ) );
352  mExpression->prepare( vlayer->pendingFields() );
353  }
354 
355  QgsCategoryList::iterator it = mCategories.begin();
356  for ( ; it != mCategories.end(); ++it )
357  {
358  it->symbol()->startRender( context, vlayer );
359 
360  if ( mRotation.data() || mSizeScale.data() )
361  {
362  QgsSymbolV2* tempSymbol = it->symbol()->clone();
363  tempSymbol->setRenderHints(( mRotation.data() ? QgsSymbolV2::DataDefinedRotation : 0 ) |
365  tempSymbol->startRender( context, vlayer );
366  mTempSymbols[ it->value().toString()] = tempSymbol;
367  }
368  }
369 
370 }
371 
373 {
374  QgsCategoryList::iterator it = mCategories.begin();
375  for ( ; it != mCategories.end(); ++it )
376  it->symbol()->stopRender( context );
377 
378  // cleanup mTempSymbols
379  QHash<QString, QgsSymbolV2*>::iterator it2 = mTempSymbols.begin();
380  for ( ; it2 != mTempSymbols.end(); ++it2 )
381  {
382  it2.value()->stopRender( context );
383  delete it2.value();
384  }
385  mTempSymbols.clear();
386  mExpression.reset();
387 }
388 
390 {
391  QSet<QString> attributes;
392 
394  {
395  attributes.unite( exp->referencedColumns().toSet() );
396  delete exp;
397  }
398 
399  if ( mRotation.data() ) attributes.unite( mRotation->referencedColumns().toSet() );
400  if ( mSizeScale.data() ) attributes.unite( mSizeScale->referencedColumns().toSet() );
401 
402  QgsCategoryList::const_iterator catIt = mCategories.constBegin();
403  for ( ; catIt != mCategories.constEnd(); ++catIt )
404  {
405  QgsSymbolV2* catSymbol = catIt->symbol();
406  if ( catSymbol )
407  {
408  attributes.unite( catSymbol->usedAttributes() );
409  }
410  }
411  return attributes.toList();
412 }
413 
415 {
416  QString s = QString( "CATEGORIZED: idx %1\n" ).arg( mAttrName );
417  for ( int i = 0; i < mCategories.count(); i++ )
418  s += mCategories[i].dump();
419  return s;
420 }
421 
423 {
425  if ( mSourceSymbol.data() )
426  r->setSourceSymbol( mSourceSymbol->clone() );
427  if ( mSourceColorRamp.data() )
428  {
429  r->setSourceColorRamp( mSourceColorRamp->clone() );
431  }
435  r->setScaleMethod( scaleMethod() );
436  return r;
437 }
438 
439 void QgsCategorizedSymbolRendererV2::toSld( QDomDocument &doc, QDomElement &element ) const
440 {
441  QgsStringMap props;
442  props[ "attribute" ] = mAttrName;
443  if ( mRotation.data() )
444  props[ "angle" ] = mRotation->expression();
445  if ( mSizeScale.data() )
446  props[ "scale" ] = mSizeScale->expression();
447 
448  // create a Rule for each range
449  for ( QgsCategoryList::const_iterator it = mCategories.constBegin(); it != mCategories.constEnd(); ++it )
450  {
451  QgsStringMap catProps( props );
452  it->toSld( doc, element, catProps );
453  }
454 }
455 
457 {
458  QgsSymbolV2List lst;
459  for ( int i = 0; i < mCategories.count(); i++ )
460  lst.append( mCategories[i].symbol() );
461  return lst;
462 }
463 
465 {
466  QDomElement symbolsElem = element.firstChildElement( "symbols" );
467  if ( symbolsElem.isNull() )
468  return NULL;
469 
470  QDomElement catsElem = element.firstChildElement( "categories" );
471  if ( catsElem.isNull() )
472  return NULL;
473 
474  QgsSymbolV2Map symbolMap = QgsSymbolLayerV2Utils::loadSymbols( symbolsElem );
475  QgsCategoryList cats;
476 
477  QDomElement catElem = catsElem.firstChildElement();
478  while ( !catElem.isNull() )
479  {
480  if ( catElem.tagName() == "category" )
481  {
482  QVariant value = QVariant( catElem.attribute( "value" ) );
483  QString symbolName = catElem.attribute( "symbol" );
484  QString label = catElem.attribute( "label" );
485  if ( symbolMap.contains( symbolName ) )
486  {
487  QgsSymbolV2* symbol = symbolMap.take( symbolName );
488  cats.append( QgsRendererCategoryV2( value, symbol, label ) );
489  }
490  }
491  catElem = catElem.nextSiblingElement();
492  }
493 
494  QString attrName = element.attribute( "attr" );
495 
497 
498  // delete symbols if there are any more
500 
501  // try to load source symbol (optional)
502  QDomElement sourceSymbolElem = element.firstChildElement( "source-symbol" );
503  if ( !sourceSymbolElem.isNull() )
504  {
505  QgsSymbolV2Map sourceSymbolMap = QgsSymbolLayerV2Utils::loadSymbols( sourceSymbolElem );
506  if ( sourceSymbolMap.contains( "0" ) )
507  {
508  r->setSourceSymbol( sourceSymbolMap.take( "0" ) );
509  }
510  QgsSymbolLayerV2Utils::clearSymbolMap( sourceSymbolMap );
511  }
512 
513  // try to load color ramp (optional)
514  QDomElement sourceColorRampElem = element.firstChildElement( "colorramp" );
515  if ( !sourceColorRampElem.isNull() && sourceColorRampElem.attribute( "name" ) == "[source]" )
516  {
517  r->setSourceColorRamp( QgsSymbolLayerV2Utils::loadColorRamp( sourceColorRampElem ) );
518  QDomElement invertedColorRampElem = element.firstChildElement( "invertedcolorramp" );
519  if ( !invertedColorRampElem.isNull() )
520  r->setInvertedColorRamp( invertedColorRampElem.attribute( "value" ) == "1" );
521  }
522 
523  QDomElement rotationElem = element.firstChildElement( "rotation" );
524  if ( !rotationElem.isNull() )
525  r->setRotationField( rotationElem.attribute( "field" ) );
526 
527  QDomElement sizeScaleElem = element.firstChildElement( "sizescale" );
528  if ( !sizeScaleElem.isNull() )
529  {
530  r->setSizeScaleField( sizeScaleElem.attribute( "field" ) );
531  r->setScaleMethod( QgsSymbolLayerV2Utils::decodeScaleMethod( sizeScaleElem.attribute( "scalemethod" ) ) );
532  }
533 
534  // TODO: symbol levels
535  return r;
536 }
537 
538 QDomElement QgsCategorizedSymbolRendererV2::save( QDomDocument& doc )
539 {
540  QDomElement rendererElem = doc.createElement( RENDERER_TAG_NAME );
541  rendererElem.setAttribute( "type", "categorizedSymbol" );
542  rendererElem.setAttribute( "symbollevels", ( mUsingSymbolLevels ? "1" : "0" ) );
543  rendererElem.setAttribute( "attr", mAttrName );
544 
545  // categories
546  int i = 0;
548  QDomElement catsElem = doc.createElement( "categories" );
549  QgsCategoryList::const_iterator it = mCategories.constBegin();
550  for ( ; it != mCategories.end(); ++it )
551  {
552  const QgsRendererCategoryV2& cat = *it;
553  QString symbolName = QString::number( i );
554  symbols.insert( symbolName, cat.symbol() );
555 
556  QDomElement catElem = doc.createElement( "category" );
557  catElem.setAttribute( "value", cat.value().toString() );
558  catElem.setAttribute( "symbol", symbolName );
559  catElem.setAttribute( "label", cat.label() );
560  catsElem.appendChild( catElem );
561  i++;
562  }
563 
564  rendererElem.appendChild( catsElem );
565 
566  // save symbols
567  QDomElement symbolsElem = QgsSymbolLayerV2Utils::saveSymbols( symbols, "symbols", doc );
568  rendererElem.appendChild( symbolsElem );
569 
570  // save source symbol
571  if ( mSourceSymbol.data() )
572  {
573  QgsSymbolV2Map sourceSymbols;
574  sourceSymbols.insert( "0", mSourceSymbol.data() );
575  QDomElement sourceSymbolElem = QgsSymbolLayerV2Utils::saveSymbols( sourceSymbols, "source-symbol", doc );
576  rendererElem.appendChild( sourceSymbolElem );
577  }
578 
579  // save source color ramp
580  if ( mSourceColorRamp.data() )
581  {
582  QDomElement colorRampElem = QgsSymbolLayerV2Utils::saveColorRamp( "[source]", mSourceColorRamp.data(), doc );
583  rendererElem.appendChild( colorRampElem );
584  QDomElement invertedElem = doc.createElement( "invertedcolorramp" );
585  invertedElem.setAttribute( "value", mInvertedColorRamp );
586  rendererElem.appendChild( invertedElem );
587  }
588 
589  QDomElement rotationElem = doc.createElement( "rotation" );
590  if ( mRotation.data() )
591  rotationElem.setAttribute( "field", QgsSymbolLayerV2Utils::fieldOrExpressionFromExpression( mRotation.data() ) );
592  rendererElem.appendChild( rotationElem );
593 
594  QDomElement sizeScaleElem = doc.createElement( "sizescale" );
595  if ( mSizeScale.data() )
596  sizeScaleElem.setAttribute( "field", QgsSymbolLayerV2Utils::fieldOrExpressionFromExpression( mSizeScale.data() ) );
597  sizeScaleElem.setAttribute( "scalemethod", QgsSymbolLayerV2Utils::encodeScaleMethod( mScaleMethod ) );
598  rendererElem.appendChild( sizeScaleElem );
599 
600  return rendererElem;
601 }
602 
604 {
605  QSettings settings;
606  bool showClassifiers = settings.value( "/qgis/showLegendClassifiers", false ).toBool();
607 
609  if ( showClassifiers )
610  {
611  lst << qMakePair( classAttribute(), QPixmap() );
612  }
613 
614  int count = categories().count();
615  for ( int i = 0; i < count; i++ )
616  {
617  const QgsRendererCategoryV2& cat = categories()[i];
618  QPixmap pix = QgsSymbolLayerV2Utils::symbolPreviewPixmap( cat.symbol(), iconSize );
619  lst << qMakePair( cat.label(), pix );
620  }
621  return lst;
622 }
623 
625 {
626  Q_UNUSED( scaleDenominator );
627  QSettings settings;
628  bool showClassifiers = settings.value( "/qgis/showLegendClassifiers", false ).toBool();
629 
631  if ( showClassifiers )
632  {
633  lst << qMakePair( classAttribute(), ( QgsSymbolV2* )0 );
634  }
635 
636  foreach ( const QgsRendererCategoryV2& cat, mCategories )
637  {
638  if ( rule.isEmpty() || cat.label() == rule )
639  {
640  lst << qMakePair( cat.label(), cat.symbol() );
641  }
642  }
643  return lst;
644 }
645 
646 
648 {
649  return mSourceSymbol.data();
650 }
652 {
653  mSourceSymbol.reset( sym );
654 }
655 
657 {
658  return mSourceColorRamp.data();
659 }
661 {
662  mSourceColorRamp.reset( ramp );
663 }
664 
665 void QgsCategorizedSymbolRendererV2::setRotationField( QString fieldOrExpression )
666 {
668 }
669 
671 {
672  return mRotation.data() ? QgsSymbolLayerV2Utils::fieldOrExpressionFromExpression( mRotation.data() ) : QString();
673 }
674 
675 void QgsCategorizedSymbolRendererV2::setSizeScaleField( QString fieldOrExpression )
676 {
678 }
679 
681 {
683 }
684 
686 {
687  int i = 0;
688  foreach ( QgsRendererCategoryV2 cat, mCategories )
689  {
690  QgsSymbolV2* symbol = sym->clone();
691  symbol->setColor( cat.symbol()->color() );
692  updateCategorySymbol( i, symbol );
693  ++i;
694  }
695 }
696 
698 {
700  QgsCategoryList::const_iterator catIt = mCategories.constBegin();
701  for ( ; catIt != mCategories.constEnd(); ++catIt )
702  {
703  setScaleMethodToSymbol( catIt->symbol(), scaleMethod );
704  }
705 }