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