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