QGIS API Documentation 4.3.0-Master (eb72899e5dc)
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 mNoData = layer->dataProvider()->sourceNoDataValue( mBand );
151 mDirectionalLengths = { mRasterUnitsPerPixelY, mRasterDiagonal, mRasterUnitsPerPixelX, mRasterDiagonal, mRasterUnitsPerPixelY, mRasterDiagonal, mRasterUnitsPerPixelX, mRasterDiagonal };
152 return true;
153}
154
155static constexpr std::array< int, 8 > COL_DIRECTION_OFFSETS { 0, 1, 1, 1, 0, -1, -1, -1 };
156static constexpr std::array< int, 8 > ROW_DIRECTION_OFFSETS { -1, -1, 0, 1, 1, 1, 0, -1 };
157
158bool QgsFillSinksWangLiuAlgorithm::isInGrid( int row, int col ) const
159{
160 return col >= 0 && col < mLayerWidth && row >= 0 && row < mLayerHeight;
161}
162
163QgsFillSinksWangLiuAlgorithm::Direction QgsFillSinksWangLiuAlgorithm::getDir( int row, int col, double z, const QgsRasterBlock *filled ) const
164{
165 Direction steepestDirection = Invalid;
166 double maxGradient = 0;
167 bool isNoData = false;
168
169 for ( Direction direction : { North, NorthEast, East, SouthEast, South, SouthWest, West, NorthWest } )
170 {
171 const int neighborCol = col + COL_DIRECTION_OFFSETS[direction];
172 const int neighborRow = row + ROW_DIRECTION_OFFSETS[direction];
173
174 if ( isInGrid( neighborRow, neighborCol ) )
175 {
176 const double neighborZ = filled->valueAndNoData( neighborRow, neighborCol, isNoData );
177 if ( !isNoData && neighborZ < z )
178 {
179 const double gradient = ( z - neighborZ ) / mDirectionalLengths[direction];
180 if ( gradient >= maxGradient )
181 {
182 maxGradient = gradient;
183 steepestDirection = direction;
184 }
185 }
186 }
187 }
188
189 return steepestDirection;
190}
191
192struct CFillSinks_WL_Node
193{
194 int row = 0;
195 int col = 0;
196 double spill = 0;
197};
198
199class CompareGreater
200{
201 public:
202 bool operator()( CFillSinks_WL_Node n1, CFillSinks_WL_Node n2 ) const { return n1.spill > n2.spill; }
203};
204
205typedef std::vector< CFillSinks_WL_Node > nodeVector;
206typedef std::priority_queue< CFillSinks_WL_Node, nodeVector, CompareGreater > PriorityQ;
207
208QVariantMap QgsFillSinksWangLiuAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
209{
210 QGS_MARK_ALGORITHM_SOURCE
211
212 const QString createOptions = parameterAsString( parameters, u"CREATION_OPTIONS"_s, context ).trimmed();
213
214 const QString filledDemOutputFile = parameterAsOutputLayer( parameters, u"OUTPUT_FILLED_DEM"_s, context );
215 const QString flowDirectionsOutputFile = parameterAsOutputLayer( parameters, u"OUTPUT_FLOW_DIRECTIONS"_s, context );
216 const QString watershedBasinsOutputFile = parameterAsOutputLayer( parameters, u"OUTPUT_WATERSHED_BASINS"_s, context );
217
218 std::unique_ptr<QgsRasterFileWriter> filledDemWriter;
219 std::unique_ptr<QgsRasterDataProvider> filledDemDestProvider;
220
221 if ( !filledDemOutputFile.isEmpty() )
222 {
223 const QString outputFormat = parameterAsOutputRasterFormat( parameters, u"OUTPUT_FILLED_DEM"_s, context );
224
225 filledDemWriter = std::make_unique<QgsRasterFileWriter>( filledDemOutputFile );
226 filledDemWriter->setOutputProviderKey( u"gdal"_s );
227 if ( !createOptions.isEmpty() )
228 {
229 filledDemWriter->setCreationOptions( createOptions.split( '|' ) );
230 }
231 filledDemWriter->setOutputFormat( outputFormat );
232
233 filledDemDestProvider.reset( filledDemWriter->createOneBandRaster( Qgis::DataType::Float32, mLayerWidth, mLayerHeight, mExtent, mCrs ) );
234
235 if ( !filledDemDestProvider )
236 throw QgsProcessingException( QObject::tr( "Could not create raster output: %1" ).arg( filledDemOutputFile ) );
237 if ( !filledDemDestProvider->isValid() )
238 throw QgsProcessingException( QObject::tr( "Could not create raster output %1: %2" ).arg( filledDemOutputFile, filledDemDestProvider->error().message( QgsErrorMessage::Text ) ) );
239
240 filledDemDestProvider->setNoDataValue( 1, mNoData );
241 filledDemDestProvider->setEditable( true );
242 }
243
244 std::unique_ptr<QgsRasterFileWriter> flowDirectionsWriter;
245 std::unique_ptr<QgsRasterDataProvider> flowDirectionsDestProvider;
246
247 if ( !flowDirectionsOutputFile.isEmpty() )
248 {
249 const QString outputFormat = parameterAsOutputRasterFormat( parameters, u"OUTPUT_FLOW_DIRECTIONS"_s, context );
250
251 flowDirectionsWriter = std::make_unique<QgsRasterFileWriter>( flowDirectionsOutputFile );
252 flowDirectionsWriter->setOutputProviderKey( u"gdal"_s );
253 flowDirectionsWriter->setOutputFormat( outputFormat );
254
255 flowDirectionsDestProvider.reset( flowDirectionsWriter->createOneBandRaster( Qgis::DataType::Byte, mLayerWidth, mLayerHeight, mExtent, mCrs ) );
256
257 if ( !flowDirectionsDestProvider )
258 throw QgsProcessingException( QObject::tr( "Could not create raster output: %1" ).arg( flowDirectionsOutputFile ) );
259 if ( !flowDirectionsDestProvider->isValid() )
260 throw QgsProcessingException( QObject::tr( "Could not create raster output %1: %2" ).arg( flowDirectionsOutputFile, flowDirectionsDestProvider->error().message( QgsErrorMessage::Text ) ) );
261
262 flowDirectionsDestProvider->setNoDataValue( 1, 255 );
263 flowDirectionsDestProvider->setEditable( true );
264 }
265
266 std::unique_ptr<QgsRasterFileWriter> watershedBasinsWriter;
267 std::unique_ptr<QgsRasterDataProvider> watershedBasinsDestProvider;
268
269 if ( !watershedBasinsOutputFile.isEmpty() )
270 {
271 const QString outputFormat = parameterAsOutputRasterFormat( parameters, u"OUTPUT_WATERSHED_BASINS"_s, context );
272
273 watershedBasinsWriter = std::make_unique<QgsRasterFileWriter>( watershedBasinsOutputFile );
274 watershedBasinsWriter->setOutputProviderKey( u"gdal"_s );
275 watershedBasinsWriter->setOutputFormat( outputFormat );
276
277 watershedBasinsDestProvider.reset( watershedBasinsWriter->createOneBandRaster( Qgis::DataType::Int32, mLayerWidth, mLayerHeight, mExtent, mCrs ) );
278
279 if ( !watershedBasinsDestProvider )
280 throw QgsProcessingException( QObject::tr( "Could not create raster output: %1" ).arg( watershedBasinsOutputFile ) );
281 if ( !watershedBasinsDestProvider->isValid() )
282 throw QgsProcessingException( QObject::tr( "Could not create raster output %1: %2" ).arg( watershedBasinsOutputFile, watershedBasinsDestProvider->error().message( QgsErrorMessage::Text ) ) );
283
284 watershedBasinsDestProvider->setNoDataValue( 1, -1 );
285 watershedBasinsDestProvider->setEditable( true );
286 }
287
288 std::unique_ptr< QgsRasterBlock > sourceDemData( mInterface->block( mBand, mExtent, mLayerWidth, mLayerHeight ) );
289 if ( !sourceDemData )
290 {
291 throw QgsProcessingException( QObject::tr( "Could not read DEM raster" ) );
292 }
293
294 auto filledDemData = std::make_unique<QgsRasterBlock>( Qgis::DataType::Float32, mLayerWidth, mLayerHeight );
295 filledDemData->setNoDataValue( mNoData );
296 filledDemData->setIsNoData();
297
298 auto watershedData = std::make_unique<QgsRasterBlock>( Qgis::DataType::Int32, mLayerWidth, mLayerHeight );
299 watershedData->setNoDataValue( -1 );
300 watershedData->setIsNoData();
301
302 auto outputFlowData = std::make_unique<QgsRasterBlock>( Qgis::DataType::Byte, mLayerWidth, mLayerHeight );
303 outputFlowData->setNoDataValue( 255 );
304 outputFlowData->setIsNoData();
305
306 auto seedData = std::make_unique<QgsRasterBlock>( Qgis::DataType::Byte, mLayerWidth, mLayerHeight );
307 seedData->fill( 0 );
308
309 double minSlope = parameterAsDouble( parameters, u"MIN_SLOPE"_s, context );
310 double mindiff[8];
311 bool preserve = false;
312 if ( minSlope > 0.0 )
313 {
314 minSlope = tan( minSlope * M_PI / 180.0 );
315 for ( int i = 0; i < 8; i++ )
316 mindiff[i] = minSlope * mDirectionalLengths[i];
317 preserve = true;
318 }
319
320 // fill priority queue with boundary, i.e. seed cells
321 CFillSinks_WL_Node tempNode;
322 PriorityQ theQueue;
323
324 long long id = 0;
325 double value = 0;
326 bool isNoData = false;
327 feedback->setProgressText( QObject::tr( "Seed boundary cells" ) );
328
329 std::size_t processed = 0;
330 const std::size_t totalCells = static_cast< std::size_t >( mLayerWidth ) * mLayerHeight;
331
332 for ( int row = 0; row < mLayerHeight; row++ )
333 {
334 if ( feedback->isCanceled() )
335 break;
336
337 for ( int col = 0; col < mLayerWidth; col++ )
338 {
339 value = sourceDemData->valueAndNoData( row, col, isNoData );
340 if ( !isNoData )
341 {
342 for ( Direction direction : { North, NorthEast, East, SouthEast, South, SouthWest, West, NorthWest } )
343 {
344 int iCol = col + COL_DIRECTION_OFFSETS[direction];
345 int iRow = row + ROW_DIRECTION_OFFSETS[direction];
346 ;
347 if ( !isInGrid( iRow, iCol ) || sourceDemData->isNoData( iRow, iCol ) )
348 {
349 const double z = value;
350 filledDemData->setValue( row, col, z );
351 seedData->setValue( row, col, 1.0 );
352 watershedData->setValue( row, col, static_cast< double >( id ) );
353 id += 1;
354
355 tempNode.row = row;
356 tempNode.col = col;
357 tempNode.spill = z;
358 theQueue.push( tempNode );
359 processed += 1;
360 break;
361 }
362 }
363 }
364 feedback->setProgress( static_cast< double >( processed ) / static_cast< double >( totalCells ) * 100 );
365 }
366 }
367
368 if ( feedback->isCanceled() )
369 return {};
370
371 // work through least cost path
372 feedback->setProgressText( QObject::tr( "Filling using least cost paths" ) );
373
374 while ( !theQueue.empty() )
375 {
376 PriorityQ::value_type tempNode = theQueue.top();
377
378 const int row = tempNode.row;
379 const int col = tempNode.col;
380 const double z = tempNode.spill;
381 theQueue.pop();
382
383 const long long id = static_cast< long long >( watershedData->value( row, col ) );
384
385 for ( Direction direction : { North, NorthEast, East, SouthEast, South, SouthWest, West, NorthWest } )
386 {
387 const int iCol = col + COL_DIRECTION_OFFSETS[direction];
388 const int iRow = row + ROW_DIRECTION_OFFSETS[direction];
389 isNoData = false;
390 const bool iInGrid = isInGrid( iRow, iCol );
391 double iz = iInGrid ? sourceDemData->valueAndNoData( iRow, iCol, isNoData ) : 0;
392 if ( iInGrid && !isNoData )
393 {
394 if ( filledDemData->isNoData( iRow, iCol ) )
395 {
396 if ( preserve )
397 {
398 iz = std::max( iz, z + mindiff[static_cast< int >( direction )] );
399 }
400 else if ( iz <= z )
401 {
402 iz = z;
403 outputFlowData->setValue( iRow, iCol, INVERSE_DIRECTION[static_cast< int >( direction )] );
404 }
405
406 tempNode.row = iRow;
407 tempNode.col = iCol;
408 tempNode.spill = iz;
409 theQueue.push( tempNode );
410
411 filledDemData->setValue( iRow, iCol, iz );
412 watershedData->setValue( iRow, iCol, id );
413 processed += 1;
414 }
415 else if ( seedData->value( iRow, iCol ) == 1 )
416 {
417 watershedData->setValue( iRow, iCol, id );
418 }
419 }
420 }
421
422 if ( outputFlowData->isNoData( row, col ) )
423 outputFlowData->setValue( row, col, getDir( row, col, z, filledDemData.get() ) );
424
425 feedback->setProgress( static_cast< double >( processed ) / static_cast< double >( totalCells ) * 100 );
426 if ( feedback->isCanceled() )
427 break;
428 }
429
430 if ( feedback->isCanceled() )
431 return {};
432
433 QVariantMap outputs;
434
435 if ( filledDemDestProvider )
436 {
437 if ( !filledDemDestProvider->writeBlock( filledDemData.get(), 1, 0, 0 ) )
438 {
439 throw QgsProcessingException( QObject::tr( "Could not write raster block: %1" ).arg( filledDemDestProvider->error().summary() ) );
440 }
441 filledDemDestProvider->setEditable( false );
442 outputs.insert( u"OUTPUT_FILLED_DEM"_s, filledDemOutputFile );
443 }
444 if ( flowDirectionsDestProvider )
445 {
446 if ( !flowDirectionsDestProvider->writeBlock( outputFlowData.get(), 1, 0, 0 ) )
447 {
448 throw QgsProcessingException( QObject::tr( "Could not write raster block: %1" ).arg( flowDirectionsDestProvider->error().summary() ) );
449 }
450 flowDirectionsDestProvider->setEditable( false );
451 outputs.insert( u"OUTPUT_FLOW_DIRECTIONS"_s, flowDirectionsOutputFile );
452 }
453 if ( watershedBasinsDestProvider )
454 {
455 if ( !watershedBasinsDestProvider->writeBlock( watershedData.get(), 1, 0, 0 ) )
456 {
457 throw QgsProcessingException( QObject::tr( "Could not write raster block: %1" ).arg( watershedBasinsDestProvider->error().summary() ) );
458 }
459 watershedBasinsDestProvider->setEditable( false );
460 outputs.insert( u"OUTPUT_WATERSHED_BASINS"_s, watershedBasinsOutputFile );
461 }
462
463 return outputs;
464}
465
466
@ Float32
Thirty two bit floating point (float).
Definition qgis.h:421
@ Byte
Eight bit unsigned integer (quint8).
Definition qgis.h:415
@ Int32
Thirty two bit signed integer (qint32).
Definition qgis.h:420
@ Advanced
Parameter is an advanced parameter which should be hidden from users by default.
Definition qgis.h:4026
@ Double
Double/float values.
Definition qgis.h:4067
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.
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.