QGIS API Documentation 4.1.0-Master (5bf3c20f3c9)
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
77 const QString &formulaString,
78 const QString &outputFile,
79 const QString &outputFormat,
80 const QgsRectangle &outputExtent,
81 int nOutputColumns,
82 int nOutputRows,
83 const QVector<QgsRasterCalculatorEntry> &rasterEntries,
84 const QgsCoordinateTransformContext &transformContext
85)
86 : mFormulaString( formulaString )
87 , mOutputFile( outputFile )
88 , mOutputFormat( outputFormat )
89 , mOutputRectangle( outputExtent )
90 , mNumOutputColumns( nOutputColumns )
91 , mNumOutputRows( nOutputRows )
92 , mRasterEntries( rasterEntries )
93 , mTransformContext( transformContext )
94{
95 //default to first layer's crs
96 if ( !mRasterEntries.isEmpty() )
97 {
98 mOutputCrs = mRasterEntries.at( 0 ).raster->crs();
99 }
100}
101
103 const QString &formulaString,
104 const QString &outputFile,
105 const QString &outputFormat,
106 const QgsRectangle &outputExtent,
107 const QgsCoordinateReferenceSystem &outputCrs,
108 int nOutputColumns,
109 int nOutputRows,
110 const QVector<QgsRasterCalculatorEntry> &rasterEntries,
111 const QgsCoordinateTransformContext &transformContext
112)
113 : mFormulaString( formulaString )
114 , mOutputFile( outputFile )
115 , mOutputFormat( outputFormat )
116 , mOutputRectangle( outputExtent )
117 , mOutputCrs( outputCrs )
118 , mNumOutputColumns( nOutputColumns )
119 , mNumOutputRows( nOutputRows )
120 , mRasterEntries( rasterEntries )
121 , mTransformContext( transformContext )
122{}
123
124// Deprecated!
126 const QString &formulaString, const QString &outputFile, const QString &outputFormat, const QgsRectangle &outputExtent, int nOutputColumns, int nOutputRows, const QVector<QgsRasterCalculatorEntry> &rasterEntries
127)
128 : mFormulaString( formulaString )
129 , mOutputFile( outputFile )
130 , mOutputFormat( outputFormat )
131 , mOutputRectangle( outputExtent )
132 , mNumOutputColumns( nOutputColumns )
133 , mNumOutputRows( nOutputRows )
134 , mRasterEntries( rasterEntries )
135{
136 //default to first layer's crs
137 if ( !mRasterEntries.isEmpty() )
138 {
139 mOutputCrs = mRasterEntries.at( 0 ).raster->crs();
140 }
141 mTransformContext = QgsProject::instance()->transformContext();
142}
143
144
145// Deprecated!
147 const QString &formulaString,
148 const QString &outputFile,
149 const QString &outputFormat,
150 const QgsRectangle &outputExtent,
151 const QgsCoordinateReferenceSystem &outputCrs,
152 int nOutputColumns,
153 int nOutputRows,
154 const QVector<QgsRasterCalculatorEntry> &rasterEntries
155)
156 : mFormulaString( formulaString )
157 , mOutputFile( outputFile )
158 , mOutputFormat( outputFormat )
159 , mOutputRectangle( outputExtent )
160 , mOutputCrs( outputCrs )
161 , mNumOutputColumns( nOutputColumns )
162 , mNumOutputRows( nOutputRows )
163 , mRasterEntries( rasterEntries )
164 , mTransformContext( QgsProject::instance()->transformContext() )
165{}
166
168{
169 mLastError.clear();
170
171 //prepare search string / tree
172 std::unique_ptr<QgsRasterCalcNode> calcNode( QgsRasterCalcNode::parseRasterCalcString( mFormulaString, mLastError ) );
173 if ( !calcNode )
174 {
175 //error
177 }
178
179 // Check input layers and bands
180 for ( const auto &entry : std::as_const( mRasterEntries ) )
181 {
182 if ( !entry.raster ) // no raster layer in entry
183 {
184 mLastError = QObject::tr( "No raster layer for entry %1" ).arg( entry.ref );
186 }
187 if ( entry.bandNumber <= 0 || entry.bandNumber > entry.raster->bandCount() )
188 {
189 mLastError = QObject::tr( "Band number %1 is not valid for entry %2" ).arg( entry.bandNumber ).arg( entry.ref );
191 }
192 }
193
194 // Check if we need to read the raster as a whole (which is memory inefficient
195 // and not interruptible by the user) by checking if any raster matrix nodes are
196 // in the expression
197 bool requiresMatrix = !calcNode->findNodes( QgsRasterCalcNode::Type::tMatrix ).isEmpty();
198
199#ifdef HAVE_OPENCL
200 // Check for matrix nodes, GPU implementation does not support them
201 if ( QgsOpenClUtils::enabled() && QgsOpenClUtils::available() && !requiresMatrix )
202 {
203 return processCalculationGPU( std::move( calcNode ), feedback );
204 }
205#endif
206
207 //open output dataset for writing
208 GDALDriverH outputDriver = openOutputDriver();
209 if ( !outputDriver )
210 {
211 mLastError = QObject::tr( "Could not obtain driver for %1" ).arg( mOutputFormat );
213 }
214
215 gdal::dataset_unique_ptr outputDataset( openOutputFile( outputDriver ) );
216 if ( !outputDataset )
217 {
218 mLastError = QObject::tr( "Could not create output %1" ).arg( mOutputFile );
220 }
221
222 GDALSetProjection( outputDataset.get(), mOutputCrs.toWkt( Qgis::CrsWktVariant::PreferredGdal ).toLocal8Bit().data() );
223 GDALRasterBandH outputRasterBand = GDALGetRasterBand( outputDataset.get(), 1 );
224
225 GDALSetRasterNoDataValue( outputRasterBand, mNoDataValue );
226
227#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION( 3, 13, 0 )
228 const bool hasReportsDuringClose = GDALDatasetGetCloseReportsProgress( outputDataset.get() );
229 const double maxProgressDuringBlockWriting = hasReportsDuringClose ? 50.0 : 100.0;
230#else
231 constexpr double maxProgressDuringBlockWriting = 100.0;
232#endif
233
234 // Take the fast route (process one line at a time) if we can
235 if ( !requiresMatrix )
236 {
237 // Map of raster names -> blocks
238 std::map<QString, std::unique_ptr<QgsRasterBlock>> inputBlocks;
239 std::map<QString, QgsRasterCalculatorEntry> uniqueRasterEntries;
240 const QList<const QgsRasterCalcNode *> rasterRefNodes = calcNode->findNodes( QgsRasterCalcNode::Type::tRasterRef );
241 for ( const QgsRasterCalcNode *r : rasterRefNodes )
242 {
243 QString layerRef( r->toString().remove( 0, 1 ) );
244 layerRef.chop( 1 );
245 if ( !inputBlocks.count( layerRef ) )
246 {
247 for ( const QgsRasterCalculatorEntry &ref : std::as_const( mRasterEntries ) )
248 {
249 if ( ref.ref == layerRef )
250 {
251 uniqueRasterEntries[layerRef] = ref;
252 inputBlocks[layerRef] = std::make_unique<QgsRasterBlock>();
253 }
254 }
255 }
256 }
257
258 //read / write line by line
259 QMap<QString, QgsRasterBlock *> _rasterData;
260 // Cast to float
261 std::vector<float> castedResult( static_cast<size_t>( mNumOutputColumns ), 0 );
262 auto rowHeight = mOutputRectangle.height() / mNumOutputRows;
263 for ( size_t row = 0; row < static_cast<size_t>( mNumOutputRows ); ++row )
264 {
265 if ( feedback )
266 {
267 feedback->setProgress( maxProgressDuringBlockWriting * static_cast<double>( row ) / mNumOutputRows );
268 }
269
270 if ( feedback && feedback->isCanceled() )
271 {
272 break;
273 }
274
275 // Calculates the rect for a single row read
276 QgsRectangle rect( mOutputRectangle );
277 rect.setYMaximum( rect.yMaximum() - rowHeight * row );
278 rect.setYMinimum( rect.yMaximum() - rowHeight );
279
280 // Read rows into input blocks
281 for ( auto &layerRef : inputBlocks )
282 {
283 QgsRasterCalculatorEntry ref = uniqueRasterEntries[layerRef.first];
284 if ( ref.raster->crs() != mOutputCrs )
285 {
287 proj.setCrs( ref.raster->crs(), mOutputCrs, mTransformContext );
288 proj.setInput( ref.raster->dataProvider() );
290 layerRef.second.reset( proj.block( ref.bandNumber, rect, mNumOutputColumns, 1 ) );
291 }
292 else
293 {
294 layerRef.second.reset( ref.raster->dataProvider()->block( ref.bandNumber, rect, mNumOutputColumns, 1 ) );
295 }
296 }
297
298 // 1 row X mNumOutputColumns matrix
299 QgsRasterMatrix resultMatrix( mNumOutputColumns, 1, nullptr, mNoDataValue );
300
301 _rasterData.clear();
302 for ( const auto &layerRef : inputBlocks )
303 {
304 _rasterData.insert( layerRef.first, layerRef.second.get() );
305 }
306
307 if ( calcNode->calculate( _rasterData, resultMatrix, 0 ) )
308 {
309 std::copy( resultMatrix.data(), resultMatrix.data() + mNumOutputColumns, castedResult.begin() );
310 if ( GDALRasterIO( outputRasterBand, GF_Write, 0, row, mNumOutputColumns, 1, castedResult.data(), mNumOutputColumns, 1, GDT_Float32, 0, 0 ) != CE_None )
311 {
312 QgsDebugError( u"RasterIO error!"_s );
313 }
314 }
315 else
316 {
317 //delete the dataset without closing (because it is faster)
318 gdal::fast_delete_and_close( outputDataset, outputDriver, mOutputFile );
320 }
321 }
322
323 if ( feedback )
324 {
325 feedback->setProgress( maxProgressDuringBlockWriting );
326 }
327 }
328 else // Original code (memory inefficient route)
329 {
330 QMap<QString, QgsRasterBlock *> inputBlocks;
331 QVector<QgsRasterCalculatorEntry>::const_iterator it = mRasterEntries.constBegin();
332 for ( ; it != mRasterEntries.constEnd(); ++it )
333 {
334 std::unique_ptr<QgsRasterBlock> block;
335 // if crs transform needed
336 if ( it->raster->crs() != mOutputCrs )
337 {
339 proj.setCrs( it->raster->crs(), mOutputCrs, it->raster->transformContext() );
340 proj.setInput( it->raster->dataProvider() );
342
343 QgsRasterBlockFeedback *rasterBlockFeedback = new QgsRasterBlockFeedback();
344 QObject::connect( feedback, &QgsFeedback::canceled, rasterBlockFeedback, &QgsRasterBlockFeedback::cancel );
345 block.reset( proj.block( it->bandNumber, mOutputRectangle, mNumOutputColumns, mNumOutputRows, rasterBlockFeedback ) );
346 if ( rasterBlockFeedback->isCanceled() )
347 {
348 qDeleteAll( inputBlocks );
350 }
351 }
352 else
353 {
354 block.reset( it->raster->dataProvider()->block( it->bandNumber, mOutputRectangle, mNumOutputColumns, mNumOutputRows ) );
355 }
356 if ( block->isEmpty() )
357 {
358 mLastError = QObject::tr( "Could not allocate required memory for %1" ).arg( it->ref );
359 qDeleteAll( inputBlocks );
361 }
362 inputBlocks.insert( it->ref, block.release() );
363 }
364
365 QgsRasterMatrix resultMatrix;
366 resultMatrix.setNodataValue( mNoDataValue );
367
368 //read / write line by line
369 for ( int i = 0; i < mNumOutputRows; ++i )
370 {
371 if ( feedback )
372 {
373 feedback->setProgress( maxProgressDuringBlockWriting * static_cast<double>( i ) / mNumOutputRows );
374 }
375
376 if ( feedback && feedback->isCanceled() )
377 {
378 break;
379 }
380
381 if ( calcNode->calculate( inputBlocks, resultMatrix, i ) )
382 {
383 bool resultIsNumber = resultMatrix.isNumber();
384 float *calcData = new float[mNumOutputColumns];
385
386 for ( int j = 0; j < mNumOutputColumns; ++j )
387 {
388 calcData[j] = ( float ) ( resultIsNumber ? resultMatrix.number() : resultMatrix.data()[j] );
389 }
390
391 //write scanline to the dataset
392 if ( GDALRasterIO( outputRasterBand, GF_Write, 0, i, mNumOutputColumns, 1, calcData, mNumOutputColumns, 1, GDT_Float32, 0, 0 ) != CE_None )
393 {
394 QgsDebugError( u"RasterIO error!"_s );
395 }
396
397 delete[] calcData;
398 }
399 else
400 {
401 qDeleteAll( inputBlocks );
402 inputBlocks.clear();
403 gdal::fast_delete_and_close( outputDataset, outputDriver, mOutputFile );
405 }
406 }
407
408 if ( feedback )
409 {
410 feedback->setProgress( maxProgressDuringBlockWriting );
411 }
412
413 //close datasets and release memory
414 calcNode.reset();
415 qDeleteAll( inputBlocks );
416 inputBlocks.clear();
417 }
418
419 if ( feedback && feedback->isCanceled() )
420 {
421 //delete the dataset without closing (because it is faster)
422 gdal::fast_delete_and_close( outputDataset, outputDriver, mOutputFile );
424 }
425
426 GDALComputeRasterStatistics( outputRasterBand, true, nullptr, nullptr, nullptr, nullptr, GdalProgressCallback, feedback );
427
428#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION( 3, 13, 0 )
429 if ( hasReportsDuringClose && feedback )
430 {
431 QgsGdalProgressAdapter progress( feedback, maxProgressDuringBlockWriting );
432 if ( GDALDatasetRunCloseWithoutDestroyingEx( outputDataset.get(), QgsGdalProgressAdapter::progressCallback, &progress ) != CE_None )
433 {
435 }
436 }
437#endif
438
440}
441
442#ifdef HAVE_OPENCL
443QgsRasterCalculator::Result QgsRasterCalculator::processCalculationGPU( std::unique_ptr<QgsRasterCalcNode> calcNode, QgsFeedback *feedback )
444{
445 QString cExpression( calcNode->toString( true ) );
446
447 QList<const QgsRasterCalcNode *> nodeList( calcNode->findNodes( QgsRasterCalcNode::Type::tRasterRef ) );
448 QSet<QString> capturedTexts;
449 for ( const auto &r : std::as_const( nodeList ) )
450 {
451 QString s( r->toString().remove( 0, 1 ) );
452 s.chop( 1 );
453 capturedTexts.insert( s );
454 }
455
456 // Extract all references
457 struct LayerRef
458 {
459 QString name;
460 int band;
461 QgsRasterLayer *layer = nullptr;
462 QString varName;
463 QString typeName;
464 size_t index;
465 size_t bufferSize;
466 size_t dataSize;
467 };
468
469 // Collects all layers, band, name, varName and size information
470 std::vector<LayerRef> inputRefs;
471 size_t refCounter = 0;
472 for ( const auto &r : capturedTexts )
473 {
474 if ( r.startsWith( '"' ) )
475 continue;
476 QStringList parts( r.split( '@' ) );
477 if ( parts.count() != 2 )
478 continue;
479 bool ok = false;
480 LayerRef entry;
481 entry.name = r;
482 entry.band = parts[1].toInt( &ok );
483 for ( const auto &ref : std::as_const( mRasterEntries ) )
484 {
485 if ( ref.ref == entry.name )
486 entry.layer = ref.raster;
487 }
488 if ( !( entry.layer && entry.layer->dataProvider() && ok ) )
489 continue;
490 entry.dataSize = entry.layer->dataProvider()->dataTypeSize( entry.band );
491 switch ( entry.layer->dataProvider()->dataType( entry.band ) )
492 {
494 entry.typeName = u"unsigned char"_s;
495 break;
497 entry.typeName = u"char"_s;
498 break;
500 entry.typeName = u"unsigned short"_s;
501 break;
503 entry.typeName = u"short"_s;
504 break;
506 entry.typeName = u"unsigned int"_s;
507 break;
509 entry.typeName = u"int"_s;
510 break;
512 entry.typeName = u"float"_s;
513 break;
514 // FIXME: not sure all OpenCL implementations support double
515 // maybe safer to fall back to the CPU implementation
516 // after a compatibility check
518 entry.typeName = u"double"_s;
519 break;
528 }
529 entry.bufferSize = entry.dataSize * mNumOutputColumns;
530 entry.index = refCounter;
531 entry.varName = u"input_raster_%1_band_%2"_s.arg( refCounter++ ).arg( entry.band );
532 inputRefs.push_back( entry );
533 }
534
535 // May throw an openCL exception
536 try
537 {
538 // Prepare context and queue
539 cl::Context ctx( QgsOpenClUtils::context() );
540 cl::CommandQueue queue( QgsOpenClUtils::commandQueue() );
541
542 // Create the C expression
543 std::vector<cl::Buffer> inputBuffers;
544 inputBuffers.reserve( inputRefs.size() );
545 QStringList inputArgs;
546 for ( const auto &ref : inputRefs )
547 {
548 cExpression.replace( u"\"%1\""_s.arg( ref.name ), u"%1[i]"_s.arg( ref.varName ) );
549 inputArgs.append( u"__global %1 *%2"_s.arg( ref.typeName, ref.varName ) );
550 inputBuffers.push_back( cl::Buffer( ctx, CL_MEM_READ_ONLY, ref.bufferSize, nullptr, nullptr ) );
551 }
552
553 //qDebug() << cExpression;
554
555 // Create the program
556 QString programTemplate( R"CL(
557 // Inputs:
558 ##INPUT_DESC##
559 // Expression: ##EXPRESSION_ORIGINAL##
560 __kernel void rasterCalculator( ##INPUT##
561 __global float *resultLine
562 )
563 {
564 // Get the index of the current element
565 const int i = get_global_id(0);
566 // Check for nodata in input
567 if ( ##INPUT_NODATA_CHECK## )
568 resultLine[i] = -FLT_MAX;
569 // Expression
570 else
571 resultLine[i] = ##EXPRESSION##;
572 }
573 )CL" );
574
575 QStringList inputDesc;
576 QStringList inputNoDataCheck;
577 for ( const auto &ref : inputRefs )
578 {
579 inputDesc.append( u" // %1 = %2"_s.arg( ref.varName, ref.name ) );
580 if ( ref.layer->dataProvider()->sourceHasNoDataValue( ref.band ) )
581 {
582 inputNoDataCheck.append( u"(float) %1[i] == (float) %2"_s.arg( ref.varName, QString::number( ref.layer->dataProvider()->sourceNoDataValue( ref.band ), 'g', 10 ) ) );
583 }
584 }
585
586 programTemplate = programTemplate.replace( "##INPUT_NODATA_CHECK##"_L1, inputNoDataCheck.isEmpty() ? u"false"_s : inputNoDataCheck.join( " || "_L1 ) );
587 programTemplate = programTemplate.replace( "##INPUT_DESC##"_L1, inputDesc.join( '\n' ) );
588 programTemplate = programTemplate.replace( "##INPUT##"_L1, !inputArgs.isEmpty() ? ( inputArgs.join( ',' ).append( ',' ) ) : QChar( ' ' ) );
589 programTemplate = programTemplate.replace( "##EXPRESSION##"_L1, cExpression );
590 programTemplate = programTemplate.replace( "##EXPRESSION_ORIGINAL##"_L1, calcNode->toString() );
591
592 // qDebug() << programTemplate;
593
594 // Create a program from the kernel source
595 cl::Program program;
596 try
597 {
599 }
600 catch ( cl::Error &e )
601 {
602 mLastError = QObject::tr( "Error compiling OpenCL kernel: %1" ).arg( e.what() );
604 }
605
606 // Create the buffers, output is float32 (4 bytes)
607 // We assume size of float = 4 because that's the size used by OpenCL and IEEE 754
608 Q_ASSERT( sizeof( float ) == 4 );
609 std::size_t resultBufferSize( 4 * static_cast<size_t>( mNumOutputColumns ) );
610 cl::Buffer resultLineBuffer( ctx, CL_MEM_WRITE_ONLY, resultBufferSize, nullptr, nullptr );
611
612 auto kernel = cl::Kernel( program, "rasterCalculator" );
613
614 for ( unsigned int i = 0; i < inputBuffers.size(); i++ )
615 {
616 kernel.setArg( i, inputBuffers.at( i ) );
617 }
618 kernel.setArg( static_cast<unsigned int>( inputBuffers.size() ), resultLineBuffer );
619
620 QgsOpenClUtils::CPLAllocator<float> resultLine( static_cast<size_t>( mNumOutputColumns ) );
621
622 //open output dataset for writing
623 GDALDriverH outputDriver = openOutputDriver();
624 if ( !outputDriver )
625 {
626 mLastError = QObject::tr( "Could not obtain driver for %1" ).arg( mOutputFormat );
628 }
629
630 gdal::dataset_unique_ptr outputDataset( openOutputFile( outputDriver ) );
631 if ( !outputDataset )
632 {
633 mLastError = QObject::tr( "Could not create output %1" ).arg( mOutputFile );
635 }
636
637#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION( 3, 13, 0 )
638 const bool hasReportsDuringClose = GDALDatasetGetCloseReportsProgress( outputDataset.get() );
639 const double maxProgressDuringBlockWriting = hasReportsDuringClose ? 50.0 : 100.0;
640#else
641 constexpr double maxProgressDuringBlockWriting = 100.0;
642#endif
643
644 GDALSetProjection( outputDataset.get(), mOutputCrs.toWkt( Qgis::CrsWktVariant::PreferredGdal ).toLocal8Bit().data() );
645
646
647 GDALRasterBandH outputRasterBand = GDALGetRasterBand( outputDataset.get(), 1 );
648 if ( !outputRasterBand )
650
651 GDALSetRasterNoDataValue( outputRasterBand, mNoDataValue );
652
653 // Input block (buffer)
654 std::unique_ptr<QgsRasterBlock> block;
655
656 // Run kernel on all scanlines
657 auto rowHeight = mOutputRectangle.height() / mNumOutputRows;
658 for ( int line = 0; line < mNumOutputRows; line++ )
659 {
660 if ( feedback && feedback->isCanceled() )
661 {
662 break;
663 }
664
665 if ( feedback )
666 {
667 feedback->setProgress( maxProgressDuringBlockWriting * static_cast<double>( line ) / mNumOutputRows );
668 }
669
670 // Read lines from rasters into the buffers
671 for ( const auto &ref : inputRefs )
672 {
673 // Read one row
674 QgsRectangle rect( mOutputRectangle );
675 rect.setYMaximum( rect.yMaximum() - rowHeight * line );
676 rect.setYMinimum( rect.yMaximum() - rowHeight );
677
678 // TODO: check if this is too slow
679 // if crs transform needed
680 if ( ref.layer->crs() != mOutputCrs )
681 {
682 QgsRasterProjector proj;
683 proj.setCrs( ref.layer->crs(), mOutputCrs, ref.layer->transformContext() );
684 proj.setInput( ref.layer->dataProvider() );
686 block.reset( proj.block( ref.band, rect, mNumOutputColumns, 1 ) );
687 }
688 else
689 {
690 block.reset( ref.layer->dataProvider()->block( ref.band, rect, mNumOutputColumns, 1 ) );
691 }
692
693 //for ( int i = 0; i < mNumOutputColumns; i++ )
694 // qDebug() << "Input: " << line << i << ref.varName << " = " << block->value( 0, i );
695 //qDebug() << "Writing buffer " << ref.index;
696
697 Q_ASSERT( ref.bufferSize == static_cast<size_t>( block->data().size() ) );
698 queue.enqueueWriteBuffer( inputBuffers[ref.index], CL_TRUE, 0, ref.bufferSize, block->bits() );
699 }
700 // Run the kernel
701 queue.enqueueNDRangeKernel( kernel, 0, cl::NDRange( mNumOutputColumns ) );
702
703 // Write the result
704 queue.enqueueReadBuffer( resultLineBuffer, CL_TRUE, 0, resultBufferSize, resultLine.get() );
705
706 //for ( int i = 0; i < mNumOutputColumns; i++ )
707 // qDebug() << "Output: " << line << i << " = " << resultLine[i];
708
709 if ( GDALRasterIO( outputRasterBand, GF_Write, 0, line, mNumOutputColumns, 1, resultLine.get(), mNumOutputColumns, 1, GDT_Float32, 0, 0 ) != CE_None )
710 {
712 }
713 }
714
715 if ( feedback && feedback->isCanceled() )
716 {
717 //delete the dataset without closing (because it is faster)
718 gdal::fast_delete_and_close( outputDataset, outputDriver, mOutputFile );
720 }
721
722 inputBuffers.clear();
723
724 GDALComputeRasterStatistics( outputRasterBand, true, nullptr, nullptr, nullptr, nullptr, GdalProgressCallback, feedback );
725
726#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION( 3, 13, 0 )
727 if ( hasReportsDuringClose && feedback )
728 {
729 QgsGdalProgressAdapter progress( feedback, maxProgressDuringBlockWriting );
730 if ( GDALDatasetRunCloseWithoutDestroyingEx( outputDataset.get(), QgsGdalProgressAdapter::progressCallback, &progress ) != CE_None )
731 {
733 }
734 }
735#endif
736 }
737 catch ( cl::Error &e )
738 {
739 mLastError = e.what();
741 }
742
744}
745#endif
746
747GDALDriverH QgsRasterCalculator::openOutputDriver()
748{
749 //open driver
750 GDALDriverH outputDriver = GDALGetDriverByName( mOutputFormat.toLocal8Bit().data() );
751
752 if ( !outputDriver )
753 {
754 return outputDriver; //return nullptr, driver does not exist
755 }
756
757 if ( !QgsGdalUtils::supportsRasterCreate( outputDriver ) )
758 {
759 return nullptr; //driver exist, but it does not support the create operation
760 }
761
762 return outputDriver;
763}
764
765gdal::dataset_unique_ptr QgsRasterCalculator::openOutputFile( GDALDriverH outputDriver )
766{
767 //open output file
768 char **papszOptions = QgsGdalUtils::papszFromStringList( mCreationOptions );
769 gdal::dataset_unique_ptr outputDataset( GDALCreate( outputDriver, mOutputFile.toUtf8().constData(), mNumOutputColumns, mNumOutputRows, 1, GDT_Float32, papszOptions ) );
770 CSLDestroy( papszOptions );
771 if ( !outputDataset )
772 {
773 return nullptr;
774 }
775
776 //assign georef information
777 double geotransform[6];
778 outputGeoTransform( geotransform );
779 GDALSetGeoTransform( outputDataset.get(), geotransform );
780
781 return outputDataset;
782}
783
784void QgsRasterCalculator::outputGeoTransform( double *transform ) const
785{
786 transform[0] = mOutputRectangle.xMinimum();
787 transform[1] = mOutputRectangle.width() / mNumOutputColumns;
788 transform[2] = 0;
789 transform[3] = mOutputRectangle.yMaximum();
790 transform[4] = 0;
791 transform[5] = -mOutputRectangle.height() / mNumOutputRows;
792}
793
795{
796 return mLastError;
797}
798
799QVector<QgsRasterCalculatorEntry> QgsRasterCalculatorEntry::rasterEntries()
800{
801 QVector<QgsRasterCalculatorEntry> availableEntries;
802 const QMap<QString, QgsMapLayer *> &layers = QgsProject::instance()->mapLayers();
803
804 auto uniqueRasterBandIdentifier = [&]( QgsRasterCalculatorEntry &entry ) -> bool {
805 unsigned int i( 1 );
806 entry.ref = u"%1@%2"_s.arg( entry.raster ? entry.raster->name() : QString() ).arg( entry.bandNumber );
807 while ( true )
808 {
809 bool unique( true );
810 for ( const auto &ref : std::as_const( availableEntries ) )
811 {
812 // Safety belt
813 if ( !( entry.raster && ref.raster ) )
814 continue;
815 // Check if is another band of the same raster
816 if ( ref.raster->publicSource() == entry.raster->publicSource() )
817 {
818 if ( ref.bandNumber != entry.bandNumber )
819 {
820 continue;
821 }
822 else // a layer with the same data source was already added to the list
823 {
824 return false;
825 }
826 }
827 // If same name but different source
828 if ( ref.ref == entry.ref )
829 {
830 unique = false;
831 entry.ref = u"%1_%2@%3"_s.arg( entry.raster->name() ).arg( i++ ).arg( entry.bandNumber );
832 }
833 }
834 if ( unique )
835 return true;
836 }
837 };
838
839 QMap<QString, QgsMapLayer *>::const_iterator layerIt = layers.constBegin();
840 for ( ; layerIt != layers.constEnd(); ++layerIt )
841 {
842 QgsRasterLayer *rlayer = qobject_cast<QgsRasterLayer *>( layerIt.value() );
843 if ( rlayer && rlayer->dataProvider() && ( rlayer->dataProvider()->capabilities() & Qgis::RasterInterfaceCapability::Size ) )
844 {
845 //get number of bands
846 for ( int i = 0; i < rlayer->bandCount(); ++i )
847 {
849 entry.raster = rlayer;
850 entry.bandNumber = i + 1;
851 if ( !uniqueRasterBandIdentifier( entry ) )
852 break;
853 availableEntries.push_back( entry );
854 }
855 }
856 }
857 return availableEntries;
858}
@ Size
Original data source size (and thus resolution) is known, it is not always available,...
Definition qgis.h:5012
@ CInt32
Complex Int32.
Definition qgis.h:404
@ Float32
Thirty two bit floating point (float).
Definition qgis.h:401
@ CFloat64
Complex Float64.
Definition qgis.h:406
@ Int16
Sixteen bit signed integer (qint16).
Definition qgis.h:398
@ ARGB32_Premultiplied
Color, alpha, red, green, blue, 4 bytes the same as QImage::Format_ARGB32_Premultiplied.
Definition qgis.h:408
@ Int8
Eight bit signed integer (qint8) (added in QGIS 3.30).
Definition qgis.h:396
@ UInt16
Sixteen bit unsigned integer (quint16).
Definition qgis.h:397
@ Byte
Eight bit unsigned integer (quint8).
Definition qgis.h:395
@ UnknownDataType
Unknown or unspecified type.
Definition qgis.h:394
@ ARGB32
Color, alpha, red, green, blue, 4 bytes the same as QImage::Format_ARGB32.
Definition qgis.h:407
@ Int32
Thirty two bit signed integer (qint32).
Definition qgis.h:400
@ Float64
Sixty four bit floating point (double).
Definition qgis.h:402
@ CFloat32
Complex Float32.
Definition qgis.h:405
@ CInt16
Complex Int16.
Definition qgis.h:403
@ UInt32
Thirty two bit unsigned integer (quint32).
Definition qgis.h:399
@ PreferredGdal
Preferred format for conversion of CRS to WKT for use with the GDAL library.
Definition qgis.h:2530
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:56
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:65
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)