QGIS API Documentation  2.18.21-Las Palmas (9fba24a)
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  if ( qIsNaN( min ) || qIsNaN( max ) )
367  {
368  return;
369  }
370 
371  QScopedPointer< QgsVectorColorRampV2 > colorRamp( mColorRampComboBox->currentColorRamp() );
372 
373  if ( mClassificationModeComboBox->itemData( mClassificationModeComboBox->currentIndex() ).toInt() == Continuous )
374  {
375  if ( colorRamp.data() )
376  {
377  numberOfEntries = colorRamp->count();
378  entryValues.reserve( numberOfEntries );
379  if ( discrete )
380  {
381  double intervalDiff = max - min;
382 
383  // remove last class when ColorRamp is gradient and discrete, as they are implemented with an extra stop
384  QgsVectorGradientColorRampV2* colorGradientRamp = dynamic_cast<QgsVectorGradientColorRampV2*>( colorRamp.data() );
385  if ( colorGradientRamp != NULL && colorGradientRamp->isDiscrete() )
386  {
387  numberOfEntries--;
388  }
389  else
390  {
391  // if color ramp is continuous scale values to get equally distributed classes.
392  // Doesn't work perfectly when stops are non equally distributed.
393  intervalDiff *= ( numberOfEntries - 1 ) / ( double )numberOfEntries;
394  }
395 
396  // skip first value (always 0.0)
397  for ( int i = 1; i < numberOfEntries; ++i )
398  {
399  double value = colorRamp->value( i );
400  entryValues.push_back( min + value * intervalDiff );
401  }
402  entryValues.push_back( std::numeric_limits<double>::infinity() );
403  }
404  else
405  {
406  for ( int i = 0; i < numberOfEntries; ++i )
407  {
408  if ( mInvertCheckBox->isChecked() )
409  {
410  double value = 1.0 - colorRamp->value( numberOfEntries - i - 1 );
411  entryValues.push_back( min + value * ( max - min ) );
412  }
413  else
414  {
415  double value = colorRamp->value( i );
416  entryValues.push_back( min + value * ( max - min ) );
417  }
418  }
419  }
420  // for continuous mode take original color map colors
421  for ( int i = 0; i < numberOfEntries; ++i )
422  {
423  int idx = mInvertCheckBox->isChecked() ? numberOfEntries - i - 1 : i;
424  entryColors.push_back( colorRamp->color( colorRamp->value( idx ) ) );
425  }
426  }
427  }
428  else // for other classification modes interpolate colors linearly
429  {
430  numberOfEntries = mNumberOfEntriesSpinBox->value();
431  if ( numberOfEntries < 2 )
432  return; // < 2 classes is not useful, shouldn't happen, but if it happens save it from crashing
433 
434  if ( mClassificationModeComboBox->itemData( mClassificationModeComboBox->currentIndex() ).toInt() == Quantile )
435  { // Quantile
436  int bandNr = mBandComboBox->itemData( bandComboIndex ).toInt();
437  //QgsRasterHistogram rasterHistogram = mRasterLayer->dataProvider()->histogram( bandNr );
438 
439  double cut1 = std::numeric_limits<double>::quiet_NaN();
440  double cut2 = std::numeric_limits<double>::quiet_NaN();
441 
442  QgsRectangle extent = mMinMaxWidget->extent();
443  int sampleSize = mMinMaxWidget->sampleSize();
444 
445  // set min and max from histogram, used later to calculate number of decimals to display
446  mRasterLayer->dataProvider()->cumulativeCut( bandNr, 0.0, 1.0, min, max, extent, sampleSize );
447 
448  entryValues.reserve( numberOfEntries );
449  if ( discrete )
450  {
451  double intervalDiff = 1.0 / ( numberOfEntries );
452  for ( int i = 1; i < numberOfEntries; ++i )
453  {
454  mRasterLayer->dataProvider()->cumulativeCut( bandNr, 0.0, i * intervalDiff, cut1, cut2, extent, sampleSize );
455  entryValues.push_back( cut2 );
456  }
457  entryValues.push_back( std::numeric_limits<double>::infinity() );
458  }
459  else
460  {
461  double intervalDiff = 1.0 / ( numberOfEntries - 1 );
462  for ( int i = 0; i < numberOfEntries; ++i )
463  {
464  mRasterLayer->dataProvider()->cumulativeCut( bandNr, 0.0, i * intervalDiff, cut1, cut2, extent, sampleSize );
465  entryValues.push_back( cut2 );
466  }
467  }
468  }
469  else // EqualInterval
470  {
471  entryValues.reserve( numberOfEntries );
472  if ( discrete )
473  {
474  // in discrete mode the lowest value is not an entry and the highest
475  // value is inf, there are ( numberOfEntries ) of which the first
476  // and last are not used.
477  double intervalDiff = ( max - min ) / ( numberOfEntries );
478 
479  for ( int i = 1; i < numberOfEntries; ++i )
480  {
481  entryValues.push_back( min + i * intervalDiff );
482  }
483  entryValues.push_back( std::numeric_limits<double>::infinity() );
484  }
485  else
486  {
487  //because the highest value is also an entry, there are (numberOfEntries - 1) intervals
488  double intervalDiff = ( max - min ) / ( numberOfEntries - 1 );
489 
490  for ( int i = 0; i < numberOfEntries; ++i )
491  {
492  entryValues.push_back( min + i * intervalDiff );
493  }
494  }
495  }
496 
497  if ( !colorRamp.data() )
498  {
499  //hard code color range from blue -> red (previous default)
500  int colorDiff = 0;
501  if ( numberOfEntries != 0 )
502  {
503  colorDiff = ( int )( 255 / numberOfEntries );
504  }
505 
506  entryColors.reserve( numberOfEntries );
507  for ( int i = 0; i < numberOfEntries; ++i )
508  {
509  QColor currentColor;
510  int idx = mInvertCheckBox->isChecked() ? numberOfEntries - i - 1 : i;
511  currentColor.setRgb( colorDiff*idx, 0, 255 - colorDiff * idx );
512  entryColors.push_back( currentColor );
513  }
514  }
515  else
516  {
517  entryColors.reserve( numberOfEntries );
518  for ( int i = 0; i < numberOfEntries; ++i )
519  {
520  int idx = mInvertCheckBox->isChecked() ? numberOfEntries - i - 1 : i;
521  entryColors.push_back( colorRamp->color((( double ) idx ) / ( numberOfEntries - 1 ) ) );
522  }
523  }
524  }
525 
526  mColormapTreeWidget->clear();
527 
528  QList<double>::const_iterator value_it = entryValues.begin();
529  QVector<QColor>::const_iterator color_it = entryColors.begin();
530 
531  // calculate a reasonable number of decimals to display
532  double maxabs = log10( qMax( qAbs( max ), qAbs( min ) ) );
533  int nDecimals = qRound( qMax( 3.0 + maxabs - log10( max - min ), maxabs <= 15.0 ? maxabs + 0.49 : 0.0 ) );
534 
535  for ( ; value_it != entryValues.end(); ++value_it, ++color_it )
536  {
537  QgsTreeWidgetItem* newItem = new QgsTreeWidgetItem( mColormapTreeWidget );
538  newItem->setText( ValueColumn, QString::number( *value_it, 'g', nDecimals ) );
539  newItem->setBackground( ColorColumn, QBrush( *color_it ) );
540  newItem->setText( LabelColumn, QString() );
541  newItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable );
542  connect( newItem, SIGNAL( itemEdited( QTreeWidgetItem*, int ) ),
543  this, SLOT( mColormapTreeWidget_itemEdited( QTreeWidgetItem*, int ) ) );
544  }
545  autoLabel();
546  emit widgetChanged();
547 }
548 
549 void QgsSingleBandPseudoColorRendererWidget::on_mClassificationModeComboBox_currentIndexChanged( int index )
550 {
551  Mode mode = static_cast< Mode >( mClassificationModeComboBox->itemData( index ).toInt() );
552  mNumberOfEntriesSpinBox->setEnabled( mode != Continuous );
553  mMinLineEdit->setEnabled( mode != Quantile );
554  mMaxLineEdit->setEnabled( mode != Quantile );
555 }
556 
557 void QgsSingleBandPseudoColorRendererWidget::on_mColorRampComboBox_currentIndexChanged( int index )
558 {
559  Q_UNUSED( index );
560  QSettings settings;
561  settings.setValue( "/Raster/defaultPalette", mColorRampComboBox->currentText() );
562 
563  QgsVectorColorRampV2* ramp = mColorRampComboBox->currentColorRamp();
564  if ( !ramp )
565  return;
566 
567  bool enableContinuous = ( ramp->count() > 0 );
568  mClassificationModeComboBox->setEnabled( enableContinuous );
569  if ( !enableContinuous )
570  {
571  mClassificationModeComboBox->setCurrentIndex( mClassificationModeComboBox->findData( EqualInterval ) );
572  }
573 }
574 
575 void QgsSingleBandPseudoColorRendererWidget::populateColormapTreeWidget( const QList<QgsColorRampShader::ColorRampItem>& colorRampItems )
576 {
577  mColormapTreeWidget->clear();
579  for ( ; it != colorRampItems.constEnd(); ++it )
580  {
581  QgsTreeWidgetItem* newItem = new QgsTreeWidgetItem( mColormapTreeWidget );
582  newItem->setText( ValueColumn, QString::number( it->value, 'g', 15 ) );
583  newItem->setBackground( ColorColumn, QBrush( it->color ) );
584  newItem->setText( LabelColumn, it->label );
585  newItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable );
586  connect( newItem, SIGNAL( itemEdited( QTreeWidgetItem*, int ) ),
587  this, SLOT( mColormapTreeWidget_itemEdited( QTreeWidgetItem*, int ) ) );
588  }
589  setUnitFromLabels();
590 }
591 
592 void QgsSingleBandPseudoColorRendererWidget::on_mLoadFromBandButton_clicked()
593 {
594  if ( !mRasterLayer || !mRasterLayer->dataProvider() )
595  {
596  return;
597  }
598 
599  int bandIndex = mBandComboBox->itemData( mBandComboBox->currentIndex() ).toInt();
600 
601 
603  if ( !colorRampList.isEmpty() )
604  {
605  populateColormapTreeWidget( colorRampList );
606  mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( QgsColorRampShader::INTERPOLATED ) );
607  }
608  else
609  {
610  QMessageBox::warning( this, tr( "Load Color Map" ), tr( "The color map for band %1 has no entries" ).arg( bandIndex ) );
611  }
612  emit widgetChanged();
613 }
614 
615 void QgsSingleBandPseudoColorRendererWidget::on_mLoadFromFileButton_clicked()
616 {
617  int lineCounter = 0;
618  bool importError = false;
619  QString badLines;
620  QSettings settings;
621  QString lastDir = settings.value( "lastColorMapDir", QDir::homePath() ).toString();
622  QString fileName = QFileDialog::getOpenFileName( this, tr( "Open file" ), lastDir, tr( "Textfile (*.txt)" ) );
623  QFile inputFile( fileName );
624  if ( inputFile.open( QFile::ReadOnly ) )
625  {
626  //clear the current tree
627  mColormapTreeWidget->clear();
628 
629  QTextStream inputStream( &inputFile );
630  QString inputLine;
631  QStringList inputStringComponents;
633 
634  //read through the input looking for valid data
635  while ( !inputStream.atEnd() )
636  {
637  lineCounter++;
638  inputLine = inputStream.readLine();
639  if ( !inputLine.isEmpty() )
640  {
641  if ( !inputLine.simplified().startsWith( '#' ) )
642  {
643  if ( inputLine.contains( "INTERPOLATION", Qt::CaseInsensitive ) )
644  {
645  inputStringComponents = inputLine.split( ':' );
646  if ( inputStringComponents.size() == 2 )
647  {
648  if ( inputStringComponents[1].trimmed().toUpper().compare( "INTERPOLATED", Qt::CaseInsensitive ) == 0 )
649  {
650  mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( QgsColorRampShader::INTERPOLATED ) );
651  }
652  else if ( inputStringComponents[1].trimmed().toUpper().compare( "DISCRETE", Qt::CaseInsensitive ) == 0 )
653  {
654  mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( QgsColorRampShader::DISCRETE ) );
655  }
656  else
657  {
658  mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( QgsColorRampShader::EXACT ) );
659  }
660  }
661  else
662  {
663  importError = true;
664  badLines = badLines + QString::number( lineCounter ) + ":\t[" + inputLine + "]\n";
665  }
666  }
667  else
668  {
669  inputStringComponents = inputLine.split( ',' );
670  if ( inputStringComponents.size() == 6 )
671  {
672  QgsColorRampShader::ColorRampItem currentItem( inputStringComponents[0].toDouble(),
673  QColor::fromRgb( inputStringComponents[1].toInt(), inputStringComponents[2].toInt(),
674  inputStringComponents[3].toInt(), inputStringComponents[4].toInt() ),
675  inputStringComponents[5] );
676  colorRampItems.push_back( currentItem );
677  }
678  else
679  {
680  importError = true;
681  badLines = badLines + QString::number( lineCounter ) + ":\t[" + inputLine + "]\n";
682  }
683  }
684  }
685  }
686  lineCounter++;
687  }
688  populateColormapTreeWidget( colorRampItems );
689 
690  QFileInfo fileInfo( fileName );
691  settings.setValue( "lastColorMapDir", fileInfo.absoluteDir().absolutePath() );
692 
693  if ( importError )
694  {
695  QMessageBox::warning( this, tr( "Import Error" ), tr( "The following lines contained errors\n\n" ) + badLines );
696  }
697  }
698  else if ( !fileName.isEmpty() )
699  {
700  QMessageBox::warning( this, tr( "Read access denied" ), tr( "Read access denied. Adjust the file permissions and try again.\n\n" ) );
701  }
702  emit widgetChanged();
703 }
704 
705 void QgsSingleBandPseudoColorRendererWidget::on_mExportToFileButton_clicked()
706 {
707  QSettings settings;
708  QString lastDir = settings.value( "lastColorMapDir", QDir::homePath() ).toString();
709  QString fileName = QFileDialog::getSaveFileName( this, tr( "Save file" ), lastDir, tr( "Textfile (*.txt)" ) );
710  if ( !fileName.isEmpty() )
711  {
712  if ( !fileName.endsWith( ".txt", Qt::CaseInsensitive ) )
713  {
714  fileName = fileName + ".txt";
715  }
716 
717  QFile outputFile( fileName );
718  if ( outputFile.open( QFile::WriteOnly ) )
719  {
720  QTextStream outputStream( &outputFile );
721  outputStream << "# " << tr( "QGIS Generated Color Map Export File" ) << '\n';
722  outputStream << "INTERPOLATION:";
723  QgsColorRampShader::ColorRamp_TYPE interpolation = static_cast< QgsColorRampShader::ColorRamp_TYPE >( mColorInterpolationComboBox->itemData( mColorInterpolationComboBox->currentIndex() ).toInt() );
724  switch ( interpolation )
725  {
727  outputStream << "INTERPOLATED\n";
728  break;
730  outputStream << "DISCRETE\n";
731  break;
733  outputStream << "EXACT\n";
734  break;
735  }
736 
737  int topLevelItemCount = mColormapTreeWidget->topLevelItemCount();
738  QTreeWidgetItem* currentItem;
739  QColor color;
740  for ( int i = 0; i < topLevelItemCount; ++i )
741  {
742  currentItem = mColormapTreeWidget->topLevelItem( i );
743  if ( !currentItem )
744  {
745  continue;
746  }
747  color = currentItem->background( ColorColumn ).color();
748  outputStream << currentItem->text( ValueColumn ).toDouble() << ',';
749  outputStream << color.red() << ',' << color.green() << ',' << color.blue() << ',' << color.alpha() << ',';
750  if ( currentItem->text( LabelColumn ).isEmpty() )
751  {
752  outputStream << "Color entry " << i + 1 << '\n';
753  }
754  else
755  {
756  outputStream << currentItem->text( LabelColumn ) << '\n';
757  }
758  }
759  outputStream.flush();
760  outputFile.close();
761 
762  QFileInfo fileInfo( fileName );
763  settings.setValue( "lastColorMapDir", fileInfo.absoluteDir().absolutePath() );
764  }
765  else
766  {
767  QMessageBox::warning( this, tr( "Write access denied" ), tr( "Write access denied. Adjust the file permissions and try again.\n\n" ) );
768  }
769  }
770 }
771 
772 void QgsSingleBandPseudoColorRendererWidget::on_mColormapTreeWidget_itemDoubleClicked( QTreeWidgetItem* item, int column )
773 {
774  if ( !item )
775  {
776  return;
777  }
778 
779  if ( column == ColorColumn )
780  {
781  item->setFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable );
782  QColor newColor = QgsColorDialogV2::getColor( item->background( column ).color(), this, "Change color", true );
783  if ( newColor.isValid() )
784  {
785  item->setBackground( ColorColumn, QBrush( newColor ) );
786  emit widgetChanged();
787  }
788  }
789  else
790  {
791  if ( column == LabelColumn )
792  {
793  // Set text color to default black, which signifies a manually edited label
794  item->setForeground( LabelColumn, QBrush() );
795  }
796  item->setFlags( Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable );
797  }
798 }
799 
801 void QgsSingleBandPseudoColorRendererWidget::mColormapTreeWidget_itemEdited( QTreeWidgetItem* item, int column )
802 {
803  Q_UNUSED( item );
804 
805  if ( column == ValueColumn )
806  {
807  mColormapTreeWidget->sortItems( ValueColumn, Qt::AscendingOrder );
808  autoLabel();
809  emit widgetChanged();
810  }
811  else if ( column == LabelColumn )
812  {
813  // call autoLabel to fill when empty or gray out when same as autoLabel
814  autoLabel();
815  }
816 }
817 
819 {
820  const QgsSingleBandPseudoColorRenderer* pr = dynamic_cast<const QgsSingleBandPseudoColorRenderer*>( r );
821  if ( pr )
822  {
823  mBandComboBox->setCurrentIndex( mBandComboBox->findData( pr->band() ) );
824 
825  const QgsRasterShader* rasterShader = pr->shader();
826  if ( rasterShader )
827  {
828  const QgsColorRampShader* colorRampShader = dynamic_cast<const QgsColorRampShader*>( rasterShader->rasterShaderFunction() );
829  if ( colorRampShader )
830  {
831  mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( colorRampShader->colorRampType() ) );
832 
833  const QList<QgsColorRampShader::ColorRampItem> colorRampItemList = colorRampShader->colorRampItemList();
835  for ( ; it != colorRampItemList.end(); ++it )
836  {
837  QgsTreeWidgetItem* newItem = new QgsTreeWidgetItem( mColormapTreeWidget );
838  newItem->setText( ValueColumn, QString::number( it->value, 'g', 15 ) );
839  newItem->setBackground( ColorColumn, QBrush( it->color ) );
840  newItem->setText( LabelColumn, it->label );
841  newItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable );
842  connect( newItem, SIGNAL( itemEdited( QTreeWidgetItem*, int ) ),
843  this, SLOT( mColormapTreeWidget_itemEdited( QTreeWidgetItem*, int ) ) );
844  }
845  setUnitFromLabels();
846  mClipCheckBox->setChecked( colorRampShader->clip() );
847  }
848  }
849  setLineEditValue( mMinLineEdit, pr->classificationMin() );
850  setLineEditValue( mMaxLineEdit, pr->classificationMax() );
851  mMinMaxOrigin = pr->classificationMinMaxOrigin();
852  showMinMaxOrigin();
853  }
854 }
855 
856 void QgsSingleBandPseudoColorRendererWidget::on_mBandComboBox_currentIndexChanged( int index )
857 {
858  QList<int> bands;
859  bands.append( mBandComboBox->itemData( index ).toInt() );
860  mMinMaxWidget->setBands( bands );
861 }
862 
863 void QgsSingleBandPseudoColorRendererWidget::on_mColorInterpolationComboBox_currentIndexChanged( int index )
864 {
865  QgsColorRampShader::ColorRamp_TYPE interpolation = static_cast< QgsColorRampShader::ColorRamp_TYPE >( mColorInterpolationComboBox->itemData( index ).toInt() );
866 
867  mClipCheckBox->setEnabled( interpolation == QgsColorRampShader::INTERPOLATED );
868 
869  QString valueLabel;
870  QString valueToolTip;
871  switch ( interpolation )
872  {
874  valueLabel = tr( "Value" );
875  valueToolTip = tr( "Value for color stop" );
876  break;
878  valueLabel = tr( "Value <=" );
879  valueToolTip = tr( "Maximum value for class" );
880  break;
882  valueLabel = tr( "Value =" );
883  valueToolTip = tr( "Value for color" );
884  break;
885  }
886 
887  QTreeWidgetItem* header = mColormapTreeWidget->headerItem();
888  header->setText( ValueColumn, valueLabel );
889  header->setToolTip( ValueColumn, valueToolTip );
890 
891  autoLabel();
892  emit widgetChanged();
893 }
894 
895 void QgsSingleBandPseudoColorRendererWidget::loadMinMax( int theBandNo, double theMin, double theMax, int theOrigin )
896 {
897  Q_UNUSED( theBandNo );
898  QgsDebugMsg( QString( "theBandNo = %1 theMin = %2 theMax = %3" ).arg( theBandNo ).arg( theMin ).arg( theMax ) );
899 
900  if ( qIsNaN( theMin ) )
901  {
902  mMinLineEdit->clear();
903  }
904  else
905  {
906  mMinLineEdit->setText( QString::number( theMin ) );
907  }
908 
909  if ( qIsNaN( theMax ) )
910  {
911  mMaxLineEdit->clear();
912  }
913  else
914  {
915  mMaxLineEdit->setText( QString::number( theMax ) );
916  }
917 
918  mMinMaxOrigin = theOrigin;
919  showMinMaxOrigin();
920 }
921 
922 void QgsSingleBandPseudoColorRendererWidget::showMinMaxOrigin()
923 {
924  mMinMaxOriginLabel->setText( QgsRasterRenderer::minMaxOriginLabel( mMinMaxOrigin ) );
925 }
926 
927 void QgsSingleBandPseudoColorRendererWidget::setLineEditValue( QLineEdit * theLineEdit, double theValue )
928 {
929  QString s;
930  if ( !qIsNaN( theValue ) )
931  {
932  s = QString::number( theValue );
933  }
934  theLineEdit->setText( s );
935 }
936 
937 double QgsSingleBandPseudoColorRendererWidget::lineEditValue( const QLineEdit * theLineEdit ) const
938 {
939  if ( theLineEdit->text().isEmpty() )
940  {
941  return std::numeric_limits<double>::quiet_NaN();
942  }
943 
944  return theLineEdit->text().toDouble();
945 }
946 
947 void QgsSingleBandPseudoColorRendererWidget::resetClassifyButton()
948 {
949  mClassifyButton->setEnabled( true );
950  double min = lineEditValue( mMinLineEdit );
951  double max = lineEditValue( mMaxLineEdit );
952  if ( qIsNaN( min ) || qIsNaN( max ) || min >= max )
953  {
954  mClassifyButton->setEnabled( false );
955  }
956 }
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)
QgsColorRampShader::ColorRamp_TYPE colorRampType() const
Get the color ramp type.
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.
virtual QList< QgsColorRampShader::ColorRampItem > colorTable(int bandNo) const
#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...
QList< QgsColorRampShader::ColorRampItem > colorRampItemList() const
Get the custom colormap.
bool isDiscrete() const
Returns true if the gradient is using discrete interpolation, rather than smoothly interpolating betw...
virtual void setData(int column, int role, const QVariant &value)
void setColorRampItemList(const QList< QgsColorRampShader::ColorRampItem > &theList)
Set custom colormap.
QgsRasterRenderer * renderer() const
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)
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)
virtual QString min(int index=0)
bool atEnd() const
void setExtent(const QgsRectangle &theExtent)
Sets the extent to use for minimum and maximum value calculation.
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
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
QString displayBandName(int band) const
Returns a band name for display.
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
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.
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)
bool clip() const
Returns whether the shader will clip values which are out of range.
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.
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
int band() const
Returns the band used by the renderer.
bool isValid() const
Base class for raster data providers.