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