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