QGIS API Documentation  2.17.0-Master (6f7b933)
qgssinglebandpseudocolorrendererwidget.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgssinglebandpseudocolorrendererwidget.cpp
3  ------------------------------------------
4  begin : February 2012
5  copyright : (C) 2012 by Marco Hugentobler
6  email : marco at sourcepole dot ch
7  ***************************************************************************/
8 
9 /***************************************************************************
10  * *
11  * This program is free software; you can redistribute it and/or modify *
12  * it under the terms of the GNU General Public License as published by *
13  * the Free Software Foundation; either version 2 of the License, or *
14  * (at your option) any later version. *
15  * *
16  ***************************************************************************/
17 
20 #include "qgsrasterlayer.h"
21 
22 // for color ramps - todo add rasterStyle and refactor raster vs. vector ramps
23 #include "qgsstylev2.h"
24 #include "qgsvectorcolorrampv2.h"
25 #include "qgscolordialog.h"
26 
27 #include <QFileDialog>
28 #include <QMessageBox>
29 #include <QSettings>
30 #include <QTextStream>
31 
32 // override setData to emit signal when edited. By default the itemChanged signal fires way too often
33 void QgsTreeWidgetItem::setData( int column, int role, const QVariant & value )
34 {
35  QTreeWidgetItem::setData( column, role, value );
36  if ( role == Qt::EditRole )
37  {
38  emit itemEdited( this, column );
39  }
40 }
41 
42 // override < operator to allow numeric sorting
47 bool QgsTreeWidgetItem::operator<( const QTreeWidgetItem & other ) const
48 {
49  int column = treeWidget()->sortColumn();
50  bool ok1, ok2, val;
51  val = text( column ).toDouble( &ok1 ) < other.text( column ).toDouble( &ok2 );
52  if ( ok1 && ok2 )
53  {
54  return val;
55  }
56  else if ( ok1 || ok2 )
57  {
58  // sort numbers before strings
59  return ok1;
60  }
61  else
62  {
63  return text( column ) < other.text( column );
64  }
65 }
66 
68  : QgsRasterRendererWidget( layer, extent )
69  , mMinMaxWidget( nullptr )
70  , mMinMaxOrigin( 0 )
71 {
72  QSettings settings;
73 
74  setupUi( this );
75 
76  mColormapTreeWidget->setColumnWidth( ColorColumn, 50 );
77 
78  QString defaultPalette = settings.value( "/Raster/defaultPalette", "Spectral" ).toString();
79 
80  mColorRampComboBox->populate( QgsStyleV2::defaultStyle() );
81 
82  QgsDebugMsg( "defaultPalette = " + defaultPalette );
83  mColorRampComboBox->setCurrentIndex( mColorRampComboBox->findText( defaultPalette ) );
84  connect( mButtonEditRamp, SIGNAL( clicked() ), mColorRampComboBox, SLOT( editSourceRamp() ) );
85 
86  if ( !mRasterLayer )
87  {
88  return;
89  }
90 
92  if ( !provider )
93  {
94  return;
95  }
96 
97  // Must be before adding items to mBandComboBox (signal)
98  mMinLineEdit->setValidator( new QDoubleValidator( mMinLineEdit ) );
99  mMaxLineEdit->setValidator( new QDoubleValidator( mMaxLineEdit ) );
100 
101  mMinMaxWidget = new QgsRasterMinMaxWidget( layer, this );
102  mMinMaxWidget->setExtent( extent );
103  mMinMaxWidget->setMapCanvas( mCanvas );
104 
105  QHBoxLayout *layout = new QHBoxLayout();
106  layout->setContentsMargins( 0, 0, 0, 0 );
107  mMinMaxContainerWidget->setLayout( layout );
108  layout->addWidget( mMinMaxWidget );
109  connect( mMinMaxWidget, SIGNAL( load( int, double, double, int ) ),
110  this, SLOT( loadMinMax( int, double, double, int ) ) );
111 
112 
113  //fill available bands into combo box
114  int nBands = provider->bandCount();
115  for ( int i = 1; i <= nBands; ++i ) //band numbering seem to start at 1
116  {
117  mBandComboBox->addItem( displayBandName( i ), i );
118  }
119 
120  mColorInterpolationComboBox->addItem( tr( "Discrete" ), QgsColorRampShader::DISCRETE );
121  mColorInterpolationComboBox->addItem( tr( "Linear" ), QgsColorRampShader::INTERPOLATED );
122  mColorInterpolationComboBox->addItem( tr( "Exact" ), QgsColorRampShader::EXACT );
123  mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( QgsColorRampShader::INTERPOLATED ) );
124  mClassificationModeComboBox->addItem( tr( "Continuous" ), Continuous );
125  mClassificationModeComboBox->addItem( tr( "Equal interval" ), EqualInterval );
126  mClassificationModeComboBox->addItem( tr( "Quantile" ), Quantile );
127 
128  mNumberOfEntriesSpinBox->setValue( 5 ); // some default
129 
130  setFromRenderer( layer->renderer() );
131 
132  // If there is currently no min/max, load default with user current default options
133  if ( mMinLineEdit->text().isEmpty() || mMaxLineEdit->text().isEmpty() )
134  {
135  mMinMaxWidget->load();
136  }
137 
138  on_mClassificationModeComboBox_currentIndexChanged( 0 );
139 
140  resetClassifyButton();
141 
142  connect( mClassificationModeComboBox, SIGNAL( currentIndexChanged( int ) ), this, SLOT( on_mClassifyButton_clicked() ) );
143  connect( mMinLineEdit, SIGNAL( textChanged( QString ) ), this, SLOT( on_mClassifyButton_clicked() ) );
144  connect( mMaxLineEdit, SIGNAL( textChanged( QString ) ), this, SLOT( on_mClassifyButton_clicked() ) );
145  connect( mColorRampComboBox, SIGNAL( sourceRampEdited() ), this, SLOT( on_mClassifyButton_clicked() ) );
146  connect( mColorRampComboBox, SIGNAL( currentIndexChanged( int ) ), this, SLOT( on_mClassifyButton_clicked() ) );
147  connect( mInvertCheckBox, SIGNAL( stateChanged( int ) ), this, SLOT( on_mClassifyButton_clicked() ) );
148  connect( mNumberOfEntriesSpinBox, SIGNAL( valueChanged( int ) ), this, SLOT( on_mClassifyButton_clicked() ) );
149  connect( mBandComboBox, SIGNAL( currentIndexChanged( int ) ), this, SLOT( on_mClassifyButton_clicked() ) );
150  connect( mClipCheckBox, SIGNAL( toggled( bool ) ), this, SIGNAL( widgetChanged() ) );
151 }
152 
154 {
155 }
156 
158 {
159  QgsRasterShader* rasterShader = new QgsRasterShader();
160  QgsColorRampShader* colorRampShader = new QgsColorRampShader();
161  colorRampShader->setClip( mClipCheckBox->isChecked() );
162 
163  //iterate through mColormapTreeWidget and set colormap info of layer
165  int topLevelItemCount = mColormapTreeWidget->topLevelItemCount();
166  QTreeWidgetItem* currentItem;
167  for ( int i = 0; i < topLevelItemCount; ++i )
168  {
169  currentItem = mColormapTreeWidget->topLevelItem( i );
170  if ( !currentItem )
171  {
172  continue;
173  }
174  QgsColorRampShader::ColorRampItem newColorRampItem;
175  newColorRampItem.value = currentItem->text( ValueColumn ).toDouble();
176  newColorRampItem.color = currentItem->background( ColorColumn ).color();
177  newColorRampItem.label = currentItem->text( LabelColumn );
178  colorRampItems.append( newColorRampItem );
179  }
180  // sort the shader items
181  qSort( colorRampItems );
182  colorRampShader->setColorRampItemList( colorRampItems );
183 
184  QgsColorRampShader::ColorRamp_TYPE interpolation = static_cast< QgsColorRampShader::ColorRamp_TYPE >( mColorInterpolationComboBox->itemData( mColorInterpolationComboBox->currentIndex() ).toInt() );
185  colorRampShader->setColorRampType( interpolation );
186  rasterShader->setRasterShaderFunction( colorRampShader );
187 
188  int bandNumber = mBandComboBox->itemData( mBandComboBox->currentIndex() ).toInt();
190 
191  renderer->setClassificationMin( lineEditValue( mMinLineEdit ) );
192  renderer->setClassificationMax( lineEditValue( mMaxLineEdit ) );
193  renderer->setClassificationMinMaxOrigin( mMinMaxOrigin );
194  return renderer;
195 }
196 
198 {
200  mMinMaxWidget->setMapCanvas( canvas );
201 }
202 
207 void QgsSingleBandPseudoColorRendererWidget::autoLabel()
208 {
209  QgsColorRampShader::ColorRamp_TYPE interpolation = static_cast< QgsColorRampShader::ColorRamp_TYPE >( mColorInterpolationComboBox->itemData( mColorInterpolationComboBox->currentIndex() ).toInt() );
210  bool discrete = interpolation == QgsColorRampShader::DISCRETE;
211  QString unit = mUnitLineEdit->text();
212  QString label;
213  int topLevelItemCount = mColormapTreeWidget->topLevelItemCount();
214  QTreeWidgetItem* currentItem;
215  for ( int i = 0; i < topLevelItemCount; ++i )
216  {
217  currentItem = mColormapTreeWidget->topLevelItem( i );
218  //If the item is null or does not have a pixel values set, skip
219  if ( !currentItem || currentItem->text( ValueColumn ).isEmpty() )
220  {
221  continue;
222  }
223 
224  if ( discrete )
225  {
226  if ( i == 0 )
227  {
228  label = "<= " + currentItem->text( ValueColumn ) + unit;
229  }
230  else if ( currentItem->text( ValueColumn ).toDouble() == std::numeric_limits<double>::infinity() )
231  {
232  label = "> " + mColormapTreeWidget->topLevelItem( i - 1 )->text( ValueColumn ) + unit;
233  }
234  else
235  {
236  label = mColormapTreeWidget->topLevelItem( i - 1 )->text( ValueColumn ) + " - " + currentItem->text( ValueColumn ) + unit;
237  }
238  }
239  else
240  {
241  label = currentItem->text( ValueColumn ) + unit;
242  }
243 
244  if ( currentItem->text( LabelColumn ).isEmpty() || currentItem->text( LabelColumn ) == label || currentItem->foreground( LabelColumn ).color() == QColor( Qt::gray ) )
245  {
246  currentItem->setText( LabelColumn, label );
247  currentItem->setForeground( LabelColumn, QBrush( QColor( Qt::gray ) ) );
248  }
249  }
250 }
251 
253 void QgsSingleBandPseudoColorRendererWidget::setUnitFromLabels()
254 {
255  QgsColorRampShader::ColorRamp_TYPE interpolation = static_cast< QgsColorRampShader::ColorRamp_TYPE >( mColorInterpolationComboBox->itemData( mColorInterpolationComboBox->currentIndex() ).toInt() );
256  bool discrete = interpolation == QgsColorRampShader::DISCRETE;
257  QStringList allSuffixes;
258  QString label;
259  int topLevelItemCount = mColormapTreeWidget->topLevelItemCount();
260  QTreeWidgetItem* currentItem;
261  for ( int i = 0; i < topLevelItemCount; ++i )
262  {
263  currentItem = mColormapTreeWidget->topLevelItem( i );
264  //If the item is null or does not have a pixel values set, skip
265  if ( !currentItem || currentItem->text( ValueColumn ).isEmpty() )
266  {
267  continue;
268  }
269 
270  if ( discrete )
271  {
272  if ( i == 0 )
273  {
274  label = "<= " + currentItem->text( ValueColumn );
275  }
276  else if ( currentItem->text( ValueColumn ).toDouble() == std::numeric_limits<double>::infinity() )
277  {
278  label = "> " + mColormapTreeWidget->topLevelItem( i - 1 )->text( ValueColumn );
279  }
280  else
281  {
282  label = mColormapTreeWidget->topLevelItem( i - 1 )->text( ValueColumn ) + " - " + currentItem->text( ValueColumn );
283  }
284  }
285  else
286  {
287  label = currentItem->text( ValueColumn );
288  }
289 
290  if ( currentItem->text( LabelColumn ).startsWith( label ) )
291  {
292  allSuffixes.append( currentItem->text( LabelColumn ).mid( label.length() ) );
293  }
294  }
295  // find most common suffix
296  QStringList suffixes = QStringList( allSuffixes );
297  suffixes.removeDuplicates();
298  int max = 0;
299  QString unit;
300  for ( int i = 0; i < suffixes.count(); ++i )
301  {
302  int n = allSuffixes.count( suffixes[i] );
303  if ( n > max )
304  {
305  max = n;
306  unit = suffixes[i];
307  }
308  }
309  // Set this suffix as unit if at least used twice
310  if ( max >= 2 )
311  {
312  mUnitLineEdit->setText( unit );
313  }
314  autoLabel();
315 }
316 
317 void QgsSingleBandPseudoColorRendererWidget::on_mAddEntryButton_clicked()
318 {
319  QgsTreeWidgetItem* newItem = new QgsTreeWidgetItem( mColormapTreeWidget );
320  newItem->setText( ValueColumn, "0" );
321  newItem->setBackground( ColorColumn, QBrush( QColor( Qt::magenta ) ) );
322  newItem->setText( LabelColumn, QString() );
323  newItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable );
324  connect( newItem, SIGNAL( itemEdited( QTreeWidgetItem*, int ) ),
325  this, SLOT( mColormapTreeWidget_itemEdited( QTreeWidgetItem*, int ) ) );
326  mColormapTreeWidget->sortItems( ValueColumn, Qt::AscendingOrder );
327  autoLabel();
328  emit widgetChanged();
329 }
330 
331 void QgsSingleBandPseudoColorRendererWidget::on_mDeleteEntryButton_clicked()
332 {
333  QTreeWidgetItem* currentItem = mColormapTreeWidget->currentItem();
334  if ( currentItem )
335  {
336  delete currentItem;
337  }
338  emit widgetChanged();
339 }
340 
341 void QgsSingleBandPseudoColorRendererWidget::on_mNumberOfEntriesSpinBox_valueChanged()
342 {
343 }
344 
345 void QgsSingleBandPseudoColorRendererWidget::on_mClassifyButton_clicked()
346 {
347  int bandComboIndex = mBandComboBox->currentIndex();
348  if ( bandComboIndex == -1 || !mRasterLayer )
349  {
350  return;
351  }
352 
353  //int bandNr = mBandComboBox->itemData( bandComboIndex ).toInt();
354  //QgsRasterBandStats myRasterBandStats = mRasterLayer->dataProvider()->bandStatistics( bandNr );
355  int numberOfEntries;
356 
357  QgsColorRampShader::ColorRamp_TYPE interpolation = static_cast< QgsColorRampShader::ColorRamp_TYPE >( mColorInterpolationComboBox->itemData( mColorInterpolationComboBox->currentIndex() ).toInt() );
358  bool discrete = interpolation == QgsColorRampShader::DISCRETE;
359 
360  QList<double> entryValues;
361  QVector<QColor> entryColors;
362 
363  double min = lineEditValue( mMinLineEdit );
364  double max = lineEditValue( mMaxLineEdit );
365 
366  QScopedPointer< QgsVectorColorRampV2 > colorRamp( mColorRampComboBox->currentColorRamp() );
367 
368  if ( mClassificationModeComboBox->itemData( mClassificationModeComboBox->currentIndex() ).toInt() == Continuous )
369  {
370  if ( colorRamp.data() )
371  {
372  numberOfEntries = colorRamp->count();
373  entryValues.reserve( numberOfEntries );
374  if ( discrete )
375  {
376  double intervalDiff = max - min;
377 
378  // remove last class when ColorRamp is gradient and discrete, as they are implemented with an extra stop
379  QgsVectorGradientColorRampV2* colorGradientRamp = dynamic_cast<QgsVectorGradientColorRampV2*>( colorRamp.data() );
380  if ( colorGradientRamp != NULL && colorGradientRamp->isDiscrete() )
381  {
382  numberOfEntries--;
383  }
384  else
385  {
386  // if color ramp is continuous scale values to get equally distributed classes.
387  // Doesn't work perfectly when stops are non equally distributed.
388  intervalDiff *= ( numberOfEntries - 1 ) / ( double )numberOfEntries;
389  }
390 
391  // skip first value (always 0.0)
392  for ( int i = 1; i < numberOfEntries; ++i )
393  {
394  double value = colorRamp->value( i );
395  entryValues.push_back( min + value * intervalDiff );
396  }
397  entryValues.push_back( std::numeric_limits<double>::infinity() );
398  }
399  else
400  {
401  for ( int i = 0; i < numberOfEntries; ++i )
402  {
403  double value = colorRamp->value( i );
404  entryValues.push_back( min + value * ( max - min ) );
405  }
406  }
407  // for continuous mode take original color map colors
408  for ( int i = 0; i < numberOfEntries; ++i )
409  {
410  entryColors.push_back( colorRamp->color( colorRamp->value( i ) ) );
411  }
412  }
413  }
414  else // for other classification modes interpolate colors linearly
415  {
416  numberOfEntries = mNumberOfEntriesSpinBox->value();
417  if ( numberOfEntries < 2 )
418  return; // < 2 classes is not useful, shouldn't happen, but if it happens save it from crashing
419 
420  if ( mClassificationModeComboBox->itemData( mClassificationModeComboBox->currentIndex() ).toInt() == Quantile )
421  { // Quantile
422  int bandNr = mBandComboBox->itemData( bandComboIndex ).toInt();
423  //QgsRasterHistogram rasterHistogram = mRasterLayer->dataProvider()->histogram( bandNr );
424 
425  double cut1 = std::numeric_limits<double>::quiet_NaN();
426  double cut2 = std::numeric_limits<double>::quiet_NaN();
427 
428  QgsRectangle extent = mMinMaxWidget->extent();
429  int sampleSize = mMinMaxWidget->sampleSize();
430 
431  // set min and max from histogram, used later to calculate number of decimals to display
432  mRasterLayer->dataProvider()->cumulativeCut( bandNr, 0.0, 1.0, min, max, extent, sampleSize );
433 
434  entryValues.reserve( numberOfEntries );
435  if ( discrete )
436  {
437  double intervalDiff = 1.0 / ( numberOfEntries );
438  for ( int i = 1; i < numberOfEntries; ++i )
439  {
440  mRasterLayer->dataProvider()->cumulativeCut( bandNr, 0.0, i * intervalDiff, cut1, cut2, extent, sampleSize );
441  entryValues.push_back( cut2 );
442  }
443  entryValues.push_back( std::numeric_limits<double>::infinity() );
444  }
445  else
446  {
447  double intervalDiff = 1.0 / ( numberOfEntries - 1 );
448  for ( int i = 0; i < numberOfEntries; ++i )
449  {
450  mRasterLayer->dataProvider()->cumulativeCut( bandNr, 0.0, i * intervalDiff, cut1, cut2, extent, sampleSize );
451  entryValues.push_back( cut2 );
452  }
453  }
454  }
455  else // EqualInterval
456  {
457  entryValues.reserve( numberOfEntries );
458  if ( discrete )
459  {
460  // in discrete mode the lowest value is not an entry and the highest
461  // value is inf, there are ( numberOfEntries ) of which the first
462  // and last are not used.
463  double intervalDiff = ( max - min ) / ( numberOfEntries );
464 
465  for ( int i = 1; i < numberOfEntries; ++i )
466  {
467  entryValues.push_back( min + i * intervalDiff );
468  }
469  entryValues.push_back( std::numeric_limits<double>::infinity() );
470  }
471  else
472  {
473  //because the highest value is also an entry, there are (numberOfEntries - 1) intervals
474  double intervalDiff = ( max - min ) / ( numberOfEntries - 1 );
475 
476  for ( int i = 0; i < numberOfEntries; ++i )
477  {
478  entryValues.push_back( min + i * intervalDiff );
479  }
480  }
481  }
482 
483  if ( !colorRamp.data() )
484  {
485  //hard code color range from blue -> red (previous default)
486  int colorDiff = 0;
487  if ( numberOfEntries != 0 )
488  {
489  colorDiff = ( int )( 255 / numberOfEntries );
490  }
491 
492  entryColors.reserve( numberOfEntries );
493  for ( int i = 0; i < numberOfEntries; ++i )
494  {
495  QColor currentColor;
496  int idx = mInvertCheckBox->isChecked() ? numberOfEntries - i - 1 : i;
497  currentColor.setRgb( colorDiff*idx, 0, 255 - colorDiff * idx );
498  entryColors.push_back( currentColor );
499  }
500  }
501  else
502  {
503  entryColors.reserve( numberOfEntries );
504  for ( int i = 0; i < numberOfEntries; ++i )
505  {
506  int idx = mInvertCheckBox->isChecked() ? numberOfEntries - i - 1 : i;
507  entryColors.push_back( colorRamp->color((( double ) idx ) / ( numberOfEntries - 1 ) ) );
508  }
509  }
510  }
511 
512  mColormapTreeWidget->clear();
513 
514  QList<double>::const_iterator value_it = entryValues.begin();
515  QVector<QColor>::const_iterator color_it = entryColors.begin();
516 
517  // calculate a reasonable number of decimals to display
518  double maxabs = log10( qMax( qAbs( max ), qAbs( min ) ) );
519  int nDecimals = qRound( qMax( 3.0 + maxabs - log10( max - min ), maxabs <= 15.0 ? maxabs + 0.49 : 0.0 ) );
520 
521  for ( ; value_it != entryValues.end(); ++value_it, ++color_it )
522  {
523  QgsTreeWidgetItem* newItem = new QgsTreeWidgetItem( mColormapTreeWidget );
524  newItem->setText( ValueColumn, QString::number( *value_it, 'g', nDecimals ) );
525  newItem->setBackground( ColorColumn, QBrush( *color_it ) );
526  newItem->setText( LabelColumn, QString() );
527  newItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable );
528  connect( newItem, SIGNAL( itemEdited( QTreeWidgetItem*, int ) ),
529  this, SLOT( mColormapTreeWidget_itemEdited( QTreeWidgetItem*, int ) ) );
530  }
531  autoLabel();
532  emit widgetChanged();
533 }
534 
535 void QgsSingleBandPseudoColorRendererWidget::on_mClassificationModeComboBox_currentIndexChanged( int index )
536 {
537  Mode mode = static_cast< Mode >( mClassificationModeComboBox->itemData( index ).toInt() );
538  mNumberOfEntriesSpinBox->setEnabled( mode != Continuous );
539  mMinLineEdit->setEnabled( mode != Quantile );
540  mMaxLineEdit->setEnabled( mode != Quantile );
541 }
542 
543 void QgsSingleBandPseudoColorRendererWidget::on_mColorRampComboBox_currentIndexChanged( int index )
544 {
545  Q_UNUSED( index );
546  QSettings settings;
547  settings.setValue( "/Raster/defaultPalette", mColorRampComboBox->currentText() );
548 
549  QgsVectorColorRampV2* ramp = mColorRampComboBox->currentColorRamp();
550  if ( !ramp )
551  return;
552 
553  bool enableContinuous = ( ramp->count() > 0 );
554  mClassificationModeComboBox->setEnabled( enableContinuous );
555  if ( !enableContinuous )
556  {
557  mClassificationModeComboBox->setCurrentIndex( mClassificationModeComboBox->findData( EqualInterval ) );
558  }
559 }
560 
561 void QgsSingleBandPseudoColorRendererWidget::populateColormapTreeWidget( const QList<QgsColorRampShader::ColorRampItem>& colorRampItems )
562 {
563  mColormapTreeWidget->clear();
565  for ( ; it != colorRampItems.constEnd(); ++it )
566  {
567  QgsTreeWidgetItem* newItem = new QgsTreeWidgetItem( mColormapTreeWidget );
568  newItem->setText( ValueColumn, QString::number( it->value, 'g', 15 ) );
569  newItem->setBackground( ColorColumn, QBrush( it->color ) );
570  newItem->setText( LabelColumn, it->label );
571  newItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable );
572  connect( newItem, SIGNAL( itemEdited( QTreeWidgetItem*, int ) ),
573  this, SLOT( mColormapTreeWidget_itemEdited( QTreeWidgetItem*, int ) ) );
574  }
575  setUnitFromLabels();
576 }
577 
578 void QgsSingleBandPseudoColorRendererWidget::on_mLoadFromBandButton_clicked()
579 {
580  if ( !mRasterLayer || !mRasterLayer->dataProvider() )
581  {
582  return;
583  }
584 
585  int bandIndex = mBandComboBox->itemData( mBandComboBox->currentIndex() ).toInt();
586 
587 
589  if ( !colorRampList.isEmpty() )
590  {
591  populateColormapTreeWidget( colorRampList );
592  mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( QgsColorRampShader::INTERPOLATED ) );
593  }
594  else
595  {
596  QMessageBox::warning( this, tr( "Load Color Map" ), tr( "The color map for band %1 has no entries" ).arg( bandIndex ) );
597  }
598  emit widgetChanged();
599 }
600 
601 void QgsSingleBandPseudoColorRendererWidget::on_mLoadFromFileButton_clicked()
602 {
603  int lineCounter = 0;
604  bool importError = false;
605  QString badLines;
606  QSettings settings;
607  QString lastDir = settings.value( "lastColorMapDir", QDir::homePath() ).toString();
608  QString fileName = QFileDialog::getOpenFileName( this, tr( "Open file" ), lastDir, tr( "Textfile (*.txt)" ) );
609  QFile inputFile( fileName );
610  if ( inputFile.open( QFile::ReadOnly ) )
611  {
612  //clear the current tree
613  mColormapTreeWidget->clear();
614 
615  QTextStream inputStream( &inputFile );
616  QString inputLine;
617  QStringList inputStringComponents;
619 
620  //read through the input looking for valid data
621  while ( !inputStream.atEnd() )
622  {
623  lineCounter++;
624  inputLine = inputStream.readLine();
625  if ( !inputLine.isEmpty() )
626  {
627  if ( !inputLine.simplified().startsWith( '#' ) )
628  {
629  if ( inputLine.contains( "INTERPOLATION", Qt::CaseInsensitive ) )
630  {
631  inputStringComponents = inputLine.split( ':' );
632  if ( inputStringComponents.size() == 2 )
633  {
634  if ( inputStringComponents[1].trimmed().toUpper().compare( "INTERPOLATED", Qt::CaseInsensitive ) == 0 )
635  {
636  mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( QgsColorRampShader::INTERPOLATED ) );
637  }
638  else if ( inputStringComponents[1].trimmed().toUpper().compare( "DISCRETE", Qt::CaseInsensitive ) == 0 )
639  {
640  mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( QgsColorRampShader::DISCRETE ) );
641  }
642  else
643  {
644  mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( QgsColorRampShader::EXACT ) );
645  }
646  }
647  else
648  {
649  importError = true;
650  badLines = badLines + QString::number( lineCounter ) + ":\t[" + inputLine + "]\n";
651  }
652  }
653  else
654  {
655  inputStringComponents = inputLine.split( ',' );
656  if ( inputStringComponents.size() == 6 )
657  {
658  QgsColorRampShader::ColorRampItem currentItem( inputStringComponents[0].toDouble(),
659  QColor::fromRgb( inputStringComponents[1].toInt(), inputStringComponents[2].toInt(),
660  inputStringComponents[3].toInt(), inputStringComponents[4].toInt() ),
661  inputStringComponents[5] );
662  colorRampItems.push_back( currentItem );
663  }
664  else
665  {
666  importError = true;
667  badLines = badLines + QString::number( lineCounter ) + ":\t[" + inputLine + "]\n";
668  }
669  }
670  }
671  }
672  lineCounter++;
673  }
674  populateColormapTreeWidget( colorRampItems );
675 
676  QFileInfo fileInfo( fileName );
677  settings.setValue( "lastColorMapDir", fileInfo.absoluteDir().absolutePath() );
678 
679  if ( importError )
680  {
681  QMessageBox::warning( this, tr( "Import Error" ), tr( "The following lines contained errors\n\n" ) + badLines );
682  }
683  }
684  else if ( !fileName.isEmpty() )
685  {
686  QMessageBox::warning( this, tr( "Read access denied" ), tr( "Read access denied. Adjust the file permissions and try again.\n\n" ) );
687  }
688  emit widgetChanged();
689 }
690 
691 void QgsSingleBandPseudoColorRendererWidget::on_mExportToFileButton_clicked()
692 {
693  QSettings settings;
694  QString lastDir = settings.value( "lastColorMapDir", QDir::homePath() ).toString();
695  QString fileName = QFileDialog::getSaveFileName( this, tr( "Save file" ), lastDir, tr( "Textfile (*.txt)" ) );
696  if ( !fileName.isEmpty() )
697  {
698  if ( !fileName.endsWith( ".txt", Qt::CaseInsensitive ) )
699  {
700  fileName = fileName + ".txt";
701  }
702 
703  QFile outputFile( fileName );
704  if ( outputFile.open( QFile::WriteOnly ) )
705  {
706  QTextStream outputStream( &outputFile );
707  outputStream << "# " << tr( "QGIS Generated Color Map Export File" ) << '\n';
708  outputStream << "INTERPOLATION:";
709  QgsColorRampShader::ColorRamp_TYPE interpolation = static_cast< QgsColorRampShader::ColorRamp_TYPE >( mColorInterpolationComboBox->itemData( mColorInterpolationComboBox->currentIndex() ).toInt() );
710  switch ( interpolation )
711  {
713  outputStream << "INTERPOLATED\n";
714  break;
716  outputStream << "DISCRETE\n";
717  break;
719  outputStream << "EXACT\n";
720  break;
721  }
722 
723  int topLevelItemCount = mColormapTreeWidget->topLevelItemCount();
724  QTreeWidgetItem* currentItem;
725  QColor color;
726  for ( int i = 0; i < topLevelItemCount; ++i )
727  {
728  currentItem = mColormapTreeWidget->topLevelItem( i );
729  if ( !currentItem )
730  {
731  continue;
732  }
733  color = currentItem->background( ColorColumn ).color();
734  outputStream << currentItem->text( ValueColumn ).toDouble() << ',';
735  outputStream << color.red() << ',' << color.green() << ',' << color.blue() << ',' << color.alpha() << ',';
736  if ( currentItem->text( LabelColumn ).isEmpty() )
737  {
738  outputStream << "Color entry " << i + 1 << '\n';
739  }
740  else
741  {
742  outputStream << currentItem->text( LabelColumn ) << '\n';
743  }
744  }
745  outputStream.flush();
746  outputFile.close();
747 
748  QFileInfo fileInfo( fileName );
749  settings.setValue( "lastColorMapDir", fileInfo.absoluteDir().absolutePath() );
750  }
751  else
752  {
753  QMessageBox::warning( this, tr( "Write access denied" ), tr( "Write access denied. Adjust the file permissions and try again.\n\n" ) );
754  }
755  }
756 }
757 
758 void QgsSingleBandPseudoColorRendererWidget::on_mColormapTreeWidget_itemDoubleClicked( QTreeWidgetItem* item, int column )
759 {
760  if ( !item )
761  {
762  return;
763  }
764 
765  if ( column == ColorColumn )
766  {
767  item->setFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable );
768  QColor newColor = QgsColorDialogV2::getColor( item->background( column ).color(), this, "Change color", true );
769  if ( newColor.isValid() )
770  {
771  item->setBackground( ColorColumn, QBrush( newColor ) );
772  emit widgetChanged();
773  }
774  }
775  else
776  {
777  if ( column == LabelColumn )
778  {
779  // Set text color to default black, which signifies a manually edited label
780  item->setForeground( LabelColumn, QBrush() );
781  }
782  item->setFlags( Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable );
783  }
784 }
785 
787 void QgsSingleBandPseudoColorRendererWidget::mColormapTreeWidget_itemEdited( QTreeWidgetItem* item, int column )
788 {
789  Q_UNUSED( item );
790 
791  if ( column == ValueColumn )
792  {
793  mColormapTreeWidget->sortItems( ValueColumn, Qt::AscendingOrder );
794  autoLabel();
795  emit widgetChanged();
796  }
797  else if ( column == LabelColumn )
798  {
799  // call autoLabel to fill when empty or gray out when same as autoLabel
800  autoLabel();
801  }
802 }
803 
805 {
806  const QgsSingleBandPseudoColorRenderer* pr = dynamic_cast<const QgsSingleBandPseudoColorRenderer*>( r );
807  if ( pr )
808  {
809  mBandComboBox->setCurrentIndex( mBandComboBox->findData( pr->band() ) );
810 
811  const QgsRasterShader* rasterShader = pr->shader();
812  if ( rasterShader )
813  {
814  const QgsColorRampShader* colorRampShader = dynamic_cast<const QgsColorRampShader*>( rasterShader->rasterShaderFunction() );
815  if ( colorRampShader )
816  {
817  mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( colorRampShader->colorRampType() ) );
818 
819  const QList<QgsColorRampShader::ColorRampItem> colorRampItemList = colorRampShader->colorRampItemList();
821  for ( ; it != colorRampItemList.end(); ++it )
822  {
823  QgsTreeWidgetItem* newItem = new QgsTreeWidgetItem( mColormapTreeWidget );
824  newItem->setText( ValueColumn, QString::number( it->value, 'g', 15 ) );
825  newItem->setBackground( ColorColumn, QBrush( it->color ) );
826  newItem->setText( LabelColumn, it->label );
827  newItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable );
828  connect( newItem, SIGNAL( itemEdited( QTreeWidgetItem*, int ) ),
829  this, SLOT( mColormapTreeWidget_itemEdited( QTreeWidgetItem*, int ) ) );
830  }
831  setUnitFromLabels();
832  mClipCheckBox->setChecked( colorRampShader->clip() );
833  }
834  }
835  setLineEditValue( mMinLineEdit, pr->classificationMin() );
836  setLineEditValue( mMaxLineEdit, pr->classificationMax() );
837  mMinMaxOrigin = pr->classificationMinMaxOrigin();
838  showMinMaxOrigin();
839  }
840 }
841 
842 void QgsSingleBandPseudoColorRendererWidget::on_mBandComboBox_currentIndexChanged( int index )
843 {
844  QList<int> bands;
845  bands.append( mBandComboBox->itemData( index ).toInt() );
846  mMinMaxWidget->setBands( bands );
847 }
848 
849 void QgsSingleBandPseudoColorRendererWidget::on_mColorInterpolationComboBox_currentIndexChanged( int index )
850 {
851  QgsColorRampShader::ColorRamp_TYPE interpolation = static_cast< QgsColorRampShader::ColorRamp_TYPE >( mColorInterpolationComboBox->itemData( index ).toInt() );
852 
853  mClipCheckBox->setEnabled( interpolation == QgsColorRampShader::INTERPOLATED );
854 
855  QString valueLabel;
856  QString valueToolTip;
857  switch ( interpolation )
858  {
860  valueLabel = tr( "Value" );
861  valueToolTip = tr( "Value for color stop" );
862  break;
864  valueLabel = tr( "Value <=" );
865  valueToolTip = tr( "Maximum value for class" );
866  break;
868  valueLabel = tr( "Value =" );
869  valueToolTip = tr( "Value for color" );
870  break;
871  }
872 
873  QTreeWidgetItem* header = mColormapTreeWidget->headerItem();
874  header->setText( ValueColumn, valueLabel );
875  header->setToolTip( ValueColumn, valueToolTip );
876 
877  autoLabel();
878  emit widgetChanged();
879 }
880 
881 void QgsSingleBandPseudoColorRendererWidget::loadMinMax( int theBandNo, double theMin, double theMax, int theOrigin )
882 {
883  Q_UNUSED( theBandNo );
884  QgsDebugMsg( QString( "theBandNo = %1 theMin = %2 theMax = %3" ).arg( theBandNo ).arg( theMin ).arg( theMax ) );
885 
886  if ( qIsNaN( theMin ) )
887  {
888  mMinLineEdit->clear();
889  }
890  else
891  {
892  mMinLineEdit->setText( QString::number( theMin ) );
893  }
894 
895  if ( qIsNaN( theMax ) )
896  {
897  mMaxLineEdit->clear();
898  }
899  else
900  {
901  mMaxLineEdit->setText( QString::number( theMax ) );
902  }
903 
904  mMinMaxOrigin = theOrigin;
905  showMinMaxOrigin();
906 }
907 
908 void QgsSingleBandPseudoColorRendererWidget::showMinMaxOrigin()
909 {
910  mMinMaxOriginLabel->setText( QgsRasterRenderer::minMaxOriginLabel( mMinMaxOrigin ) );
911 }
912 
913 void QgsSingleBandPseudoColorRendererWidget::setLineEditValue( QLineEdit * theLineEdit, double theValue )
914 {
915  QString s;
916  if ( !qIsNaN( theValue ) )
917  {
918  s = QString::number( theValue );
919  }
920  theLineEdit->setText( s );
921 }
922 
923 double QgsSingleBandPseudoColorRendererWidget::lineEditValue( const QLineEdit * theLineEdit ) const
924 {
925  if ( theLineEdit->text().isEmpty() )
926  {
927  return std::numeric_limits<double>::quiet_NaN();
928  }
929 
930  return theLineEdit->text().toDouble();
931 }
932 
933 void QgsSingleBandPseudoColorRendererWidget::resetClassifyButton()
934 {
935  mClassifyButton->setEnabled( true );
936  double min = lineEditValue( mMinLineEdit );
937  double max = lineEditValue( mMaxLineEdit );
938  if ( qIsNaN( min ) || qIsNaN( max ) || min >= max )
939  {
940  mClassifyButton->setEnabled( false );
941  }
942 }
QLayout * layout() const
Assigns the color of the exact matching value in the color ramp item list.
virtual int bandCount() const =0
Get number of bands.
static unsigned index
A rectangle specified with double values.
Definition: qgsrectangle.h:35
Interface for all raster shaders.
void setBackground(int column, const QBrush &brush)
void setContentsMargins(int left, int top, int right, int bottom)
void setupUi(QWidget *widget)
int sortColumn() const
void itemEdited(QTreeWidgetItem *item, int column)
This signal is emitted when the contents of the column in the specified item has been edited by the u...
QString readLine(qint64 maxlen)
iterator begin()
void push_back(const T &value)
void setText(const QString &)
void setToolTip(int column, const QString &toolTip)
A ramp shader will color a raster pixel based on a list of values ranges in a ramp.
#define QgsDebugMsg(str)
Definition: qgslogger.h:33
This class provides qgis with the ability to render raster datasets onto the mapcanvas.
void setBands(const QList< int > &theBands)
QStringList split(const QString &sep, SplitBehavior behavior, Qt::CaseSensitivity cs) const
void reserve(int alloc)
int removeDuplicates()
QString simplified() const
virtual bool operator<(const QTreeWidgetItem &other) const
Returns true if the text in the item is less than the text in the other item, otherwise returns false...
virtual void setData(int column, int role, const QVariant &value)
void setColorRampItemList(const QList< QgsColorRampShader::ColorRampItem > &theList)
Set custom colormap.
void setClip(bool clip)
Sets whether the shader should not render values out of range.
QBrush foreground(int column) const
void setRgb(int r, int g, int b, int a)
double toDouble(bool *ok) const
QString homePath()
QString tr(const char *sourceText, const char *disambiguation, int n)
void loadMinMax(int theBandNo, double theMin, double theMax, int theOrigin)
Map canvas is a class for displaying all GIS data types on a canvas.
Definition: qgsmapcanvas.h:109
int size() const
virtual void setMapCanvas(QgsMapCanvas *canvas)
Sets the map canvas associated with the widget.
const QColor & color() const
void setMapCanvas(QgsMapCanvas *canvas) override
Sets the map canvas associated with the widget.
void setValue(const QString &key, const QVariant &value)
QList< QgsColorRampShader::ColorRampItem > colorRampItemList() const
Get the custom colormap.
void setFlags(QFlags< Qt::ItemFlag > flags)
QColor fromRgb(QRgb rgb)
void addWidget(QWidget *widget, int stretch, QFlags< Qt::AlignmentFlag > alignment)
QString number(int n, int base)
int count(const T &value) const
void append(const T &value)
QgsColorRampShader::ColorRamp_TYPE colorRampType() const
Get the color ramp type.
virtual QString min(int index=0)
bool atEnd() const
QgsRasterRenderer * renderer() const
void setExtent(const QgsRectangle &theExtent)
Sets the extent to use for minimum and maximum value calculation.
bool clip() const
Returns whether the shader will clip values which are out of range.
virtual void setData(int column, int role, const QVariant &value)
Sets the value for the item&#39;s column and role to the given value.
int red() const
int band() const
Returns the band used by the renderer.
QgsRasterShaderFunction * rasterShaderFunction()
bool isEmpty() const
static QgsStyleV2 * defaultStyle()
return default application-wide style
Definition: qgsstylev2.cpp:51
bool isEmpty() const
static QString minMaxOriginLabel(int theOrigin)
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const
Raster renderer pipe for single band pseudocolor.
QDir absoluteDir() const
bool endsWith(const QString &s, Qt::CaseSensitivity cs) const
QTreeWidget * treeWidget() const
virtual bool open(QFlags< QIODevice::OpenModeFlag > mode)
void setRasterShaderFunction(QgsRasterShaderFunction *)
A public method that allows the user to set their own shader function.
int alpha() const
void setColorRampType(QgsColorRampShader::ColorRamp_TYPE theColorRampType)
Set the color ramp type.
int green() const
iterator end()
void reserve(int size)
bool contains(QChar ch, Qt::CaseSensitivity cs) const
int sampleSize()
Return the selected sample size.
virtual void close()
int blue() const
QString displayBandName(int band) const
Returns a band name for display.
QVariant value(const QString &key, const QVariant &defaultValue) const
virtual int count() const =0
Returns number of defined colors, or -1 if undefined.
static QColor getColor(const QColor &initialColor, QWidget *parent, const QString &title=QString(), const bool allowAlpha=false)
Return a color selection from a color dialog.
QString mid(int position, int n) const
QgsRectangle extent()
Return the extent selected by the user.
void setMapCanvas(QgsMapCanvas *canvas)
Sets the map canvas associated with the widget.
void flush()
QString absolutePath() const
Custom QTreeWidgetItem with extra signal when item is edited and numeric sorting. ...
Interpolates the color between two class breaks linearly.
Assigns the color of the higher class for every pixel between two class breaks.
bool isDiscrete() const
Returns true if the gradient is using discrete interpolation, rather than smoothly interpolating betw...
QgsSingleBandPseudoColorRendererWidget(QgsRasterLayer *layer, const QgsRectangle &extent=QgsRectangle())
void setText(int column, const QString &text)
QString getSaveFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFlags< QFileDialog::Option > options)
int length() const
void push_back(const T &value)
StandardButton warning(QWidget *parent, const QString &title, const QString &text, QFlags< QMessageBox::StandardButton > buttons, StandardButton defaultButton)
virtual QString max(int index=0)
QgsRasterDataProvider * dataProvider()
Returns the data provider.
virtual double value(int index) const override
Returns relative value between [0,1] of color at specified index.
QString getOpenFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFlags< QFileDialog::Option > options)
const_iterator constEnd() const
const_iterator constBegin() const
virtual void cumulativeCut(int theBandNo, double theLowerCount, double theUpperCount, double &theLowerValue, double &theUpperValue, const QgsRectangle &theExtent=QgsRectangle(), int theSampleSize=0)
Find values for cumulative pixel count cut.
bool connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QgsMapCanvas * mCanvas
Associated map canvas.
virtual QList< QgsColorRampShader::ColorRampItem > colorTable(int bandNo) const
void widgetChanged()
Emitted when something on the widget has changed.
Abstract base class for color ramps.
QString toString() const
Gradient color ramp, which smoothly interpolates between two colors and also supports optional extra ...
QString text(int column) const
ColorRamp_TYPE
Supported methods for color interpolation.
iterator begin()
void setForeground(int column, const QBrush &brush)
Raster renderer pipe that applies colors to a raster.
QBrush background(int column) const
bool isValid() const
Base class for raster data providers.