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