QGIS API Documentation  3.2.0-Bonn (bc43194)
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 #include "qgsrasterdataprovider.h"
22 #include "qgsrastershader.h"
23 #include "qgsrasterminmaxwidget.h"
24 #include "qgstreewidgetitem.h"
25 #include "qgssettings.h"
26 
27 // for color ramps - todo add rasterStyle and refactor raster vs. vector ramps
28 #include "qgsstyle.h"
29 #include "qgscolorramp.h"
30 #include "qgscolorrampbutton.h"
31 #include "qgscolordialog.h"
32 
33 #include <QCursor>
34 #include <QPushButton>
35 #include <QInputDialog>
36 #include <QFileDialog>
37 #include <QMenu>
38 #include <QMessageBox>
39 #include <QTextStream>
40 #include <QTreeView>
41 
43  : QgsRasterRendererWidget( layer, extent )
44  , mMinMaxOrigin( 0 )
45 {
46  QgsSettings settings;
47 
48  setupUi( this );
49 
50  connect( mAddEntryButton, &QPushButton::clicked, this, &QgsSingleBandPseudoColorRendererWidget::mAddEntryButton_clicked );
51  connect( mDeleteEntryButton, &QPushButton::clicked, this, &QgsSingleBandPseudoColorRendererWidget::mDeleteEntryButton_clicked );
52  connect( mLoadFromBandButton, &QPushButton::clicked, this, &QgsSingleBandPseudoColorRendererWidget::mLoadFromBandButton_clicked );
53  connect( mLoadFromFileButton, &QPushButton::clicked, this, &QgsSingleBandPseudoColorRendererWidget::mLoadFromFileButton_clicked );
54  connect( mExportToFileButton, &QPushButton::clicked, this, &QgsSingleBandPseudoColorRendererWidget::mExportToFileButton_clicked );
55  connect( mUnitLineEdit, &QLineEdit::textEdited, this, &QgsSingleBandPseudoColorRendererWidget::mUnitLineEdit_textEdited );
56  connect( mColormapTreeWidget, &QTreeWidget::itemDoubleClicked, this, &QgsSingleBandPseudoColorRendererWidget::mColormapTreeWidget_itemDoubleClicked );
57  connect( mColorInterpolationComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsSingleBandPseudoColorRendererWidget::mColorInterpolationComboBox_currentIndexChanged );
58  connect( mMinLineEdit, &QLineEdit::textChanged, this, &QgsSingleBandPseudoColorRendererWidget::mMinLineEdit_textChanged );
59  connect( mMaxLineEdit, &QLineEdit::textChanged, this, &QgsSingleBandPseudoColorRendererWidget::mMaxLineEdit_textChanged );
60  connect( mMinLineEdit, &QLineEdit::textEdited, this, &QgsSingleBandPseudoColorRendererWidget::mMinLineEdit_textEdited );
61  connect( mMaxLineEdit, &QLineEdit::textEdited, this, &QgsSingleBandPseudoColorRendererWidget::mMaxLineEdit_textEdited );
62  connect( mClassificationModeComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsSingleBandPseudoColorRendererWidget::mClassificationModeComboBox_currentIndexChanged );
63 
64  contextMenu = new QMenu( tr( "Options" ), this );
65  contextMenu->addAction( tr( "Change Color…" ), this, SLOT( changeColor() ) );
66  contextMenu->addAction( tr( "Change Opacity…" ), this, SLOT( changeOpacity() ) );
67 
68  mColormapTreeWidget->setColumnWidth( ColorColumn, 50 );
69  mColormapTreeWidget->setContextMenuPolicy( Qt::CustomContextMenu );
70  mColormapTreeWidget->setSelectionMode( QAbstractItemView::ExtendedSelection );
71  connect( mColormapTreeWidget, &QTreeView::customContextMenuRequested, this, [ = ]( QPoint ) { contextMenu->exec( QCursor::pos() ); }
72  );
73 
74  QString defaultPalette = settings.value( QStringLiteral( "Raster/defaultPalette" ), "" ).toString();
75  btnColorRamp->setColorRampFromName( defaultPalette );
76 
77  if ( !mRasterLayer )
78  {
79  return;
80  }
81 
83  if ( !provider )
84  {
85  return;
86  }
87 
88  // Must be before adding items to mBandComboBox (signal)
89  mMinLineEdit->setValidator( new QDoubleValidator( mMinLineEdit ) );
90  mMaxLineEdit->setValidator( new QDoubleValidator( mMaxLineEdit ) );
91 
92  mMinMaxWidget = new QgsRasterMinMaxWidget( layer, this );
93  mMinMaxWidget->setExtent( extent );
94  mMinMaxWidget->setMapCanvas( mCanvas );
95 
96  QHBoxLayout *layout = new QHBoxLayout();
97  layout->setContentsMargins( 0, 0, 0, 0 );
98  mMinMaxContainerWidget->setLayout( layout );
99  layout->addWidget( mMinMaxWidget );
100 
101  mBandComboBox->setLayer( mRasterLayer );
102 
103  mColorInterpolationComboBox->addItem( tr( "Discrete" ), QgsColorRampShader::Discrete );
104  mColorInterpolationComboBox->addItem( tr( "Linear" ), QgsColorRampShader::Interpolated );
105  mColorInterpolationComboBox->addItem( tr( "Exact" ), QgsColorRampShader::Exact );
106  mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( QgsColorRampShader::Interpolated ) );
107 
108  mClassificationModeComboBox->addItem( tr( "Continuous" ), QgsColorRampShader::Continuous );
109  mClassificationModeComboBox->addItem( tr( "Equal Interval" ), QgsColorRampShader::EqualInterval );
110  mClassificationModeComboBox->addItem( tr( "Quantile" ), QgsColorRampShader::Quantile );
111  mClassificationModeComboBox->setCurrentIndex( mClassificationModeComboBox->findData( QgsColorRampShader::Continuous ) );
112 
113  mNumberOfEntriesSpinBox->setValue( 5 ); // some default
114 
115  setFromRenderer( layer->renderer() );
116 
119 
120  // If there is currently no min/max, load default with user current default options
121  if ( mMinLineEdit->text().isEmpty() || mMaxLineEdit->text().isEmpty() )
122  {
123  QgsRasterMinMaxOrigin minMaxOrigin = mMinMaxWidget->minMaxOrigin();
124  if ( minMaxOrigin.limits() == QgsRasterMinMaxOrigin::None )
125  {
127  mMinMaxWidget->setFromMinMaxOrigin( minMaxOrigin );
128  }
129  mMinMaxWidget->doComputations();
130  }
131 
132  mClassificationModeComboBox_currentIndexChanged( 0 );
133 
134  resetClassifyButton();
135 
136  connect( mClassificationModeComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsSingleBandPseudoColorRendererWidget::classify );
137  connect( mClassifyButton, &QPushButton::clicked, this, &QgsSingleBandPseudoColorRendererWidget::applyColorRamp );
138  connect( btnColorRamp, &QgsColorRampButton::colorRampChanged, this, &QgsSingleBandPseudoColorRendererWidget::applyColorRamp );
139  connect( mNumberOfEntriesSpinBox, static_cast < void ( QSpinBox::* )( int ) > ( &QSpinBox::valueChanged ), this, &QgsSingleBandPseudoColorRendererWidget::classify );
141  connect( mBandComboBox, &QgsRasterBandComboBox::bandChanged, this, &QgsSingleBandPseudoColorRendererWidget::bandChanged );
142  connect( mClipCheckBox, &QAbstractButton::toggled, this, &QgsRasterRendererWidget::widgetChanged );
143 }
144 
146 {
147  QgsRasterShader *rasterShader = new QgsRasterShader();
148  QgsColorRampShader *colorRampShader = new QgsColorRampShader( lineEditValue( mMinLineEdit ), lineEditValue( mMaxLineEdit ) );
149  colorRampShader->setColorRampType( static_cast< QgsColorRampShader::Type >( mColorInterpolationComboBox->currentData().toInt() ) );
150  colorRampShader->setClassificationMode( static_cast< QgsColorRampShader::ClassificationMode >( mClassificationModeComboBox->currentData().toInt() ) );
151  colorRampShader->setClip( mClipCheckBox->isChecked() );
152 
153  //iterate through mColormapTreeWidget and set colormap info of layer
154  QList<QgsColorRampShader::ColorRampItem> colorRampItems;
155  int topLevelItemCount = mColormapTreeWidget->topLevelItemCount();
156  QTreeWidgetItem *currentItem = nullptr;
157  for ( int i = 0; i < topLevelItemCount; ++i )
158  {
159  currentItem = mColormapTreeWidget->topLevelItem( i );
160  if ( !currentItem )
161  {
162  continue;
163  }
164  QgsColorRampShader::ColorRampItem newColorRampItem;
165  newColorRampItem.value = currentItem->text( ValueColumn ).toDouble();
166  newColorRampItem.color = currentItem->background( ColorColumn ).color();
167  newColorRampItem.label = currentItem->text( LabelColumn );
168  colorRampItems.append( newColorRampItem );
169  }
170  // sort the shader items
171  std::sort( colorRampItems.begin(), colorRampItems.end() );
172  colorRampShader->setColorRampItemList( colorRampItems );
173 
174  if ( !btnColorRamp->isNull() )
175  {
176  colorRampShader->setSourceColorRamp( btnColorRamp->colorRamp() );
177  }
178 
179  rasterShader->setRasterShaderFunction( colorRampShader );
180 
181  int bandNumber = mBandComboBox->currentBand();
183  renderer->setClassificationMin( lineEditValue( mMinLineEdit ) );
184  renderer->setClassificationMax( lineEditValue( mMaxLineEdit ) );
185  renderer->setMinMaxOrigin( mMinMaxWidget->minMaxOrigin() );
186  return renderer;
187 }
188 
190 {
191  mMinMaxWidget->doComputations();
192 }
193 
195 {
197  mMinMaxWidget->setMapCanvas( canvas );
198 }
199 
200 void QgsSingleBandPseudoColorRendererWidget::autoLabel()
201 {
202  QgsColorRampShader::Type interpolation = static_cast< QgsColorRampShader::Type >( mColorInterpolationComboBox->currentData().toInt() );
203  bool discrete = interpolation == QgsColorRampShader::Discrete;
204  QString unit = mUnitLineEdit->text();
205  QString label;
206  int topLevelItemCount = mColormapTreeWidget->topLevelItemCount();
207  QTreeWidgetItem *currentItem = nullptr;
208  for ( int i = 0; i < topLevelItemCount; ++i )
209  {
210  currentItem = mColormapTreeWidget->topLevelItem( i );
211  //If the item is null or does not have a pixel values set, skip
212  if ( !currentItem || currentItem->text( ValueColumn ).isEmpty() )
213  {
214  continue;
215  }
216 
217  if ( discrete )
218  {
219  if ( i == 0 )
220  {
221  label = "<= " + currentItem->text( ValueColumn ) + unit;
222  }
223  else if ( currentItem->text( ValueColumn ).toDouble() == std::numeric_limits<double>::infinity() )
224  {
225  label = "> " + mColormapTreeWidget->topLevelItem( i - 1 )->text( ValueColumn ) + unit;
226  }
227  else
228  {
229  label = mColormapTreeWidget->topLevelItem( i - 1 )->text( ValueColumn ) + " - " + currentItem->text( ValueColumn ) + unit;
230  }
231  }
232  else
233  {
234  label = currentItem->text( ValueColumn ) + unit;
235  }
236 
237  if ( currentItem->text( LabelColumn ).isEmpty() || currentItem->text( LabelColumn ) == label || currentItem->foreground( LabelColumn ).color() == QColor( Qt::gray ) )
238  {
239  currentItem->setText( LabelColumn, label );
240  currentItem->setForeground( LabelColumn, QBrush( QColor( Qt::gray ) ) );
241  }
242  }
243 }
244 
245 void QgsSingleBandPseudoColorRendererWidget::setUnitFromLabels()
246 {
247  QgsColorRampShader::Type interpolation = static_cast< QgsColorRampShader::Type >( mColorInterpolationComboBox->currentData().toInt() );
248  bool discrete = interpolation == QgsColorRampShader::Discrete;
249  QStringList allSuffixes;
250  QString label;
251  int topLevelItemCount = mColormapTreeWidget->topLevelItemCount();
252  QTreeWidgetItem *currentItem = nullptr;
253  for ( int i = 0; i < topLevelItemCount; ++i )
254  {
255  currentItem = mColormapTreeWidget->topLevelItem( i );
256  //If the item is null or does not have a pixel values set, skip
257  if ( !currentItem || currentItem->text( ValueColumn ).isEmpty() )
258  {
259  continue;
260  }
261 
262  if ( discrete )
263  {
264  if ( i == 0 )
265  {
266  label = "<= " + currentItem->text( ValueColumn );
267  }
268  else if ( currentItem->text( ValueColumn ).toDouble() == std::numeric_limits<double>::infinity() )
269  {
270  label = "> " + mColormapTreeWidget->topLevelItem( i - 1 )->text( ValueColumn );
271  }
272  else
273  {
274  label = mColormapTreeWidget->topLevelItem( i - 1 )->text( ValueColumn ) + " - " + currentItem->text( ValueColumn );
275  }
276  }
277  else
278  {
279  label = currentItem->text( ValueColumn );
280  }
281 
282  if ( currentItem->text( LabelColumn ).startsWith( label ) )
283  {
284  allSuffixes.append( currentItem->text( LabelColumn ).mid( label.length() ) );
285  }
286  }
287  // find most common suffix
288  QStringList suffixes = QStringList( allSuffixes );
289  suffixes.removeDuplicates();
290  int max = 0;
291  QString unit;
292  for ( int i = 0; i < suffixes.count(); ++i )
293  {
294  int n = allSuffixes.count( suffixes[i] );
295  if ( n > max )
296  {
297  max = n;
298  unit = suffixes[i];
299  }
300  }
301  // Set this suffix as unit if at least used twice
302  if ( max >= 2 )
303  {
304  mUnitLineEdit->setText( unit );
305  }
306  autoLabel();
307 }
308 
309 void QgsSingleBandPseudoColorRendererWidget::mAddEntryButton_clicked()
310 {
311  QgsTreeWidgetItemObject *newItem = new QgsTreeWidgetItemObject( mColormapTreeWidget );
312  newItem->setText( ValueColumn, QStringLiteral( "0" ) );
313  newItem->setBackground( ColorColumn, QBrush( QColor( Qt::magenta ) ) );
314  newItem->setText( LabelColumn, QString() );
315  newItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable );
316  connect( newItem, &QgsTreeWidgetItemObject::itemEdited,
317  this, &QgsSingleBandPseudoColorRendererWidget::mColormapTreeWidget_itemEdited );
318  mColormapTreeWidget->sortItems( ValueColumn, Qt::AscendingOrder );
319  autoLabel();
320 
322  emit widgetChanged();
323 }
324 
325 void QgsSingleBandPseudoColorRendererWidget::mDeleteEntryButton_clicked()
326 {
327  QList<QTreeWidgetItem *> itemList;
328  itemList = mColormapTreeWidget->selectedItems();
329  if ( itemList.isEmpty() )
330  {
331  return;
332  }
333 
334  Q_FOREACH ( QTreeWidgetItem *item, itemList )
335  {
336  delete item;
337  }
338 
340  emit widgetChanged();
341 }
342 
344 {
345  std::unique_ptr< QgsColorRamp > ramp( btnColorRamp->colorRamp() );
346  if ( !ramp || std::isnan( lineEditValue( mMinLineEdit ) ) || std::isnan( lineEditValue( mMaxLineEdit ) ) )
347  {
348  return;
349  }
350 
351  std::unique_ptr<QgsSingleBandPseudoColorRenderer> pr( new QgsSingleBandPseudoColorRenderer( mRasterLayer->dataProvider(), mBandComboBox->currentBand(), nullptr ) );
352  pr->setClassificationMin( lineEditValue( mMinLineEdit ) );
353  pr->setClassificationMax( lineEditValue( mMaxLineEdit ) );
354  pr->createShader( ramp.release(), static_cast< QgsColorRampShader::Type >( mColorInterpolationComboBox->currentData().toInt() ), static_cast< QgsColorRampShader::ClassificationMode >( mClassificationModeComboBox->currentData().toInt() ), mNumberOfEntriesSpinBox->value(), mClipCheckBox->isChecked(), minMaxWidget()->extent() );
355 
356  const QgsRasterShader *rasterShader = pr->shader();
357  if ( rasterShader )
358  {
359  const QgsColorRampShader *colorRampShader = dynamic_cast<const QgsColorRampShader *>( rasterShader->rasterShaderFunction() );
360  if ( colorRampShader )
361  {
362  mColormapTreeWidget->clear();
363 
364  const QList<QgsColorRampShader::ColorRampItem> colorRampItemList = colorRampShader->colorRampItemList();
365  for ( const QgsColorRampShader::ColorRampItem &item : colorRampItemList )
366  {
367  QgsTreeWidgetItemObject *newItem = new QgsTreeWidgetItemObject( mColormapTreeWidget );
368  newItem->setText( ValueColumn, QString::number( item.value, 'g', 15 ) );
369  newItem->setBackground( ColorColumn, QBrush( item.color ) );
370  newItem->setText( LabelColumn, item.label );
371  newItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable );
372  connect( newItem, &QgsTreeWidgetItemObject::itemEdited,
373  this, &QgsSingleBandPseudoColorRendererWidget::mColormapTreeWidget_itemEdited );
374  }
375  mClipCheckBox->setChecked( colorRampShader->clip() );
376  }
377  }
378 
379  autoLabel();
380  emit widgetChanged();
381 }
382 
383 void QgsSingleBandPseudoColorRendererWidget::mClassificationModeComboBox_currentIndexChanged( int index )
384 {
385  QgsColorRampShader::ClassificationMode mode = static_cast< QgsColorRampShader::ClassificationMode >( mClassificationModeComboBox->itemData( index ).toInt() );
386  mNumberOfEntriesSpinBox->setEnabled( mode != QgsColorRampShader::Continuous );
387  mMinLineEdit->setEnabled( mode != QgsColorRampShader::Quantile );
388  mMaxLineEdit->setEnabled( mode != QgsColorRampShader::Quantile );
389 }
390 
391 void QgsSingleBandPseudoColorRendererWidget::applyColorRamp()
392 {
393  std::unique_ptr< QgsColorRamp > ramp( btnColorRamp->colorRamp() );
394  if ( !ramp )
395  {
396  return;
397  }
398 
399  if ( !btnColorRamp->colorRampName().isEmpty() )
400  {
401  // Remember last used color ramp
402  QgsSettings settings;
403  settings.setValue( QStringLiteral( "Raster/defaultPalette" ), btnColorRamp->colorRampName() );
404  }
405 
406  bool enableContinuous = ( ramp->count() > 0 );
407  mClassificationModeComboBox->setEnabled( enableContinuous );
408  if ( !enableContinuous )
409  {
410  mClassificationModeComboBox->setCurrentIndex( mClassificationModeComboBox->findData( QgsColorRampShader::EqualInterval ) );
411  }
412 
413  classify();
414 }
415 
416 void QgsSingleBandPseudoColorRendererWidget::populateColormapTreeWidget( const QList<QgsColorRampShader::ColorRampItem> &colorRampItems )
417 {
418  mColormapTreeWidget->clear();
419  QList<QgsColorRampShader::ColorRampItem>::const_iterator it = colorRampItems.constBegin();
420  for ( ; it != colorRampItems.constEnd(); ++it )
421  {
422  QgsTreeWidgetItemObject *newItem = new QgsTreeWidgetItemObject( mColormapTreeWidget );
423  newItem->setText( ValueColumn, QString::number( it->value, 'g', 15 ) );
424  newItem->setBackground( ColorColumn, QBrush( it->color ) );
425  newItem->setText( LabelColumn, it->label );
426  newItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable );
427  connect( newItem, &QgsTreeWidgetItemObject::itemEdited,
428  this, &QgsSingleBandPseudoColorRendererWidget::mColormapTreeWidget_itemEdited );
429  }
430  setUnitFromLabels();
431 }
432 
433 void QgsSingleBandPseudoColorRendererWidget::mLoadFromBandButton_clicked()
434 {
435  if ( !mRasterLayer || !mRasterLayer->dataProvider() )
436  {
437  return;
438  }
439 
440  int bandIndex = mBandComboBox->currentBand();
441 
442 
443  QList<QgsColorRampShader::ColorRampItem> colorRampList = mRasterLayer->dataProvider()->colorTable( bandIndex );
444  if ( !colorRampList.isEmpty() )
445  {
446  populateColormapTreeWidget( colorRampList );
447  mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( QgsColorRampShader::Interpolated ) );
448  }
449  else
450  {
451  QMessageBox::warning( this, tr( "Load Color Map" ), tr( "The color map for band %1 has no entries." ).arg( bandIndex ) );
452  }
453 
455  emit widgetChanged();
456 }
457 
458 void QgsSingleBandPseudoColorRendererWidget::mLoadFromFileButton_clicked()
459 {
460  int lineCounter = 0;
461  bool importError = false;
462  QString badLines;
463  QgsSettings settings;
464  QString lastDir = settings.value( QStringLiteral( "lastColorMapDir" ), QDir::homePath() ).toString();
465  QString fileName = QFileDialog::getOpenFileName( this, tr( "Load Color Map from File" ), lastDir, tr( "Textfile (*.txt)" ) );
466  QFile inputFile( fileName );
467  if ( inputFile.open( QFile::ReadOnly ) )
468  {
469  //clear the current tree
470  mColormapTreeWidget->clear();
471 
472  QTextStream inputStream( &inputFile );
473  QString inputLine;
474  QStringList inputStringComponents;
475  QList<QgsColorRampShader::ColorRampItem> colorRampItems;
476 
477  //read through the input looking for valid data
478  while ( !inputStream.atEnd() )
479  {
480  lineCounter++;
481  inputLine = inputStream.readLine();
482  if ( !inputLine.isEmpty() )
483  {
484  if ( !inputLine.simplified().startsWith( '#' ) )
485  {
486  if ( inputLine.contains( QLatin1String( "INTERPOLATION" ), Qt::CaseInsensitive ) )
487  {
488  inputStringComponents = inputLine.split( ':' );
489  if ( inputStringComponents.size() == 2 )
490  {
491  if ( inputStringComponents[1].trimmed().toUpper().compare( QLatin1String( "INTERPOLATED" ), Qt::CaseInsensitive ) == 0 )
492  {
493  mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( QgsColorRampShader::Interpolated ) );
494  }
495  else if ( inputStringComponents[1].trimmed().toUpper().compare( QLatin1String( "DISCRETE" ), Qt::CaseInsensitive ) == 0 )
496  {
497  mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( QgsColorRampShader::Discrete ) );
498  }
499  else
500  {
501  mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( QgsColorRampShader::Exact ) );
502  }
503  }
504  else
505  {
506  importError = true;
507  badLines = badLines + QString::number( lineCounter ) + ":\t[" + inputLine + "]\n";
508  }
509  }
510  else
511  {
512  inputStringComponents = inputLine.split( ',' );
513  if ( inputStringComponents.size() == 6 )
514  {
515  QgsColorRampShader::ColorRampItem currentItem( inputStringComponents[0].toDouble(),
516  QColor::fromRgb( inputStringComponents[1].toInt(), inputStringComponents[2].toInt(),
517  inputStringComponents[3].toInt(), inputStringComponents[4].toInt() ),
518  inputStringComponents[5] );
519  colorRampItems.push_back( currentItem );
520  }
521  else
522  {
523  importError = true;
524  badLines = badLines + QString::number( lineCounter ) + ":\t[" + inputLine + "]\n";
525  }
526  }
527  }
528  }
529  lineCounter++;
530  }
531  populateColormapTreeWidget( colorRampItems );
532 
533  QFileInfo fileInfo( fileName );
534  settings.setValue( QStringLiteral( "lastColorMapDir" ), fileInfo.absoluteDir().absolutePath() );
535 
536  if ( importError )
537  {
538  QMessageBox::warning( this, tr( "Load Color Map from File" ), tr( "The following lines contained errors\n\n" ) + badLines );
539  }
540  }
541  else if ( !fileName.isEmpty() )
542  {
543  QMessageBox::warning( this, tr( "Load Color Map from File" ), tr( "Read access denied. Adjust the file permissions and try again.\n\n" ) );
544  }
545 
547  emit widgetChanged();
548 }
549 
550 void QgsSingleBandPseudoColorRendererWidget::mExportToFileButton_clicked()
551 {
552  QgsSettings settings;
553  QString lastDir = settings.value( QStringLiteral( "lastColorMapDir" ), QDir::homePath() ).toString();
554  QString fileName = QFileDialog::getSaveFileName( this, tr( "Save Color Map as File" ), lastDir, tr( "Textfile (*.txt)" ) );
555  if ( !fileName.isEmpty() )
556  {
557  if ( !fileName.endsWith( QLatin1String( ".txt" ), Qt::CaseInsensitive ) )
558  {
559  fileName = fileName + ".txt";
560  }
561 
562  QFile outputFile( fileName );
563  if ( outputFile.open( QFile::WriteOnly | QIODevice::Truncate ) )
564  {
565  QTextStream outputStream( &outputFile );
566  outputStream << "# " << tr( "QGIS Generated Color Map Export File" ) << '\n';
567  outputStream << "INTERPOLATION:";
568  QgsColorRampShader::Type interpolation = static_cast< QgsColorRampShader::Type >( mColorInterpolationComboBox->currentData().toInt() );
569  switch ( interpolation )
570  {
572  outputStream << "INTERPOLATED\n";
573  break;
575  outputStream << "DISCRETE\n";
576  break;
578  outputStream << "EXACT\n";
579  break;
580  }
581 
582  int topLevelItemCount = mColormapTreeWidget->topLevelItemCount();
583  QTreeWidgetItem *currentItem = nullptr;
584  QColor color;
585  for ( int i = 0; i < topLevelItemCount; ++i )
586  {
587  currentItem = mColormapTreeWidget->topLevelItem( i );
588  if ( !currentItem )
589  {
590  continue;
591  }
592  color = currentItem->background( ColorColumn ).color();
593  outputStream << currentItem->text( ValueColumn ).toDouble() << ',';
594  outputStream << color.red() << ',' << color.green() << ',' << color.blue() << ',' << color.alpha() << ',';
595  if ( currentItem->text( LabelColumn ).isEmpty() )
596  {
597  outputStream << "Color entry " << i + 1 << '\n';
598  }
599  else
600  {
601  outputStream << currentItem->text( LabelColumn ) << '\n';
602  }
603  }
604  outputStream.flush();
605  outputFile.close();
606 
607  QFileInfo fileInfo( fileName );
608  settings.setValue( QStringLiteral( "lastColorMapDir" ), fileInfo.absoluteDir().absolutePath() );
609  }
610  else
611  {
612  QMessageBox::warning( this, tr( "Save Color Map as File" ), tr( "Write access denied. Adjust the file permissions and try again.\n\n" ) );
613  }
614  }
615 }
616 
617 void QgsSingleBandPseudoColorRendererWidget::mColormapTreeWidget_itemDoubleClicked( QTreeWidgetItem *item, int column )
618 {
619  if ( !item )
620  {
621  return;
622  }
623 
624  if ( column == ColorColumn )
625  {
626  item->setFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable );
627  QColor newColor = QgsColorDialog::getColor( item->background( column ).color(), this, QStringLiteral( "Change Color" ), true );
628  if ( newColor.isValid() )
629  {
630  item->setBackground( ColorColumn, QBrush( newColor ) );
632  emit widgetChanged();
633  }
634  }
635  else
636  {
637  if ( column == LabelColumn )
638  {
639  // Set text color to default black, which signifies a manually edited label
640  item->setForeground( LabelColumn, QBrush() );
641  }
642  item->setFlags( Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable );
643  }
644 }
645 
646 void QgsSingleBandPseudoColorRendererWidget::mColormapTreeWidget_itemEdited( QTreeWidgetItem *item, int column )
647 {
648  Q_UNUSED( item );
649 
650  if ( column == ValueColumn )
651  {
652  mColormapTreeWidget->sortItems( ValueColumn, Qt::AscendingOrder );
653  autoLabel();
654 
656 
657  emit widgetChanged();
658  }
659  else if ( column == LabelColumn )
660  {
661  // call autoLabel to fill when empty or gray out when same as autoLabel
662  autoLabel();
663  emit widgetChanged();
664  }
665 }
666 
668 {
669  const QgsSingleBandPseudoColorRenderer *pr = dynamic_cast<const QgsSingleBandPseudoColorRenderer *>( r );
670  if ( pr )
671  {
672  mBandComboBox->setBand( pr->band() );
673  mMinMaxWidget->setBands( QList< int >() << pr->band() );
674 
675  const QgsRasterShader *rasterShader = pr->shader();
676  if ( rasterShader )
677  {
678  const QgsColorRampShader *colorRampShader = dynamic_cast<const QgsColorRampShader *>( rasterShader->rasterShaderFunction() );
679  if ( colorRampShader )
680  {
681  if ( colorRampShader->sourceColorRamp() )
682  {
683  btnColorRamp->setColorRamp( colorRampShader->sourceColorRamp() );
684  }
685  else
686  {
687  QgsSettings settings;
688  QString defaultPalette = settings.value( QStringLiteral( "/Raster/defaultPalette" ), "Spectral" ).toString();
689  btnColorRamp->setColorRampFromName( defaultPalette );
690  }
691 
692  mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( colorRampShader->colorRampType() ) );
693 
694  const QList<QgsColorRampShader::ColorRampItem> colorRampItemList = colorRampShader->colorRampItemList();
695  QList<QgsColorRampShader::ColorRampItem>::const_iterator it = colorRampItemList.constBegin();
696  for ( ; it != colorRampItemList.end(); ++it )
697  {
698  QgsTreeWidgetItemObject *newItem = new QgsTreeWidgetItemObject( mColormapTreeWidget );
699  newItem->setText( ValueColumn, QString::number( it->value, 'g', 15 ) );
700  newItem->setBackground( ColorColumn, QBrush( it->color ) );
701  newItem->setText( LabelColumn, it->label );
702  newItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable );
703  connect( newItem, &QgsTreeWidgetItemObject::itemEdited,
704  this, &QgsSingleBandPseudoColorRendererWidget::mColormapTreeWidget_itemEdited );
705  }
706  setUnitFromLabels();
707  mClipCheckBox->setChecked( colorRampShader->clip() );
708 
709  mClassificationModeComboBox->setCurrentIndex( mClassificationModeComboBox->findData( colorRampShader->classificationMode() ) );
710  mNumberOfEntriesSpinBox->setValue( colorRampShader->colorRampItemList().count() ); // some default
711  }
712  }
713  setLineEditValue( mMinLineEdit, pr->classificationMin() );
714  setLineEditValue( mMaxLineEdit, pr->classificationMax() );
715 
716  mMinMaxWidget->setFromMinMaxOrigin( pr->minMaxOrigin() );
717  }
718  else
719  {
720  mMinMaxWidget->setBands( QList< int >() << mBandComboBox->currentBand() );
721  }
722 }
723 
724 void QgsSingleBandPseudoColorRendererWidget::bandChanged()
725 {
726  QList<int> bands;
727  bands.append( mBandComboBox->currentBand() );
728  mMinMaxWidget->setBands( bands );
729 }
730 
731 void QgsSingleBandPseudoColorRendererWidget::mColorInterpolationComboBox_currentIndexChanged( int index )
732 {
733  QgsColorRampShader::Type interpolation = static_cast< QgsColorRampShader::Type >( mColorInterpolationComboBox->itemData( index ).toInt() );
734 
735  mClipCheckBox->setEnabled( interpolation == QgsColorRampShader::Interpolated );
736 
737  QString valueLabel;
738  QString valueToolTip;
739  switch ( interpolation )
740  {
742  valueLabel = tr( "Value" );
743  valueToolTip = tr( "Value for color stop" );
744  break;
746  valueLabel = tr( "Value <=" );
747  valueToolTip = tr( "Maximum value for class" );
748  break;
750  valueLabel = tr( "Value =" );
751  valueToolTip = tr( "Value for color" );
752  break;
753  }
754 
755  QTreeWidgetItem *header = mColormapTreeWidget->headerItem();
756  header->setText( ValueColumn, valueLabel );
757  header->setToolTip( ValueColumn, valueToolTip );
758 
759  autoLabel();
760  emit widgetChanged();
761 }
762 
764 {
765  Q_UNUSED( bandNo );
766  QgsDebugMsg( QString( "theBandNo = %1 min = %2 max = %3" ).arg( bandNo ).arg( min ).arg( max ) );
767 
768  double oldMin = lineEditValue( mMinLineEdit );
769  double oldMax = lineEditValue( mMaxLineEdit );
770 
771  if ( std::isnan( min ) )
772  {
773  whileBlocking( mMinLineEdit )->clear();
774  }
775  else
776  {
777  whileBlocking( mMinLineEdit )->setText( QString::number( min ) );
778  }
779 
780  if ( std::isnan( max ) )
781  {
782  whileBlocking( mMaxLineEdit )->clear();
783  }
784  else
785  {
786  whileBlocking( mMaxLineEdit )->setText( QString::number( max ) );
787  }
788 
789  if ( oldMin != min || oldMax != max )
790  {
791  classify();
792  }
793 }
794 
796 {
797  QTreeWidgetItem *item = mColormapTreeWidget->topLevelItem( 0 );
798  if ( !item )
799  {
800  return;
801  }
802 
803  double min = item->text( ValueColumn ).toDouble();
804  item = mColormapTreeWidget->topLevelItem( mColormapTreeWidget->topLevelItemCount() - 1 );
805  double max = item->text( ValueColumn ).toDouble();
806 
807  whileBlocking( mMinLineEdit )->setText( QString::number( min ) );
808  whileBlocking( mMaxLineEdit )->setText( QString::number( max ) );
809  minMaxModified();
810 }
811 
812 void QgsSingleBandPseudoColorRendererWidget::setLineEditValue( QLineEdit *lineEdit, double value )
813 {
814  QString s;
815  if ( !std::isnan( value ) )
816  {
817  s = QString::number( value );
818  }
819  lineEdit->setText( s );
820 }
821 
822 double QgsSingleBandPseudoColorRendererWidget::lineEditValue( const QLineEdit *lineEdit ) const
823 {
824  if ( lineEdit->text().isEmpty() )
825  {
826  return std::numeric_limits<double>::quiet_NaN();
827  }
828 
829  return lineEdit->text().toDouble();
830 }
831 
832 void QgsSingleBandPseudoColorRendererWidget::resetClassifyButton()
833 {
834  mClassifyButton->setEnabled( true );
835  double min = lineEditValue( mMinLineEdit );
836  double max = lineEditValue( mMaxLineEdit );
837  if ( std::isnan( min ) || std::isnan( max ) || min >= max )
838  {
839  mClassifyButton->setEnabled( false );
840  }
841 }
842 
843 void QgsSingleBandPseudoColorRendererWidget::changeColor()
844 {
845  QList<QTreeWidgetItem *> itemList;
846  itemList = mColormapTreeWidget->selectedItems();
847  if ( itemList.isEmpty() )
848  {
849  return;
850  }
851  QTreeWidgetItem *firstItem = itemList.first();
852 
853  QColor newColor = QgsColorDialog::getColor( firstItem->background( ColorColumn ).color(), this, QStringLiteral( "Change Color" ), true );
854  if ( newColor.isValid() )
855  {
856  Q_FOREACH ( QTreeWidgetItem *item, itemList )
857  {
858  item->setFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable );
859  item->setBackground( ColorColumn, QBrush( newColor ) );
860  }
861 
863  emit widgetChanged();
864  }
865 }
866 
867 void QgsSingleBandPseudoColorRendererWidget::changeOpacity()
868 {
869  QList<QTreeWidgetItem *> itemList;
870  itemList = mColormapTreeWidget->selectedItems();
871  if ( itemList.isEmpty() )
872  {
873  return;
874  }
875  QTreeWidgetItem *firstItem = itemList.first();
876 
877  bool ok;
878  double oldOpacity = firstItem->background( ColorColumn ).color().alpha() / 255 * 100;
879  double opacity = QInputDialog::getDouble( this, tr( "Opacity" ), tr( "Change color opacity [%]" ), oldOpacity, 0.0, 100.0, 0, &ok );
880  if ( ok )
881  {
882  int newOpacity = opacity / 100 * 255;
883  Q_FOREACH ( QTreeWidgetItem *item, itemList )
884  {
885  QColor newColor = item->background( ColorColumn ).color();
886  newColor.setAlpha( newOpacity );
887  item->setBackground( ColorColumn, QBrush( newColor ) );
888  }
889 
891  emit widgetChanged();
892  }
893 }
894 
895 void QgsSingleBandPseudoColorRendererWidget::mMinLineEdit_textEdited( const QString & )
896 {
897  minMaxModified();
898  classify();
899 }
900 
901 void QgsSingleBandPseudoColorRendererWidget::mMaxLineEdit_textEdited( const QString & )
902 {
903  minMaxModified();
904  classify();
905 }
906 
907 void QgsSingleBandPseudoColorRendererWidget::minMaxModified()
908 {
909  mMinMaxWidget->userHasSetManualMinMaxValues();
910 }
QgsRasterMinMaxOrigin::Limits limits() const
Returns the raster limits.
void setExtent(const QgsRectangle &extent)
Sets the extent to use for minimum and maximum value calculation.
static QColor getColor(const QColor &initialColor, QWidget *parent, const QString &title=QString(), bool allowOpacity=false)
Returns a color selection from a color dialog.
A rectangle specified with double values.
Definition: qgsrectangle.h:40
Interface for all raster shaders.
void setColorRampItemList(const QList< QgsColorRampShader::ColorRampItem > &list)
Sets a custom colormap.
Uses quantile (i.e. equal pixel) count.
This class is a composition of two QSettings instances:
Definition: qgssettings.h:58
void loadMinMaxFromTree()
called when the color ramp tree has changed
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
void colorRampChanged()
Emitted whenever a new color ramp is set for the button.
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
This class provides qgis with the ability to render raster datasets onto the mapcanvas.
QList< QgsColorRampShader::ColorRampItem > colorRampItemList() const
Returns the custom colormap.
QgsRasterMinMaxOrigin minMaxOrigin()
Returns a QgsRasterMinMaxOrigin object with the widget values.
const QgsRasterMinMaxOrigin & minMaxOrigin() const
Returns const reference to origin of min/max values.
QgsRasterRenderer * renderer() const
void setClip(bool clip)
Sets whether the shader should not render values out of range.
QgsRasterShader * shader()
Returns the raster shader.
Map canvas is a class for displaying all GIS data types on a canvas.
Definition: qgsmapcanvas.h:74
Type
Supported methods for color interpolation.
virtual void setMapCanvas(QgsMapCanvas *canvas)
Sets the map canvas associated with the widget.
QgsRasterMinMaxWidget * minMaxWidget() override
Returns min/max widget when it exists.
void setMapCanvas(QgsMapCanvas *canvas) override
Sets the map canvas associated with the widget.
void widgetChanged()
Emitted when something on the widget has changed.
void setColorRampType(QgsColorRampShader::Type colorRampType)
Sets the color ramp type.
QgsRasterDataProvider * dataProvider() override
Returns the layer&#39;s data provider.
This class describes the origin of min/max values.
virtual QString min(int index=0)
void bandChanged(int band)
This signal is emitted when the currently selected band changes.
QgsRasterShaderFunction * rasterShaderFunction()
void setBands(const QList< int > &bands)
Raster renderer pipe for single band pseudocolor.
void setRasterShaderFunction(QgsRasterShaderFunction *function)
A public method that allows the user to set their own shader function.
void doComputations() override
Load programmatically with current values.
void setSourceColorRamp(QgsColorRamp *colorramp)
Set the source color ramp.
Custom QgsTreeWidgetItem with extra signals when item is edited.
void setMinMaxOrigin(const QgsRasterMinMaxOrigin &origin)
Sets origin of min/max values.
void setLimits(QgsRasterMinMaxOrigin::Limits limits)
Sets the limits.
Type colorRampType() const
Returns the color ramp type.
void userHasSetManualMinMaxValues()
Uncheck cumulative cut, min/max, std-dev radio buttons.
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition: qgis.h:224
QgsRectangle extent()
Returns the extent selected by the user.
Assigns the color of the exact matching value in the color ramp item list.
void setMapCanvas(QgsMapCanvas *canvas)
Sets the map canvas associated with the widget.
Uses breaks from color palette.
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
void setClassificationMode(ClassificationMode classificationMode)
Sets classification mode.
void classify()
Executes the single band pseudo raster classficiation.
QgsSingleBandPseudoColorRendererWidget(QgsRasterLayer *layer, const QgsRectangle &extent=QgsRectangle())
bool clip() const
Returns whether the shader will clip values which are out of range.
void setBand(int bandNo)
Sets the band used by the renderer.
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...
Interpolates the color between two class breaks linearly.
void load(int bandNo, double min, double max)
signal emitted when new min/max values are computed from statistics.
virtual QString max(int index=0)
ClassificationMode
Classification modes used to create the color ramp shader.
Assigns the color of the higher class for every pixel between two class breaks.
ClassificationMode classificationMode() const
Returns the classification mode.
QgsMapCanvas * mCanvas
Associated map canvas.
QgsColorRamp * sourceColorRamp() const
Gets the source color ramp.
void widgetChanged()
Emitted when something on the widget has changed.
Raster renderer pipe that applies colors to a raster.
void doComputations()
Load programmatically with current values.
int band() const
Returns the band used by the renderer.
void loadMinMax(int bandNo, double min, double max)
called when new min/max values are loaded
Base class for raster data providers.
void setFromMinMaxOrigin(const QgsRasterMinMaxOrigin &)
Sets the "source" of min/max values.