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