diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index 0fd42b43673b..55a81182d532 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -3,6 +3,7 @@ SET(QGIS_APP_SRCS qgisappinterface.cpp qgisappstylesheet.cpp qgsabout.cpp + qgsalignrasterdialog.cpp qgsapplayertreeviewmenuprovider.cpp qgssponsors.cpp qgsaddattrdialog.cpp @@ -164,6 +165,7 @@ SET (QGIS_APP_MOC_HDRS qgisappstylesheet.h qgsabout.h qgsaddattrdialog.h + qgsalignrasterdialog.h qgsjoindialog.h qgsaddtaborgroup.h qgsannotationwidget.h diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index 1585f002068b..99ed62a34315 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -100,6 +100,7 @@ #include "qgis.h" #include "qgisplugin.h" #include "qgsabout.h" +#include "qgsalignrasterdialog.h" #include "qgsapplayertreeviewmenuprovider.h" #include "qgsapplication.h" #include "qgsattributeaction.h" @@ -1232,6 +1233,7 @@ void QgisApp::createActions() connect( mActionNewSpatiaLiteLayer, SIGNAL( triggered() ), this, SLOT( newSpatialiteLayer() ) ); connect( mActionNewMemoryLayer, SIGNAL( triggered() ), this, SLOT( newMemoryLayer() ) ); connect( mActionShowRasterCalculator, SIGNAL( triggered() ), this, SLOT( showRasterCalculator() ) ); + connect( mActionShowAlignRasterTool, SIGNAL( triggered() ), this, SLOT( showAlignRasterTool() ) ); connect( mActionEmbedLayers, SIGNAL( triggered() ), this, SLOT( embedLayers() ) ); connect( mActionAddLayerDefinition, SIGNAL( triggered() ), this, SLOT( addLayerDefinition() ) ); connect( mActionAddOgrLayer, SIGNAL( triggered() ), this, SLOT( addVectorLayer() ) ); @@ -4001,6 +4003,14 @@ void QgisApp::showRasterCalculator() } } + +void QgisApp::showAlignRasterTool() +{ + QgsAlignRasterDialog dlg( this ); + dlg.exec(); +} + + void QgisApp::fileOpen() { // possibly save any pending work before opening a new project diff --git a/src/app/qgisapp.h b/src/app/qgisapp.h index b94d0eebf0bd..2e85b9a11e7f 100644 --- a/src/app/qgisapp.h +++ b/src/app/qgisapp.h @@ -904,6 +904,8 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow void fileNewFromDefaultTemplate(); //! Calculate new rasters from existing ones void showRasterCalculator(); + //! Open dialog to align raster layers + void showAlignRasterTool(); void embedLayers(); //! Create a new empty vector layer diff --git a/src/app/qgsalignrasterdialog.cpp b/src/app/qgsalignrasterdialog.cpp new file mode 100644 index 000000000000..cbcdc5acc982 --- /dev/null +++ b/src/app/qgsalignrasterdialog.cpp @@ -0,0 +1,304 @@ +#include "qgsalignrasterdialog.h" + +#include "qgsapplication.h" +#include "qgsalignraster.h" +#include "qgsdataitem.h" +#include "qgsmaplayercombobox.h" +#include "qgsmaplayerregistry.h" +#include "qgsrasterlayer.h" + +#include +#include +#include +#include +#include +#include +#include +#include + + +static QgsMapLayer* _rasterLayer( const QString& filename ) +{ + QMap layers = QgsMapLayerRegistry::instance()->mapLayers(); + foreach ( QgsMapLayer* layer, layers.values() ) + { + if ( layer->type() == QgsMapLayer::RasterLayer && layer->source() == filename ) + return layer; + } + return 0; +} + +static QString _rasterLayerName( const QString& filename ) +{ + if ( QgsMapLayer* layer = _rasterLayer( filename ) ) + return layer->name(); + + QFileInfo fi( filename ); + return fi.baseName(); +} + + + +/** Helper class to report progress */ +struct QgsAlignRasterDialogProgress : public QgsAlignRaster::ProgressHandler +{ + QgsAlignRasterDialogProgress( QProgressBar* pb ) : mPb( pb ) {} + virtual bool progress( double complete ) + { + mPb->setValue(( int ) qRound( complete * 100 ) ); + qApp->processEvents(); // to actually show the progress in GUI + return true; + } + +protected: + QProgressBar* mPb; +}; + + +QgsAlignRasterDialog::QgsAlignRasterDialog( QWidget *parent ) + : QDialog( parent ) +{ + setupUi( this ); + + mBtnAdd->setIcon( QIcon( QgsApplication::iconPath( "symbologyAdd.svg" ) ) ); + mBtnEdit->setIcon( QIcon( QgsApplication::iconPath( "symbologyEdit.png" ) ) ); + mBtnRemove->setIcon( QIcon( QgsApplication::iconPath( "symbologyRemove.svg" ) ) ); + + mAlign = new QgsAlignRaster; + mAlign->setProgressHandler( new QgsAlignRasterDialogProgress( mProgress ) ); + + connect( mBtnAdd, SIGNAL( clicked( bool ) ), this, SLOT( addLayer() ) ); + connect( mBtnRemove, SIGNAL( clicked( bool ) ), this, SLOT( removeLayer() ) ); + connect( mBtnEdit, SIGNAL( clicked( bool ) ), this, SLOT( editLayer() ) ); + + connect( mCboReferenceLayer, SIGNAL( currentIndexChanged( int ) ), this, SLOT( updateConfigFromReferenceLayer() ) ); + + mClipExtentGroupBox->setChecked( false ); + + // TODO: auto-detect reference layer + + connect( buttonBox, SIGNAL( accepted() ), this, SLOT( runAlign() ) ); + + populateLayersView(); +} + +QgsAlignRasterDialog::~QgsAlignRasterDialog() +{ + delete mAlign; +} + + +void QgsAlignRasterDialog::populateLayersView() +{ + mCboReferenceLayer->clear(); + + QStandardItemModel* model = new QStandardItemModel(); + foreach ( QgsAlignRaster::Item item, mAlign->rasters() ) + { + QString layerName = _rasterLayerName( item.inputFilename ); + + QStandardItem* si = new QStandardItem( QgsLayerItem::iconRaster(), layerName ); + model->appendRow( si ); + + mCboReferenceLayer->addItem( layerName ); + } + + mViewLayers->setModel( model ); + + buttonBox->button( QDialogButtonBox::Ok )->setEnabled( model->rowCount() > 0 ); +} + +void QgsAlignRasterDialog::addLayer() +{ + QgsAlignRasterLayerConfigDialog d; + if ( !d.exec() ) + return; + + QgsAlignRaster::List list = mAlign->rasters(); + + QgsAlignRaster::Item item( d.inputFilename(), d.outputFilename() ); + item.resampleMethod = ( QgsAlignRaster::ResampleAlg ) d.resampleMethod(); + item.rescaleValues = d.rescaleValues(); + list.append( item ); + + mAlign->setRasters( list ); + + populateLayersView(); +} + +void QgsAlignRasterDialog::removeLayer() +{ + QModelIndex current = mViewLayers->currentIndex(); + if ( !current.isValid() ) + return; + + QgsAlignRaster::List list = mAlign->rasters(); + list.removeAt( current.row() ); + mAlign->setRasters( list ); + + populateLayersView(); +} + +void QgsAlignRasterDialog::editLayer() +{ + QModelIndex current = mViewLayers->currentIndex(); + if ( !current.isValid() ) + return; + + QgsAlignRaster::List list = mAlign->rasters(); + QgsAlignRaster::Item item = list.at( current.row() ); + + QgsAlignRasterLayerConfigDialog d; + d.setItem( item.inputFilename, item.outputFilename, item.resampleMethod, item.rescaleValues ); + if ( !d.exec() ) + return; + + QgsAlignRaster::Item itemNew( d.inputFilename(), d.outputFilename() ); + itemNew.resampleMethod = ( QgsAlignRaster::ResampleAlg ) d.resampleMethod(); + itemNew.rescaleValues = d.rescaleValues(); + list[current.row()] = itemNew; + + populateLayersView(); +} + +void QgsAlignRasterDialog::updateConfigFromReferenceLayer() +{ + int index = mCboReferenceLayer->currentIndex(); + if ( index < 0 ) + return; + + mAlign->setParametersFromRaster( mAlign->rasters().at( index ).inputFilename ); + + QgsCoordinateReferenceSystem destCRS( mAlign->destinationCRS() ); + mCrsSelector->setCrs( destCRS ); + + QSizeF cellSize = mAlign->cellSize(); + mSpinCellSizeX->setValue( cellSize.width() ); + mSpinCellSizeY->setValue( cellSize.height() ); + + QPointF gridOffset = mAlign->gridOffset(); + mSpinGridOffsetX->setValue( gridOffset.x() ); + mSpinGridOffsetY->setValue( gridOffset.y() ); + + mClipExtentGroupBox->setOriginalExtent( mAlign->clipExtent(), destCRS ); +} + +void QgsAlignRasterDialog::runAlign() +{ + setEnabled( false ); + + bool res = mAlign->run(); + + setEnabled( true ); + + if ( res ) + { + if ( mChkAddToCanvas->isChecked() ) + { + foreach ( const QgsAlignRaster::Item& item, mAlign->rasters() ) + { + QgsRasterLayer* layer = new QgsRasterLayer( item.outputFilename, QFileInfo( item.outputFilename ).baseName() ); + if ( layer->isValid() ) + QgsMapLayerRegistry::instance()->addMapLayer( layer ); + else + delete layer; + } + } + } + else + { + // TODO: error message with reason + QMessageBox::critical( this, tr( "Align Rasters" ), tr( "Failed to align rasters." ) ); + } +} + + +// ------ + + +QgsAlignRasterLayerConfigDialog::QgsAlignRasterLayerConfigDialog() +{ + QVBoxLayout* layout = new QVBoxLayout(); + + cboLayers = new QgsMapLayerComboBox( this ); + cboLayers->setFilters( QgsMapLayerProxyModel::RasterLayer ); + + cboResample = new QComboBox( this ); + QStringList methods; + methods << tr( "Nearest neighbour" ) << tr( "Bilinear (2x2 kernel)" ) + << tr( "Cubic (4x4 kernel)" ) << tr( "Cubic B-Spline (4x4 kernel)" ) << tr( "Lanczos (6x6 kernel)" ) + << tr( "Average" ) << tr( "Mode" ); + cboResample->addItems( methods ); + + editOutput = new QLineEdit( this ); + btnBrowse = new QPushButton( tr( "Browse..." ), this ); + connect( btnBrowse, SIGNAL( clicked( bool ) ), this, SLOT( browseOutputFilename() ) ); + + QHBoxLayout* layoutOutput = new QHBoxLayout(); + layoutOutput->addWidget( editOutput ); + layoutOutput->addWidget( btnBrowse ); + + chkRescale = new QCheckBox( tr( "Rescale values according to the cell size" ), this ); + + btnBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, this ); + connect( btnBox, SIGNAL( accepted() ), this, SLOT( accept() ) ); + connect( btnBox, SIGNAL( rejected() ), this, SLOT( reject() ) ); + + layout->addWidget( new QLabel( tr( "Input raster layer:" ), this ) ); + layout->addWidget( cboLayers ); + layout->addWidget( new QLabel( tr( "Output raster filename:" ), this ) ); + layout->addLayout( layoutOutput ); + layout->addWidget( new QLabel( tr( "Resampling method:" ), this ) ); + layout->addWidget( cboResample ); + layout->addWidget( chkRescale ); + layout->addWidget( btnBox ); + setLayout( layout ); +} + +QString QgsAlignRasterLayerConfigDialog::inputFilename() const +{ + QgsRasterLayer* l = qobject_cast( cboLayers->currentLayer() ); + return l ? l->source() : QString(); +} + +QString QgsAlignRasterLayerConfigDialog::outputFilename() const +{ + return editOutput->text(); +} + +int QgsAlignRasterLayerConfigDialog::resampleMethod() const +{ + return cboResample->currentIndex(); +} + +bool QgsAlignRasterLayerConfigDialog::rescaleValues() const +{ + return chkRescale->isChecked(); +} + +void QgsAlignRasterLayerConfigDialog::setItem( const QString& inputFilename, const QString& outputFilename, + int resampleMethod, bool rescaleValues ) +{ + cboLayers->setLayer( _rasterLayer( inputFilename ) ); + editOutput->setText( outputFilename ); + cboResample->setCurrentIndex( resampleMethod ); + chkRescale->setChecked( rescaleValues ); +} + +void QgsAlignRasterLayerConfigDialog::browseOutputFilename() +{ + QSettings settings; + QString dirName = editOutput->text().isEmpty() ? settings.value( "/UI/lastRasterFileDir", "." ).toString() : editOutput->text(); + + QString fileName = QFileDialog::getSaveFileName( this, tr( "Select output file" ), dirName, tr( "GeoTIFF" ) + " (*.tif *.tiff *.TIF *.TIFF)" ); + + if ( !fileName.isEmpty() ) + { + // ensure the user never ommited the extension from the file name + if ( !fileName.toLower().endsWith( ".tif" ) && !fileName.toLower().endsWith( ".tiff" ) ) + { + fileName += ".tif"; + } + editOutput->setText( fileName ); + } +} diff --git a/src/app/qgsalignrasterdialog.h b/src/app/qgsalignrasterdialog.h new file mode 100644 index 000000000000..c213ee57e50a --- /dev/null +++ b/src/app/qgsalignrasterdialog.h @@ -0,0 +1,67 @@ +#ifndef QGSALIGNRASTERDIALOG_H +#define QGSALIGNRASTERDIALOG_H + +#include + +#include "ui_qgsalignrasterdialog.h" + +class QgsAlignRaster; + +/** Dialog providing user interface for QgsAlignRaster */ +class QgsAlignRasterDialog : public QDialog, private Ui::QgsAlignRasterDialog +{ + Q_OBJECT + public: + explicit QgsAlignRasterDialog( QWidget *parent = 0 ); + ~QgsAlignRasterDialog(); + + signals: + + protected slots: + void addLayer(); + void removeLayer(); + void editLayer(); + + void updateConfigFromReferenceLayer(); + + void runAlign(); + + protected: + void populateLayersView(); + + protected: + QgsAlignRaster* mAlign; +}; + + +class QgsMapLayerComboBox; +class QCheckBox; + +/** Simple dialog to display details of one layer's configuration */ +class QgsAlignRasterLayerConfigDialog : public QDialog +{ + Q_OBJECT + public: + QgsAlignRasterLayerConfigDialog(); + + QString inputFilename() const; + QString outputFilename() const; + int resampleMethod() const; + bool rescaleValues() const; + + void setItem( const QString& inputFilename, const QString& outputFilename, int resampleMethod, bool rescaleValues ); + + protected slots: + void browseOutputFilename(); + + protected: + QgsMapLayerComboBox* cboLayers; + QLineEdit* editOutput; + QPushButton* btnBrowse; + QComboBox* cboResample; + QCheckBox* chkRescale; + QDialogButtonBox* btnBox; +}; + + +#endif // QGSALIGNRASTERDIALOG_H diff --git a/src/ui/qgisapp.ui b/src/ui/qgisapp.ui index 1f0e126ea905..08391c307828 100755 --- a/src/ui/qgisapp.ui +++ b/src/ui/qgisapp.ui @@ -17,7 +17,7 @@ 0 0 1050 - 25 + 27 @@ -222,6 +222,7 @@ &Raster + @@ -2300,6 +2301,11 @@ Acts on currently active editable layer Show statistical summary + + + Align Rasters... + + diff --git a/src/ui/qgsalignrasterdialog.ui b/src/ui/qgsalignrasterdialog.ui new file mode 100644 index 000000000000..922d34b5d62d --- /dev/null +++ b/src/ui/qgsalignrasterdialog.ui @@ -0,0 +1,258 @@ + + + QgsAlignRasterDialog + + + + 0 + 0 + 511 + 415 + + + + Align Rasters + + + + + + + + Raster layers to align: + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + true + + + + + + + / + + + true + + + + + + + - + + + true + + + + + + + + + false + + + true + + + + + + + + + Grid Offset + + + + + + + Clip to Extent + + + true + + + + + + + + Reference Layer + + + + + + + + + + + + + Cell Size + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + + + 6 + + + 999999.000000000000000 + + + + + + + 6 + + + 999999.000000000000000 + + + + + + + + + CRS + + + + + + + + + 6 + + + 999999.000000000000000 + + + + + + + 6 + + + 999999.000000000000000 + + + + + + + + + Add aligned rasters to map canvas + + + true + + + + + + + + + + + 0 + + + + + + + QDialogButtonBox::Close|QDialogButtonBox::Ok + + + + + + + + + + QgsProjectionSelectionWidget + QWidget +
qgsprojectionselectionwidget.h
+ 1 +
+ + QgsExtentGroupBox + QGroupBox +
qgsextentgroupbox.h
+ 1 +
+
+ + mBtnAdd + mBtnEdit + mBtnRemove + mViewLayers + mCboReferenceLayer + mSpinCellSizeX + mSpinCellSizeY + mSpinGridOffsetX + mSpinGridOffsetY + mClipExtentGroupBox + mChkAddToCanvas + buttonBox + + + + + buttonBox + rejected() + QgsAlignRasterDialog + close() + + + 285 + 394 + + + 199 + 206 + + + + +