QGIS/src/gui/qgsrasterlayersaveasdialog.cpp
Nyall Dawson c4c8aab7b4 Improve documentation for raster pyramid creation methods/classes
The PyQGIS documentation for this workflow was very poorly documented
2021-03-31 10:59:47 +10:00

968 lines
32 KiB
C++

/***************************************************************************
qgsrasterlayersaveasdialog.cpp
---------------------
begin : May 2012
copyright : (C) 2012 by Marco Hugentobler
email : marco dot hugentobler at sourcepole dot ch
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgsapplication.h"
#include "qgsgdalutils.h"
#include "qgslogger.h"
#include "qgscoordinatetransform.h"
#include "qgsrasterlayer.h"
#include "qgsrasterlayersaveasdialog.h"
#include "qgsrasterdataprovider.h"
#include "qgsrasterformatsaveoptionswidget.h"
#include "qgsrasterrenderer.h"
#include "qgsrastertransparency.h"
#include "qgsprojectionselectiondialog.h"
#include "qgssettings.h"
#include "qgsrasterfilewriter.h"
#include "qgsvectorlayer.h"
#include "cpl_string.h"
#include "qgsproject.h"
#include <gdal.h>
#include "qgsmessagelog.h"
#include "qgsgui.h"
#include "qgsdoublevalidator.h"
#include <QFileDialog>
#include <QMessageBox>
#include <QRegularExpression>
QgsRasterLayerSaveAsDialog::QgsRasterLayerSaveAsDialog( QgsRasterLayer *rasterLayer,
QgsRasterDataProvider *sourceProvider, const QgsRectangle &currentExtent,
const QgsCoordinateReferenceSystem &layerCrs, const QgsCoordinateReferenceSystem &currentCrs,
QWidget *parent, Qt::WindowFlags f )
: QDialog( parent, f )
, mRasterLayer( rasterLayer )
, mDataProvider( sourceProvider )
, mCurrentExtent( currentExtent )
, mLayerCrs( layerCrs )
, mCurrentCrs( currentCrs )
, mResolutionState( OriginalResolution )
{
setupUi( this );
QgsGui::instance()->enableAutoGeometryRestore( this );
connect( mRawModeRadioButton, &QRadioButton::toggled, this, &QgsRasterLayerSaveAsDialog::mRawModeRadioButton_toggled );
connect( mFormatComboBox, &QComboBox::currentTextChanged, this, &QgsRasterLayerSaveAsDialog::mFormatComboBox_currentIndexChanged );
connect( mResolutionRadioButton, &QRadioButton::toggled, this, &QgsRasterLayerSaveAsDialog::mResolutionRadioButton_toggled );
connect( mOriginalResolutionPushButton, &QPushButton::clicked, this, &QgsRasterLayerSaveAsDialog::mOriginalResolutionPushButton_clicked );
connect( mXResolutionLineEdit, &QLineEdit::textEdited, this, &QgsRasterLayerSaveAsDialog::mXResolutionLineEdit_textEdited );
connect( mYResolutionLineEdit, &QLineEdit::textEdited, this, &QgsRasterLayerSaveAsDialog::mYResolutionLineEdit_textEdited );
connect( mOriginalSizePushButton, &QPushButton::clicked, this, &QgsRasterLayerSaveAsDialog::mOriginalSizePushButton_clicked );
connect( mColumnsLineEdit, &QLineEdit::textEdited, this, &QgsRasterLayerSaveAsDialog::mColumnsLineEdit_textEdited );
connect( mRowsLineEdit, &QLineEdit::textEdited, this, &QgsRasterLayerSaveAsDialog::mRowsLineEdit_textEdited );
connect( mAddNoDataManuallyToolButton, &QPushButton::clicked, this, &QgsRasterLayerSaveAsDialog::mAddNoDataManuallyToolButton_clicked );
connect( mLoadTransparentNoDataToolButton, &QPushButton::clicked, this, &QgsRasterLayerSaveAsDialog::mLoadTransparentNoDataToolButton_clicked );
connect( mRemoveSelectedNoDataToolButton, &QPushButton::clicked, this, &QgsRasterLayerSaveAsDialog::mRemoveSelectedNoDataToolButton_clicked );
connect( mRemoveAllNoDataToolButton, &QPushButton::clicked, this, &QgsRasterLayerSaveAsDialog::mRemoveAllNoDataToolButton_clicked );
connect( mTileModeCheckBox, &QCheckBox::toggled, this, &QgsRasterLayerSaveAsDialog::mTileModeCheckBox_toggled );
connect( mPyramidsGroupBox, &QgsCollapsibleGroupBox::toggled, this, &QgsRasterLayerSaveAsDialog::mPyramidsGroupBox_toggled );
mAddNoDataManuallyToolButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/symbologyAdd.svg" ) ) );
mLoadTransparentNoDataToolButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionFileOpen.svg" ) ) );
mRemoveSelectedNoDataToolButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/symbologyRemove.svg" ) ) );
mRemoveAllNoDataToolButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionRemove.svg" ) ) );
mNoDataTableWidget->setColumnCount( 2 );
mNoDataTableWidget->setHorizontalHeaderItem( 0, new QTableWidgetItem( tr( "From" ) ) );
mNoDataTableWidget->setHorizontalHeaderItem( 1, new QTableWidgetItem( tr( "To" ) ) );
mRawModeRadioButton_toggled( true );
setValidators();
toggleResolutionSize();
insertAvailableOutputFormats();
//fill reasonable default values depending on the provider
if ( mDataProvider )
{
if ( mDataProvider->capabilities() & QgsRasterDataProvider::Size )
{
setOriginalResolution();
int xSize = mDataProvider->xSize();
int ySize = mDataProvider->ySize();
mMaximumSizeXLineEdit->setText( QString::number( xSize ) );
mMaximumSizeYLineEdit->setText( QString::number( ySize ) );
}
else //wms, sometimes wcs
{
mTileModeCheckBox->setChecked( true );
mMaximumSizeXLineEdit->setText( QString::number( 2000 ) );
mMaximumSizeYLineEdit->setText( QString::number( 2000 ) );
}
// setup creation option widget
mCreateOptionsWidget->setProvider( mDataProvider->name() );
if ( mDataProvider->name() == QLatin1String( "gdal" ) )
{
mCreateOptionsWidget->setFormat( mFormatComboBox->currentData().toString() );
}
mCreateOptionsWidget->setRasterLayer( mRasterLayer );
mCreateOptionsWidget->update();
}
// Only do pyramids if dealing directly with GDAL.
if ( mDataProvider && mDataProvider->capabilities() & QgsRasterDataProvider::BuildPyramids )
{
// setup pyramids option widget
// mPyramidsOptionsWidget->createOptionsWidget()->setType( QgsRasterFormatSaveOptionsWidget::ProfileLineEdit );
mPyramidsOptionsWidget->createOptionsWidget()->setRasterLayer( mRasterLayer );
// TODO enable "use existing", has no effect for now, because using Create() in gdal provider
// if ( ! mDataProvider->hasPyramids() )
// mPyramidsButtonGroup->button( QgsRaster::PyramidsCopyExisting )->setEnabled( false );
mPyramidsUseExistingCheckBox->setEnabled( false );
mPyramidsUseExistingCheckBox->setVisible( false );
populatePyramidsLevels();
connect( mPyramidsOptionsWidget, &QgsRasterPyramidsOptionsWidget::overviewListChanged,
this, &QgsRasterLayerSaveAsDialog::populatePyramidsLevels );
}
else
{
mPyramidsGroupBox->setEnabled( false );
}
// restore checked state for most groupboxes (default is to restore collapsed state)
// create options and pyramids will be preset, if user has selected defaults in the gdal options dlg
mCreateOptionsGroupBox->setSaveCheckedState( true );
//mTilesGroupBox->setSaveCheckedState( true );
// don't restore nodata, it needs user input
// pyramids are not necessarily built every time
mCrsSelector->setLayerCrs( mLayerCrs );
//default to layer CRS - see https://github.com/qgis/QGIS/issues/22211 for discussion
mCrsSelector->setCrs( mLayerCrs );
connect( mCrsSelector, &QgsProjectionSelectionWidget::crsChanged,
this, &QgsRasterLayerSaveAsDialog::crsChanged );
QPushButton *okButton = mButtonBox->button( QDialogButtonBox::Ok );
if ( okButton )
{
okButton->setEnabled( false );
}
#ifdef Q_OS_WIN
mHelpButtonBox->setVisible( false );
mButtonBox->addButton( QDialogButtonBox::Help );
connect( mButtonBox, &QDialogButtonBox::helpRequested, this, &QgsRasterLayerSaveAsDialog::showHelp );
#else
connect( mHelpButtonBox, &QDialogButtonBox::helpRequested, this, &QgsRasterLayerSaveAsDialog::showHelp );
#endif
connect( mButtonBox, &QDialogButtonBox::accepted, this, &QgsRasterLayerSaveAsDialog::accept );
connect( mButtonBox, &QDialogButtonBox::rejected, this, &QgsRasterLayerSaveAsDialog::reject );
mExtentGroupBox->setOutputCrs( outputCrs() );
mExtentGroupBox->setOriginalExtent( mDataProvider->extent(), mLayerCrs );
mExtentGroupBox->setCurrentExtent( mCurrentExtent, mCurrentCrs );
mExtentGroupBox->setOutputExtentFromOriginal();
connect( mExtentGroupBox, &QgsExtentGroupBox::extentChanged, this, &QgsRasterLayerSaveAsDialog::extentChanged );
recalcResolutionSize();
QgsSettings settings;
if ( mTileModeCheckBox->isChecked() )
{
mTilesGroupBox->show();
mFilename->setStorageMode( QgsFileWidget::GetDirectory );
mFilename->setDialogTitle( tr( "Select Output Directory" ) );
}
else
{
mTilesGroupBox->hide();
mFilename->setStorageMode( QgsFileWidget::SaveFile );
mFilename->setDialogTitle( tr( "Save Layer As" ) );
}
mFilename->setDefaultRoot( settings.value( QStringLiteral( "UI/lastRasterFileDir" ), QDir::homePath() ).toString() );
connect( mFilename, &QgsFileWidget::fileChanged, this, [ = ]( const QString & filePath )
{
QgsSettings settings;
QFileInfo tmplFileInfo( filePath );
settings.setValue( QStringLiteral( "UI/lastRasterFileDir" ), tmplFileInfo.absolutePath() );
if ( !filePath.isEmpty() && mLayerName->isEnabled() )
{
QFileInfo fileInfo( filePath );
mLayerName->setText( fileInfo.baseName() );
}
if ( mTileModeCheckBox->isChecked() )
{
QString fileName = filePath;
Q_FOREVER
{
// TODO: would not it be better to select .vrt file instead of directory?
//fileName = QFileDialog::getSaveFileName( this, tr( "Select output file" ), QString(), tr( "VRT" ) + " (*.vrt *.VRT)" );
if ( fileName.isEmpty() )
break; // canceled
// Check if directory is empty
QDir dir( fileName );
QString baseName = QFileInfo( fileName ).baseName();
QStringList filters;
filters << QStringLiteral( "%1.*" ).arg( baseName );
QStringList files = dir.entryList( filters );
if ( files.isEmpty() )
break;
if ( QMessageBox::warning( this, tr( "Save Raster Layer" ),
tr( "The directory %1 contains files which will be overwritten: %2" ).arg( dir.absolutePath(), files.join( QLatin1String( ", " ) ) ),
QMessageBox::Ok | QMessageBox::Cancel ) == QMessageBox::Ok )
break;
fileName = QFileDialog::getExistingDirectory( this, tr( "Select output directory" ), tmplFileInfo.absolutePath() );
}
}
QPushButton *okButton = mButtonBox->button( QDialogButtonBox::Ok );
if ( !okButton )
{
return;
}
okButton->setEnabled( tmplFileInfo.absoluteDir().exists() );
} );
}
void QgsRasterLayerSaveAsDialog::insertAvailableOutputFormats()
{
GDALAllRegister();
int nDrivers = GDALGetDriverCount();
QMap< int, QPair< QString, QString > > topPriorityDrivers;
QMap< QString, QString > lowPriorityDrivers;
for ( int i = 0; i < nDrivers; ++i )
{
GDALDriverH driver = GDALGetDriver( i );
if ( driver )
{
if ( QgsGdalUtils::supportsRasterCreate( driver ) )
{
QString driverShortName = GDALGetDriverShortName( driver );
QString driverLongName = GDALGetDriverLongName( driver );
if ( driverShortName == QLatin1String( "MEM" ) )
{
// in memory rasters are not (yet) supported because the GDAL dataset handle
// would need to be passed directly to QgsRasterLayer (it is not possible to
// close it in raster calculator and reopen the dataset again in raster layer)
continue;
}
else if ( driverShortName == QLatin1String( "VRT" ) )
{
// skip GDAL vrt driver, since we handle that format manually
continue;
}
else if ( driverShortName == QLatin1String( "GTiff" ) )
{
// always list geotiff first
topPriorityDrivers.insert( 1, qMakePair( driverLongName, driverShortName ) );
}
else if ( driverShortName == QLatin1String( "GPKG" ) )
{
// and gpkg second
topPriorityDrivers.insert( 2, qMakePair( driverLongName, driverShortName ) );
}
else
{
lowPriorityDrivers.insert( driverLongName, driverShortName );
}
}
}
}
// will be sorted by priority, so that geotiff and geopackage are listed first
for ( auto priorityDriversIt = topPriorityDrivers.constBegin(); priorityDriversIt != topPriorityDrivers.constEnd(); ++priorityDriversIt )
{
mFormatComboBox->addItem( priorityDriversIt.value().first, priorityDriversIt.value().second );
}
// will be sorted by driver name
for ( auto lowPriorityDriversIt = lowPriorityDrivers.constBegin(); lowPriorityDriversIt != lowPriorityDrivers.constEnd(); ++lowPriorityDriversIt )
{
mFormatComboBox->addItem( lowPriorityDriversIt.key(), lowPriorityDriversIt.value() );
}
}
void QgsRasterLayerSaveAsDialog::setValidators()
{
mXResolutionLineEdit->setValidator( new QgsDoubleValidator( this ) );
mYResolutionLineEdit->setValidator( new QgsDoubleValidator( this ) );
mColumnsLineEdit->setValidator( new QIntValidator( this ) );
mRowsLineEdit->setValidator( new QIntValidator( this ) );
mMaximumSizeXLineEdit->setValidator( new QIntValidator( this ) );
mMaximumSizeYLineEdit->setValidator( new QIntValidator( this ) );
}
void QgsRasterLayerSaveAsDialog::mFormatComboBox_currentIndexChanged( const QString & )
{
//gdal-specific
if ( mDataProvider && mDataProvider->name() == QLatin1String( "gdal" ) )
{
mCreateOptionsWidget->setFormat( outputFormat() );
mCreateOptionsWidget->update();
}
QStringList extensions = QgsRasterFileWriter::extensionsForFormat( outputFormat() );
QString filter;
if ( extensions.empty() )
filter = tr( "All files (*.*)" );
else
{
filter = QStringLiteral( "%1 (*.%2);;%3" ).arg( mFormatComboBox->currentText(),
extensions.join( QLatin1String( " *." ) ),
tr( "All files (*.*)" ) );
}
mFilename->setFilter( filter );
// Disable mTileModeCheckBox for GeoPackages
mTileModeCheckBox->setEnabled( outputFormat() != QLatin1String( "GPKG" ) );
mFilename->setConfirmOverwrite( outputFormat() != QLatin1String( "GPKG" ) );
mLayerName->setEnabled( outputFormat() == QLatin1String( "GPKG" ) );
if ( mLayerName->isEnabled() )
{
QString layerName = QFileInfo( mFilename->filePath() ).baseName();
mLayerName->setText( layerName );
mTileModeCheckBox->setChecked( false );
}
else
{
mLayerName->setText( QString() );
}
}
int QgsRasterLayerSaveAsDialog::nColumns() const
{
return mColumnsLineEdit->text().toInt();
}
int QgsRasterLayerSaveAsDialog::nRows() const
{
return mRowsLineEdit->text().toInt();
}
double QgsRasterLayerSaveAsDialog::xResolution() const
{
return QgsDoubleValidator::toDouble( mXResolutionLineEdit->text() );
}
double QgsRasterLayerSaveAsDialog::yResolution() const
{
return QgsDoubleValidator::toDouble( mYResolutionLineEdit->text() );
}
int QgsRasterLayerSaveAsDialog::maximumTileSizeX() const
{
return mMaximumSizeXLineEdit->text().toInt();
}
int QgsRasterLayerSaveAsDialog::maximumTileSizeY() const
{
return mMaximumSizeYLineEdit->text().toInt();
}
bool QgsRasterLayerSaveAsDialog::tileMode() const
{
return mTileModeCheckBox->isChecked();
}
bool QgsRasterLayerSaveAsDialog::addToCanvas() const
{
return mAddToCanvas->isChecked();
}
void QgsRasterLayerSaveAsDialog::setAddToCanvas( bool checked )
{
mAddToCanvas->setChecked( checked );
}
QString QgsRasterLayerSaveAsDialog::outputFileName() const
{
QString fileName = mFilename->filePath();
if ( mFilename->storageMode() != QgsFileWidget::GetDirectory )
{
QStringList extensions = QgsRasterFileWriter::extensionsForFormat( outputFormat() );
QString defaultExt;
if ( !extensions.empty() )
{
defaultExt = extensions.at( 0 );
}
// ensure the user never omits the extension from the file name
QFileInfo fi( fileName );
if ( !fileName.isEmpty() && fi.suffix().isEmpty() )
{
fileName += '.' + defaultExt;
}
}
return fileName;
}
QString QgsRasterLayerSaveAsDialog::outputLayerName() const
{
if ( mLayerName->text().isEmpty() && outputFormat() == QLatin1String( "GPKG" ) && !mTileModeCheckBox->isChecked() )
{
// Always return layer name for GeoPackages
return QFileInfo( mFilename->filePath() ).baseName();
}
else
{
return mLayerName->text();
}
}
QString QgsRasterLayerSaveAsDialog::outputFormat() const
{
return mFormatComboBox->currentData().toString();
}
QStringList QgsRasterLayerSaveAsDialog::createOptions() const
{
QStringList options = mCreateOptionsGroupBox->isChecked() ? mCreateOptionsWidget->options() : QStringList();
if ( outputFormat() == QLatin1String( "GPKG" ) )
{
// Overwrite the GPKG table options
int indx = options.indexOf( QRegularExpression( "^RASTER_TABLE=.*", QRegularExpression::CaseInsensitiveOption | QRegularExpression::MultilineOption ) );
if ( indx > -1 )
{
options.replace( indx, QStringLiteral( "RASTER_TABLE=%1" ).arg( outputLayerName() ) );
}
else
{
options.append( QStringLiteral( "RASTER_TABLE=%1" ).arg( outputLayerName() ) );
}
// Only enable the append mode if the layer doesn't exist yet. For existing layers a 'confirm overwrite' dialog will be shown.
if ( !outputLayerExists() )
{
indx = options.indexOf( QRegularExpression( "^APPEND_SUBDATASET=.*", QRegularExpression::CaseInsensitiveOption | QRegularExpression::MultilineOption ) );
if ( indx > -1 )
{
options.replace( indx, QStringLiteral( "APPEND_SUBDATASET=YES" ) );
}
else
{
options.append( QStringLiteral( "APPEND_SUBDATASET=YES" ) );
}
}
}
return options;
}
QgsRectangle QgsRasterLayerSaveAsDialog::outputRectangle() const
{
return mExtentGroupBox->outputExtent();
}
void QgsRasterLayerSaveAsDialog::hideFormat()
{
mFormatLabel->hide();
mFormatComboBox->hide();
}
void QgsRasterLayerSaveAsDialog::hideOutput()
{
mSaveAsLabel->hide();
mFilename->hide();
QPushButton *okButton = mButtonBox->button( QDialogButtonBox::Ok );
if ( okButton )
{
okButton->setEnabled( true );
}
}
void QgsRasterLayerSaveAsDialog::toggleResolutionSize()
{
bool hasResolution = mDataProvider && mDataProvider->capabilities() & QgsRasterDataProvider::Size;
bool on = mResolutionRadioButton->isChecked();
mXResolutionLineEdit->setEnabled( on );
mYResolutionLineEdit->setEnabled( on );
mOriginalResolutionPushButton->setEnabled( on && hasResolution );
mColumnsLineEdit->setEnabled( !on );
mRowsLineEdit->setEnabled( !on );
mOriginalSizePushButton->setEnabled( !on && hasResolution );
}
void QgsRasterLayerSaveAsDialog::setOriginalResolution()
{
double xRes, yRes;
if ( mDataProvider->capabilities() & QgsRasterDataProvider::Size )
{
xRes = mDataProvider->extent().width() / mDataProvider->xSize();
yRes = mDataProvider->extent().height() / mDataProvider->ySize();
}
else
{
// Init to something if no original resolution is available
xRes = yRes = mDataProvider->extent().width() / 100;
}
setResolution( xRes, yRes, mLayerCrs );
mResolutionState = OriginalResolution;
recalcSize();
}
void QgsRasterLayerSaveAsDialog::setResolution( double xRes, double yRes, const QgsCoordinateReferenceSystem &srcCrs )
{
if ( srcCrs != outputCrs() )
{
// We reproject pixel rectangle from center of selected extent, of course, it gives
// bigger xRes,yRes than reprojected edges (envelope), it may also be that
// close to margins are higher resolutions (even very, too high)
// TODO: consider more precise resolution calculation
QgsPointXY center = outputRectangle().center();
QgsCoordinateTransform ct( srcCrs, outputCrs(), QgsProject::instance() );
QgsPointXY srsCenter = ct.transform( center, QgsCoordinateTransform::ReverseTransform );
QgsRectangle srcExtent( srsCenter.x() - xRes / 2, srsCenter.y() - yRes / 2, srsCenter.x() + xRes / 2, srsCenter.y() + yRes / 2 );
QgsRectangle extent = ct.transform( srcExtent );
xRes = extent.width();
yRes = extent.height();
}
mXResolutionLineEdit->setText( QLocale().toString( xRes ) );
mYResolutionLineEdit->setText( QLocale().toString( yRes ) );
}
void QgsRasterLayerSaveAsDialog::recalcSize()
{
QgsRectangle extent = outputRectangle();
int xSize = xResolution() != 0 ? static_cast<int>( std::round( extent.width() / xResolution() ) ) : 0;
int ySize = yResolution() != 0 ? static_cast<int>( std::round( extent.height() / yResolution() ) ) : 0;
mColumnsLineEdit->setText( QString::number( xSize ) );
mRowsLineEdit->setText( QString::number( ySize ) );
updateResolutionStateMsg();
}
void QgsRasterLayerSaveAsDialog::setOriginalSize()
{
mColumnsLineEdit->setText( QString::number( mDataProvider->xSize() ) );
mRowsLineEdit->setText( QString::number( mDataProvider->ySize() ) );
recalcResolution();
}
void QgsRasterLayerSaveAsDialog::recalcResolution()
{
QgsRectangle extent = outputRectangle();
double xRes = nColumns() != 0 ? extent.width() / nColumns() : 0;
double yRes = nRows() != 0 ? extent.height() / nRows() : 0;
mXResolutionLineEdit->setText( QLocale().toString( xRes ) );
mYResolutionLineEdit->setText( QLocale().toString( yRes ) );
updateResolutionStateMsg();
}
void QgsRasterLayerSaveAsDialog::recalcResolutionSize()
{
if ( mResolutionRadioButton->isChecked() )
{
recalcSize();
}
else
{
mResolutionState = UserResolution;
recalcResolution();
}
}
void QgsRasterLayerSaveAsDialog::updateResolutionStateMsg()
{
QString msg;
switch ( mResolutionState )
{
case OriginalResolution:
msg = tr( "layer" );
break;
case UserResolution:
msg = tr( "user defined" );
break;
default:
break;
}
msg = tr( "Resolution (current: %1)" ).arg( msg );
mResolutionGroupBox->setTitle( msg );
}
void QgsRasterLayerSaveAsDialog::extentChanged()
{
// Whenever extent changes with fixed size, original resolution is lost
if ( mSizeRadioButton->isChecked() )
{
mResolutionState = UserResolution;
}
recalcResolutionSize();
}
void QgsRasterLayerSaveAsDialog::crsChanged()
{
if ( outputCrs() != mPreviousCrs )
{
mExtentGroupBox->setOutputCrs( outputCrs() );
// Reset resolution
if ( mResolutionRadioButton->isChecked() )
{
if ( mResolutionState == OriginalResolution )
{
setOriginalResolution();
}
else
{
// reset from present resolution and present crs
setResolution( xResolution(), yResolution(), mPreviousCrs );
}
}
else
{
// Size does not change, we just recalc resolution from new extent
recalcResolution();
}
}
mPreviousCrs = outputCrs();
}
QgsCoordinateReferenceSystem QgsRasterLayerSaveAsDialog::outputCrs()
{
return mCrsSelector->crs();
}
QgsRasterLayerSaveAsDialog::Mode QgsRasterLayerSaveAsDialog::mode() const
{
if ( mRenderedModeRadioButton->isChecked() ) return RenderedImageMode;
return RawDataMode;
}
void QgsRasterLayerSaveAsDialog::mRawModeRadioButton_toggled( bool checked )
{
mNoDataGroupBox->setEnabled( checked && mDataProvider->bandCount() == 1 );
}
void QgsRasterLayerSaveAsDialog::mAddNoDataManuallyToolButton_clicked()
{
addNoDataRow( std::numeric_limits<double>::quiet_NaN(), std::numeric_limits<double>::quiet_NaN() );
}
void QgsRasterLayerSaveAsDialog::mLoadTransparentNoDataToolButton_clicked()
{
if ( !mRasterLayer->renderer() ) return;
const QgsRasterTransparency *rasterTransparency = mRasterLayer->renderer()->rasterTransparency();
if ( !rasterTransparency ) return;
const auto constTransparentSingleValuePixelList = rasterTransparency->transparentSingleValuePixelList();
for ( const QgsRasterTransparency::TransparentSingleValuePixel &transparencyPixel : constTransparentSingleValuePixelList )
{
if ( transparencyPixel.percentTransparent == 100 )
{
addNoDataRow( transparencyPixel.min, transparencyPixel.max );
if ( transparencyPixel.min != transparencyPixel.max )
{
setNoDataToEdited( mNoDataTableWidget->rowCount() - 1 );
}
}
}
}
void QgsRasterLayerSaveAsDialog::mRemoveSelectedNoDataToolButton_clicked()
{
mNoDataTableWidget->removeRow( mNoDataTableWidget->currentRow() );
}
void QgsRasterLayerSaveAsDialog::mRemoveAllNoDataToolButton_clicked()
{
while ( mNoDataTableWidget->rowCount() > 0 )
{
mNoDataTableWidget->removeRow( 0 );
}
}
void QgsRasterLayerSaveAsDialog::addNoDataRow( double min, double max )
{
mNoDataTableWidget->insertRow( mNoDataTableWidget->rowCount() );
for ( int i = 0; i < 2; i++ )
{
double value = i == 0 ? min : max;
QLineEdit *lineEdit = new QLineEdit();
lineEdit->setFrame( false );
lineEdit->setContentsMargins( 1, 1, 1, 1 );
QString valueString;
switch ( mRasterLayer->dataProvider()->sourceDataType( 1 ) )
{
case Qgis::Float32:
case Qgis::Float64:
lineEdit->setValidator( new QgsDoubleValidator( nullptr ) );
if ( !std::isnan( value ) )
{
valueString = QgsRasterBlock::printValue( value );
}
break;
default:
lineEdit->setValidator( new QIntValidator( nullptr ) );
if ( !std::isnan( value ) )
{
valueString = QLocale().toString( static_cast<int>( value ) );
}
break;
}
lineEdit->setText( valueString );
mNoDataTableWidget->setCellWidget( mNoDataTableWidget->rowCount() - 1, i, lineEdit );
adjustNoDataCellWidth( mNoDataTableWidget->rowCount() - 1, i );
connect( lineEdit, &QLineEdit::textEdited, this, &QgsRasterLayerSaveAsDialog::noDataCellTextEdited );
}
mNoDataTableWidget->resizeColumnsToContents();
mNoDataTableWidget->resizeRowsToContents();
}
void QgsRasterLayerSaveAsDialog::noDataCellTextEdited( const QString &text )
{
Q_UNUSED( text )
QLineEdit *lineEdit = qobject_cast<QLineEdit *>( sender() );
if ( !lineEdit ) return;
int row = -1;
int column = -1;
for ( int r = 0; r < mNoDataTableWidget->rowCount(); r++ )
{
for ( int c = 0; c < mNoDataTableWidget->columnCount(); c++ )
{
if ( mNoDataTableWidget->cellWidget( r, c ) == sender() )
{
row = r;
column = c;
break;
}
}
if ( row != -1 ) break;
}
QgsDebugMsg( QStringLiteral( "row = %1 column =%2" ).arg( row ).arg( column ) );
if ( column == 0 )
{
QLineEdit *toLineEdit = dynamic_cast<QLineEdit *>( mNoDataTableWidget->cellWidget( row, 1 ) );
if ( !toLineEdit ) return;
bool toChanged = mNoDataToEdited.value( row );
QgsDebugMsg( QStringLiteral( "toChanged = %1" ).arg( toChanged ) );
if ( !toChanged )
{
toLineEdit->setText( lineEdit->text() );
}
}
else if ( column == 1 )
{
setNoDataToEdited( row );
}
}
void QgsRasterLayerSaveAsDialog::mTileModeCheckBox_toggled( bool toggled )
{
if ( toggled )
{
// enable pyramids
// Disabled (Radim), auto enabling of pyramids was making impression that
// we (programmers) know better what you (user) want to do,
// certainly auto expanding was a bad experience
//if ( ! mPyramidsGroupBox->isChecked() )
// mPyramidsGroupBox->setChecked( true );
// Auto expanding mPyramidsGroupBox is bad - it auto scrolls content of dialog
//if ( mPyramidsGroupBox->isCollapsed() )
// mPyramidsGroupBox->setCollapsed( false );
//mPyramidsOptionsWidget->checkAllLevels( true );
// Show / hide tile options
mTilesGroupBox->show();
mFilename->setStorageMode( QgsFileWidget::GetDirectory );
mFilename->setDialogTitle( tr( "Select Output Directory" ) );
}
else
{
mTilesGroupBox->hide();
mFilename->setStorageMode( QgsFileWidget::SaveFile );
mFilename->setDialogTitle( tr( "Save Layer As" ) );
}
}
void QgsRasterLayerSaveAsDialog::mPyramidsGroupBox_toggled( bool toggled )
{
Q_UNUSED( toggled )
populatePyramidsLevels();
}
void QgsRasterLayerSaveAsDialog::populatePyramidsLevels()
{
QString text;
if ( mPyramidsGroupBox->isChecked() )
{
QList<QgsRasterPyramid> myPyramidList;
// if use existing, get pyramids from actual layer
// but that's not available yet
if ( mPyramidsUseExistingCheckBox->isChecked() )
{
myPyramidList = mDataProvider->buildPyramidList();
}
else
{
if ( ! mPyramidsOptionsWidget->overviewList().isEmpty() )
myPyramidList = mDataProvider->buildPyramidList( mPyramidsOptionsWidget->overviewList() );
}
for ( const QgsRasterPyramid &pyramid : std::as_const( myPyramidList ) )
{
if ( ! mPyramidsUseExistingCheckBox->isChecked() || pyramid.getExists() )
{
text += QString::number( pyramid.getXDim() ) + QStringLiteral( "x" ) +
QString::number( pyramid.getYDim() ) + ' ';
}
}
}
mPyramidResolutionsLineEdit->setText( text.trimmed() );
}
void QgsRasterLayerSaveAsDialog::setNoDataToEdited( int row )
{
if ( row >= mNoDataToEdited.size() )
{
mNoDataToEdited.resize( row + 1 );
}
mNoDataToEdited[row] = true;
}
double QgsRasterLayerSaveAsDialog::noDataCellValue( int row, int column ) const
{
QLineEdit *lineEdit = dynamic_cast<QLineEdit *>( mNoDataTableWidget->cellWidget( row, column ) );
if ( !lineEdit || lineEdit->text().isEmpty() )
{
return std::numeric_limits<double>::quiet_NaN();
}
return QgsDoubleValidator::toDouble( lineEdit->text() );
}
void QgsRasterLayerSaveAsDialog::adjustNoDataCellWidth( int row, int column )
{
QLineEdit *lineEdit = dynamic_cast<QLineEdit *>( mNoDataTableWidget->cellWidget( row, column ) );
if ( !lineEdit ) return;
int width = std::max( lineEdit->fontMetrics().boundingRect( lineEdit->text() ).width() + 10, 100 );
width = std::max( width, mNoDataTableWidget->columnWidth( column ) );
lineEdit->setFixedWidth( width );
}
QgsRasterRangeList QgsRasterLayerSaveAsDialog::noData() const
{
QgsRasterRangeList noDataList;
if ( ! mNoDataGroupBox->isChecked() )
return noDataList;
int rows = mNoDataTableWidget->rowCount();
noDataList.reserve( rows );
for ( int r = 0; r < rows; r++ )
{
QgsRasterRange noData( noDataCellValue( r, 0 ), noDataCellValue( r, 1 ) );
noDataList.append( noData );
}
return noDataList;
}
QList<int> QgsRasterLayerSaveAsDialog::pyramidsList() const
{
return mPyramidsGroupBox->isChecked() ? mPyramidsOptionsWidget->overviewList() : QList<int>();
}
QgsRaster::RasterBuildPyramids QgsRasterLayerSaveAsDialog::buildPyramidsFlag() const
{
if ( ! mPyramidsGroupBox->isChecked() )
return QgsRaster::PyramidsFlagNo;
else if ( mPyramidsUseExistingCheckBox->isChecked() )
return QgsRaster::PyramidsCopyExisting;
else
return QgsRaster::PyramidsFlagYes;
}
bool QgsRasterLayerSaveAsDialog::validate() const
{
if ( mCreateOptionsGroupBox->isChecked() )
{
QString message = mCreateOptionsWidget->validateOptions( true, false );
if ( !message.isNull() )
return false;
}
if ( mPyramidsGroupBox->isChecked() )
{
QString message = mPyramidsOptionsWidget->createOptionsWidget()->validateOptions( true, false );
if ( !message.isNull() )
return false;
}
return true;
}
bool QgsRasterLayerSaveAsDialog::outputLayerExists() const
{
QString vectorUri;
QString rasterUri;
if ( outputFormat() == QLatin1String( "GPKG" ) )
{
rasterUri = QStringLiteral( "GPKG:%1:%2" ).arg( outputFileName(), outputLayerName() );
vectorUri = QStringLiteral( "%1|layername=%2" ).arg( outputFileName(), outputLayerName() );
}
else
{
rasterUri = outputFileName();
}
QgsRasterLayer rasterLayer( rasterUri, QString( ), QStringLiteral( "gdal" ) );
if ( !vectorUri.isEmpty() )
{
QgsVectorLayer vectorLayer( vectorUri, QString( ), QStringLiteral( "ogr" ) );
return rasterLayer.isValid() || vectorLayer.isValid();
}
else
{
return rasterLayer.isValid();
}
}
void QgsRasterLayerSaveAsDialog::accept()
{
if ( !validate() )
{
return;
}
if ( outputFormat() == QLatin1String( "GPKG" ) && outputLayerExists() &&
QMessageBox::warning( this, tr( "Save Raster Layer" ),
tr( "The layer %1 already exists in the target file, and overwriting layers in GeoPackage is not supported. "
"Do you want to overwrite the whole file?" ).arg( outputLayerName() ),
QMessageBox::Yes | QMessageBox::No ) == QMessageBox::No )
{
return;
}
QDialog::accept();
}
void QgsRasterLayerSaveAsDialog::showHelp()
{
QgsHelp::openHelp( QStringLiteral( "managing_data_source/create_layers.html#creating-new-layers-from-an-existing-layer" ) );
}