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