QGIS API Documentation 3.28.0-Firenze (ed3ad0430f)
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"
26#include "qgssettings.h"
27#include "qgsrasterfilewriter.h"
28#include "qgsvectorlayer.h"
29#include "cpl_string.h"
30#include "qgsproject.h"
31#include <gdal.h>
32#include "qgsmessagelog.h"
33#include "qgsgui.h"
34#include "qgsdoublevalidator.h"
35#include "qgsdatums.h"
36
37#include <QFileDialog>
38#include <QMessageBox>
39#include <QRegularExpression>
40
42 QgsRasterDataProvider *sourceProvider, const QgsRectangle &currentExtent,
43 const QgsCoordinateReferenceSystem &layerCrs, const QgsCoordinateReferenceSystem &currentCrs,
44 QWidget *parent, Qt::WindowFlags f )
45 : QDialog( parent, f )
46 , mRasterLayer( rasterLayer )
47 , mDataProvider( sourceProvider )
48 , mCurrentExtent( currentExtent )
49 , mLayerCrs( layerCrs )
50 , mCurrentCrs( currentCrs )
51 , mResolutionState( OriginalResolution )
52{
53 setupUi( this );
55 connect( mRawModeRadioButton, &QRadioButton::toggled, this, &QgsRasterLayerSaveAsDialog::mRawModeRadioButton_toggled );
56 connect( mFormatComboBox, &QComboBox::currentTextChanged, this, &QgsRasterLayerSaveAsDialog::mFormatComboBox_currentIndexChanged );
57 connect( mResolutionRadioButton, &QRadioButton::toggled, this, &QgsRasterLayerSaveAsDialog::mResolutionRadioButton_toggled );
58 connect( mOriginalResolutionPushButton, &QPushButton::clicked, this, &QgsRasterLayerSaveAsDialog::mOriginalResolutionPushButton_clicked );
59 connect( mXResolutionLineEdit, &QLineEdit::textEdited, this, &QgsRasterLayerSaveAsDialog::mXResolutionLineEdit_textEdited );
60 connect( mYResolutionLineEdit, &QLineEdit::textEdited, this, &QgsRasterLayerSaveAsDialog::mYResolutionLineEdit_textEdited );
61 connect( mOriginalSizePushButton, &QPushButton::clicked, this, &QgsRasterLayerSaveAsDialog::mOriginalSizePushButton_clicked );
62 connect( mColumnsLineEdit, &QLineEdit::textEdited, this, &QgsRasterLayerSaveAsDialog::mColumnsLineEdit_textEdited );
63 connect( mRowsLineEdit, &QLineEdit::textEdited, this, &QgsRasterLayerSaveAsDialog::mRowsLineEdit_textEdited );
64 connect( mAddNoDataManuallyToolButton, &QPushButton::clicked, this, &QgsRasterLayerSaveAsDialog::mAddNoDataManuallyToolButton_clicked );
65 connect( mLoadTransparentNoDataToolButton, &QPushButton::clicked, this, &QgsRasterLayerSaveAsDialog::mLoadTransparentNoDataToolButton_clicked );
66 connect( mRemoveSelectedNoDataToolButton, &QPushButton::clicked, this, &QgsRasterLayerSaveAsDialog::mRemoveSelectedNoDataToolButton_clicked );
67 connect( mRemoveAllNoDataToolButton, &QPushButton::clicked, this, &QgsRasterLayerSaveAsDialog::mRemoveAllNoDataToolButton_clicked );
68 connect( mTileModeCheckBox, &QCheckBox::toggled, this, &QgsRasterLayerSaveAsDialog::mTileModeCheckBox_toggled );
69 connect( mPyramidsGroupBox, &QgsCollapsibleGroupBox::toggled, this, &QgsRasterLayerSaveAsDialog::mPyramidsGroupBox_toggled );
70 mAddNoDataManuallyToolButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/symbologyAdd.svg" ) ) );
71 mLoadTransparentNoDataToolButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionFileOpen.svg" ) ) );
72 mRemoveSelectedNoDataToolButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/symbologyRemove.svg" ) ) );
73 mRemoveAllNoDataToolButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionRemove.svg" ) ) );
74
75 mNoDataTableWidget->setColumnCount( 2 );
76 mNoDataTableWidget->setHorizontalHeaderItem( 0, new QTableWidgetItem( tr( "From" ) ) );
77 mNoDataTableWidget->setHorizontalHeaderItem( 1, new QTableWidgetItem( tr( "To" ) ) );
78
79 mRawModeRadioButton_toggled( true );
80
81 setValidators();
82
83 toggleResolutionSize();
84
85 insertAvailableOutputFormats();
86
87 //fill reasonable default values depending on the provider
88 if ( mDataProvider )
89 {
90 if ( mDataProvider->capabilities() & QgsRasterDataProvider::Size )
91 {
92 setOriginalResolution();
93 int xSize = mDataProvider->xSize();
94 int ySize = mDataProvider->ySize();
95 mMaximumSizeXLineEdit->setText( QString::number( xSize ) );
96 mMaximumSizeYLineEdit->setText( QString::number( ySize ) );
97 }
98 else //wms, sometimes wcs
99 {
100 mTileModeCheckBox->setChecked( true );
101 mMaximumSizeXLineEdit->setText( QString::number( 2000 ) );
102 mMaximumSizeYLineEdit->setText( QString::number( 2000 ) );
103 }
104
105 // setup creation option widget
106 mCreateOptionsWidget->setProvider( mDataProvider->name() );
107 if ( mDataProvider->name() == QLatin1String( "gdal" ) )
108 {
109 mCreateOptionsWidget->setFormat( mFormatComboBox->currentData().toString() );
110 }
111 mCreateOptionsWidget->setRasterLayer( mRasterLayer );
112 mCreateOptionsWidget->update();
113 }
114
115 // Only do pyramids if dealing directly with GDAL.
116 if ( mDataProvider && mDataProvider->capabilities() & QgsRasterDataProvider::BuildPyramids )
117 {
118 // setup pyramids option widget
119 // mPyramidsOptionsWidget->createOptionsWidget()->setType( QgsRasterFormatSaveOptionsWidget::ProfileLineEdit );
120 mPyramidsOptionsWidget->createOptionsWidget()->setRasterLayer( mRasterLayer );
121
122 // TODO enable "use existing", has no effect for now, because using Create() in gdal provider
123 // if ( ! mDataProvider->hasPyramids() )
124 // mPyramidsButtonGroup->button( QgsRaster::PyramidsCopyExisting )->setEnabled( false );
125 mPyramidsUseExistingCheckBox->setEnabled( false );
126 mPyramidsUseExistingCheckBox->setVisible( false );
127
128 populatePyramidsLevels();
129 connect( mPyramidsOptionsWidget, &QgsRasterPyramidsOptionsWidget::overviewListChanged,
130 this, &QgsRasterLayerSaveAsDialog::populatePyramidsLevels );
131 }
132 else
133 {
134 mPyramidsGroupBox->setEnabled( false );
135 }
136
137 // restore checked state for most groupboxes (default is to restore collapsed state)
138 // create options and pyramids will be preset, if user has selected defaults in the gdal options dlg
139 mCreateOptionsGroupBox->setSaveCheckedState( true );
140 //mTilesGroupBox->setSaveCheckedState( true );
141 // don't restore nodata, it needs user input
142 // pyramids are not necessarily built every time
143
144 try
145 {
146 const QgsDatumEnsemble ensemble = mLayerCrs.datumEnsemble();
147 if ( ensemble.isValid() )
148 {
149 mCrsSelector->setSourceEnsemble( ensemble.name() );
150 }
151 }
152 catch ( QgsNotSupportedException & )
153 {
154 }
155 mCrsSelector->setShowAccuracyWarnings( true );
156
157 mCrsSelector->setLayerCrs( mLayerCrs );
158 //default to layer CRS - see https://github.com/qgis/QGIS/issues/22211 for discussion
159 mCrsSelector->setCrs( mLayerCrs );
160
161 connect( mCrsSelector, &QgsProjectionSelectionWidget::crsChanged,
162 this, &QgsRasterLayerSaveAsDialog::crsChanged );
163
164 QPushButton *okButton = mButtonBox->button( QDialogButtonBox::Ok );
165 if ( okButton )
166 {
167 okButton->setEnabled( false );
168 }
169
170#ifdef Q_OS_WIN
171 mHelpButtonBox->setVisible( false );
172 mButtonBox->addButton( QDialogButtonBox::Help );
173 connect( mButtonBox, &QDialogButtonBox::helpRequested, this, &QgsRasterLayerSaveAsDialog::showHelp );
174#else
175 connect( mHelpButtonBox, &QDialogButtonBox::helpRequested, this, &QgsRasterLayerSaveAsDialog::showHelp );
176#endif
177 connect( mButtonBox, &QDialogButtonBox::accepted, this, &QgsRasterLayerSaveAsDialog::accept );
178 connect( mButtonBox, &QDialogButtonBox::rejected, this, &QgsRasterLayerSaveAsDialog::reject );
179
180 mExtentGroupBox->setOutputCrs( outputCrs() );
181 mExtentGroupBox->setOriginalExtent( mDataProvider->extent(), mLayerCrs );
182 mExtentGroupBox->setCurrentExtent( mCurrentExtent, mCurrentCrs );
183 mExtentGroupBox->setOutputExtentFromOriginal();
184 connect( mExtentGroupBox, &QgsExtentGroupBox::extentChanged, this, &QgsRasterLayerSaveAsDialog::extentChanged );
185
186 recalcResolutionSize();
187
188 QgsSettings settings;
189
190 if ( mTileModeCheckBox->isChecked() )
191 {
192 mTilesGroupBox->show();
193 mFilename->setStorageMode( QgsFileWidget::GetDirectory );
194 mFilename->setDialogTitle( tr( "Select Output Directory" ) );
195 }
196 else
197 {
198 mTilesGroupBox->hide();
199 mFilename->setStorageMode( QgsFileWidget::SaveFile );
200 mFilename->setDialogTitle( tr( "Save Layer As" ) );
201 }
202
203 mFilename->setDefaultRoot( settings.value( QStringLiteral( "UI/lastRasterFileDir" ), QDir::homePath() ).toString() );
204 connect( mFilename, &QgsFileWidget::fileChanged, this, [ = ]( const QString & filePath )
205 {
206 QgsSettings settings;
207 QFileInfo tmplFileInfo( filePath );
208 settings.setValue( QStringLiteral( "UI/lastRasterFileDir" ), tmplFileInfo.absolutePath() );
209
210 if ( !filePath.isEmpty() && mLayerName->isEnabled() )
211 {
212 QFileInfo fileInfo( filePath );
213 mLayerName->setText( fileInfo.baseName() );
214 }
215
216 if ( mTileModeCheckBox->isChecked() )
217 {
218 QString fileName = filePath;
219 Q_FOREVER
220 {
221 // TODO: would not it be better to select .vrt file instead of directory?
222 //fileName = QFileDialog::getSaveFileName( this, tr( "Select output file" ), QString(), tr( "VRT" ) + " (*.vrt *.VRT)" );
223 if ( fileName.isEmpty() )
224 break; // canceled
225
226 // Check if directory is empty
227 QDir dir( fileName );
228 QString baseName = QFileInfo( fileName ).baseName();
229 QStringList filters;
230 filters << QStringLiteral( "%1.*" ).arg( baseName );
231 QStringList files = dir.entryList( filters );
232 if ( files.isEmpty() )
233 break;
234
235 if ( QMessageBox::warning( this, tr( "Save Raster Layer" ),
236 tr( "The directory %1 contains files which will be overwritten: %2" ).arg( dir.absolutePath(), files.join( QLatin1String( ", " ) ) ),
237 QMessageBox::Ok | QMessageBox::Cancel ) == QMessageBox::Ok )
238 break;
239
240 fileName = QFileDialog::getExistingDirectory( this, tr( "Select output directory" ), tmplFileInfo.absolutePath() );
241 }
242 }
243
244 QPushButton *okButton = mButtonBox->button( QDialogButtonBox::Ok );
245 if ( !okButton )
246 {
247 return;
248 }
249 okButton->setEnabled( tmplFileInfo.absoluteDir().exists() );
250 } );
251}
252
253void QgsRasterLayerSaveAsDialog::insertAvailableOutputFormats()
254{
255 GDALAllRegister();
256
257 int nDrivers = GDALGetDriverCount();
258 QMap< int, QPair< QString, QString > > topPriorityDrivers;
259 QMap< QString, QString > lowPriorityDrivers;
260
261 for ( int i = 0; i < nDrivers; ++i )
262 {
263 GDALDriverH driver = GDALGetDriver( i );
264 if ( driver )
265 {
267 {
268 QString driverShortName = GDALGetDriverShortName( driver );
269 QString driverLongName = GDALGetDriverLongName( driver );
270 if ( driverShortName == QLatin1String( "MEM" ) )
271 {
272 // in memory rasters are not (yet) supported because the GDAL dataset handle
273 // would need to be passed directly to QgsRasterLayer (it is not possible to
274 // close it in raster calculator and reopen the dataset again in raster layer)
275 continue;
276 }
277 else if ( driverShortName == QLatin1String( "VRT" ) )
278 {
279 // skip GDAL vrt driver, since we handle that format manually
280 continue;
281 }
282 else if ( driverShortName == QLatin1String( "GTiff" ) )
283 {
284 // always list geotiff first
285 topPriorityDrivers.insert( 1, qMakePair( driverLongName, driverShortName ) );
286 }
287 else if ( driverShortName == QLatin1String( "GPKG" ) )
288 {
289 // and gpkg second
290 topPriorityDrivers.insert( 2, qMakePair( driverLongName, driverShortName ) );
291 }
292 else
293 {
294 lowPriorityDrivers.insert( driverLongName, driverShortName );
295 }
296 }
297 }
298 }
299
300 // will be sorted by priority, so that geotiff and geopackage are listed first
301 for ( auto priorityDriversIt = topPriorityDrivers.constBegin(); priorityDriversIt != topPriorityDrivers.constEnd(); ++priorityDriversIt )
302 {
303 mFormatComboBox->addItem( priorityDriversIt.value().first, priorityDriversIt.value().second );
304 }
305 // will be sorted by driver name
306 for ( auto lowPriorityDriversIt = lowPriorityDrivers.constBegin(); lowPriorityDriversIt != lowPriorityDrivers.constEnd(); ++lowPriorityDriversIt )
307 {
308 mFormatComboBox->addItem( lowPriorityDriversIt.key(), lowPriorityDriversIt.value() );
309 }
310
311}
312
313void QgsRasterLayerSaveAsDialog::setValidators()
314{
315 mXResolutionLineEdit->setValidator( new QgsDoubleValidator( this ) );
316 mYResolutionLineEdit->setValidator( new QgsDoubleValidator( this ) );
317 mColumnsLineEdit->setValidator( new QIntValidator( this ) );
318 mRowsLineEdit->setValidator( new QIntValidator( this ) );
319 mMaximumSizeXLineEdit->setValidator( new QIntValidator( this ) );
320 mMaximumSizeYLineEdit->setValidator( new QIntValidator( this ) );
321}
322
323void QgsRasterLayerSaveAsDialog::mFormatComboBox_currentIndexChanged( const QString & )
324{
325 //gdal-specific
326 if ( mDataProvider && mDataProvider->name() == QLatin1String( "gdal" ) )
327 {
328 mCreateOptionsWidget->setFormat( outputFormat() );
329 mCreateOptionsWidget->update();
330 }
331
332 QStringList extensions = QgsRasterFileWriter::extensionsForFormat( outputFormat() );
333 QString filter;
334 if ( extensions.empty() )
335 filter = tr( "All files (*.*)" );
336 else
337 {
338 filter = QStringLiteral( "%1 (*.%2);;%3" ).arg( mFormatComboBox->currentText(),
339 extensions.join( QLatin1String( " *." ) ),
340 tr( "All files (*.*)" ) );
341 }
342 mFilename->setFilter( filter );
343
344 // Disable mTileModeCheckBox for GeoPackages
345 mTileModeCheckBox->setEnabled( outputFormat() != QLatin1String( "GPKG" ) );
346 mFilename->setConfirmOverwrite( outputFormat() != QLatin1String( "GPKG" ) );
347 mLayerName->setEnabled( outputFormat() == QLatin1String( "GPKG" ) );
348 if ( mLayerName->isEnabled() )
349 {
350 QString layerName = QFileInfo( mFilename->filePath() ).baseName();
351 mLayerName->setText( layerName );
352 mTileModeCheckBox->setChecked( false );
353 }
354 else
355 {
356 mLayerName->setText( QString() );
357 }
358}
359
361{
362 return mColumnsLineEdit->text().toInt();
363}
364
366{
367 return mRowsLineEdit->text().toInt();
368}
369
371{
372 return QgsDoubleValidator::toDouble( mXResolutionLineEdit->text() );
373}
374
376{
377 return QgsDoubleValidator::toDouble( mYResolutionLineEdit->text() );
378}
379
381{
382 return mMaximumSizeXLineEdit->text().toInt();
383}
384
386{
387 return mMaximumSizeYLineEdit->text().toInt();
388}
389
391{
392 return mTileModeCheckBox->isChecked();
393}
394
396{
397 return mAddToCanvas->isChecked();
398}
399
401{
402 mAddToCanvas->setChecked( checked );
403}
404
406{
407 QString fileName = mFilename->filePath();
408
409 if ( mFilename->storageMode() != QgsFileWidget::GetDirectory )
410 {
411 QStringList extensions = QgsRasterFileWriter::extensionsForFormat( outputFormat() );
412 QString defaultExt;
413 if ( !extensions.empty() )
414 {
415 defaultExt = extensions.at( 0 );
416 }
417
418 // ensure the user never omits the extension from the file name
419 QFileInfo fi( fileName );
420 if ( !fileName.isEmpty() && fi.suffix().isEmpty() && !defaultExt.isEmpty() )
421 {
422 fileName += '.' + defaultExt;
423 }
424 }
425
426 return fileName;
427}
428
430{
431 if ( mLayerName->text().isEmpty() && outputFormat() == QLatin1String( "GPKG" ) && !mTileModeCheckBox->isChecked() )
432 {
433 // Always return layer name for GeoPackages
434 return QFileInfo( mFilename->filePath() ).baseName();
435 }
436 else
437 {
438 return mLayerName->text();
439 }
440}
441
443{
444 return mFormatComboBox->currentData().toString();
445}
446
448{
449 QStringList options = mCreateOptionsGroupBox->isChecked() ? mCreateOptionsWidget->options() : QStringList();
450 if ( outputFormat() == QLatin1String( "GPKG" ) )
451 {
452 // Overwrite the GPKG table options
453 int indx = options.indexOf( QRegularExpression( "^RASTER_TABLE=.*", QRegularExpression::CaseInsensitiveOption | QRegularExpression::MultilineOption ) );
454 if ( indx > -1 )
455 {
456 options.replace( indx, QStringLiteral( "RASTER_TABLE=%1" ).arg( outputLayerName() ) );
457 }
458 else
459 {
460 options.append( QStringLiteral( "RASTER_TABLE=%1" ).arg( outputLayerName() ) );
461 }
462
463 // Only enable the append mode if the layer doesn't exist yet. For existing layers a 'confirm overwrite' dialog will be shown.
464 if ( !outputLayerExists() )
465 {
466 indx = options.indexOf( QRegularExpression( "^APPEND_SUBDATASET=.*", QRegularExpression::CaseInsensitiveOption | QRegularExpression::MultilineOption ) );
467 if ( indx > -1 )
468 {
469 options.replace( indx, QStringLiteral( "APPEND_SUBDATASET=YES" ) );
470 }
471 else
472 {
473 options.append( QStringLiteral( "APPEND_SUBDATASET=YES" ) );
474 }
475 }
476 }
477 return options;
478}
479
481{
482 return mExtentGroupBox->outputExtent();
483}
484
486{
487 mFormatLabel->hide();
488 mFormatComboBox->hide();
489}
490
492{
493 mSaveAsLabel->hide();
494 mFilename->hide();
495 QPushButton *okButton = mButtonBox->button( QDialogButtonBox::Ok );
496 if ( okButton )
497 {
498 okButton->setEnabled( true );
499 }
500}
501
502void QgsRasterLayerSaveAsDialog::toggleResolutionSize()
503{
504 bool hasResolution = mDataProvider && mDataProvider->capabilities() & QgsRasterDataProvider::Size;
505
506 bool on = mResolutionRadioButton->isChecked();
507 mXResolutionLineEdit->setEnabled( on );
508 mYResolutionLineEdit->setEnabled( on );
509 mOriginalResolutionPushButton->setEnabled( on && hasResolution );
510 mColumnsLineEdit->setEnabled( !on );
511 mRowsLineEdit->setEnabled( !on );
512 mOriginalSizePushButton->setEnabled( !on && hasResolution );
513}
514
515void QgsRasterLayerSaveAsDialog::setOriginalResolution()
516{
517 double xRes, yRes;
518
519 if ( mDataProvider->capabilities() & QgsRasterDataProvider::Size )
520 {
521 xRes = mDataProvider->extent().width() / mDataProvider->xSize();
522 yRes = mDataProvider->extent().height() / mDataProvider->ySize();
523 }
524 else
525 {
526 // Init to something if no original resolution is available
527 xRes = yRes = mDataProvider->extent().width() / 100;
528 }
529 setResolution( xRes, yRes, mLayerCrs );
530 mResolutionState = OriginalResolution;
531 recalcSize();
532}
533
534void QgsRasterLayerSaveAsDialog::setResolution( double xRes, double yRes, const QgsCoordinateReferenceSystem &srcCrs )
535{
536 if ( srcCrs != outputCrs() )
537 {
538 // We reproject pixel rectangle from center of selected extent, of course, it gives
539 // bigger xRes,yRes than reprojected edges (envelope), it may also be that
540 // close to margins are higher resolutions (even very, too high)
541 // TODO: consider more precise resolution calculation
542
543 QgsPointXY center = outputRectangle().center();
545 QgsPointXY srsCenter = ct.transform( center, Qgis::TransformDirection::Reverse );
546
547 QgsRectangle srcExtent( srsCenter.x() - xRes / 2, srsCenter.y() - yRes / 2, srsCenter.x() + xRes / 2, srsCenter.y() + yRes / 2 );
548
549 QgsRectangle extent = ct.transform( srcExtent );
550 xRes = extent.width();
551 yRes = extent.height();
552 }
553 mXResolutionLineEdit->setText( QLocale().toString( xRes ) );
554 mYResolutionLineEdit->setText( QLocale().toString( yRes ) );
555}
556
557void QgsRasterLayerSaveAsDialog::recalcSize()
558{
559 QgsRectangle extent = outputRectangle();
560 int xSize = xResolution() != 0 ? static_cast<int>( std::round( extent.width() / xResolution() ) ) : 0;
561 int ySize = yResolution() != 0 ? static_cast<int>( std::round( extent.height() / yResolution() ) ) : 0;
562 mColumnsLineEdit->setText( QString::number( xSize ) );
563 mRowsLineEdit->setText( QString::number( ySize ) );
564 updateResolutionStateMsg();
565}
566
567void QgsRasterLayerSaveAsDialog::setOriginalSize()
568{
569 mColumnsLineEdit->setText( QString::number( mDataProvider->xSize() ) );
570 mRowsLineEdit->setText( QString::number( mDataProvider->ySize() ) );
571 recalcResolution();
572}
573
574void QgsRasterLayerSaveAsDialog::recalcResolution()
575{
576 QgsRectangle extent = outputRectangle();
577 double xRes = nColumns() != 0 ? extent.width() / nColumns() : 0;
578 double yRes = nRows() != 0 ? extent.height() / nRows() : 0;
579 mXResolutionLineEdit->setText( QLocale().toString( xRes ) );
580 mYResolutionLineEdit->setText( QLocale().toString( yRes ) );
581 updateResolutionStateMsg();
582}
583
584void QgsRasterLayerSaveAsDialog::recalcResolutionSize()
585{
586 if ( mResolutionRadioButton->isChecked() )
587 {
588 recalcSize();
589 }
590 else
591 {
592 mResolutionState = UserResolution;
593 recalcResolution();
594 }
595}
596
597void QgsRasterLayerSaveAsDialog::updateResolutionStateMsg()
598{
599 QString msg;
600 switch ( mResolutionState )
601 {
603 msg = tr( "layer" );
604 break;
605 case UserResolution:
606 msg = tr( "user defined" );
607 break;
608 default:
609 break;
610 }
611 msg = tr( "Resolution (current: %1)" ).arg( msg );
612 mResolutionGroupBox->setTitle( msg );
613}
614
615void QgsRasterLayerSaveAsDialog::extentChanged()
616{
617 // Whenever extent changes with fixed size, original resolution is lost
618 if ( mSizeRadioButton->isChecked() )
619 {
620 mResolutionState = UserResolution;
621 }
622 recalcResolutionSize();
623}
624
625void QgsRasterLayerSaveAsDialog::crsChanged()
626{
627 if ( outputCrs() != mPreviousCrs )
628 {
629 mExtentGroupBox->setOutputCrs( outputCrs() );
630
631 // Reset resolution
632 if ( mResolutionRadioButton->isChecked() )
633 {
634 if ( mResolutionState == OriginalResolution )
635 {
636 setOriginalResolution();
637 }
638 else
639 {
640 // reset from present resolution and present crs
641 setResolution( xResolution(), yResolution(), mPreviousCrs );
642 }
643 }
644 else
645 {
646 // Size does not change, we just recalc resolution from new extent
647 recalcResolution();
648 }
649 }
650 mPreviousCrs = outputCrs();
651}
652
654{
655 return mCrsSelector->crs();
656}
657
659{
660 if ( mRenderedModeRadioButton->isChecked() ) return RenderedImageMode;
661 return RawDataMode;
662}
663
664void QgsRasterLayerSaveAsDialog::mRawModeRadioButton_toggled( bool checked )
665{
666 mNoDataGroupBox->setEnabled( checked && mDataProvider->bandCount() == 1 );
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 ( transparencyPixel.percentTransparent == 100 )
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 QgsDebugMsg( QStringLiteral( "row = %1 column =%2" ).arg( row ).arg( column ) );
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 QgsDebugMsg( QStringLiteral( "toChanged = %1" ).arg( toChanged ) );
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}
@ Float32
Thirty two bit floating point (float)
@ Float64
Sixty four bit floating point (double)
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 SIP_THROW(QgsNotSupportedException)
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:178
static void openHelp(const QString &key)
Opens help topic for the given help key using default system web browser.
Definition: qgshelp.cpp:38
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:59
double y
Definition: qgspointxy.h:63
Q_GADGET double x
Definition: qgspointxy.h:62
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:477
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.
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.
QgsRaster::RasterBuildPyramids buildPyramidsFlag() const
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...
QList< QgsRasterTransparency::TransparentSingleValuePixel > transparentSingleValuePixelList() const
Returns the transparent single value pixel list.
RasterBuildPyramids
Definition: qgsraster.h:75
@ PyramidsFlagYes
Definition: qgsraster.h:77
@ PyramidsFlagNo
Definition: qgsraster.h:76
@ PyramidsCopyExisting
Definition: qgsraster.h:78
A rectangle specified with double values.
Definition: qgsrectangle.h:42
double height() const SIP_HOLDGIL
Returns the height of the rectangle.
Definition: qgsrectangle.h:230
double width() const SIP_HOLDGIL
Returns the width of the rectangle.
Definition: qgsrectangle.h:223
QgsPointXY center() const SIP_HOLDGIL
Returns the center point of the rectangle.
Definition: qgsrectangle.h:251
This class is a composition of two QSettings instances:
Definition: qgssettings.h:62
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
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
QList< QgsRasterRange > QgsRasterRangeList