diff --git a/python/analysis/raster/qgsalignraster.sip b/python/analysis/raster/qgsalignraster.sip index 8a8719eed17..a45b8f0faf1 100644 --- a/python/analysis/raster/qgsalignraster.sip +++ b/python/analysis/raster/qgsalignraster.sip @@ -132,6 +132,10 @@ class QgsAlignRaster //! @return true on success bool run(); + //! Return error from a previous run() call. + //! Error message is empty if run() succeeded (returned true) + QString errorMessage() const; + //! write contents of the object to standard error stream - for debugging void dump() const; diff --git a/src/analysis/raster/qgsalignraster.cpp b/src/analysis/raster/qgsalignraster.cpp index d848bad4c17..5b1c20c7654 100644 --- a/src/analysis/raster/qgsalignraster.cpp +++ b/src/analysis/raster/qgsalignraster.cpp @@ -42,6 +42,11 @@ static double floor_with_tolerance( double value ) return qFloor( value ); } +static double fmod_with_tolerance( double num, double denom ) +{ + return num - floor_with_tolerance( num / denom ) * denom; +} + static QgsRectangle transform_to_extent( const double* geotransform, double xSize, double ySize ) { @@ -173,7 +178,13 @@ bool QgsAlignRaster::determineTransformAndSize() // handle (setting it to NULL). void* hTransformArg = GDALCreateGenImgProjTransformer( info.mDataset, info.mCrsWkt.constData(), NULL, mCrsWkt.constData(), FALSE, 0, 1 ); if ( !hTransformArg ) + { + mErrorMessage = QString( "GDALCreateGenImgProjTransformer failed.\n\n" + "Source WKT:\n%1\n\nDestination WKT:\n%2" ) + .arg( QString::fromAscii( info.mCrsWkt ) ) + .arg( QString::fromAscii( mCrsWkt ) ); return false; + } // Get approximate output georeferenced bounds and resolution for file. double adfDstGeoTransform[6]; @@ -187,7 +198,10 @@ bool QgsAlignRaster::determineTransformAndSize() r.srcCellSizeInDestCRS = fabs( adfDstGeoTransform[1] * adfDstGeoTransform[5] ); if ( eErr != CE_None ) + { + mErrorMessage = QString( "GDALSuggestedWarpOutput2 failed.\n\n" + r.inputFilename ); return false; + } if ( finalExtent[0] == 0 && finalExtent[1] == 0 && finalExtent[2] == 0 && finalExtent[3] == 0 ) { @@ -234,7 +248,10 @@ bool QgsAlignRaster::determineTransformAndSize() mYSize = floor_with_tolerance(( finalExtent[3] - originY ) / mCellSizeY ); if ( mXSize <= 0 || mYSize <= 0 ) + { + mErrorMessage = QObject::tr( "Configured inputs have no common intersecting area." ); return false; + } // build final geotransform... mGeoTransform[0] = originX; @@ -250,6 +267,8 @@ bool QgsAlignRaster::determineTransformAndSize() bool QgsAlignRaster::run() { + mErrorMessage.clear(); + // consider extent of all layers and setup geotransform and output grid size if ( !determineTransformAndSize() ) return false; @@ -283,12 +302,18 @@ bool QgsAlignRaster::createAndWarp( const Item& raster ) { GDALDriverH hDriver = GDALGetDriverByName( "GTiff" ); if ( !hDriver ) + { + mErrorMessage = QString( "GDALGetDriverByName(GTiff) failed." ); return false; + } // Open the source file. GDALDatasetH hSrcDS = GDALOpen( raster.inputFilename.toLocal8Bit().constData(), GA_ReadOnly ); if ( !hSrcDS ) + { + mErrorMessage = QObject::tr( "Unable to open input file: " ) + raster.inputFilename; return false; + } // Create output with same datatype as first input band. @@ -302,6 +327,7 @@ bool QgsAlignRaster::createAndWarp( const Item& raster ) if ( !hDstDS ) { GDALClose( hSrcDS ); + mErrorMessage = QObject::tr( "Unable to create output file: " ) + raster.outputFilename; return false; } @@ -401,8 +427,8 @@ QSizeF QgsAlignRaster::RasterInfo::cellSize() const QPointF QgsAlignRaster::RasterInfo::gridOffset() const { - return QPointF( fmod( mGeoTransform[0], cellSize().width() ), - fmod( mGeoTransform[3], cellSize().height() ) ); + return QPointF( fmod_with_tolerance( mGeoTransform[0], cellSize().width() ), + fmod_with_tolerance( mGeoTransform[3], cellSize().height() ) ); } QgsRectangle QgsAlignRaster::RasterInfo::extent() const diff --git a/src/analysis/raster/qgsalignraster.h b/src/analysis/raster/qgsalignraster.h index 12f73e239a0..b15acde49c2 100644 --- a/src/analysis/raster/qgsalignraster.h +++ b/src/analysis/raster/qgsalignraster.h @@ -178,6 +178,10 @@ class ANALYSIS_EXPORT QgsAlignRaster //! @return true on success bool run(); + //! Return error from a previous run() call. + //! Error message is empty if run() succeeded (returned true) + QString errorMessage() const { return mErrorMessage; } + //! write contents of the object to standard error stream - for debugging void dump() const; @@ -196,6 +200,9 @@ class ANALYSIS_EXPORT QgsAlignRaster //! Object that facilitates reporting of progress / cancellation ProgressHandler* mProgressHandler; + //! Last error message from run() + QString mErrorMessage; + //! List of rasters to be aligned (with their output files and other options) List mRasters; diff --git a/src/app/qgsalignrasterdialog.cpp b/src/app/qgsalignrasterdialog.cpp index cbcdc5acc98..b12fa02189c 100644 --- a/src/app/qgsalignrasterdialog.cpp +++ b/src/app/qgsalignrasterdialog.cpp @@ -157,6 +157,7 @@ void QgsAlignRasterDialog::editLayer() itemNew.resampleMethod = ( QgsAlignRaster::ResampleAlg ) d.resampleMethod(); itemNew.rescaleValues = d.rescaleValues(); list[current.row()] = itemNew; + mAlign->setRasters( list ); populateLayersView(); } @@ -207,8 +208,7 @@ void QgsAlignRasterDialog::runAlign() } else { - // TODO: error message with reason - QMessageBox::critical( this, tr( "Align Rasters" ), tr( "Failed to align rasters." ) ); + QMessageBox::critical( this, tr( "Align Rasters" ), tr( "Failed to align rasters:" ) + "\n\n" + mAlign->errorMessage() ); } } diff --git a/tests/src/analysis/testqgsalignraster.cpp b/tests/src/analysis/testqgsalignraster.cpp index 47c377344d5..c9d3ecf73f5 100644 --- a/tests/src/analysis/testqgsalignraster.cpp +++ b/tests/src/analysis/testqgsalignraster.cpp @@ -16,6 +16,7 @@ #include #include "qgsalignraster.h" +#include "qgsrectangle.h" #include @@ -43,6 +44,15 @@ class TestAlignRaster : public QObject SRC_FILE = QString( TEST_DATA_DIR ) + QDir::separator() + "float1-16.tif"; } + void testRasterInfo() + { + QgsAlignRaster::RasterInfo out( SRC_FILE ); + QVERIFY( out.isValid() ); + QCOMPARE( out.cellSize(), QSizeF( 0.2, 0.2 ) ); + QCOMPARE( out.gridOffset(), QPointF( 0.0, 0.0 ) ); + QCOMPARE( out.extent(), QgsRectangle( 106.0, -7.0, 106.8, -6.2 ) ); + } + void testClip() { QString tmpFile( _tempFile( "clip" ) ); @@ -159,9 +169,10 @@ class TestAlignRaster : public QObject align.setRasters( rasters ); align.setParametersFromRaster( SRC_FILE ); align.setCellSize( 0.4, 0.4 ); - QPointF offset = align.gridOffset(); - offset.rx() += 0.2; - align.setGridOffset( offset ); + // a technicality: the raster's origin is at y=-7 so we need to shift the offset, + // because with zero Y offset the grid goes -6.8 ... -7.2 - with offset of 0.2 + // the grid will start at -7 (with X axis no need for shift as 106 % 0.4 == 0) + align.setGridOffset( QPointF( 0.0, 0.2 ) ); bool res = align.run(); QVERIFY( res ); @@ -185,9 +196,10 @@ class TestAlignRaster : public QObject align.setRasters( rasters ); align.setParametersFromRaster( SRC_FILE ); align.setCellSize( 0.4, 0.4 ); - QPointF offset = align.gridOffset(); - offset.rx() += 0.2; - align.setGridOffset( offset ); + // a technicality: the raster's origin is at y=-7 so we need to shift the offset, + // because with zero Y offset the grid goes -6.8 ... -7.2 - with offset of 0.2 + // the grid will start at -7 (with X axis no need for shift as 106 % 0.4 == 0) + align.setGridOffset( QPointF( 0.0, 0.2 ) ); bool res = align.run(); QVERIFY( res );