QGIS API Documentation  3.0.2-Girona (307d082)
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  QgsSingleBandPseudoColorRenderer *pr = new QgsSingleBandPseudoColorRenderer( mRasterLayer->dataProvider(), mBandComboBox->currentBand(), nullptr );
352  pr->setClassificationMin( lineEditValue( mMinLineEdit ) );
353  pr->setClassificationMax( lineEditValue( mMaxLineEdit ) );
354  pr->createShader( ramp.get(), 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  QList<QgsColorRampShader::ColorRampItem>::const_iterator it = colorRampItemList.constBegin();
366  for ( ; it != colorRampItemList.end(); ++it )
367  {
368  QgsTreeWidgetItemObject *newItem = new QgsTreeWidgetItemObject( mColormapTreeWidget );
369  newItem->setText( ValueColumn, QString::number( it->value, 'g', 15 ) );
370  newItem->setBackground( ColorColumn, QBrush( it->color ) );
371  newItem->setText( LabelColumn, it->label );
372  newItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable );
373  connect( newItem, &QgsTreeWidgetItemObject::itemEdited,
374  this, &QgsSingleBandPseudoColorRendererWidget::mColormapTreeWidget_itemEdited );
375  }
376  mClipCheckBox->setChecked( colorRampShader->clip() );
377  }
378  }
379 
380  autoLabel();
381  emit widgetChanged();
382 }
383 
384 void QgsSingleBandPseudoColorRendererWidget::mClassificationModeComboBox_currentIndexChanged( int index )
385 {
386  QgsColorRampShader::ClassificationMode mode = static_cast< QgsColorRampShader::ClassificationMode >( mClassificationModeComboBox->itemData( index ).toInt() );
387  mNumberOfEntriesSpinBox->setEnabled( mode != QgsColorRampShader::Continuous );
388  mMinLineEdit->setEnabled( mode != QgsColorRampShader::Quantile );
389  mMaxLineEdit->setEnabled( mode != QgsColorRampShader::Quantile );
390 }
391 
392 void QgsSingleBandPseudoColorRendererWidget::applyColorRamp()
393 {
394  std::unique_ptr< QgsColorRamp > ramp( btnColorRamp->colorRamp() );
395  if ( !ramp )
396  {
397  return;
398  }
399 
400  if ( !btnColorRamp->colorRampName().isEmpty() )
401  {
402  // Remember last used color ramp
403  QgsSettings settings;
404  settings.setValue( QStringLiteral( "Raster/defaultPalette" ), btnColorRamp->colorRampName() );
405  }
406 
407  bool enableContinuous = ( ramp->count() > 0 );
408  mClassificationModeComboBox->setEnabled( enableContinuous );
409  if ( !enableContinuous )
410  {
411  mClassificationModeComboBox->setCurrentIndex( mClassificationModeComboBox->findData( QgsColorRampShader::EqualInterval ) );
412  }
413 
414  classify();
415 }
416 
417 void QgsSingleBandPseudoColorRendererWidget::populateColormapTreeWidget( const QList<QgsColorRampShader::ColorRampItem> &colorRampItems )
418 {
419  mColormapTreeWidget->clear();
420  QList<QgsColorRampShader::ColorRampItem>::const_iterator it = colorRampItems.constBegin();
421  for ( ; it != colorRampItems.constEnd(); ++it )
422  {
423  QgsTreeWidgetItemObject *newItem = new QgsTreeWidgetItemObject( mColormapTreeWidget );
424  newItem->setText( ValueColumn, QString::number( it->value, 'g', 15 ) );
425  newItem->setBackground( ColorColumn, QBrush( it->color ) );
426  newItem->setText( LabelColumn, it->label );
427  newItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable );
428  connect( newItem, &QgsTreeWidgetItemObject::itemEdited,
429  this, &QgsSingleBandPseudoColorRendererWidget::mColormapTreeWidget_itemEdited );
430  }
431  setUnitFromLabels();
432 }
433 
434 void QgsSingleBandPseudoColorRendererWidget::mLoadFromBandButton_clicked()
435 {
436  if ( !mRasterLayer || !mRasterLayer->dataProvider() )
437  {
438  return;
439  }
440 
441  int bandIndex = mBandComboBox->currentBand();
442 
443 
444  QList<QgsColorRampShader::ColorRampItem> colorRampList = mRasterLayer->dataProvider()->colorTable( bandIndex );
445  if ( !colorRampList.isEmpty() )
446  {
447  populateColormapTreeWidget( colorRampList );
448  mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( QgsColorRampShader::Interpolated ) );
449  }
450  else
451  {
452  QMessageBox::warning( this, tr( "Load Color Map" ), tr( "The color map for band %1 has no entries." ).arg( bandIndex ) );
453  }
454 
456  emit widgetChanged();
457 }
458 
459 void QgsSingleBandPseudoColorRendererWidget::mLoadFromFileButton_clicked()
460 {
461  int lineCounter = 0;
462  bool importError = false;
463  QString badLines;
464  QgsSettings settings;
465  QString lastDir = settings.value( QStringLiteral( "lastColorMapDir" ), QDir::homePath() ).toString();
466  QString fileName = QFileDialog::getOpenFileName( this, tr( "Load Color Map from File" ), lastDir, tr( "Textfile (*.txt)" ) );
467  QFile inputFile( fileName );
468  if ( inputFile.open( QFile::ReadOnly ) )
469  {
470  //clear the current tree
471  mColormapTreeWidget->clear();
472 
473  QTextStream inputStream( &inputFile );
474  QString inputLine;
475  QStringList inputStringComponents;
476  QList<QgsColorRampShader::ColorRampItem> colorRampItems;
477 
478  //read through the input looking for valid data
479  while ( !inputStream.atEnd() )
480  {
481  lineCounter++;
482  inputLine = inputStream.readLine();
483  if ( !inputLine.isEmpty() )
484  {
485  if ( !inputLine.simplified().startsWith( '#' ) )
486  {
487  if ( inputLine.contains( QLatin1String( "INTERPOLATION" ), Qt::CaseInsensitive ) )
488  {
489  inputStringComponents = inputLine.split( ':' );
490  if ( inputStringComponents.size() == 2 )
491  {
492  if ( inputStringComponents[1].trimmed().toUpper().compare( QLatin1String( "INTERPOLATED" ), Qt::CaseInsensitive ) == 0 )
493  {
494  mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( QgsColorRampShader::Interpolated ) );
495  }
496  else if ( inputStringComponents[1].trimmed().toUpper().compare( QLatin1String( "DISCRETE" ), Qt::CaseInsensitive ) == 0 )
497  {
498  mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( QgsColorRampShader::Discrete ) );
499  }
500  else
501  {
502  mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( QgsColorRampShader::Exact ) );
503  }
504  }
505  else
506  {
507  importError = true;
508  badLines = badLines + QString::number( lineCounter ) + ":\t[" + inputLine + "]\n";
509  }
510  }
511  else
512  {
513  inputStringComponents = inputLine.split( ',' );
514  if ( inputStringComponents.size() == 6 )
515  {
516  QgsColorRampShader::ColorRampItem currentItem( inputStringComponents[0].toDouble(),
517  QColor::fromRgb( inputStringComponents[1].toInt(), inputStringComponents[2].toInt(),
518  inputStringComponents[3].toInt(), inputStringComponents[4].toInt() ),
519  inputStringComponents[5] );
520  colorRampItems.push_back( currentItem );
521  }
522  else
523  {
524  importError = true;
525  badLines = badLines + QString::number( lineCounter ) + ":\t[" + inputLine + "]\n";
526  }
527  }
528  }
529  }
530  lineCounter++;
531  }
532  populateColormapTreeWidget( colorRampItems );
533 
534  QFileInfo fileInfo( fileName );
535  settings.setValue( QStringLiteral( "lastColorMapDir" ), fileInfo.absoluteDir().absolutePath() );
536 
537  if ( importError )
538  {
539  QMessageBox::warning( this, tr( "Load Color Map from File" ), tr( "The following lines contained errors\n\n" ) + badLines );
540  }
541  }
542  else if ( !fileName.isEmpty() )
543  {
544  QMessageBox::warning( this, tr( "Load Color Map from File" ), tr( "Read access denied. Adjust the file permissions and try again.\n\n" ) );
545  }
546 
548  emit widgetChanged();
549 }
550 
551 void QgsSingleBandPseudoColorRendererWidget::mExportToFileButton_clicked()
552 {
553  QgsSettings settings;
554  QString lastDir = settings.value( QStringLiteral( "lastColorMapDir" ), QDir::homePath() ).toString();
555  QString fileName = QFileDialog::getSaveFileName( this, tr( "Save Color Map as File" ), lastDir, tr( "Textfile (*.txt)" ) );
556  if ( !fileName.isEmpty() )
557  {
558  if ( !fileName.endsWith( QLatin1String( ".txt" ), Qt::CaseInsensitive ) )
559  {
560  fileName = fileName + ".txt";
561  }
562 
563  QFile outputFile( fileName );
564  if ( outputFile.open( QFile::WriteOnly | QIODevice::Truncate ) )
565  {
566  QTextStream outputStream( &outputFile );
567  outputStream << "# " << tr( "QGIS Generated Color Map Export File" ) << '\n';
568  outputStream << "INTERPOLATION:";
569  QgsColorRampShader::Type interpolation = static_cast< QgsColorRampShader::Type >( mColorInterpolationComboBox->currentData().toInt() );
570  switch ( interpolation )
571  {
573  outputStream << "INTERPOLATED\n";
574  break;
576  outputStream << "DISCRETE\n";
577  break;
579  outputStream << "EXACT\n";
580  break;
581  }
582 
583  int topLevelItemCount = mColormapTreeWidget->topLevelItemCount();
584  QTreeWidgetItem *currentItem = nullptr;
585  QColor color;
586  for ( int i = 0; i < topLevelItemCount; ++i )
587  {
588  currentItem = mColormapTreeWidget->topLevelItem( i );
589  if ( !currentItem )
590  {
591  continue;
592  }
593  color = currentItem->background( ColorColumn ).color();
594  outputStream << currentItem->text( ValueColumn ).toDouble() << ',';
595  outputStream << color.red() << ',' << color.green() << ',' << color.blue() << ',' << color.alpha() << ',';
596  if ( currentItem->text( LabelColumn ).isEmpty() )
597  {
598  outputStream << "Color entry " << i + 1 << '\n';
599  }
600  else
601  {
602  outputStream << currentItem->text( LabelColumn ) << '\n';
603  }
604  }
605  outputStream.flush();
606  outputFile.close();
607 
608  QFileInfo fileInfo( fileName );
609  settings.setValue( QStringLiteral( "lastColorMapDir" ), fileInfo.absoluteDir().absolutePath() );
610  }
611  else
612  {
613  QMessageBox::warning( this, tr( "Save Color Map as File" ), tr( "Write access denied. Adjust the file permissions and try again.\n\n" ) );
614  }
615  }
616 }
617 
618 void QgsSingleBandPseudoColorRendererWidget::mColormapTreeWidget_itemDoubleClicked( QTreeWidgetItem *item, int column )
619 {
620  if ( !item )
621  {
622  return;
623  }
624 
625  if ( column == ColorColumn )
626  {
627  item->setFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable );
628  QColor newColor = QgsColorDialog::getColor( item->background( column ).color(), this, QStringLiteral( "Change Color" ), true );
629  if ( newColor.isValid() )
630  {
631  item->setBackground( ColorColumn, QBrush( newColor ) );
633  emit widgetChanged();
634  }
635  }
636  else
637  {
638  if ( column == LabelColumn )
639  {
640  // Set text color to default black, which signifies a manually edited label
641  item->setForeground( LabelColumn, QBrush() );
642  }
643  item->setFlags( Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable );
644  }
645 }
646 
647 void QgsSingleBandPseudoColorRendererWidget::mColormapTreeWidget_itemEdited( QTreeWidgetItem *item, int column )
648 {
649  Q_UNUSED( item );
650 
651  if ( column == ValueColumn )
652  {
653  mColormapTreeWidget->sortItems( ValueColumn, Qt::AscendingOrder );
654  autoLabel();
655 
657 
658  emit widgetChanged();
659  }
660  else if ( column == LabelColumn )
661  {
662  // call autoLabel to fill when empty or gray out when same as autoLabel
663  autoLabel();
664  emit widgetChanged();
665  }
666 }
667 
669 {
670  const QgsSingleBandPseudoColorRenderer *pr = dynamic_cast<const QgsSingleBandPseudoColorRenderer *>( r );
671  if ( pr )
672  {
673  mBandComboBox->setBand( pr->band() );
674  mMinMaxWidget->setBands( QList< int >() << pr->band() );
675 
676  const QgsRasterShader *rasterShader = pr->shader();
677  if ( rasterShader )
678  {
679  const QgsColorRampShader *colorRampShader = dynamic_cast<const QgsColorRampShader *>( rasterShader->rasterShaderFunction() );
680  if ( colorRampShader )
681  {
682  if ( colorRampShader->sourceColorRamp() )
683  {
684  btnColorRamp->setColorRamp( colorRampShader->sourceColorRamp() );
685  }
686  else
687  {
688  QgsSettings settings;
689  QString defaultPalette = settings.value( QStringLiteral( "/Raster/defaultPalette" ), "Spectral" ).toString();
690  btnColorRamp->setColorRampFromName( defaultPalette );
691  }
692 
693  mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( colorRampShader->colorRampType() ) );
694 
695  const QList<QgsColorRampShader::ColorRampItem> colorRampItemList = colorRampShader->colorRampItemList();
696  QList<QgsColorRampShader::ColorRampItem>::const_iterator it = colorRampItemList.constBegin();
697  for ( ; it != colorRampItemList.end(); ++it )
698  {
699  QgsTreeWidgetItemObject *newItem = new QgsTreeWidgetItemObject( mColormapTreeWidget );
700  newItem->setText( ValueColumn, QString::number( it->value, 'g', 15 ) );
701  newItem->setBackground( ColorColumn, QBrush( it->color ) );
702  newItem->setText( LabelColumn, it->label );
703  newItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable );
704  connect( newItem, &QgsTreeWidgetItemObject::itemEdited,
705  this, &QgsSingleBandPseudoColorRendererWidget::mColormapTreeWidget_itemEdited );
706  }
707  setUnitFromLabels();
708  mClipCheckBox->setChecked( colorRampShader->clip() );
709 
710  mClassificationModeComboBox->setCurrentIndex( mClassificationModeComboBox->findData( colorRampShader->classificationMode() ) );
711  mNumberOfEntriesSpinBox->setValue( colorRampShader->colorRampItemList().count() ); // some default
712  }
713  }
714  setLineEditValue( mMinLineEdit, pr->classificationMin() );
715  setLineEditValue( mMaxLineEdit, pr->classificationMax() );
716 
717  mMinMaxWidget->setFromMinMaxOrigin( pr->minMaxOrigin() );
718  }
719  else
720  {
721  mMinMaxWidget->setBands( QList< int >() << mBandComboBox->currentBand() );
722  }
723 }
724 
725 void QgsSingleBandPseudoColorRendererWidget::bandChanged()
726 {
727  QList<int> bands;
728  bands.append( mBandComboBox->currentBand() );
729  mMinMaxWidget->setBands( bands );
730 }
731 
732 void QgsSingleBandPseudoColorRendererWidget::mColorInterpolationComboBox_currentIndexChanged( int index )
733 {
734  QgsColorRampShader::Type interpolation = static_cast< QgsColorRampShader::Type >( mColorInterpolationComboBox->itemData( index ).toInt() );
735 
736  mClipCheckBox->setEnabled( interpolation == QgsColorRampShader::Interpolated );
737 
738  QString valueLabel;
739  QString valueToolTip;
740  switch ( interpolation )
741  {
743  valueLabel = tr( "Value" );
744  valueToolTip = tr( "Value for color stop" );
745  break;
747  valueLabel = tr( "Value <=" );
748  valueToolTip = tr( "Maximum value for class" );
749  break;
751  valueLabel = tr( "Value =" );
752  valueToolTip = tr( "Value for color" );
753  break;
754  }
755 
756  QTreeWidgetItem *header = mColormapTreeWidget->headerItem();
757  header->setText( ValueColumn, valueLabel );
758  header->setToolTip( ValueColumn, valueToolTip );
759 
760  autoLabel();
761  emit widgetChanged();
762 }
763 
765 {
766  Q_UNUSED( bandNo );
767  QgsDebugMsg( QString( "theBandNo = %1 min = %2 max = %3" ).arg( bandNo ).arg( min ).arg( max ) );
768 
769  double oldMin = lineEditValue( mMinLineEdit );
770  double oldMax = lineEditValue( mMaxLineEdit );
771 
772  if ( std::isnan( min ) )
773  {
774  whileBlocking( mMinLineEdit )->clear();
775  }
776  else
777  {
778  whileBlocking( mMinLineEdit )->setText( QString::number( min ) );
779  }
780 
781  if ( std::isnan( max ) )
782  {
783  whileBlocking( mMaxLineEdit )->clear();
784  }
785  else
786  {
787  whileBlocking( mMaxLineEdit )->setText( QString::number( max ) );
788  }
789 
790  if ( oldMin != min || oldMax != max )
791  {
792  classify();
793  }
794 }
795 
797 {
798  QTreeWidgetItem *item = mColormapTreeWidget->topLevelItem( 0 );
799  if ( !item )
800  {
801  return;
802  }
803 
804  double min = item->text( ValueColumn ).toDouble();
805  item = mColormapTreeWidget->topLevelItem( mColormapTreeWidget->topLevelItemCount() - 1 );
806  double max = item->text( ValueColumn ).toDouble();
807 
808  whileBlocking( mMinLineEdit )->setText( QString::number( min ) );
809  whileBlocking( mMaxLineEdit )->setText( QString::number( max ) );
810  minMaxModified();
811 }
812 
813 void QgsSingleBandPseudoColorRendererWidget::setLineEditValue( QLineEdit *lineEdit, double value )
814 {
815  QString s;
816  if ( !std::isnan( value ) )
817  {
818  s = QString::number( value );
819  }
820  lineEdit->setText( s );
821 }
822 
823 double QgsSingleBandPseudoColorRendererWidget::lineEditValue( const QLineEdit *lineEdit ) const
824 {
825  if ( lineEdit->text().isEmpty() )
826  {
827  return std::numeric_limits<double>::quiet_NaN();
828  }
829 
830  return lineEdit->text().toDouble();
831 }
832 
833 void QgsSingleBandPseudoColorRendererWidget::resetClassifyButton()
834 {
835  mClassifyButton->setEnabled( true );
836  double min = lineEditValue( mMinLineEdit );
837  double max = lineEditValue( mMaxLineEdit );
838  if ( std::isnan( min ) || std::isnan( max ) || min >= max )
839  {
840  mClassifyButton->setEnabled( false );
841  }
842 }
843 
844 void QgsSingleBandPseudoColorRendererWidget::changeColor()
845 {
846  QList<QTreeWidgetItem *> itemList;
847  itemList = mColormapTreeWidget->selectedItems();
848  if ( itemList.isEmpty() )
849  {
850  return;
851  }
852  QTreeWidgetItem *firstItem = itemList.first();
853 
854  QColor newColor = QgsColorDialog::getColor( firstItem->background( ColorColumn ).color(), this, QStringLiteral( "Change Color" ), true );
855  if ( newColor.isValid() )
856  {
857  Q_FOREACH ( QTreeWidgetItem *item, itemList )
858  {
859  item->setFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable );
860  item->setBackground( ColorColumn, QBrush( newColor ) );
861  }
862 
864  emit widgetChanged();
865  }
866 }
867 
868 void QgsSingleBandPseudoColorRendererWidget::changeOpacity()
869 {
870  QList<QTreeWidgetItem *> itemList;
871  itemList = mColormapTreeWidget->selectedItems();
872  if ( itemList.isEmpty() )
873  {
874  return;
875  }
876  QTreeWidgetItem *firstItem = itemList.first();
877 
878  bool ok;
879  double oldOpacity = firstItem->background( ColorColumn ).color().alpha() / 255 * 100;
880  double opacity = QInputDialog::getDouble( this, tr( "Opacity" ), tr( "Change color opacity [%]" ), oldOpacity, 0.0, 100.0, 0, &ok );
881  if ( ok )
882  {
883  int newOpacity = opacity / 100 * 255;
884  Q_FOREACH ( QTreeWidgetItem *item, itemList )
885  {
886  QColor newColor = item->background( ColorColumn ).color();
887  newColor.setAlpha( newOpacity );
888  item->setBackground( ColorColumn, QBrush( newColor ) );
889  }
890 
892  emit widgetChanged();
893  }
894 }
895 
896 void QgsSingleBandPseudoColorRendererWidget::mMinLineEdit_textEdited( const QString & )
897 {
898  minMaxModified();
899  classify();
900 }
901 
902 void QgsSingleBandPseudoColorRendererWidget::mMaxLineEdit_textEdited( const QString & )
903 {
904  minMaxModified();
905  classify();
906 }
907 
908 void QgsSingleBandPseudoColorRendererWidget::minMaxModified()
909 {
910  mMinMaxWidget->userHasSetManualMinMaxValues();
911 }
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:57
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.
#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
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.
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition: qgis.h:224
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.