mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-14 00:07:35 -04:00
[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:
parent
f5e4c9d617
commit
f6f6ebdb44
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 )
|
||||
{
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user