QGIS API Documentation 3.43.0-Master (b60ef06885e)
qgsninecellfilter.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsninecellfilter.h - description
3 -------------------
4 begin : August 6th, 2009
5 copyright : (C) 2009 by Marco Hugentobler
6 email : marco dot hugentobler at karto dot baug dot ethz dot ch
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
18#include "qgsgdalutils.h"
19#include "qgsninecellfilter.h"
20#include "qgslogger.h"
21#include "cpl_string.h"
22#include "qgsfeedback.h"
23#include "qgsogrutils.h"
24#include "qgsmessagelog.h"
25#include "qgsconfig.h"
26
27#ifdef HAVE_OPENCL
28#include "qgsopenclutils.h"
29#endif
30
31#include <QFile>
32#include <QDebug>
33#include <QFileInfo>
34#include <iterator>
35
36QgsNineCellFilter::QgsNineCellFilter( const QString &inputFile, const QString &outputFile, const QString &outputFormat )
37 : mInputFile( inputFile )
38 , mOutputFile( outputFile )
39 , mOutputFormat( outputFormat )
40{
41}
42
43// TODO: return an anum instead of an int
45{
46#ifdef HAVE_OPENCL
47 if ( QgsOpenClUtils::enabled() && QgsOpenClUtils::available() && !openClProgramBaseName().isEmpty() )
48 {
49 // Load the program sources
50 const QString source( QgsOpenClUtils::sourceFromBaseName( openClProgramBaseName() ) );
51 if ( !source.isEmpty() )
52 {
53 try
54 {
55 QgsDebugMsgLevel( QStringLiteral( "Running OpenCL program: %1" ).arg( openClProgramBaseName() ), 2 );
56 return processRasterGPU( source, feedback );
57 }
58 catch ( cl::Error &e )
59 {
60 const QString err = QObject::tr( "Error running OpenCL program: %1 - %2" ).arg( e.what(), QgsOpenClUtils::errorText( e.err() ) );
62 throw QgsProcessingException( err );
63 }
64 }
65 else
66 {
67 const QString err = QObject::tr( "Error loading OpenCL program sources" );
69 throw QgsProcessingException( err );
70 }
71 }
72 else
73 {
74 return processRasterCPU( feedback );
75 }
76#ifndef _MSC_VER
77 return 1;
78#endif
79#else
80 return processRasterCPU( feedback );
81#endif
82}
83
84gdal::dataset_unique_ptr QgsNineCellFilter::openInputFile( int &nCellsX, int &nCellsY )
85{
86 gdal::dataset_unique_ptr inputDataset( GDALOpen( mInputFile.toUtf8().constData(), GA_ReadOnly ) );
87 if ( inputDataset )
88 {
89 nCellsX = GDALGetRasterXSize( inputDataset.get() );
90 nCellsY = GDALGetRasterYSize( inputDataset.get() );
91
92 //we need at least one band
93 if ( GDALGetRasterCount( inputDataset.get() ) < 1 )
94 {
95 return nullptr;
96 }
97 }
98 return inputDataset;
99}
100
101GDALDriverH QgsNineCellFilter::openOutputDriver()
102{
103 //open driver
104 GDALDriverH outputDriver = GDALGetDriverByName( mOutputFormat.toLocal8Bit().data() );
105
106 if ( !outputDriver )
107 {
108 return outputDriver; //return nullptr, driver does not exist
109 }
110
111 if ( !QgsGdalUtils::supportsRasterCreate( outputDriver ) )
112 {
113 return nullptr; //driver exist, but it does not support the create operation
114 }
115
116 return outputDriver;
117}
118
119
120gdal::dataset_unique_ptr QgsNineCellFilter::openOutputFile( GDALDatasetH inputDataset, GDALDriverH outputDriver )
121{
122 if ( !inputDataset )
123 {
124 return nullptr;
125 }
126
127 const int xSize = GDALGetRasterXSize( inputDataset );
128 const int ySize = GDALGetRasterYSize( inputDataset );
129
130 //open output file
132 gdal::dataset_unique_ptr outputDataset( GDALCreate( outputDriver, mOutputFile.toUtf8().constData(), xSize, ySize, 1, GDT_Float32, papszOptions ) );
133 CSLDestroy( papszOptions );
134 if ( !outputDataset )
135 {
136 return outputDataset;
137 }
138
139 //get geotransform from inputDataset
140 double geotransform[6];
141 if ( GDALGetGeoTransform( inputDataset, geotransform ) != CE_None )
142 {
143 return nullptr;
144 }
145 GDALSetGeoTransform( outputDataset.get(), geotransform );
146
147 //make sure mCellSizeX and mCellSizeY are always > 0
148 mCellSizeX = geotransform[1];
149 if ( mCellSizeX < 0 )
150 {
152 }
153 mCellSizeY = geotransform[5];
154 if ( mCellSizeY < 0 )
155 {
157 }
158
159 const char *projection = GDALGetProjectionRef( inputDataset );
160 GDALSetProjection( outputDataset.get(), projection );
161
162 return outputDataset;
163}
164
165#ifdef HAVE_OPENCL
166
167// TODO: return an anum instead of an int
168int QgsNineCellFilter::processRasterGPU( const QString &source, QgsFeedback *feedback )
169{
170 GDALAllRegister();
171
172 //open input file
173 int xSize, ySize;
174 const gdal::dataset_unique_ptr inputDataset( openInputFile( xSize, ySize ) );
175 if ( !inputDataset )
176 {
177 return 1; //opening of input file failed
178 }
179
180 //output driver
181 GDALDriverH outputDriver = openOutputDriver();
182 if ( !outputDriver )
183 {
184 return 2;
185 }
186
187 gdal::dataset_unique_ptr outputDataset( openOutputFile( inputDataset.get(), outputDriver ) );
188 if ( !outputDataset )
189 {
190 return 3; //create operation on output file failed
191 }
192
193 //open first raster band for reading (operation is only for single band raster)
194 GDALRasterBandH rasterBand = GDALGetRasterBand( inputDataset.get(), 1 );
195 if ( !rasterBand )
196 {
197 return 4;
198 }
199 mInputNodataValue = GDALGetRasterNoDataValue( rasterBand, nullptr );
200
201 GDALRasterBandH outputRasterBand = GDALGetRasterBand( outputDataset.get(), 1 );
202 if ( !outputRasterBand )
203 {
204 return 5;
205 }
206 // set nodata value
207 GDALSetRasterNoDataValue( outputRasterBand, mOutputNodataValue );
208
209 if ( ySize < 3 ) //we require at least three rows (should be true for most datasets)
210 {
211 return 6;
212 }
213
214 // Prepare context and queue
215 const cl::Context ctx = QgsOpenClUtils::context();
216 cl::CommandQueue queue = QgsOpenClUtils::commandQueue();
217
218 //keep only three scanlines in memory at a time, make room for initial and final nodata
219 QgsOpenClUtils::CPLAllocator<float> scanLine( xSize + 2 );
220 QgsOpenClUtils::CPLAllocator<float> resultLine( xSize );
221
222 // Cast to float (because double just crashes on some GPUs)
223 std::vector<float> rasterParams;
224
225 rasterParams.push_back( mInputNodataValue ); // 0
226 rasterParams.push_back( mOutputNodataValue ); // 1
227 rasterParams.push_back( mZFactor ); // 2
228 rasterParams.push_back( mCellSizeX ); // 3
229 rasterParams.push_back( mCellSizeY ); // 4
230
231 // Allow subclasses to add extra params needed for computation:
232 // used to pass additional args to opencl program
233 addExtraRasterParams( rasterParams );
234
235 const std::size_t bufferSize( sizeof( float ) * ( xSize + 2 ) );
236 const std::size_t inputSize( sizeof( float ) * ( xSize ) );
237
238 cl::Buffer rasterParamsBuffer( queue, rasterParams.begin(), rasterParams.end(), true, false, nullptr );
239 cl::Buffer scanLine1Buffer( ctx, CL_MEM_READ_ONLY, bufferSize, nullptr, nullptr );
240 cl::Buffer scanLine2Buffer( ctx, CL_MEM_READ_ONLY, bufferSize, nullptr, nullptr );
241 cl::Buffer scanLine3Buffer( ctx, CL_MEM_READ_ONLY, bufferSize, nullptr, nullptr );
242 cl::Buffer *scanLineBuffer[3] = { &scanLine1Buffer, &scanLine2Buffer, &scanLine3Buffer };
243 cl::Buffer resultLineBuffer( ctx, CL_MEM_WRITE_ONLY, inputSize, nullptr, nullptr );
244
245 // Create a program from the kernel source
246 const cl::Program program( QgsOpenClUtils::buildProgram( source, QgsOpenClUtils::ExceptionBehavior::Throw ) );
247
248 // Create the OpenCL kernel
249 auto kernel = cl::KernelFunctor<
250 cl::Buffer &,
251 cl::Buffer &,
252 cl::Buffer &,
253 cl::Buffer &,
254 cl::Buffer &>( program, "processNineCellWindow" );
255
256 // Rotate buffer index
257 std::vector<int> rowIndex = { 0, 1, 2 };
258
259 // values outside the layer extent (if the 3x3 window is on the border) are sent to the processing method as (input) nodata values
260 for ( int i = 0; i < ySize; ++i )
261 {
262 if ( feedback && feedback->isCanceled() )
263 {
264 break;
265 }
266
267 if ( feedback )
268 {
269 feedback->setProgress( 100.0 * static_cast<double>( i ) / ySize );
270 }
271
272 if ( i == 0 )
273 {
274 // Fill scanline 1 with (input) nodata for the values above the first row and
275 // feed scanline2 with the first actual data row
276 for ( int a = 0; a < xSize + 2; ++a )
277 {
278 scanLine[a] = mInputNodataValue;
279 }
280 queue.enqueueWriteBuffer( scanLine1Buffer, CL_TRUE, 0, bufferSize, scanLine.get() );
281
282 // Read scanline2: first real raster row
283 if ( GDALRasterIO( rasterBand, GF_Read, 0, i, xSize, 1, &scanLine[1], xSize, 1, GDT_Float32, 0, 0 ) != CE_None )
284 {
285 QgsDebugError( QStringLiteral( "Raster IO Error" ) );
286 }
287 queue.enqueueWriteBuffer( scanLine2Buffer, CL_TRUE, 0, bufferSize, scanLine.get() );
288
289 // Read scanline3: second real raster row
290 if ( GDALRasterIO( rasterBand, GF_Read, 0, i + 1, xSize, 1, &scanLine[1], xSize, 1, GDT_Float32, 0, 0 ) != CE_None )
291 {
292 QgsDebugError( QStringLiteral( "Raster IO Error" ) );
293 }
294 queue.enqueueWriteBuffer( scanLine3Buffer, CL_TRUE, 0, bufferSize, scanLine.get() );
295 }
296 else
297 {
298 // Normally fetch only scanLine3 and move forward one row
299 // Read scanline 3, fill the last row with nodata values if it's the last iteration
300 if ( i == ySize - 1 ) //fill the row below the bottom with nodata values
301 {
302 for ( int a = 0; a < xSize + 2; ++a )
303 {
304 scanLine[a] = mInputNodataValue;
305 }
306 queue.enqueueWriteBuffer( *scanLineBuffer[rowIndex[2]], CL_TRUE, 0, bufferSize, scanLine.get() ); // row 0
307 }
308 else // Read line i + 1 and put it into scanline 3
309 // Overwrite from input, skip first and last
310 {
311 if ( GDALRasterIO( rasterBand, GF_Read, 0, i + 1, xSize, 1, &scanLine[1], xSize, 1, GDT_Float32, 0, 0 ) != CE_None )
312 {
313 QgsDebugError( QStringLiteral( "Raster IO Error" ) );
314 }
315 queue.enqueueWriteBuffer( *scanLineBuffer[rowIndex[2]], CL_TRUE, 0, bufferSize, scanLine.get() ); // row 0
316 }
317 }
318
319 kernel( cl::EnqueueArgs( queue, cl::NDRange( xSize ) ), *scanLineBuffer[rowIndex[0]], *scanLineBuffer[rowIndex[1]], *scanLineBuffer[rowIndex[2]], resultLineBuffer, rasterParamsBuffer );
320
321 queue.enqueueReadBuffer( resultLineBuffer, CL_TRUE, 0, inputSize, resultLine.get() );
322
323 if ( GDALRasterIO( outputRasterBand, GF_Write, 0, i, xSize, 1, resultLine.get(), xSize, 1, GDT_Float32, 0, 0 ) != CE_None )
324 {
325 QgsDebugError( QStringLiteral( "Raster IO Error" ) );
326 }
327 std::rotate( rowIndex.begin(), rowIndex.begin() + 1, rowIndex.end() );
328 }
329
330 if ( feedback && feedback->isCanceled() )
331 {
332 //delete the dataset without closing (because it is faster)
333 gdal::fast_delete_and_close( outputDataset, outputDriver, mOutputFile );
334 return 7;
335 }
336 return 0;
337}
338#endif
339
340
341// TODO: return an anum instead of an int
342int QgsNineCellFilter::processRasterCPU( QgsFeedback *feedback )
343{
344 GDALAllRegister();
345
346 //open input file
347 int xSize, ySize;
348 const gdal::dataset_unique_ptr inputDataset( openInputFile( xSize, ySize ) );
349 if ( !inputDataset )
350 {
351 return 1; //opening of input file failed
352 }
353
354 //output driver
355 GDALDriverH outputDriver = openOutputDriver();
356 if ( !outputDriver )
357 {
358 return 2;
359 }
360
361 gdal::dataset_unique_ptr outputDataset( openOutputFile( inputDataset.get(), outputDriver ) );
362 if ( !outputDataset )
363 {
364 return 3; //create operation on output file failed
365 }
366
367 //open first raster band for reading (operation is only for single band raster)
368 GDALRasterBandH rasterBand = GDALGetRasterBand( inputDataset.get(), 1 );
369 if ( !rasterBand )
370 {
371 return 4;
372 }
373 mInputNodataValue = GDALGetRasterNoDataValue( rasterBand, nullptr );
374
375 GDALRasterBandH outputRasterBand = GDALGetRasterBand( outputDataset.get(), 1 );
376 if ( !outputRasterBand )
377 {
378 return 5;
379 }
380 // set nodata value
381 GDALSetRasterNoDataValue( outputRasterBand, mOutputNodataValue );
382
383 if ( ySize < 3 ) //we require at least three rows (should be true for most datasets)
384 {
385 return 6;
386 }
387
388 //keep only three scanlines in memory at a time, make room for initial and final nodata
389 const std::size_t bufferSize( sizeof( float ) * ( xSize + 2 ) );
390 float *scanLine1 = ( float * ) CPLMalloc( bufferSize );
391 float *scanLine2 = ( float * ) CPLMalloc( bufferSize );
392 float *scanLine3 = ( float * ) CPLMalloc( bufferSize );
393
394 float *resultLine = ( float * ) CPLMalloc( sizeof( float ) * xSize );
395
396 //values outside the layer extent (if the 3x3 window is on the border) are sent to the processing method as (input) nodata values
397 for ( int yIndex = 0; yIndex < ySize; ++yIndex )
398 {
399 if ( feedback && feedback->isCanceled() )
400 {
401 break;
402 }
403
404 if ( feedback )
405 {
406 feedback->setProgress( 100.0 * static_cast<double>( yIndex ) / ySize );
407 }
408
409 if ( yIndex == 0 )
410 {
411 //fill scanline 1 with (input) nodata for the values above the first row and feed scanline2 with the first row
412 for ( int a = 0; a < xSize + 2; ++a )
413 {
414 scanLine1[a] = mInputNodataValue;
415 }
416 // Read scanline2
417 if ( GDALRasterIO( rasterBand, GF_Read, 0, 0, xSize, 1, &scanLine2[1], xSize, 1, GDT_Float32, 0, 0 ) != CE_None )
418 {
419 QgsDebugError( QStringLiteral( "Raster IO Error" ) );
420 }
421 }
422 else
423 {
424 //normally fetch only scanLine3 and release scanline 1 if we move forward one row
425 CPLFree( scanLine1 );
426 scanLine1 = scanLine2;
427 scanLine2 = scanLine3;
428 scanLine3 = ( float * ) CPLMalloc( bufferSize );
429 }
430
431 // Read scanline 3
432 if ( yIndex == ySize - 1 ) //fill the row below the bottom with nodata values
433 {
434 for ( int a = 0; a < xSize + 2; ++a )
435 {
436 scanLine3[a] = mInputNodataValue;
437 }
438 }
439 else
440 {
441 if ( GDALRasterIO( rasterBand, GF_Read, 0, yIndex + 1, xSize, 1, &scanLine3[1], xSize, 1, GDT_Float32, 0, 0 ) != CE_None )
442 {
443 QgsDebugError( QStringLiteral( "Raster IO Error" ) );
444 }
445 }
446 // Set first and last extra columns to nodata
447 scanLine1[0] = scanLine1[xSize + 1] = mInputNodataValue;
448 scanLine2[0] = scanLine2[xSize + 1] = mInputNodataValue;
449 scanLine3[0] = scanLine3[xSize + 1] = mInputNodataValue;
450
451
452 // j is the x axis index, skip 0 and last cell that have been filled with nodata
453 for ( int xIndex = 0; xIndex < xSize; ++xIndex )
454 {
455 // cells(x, y) x11, x21, x31, x12, x22, x32, x13, x23, x33
456 resultLine[xIndex] = processNineCellWindow( &scanLine1[xIndex], &scanLine1[xIndex + 1], &scanLine1[xIndex + 2], &scanLine2[xIndex], &scanLine2[xIndex + 1], &scanLine2[xIndex + 2], &scanLine3[xIndex], &scanLine3[xIndex + 1], &scanLine3[xIndex + 2] );
457 }
458
459 if ( GDALRasterIO( outputRasterBand, GF_Write, 0, yIndex, xSize, 1, resultLine, xSize, 1, GDT_Float32, 0, 0 ) != CE_None )
460 {
461 QgsDebugError( QStringLiteral( "Raster IO Error" ) );
462 }
463 }
464
465 CPLFree( resultLine );
466 CPLFree( scanLine1 );
467 CPLFree( scanLine2 );
468 CPLFree( scanLine3 );
469
470 if ( feedback && feedback->isCanceled() )
471 {
472 //delete the dataset without closing (because it is faster)
473 gdal::fast_delete_and_close( outputDataset, outputDriver, mOutputFile );
474 return 7;
475 }
476 return 0;
477}
@ Critical
Critical/error message.
Definition qgis.h:157
Base class for feedback objects to be used for cancellation of something running in a worker thread.
Definition qgsfeedback.h:44
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition qgsfeedback.h:53
void setProgress(double progress)
Sets the current progress for the feedback object.
Definition qgsfeedback.h:61
static bool supportsRasterCreate(GDALDriverH driver)
Reads whether a driver supports GDALCreate() for raster purposes.
static char ** papszFromStringList(const QStringList &list)
Helper function.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true, const char *file=__builtin_FILE(), const char *function=__builtin_FUNCTION(), int line=__builtin_LINE())
Adds a message to the log instance (and creates it if necessary).
int processRaster(QgsFeedback *feedback=nullptr)
Starts the calculation, reads from mInputFile and stores the result in mOutputFile.
QStringList mCreationOptions
virtual float processNineCellWindow(float *x11, float *x21, float *x31, float *x12, float *x22, float *x32, float *x13, float *x23, float *x33)=0
Calculates output value from nine input values.
QgsNineCellFilter(const QString &inputFile, const QString &outputFile, const QString &outputFormat)
Constructor that takes input file, output file and output format (GDAL string)
double mOutputNodataValue
The nodata value of the output layer.
double mInputNodataValue
The nodata value of the input layer.
double mZFactor
Scale factor for z-value if x-/y- units are different to z-units (111120 for degree->meters and 37040...
static Q_DECL_DEPRECATED cl::Program buildProgram(const cl::Context &context, const QString &source, ExceptionBehavior exceptionBehavior=Catch)
Build the program from source in the given context and depending on exceptionBehavior can throw or ca...
static cl::Context context()
Context factory.
static bool enabled()
Returns true if OpenCL is enabled in the user settings.
static bool available()
Checks whether a suitable OpenCL platform and device is available on this system and initialize the Q...
static QString errorText(const int errorCode)
Returns a string representation from an OpenCL errorCode.
static cl::CommandQueue commandQueue()
Create an OpenCL command queue from the default context.
static QString sourceFromBaseName(const QString &baseName)
Returns the full path to a an OpenCL source file from the baseName ('.cl' extension is automatically ...
@ Throw
Write errors in the message log and re-throw exceptions.
static QLatin1String LOGMESSAGE_TAG
OpenCL string for message logs.
Custom exception class for processing related exceptions.
void CORE_EXPORT fast_delete_and_close(dataset_unique_ptr &dataset, GDALDriverH driver, const QString &path)
Performs a fast close of an unwanted GDAL dataset handle by deleting the underlying data store.
std::unique_ptr< std::remove_pointer< GDALDatasetH >::type, GDALDatasetCloser > dataset_unique_ptr
Scoped GDAL dataset.
void * GDALDatasetH
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:41
#define QgsDebugError(str)
Definition qgslogger.h:40
Tiny smart-pointer-like wrapper around CPLMalloc and CPLFree: this is needed because OpenCL C++ API m...