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