QGIS API Documentation  3.10.0-A Coruña (6c816b4204)
qgsrasterlayersaveasdialog.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsrasterlayersaveasdialog.cpp
3  ---------------------
4  begin : May 2012
5  copyright : (C) 2012 by Marco Hugentobler
6  email : marco dot hugentobler at sourcepole dot ch
7  ***************************************************************************
8  * *
9  * This program is free software; you can redistribute it and/or modify *
10  * it under the terms of the GNU General Public License as published by *
11  * the Free Software Foundation; either version 2 of the License, or *
12  * (at your option) any later version. *
13  * *
14  ***************************************************************************/
15 #include "qgsapplication.h"
16 #include "qgsgdalutils.h"
17 #include "qgslogger.h"
18 #include "qgscoordinatetransform.h"
19 #include "qgsrasterlayer.h"
21 #include "qgsrasterdataprovider.h"
23 #include "qgsrasterrenderer.h"
24 #include "qgsrastertransparency.h"
26 #include "qgssettings.h"
27 #include "qgsrasterfilewriter.h"
28 #include "qgsvectorlayer.h"
29 #include "cpl_string.h"
30 #include "qgsproject.h"
31 #include <gdal.h>
32 #include "qgsmessagelog.h"
33 #include "qgsgui.h"
34 
35 #include <QFileDialog>
36 #include <QMessageBox>
37 
39  QgsRasterDataProvider *sourceProvider, const QgsRectangle &currentExtent,
40  const QgsCoordinateReferenceSystem &layerCrs, const QgsCoordinateReferenceSystem &currentCrs,
41  QWidget *parent, Qt::WindowFlags f )
42  : QDialog( parent, f )
43  , mRasterLayer( rasterLayer )
44  , mDataProvider( sourceProvider )
45  , mCurrentExtent( currentExtent )
46  , mLayerCrs( layerCrs )
47  , mCurrentCrs( currentCrs )
48  , mResolutionState( OriginalResolution )
49 {
50  setupUi( this );
52  connect( mRawModeRadioButton, &QRadioButton::toggled, this, &QgsRasterLayerSaveAsDialog::mRawModeRadioButton_toggled );
53  connect( mFormatComboBox, &QComboBox::currentTextChanged, this, &QgsRasterLayerSaveAsDialog::mFormatComboBox_currentIndexChanged );
54  connect( mResolutionRadioButton, &QRadioButton::toggled, this, &QgsRasterLayerSaveAsDialog::mResolutionRadioButton_toggled );
55  connect( mOriginalResolutionPushButton, &QPushButton::clicked, this, &QgsRasterLayerSaveAsDialog::mOriginalResolutionPushButton_clicked );
56  connect( mXResolutionLineEdit, &QLineEdit::textEdited, this, &QgsRasterLayerSaveAsDialog::mXResolutionLineEdit_textEdited );
57  connect( mYResolutionLineEdit, &QLineEdit::textEdited, this, &QgsRasterLayerSaveAsDialog::mYResolutionLineEdit_textEdited );
58  connect( mOriginalSizePushButton, &QPushButton::clicked, this, &QgsRasterLayerSaveAsDialog::mOriginalSizePushButton_clicked );
59  connect( mColumnsLineEdit, &QLineEdit::textEdited, this, &QgsRasterLayerSaveAsDialog::mColumnsLineEdit_textEdited );
60  connect( mRowsLineEdit, &QLineEdit::textEdited, this, &QgsRasterLayerSaveAsDialog::mRowsLineEdit_textEdited );
61  connect( mAddNoDataManuallyToolButton, &QPushButton::clicked, this, &QgsRasterLayerSaveAsDialog::mAddNoDataManuallyToolButton_clicked );
62  connect( mLoadTransparentNoDataToolButton, &QPushButton::clicked, this, &QgsRasterLayerSaveAsDialog::mLoadTransparentNoDataToolButton_clicked );
63  connect( mRemoveSelectedNoDataToolButton, &QPushButton::clicked, this, &QgsRasterLayerSaveAsDialog::mRemoveSelectedNoDataToolButton_clicked );
64  connect( mRemoveAllNoDataToolButton, &QPushButton::clicked, this, &QgsRasterLayerSaveAsDialog::mRemoveAllNoDataToolButton_clicked );
65  connect( mTileModeCheckBox, &QCheckBox::toggled, this, &QgsRasterLayerSaveAsDialog::mTileModeCheckBox_toggled );
66  connect( mPyramidsGroupBox, &QgsCollapsibleGroupBox::toggled, this, &QgsRasterLayerSaveAsDialog::mPyramidsGroupBox_toggled );
67  mAddNoDataManuallyToolButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/symbologyAdd.svg" ) ) );
68  mLoadTransparentNoDataToolButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionFileOpen.svg" ) ) );
69  mRemoveSelectedNoDataToolButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/symbologyRemove.svg" ) ) );
70  mRemoveAllNoDataToolButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionRemove.svg" ) ) );
71 
72  mNoDataTableWidget->setColumnCount( 2 );
73  mNoDataTableWidget->setHorizontalHeaderItem( 0, new QTableWidgetItem( tr( "From" ) ) );
74  mNoDataTableWidget->setHorizontalHeaderItem( 1, new QTableWidgetItem( tr( "To" ) ) );
75 
76  mRawModeRadioButton_toggled( true );
77 
78  setValidators();
79 
80  toggleResolutionSize();
81 
82  insertAvailableOutputFormats();
83 
84  //fill reasonable default values depending on the provider
85  if ( mDataProvider )
86  {
87  if ( mDataProvider->capabilities() & QgsRasterDataProvider::Size )
88  {
89  setOriginalResolution();
90  int xSize = mDataProvider->xSize();
91  int ySize = mDataProvider->ySize();
92  mMaximumSizeXLineEdit->setText( QString::number( xSize ) );
93  mMaximumSizeYLineEdit->setText( QString::number( ySize ) );
94  }
95  else //wms, sometimes wcs
96  {
97  mTileModeCheckBox->setChecked( true );
98  mMaximumSizeXLineEdit->setText( QString::number( 2000 ) );
99  mMaximumSizeYLineEdit->setText( QString::number( 2000 ) );
100  }
101 
102  // setup creation option widget
103  mCreateOptionsWidget->setProvider( mDataProvider->name() );
104  if ( mDataProvider->name() == QLatin1String( "gdal" ) )
105  {
106  mCreateOptionsWidget->setFormat( mFormatComboBox->currentData().toString() );
107  }
108  mCreateOptionsWidget->setRasterLayer( mRasterLayer );
109  mCreateOptionsWidget->update();
110  }
111 
112  // Only do pyramids if dealing directly with GDAL.
113  if ( mDataProvider && mDataProvider->capabilities() & QgsRasterDataProvider::BuildPyramids )
114  {
115  // setup pyramids option widget
116  // mPyramidsOptionsWidget->createOptionsWidget()->setType( QgsRasterFormatSaveOptionsWidget::ProfileLineEdit );
117  mPyramidsOptionsWidget->createOptionsWidget()->setRasterLayer( mRasterLayer );
118 
119  // TODO enable "use existing", has no effect for now, because using Create() in gdal provider
120  // if ( ! mDataProvider->hasPyramids() )
121  // mPyramidsButtonGroup->button( QgsRaster::PyramidsCopyExisting )->setEnabled( false );
122  mPyramidsUseExistingCheckBox->setEnabled( false );
123  mPyramidsUseExistingCheckBox->setVisible( false );
124 
125  populatePyramidsLevels();
126  connect( mPyramidsOptionsWidget, &QgsRasterPyramidsOptionsWidget::overviewListChanged,
127  this, &QgsRasterLayerSaveAsDialog::populatePyramidsLevels );
128  }
129  else
130  {
131  mPyramidsGroupBox->setEnabled( false );
132  }
133 
134  // restore checked state for most groupboxes (default is to restore collapsed state)
135  // create options and pyramids will be preset, if user has selected defaults in the gdal options dlg
136  mCreateOptionsGroupBox->setSaveCheckedState( true );
137  //mTilesGroupBox->setSaveCheckedState( true );
138  // don't restore nodata, it needs user input
139  // pyramids are not necessarily built every time
140 
141  mCrsSelector->setLayerCrs( mLayerCrs );
142  //default to layer CRS - see https://github.com/qgis/QGIS/issues/22211 for discussion
143  mCrsSelector->setCrs( mLayerCrs );
144 
145  connect( mCrsSelector, &QgsProjectionSelectionWidget::crsChanged,
146  this, &QgsRasterLayerSaveAsDialog::crsChanged );
147 
148  QPushButton *okButton = mButtonBox->button( QDialogButtonBox::Ok );
149  if ( okButton )
150  {
151  okButton->setEnabled( false );
152  }
153 
154 #ifdef Q_OS_WIN
155  mHelpButtonBox->setVisible( false );
156  mButtonBox->addButton( QDialogButtonBox::Help );
157  connect( mButtonBox, &QDialogButtonBox::helpRequested, this, &QgsRasterLayerSaveAsDialog::showHelp );
158 #else
159  connect( mHelpButtonBox, &QDialogButtonBox::helpRequested, this, &QgsRasterLayerSaveAsDialog::showHelp );
160 #endif
161  connect( mButtonBox, &QDialogButtonBox::accepted, this, &QgsRasterLayerSaveAsDialog::accept );
162  connect( mButtonBox, &QDialogButtonBox::rejected, this, &QgsRasterLayerSaveAsDialog::reject );
163 
164  mExtentGroupBox->setOutputCrs( outputCrs() );
165  mExtentGroupBox->setOriginalExtent( mDataProvider->extent(), mLayerCrs );
166  mExtentGroupBox->setCurrentExtent( mCurrentExtent, mCurrentCrs );
167  mExtentGroupBox->setOutputExtentFromOriginal();
168  connect( mExtentGroupBox, &QgsExtentGroupBox::extentChanged, this, &QgsRasterLayerSaveAsDialog::extentChanged );
169 
170  recalcResolutionSize();
171 
172  QgsSettings settings;
173 
174  if ( mTileModeCheckBox->isChecked() )
175  {
176  mTilesGroupBox->show();
177  mFilename->setStorageMode( QgsFileWidget::GetDirectory );
178  mFilename->setDialogTitle( tr( "Select Output Directory" ) );
179  }
180  else
181  {
182  mTilesGroupBox->hide();
183  mFilename->setStorageMode( QgsFileWidget::SaveFile );
184  mFilename->setDialogTitle( tr( "Save Layer As" ) );
185  }
186 
187  mFilename->setDefaultRoot( settings.value( QStringLiteral( "UI/lastRasterFileDir" ), QDir::homePath() ).toString() );
188  connect( mFilename, &QgsFileWidget::fileChanged, this, [ = ]( const QString & filePath )
189  {
190  QgsSettings settings;
191  QFileInfo tmplFileInfo( filePath );
192  settings.setValue( QStringLiteral( "UI/lastRasterFileDir" ), tmplFileInfo.absolutePath() );
193 
194  if ( !filePath.isEmpty() && mLayerName->isEnabled() )
195  {
196  QFileInfo fileInfo( filePath );
197  mLayerName->setText( fileInfo.baseName() );
198  }
199 
200  if ( mTileModeCheckBox->isChecked() )
201  {
202  QString fileName = filePath;
203  Q_FOREVER
204  {
205  // TODO: would not it be better to select .vrt file instead of directory?
206  //fileName = QFileDialog::getSaveFileName( this, tr( "Select output file" ), QString(), tr( "VRT" ) + " (*.vrt *.VRT)" );
207  if ( fileName.isEmpty() )
208  break; // canceled
209 
210  // Check if directory is empty
211  QDir dir( fileName );
212  QString baseName = QFileInfo( fileName ).baseName();
213  QStringList filters;
214  filters << QStringLiteral( "%1.*" ).arg( baseName );
215  QStringList files = dir.entryList( filters );
216  if ( files.isEmpty() )
217  break;
218 
219  if ( QMessageBox::warning( this, tr( "Save Raster Layer" ),
220  tr( "The directory %1 contains files which will be overwritten: %2" ).arg( dir.absolutePath(), files.join( QStringLiteral( ", " ) ) ),
221  QMessageBox::Ok | QMessageBox::Cancel ) == QMessageBox::Ok )
222  break;
223 
224  fileName = QFileDialog::getExistingDirectory( this, tr( "Select output directory" ), tmplFileInfo.absolutePath() );
225  }
226  }
227 
228  QPushButton *okButton = mButtonBox->button( QDialogButtonBox::Ok );
229  if ( !okButton )
230  {
231  return;
232  }
233  okButton->setEnabled( tmplFileInfo.absoluteDir().exists() );
234  } );
235 }
236 
237 void QgsRasterLayerSaveAsDialog::insertAvailableOutputFormats()
238 {
239  GDALAllRegister();
240 
241  int nDrivers = GDALGetDriverCount();
242  QMap< int, QPair< QString, QString > > topPriorityDrivers;
243  QMap< QString, QString > lowPriorityDrivers;
244 
245  for ( int i = 0; i < nDrivers; ++i )
246  {
247  GDALDriverH driver = GDALGetDriver( i );
248  if ( driver )
249  {
250  if ( QgsGdalUtils::supportsRasterCreate( driver ) )
251  {
252  QString driverShortName = GDALGetDriverShortName( driver );
253  QString driverLongName = GDALGetDriverLongName( driver );
254  if ( driverShortName == QLatin1String( "MEM" ) )
255  {
256  // in memory rasters are not (yet) supported because the GDAL dataset handle
257  // would need to be passed directly to QgsRasterLayer (it is not possible to
258  // close it in raster calculator and reopen the dataset again in raster layer)
259  continue;
260  }
261  else if ( driverShortName == QLatin1String( "VRT" ) )
262  {
263  // skip GDAL vrt driver, since we handle that format manually
264  continue;
265  }
266  else if ( driverShortName == QStringLiteral( "GTiff" ) )
267  {
268  // always list geotiff first
269  topPriorityDrivers.insert( 1, qMakePair( driverLongName, driverShortName ) );
270  }
271  else if ( driverShortName == QStringLiteral( "GPKG" ) )
272  {
273  // and gpkg second
274  topPriorityDrivers.insert( 2, qMakePair( driverLongName, driverShortName ) );
275  }
276  else
277  {
278  lowPriorityDrivers.insert( driverLongName, driverShortName );
279  }
280  }
281  }
282  }
283 
284  // will be sorted by priority, so that geotiff and geopackage are listed first
285  for ( auto priorityDriversIt = topPriorityDrivers.constBegin(); priorityDriversIt != topPriorityDrivers.constEnd(); ++priorityDriversIt )
286  {
287  mFormatComboBox->addItem( priorityDriversIt.value().first, priorityDriversIt.value().second );
288  }
289  // will be sorted by driver name
290  for ( auto lowPriorityDriversIt = lowPriorityDrivers.constBegin(); lowPriorityDriversIt != lowPriorityDrivers.constEnd(); ++lowPriorityDriversIt )
291  {
292  mFormatComboBox->addItem( lowPriorityDriversIt.key(), lowPriorityDriversIt.value() );
293  }
294 
295 }
296 
297 void QgsRasterLayerSaveAsDialog::setValidators()
298 {
299  mXResolutionLineEdit->setValidator( new QDoubleValidator( this ) );
300  mYResolutionLineEdit->setValidator( new QDoubleValidator( this ) );
301  mColumnsLineEdit->setValidator( new QIntValidator( this ) );
302  mRowsLineEdit->setValidator( new QIntValidator( this ) );
303  mMaximumSizeXLineEdit->setValidator( new QIntValidator( this ) );
304  mMaximumSizeYLineEdit->setValidator( new QIntValidator( this ) );
305 }
306 
307 void QgsRasterLayerSaveAsDialog::mFormatComboBox_currentIndexChanged( const QString & )
308 {
309  //gdal-specific
310  if ( mDataProvider && mDataProvider->name() == QLatin1String( "gdal" ) )
311  {
312  mCreateOptionsWidget->setFormat( outputFormat() );
313  mCreateOptionsWidget->update();
314  }
315 
316  QStringList extensions = QgsRasterFileWriter::extensionsForFormat( outputFormat() );
317  QString filter;
318  if ( extensions.empty() )
319  filter = tr( "All files (*.*)" );
320  else
321  {
322  filter = QStringLiteral( "%1 (*.%2);;%3" ).arg( mFormatComboBox->currentText(),
323  extensions.join( QStringLiteral( " *." ) ),
324  tr( "All files (*.*)" ) );
325  }
326  mFilename->setFilter( filter );
327 
328  // Disable mTileModeCheckBox for GeoPackages
329  mTileModeCheckBox->setEnabled( outputFormat() != QStringLiteral( "GPKG" ) );
330  mFilename->setConfirmOverwrite( outputFormat() != QStringLiteral( "GPKG" ) );
331  mLayerName->setEnabled( outputFormat() == QStringLiteral( "GPKG" ) );
332  if ( mLayerName->isEnabled() )
333  {
334  QString layerName = QFileInfo( mFilename->filePath() ).baseName();
335  mLayerName->setText( layerName );
336  mTileModeCheckBox->setChecked( false );
337  }
338  else
339  {
340  mLayerName->setText( QString() );
341  }
342 }
343 
345 {
346  return mColumnsLineEdit->text().toInt();
347 }
348 
350 {
351  return mRowsLineEdit->text().toInt();
352 }
353 
355 {
356  return mXResolutionLineEdit->text().toDouble();
357 }
358 
360 {
361  return mYResolutionLineEdit->text().toDouble();
362 }
363 
365 {
366  return mMaximumSizeXLineEdit->text().toInt();
367 }
368 
370 {
371  return mMaximumSizeYLineEdit->text().toInt();
372 }
373 
375 {
376  return mTileModeCheckBox->isChecked();
377 }
378 
380 {
381  return mAddToCanvas->isChecked();
382 }
383 
385 {
386  mAddToCanvas->setChecked( checked );
387 }
388 
390 {
391  QString fileName = mFilename->filePath();
392 
393  if ( mFilename->storageMode() != QgsFileWidget::GetDirectory )
394  {
395  QStringList extensions = QgsRasterFileWriter::extensionsForFormat( outputFormat() );
396  QString defaultExt;
397  if ( !extensions.empty() )
398  {
399  defaultExt = extensions.at( 0 );
400  }
401 
402  // ensure the user never omits the extension from the file name
403  QFileInfo fi( fileName );
404  if ( !fileName.isEmpty() && fi.suffix().isEmpty() )
405  {
406  fileName += '.' + defaultExt;
407  }
408  }
409 
410  return fileName;
411 }
412 
414 {
415  if ( mLayerName->text().isEmpty() && outputFormat() == QStringLiteral( "GPKG" ) && !mTileModeCheckBox->isChecked() )
416  {
417  // Always return layer name for GeoPackages
418  return QFileInfo( mFilename->filePath() ).baseName();
419  }
420  else
421  {
422  return mLayerName->text();
423  }
424 }
425 
427 {
428  return mFormatComboBox->currentData().toString();
429 }
430 
432 {
433  QStringList options = mCreateOptionsGroupBox->isChecked() ? mCreateOptionsWidget->options() : QStringList();
434  if ( outputFormat() == QStringLiteral( "GPKG" ) )
435  {
436  // Overwrite the GPKG table options
437  int indx = options.indexOf( QRegularExpression( "^RASTER_TABLE=.*", QRegularExpression::CaseInsensitiveOption | QRegularExpression::MultilineOption ) );
438  if ( indx > -1 )
439  {
440  options.replace( indx, QStringLiteral( "RASTER_TABLE=%1" ).arg( outputLayerName() ) );
441  }
442  else
443  {
444  options.append( QStringLiteral( "RASTER_TABLE=%1" ).arg( outputLayerName() ) );
445  }
446 
447  // Only enable the append mode if the layer doesn't exist yet. For existing layers a 'confirm overwrite' dialog will be shown.
448  if ( !outputLayerExists() )
449  {
450  indx = options.indexOf( QRegularExpression( "^APPEND_SUBDATASET=.*", QRegularExpression::CaseInsensitiveOption | QRegularExpression::MultilineOption ) );
451  if ( indx > -1 )
452  {
453  options.replace( indx, QStringLiteral( "APPEND_SUBDATASET=YES" ) );
454  }
455  else
456  {
457  options.append( QStringLiteral( "APPEND_SUBDATASET=YES" ) );
458  }
459  }
460  }
461  return options;
462 }
463 
465 {
466  return mExtentGroupBox->outputExtent();
467 }
468 
470 {
471  mFormatLabel->hide();
472  mFormatComboBox->hide();
473 }
474 
476 {
477  mSaveAsLabel->hide();
478  mFilename->hide();
479  QPushButton *okButton = mButtonBox->button( QDialogButtonBox::Ok );
480  if ( okButton )
481  {
482  okButton->setEnabled( true );
483  }
484 }
485 
486 void QgsRasterLayerSaveAsDialog::toggleResolutionSize()
487 {
488  bool hasResolution = mDataProvider && mDataProvider->capabilities() & QgsRasterDataProvider::Size;
489 
490  bool on = mResolutionRadioButton->isChecked();
491  mXResolutionLineEdit->setEnabled( on );
492  mYResolutionLineEdit->setEnabled( on );
493  mOriginalResolutionPushButton->setEnabled( on && hasResolution );
494  mColumnsLineEdit->setEnabled( !on );
495  mRowsLineEdit->setEnabled( !on );
496  mOriginalSizePushButton->setEnabled( !on && hasResolution );
497 }
498 
499 void QgsRasterLayerSaveAsDialog::setOriginalResolution()
500 {
501  double xRes, yRes;
502 
503  if ( mDataProvider->capabilities() & QgsRasterDataProvider::Size )
504  {
505  xRes = mDataProvider->extent().width() / mDataProvider->xSize();
506  yRes = mDataProvider->extent().height() / mDataProvider->ySize();
507  }
508  else
509  {
510  // Init to something if no original resolution is available
511  xRes = yRes = mDataProvider->extent().width() / 100;
512  }
513  setResolution( xRes, yRes, mLayerCrs );
514  mResolutionState = OriginalResolution;
515  recalcSize();
516 }
517 
518 void QgsRasterLayerSaveAsDialog::setResolution( double xRes, double yRes, const QgsCoordinateReferenceSystem &srcCrs )
519 {
520  if ( srcCrs != outputCrs() )
521  {
522  // We reproject pixel rectangle from center of selected extent, of course, it gives
523  // bigger xRes,yRes than reprojected edges (envelope), it may also be that
524  // close to margins are higher resolutions (even very, too high)
525  // TODO: consider more precise resolution calculation
526 
527  QgsPointXY center = outputRectangle().center();
530 
531  QgsRectangle srcExtent( srsCenter.x() - xRes / 2, srsCenter.y() - yRes / 2, srsCenter.x() + xRes / 2, srsCenter.y() + yRes / 2 );
532 
533  QgsRectangle extent = ct.transform( srcExtent );
534  xRes = extent.width();
535  yRes = extent.height();
536  }
537  mXResolutionLineEdit->setText( QString::number( xRes ) );
538  mYResolutionLineEdit->setText( QString::number( yRes ) );
539 }
540 
541 void QgsRasterLayerSaveAsDialog::recalcSize()
542 {
543  QgsRectangle extent = outputRectangle();
544  int xSize = xResolution() != 0 ? static_cast<int>( std::round( extent.width() / xResolution() ) ) : 0;
545  int ySize = yResolution() != 0 ? static_cast<int>( std::round( extent.height() / yResolution() ) ) : 0;
546  mColumnsLineEdit->setText( QString::number( xSize ) );
547  mRowsLineEdit->setText( QString::number( ySize ) );
548  updateResolutionStateMsg();
549 }
550 
551 void QgsRasterLayerSaveAsDialog::setOriginalSize()
552 {
553  mColumnsLineEdit->setText( QString::number( mDataProvider->xSize() ) );
554  mRowsLineEdit->setText( QString::number( mDataProvider->ySize() ) );
555  recalcResolution();
556 }
557 
558 void QgsRasterLayerSaveAsDialog::recalcResolution()
559 {
560  QgsRectangle extent = outputRectangle();
561  double xRes = nColumns() != 0 ? extent.width() / nColumns() : 0;
562  double yRes = nRows() != 0 ? extent.height() / nRows() : 0;
563  mXResolutionLineEdit->setText( QString::number( xRes ) );
564  mYResolutionLineEdit->setText( QString::number( yRes ) );
565  updateResolutionStateMsg();
566 }
567 
568 void QgsRasterLayerSaveAsDialog::recalcResolutionSize()
569 {
570  if ( mResolutionRadioButton->isChecked() )
571  {
572  recalcSize();
573  }
574  else
575  {
576  mResolutionState = UserResolution;
577  recalcResolution();
578  }
579 }
580 
581 void QgsRasterLayerSaveAsDialog::updateResolutionStateMsg()
582 {
583  QString msg;
584  switch ( mResolutionState )
585  {
586  case OriginalResolution:
587  msg = tr( "layer" );
588  break;
589  case UserResolution:
590  msg = tr( "user defined" );
591  break;
592  default:
593  break;
594  }
595  msg = tr( "Resolution (current: %1)" ).arg( msg );
596  mResolutionGroupBox->setTitle( msg );
597 }
598 
599 void QgsRasterLayerSaveAsDialog::extentChanged()
600 {
601  // Whenever extent changes with fixed size, original resolution is lost
602  if ( mSizeRadioButton->isChecked() )
603  {
604  mResolutionState = UserResolution;
605  }
606  recalcResolutionSize();
607 }
608 
609 void QgsRasterLayerSaveAsDialog::crsChanged()
610 {
611  if ( outputCrs() != mPreviousCrs )
612  {
613  mExtentGroupBox->setOutputCrs( outputCrs() );
614 
615  // Reset resolution
616  if ( mResolutionRadioButton->isChecked() )
617  {
618  if ( mResolutionState == OriginalResolution )
619  {
620  setOriginalResolution();
621  }
622  else
623  {
624  // reset from present resolution and present crs
625  setResolution( xResolution(), yResolution(), mPreviousCrs );
626  }
627  }
628  else
629  {
630  // Size does not change, we just recalc resolution from new extent
631  recalcResolution();
632  }
633  }
634  mPreviousCrs = outputCrs();
635 }
636 
638 {
639  return mCrsSelector->crs();
640 }
641 
643 {
644  if ( mRenderedModeRadioButton->isChecked() ) return RenderedImageMode;
645  return RawDataMode;
646 }
647 
648 void QgsRasterLayerSaveAsDialog::mRawModeRadioButton_toggled( bool checked )
649 {
650  mNoDataGroupBox->setEnabled( checked && mDataProvider->bandCount() == 1 );
651 }
652 
653 void QgsRasterLayerSaveAsDialog::mAddNoDataManuallyToolButton_clicked()
654 {
655  addNoDataRow( std::numeric_limits<double>::quiet_NaN(), std::numeric_limits<double>::quiet_NaN() );
656 }
657 
658 void QgsRasterLayerSaveAsDialog::mLoadTransparentNoDataToolButton_clicked()
659 {
660  if ( !mRasterLayer->renderer() ) return;
661  const QgsRasterTransparency *rasterTransparency = mRasterLayer->renderer()->rasterTransparency();
662  if ( !rasterTransparency ) return;
663 
664  const auto constTransparentSingleValuePixelList = rasterTransparency->transparentSingleValuePixelList();
665  for ( const QgsRasterTransparency::TransparentSingleValuePixel &transparencyPixel : constTransparentSingleValuePixelList )
666  {
667  if ( transparencyPixel.percentTransparent == 100 )
668  {
669  addNoDataRow( transparencyPixel.min, transparencyPixel.max );
670  if ( transparencyPixel.min != transparencyPixel.max )
671  {
672  setNoDataToEdited( mNoDataTableWidget->rowCount() - 1 );
673  }
674  }
675  }
676 }
677 
678 void QgsRasterLayerSaveAsDialog::mRemoveSelectedNoDataToolButton_clicked()
679 {
680  mNoDataTableWidget->removeRow( mNoDataTableWidget->currentRow() );
681 }
682 
683 void QgsRasterLayerSaveAsDialog::mRemoveAllNoDataToolButton_clicked()
684 {
685  while ( mNoDataTableWidget->rowCount() > 0 )
686  {
687  mNoDataTableWidget->removeRow( 0 );
688  }
689 }
690 
691 void QgsRasterLayerSaveAsDialog::addNoDataRow( double min, double max )
692 {
693  mNoDataTableWidget->insertRow( mNoDataTableWidget->rowCount() );
694  for ( int i = 0; i < 2; i++ )
695  {
696  double value = i == 0 ? min : max;
697  QLineEdit *lineEdit = new QLineEdit();
698  lineEdit->setFrame( false );
699  lineEdit->setContentsMargins( 1, 1, 1, 1 );
700  QString valueString;
701  switch ( mRasterLayer->dataProvider()->sourceDataType( 1 ) )
702  {
703  case Qgis::Float32:
704  case Qgis::Float64:
705  lineEdit->setValidator( new QDoubleValidator( nullptr ) );
706  if ( !std::isnan( value ) )
707  {
708  valueString = QgsRasterBlock::printValue( value );
709  }
710  break;
711  default:
712  lineEdit->setValidator( new QIntValidator( nullptr ) );
713  if ( !std::isnan( value ) )
714  {
715  valueString = QString::number( static_cast<int>( value ) );
716  }
717  break;
718  }
719  lineEdit->setText( valueString );
720  mNoDataTableWidget->setCellWidget( mNoDataTableWidget->rowCount() - 1, i, lineEdit );
721 
722  adjustNoDataCellWidth( mNoDataTableWidget->rowCount() - 1, i );
723 
724  connect( lineEdit, &QLineEdit::textEdited, this, &QgsRasterLayerSaveAsDialog::noDataCellTextEdited );
725  }
726  mNoDataTableWidget->resizeColumnsToContents();
727  mNoDataTableWidget->resizeRowsToContents();
728 }
729 
730 void QgsRasterLayerSaveAsDialog::noDataCellTextEdited( const QString &text )
731 {
732  Q_UNUSED( text )
733 
734  QLineEdit *lineEdit = qobject_cast<QLineEdit *>( sender() );
735  if ( !lineEdit ) return;
736  int row = -1;
737  int column = -1;
738  for ( int r = 0; r < mNoDataTableWidget->rowCount(); r++ )
739  {
740  for ( int c = 0; c < mNoDataTableWidget->columnCount(); c++ )
741  {
742  if ( mNoDataTableWidget->cellWidget( r, c ) == sender() )
743  {
744  row = r;
745  column = c;
746  break;
747  }
748  }
749  if ( row != -1 ) break;
750  }
751  QgsDebugMsg( QStringLiteral( "row = %1 column =%2" ).arg( row ).arg( column ) );
752 
753  if ( column == 0 )
754  {
755  QLineEdit *toLineEdit = dynamic_cast<QLineEdit *>( mNoDataTableWidget->cellWidget( row, 1 ) );
756  if ( !toLineEdit ) return;
757  bool toChanged = mNoDataToEdited.value( row );
758  QgsDebugMsg( QStringLiteral( "toChanged = %1" ).arg( toChanged ) );
759  if ( !toChanged )
760  {
761  toLineEdit->setText( lineEdit->text() );
762  }
763  }
764  else if ( column == 1 )
765  {
766  setNoDataToEdited( row );
767  }
768 }
769 
770 void QgsRasterLayerSaveAsDialog::mTileModeCheckBox_toggled( bool toggled )
771 {
772  if ( toggled )
773  {
774  // enable pyramids
775 
776  // Disabled (Radim), auto enabling of pyramids was making impression that
777  // we (programmers) know better what you (user) want to do,
778  // certainly auto expanding was a bad experience
779 
780  //if ( ! mPyramidsGroupBox->isChecked() )
781  // mPyramidsGroupBox->setChecked( true );
782 
783  // Auto expanding mPyramidsGroupBox is bad - it auto scrolls content of dialog
784  //if ( mPyramidsGroupBox->isCollapsed() )
785  // mPyramidsGroupBox->setCollapsed( false );
786  //mPyramidsOptionsWidget->checkAllLevels( true );
787 
788  // Show / hide tile options
789  mTilesGroupBox->show();
790  mFilename->setStorageMode( QgsFileWidget::GetDirectory );
791  mFilename->setDialogTitle( tr( "Select Output Directory" ) );
792  }
793  else
794  {
795  mTilesGroupBox->hide();
796  mFilename->setStorageMode( QgsFileWidget::SaveFile );
797  mFilename->setDialogTitle( tr( "Save Layer As" ) );
798  }
799 }
800 
801 void QgsRasterLayerSaveAsDialog::mPyramidsGroupBox_toggled( bool toggled )
802 {
803  Q_UNUSED( toggled )
804  populatePyramidsLevels();
805 }
806 
807 void QgsRasterLayerSaveAsDialog::populatePyramidsLevels()
808 {
809  QString text;
810 
811  if ( mPyramidsGroupBox->isChecked() )
812  {
813  QList<QgsRasterPyramid> myPyramidList;
814  // if use existing, get pyramids from actual layer
815  // but that's not available yet
816  if ( mPyramidsUseExistingCheckBox->isChecked() )
817  {
818  myPyramidList = mDataProvider->buildPyramidList();
819  }
820  else
821  {
822  if ( ! mPyramidsOptionsWidget->overviewList().isEmpty() )
823  myPyramidList = mDataProvider->buildPyramidList( mPyramidsOptionsWidget->overviewList() );
824  }
825  QList<QgsRasterPyramid>::iterator myRasterPyramidIterator;
826  for ( myRasterPyramidIterator = myPyramidList.begin();
827  myRasterPyramidIterator != myPyramidList.end();
828  ++myRasterPyramidIterator )
829  {
830  if ( ! mPyramidsUseExistingCheckBox->isChecked() || myRasterPyramidIterator->exists )
831  {
832  text += QString::number( myRasterPyramidIterator->xDim ) + QStringLiteral( "x" ) +
833  QString::number( myRasterPyramidIterator->yDim ) + ' ';
834  }
835  }
836  }
837 
838  mPyramidResolutionsLineEdit->setText( text.trimmed() );
839 }
840 
841 void QgsRasterLayerSaveAsDialog::setNoDataToEdited( int row )
842 {
843  if ( row >= mNoDataToEdited.size() )
844  {
845  mNoDataToEdited.resize( row + 1 );
846  }
847  mNoDataToEdited[row] = true;
848 }
849 
850 double QgsRasterLayerSaveAsDialog::noDataCellValue( int row, int column ) const
851 {
852  QLineEdit *lineEdit = dynamic_cast<QLineEdit *>( mNoDataTableWidget->cellWidget( row, column ) );
853  if ( !lineEdit || lineEdit->text().isEmpty() )
854  {
855  std::numeric_limits<double>::quiet_NaN();
856  }
857  return lineEdit->text().toDouble();
858 }
859 
860 void QgsRasterLayerSaveAsDialog::adjustNoDataCellWidth( int row, int column )
861 {
862  QLineEdit *lineEdit = dynamic_cast<QLineEdit *>( mNoDataTableWidget->cellWidget( row, column ) );
863  if ( !lineEdit ) return;
864 
865  int width = std::max( lineEdit->fontMetrics().width( lineEdit->text() ) + 10, 100 );
866  width = std::max( width, mNoDataTableWidget->columnWidth( column ) );
867 
868  lineEdit->setFixedWidth( width );
869 }
870 
872 {
873  QgsRasterRangeList noDataList;
874  if ( ! mNoDataGroupBox->isChecked() )
875  return noDataList;
876 
877  int rows = mNoDataTableWidget->rowCount();
878  noDataList.reserve( rows );
879  for ( int r = 0; r < rows; r++ )
880  {
881  QgsRasterRange noData( noDataCellValue( r, 0 ), noDataCellValue( r, 1 ) );
882  noDataList.append( noData );
883 
884  }
885  return noDataList;
886 }
887 
889 {
890  return mPyramidsGroupBox->isChecked() ? mPyramidsOptionsWidget->overviewList() : QList<int>();
891 }
892 
894 {
895  if ( ! mPyramidsGroupBox->isChecked() )
897  else if ( mPyramidsUseExistingCheckBox->isChecked() )
899  else
901 }
902 
903 bool QgsRasterLayerSaveAsDialog::validate() const
904 {
905  if ( mCreateOptionsGroupBox->isChecked() )
906  {
907  QString message = mCreateOptionsWidget->validateOptions( true, false );
908  if ( !message.isNull() )
909  return false;
910  }
911  if ( mPyramidsGroupBox->isChecked() )
912  {
913  QString message = mPyramidsOptionsWidget->createOptionsWidget()->validateOptions( true, false );
914  if ( !message.isNull() )
915  return false;
916  }
917  return true;
918 }
919 
920 bool QgsRasterLayerSaveAsDialog::outputLayerExists() const
921 {
922  QString vectorUri;
923  QString rasterUri;
924  if ( outputFormat() == QStringLiteral( "GPKG" ) )
925  {
926  rasterUri = QStringLiteral( "GPKG:%1:%2" ).arg( outputFileName(), outputLayerName() );
927  vectorUri = QStringLiteral( "%1|layername=%2" ).arg( outputFileName(), outputLayerName() );
928  }
929  else
930  {
931  rasterUri = outputFileName();
932  }
933 
934  QgsRasterLayer rasterLayer( rasterUri, QString( ), QStringLiteral( "gdal" ) );
935  if ( !vectorUri.isEmpty() )
936  {
937  QgsVectorLayer vectorLayer( vectorUri, QString( ), QStringLiteral( "ogr" ) );
938  return rasterLayer.isValid() || vectorLayer.isValid();
939  }
940  else
941  {
942  return rasterLayer.isValid();
943  }
944 }
945 
947 {
948  if ( !validate() )
949  {
950  return;
951  }
952 
953  if ( outputFormat() == QStringLiteral( "GPKG" ) && outputLayerExists() &&
954  QMessageBox::warning( this, tr( "Save Raster Layer" ),
955  tr( "The layer %1 already exists in the target file, and overwriting layers in GeoPackage is not supported. "
956  "Do you want to overwrite the whole file?" ).arg( outputLayerName() ),
957  QMessageBox::Yes | QMessageBox::No ) == QMessageBox::No )
958  {
959  return;
960  }
961 
962  QDialog::accept();
963 }
964 
965 void QgsRasterLayerSaveAsDialog::showHelp()
966 {
967  QgsHelp::openHelp( QStringLiteral( "managing_data_source/create_layers.html#save-layer-from-an-existing-file" ) );
968 }
virtual int bandCount() const =0
Gets number of bands.
A rectangle specified with double values.
Definition: qgsrectangle.h:41
QString outputLayerName() const
Name of the output layer within GeoPackage file.
static QString printValue(double value)
Print double value with all necessary significant digits.
QgsRaster::RasterBuildPyramids buildPyramidsFlag() const
void extentChanged(const QgsRectangle &r)
Emitted when the widget&#39;s extent is changed.
This class is a composition of two QSettings instances:
Definition: qgssettings.h:58
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.
Select a directory.
Definition: qgsfilewidget.h:66
QgsPointXY transform(const QgsPointXY &point, TransformDirection direction=ForwardTransform) const SIP_THROW(QgsCsException)
Transform the point from the source CRS to the destination CRS.
double y
Definition: qgspointxy.h:48
A class to represent a 2D point.
Definition: qgspointxy.h:43
virtual int ySize() const
static QStringList extensionsForFormat(const QString &format)
Returns a list of known file extensions for the given GDAL driver format.
Qgis::DataType sourceDataType(int bandNo) const override=0
Returns source data type for the band specified by number, source data type may be shorter than dataT...
static QIcon getThemeIcon(const QString &name)
Helper to get a theme icon.
Raster values range container.
QgsCoordinateReferenceSystem outputCrs()
QgsRasterRenderer * renderer() const
Returns the raster&#39;s renderer.
Thirty two bit floating point (float)
Definition: qgis.h:88
static QgsGui * instance()
Returns a pointer to the singleton instance.
Definition: qgsgui.cpp:61
bool isValid() const
Returns the status of the layer.
static bool supportsRasterCreate(GDALDriverH driver)
Reads whether a driver supports GDALCreate() for raster purposes.
const QgsRasterTransparency * rasterTransparency() const
virtual QString name() const =0
Returns a provider name.
Sixty four bit floating point (double)
Definition: qgis.h:89
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
QgsRasterDataProvider * dataProvider() override
Returns the source data provider.
virtual QList< QgsRasterPyramid > buildPyramidList(QList< int > overviewList=QList< int >())
Returns the raster layers pyramid list.
virtual int capabilities() const
Returns a bitmask containing the supported capabilities.
QgsRectangle extent() const override=0
Returns the extent of the layer.
double width() const
Returns the width of the rectangle.
Definition: qgsrectangle.h:202
void fileChanged(const QString &path)
Emitted whenever the current file or directory path is changed.
bool addToCanvas() const
Returns true if the "add to canvas" checkbox is checked.
double x
Definition: qgspointxy.h:47
QList< QgsRasterTransparency::TransparentSingleValuePixel > transparentSingleValuePixelList() const
Returns the transparent single value pixel list.
QgsRasterLayerSaveAsDialog(QgsRasterLayer *rasterLayer, QgsRasterDataProvider *sourceProvider, const QgsRectangle &currentExtent, const QgsCoordinateReferenceSystem &layerCrs, const QgsCoordinateReferenceSystem &currentCrs, QWidget *parent=nullptr, Qt::WindowFlags f=nullptr)
Constructor for QgsRasterLayerSaveAsDialog.
Transform from destination to source CRS.
QList< QgsRasterRange > QgsRasterRangeList
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
void crsChanged(const QgsCoordinateReferenceSystem &)
Emitted when the selected CRS is changed.
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:442
This class represents a coordinate reference system (CRS).
Select a single new or pre-existing file.
Definition: qgsfilewidget.h:68
static void enableAutoGeometryRestore(QWidget *widget, const QString &key=QString())
Register the widget to allow its position to be automatically saved and restored when open and closed...
Definition: qgsgui.cpp:127
static void openHelp(const QString &key)
Opens help topic for the given help key using default system web browser.
Definition: qgshelp.cpp:36
Class for doing transforms between two map coordinate systems.
RasterBuildPyramids
Definition: qgsraster.h:74
Defines the list of pixel values to be considered as transparent or semi transparent when rendering r...
QgsPointXY center() const
Returns the center point of the rectangle.
Definition: qgsrectangle.h:230
Represents a vector layer which manages a vector based data sets.
virtual int xSize() const
Gets raster size.
double height() const
Returns the height of the rectangle.
Definition: qgsrectangle.h:209
void setAddToCanvas(bool checked)
Sets whether the "add to canvas" checkbox should be checked.
Base class for raster data providers.