QGIS API Documentation  2.0.1-Dufour
 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 
94 double QgsVectorGradientColorRampV2::value( int index ) const
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 
223 
224 
226  int satMin, int satMax, int valMin, int valMax )
227  : mCount( count ), mHueMin( hueMin ), mHueMax( hueMax ),
228  mSatMin( satMin ), mSatMax( satMax ), mValMin( valMin ), mValMax( valMax )
229 {
230  updateColors();
231 }
232 
234 {
239 
240  if ( props.contains( "count" ) ) count = props["count"].toInt();
241  if ( props.contains( "hueMin" ) ) hueMin = props["hueMin"].toInt();
242  if ( props.contains( "hueMax" ) ) hueMax = props["hueMax"].toInt();
243  if ( props.contains( "satMin" ) ) satMin = props["satMin"].toInt();
244  if ( props.contains( "satMax" ) ) satMax = props["satMax"].toInt();
245  if ( props.contains( "valMin" ) ) valMin = props["valMin"].toInt();
246  if ( props.contains( "valMax" ) ) valMax = props["valMax"].toInt();
247 
248  return new QgsVectorRandomColorRampV2( count, hueMin, hueMax, satMin, satMax, valMin, valMax );
249 }
250 
251 double QgsVectorRandomColorRampV2::value( int index ) const
252 {
253  if ( mColors.size() < 1 ) return 0;
254  return index / mColors.size() - 1;
255 }
256 
257 QColor QgsVectorRandomColorRampV2::color( double value ) const
258 {
259  int colorCnt = mColors.count();
260  int colorIdx = ( int )( value * colorCnt );
261 
262  if ( colorIdx >= 0 && colorIdx < colorCnt )
263  return mColors.at( colorIdx );
264 
265  return QColor();
266 }
267 
269 {
271 }
272 
274 {
275  QgsStringMap map;
276  map["count"] = QString::number( mCount );
277  map["hueMin"] = QString::number( mHueMin );
278  map["hueMax"] = QString::number( mHueMax );
279  map["satMin"] = QString::number( mSatMin );
280  map["satMax"] = QString::number( mSatMax );
281  map["valMin"] = QString::number( mValMin );
282  map["valMax"] = QString::number( mValMax );
283  return map;
284 }
285 
287 {
288  int h, s, v;
289 
290  mColors.clear();
291  //start hue at random angle
292  double currentHueAngle = 360.0 * ( double )rand() / RAND_MAX;
293 
294  for ( int i = 0; i < mCount; i++ )
295  {
296  //increment hue by golden ratio (approx 137.507 degrees)
297  //as this minimises hue nearness as count increases
298  //see http://basecase.org/env/on-rainbows for more details
299  currentHueAngle += 137.50776;
300  //scale hue to between mHueMax and mHueMin
301  h = ( fmod( currentHueAngle, 360.0 ) / 360.0 ) * ( mHueMax - mHueMin ) + mHueMin;
302  s = ( rand() % ( mSatMax - mSatMin + 1 ) ) + mSatMin;
303  v = ( rand() % ( mValMax - mValMin + 1 ) ) + mValMin;
304  mColors.append( QColor::fromHsv( h, s, v ) );
305  }
306 }
307 
308 
310 
312  : mSchemeName( schemeName ), mColors( colors )
313 {
314  loadPalette();
315 }
316 
318 {
321 
322  if ( props.contains( "schemeName" ) )
323  schemeName = props["schemeName"];
324  if ( props.contains( "colors" ) )
325  colors = props["colors"].toInt();
326 
327  return new QgsVectorColorBrewerColorRampV2( schemeName, colors );
328 }
329 
331 {
333 }
334 
336 {
338 }
339 
341 {
342  return QgsColorBrewerPalette::listSchemeVariants( schemeName );
343 }
344 
345 double QgsVectorColorBrewerColorRampV2::value( int index ) const
346 {
347  if ( mPalette.size() < 1 ) return 0;
348  return index / mPalette.size() - 1;
349 }
350 
351 QColor QgsVectorColorBrewerColorRampV2::color( double value ) const
352 {
353  if ( mPalette.isEmpty() || value < 0 || value > 1 )
354  return QColor( 255, 0, 0 ); // red color as a warning :)
355 
356  int paletteEntry = ( int )( value * mPalette.count() );
357  if ( paletteEntry >= mPalette.count() )
358  paletteEntry = mPalette.count() - 1;
359  return mPalette.at( paletteEntry );
360 }
361 
363 {
365 }
366 
368 {
369  QgsStringMap map;
370  map["schemeName"] = mSchemeName;
371  map["colors"] = QString::number( mColors );
372  return map;
373 }
374 
375 
377 
378 
379 QgsCptCityColorRampV2::QgsCptCityColorRampV2( QString schemeName, QString variantName,
380  bool doLoadFile )
382  mSchemeName( schemeName ), mVariantName( variantName ),
383  mVariantList( QStringList() ), mFileLoaded( false ), mMultiStops( false )
384 {
385  // TODO replace this with hard-coded data in the default case
386  // don't load file if variant is missing
387  if ( doLoadFile && ( variantName != QString() || mVariantList.isEmpty() ) )
388  loadFile();
389 }
390 
391 QgsCptCityColorRampV2::QgsCptCityColorRampV2( QString schemeName, QStringList variantList,
392  QString variantName, bool doLoadFile )
394  mSchemeName( schemeName ), mVariantName( variantName ),
395  mVariantList( variantList ), mFileLoaded( false ), mMultiStops( false )
396 {
398 
399  // TODO replace this with hard-coded data in the default case
400  // don't load file if variant is missing
401  if ( doLoadFile && ( variantName != QString() || mVariantList.isEmpty() ) )
402  loadFile();
403 }
404 
406 {
409 
410  if ( props.contains( "schemeName" ) )
411  schemeName = props["schemeName"];
412  if ( props.contains( "variantName" ) )
413  variantName = props["variantName"];
414 
415  return new QgsCptCityColorRampV2( schemeName, variantName );
416 }
417 
419 {
420  QgsCptCityColorRampV2* ramp = new QgsCptCityColorRampV2( "", "", false );
421  ramp->copy( this );
422  return ramp;
423 }
424 
426 {
427  if ( ! other )
428  return;
429  mColor1 = other->color1();
430  mColor2 = other->color2();
431  mDiscrete = other->isDiscrete();
432  mStops = other->stops();
433  mSchemeName = other->mSchemeName;
434  mVariantName = other->mVariantName;
435  mVariantList = other->mVariantList;
436  mFileLoaded = other->mFileLoaded;
437 }
438 
440 {
443  // add author and copyright information
444  // TODO also add COPYING.xml file/link?
446  info["cpt-city-gradient"] = "<cpt-city>/" + mSchemeName + mVariantName + ".svg";
447  QString copyingFilename = copyingFileName();
448  copyingFilename.remove( QgsCptCityArchive::defaultBaseDir() );
449  info["cpt-city-license"] = "<cpt-city>" + copyingFilename;
450  ramp->setInfo( info );
451  return ramp;
452 }
453 
454 
456 {
457  QgsStringMap map;
458  map["schemeName"] = mSchemeName;
459  map["variantName"] = mVariantName;
460  return map;
461 }
462 
463 
465 {
466  if ( mSchemeName == "" )
467  return QString();
468  else
469  {
470  return QgsCptCityArchive::defaultBaseDir() + QDir::separator() + mSchemeName + mVariantName + ".svg";
471  }
472 }
473 
475 {
476  return QgsCptCityArchive::findFileName( "COPYING.xml", QFileInfo( fileName() ).dir().path(),
478 }
479 
481 {
482  return QgsCptCityArchive::findFileName( "DESC.xml", QFileInfo( fileName() ).dir().path(),
484 }
485 
487 {
489 }
490 
492 {
493  if ( mFileLoaded )
494  {
495  QgsDebugMsg( "File already loaded for " + mSchemeName + mVariantName );
496  return true;
497  }
498 
499  // get filename
500  QString filename = fileName();
501  if ( filename.isNull() )
502  {
503  QgsDebugMsg( "Couldn't get fileName() for " + mSchemeName + mVariantName );
504  return false;
505  }
506 
507  QgsDebugMsg( QString( "filename= %1 loaded=%2" ).arg( filename ).arg( mFileLoaded ) );
508 
509  // get color ramp from svg file
510  QMap< double, QPair<QColor, QColor> > colorMap =
512 
513  // add colors to palette
514  mFileLoaded = false;
515  mStops.clear();
516  QMap<double, QPair<QColor, QColor> >::const_iterator it, prev;
517  // first detect if file is gradient is continuous or dicrete
518  // discrete: stop contains 2 colors and first color is identical to previous second
519  // multi: stop contains 2 colors and no relation with previous stop
520  mDiscrete = false;
521  mMultiStops = false;
522  it = prev = colorMap.constBegin();
523  while ( it != colorMap.constEnd() )
524  {
525  // look for stops that contain multiple values
526  if ( it != colorMap.constBegin() && ( it.value().first != it.value().second ) )
527  {
528  if ( it.value().first == prev.value().second )
529  {
530  mDiscrete = true;
531  break;
532  }
533  else
534  {
535  mMultiStops = true;
536  break;
537  }
538  }
539  prev = it;
540  ++it;
541  }
542 
543  // fill all stops
544  it = prev = colorMap.constBegin();
545  while ( it != colorMap.constEnd() )
546  {
547  if ( mDiscrete )
548  {
549  // mPalette << qMakePair( it.key(), it.value().second );
550  mStops.append( QgsGradientStop( it.key(), it.value().second ) );
551  }
552  else
553  {
554  // mPalette << qMakePair( it.key(), it.value().first );
555  mStops.append( QgsGradientStop( it.key(), it.value().first ) );
556  if (( mMultiStops ) &&
557  ( it.key() != 0.0 && it.key() != 1.0 ) )
558  {
559  mStops.append( QgsGradientStop( it.key(), it.value().second ) );
560  }
561  }
562  prev = it;
563  ++it;
564  }
565 
566  // remove first and last items (mColor1 and mColor2)
567  if ( ! mStops.isEmpty() && mStops.first().offset == 0.0 )
568  mColor1 = mStops.takeFirst().color;
569  if ( ! mStops.isEmpty() && mStops.last().offset == 1.0 )
570  mColor2 = mStops.takeLast().color;
571 
572  mFileLoaded = true;
573  return true;
574 }
575