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