QGIS API Documentation 3.41.0-Master (cea29feecf2)
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
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 mCreateOptionsWidget->setProvider( mDataProvider->name() );
104 if ( mDataProvider->name() == QLatin1String( "gdal" ) )
105 {
106 mCreateOptionsWidget->setFormat( mFormatComboBox->currentData().toString() );
107 }
108 mCreateOptionsWidget->setRasterLayer( mRasterLayer );
109 mCreateOptionsWidget->update();
110 }
111
112 // Only do pyramids if dealing directly with GDAL.
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 mCreateOptionsWidget->setFormat( outputFormat() );
321 mCreateOptionsWidget->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 QStringList options = mCreateOptionsGroupBox->isChecked() ? mCreateOptionsWidget->options() : QStringList();
440 if ( outputFormat() == QLatin1String( "GPKG" ) )
441 {
442 // Overwrite the GPKG table options
443 int indx = options.indexOf( QRegularExpression( "^RASTER_TABLE=.*", QRegularExpression::CaseInsensitiveOption | QRegularExpression::MultilineOption ) );
444 if ( indx > -1 )
445 {
446 options.replace( indx, QStringLiteral( "RASTER_TABLE=%1" ).arg( outputLayerName() ) );
447 }
448 else
449 {
450 options.append( QStringLiteral( "RASTER_TABLE=%1" ).arg( outputLayerName() ) );
451 }
452
453 // Only enable the append mode if the layer doesn't exist yet. For existing layers a 'confirm overwrite' dialog will be shown.
454 if ( !outputLayerExists() )
455 {
456 indx = options.indexOf( QRegularExpression( "^APPEND_SUBDATASET=.*", QRegularExpression::CaseInsensitiveOption | QRegularExpression::MultilineOption ) );
457 if ( indx > -1 )
458 {
459 options.replace( indx, QStringLiteral( "APPEND_SUBDATASET=YES" ) );
460 }
461 else
462 {
463 options.append( QStringLiteral( "APPEND_SUBDATASET=YES" ) );
464 }
465 }
466 }
467 return options;
468}
469
471{
472 return mExtentGroupBox->outputExtent();
473}
474
476{
477 mFormatLabel->hide();
478 mFormatComboBox->hide();
479}
480
482{
483 mSaveAsLabel->hide();
484 mFilename->hide();
485 QPushButton *okButton = mButtonBox->button( QDialogButtonBox::Ok );
486 if ( okButton )
487 {
488 okButton->setEnabled( true );
489 }
490}
491
492void QgsRasterLayerSaveAsDialog::toggleResolutionSize()
493{
494 bool hasResolution = mDataProvider && mDataProvider->capabilities() & Qgis::RasterInterfaceCapability::Size;
495
496 bool on = mResolutionRadioButton->isChecked();
497 mXResolutionLineEdit->setEnabled( on );
498 mYResolutionLineEdit->setEnabled( on );
499 mOriginalResolutionPushButton->setEnabled( on && hasResolution );
500 mColumnsLineEdit->setEnabled( !on );
501 mRowsLineEdit->setEnabled( !on );
502 mOriginalSizePushButton->setEnabled( !on && hasResolution );
503}
504
505void QgsRasterLayerSaveAsDialog::setOriginalResolution()
506{
507 double xRes, yRes;
508
510 {
511 xRes = mDataProvider->extent().width() / mDataProvider->xSize();
512 yRes = mDataProvider->extent().height() / mDataProvider->ySize();
513 }
514 else
515 {
516 // Init to something if no original resolution is available
517 xRes = yRes = mDataProvider->extent().width() / 100;
518 }
519 setResolution( xRes, yRes, mLayerCrs );
520 mResolutionState = OriginalResolution;
521 recalcSize();
522}
523
524void QgsRasterLayerSaveAsDialog::setResolution( double xRes, double yRes, const QgsCoordinateReferenceSystem &srcCrs )
525{
526 if ( srcCrs != outputCrs() )
527 {
528 // We reproject pixel rectangle from center of selected extent, of course, it gives
529 // bigger xRes,yRes than reprojected edges (envelope), it may also be that
530 // close to margins are higher resolutions (even very, too high)
531 // TODO: consider more precise resolution calculation
532
533 QgsPointXY center = outputRectangle().center();
535 QgsPointXY srsCenter = ct.transform( center, Qgis::TransformDirection::Reverse );
536
537 QgsRectangle srcExtent( srsCenter.x() - xRes / 2, srsCenter.y() - yRes / 2, srsCenter.x() + xRes / 2, srsCenter.y() + yRes / 2 );
538
539 QgsRectangle extent = ct.transform( srcExtent );
540 xRes = extent.width();
541 yRes = extent.height();
542 }
543 mXResolutionLineEdit->setText( QLocale().toString( xRes ) );
544 mYResolutionLineEdit->setText( QLocale().toString( yRes ) );
545}
546
547void QgsRasterLayerSaveAsDialog::recalcSize()
548{
549 QgsRectangle extent = outputRectangle();
550 int xSize = xResolution() != 0 ? static_cast<int>( std::round( extent.width() / xResolution() ) ) : 0;
551 int ySize = yResolution() != 0 ? static_cast<int>( std::round( extent.height() / yResolution() ) ) : 0;
552 mColumnsLineEdit->setText( QString::number( xSize ) );
553 mRowsLineEdit->setText( QString::number( ySize ) );
554 updateResolutionStateMsg();
555}
556
557void QgsRasterLayerSaveAsDialog::setOriginalSize()
558{
559 mColumnsLineEdit->setText( QString::number( mDataProvider->xSize() ) );
560 mRowsLineEdit->setText( QString::number( mDataProvider->ySize() ) );
561 recalcResolution();
562}
563
564void QgsRasterLayerSaveAsDialog::recalcResolution()
565{
566 QgsRectangle extent = outputRectangle();
567 double xRes = nColumns() != 0 ? extent.width() / nColumns() : 0;
568 double yRes = nRows() != 0 ? extent.height() / nRows() : 0;
569 mXResolutionLineEdit->setText( QLocale().toString( xRes ) );
570 mYResolutionLineEdit->setText( QLocale().toString( yRes ) );
571 updateResolutionStateMsg();
572}
573
574void QgsRasterLayerSaveAsDialog::recalcResolutionSize()
575{
576 if ( mResolutionRadioButton->isChecked() )
577 {
578 recalcSize();
579 }
580 else
581 {
582 mResolutionState = UserResolution;
583 recalcResolution();
584 }
585}
586
587void QgsRasterLayerSaveAsDialog::updateResolutionStateMsg()
588{
589 QString msg;
590 switch ( mResolutionState )
591 {
593 msg = tr( "layer" );
594 break;
595 case UserResolution:
596 msg = tr( "user defined" );
597 break;
598 default:
599 break;
600 }
601 msg = tr( "Resolution (current: %1)" ).arg( msg );
602 mResolutionGroupBox->setTitle( msg );
603}
604
605void QgsRasterLayerSaveAsDialog::extentChanged()
606{
607 // Whenever extent changes with fixed size, original resolution is lost
608 if ( mSizeRadioButton->isChecked() )
609 {
610 mResolutionState = UserResolution;
611 }
612 recalcResolutionSize();
613}
614
615void QgsRasterLayerSaveAsDialog::crsChanged()
616{
617 if ( outputCrs() != mPreviousCrs )
618 {
619 mExtentGroupBox->setOutputCrs( outputCrs() );
620
621 // Reset resolution
622 if ( mResolutionRadioButton->isChecked() )
623 {
624 if ( mResolutionState == OriginalResolution )
625 {
626 setOriginalResolution();
627 }
628 else
629 {
630 // reset from present resolution and present crs
631 setResolution( xResolution(), yResolution(), mPreviousCrs );
632 }
633 }
634 else
635 {
636 // Size does not change, we just recalc resolution from new extent
637 recalcResolution();
638 }
639 }
640 mPreviousCrs = outputCrs();
641}
642
644{
645 return mCrsSelector->crs();
646}
647
649{
650 if ( mRenderedModeRadioButton->isChecked() )
651 return RenderedImageMode;
652 return RawDataMode;
653}
654
655void QgsRasterLayerSaveAsDialog::mRawModeRadioButton_toggled( bool checked )
656{
657 mNoDataGroupBox->setEnabled( checked && mDataProvider->bandCount() == 1 );
658 mNoDataGroupBox->setCollapsed( !mNoDataGroupBox->isEnabled() );
659}
660
661void QgsRasterLayerSaveAsDialog::mAddNoDataManuallyToolButton_clicked()
662{
663 addNoDataRow( std::numeric_limits<double>::quiet_NaN(), std::numeric_limits<double>::quiet_NaN() );
664}
665
666void QgsRasterLayerSaveAsDialog::mLoadTransparentNoDataToolButton_clicked()
667{
668 if ( !mRasterLayer->renderer() )
669 return;
670 const QgsRasterTransparency *rasterTransparency = mRasterLayer->renderer()->rasterTransparency();
671 if ( !rasterTransparency )
672 return;
673
674 const auto constTransparentSingleValuePixelList = rasterTransparency->transparentSingleValuePixelList();
675 for ( const QgsRasterTransparency::TransparentSingleValuePixel &transparencyPixel : constTransparentSingleValuePixelList )
676 {
677 if ( qgsDoubleNear( transparencyPixel.opacity, 0 ) )
678 {
679 addNoDataRow( transparencyPixel.min, transparencyPixel.max );
680 if ( transparencyPixel.min != transparencyPixel.max )
681 {
682 setNoDataToEdited( mNoDataTableWidget->rowCount() - 1 );
683 }
684 }
685 }
686}
687
688void QgsRasterLayerSaveAsDialog::mRemoveSelectedNoDataToolButton_clicked()
689{
690 mNoDataTableWidget->removeRow( mNoDataTableWidget->currentRow() );
691}
692
693void QgsRasterLayerSaveAsDialog::mRemoveAllNoDataToolButton_clicked()
694{
695 while ( mNoDataTableWidget->rowCount() > 0 )
696 {
697 mNoDataTableWidget->removeRow( 0 );
698 }
699}
700
701void QgsRasterLayerSaveAsDialog::addNoDataRow( double min, double max )
702{
703 mNoDataTableWidget->insertRow( mNoDataTableWidget->rowCount() );
704 for ( int i = 0; i < 2; i++ )
705 {
706 double value = i == 0 ? min : max;
707 QLineEdit *lineEdit = new QLineEdit();
708 lineEdit->setFrame( false );
709 lineEdit->setContentsMargins( 1, 1, 1, 1 );
710 QString valueString;
711 switch ( mRasterLayer->dataProvider()->sourceDataType( 1 ) )
712 {
715 lineEdit->setValidator( new QgsDoubleValidator( nullptr ) );
716 if ( !std::isnan( value ) )
717 {
718 valueString = QgsRasterBlock::printValue( value );
719 }
720 break;
721 default:
722 lineEdit->setValidator( new QIntValidator( nullptr ) );
723 if ( !std::isnan( value ) )
724 {
725 valueString = QLocale().toString( static_cast<int>( value ) );
726 }
727 break;
728 }
729 lineEdit->setText( valueString );
730 mNoDataTableWidget->setCellWidget( mNoDataTableWidget->rowCount() - 1, i, lineEdit );
731
732 adjustNoDataCellWidth( mNoDataTableWidget->rowCount() - 1, i );
733
734 connect( lineEdit, &QLineEdit::textEdited, this, &QgsRasterLayerSaveAsDialog::noDataCellTextEdited );
735 }
736 mNoDataTableWidget->resizeColumnsToContents();
737 mNoDataTableWidget->resizeRowsToContents();
738}
739
740void QgsRasterLayerSaveAsDialog::noDataCellTextEdited( const QString &text )
741{
742 Q_UNUSED( text )
743
744 QLineEdit *lineEdit = qobject_cast<QLineEdit *>( sender() );
745 if ( !lineEdit )
746 return;
747 int row = -1;
748 int column = -1;
749 for ( int r = 0; r < mNoDataTableWidget->rowCount(); r++ )
750 {
751 for ( int c = 0; c < mNoDataTableWidget->columnCount(); c++ )
752 {
753 if ( mNoDataTableWidget->cellWidget( r, c ) == sender() )
754 {
755 row = r;
756 column = c;
757 break;
758 }
759 }
760 if ( row != -1 )
761 break;
762 }
763 QgsDebugMsgLevel( QStringLiteral( "row = %1 column =%2" ).arg( row ).arg( column ), 2 );
764
765 if ( column == 0 )
766 {
767 QLineEdit *toLineEdit = dynamic_cast<QLineEdit *>( mNoDataTableWidget->cellWidget( row, 1 ) );
768 if ( !toLineEdit )
769 return;
770 bool toChanged = mNoDataToEdited.value( row );
771 QgsDebugMsgLevel( QStringLiteral( "toChanged = %1" ).arg( toChanged ), 2 );
772 if ( !toChanged )
773 {
774 toLineEdit->setText( lineEdit->text() );
775 }
776 }
777 else if ( column == 1 )
778 {
779 setNoDataToEdited( row );
780 }
781}
782
783void QgsRasterLayerSaveAsDialog::mTileModeCheckBox_toggled( bool toggled )
784{
785 if ( toggled )
786 {
787 // enable pyramids
788
789 // Disabled (Radim), auto enabling of pyramids was making impression that
790 // we (programmers) know better what you (user) want to do,
791 // certainly auto expanding was a bad experience
792
793 //if ( ! mPyramidsGroupBox->isChecked() )
794 // mPyramidsGroupBox->setChecked( true );
795
796 // Auto expanding mPyramidsGroupBox is bad - it auto scrolls content of dialog
797 //if ( mPyramidsGroupBox->isCollapsed() )
798 // mPyramidsGroupBox->setCollapsed( false );
799 //mPyramidsOptionsWidget->checkAllLevels( true );
800
801 // Show / hide tile options
802 mTilesGroupBox->show();
803 mFilename->setStorageMode( QgsFileWidget::GetDirectory );
804 mFilename->setDialogTitle( tr( "Select Output Directory" ) );
805 }
806 else
807 {
808 mTilesGroupBox->hide();
809 mFilename->setStorageMode( QgsFileWidget::SaveFile );
810 mFilename->setDialogTitle( tr( "Save Layer As" ) );
811 }
812}
813
814void QgsRasterLayerSaveAsDialog::mPyramidsGroupBox_toggled( bool toggled )
815{
816 Q_UNUSED( toggled )
817 populatePyramidsLevels();
818}
819
820void QgsRasterLayerSaveAsDialog::populatePyramidsLevels()
821{
822 QString text;
823
824 if ( mPyramidsGroupBox->isChecked() )
825 {
826 QList<QgsRasterPyramid> myPyramidList;
827 // if use existing, get pyramids from actual layer
828 // but that's not available yet
829 if ( mPyramidsUseExistingCheckBox->isChecked() )
830 {
831 myPyramidList = mDataProvider->buildPyramidList();
832 }
833 else
834 {
835 if ( !mPyramidsOptionsWidget->overviewList().isEmpty() )
836 myPyramidList = mDataProvider->buildPyramidList( mPyramidsOptionsWidget->overviewList() );
837 }
838 for ( const QgsRasterPyramid &pyramid : std::as_const( myPyramidList ) )
839 {
840 if ( !mPyramidsUseExistingCheckBox->isChecked() || pyramid.getExists() )
841 {
842 text += QString::number( pyramid.getXDim() ) + QStringLiteral( "x" ) + QString::number( pyramid.getYDim() ) + ' ';
843 }
844 }
845 }
846
847 mPyramidResolutionsLineEdit->setText( text.trimmed() );
848}
849
850void QgsRasterLayerSaveAsDialog::setNoDataToEdited( int row )
851{
852 if ( row >= mNoDataToEdited.size() )
853 {
854 mNoDataToEdited.resize( row + 1 );
855 }
856 mNoDataToEdited[row] = true;
857}
858
859double QgsRasterLayerSaveAsDialog::noDataCellValue( int row, int column ) const
860{
861 QLineEdit *lineEdit = dynamic_cast<QLineEdit *>( mNoDataTableWidget->cellWidget( row, column ) );
862 if ( !lineEdit || lineEdit->text().isEmpty() )
863 {
864 return std::numeric_limits<double>::quiet_NaN();
865 }
866 return QgsDoubleValidator::toDouble( lineEdit->text() );
867}
868
869void QgsRasterLayerSaveAsDialog::adjustNoDataCellWidth( int row, int column )
870{
871 QLineEdit *lineEdit = dynamic_cast<QLineEdit *>( mNoDataTableWidget->cellWidget( row, column ) );
872 if ( !lineEdit )
873 return;
874
875 int width = std::max( lineEdit->fontMetrics().boundingRect( lineEdit->text() ).width() + 10, 100 );
876 width = std::max( width, mNoDataTableWidget->columnWidth( column ) );
877
878 lineEdit->setFixedWidth( width );
879}
880
882{
883 QgsRasterRangeList noDataList;
884 if ( !mNoDataGroupBox->isChecked() )
885 return noDataList;
886
887 int rows = mNoDataTableWidget->rowCount();
888 noDataList.reserve( rows );
889 for ( int r = 0; r < rows; r++ )
890 {
891 QgsRasterRange noData( noDataCellValue( r, 0 ), noDataCellValue( r, 1 ) );
892 noDataList.append( noData );
893 }
894 return noDataList;
895}
896
898{
899 return mPyramidsGroupBox->isChecked() ? mPyramidsOptionsWidget->overviewList() : QList<int>();
900}
901
903{
904 if ( !mPyramidsGroupBox->isChecked() )
906 else if ( mPyramidsUseExistingCheckBox->isChecked() )
908 else
910}
911
912bool QgsRasterLayerSaveAsDialog::validate() const
913{
914 if ( mCreateOptionsGroupBox->isChecked() )
915 {
916 QString message = mCreateOptionsWidget->validateOptions( true, false );
917 if ( !message.isNull() )
918 return false;
919 }
920 if ( mPyramidsGroupBox->isChecked() )
921 {
922 QString message = mPyramidsOptionsWidget->createOptionsWidget()->validateOptions( true, false );
923 if ( !message.isNull() )
924 return false;
925 }
926 return true;
927}
928
929bool QgsRasterLayerSaveAsDialog::outputLayerExists() const
930{
931 QString vectorUri;
932 QString rasterUri;
933 if ( outputFormat() == QLatin1String( "GPKG" ) )
934 {
935 rasterUri = QStringLiteral( "GPKG:%1:%2" ).arg( outputFileName(), outputLayerName() );
936 vectorUri = QStringLiteral( "%1|layername=%2" ).arg( outputFileName(), outputLayerName() );
937 }
938 else
939 {
940 rasterUri = outputFileName();
941 }
942
943 QgsRasterLayer rasterLayer( rasterUri, QString(), QStringLiteral( "gdal" ) );
944 if ( !vectorUri.isEmpty() )
945 {
946 QgsVectorLayer vectorLayer( vectorUri, QString(), QStringLiteral( "ogr" ) );
947 return rasterLayer.isValid() || vectorLayer.isValid();
948 }
949 else
950 {
951 return rasterLayer.isValid();
952 }
953}
954
956{
957 if ( !validate() )
958 {
959 return;
960 }
961
962 if ( QgsMapLayerUtils::isOpenStreetMapLayer( mRasterLayer ) )
963 {
964 const int nbTilesWidth = std::ceil( nColumns() / 256 );
965 const int nbTilesHeight = std::ceil( nRows() / 256 );
966 int64_t totalTiles = static_cast<int64_t>( nbTilesWidth ) * nbTilesHeight;
967
968 if ( totalTiles > MAXIMUM_OPENSTREETMAP_TILES_FETCH )
969 {
970 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 );
971 return;
972 }
973 }
974
975 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. "
976 "Do you want to overwrite the whole file?" )
977 .arg( outputLayerName() ),
978 QMessageBox::Yes | QMessageBox::No )
979 == QMessageBox::No )
980 {
981 return;
982 }
983
984 QDialog::accept();
985}
986
987void QgsRasterLayerSaveAsDialog::showHelp()
988{
989 QgsHelp::openHelp( QStringLiteral( "managing_data_source/create_layers.html#creating-new-layers-from-an-existing-layer" ) );
990}
@ 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:4526
@ 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: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.
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.
QgsPointXY center
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:6024
#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.