QGIS API Documentation 4.3.0-Master (0d5b841b09e)
Loading...
Searching...
No Matches
qgsalgorithmfillsinkswangliu.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsalgorithmfillsinkswangliu.cpp
3 ---------------------
4 begin : April 2025
5 copyright : (C) 2025 by Nyall Dawson
6 email : nyall dot dawson at gmail dot com
7 ***************************************************************************/
8
9/***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
19
21#include "qgsrasterfilewriter.h"
22
23#include <QString>
24#include <queue>
25
26using namespace Qt::StringLiterals;
27
29
30QString QgsFillSinksWangLiuAlgorithm::name() const
31{
32 return u"fillsinkswangliu"_s;
33}
34
35QString QgsFillSinksWangLiuAlgorithm::displayName() const
36{
37 return QObject::tr( "Fill sinks (Wang & Liu)" );
38}
39
40QStringList QgsFillSinksWangLiuAlgorithm::tags() const
41{
42 return QObject::tr( "fill,filter,slope,dsm,dtm,terrain,water,shed,basin,direction,flow" ).split( ',' );
43}
44
45QString QgsFillSinksWangLiuAlgorithm::group() const
46{
47 return QObject::tr( "Raster terrain analysis" );
48}
49
50QString QgsFillSinksWangLiuAlgorithm::groupId() const
51{
52 return u"rasterterrainanalysis"_s;
53}
54
55QString QgsFillSinksWangLiuAlgorithm::shortHelpString() const
56{
57 return QObject::tr(
58 "This algorithm uses a method proposed by Wang & Liu to identify and fill surface depressions in digital elevation models.\n\n"
59
60 "The method was enhanced to allow the creation of hydrologically sound elevation models, i.e. not only to fill the depression(s) "
61 "but also to preserve a downward slope along the flow path. If desired, this is accomplished by preserving a minimum slope "
62 "gradient (and thus elevation difference) between cells.\n\n"
63
64 "This algorithm is a port of the SAGA 'Fill Sinks (Wang & Liu)' tool."
65 );
66}
67
68QList<QgsAcademicReference> QgsFillSinksWangLiuAlgorithm::academicReferences() const
69{
71 { u"Wang, L."_s, u"Liu, H."_s },
72 2006,
73 u"An efficient method for identifying and filling surface depressions in digital elevation models for hydrologic analysis and modelling."_s,
74 u"International Journal of Geographical Information Science"_s,
75 u"20"_s,
76 u"No. 2"_s,
77 u"193-213"_s
78 );
79 return { ref };
80}
81
82QList<QgsProcessingAlgorithm::ExternalLink> QgsFillSinksWangLiuAlgorithm::externalLinks() const
83{
84 return {
85 QgsProcessingAlgorithm::ExternalLink { QObject::tr( "SAGA tool source code" ), u"https://sourceforge.net/p/saga-gis/code/ci/72d9890b130fd446d7ffb7251c44b2e98c8e1e53/tree/saga-gis/src/tools/terrain_analysis/ta_preprocessor/FillSinks_WL.cpp"_s }
86 };
87}
88
89QString QgsFillSinksWangLiuAlgorithm::shortDescription() const
90{
91 return QObject::tr( "Identifies and fills surface depressions in digital elevation models using a method proposed by Wang & Liu." );
92}
93
94void QgsFillSinksWangLiuAlgorithm::initAlgorithm( const QVariantMap & )
95{
96 addParameter( new QgsProcessingParameterRasterLayer( u"INPUT"_s, QObject::tr( "Input layer" ) ) );
97
98 addParameter( new QgsProcessingParameterBand( u"BAND"_s, QObject::tr( "Band number" ), 1, u"INPUT"_s ) );
99
100 auto minSlopeParam = std::make_unique<QgsProcessingParameterNumber>( u"MIN_SLOPE"_s, QObject::tr( "Minimum slope (degrees)" ), Qgis::ProcessingNumberParameterType::Double, 0.01, false, 0 );
101 minSlopeParam->setHelp(
102 QObject::tr( "Minimum slope gradient to preserve from cell to cell. With a value of zero sinks are filled up to the spill elevation (which results in flat areas). Units are degrees." )
103 );
104 addParameter( minSlopeParam.release() );
105
106 auto createOptsParam = std::make_unique<QgsProcessingParameterString>( u"CREATION_OPTIONS"_s, QObject::tr( "Creation options" ), QVariant(), false, true );
107 createOptsParam->setMetadata( QVariantMap( { { u"widget_wrapper"_s, QVariantMap( { { u"widget_type"_s, u"rasteroptions"_s } } ) } } ) );
108 createOptsParam->setFlags( createOptsParam->flags() | Qgis::ProcessingParameterFlag::Advanced );
109 addParameter( createOptsParam.release() );
110
111 auto outputFilledDem = std::make_unique<QgsProcessingParameterRasterDestination>( u"OUTPUT_FILLED_DEM"_s, QObject::tr( "Output layer (filled DEM)" ), QVariant(), true, true );
112 outputFilledDem->setHelp( QObject::tr( "Depression-free digital elevation model." ) );
113 addParameter( outputFilledDem.release() );
114
115 auto outputFlowDirections = std::make_unique<QgsProcessingParameterRasterDestination>( u"OUTPUT_FLOW_DIRECTIONS"_s, QObject::tr( "Output layer (flow directions)" ), QVariant(), true, false );
116 outputFlowDirections->setHelp( QObject::tr( "Computed flow directions, 0=N, 1=NE, 2=E, ... 7=NW." ) );
117 addParameter( outputFlowDirections.release() );
118
119 auto outputWatershedBasins = std::make_unique<QgsProcessingParameterRasterDestination>( u"OUTPUT_WATERSHED_BASINS"_s, QObject::tr( "Output layer (watershed basins)" ), QVariant(), true, false );
120 outputWatershedBasins->setHelp( QObject::tr( "Delineated watershed basin." ) );
121 addParameter( outputWatershedBasins.release() );
122}
123
124QgsFillSinksWangLiuAlgorithm *QgsFillSinksWangLiuAlgorithm::createInstance() const
125{
126 return new QgsFillSinksWangLiuAlgorithm();
127}
128
129bool QgsFillSinksWangLiuAlgorithm::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback * )
130{
131 QgsRasterLayer *layer = parameterAsRasterLayer( parameters, u"INPUT"_s, context );
132 if ( !layer )
133 throw QgsProcessingException( invalidRasterError( parameters, u"INPUT"_s ) );
134
135 const int band = parameterAsInt( parameters, u"BAND"_s, context );
136
137 mBand = parameterAsInt( parameters, u"BAND"_s, context );
138 if ( mBand < 1 || mBand > layer->bandCount() )
139 throw QgsProcessingException( QObject::tr( "Invalid band number for BAND (%1): Valid values for input raster are 1 to %2" ).arg( mBand ).arg( layer->bandCount() ) );
140
141 mInterface.reset( layer->dataProvider()->clone() );
142 mHasNoDataValue = layer->dataProvider()->sourceHasNoDataValue( band );
143 mLayerWidth = layer->width();
144 mLayerHeight = layer->height();
145 mExtent = layer->extent();
146 mCrs = layer->crs();
147 mRasterUnitsPerPixelX = layer->rasterUnitsPerPixelX();
148 mRasterUnitsPerPixelY = layer->rasterUnitsPerPixelY();
149 mRasterDiagonal = std::sqrt( mRasterUnitsPerPixelX * mRasterUnitsPerPixelX + mRasterUnitsPerPixelY * mRasterUnitsPerPixelY );
150 mDataType = layer->dataProvider()->dataType( mBand );
151 mNoData = layer->dataProvider()->sourceNoDataValue( mBand );
152 mDirectionalLengths = { mRasterUnitsPerPixelY, mRasterDiagonal, mRasterUnitsPerPixelX, mRasterDiagonal, mRasterUnitsPerPixelY, mRasterDiagonal, mRasterUnitsPerPixelX, mRasterDiagonal };
153 return true;
154}
155
156static constexpr std::array< int, 8 > COL_DIRECTION_OFFSETS { 0, 1, 1, 1, 0, -1, -1, -1 };
157static constexpr std::array< int, 8 > ROW_DIRECTION_OFFSETS { -1, -1, 0, 1, 1, 1, 0, -1 };
158
159bool QgsFillSinksWangLiuAlgorithm::isInGrid( int row, int col ) const
160{
161 return col >= 0 && col < mLayerWidth && row >= 0 && row < mLayerHeight;
162}
163
164QgsFillSinksWangLiuAlgorithm::Direction QgsFillSinksWangLiuAlgorithm::getDir( int row, int col, double z, const QgsRasterBlock *filled ) const
165{
166 Direction steepestDirection = Invalid;
167 double maxGradient = 0;
168 bool isNoData = false;
169
170 for ( Direction direction : { North, NorthEast, East, SouthEast, South, SouthWest, West, NorthWest } )
171 {
172 const int neighborCol = col + COL_DIRECTION_OFFSETS[direction];
173 const int neighborRow = row + ROW_DIRECTION_OFFSETS[direction];
174
175 if ( isInGrid( neighborRow, neighborCol ) )
176 {
177 const double neighborZ = filled->valueAndNoData( neighborRow, neighborCol, isNoData );
178 if ( !isNoData && neighborZ < z )
179 {
180 const double gradient = ( z - neighborZ ) / mDirectionalLengths[direction];
181 if ( gradient >= maxGradient )
182 {
183 maxGradient = gradient;
184 steepestDirection = direction;
185 }
186 }
187 }
188 }
189
190 return steepestDirection;
191}
192
193struct CFillSinks_WL_Node
194{
195 int row = 0;
196 int col = 0;
197 double spill = 0;
198};
199
200class CompareGreater
201{
202 public:
203 bool operator()( CFillSinks_WL_Node n1, CFillSinks_WL_Node n2 ) const { return n1.spill > n2.spill; }
204};
205
206typedef std::vector< CFillSinks_WL_Node > nodeVector;
207typedef std::priority_queue< CFillSinks_WL_Node, nodeVector, CompareGreater > PriorityQ;
208
209QVariantMap QgsFillSinksWangLiuAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
210{
211 QGS_MARK_ALGORITHM_SOURCE
212
213 const QString createOptions = parameterAsString( parameters, u"CREATION_OPTIONS"_s, context ).trimmed();
214
215 const QString filledDemOutputFile = parameterAsOutputLayer( parameters, u"OUTPUT_FILLED_DEM"_s, context );
216 const QString flowDirectionsOutputFile = parameterAsOutputLayer( parameters, u"OUTPUT_FLOW_DIRECTIONS"_s, context );
217 const QString watershedBasinsOutputFile = parameterAsOutputLayer( parameters, u"OUTPUT_WATERSHED_BASINS"_s, context );
218
219 std::unique_ptr<QgsRasterFileWriter> filledDemWriter;
220 std::unique_ptr<QgsRasterDataProvider> filledDemDestProvider;
221
222 if ( !filledDemOutputFile.isEmpty() )
223 {
224 const QString outputFormat = parameterAsOutputRasterFormat( parameters, u"OUTPUT_FILLED_DEM"_s, context );
225
226 filledDemWriter = std::make_unique<QgsRasterFileWriter>( filledDemOutputFile );
227 filledDemWriter->setOutputProviderKey( u"gdal"_s );
228 if ( !createOptions.isEmpty() )
229 {
230 filledDemWriter->setCreationOptions( createOptions.split( '|' ) );
231 }
232 filledDemWriter->setOutputFormat( outputFormat );
233
234 filledDemDestProvider.reset( filledDemWriter->createOneBandRaster( mDataType, mLayerWidth, mLayerHeight, mExtent, mCrs ) );
235
236 if ( !filledDemDestProvider )
237 throw QgsProcessingException( QObject::tr( "Could not create raster output: %1" ).arg( filledDemOutputFile ) );
238 if ( !filledDemDestProvider->isValid() )
239 throw QgsProcessingException( QObject::tr( "Could not create raster output %1: %2" ).arg( filledDemOutputFile, filledDemDestProvider->error().message( QgsErrorMessage::Text ) ) );
240
241 filledDemDestProvider->setNoDataValue( 1, mNoData );
242 filledDemDestProvider->setEditable( true );
243 }
244
245 std::unique_ptr<QgsRasterFileWriter> flowDirectionsWriter;
246 std::unique_ptr<QgsRasterDataProvider> flowDirectionsDestProvider;
247
248 if ( !flowDirectionsOutputFile.isEmpty() )
249 {
250 const QString outputFormat = parameterAsOutputRasterFormat( parameters, u"OUTPUT_FLOW_DIRECTIONS"_s, context );
251
252 flowDirectionsWriter = std::make_unique<QgsRasterFileWriter>( flowDirectionsOutputFile );
253 flowDirectionsWriter->setOutputProviderKey( u"gdal"_s );
254 flowDirectionsWriter->setOutputFormat( outputFormat );
255
256 flowDirectionsDestProvider.reset( flowDirectionsWriter->createOneBandRaster( Qgis::DataType::Byte, mLayerWidth, mLayerHeight, mExtent, mCrs ) );
257
258 if ( !flowDirectionsDestProvider )
259 throw QgsProcessingException( QObject::tr( "Could not create raster output: %1" ).arg( flowDirectionsOutputFile ) );
260 if ( !flowDirectionsDestProvider->isValid() )
261 throw QgsProcessingException( QObject::tr( "Could not create raster output %1: %2" ).arg( flowDirectionsOutputFile, flowDirectionsDestProvider->error().message( QgsErrorMessage::Text ) ) );
262
263 flowDirectionsDestProvider->setNoDataValue( 1, 255 );
264 flowDirectionsDestProvider->setEditable( true );
265 }
266
267 std::unique_ptr<QgsRasterFileWriter> watershedBasinsWriter;
268 std::unique_ptr<QgsRasterDataProvider> watershedBasinsDestProvider;
269
270 if ( !watershedBasinsOutputFile.isEmpty() )
271 {
272 const QString outputFormat = parameterAsOutputRasterFormat( parameters, u"OUTPUT_WATERSHED_BASINS"_s, context );
273
274 watershedBasinsWriter = std::make_unique<QgsRasterFileWriter>( watershedBasinsOutputFile );
275 watershedBasinsWriter->setOutputProviderKey( u"gdal"_s );
276 watershedBasinsWriter->setOutputFormat( outputFormat );
277
278 watershedBasinsDestProvider.reset( watershedBasinsWriter->createOneBandRaster( Qgis::DataType::Int32, mLayerWidth, mLayerHeight, mExtent, mCrs ) );
279
280 if ( !watershedBasinsDestProvider )
281 throw QgsProcessingException( QObject::tr( "Could not create raster output: %1" ).arg( watershedBasinsOutputFile ) );
282 if ( !watershedBasinsDestProvider->isValid() )
283 throw QgsProcessingException( QObject::tr( "Could not create raster output %1: %2" ).arg( watershedBasinsOutputFile, watershedBasinsDestProvider->error().message( QgsErrorMessage::Text ) ) );
284
285 watershedBasinsDestProvider->setNoDataValue( 1, -1 );
286 watershedBasinsDestProvider->setEditable( true );
287 }
288
289 std::unique_ptr< QgsRasterBlock > sourceDemData( mInterface->block( mBand, mExtent, mLayerWidth, mLayerHeight ) );
290 if ( !sourceDemData )
291 {
292 throw QgsProcessingException( QObject::tr( "Could not read DEM raster" ) );
293 }
294
295 auto filledDemData = std::make_unique<QgsRasterBlock>( mDataType, mLayerWidth, mLayerHeight );
296 filledDemData->setNoDataValue( mNoData );
297 filledDemData->setIsNoData();
298
299 auto watershedData = std::make_unique<QgsRasterBlock>( Qgis::DataType::Int32, mLayerWidth, mLayerHeight );
300 watershedData->setNoDataValue( -1 );
301 watershedData->setIsNoData();
302
303 auto outputFlowData = std::make_unique<QgsRasterBlock>( Qgis::DataType::Byte, mLayerWidth, mLayerHeight );
304 outputFlowData->setNoDataValue( 255 );
305 outputFlowData->setIsNoData();
306
307 auto seedData = std::make_unique<QgsRasterBlock>( Qgis::DataType::Byte, mLayerWidth, mLayerHeight );
308 seedData->fill( 0 );
309
310 double minSlope = parameterAsDouble( parameters, u"MIN_SLOPE"_s, context );
311 double mindiff[8];
312 bool preserve = false;
313 if ( minSlope > 0.0 )
314 {
315 minSlope = tan( minSlope * M_PI / 180.0 );
316 for ( int i = 0; i < 8; i++ )
317 mindiff[i] = minSlope * mDirectionalLengths[i];
318 preserve = true;
319 }
320
321 // fill priority queue with boundary, i.e. seed cells
322 CFillSinks_WL_Node tempNode;
323 PriorityQ theQueue;
324
325 long long id = 0;
326 double value = 0;
327 bool isNoData = false;
328 feedback->setProgressText( QObject::tr( "Seed boundary cells" ) );
329
330 std::size_t processed = 0;
331 const std::size_t totalCells = static_cast< std::size_t >( mLayerWidth ) * mLayerHeight;
332
333 for ( int row = 0; row < mLayerHeight; row++ )
334 {
335 if ( feedback->isCanceled() )
336 break;
337
338 for ( int col = 0; col < mLayerWidth; col++ )
339 {
340 value = sourceDemData->valueAndNoData( row, col, isNoData );
341 if ( !isNoData )
342 {
343 for ( Direction direction : { North, NorthEast, East, SouthEast, South, SouthWest, West, NorthWest } )
344 {
345 int iCol = col + COL_DIRECTION_OFFSETS[direction];
346 int iRow = row + ROW_DIRECTION_OFFSETS[direction];
347 ;
348 if ( !isInGrid( iRow, iCol ) || sourceDemData->isNoData( iRow, iCol ) )
349 {
350 const double z = value;
351 filledDemData->setValue( row, col, z );
352 seedData->setValue( row, col, 1.0 );
353 watershedData->setValue( row, col, static_cast< double >( id ) );
354 id += 1;
355
356 tempNode.row = row;
357 tempNode.col = col;
358 tempNode.spill = z;
359 theQueue.push( tempNode );
360 processed += 1;
361 break;
362 }
363 }
364 }
365 feedback->setProgress( static_cast< double >( processed ) / static_cast< double >( totalCells ) * 100 );
366 }
367 }
368
369 if ( feedback->isCanceled() )
370 return {};
371
372 // work through least cost path
373 feedback->setProgressText( QObject::tr( "Filling using least cost paths" ) );
374
375 while ( !theQueue.empty() )
376 {
377 PriorityQ::value_type tempNode = theQueue.top();
378
379 const int row = tempNode.row;
380 const int col = tempNode.col;
381 const double z = tempNode.spill;
382 theQueue.pop();
383
384 const long long id = static_cast< long long >( watershedData->value( row, col ) );
385
386 for ( Direction direction : { North, NorthEast, East, SouthEast, South, SouthWest, West, NorthWest } )
387 {
388 const int iCol = col + COL_DIRECTION_OFFSETS[direction];
389 const int iRow = row + ROW_DIRECTION_OFFSETS[direction];
390 isNoData = false;
391 const bool iInGrid = isInGrid( iRow, iCol );
392 double iz = iInGrid ? sourceDemData->valueAndNoData( iRow, iCol, isNoData ) : 0;
393 if ( iInGrid && !isNoData )
394 {
395 if ( filledDemData->isNoData( iRow, iCol ) )
396 {
397 if ( preserve )
398 {
399 iz = std::max( iz, z + mindiff[static_cast< int >( direction )] );
400 }
401 else if ( iz <= z )
402 {
403 iz = z;
404 outputFlowData->setValue( iRow, iCol, INVERSE_DIRECTION[static_cast< int >( direction )] );
405 }
406
407 tempNode.row = iRow;
408 tempNode.col = iCol;
409 tempNode.spill = iz;
410 theQueue.push( tempNode );
411
412 filledDemData->setValue( iRow, iCol, iz );
413 watershedData->setValue( iRow, iCol, id );
414 processed += 1;
415 }
416 else if ( seedData->value( iRow, iCol ) == 1 )
417 {
418 watershedData->setValue( iRow, iCol, id );
419 }
420 }
421 }
422
423 if ( outputFlowData->isNoData( row, col ) )
424 outputFlowData->setValue( row, col, getDir( row, col, z, filledDemData.get() ) );
425
426 feedback->setProgress( static_cast< double >( processed ) / static_cast< double >( totalCells ) * 100 );
427 if ( feedback->isCanceled() )
428 break;
429 }
430
431 if ( feedback->isCanceled() )
432 return {};
433
434 QVariantMap outputs;
435
436 if ( filledDemDestProvider )
437 {
438 if ( !filledDemDestProvider->writeBlock( filledDemData.get(), 1, 0, 0 ) )
439 {
440 throw QgsProcessingException( QObject::tr( "Could not write raster block: %1" ).arg( filledDemDestProvider->error().summary() ) );
441 }
442 filledDemDestProvider->setEditable( false );
443 outputs.insert( u"OUTPUT_FILLED_DEM"_s, filledDemOutputFile );
444 }
445 if ( flowDirectionsDestProvider )
446 {
447 if ( !flowDirectionsDestProvider->writeBlock( outputFlowData.get(), 1, 0, 0 ) )
448 {
449 throw QgsProcessingException( QObject::tr( "Could not write raster block: %1" ).arg( flowDirectionsDestProvider->error().summary() ) );
450 }
451 flowDirectionsDestProvider->setEditable( false );
452 outputs.insert( u"OUTPUT_FLOW_DIRECTIONS"_s, flowDirectionsOutputFile );
453 }
454 if ( watershedBasinsDestProvider )
455 {
456 if ( !watershedBasinsDestProvider->writeBlock( watershedData.get(), 1, 0, 0 ) )
457 {
458 throw QgsProcessingException( QObject::tr( "Could not write raster block: %1" ).arg( watershedBasinsDestProvider->error().summary() ) );
459 }
460 watershedBasinsDestProvider->setEditable( false );
461 outputs.insert( u"OUTPUT_WATERSHED_BASINS"_s, watershedBasinsOutputFile );
462 }
463
464 return outputs;
465}
466
467
@ Byte
Eight bit unsigned integer (quint8).
Definition qgis.h:395
@ Int32
Thirty two bit signed integer (qint32).
Definition qgis.h:400
@ Advanced
Parameter is an advanced parameter which should be hidden from users by default.
Definition qgis.h:3984
@ Double
Double/float values.
Definition qgis.h:4025
Encapsulates an academic reference and formats it according to style guidelines.
static QgsAcademicReference createJournalArticle(const QStringList &authors, int year, const QString &title, const QString &journal, const QString &volume=QString(), const QString &issue=QString(), const QString &pages=QString())
Creates a journal article reference.
@ Text
Plain text format.
Definition qgserror.h:40
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition qgsfeedback.h:56
void setProgress(double progress)
Sets the current progress for the feedback object.
Definition qgsfeedback.h:65
virtual Q_INVOKABLE QgsRectangle extent() const
Returns the extent of the layer.
QgsCoordinateReferenceSystem crs
Definition qgsmaplayer.h:90
Contains information about the context in which a processing algorithm is executed.
Custom exception class for processing related exceptions.
Base class for providing feedback from a processing algorithm.
virtual void setProgressText(const QString &text)
Sets a progress report text string.
A raster band parameter for Processing algorithms.
A raster layer parameter for processing algorithms.
Raster data container.
double valueAndNoData(int row, int column, bool &isNoData) const
Reads a single value from the pixel at row and column, if type of block is numeric.
QgsRasterDataProvider * clone() const override=0
Clone itself, create deep copy.
virtual bool sourceHasNoDataValue(int bandNo) const
Returns true if source band has no data value.
virtual double sourceNoDataValue(int bandNo) const
Value representing no data value.
Qgis::DataType dataType(int bandNo) const override=0
Returns data type for the band specified by number.
Represents a raster layer.
int height() const
Returns the height of the (unclipped) raster.
int bandCount() const
Returns the number of bands in this layer.
double rasterUnitsPerPixelX() const
Returns the number of raster units per each raster pixel in X axis.
QgsRasterDataProvider * dataProvider() override
Returns the source data provider.
double rasterUnitsPerPixelY() const
Returns the number of raster units per each raster pixel in Y axis.
int width() const
Returns the width of the (unclipped) raster.