QGIS API Documentation  3.4.15-Madeira (e83d02e274)
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://issues.qgis.org/issues/14209 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  connect( mButtonBox, &QDialogButtonBox::helpRequested, this, &QgsRasterLayerSaveAsDialog::showHelp );
153  connect( mButtonBox, &QDialogButtonBox::accepted, this, &QgsRasterLayerSaveAsDialog::accept );
154  connect( mButtonBox, &QDialogButtonBox::rejected, this, &QgsRasterLayerSaveAsDialog::reject );
155 
156  mExtentGroupBox->setOutputCrs( outputCrs() );
157  mExtentGroupBox->setOriginalExtent( mDataProvider->extent(), mLayerCrs );
158  mExtentGroupBox->setCurrentExtent( mCurrentExtent, mCurrentCrs );
159  mExtentGroupBox->setOutputExtentFromOriginal();
160  connect( mExtentGroupBox, &QgsExtentGroupBox::extentChanged, this, &QgsRasterLayerSaveAsDialog::extentChanged );
161 
162  recalcResolutionSize();
163 
164  QgsSettings settings;
165  restoreGeometry( settings.value( QStringLiteral( "Windows/RasterLayerSaveAs/geometry" ) ).toByteArray() );
166 
167  if ( mTileModeCheckBox->isChecked() )
168  {
169  mTilesGroupBox->show();
170  mFilename->setStorageMode( QgsFileWidget::GetDirectory );
171  mFilename->setDialogTitle( tr( "Select Output Directory" ) );
172  }
173  else
174  {
175  mTilesGroupBox->hide();
176  mFilename->setStorageMode( QgsFileWidget::SaveFile );
177  mFilename->setDialogTitle( tr( "Save Layer As" ) );
178  }
179 
180  mFilename->setDefaultRoot( settings.value( QStringLiteral( "UI/lastRasterFileDir" ), QDir::homePath() ).toString() );
181  connect( mFilename, &QgsFileWidget::fileChanged, this, [ = ]( const QString & filePath )
182  {
183  QgsSettings settings;
184  QFileInfo tmplFileInfo( filePath );
185  settings.setValue( QStringLiteral( "UI/lastRasterFileDir" ), tmplFileInfo.absolutePath() );
186 
187  if ( !filePath.isEmpty() && mLayerName->isEnabled() )
188  {
189  QFileInfo fileInfo( filePath );
190  mLayerName->setText( fileInfo.baseName() );
191  }
192 
193  if ( mTileModeCheckBox->isChecked() )
194  {
195  QString fileName = filePath;
196  Q_FOREVER
197  {
198  // TODO: would not it be better to select .vrt file instead of directory?
199  //fileName = QFileDialog::getSaveFileName( this, tr( "Select output file" ), QString(), tr( "VRT" ) + " (*.vrt *.VRT)" );
200  if ( fileName.isEmpty() )
201  break; // canceled
202 
203  // Check if directory is empty
204  QDir dir( fileName );
205  QString baseName = QFileInfo( fileName ).baseName();
206  QStringList filters;
207  filters << QStringLiteral( "%1.*" ).arg( baseName );
208  QStringList files = dir.entryList( filters );
209  if ( files.isEmpty() )
210  break;
211 
212  if ( QMessageBox::warning( this, tr( "Save Raster Layer" ),
213  tr( "The directory %1 contains files which will be overwritten: %2" ).arg( dir.absolutePath(), files.join( QStringLiteral( ", " ) ) ),
214  QMessageBox::Ok | QMessageBox::Cancel ) == QMessageBox::Ok )
215  break;
216 
217  fileName = QFileDialog::getExistingDirectory( this, tr( "Select output directory" ), tmplFileInfo.absolutePath() );
218  }
219  }
220 
221  QPushButton *okButton = mButtonBox->button( QDialogButtonBox::Ok );
222  if ( !okButton )
223  {
224  return;
225  }
226  okButton->setEnabled( tmplFileInfo.absoluteDir().exists() );
227  } );
228 }
229 
231 {
232  QgsSettings settings;
233  settings.setValue( QStringLiteral( "Windows/RasterLayerSaveAs/geometry" ), saveGeometry() );
234 }
235 
236 void QgsRasterLayerSaveAsDialog::insertAvailableOutputFormats()
237 {
238  GDALAllRegister();
239 
240  int nDrivers = GDALGetDriverCount();
241  QMap< int, QPair< QString, QString > > topPriorityDrivers;
242  QMap< QString, QString > lowPriorityDrivers;
243 
244  for ( int i = 0; i < nDrivers; ++i )
245  {
246  GDALDriverH driver = GDALGetDriver( i );
247  if ( driver )
248  {
249  if ( QgsGdalUtils::supportsRasterCreate( driver ) )
250  {
251  QString driverShortName = GDALGetDriverShortName( driver );
252  QString driverLongName = GDALGetDriverLongName( driver );
253  if ( driverShortName == QLatin1String( "MEM" ) )
254  {
255  // in memory rasters are not (yet) supported because the GDAL dataset handle
256  // would need to be passed directly to QgsRasterLayer (it is not possible to
257  // close it in raster calculator and reopen the dataset again in raster layer)
258  continue;
259  }
260  else if ( driverShortName == QLatin1String( "VRT" ) )
261  {
262  // skip GDAL vrt driver, since we handle that format manually
263  continue;
264  }
265  else if ( driverShortName == QStringLiteral( "GTiff" ) )
266  {
267  // always list geotiff first
268  topPriorityDrivers.insert( 1, qMakePair( driverLongName, driverShortName ) );
269  }
270  else if ( driverShortName == QStringLiteral( "GPKG" ) )
271  {
272  // and gpkg second
273  topPriorityDrivers.insert( 2, qMakePair( driverLongName, driverShortName ) );
274  }
275  else
276  {
277  lowPriorityDrivers.insert( driverLongName, driverShortName );
278  }
279  }
280  }
281  }
282 
283  // will be sorted by priority, so that geotiff and geopackage are listed first
284  for ( auto priorityDriversIt = topPriorityDrivers.constBegin(); priorityDriversIt != topPriorityDrivers.constEnd(); ++priorityDriversIt )
285  {
286  mFormatComboBox->addItem( priorityDriversIt.value().first, priorityDriversIt.value().second );
287  }
288  // will be sorted by driver name
289  for ( auto lowPriorityDriversIt = lowPriorityDrivers.constBegin(); lowPriorityDriversIt != lowPriorityDrivers.constEnd(); ++lowPriorityDriversIt )
290  {
291  mFormatComboBox->addItem( lowPriorityDriversIt.key(), lowPriorityDriversIt.value() );
292  }
293 
294 }
295 
296 void QgsRasterLayerSaveAsDialog::setValidators()
297 {
298  mXResolutionLineEdit->setValidator( new QDoubleValidator( this ) );
299  mYResolutionLineEdit->setValidator( new QDoubleValidator( this ) );
300  mColumnsLineEdit->setValidator( new QIntValidator( this ) );
301  mRowsLineEdit->setValidator( new QIntValidator( this ) );
302  mMaximumSizeXLineEdit->setValidator( new QIntValidator( this ) );
303  mMaximumSizeYLineEdit->setValidator( new QIntValidator( this ) );
304 }
305 
306 void QgsRasterLayerSaveAsDialog::mFormatComboBox_currentIndexChanged( const QString & )
307 {
308  //gdal-specific
309  if ( mDataProvider && mDataProvider->name() == QLatin1String( "gdal" ) )
310  {
311  mCreateOptionsWidget->setFormat( outputFormat() );
312  mCreateOptionsWidget->update();
313  }
314 
315  QStringList extensions = QgsRasterFileWriter::extensionsForFormat( outputFormat() );
316  QString filter;
317  if ( extensions.empty() )
318  filter = tr( "All files (*.*)" );
319  else
320  {
321  filter = QStringLiteral( "%1 (*.%2);;%3" ).arg( mFormatComboBox->currentText(),
322  extensions.join( QStringLiteral( " *." ) ),
323  tr( "All files (*.*)" ) );
324  }
325  mFilename->setFilter( filter );
326 
327  // Disable mTileModeCheckBox for GeoPackages
328  mTileModeCheckBox->setEnabled( outputFormat() != QStringLiteral( "GPKG" ) );
329  mFilename->setConfirmOverwrite( outputFormat() != QStringLiteral( "GPKG" ) );
330  mLayerName->setEnabled( outputFormat() == QStringLiteral( "GPKG" ) );
331  if ( mLayerName->isEnabled() )
332  {
333  QString layerName = QFileInfo( mFilename->filePath() ).baseName();
334  mLayerName->setText( layerName );
335  mTileModeCheckBox->setChecked( false );
336  }
337  else
338  {
339  mLayerName->setText( QString() );
340  }
341 }
342 
344 {
345  return mColumnsLineEdit->text().toInt();
346 }
347 
349 {
350  return mRowsLineEdit->text().toInt();
351 }
352 
354 {
355  return mXResolutionLineEdit->text().toDouble();
356 }
357 
359 {
360  return mYResolutionLineEdit->text().toDouble();
361 }
362 
364 {
365  return mMaximumSizeXLineEdit->text().toInt();
366 }
367 
369 {
370  return mMaximumSizeYLineEdit->text().toInt();
371 }
372 
374 {
375  return mTileModeCheckBox->isChecked();
376 }
377 
379 {
380  return mAddToCanvas->isChecked();
381 }
382 
384 {
385  QString fileName = mFilename->filePath();
386 
387  if ( mFilename->storageMode() != QgsFileWidget::GetDirectory )
388  {
389  QStringList extensions = QgsRasterFileWriter::extensionsForFormat( outputFormat() );
390  QString defaultExt;
391  if ( !extensions.empty() )
392  {
393  defaultExt = extensions.at( 0 );
394  }
395 
396  // ensure the user never omits the extension from the file name
397  QFileInfo fi( fileName );
398  if ( !fileName.isEmpty() && fi.suffix().isEmpty() )
399  {
400  fileName += '.' + defaultExt;
401  }
402  }
403 
404  return fileName;
405 }
406 
408 {
409  if ( mLayerName->text().isEmpty() && outputFormat() == QStringLiteral( "GPKG" ) && !mTileModeCheckBox->isChecked() )
410  {
411  // Always return layer name for GeoPackages
412  return QFileInfo( mFilename->filePath() ).baseName();
413  }
414  else
415  {
416  return mLayerName->text();
417  }
418 }
419 
421 {
422  return mFormatComboBox->currentData().toString();
423 }
424 
426 {
427  QStringList options = mCreateOptionsGroupBox->isChecked() ? mCreateOptionsWidget->options() : QStringList();
428  if ( outputFormat() == QStringLiteral( "GPKG" ) )
429  {
430  // Overwrite the GPKG table options
431  int indx = options.indexOf( QRegularExpression( "^RASTER_TABLE=.*", QRegularExpression::CaseInsensitiveOption | QRegularExpression::MultilineOption ) );
432  if ( indx > -1 )
433  {
434  options.replace( indx, QStringLiteral( "RASTER_TABLE=%1" ).arg( outputLayerName() ) );
435  }
436  else
437  {
438  options.append( QStringLiteral( "RASTER_TABLE=%1" ).arg( outputLayerName() ) );
439  }
440 
441  // Only enable the append mode if the layer doesn't exist yet. For existing layers a 'confirm overwrite' dialog will be shown.
442  if ( !outputLayerExists() )
443  {
444  indx = options.indexOf( QRegularExpression( "^APPEND_SUBDATASET=.*", QRegularExpression::CaseInsensitiveOption | QRegularExpression::MultilineOption ) );
445  if ( indx > -1 )
446  {
447  options.replace( indx, QStringLiteral( "APPEND_SUBDATASET=YES" ) );
448  }
449  else
450  {
451  options.append( QStringLiteral( "APPEND_SUBDATASET=YES" ) );
452  }
453  }
454  }
455  return options;
456 }
457 
459 {
460  return mExtentGroupBox->outputExtent();
461 }
462 
464 {
465  mFormatLabel->hide();
466  mFormatComboBox->hide();
467 }
468 
470 {
471  mSaveAsLabel->hide();
472  mFilename->hide();
473  QPushButton *okButton = mButtonBox->button( QDialogButtonBox::Ok );
474  if ( okButton )
475  {
476  okButton->setEnabled( true );
477  }
478 }
479 
480 void QgsRasterLayerSaveAsDialog::toggleResolutionSize()
481 {
482  bool hasResolution = mDataProvider && mDataProvider->capabilities() & QgsRasterDataProvider::Size;
483 
484  bool on = mResolutionRadioButton->isChecked();
485  mXResolutionLineEdit->setEnabled( on );
486  mYResolutionLineEdit->setEnabled( on );
487  mOriginalResolutionPushButton->setEnabled( on && hasResolution );
488  mColumnsLineEdit->setEnabled( !on );
489  mRowsLineEdit->setEnabled( !on );
490  mOriginalSizePushButton->setEnabled( !on && hasResolution );
491 }
492 
493 void QgsRasterLayerSaveAsDialog::setOriginalResolution()
494 {
495  double xRes, yRes;
496 
497  if ( mDataProvider->capabilities() & QgsRasterDataProvider::Size )
498  {
499  xRes = mDataProvider->extent().width() / mDataProvider->xSize();
500  yRes = mDataProvider->extent().height() / mDataProvider->ySize();
501  }
502  else
503  {
504  // Init to something if no original resolution is available
505  xRes = yRes = mDataProvider->extent().width() / 100;
506  }
507  setResolution( xRes, yRes, mLayerCrs );
508  mResolutionState = OriginalResolution;
509  recalcSize();
510 }
511 
512 void QgsRasterLayerSaveAsDialog::setResolution( double xRes, double yRes, const QgsCoordinateReferenceSystem &srcCrs )
513 {
514  if ( srcCrs != outputCrs() )
515  {
516  // We reproject pixel rectangle from center of selected extent, of course, it gives
517  // bigger xRes,yRes than reprojected edges (envelope), it may also be that
518  // close to margins are higher resolutions (even very, too high)
519  // TODO: consider more precise resolution calculation
520 
521  QgsPointXY center = outputRectangle().center();
524 
525  QgsRectangle srcExtent( srsCenter.x() - xRes / 2, srsCenter.y() - yRes / 2, srsCenter.x() + xRes / 2, srsCenter.y() + yRes / 2 );
526 
527  QgsRectangle extent = ct.transform( srcExtent );
528  xRes = extent.width();
529  yRes = extent.height();
530  }
531  mXResolutionLineEdit->setText( QString::number( xRes ) );
532  mYResolutionLineEdit->setText( QString::number( yRes ) );
533 }
534 
535 void QgsRasterLayerSaveAsDialog::recalcSize()
536 {
537  QgsRectangle extent = outputRectangle();
538  int xSize = xResolution() != 0 ? static_cast<int>( std::round( extent.width() / xResolution() ) ) : 0;
539  int ySize = yResolution() != 0 ? static_cast<int>( std::round( extent.height() / yResolution() ) ) : 0;
540  mColumnsLineEdit->setText( QString::number( xSize ) );
541  mRowsLineEdit->setText( QString::number( ySize ) );
542  updateResolutionStateMsg();
543 }
544 
545 void QgsRasterLayerSaveAsDialog::setOriginalSize()
546 {
547  mColumnsLineEdit->setText( QString::number( mDataProvider->xSize() ) );
548  mRowsLineEdit->setText( QString::number( mDataProvider->ySize() ) );
549  recalcResolution();
550 }
551 
552 void QgsRasterLayerSaveAsDialog::recalcResolution()
553 {
554  QgsRectangle extent = outputRectangle();
555  double xRes = nColumns() != 0 ? extent.width() / nColumns() : 0;
556  double yRes = nRows() != 0 ? extent.height() / nRows() : 0;
557  mXResolutionLineEdit->setText( QString::number( xRes ) );
558  mYResolutionLineEdit->setText( QString::number( yRes ) );
559  updateResolutionStateMsg();
560 }
561 
562 void QgsRasterLayerSaveAsDialog::recalcResolutionSize()
563 {
564  if ( mResolutionRadioButton->isChecked() )
565  {
566  recalcSize();
567  }
568  else
569  {
570  mResolutionState = UserResolution;
571  recalcResolution();
572  }
573 }
574 
575 void QgsRasterLayerSaveAsDialog::updateResolutionStateMsg()
576 {
577  QString msg;
578  switch ( mResolutionState )
579  {
580  case OriginalResolution:
581  msg = tr( "layer" );
582  break;
583  case UserResolution:
584  msg = tr( "user defined" );
585  break;
586  default:
587  break;
588  }
589  msg = tr( "Resolution (current: %1)" ).arg( msg );
590  mResolutionGroupBox->setTitle( msg );
591 }
592 
593 void QgsRasterLayerSaveAsDialog::extentChanged()
594 {
595  // Whenever extent changes with fixed size, original resolution is lost
596  if ( mSizeRadioButton->isChecked() )
597  {
598  mResolutionState = UserResolution;
599  }
600  recalcResolutionSize();
601 }
602 
603 void QgsRasterLayerSaveAsDialog::crsChanged()
604 {
605  if ( outputCrs() != mPreviousCrs )
606  {
607  mExtentGroupBox->setOutputCrs( outputCrs() );
608 
609  // Reset resolution
610  if ( mResolutionRadioButton->isChecked() )
611  {
612  if ( mResolutionState == OriginalResolution )
613  {
614  setOriginalResolution();
615  }
616  else
617  {
618  // reset from present resolution and present crs
619  setResolution( xResolution(), yResolution(), mPreviousCrs );
620  }
621  }
622  else
623  {
624  // Size does not change, we just recalc resolution from new extent
625  recalcResolution();
626  }
627  }
628  mPreviousCrs = outputCrs();
629 }
630 
632 {
633  return mCrsSelector->crs();
634 }
635 
637 {
638  if ( mRenderedModeRadioButton->isChecked() ) return RenderedImageMode;
639  return RawDataMode;
640 }
641 
642 void QgsRasterLayerSaveAsDialog::mRawModeRadioButton_toggled( bool checked )
643 {
644  mNoDataGroupBox->setEnabled( checked && mDataProvider->bandCount() == 1 );
645 }
646 
647 void QgsRasterLayerSaveAsDialog::mAddNoDataManuallyToolButton_clicked()
648 {
649  addNoDataRow( std::numeric_limits<double>::quiet_NaN(), std::numeric_limits<double>::quiet_NaN() );
650 }
651 
652 void QgsRasterLayerSaveAsDialog::mLoadTransparentNoDataToolButton_clicked()
653 {
654  if ( !mRasterLayer->renderer() ) return;
655  const QgsRasterTransparency *rasterTransparency = mRasterLayer->renderer()->rasterTransparency();
656  if ( !rasterTransparency ) return;
657 
658  Q_FOREACH ( const QgsRasterTransparency::TransparentSingleValuePixel &transparencyPixel, rasterTransparency->transparentSingleValuePixelList() )
659  {
660  if ( transparencyPixel.percentTransparent == 100 )
661  {
662  addNoDataRow( transparencyPixel.min, transparencyPixel.max );
663  if ( transparencyPixel.min != transparencyPixel.max )
664  {
665  setNoDataToEdited( mNoDataTableWidget->rowCount() - 1 );
666  }
667  }
668  }
669 }
670 
671 void QgsRasterLayerSaveAsDialog::mRemoveSelectedNoDataToolButton_clicked()
672 {
673  mNoDataTableWidget->removeRow( mNoDataTableWidget->currentRow() );
674 }
675 
676 void QgsRasterLayerSaveAsDialog::mRemoveAllNoDataToolButton_clicked()
677 {
678  while ( mNoDataTableWidget->rowCount() > 0 )
679  {
680  mNoDataTableWidget->removeRow( 0 );
681  }
682 }
683 
684 void QgsRasterLayerSaveAsDialog::addNoDataRow( double min, double max )
685 {
686  mNoDataTableWidget->insertRow( mNoDataTableWidget->rowCount() );
687  for ( int i = 0; i < 2; i++ )
688  {
689  double value = i == 0 ? min : max;
690  QLineEdit *lineEdit = new QLineEdit();
691  lineEdit->setFrame( false );
692  lineEdit->setContentsMargins( 1, 1, 1, 1 );
693  QString valueString;
694  switch ( mRasterLayer->dataProvider()->sourceDataType( 1 ) )
695  {
696  case Qgis::Float32:
697  case Qgis::Float64:
698  lineEdit->setValidator( new QDoubleValidator( nullptr ) );
699  if ( !std::isnan( value ) )
700  {
701  valueString = QgsRasterBlock::printValue( value );
702  }
703  break;
704  default:
705  lineEdit->setValidator( new QIntValidator( nullptr ) );
706  if ( !std::isnan( value ) )
707  {
708  valueString = QString::number( static_cast<int>( value ) );
709  }
710  break;
711  }
712  lineEdit->setText( valueString );
713  mNoDataTableWidget->setCellWidget( mNoDataTableWidget->rowCount() - 1, i, lineEdit );
714 
715  adjustNoDataCellWidth( mNoDataTableWidget->rowCount() - 1, i );
716 
717  connect( lineEdit, &QLineEdit::textEdited, this, &QgsRasterLayerSaveAsDialog::noDataCellTextEdited );
718  }
719  mNoDataTableWidget->resizeColumnsToContents();
720  mNoDataTableWidget->resizeRowsToContents();
721 }
722 
723 void QgsRasterLayerSaveAsDialog::noDataCellTextEdited( const QString &text )
724 {
725  Q_UNUSED( text );
726 
727  QLineEdit *lineEdit = dynamic_cast<QLineEdit *>( sender() );
728  if ( !lineEdit ) return;
729  int row = -1;
730  int column = -1;
731  for ( int r = 0; r < mNoDataTableWidget->rowCount(); r++ )
732  {
733  for ( int c = 0; c < mNoDataTableWidget->columnCount(); c++ )
734  {
735  if ( mNoDataTableWidget->cellWidget( r, c ) == sender() )
736  {
737  row = r;
738  column = c;
739  break;
740  }
741  }
742  if ( row != -1 ) break;
743  }
744  QgsDebugMsg( QStringLiteral( "row = %1 column =%2" ).arg( row ).arg( column ) );
745 
746  if ( column == 0 )
747  {
748  QLineEdit *toLineEdit = dynamic_cast<QLineEdit *>( mNoDataTableWidget->cellWidget( row, 1 ) );
749  if ( !toLineEdit ) return;
750  bool toChanged = mNoDataToEdited.value( row );
751  QgsDebugMsg( QStringLiteral( "toChanged = %1" ).arg( toChanged ) );
752  if ( !toChanged )
753  {
754  toLineEdit->setText( lineEdit->text() );
755  }
756  }
757  else if ( column == 1 )
758  {
759  setNoDataToEdited( row );
760  }
761 }
762 
763 void QgsRasterLayerSaveAsDialog::mTileModeCheckBox_toggled( bool toggled )
764 {
765  if ( toggled )
766  {
767  // enable pyramids
768 
769  // Disabled (Radim), auto enabling of pyramids was making impression that
770  // we (programmers) know better what you (user) want to do,
771  // certainly auto expanding was a bad experience
772 
773  //if ( ! mPyramidsGroupBox->isChecked() )
774  // mPyramidsGroupBox->setChecked( true );
775 
776  // Auto expanding mPyramidsGroupBox is bad - it auto scrolls content of dialog
777  //if ( mPyramidsGroupBox->isCollapsed() )
778  // mPyramidsGroupBox->setCollapsed( false );
779  //mPyramidsOptionsWidget->checkAllLevels( true );
780 
781  // Show / hide tile options
782  mTilesGroupBox->show();
783  mFilename->setStorageMode( QgsFileWidget::GetDirectory );
784  mFilename->setDialogTitle( tr( "Select Output Directory" ) );
785  }
786  else
787  {
788  mTilesGroupBox->hide();
789  mFilename->setStorageMode( QgsFileWidget::SaveFile );
790  mFilename->setDialogTitle( tr( "Save Layer As" ) );
791  }
792 }
793 
794 void QgsRasterLayerSaveAsDialog::mPyramidsGroupBox_toggled( bool toggled )
795 {
796  Q_UNUSED( toggled );
797  populatePyramidsLevels();
798 }
799 
800 void QgsRasterLayerSaveAsDialog::populatePyramidsLevels()
801 {
802  QString text;
803 
804  if ( mPyramidsGroupBox->isChecked() )
805  {
806  QList<QgsRasterPyramid> myPyramidList;
807  // if use existing, get pyramids from actual layer
808  // but that's not available yet
809  if ( mPyramidsUseExistingCheckBox->isChecked() )
810  {
811  myPyramidList = mDataProvider->buildPyramidList();
812  }
813  else
814  {
815  if ( ! mPyramidsOptionsWidget->overviewList().isEmpty() )
816  myPyramidList = mDataProvider->buildPyramidList( mPyramidsOptionsWidget->overviewList() );
817  }
818  QList<QgsRasterPyramid>::iterator myRasterPyramidIterator;
819  for ( myRasterPyramidIterator = myPyramidList.begin();
820  myRasterPyramidIterator != myPyramidList.end();
821  ++myRasterPyramidIterator )
822  {
823  if ( ! mPyramidsUseExistingCheckBox->isChecked() || myRasterPyramidIterator->exists )
824  {
825  text += QString::number( myRasterPyramidIterator->xDim ) + QStringLiteral( "x" ) +
826  QString::number( myRasterPyramidIterator->yDim ) + ' ';
827  }
828  }
829  }
830 
831  mPyramidResolutionsLineEdit->setText( text.trimmed() );
832 }
833 
834 void QgsRasterLayerSaveAsDialog::setNoDataToEdited( int row )
835 {
836  if ( row >= mNoDataToEdited.size() )
837  {
838  mNoDataToEdited.resize( row + 1 );
839  }
840  mNoDataToEdited[row] = true;
841 }
842 
843 double QgsRasterLayerSaveAsDialog::noDataCellValue( int row, int column ) const
844 {
845  QLineEdit *lineEdit = dynamic_cast<QLineEdit *>( mNoDataTableWidget->cellWidget( row, column ) );
846  if ( !lineEdit || lineEdit->text().isEmpty() )
847  {
848  std::numeric_limits<double>::quiet_NaN();
849  }
850  return lineEdit->text().toDouble();
851 }
852 
853 void QgsRasterLayerSaveAsDialog::adjustNoDataCellWidth( int row, int column )
854 {
855  QLineEdit *lineEdit = dynamic_cast<QLineEdit *>( mNoDataTableWidget->cellWidget( row, column ) );
856  if ( !lineEdit ) return;
857 
858  int width = std::max( lineEdit->fontMetrics().width( lineEdit->text() ) + 10, 100 );
859  width = std::max( width, mNoDataTableWidget->columnWidth( column ) );
860 
861  lineEdit->setFixedWidth( width );
862 }
863 
865 {
866  QgsRasterRangeList noDataList;
867  if ( ! mNoDataGroupBox->isChecked() )
868  return noDataList;
869 
870  int rows = mNoDataTableWidget->rowCount();
871  noDataList.reserve( rows );
872  for ( int r = 0; r < rows; r++ )
873  {
874  QgsRasterRange noData( noDataCellValue( r, 0 ), noDataCellValue( r, 1 ) );
875  noDataList.append( noData );
876 
877  }
878  return noDataList;
879 }
880 
882 {
883  return mPyramidsGroupBox->isChecked() ? mPyramidsOptionsWidget->overviewList() : QList<int>();
884 }
885 
887 {
888  if ( ! mPyramidsGroupBox->isChecked() )
890  else if ( mPyramidsUseExistingCheckBox->isChecked() )
892  else
894 }
895 
896 bool QgsRasterLayerSaveAsDialog::validate() const
897 {
898  if ( mCreateOptionsGroupBox->isChecked() )
899  {
900  QString message = mCreateOptionsWidget->validateOptions( true, false );
901  if ( !message.isNull() )
902  return false;
903  }
904  if ( mPyramidsGroupBox->isChecked() )
905  {
906  QString message = mPyramidsOptionsWidget->createOptionsWidget()->validateOptions( true, false );
907  if ( !message.isNull() )
908  return false;
909  }
910  return true;
911 }
912 
913 bool QgsRasterLayerSaveAsDialog::outputLayerExists() const
914 {
915  QString vectorUri;
916  QString rasterUri;
917  if ( outputFormat() == QStringLiteral( "GPKG" ) )
918  {
919  rasterUri = QStringLiteral( "GPKG:%1:%2" ).arg( outputFileName(), outputLayerName() );
920  vectorUri = QStringLiteral( "%1|layername=%2" ).arg( outputFileName(), outputLayerName() );
921  }
922  else
923  {
924  rasterUri = outputFileName();
925  }
926 
927  QgsRasterLayer rasterLayer( rasterUri, QString( ), QStringLiteral( "gdal" ) );
928  if ( !vectorUri.isEmpty() )
929  {
930  QgsVectorLayer vectorLayer( vectorUri, QString( ), QStringLiteral( "ogr" ) );
931  return rasterLayer.isValid() || vectorLayer.isValid();
932  }
933  else
934  {
935  return rasterLayer.isValid();
936  }
937 }
938 
940 {
941  if ( !validate() )
942  {
943  return;
944  }
945 
946  if ( outputFormat() == QStringLiteral( "GPKG" ) && outputLayerExists() &&
947  QMessageBox::warning( this, tr( "Save Raster Layer" ),
948  tr( "The layer %1 already exists in the target file, and overwriting layers in GeoPackage is not supported. "
949  "Do you want to overwrite the whole file?" ).arg( outputLayerName() ),
950  QMessageBox::Yes | QMessageBox::No ) == QMessageBox::No )
951  {
952  return;
953  }
954 
955  QDialog::accept();
956 }
957 
958 void QgsRasterLayerSaveAsDialog::showHelp()
959 {
960  QgsHelp::openHelp( QStringLiteral( "managing_data_source/create_layers.html#save-layer-from-an-existing-file" ) );
961 }
virtual int bandCount() const =0
Gets number of bands.
QList< QgsRasterTransparency::TransparentSingleValuePixel > transparentSingleValuePixelList() const
Returns the transparent single value pixel list.
A rectangle specified with double values.
Definition: qgsrectangle.h:40
static QString printValue(double value)
Print double value with all necessary significant digits.
bool isValid() const
Returns the status of the layer.
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
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
This class provides qgis with the ability to render raster datasets onto the mapcanvas.
Select a single file.
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
static QStringList extensionsForFormat(const QString &format)
Returns a list of known file extensions for the given GDAL driver format.
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
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()
Thirty two bit floating point (float)
Definition: qgis.h:100
static bool supportsRasterCreate(GDALDriverH driver)
Reads whether a driver supports GDALCreate() for raster purposes.
virtual QString name() const =0
Returns a provider name.
virtual int ySize() const
Sixty four bit floating point (double)
Definition: qgis.h:101
void saveGeometry(QWidget *widget, const QString &keyName)
Save the wigget geometry into settings.
const QgsRasterTransparency * rasterTransparency() const
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.
QgsRasterRenderer * renderer() const
virtual QList< QgsRasterPyramid > buildPyramidList(QList< int > overviewList=QList< int >())
Returns the raster layers pyramid list.
QgsRectangle extent() const override=0
Returns the extent of the layer.
QgsRaster::RasterBuildPyramids buildPyramidsFlag() const
QgsPointXY center() const
Returns the center point of the rectangle.
Definition: qgsrectangle.h:229
double x
Definition: qgspointxy.h:47
virtual int capabilities() const
Returns a bitmask containing the supported capabilities.
QgsRasterRangeList noData() const
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
virtual int xSize() const
Gets raster size.
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:411
This class represents a coordinate reference system (CRS).
Select multiple files.
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:35
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...
double width() const
Returns the width of the rectangle.
Definition: qgsrectangle.h:201
Represents a vector layer which manages a vector based data sets.
QString outputLayerName() const
Name of the output layer within GeoPackage file.
double height() const
Returns the height of the rectangle.
Definition: qgsrectangle.h:208
Base class for raster data providers.