QGIS API Documentation 3.43.0-Master (c4a2e9c6d2f)
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
44{
45#ifdef HAVE_OPENCL
46 if ( QgsOpenClUtils::enabled() && QgsOpenClUtils::available() && !openClProgramBaseName().isEmpty() )
47 {
48 // Load the program sources
49 const QString source( QgsOpenClUtils::sourceFromBaseName( openClProgramBaseName() ) );
50 if ( !source.isEmpty() )
51 {
52 try
53 {
54 QgsDebugMsgLevel( QStringLiteral( "Running OpenCL program: %1" ).arg( openClProgramBaseName() ), 2 );
55 return processRasterGPU( source, feedback );
56 }
57 catch ( cl::Error &e )
58 {
59 const QString err = QObject::tr( "Error running OpenCL program: %1 - %2" ).arg( e.what(), QgsOpenClUtils::errorText( e.err() ) );
61 throw QgsProcessingException( err );
62 }
63 }
64 else
65 {
66 const QString err = QObject::tr( "Error loading OpenCL program sources" );
68 throw QgsProcessingException( err );
69 }
70 }
71 else
72 {
73 return processRasterCPU( feedback );
74 }
75#ifndef _MSC_VER
77#endif
78#else
79 return processRasterCPU( feedback );
80#endif
81}
82
83gdal::dataset_unique_ptr QgsNineCellFilter::openInputFile( int &nCellsX, int &nCellsY )
84{
85 gdal::dataset_unique_ptr inputDataset( GDALOpen( mInputFile.toUtf8().constData(), GA_ReadOnly ) );
86 if ( inputDataset )
87 {
88 nCellsX = GDALGetRasterXSize( inputDataset.get() );
89 nCellsY = GDALGetRasterYSize( inputDataset.get() );
90
91 //we need at least one band
92 if ( GDALGetRasterCount( inputDataset.get() ) < 1 )
93 {
94 return nullptr;
95 }
96 }
97 return inputDataset;
98}
99
100GDALDriverH QgsNineCellFilter::openOutputDriver()
101{
102 //open driver
103 GDALDriverH outputDriver = GDALGetDriverByName( mOutputFormat.toLocal8Bit().data() );
104
105 if ( !outputDriver )
106 {
107 return outputDriver; //return nullptr, driver does not exist
108 }
109
110 if ( !QgsGdalUtils::supportsRasterCreate( outputDriver ) )
111 {
112 return nullptr; //driver exist, but it does not support the create operation
113 }
114
115 return outputDriver;
116}
117
118gdal::dataset_unique_ptr QgsNineCellFilter::openOutputFile( GDALDatasetH inputDataset, GDALDriverH outputDriver )
119{
120 if ( !inputDataset )
121 {
122 return nullptr;
123 }
124
125 const int xSize = GDALGetRasterXSize( inputDataset );
126 const int ySize = GDALGetRasterYSize( inputDataset );
127
128 //open output file
130 gdal::dataset_unique_ptr outputDataset( GDALCreate( outputDriver, mOutputFile.toUtf8().constData(), xSize, ySize, 1, GDT_Float32, papszOptions ) );
131 CSLDestroy( papszOptions );
132 if ( !outputDataset )
133 {
134 return outputDataset;
135 }
136
137 //get geotransform from inputDataset
138 double geotransform[6];
139 if ( GDALGetGeoTransform( inputDataset, geotransform ) != CE_None )
140 {
141 return nullptr;
142 }
143 GDALSetGeoTransform( outputDataset.get(), geotransform );
144
145 //make sure mCellSizeX and mCellSizeY are always > 0
146 mCellSizeX = geotransform[1];
147 if ( mCellSizeX < 0 )
148 {
150 }
151 mCellSizeY = geotransform[5];
152 if ( mCellSizeY < 0 )
153 {
155 }
156
157 const char *projection = GDALGetProjectionRef( inputDataset );
158 GDALSetProjection( outputDataset.get(), projection );
159
160 return outputDataset;
161}
162
163#ifdef HAVE_OPENCL
164
165QgsNineCellFilter::Result QgsNineCellFilter::processRasterGPU( const QString &source, QgsFeedback *feedback )
166{
167 GDALAllRegister();
168
169 //open input file
170 int xSize, ySize;
171 const gdal::dataset_unique_ptr inputDataset( openInputFile( xSize, ySize ) );
172 if ( !inputDataset )
173 {
174 return QgsNineCellFilter::Result::InputLayerError; //opening of input file failed
175 }
176
177 //output driver
178 GDALDriverH outputDriver = openOutputDriver();
179 if ( !outputDriver )
180 {
182 }
183
184 gdal::dataset_unique_ptr outputDataset( openOutputFile( inputDataset.get(), outputDriver ) );
185 if ( !outputDataset )
186 {
187 return QgsNineCellFilter::Result::CreateOutputError; //create operation on output file failed
188 }
189
190 //open first raster band for reading (operation is only for single band raster)
191 GDALRasterBandH rasterBand = GDALGetRasterBand( inputDataset.get(), 1 );
192 if ( !rasterBand )
193 {
195 }
196 mInputNodataValue = GDALGetRasterNoDataValue( rasterBand, nullptr );
197
198 GDALRasterBandH outputRasterBand = GDALGetRasterBand( outputDataset.get(), 1 );
199 if ( !outputRasterBand )
200 {
202 }
203 // set nodata value
204 GDALSetRasterNoDataValue( outputRasterBand, mOutputNodataValue );
205
206 if ( ySize < 3 ) //we require at least three rows (should be true for most datasets)
207 {
209 }
210
211 // Prepare context and queue
212 const cl::Context ctx = QgsOpenClUtils::context();
213 cl::CommandQueue queue = QgsOpenClUtils::commandQueue();
214
215 //keep only three scanlines in memory at a time, make room for initial and final nodata
216 QgsOpenClUtils::CPLAllocator<float> scanLine( xSize + 2 );
217 QgsOpenClUtils::CPLAllocator<float> resultLine( xSize );
218
219 // Cast to float (because double just crashes on some GPUs)
220 std::vector<float> rasterParams;
221
222 rasterParams.push_back( mInputNodataValue ); // 0
223 rasterParams.push_back( mOutputNodataValue ); // 1
224 rasterParams.push_back( mZFactor ); // 2
225 rasterParams.push_back( mCellSizeX ); // 3
226 rasterParams.push_back( mCellSizeY ); // 4
227
228 // Allow subclasses to add extra params needed for computation:
229 // used to pass additional args to opencl program
230 addExtraRasterParams( rasterParams );
231
232 const std::size_t bufferSize( sizeof( float ) * ( xSize + 2 ) );
233 const std::size_t inputSize( sizeof( float ) * ( xSize ) );
234
235 cl::Buffer rasterParamsBuffer( queue, rasterParams.begin(), rasterParams.end(), true, false, nullptr );
236 cl::Buffer scanLine1Buffer( ctx, CL_MEM_READ_ONLY, bufferSize, nullptr, nullptr );
237 cl::Buffer scanLine2Buffer( ctx, CL_MEM_READ_ONLY, bufferSize, nullptr, nullptr );
238 cl::Buffer scanLine3Buffer( ctx, CL_MEM_READ_ONLY, bufferSize, nullptr, nullptr );
239 cl::Buffer *scanLineBuffer[3] = { &scanLine1Buffer, &scanLine2Buffer, &scanLine3Buffer };
240 cl::Buffer resultLineBuffer( ctx, CL_MEM_WRITE_ONLY, inputSize, nullptr, nullptr );
241
242 // Create a program from the kernel source
243 const cl::Program program( QgsOpenClUtils::buildProgram( source, QgsOpenClUtils::ExceptionBehavior::Throw ) );
244
245 // Create the OpenCL kernel
246 auto kernel = cl::KernelFunctor<
247 cl::Buffer &,
248 cl::Buffer &,
249 cl::Buffer &,
250 cl::Buffer &,
251 cl::Buffer &>( program, "processNineCellWindow" );
252
253 // Rotate buffer index
254 std::vector<int> rowIndex = { 0, 1, 2 };
255
256 // values outside the layer extent (if the 3x3 window is on the border) are sent to the processing method as (input) nodata values
257 for ( int i = 0; i < ySize; ++i )
258 {
259 if ( feedback && feedback->isCanceled() )
260 {
261 break;
262 }
263
264 if ( feedback )
265 {
266 feedback->setProgress( 100.0 * static_cast<double>( i ) / ySize );
267 }
268
269 if ( i == 0 )
270 {
271 // Fill scanline 1 with (input) nodata for the values above the first row and
272 // feed scanline2 with the first actual data row
273 for ( int a = 0; a < xSize + 2; ++a )
274 {
275 scanLine[a] = mInputNodataValue;
276 }
277 queue.enqueueWriteBuffer( scanLine1Buffer, CL_TRUE, 0, bufferSize, scanLine.get() );
278
279 // Read scanline2: first real raster row
280 if ( GDALRasterIO( rasterBand, GF_Read, 0, i, xSize, 1, &scanLine[1], xSize, 1, GDT_Float32, 0, 0 ) != CE_None )
281 {
282 QgsDebugError( QStringLiteral( "Raster IO Error" ) );
283 }
284 queue.enqueueWriteBuffer( scanLine2Buffer, CL_TRUE, 0, bufferSize, scanLine.get() );
285
286 // Read scanline3: second real raster row
287 if ( GDALRasterIO( rasterBand, GF_Read, 0, i + 1, xSize, 1, &scanLine[1], xSize, 1, GDT_Float32, 0, 0 ) != CE_None )
288 {
289 QgsDebugError( QStringLiteral( "Raster IO Error" ) );
290 }
291 queue.enqueueWriteBuffer( scanLine3Buffer, CL_TRUE, 0, bufferSize, scanLine.get() );
292 }
293 else
294 {
295 // Normally fetch only scanLine3 and move forward one row
296 // Read scanline 3, fill the last row with nodata values if it's the last iteration
297 if ( i == ySize - 1 ) //fill the row below the bottom with nodata values
298 {
299 for ( int a = 0; a < xSize + 2; ++a )
300 {
301 scanLine[a] = mInputNodataValue;
302 }
303 queue.enqueueWriteBuffer( *scanLineBuffer[rowIndex[2]], CL_TRUE, 0, bufferSize, scanLine.get() ); // row 0
304 }
305 else // Read line i + 1 and put it into scanline 3
306 // Overwrite from input, skip first and last
307 {
308 if ( GDALRasterIO( rasterBand, GF_Read, 0, i + 1, xSize, 1, &scanLine[1], xSize, 1, GDT_Float32, 0, 0 ) != CE_None )
309 {
310 QgsDebugError( QStringLiteral( "Raster IO Error" ) );
311 }
312 queue.enqueueWriteBuffer( *scanLineBuffer[rowIndex[2]], CL_TRUE, 0, bufferSize, scanLine.get() ); // row 0
313 }
314 }
315
316 kernel( cl::EnqueueArgs( queue, cl::NDRange( xSize ) ), *scanLineBuffer[rowIndex[0]], *scanLineBuffer[rowIndex[1]], *scanLineBuffer[rowIndex[2]], resultLineBuffer, rasterParamsBuffer );
317
318 queue.enqueueReadBuffer( resultLineBuffer, CL_TRUE, 0, inputSize, resultLine.get() );
319
320 if ( GDALRasterIO( outputRasterBand, GF_Write, 0, i, xSize, 1, resultLine.get(), xSize, 1, GDT_Float32, 0, 0 ) != CE_None )
321 {
322 QgsDebugError( QStringLiteral( "Raster IO Error" ) );
323 }
324 std::rotate( rowIndex.begin(), rowIndex.begin() + 1, rowIndex.end() );
325 }
326
327 if ( feedback && feedback->isCanceled() )
328 {
329 //delete the dataset without closing (because it is faster)
330 gdal::fast_delete_and_close( outputDataset, outputDriver, mOutputFile );
332 }
334}
335#endif
336
337
338QgsNineCellFilter::Result QgsNineCellFilter::processRasterCPU( QgsFeedback *feedback )
339{
340 GDALAllRegister();
341
342 //open input file
343 int xSize, ySize;
344 const gdal::dataset_unique_ptr inputDataset( openInputFile( xSize, ySize ) );
345 if ( !inputDataset )
346 {
347 return QgsNineCellFilter::Result::InputLayerError; //opening of input file failed
348 }
349
350 //output driver
351 GDALDriverH outputDriver = openOutputDriver();
352 if ( !outputDriver )
353 {
355 }
356
357 gdal::dataset_unique_ptr outputDataset( openOutputFile( inputDataset.get(), outputDriver ) );
358 if ( !outputDataset )
359 {
360 return QgsNineCellFilter::Result::CreateOutputError; //create operation on output file failed
361 }
362
363 //open first raster band for reading (operation is only for single band raster)
364 GDALRasterBandH rasterBand = GDALGetRasterBand( inputDataset.get(), 1 );
365 if ( !rasterBand )
366 {
368 }
369 mInputNodataValue = GDALGetRasterNoDataValue( rasterBand, nullptr );
370
371 GDALRasterBandH outputRasterBand = GDALGetRasterBand( outputDataset.get(), 1 );
372 if ( !outputRasterBand )
373 {
375 }
376 // set nodata value
377 GDALSetRasterNoDataValue( outputRasterBand, mOutputNodataValue );
378
379 if ( ySize < 3 ) //we require at least three rows (should be true for most datasets)
380 {
382 }
383
384 //keep only three scanlines in memory at a time, make room for initial and final nodata
385 const std::size_t bufferSize( sizeof( float ) * ( xSize + 2 ) );
386 float *scanLine1 = ( float * ) CPLMalloc( bufferSize );
387 float *scanLine2 = ( float * ) CPLMalloc( bufferSize );
388 float *scanLine3 = ( float * ) CPLMalloc( bufferSize );
389
390 float *resultLine = ( float * ) CPLMalloc( sizeof( float ) * xSize );
391
392 //values outside the layer extent (if the 3x3 window is on the border) are sent to the processing method as (input) nodata values
393 for ( int yIndex = 0; yIndex < ySize; ++yIndex )
394 {
395 if ( feedback && feedback->isCanceled() )
396 {
397 break;
398 }
399
400 if ( feedback )
401 {
402 feedback->setProgress( 100.0 * static_cast<double>( yIndex ) / ySize );
403 }
404
405 if ( yIndex == 0 )
406 {
407 //fill scanline 1 with (input) nodata for the values above the first row and feed scanline2 with the first row
408 for ( int a = 0; a < xSize + 2; ++a )
409 {
410 scanLine1[a] = mInputNodataValue;
411 }
412 // Read scanline2
413 if ( GDALRasterIO( rasterBand, GF_Read, 0, 0, xSize, 1, &scanLine2[1], xSize, 1, GDT_Float32, 0, 0 ) != CE_None )
414 {
415 QgsDebugError( QStringLiteral( "Raster IO Error" ) );
416 }
417 }
418 else
419 {
420 //normally fetch only scanLine3 and release scanline 1 if we move forward one row
421 CPLFree( scanLine1 );
422 scanLine1 = scanLine2;
423 scanLine2 = scanLine3;
424 scanLine3 = ( float * ) CPLMalloc( bufferSize );
425 }
426
427 // Read scanline 3
428 if ( yIndex == ySize - 1 ) //fill the row below the bottom with nodata values
429 {
430 for ( int a = 0; a < xSize + 2; ++a )
431 {
432 scanLine3[a] = mInputNodataValue;
433 }
434 }
435 else
436 {
437 if ( GDALRasterIO( rasterBand, GF_Read, 0, yIndex + 1, xSize, 1, &scanLine3[1], xSize, 1, GDT_Float32, 0, 0 ) != CE_None )
438 {
439 QgsDebugError( QStringLiteral( "Raster IO Error" ) );
440 }
441 }
442 // Set first and last extra columns to nodata
443 scanLine1[0] = scanLine1[xSize + 1] = mInputNodataValue;
444 scanLine2[0] = scanLine2[xSize + 1] = mInputNodataValue;
445 scanLine3[0] = scanLine3[xSize + 1] = mInputNodataValue;
446
447
448 // j is the x axis index, skip 0 and last cell that have been filled with nodata
449 for ( int xIndex = 0; xIndex < xSize; ++xIndex )
450 {
451 // cells(x, y) x11, x21, x31, x12, x22, x32, x13, x23, x33
452 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] );
453 }
454
455 if ( GDALRasterIO( outputRasterBand, GF_Write, 0, yIndex, xSize, 1, resultLine, xSize, 1, GDT_Float32, 0, 0 ) != CE_None )
456 {
457 QgsDebugError( QStringLiteral( "Raster IO Error" ) );
458 }
459 }
460
461 CPLFree( resultLine );
462 CPLFree( scanLine1 );
463 CPLFree( scanLine2 );
464 CPLFree( scanLine3 );
465
466 if ( feedback && feedback->isCanceled() )
467 {
468 //delete the dataset without closing (because it is faster)
469 gdal::fast_delete_and_close( outputDataset, outputDriver, mOutputFile );
471 }
473}
@ 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).
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.
Result
Result of the calculation.
@ Canceled
User canceled calculation.
@ InputLayerError
Error reading input file.
@ RasterSizeError
Raster height is too small (need at least 3 rows)
@ Success
Operation completed successfully.
@ CreateOutputError
Error creating output file.
@ DriverError
Could not open the driver for the specified format.
@ InputBandError
Error reading input raster band.
@ OutputBandError
Error reading output raster band.
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.
Result processRaster(QgsFeedback *feedback=nullptr)
Starts the calculation, reads from mInputFile and stores the result in mOutputFile.
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...