[FEATURE] API to enable/disable editing of raster data

To create a 2x2 raster block with one byte per pixel:

```
block = QgsRasterBlock(Qgis.Byte, 2, 2)
block.setData(b'\xaa\xbb\xcc\xdd')
```

To overwrite existing raster data at position 0,0 by the 2x2 block:

```
provider.setEditable(True)
provider.writeBlock(block, band, 0, 0)
provider.setEditable(False)
```
This commit is contained in:
Martin Dobias 2017-01-19 11:48:07 +08:00
parent f5e4c9d617
commit f6f6ebdb44
9 changed files with 245 additions and 1 deletions

View File

@ -205,6 +205,16 @@ class QgsRasterBlock
*/
QByteArray data() const;
/** Rewrite raw pixel data.
* If the data array is shorter than the internal array within the raster block object,
* pixels at the end will stay untouched. If the data array is longer than the internal
* array, only the initial data from the input array will be used.
* Optionally it is possible to set non-zero offset (in bytes) if the input data should
* overwrite data somewhere in the middle of the internal buffer.
* @note added in QGIS 3.0
*/
void setData( const QByteArray& data, int offset = 0 );
/** \brief Get pointer to data
* @param row row index
* @param column column index

View File

@ -233,10 +233,44 @@ class QgsRasterDataProvider : QgsDataProvider, QgsRasterInterface
/** Current time stamp of data source */
virtual QDateTime dataTimestamp() const;
/** Checks whether the provider is in editing mode, i.e. raster write operations will be accepted.
* By default providers are not editable. Use setEditable() method to enable/disable editing.
* @see setEditable(), writeBlock()
* @note added in QGIS 3.0
*/
virtual bool isEditable() const;
/** Turns on/off editing mode of the provider. When in editing mode, it is possible
* to overwrite data of the provider using writeBlock() calls.
* @note Only some providers support editing mode and even those may fail to turn turn
* the underlying data source into editing mode, so it is necessery to check afterwards
* with isEditable() whether the operation was successful.
* @see isEditable(), writeBlock()
* @note added in QGIS 3.0
*/
virtual void setEditable( bool enabled );
/** Writes into the provider datasource*/
// TODO: add data type (may be defferent from band type)
virtual bool write( void* data, int band, int width, int height, int xOffset, int yOffset );
/** Writes pixel data from a raster block into the provider data source.
*
* This will override previously stored pixel values. It is assumed that cells in the passed
* raster block are aligned with the cells of the data source. If raster block does not cover
* the whole area of the data source, only a subset of pixels covered by the raster block
* will be overwritten. By default, writing of raster data starts from the first cell
* of the raster - it is possible to set offset in pixels by specifying non-zero
* xOffset and yOffset values.
*
* Writing is supported only by some data providers. Provider has to be in editing mode
* in order to allow write operations.
* @see isEditable(), setEditable()
* @returns true on success
* @note added in QGIS 3.0
*/
bool writeBlock( QgsRasterBlock* block, int band, int xOffset = 0, int yOffset = 0 );
/** Creates a new dataset with mDataSourceURI */
static QgsRasterDataProvider* create( const QString &providerKey,
const QString &uri,

View File

@ -653,6 +653,23 @@ QByteArray QgsRasterBlock::data() const
return QByteArray();
}
void QgsRasterBlock::setData( const QByteArray& data, int offset )
{
if ( offset < 0 )
return; // negative offsets not allowed
if ( mData )
{
int len = qMin( data.size(), typeSize( mDataType ) * mWidth * mHeight - offset );
::memcpy( static_cast<char *>( mData ) + offset, data.constData(), len );
}
else if ( mImage && mImage->constBits() )
{
int len = qMin( data.size(), mImage->byteCount() - offset );
::memcpy( mImage->bits() + offset, data.constData(), len );
}
}
char * QgsRasterBlock::bits( qgssize index )
{
// Not testing type to avoid too much overhead because this method is called per pixel

View File

@ -270,6 +270,16 @@ class CORE_EXPORT QgsRasterBlock
*/
QByteArray data() const;
/** Rewrite raw pixel data.
* If the data array is shorter than the internal array within the raster block object,
* pixels at the end will stay untouched. If the data array is longer than the internal
* array, only the initial data from the input array will be used.
* Optionally it is possible to set non-zero offset (in bytes) if the input data should
* overwrite data somewhere in the middle of the internal buffer.
* @note added in QGIS 3.0
*/
void setData( const QByteArray& data, int offset = 0 );
/** \brief Get pointer to data
* @param row row index
* @param column column index

View File

@ -339,6 +339,18 @@ QString QgsRasterDataProvider::lastErrorFormat()
return QStringLiteral( "text/plain" );
}
bool QgsRasterDataProvider::writeBlock( QgsRasterBlock* block, int band, int xOffset, int yOffset )
{
if ( !block )
return false;
if ( !isEditable() )
{
QgsDebugMsg( "writeBlock() called on read-only provider." );
return false;
}
return write( block->bits(), band, block->width(), block->height(), xOffset, yOffset );
}
typedef QList<QPair<QString, QString> > *pyramidResamplingMethods_t();
QList<QPair<QString, QString> > QgsRasterDataProvider::pyramidResamplingMethods( const QString& providerKey )
{

View File

@ -351,6 +351,23 @@ class CORE_EXPORT QgsRasterDataProvider : public QgsDataProvider, public QgsRast
//! Current time stamp of data source
virtual QDateTime dataTimestamp() const override { return QDateTime(); }
/** Checks whether the provider is in editing mode, i.e. raster write operations will be accepted.
* By default providers are not editable. Use setEditable() method to enable/disable editing.
* @see setEditable(), writeBlock()
* @note added in QGIS 3.0
*/
virtual bool isEditable() const { return false; }
/** Turns on/off editing mode of the provider. When in editing mode, it is possible
* to overwrite data of the provider using writeBlock() calls.
* @note Only some providers support editing mode and even those may fail to turn turn
* the underlying data source into editing mode, so it is necessery to check afterwards
* with isEditable() whether the operation was successful.
* @see isEditable(), writeBlock()
* @note added in QGIS 3.0
*/
virtual void setEditable( bool enabled ) { Q_UNUSED( enabled ); }
//! Writes into the provider datasource
// TODO: add data type (may be defferent from band type)
virtual bool write( void* data, int band, int width, int height, int xOffset, int yOffset )
@ -364,6 +381,23 @@ class CORE_EXPORT QgsRasterDataProvider : public QgsDataProvider, public QgsRast
return false;
}
/** Writes pixel data from a raster block into the provider data source.
*
* This will override previously stored pixel values. It is assumed that cells in the passed
* raster block are aligned with the cells of the data source. If raster block does not cover
* the whole area of the data source, only a subset of pixels covered by the raster block
* will be overwritten. By default, writing of raster data starts from the first cell
* of the raster - it is possible to set offset in pixels by specifying non-zero
* xOffset and yOffset values.
*
* Writing is supported only by some data providers. Provider has to be in editing mode
* in order to allow write operations.
* @see isEditable(), setEditable()
* @returns true on success
* @note added in QGIS 3.0
*/
bool writeBlock( QgsRasterBlock* block, int band, int xOffset = 0, int yOffset = 0 );
//! Creates a new dataset with mDataSourceURI
static QgsRasterDataProvider* create( const QString &providerKey,
const QString &uri,

View File

@ -2928,6 +2928,40 @@ QString QgsGdalProvider::validatePyramidsConfigOptions( QgsRaster::RasterPyramid
return QString();
}
bool QgsGdalProvider::isEditable() const
{
return mUpdate;
}
void QgsGdalProvider::setEditable( bool enabled )
{
if ( enabled == mUpdate )
return;
if ( !mValid )
return;
if ( mGdalDataset != mGdalBaseDataset )
return; // ignore the case of warped VRT for now (more complicated setup)
closeDataset();
mUpdate = enabled;
// reopen the dataset
mGdalBaseDataset = gdalOpen( dataSourceUri().toUtf8().constData(), mUpdate ? GA_Update : GA_ReadOnly );
if ( !mGdalBaseDataset )
{
QString msg = QStringLiteral( "Cannot reopen GDAL dataset %1:\n%2" ).arg( dataSourceUri(), QString::fromUtf8( CPLGetLastErrorMsg() ) );
appendError( ERRMSG( msg ) );
return;
}
//Since we are not a virtual warped dataset, mGdalDataSet and mGdalBaseDataset are supposed to be the same
mGdalDataset = mGdalBaseDataset;
mValid = true;
}
// pyramids resampling
// see http://www.gdal.org/gdaladdo.html

View File

@ -148,14 +148,16 @@ class QgsGdalProvider : public QgsRasterDataProvider, QgsGdalProviderBase
static QMap<QString, QString> supportedMimes();
bool isEditable() const override;
void setEditable( bool enabled ) override;
bool write( void* data, int band, int width, int height, int xOffset, int yOffset ) override;
bool setNoDataValue( int bandNo, double noDataValue ) override;
bool remove() override;
QString validateCreationOptions( const QStringList& createOptions, const QString& format ) override;
QString validatePyramidsConfigOptions( QgsRaster::RasterPyramidsFormat pyramidsFormat,
const QStringList & theConfigOptions, const QString & fileFormat ) override;
private:
// update mode
bool mUpdate;

View File

@ -16,6 +16,7 @@
#include "qgstest.h"
#include <QObject>
#include <QString>
#include <QTemporaryFile>
#include "qgsrasterlayer.h"
#include "qgsrasterdataprovider.h"
@ -41,6 +42,7 @@ class TestQgsRasterBlock : public QObject
void cleanup() {} // will be called after every testfunction.
void testBasic();
void testWrite();
private:
@ -116,6 +118,95 @@ void TestQgsRasterBlock::testBasic()
QCOMPARE( data.at( 1 ), ( char ) 5 );
QCOMPARE( data.at( 10 ), ( char ) 27 );
// setData()
QByteArray newData( "\xaa\xbb\xcc\xdd" );
block->setData( newData, 1 );
QByteArray data2 = block->data();
QCOMPARE( data2.at( 0 ), ( char ) 2 );
QCOMPARE( data2.at( 1 ), '\xaa' );
QCOMPARE( data2.at( 2 ), '\xbb' );
QCOMPARE( data2.at( 10 ), ( char ) 27 );
delete block;
}
void TestQgsRasterBlock::testWrite()
{
QgsRectangle extent = mpRasterLayer->extent();
int nCols = mpRasterLayer->width(), nRows = mpRasterLayer->height();
double tform[] =
{
extent.xMinimum(), extent.width() / nCols, 0.0,
extent.yMaximum(), 0.0, -extent.height() / nRows
};
// generate unique filename (need to open the file first to generate it)
QTemporaryFile tmpFile;
tmpFile.open();
tmpFile.close();
// create a GeoTIFF - this will create data provider in editable mode
QString filename = tmpFile.fileName();
QgsRasterDataProvider* dp = QgsRasterDataProvider::create( "gdal", filename, "GTiff", 1, Qgis::Byte, 10, 10, tform, mpRasterLayer->crs() );
QgsRasterBlock* block = mpRasterLayer->dataProvider()->block( 1, mpRasterLayer->extent(), mpRasterLayer->width(), mpRasterLayer->height() );
QByteArray origData = block->data();
origData.detach(); // make sure we have private copy independent from independent block content
QCOMPARE( origData.at( 0 ), ( char ) 2 );
QCOMPARE( origData.at( 1 ), ( char ) 5 );
// change first two pixels
block->setData( QByteArray( "\xa0\xa1" ) );
bool res = dp->writeBlock( block, 1 );
QVERIFY( res );
QgsRasterBlock* block2 = dp->block( 1, mpRasterLayer->extent(), mpRasterLayer->width(), mpRasterLayer->height() );
QByteArray newData2 = block2->data();
QCOMPARE( newData2.at( 0 ), '\xa0' );
QCOMPARE( newData2.at( 1 ), '\xa1' );
delete block2;
delete dp;
// newly open raster and verify the write was permanent
QgsRasterLayer* rlayer = new QgsRasterLayer( filename, "tmp", "gdal" );
QVERIFY( rlayer->isValid() );
QgsRasterBlock* block3 = rlayer->dataProvider()->block( 1, rlayer->extent(), rlayer->width(), rlayer->height() );
QByteArray newData3 = block3->data();
QCOMPARE( newData3.at( 0 ), '\xa0' );
QCOMPARE( newData3.at( 1 ), '\xa1' );
QgsRasterBlock* block4 = new QgsRasterBlock( Qgis::Byte, 1, 2 );
block4->setData( QByteArray( "\xb0\xb1" ) );
// cannot write when provider is not editable
res = rlayer->dataProvider()->writeBlock( block4, 1 );
QVERIFY( !res );
// make the provider editable
QVERIFY( !rlayer->dataProvider()->isEditable() );
rlayer->dataProvider()->setEditable( true );
QVERIFY( rlayer->dataProvider()->isEditable() );
res = rlayer->dataProvider()->writeBlock( block4, 1 );
QVERIFY( res );
rlayer->dataProvider()->setEditable( false );
QVERIFY( !rlayer->dataProvider()->isEditable() );
// verify the change is there
QgsRasterBlock* block5 = rlayer->dataProvider()->block( 1, rlayer->extent(), rlayer->width(), rlayer->height() );
QByteArray newData5 = block5->data();
QCOMPARE( newData5.at( 0 ), '\xb0' );
QCOMPARE( newData5.at( 1 ), '\xa1' ); // original data
QCOMPARE( newData5.at( 10 ), '\xb1' );
delete block3;
delete block4;
delete block5;
delete rlayer;
delete block;
}