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