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