Skip to content

Commit f6f6ebd

Browse files
committed
[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) ```
1 parent f5e4c9d commit f6f6ebd

9 files changed

Lines changed: 245 additions & 1 deletion

File tree

‎python/core/raster/qgsrasterblock.sip‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,16 @@ class QgsRasterBlock
205205
*/
206206
QByteArray data() const;
207207

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

‎python/core/raster/qgsrasterdataprovider.sip‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,10 +233,44 @@ class QgsRasterDataProvider : QgsDataProvider, QgsRasterInterface
233233
/** Current time stamp of data source */
234234
virtual QDateTime dataTimestamp() const;
235235

236+
/** Checks whether the provider is in editing mode, i.e. raster write operations will be accepted.
237+
* By default providers are not editable. Use setEditable() method to enable/disable editing.
238+
* @see setEditable(), writeBlock()
239+
* @note added in QGIS 3.0
240+
*/
241+
virtual bool isEditable() const;
242+
243+
/** Turns on/off editing mode of the provider. When in editing mode, it is possible
244+
* to overwrite data of the provider using writeBlock() calls.
245+
* @note Only some providers support editing mode and even those may fail to turn turn
246+
* the underlying data source into editing mode, so it is necessery to check afterwards
247+
* with isEditable() whether the operation was successful.
248+
* @see isEditable(), writeBlock()
249+
* @note added in QGIS 3.0
250+
*/
251+
virtual void setEditable( bool enabled );
252+
236253
/** Writes into the provider datasource*/
237254
// TODO: add data type (may be defferent from band type)
238255
virtual bool write( void* data, int band, int width, int height, int xOffset, int yOffset );
239256

257+
/** Writes pixel data from a raster block into the provider data source.
258+
*
259+
* This will override previously stored pixel values. It is assumed that cells in the passed
260+
* raster block are aligned with the cells of the data source. If raster block does not cover
261+
* the whole area of the data source, only a subset of pixels covered by the raster block
262+
* will be overwritten. By default, writing of raster data starts from the first cell
263+
* of the raster - it is possible to set offset in pixels by specifying non-zero
264+
* xOffset and yOffset values.
265+
*
266+
* Writing is supported only by some data providers. Provider has to be in editing mode
267+
* in order to allow write operations.
268+
* @see isEditable(), setEditable()
269+
* @returns true on success
270+
* @note added in QGIS 3.0
271+
*/
272+
bool writeBlock( QgsRasterBlock* block, int band, int xOffset = 0, int yOffset = 0 );
273+
240274
/** Creates a new dataset with mDataSourceURI */
241275
static QgsRasterDataProvider* create( const QString &providerKey,
242276
const QString &uri,

‎src/core/raster/qgsrasterblock.cpp‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -653,6 +653,23 @@ QByteArray QgsRasterBlock::data() const
653653
return QByteArray();
654654
}
655655

656+
void QgsRasterBlock::setData( const QByteArray& data, int offset )
657+
{
658+
if ( offset < 0 )
659+
return; // negative offsets not allowed
660+
661+
if ( mData )
662+
{
663+
int len = qMin( data.size(), typeSize( mDataType ) * mWidth * mHeight - offset );
664+
::memcpy( static_cast<char *>( mData ) + offset, data.constData(), len );
665+
}
666+
else if ( mImage && mImage->constBits() )
667+
{
668+
int len = qMin( data.size(), mImage->byteCount() - offset );
669+
::memcpy( mImage->bits() + offset, data.constData(), len );
670+
}
671+
}
672+
656673
char * QgsRasterBlock::bits( qgssize index )
657674
{
658675
// Not testing type to avoid too much overhead because this method is called per pixel

‎src/core/raster/qgsrasterblock.h‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,16 @@ class CORE_EXPORT QgsRasterBlock
270270
*/
271271
QByteArray data() const;
272272

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

‎src/core/raster/qgsrasterdataprovider.cpp‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,18 @@ QString QgsRasterDataProvider::lastErrorFormat()
339339
return QStringLiteral( "text/plain" );
340340
}
341341

342+
bool QgsRasterDataProvider::writeBlock( QgsRasterBlock* block, int band, int xOffset, int yOffset )
343+
{
344+
if ( !block )
345+
return false;
346+
if ( !isEditable() )
347+
{
348+
QgsDebugMsg( "writeBlock() called on read-only provider." );
349+
return false;
350+
}
351+
return write( block->bits(), band, block->width(), block->height(), xOffset, yOffset );
352+
}
353+
342354
typedef QList<QPair<QString, QString> > *pyramidResamplingMethods_t();
343355
QList<QPair<QString, QString> > QgsRasterDataProvider::pyramidResamplingMethods( const QString& providerKey )
344356
{

‎src/core/raster/qgsrasterdataprovider.h‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,23 @@ class CORE_EXPORT QgsRasterDataProvider : public QgsDataProvider, public QgsRast
351351
//! Current time stamp of data source
352352
virtual QDateTime dataTimestamp() const override { return QDateTime(); }
353353

354+
/** Checks whether the provider is in editing mode, i.e. raster write operations will be accepted.
355+
* By default providers are not editable. Use setEditable() method to enable/disable editing.
356+
* @see setEditable(), writeBlock()
357+
* @note added in QGIS 3.0
358+
*/
359+
virtual bool isEditable() const { return false; }
360+
361+
/** Turns on/off editing mode of the provider. When in editing mode, it is possible
362+
* to overwrite data of the provider using writeBlock() calls.
363+
* @note Only some providers support editing mode and even those may fail to turn turn
364+
* the underlying data source into editing mode, so it is necessery to check afterwards
365+
* with isEditable() whether the operation was successful.
366+
* @see isEditable(), writeBlock()
367+
* @note added in QGIS 3.0
368+
*/
369+
virtual void setEditable( bool enabled ) { Q_UNUSED( enabled ); }
370+
354371
//! Writes into the provider datasource
355372
// TODO: add data type (may be defferent from band type)
356373
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
364381
return false;
365382
}
366383

384+
/** Writes pixel data from a raster block into the provider data source.
385+
*
386+
* This will override previously stored pixel values. It is assumed that cells in the passed
387+
* raster block are aligned with the cells of the data source. If raster block does not cover
388+
* the whole area of the data source, only a subset of pixels covered by the raster block
389+
* will be overwritten. By default, writing of raster data starts from the first cell
390+
* of the raster - it is possible to set offset in pixels by specifying non-zero
391+
* xOffset and yOffset values.
392+
*
393+
* Writing is supported only by some data providers. Provider has to be in editing mode
394+
* in order to allow write operations.
395+
* @see isEditable(), setEditable()
396+
* @returns true on success
397+
* @note added in QGIS 3.0
398+
*/
399+
bool writeBlock( QgsRasterBlock* block, int band, int xOffset = 0, int yOffset = 0 );
400+
367401
//! Creates a new dataset with mDataSourceURI
368402
static QgsRasterDataProvider* create( const QString &providerKey,
369403
const QString &uri,

‎src/providers/gdal/qgsgdalprovider.cpp‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2928,6 +2928,40 @@ QString QgsGdalProvider::validatePyramidsConfigOptions( QgsRaster::RasterPyramid
29282928
return QString();
29292929
}
29302930

2931+
bool QgsGdalProvider::isEditable() const
2932+
{
2933+
return mUpdate;
2934+
}
2935+
2936+
void QgsGdalProvider::setEditable( bool enabled )
2937+
{
2938+
if ( enabled == mUpdate )
2939+
return;
2940+
2941+
if ( !mValid )
2942+
return;
2943+
2944+
if ( mGdalDataset != mGdalBaseDataset )
2945+
return; // ignore the case of warped VRT for now (more complicated setup)
2946+
2947+
closeDataset();
2948+
2949+
mUpdate = enabled;
2950+
2951+
// reopen the dataset
2952+
mGdalBaseDataset = gdalOpen( dataSourceUri().toUtf8().constData(), mUpdate ? GA_Update : GA_ReadOnly );
2953+
if ( !mGdalBaseDataset )
2954+
{
2955+
QString msg = QStringLiteral( "Cannot reopen GDAL dataset %1:\n%2" ).arg( dataSourceUri(), QString::fromUtf8( CPLGetLastErrorMsg() ) );
2956+
appendError( ERRMSG( msg ) );
2957+
return;
2958+
}
2959+
2960+
//Since we are not a virtual warped dataset, mGdalDataSet and mGdalBaseDataset are supposed to be the same
2961+
mGdalDataset = mGdalBaseDataset;
2962+
mValid = true;
2963+
}
2964+
29312965
// pyramids resampling
29322966

29332967
// see http://www.gdal.org/gdaladdo.html

‎src/providers/gdal/qgsgdalprovider.h‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,14 +148,16 @@ class QgsGdalProvider : public QgsRasterDataProvider, QgsGdalProviderBase
148148

149149
static QMap<QString, QString> supportedMimes();
150150

151+
bool isEditable() const override;
152+
void setEditable( bool enabled ) override;
151153
bool write( void* data, int band, int width, int height, int xOffset, int yOffset ) override;
154+
152155
bool setNoDataValue( int bandNo, double noDataValue ) override;
153156
bool remove() override;
154157

155158
QString validateCreationOptions( const QStringList& createOptions, const QString& format ) override;
156159
QString validatePyramidsConfigOptions( QgsRaster::RasterPyramidsFormat pyramidsFormat,
157160
const QStringList & theConfigOptions, const QString & fileFormat ) override;
158-
159161
private:
160162
// update mode
161163
bool mUpdate;

‎tests/src/core/testqgsrasterblock.cpp‎

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "qgstest.h"
1717
#include <QObject>
1818
#include <QString>
19+
#include <QTemporaryFile>
1920

2021
#include "qgsrasterlayer.h"
2122
#include "qgsrasterdataprovider.h"
@@ -41,6 +42,7 @@ class TestQgsRasterBlock : public QObject
4142
void cleanup() {} // will be called after every testfunction.
4243

4344
void testBasic();
45+
void testWrite();
4446

4547
private:
4648

@@ -116,6 +118,95 @@ void TestQgsRasterBlock::testBasic()
116118
QCOMPARE( data.at( 1 ), ( char ) 5 );
117119
QCOMPARE( data.at( 10 ), ( char ) 27 );
118120

121+
// setData()
122+
QByteArray newData( "\xaa\xbb\xcc\xdd" );
123+
block->setData( newData, 1 );
124+
QByteArray data2 = block->data();
125+
QCOMPARE( data2.at( 0 ), ( char ) 2 );
126+
QCOMPARE( data2.at( 1 ), '\xaa' );
127+
QCOMPARE( data2.at( 2 ), '\xbb' );
128+
QCOMPARE( data2.at( 10 ), ( char ) 27 );
129+
130+
delete block;
131+
}
132+
133+
void TestQgsRasterBlock::testWrite()
134+
{
135+
QgsRectangle extent = mpRasterLayer->extent();
136+
int nCols = mpRasterLayer->width(), nRows = mpRasterLayer->height();
137+
double tform[] =
138+
{
139+
extent.xMinimum(), extent.width() / nCols, 0.0,
140+
extent.yMaximum(), 0.0, -extent.height() / nRows
141+
};
142+
143+
// generate unique filename (need to open the file first to generate it)
144+
QTemporaryFile tmpFile;
145+
tmpFile.open();
146+
tmpFile.close();
147+
148+
// create a GeoTIFF - this will create data provider in editable mode
149+
QString filename = tmpFile.fileName();
150+
QgsRasterDataProvider* dp = QgsRasterDataProvider::create( "gdal", filename, "GTiff", 1, Qgis::Byte, 10, 10, tform, mpRasterLayer->crs() );
151+
152+
QgsRasterBlock* block = mpRasterLayer->dataProvider()->block( 1, mpRasterLayer->extent(), mpRasterLayer->width(), mpRasterLayer->height() );
153+
154+
QByteArray origData = block->data();
155+
origData.detach(); // make sure we have private copy independent from independent block content
156+
QCOMPARE( origData.at( 0 ), ( char ) 2 );
157+
QCOMPARE( origData.at( 1 ), ( char ) 5 );
158+
159+
// change first two pixels
160+
block->setData( QByteArray( "\xa0\xa1" ) );
161+
bool res = dp->writeBlock( block, 1 );
162+
QVERIFY( res );
163+
164+
QgsRasterBlock* block2 = dp->block( 1, mpRasterLayer->extent(), mpRasterLayer->width(), mpRasterLayer->height() );
165+
QByteArray newData2 = block2->data();
166+
QCOMPARE( newData2.at( 0 ), '\xa0' );
167+
QCOMPARE( newData2.at( 1 ), '\xa1' );
168+
169+
delete block2;
170+
delete dp;
171+
172+
// newly open raster and verify the write was permanent
173+
QgsRasterLayer* rlayer = new QgsRasterLayer( filename, "tmp", "gdal" );
174+
QVERIFY( rlayer->isValid() );
175+
QgsRasterBlock* block3 = rlayer->dataProvider()->block( 1, rlayer->extent(), rlayer->width(), rlayer->height() );
176+
QByteArray newData3 = block3->data();
177+
QCOMPARE( newData3.at( 0 ), '\xa0' );
178+
QCOMPARE( newData3.at( 1 ), '\xa1' );
179+
180+
QgsRasterBlock* block4 = new QgsRasterBlock( Qgis::Byte, 1, 2 );
181+
block4->setData( QByteArray( "\xb0\xb1" ) );
182+
183+
// cannot write when provider is not editable
184+
res = rlayer->dataProvider()->writeBlock( block4, 1 );
185+
QVERIFY( !res );
186+
187+
// make the provider editable
188+
QVERIFY( !rlayer->dataProvider()->isEditable() );
189+
rlayer->dataProvider()->setEditable( true );
190+
QVERIFY( rlayer->dataProvider()->isEditable() );
191+
192+
res = rlayer->dataProvider()->writeBlock( block4, 1 );
193+
QVERIFY( res );
194+
195+
rlayer->dataProvider()->setEditable( false );
196+
QVERIFY( !rlayer->dataProvider()->isEditable() );
197+
198+
// verify the change is there
199+
QgsRasterBlock* block5 = rlayer->dataProvider()->block( 1, rlayer->extent(), rlayer->width(), rlayer->height() );
200+
QByteArray newData5 = block5->data();
201+
QCOMPARE( newData5.at( 0 ), '\xb0' );
202+
QCOMPARE( newData5.at( 1 ), '\xa1' ); // original data
203+
QCOMPARE( newData5.at( 10 ), '\xb1' );
204+
205+
delete block3;
206+
delete block4;
207+
delete block5;
208+
delete rlayer;
209+
119210
delete block;
120211
}
121212

0 commit comments

Comments
 (0)