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