Skip to content

Commit

Permalink
Add API to QgsRasterBlock to obtain both pixel value AND no data
Browse files Browse the repository at this point in the history
flag in a single call

This is much more efficient then making two calls, since the
QgsRasterBlock::isNoData() check internally calls QgsRasterBlock::value().
So by requiring API users to make the two separate calls individually,
we double the time this process takes...
  • Loading branch information
nyalldawson committed Jan 22, 2019
1 parent bb2c366 commit ddd357c
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 34 deletions.
27 changes: 20 additions & 7 deletions python/core/auto_generated/raster/qgsrasterblock.sip.in
Original file line number Diff line number Diff line change
Expand Up @@ -153,20 +153,26 @@ returned value is undefined.
:param row: row index
:param column: column index

:return: value *
:return: value

.. seealso:: :py:func:`valueAndNoData`
%End


double value( qgssize index ) const;
%Docstring
Read a single value if type of block is numeric. If type is color,
Reads a single value if type of block is numeric. If type is color,
returned value is undefined.

:param index: data matrix index (long type in Python)

:return: value *
:return: value

.. seealso:: :py:func:`valueAndNoData`
%End



QRgb color( int row, int column ) const;
%Docstring
Read a single color
Expand All @@ -188,12 +194,14 @@ Read a single value

bool isNoData( int row, int column ) const;
%Docstring
Check if value at position is no data
Checks if value at position is no data

:param row: row index
:param column: column index

:return: true if value is no data *
:return: true if value is no data

.. seealso:: :py:func:`valueAndNoData`
%End

bool isNoData( qgssize row, qgssize column ) const;
Expand All @@ -203,7 +211,9 @@ Check if value at position is no data
:param row: row index
:param column: column index

:return: true if value is no data *
:return: true if value is no data

.. seealso:: :py:func:`valueAndNoData`
%End

bool isNoData( qgssize index ) const;
Expand All @@ -212,7 +222,9 @@ Check if value at position is no data

:param index: data matrix index (long type in Python)

:return: true if value is no data *
:return: true if value is no data

.. seealso:: :py:func:`valueAndNoData`
%End

bool setValue( int row, int column, double value );
Expand Down Expand Up @@ -441,6 +453,7 @@ Returns the height (number of rows) of the raster block.




/************************************************************************
* This file has been generated automatically from *
* *
Expand Down
129 changes: 102 additions & 27 deletions src/core/raster/qgsrasterblock.h
Original file line number Diff line number Diff line change
Expand Up @@ -179,23 +179,58 @@ class CORE_EXPORT QgsRasterBlock
static QByteArray valueBytes( Qgis::DataType dataType, double value );

/**
* \brief Read a single value if type of block is numeric. If type is color,
* returned value is undefined.
* \param row row index
* \param column column index
* \returns value */
* Read a single value if type of block is numeric. If type is color,
* returned value is undefined.
* \param row row index
* \param column column index
* \returns value
* \see valueAndNoData()
*/
double value( int row, int column ) const
{
return value( static_cast< qgssize >( row ) * mWidth + column );
}

/**
* \brief Read a single value if type of block is numeric. If type is color,
* returned value is undefined.
* \param index data matrix index (long type in Python)
* \returns value */
* Reads a single value from the pixel at \a row and \a column, if type of block is numeric. If type is color,
* returned value is undefined.
*
* Additionally, the \a isNoData argument will be set to true if the pixel represents a nodata value. This method
* is more efficient then calling isNoData() and value() separately.
*
* \note Not available in Python bindings
* \see value()
* \see isNoData()
* \since QGIS 3.6
*/
double valueAndNoData( int row, int column, bool &isNoData ) const SIP_SKIP
{
return valueAndNoData( static_cast< qgssize >( row ) * mWidth + column, isNoData );
}

/**
* Reads a single value if type of block is numeric. If type is color,
* returned value is undefined.
* \param index data matrix index (long type in Python)
* \returns value
* \see valueAndNoData()
*/
double value( qgssize index ) const;

/**
* Reads a single value from the pixel at the specified data matrix \a index, if type of block is numeric. If type is color,
* returned value is undefined.
*
* Additionally, the \a isNoData argument will be set to true if the pixel represents a nodata value. This method
* is more efficient then calling isNoData() and value() separately.
*
* \note Not available in Python bindings
* \see value()
* \see isNoData()
* \since QGIS 3.6
*/
double valueAndNoData( qgssize index, bool &isNoData ) const SIP_SKIP;

/**
* Gives direct access to the raster block data.
* The data type of the block must be Qgis::Byte otherwise it returns null pointer.
Expand Down Expand Up @@ -234,29 +269,35 @@ class CORE_EXPORT QgsRasterBlock
}

/**
* \brief Check if value at position is no data
* \param row row index
* \param column column index
* \returns true if value is no data */
* Checks if value at position is no data
* \param row row index
* \param column column index
* \returns true if value is no data
* \see valueAndNoData()
*/
bool isNoData( int row, int column ) const
{
return isNoData( static_cast< qgssize >( row ) * mWidth + column );
}

/**
* \brief Check if value at position is no data
* \param row row index
* \param column column index
* \returns true if value is no data */
* Check if value at position is no data
* \param row row index
* \param column column index
* \returns true if value is no data
* \see valueAndNoData()
*/
bool isNoData( qgssize row, qgssize column ) const
{
return isNoData( row * static_cast< qgssize >( mWidth ) + column );
}

