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