bool QgsRasterChecker::runTest( QString theVerifiedKey, QString theVerifiedUri, QString theExpectedKey, QString theExpectedUri ) { bool ok = true; mReport += "\n\n"; //QgsRasterDataProvider* verifiedProvider = QgsRasterLayer::loadProvider( theVerifiedKey, theVerifiedUri ); QgsRasterDataProvider* verifiedProvider = ( QgsRasterDataProvider* ) QgsProviderRegistry::instance()->provider( theVerifiedKey, theVerifiedUri ); if ( !verifiedProvider || !verifiedProvider->isValid() ) { error( QString( "Cannot load provider %1 with URI: %2" ).arg( theVerifiedKey ).arg( theVerifiedUri ), mReport ); ok = false; } //QgsRasterDataProvider* expectedProvider = QgsRasterLayer::loadProvider( theExpectedKey, theExpectedUri ); QgsRasterDataProvider* expectedProvider = ( QgsRasterDataProvider* ) QgsProviderRegistry::instance()->provider( theExpectedKey, theExpectedUri ); if ( !expectedProvider || !expectedProvider->isValid() ) { error( QString( "Cannot load provider %1 with URI: %2" ).arg( theExpectedKey ).arg( theExpectedUri ), mReport ); ok = false; } if ( !ok ) return false; mReport += QString( "Verified URI: %1<br>" ).arg( theVerifiedUri.replace( "&", "&" ) ); mReport += QString( "Expected URI: %1<br>" ).arg( theExpectedUri.replace( "&", "&" ) ); mReport += "<br>"; mReport += QString( "<table style='%1'>\n" ).arg( mTabStyle ); mReport += compareHead(); compare( "Band count", verifiedProvider->bandCount(), expectedProvider->bandCount(), mReport, ok ); compare( "Width", verifiedProvider->xSize(), expectedProvider->xSize(), mReport, ok ); compare( "Height", verifiedProvider->ySize(), expectedProvider->ySize(), mReport, ok ); compareRow( "Extent", verifiedProvider->extent().toString(), expectedProvider->extent().toString(), mReport, verifiedProvider->extent() == expectedProvider->extent() ); if ( verifiedProvider->extent() != expectedProvider->extent() ) ok = false; mReport += "</table>\n"; if ( !ok ) return false; bool allOk = true; for ( int band = 1; band <= expectedProvider->bandCount(); band++ ) { bool bandOk = true; mReport += QString( "<h3>Band %1</h3>\n" ).arg( band ); mReport += QString( "<table style='%1'>\n" ).arg( mTabStyle ); mReport += compareHead(); // Data types may differ (?) bool typesOk = true; compare( "Source data type", verifiedProvider->srcDataType( band ), expectedProvider->srcDataType( band ), mReport, typesOk ); compare( "Data type", verifiedProvider->dataType( band ), expectedProvider->dataType( band ), mReport, typesOk ) ; // TODO: not yet sure if noDataValue() should exist at all //compare( "No data (NULL) value", verifiedProvider->noDataValue( band ), expectedProvider->noDataValue( band ), mReport, typesOk ); bool statsOk = true; QgsRasterBandStats verifiedStats = verifiedProvider->bandStatistics( band ); QgsRasterBandStats expectedStats = expectedProvider->bandStatistics( band ); // Min/max may 'slightly' differ, for big numbers however, the difference may // be quite big, for example for Float32 with max -3.332e+38, the difference is 1.47338e+24 double tol = tolerance( expectedStats.minimumValue ); compare( "Minimum value", verifiedStats.minimumValue, expectedStats.minimumValue, mReport, statsOk, tol ); tol = tolerance( expectedStats.maximumValue ); compare( "Maximum value", verifiedStats.maximumValue, expectedStats.maximumValue, mReport, statsOk, tol ); // TODO: enable once fixed (WCS excludes nulls but GDAL does not) //compare( "Cells count", verifiedStats.elementCount, expectedStats.elementCount, mReport, statsOk ); tol = tolerance( expectedStats.mean ); compare( "Mean", verifiedStats.mean, expectedStats.mean, mReport, statsOk, tol ); // stdDev usually differ significantly tol = tolerance( expectedStats.stdDev, 1 ); compare( "Standard deviation", verifiedStats.stdDev, expectedStats.stdDev, mReport, statsOk, tol ); mReport += "</table>"; mReport += "<br>"; if ( !bandOk ) { allOk = false; continue; } if ( !statsOk || !typesOk ) { allOk = false; // create values table anyway so that values are available } mReport += "<table><tr>"; mReport += "<td>Data comparison</td>"; mReport += QString( "<td style='%1 %2 border: 1px solid'>correct value</td>" ).arg( mCellStyle ).arg( mOkStyle ); mReport += "<td></td>"; mReport += QString( "<td style='%1 %2 border: 1px solid'>wrong value<br>expected value</td></tr>" ).arg( mCellStyle ).arg( mErrStyle ); mReport += "</tr></table>"; mReport += "<br>"; int width = expectedProvider->xSize(); int height = expectedProvider->ySize(); QgsRasterBlock *expectedBlock = expectedProvider->block( band, expectedProvider->extent(), width, height ); QgsRasterBlock *verifiedBlock = verifiedProvider->block( band, expectedProvider->extent(), width, height ); if ( !expectedBlock || !expectedBlock->isValid() || !verifiedBlock || !verifiedBlock->isValid() ) { allOk = false; mReport += "cannot read raster block"; continue; } // compare data values QString htmlTable = QString( "<table style='%1'>" ).arg( mTabStyle ); for ( int row = 0; row < height; row ++ ) { htmlTable += "<tr>"; for ( int col = 0; col < width; col ++ ) { bool cellOk = true; double verifiedVal = verifiedBlock->value( row, col ); double expectedVal = expectedBlock->value( row, col ); QString valStr; if ( compare( verifiedVal, expectedVal, 0 ) ) { valStr = QString( "%1" ).arg( verifiedVal ); } else { cellOk = false; allOk = false; valStr = QString( "%1<br>%2" ).arg( verifiedVal ).arg( expectedVal ); } htmlTable += QString( "<td style='%1 %2'>%3</td>" ).arg( mCellStyle ).arg( cellOk ? mOkStyle : mErrStyle ).arg( valStr ); } htmlTable += "</tr>"; } htmlTable += "</table>"; mReport += htmlTable; delete expectedBlock; delete verifiedBlock; } delete verifiedProvider; delete expectedProvider; return allOk; }
QgsRasterBlock * QgsRasterProjector::block( int bandNo, QgsRectangle const & extent, int width, int height ) { QgsDebugMsg( QString( "extent:\n%1" ).arg( extent.toString() ) ); QgsDebugMsg( QString( "width = %1 height = %2" ).arg( width ).arg( height ) ); if ( !mInput ) { QgsDebugMsg( "Input not set" ); return new QgsRasterBlock(); } if ( ! mSrcCRS.isValid() || ! mDestCRS.isValid() || mSrcCRS == mDestCRS ) { QgsDebugMsg( "No projection necessary" ); return mInput->block( bandNo, extent, width, height ); } mDestExtent = extent; mDestRows = height; mDestCols = width; calc(); QgsDebugMsg( QString( "srcExtent:\n%1" ).arg( srcExtent().toString() ) ); QgsDebugMsg( QString( "srcCols = %1 srcRows = %2" ).arg( srcCols() ).arg( srcRows() ) ); // If we zoom out too much, projector srcRows / srcCols maybe 0, which can cause problems in providers if ( srcRows() <= 0 || srcCols() <= 0 ) { QgsDebugMsg( "Zero srcRows or srcCols" ); return new QgsRasterBlock(); } QgsRasterBlock *inputBlock = mInput->block( bandNo, srcExtent(), srcCols(), srcRows() ); if ( !inputBlock || inputBlock->isEmpty() ) { QgsDebugMsg( "No raster data!" ); delete inputBlock; return new QgsRasterBlock(); } qgssize pixelSize = QgsRasterBlock::typeSize( mInput->dataType( bandNo ) ); QgsRasterBlock *outputBlock; if ( inputBlock->hasNoDataValue() ) { outputBlock = new QgsRasterBlock( inputBlock->dataType(), width, height, inputBlock->noDataValue() ); } else { outputBlock = new QgsRasterBlock( inputBlock->dataType(), width, height ); } if ( !outputBlock->isValid() ) { QgsDebugMsg( "Cannot create block" ); delete inputBlock; return outputBlock; } // set output to no data, it should be fast outputBlock->setIsNoData(); // No data: because isNoData()/setIsNoData() is slow with respect to simple memcpy, // we use if only if necessary: // 1) no data value exists (numerical) -> memcpy, not necessary isNoData()/setIsNoData() // 2) no data value does not exist but it may contain no data (numerical no data bitmap) // -> must use isNoData()/setIsNoData() // 3) no data are not used (no no data value, no no data bitmap) -> simple memcpy // 4) image - simple memcpy // To copy no data values stored in bitmaps we have to use isNoData()/setIsNoData(), // we cannot fill output block with no data because we use memcpy for data, not setValue(). bool doNoData = !QgsRasterBlock::typeIsNumeric( inputBlock->dataType() ) && inputBlock->hasNoData() && !inputBlock->hasNoDataValue(); const QgsCoordinateTransform* inverseCt = nullptr; if ( !mApproximate ) { inverseCt = QgsCoordinateTransformCache::instance()->transform( mDestCRS.authid(), mSrcCRS.authid(), mDestDatumTransform, mSrcDatumTransform ); } outputBlock->setIsNoData(); int srcRow, srcCol; for ( int i = 0; i < height; ++i ) { for ( int j = 0; j < width; ++j ) { bool inside = srcRowCol( i, j, &srcRow, &srcCol, inverseCt ); if ( !inside ) continue; // we have everything set to no data qgssize srcIndex = ( qgssize )srcRow * mSrcCols + srcCol; QgsDebugMsgLevel( QString( "row = %1 col = %2 srcRow = %3 srcCol = %4" ).arg( i ).arg( j ).arg( srcRow ).arg( srcCol ), 5 ); // isNoData() may be slow so we check doNoData first if ( doNoData && inputBlock->isNoData( srcRow, srcCol ) ) { outputBlock->setIsNoData( i, j ); continue; } qgssize destIndex = ( qgssize )i * width + j; char *srcBits = inputBlock->bits( srcIndex ); char *destBits = outputBlock->bits( destIndex ); if ( !srcBits ) { QgsDebugMsg( QString( "Cannot get input block data: row = %1 col = %2" ).arg( i ).arg( j ) ); continue; } if ( !destBits ) { QgsDebugMsg( QString( "Cannot set output block data: srcRow = %1 srcCol = %2" ).arg( srcRow ).arg( srcCol ) ); continue; } memcpy( destBits, srcBits, pixelSize ); outputBlock->setIsData( i, j ); } } delete inputBlock; return outputBlock; }