29 #include "cpl_string.h"
37 #include <QFileDialog>
38 #include <QMessageBox>
39 #include <QRegularExpression>
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 )
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 );
75 mNoDataTableWidget->setColumnCount( 2 );
76 mNoDataTableWidget->setHorizontalHeaderItem( 0,
new QTableWidgetItem( tr(
"From" ) ) );
77 mNoDataTableWidget->setHorizontalHeaderItem( 1,
new QTableWidgetItem( tr(
"To" ) ) );
79 mRawModeRadioButton_toggled(
true );
83 toggleResolutionSize();
85 insertAvailableOutputFormats();
92 setOriginalResolution();
93 int xSize = mDataProvider->
xSize();
94 int ySize = mDataProvider->
ySize();
95 mMaximumSizeXLineEdit->setText( QString::number( xSize ) );
96 mMaximumSizeYLineEdit->setText( QString::number( ySize ) );
100 mTileModeCheckBox->setChecked(
true );
101 mMaximumSizeXLineEdit->setText( QString::number( 2000 ) );
102 mMaximumSizeYLineEdit->setText( QString::number( 2000 ) );
106 mCreateOptionsWidget->setProvider( mDataProvider->
name() );
107 if ( mDataProvider->
name() == QLatin1String(
"gdal" ) )
109 mCreateOptionsWidget->setFormat( mFormatComboBox->currentData().toString() );
111 mCreateOptionsWidget->setRasterLayer( mRasterLayer );
112 mCreateOptionsWidget->update();
120 mPyramidsOptionsWidget->createOptionsWidget()->setRasterLayer( mRasterLayer );
125 mPyramidsUseExistingCheckBox->setEnabled(
false );
126 mPyramidsUseExistingCheckBox->setVisible(
false );
128 populatePyramidsLevels();
130 this, &QgsRasterLayerSaveAsDialog::populatePyramidsLevels );
134 mPyramidsGroupBox->setEnabled(
false );
139 mCreateOptionsGroupBox->setSaveCheckedState(
true );
149 mCrsSelector->setSourceEnsemble( ensemble.
name() );
155 mCrsSelector->setShowAccuracyWarnings(
true );
157 mCrsSelector->setLayerCrs( mLayerCrs );
159 mCrsSelector->setCrs( mLayerCrs );
162 this, &QgsRasterLayerSaveAsDialog::crsChanged );
164 QPushButton *okButton = mButtonBox->button( QDialogButtonBox::Ok );
167 okButton->setEnabled(
false );
171 mHelpButtonBox->setVisible(
false );
172 mButtonBox->addButton( QDialogButtonBox::Help );
173 connect( mButtonBox, &QDialogButtonBox::helpRequested,
this, &QgsRasterLayerSaveAsDialog::showHelp );
175 connect( mHelpButtonBox, &QDialogButtonBox::helpRequested,
this, &QgsRasterLayerSaveAsDialog::showHelp );
178 connect( mButtonBox, &QDialogButtonBox::rejected,
this, &QgsRasterLayerSaveAsDialog::reject );
180 mExtentGroupBox->setOutputCrs(
outputCrs() );
181 mExtentGroupBox->setOriginalExtent( mDataProvider->
extent(), mLayerCrs );
182 mExtentGroupBox->setCurrentExtent( mCurrentExtent, mCurrentCrs );
183 mExtentGroupBox->setOutputExtentFromOriginal();
186 recalcResolutionSize();
190 if ( mTileModeCheckBox->isChecked() )
192 mTilesGroupBox->show();
194 mFilename->setDialogTitle( tr(
"Select Output Directory" ) );
198 mTilesGroupBox->hide();
200 mFilename->setDialogTitle( tr(
"Save Layer As" ) );
203 mFilename->setDefaultRoot( settings.
value( QStringLiteral(
"UI/lastRasterFileDir" ), QDir::homePath() ).toString() );
207 QFileInfo tmplFileInfo( filePath );
208 settings.
setValue( QStringLiteral(
"UI/lastRasterFileDir" ), tmplFileInfo.absolutePath() );
210 if ( !filePath.isEmpty() && mLayerName->isEnabled() )
212 QFileInfo fileInfo( filePath );
213 mLayerName->setText( fileInfo.baseName() );
216 if ( mTileModeCheckBox->isChecked() )
218 QString fileName = filePath;
223 if ( fileName.isEmpty() )
227 QDir dir( fileName );
228 QString baseName = QFileInfo( fileName ).baseName();
230 filters << QStringLiteral(
"%1.*" ).arg( baseName );
231 QStringList files = dir.entryList( filters );
232 if ( files.isEmpty() )
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 )
240 fileName = QFileDialog::getExistingDirectory(
this, tr(
"Select output directory" ), tmplFileInfo.absolutePath() );
244 QPushButton *okButton = mButtonBox->button( QDialogButtonBox::Ok );
249 okButton->setEnabled( tmplFileInfo.absoluteDir().exists() );
253 void QgsRasterLayerSaveAsDialog::insertAvailableOutputFormats()
257 int nDrivers = GDALGetDriverCount();
258 QMap< int, QPair< QString, QString > > topPriorityDrivers;
259 QMap< QString, QString > lowPriorityDrivers;
261 for (
int i = 0; i < nDrivers; ++i )
263 GDALDriverH driver = GDALGetDriver( i );
268 QString driverShortName = GDALGetDriverShortName( driver );
269 QString driverLongName = GDALGetDriverLongName( driver );
270 if ( driverShortName == QLatin1String(
"MEM" ) )
277 else if ( driverShortName == QLatin1String(
"VRT" ) )
282 else if ( driverShortName == QLatin1String(
"GTiff" ) )
285 topPriorityDrivers.insert( 1, qMakePair( driverLongName, driverShortName ) );
287 else if ( driverShortName == QLatin1String(
"GPKG" ) )
290 topPriorityDrivers.insert( 2, qMakePair( driverLongName, driverShortName ) );
294 lowPriorityDrivers.insert( driverLongName, driverShortName );
301 for (
auto priorityDriversIt = topPriorityDrivers.constBegin(); priorityDriversIt != topPriorityDrivers.constEnd(); ++priorityDriversIt )
303 mFormatComboBox->addItem( priorityDriversIt.value().first, priorityDriversIt.value().second );
306 for (
auto lowPriorityDriversIt = lowPriorityDrivers.constBegin(); lowPriorityDriversIt != lowPriorityDrivers.constEnd(); ++lowPriorityDriversIt )
308 mFormatComboBox->addItem( lowPriorityDriversIt.key(), lowPriorityDriversIt.value() );
313 void QgsRasterLayerSaveAsDialog::setValidators()
317 mColumnsLineEdit->setValidator(
new QIntValidator(
this ) );
318 mRowsLineEdit->setValidator(
new QIntValidator(
this ) );
319 mMaximumSizeXLineEdit->setValidator(
new QIntValidator(
this ) );
320 mMaximumSizeYLineEdit->setValidator(
new QIntValidator(
this ) );
323 void QgsRasterLayerSaveAsDialog::mFormatComboBox_currentIndexChanged(
const QString & )
326 if ( mDataProvider && mDataProvider->
name() == QLatin1String(
"gdal" ) )
329 mCreateOptionsWidget->update();
334 if ( extensions.empty() )
335 filter = tr(
"All files (*.*)" );
338 filter = QStringLiteral(
"%1 (*.%2);;%3" ).arg( mFormatComboBox->currentText(),
339 extensions.join( QLatin1String(
" *." ) ),
340 tr(
"All files (*.*)" ) );
342 mFilename->setFilter( filter );
345 mTileModeCheckBox->setEnabled(
outputFormat() != QLatin1String(
"GPKG" ) );
346 mFilename->setConfirmOverwrite(
outputFormat() != QLatin1String(
"GPKG" ) );
347 mLayerName->setEnabled(
outputFormat() == QLatin1String(
"GPKG" ) );
348 if ( mLayerName->isEnabled() )
350 QString layerName = QFileInfo( mFilename->filePath() ).baseName();
351 mLayerName->setText( layerName );
352 mTileModeCheckBox->setChecked(
false );
356 mLayerName->setText( QString() );
362 return mColumnsLineEdit->text().toInt();
367 return mRowsLineEdit->text().toInt();
382 return mMaximumSizeXLineEdit->text().toInt();
387 return mMaximumSizeYLineEdit->text().toInt();
392 return mTileModeCheckBox->isChecked();
397 return mAddToCanvas->isChecked();
402 mAddToCanvas->setChecked( checked );
407 QString fileName = mFilename->filePath();
413 if ( !extensions.empty() )
415 defaultExt = extensions.at( 0 );
419 QFileInfo fi( fileName );
420 if ( !fileName.isEmpty() && fi.suffix().isEmpty() && !defaultExt.isEmpty() )
422 fileName +=
'.' + defaultExt;
431 if ( mLayerName->text().isEmpty() &&
outputFormat() == QLatin1String(
"GPKG" ) && !mTileModeCheckBox->isChecked() )
434 return QFileInfo( mFilename->filePath() ).baseName();
438 return mLayerName->text();
444 return mFormatComboBox->currentData().toString();
449 QStringList options = mCreateOptionsGroupBox->isChecked() ? mCreateOptionsWidget->options() : QStringList();
453 int indx = options.indexOf( QRegularExpression(
"^RASTER_TABLE=.*", QRegularExpression::CaseInsensitiveOption | QRegularExpression::MultilineOption ) );
456 options.replace( indx, QStringLiteral(
"RASTER_TABLE=%1" ).arg(
outputLayerName() ) );
460 options.append( QStringLiteral(
"RASTER_TABLE=%1" ).arg(
outputLayerName() ) );
464 if ( !outputLayerExists() )
466 indx = options.indexOf( QRegularExpression(
"^APPEND_SUBDATASET=.*", QRegularExpression::CaseInsensitiveOption | QRegularExpression::MultilineOption ) );
469 options.replace( indx, QStringLiteral(
"APPEND_SUBDATASET=YES" ) );
473 options.append( QStringLiteral(
"APPEND_SUBDATASET=YES" ) );
482 return mExtentGroupBox->outputExtent();
487 mFormatLabel->hide();
488 mFormatComboBox->hide();
493 mSaveAsLabel->hide();
495 QPushButton *okButton = mButtonBox->button( QDialogButtonBox::Ok );
498 okButton->setEnabled(
true );
502 void QgsRasterLayerSaveAsDialog::toggleResolutionSize()
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 );
515 void QgsRasterLayerSaveAsDialog::setOriginalResolution()
527 xRes = yRes = mDataProvider->
extent().
width() / 100;
529 setResolution( xRes, yRes, mLayerCrs );
545 QgsPointXY srsCenter = ct.transform( center, Qgis::TransformDirection::Reverse );
547 QgsRectangle srcExtent( srsCenter.
x() - xRes / 2, srsCenter.
y() - yRes / 2, srsCenter.
x() + xRes / 2, srsCenter.
y() + yRes / 2 );
550 xRes = extent.
width();
553 mXResolutionLineEdit->setText( QLocale().toString( xRes ) );
554 mYResolutionLineEdit->setText( QLocale().toString( yRes ) );
557 void QgsRasterLayerSaveAsDialog::recalcSize()
562 mColumnsLineEdit->setText( QString::number( xSize ) );
563 mRowsLineEdit->setText( QString::number( ySize ) );
564 updateResolutionStateMsg();
567 void QgsRasterLayerSaveAsDialog::setOriginalSize()
569 mColumnsLineEdit->setText( QString::number( mDataProvider->
xSize() ) );
570 mRowsLineEdit->setText( QString::number( mDataProvider->
ySize() ) );
574 void QgsRasterLayerSaveAsDialog::recalcResolution()
579 mXResolutionLineEdit->setText( QLocale().toString( xRes ) );
580 mYResolutionLineEdit->setText( QLocale().toString( yRes ) );
581 updateResolutionStateMsg();
584 void QgsRasterLayerSaveAsDialog::recalcResolutionSize()
586 if ( mResolutionRadioButton->isChecked() )
597 void QgsRasterLayerSaveAsDialog::updateResolutionStateMsg()
600 switch ( mResolutionState )
606 msg = tr(
"user defined" );
611 msg = tr(
"Resolution (current: %1)" ).arg( msg );
612 mResolutionGroupBox->setTitle( msg );
615 void QgsRasterLayerSaveAsDialog::extentChanged()
618 if ( mSizeRadioButton->isChecked() )
622 recalcResolutionSize();
625 void QgsRasterLayerSaveAsDialog::crsChanged()
629 mExtentGroupBox->setOutputCrs(
outputCrs() );
632 if ( mResolutionRadioButton->isChecked() )
636 setOriginalResolution();
655 return mCrsSelector->crs();
664 void QgsRasterLayerSaveAsDialog::mRawModeRadioButton_toggled(
bool checked )
666 mNoDataGroupBox->setEnabled( checked && mDataProvider->
bandCount() == 1 );
669 void QgsRasterLayerSaveAsDialog::mAddNoDataManuallyToolButton_clicked()
671 addNoDataRow( std::numeric_limits<double>::quiet_NaN(), std::numeric_limits<double>::quiet_NaN() );
674 void QgsRasterLayerSaveAsDialog::mLoadTransparentNoDataToolButton_clicked()
676 if ( !mRasterLayer->
renderer() )
return;
678 if ( !rasterTransparency )
return;
683 if ( transparencyPixel.percentTransparent == 100 )
685 addNoDataRow( transparencyPixel.min, transparencyPixel.max );
686 if ( transparencyPixel.min != transparencyPixel.max )
688 setNoDataToEdited( mNoDataTableWidget->rowCount() - 1 );
694 void QgsRasterLayerSaveAsDialog::mRemoveSelectedNoDataToolButton_clicked()
696 mNoDataTableWidget->removeRow( mNoDataTableWidget->currentRow() );
699 void QgsRasterLayerSaveAsDialog::mRemoveAllNoDataToolButton_clicked()
701 while ( mNoDataTableWidget->rowCount() > 0 )
703 mNoDataTableWidget->removeRow( 0 );
707 void QgsRasterLayerSaveAsDialog::addNoDataRow(
double min,
double max )
709 mNoDataTableWidget->insertRow( mNoDataTableWidget->rowCount() );
710 for (
int i = 0; i < 2; i++ )
712 double value = i == 0 ? min : max;
713 QLineEdit *lineEdit =
new QLineEdit();
714 lineEdit->setFrame(
false );
715 lineEdit->setContentsMargins( 1, 1, 1, 1 );
722 if ( !std::isnan( value ) )
728 lineEdit->setValidator(
new QIntValidator(
nullptr ) );
729 if ( !std::isnan( value ) )
731 valueString = QLocale().toString(
static_cast<int>( value ) );
735 lineEdit->setText( valueString );
736 mNoDataTableWidget->setCellWidget( mNoDataTableWidget->rowCount() - 1, i, lineEdit );
738 adjustNoDataCellWidth( mNoDataTableWidget->rowCount() - 1, i );
740 connect( lineEdit, &QLineEdit::textEdited,
this, &QgsRasterLayerSaveAsDialog::noDataCellTextEdited );
742 mNoDataTableWidget->resizeColumnsToContents();
743 mNoDataTableWidget->resizeRowsToContents();
746 void QgsRasterLayerSaveAsDialog::noDataCellTextEdited(
const QString &text )
750 QLineEdit *lineEdit = qobject_cast<QLineEdit *>( sender() );
751 if ( !lineEdit )
return;
754 for (
int r = 0; r < mNoDataTableWidget->rowCount(); r++ )
756 for (
int c = 0;
c < mNoDataTableWidget->columnCount();
c++ )
758 if ( mNoDataTableWidget->cellWidget( r,
c ) == sender() )
765 if ( row != -1 )
break;
767 QgsDebugMsg( QStringLiteral(
"row = %1 column =%2" ).arg( row ).arg( column ) );
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 ) );
777 toLineEdit->setText( lineEdit->text() );
780 else if ( column == 1 )
782 setNoDataToEdited( row );
786 void QgsRasterLayerSaveAsDialog::mTileModeCheckBox_toggled(
bool toggled )
805 mTilesGroupBox->show();
807 mFilename->setDialogTitle( tr(
"Select Output Directory" ) );
811 mTilesGroupBox->hide();
813 mFilename->setDialogTitle( tr(
"Save Layer As" ) );
817 void QgsRasterLayerSaveAsDialog::mPyramidsGroupBox_toggled(
bool toggled )
820 populatePyramidsLevels();
823 void QgsRasterLayerSaveAsDialog::populatePyramidsLevels()
827 if ( mPyramidsGroupBox->isChecked() )
829 QList<QgsRasterPyramid> myPyramidList;
832 if ( mPyramidsUseExistingCheckBox->isChecked() )
838 if ( ! mPyramidsOptionsWidget->overviewList().isEmpty() )
839 myPyramidList = mDataProvider->
buildPyramidList( mPyramidsOptionsWidget->overviewList() );
843 if ( ! mPyramidsUseExistingCheckBox->isChecked() || pyramid.getExists() )
845 text += QString::number( pyramid.getXDim() ) + QStringLiteral(
"x" ) +
846 QString::number( pyramid.getYDim() ) +
' ';
851 mPyramidResolutionsLineEdit->setText( text.trimmed() );
854 void QgsRasterLayerSaveAsDialog::setNoDataToEdited(
int row )
856 if ( row >= mNoDataToEdited.size() )
858 mNoDataToEdited.resize( row + 1 );
860 mNoDataToEdited[row] =
true;
863 double QgsRasterLayerSaveAsDialog::noDataCellValue(
int row,
int column )
const
865 QLineEdit *lineEdit =
dynamic_cast<QLineEdit *
>( mNoDataTableWidget->cellWidget( row, column ) );
866 if ( !lineEdit || lineEdit->text().isEmpty() )
868 return std::numeric_limits<double>::quiet_NaN();
873 void QgsRasterLayerSaveAsDialog::adjustNoDataCellWidth(
int row,
int column )
875 QLineEdit *lineEdit =
dynamic_cast<QLineEdit *
>( mNoDataTableWidget->cellWidget( row, column ) );
876 if ( !lineEdit )
return;
878 int width = std::max( lineEdit->fontMetrics().boundingRect( lineEdit->text() ).width() + 10, 100 );
879 width = std::max( width, mNoDataTableWidget->columnWidth( column ) );
881 lineEdit->setFixedWidth( width );
887 if ( ! mNoDataGroupBox->isChecked() )
890 int rows = mNoDataTableWidget->rowCount();
891 noDataList.reserve( rows );
892 for (
int r = 0; r < rows; r++ )
895 noDataList.append(
noData );
903 return mPyramidsGroupBox->isChecked() ? mPyramidsOptionsWidget->overviewList() : QList<int>();
908 if ( ! mPyramidsGroupBox->isChecked() )
910 else if ( mPyramidsUseExistingCheckBox->isChecked() )
916 bool QgsRasterLayerSaveAsDialog::validate()
const
918 if ( mCreateOptionsGroupBox->isChecked() )
920 QString message = mCreateOptionsWidget->validateOptions(
true,
false );
921 if ( !message.isNull() )
924 if ( mPyramidsGroupBox->isChecked() )
926 QString message = mPyramidsOptionsWidget->createOptionsWidget()->validateOptions(
true,
false );
927 if ( !message.isNull() )
933 bool QgsRasterLayerSaveAsDialog::outputLayerExists()
const
947 QgsRasterLayer rasterLayer( rasterUri, QString( ), QStringLiteral(
"gdal" ) );
948 if ( !vectorUri.isEmpty() )
950 QgsVectorLayer vectorLayer( vectorUri, QString( ), QStringLiteral(
"ogr" ) );
951 return rasterLayer.isValid() || vectorLayer.isValid();
955 return rasterLayer.isValid();
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. "
970 QMessageBox::Yes | QMessageBox::No ) == QMessageBox::No )
978 void QgsRasterLayerSaveAsDialog::showHelp()
980 QgsHelp::openHelp( QStringLiteral(
"managing_data_source/create_layers.html#creating-new-layers-from-an-existing-layer" ) );