QGIS API Documentation 3.99.0-Master (8e76e220402)
Loading...
Searching...
No Matches
qgsrastercalculator.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsrastercalculator.cpp - description
3 -----------------------
4 begin : September 28th, 2010
5 copyright : (C) 2010 by Marco Hugentobler
6 email : marco dot hugentobler at sourcepole 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 "qgsrastercalculator.h"
19
20#include <cpl_string.h>
21#include <gdalwarper.h>
22
23#include "qgsfeedback.h"
24#include "qgsgdalutils.h"
25#include "qgsogrutils.h"
26#include "qgsproject.h"
28#include "qgsrasterinterface.h"
29#include "qgsrasterlayer.h"
30#include "qgsrastermatrix.h"
31#include "qgsrasterprojector.h"
32
33#include <QFile>
34#include <QString>
35
36using namespace Qt::StringLiterals;
37
38#ifdef HAVE_OPENCL
39#include "qgsopenclutils.h"
40#include "qgsgdalutils.h"
41#endif
42
43
44//
45// global callback function
46//
47int CPL_STDCALL GdalProgressCallback( double dfComplete, const char *pszMessage, void *pProgressArg )
48{
49 Q_UNUSED( pszMessage )
50
51 static double sDfLastComplete = -1.0;
52
53 QgsFeedback *feedback = static_cast<QgsFeedback *>( pProgressArg );
54
55 if ( sDfLastComplete > dfComplete )
56 {
57 if ( sDfLastComplete >= 1.0 )
58 sDfLastComplete = -1.0;
59 else
60 sDfLastComplete = dfComplete;
61 }
62
63 if ( std::floor( sDfLastComplete * 10 ) != std::floor( dfComplete * 10 ) )
64 {
65 if ( feedback )
66 feedback->setProgress( dfComplete * 100 );
67 }
68 sDfLastComplete = dfComplete;
69
70 if ( feedback && feedback->isCanceled() )
71 return false;
72
73 return true;
74}
75
76QgsRasterCalculator::QgsRasterCalculator( const QString &formulaString, const QString &outputFile, const QString &outputFormat, const QgsRectangle &outputExtent, int nOutputColumns, int nOutputRows, const QVector<QgsRasterCalculatorEntry> &rasterEntries, const QgsCoordinateTransformContext &transformContext )
77 : mFormulaString( formulaString )
78 , mOutputFile( outputFile )
79 , mOutputFormat( outputFormat )
80 , mOutputRectangle( outputExtent )
81 , mNumOutputColumns( nOutputColumns )
82 , mNumOutputRows( nOutputRows )
83 , mRasterEntries( rasterEntries )
84 , mTransformContext( transformContext )
85{
86 //default to first layer's crs
87 if ( !mRasterEntries.isEmpty() )
88 {
89 mOutputCrs = mRasterEntries.at( 0 ).raster->crs();
90 }
91}
92
93QgsRasterCalculator::QgsRasterCalculator( const QString &formulaString, const QString &outputFile, const QString &outputFormat, const QgsRectangle &outputExtent, const QgsCoordinateReferenceSystem &outputCrs, int nOutputColumns, int nOutputRows, const QVector<QgsRasterCalculatorEntry> &rasterEntries, const QgsCoordinateTransformContext &transformContext )
94 : mFormulaString( formulaString )
95 , mOutputFile( outputFile )
96 , mOutputFormat( outputFormat )
97 , mOutputRectangle( outputExtent )
98 , mOutputCrs( outputCrs )
99 , mNumOutputColumns( nOutputColumns )
100 , mNumOutputRows( nOutputRows )
101 , mRasterEntries( rasterEntries )
102 , mTransformContext( transformContext )
103{
104}
105
106// Deprecated!
107QgsRasterCalculator::QgsRasterCalculator( const QString &formulaString, const QString &outputFile, const QString &outputFormat, const QgsRectangle &outputExtent, int nOutputColumns, int nOutputRows, const QVector<QgsRasterCalculatorEntry> &rasterEntries )
108 : mFormulaString( formulaString )
109 , mOutputFile( outputFile )
110 , mOutputFormat( outputFormat )
111 , mOutputRectangle( outputExtent )
112 , mNumOutputColumns( nOutputColumns )
113 , mNumOutputRows( nOutputRows )
114 , mRasterEntries( rasterEntries )
115{
116 //default to first layer's crs
117 if ( !mRasterEntries.isEmpty() )
118 {
119 mOutputCrs = mRasterEntries.at( 0 ).raster->crs();
120 }
121 mTransformContext = QgsProject::instance()->transformContext();
122}
123
124
125// Deprecated!
126QgsRasterCalculator::QgsRasterCalculator( const QString &formulaString, const QString &outputFile, const QString &outputFormat, const QgsRectangle &outputExtent, const QgsCoordinateReferenceSystem &outputCrs, int nOutputColumns, int nOutputRows, const QVector<QgsRasterCalculatorEntry> &rasterEntries )
127 : mFormulaString( formulaString )
128 , mOutputFile( outputFile )
129 , mOutputFormat( outputFormat )
130 , mOutputRectangle( outputExtent )
131 , mOutputCrs( outputCrs )
132 , mNumOutputColumns( nOutputColumns )
133 , mNumOutputRows( nOutputRows )
134 , mRasterEntries( rasterEntries )
135 , mTransformContext( QgsProject::instance()->transformContext() )
136{
137}
138
140{
141 mLastError.clear();
142
143 //prepare search string / tree
144 std::unique_ptr<QgsRasterCalcNode> calcNode( QgsRasterCalcNode::parseRasterCalcString( mFormulaString, mLastError ) );
145 if ( !calcNode )
146 {
147 //error
149 }
150
151 // Check input layers and bands
152 for ( const auto &entry : std::as_const( mRasterEntries ) )
153 {
154 if ( !entry.raster ) // no raster layer in entry
155 {
156 mLastError = QObject::tr( "No raster layer for entry %1" ).arg( entry.ref );
158 }
159 if ( entry.bandNumber <= 0 || entry.bandNumber > entry.raster->bandCount() )
160 {
161 mLastError = QObject::tr( "Band number %1 is not valid for entry %2" ).arg( entry.bandNumber ).arg( entry.ref );
163 }
164 }
165
166 // Check if we need to read the raster as a whole (which is memory inefficient
167 // and not interruptible by the user) by checking if any raster matrix nodes are
168 // in the expression
169 bool requiresMatrix = !calcNode->findNodes( QgsRasterCalcNode::Type::tMatrix ).isEmpty();
170
171#ifdef HAVE_OPENCL
172 // Check for matrix nodes, GPU implementation does not support them
173 if ( QgsOpenClUtils::enabled() && QgsOpenClUtils::available() && !requiresMatrix )
174 {
175 return processCalculationGPU( std::move( calcNode ), feedback );
176 }
177#endif
178
179 //open output dataset for writing
180 GDALDriverH outputDriver = openOutputDriver();
181 if ( !outputDriver )
182 {
183 mLastError = QObject::tr( "Could not obtain driver for %1" ).arg( mOutputFormat );
185 }
186
187 gdal::dataset_unique_ptr outputDataset( openOutputFile( outputDriver ) );
188 if ( !outputDataset )
189 {
190 mLastError = QObject::tr( "Could not create output %1" ).arg( mOutputFile );
192 }
193
194 GDALSetProjection( outputDataset.get(), mOutputCrs.toWkt( Qgis::CrsWktVariant::PreferredGdal ).toLocal8Bit().data() );
195 GDALRasterBandH outputRasterBand = GDALGetRasterBand( outputDataset.get(), 1 );
196
197 GDALSetRasterNoDataValue( outputRasterBand, mNoDataValue );
198
199#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION( 3, 13, 0 )
200 const bool hasReportsDuringClose = GDALDatasetGetCloseReportsProgress( outputDataset.get() );
201 const double maxProgressDuringBlockWriting = hasReportsDuringClose ? 50.0 : 100.0;
202#else
203 constexpr double maxProgressDuringBlockWriting = 100.0;
204#endif
205
206 // Take the fast route (process one line at a time) if we can
207 if ( !requiresMatrix )
208 {
209 // Map of raster names -> blocks
210 std::map<QString, std::unique_ptr<QgsRasterBlock>> inputBlocks;
211 std::map<QString, QgsRasterCalculatorEntry> uniqueRasterEntries;
212 const QList<const QgsRasterCalcNode *> rasterRefNodes = calcNode->findNodes( QgsRasterCalcNode::Type::tRasterRef );
213 for ( const QgsRasterCalcNode *r : rasterRefNodes )
214 {
215 QString layerRef( r->toString().remove( 0, 1 ) );
216 layerRef.chop( 1 );
217 if ( !inputBlocks.count( layerRef ) )
218 {
219 for ( const QgsRasterCalculatorEntry &ref : std::as_const( mRasterEntries ) )
220 {
221 if ( ref.ref == layerRef )
222 {
223 uniqueRasterEntries[layerRef] = ref;
224 inputBlocks[layerRef] = std::make_unique<QgsRasterBlock>();
225 }
226 }
227 }
228 }
229
230 //read / write line by line
231 QMap<QString, QgsRasterBlock *> _rasterData;
232 // Cast to float
233 std::vector<float> castedResult( static_cast<size_t>( mNumOutputColumns ), 0 );
234 auto rowHeight = mOutputRectangle.height() / mNumOutputRows;
235 for ( size_t row = 0; row < static_cast<size_t>( mNumOutputRows ); ++row )
236 {
237 if ( feedback )
238 {
239 feedback->setProgress( maxProgressDuringBlockWriting * static_cast<double>( row ) / mNumOutputRows );
240 }
241
242 if ( feedback && feedback->isCanceled() )
243 {
244 break;
245 }
246
247 // Calculates the rect for a single row read
248 QgsRectangle rect( mOutputRectangle );
249 rect.setYMaximum( rect.yMaximum() - rowHeight * row );
250 rect.setYMinimum( rect.yMaximum() - rowHeight );
251
252 // Read rows into input blocks
253 for ( auto &layerRef : inputBlocks )
254 {
255 QgsRasterCalculatorEntry ref = uniqueRasterEntries[layerRef.first];
256 if ( ref.raster->crs() != mOutputCrs )
257 {
259 proj.setCrs( ref.raster->crs(), mOutputCrs, mTransformContext );
260 proj.setInput( ref.raster->dataProvider() );
262 layerRef.second.reset( proj.block( ref.bandNumber, rect, mNumOutputColumns, 1 ) );
263 }
264 else
265 {
266 layerRef.second.reset( ref.raster->dataProvider()->block( ref.bandNumber, rect, mNumOutputColumns, 1 ) );
267 }
268 }
269
270 // 1 row X mNumOutputColumns matrix
271 QgsRasterMatrix resultMatrix( mNumOutputColumns, 1, nullptr, mNoDataValue );
272
273 _rasterData.clear();
274 for ( const auto &layerRef : inputBlocks )
275 {
276 _rasterData.insert( layerRef.first, layerRef.second.get() );
277 }
278
279 if ( calcNode->calculate( _rasterData, resultMatrix, 0 ) )
280 {
281 std::copy( resultMatrix.data(), resultMatrix.data() + mNumOutputColumns, castedResult.begin() );
282 if ( GDALRasterIO( outputRasterBand, GF_Write, 0, row, mNumOutputColumns, 1, castedResult.data(), mNumOutputColumns, 1, GDT_Float32, 0, 0 ) != CE_None )
283 {
284 QgsDebugError( u"RasterIO error!"_s );
285 }
286 }
287 else
288 {
289 //delete the dataset without closing (because it is faster)
290 gdal::fast_delete_and_close( outputDataset, outputDriver, mOutputFile );
292 }
293 }
294
295 if ( feedback )
296 {
297 feedback->setProgress( maxProgressDuringBlockWriting );
298 }
299 }
300 else // Original code (memory inefficient route)
301 {
302 QMap<QString, QgsRasterBlock *> inputBlocks;
303 QVector<QgsRasterCalculatorEntry>::const_iterator it = mRasterEntries.constBegin();
304 for ( ; it != mRasterEntries.constEnd(); ++it )
305 {
306 std::unique_ptr<QgsRasterBlock> block;
307 // if crs transform needed
308 if ( it->raster->crs() != mOutputCrs )
309 {
311 proj.setCrs( it->raster->crs(), mOutputCrs, it->raster->transformContext() );
312 proj.setInput( it->raster->dataProvider() );
314
315 QgsRasterBlockFeedback *rasterBlockFeedback = new QgsRasterBlockFeedback();
316 QObject::connect( feedback, &QgsFeedback::canceled, rasterBlockFeedback, &QgsRasterBlockFeedback::cancel );
317 block.reset( proj.block( it->bandNumber, mOutputRectangle, mNumOutputColumns, mNumOutputRows, rasterBlockFeedback ) );
318 if ( rasterBlockFeedback->isCanceled() )
319 {
320 qDeleteAll( inputBlocks );
322 }
323 }
324 else
325 {
326 block.reset( it->raster->dataProvider()->block( it->bandNumber, mOutputRectangle, mNumOutputColumns, mNumOutputRows ) );
327 }
328 if ( block->isEmpty() )
329 {
330 mLastError = QObject::tr( "Could not allocate required memory for %1" ).arg( it->ref );
331 qDeleteAll( inputBlocks );
333 }
334 inputBlocks.insert( it->ref, block.release() );
335 }
336
337 QgsRasterMatrix resultMatrix;
338 resultMatrix.setNodataValue( mNoDataValue );
339
340 //read / write line by line
341 for ( int i = 0; i < mNumOutputRows; ++i )
342 {
343 if ( feedback )
344 {
345 feedback->setProgress( maxProgressDuringBlockWriting * static_cast<double>( i ) / mNumOutputRows );
346 }
347
348 if ( feedback && feedback->isCanceled() )
349 {
350 break;
351 }
352
353 if ( calcNode->calculate( inputBlocks, resultMatrix, i ) )
354 {
355 bool resultIsNumber = resultMatrix.isNumber();
356 float *calcData = new float[mNumOutputColumns];
357
358 for ( int j = 0; j < mNumOutputColumns; ++j )
359 {
360 calcData[j] = ( float ) ( resultIsNumber ? resultMatrix.number() : resultMatrix.data()[j] );
361 }
362
363 //write scanline to the dataset
364 if ( GDALRasterIO( outputRasterBand, GF_Write, 0, i, mNumOutputColumns, 1, calcData, mNumOutputColumns, 1, GDT_Float32, 0, 0 ) != CE_None )
365 {
366 QgsDebugError( u"RasterIO error!"_s );
367 }
368
369 delete[] calcData;
370 }
371 else
372 {
373 qDeleteAll( inputBlocks );
374 inputBlocks.clear();
375 gdal::fast_delete_and_close( outputDataset, outputDriver, mOutputFile );
377 }
378 }
379
380 if ( feedback )
381 {
382 feedback->setProgress( maxProgressDuringBlockWriting );
383 }
384
385 //close datasets and release memory
386 calcNode.reset();
387 qDeleteAll( inputBlocks );
388 inputBlocks.clear();
389 }
390
391 if ( feedback && feedback->isCanceled() )
392 {
393 //delete the dataset without closing (because it is faster)
394 gdal::fast_delete_and_close( outputDataset, outputDriver, mOutputFile );
396 }
397
398 GDALComputeRasterStatistics( outputRasterBand, true, nullptr, nullptr, nullptr, nullptr, GdalProgressCallback, feedback );
399
400#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION( 3, 13, 0 )
401 if ( hasReportsDuringClose && feedback )
402 {
403 QgsGdalProgressAdapter progress( feedback, maxProgressDuringBlockWriting );
404 if ( GDALDatasetRunCloseWithoutDestroyingEx(
405 outputDataset.get(), QgsGdalProgressAdapter::progressCallback, &progress
406 )
407 != CE_None )
408 {
410 }
411 }
412#endif
413
415}
416
417#ifdef HAVE_OPENCL
418QgsRasterCalculator::Result QgsRasterCalculator::processCalculationGPU( std::unique_ptr<QgsRasterCalcNode> calcNode, QgsFeedback *feedback )
419{
420 QString cExpression( calcNode->toString( true ) );
421
422 QList<const QgsRasterCalcNode *> nodeList( calcNode->findNodes( QgsRasterCalcNode::Type::tRasterRef ) );
423 QSet<QString> capturedTexts;
424 for ( const auto &r : std::as_const( nodeList ) )
425 {
426 QString s( r->toString().remove( 0, 1 ) );
427 s.chop( 1 );
428 capturedTexts.insert( s );
429 }
430
431 // Extract all references
432 struct LayerRef
433 {
434 QString name;
435 int band;
436 QgsRasterLayer *layer = nullptr;
437 QString varName;
438 QString typeName;
439 size_t index;
440 size_t bufferSize;
441 size_t dataSize;
442 };
443
444 // Collects all layers, band, name, varName and size information
445 std::vector<LayerRef> inputRefs;
446 size_t refCounter = 0;
447 for ( const auto &r : capturedTexts )
448 {
449 if ( r.startsWith( '"' ) )
450 continue;
451 QStringList parts( r.split( '@' ) );
452 if ( parts.count() != 2 )
453 continue;
454 bool ok = false;
455 LayerRef entry;
456 entry.name = r;
457 entry.band = parts[1].toInt( &ok );
458 for ( const auto &ref : std::as_const( mRasterEntries ) )
459 {
460 if ( ref.ref == entry.name )
461 entry.layer = ref.raster;
462 }
463 if ( !( entry.layer && entry.layer->dataProvider() && ok ) )
464 continue;
465 entry.dataSize = entry.layer->dataProvider()->dataTypeSize( entry.band );
466 switch ( entry.layer->dataProvider()->dataType( entry.band ) )
467 {
469 entry.typeName = u"unsigned char"_s;
470 break;
472 entry.typeName = u"char"_s;
473 break;
475 entry.typeName = u"unsigned short"_s;
476 break;
478 entry.typeName = u"short"_s;
479 break;
481 entry.typeName = u"unsigned int"_s;
482 break;
484 entry.typeName = u"int"_s;
485 break;
487 entry.typeName = u"float"_s;
488 break;
489 // FIXME: not sure all OpenCL implementations support double
490 // maybe safer to fall back to the CPU implementation
491 // after a compatibility check
493 entry.typeName = u"double"_s;
494 break;
503 }
504 entry.bufferSize = entry.dataSize * mNumOutputColumns;
505 entry.index = refCounter;
506 entry.varName = u"input_raster_%1_band_%2"_s
507 .arg( refCounter++ )
508 .arg( entry.band );
509 inputRefs.push_back( entry );
510 }
511
512 // May throw an openCL exception
513 try
514 {
515 // Prepare context and queue
516 cl::Context ctx( QgsOpenClUtils::context() );
517 cl::CommandQueue queue( QgsOpenClUtils::commandQueue() );
518
519 // Create the C expression
520 std::vector<cl::Buffer> inputBuffers;
521 inputBuffers.reserve( inputRefs.size() );
522 QStringList inputArgs;
523 for ( const auto &ref : inputRefs )
524 {
525 cExpression.replace( u"\"%1\""_s.arg( ref.name ), u"%1[i]"_s.arg( ref.varName ) );
526 inputArgs.append( u"__global %1 *%2"_s
527 .arg( ref.typeName, ref.varName ) );
528 inputBuffers.push_back( cl::Buffer( ctx, CL_MEM_READ_ONLY, ref.bufferSize, nullptr, nullptr ) );
529 }
530
531 //qDebug() << cExpression;
532
533 // Create the program
534 QString programTemplate( R"CL(
535 // Inputs:
536 ##INPUT_DESC##
537 // Expression: ##EXPRESSION_ORIGINAL##
538 __kernel void rasterCalculator( ##INPUT##
539 __global float *resultLine
540 )
541 {
542 // Get the index of the current element
543 const int i = get_global_id(0);
544 // Check for nodata in input
545 if ( ##INPUT_NODATA_CHECK## )
546 resultLine[i] = -FLT_MAX;
547 // Expression
548 else
549 resultLine[i] = ##EXPRESSION##;
550 }
551 )CL" );
552
553 QStringList inputDesc;
554 QStringList inputNoDataCheck;
555 for ( const auto &ref : inputRefs )
556 {
557 inputDesc.append( u" // %1 = %2"_s.arg( ref.varName, ref.name ) );
558 if ( ref.layer->dataProvider()->sourceHasNoDataValue( ref.band ) )
559 {
560 inputNoDataCheck.append( u"(float) %1[i] == (float) %2"_s.arg( ref.varName, QString::number( ref.layer->dataProvider()->sourceNoDataValue( ref.band ), 'g', 10 ) ) );
561 }
562 }
563
564 programTemplate = programTemplate.replace( "##INPUT_NODATA_CHECK##"_L1, inputNoDataCheck.isEmpty() ? u"false"_s : inputNoDataCheck.join( " || "_L1 ) );
565 programTemplate = programTemplate.replace( "##INPUT_DESC##"_L1, inputDesc.join( '\n' ) );
566 programTemplate = programTemplate.replace( "##INPUT##"_L1, !inputArgs.isEmpty() ? ( inputArgs.join( ',' ).append( ',' ) ) : QChar( ' ' ) );
567 programTemplate = programTemplate.replace( "##EXPRESSION##"_L1, cExpression );
568 programTemplate = programTemplate.replace( "##EXPRESSION_ORIGINAL##"_L1, calcNode->toString() );
569
570 // qDebug() << programTemplate;
571
572 // Create a program from the kernel source
573 cl::Program program;
574 try
575 {
577 }
578 catch ( cl::Error &e )
579 {
580 mLastError = QObject::tr( "Error compiling OpenCL kernel: %1" ).arg( e.what() );
582 }
583
584 // Create the buffers, output is float32 (4 bytes)
585 // We assume size of float = 4 because that's the size used by OpenCL and IEEE 754
586 Q_ASSERT( sizeof( float ) == 4 );
587 std::size_t resultBufferSize( 4 * static_cast<size_t>( mNumOutputColumns ) );
588 cl::Buffer resultLineBuffer( ctx, CL_MEM_WRITE_ONLY, resultBufferSize, nullptr, nullptr );
589
590 auto kernel = cl::Kernel( program, "rasterCalculator" );
591
592 for ( unsigned int i = 0; i < inputBuffers.size(); i++ )
593 {
594 kernel.setArg( i, inputBuffers.at( i ) );
595 }
596 kernel.setArg( static_cast<unsigned int>( inputBuffers.size() ), resultLineBuffer );
597
598 QgsOpenClUtils::CPLAllocator<float> resultLine( static_cast<size_t>( mNumOutputColumns ) );
599
600 //open output dataset for writing
601 GDALDriverH outputDriver = openOutputDriver();
602 if ( !outputDriver )
603 {
604 mLastError = QObject::tr( "Could not obtain driver for %1" ).arg( mOutputFormat );
606 }
607
608 gdal::dataset_unique_ptr outputDataset( openOutputFile( outputDriver ) );
609 if ( !outputDataset )
610 {
611 mLastError = QObject::tr( "Could not create output %1" ).arg( mOutputFile );
613 }
614
615#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION( 3, 13, 0 )
616 const bool hasReportsDuringClose = GDALDatasetGetCloseReportsProgress( outputDataset.get() );
617 const double maxProgressDuringBlockWriting = hasReportsDuringClose ? 50.0 : 100.0;
618#else
619 constexpr double maxProgressDuringBlockWriting = 100.0;
620#endif
621
622 GDALSetProjection( outputDataset.get(), mOutputCrs.toWkt( Qgis::CrsWktVariant::PreferredGdal ).toLocal8Bit().data() );
623
624
625 GDALRasterBandH outputRasterBand = GDALGetRasterBand( outputDataset.get(), 1 );
626 if ( !outputRasterBand )
628
629 GDALSetRasterNoDataValue( outputRasterBand, mNoDataValue );
630
631 // Input block (buffer)
632 std::unique_ptr<QgsRasterBlock> block;
633
634 // Run kernel on all scanlines
635 auto rowHeight = mOutputRectangle.height() / mNumOutputRows;
636 for ( int line = 0; line < mNumOutputRows; line++ )
637 {
638 if ( feedback && feedback->isCanceled() )
639 {
640 break;
641 }
642
643 if ( feedback )
644 {
645 feedback->setProgress( maxProgressDuringBlockWriting * static_cast<double>( line ) / mNumOutputRows );
646 }
647
648 // Read lines from rasters into the buffers
649 for ( const auto &ref : inputRefs )
650 {
651 // Read one row
652 QgsRectangle rect( mOutputRectangle );
653 rect.setYMaximum( rect.yMaximum() - rowHeight * line );
654 rect.setYMinimum( rect.yMaximum() - rowHeight );
655
656 // TODO: check if this is too slow
657 // if crs transform needed
658 if ( ref.layer->crs() != mOutputCrs )
659 {
660 QgsRasterProjector proj;
661 proj.setCrs( ref.layer->crs(), mOutputCrs, ref.layer->transformContext() );
662 proj.setInput( ref.layer->dataProvider() );
664 block.reset( proj.block( ref.band, rect, mNumOutputColumns, 1 ) );
665 }
666 else
667 {
668 block.reset( ref.layer->dataProvider()->block( ref.band, rect, mNumOutputColumns, 1 ) );
669 }
670
671 //for ( int i = 0; i < mNumOutputColumns; i++ )
672 // qDebug() << "Input: " << line << i << ref.varName << " = " << block->value( 0, i );
673 //qDebug() << "Writing buffer " << ref.index;
674
675 Q_ASSERT( ref.bufferSize == static_cast<size_t>( block->data().size() ) );
676 queue.enqueueWriteBuffer( inputBuffers[ref.index], CL_TRUE, 0, ref.bufferSize, block->bits() );
677 }
678 // Run the kernel
679 queue.enqueueNDRangeKernel(
680 kernel,
681 0,
682 cl::NDRange( mNumOutputColumns )
683 );
684
685 // Write the result
686 queue.enqueueReadBuffer( resultLineBuffer, CL_TRUE, 0, resultBufferSize, resultLine.get() );
687
688 //for ( int i = 0; i < mNumOutputColumns; i++ )
689 // qDebug() << "Output: " << line << i << " = " << resultLine[i];
690
691 if ( GDALRasterIO( outputRasterBand, GF_Write, 0, line, mNumOutputColumns, 1, resultLine.get(), mNumOutputColumns, 1, GDT_Float32, 0, 0 ) != CE_None )
692 {
694 }
695 }
696
697 if ( feedback && feedback->isCanceled() )
698 {
699 //delete the dataset without closing (because it is faster)
700 gdal::fast_delete_and_close( outputDataset, outputDriver, mOutputFile );
702 }
703
704 inputBuffers.clear();
705
706 GDALComputeRasterStatistics( outputRasterBand, true, nullptr, nullptr, nullptr, nullptr, GdalProgressCallback, feedback );
707
708#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION( 3, 13, 0 )
709 if ( hasReportsDuringClose && feedback )
710 {
711 QgsGdalProgressAdapter progress( feedback, maxProgressDuringBlockWriting );
712 if ( GDALDatasetRunCloseWithoutDestroyingEx(
713 outputDataset.get(), QgsGdalProgressAdapter::progressCallback, &progress
714 )
715 != CE_None )
716 {
718 }
719 }
720#endif
721 }
722 catch ( cl::Error &e )
723 {
724 mLastError = e.what();
726 }
727
729}
730#endif
731
732GDALDriverH QgsRasterCalculator::openOutputDriver()
733{
734 //open driver
735 GDALDriverH outputDriver = GDALGetDriverByName( mOutputFormat.toLocal8Bit().data() );
736
737 if ( !outputDriver )
738 {
739 return outputDriver; //return nullptr, driver does not exist
740 }
741
742 if ( !QgsGdalUtils::supportsRasterCreate( outputDriver ) )
743 {
744 return nullptr; //driver exist, but it does not support the create operation
745 }
746
747 return outputDriver;
748}
749
750gdal::dataset_unique_ptr QgsRasterCalculator::openOutputFile( GDALDriverH outputDriver )
751{
752 //open output file
753 char **papszOptions = QgsGdalUtils::papszFromStringList( mCreationOptions );
754 gdal::dataset_unique_ptr outputDataset( GDALCreate( outputDriver, mOutputFile.toUtf8().constData(), mNumOutputColumns, mNumOutputRows, 1, GDT_Float32, papszOptions ) );
755 CSLDestroy( papszOptions );
756 if ( !outputDataset )
757 {
758 return nullptr;
759 }
760
761 //assign georef information
762 double geotransform[6];
763 outputGeoTransform( geotransform );
764 GDALSetGeoTransform( outputDataset.get(), geotransform );
765
766 return outputDataset;
767}
768
769void QgsRasterCalculator::outputGeoTransform( double *transform ) const
770{
771 transform[0] = mOutputRectangle.xMinimum();
772 transform[1] = mOutputRectangle.width() / mNumOutputColumns;
773 transform[2] = 0;
774 transform[3] = mOutputRectangle.yMaximum();
775 transform[4] = 0;
776 transform[5] = -mOutputRectangle.height() / mNumOutputRows;
777}
778
780{
781 return mLastError;
782}
783
784QVector<QgsRasterCalculatorEntry> QgsRasterCalculatorEntry::rasterEntries()
785{
786 QVector<QgsRasterCalculatorEntry> availableEntries;
787 const QMap<QString, QgsMapLayer *> &layers = QgsProject::instance()->mapLayers();
788
789 auto uniqueRasterBandIdentifier = [&]( QgsRasterCalculatorEntry &entry ) -> bool {
790 unsigned int i( 1 );
791 entry.ref = u"%1@%2"_s.arg( entry.raster ? entry.raster->name() : QString() ).arg( entry.bandNumber );
792 while ( true )
793 {
794 bool unique( true );
795 for ( const auto &ref : std::as_const( availableEntries ) )
796 {
797 // Safety belt
798 if ( !( entry.raster && ref.raster ) )
799 continue;
800 // Check if is another band of the same raster
801 if ( ref.raster->publicSource() == entry.raster->publicSource() )
802 {
803 if ( ref.bandNumber != entry.bandNumber )
804 {
805 continue;
806 }
807 else // a layer with the same data source was already added to the list
808 {
809 return false;
810 }
811 }
812 // If same name but different source
813 if ( ref.ref == entry.ref )
814 {
815 unique = false;
816 entry.ref = u"%1_%2@%3"_s.arg( entry.raster->name() ).arg( i++ ).arg( entry.bandNumber );
817 }
818 }
819 if ( unique )
820 return true;
821 }
822 };
823
824 QMap<QString, QgsMapLayer *>::const_iterator layerIt = layers.constBegin();
825 for ( ; layerIt != layers.constEnd(); ++layerIt )
826 {
827 QgsRasterLayer *rlayer = qobject_cast<QgsRasterLayer *>( layerIt.value() );
828 if ( rlayer && rlayer->dataProvider() && ( rlayer->dataProvider()->capabilities() & Qgis::RasterInterfaceCapability::Size ) )
829 {
830 //get number of bands
831 for ( int i = 0; i < rlayer->bandCount(); ++i )
832 {
834 entry.raster = rlayer;
835 entry.bandNumber = i + 1;
836 if ( !uniqueRasterBandIdentifier( entry ) )
837 break;
838 availableEntries.push_back( entry );
839 }
840 }
841 }
842 return availableEntries;
843}
@ Size
Original data source size (and thus resolution) is known, it is not always available,...
Definition qgis.h:4962
@ CInt32
Complex Int32.
Definition qgis.h:390
@ Float32
Thirty two bit floating point (float).
Definition qgis.h:387
@ CFloat64
Complex Float64.
Definition qgis.h:392
@ Int16
Sixteen bit signed integer (qint16).
Definition qgis.h:384
@ ARGB32_Premultiplied
Color, alpha, red, green, blue, 4 bytes the same as QImage::Format_ARGB32_Premultiplied.
Definition qgis.h:394
@ Int8
Eight bit signed integer (qint8) (added in QGIS 3.30).
Definition qgis.h:382
@ UInt16
Sixteen bit unsigned integer (quint16).
Definition qgis.h:383
@ Byte
Eight bit unsigned integer (quint8).
Definition qgis.h:381
@ UnknownDataType
Unknown or unspecified type.
Definition qgis.h:380
@ ARGB32
Color, alpha, red, green, blue, 4 bytes the same as QImage::Format_ARGB32.
Definition qgis.h:393
@ Int32
Thirty two bit signed integer (qint32).
Definition qgis.h:386
@ Float64
Sixty four bit floating point (double).
Definition qgis.h:388
@ CFloat32
Complex Float32.
Definition qgis.h:391
@ CInt16
Complex Int16.
Definition qgis.h:389
@ UInt32
Thirty two bit unsigned integer (quint32).
Definition qgis.h:385
@ PreferredGdal
Preferred format for conversion of CRS to WKT for use with the GDAL library.
Definition qgis.h:2499
Represents a coordinate reference system (CRS).
Contains information about the context in which a coordinate transform is executed.
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 canceled()
Internal routines can connect to this signal if they use event loop.
void cancel()
Tells the internal routines that the current operation should be canceled. This should be run by the ...
void setProgress(double progress)
Sets the current progress for the feedback object.
Definition qgsfeedback.h:63
Utility class to map from GDALProgressFunc to QgsFeedback.
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.
QgsCoordinateReferenceSystem crs
Definition qgsmaplayer.h:90
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 cl::CommandQueue commandQueue()
Create an OpenCL command queue from the default context.
@ Throw
Write errors in the message log and re-throw exceptions.
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition qgsproject.h:113
static QgsProject * instance()
Returns the QgsProject singleton instance.
QMap< QString, QgsMapLayer * > mapLayers(const bool validOnly=false) const
Returns a map of all registered layers by layer ID.
Feedback object tailored for raster block reading.
Represents a node in a raster calculator.
static QgsRasterCalcNode * parseRasterCalcString(const QString &str, QString &parserErrorMsg)
Represents an individual raster layer/band number entry within a raster calculation.
QgsRasterLayer * raster
Raster layer associated with entry.
static QVector< QgsRasterCalculatorEntry > rasterEntries()
Creates a list of raster entries from the current project.
int bandNumber
Band number for entry.
QString ref
Name of entry.
QgsRasterCalculator(const QString &formulaString, const QString &outputFile, const QString &outputFormat, const QgsRectangle &outputExtent, const QgsCoordinateReferenceSystem &outputCrs, int nOutputColumns, int nOutputRows, const QVector< QgsRasterCalculatorEntry > &rasterEntries, const QgsCoordinateTransformContext &transformContext)
QgsRasterCalculator constructor.
QString lastError() const
Returns a description of the last error encountered.
Result processCalculation(QgsFeedback *feedback=nullptr)
Starts the calculation and writes a new raster.
Result
Result of the calculation.
@ Canceled
User canceled calculation.
@ InputLayerError
Error reading input layer.
@ BandError
Invalid band number for input.
@ Success
Calculation successful.
@ CreateOutputError
Error creating output data file.
@ ParserError
Error parsing formula.
@ CalculationError
Error occurred while performing calculation.
@ MemoryError
Error allocating memory for result.
@ OpenCLKernelBuildError
Error building OpenCL kernel.
QgsRasterBlock * block(int bandNo, const QgsRectangle &boundingBox, int width, int height, QgsRasterBlockFeedback *feedback=nullptr) override
Read block of data using given extent and size.
virtual Qgis::RasterInterfaceCapabilities capabilities() const
Returns the capabilities supported by the interface.
virtual bool setInput(QgsRasterInterface *input)
Set input.
Represents a raster layer.
int bandCount() const
Returns the number of bands in this layer.
QgsRasterDataProvider * dataProvider() override
Returns the source data provider.
Represents a matrix in a raster calculator operation.
bool isNumber() const
Returns true if matrix is 1x1 (=scalar number).
void setNodataValue(double d)
double * data()
Returns data array (but not ownership).
double number() const
Implements approximate projection support for optimised raster transformation.
void setPrecision(Precision precision)
QgsRasterBlock * block(int bandNo, const QgsRectangle &extent, int width, int height, QgsRasterBlockFeedback *feedback=nullptr) override
Read block of data using given extent and size.
@ Exact
Exact, precise but slow.
Q_DECL_DEPRECATED void setCrs(const QgsCoordinateReferenceSystem &srcCRS, const QgsCoordinateReferenceSystem &destCRS, int srcDatumTransform=-1, int destDatumTransform=-1)
Sets the source and destination CRS.
A rectangle specified with double values.
void setYMinimum(double y)
Set the minimum y value.
void setYMaximum(double y)
Set the maximum y value.
double yMaximum
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.
#define QgsDebugError(str)
Definition qgslogger.h:59
int CPL_STDCALL GdalProgressCallback(double dfComplete, const char *pszMessage, void *pProgressArg)