QGIS API Documentation  2.99.0-Master (19b062c)
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  , mDisableMinMaxWidgetRefresh( false )
45  , mMinMaxOrigin( 0 )
46 {
47  QgsSettings settings;
48 
49  setupUi( this );
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, [ = ]( const 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 
103 
104  mBandComboBox->setLayer( mRasterLayer );
105 
106  mColorInterpolationComboBox->addItem( tr( "Discrete" ), QgsColorRampShader::Discrete );
107  mColorInterpolationComboBox->addItem( tr( "Linear" ), QgsColorRampShader::Interpolated );
108  mColorInterpolationComboBox->addItem( tr( "Exact" ), QgsColorRampShader::Exact );
109  mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( QgsColorRampShader::Interpolated ) );
110 
111  mClassificationModeComboBox->addItem( tr( "Continuous" ), QgsColorRampShader::Continuous );
112  mClassificationModeComboBox->addItem( tr( "Equal interval" ), QgsColorRampShader::EqualInterval );
113  mClassificationModeComboBox->addItem( tr( "Quantile" ), QgsColorRampShader::Quantile );
114  mClassificationModeComboBox->setCurrentIndex( mClassificationModeComboBox->findData( QgsColorRampShader::Continuous ) );
115 
116  mNumberOfEntriesSpinBox->setValue( 5 ); // some default
117 
118  setFromRenderer( layer->renderer() );
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  if ( mColormapTreeWidget->topLevelItemCount() == 0 )
193  applyColorRamp();
194 }
195 
197 {
199  mMinMaxWidget->setMapCanvas( canvas );
200 }
201 
202 void QgsSingleBandPseudoColorRendererWidget::autoLabel()
203 {
204  QgsColorRampShader::Type interpolation = static_cast< QgsColorRampShader::Type >( mColorInterpolationComboBox->currentData().toInt() );
205  bool discrete = interpolation == QgsColorRampShader::Discrete;
206  QString unit = mUnitLineEdit->text();
207  QString label;
208  int topLevelItemCount = mColormapTreeWidget->topLevelItemCount();
209  QTreeWidgetItem *currentItem = nullptr;
210  for ( int i = 0; i < topLevelItemCount; ++i )
211  {
212  currentItem = mColormapTreeWidget->topLevelItem( i );
213  //If the item is null or does not have a pixel values set, skip
214  if ( !currentItem || currentItem->text( ValueColumn ).isEmpty() )
215  {
216  continue;
217  }
218 
219  if ( discrete )
220  {
221  if ( i == 0 )
222  {
223  label = "<= " + currentItem->text( ValueColumn ) + unit;
224  }
225  else if ( currentItem->text( ValueColumn ).toDouble() == std::numeric_limits<double>::infinity() )
226  {
227  label = "> " + mColormapTreeWidget->topLevelItem( i - 1 )->text( ValueColumn ) + unit;
228  }
229  else
230  {
231  label = mColormapTreeWidget->topLevelItem( i - 1 )->text( ValueColumn ) + " - " + currentItem->text( ValueColumn ) + unit;
232  }
233  }
234  else
235  {
236  label = currentItem->text( ValueColumn ) + unit;
237  }
238 
239  if ( currentItem->text( LabelColumn ).isEmpty() || currentItem->text( LabelColumn ) == label || currentItem->foreground( LabelColumn ).color() == QColor( Qt::gray ) )
240  {
241  currentItem->setText( LabelColumn, label );
242  currentItem->setForeground( LabelColumn, QBrush( QColor( Qt::gray ) ) );
243  }
244  }
245 }
246 
247 void QgsSingleBandPseudoColorRendererWidget::setUnitFromLabels()
248 {
249  QgsColorRampShader::Type interpolation = static_cast< QgsColorRampShader::Type >( mColorInterpolationComboBox->currentData().toInt() );
250  bool discrete = interpolation == QgsColorRampShader::Discrete;
251  QStringList allSuffixes;
252  QString label;
253  int topLevelItemCount = mColormapTreeWidget->topLevelItemCount();
254  QTreeWidgetItem *currentItem = nullptr;
255  for ( int i = 0; i < topLevelItemCount; ++i )
256  {
257  currentItem = mColormapTreeWidget->topLevelItem( i );
258  //If the item is null or does not have a pixel values set, skip
259  if ( !currentItem || currentItem->text( ValueColumn ).isEmpty() )
260  {
261  continue;
262  }
263 
264  if ( discrete )
265  {
266  if ( i == 0 )
267  {
268  label = "<= " + currentItem->text( ValueColumn );
269  }
270  else if ( currentItem->text( ValueColumn ).toDouble() == std::numeric_limits<double>::infinity() )
271  {
272  label = "> " + mColormapTreeWidget->topLevelItem( i - 1 )->text( ValueColumn );
273  }
274  else
275  {
276  label = mColormapTreeWidget->topLevelItem( i - 1 )->text( ValueColumn ) + " - " + currentItem->text( ValueColumn );
277  }
278  }
279  else
280  {
281  label = currentItem->text( ValueColumn );
282  }
283 
284  if ( currentItem->text( LabelColumn ).startsWith( label ) )
285  {
286  allSuffixes.append( currentItem->text( LabelColumn ).mid( label.length() ) );
287  }
288  }
289  // find most common suffix
290  QStringList suffixes = QStringList( allSuffixes );
291  suffixes.removeDuplicates();
292  int max = 0;
293  QString unit;
294  for ( int i = 0; i < suffixes.count(); ++i )
295  {
296  int n = allSuffixes.count( suffixes[i] );
297  if ( n > max )
298  {
299  max = n;
300  unit = suffixes[i];
301  }
302  }
303  // Set this suffix as unit if at least used twice
304  if ( max >= 2 )
305  {
306  mUnitLineEdit->setText( unit );
307  }
308  autoLabel();
309 }
310 
311 void QgsSingleBandPseudoColorRendererWidget::mAddEntryButton_clicked()
312 {
313  QgsTreeWidgetItemObject *newItem = new QgsTreeWidgetItemObject( mColormapTreeWidget );
314  newItem->setText( ValueColumn, QStringLiteral( "0" ) );
315  newItem->setBackground( ColorColumn, QBrush( QColor( Qt::magenta ) ) );
316  newItem->setText( LabelColumn, QString() );
317  newItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable );
318  connect( newItem, &QgsTreeWidgetItemObject::itemEdited,
319  this, &QgsSingleBandPseudoColorRendererWidget::mColormapTreeWidget_itemEdited );
320  mColormapTreeWidget->sortItems( ValueColumn, Qt::AscendingOrder );
321  autoLabel();
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  emit widgetChanged();
339 }
340 
342 {
343  std::unique_ptr< QgsColorRamp > ramp( btnColorRamp->colorRamp() );
344  if ( !ramp || std::isnan( lineEditValue( mMinLineEdit ) ) || std::isnan( lineEditValue( mMaxLineEdit ) ) )
345  {
346  return;
347  }
348 
349  QgsSingleBandPseudoColorRenderer *pr = new QgsSingleBandPseudoColorRenderer( mRasterLayer->dataProvider(), mBandComboBox->currentBand(), nullptr );
350  pr->setClassificationMin( lineEditValue( mMinLineEdit ) );
351  pr->setClassificationMax( lineEditValue( mMaxLineEdit ) );
352  pr->createShader( ramp.get(), static_cast< QgsColorRampShader::Type >( mColorInterpolationComboBox->currentData().toInt() ), static_cast< QgsColorRampShader::ClassificationMode >( mClassificationModeComboBox->currentData().toInt() ), mNumberOfEntriesSpinBox->value(), mClipCheckBox->isChecked(), minMaxWidget()->extent() );
353 
354  const QgsRasterShader *rasterShader = pr->shader();
355  if ( rasterShader )
356  {
357  const QgsColorRampShader *colorRampShader = dynamic_cast<const QgsColorRampShader *>( rasterShader->rasterShaderFunction() );
358  if ( colorRampShader )
359  {
360  mColormapTreeWidget->clear();
361 
362  const QList<QgsColorRampShader::ColorRampItem> colorRampItemList = colorRampShader->colorRampItemList();
363  QList<QgsColorRampShader::ColorRampItem>::const_iterator it = colorRampItemList.constBegin();
364  for ( ; it != colorRampItemList.end(); ++it )
365  {
366  QgsTreeWidgetItemObject *newItem = new QgsTreeWidgetItemObject( mColormapTreeWidget );
367  newItem->setText( ValueColumn, QString::number( it->value, 'g', 15 ) );
368  newItem->setBackground( ColorColumn, QBrush( it->color ) );
369  newItem->setText( LabelColumn, it->label );
370  newItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable );
371  connect( newItem, &QgsTreeWidgetItemObject::itemEdited,
372  this, &QgsSingleBandPseudoColorRendererWidget::mColormapTreeWidget_itemEdited );
373  }
374  mClipCheckBox->setChecked( colorRampShader->clip() );
375  }
376  }
377 
378  autoLabel();
379  emit widgetChanged();
380 }
381 
382 void QgsSingleBandPseudoColorRendererWidget::mClassificationModeComboBox_currentIndexChanged( int index )
383 {
384  QgsColorRampShader::ClassificationMode mode = static_cast< QgsColorRampShader::ClassificationMode >( mClassificationModeComboBox->itemData( index ).toInt() );
385  mNumberOfEntriesSpinBox->setEnabled( mode != QgsColorRampShader::Continuous );
386  mMinLineEdit->setEnabled( mode != QgsColorRampShader::Quantile );
387  mMaxLineEdit->setEnabled( mode != QgsColorRampShader::Quantile );
388 }
389 
390 void QgsSingleBandPseudoColorRendererWidget::applyColorRamp()
391 {
392  std::unique_ptr< QgsColorRamp > ramp( btnColorRamp->colorRamp() );
393  if ( !ramp )
394  {
395  return;
396  }
397 
398  if ( !btnColorRamp->colorRampName().isEmpty() )
399  {
400  // Remember last used color ramp
401  QgsSettings settings;
402  settings.setValue( QStringLiteral( "Raster/defaultPalette" ), btnColorRamp->colorRampName() );
403  }
404 
405  bool enableContinuous = ( ramp->count() > 0 );
406  mClassificationModeComboBox->setEnabled( enableContinuous );
407  if ( !enableContinuous )
408  {
409  mClassificationModeComboBox->setCurrentIndex( mClassificationModeComboBox->findData( QgsColorRampShader::EqualInterval ) );
410  }
411 
412  classify();
413 }
414 
415 void QgsSingleBandPseudoColorRendererWidget::populateColormapTreeWidget( const QList<QgsColorRampShader::ColorRampItem> &colorRampItems )
416 {
417  mColormapTreeWidget->clear();
418  QList<QgsColorRampShader::ColorRampItem>::const_iterator it = colorRampItems.constBegin();
419  for ( ; it != colorRampItems.constEnd(); ++it )
420  {
421  QgsTreeWidgetItemObject *newItem = new QgsTreeWidgetItemObject( mColormapTreeWidget );
422  newItem->setText( ValueColumn, QString::number( it->value, 'g', 15 ) );
423  newItem->setBackground( ColorColumn, QBrush( it->color ) );
424  newItem->setText( LabelColumn, it->label );
425  newItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable );
426  connect( newItem, &QgsTreeWidgetItemObject::itemEdited,
427  this, &QgsSingleBandPseudoColorRendererWidget::mColormapTreeWidget_itemEdited );
428  }
429  setUnitFromLabels();
430 }
431 
432 void QgsSingleBandPseudoColorRendererWidget::mLoadFromBandButton_clicked()
433 {
434  if ( !mRasterLayer || !mRasterLayer->dataProvider() )
435  {
436  return;
437  }
438 
439  int bandIndex = mBandComboBox->currentBand();
440 
441 
442  QList<QgsColorRampShader::ColorRampItem> colorRampList = mRasterLayer->dataProvider()->colorTable( bandIndex );
443  if ( !colorRampList.isEmpty() )
444  {
445  populateColormapTreeWidget( colorRampList );
446  mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( QgsColorRampShader::Interpolated ) );
447  }
448  else
449  {
450  QMessageBox::warning( this, tr( "Load Color Map" ), tr( "The color map for band %1 has no entries" ).arg( bandIndex ) );
451  }
452  emit widgetChanged();
453 }
454 
455 void QgsSingleBandPseudoColorRendererWidget::mLoadFromFileButton_clicked()
456 {
457  int lineCounter = 0;
458  bool importError = false;
459  QString badLines;
460  QgsSettings settings;
461  QString lastDir = settings.value( QStringLiteral( "lastColorMapDir" ), QDir::homePath() ).toString();
462  QString fileName = QFileDialog::getOpenFileName( this, tr( "Open file" ), lastDir, tr( "Textfile (*.txt)" ) );
463  QFile inputFile( fileName );
464  if ( inputFile.open( QFile::ReadOnly ) )
465  {
466  //clear the current tree
467  mColormapTreeWidget->clear();
468 
469  QTextStream inputStream( &inputFile );
470  QString inputLine;
471  QStringList inputStringComponents;
472  QList<QgsColorRampShader::ColorRampItem> colorRampItems;
473 
474  //read through the input looking for valid data
475  while ( !inputStream.atEnd() )
476  {
477  lineCounter++;
478  inputLine = inputStream.readLine();
479  if ( !inputLine.isEmpty() )
480  {
481  if ( !inputLine.simplified().startsWith( '#' ) )
482  {
483  if ( inputLine.contains( QLatin1String( "INTERPOLATION" ), Qt::CaseInsensitive ) )
484  {
485  inputStringComponents = inputLine.split( ':' );
486  if ( inputStringComponents.size() == 2 )
487  {
488  if ( inputStringComponents[1].trimmed().toUpper().compare( QLatin1String( "INTERPOLATED" ), Qt::CaseInsensitive ) == 0 )
489  {
490  mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( QgsColorRampShader::Interpolated ) );
491  }
492  else if ( inputStringComponents[1].trimmed().toUpper().compare( QLatin1String( "DISCRETE" ), Qt::CaseInsensitive ) == 0 )
493  {
494  mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( QgsColorRampShader::Discrete ) );
495  }
496  else
497  {
498  mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( QgsColorRampShader::Exact ) );
499  }
500  }
501  else
502  {
503  importError = true;
504  badLines = badLines + QString::number( lineCounter ) + ":\t[" + inputLine + "]\n";
505  }
506  }
507  else
508  {
509  inputStringComponents = inputLine.split( ',' );
510  if ( inputStringComponents.size() == 6 )
511  {
512  QgsColorRampShader::ColorRampItem currentItem( inputStringComponents[0].toDouble(),
513  QColor::fromRgb( inputStringComponents[1].toInt(), inputStringComponents[2].toInt(),
514  inputStringComponents[3].toInt(), inputStringComponents[4].toInt() ),
515  inputStringComponents[5] );
516  colorRampItems.push_back( currentItem );
517  }
518  else
519  {
520  importError = true;
521  badLines = badLines + QString::number( lineCounter ) + ":\t[" + inputLine + "]\n";
522  }
523  }
524  }
525  }
526  lineCounter++;
527  }
528  populateColormapTreeWidget( colorRampItems );
529 
530  QFileInfo fileInfo( fileName );
531  settings.setValue( QStringLiteral( "lastColorMapDir" ), fileInfo.absoluteDir().absolutePath() );
532 
533  if ( importError )
534  {
535  QMessageBox::warning( this, tr( "Import Error" ), tr( "The following lines contained errors\n\n" ) + badLines );
536  }
537  }
538  else if ( !fileName.isEmpty() )
539  {
540  QMessageBox::warning( this, tr( "Read access denied" ), tr( "Read access denied. Adjust the file permissions and try again.\n\n" ) );
541  }
542  emit widgetChanged();
543 }
544 
545 void QgsSingleBandPseudoColorRendererWidget::mExportToFileButton_clicked()
546 {
547  QgsSettings settings;
548  QString lastDir = settings.value( QStringLiteral( "lastColorMapDir" ), QDir::homePath() ).toString();
549  QString fileName = QFileDialog::getSaveFileName( this, tr( "Save file" ), lastDir, tr( "Textfile (*.txt)" ) );
550  if ( !fileName.isEmpty() )
551  {
552  if ( !fileName.endsWith( QLatin1String( ".txt" ), Qt::CaseInsensitive ) )
553  {
554  fileName = fileName + ".txt";
555  }
556 
557  QFile outputFile( fileName );
558  if ( outputFile.open( QFile::WriteOnly | QIODevice::Truncate ) )
559  {
560  QTextStream outputStream( &outputFile );
561  outputStream << "# " << tr( "QGIS Generated Color Map Export File" ) << '\n';
562  outputStream << "INTERPOLATION:";
563  QgsColorRampShader::Type interpolation = static_cast< QgsColorRampShader::Type >( mColorInterpolationComboBox->currentData().toInt() );
564  switch ( interpolation )
565  {
567  outputStream << "INTERPOLATED\n";
568  break;
570  outputStream << "DISCRETE\n";
571  break;
573  outputStream << "EXACT\n";
574  break;
575  }
576 
577  int topLevelItemCount = mColormapTreeWidget->topLevelItemCount();
578  QTreeWidgetItem *currentItem = nullptr;
579  QColor color;
580  for ( int i = 0; i < topLevelItemCount; ++i )
581  {
582  currentItem = mColormapTreeWidget->topLevelItem( i );
583  if ( !currentItem )
584  {
585  continue;
586  }
587  color = currentItem->background( ColorColumn ).color();
588  outputStream << currentItem->text( ValueColumn ).toDouble() << ',';
589  outputStream << color.red() << ',' << color.green() << ',' << color.blue() << ',' << color.alpha() << ',';
590  if ( currentItem->text( LabelColumn ).isEmpty() )
591  {
592  outputStream << "Color entry " << i + 1 << '\n';
593  }
594  else
595  {
596  outputStream << currentItem->text( LabelColumn ) << '\n';
597  }
598  }
599  outputStream.flush();
600  outputFile.close();
601 
602  QFileInfo fileInfo( fileName );
603  settings.setValue( QStringLiteral( "lastColorMapDir" ), fileInfo.absoluteDir().absolutePath() );
604  }
605  else
606  {
607  QMessageBox::warning( this, tr( "Write access denied" ), tr( "Write access denied. Adjust the file permissions and try again.\n\n" ) );
608  }
609  }
610 }
611 
612 void QgsSingleBandPseudoColorRendererWidget::mColormapTreeWidget_itemDoubleClicked( QTreeWidgetItem *item, int column )
613 {
614  if ( !item )
615  {
616  return;
617  }
618 
619  if ( column == ColorColumn )
620  {
621  item->setFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable );
622  QColor newColor = QgsColorDialog::getColor( item->background( column ).color(), this, QStringLiteral( "Change color" ), true );
623  if ( newColor.isValid() )
624  {
625  item->setBackground( ColorColumn, QBrush( newColor ) );
626  emit widgetChanged();
627  }
628  }
629  else
630  {
631  if ( column == LabelColumn )
632  {
633  // Set text color to default black, which signifies a manually edited label
634  item->setForeground( LabelColumn, QBrush() );
635  }
636  item->setFlags( Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable );
637  }
638 }
639 
640 void QgsSingleBandPseudoColorRendererWidget::mColormapTreeWidget_itemEdited( QTreeWidgetItem *item, int column )
641 {
642  Q_UNUSED( item );
643 
644  if ( column == ValueColumn )
645  {
646  mColormapTreeWidget->sortItems( ValueColumn, Qt::AscendingOrder );
647  autoLabel();
648  emit widgetChanged();
649  }
650  else if ( column == LabelColumn )
651  {
652  // call autoLabel to fill when empty or gray out when same as autoLabel
653  autoLabel();
654  emit widgetChanged();
655  }
656 }
657 
659 {
660  const QgsSingleBandPseudoColorRenderer *pr = dynamic_cast<const QgsSingleBandPseudoColorRenderer *>( r );
661  if ( pr )
662  {
663  mBandComboBox->setBand( pr->band() );
664  mMinMaxWidget->setBands( QList< int >() << pr->band() );
665 
666  const QgsRasterShader *rasterShader = pr->shader();
667  if ( rasterShader )
668  {
669  const QgsColorRampShader *colorRampShader = dynamic_cast<const QgsColorRampShader *>( rasterShader->rasterShaderFunction() );
670  if ( colorRampShader )
671  {
672  if ( colorRampShader->sourceColorRamp() )
673  {
674  btnColorRamp->setColorRamp( colorRampShader->sourceColorRamp() );
675  }
676  else
677  {
678  QgsSettings settings;
679  QString defaultPalette = settings.value( QStringLiteral( "/Raster/defaultPalette" ), "Spectral" ).toString();
680  btnColorRamp->setColorRampFromName( defaultPalette );
681  }
682 
683  mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( colorRampShader->colorRampType() ) );
684 
685  const QList<QgsColorRampShader::ColorRampItem> colorRampItemList = colorRampShader->colorRampItemList();
686  QList<QgsColorRampShader::ColorRampItem>::const_iterator it = colorRampItemList.constBegin();
687  for ( ; it != colorRampItemList.end(); ++it )
688  {
689  QgsTreeWidgetItemObject *newItem = new QgsTreeWidgetItemObject( mColormapTreeWidget );
690  newItem->setText( ValueColumn, QString::number( it->value, 'g', 15 ) );
691  newItem->setBackground( ColorColumn, QBrush( it->color ) );
692  newItem->setText( LabelColumn, it->label );
693  newItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable );
694  connect( newItem, &QgsTreeWidgetItemObject::itemEdited,
695  this, &QgsSingleBandPseudoColorRendererWidget::mColormapTreeWidget_itemEdited );
696  }
697  setUnitFromLabels();
698  mClipCheckBox->setChecked( colorRampShader->clip() );
699 
700  mClassificationModeComboBox->setCurrentIndex( mClassificationModeComboBox->findData( colorRampShader->classificationMode() ) );
701  mNumberOfEntriesSpinBox->setValue( colorRampShader->colorRampItemList().count() ); // some default
702  }
703  }
704  setLineEditValue( mMinLineEdit, pr->classificationMin() );
705  setLineEditValue( mMaxLineEdit, pr->classificationMax() );
706 
707  mMinMaxWidget->setFromMinMaxOrigin( pr->minMaxOrigin() );
708  }
709  else
710  {
711  mMinMaxWidget->setBands( QList< int >() << mBandComboBox->currentBand() );
712  }
713 }
714 
715 void QgsSingleBandPseudoColorRendererWidget::bandChanged()
716 {
717  QList<int> bands;
718  bands.append( mBandComboBox->currentBand() );
719  mMinMaxWidget->setBands( bands );
720 }
721 
722 void QgsSingleBandPseudoColorRendererWidget::mColorInterpolationComboBox_currentIndexChanged( int index )
723 {
724  QgsColorRampShader::Type interpolation = static_cast< QgsColorRampShader::Type >( mColorInterpolationComboBox->itemData( index ).toInt() );
725 
726  mClipCheckBox->setEnabled( interpolation == QgsColorRampShader::Interpolated );
727 
728  QString valueLabel;
729  QString valueToolTip;
730  switch ( interpolation )
731  {
733  valueLabel = tr( "Value" );
734  valueToolTip = tr( "Value for color stop" );
735  break;
737  valueLabel = tr( "Value <=" );
738  valueToolTip = tr( "Maximum value for class" );
739  break;
741  valueLabel = tr( "Value =" );
742  valueToolTip = tr( "Value for color" );
743  break;
744  }
745 
746  QTreeWidgetItem *header = mColormapTreeWidget->headerItem();
747  header->setText( ValueColumn, valueLabel );
748  header->setToolTip( ValueColumn, valueToolTip );
749 
750  autoLabel();
751  emit widgetChanged();
752 }
753 
755 {
756  Q_UNUSED( bandNo );
757  QgsDebugMsg( QString( "theBandNo = %1 min = %2 max = %3" ).arg( bandNo ).arg( min ).arg( max ) );
758 
759  mDisableMinMaxWidgetRefresh = true;
760  if ( std::isnan( min ) )
761  {
762  mMinLineEdit->clear();
763  }
764  else
765  {
766  mMinLineEdit->setText( QString::number( min ) );
767  }
768 
769  if ( std::isnan( max ) )
770  {
771  mMaxLineEdit->clear();
772  }
773  else
774  {
775  mMaxLineEdit->setText( QString::number( max ) );
776  }
777  mDisableMinMaxWidgetRefresh = false;
778  classify();
779 }
780 
781 void QgsSingleBandPseudoColorRendererWidget::setLineEditValue( QLineEdit *lineEdit, double value )
782 {
783  QString s;
784  if ( !std::isnan( value ) )
785  {
786  s = QString::number( value );
787  }
788  lineEdit->setText( s );
789 }
790 
791 double QgsSingleBandPseudoColorRendererWidget::lineEditValue( const QLineEdit *lineEdit ) const
792 {
793  if ( lineEdit->text().isEmpty() )
794  {
795  return std::numeric_limits<double>::quiet_NaN();
796  }
797 
798  return lineEdit->text().toDouble();
799 }
800 
801 void QgsSingleBandPseudoColorRendererWidget::resetClassifyButton()
802 {
803  mClassifyButton->setEnabled( true );
804  double min = lineEditValue( mMinLineEdit );
805  double max = lineEditValue( mMaxLineEdit );
806  if ( std::isnan( min ) || std::isnan( max ) || min >= max )
807  {
808  mClassifyButton->setEnabled( false );
809  }
810 }
811 
812 void QgsSingleBandPseudoColorRendererWidget::changeColor()
813 {
814  QList<QTreeWidgetItem *> itemList;
815  itemList = mColormapTreeWidget->selectedItems();
816  if ( itemList.isEmpty() )
817  {
818  return;
819  }
820  QTreeWidgetItem *firstItem = itemList.first();
821 
822  QColor newColor = QgsColorDialog::getColor( firstItem->background( ColorColumn ).color(), this, QStringLiteral( "Change color" ), true );
823  if ( newColor.isValid() )
824  {
825  Q_FOREACH ( QTreeWidgetItem *item, itemList )
826  {
827  item->setFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable );
828  item->setBackground( ColorColumn, QBrush( newColor ) );
829  }
830  emit widgetChanged();
831  }
832 }
833 
834 void QgsSingleBandPseudoColorRendererWidget::changeOpacity()
835 {
836  QList<QTreeWidgetItem *> itemList;
837  itemList = mColormapTreeWidget->selectedItems();
838  if ( itemList.isEmpty() )
839  {
840  return;
841  }
842  QTreeWidgetItem *firstItem = itemList.first();
843 
844  bool ok;
845  double oldOpacity = firstItem->background( ColorColumn ).color().alpha() / 255 * 100;
846  double opacity = QInputDialog::getDouble( this, tr( "Opacity" ), tr( "Change color opacity [%]" ), oldOpacity, 0.0, 100.0, 0, &ok );
847  if ( ok )
848  {
849  int newOpacity = opacity / 100 * 255;
850  Q_FOREACH ( QTreeWidgetItem *item, itemList )
851  {
852  QColor newColor = item->background( ColorColumn ).color();
853  newColor.setAlpha( newOpacity );
854  item->setBackground( ColorColumn, QBrush( newColor ) );
855  }
856  emit widgetChanged();
857  }
858 }
859 
860 void QgsSingleBandPseudoColorRendererWidget::mMinLineEdit_textEdited( const QString & )
861 {
862  minMaxModified();
863  classify();
864 }
865 
866 void QgsSingleBandPseudoColorRendererWidget::mMaxLineEdit_textEdited( const QString & )
867 {
868  minMaxModified();
869  classify();
870 }
871 
872 void QgsSingleBandPseudoColorRendererWidget::minMaxModified()
873 {
874  if ( !mDisableMinMaxWidgetRefresh )
875  {
876  mMinMaxWidget->userHasSetManualMinMaxValues();
877  }
878 }
QgsRasterMinMaxOrigin::Limits limits() const
Return limits.
void setExtent(const QgsRectangle &extent)
Sets the extent to use for minimum and maximum value calculation.
A rectangle specified with double values.
Definition: qgsrectangle.h:39
Interface for all raster shaders.
void setColorRampItemList(const QList< QgsColorRampShader::ColorRampItem > &list)
Set custom colormap.
Uses quantile (i.e. equal pixel) count.
This class is a composition of two QSettings instances:
Definition: qgssettings.h:55
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.
#define QgsDebugMsg(str)
Definition: qgslogger.h:37
This class provides qgis with the ability to render raster datasets onto the mapcanvas.
QList< QgsColorRampShader::ColorRampItem > colorRampItemList() const
Get the custom colormap.
QgsRasterMinMaxOrigin minMaxOrigin()
Return 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
Return 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)
Set the color ramp type.
QgsRasterDataProvider * dataProvider() override
void setValue(const QString &key, const QVariant &value, const QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
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)
Set limits.
Type colorRampType() const
Get the color ramp type.
void userHasSetManualMinMaxValues()
Uncheck cumulative cut, min/max, std-dev radio buttons.
static QColor getColor(const QColor &initialColor, QWidget *parent, const QString &title=QString(), const bool allowOpacity=false)
Return a color selection from a color dialog.
QgsRectangle extent()
Return 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.
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), const Section section=NoSection) const
Returns the value for setting key.
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)
void createShader(QgsColorRamp *colorRamp=nullptr, QgsColorRampShader::Type colorRampType=QgsColorRampShader::Interpolated, QgsColorRampShader::ClassificationMode classificationMode=QgsColorRampShader::Continuous, int classes=0, bool clip=false, const QgsRectangle &extent=QgsRectangle())
Creates a color ramp shader.
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
Get 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 &)
Set the "source" of min/max values.