QGIS API Documentation  master-59fd5e0
src/gui/raster/qgsrasterhistogramwidget.cpp
Go to the documentation of this file.
00001 /***************************************************************************
00002                          qgsrasterhistogramwidget.cpp
00003                          ---------------------------
00004     begin                : July 2012
00005     copyright            : (C) 2012 by Etienne Tourigny
00006     email                : etourigny dot dev at gmail dot com
00007  ***************************************************************************/
00008 
00009 /***************************************************************************
00010  *                                                                         *
00011  *   This program is free software; you can redistribute it and/or modify  *
00012  *   it under the terms of the GNU General Public License as published by  *
00013  *   the Free Software Foundation; either version 2 of the License, or     *
00014  *   (at your option) any later version.                                   *
00015  *                                                                         *
00016  ***************************************************************************/
00017 
00018 #include "qgsapplication.h"
00019 #include "qgisgui.h"
00020 #include "qgsrasterrendererregistry.h"
00021 #include "qgsrasterrendererwidget.h"
00022 #include "qgsrasterhistogramwidget.h"
00023 
00024 #include <QMenu>
00025 #include <QFileInfo>
00026 #include <QDir>
00027 #include <QPainter>
00028 #include <QSettings>
00029 
00030 // QWT Charting widget
00031 #include <qwt_global.h>
00032 #include <qwt_plot_canvas.h>
00033 #include <qwt_legend.h>
00034 #include <qwt_plot.h>
00035 #include <qwt_plot_curve.h>
00036 #include <qwt_plot_grid.h>
00037 #include <qwt_plot_marker.h>
00038 #include <qwt_plot_picker.h>
00039 #include <qwt_picker_machine.h>
00040 #include <qwt_plot_zoomer.h>
00041 #include <qwt_plot_layout.h>
00042 #if defined(QWT_VERSION) && QWT_VERSION>=0x060000
00043 #include <qwt_plot_renderer.h>
00044 #endif
00045 
00046 #define RASTER_HISTOGRAM_BINS 256
00047 
00048 QgsRasterHistogramWidget::QgsRasterHistogramWidget( QgsRasterLayer* lyr, QWidget *parent )
00049     : QWidget( parent ),
00050     mRasterLayer( lyr ), mRendererWidget( 0 )
00051 {
00052   setupUi( this );
00053 
00054   mSaveAsImageButton->setIcon( QgsApplication::getThemeIcon( "/mActionFileSave.png" ) );
00055 
00056   mRendererWidget = 0;
00057   mRendererName = "singlebandgray";
00058 
00059   mHistoMin = 0;
00060   mHistoMax = 0;
00061 
00062   mHistoPicker = NULL;
00063   mHistoZoomer = NULL;
00064   mHistoMarkerMin = NULL;
00065   mHistoMarkerMax = NULL;
00066 
00067   QSettings settings;
00068   mHistoShowMarkers = settings.value( "/Raster/histogram/showMarkers", false ).toBool();
00069   // mHistoLoadApplyAll = settings.value( "/Raster/histogram/loadApplyAll", false ).toBool();
00070   mHistoZoomToMinMax = settings.value( "/Raster/histogram/zoomToMinMax", false ).toBool();
00071   mHistoUpdateStyleToMinMax = settings.value( "/Raster/histogram/updateStyleToMinMax", true ).toBool();
00072   // mHistoShowBands = (HistoShowBands) settings.value( "/Raster/histogram/showBands", (int) ShowAll ).toInt();
00073   mHistoShowBands = ShowAll;
00074 
00075   if ( true )
00076   {
00077     //band selector
00078     int myBandCountInt = mRasterLayer->bandCount();
00079     for ( int myIteratorInt = 1;
00080           myIteratorInt <= myBandCountInt;
00081           ++myIteratorInt )
00082     {
00083       cboHistoBand->addItem( mRasterLayer->bandName( myIteratorInt ) );
00084     }
00085 
00086     // histo min/max selectors
00087     leHistoMin->setValidator( new QDoubleValidator( this ) );
00088     leHistoMax->setValidator( new QDoubleValidator( this ) );
00089     // this might generate many refresh events! test..
00090     // connect( leHistoMin, SIGNAL( textChanged( const QString & ) ), this, SLOT( updateHistoMarkers() ) );
00091     // connect( leHistoMax, SIGNAL( textChanged( const QString & ) ), this, SLOT( updateHistoMarkers() ) );
00092     // connect( leHistoMin, SIGNAL( textChanged( const QString & ) ), this, SLOT( applyHistoMin() ) );
00093     // connect( leHistoMax, SIGNAL( textChanged( const QString & ) ), this, SLOT( applyHistoMax() ) );
00094     connect( leHistoMin, SIGNAL( editingFinished() ), this, SLOT( applyHistoMin() ) );
00095     connect( leHistoMax, SIGNAL( editingFinished() ), this, SLOT( applyHistoMax() ) );
00096 
00097     // histo actions
00098     QMenu* menu = new QMenu( this );
00099     menu->setSeparatorsCollapsible( false );
00100     btnHistoActions->setMenu( menu );
00101     QActionGroup* group;
00102     QAction* action;
00103 
00104     // min/max options
00105     group = new QActionGroup( this );
00106     group->setExclusive( false );
00107     connect( group, SIGNAL( triggered( QAction* ) ), this, SLOT( histoActionTriggered( QAction* ) ) );
00108     action = new QAction( tr( "Min/Max options" ), group );
00109     action->setSeparator( true );
00110     menu->addAction( action );
00111     action = new QAction( tr( "Always show min/max markers" ), group );
00112     action->setData( QVariant( "Show markers" ) );
00113     action->setCheckable( true );
00114     action->setChecked( mHistoShowMarkers );
00115     menu->addAction( action );
00116     action = new QAction( tr( "Zoom to min/max" ), group );
00117     action->setData( QVariant( "Zoom min_max" ) );
00118     action->setCheckable( true );
00119     action->setChecked( mHistoZoomToMinMax );
00120     menu->addAction( action );
00121     action = new QAction( tr( "Update style to min/max" ), group );
00122     action->setData( QVariant( "Update min_max" ) );
00123     action->setCheckable( true );
00124     action->setChecked( mHistoUpdateStyleToMinMax );
00125     menu->addAction( action );
00126 
00127     // visibility options
00128     group = new QActionGroup( this );
00129     group->setExclusive( false );
00130     connect( group, SIGNAL( triggered( QAction* ) ), this, SLOT( histoActionTriggered( QAction* ) ) );
00131     action = new QAction( tr( "Visibility" ), group );
00132     action->setSeparator( true );
00133     menu->addAction( action );
00134     group = new QActionGroup( this );
00135     group->setExclusive( true ); // these options are exclusive
00136     connect( group, SIGNAL( triggered( QAction* ) ), this, SLOT( histoActionTriggered( QAction* ) ) );
00137     action = new QAction( tr( "Show all bands" ), group );
00138     action->setData( QVariant( "Show all" ) );
00139     action->setCheckable( true );
00140     action->setChecked( mHistoShowBands == ShowAll );
00141     menu->addAction( action );
00142     action = new QAction( tr( "Show RGB/Gray band(s)" ), group );
00143     action->setData( QVariant( "Show RGB" ) );
00144     action->setCheckable( true );
00145     action->setChecked( mHistoShowBands == ShowRGB );
00146     menu->addAction( action );
00147     action = new QAction( tr( "Show selected band" ), group );
00148     action->setData( QVariant( "Show selected" ) );
00149     action->setCheckable( true );
00150     action->setChecked( mHistoShowBands == ShowSelected );
00151     menu->addAction( action );
00152 
00153     // actions
00154     action = new QAction( tr( "Actions" ), group );
00155     action->setSeparator( true );
00156     menu->addAction( action );
00157 
00158     // load actions
00159     group = new QActionGroup( this );
00160     group->setExclusive( false );
00161     connect( group, SIGNAL( triggered( QAction* ) ), this, SLOT( histoActionTriggered( QAction* ) ) );
00162     action = new QAction( tr( "Reset" ), group );
00163     action->setData( QVariant( "Load reset" ) );
00164     menu->addAction( action );
00165 
00166     // these actions have been disabled for api cleanup, restore them eventually
00167 #if 0
00168     // Load min/max needs 3 params (method, extent, accuracy), cannot put it in single item
00169     action = new QAction( tr( "Load min/max" ), group );
00170     action->setSeparator( true );
00171     menu->addAction( action );
00172     action = new QAction( tr( "Estimate (faster)" ), group );
00173     action->setData( QVariant( "Load estimate" ) );
00174     menu->addAction( action );
00175     action = new QAction( tr( "Actual (slower)" ), group );
00176     action->setData( QVariant( "Load actual" ) );
00177     menu->addAction( action );
00178     action = new QAction( tr( "Current extent" ), group );
00179     action->setData( QVariant( "Load extent" ) );
00180     menu->addAction( action );
00181     action = new QAction( tr( "Use stddev (1.0)" ), group );
00182     action->setData( QVariant( "Load 1 stddev" ) );
00183     menu->addAction( action );
00184     action = new QAction( tr( "Use stddev (custom)" ), group );
00185     action->setData( QVariant( "Load stddev" ) );
00186     menu->addAction( action );
00187     action = new QAction( tr( "Load for each band" ), group );
00188     action->setData( QVariant( "Load apply all" ) );
00189     action->setCheckable( true );
00190     action->setChecked( mHistoLoadApplyAll );
00191     menu->addAction( action );
00192 #endif
00193 
00194     //others
00195     action = new QAction( tr( "Recompute Histogram" ), group );
00196     action->setData( QVariant( "Compute histogram" ) );
00197     menu->addAction( action );
00198 
00199   }
00200 
00201 } // QgsRasterHistogramWidget ctor
00202 
00203 
00204 QgsRasterHistogramWidget::~QgsRasterHistogramWidget()
00205 {
00206 }
00207 
00208 void QgsRasterHistogramWidget::setRendererWidget( const QString& name, QgsRasterRendererWidget* rendererWidget )
00209 {
00210   mRendererName = name;
00211   mRendererWidget = rendererWidget;
00212   refreshHistogram();
00213   on_cboHistoBand_currentIndexChanged( -1 );
00214 }
00215 
00216 void QgsRasterHistogramWidget::setActive( bool theActiveFlag )
00217 {
00218   if ( theActiveFlag )
00219   {
00220     refreshHistogram();
00221     on_cboHistoBand_currentIndexChanged( -1 );
00222   }
00223   else
00224   {
00225     if ( QApplication::overrideCursor() )
00226       QApplication::restoreOverrideCursor();
00227     btnHistoMin->setChecked( false );
00228     btnHistoMax->setChecked( false );
00229   }
00230 }
00231 
00232 void QgsRasterHistogramWidget::on_btnHistoCompute_clicked()
00233 {
00234 // Histogram computation can be called either by clicking the "Compute Histogram" button
00235 // which is only visible if there is no cached histogram or by calling the
00236 // "Compute Histogram" action. Due to limitations in the gdal api, it is not possible
00237 // to re-calculate the histogramif it has already been calculated
00238   computeHistogram( true );
00239   refreshHistogram();
00240 }
00241 
00242 bool QgsRasterHistogramWidget::computeHistogram( bool forceComputeFlag )
00243 {
00244   const int BINCOUNT = RASTER_HISTOGRAM_BINS;
00245   //bool myIgnoreOutOfRangeFlag = true;
00246   //bool myThoroughBandScanFlag = false;
00247   int myBandCountInt = mRasterLayer->bandCount();
00248 
00249   // if forceComputeFlag = false make sure raster has cached histogram, else return false
00250   if ( ! forceComputeFlag )
00251   {
00252     for ( int myIteratorInt = 1;
00253           myIteratorInt <= myBandCountInt;
00254           ++myIteratorInt )
00255     {
00256       //if ( ! mRasterLayer->hasCachedHistogram( myIteratorInt, BINCOUNT ) )
00257       int sampleSize = 250000; // number of sample cells
00258       if ( !mRasterLayer->dataProvider()->hasHistogram( myIteratorInt, BINCOUNT, std::numeric_limits<double>::quiet_NaN(), std::numeric_limits<double>::quiet_NaN(), QgsRectangle(), sampleSize ) )
00259       {
00260         QgsDebugMsg( QString( "band %1 does not have cached histo" ).arg( myIteratorInt ) );
00261         return false;
00262       }
00263     }
00264   }
00265 
00266   // compute histogram
00267   stackedWidget2->setCurrentIndex( 1 );
00268   connect( mRasterLayer, SIGNAL( progressUpdate( int ) ), mHistogramProgress, SLOT( setValue( int ) ) );
00269   QApplication::setOverrideCursor( Qt::WaitCursor );
00270 
00271   for ( int myIteratorInt = 1;
00272         myIteratorInt <= myBandCountInt;
00273         ++myIteratorInt )
00274   {
00275     //mRasterLayer->populateHistogram( myIteratorInt, BINCOUNT, myIgnoreOutOfRangeFlag, myThoroughBandScanFlag );
00276     int sampleSize = 250000; // number of sample cells
00277     mRasterLayer->dataProvider()->histogram( myIteratorInt, BINCOUNT, std::numeric_limits<double>::quiet_NaN(), std::numeric_limits<double>::quiet_NaN(), QgsRectangle(), sampleSize );
00278   }
00279 
00280   disconnect( mRasterLayer, SIGNAL( progressUpdate( int ) ), mHistogramProgress, SLOT( setValue( int ) ) );
00281   // mHistogramProgress->hide();
00282   stackedWidget2->setCurrentIndex( 0 );
00283   QApplication::restoreOverrideCursor();
00284 
00285   return true;
00286 }
00287 
00288 
00289 void QgsRasterHistogramWidget::refreshHistogram()
00290 {
00291   // Explanation:
00292   // We use the gdal histogram creation routine is called for each selected
00293   // layer. Currently the hist is hardcoded to create 256 bins. Each bin stores
00294   // the total number of cells that fit into the range defined by that bin.
00295   //
00296   // The graph routine below determines the greatest number of pixels in any given
00297   // bin in all selected layers, and the min. It then draws a scaled line between min
00298   // and max - scaled to image height. 1 line drawn per selected band
00299   //
00300   const int BINCOUNT = RASTER_HISTOGRAM_BINS;
00301   int myBandCountInt = mRasterLayer->bandCount();
00302 
00303   QgsDebugMsg( "entered." );
00304 
00305   if ( ! computeHistogram( false ) )
00306   {
00307     QgsDebugMsg( QString( "raster does not have cached histogram" ) );
00308     stackedWidget2->setCurrentIndex( 2 );
00309     return;
00310   }
00311 
00312 #if defined(QWT_VERSION) && QWT_VERSION>=0x060000
00313   mpPlot->detachItems();
00314 #else
00315   mpPlot->clear();
00316 #endif
00317   //ensure all children get removed
00318   mpPlot->setAutoDelete( true );
00319   mpPlot->setTitle( QObject::tr( "Raster Histogram" ) );
00320   mpPlot->insertLegend( new QwtLegend(), QwtPlot::BottomLegend );
00321   // Set axis titles
00322   mpPlot->setAxisTitle( QwtPlot::xBottom, QObject::tr( "Pixel Value" ) );
00323   mpPlot->setAxisTitle( QwtPlot::yLeft, QObject::tr( "Frequency" ) );
00324   mpPlot->setAxisAutoScale( QwtPlot::yLeft );
00325 
00326   // x axis scale only set after computing global min/max across bands (see below)
00327   // add a grid
00328   QwtPlotGrid * myGrid = new QwtPlotGrid();
00329   myGrid->attach( mpPlot );
00330 
00331   // make colors list
00332   mHistoColors.clear();
00333   mHistoColors << Qt::black; // first element, not used
00334   QVector<QColor> myColors;
00335   myColors << Qt::red << Qt::green << Qt::blue << Qt::magenta << Qt::darkYellow << Qt::cyan;
00336   srand( myBandCountInt * 100 ); // make sure colors are always the same for a given band count
00337   while ( myColors.size() <= myBandCountInt )
00338   {
00339     myColors <<
00340     QColor( 1 + ( int )( 255.0 * rand() / ( RAND_MAX + 1.0 ) ),
00341             1 + ( int )( 255.0 * rand() / ( RAND_MAX + 1.0 ) ),
00342             1 + ( int )( 255.0 * rand() / ( RAND_MAX + 1.0 ) ) );
00343   }
00344 
00345   // assign colors to each band, depending on the current RGB/gray band selection
00346   // grayscale
00347   QList< int > mySelectedBands = rendererSelectedBands();
00348   if ( mRendererName == "singlebandgray" )
00349   {
00350     int myGrayBand = mySelectedBands[0];
00351     for ( int i = 1; i <= myBandCountInt; i++ )
00352     {
00353       if ( i == myGrayBand )
00354       {
00355         mHistoColors << Qt::darkGray;
00356         cboHistoBand->setItemData( i - 1, Qt::darkGray, Qt::ForegroundRole );
00357       }
00358       else
00359       {
00360         if ( ! myColors.isEmpty() )
00361         {
00362           mHistoColors << myColors.first();
00363           myColors.pop_front();
00364         }
00365         else
00366         {
00367           mHistoColors << Qt::black;
00368         }
00369         cboHistoBand->setItemData( i - 1, Qt::black, Qt::ForegroundRole );
00370       }
00371     }
00372   }
00373   // RGB
00374   else if ( mRendererName == "multibandcolor" )
00375   {
00376     int myRedBand = mySelectedBands[0];
00377     int myGreenBand = mySelectedBands[1];
00378     int myBlueBand = mySelectedBands[2];
00379     // remove RGB, which are reserved for the actual RGB bands
00380     // show name of RGB bands in appropriate color in bold
00381     myColors.remove( 0, 3 );
00382     for ( int i = 1; i <= myBandCountInt; i++ )
00383     {
00384       QColor myColor;
00385       if ( i == myRedBand )
00386         myColor = Qt::red;
00387       else if ( i == myGreenBand )
00388         myColor = Qt::green;
00389       else if ( i == myBlueBand )
00390         myColor = Qt::blue;
00391       else
00392       {
00393         if ( ! myColors.isEmpty() )
00394         {
00395           myColor = myColors.first();
00396           myColors.pop_front();
00397         }
00398         else
00399         {
00400           myColor = Qt::black;
00401         }
00402         cboHistoBand->setItemData( i - 1, Qt::black, Qt::ForegroundRole );
00403       }
00404       if ( i == myRedBand ||  i == myGreenBand || i == myBlueBand )
00405       {
00406         cboHistoBand->setItemData( i - 1, myColor, Qt::ForegroundRole );
00407       }
00408       mHistoColors << myColor;
00409     }
00410   }
00411   else
00412   {
00413     mHistoColors << myColors;
00414   }
00415 
00416   //
00417   //now draw actual graphs
00418   //
00419 
00420   //somtimes there are more bins than needed
00421   //we find out the last one that actually has data in it
00422   //so we can discard the rest and set the x-axis scales correctly
00423   //
00424   // scan through to get counts from layers' histograms
00425   //
00426   mHistoMin = 0;
00427   mHistoMax = 0;
00428   bool myFirstIteration = true;
00429   /* get selected band list, if mHistoShowBands != ShowAll */
00430   mySelectedBands = histoSelectedBands();
00431   double myBinXStep = 1;
00432   double myBinX = 0;
00433 
00434   for ( int myIteratorInt = 1;
00435         myIteratorInt <= myBandCountInt;
00436         ++myIteratorInt )
00437   {
00438     /* skip this band if mHistoShowBands != ShowAll and this band is not selected */
00439     if ( mHistoShowBands != ShowAll )
00440     {
00441       if ( ! mySelectedBands.contains( myIteratorInt ) )
00442         continue;
00443     }
00444     int sampleSize = 250000; // number of sample cells
00445     //QgsRasterBandStats myRasterBandStats = mRasterLayer->dataProvider()->bandStatistics( myIteratorInt );
00446     // mRasterLayer->populateHistogram( myIteratorInt, BINCOUNT, myIgnoreOutOfRangeFlag, myThoroughBandScanFlag );
00447     QgsRasterHistogram myHistogram = mRasterLayer->dataProvider()->histogram( myIteratorInt, BINCOUNT, std::numeric_limits<double>::quiet_NaN(), std::numeric_limits<double>::quiet_NaN(), QgsRectangle(), sampleSize );
00448 
00449     QwtPlotCurve * mypCurve = new QwtPlotCurve( tr( "Band %1" ).arg( myIteratorInt ) );
00450     //mypCurve->setCurveAttribute( QwtPlotCurve::Fitted );
00451     mypCurve->setRenderHint( QwtPlotItem::RenderAntialiased );
00452     mypCurve->setPen( QPen( mHistoColors.at( myIteratorInt ) ) );
00453 #if defined(QWT_VERSION) && QWT_VERSION>=0x060000
00454     QVector<QPointF> data;
00455 #else
00456     QVector<double> myX2Data;
00457     QVector<double> myY2Data;
00458 #endif
00459     // calculate first bin x value and bin step size if not Byte data
00460     if ( mRasterLayer->dataProvider()->srcDataType( myIteratorInt ) != QGis::Byte )
00461     {
00462       //myBinXStep = myRasterBandStats.range / BINCOUNT;
00463       //myBinX = myRasterBandStats.minimumValue + myBinXStep / 2.0;
00464       myBinXStep = ( myHistogram.maximum - myHistogram.minimum ) / BINCOUNT;
00465       myBinX = myHistogram.minimum + myBinXStep / 2.0;
00466     }
00467     else
00468     {
00469       myBinXStep = 1;
00470       myBinX = 0;
00471     }
00472 
00473     for ( int myBin = 0; myBin < BINCOUNT; myBin++ )
00474     {
00475       //int myBinValue = myRasterBandStats.histogramVector->at( myBin );
00476       int myBinValue = myHistogram.histogramVector.at( myBin );
00477 #if defined(QWT_VERSION) && QWT_VERSION>=0x060000
00478       data << QPointF( myBinX, myBinValue );
00479 #else
00480       myX2Data.append( double( myBinX ) );
00481       myY2Data.append( double( myBinValue ) );
00482 #endif
00483       myBinX += myBinXStep;
00484     }
00485 #if defined(QWT_VERSION) && QWT_VERSION>=0x060000
00486     mypCurve->setSamples( data );
00487 #else
00488     mypCurve->setData( myX2Data, myY2Data );
00489 #endif
00490     mypCurve->attach( mpPlot );
00491     if ( myFirstIteration || mHistoMin > myHistogram.minimum )
00492     {
00493       mHistoMin = myHistogram.minimum;
00494     }
00495     if ( myFirstIteration || mHistoMax < myHistogram.maximum )
00496     {
00497       mHistoMax = myHistogram.maximum;
00498     }
00499     QgsDebugMsg( QString( "computed histo min = %1 max = %2" ).arg( mHistoMin ).arg( mHistoMax ) );
00500     myFirstIteration = false;
00501   }
00502   // for x axis use band pixel values rather than gdal hist. bin values
00503   // subtract -0.5 to prevent rounding errors
00504   // see http://www.gdal.org/classGDALRasterBand.html#3f8889607d3b2294f7e0f11181c201c8
00505   // fix x range for non-Byte data
00506   mpPlot->setAxisScale( QwtPlot::xBottom,
00507                         mHistoMin - myBinXStep / 2,
00508                         mHistoMax + myBinXStep / 2 );
00509 
00510   mpPlot->replot();
00511 
00512   // histo plot markers
00513   // memory leak?
00514   mHistoMarkerMin = new QwtPlotMarker();
00515   mHistoMarkerMin->attach( mpPlot );
00516   mHistoMarkerMax = new QwtPlotMarker();
00517   mHistoMarkerMax->attach( mpPlot );
00518   updateHistoMarkers();
00519 
00520   // histo picker
00521   if ( ! mHistoPicker )
00522   {
00523     mHistoPicker = new QwtPlotPicker( mpPlot->canvas() );
00524     // mHistoPicker->setTrackerMode( QwtPicker::ActiveOnly );
00525     mHistoPicker->setTrackerMode( QwtPicker::AlwaysOff );
00526     mHistoPicker->setRubberBand( QwtPicker::VLineRubberBand );
00527     mHistoPicker->setEnabled( false );
00528 #if defined(QWT_VERSION) && QWT_VERSION>=0x060000
00529     mHistoPicker->setStateMachine( new QwtPickerDragPointMachine );
00530     connect( mHistoPicker, SIGNAL( selected( const QPointF & ) ), this, SLOT( histoPickerSelected( const QPointF & ) ) );
00531 #else
00532     mHistoPicker->setSelectionFlags( QwtPicker::PointSelection | QwtPicker::DragSelection );
00533     connect( mHistoPicker, SIGNAL( selected( const QwtDoublePoint & ) ), this, SLOT( histoPickerSelectedQwt5( const QwtDoublePoint & ) ) );
00534 #endif
00535   }
00536 
00537   // plot zoomer
00538   if ( ! mHistoZoomer )
00539   {
00540     mHistoZoomer = new QwtPlotZoomer( mpPlot->canvas() );
00541 #if defined(QWT_VERSION) && QWT_VERSION>=0x060000
00542     mHistoZoomer->setStateMachine( new QwtPickerDragRectMachine );
00543 #else
00544     mHistoZoomer->setSelectionFlags( QwtPicker::RectSelection | QwtPicker::DragSelection );
00545 #endif
00546     mHistoZoomer->setTrackerMode( QwtPicker::AlwaysOff );
00547     mHistoZoomer->setEnabled( true );
00548   }
00549 
00550   disconnect( mRasterLayer, SIGNAL( progressUpdate( int ) ), mHistogramProgress, SLOT( setValue( int ) ) );
00551   stackedWidget2->setCurrentIndex( 0 );
00552   // icon from http://findicons.com/icon/169577/14_zoom?id=171427
00553   mpPlot->canvas()->setCursor( QCursor( QgsApplication::getThemePixmap( "/mIconZoom.png" ) ) );
00554   //  on_cboHistoBand_currentIndexChanged( -1 );
00555   QApplication::restoreOverrideCursor();
00556 }
00557 
00558 void QgsRasterHistogramWidget::on_mSaveAsImageButton_clicked()
00559 {
00560   if ( mpPlot == 0 )
00561   {
00562     return;
00563   }
00564 
00565   QPair< QString, QString> myFileNameAndFilter = QgisGui::getSaveAsImageName( this, tr( "Choose a file name to save the map image as" ) );
00566   QFileInfo myInfo( myFileNameAndFilter.first );
00567   if ( QFileInfo( myFileNameAndFilter.first ).baseName() != "" )
00568   {
00569     histoSaveAsImage( myFileNameAndFilter.first );
00570   }
00571 }
00572 
00573 bool QgsRasterHistogramWidget::histoSaveAsImage( const QString& theFilename,
00574     int width, int height, int quality )
00575 {
00576   // make sure dir. exists
00577   QFileInfo myInfo( theFilename );
00578   QDir myDir( myInfo.dir() );
00579   if ( ! myDir.exists() )
00580   {
00581     QgsDebugMsg( QString( "Error, directory %1 non-existent (theFilename = %2)" ).arg( myDir.absolutePath() ).arg( theFilename ) );
00582     return false;
00583   }
00584 
00585   // prepare the pixmap
00586   QPixmap myPixmap( width, height );
00587   QRect myQRect( 5, 5, width - 10, height - 10 ); // leave a 5px border on all sides
00588   myPixmap.fill( Qt::white ); // Qt::transparent ?
00589 
00590 #if defined(QWT_VERSION) && QWT_VERSION>=0x060000
00591   QwtPlotRenderer myRenderer;
00592   myRenderer.setDiscardFlags( QwtPlotRenderer::DiscardBackground |
00593                               QwtPlotRenderer::DiscardCanvasBackground );
00594   myRenderer.setLayoutFlags( QwtPlotRenderer::FrameWithScales );
00595 
00596   QPainter myPainter;
00597   myPainter.begin( &myPixmap );
00598   myRenderer.render( mpPlot, &myPainter, myQRect );
00599   myPainter.end();
00600 #else
00601   QwtPlotPrintFilter myFilter;
00602   int myOptions = QwtPlotPrintFilter::PrintAll;
00603   myOptions &= ~QwtPlotPrintFilter::PrintBackground;
00604   myOptions |= QwtPlotPrintFilter::PrintFrameWithScales;
00605   myFilter.setOptions( myOptions );
00606 
00607   QPainter myPainter;
00608   myPainter.begin( &myPixmap );
00609   mpPlot->print( &myPainter, myQRect, myFilter );
00610   myPainter.end();
00611 
00612   // "fix" for bug in qwt5 - legend and plot shifts a bit
00613   // can't see how to avoid this without picking qwt5 apart...
00614   refreshHistogram();
00615   refreshHistogram();
00616 #endif
00617 
00618   // save pixmap to file
00619   myPixmap.save( theFilename, 0, quality );
00620 
00621   // should do more error checking
00622   return true;
00623 }
00624 
00625 void QgsRasterHistogramWidget::setSelectedBand( int theBandNo )
00626 {
00627   cboHistoBand->setCurrentIndex( theBandNo - 1 );
00628 }
00629 
00630 void QgsRasterHistogramWidget::on_cboHistoBand_currentIndexChanged( int index )
00631 {
00632   if ( mHistoShowBands == ShowSelected )
00633     refreshHistogram();
00634 
00635   // get the current index value, index can be -1
00636   index = cboHistoBand->currentIndex();
00637   if ( mHistoPicker != NULL )
00638   {
00639     mHistoPicker->setEnabled( false );
00640     mHistoPicker->setRubberBandPen( QPen( mHistoColors.at( index + 1 ) ) );
00641   }
00642   if ( mHistoZoomer != NULL )
00643     mHistoZoomer->setEnabled( true );
00644   btnHistoMin->setEnabled( true );
00645   btnHistoMax->setEnabled( true );
00646 
00647   QPair< QString, QString > myMinMax = rendererMinMax( index + 1 );
00648   leHistoMin->setText( myMinMax.first );
00649   leHistoMax->setText( myMinMax.second );
00650 
00651   applyHistoMin();
00652   applyHistoMax();
00653 }
00654 
00655 void QgsRasterHistogramWidget::histoActionTriggered( QAction* action )
00656 {
00657   if ( ! action )
00658     return;
00659   histoAction( action->data().toString(), action->isChecked() );
00660 }
00661 
00662 void QgsRasterHistogramWidget::histoAction( const QString actionName, bool actionFlag )
00663 {
00664   if ( actionName == "" )
00665     return;
00666 
00667   // this approach is a bit of a hack, but this way we don't have to define slots for each action
00668   QgsDebugMsg( QString( "band = %1 action = %2" ).arg( cboHistoBand->currentIndex() + 1 ).arg( actionName ) );
00669 
00670   // checkeable actions
00671   if ( actionName == "Show markers" )
00672   {
00673     mHistoShowMarkers = actionFlag;
00674     QSettings settings;
00675     settings.setValue( "/Raster/histogram/showMarkers", mHistoShowMarkers );
00676     updateHistoMarkers();
00677     return;
00678   }
00679   else if ( actionName == "Zoom min_max" )
00680   {
00681     mHistoZoomToMinMax = actionFlag;
00682     QSettings settings;
00683     settings.setValue( "/Raster/histogram/zoomToMinMax", mHistoZoomToMinMax );
00684     return;
00685   }
00686   else if ( actionName == "Update min_max" )
00687   {
00688     mHistoUpdateStyleToMinMax = actionFlag;
00689     QSettings settings;
00690     settings.setValue( "/Raster/histogram/updateStyleToMinMax", mHistoUpdateStyleToMinMax );
00691     return;
00692   }
00693   else if ( actionName == "Show all" )
00694   {
00695     mHistoShowBands = ShowAll;
00696     // settings.setValue( "/Raster/histogram/showBands", (int)mHistoShowBands );
00697     refreshHistogram();
00698     return;
00699   }
00700   else if ( actionName == "Show selected" )
00701   {
00702     mHistoShowBands = ShowSelected;
00703     // settings.setValue( "/Raster/histogram/showBands", (int)mHistoShowBands );
00704     refreshHistogram();
00705     return;
00706   }
00707   else if ( actionName == "Show RGB" )
00708   {
00709     mHistoShowBands = ShowRGB;
00710     // settings.setValue( "/Raster/histogram/showBands", (int)mHistoShowBands );
00711     refreshHistogram();
00712     return;
00713   }
00714 #if 0
00715   else if ( actionName == "Load apply all" )
00716   {
00717     mHistoLoadApplyAll = actionFlag;
00718     settings.setValue( "/Raster/histogram/loadApplyAll", mHistoLoadApplyAll );
00719     return;
00720   }
00721 #endif
00722   // Load actions
00723   // TODO - separate calculations from rendererwidget so we can do them without
00724   else if ( actionName.left( 5 ) == "Load " && mRendererWidget )
00725   {
00726     QVector<int> myBands;
00727     bool ok = false;
00728 
00729 #if 0
00730     double minMaxValues[2];
00731 
00732     // find which band(s) need updating (all or current)
00733     if ( mHistoLoadApplyAll )
00734     {
00735       int myBandCountInt = mRasterLayer->bandCount();
00736       for ( int i = 1; i <= myBandCountInt; i++ )
00737       {
00738         if ( i != cboHistoBand->currentIndex() + 1 )
00739           myBands << i;
00740       }
00741     }
00742 #endif
00743 
00744     // add current band to the end
00745     myBands << cboHistoBand->currentIndex() + 1;
00746 
00747     // get stddev value once if needed
00748     /*
00749     double myStdDev = 1.0;
00750     if ( actionName == "Load stddev" )
00751     {
00752       myStdDev = mRendererWidget->stdDev().toDouble();
00753     }
00754     */
00755 
00756     // don't update markers every time
00757     leHistoMin->blockSignals( true );
00758     leHistoMax->blockSignals( true );
00759 
00760     // process each band
00761     foreach ( int theBandNo, myBands )
00762     {
00763       ok = false;
00764 #if 0
00765       if ( actionName == "Load actual" )
00766       {
00767         ok = mRendererWidget->bandMinMax( QgsRasterRendererWidget::Actual,
00768                                           theBandNo, minMaxValues );
00769       }
00770       else if ( actionName == "Load estimate" )
00771       {
00772         ok = mRendererWidget->bandMinMax( QgsRasterRendererWidget::Estimate,
00773                                           theBandNo, minMaxValues );
00774       }
00775       else if ( actionName == "Load extent" )
00776       {
00777         ok = mRendererWidget->bandMinMax( QgsRasterRendererWidget::CurrentExtent,
00778                                           theBandNo, minMaxValues );
00779       }
00780       else if ( actionName == "Load 1 stddev" ||
00781                 actionName == "Load stddev" )
00782       {
00783         ok = mRendererWidget->bandMinMaxFromStdDev( myStdDev, theBandNo, minMaxValues );
00784       }
00785 #endif
00786 
00787       // apply current item
00788       cboHistoBand->setCurrentIndex( theBandNo - 1 );
00789       if ( !ok || actionName == "Load reset" )
00790       {
00791         leHistoMin->clear();
00792         leHistoMax->clear();
00793 #if 0
00794         // TODO - fix gdal provider: changes data type when nodata value is not found
00795         // this prevents us from getting proper min and max values here
00796         minMaxValues[0] = QgsContrastEnhancement::minimumValuePossible(
00797                             ( QGis::DataType ) mRasterLayer->dataProvider()->dataType( theBandNo ) );
00798         minMaxValues[1] = QgsContrastEnhancement::maximumValuePossible(
00799                             ( QGis::DataType ) mRasterLayer->dataProvider()->dataType( theBandNo ) );
00800       }
00801       else
00802       {
00803         leHistoMin->setText( QString::number( minMaxValues[0] ) );
00804         leHistoMax->setText( QString::number( minMaxValues[1] ) );
00805 #endif
00806       }
00807       applyHistoMin( );
00808       applyHistoMax( );
00809     }
00810     // update markers
00811     leHistoMin->blockSignals( false );
00812     leHistoMax->blockSignals( false );
00813     updateHistoMarkers();
00814   }
00815   else if ( actionName == "Compute histogram" )
00816   {
00817     on_btnHistoCompute_clicked();
00818   }
00819   else
00820   {
00821     QgsDebugMsg( "Invalid action " + actionName );
00822     return;
00823   }
00824 }
00825 
00826 void QgsRasterHistogramWidget::applyHistoMin( )
00827 {
00828   if ( ! mRendererWidget )
00829     return;
00830 
00831   int theBandNo = cboHistoBand->currentIndex() + 1;
00832   QList< int > mySelectedBands = rendererSelectedBands();
00833   QString min;
00834   for ( int i = 0; i <= mySelectedBands.size(); i++ )
00835   {
00836     if ( theBandNo == mRendererWidget->selectedBand( i ) )
00837     {
00838       min = leHistoMin->text();
00839       if ( mHistoUpdateStyleToMinMax )
00840         mRendererWidget->setMin( min, i );
00841     }
00842   }
00843 
00844   updateHistoMarkers();
00845 
00846   if ( ! min.isEmpty() && mHistoZoomToMinMax && mHistoZoomer )
00847   {
00848     QRectF rect = mHistoZoomer->zoomRect();
00849     rect.setLeft( min.toDouble() );
00850     mHistoZoomer->zoom( rect );
00851   }
00852 
00853 }
00854 
00855 void QgsRasterHistogramWidget::applyHistoMax( )
00856 {
00857   if ( ! mRendererWidget )
00858     return;
00859 
00860   int theBandNo = cboHistoBand->currentIndex() + 1;
00861   QList< int > mySelectedBands = rendererSelectedBands();
00862   QString max;
00863   for ( int i = 0; i <= mySelectedBands.size(); i++ )
00864   {
00865     if ( theBandNo == mRendererWidget->selectedBand( i ) )
00866     {
00867       max = leHistoMax->text();
00868       if ( mHistoUpdateStyleToMinMax )
00869         mRendererWidget->setMax( max, i );
00870     }
00871   }
00872 
00873   updateHistoMarkers();
00874 
00875   if ( ! max.isEmpty() && mHistoZoomToMinMax && mHistoZoomer )
00876   {
00877     QRectF rect = mHistoZoomer->zoomRect();
00878     rect.setRight( max.toDouble() );
00879     mHistoZoomer->zoom( rect );
00880   }
00881 }
00882 
00883 void QgsRasterHistogramWidget::on_btnHistoMin_toggled()
00884 {
00885   if ( mpPlot != NULL && mHistoPicker != NULL )
00886   {
00887     if ( QApplication::overrideCursor() )
00888       QApplication::restoreOverrideCursor();
00889     if ( btnHistoMin->isChecked() )
00890     {
00891       btnHistoMax->setChecked( false );
00892       QApplication::setOverrideCursor( Qt::PointingHandCursor );
00893     }
00894     if ( mHistoZoomer != NULL )
00895       mHistoZoomer->setEnabled( ! btnHistoMax->isChecked() );
00896     mHistoPicker->setEnabled( btnHistoMin->isChecked() );
00897   }
00898   updateHistoMarkers();
00899 }
00900 
00901 void QgsRasterHistogramWidget::on_btnHistoMax_toggled()
00902 {
00903   if ( mpPlot != NULL && mHistoPicker != NULL )
00904   {
00905     if ( QApplication::overrideCursor() )
00906       QApplication::restoreOverrideCursor();
00907     if ( btnHistoMax->isChecked() )
00908     {
00909       btnHistoMin->setChecked( false );
00910       QApplication::setOverrideCursor( Qt::PointingHandCursor );
00911     }
00912     if ( mHistoZoomer != NULL )
00913       mHistoZoomer->setEnabled( ! btnHistoMax->isChecked() );
00914     mHistoPicker->setEnabled( btnHistoMax->isChecked() );
00915   }
00916   updateHistoMarkers();
00917 }
00918 
00919 // local function used by histoPickerSelected(), to get a rounded picked value
00920 // this is sensitive and may not always be correct, needs more testing
00921 QString findClosestTickVal( double target, const QwtScaleDiv * scale, int div = 100 )
00922 {
00923   if ( !scale ) return "";
00924 
00925   QList< double > minorTicks = scale->ticks( QwtScaleDiv::MinorTick );
00926   QList< double > majorTicks = scale->ticks( QwtScaleDiv::MajorTick );
00927   double diff = ( minorTicks[1] - minorTicks[0] ) / div;
00928   double min = majorTicks[0] - diff;
00929   if ( min > target )
00930     min -= ( majorTicks[1] - majorTicks[0] );
00931 #if defined(QWT_VERSION) && QWT_VERSION<0x050200
00932   double max = scale->hBound();
00933 #else
00934   double max = scale->upperBound();
00935 #endif
00936   double closest = target;
00937   double current = min;
00938 
00939   while ( current < max )
00940   {
00941     current += diff;
00942     if ( current > target )
00943     {
00944       closest = ( abs( target - current + diff ) < abs( target - current ) ) ? current - diff : current;
00945       break;
00946     }
00947   }
00948 
00949   // QgsDebugMsg( QString( "target=%1 div=%2 closest=%3" ).arg( target ).arg( div ).arg( closest ) );
00950   return QString::number( closest );
00951 }
00952 
00953 void QgsRasterHistogramWidget::histoPickerSelected( const QPointF & pos )
00954 {
00955   if ( btnHistoMin->isChecked() || btnHistoMax->isChecked() )
00956   {
00957 #if defined(QWT_VERSION) && QWT_VERSION>=0x060100
00958     const QwtScaleDiv * scale = &mpPlot->axisScaleDiv( QwtPlot::xBottom );
00959 #else
00960     const QwtScaleDiv * scale = mpPlot->axisScaleDiv( QwtPlot::xBottom );
00961 #endif
00962 
00963     if ( btnHistoMin->isChecked() )
00964     {
00965       leHistoMin->setText( findClosestTickVal( pos.x(), scale ) );
00966       applyHistoMin();
00967       btnHistoMin->setChecked( false );
00968     }
00969     else // if ( btnHistoMax->isChecked() )
00970     {
00971       leHistoMax->setText( findClosestTickVal( pos.x(), scale ) );
00972       applyHistoMax();
00973       btnHistoMax->setChecked( false );
00974     }
00975   }
00976   if ( QApplication::overrideCursor() )
00977     QApplication::restoreOverrideCursor();
00978 }
00979 
00980 void QgsRasterHistogramWidget::histoPickerSelectedQwt5( const QwtDoublePoint & pos )
00981 {
00982   histoPickerSelected( QPointF( pos.x(), pos.y() ) );
00983 }
00984 
00985 void QgsRasterHistogramWidget::updateHistoMarkers( )
00986 {
00987   // hack to not update markers
00988   if ( leHistoMin->signalsBlocked() )
00989     return;
00990   // todo error checking
00991   if ( mpPlot == NULL || mHistoMarkerMin == NULL || mHistoMarkerMax == NULL )
00992     return;
00993 
00994   int theBandNo = cboHistoBand->currentIndex() + 1;
00995   QList< int > mySelectedBands = histoSelectedBands();
00996 
00997   if (( ! mHistoShowMarkers && ! btnHistoMin->isChecked() && ! btnHistoMax->isChecked() ) ||
00998       ( ! mySelectedBands.isEmpty() && ! mySelectedBands.contains( theBandNo ) ) )
00999   {
01000     mHistoMarkerMin->hide();
01001     mHistoMarkerMax->hide();
01002     mpPlot->replot();
01003     return;
01004   }
01005 
01006   double minVal = mHistoMin;
01007   double maxVal = mHistoMax;
01008   QString minStr = leHistoMin->text();
01009   QString maxStr = leHistoMax->text();
01010   if ( minStr != "" )
01011     minVal = minStr.toDouble();
01012   if ( maxStr != "" )
01013     maxVal = maxStr.toDouble();
01014 
01015   QPen linePen = QPen( mHistoColors.at( theBandNo ) );
01016   linePen.setStyle( Qt::DashLine );
01017   mHistoMarkerMin->setLineStyle( QwtPlotMarker::VLine );
01018   mHistoMarkerMin->setLinePen( linePen );
01019   mHistoMarkerMin->setXValue( minVal );
01020   mHistoMarkerMin->show();
01021   mHistoMarkerMax->setLineStyle( QwtPlotMarker::VLine );
01022   mHistoMarkerMax->setLinePen( linePen );
01023   mHistoMarkerMax->setXValue( maxVal );
01024   mHistoMarkerMax->show();
01025 
01026   mpPlot->replot();
01027 }
01028 
01029 
01030 QList< int > QgsRasterHistogramWidget::histoSelectedBands()
01031 {
01032   QList< int > mySelectedBands;
01033 
01034   if ( mHistoShowBands != ShowAll )
01035   {
01036     if ( mHistoShowBands == ShowSelected )
01037     {
01038       mySelectedBands << cboHistoBand->currentIndex() + 1;
01039     }
01040     else if ( mHistoShowBands == ShowRGB )
01041     {
01042       mySelectedBands = rendererSelectedBands();
01043     }
01044   }
01045 
01046   return mySelectedBands;
01047 }
01048 
01049 QList< int > QgsRasterHistogramWidget::rendererSelectedBands()
01050 {
01051   QList< int > mySelectedBands;
01052 
01053   if ( ! mRendererWidget )
01054   {
01055     mySelectedBands << -1 << -1 << -1; // make sure we return 3 elements
01056     return mySelectedBands;
01057   }
01058 
01059   if ( mRendererName == "singlebandgray" )
01060   {
01061     mySelectedBands << mRendererWidget->selectedBand( );
01062   }
01063   else if ( mRendererName == "multibandcolor" )
01064   {
01065     for ( int i = 0; i <= 2; i++ )
01066     {
01067       mySelectedBands << mRendererWidget->selectedBand( i );
01068     }
01069   }
01070 
01071   return mySelectedBands;
01072 }
01073 
01074 QPair< QString, QString > QgsRasterHistogramWidget::rendererMinMax( int theBandNo )
01075 {
01076   QPair< QString, QString > myMinMax;
01077 
01078   if ( ! mRendererWidget )
01079     return myMinMax;
01080 
01081   if ( mRendererName == "singlebandgray" )
01082   {
01083     if ( theBandNo == mRendererWidget->selectedBand( ) )
01084     {
01085       myMinMax.first = mRendererWidget->min();
01086       myMinMax.second = mRendererWidget->max();
01087     }
01088   }
01089   else if ( mRendererName == "multibandcolor" )
01090   {
01091     for ( int i = 0; i <= 2; i++ )
01092     {
01093       if ( theBandNo == mRendererWidget->selectedBand( i ) )
01094       {
01095         myMinMax.first = mRendererWidget->min( i );
01096         myMinMax.second = mRendererWidget->max( i );
01097         break;
01098       }
01099     }
01100   }
01101 
01102   // TODO - there are 2 definitions of raster data type that should be unified
01103   // QgsRasterDataProvider::DataType and QGis::DataType
01104   // TODO - fix gdal provider: changes data type when nodata value is not found
01105   // this prevents us from getting proper min and max values here
01106   // minStr = QString::number( QgsContrastEnhancement::minimumValuePossible( ( QGis::DataType )
01107   //                                                                         mRasterLayer->dataProvider()->dataType( theBandNo ) ) );
01108   // maxStr = QString::number( QgsContrastEnhancement::maximumValuePossible( ( QGis::DataType )
01109   //                                                                         mRasterLayer->dataProvider()->dataType( theBandNo ) ) );
01110 
01111   // if we get an empty result, fill with default value (histo min/max)
01112   if ( myMinMax.first.isEmpty() )
01113     myMinMax.first = QString::number( mHistoMin );
01114   if ( myMinMax.second.isEmpty() )
01115     myMinMax.second = QString::number( mHistoMax );
01116 
01117   QgsDebugMsg( QString( "bandNo %1 got min/max [%2] [%3]" ).arg( theBandNo ).arg( myMinMax.first ).arg( myMinMax.second ) );
01118 
01119   return myMinMax;
01120 }
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Defines