Quantum GIS API Documentation  master-ce49b66
src/core/symbology-ng/qgsvectorcolorrampv2.cpp
Go to the documentation of this file.
00001 /***************************************************************************
00002     qgsvectorcolorrampv2.cpp
00003     ---------------------
00004     begin                : November 2009
00005     copyright            : (C) 2009 by Martin Dobias
00006     email                : wonder dot sk at gmail dot com
00007  ***************************************************************************
00008  *                                                                         *
00009  *   This program is free software; you can redistribute it and/or modify  *
00010  *   it under the terms of the GNU General Public License as published by  *
00011  *   the Free Software Foundation; either version 2 of the License, or     *
00012  *   (at your option) any later version.                                   *
00013  *                                                                         *
00014  ***************************************************************************/
00015 
00016 #include "qgsvectorcolorrampv2.h"
00017 #include "qgscolorbrewerpalette.h"
00018 #include "qgscptcityarchive.h"
00019 
00020 #include "qgssymbollayerv2utils.h"
00021 #include "qgsapplication.h"
00022 #include "qgslogger.h"
00023 
00024 #include <stdlib.h> // for random()
00025 #include <QTime>
00026 
00028 
00029 static QColor _interpolate( QColor c1, QColor c2, double value )
00030 {
00031   int r = ( int )( c1.red() + value * ( c2.red() - c1.red() ) );
00032   int g = ( int )( c1.green() + value * ( c2.green() - c1.green() ) );
00033   int b = ( int )( c1.blue() + value * ( c2.blue() - c1.blue() ) );
00034   int a = ( int )( c1.alpha() + value * ( c2.alpha() - c1.alpha() ) );
00035 
00036   return QColor::fromRgb( r, g, b, a );
00037 }
00038 
00040 
00041 QgsVectorGradientColorRampV2::QgsVectorGradientColorRampV2( QColor color1, QColor color2,
00042     bool discrete, QgsGradientStopsList stops )
00043     : mColor1( color1 ), mColor2( color2 ), mDiscrete( discrete ), mStops( stops )
00044 {
00045 }
00046 
00047 QgsVectorColorRampV2* QgsVectorGradientColorRampV2::create( const QgsStringMap& props )
00048 {
00049   // color1 and color2
00050   QColor color1 = DEFAULT_GRADIENT_COLOR1;
00051   QColor color2 = DEFAULT_GRADIENT_COLOR2;
00052   if ( props.contains( "color1" ) )
00053     color1 = QgsSymbolLayerV2Utils::decodeColor( props["color1"] );
00054   if ( props.contains( "color2" ) )
00055     color2 = QgsSymbolLayerV2Utils::decodeColor( props["color2"] );
00056 
00057   //stops
00058   QgsGradientStopsList stops;
00059   if ( props.contains( "stops" ) )
00060   {
00061     foreach ( QString stop, props["stops"].split( ':' ) )
00062     {
00063       int i = stop.indexOf( ';' );
00064       if ( i == -1 )
00065         continue;
00066 
00067       QColor c = QgsSymbolLayerV2Utils::decodeColor( stop.mid( i + 1 ) );
00068       stops.append( QgsGradientStop( stop.left( i ).toDouble(), c ) );
00069     }
00070   }
00071 
00072   // discrete vs. continuous
00073   bool discrete = false;
00074   if ( props.contains( "discrete" ) )
00075   {
00076     if ( props["discrete"] == "1" )
00077       discrete = true;
00078   }
00079 
00080   // search for information keys starting with "info_"
00081   QgsStringMap info;
00082   for ( QgsStringMap::const_iterator it = props.constBegin();
00083         it != props.constEnd(); ++it )
00084   {
00085     if ( it.key().startsWith( "info_" ) )
00086       info[ it.key().mid( 5 )] = it.value();
00087   }
00088 
00089   QgsVectorGradientColorRampV2* r = new QgsVectorGradientColorRampV2( color1, color2, discrete, stops );
00090   r->setInfo( info );
00091   return r;
00092 }
00093 
00094 double QgsVectorGradientColorRampV2::value( int index ) const
00095 {
00096   if ( index <= 0 )
00097   {
00098     return 0;
00099   }
00100   else if ( index >= mStops.size() + 1 )
00101   {
00102     return 1;
00103   }
00104   else
00105   {
00106     return mStops[index-1].offset;
00107   }
00108 }
00109 
00110 QColor QgsVectorGradientColorRampV2::color( double value ) const
00111 {
00112   if ( mStops.isEmpty() )
00113   {
00114     if ( mDiscrete )
00115       return mColor1;
00116     return _interpolate( mColor1, mColor2, value );
00117   }
00118   else
00119   {
00120     double lower = 0, upper = 0;
00121     QColor c1 = mColor1, c2;
00122     for ( QgsGradientStopsList::const_iterator it = mStops.begin(); it != mStops.end(); ++it )
00123     {
00124       if ( it->offset > value )
00125       {
00126         if ( mDiscrete )
00127           return c1;
00128 
00129         upper = it->offset;
00130         c2 = it->color;
00131 
00132         return upper == lower ? c1 : _interpolate( c1, c2, ( value - lower ) / ( upper - lower ) );
00133       }
00134       lower = it->offset;
00135       c1 = it->color;
00136     }
00137 
00138     if ( mDiscrete )
00139       return c1;
00140 
00141     upper = 1;
00142     c2 = mColor2;
00143     return upper == lower ? c1 : _interpolate( c1, c2, ( value - lower ) / ( upper - lower ) );
00144   }
00145 }
00146 
00147 QgsVectorColorRampV2* QgsVectorGradientColorRampV2::clone() const
00148 {
00149   QgsVectorGradientColorRampV2* r = new QgsVectorGradientColorRampV2( mColor1, mColor2,
00150       mDiscrete, mStops );
00151   r->setInfo( mInfo );
00152   return r;
00153 }
00154 
00155 QgsStringMap QgsVectorGradientColorRampV2::properties() const
00156 {
00157   QgsStringMap map;
00158   map["color1"] = QgsSymbolLayerV2Utils::encodeColor( mColor1 );
00159   map["color2"] = QgsSymbolLayerV2Utils::encodeColor( mColor2 );
00160   if ( !mStops.isEmpty() )
00161   {
00162     QStringList lst;
00163     for ( QgsGradientStopsList::const_iterator it = mStops.begin(); it != mStops.end(); ++it )
00164     {
00165       lst.append( QString( "%1;%2" ).arg( it->offset ).arg( QgsSymbolLayerV2Utils::encodeColor( it->color ) ) );
00166     }
00167     map["stops"] = lst.join( ":" );
00168   }
00169 
00170   map["discrete"] = mDiscrete ? "1" : "0";
00171 
00172   for ( QgsStringMap::const_iterator it = mInfo.constBegin();
00173         it != mInfo.constEnd(); ++it )
00174   {
00175     map["info_" + it.key()] = it.value();
00176   }
00177 
00178   return map;
00179 }
00180 void QgsVectorGradientColorRampV2::convertToDiscrete( bool discrete )
00181 {
00182   if ( discrete == mDiscrete )
00183     return;
00184 
00185   // if going to/from Discrete, re-arrange stops
00186   // this will only work when stops are equally-spaced
00187   QgsGradientStopsList newStops;
00188   if ( discrete )
00189   {
00190     // re-arrange stops offset
00191     int numStops = mStops.count() + 2;
00192     int i = 1;
00193     for ( QgsGradientStopsList::const_iterator it = mStops.begin();
00194           it != mStops.end(); ++it )
00195     {
00196       newStops.append( QgsGradientStop(( double ) i / numStops, it->color ) );
00197       if ( i == numStops - 1 )
00198         break;
00199       i++;
00200     }
00201     // replicate last color
00202     newStops.append( QgsGradientStop(( double ) i / numStops, mColor2 ) );
00203   }
00204   else
00205   {
00206     // re-arrange stops offset, remove duplicate last color
00207     int numStops = mStops.count() + 2;
00208     int i = 1;
00209     for ( QgsGradientStopsList::const_iterator it = mStops.begin();
00210           it != mStops.end(); ++it )
00211     {
00212       newStops.append( QgsGradientStop(( double ) i / ( numStops - 2 ), it->color ) );
00213       if ( i == numStops - 3 )
00214         break;
00215       i++;
00216     }
00217   }
00218   mStops = newStops;
00219   mDiscrete = discrete;
00220 }
00221 
00223 
00224 
00225 QgsVectorRandomColorRampV2::QgsVectorRandomColorRampV2( int count, int hueMin, int hueMax,
00226     int satMin, int satMax, int valMin, int valMax )
00227     : mCount( count ), mHueMin( hueMin ), mHueMax( hueMax ),
00228     mSatMin( satMin ), mSatMax( satMax ), mValMin( valMin ), mValMax( valMax )
00229 {
00230   updateColors();
00231 }
00232 
00233 QgsVectorColorRampV2* QgsVectorRandomColorRampV2::create( const QgsStringMap& props )
00234 {
00235   int count = DEFAULT_RANDOM_COUNT;
00236   int hueMin = DEFAULT_RANDOM_HUE_MIN, hueMax = DEFAULT_RANDOM_HUE_MAX;
00237   int satMin = DEFAULT_RANDOM_SAT_MIN, satMax = DEFAULT_RANDOM_SAT_MAX;
00238   int valMin = DEFAULT_RANDOM_VAL_MIN, valMax = DEFAULT_RANDOM_VAL_MAX;
00239 
00240   if ( props.contains( "count" ) ) count = props["count"].toInt();
00241   if ( props.contains( "hueMin" ) ) hueMin = props["hueMin"].toInt();
00242   if ( props.contains( "hueMax" ) ) hueMax = props["hueMax"].toInt();
00243   if ( props.contains( "satMin" ) ) satMin = props["satMin"].toInt();
00244   if ( props.contains( "satMax" ) ) satMax = props["satMax"].toInt();
00245   if ( props.contains( "valMin" ) ) valMin = props["valMin"].toInt();
00246   if ( props.contains( "valMax" ) ) valMax = props["valMax"].toInt();
00247 
00248   return new QgsVectorRandomColorRampV2( count, hueMin, hueMax, satMin, satMax, valMin, valMax );
00249 }
00250 
00251 double QgsVectorRandomColorRampV2::value( int index ) const
00252 {
00253   if ( mColors.size() < 1 ) return 0;
00254   return index / mColors.size() - 1;
00255 }
00256 
00257 QColor QgsVectorRandomColorRampV2::color( double value ) const
00258 {
00259   int colorCnt = mColors.count();
00260   int colorIdx = ( int )( value * colorCnt );
00261 
00262   if ( colorIdx >= 0 && colorIdx < colorCnt )
00263     return mColors.at( colorIdx );
00264 
00265   return QColor();
00266 }
00267 
00268 QgsVectorColorRampV2* QgsVectorRandomColorRampV2::clone() const
00269 {
00270   return new QgsVectorRandomColorRampV2( mCount, mHueMin, mHueMax, mSatMin, mSatMax, mValMin, mValMax );
00271 }
00272 
00273 QgsStringMap QgsVectorRandomColorRampV2::properties() const
00274 {
00275   QgsStringMap map;
00276   map["count"] = QString::number( mCount );
00277   map["hueMin"] = QString::number( mHueMin );
00278   map["hueMax"] = QString::number( mHueMax );
00279   map["satMin"] = QString::number( mSatMin );
00280   map["satMax"] = QString::number( mSatMax );
00281   map["valMin"] = QString::number( mValMin );
00282   map["valMax"] = QString::number( mValMax );
00283   return map;
00284 }
00285 
00286 void QgsVectorRandomColorRampV2::updateColors()
00287 {
00288   int h, s, v;
00289 
00290   mColors.clear();
00291   for ( int i = 0; i < mCount; i++ )
00292   {
00293     h = ( rand() % ( mHueMax - mHueMin + 1 ) ) + mHueMin;
00294     s = ( rand() % ( mSatMax - mSatMin + 1 ) ) + mSatMin;
00295     v = ( rand() % ( mValMax - mValMin + 1 ) ) + mValMin;
00296     mColors.append( QColor::fromHsv( h, s, v ) );
00297   }
00298 }
00299 
00300 
00302 
00303 QgsVectorColorBrewerColorRampV2::QgsVectorColorBrewerColorRampV2( QString schemeName, int colors )
00304     : mSchemeName( schemeName ), mColors( colors )
00305 {
00306   loadPalette();
00307 }
00308 
00309 QgsVectorColorRampV2* QgsVectorColorBrewerColorRampV2::create( const QgsStringMap& props )
00310 {
00311   QString schemeName = DEFAULT_COLORBREWER_SCHEMENAME;
00312   int colors = DEFAULT_COLORBREWER_COLORS;
00313 
00314   if ( props.contains( "schemeName" ) )
00315     schemeName = props["schemeName"];
00316   if ( props.contains( "colors" ) )
00317     colors = props["colors"].toInt();
00318 
00319   return new QgsVectorColorBrewerColorRampV2( schemeName, colors );
00320 }
00321 
00322 void QgsVectorColorBrewerColorRampV2::loadPalette()
00323 {
00324   mPalette = QgsColorBrewerPalette::listSchemeColors( mSchemeName, mColors );
00325 }
00326 
00327 QStringList QgsVectorColorBrewerColorRampV2::listSchemeNames()
00328 {
00329   return QgsColorBrewerPalette::listSchemes();
00330 }
00331 
00332 QList<int> QgsVectorColorBrewerColorRampV2::listSchemeVariants( QString schemeName )
00333 {
00334   return QgsColorBrewerPalette::listSchemeVariants( schemeName );
00335 }
00336 
00337 double QgsVectorColorBrewerColorRampV2::value( int index ) const
00338 {
00339   if ( mPalette.size() < 1 ) return 0;
00340   return index / mPalette.size() - 1;
00341 }
00342 
00343 QColor QgsVectorColorBrewerColorRampV2::color( double value ) const
00344 {
00345   if ( mPalette.isEmpty() || value < 0 || value > 1 )
00346     return QColor( 255, 0, 0 ); // red color as a warning :)
00347 
00348   int paletteEntry = ( int )( value * mPalette.count() );
00349   if ( paletteEntry >= mPalette.count() )
00350     paletteEntry = mPalette.count() - 1;
00351   return mPalette.at( paletteEntry );
00352 }
00353 
00354 QgsVectorColorRampV2* QgsVectorColorBrewerColorRampV2::clone() const
00355 {
00356   return new QgsVectorColorBrewerColorRampV2( mSchemeName, mColors );
00357 }
00358 
00359 QgsStringMap QgsVectorColorBrewerColorRampV2::properties() const
00360 {
00361   QgsStringMap map;
00362   map["schemeName"] = mSchemeName;
00363   map["colors"] = QString::number( mColors );
00364   return map;
00365 }
00366 
00367 
00369 
00370 
00371 QgsCptCityColorRampV2::QgsCptCityColorRampV2( QString schemeName, QString variantName,
00372     bool doLoadFile )
00373     : QgsVectorGradientColorRampV2(),
00374     mSchemeName( schemeName ), mVariantName( variantName ),
00375     mVariantList( QStringList() ), mFileLoaded( false ), mMultiStops( false )
00376 {
00377   // TODO replace this with hard-coded data in the default case
00378   // don't load file if variant is missing
00379   if ( doLoadFile && ( variantName != QString() || mVariantList.isEmpty() ) )
00380     loadFile();
00381 }
00382 
00383 QgsCptCityColorRampV2::QgsCptCityColorRampV2( QString schemeName,  QStringList variantList,
00384     QString variantName, bool doLoadFile )
00385     : QgsVectorGradientColorRampV2(),
00386     mSchemeName( schemeName ), mVariantName( variantName ),
00387     mVariantList( variantList ), mFileLoaded( false ), mMultiStops( false )
00388 {
00389   mVariantList = variantList;
00390 
00391   // TODO replace this with hard-coded data in the default case
00392   // don't load file if variant is missing
00393   if ( doLoadFile && ( variantName != QString() || mVariantList.isEmpty() ) )
00394     loadFile();
00395 }
00396 
00397 QgsVectorColorRampV2* QgsCptCityColorRampV2::create( const QgsStringMap& props )
00398 {
00399   QString schemeName = DEFAULT_CPTCITY_SCHEMENAME;
00400   QString variantName = DEFAULT_CPTCITY_VARIANTNAME;
00401 
00402   if ( props.contains( "schemeName" ) )
00403     schemeName = props["schemeName"];
00404   if ( props.contains( "variantName" ) )
00405     variantName = props["variantName"];
00406 
00407   return new QgsCptCityColorRampV2( schemeName, variantName );
00408 }
00409 
00410 QgsVectorColorRampV2* QgsCptCityColorRampV2::clone() const
00411 {
00412   QgsCptCityColorRampV2* ramp = new QgsCptCityColorRampV2( "", "", false );
00413   ramp->copy( this );
00414   return ramp;
00415 }
00416 
00417 void QgsCptCityColorRampV2::copy( const QgsCptCityColorRampV2* other )
00418 {
00419   if ( ! other )
00420     return;
00421   mColor1 = other->color1();
00422   mColor2 = other->color2();
00423   mDiscrete = other->isDiscrete();
00424   mStops = other->stops();
00425   mSchemeName = other->mSchemeName;
00426   mVariantName = other->mVariantName;
00427   mVariantList = other->mVariantList;
00428   mFileLoaded = other->mFileLoaded;
00429 }
00430 
00431 QgsVectorGradientColorRampV2* QgsCptCityColorRampV2::cloneGradientRamp() const
00432 {
00433   QgsVectorGradientColorRampV2* ramp =
00434     new QgsVectorGradientColorRampV2( mColor1, mColor2, mDiscrete, mStops );
00435   // add author and copyright information
00436   // TODO also add COPYING.xml file/link?
00437   QgsStringMap info = copyingInfo();
00438   info["cpt-city-gradient"] = "<cpt-city>/" + mSchemeName + mVariantName + ".svg";
00439   QString copyingFilename = copyingFileName();
00440   copyingFilename.remove( QgsCptCityArchive::defaultBaseDir() );
00441   info["cpt-city-license"] = "<cpt-city>" + copyingFilename;
00442   ramp->setInfo( info );
00443   return ramp;
00444 }
00445 
00446 
00447 QgsStringMap QgsCptCityColorRampV2::properties() const
00448 {
00449   QgsStringMap map;
00450   map["schemeName"] = mSchemeName;
00451   map["variantName"] = mVariantName;
00452   return map;
00453 }
00454 
00455 
00456 QString QgsCptCityColorRampV2::fileName() const
00457 {
00458   if ( mSchemeName == "" )
00459     return QString();
00460   else
00461   {
00462     return QgsCptCityArchive::defaultBaseDir() + QDir::separator() + mSchemeName + mVariantName + ".svg";
00463   }
00464 }
00465 
00466 QString QgsCptCityColorRampV2::copyingFileName() const
00467 {
00468   return QgsCptCityArchive::findFileName( "COPYING.xml", QFileInfo( fileName() ).dir().path(),
00469                                           QgsCptCityArchive::defaultBaseDir() );
00470 }
00471 
00472 QString QgsCptCityColorRampV2::descFileName() const
00473 {
00474   return QgsCptCityArchive::findFileName( "DESC.xml", QFileInfo( fileName() ).dir().path(),
00475                                           QgsCptCityArchive::defaultBaseDir() );
00476 }
00477 
00478 QgsStringMap QgsCptCityColorRampV2::copyingInfo( ) const
00479 {
00480   return QgsCptCityArchive::copyingInfo( copyingFileName() );
00481 }
00482 
00483 bool QgsCptCityColorRampV2::loadFile()
00484 {
00485   if ( mFileLoaded )
00486   {
00487     QgsDebugMsg( "File already loaded for " + mSchemeName + mVariantName );
00488     return true;
00489   }
00490 
00491   // get filename
00492   QString filename = fileName();
00493   if ( filename.isNull() )
00494   {
00495     QgsDebugMsg( "Couldn't get fileName() for " + mSchemeName + mVariantName );
00496     return false;
00497   }
00498 
00499   QgsDebugMsg( QString( "filename= %1 loaded=%2" ).arg( filename ).arg( mFileLoaded ) );
00500 
00501   // get color ramp from svg file
00502   QMap< double, QPair<QColor, QColor> > colorMap =
00503     QgsCptCityArchive::gradientColorMap( filename );
00504 
00505   // add colors to palette
00506   mFileLoaded = false;
00507   mStops.clear();
00508   QMap<double, QPair<QColor, QColor> >::const_iterator it, prev;
00509   // first detect if file is gradient is continuous or dicrete
00510   // discrete: stop contains 2 colors and first color is identical to previous second
00511   // multi: stop contains 2 colors and no relation with previous stop
00512   mDiscrete = false;
00513   mMultiStops = false;
00514   it = prev = colorMap.constBegin();
00515   while ( it != colorMap.constEnd() )
00516   {
00517     // look for stops that contain multiple values
00518     if ( it != colorMap.constBegin() && ( it.value().first != it.value().second ) )
00519     {
00520       if ( it.value().first == prev.value().second )
00521       {
00522         mDiscrete = true;
00523         break;
00524       }
00525       else
00526       {
00527         mMultiStops = true;
00528         break;
00529       }
00530     }
00531     prev = it;
00532     ++it;
00533   }
00534 
00535   // fill all stops
00536   it = prev = colorMap.constBegin();
00537   while ( it != colorMap.constEnd() )
00538   {
00539     if ( mDiscrete )
00540     {
00541       // mPalette << qMakePair( it.key(), it.value().second );
00542       mStops.append( QgsGradientStop( it.key(), it.value().second ) );
00543     }
00544     else
00545     {
00546       // mPalette << qMakePair( it.key(), it.value().first );
00547       mStops.append( QgsGradientStop( it.key(), it.value().first ) );
00548       if (( mMultiStops ) &&
00549           ( it.key() != 0.0 && it.key() != 1.0 ) )
00550       {
00551         mStops.append( QgsGradientStop( it.key(), it.value().second ) );
00552       }
00553     }
00554     prev = it;
00555     ++it;
00556   }
00557 
00558   // remove first and last items (mColor1 and mColor2)
00559   if ( ! mStops.isEmpty() && mStops.first().offset == 0.0 )
00560     mColor1 = mStops.takeFirst().color;
00561   if ( ! mStops.isEmpty() && mStops.last().offset == 1.0 )
00562     mColor2 = mStops.takeLast().color;
00563 
00564   mFileLoaded = true;
00565   return true;
00566 }
00567 
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Defines