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