QGIS API Documentation  2.0.1-Dufour
 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  : mValue(), mSymbol( 0 ), mLabel()
33 {
34 }
35 
36 QgsRendererCategoryV2::QgsRendererCategoryV2( QVariant value, QgsSymbolV2* symbol, QString label )
37  : mValue( value ), mSymbol( symbol ), mLabel( label )
38 {
39 }
40 
42  : mValue( cat.mValue ), mSymbol( 0 ), mLabel( cat.mLabel )
43 {
44  if ( cat.mSymbol )
45  {
46  mSymbol = cat.mSymbol->clone();
47  }
48 }
49 
51 {
52  if ( mSymbol ) delete mSymbol;
53 }
54 
56 {
57  mValue = cat.mValue;
58  mLabel = cat.mLabel;
59  mSymbol = 0;
60  if ( cat.mSymbol )
61  {
62  mSymbol = cat.mSymbol->clone();
63  }
64  return *this;
65 }
66 
68 {
69  return mValue;
70 }
71 
73 {
74  return mSymbol;
75 }
76 
78 {
79  return mLabel;
80 }
81 
82 void QgsRendererCategoryV2::setValue( const QVariant &value )
83 {
84  mValue = value;
85 }
86 
88 {
89  if ( mSymbol == s )
90  return;
91  delete mSymbol;
92  mSymbol = s;
93 }
94 
95 void QgsRendererCategoryV2::setLabel( const QString &label )
96 {
97  mLabel = label;
98 }
99 
101 {
102  return QString( "%1::%2::%3\n" ).arg( mValue.toString() ).arg( mLabel ).arg( mSymbol->dump() );
103 }
104 
105 void QgsRendererCategoryV2::toSld( QDomDocument &doc, QDomElement &element, QgsStringMap props ) const
106 {
107  if ( !mSymbol || props.value( "attribute", "" ).isEmpty() )
108  return;
109 
110  QString attrName = props[ "attribute" ];
111 
112  QDomElement ruleElem = doc.createElement( "se:Rule" );
113  element.appendChild( ruleElem );
114 
115  QDomElement nameElem = doc.createElement( "se:Name" );
116  nameElem.appendChild( doc.createTextNode( mLabel ) );
117  ruleElem.appendChild( nameElem );
118 
119  QDomElement descrElem = doc.createElement( "se:Description" );
120  QDomElement titleElem = doc.createElement( "se:Title" );
121  QString descrStr = QString( "%1 is '%2'" ).arg( attrName ).arg( mValue.toString() );
122  titleElem.appendChild( doc.createTextNode( !mLabel.isEmpty() ? mLabel : descrStr ) );
123  descrElem.appendChild( titleElem );
124  ruleElem.appendChild( descrElem );
125 
126  // create the ogc:Filter for the range
127  QString filterFunc = QString( "%1 = '%2'" )
128  .arg( attrName.replace( "\"", "\"\"" ) )
129  .arg( mValue.toString().replace( "'", "''" ) );
130  QgsSymbolLayerV2Utils::createFunctionElement( doc, ruleElem, filterFunc );
131 
132  mSymbol->toSld( doc, ruleElem, props );
133 }
134 
136 
138  : QgsFeatureRendererV2( "categorizedSymbol" ),
139  mAttrName( attrName ),
140  mCategories( categories ),
141  mSourceSymbol( NULL ),
142  mSourceColorRamp( NULL ),
143  mScaleMethod( DEFAULT_SCALE_METHOD ),
144  mRotationFieldIdx( -1 ),
145  mSizeScaleFieldIdx( -1 )
146 {
147  for ( int i = 0; i < mCategories.count(); ++i )
148  {
150  if ( cat.symbol() == NULL )
151  {
152  QgsDebugMsg( "invalid symbol in a category! ignoring..." );
153  mCategories.removeAt( i-- );
154  }
155  //mCategories.insert(cat.value().toString(), cat);
156  }
157 }
158 
160 {
161  mCategories.clear(); // this should also call destructors of symbols
162  delete mSourceSymbol;
163  delete mSourceColorRamp;
164 }
165 
167 {
168  mSymbolHash.clear();
169 
170  for ( int i = 0; i < mCategories.count(); ++i )
171  {
173  mSymbolHash.insert( cat.value().toString(), cat.symbol() );
174  }
175 }
176 
178 {
179  // TODO: special case for int, double
180  QHash<QString, QgsSymbolV2*>::iterator it = mSymbolHash.find( value.toString() );
181  if ( it == mSymbolHash.end() )
182  {
183  if ( mSymbolHash.count() == 0 )
184  {
185  QgsDebugMsg( "there are no hashed symbols!!!" );
186  }
187  else
188  {
189  QgsDebugMsgLevel( "attribute value not found: " + value.toString(), 3 );
190  }
191  return NULL;
192  }
193 
194  return *it;
195 }
196 
198 {
199  const QgsAttributes& attrs = feature.attributes();
200  if ( mAttrNum < 0 || mAttrNum >= attrs.count() )
201  {
202  QgsDebugMsg( "attribute '" + mAttrName + "' (index " + QString::number( mAttrNum ) + ") required by renderer not found" );
203  return NULL;
204  }
205 
206  // find the right symbol for the category
207  QgsSymbolV2* symbol = symbolForValue( attrs[mAttrNum] );
208  if ( symbol == NULL )
209  {
210  // if no symbol found use default one
211  return symbolForValue( QVariant( "" ) );
212  }
213 
214  if ( mRotationFieldIdx == -1 && mSizeScaleFieldIdx == -1 )
215  return symbol; // no data-defined rotation/scaling - just return the symbol
216 
217  // find out rotation, size scale
218  double rotation = 0;
219  double sizeScale = 1;
220  if ( mRotationFieldIdx != -1 )
221  rotation = attrs[mRotationFieldIdx].toDouble();
222  if ( mSizeScaleFieldIdx != -1 )
223  sizeScale = attrs[mSizeScaleFieldIdx].toDouble();
224 
225  // take a temporary symbol (or create it if doesn't exist)
226  QgsSymbolV2* tempSymbol = mTempSymbols[attrs[mAttrNum].toString()];
227 
228  // modify the temporary symbol and return it
229  if ( tempSymbol->type() == QgsSymbolV2::Marker )
230  {
231  QgsMarkerSymbolV2* markerSymbol = static_cast<QgsMarkerSymbolV2*>( tempSymbol );
232  if ( mRotationFieldIdx != -1 )
233  markerSymbol->setAngle( rotation );
234  if ( mSizeScaleFieldIdx != -1 )
235  markerSymbol->setSize( sizeScale * static_cast<QgsMarkerSymbolV2*>( symbol )->size() );
236  markerSymbol->setScaleMethod( mScaleMethod );
237  }
238  else if ( tempSymbol->type() == QgsSymbolV2::Line )
239  {
240  QgsLineSymbolV2* lineSymbol = static_cast<QgsLineSymbolV2*>( tempSymbol );
241  if ( mSizeScaleFieldIdx != -1 )
242  lineSymbol->setWidth( sizeScale * static_cast<QgsLineSymbolV2*>( symbol )->width() );
243  }
244 
245  return tempSymbol;
246 }
247 
249 {
250  for ( int i = 0; i < mCategories.count(); i++ )
251  {
252  if ( mCategories[i].value() == val )
253  return i;
254  }
255  return -1;
256 }
257 
258 bool QgsCategorizedSymbolRendererV2::updateCategoryValue( int catIndex, const QVariant &value )
259 {
260  if ( catIndex < 0 || catIndex >= mCategories.size() )
261  return false;
262  mCategories[catIndex].setValue( value );
263  return true;
264 }
265 
267 {
268  if ( catIndex < 0 || catIndex >= mCategories.size() )
269  return false;
270  mCategories[catIndex].setSymbol( symbol );
271  return true;
272 }
273 
274 bool QgsCategorizedSymbolRendererV2::updateCategoryLabel( int catIndex, QString label )
275 {
276  if ( catIndex < 0 || catIndex >= mCategories.size() )
277  return false;
278  mCategories[catIndex].setLabel( label );
279  return true;
280 }
281 
283 {
284  if ( cat.symbol() == NULL )
285  {
286  QgsDebugMsg( "invalid symbol in a category! ignoring..." );
287  }
288  else
289  {
290  mCategories.append( cat );
291  }
292 }
293 
295 {
296  if ( catIndex < 0 || catIndex >= mCategories.size() )
297  return false;
298 
299  mCategories.removeAt( catIndex );
300  return true;
301 }
302 
304 {
305  mCategories.clear();
306 }
307 
309 {
310  if ( from < 0 || from >= mCategories.size() || to < 0 || to >= mCategories.size() ) return;
311  mCategories.move( from, to );
312 }
313 
315 {
316  return qgsVariantLessThan( c1.value(), c2.value() );
317 }
319 {
320  return qgsVariantGreaterThan( c1.value(), c2.value() );
321 }
322 
324 {
325  if ( order == Qt::AscendingOrder )
326  {
327  qSort( mCategories.begin(), mCategories.end(), valueLessThan );
328  }
329  else
330  {
331  qSort( mCategories.begin(), mCategories.end(), valueGreaterThan );
332  }
333 }
334 
336 {
337  return QString::localeAwareCompare( c1.label(), c2.label() ) < 0;
338 }
339 
341 {
342  return !labelLessThan( c1, c2 );
343 }
344 
346 {
347  if ( order == Qt::AscendingOrder )
348  {
349  qSort( mCategories.begin(), mCategories.end(), labelLessThan );
350  }
351  else
352  {
353  qSort( mCategories.begin(), mCategories.end(), labelGreaterThan );
354  }
355 }
356 
358 {
359  // make sure that the hash table is up to date
360  rebuildHash();
361 
362  // find out classification attribute index from name
363  mAttrNum = vlayer ? vlayer->fieldNameIndex( mAttrName ) : -1;
364 
365  mRotationFieldIdx = ( mRotationField.isEmpty() ? -1 : vlayer->fieldNameIndex( mRotationField ) );
366  mSizeScaleFieldIdx = ( mSizeScaleField.isEmpty() ? -1 : vlayer->fieldNameIndex( mSizeScaleField ) );
367 
368  QgsCategoryList::iterator it = mCategories.begin();
369  for ( ; it != mCategories.end(); ++it )
370  {
371  it->symbol()->startRender( context, vlayer );
372 
373  if ( mRotationFieldIdx != -1 || mSizeScaleFieldIdx != -1 )
374  {
375  QgsSymbolV2* tempSymbol = it->symbol()->clone();
378  tempSymbol->startRender( context, vlayer );
379  mTempSymbols[ it->value().toString()] = tempSymbol;
380  }
381  }
382 
383 }
384 
386 {
387  QgsCategoryList::iterator it = mCategories.begin();
388  for ( ; it != mCategories.end(); ++it )
389  it->symbol()->stopRender( context );
390 
391  // cleanup mTempSymbols
392  QHash<QString, QgsSymbolV2*>::iterator it2 = mTempSymbols.begin();
393  for ( ; it2 != mTempSymbols.end(); ++it2 )
394  {
395  it2.value()->stopRender( context );
396  delete it2.value();
397  }
398  mTempSymbols.clear();
399 }
400 
402 {
403  QSet<QString> attributes;
404  attributes.insert( mAttrName );
405  if ( !mRotationField.isEmpty() )
406  {
407  attributes.insert( mRotationField );
408  }
409  if ( !mSizeScaleField.isEmpty() )
410  {
411  attributes.insert( mSizeScaleField );
412  }
413 
414  QgsCategoryList::const_iterator catIt = mCategories.constBegin();
415  for ( ; catIt != mCategories.constEnd(); ++catIt )
416  {
417  QgsSymbolV2* catSymbol = catIt->symbol();
418  if ( catSymbol )
419  {
420  attributes.unite( catSymbol->usedAttributes() );
421  }
422  }
423  return attributes.toList();
424 }
425 
427 {
428  QString s = QString( "CATEGORIZED: idx %1\n" ).arg( mAttrName );
429  for ( int i = 0; i < mCategories.count(); i++ )
430  s += mCategories[i].dump();
431  return s;
432 }
433 
435 {
437  if ( mSourceSymbol )
439  if ( mSourceColorRamp )
444  r->setScaleMethod( scaleMethod() );
445  return r;
446 }
447 
448 void QgsCategorizedSymbolRendererV2::toSld( QDomDocument &doc, QDomElement &element ) const
449 {
450  QgsStringMap props;
451  props[ "attribute" ] = mAttrName;
452  if ( !mRotationField.isEmpty() )
453  props[ "angle" ] = QString( mRotationField ).append( "\"" ).prepend( "\"" );
454  if ( !mSizeScaleField.isEmpty() )
455  props[ "scale" ] = QString( mSizeScaleField ).append( "\"" ).prepend( "\"" );
456 
457  // create a Rule for each range
458  for ( QgsCategoryList::const_iterator it = mCategories.constBegin(); it != mCategories.constEnd(); it++ )
459  {
460  QgsStringMap catProps( props );
461  it->toSld( doc, element, catProps );
462  }
463 }
464 
466 {
467  QgsSymbolV2List lst;
468  for ( int i = 0; i < mCategories.count(); i++ )
469  lst.append( mCategories[i].symbol() );
470  return lst;
471 }
472 
474 {
475  QDomElement symbolsElem = element.firstChildElement( "symbols" );
476  if ( symbolsElem.isNull() )
477  return NULL;
478 
479  QDomElement catsElem = element.firstChildElement( "categories" );
480  if ( catsElem.isNull() )
481  return NULL;
482 
483  QgsSymbolV2Map symbolMap = QgsSymbolLayerV2Utils::loadSymbols( symbolsElem );
484  QgsCategoryList cats;
485 
486  QDomElement catElem = catsElem.firstChildElement();
487  while ( !catElem.isNull() )
488  {
489  if ( catElem.tagName() == "category" )
490  {
491  QVariant value = QVariant( catElem.attribute( "value" ) );
492  QString symbolName = catElem.attribute( "symbol" );
493  QString label = catElem.attribute( "label" );
494  if ( symbolMap.contains( symbolName ) )
495  {
496  QgsSymbolV2* symbol = symbolMap.take( symbolName );
497  cats.append( QgsRendererCategoryV2( value, symbol, label ) );
498  }
499  }
500  catElem = catElem.nextSiblingElement();
501  }
502 
503  QString attrName = element.attribute( "attr" );
504 
506 
507  // delete symbols if there are any more
509 
510  // try to load source symbol (optional)
511  QDomElement sourceSymbolElem = element.firstChildElement( "source-symbol" );
512  if ( !sourceSymbolElem.isNull() )
513  {
514  QgsSymbolV2Map sourceSymbolMap = QgsSymbolLayerV2Utils::loadSymbols( sourceSymbolElem );
515  if ( sourceSymbolMap.contains( "0" ) )
516  {
517  r->setSourceSymbol( sourceSymbolMap.take( "0" ) );
518  }
519  QgsSymbolLayerV2Utils::clearSymbolMap( sourceSymbolMap );
520  }
521 
522  // try to load color ramp (optional)
523  QDomElement sourceColorRampElem = element.firstChildElement( "colorramp" );
524  if ( !sourceColorRampElem.isNull() && sourceColorRampElem.attribute( "name" ) == "[source]" )
525  {
526  r->setSourceColorRamp( QgsSymbolLayerV2Utils::loadColorRamp( sourceColorRampElem ) );
527  }
528 
529  QDomElement rotationElem = element.firstChildElement( "rotation" );
530  if ( !rotationElem.isNull() )
531  r->setRotationField( rotationElem.attribute( "field" ) );
532 
533  QDomElement sizeScaleElem = element.firstChildElement( "sizescale" );
534  if ( !sizeScaleElem.isNull() )
535  {
536  r->setSizeScaleField( sizeScaleElem.attribute( "field" ) );
537  r->setScaleMethod( QgsSymbolLayerV2Utils::decodeScaleMethod( sizeScaleElem.attribute( "scalemethod" ) ) );
538  }
539 
540  // TODO: symbol levels
541  return r;
542 }
543 
544 QDomElement QgsCategorizedSymbolRendererV2::save( QDomDocument& doc )
545 {
546  QDomElement rendererElem = doc.createElement( RENDERER_TAG_NAME );
547  rendererElem.setAttribute( "type", "categorizedSymbol" );
548  rendererElem.setAttribute( "symbollevels", ( mUsingSymbolLevels ? "1" : "0" ) );
549  rendererElem.setAttribute( "attr", mAttrName );
550 
551  // categories
552  int i = 0;
554  QDomElement catsElem = doc.createElement( "categories" );
555  QgsCategoryList::const_iterator it = mCategories.constBegin();
556  for ( ; it != mCategories.end(); it++ )
557  {
558  const QgsRendererCategoryV2& cat = *it;
559  QString symbolName = QString::number( i );
560  symbols.insert( symbolName, cat.symbol() );
561 
562  QDomElement catElem = doc.createElement( "category" );
563  catElem.setAttribute( "value", cat.value().toString() );
564  catElem.setAttribute( "symbol", symbolName );
565  catElem.setAttribute( "label", cat.label() );
566  catsElem.appendChild( catElem );
567  i++;
568  }
569 
570  rendererElem.appendChild( catsElem );
571 
572  // save symbols
573  QDomElement symbolsElem = QgsSymbolLayerV2Utils::saveSymbols( symbols, "symbols", doc );
574  rendererElem.appendChild( symbolsElem );
575 
576  // save source symbol
577  if ( mSourceSymbol )
578  {
579  QgsSymbolV2Map sourceSymbols;
580  sourceSymbols.insert( "0", mSourceSymbol );
581  QDomElement sourceSymbolElem = QgsSymbolLayerV2Utils::saveSymbols( sourceSymbols, "source-symbol", doc );
582  rendererElem.appendChild( sourceSymbolElem );
583  }
584 
585  // save source color ramp
586  if ( mSourceColorRamp )
587  {
588  QDomElement colorRampElem = QgsSymbolLayerV2Utils::saveColorRamp( "[source]", mSourceColorRamp, doc );
589  rendererElem.appendChild( colorRampElem );
590  }
591 
592  QDomElement rotationElem = doc.createElement( "rotation" );
593  rotationElem.setAttribute( "field", mRotationField );
594  rendererElem.appendChild( rotationElem );
595 
596  QDomElement sizeScaleElem = doc.createElement( "sizescale" );
597  sizeScaleElem.setAttribute( "field", mSizeScaleField );
598  sizeScaleElem.setAttribute( "scalemethod", QgsSymbolLayerV2Utils::encodeScaleMethod( mScaleMethod ) );
599  rendererElem.appendChild( sizeScaleElem );
600 
601  return rendererElem;
602 }
603 
605 {
606  QSettings settings;
607  bool showClassifiers = settings.value( "/qgis/showLegendClassifiers", false ).toBool();
608 
610  if ( showClassifiers )
611  {
612  lst << qMakePair( classAttribute(), QPixmap() );
613  }
614 
615  int count = categories().count();
616  for ( int i = 0; i < count; i++ )
617  {
618  const QgsRendererCategoryV2& cat = categories()[i];
619  QPixmap pix = QgsSymbolLayerV2Utils::symbolPreviewPixmap( cat.symbol(), iconSize );
620  lst << qMakePair( cat.label(), pix );
621  }
622  return lst;
623 }
624 
626 {
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  lst << qMakePair( cat.label(), cat.symbol() );
639  }
640  return lst;
641 }
642 
643 
645 {
646  return mSourceSymbol;
647 }
649 {
650  delete mSourceSymbol;
651  mSourceSymbol = sym;
652 }
653 
655 {
656  return mSourceColorRamp;
657 }
659 {
660  delete mSourceColorRamp;
661  mSourceColorRamp = ramp;
662 }
663 
665 {
666  int i = 0;
667  foreach ( QgsRendererCategoryV2 cat, mCategories )
668  {
669  QgsSymbolV2* symbol = sym->clone();
670  symbol->setColor( cat.symbol()->color() );
671  updateCategorySymbol( i, symbol );
672  ++i;
673  }
674 }
675 
677 {
679  QgsCategoryList::const_iterator catIt = mCategories.constBegin();
680  for ( ; catIt != mCategories.constEnd(); ++catIt )
681  {
682  setScaleMethodToSymbol( catIt->symbol(), scaleMethod );
683  }
684 }