QGIS API Documentation  2.5.0-Master
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
qgsvectorcolorrampv2.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsvectorcolorrampv2.cpp
3  ---------------------
4  begin : November 2009
5  copyright : (C) 2009 by Martin Dobias
6  email : wonder dot sk at gmail dot com
7  ***************************************************************************
8  * *
9  * This program is free software; you can redistribute it and/or modify *
10  * it under the terms of the GNU General Public License as published by *
11  * the Free Software Foundation; either version 2 of the License, or *
12  * (at your option) any later version. *
13  * *
14  ***************************************************************************/
15 
16 #include "qgsvectorcolorrampv2.h"
17 #include "qgscolorbrewerpalette.h"
18 #include "qgscptcityarchive.h"
19 
20 #include "qgssymbollayerv2utils.h"
21 #include "qgsapplication.h"
22 #include "qgslogger.h"
23 
24 #include <stdlib.h> // for random()
25 #include <QTime>
26 
28 
29 static QColor _interpolate( QColor c1, QColor c2, double value )
30 {
31  int r = ( int )( c1.red() + value * ( c2.red() - c1.red() ) );
32  int g = ( int )( c1.green() + value * ( c2.green() - c1.green() ) );
33  int b = ( int )( c1.blue() + value * ( c2.blue() - c1.blue() ) );
34  int a = ( int )( c1.alpha() + value * ( c2.alpha() - c1.alpha() ) );
35 
36  return QColor::fromRgb( r, g, b, a );
37 }
38 
40 
42  bool discrete, QgsGradientStopsList stops )
43  : mColor1( color1 ), mColor2( color2 ), mDiscrete( discrete ), mStops( stops )
44 {
45 }
46 
48 {
49  // color1 and color2
52  if ( props.contains( "color1" ) )
53  color1 = QgsSymbolLayerV2Utils::decodeColor( props["color1"] );
54  if ( props.contains( "color2" ) )
55  color2 = QgsSymbolLayerV2Utils::decodeColor( props["color2"] );
56 
57  //stops
59  if ( props.contains( "stops" ) )
60  {
61  foreach ( QString stop, props["stops"].split( ':' ) )
62  {
63  int i = stop.indexOf( ';' );
64  if ( i == -1 )
65  continue;
66 
67  QColor c = QgsSymbolLayerV2Utils::decodeColor( stop.mid( i + 1 ) );
68  stops.append( QgsGradientStop( stop.left( i ).toDouble(), c ) );
69  }
70  }
71 
72  // discrete vs. continuous
73  bool discrete = false;
74  if ( props.contains( "discrete" ) )
75  {
76  if ( props["discrete"] == "1" )
77  discrete = true;
78  }
79 
80  // search for information keys starting with "info_"
82  for ( QgsStringMap::const_iterator it = props.constBegin();
83  it != props.constEnd(); ++it )
84  {
85  if ( it.key().startsWith( "info_" ) )
86  info[ it.key().mid( 5 )] = it.value();
87  }
88 
89  QgsVectorGradientColorRampV2* r = new QgsVectorGradientColorRampV2( color1, color2, discrete, stops );
90  r->setInfo( info );
91  return r;
92 }
93 
95 {
96  if ( index <= 0 )
97  {
98  return 0;
99  }
100  else if ( index >= mStops.size() + 1 )
101  {
102  return 1;
103  }
104  else
105  {
106  return mStops[index-1].offset;
107  }
108 }
109 
110 QColor QgsVectorGradientColorRampV2::color( double value ) const
111 {
112  if ( mStops.isEmpty() )
113  {
114  if ( mDiscrete )
115  return mColor1;
116  return _interpolate( mColor1, mColor2, value );
117  }
118  else
119  {
120  double lower = 0, upper = 0;
121  QColor c1 = mColor1, c2;
122  for ( QgsGradientStopsList::const_iterator it = mStops.begin(); it != mStops.end(); ++it )
123  {
124  if ( it->offset > value )
125  {
126  if ( mDiscrete )
127  return c1;
128 
129  upper = it->offset;
130  c2 = it->color;
131 
132  return upper == lower ? c1 : _interpolate( c1, c2, ( value - lower ) / ( upper - lower ) );
133  }
134  lower = it->offset;
135  c1 = it->color;
136  }
137 
138  if ( mDiscrete )
139  return c1;
140 
141  upper = 1;
142  c2 = mColor2;
143  return upper == lower ? c1 : _interpolate( c1, c2, ( value - lower ) / ( upper - lower ) );
144  }
145 }
146 
148 {
150  mDiscrete, mStops );
151  r->setInfo( mInfo );
152  return r;
153 }
154 
156 {
157  QgsStringMap map;
158  map["color1"] = QgsSymbolLayerV2Utils::encodeColor( mColor1 );
159  map["color2"] = QgsSymbolLayerV2Utils::encodeColor( mColor2 );
160  if ( !mStops.isEmpty() )
161  {
162  QStringList lst;
163  for ( QgsGradientStopsList::const_iterator it = mStops.begin(); it != mStops.end(); ++it )
164  {
165  lst.append( QString( "%1;%2" ).arg( it->offset ).arg( QgsSymbolLayerV2Utils::encodeColor( it->color ) ) );
166  }
167  map["stops"] = lst.join( ":" );
168  }
169 
170  map["discrete"] = mDiscrete ? "1" : "0";
171 
172  for ( QgsStringMap::const_iterator it = mInfo.constBegin();
173  it != mInfo.constEnd(); ++it )
174  {
175  map["info_" + it.key()] = it.value();
176  }
177 
178  return map;
179 }
181 {
182  if ( discrete == mDiscrete )
183  return;
184 
185  // if going to/from Discrete, re-arrange stops
186  // this will only work when stops are equally-spaced
187  QgsGradientStopsList newStops;
188  if ( discrete )
189  {
190  // re-arrange stops offset
191  int numStops = mStops.count() + 2;
192  int i = 1;
193  for ( QgsGradientStopsList::const_iterator it = mStops.begin();
194  it != mStops.end(); ++it )
195  {
196  newStops.append( QgsGradientStop(( double ) i / numStops, it->color ) );
197  if ( i == numStops - 1 )
198  break;
199  i++;
200  }
201  // replicate last color
202  newStops.append( QgsGradientStop(( double ) i / numStops, mColor2 ) );
203  }
204  else
205  {
206  // re-arrange stops offset, remove duplicate last color
207  int numStops = mStops.count() + 2;
208  int i = 1;
209  for ( QgsGradientStopsList::const_iterator it = mStops.begin();
210  it != mStops.end(); ++it )
211  {
212  newStops.append( QgsGradientStop(( double ) i / ( numStops - 2 ), it->color ) );
213  if ( i == numStops - 3 )
214  break;
215  i++;
216  }
217  }
218  mStops = newStops;
219  mDiscrete = discrete;
220 }
221 
222 void QgsVectorGradientColorRampV2::addStopsToGradient( QGradient* gradient, double alpha )
223 {
224  //copy color ramp stops to a QGradient
225  QColor color1 = mColor1;
226  QColor color2 = mColor2;
227  if ( alpha < 1 )
228  {
229  color1.setAlpha( color1.alpha() * alpha );
230  color2.setAlpha( color2.alpha() * alpha );
231  }
232  gradient->setColorAt( 0, color1 );
233  gradient->setColorAt( 1, color2 );
234 
235  for ( QgsGradientStopsList::const_iterator it = mStops.begin();
236  it != mStops.end(); ++it )
237  {
238  QColor rampColor = it->color;
239  if ( alpha < 1 )
240  {
241  rampColor.setAlpha( rampColor.alpha() * alpha );
242  }
243  gradient->setColorAt( it->offset , rampColor );
244  }
245 }
246 
247 
249 
250 
252  int satMin, int satMax, int valMin, int valMax )
253  : mCount( count ), mHueMin( hueMin ), mHueMax( hueMax ),
254  mSatMin( satMin ), mSatMax( satMax ), mValMin( valMin ), mValMax( valMax )
255 {
256  updateColors();
257 }
258 
260 {
265 
266  if ( props.contains( "count" ) ) count = props["count"].toInt();
267  if ( props.contains( "hueMin" ) ) hueMin = props["hueMin"].toInt();
268  if ( props.contains( "hueMax" ) ) hueMax = props["hueMax"].toInt();
269  if ( props.contains( "satMin" ) ) satMin = props["satMin"].toInt();
270  if ( props.contains( "satMax" ) ) satMax = props["satMax"].toInt();
271  if ( props.contains( "valMin" ) ) valMin = props["valMin"].toInt();
272  if ( props.contains( "valMax" ) ) valMax = props["valMax"].toInt();
273 
274  return new QgsVectorRandomColorRampV2( count, hueMin, hueMax, satMin, satMax, valMin, valMax );
275 }
276 
278 {
279  if ( mColors.size() < 1 ) return 0;
280  return index / mColors.size() - 1;
281 }
282 
283 QColor QgsVectorRandomColorRampV2::color( double value ) const
284 {
285  int colorCnt = mColors.count();
286  int colorIdx = ( int )( value * colorCnt );
287 
288  if ( colorIdx >= 0 && colorIdx < colorCnt )
289  return mColors.at( colorIdx );
290 
291  return QColor();
292 }
293 
295 {
297 }
298 
300 {
301  QgsStringMap map;
302  map["count"] = QString::number( mCount );
303  map["hueMin"] = QString::number( mHueMin );
304  map["hueMax"] = QString::number( mHueMax );
305  map["satMin"] = QString::number( mSatMin );
306  map["satMax"] = QString::number( mSatMax );
307  map["valMin"] = QString::number( mValMin );
308  map["valMax"] = QString::number( mValMax );
309  return map;
310 }
311 
313  int hueMax, int hueMin, int satMax, int satMin, int valMax, int valMin )
314 {
315  int h, s, v;
316  QList<QColor> colors;
317 
318  //start hue at random angle
319  double currentHueAngle = 360.0 * ( double )rand() / RAND_MAX;
320 
321  for ( int i = 0; i < count; i++ )
322  {
323  //increment hue by golden ratio (approx 137.507 degrees)
324  //as this minimises hue nearness as count increases
325  //see http://basecase.org/env/on-rainbows for more details
326  currentHueAngle += 137.50776;
327  //scale hue to between hueMax and hueMin
328  h = ( fmod( currentHueAngle, 360.0 ) / 360.0 ) * ( hueMax - hueMin ) + hueMin;
329  s = ( rand() % ( satMax - satMin + 1 ) ) + satMin;
330  v = ( rand() % ( valMax - valMin + 1 ) ) + valMin;
331  colors.append( QColor::fromHsv( h, s, v ) );
332  }
333  return colors;
334 }
335 
337 {
339 }
340 
342 
344 {
345  srand( QTime::currentTime().msec() );
346 }
347 
349 {
350 
351 }
352 
354 {
355  return INT_MAX;
356 }
357 
358 double QgsRandomColorsV2::value( int index ) const
359 {
360  Q_UNUSED( index );
361  return 0.0;
362 }
363 
364 QColor QgsRandomColorsV2::color( double value ) const
365 {
366  Q_UNUSED( value );
367  int minVal = 130;
368  int maxVal = 255;
369  int h = 1 + ( int )( 360.0 * rand() / ( RAND_MAX + 1.0 ) );
371  int v = ( rand() % ( maxVal - minVal + 1 ) ) + minVal;
372  return QColor::fromHsv( h, s, v );
373 }
374 
375 QString QgsRandomColorsV2::type() const
376 {
377  return "randomcolors";
378 }
379 
381 {
382  return new QgsRandomColorsV2();
383 }
384 
386 {
387  return QgsStringMap();
388 }
389 
391 
393  : mSchemeName( schemeName ), mColors( colors )
394 {
395  loadPalette();
396 }
397 
399 {
402 
403  if ( props.contains( "schemeName" ) )
404  schemeName = props["schemeName"];
405  if ( props.contains( "colors" ) )
406  colors = props["colors"].toInt();
407 
408  return new QgsVectorColorBrewerColorRampV2( schemeName, colors );
409 }
410 
412 {
414 }
415 
417 {
419 }
420 
422 {
423  return QgsColorBrewerPalette::listSchemeVariants( schemeName );
424 }
425 
427 {
428  if ( mPalette.size() < 1 ) return 0;
429  return index / mPalette.size() - 1;
430 }
431 
432 QColor QgsVectorColorBrewerColorRampV2::color( double value ) const
433 {
434  if ( mPalette.isEmpty() || value < 0 || value > 1 )
435  return QColor( 255, 0, 0 ); // red color as a warning :)
436 
437  int paletteEntry = ( int )( value * mPalette.count() );
438  if ( paletteEntry >= mPalette.count() )
439  paletteEntry = mPalette.count() - 1;
440  return mPalette.at( paletteEntry );
441 }
442 
444 {
446 }
447 
449 {
450  QgsStringMap map;
451  map["schemeName"] = mSchemeName;
452  map["colors"] = QString::number( mColors );
453  return map;
454 }
455 
456 
458 
459 
460 QgsCptCityColorRampV2::QgsCptCityColorRampV2( QString schemeName, QString variantName,
461  bool doLoadFile )
463  mSchemeName( schemeName ), mVariantName( variantName ),
464  mVariantList( QStringList() ), mFileLoaded( false ), mMultiStops( false )
465 {
466  // TODO replace this with hard-coded data in the default case
467  // don't load file if variant is missing
468  if ( doLoadFile && ( variantName != QString() || mVariantList.isEmpty() ) )
469  loadFile();
470 }
471 
472 QgsCptCityColorRampV2::QgsCptCityColorRampV2( QString schemeName, QStringList variantList,
473  QString variantName, bool doLoadFile )
475  mSchemeName( schemeName ), mVariantName( variantName ),
476  mVariantList( variantList ), mFileLoaded( false ), mMultiStops( false )
477 {
479 
480  // TODO replace this with hard-coded data in the default case
481  // don't load file if variant is missing
482  if ( doLoadFile && ( variantName != QString() || mVariantList.isEmpty() ) )
483  loadFile();
484 }
485 
487 {
490 
491  if ( props.contains( "schemeName" ) )
492  schemeName = props["schemeName"];
493  if ( props.contains( "variantName" ) )
494  variantName = props["variantName"];
495 
496  return new QgsCptCityColorRampV2( schemeName, variantName );
497 }
498 
500 {
501  QgsCptCityColorRampV2* ramp = new QgsCptCityColorRampV2( "", "", false );
502  ramp->copy( this );
503  return ramp;
504 }
505 
507 {
508  if ( ! other )
509  return;
510  mColor1 = other->color1();
511  mColor2 = other->color2();
512  mDiscrete = other->isDiscrete();
513  mStops = other->stops();
514  mSchemeName = other->mSchemeName;
515  mVariantName = other->mVariantName;
516  mVariantList = other->mVariantList;
517  mFileLoaded = other->mFileLoaded;
518 }
519 
521 {
524  // add author and copyright information
525  // TODO also add COPYING.xml file/link?
527  info["cpt-city-gradient"] = "<cpt-city>/" + mSchemeName + mVariantName + ".svg";
528  QString copyingFilename = copyingFileName();
529  copyingFilename.remove( QgsCptCityArchive::defaultBaseDir() );
530  info["cpt-city-license"] = "<cpt-city>" + copyingFilename;
531  ramp->setInfo( info );
532  return ramp;
533 }
534 
535 
537 {
538  QgsStringMap map;
539  map["schemeName"] = mSchemeName;
540  map["variantName"] = mVariantName;
541  return map;
542 }
543 
544 
546 {
547  if ( mSchemeName == "" )
548  return QString();
549  else
550  {
551  return QgsCptCityArchive::defaultBaseDir() + QDir::separator() + mSchemeName + mVariantName + ".svg";
552  }
553 }
554 
556 {
557  return QgsCptCityArchive::findFileName( "COPYING.xml", QFileInfo( fileName() ).dir().path(),
559 }
560 
562 {
563  return QgsCptCityArchive::findFileName( "DESC.xml", QFileInfo( fileName() ).dir().path(),
565 }
566 
568 {
570 }
571 
573 {
574  if ( mFileLoaded )
575  {
576  QgsDebugMsg( "File already loaded for " + mSchemeName + mVariantName );
577  return true;
578  }
579 
580  // get filename
581  QString filename = fileName();
582  if ( filename.isNull() )
583  {
584  QgsDebugMsg( "Couldn't get fileName() for " + mSchemeName + mVariantName );
585  return false;
586  }
587 
588  QgsDebugMsg( QString( "filename= %1 loaded=%2" ).arg( filename ).arg( mFileLoaded ) );
589 
590  // get color ramp from svg file
591  QMap< double, QPair<QColor, QColor> > colorMap =
593 
594  // add colors to palette
595  mFileLoaded = false;
596  mStops.clear();
597  QMap<double, QPair<QColor, QColor> >::const_iterator it, prev;
598  // first detect if file is gradient is continuous or dicrete
599  // discrete: stop contains 2 colors and first color is identical to previous second
600  // multi: stop contains 2 colors and no relation with previous stop
601  mDiscrete = false;
602  mMultiStops = false;
603  it = prev = colorMap.constBegin();
604  while ( it != colorMap.constEnd() )
605  {
606  // look for stops that contain multiple values
607  if ( it != colorMap.constBegin() && ( it.value().first != it.value().second ) )
608  {
609  if ( it.value().first == prev.value().second )
610  {
611  mDiscrete = true;
612  break;
613  }
614  else
615  {
616  mMultiStops = true;
617  break;
618  }
619  }
620  prev = it;
621  ++it;
622  }
623 
624  // fill all stops
625  it = prev = colorMap.constBegin();
626  while ( it != colorMap.constEnd() )
627  {
628  if ( mDiscrete )
629  {
630  // mPalette << qMakePair( it.key(), it.value().second );
631  mStops.append( QgsGradientStop( it.key(), it.value().second ) );
632  }
633  else
634  {
635  // mPalette << qMakePair( it.key(), it.value().first );
636  mStops.append( QgsGradientStop( it.key(), it.value().first ) );
637  if (( mMultiStops ) &&
638  ( it.key() != 0.0 && it.key() != 1.0 ) )
639  {
640  mStops.append( QgsGradientStop( it.key(), it.value().second ) );
641  }
642  }
643  prev = it;
644  ++it;
645  }
646 
647  // remove first and last items (mColor1 and mColor2)
648  if ( ! mStops.isEmpty() && mStops.first().offset == 0.0 )
649  mColor1 = mStops.takeFirst().color;
650  if ( ! mStops.isEmpty() && mStops.last().offset == 1.0 )
651  mColor2 = mStops.takeLast().color;
652 
653  mFileLoaded = true;
654  return true;
655 }
656 
static QgsVectorColorRampV2 * create(const QgsStringMap &properties=QgsStringMap())
static QgsVectorColorRampV2 * create(const QgsStringMap &properties=QgsStringMap())
static unsigned index
#define DEFAULT_CPTCITY_VARIANTNAME
double value(int index) const
QgsVectorGradientColorRampV2(QColor color1=DEFAULT_GRADIENT_COLOR1, QColor color2=DEFAULT_GRADIENT_COLOR2, bool discrete=false, QgsGradientStopsList stops=QgsGradientStopsList())
void copy(const QgsCptCityColorRampV2 *other)
static QList< int > listSchemeVariants(QString schemeName)
QgsVectorColorRampV2 * clone() const
#define QgsDebugMsg(str)
Definition: qgslogger.h:36
static QString defaultBaseDir()
#define DEFAULT_COLORBREWER_SCHEMENAME
#define DEFAULT_RANDOM_HUE_MIN
static QColor _interpolate(QColor c1, QColor c2, double value)
void setInfo(const QgsStringMap &info)
static QList< int > listSchemeVariants(QString schemeName)
static QColor decodeColor(QString str)
#define DEFAULT_CPTCITY_SCHEMENAME
QMap< QString, QString > QgsStringMap
Definition: qgis.h:416
#define DEFAULT_RANDOM_SAT_MAX
static QString encodeColor(QColor color)
virtual QgsStringMap properties() const
virtual QgsStringMap properties() const
QString variantName() const
virtual QColor color(double value) const
QStringList variantList() const
#define DEFAULT_GRADIENT_COLOR2
static QList< QColor > listSchemeColors(QString schemeName, int colors)
QgsVectorRandomColorRampV2(int count=DEFAULT_RANDOM_COUNT, int hueMin=DEFAULT_RANDOM_HUE_MIN, int hueMax=DEFAULT_RANDOM_HUE_MAX, int satMin=DEFAULT_RANDOM_SAT_MIN, int satMax=DEFAULT_RANDOM_SAT_MAX, int valMin=DEFAULT_RANDOM_VAL_MIN, int valMax=DEFAULT_RANDOM_VAL_MAX)
QColor color(double value) const
#define DEFAULT_GRADIENT_COLOR1
#define DEFAULT_RANDOM_VAL_MAX
QgsStringMap copyingInfo() const
virtual double value(int index) const
static QMap< QString, QString > copyingInfo(const QString &fileName)
static QList< QColor > randomColors(int count, int hueMax=DEFAULT_RANDOM_HUE_MAX, int hueMin=DEFAULT_RANDOM_HUE_MIN, int satMax=DEFAULT_RANDOM_SAT_MAX, int satMin=DEFAULT_RANDOM_SAT_MIN, int valMax=DEFAULT_RANDOM_VAL_MAX, int valMin=DEFAULT_RANDOM_VAL_MIN)
get a list of random colors
virtual QgsStringMap properties() const
virtual double value(int index) const
virtual QgsStringMap properties() const
#define DEFAULT_RANDOM_SAT_MIN
static QgsVectorColorRampV2 * create(const QgsStringMap &properties=QgsStringMap())
virtual QColor color(double value) const
virtual QgsVectorColorRampV2 * clone() const
QgsVectorGradientColorRampV2 * cloneGradientRamp() const
int ANALYSIS_EXPORT lower(int n, int i)
lower function
QList< QgsGradientStop > QgsGradientStopsList
virtual double value(int index) const
#define DEFAULT_COLORBREWER_COLORS
static QStringList listSchemes()
const QgsGradientStopsList & stops() const
virtual QgsVectorColorRampV2 * clone() const
static QgsVectorColorRampV2 * create(const QgsStringMap &properties=QgsStringMap())
#define DEFAULT_RANDOM_HUE_MAX
virtual QgsVectorColorRampV2 * clone() const
QgsCptCityColorRampV2(QString schemeName=DEFAULT_CPTCITY_SCHEMENAME, QString variantName=DEFAULT_CPTCITY_VARIANTNAME, bool doLoadFile=true)
QgsStringMap properties() const
static QMap< double, QPair< QColor, QColor > > gradientColorMap(const QString &fileName)
note not available in python bindings
QgsVectorColorBrewerColorRampV2(QString schemeName=DEFAULT_COLORBREWER_SCHEMENAME, int colors=DEFAULT_COLORBREWER_COLORS)
virtual QColor color(double value) const
#define DEFAULT_RANDOM_VAL_MIN
void addStopsToGradient(QGradient *gradient, double alpha=1)
copy color ramp stops to a QGradient
QString copyingFileName() const
#define DEFAULT_RANDOM_COUNT
static QString findFileName(const QString &target, const QString &startDir, const QString &baseDir)
virtual QgsVectorColorRampV2 * clone() const