|
QGIS API Documentation
master-59fd5e0
|
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 }