bool QgsRasterChecker::runTest( const QString& theVerifiedKey, QString theVerifiedUri, const QString& theExpectedKey, QString theExpectedUri ) { bool ok = true; mReport += "\n\n"; //QgsRasterDataProvider* verifiedProvider = QgsRasterLayer::loadProvider( theVerifiedKey, theVerifiedUri ); QgsRasterDataProvider* verifiedProvider = dynamic_cast< QgsRasterDataProvider* >( QgsProviderRegistry::instance()->provider( theVerifiedKey, theVerifiedUri ) ); if ( !verifiedProvider || !verifiedProvider->isValid() ) { error( QString( "Cannot load provider %1 with URI: %2" ).arg( theVerifiedKey, theVerifiedUri ), mReport ); ok = false; } //QgsRasterDataProvider* expectedProvider = QgsRasterLayer::loadProvider( theExpectedKey, theExpectedUri ); QgsRasterDataProvider* expectedProvider = dynamic_cast< QgsRasterDataProvider* >( QgsProviderRegistry::instance()->provider( theExpectedKey, theExpectedUri ) ); if ( !expectedProvider || !expectedProvider->isValid() ) { error( QString( "Cannot load provider %1 with URI: %2" ).arg( theExpectedKey, 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++ ) { 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->sourceDataType( band ), expectedProvider->sourceDataType( band ), mReport, typesOk ); compare( "Data type", verifiedProvider->dataType( band ), expectedProvider->dataType( band ), mReport, typesOk ); // Check nodata bool noDataOk = true; compare( "No data (NULL) value existence flag", verifiedProvider->sourceHasNoDataValue( band ), expectedProvider->sourceHasNoDataValue( band ), mReport, noDataOk ); if ( verifiedProvider->sourceHasNoDataValue( band ) && expectedProvider->sourceHasNoDataValue( band ) ) { compare( "No data (NULL) value", verifiedProvider->sourceNoDataValue( band ), expectedProvider->sourceNoDataValue( band ), mReport, noDataOk ); } 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 ( !statsOk || !typesOk || !noDataOk ) { 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, mOkStyle ); mReport += "<td></td>"; mReport += QString( "<td style='%1 %2 border: 1px solid'>wrong value<br>expected value</td></tr>" ).arg( mCellStyle, 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, cellOk ? mOkStyle : mErrStyle, valStr ); } htmlTable += "</tr>"; } htmlTable += "</table>"; mReport += htmlTable; delete expectedBlock; delete verifiedBlock; } delete verifiedProvider; delete expectedProvider; return allOk; }
QgsRasterFileWriter::WriterError QgsRasterFileWriter::writeDataRaster( const QgsRasterPipe *pipe, QgsRasterIterator *iter, int nCols, int nRows, const QgsRectangle &outputExtent, const QgsCoordinateReferenceSystem &crs, QgsRasterBlockFeedback *feedback ) { QgsDebugMsgLevel( "Entered", 4 ); if ( !iter ) { return SourceProviderError; } const QgsRasterInterface *iface = pipe->last(); if ( !iface ) { return SourceProviderError; } QgsRasterDataProvider *srcProvider = const_cast<QgsRasterDataProvider *>( dynamic_cast<const QgsRasterDataProvider *>( iface->sourceInput() ) ); if ( !srcProvider ) { QgsDebugMsg( "Cannot get source data provider" ); return SourceProviderError; } iter->setMaximumTileWidth( mMaxTileWidth ); iter->setMaximumTileHeight( mMaxTileHeight ); int nBands = iface->bandCount(); if ( nBands < 1 ) { return SourceProviderError; } //check if all the bands have the same data type size, otherwise we cannot write it to the provider //(at least not with the current interface) int dataTypeSize = QgsRasterBlock::typeSize( srcProvider->sourceDataType( 1 ) ); for ( int i = 2; i <= nBands; ++i ) { if ( QgsRasterBlock::typeSize( srcProvider->sourceDataType( 1 ) ) != dataTypeSize ) { return DestProviderError; } } // Output data type - source data type is preferred but it may happen that we need // to set 'no data' value (which was not set on source data) if output extent // is larger than source extent (with or without reprojection) and there is no 'free' // (not used) value available QList<bool> destHasNoDataValueList; QList<double> destNoDataValueList; QList<Qgis::DataType> destDataTypeList; destDataTypeList.reserve( nBands ); destHasNoDataValueList.reserve( nBands ); destNoDataValueList.reserve( nBands ); for ( int bandNo = 1; bandNo <= nBands; bandNo++ ) { QgsRasterNuller *nuller = pipe->nuller(); bool srcHasNoDataValue = srcProvider->sourceHasNoDataValue( bandNo ); bool destHasNoDataValue = false; double destNoDataValue = std::numeric_limits<double>::quiet_NaN(); Qgis::DataType destDataType = srcProvider->sourceDataType( bandNo ); // TODO: verify what happens/should happen if srcNoDataValue is disabled by setUseSrcNoDataValue QgsDebugMsgLevel( QString( "srcHasNoDataValue = %1 srcNoDataValue = %2" ).arg( srcHasNoDataValue ).arg( srcProvider->sourceNoDataValue( bandNo ) ), 4 ); if ( srcHasNoDataValue ) { // If source has no data value, it is used by provider destNoDataValue = srcProvider->sourceNoDataValue( bandNo ); destHasNoDataValue = true; } else if ( nuller && !nuller->noData( bandNo ).isEmpty() ) { // Use one user defined no data value destNoDataValue = nuller->noData( bandNo ).value( 0 ).min(); destHasNoDataValue = true; } else { // Verify if we really need no data value, i.e. QgsRectangle srcExtent = outputExtent; QgsRasterProjector *projector = pipe->projector(); if ( projector && projector->destinationCrs() != projector->sourceCrs() ) { Q_NOWARN_DEPRECATED_PUSH QgsCoordinateTransform ct( projector->destinationCrs(), projector->sourceCrs() ); Q_NOWARN_DEPRECATED_POP srcExtent = ct.transformBoundingBox( outputExtent ); } if ( !srcProvider->extent().contains( srcExtent ) ) { // Destination extent is larger than source extent, we need destination no data values // Get src sample statistics (estimation from sample) QgsRasterBandStats stats = srcProvider->bandStatistics( bandNo, QgsRasterBandStats::Min | QgsRasterBandStats::Max, srcExtent, 250000 ); // Test if we have free (not used) values double typeMinValue = QgsContrastEnhancement::maximumValuePossible( static_cast< Qgis::DataType >( srcProvider->sourceDataType( bandNo ) ) ); double typeMaxValue = QgsContrastEnhancement::maximumValuePossible( static_cast< Qgis::DataType >( srcProvider->sourceDataType( bandNo ) ) ); if ( stats.minimumValue > typeMinValue ) { destNoDataValue = typeMinValue; } else if ( stats.maximumValue < typeMaxValue ) { destNoDataValue = typeMaxValue; } else { // We have to use wider type destDataType = QgsRasterBlock::typeWithNoDataValue( destDataType, &destNoDataValue ); } destHasNoDataValue = true; } } if ( nuller && destHasNoDataValue ) { nuller->setOutputNoDataValue( bandNo, destNoDataValue ); } QgsDebugMsgLevel( QString( "bandNo = %1 destDataType = %2 destHasNoDataValue = %3 destNoDataValue = %4" ).arg( bandNo ).arg( destDataType ).arg( destHasNoDataValue ).arg( destNoDataValue ), 4 ); destDataTypeList.append( destDataType ); destHasNoDataValueList.append( destHasNoDataValue ); destNoDataValueList.append( destNoDataValue ); } Qgis::DataType destDataType = destDataTypeList.value( 0 ); // Currently write API supports one output type for dataset only -> find the widest for ( int i = 1; i < nBands; i++ ) { if ( destDataTypeList.value( i ) > destDataType ) { destDataType = destDataTypeList.value( i ); // no data value may be left per band (for future) } } //create destProvider for whole dataset here QgsRasterDataProvider *destProvider = nullptr; double pixelSize; double geoTransform[6]; globalOutputParameters( outputExtent, nCols, nRows, geoTransform, pixelSize ); // initOutput() returns 0 in tile mode! destProvider = initOutput( nCols, nRows, crs, geoTransform, nBands, destDataType, destHasNoDataValueList, destNoDataValueList ); WriterError error = writeDataRaster( pipe, iter, nCols, nRows, outputExtent, crs, destDataType, destHasNoDataValueList, destNoDataValueList, destProvider, feedback ); if ( error == NoDataConflict ) { // The value used for no data was found in source data, we must use wider data type if ( destProvider ) // no tiles { destProvider->remove(); delete destProvider; destProvider = nullptr; } else // VRT { // TODO: remove created VRT } // But we don't know which band -> wider all for ( int i = 0; i < nBands; i++ ) { double destNoDataValue; Qgis::DataType destDataType = QgsRasterBlock::typeWithNoDataValue( destDataTypeList.value( i ), &destNoDataValue ); destDataTypeList.replace( i, destDataType ); destNoDataValueList.replace( i, destNoDataValue ); } destDataType = destDataTypeList.value( 0 ); // Try again destProvider = initOutput( nCols, nRows, crs, geoTransform, nBands, destDataType, destHasNoDataValueList, destNoDataValueList ); error = writeDataRaster( pipe, iter, nCols, nRows, outputExtent, crs, destDataType, destHasNoDataValueList, destNoDataValueList, destProvider, feedback ); } delete destProvider; return error; }