/**
* \brief Check if value at position is no data
* \param index data matrix index (long type in Python)
* \returns true if value is no data */
* Check if value at position is no data
* \param index data matrix index (long type in Python)
* \returns true if value is no data
* \see valueAndNoData()
*/
bool isNoData( qgssize index ) const
{
if ( !mHasNoDataValue && !mNoDataBitmap )
Expand Down Expand Up @@ -683,25 +724,18 @@ inline double QgsRasterBlock::readValue( void *data, Qgis::DataType type, qgssiz
{
case Qgis::Byte:
return static_cast< double >( ( static_cast< quint8 * >( data ) )[index] );
break;
case Qgis::UInt16:
return static_cast< double >( ( static_cast< quint16 * >( data ) )[index] );
break;
case Qgis::Int16:
return static_cast< double >( ( static_cast< qint16 * >( data ) )[index] );
break;
case Qgis::UInt32:
return static_cast< double >( ( static_cast< quint32 * >( data ) )[index] );
break;
case Qgis::Int32:
return static_cast< double >( ( static_cast< qint32 * >( data ) )[index] );
break;
case Qgis::Float32:
return static_cast< double >( ( static_cast< float * >( data ) )[index] );
break;
case Qgis::Float64:
return static_cast< double >( ( static_cast< double * >( data ) )[index] );
break;
default:
QgsDebugMsg( QStringLiteral( "Data type %1 is not supported" ).arg( type ) );
break;
Expand Down Expand Up @@ -753,6 +787,47 @@ inline double QgsRasterBlock::value( qgssize index ) const SIP_SKIP
return readValue( mData, mDataType, index );
}

inline double QgsRasterBlock::valueAndNoData( qgssize index, bool &isNoData ) const SIP_SKIP
{
if ( !mData )
{
QgsDebugMsg( QStringLiteral( "Data block not allocated" ) );
isNoData = true;
return std::numeric_limits<double>::quiet_NaN();
}
if ( index >= static_cast< qgssize >( mWidth )*mHeight )
{
QgsDebugMsg( QStringLiteral( "Index %1 out of range (%2 x %3)" ).arg( index ).arg( mWidth ).arg( mHeight ) );
isNoData = true; // we consider no data if outside
return std::numeric_limits<double>::quiet_NaN();
}

const double val = readValue( mData, mDataType, index );

if ( !mHasNoDataValue && !mNoDataBitmap )
{
isNoData = false;
return val;
}

if ( mHasNoDataValue )
{
isNoData = isNoDataValue( val );
return val;
}
// use no data bitmap
if ( !mNoDataBitmap )
{
// no data are not defined
isNoData = false;
return val;
}

// no data is a bitmap
isNoData = QgsRasterBlock::isNoData( index );
return val;
}

inline bool QgsRasterBlock::isNoDataValue( double value ) const SIP_SKIP
{
return std::isnan( value ) || qgsDoubleNear( value, mNoDataValue );
Expand Down
27 changes: 27 additions & 0 deletions tests/src/core/testqgsrasterblock.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ void TestQgsRasterBlock::testBasic()

QgsRasterBlock *block = provider->block( 1, fullExtent, width, height );

bool isNoData = false;

QCOMPARE( block->width(), 10 );
QCOMPARE( block->height(), 10 );

Expand All @@ -96,16 +98,41 @@ void TestQgsRasterBlock::testBasic()
QCOMPARE( block->value( 0, 0 ), 2. );
QCOMPARE( block->value( 0, 1 ), 5. );
QCOMPARE( block->value( 1, 0 ), 27. );
QCOMPARE( block->valueAndNoData( 0, 0, isNoData ), 2. );
QVERIFY( !isNoData );
QCOMPARE( block->valueAndNoData( 0, 1, isNoData ), 5. );
QVERIFY( !isNoData );
QCOMPARE( block->valueAndNoData( 1, 0, isNoData ), 27. );
QVERIFY( !isNoData );
QVERIFY( std::isnan( block->valueAndNoData( mpRasterLayer->width() + 1, 0, isNoData ) ) );
QVERIFY( isNoData );

// value() with index
QCOMPARE( block->value( 0 ), 2. );
QCOMPARE( block->value( 1 ), 5. );
QCOMPARE( block->value( 10 ), 27. );
QCOMPARE( block->valueAndNoData( 0, isNoData ), 2. );
QVERIFY( !isNoData );
QCOMPARE( block->valueAndNoData( 1, isNoData ), 5. );
QVERIFY( !isNoData );
QCOMPARE( block->valueAndNoData( 10, isNoData ), 27. );
QVERIFY( !isNoData );
QVERIFY( std::isnan( block->valueAndNoData( mpRasterLayer->width() * mpRasterLayer->height(), isNoData ) ) );
QVERIFY( isNoData );

// isNoData()
QCOMPARE( block->isNoData( 0, 1 ), false );
QCOMPARE( block->isNoData( 0, 2 ), true );
QCOMPARE( block->isNoData( 1 ), false );
QCOMPARE( block->isNoData( 2 ), true );
QCOMPARE( block->valueAndNoData( 0, 1, isNoData ), 5. );
QVERIFY( !isNoData );
block->valueAndNoData( 0, 2, isNoData );
QVERIFY( isNoData );
QCOMPARE( block->valueAndNoData( 1, isNoData ), 5. );
QVERIFY( !isNoData );
block->valueAndNoData( 2, isNoData );
QVERIFY( isNoData );

// data()
QByteArray data = block->data();
Expand Down

0 comments on commit ddd357c

Please sign in to comment.