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