QGIS API Documentation  2.17.0-Master (bf77d09)
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 }
151 
153 {
154 }
155 
157 {
158  QgsRasterShader* rasterShader = new QgsRasterShader();
159  QgsColorRampShader* colorRampShader = new QgsColorRampShader();
160  colorRampShader->setClip( mClipCheckBox->isChecked() );
161 
162  //iterate through mColormapTreeWidget and set colormap info of layer
164  int topLevelItemCount = mColormapTreeWidget->topLevelItemCount();
165  QTreeWidgetItem* currentItem;
166  for ( int i = 0; i < topLevelItemCount; ++i )
167  {
168  currentItem = mColormapTreeWidget->topLevelItem( i );
169  if ( !currentItem )
170  {
171  continue;
172  }
173  QgsColorRampShader::ColorRampItem newColorRampItem;
174  newColorRampItem.value = currentItem->text( ValueColumn ).toDouble();
175  newColorRampItem.color = currentItem->background( ColorColumn ).color();
176  newColorRampItem.label = currentItem->text( LabelColumn );
177  colorRampItems.append( newColorRampItem );
178  }
179  // sort the shader items
180  qSort( colorRampItems );
181  colorRampShader->setColorRampItemList( colorRampItems );
182 
183  QgsColorRampShader::ColorRamp_TYPE interpolation = static_cast< QgsColorRampShader::ColorRamp_TYPE >( mColorInterpolationComboBox->itemData( mColorInterpolationComboBox->currentIndex() ).toInt() );
184  colorRampShader->setColorRampType( interpolation );
185  rasterShader->setRasterShaderFunction( colorRampShader );
186 
187  int bandNumber = mBandComboBox->itemData( mBandComboBox->currentIndex() ).toInt();
189 
190  renderer->setClassificationMin( lineEditValue( mMinLineEdit ) );
191  renderer->setClassificationMax( lineEditValue( mMaxLineEdit ) );
192  renderer->setClassificationMinMaxOrigin( mMinMaxOrigin );
193  return renderer;
194 }
195 
197 {
199  mMinMaxWidget->setMapCanvas( canvas );
200 }
201 
206 void QgsSingleBandPseudoColorRendererWidget::autoLabel()
207 {
208  QgsColorRampShader::ColorRamp_TYPE interpolation = static_cast< QgsColorRampShader::ColorRamp_TYPE >( mColorInterpolationComboBox->itemData( mColorInterpolationComboBox->currentIndex() ).toInt() );
209  bool discrete = interpolation == QgsColorRampShader::DISCRETE;
210  QString unit = mUnitLineEdit->text();
211  QString label;
212  int topLevelItemCount = mColormapTreeWidget->topLevelItemCount();
213  QTreeWidgetItem* currentItem;
214  for ( int i = 0; i < topLevelItemCount; ++i )
215  {
216  currentItem = mColormapTreeWidget->topLevelItem( i );
217  //If the item is null or does not have a pixel values set, skip
218  if ( !currentItem || currentItem->text( ValueColumn ).isEmpty() )
219  {
220  continue;
221  }
222 
223  if ( discrete )
224  {
225  if ( i == 0 )
226  {
227  label = "<= " + currentItem->text( ValueColumn ) + unit;
228  }
229  else if ( currentItem->text( ValueColumn ).toDouble() == std::numeric_limits<double>::infinity() )
230  {
231  label = "> " + mColormapTreeWidget->topLevelItem( i - 1 )->text( ValueColumn ) + unit;
232  }
233  else
234  {
235  label = mColormapTreeWidget->topLevelItem( i - 1 )->text( ValueColumn ) + " - " + currentItem->text( ValueColumn ) + unit;
236  }
237  }
238  else
239  {
240  label = currentItem->text( ValueColumn ) + unit;
241  }
242 
243  if ( currentItem->text( LabelColumn ).isEmpty() || currentItem->text( LabelColumn ) == label || currentItem->foreground( LabelColumn ).color() == QColor( Qt::gray ) )
244  {
245  currentItem->setText( LabelColumn, label );
246  currentItem->setForeground( LabelColumn, QBrush( QColor( Qt::gray ) ) );
247  }
248  }
249 }
250 
252 void QgsSingleBandPseudoColorRendererWidget::setUnitFromLabels()
253 {
254  QgsColorRampShader::ColorRamp_TYPE interpolation = static_cast< QgsColorRampShader::ColorRamp_TYPE >( mColorInterpolationComboBox->itemData( mColorInterpolationComboBox->currentIndex() ).toInt() );
255  bool discrete = interpolation == QgsColorRampShader::DISCRETE;
256  QStringList allSuffixes;
257  QString label;
258  int topLevelItemCount = mColormapTreeWidget->topLevelItemCount();
259  QTreeWidgetItem* currentItem;
260  for ( int i = 0; i < topLevelItemCount; ++i )
261  {
262  currentItem = mColormapTreeWidget->topLevelItem( i );
263  //If the item is null or does not have a pixel values set, skip
264  if ( !currentItem || currentItem->text( ValueColumn ).isEmpty() )
265  {
266  continue;
267  }
268 
269  if ( discrete )
270  {
271  if ( i == 0 )
272  {
273  label = "<= " + currentItem->text( ValueColumn );
274  }
275  else if ( currentItem->text( ValueColumn ).toDouble() == std::numeric_limits<double>::infinity() )
276  {
277  label = "> " + mColormapTreeWidget->topLevelItem( i - 1 )->text( ValueColumn );
278  }
279  else
280  {
281  label = mColormapTreeWidget->topLevelItem( i - 1 )->text( ValueColumn ) + " - " + currentItem->text( ValueColumn );
282  }
283  }
284  else
285  {
286  label = currentItem->text( ValueColumn );
287  }
288 
289  if ( currentItem->text( LabelColumn ).startsWith( label ) )
290  {
291  allSuffixes.append( currentItem->text( LabelColumn ).mid( label.length() ) );
292  }
293  }
294  // find most common suffix
295  QStringList suffixes = QStringList( allSuffixes );
296  suffixes.removeDuplicates();
297  int max = 0;
298  QString unit;
299  for ( int i = 0; i < suffixes.count(); ++i )
300  {
301  int n = allSuffixes.count( suffixes[i] );
302  if ( n > max )
303  {
304  max = n;
305  unit = suffixes[i];
306  }
307  }
308  // Set this suffix as unit if at least used twice
309  if ( max >= 2 )
310  {
311  mUnitLineEdit->setText( unit );
312  }
313  autoLabel();
314 }
315 
316 void QgsSingleBandPseudoColorRendererWidget::on_mAddEntryButton_clicked()
317 {
318  QgsTreeWidgetItem* newItem = new QgsTreeWidgetItem( mColormapTreeWidget );
319  newItem->setText( ValueColumn, "0" );
320  newItem->setBackground( ColorColumn, QBrush( QColor( Qt::magenta ) ) );
321  newItem->setText( LabelColumn, QString() );
322  newItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable );
323  connect( newItem, SIGNAL( itemEdited( QTreeWidgetItem*, int ) ),
324  this, SLOT( mColormapTreeWidget_itemEdited( QTreeWidgetItem*, int ) ) );
325  mColormapTreeWidget->sortItems( ValueColumn, Qt::AscendingOrder );
326  autoLabel();
327  emit widgetChanged();
328 }
329 
330 void QgsSingleBandPseudoColorRendererWidget::on_mDeleteEntryButton_clicked()
331 {
332  QTreeWidgetItem* currentItem = mColormapTreeWidget->currentItem();
333  if ( currentItem )
334  {
335  delete currentItem;
336  }
337  emit widgetChanged();
338 }
339 
340 void QgsSingleBandPseudoColorRendererWidget::on_mNumberOfEntriesSpinBox_valueChanged()
341 {
342 }
343 
344 void QgsSingleBandPseudoColorRendererWidget::on_mClassifyButton_clicked()
345 {
346  int bandComboIndex = mBandComboBox->currentIndex();
347  if ( bandComboIndex == -1 || !mRasterLayer )
348  {
349  return;
350  }
351 
352  //int bandNr = mBandComboBox->itemData( bandComboIndex ).toInt();
353  //QgsRasterBandStats myRasterBandStats = mRasterLayer->dataProvider()->bandStatistics( bandNr );
354  int numberOfEntries;
355 
356  QgsColorRampShader::ColorRamp_TYPE interpolation = static_cast< QgsColorRampShader::ColorRamp_TYPE >( mColorInterpolationComboBox->itemData( mColorInterpolationComboBox->currentIndex() ).toInt() );
357  bool discrete = interpolation == QgsColorRampShader::DISCRETE;
358 
359  QList<double> entryValues;
360  QVector<QColor> entryColors;
361 
362  double min = lineEditValue( mMinLineEdit );
363  double max = lineEditValue( mMaxLineEdit );
364 
365  QScopedPointer< QgsVectorColorRampV2 > colorRamp( mColorRampComboBox->currentColorRamp() );
366 
367  if ( mClassificationModeComboBox->itemData( mClassificationModeComboBox->currentIndex() ).toInt() == Continuous )
368  {
369  if ( colorRamp.data() )
370  {
371  numberOfEntries = colorRamp->count();
372  entryValues.reserve( numberOfEntries );
373  if ( discrete )
374  {
375  double intervalDiff = max - min;
376 
377  // remove last class when ColorRamp is gradient and discrete, as they are implemented with an extra stop
378  QgsVectorGradientColorRampV2* colorGradientRamp = dynamic_cast<QgsVectorGradientColorRampV2*>( colorRamp.data() );
379  if ( colorGradientRamp != NULL && colorGradientRamp->isDiscrete() )
380  {
381  numberOfEntries--;
382  }
383  else
384  {
385  // if color ramp is continuous scale values to get equally distributed classes.
386  // Doesn't work perfectly when stops are non equally distributed.
387  intervalDiff *= ( numberOfEntries - 1 ) / ( double )numberOfEntries;
388  }
389 
390  // skip first value (always 0.0)
391  for ( int i = 1; i < numberOfEntries; ++i )
392  {
393  double value = colorRamp->value( i );
394  entryValues.push_back( min + value * intervalDiff );
395  }
396  entryValues.push_back( std::numeric_limits<double>::infinity() );
397  }
398  else
399  {
400  for ( int i = 0; i < numberOfEntries; ++i )
401  {
402  double value = colorRamp->value( i );
403  entryValues.push_back( min + value * ( max - min ) );
404  }
405  }
406  // for continuous mode take original color map colors
407  for ( int i = 0; i < numberOfEntries; ++i )
408  {
409  entryColors.push_back( colorRamp->color( colorRamp->value( i ) ) );
410  }
411  }
412  }
413  else // for other classification modes interpolate colors linearly
414  {
415  numberOfEntries = mNumberOfEntriesSpinBox->value();
416  if ( numberOfEntries < 2 )
417  return; // < 2 classes is not useful, shouldn't happen, but if it happens save it from crashing
418 
419  if ( mClassificationModeComboBox->itemData( mClassificationModeComboBox->currentIndex() ).toInt() == Quantile )
420  { // Quantile
421  int bandNr = mBandComboBox->itemData( bandComboIndex ).toInt();
422  //QgsRasterHistogram rasterHistogram = mRasterLayer->dataProvider()->histogram( bandNr );
423 
424  double cut1 = std::numeric_limits<double>::quiet_NaN();
425  double cut2 = std::numeric_limits<double>::quiet_NaN();
426 
427  QgsRectangle extent = mMinMaxWidget->extent();
428  int sampleSize = mMinMaxWidget->sampleSize();
429 
430  // set min and max from histogram, used later to calculate number of decimals to display
431  mRasterLayer->dataProvider()->cumulativeCut( bandNr, 0.0, 1.0, min, max, extent, sampleSize );
432 
433  entryValues.reserve( numberOfEntries );
434  if ( discrete )
435  {
436  double intervalDiff = 1.0 / ( numberOfEntries );
437  for ( int i = 1; i < numberOfEntries; ++i )
438  {
439  mRasterLayer->dataProvider()->cumulativeCut( bandNr, 0.0, i * intervalDiff, cut1, cut2, extent, sampleSize );
440  entryValues.push_back( cut2 );
441  }
442  entryValues.push_back( std::numeric_limits<double>::infinity() );
443  }
444  else
445  {
446  double intervalDiff = 1.0 / ( numberOfEntries - 1 );
447  for ( int i = 0; i < numberOfEntries; ++i )
448  {
449  mRasterLayer->dataProvider()->cumulativeCut( bandNr, 0.0, i * intervalDiff, cut1, cut2, extent, sampleSize );
450  entryValues.push_back( cut2 );
451  }
452  }
453  }
454  else // EqualInterval
455  {
456  entryValues.reserve( numberOfEntries );
457  if ( discrete )
458  {
459  // in discrete mode the lowest value is not an entry and the highest
460  // value is inf, there are ( numberOfEntries ) of which the first
461  // and last are not used.
462  double intervalDiff = ( max - min ) / ( numberOfEntries );
463 
464  for ( int i = 1; i < numberOfEntries; ++i )
465  {
466  entryValues.push_back( min + i * intervalDiff );
467  }
468  entryValues.push_back( std::numeric_limits<double>::infinity() );
469  }
470  else
471  {
472  //because the highest value is also an entry, there are (numberOfEntries - 1) intervals
473  double intervalDiff = ( max - min ) / ( numberOfEntries - 1 );
474 
475  for ( int i = 0; i < numberOfEntries; ++i )
476  {
477  entryValues.push_back( min + i * intervalDiff );
478  }
479  }
480  }
481 
482  if ( !colorRamp.data() )
483  {
484  //hard code color range from blue -> red (previous default)
485  int colorDiff = 0;
486  if ( numberOfEntries != 0 )
487  {
488  colorDiff = ( int )( 255 / numberOfEntries );
489  }
490 
491  entryColors.reserve( numberOfEntries );
492  for ( int i = 0; i < numberOfEntries; ++i )
493  {
494  QColor currentColor;
495  int idx = mInvertCheckBox->isChecked() ? numberOfEntries - i - 1 : i;
496  currentColor.setRgb( colorDiff*idx, 0, 255 - colorDiff * idx );
497  entryColors.push_back( currentColor );
498  }
499  }
500  else
501  {
502  entryColors.reserve( numberOfEntries );
503  for ( int i = 0; i < numberOfEntries; ++i )
504  {
505  int idx = mInvertCheckBox->isChecked() ? numberOfEntries - i - 1 : i;
506  entryColors.push_back( colorRamp->color((( double ) idx ) / ( numberOfEntries - 1 ) ) );
507  }
508  }
509  }
510 
511  mColormapTreeWidget->clear();
512 
513  QList<double>::const_iterator value_it = entryValues.begin();
514  QVector<QColor>::const_iterator color_it = entryColors.begin();
515 
516  // calculate a reasonable number of decimals to display
517  double maxabs = log10( qMax( qAbs( max ), qAbs( min ) ) );
518  int nDecimals = qRound( qMax( 3.0 + maxabs - log10( max - min ), maxabs <= 15.0 ? maxabs + 0.49 : 0.0 ) );
519 
520  for ( ; value_it != entryValues.end(); ++value_it, ++color_it )
521  {
522  QgsTreeWidgetItem* newItem = new QgsTreeWidgetItem( mColormapTreeWidget );
523  newItem->setText( ValueColumn, QString::number( *value_it, 'g', nDecimals ) );
524  newItem->setBackground( ColorColumn, QBrush( *color_it ) );
525  newItem->setText( LabelColumn, QString() );
526  newItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable );
527  connect( newItem, SIGNAL( itemEdited( QTreeWidgetItem*, int ) ),
528  this, SLOT( mColormapTreeWidget_itemEdited( QTreeWidgetItem*, int ) ) );
529  }
530  autoLabel();
531  emit widgetChanged();
532 }
533 
534 void QgsSingleBandPseudoColorRendererWidget::on_mClassificationModeComboBox_currentIndexChanged( int index )
535 {
536  Mode mode = static_cast< Mode >( mClassificationModeComboBox->itemData( index ).toInt() );
537  mNumberOfEntriesSpinBox->setEnabled( mode != Continuous );
538  mMinLineEdit->setEnabled( mode != Quantile );
539  mMaxLineEdit->setEnabled( mode != Quantile );
540 }
541 
542 void QgsSingleBandPseudoColorRendererWidget::on_mColorRampComboBox_currentIndexChanged( int index )
543 {
544  Q_UNUSED( index );
545  QSettings settings;
546  settings.setValue( "/Raster/defaultPalette", mColorRampComboBox->currentText() );
547 
548  QgsVectorColorRampV2* ramp = mColorRampComboBox->currentColorRamp();
549  if ( !ramp )
550  return;
551 
552  bool enableContinuous = ( ramp->count() > 0 );
553  mClassificationModeComboBox->setEnabled( enableContinuous );
554  if ( !enableContinuous )
555  {
556  mClassificationModeComboBox->setCurrentIndex( mClassificationModeComboBox->findData( EqualInterval ) );
557  }
558 }
559 
560 void QgsSingleBandPseudoColorRendererWidget::populateColormapTreeWidget( const QList<QgsColorRampShader::ColorRampItem>& colorRampItems )
561 {
562  mColormapTreeWidget->clear();
564  for ( ; it != colorRampItems.constEnd(); ++it )
565  {
566  QgsTreeWidgetItem* newItem = new QgsTreeWidgetItem( mColormapTreeWidget );
567  newItem->setText( ValueColumn, QString::number( it->value, 'g', 15 ) );
568  newItem->setBackground( ColorColumn, QBrush( it->color ) );
569  newItem->setText( LabelColumn, it->label );
570  newItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable );
571  connect( newItem, SIGNAL( itemEdited( QTreeWidgetItem*, int ) ),
572  this, SLOT( mColormapTreeWidget_itemEdited( QTreeWidgetItem*, int ) ) );
573  }
574  setUnitFromLabels();
575 }
576 
577 void QgsSingleBandPseudoColorRendererWidget::on_mLoadFromBandButton_clicked()
578 {
579  if ( !mRasterLayer || !mRasterLayer->dataProvider() )
580  {
581  return;
582  }
583 
584  int bandIndex = mBandComboBox->itemData( mBandComboBox->currentIndex() ).toInt();
585 
586 
588  if ( !colorRampList.isEmpty() )
589  {
590  populateColormapTreeWidget( colorRampList );
591  mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( QgsColorRampShader::INTERPOLATED ) );
592  }
593  else
594  {
595  QMessageBox::warning( this, tr( "Load Color Map" ), tr( "The color map for band %1 has no entries" ).arg( bandIndex ) );
596  }
597  emit widgetChanged();
598 }
599 
600 void QgsSingleBandPseudoColorRendererWidget::on_mLoadFromFileButton_clicked()
601 {
602  int lineCounter = 0;
603  bool importError = false;
604  QString badLines;
605  QSettings settings;
606  QString lastDir = settings.value( "lastColorMapDir", QDir::homePath() ).toString();
607  QString fileName = QFileDialog::getOpenFileName( this, tr( "Open file" ), lastDir, tr( "Textfile (*.txt)" ) );
608  QFile inputFile( fileName );
609  if ( inputFile.open( QFile::ReadOnly ) )
610  {
611  //clear the current tree
612  mColormapTreeWidget->clear();
613 
614  QTextStream inputStream( &inputFile );
615  QString inputLine;
616  QStringList inputStringComponents;
618 
619  //read through the input looking for valid data
620  while ( !inputStream.atEnd() )
621  {
622  lineCounter++;
623  inputLine = inputStream.readLine();
624  if ( !inputLine.isEmpty() )
625  {
626  if ( !inputLine.simplified().startsWith( '#' ) )
627  {
628  if ( inputLine.contains( "INTERPOLATION", Qt::CaseInsensitive ) )
629  {
630  inputStringComponents = inputLine.split( ':' );
631  if ( inputStringComponents.size() == 2 )
632  {
633  if ( inputStringComponents[1].trimmed().toUpper().compare( "INTERPOLATED", Qt::CaseInsensitive ) == 0 )
634  {
635  mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( QgsColorRampShader::INTERPOLATED ) );
636  }
637  else if ( inputStringComponents[1].trimmed().toUpper().compare( "DISCRETE", Qt::CaseInsensitive ) == 0 )
638  {
639  mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( QgsColorRampShader::DISCRETE ) );
640  }
641  else
642  {
643  mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( QgsColorRampShader::EXACT ) );
644  }
645  }
646  else
647  {
648  importError = true;
649  badLines = badLines + QString::number( lineCounter ) + ":\t[" + inputLine + "]\n";
650  }
651  }
652  else
653  {
654  inputStringComponents = inputLine.split( ',' );
655  if ( inputStringComponents.size() == 6 )
656  {
657  QgsColorRampShader::ColorRampItem currentItem( inputStringComponents[0].toDouble(),
658  QColor::fromRgb( inputStringComponents[1].toInt(), inputStringComponents[2].toInt(),
659  inputStringComponents[3].toInt(), inputStringComponents[4].toInt() ),
660  inputStringComponents[5] );
661  colorRampItems.push_back( currentItem );
662  }
663  else
664  {
665  importError = true;
666  badLines = badLines + QString::number( lineCounter ) + ":\t[" + inputLine + "]\n";
667  }
668  }
669  }
670  }
671  lineCounter++;
672  }
673  populateColormapTreeWidget( colorRampItems );
674 
675  QFileInfo fileInfo( fileName );
676  settings.setValue( "lastColorMapDir", fileInfo.absoluteDir().absolutePath() );
677 
678  if ( importError )
679  {
680  QMessageBox::warning( this, tr( "Import Error" ), tr( "The following lines contained errors\n\n" ) + badLines );
681  }
682  }
683  else if ( !fileName.isEmpty() )
684  {
685  QMessageBox::warning( this, tr( "Read access denied" ), tr( "Read access denied. Adjust the file permissions and try again.\n\n" ) );
686  }
687  emit widgetChanged();
688 }
689 
690 void QgsSingleBandPseudoColorRendererWidget::on_mExportToFileButton_clicked()
691 {
692  QSettings settings;
693  QString lastDir = settings.value( "lastColorMapDir", QDir::homePath() ).toString();
694  QString fileName = QFileDialog::getSaveFileName( this, tr( "Save file" ), lastDir, tr( "Textfile (*.txt)" ) );
695  if ( !fileName.isEmpty() )
696  {
697  if ( !fileName.endsWith( ".txt", Qt::CaseInsensitive ) )
698  {
699  fileName = fileName + ".txt";
700  }
701 
702  QFile outputFile( fileName );
703  if ( outputFile.open( QFile::WriteOnly ) )
704  {
705  QTextStream outputStream( &outputFile );
706  outputStream << "# " << tr( "QGIS Generated Color Map Export File" ) << '\n';
707  outputStream << "INTERPOLATION:";
708  QgsColorRampShader::ColorRamp_TYPE interpolation = static_cast< QgsColorRampShader::ColorRamp_TYPE >( mColorInterpolationComboBox->itemData( mColorInterpolationComboBox->currentIndex() ).toInt() );
709  switch ( interpolation )
710  {
712  outputStream << "INTERPOLATED\n";
713  break;
715  outputStream << "DISCRETE\n";
716  break;
718  outputStream << "EXACT\n";
719  break;
720  }
721 
722  int topLevelItemCount = mColormapTreeWidget->topLevelItemCount();
723  QTreeWidgetItem* currentItem;
724  QColor color;
725  for ( int i = 0; i < topLevelItemCount; ++i )
726  {
727  currentItem = mColormapTreeWidget->topLevelItem( i );
728  if ( !currentItem )
729  {
730  continue;
731  }
732  color = currentItem->background( ColorColumn ).color();
733  outputStream << currentItem->text( ValueColumn ).toDouble() << ',';
734  outputStream << color.red() << ',' << color.green() << ',' << color.blue() << ',' << color.alpha() << ',';
735  if ( currentItem->text( LabelColumn ).isEmpty() )
736  {
737  outputStream << "Color entry " << i + 1 << '\n';
738  }
739  else
740  {
741  outputStream << currentItem->text( LabelColumn ) << '\n';
742  }
743  }
744  outputStream.flush();
745  outputFile.close();
746 
747  QFileInfo fileInfo( fileName );
748  settings.setValue( "lastColorMapDir", fileInfo.absoluteDir().absolutePath() );
749  }
750  else
751  {
752  QMessageBox::warning( this, tr( "Write access denied" ), tr( "Write access denied. Adjust the file permissions and try again.\n\n" ) );
753  }
754  }
755 }
756 
757 void QgsSingleBandPseudoColorRendererWidget::on_mColormapTreeWidget_itemDoubleClicked( QTreeWidgetItem* item, int column )
758 {
759  if ( !item )
760  {
761  return;
762  }
763 
764  if ( column == ColorColumn )
765  {
766  item->setFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable );
767  QColor newColor = QgsColorDialogV2::getColor( item->background( column ).color(), this, "Change color", true );
768  if ( newColor.isValid() )
769  {
770  item->setBackground( ColorColumn, QBrush( newColor ) );
771  emit widgetChanged();
772  }
773  }
774  else
775  {
776  if ( column == LabelColumn )
777  {
778  // Set text color to default black, which signifies a manually edited label
779  item->setForeground( LabelColumn, QBrush() );
780  }
781  item->setFlags( Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable );
782  }
783 }
784 
786 void QgsSingleBandPseudoColorRendererWidget::mColormapTreeWidget_itemEdited( QTreeWidgetItem* item, int column )
787 {
788  Q_UNUSED( item );
789 
790  if ( column == ValueColumn )
791  {
792  mColormapTreeWidget->sortItems( ValueColumn, Qt::AscendingOrder );
793  autoLabel();
794  }
795  else if ( column == LabelColumn )
796  {
797  // call autoLabel to fill when empty or gray out when same as autoLabel
798  autoLabel();
799  }
800 }
801 
803 {
804  const QgsSingleBandPseudoColorRenderer* pr = dynamic_cast<const QgsSingleBandPseudoColorRenderer*>( r );
805  if ( pr )
806  {
807  mBandComboBox->setCurrentIndex( mBandComboBox->findData( pr->band() ) );
808 
809  const QgsRasterShader* rasterShader = pr->shader();
810  if ( rasterShader )
811  {
812  const QgsColorRampShader* colorRampShader = dynamic_cast<const QgsColorRampShader*>( rasterShader->rasterShaderFunction() );
813  if ( colorRampShader )
814  {
815  mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( colorRampShader->colorRampType() ) );
816 
817  const QList<QgsColorRampShader::ColorRampItem> colorRampItemList = colorRampShader->colorRampItemList();
819  for ( ; it != colorRampItemList.end(); ++it )
820  {
821  QgsTreeWidgetItem* newItem = new QgsTreeWidgetItem( mColormapTreeWidget );
822  newItem->setText( ValueColumn, QString::number( it->value, 'g', 15 ) );
823  newItem->setBackground( ColorColumn, QBrush( it->color ) );
824  newItem->setText( LabelColumn, it->label );
825  newItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable );
826  connect( newItem, SIGNAL( itemEdited( QTreeWidgetItem*, int ) ),
827  this, SLOT( mColormapTreeWidget_itemEdited( QTreeWidgetItem*, int ) ) );
828  }
829  setUnitFromLabels();
830  mClipCheckBox->setChecked( colorRampShader->clip() );
831  }
832  }
833  setLineEditValue( mMinLineEdit, pr->classificationMin() );
834  setLineEditValue( mMaxLineEdit, pr->classificationMax() );
835  mMinMaxOrigin = pr->classificationMinMaxOrigin();
836  showMinMaxOrigin();
837  }
838 }
839 
840 void QgsSingleBandPseudoColorRendererWidget::on_mBandComboBox_currentIndexChanged( int index )
841 {
842  QList<int> bands;
843  bands.append( mBandComboBox->itemData( index ).toInt() );
844  mMinMaxWidget->setBands( bands );
845 }
846 
847 void QgsSingleBandPseudoColorRendererWidget::on_mColorInterpolationComboBox_currentIndexChanged( int index )
848 {
849  QgsColorRampShader::ColorRamp_TYPE interpolation = static_cast< QgsColorRampShader::ColorRamp_TYPE >( mColorInterpolationComboBox->itemData( index ).toInt() );
850 
851  mClipCheckBox->setEnabled( interpolation == QgsColorRampShader::INTERPOLATED );
852 
853  QString valueLabel;
854  QString valueToolTip;
855  switch ( interpolation )
856  {
858  valueLabel = tr( "Value" );
859  valueToolTip = tr( "Value for color stop" );
860  break;
862  valueLabel = tr( "Value <=" );
863  valueToolTip = tr( "Maximum value for class" );
864  break;
866  valueLabel = tr( "Value =" );
867  valueToolTip = tr( "Value for color" );
868  break;
869  }
870 
871  QTreeWidgetItem* header = mColormapTreeWidget->headerItem();
872  header->setText( ValueColumn, valueLabel );
873  header->setToolTip( ValueColumn, valueToolTip );
874 
875  autoLabel();
876 }
877 
878 void QgsSingleBandPseudoColorRendererWidget::loadMinMax( int theBandNo, double theMin, double theMax, int theOrigin )
879 {
880  Q_UNUSED( theBandNo );
881  QgsDebugMsg( QString( "theBandNo = %1 theMin = %2 theMax = %3" ).arg( theBandNo ).arg( theMin ).arg( theMax ) );
882 
883  if ( qIsNaN( theMin ) )
884  {
885  mMinLineEdit->clear();
886  }
887  else
888  {
889  mMinLineEdit->setText( QString::number( theMin ) );
890  }
891 
892  if ( qIsNaN( theMax ) )
893  {
894  mMaxLineEdit->clear();
895  }
896  else
897  {
898  mMaxLineEdit->setText( QString::number( theMax ) );
899  }
900 
901  mMinMaxOrigin = theOrigin;
902  showMinMaxOrigin();
903 }
904 
905 void QgsSingleBandPseudoColorRendererWidget::showMinMaxOrigin()
906 {
907  mMinMaxOriginLabel->setText( QgsRasterRenderer::minMaxOriginLabel( mMinMaxOrigin ) );
908 }
909 
910 void QgsSingleBandPseudoColorRendererWidget::setLineEditValue( QLineEdit * theLineEdit, double theValue )
911 {
912  QString s;
913  if ( !qIsNaN( theValue ) )
914  {
915  s = QString::number( theValue );
916  }
917  theLineEdit->setText( s );
918 }
919 
920 double QgsSingleBandPseudoColorRendererWidget::lineEditValue( const QLineEdit * theLineEdit ) const
921 {
922  if ( theLineEdit->text().isEmpty() )
923  {
924  return std::numeric_limits<double>::quiet_NaN();
925  }
926 
927  return theLineEdit->text().toDouble();
928 }
929 
930 void QgsSingleBandPseudoColorRendererWidget::resetClassifyButton()
931 {
932  mClassifyButton->setEnabled( true );
933  double min = lineEditValue( mMinLineEdit );
934  double max = lineEditValue( mMaxLineEdit );
935  if ( qIsNaN( min ) || qIsNaN( max ) || min >= max )
936  {
937  mClassifyButton->setEnabled( false );
938  }
939 }
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.