qgis / src / analysis / raster / qgsrastercalculator.cpp @ master
History | View | Annotate | Download (26.7 KB)
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 "qgsgdalutils.h" |
19 |
#include "qgsrastercalculator.h" |
20 |
#include "qgsrasterdataprovider.h" |
21 |
#include "qgsrasterinterface.h" |
22 |
#include "qgsrasterlayer.h" |
23 |
#include "qgsrastermatrix.h" |
24 |
#include "qgsrasterprojector.h" |
25 |
#include "qgsfeedback.h" |
26 |
#include "qgsogrutils.h" |
27 |
#include "qgsproject.h" |
28 |
|
29 |
#include <QFile> |
30 |
|
31 |
#include <cpl_string.h> |
32 |
#include <gdalwarper.h> |
33 |
|
34 |
#ifdef HAVE_OPENCL
|
35 |
#include "qgsopenclutils.h" |
36 |
#include "qgsgdalutils.h" |
37 |
#endif
|
38 |
|
39 |
|
40 |
//
|
41 |
// global callback function
|
42 |
//
|
43 |
int CPL_STDCALL GdalProgressCallback( double dfComplete, const char *pszMessage, void *pProgressArg ) |
44 |
{ |
45 |
Q_UNUSED( pszMessage ) |
46 |
|
47 |
static double sDfLastComplete = -1.0; |
48 |
|
49 |
QgsFeedback *feedback = static_cast<QgsFeedback *>( pProgressArg );
|
50 |
|
51 |
if ( sDfLastComplete > dfComplete )
|
52 |
{ |
53 |
if ( sDfLastComplete >= 1.0 ) |
54 |
sDfLastComplete = -1.0; |
55 |
else
|
56 |
sDfLastComplete = dfComplete; |
57 |
} |
58 |
|
59 |
if ( std::floor( sDfLastComplete * 10 ) != std::floor( dfComplete * 10 ) ) |
60 |
{ |
61 |
if ( feedback )
|
62 |
feedback->setProgress( dfComplete * 100 );
|
63 |
} |
64 |
sDfLastComplete = dfComplete; |
65 |
|
66 |
if ( feedback && feedback->isCanceled() )
|
67 |
return false; |
68 |
|
69 |
return true; |
70 |
} |
71 |
|
72 |
QgsRasterCalculator::QgsRasterCalculator( const QString &formulaString, const QString &outputFile, const QString &outputFormat, const QgsRectangle &outputExtent, int nOutputColumns, int nOutputRows, const QVector<QgsRasterCalculatorEntry> &rasterEntries, const QgsCoordinateTransformContext &transformContext ) |
73 |
: mFormulaString( formulaString ) |
74 |
, mOutputFile( outputFile ) |
75 |
, mOutputFormat( outputFormat ) |
76 |
, mOutputRectangle( outputExtent ) |
77 |
, mNumOutputColumns( nOutputColumns ) |
78 |
, mNumOutputRows( nOutputRows ) |
79 |
, mRasterEntries( rasterEntries ) |
80 |
, mTransformContext( transformContext ) |
81 |
{ |
82 |
} |
83 |
|
84 |
QgsRasterCalculator::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 ) |
85 |
: mFormulaString( formulaString ) |
86 |
, mOutputFile( outputFile ) |
87 |
, mOutputFormat( outputFormat ) |
88 |
, mOutputRectangle( outputExtent ) |
89 |
, mOutputCrs( outputCrs ) |
90 |
, mNumOutputColumns( nOutputColumns ) |
91 |
, mNumOutputRows( nOutputRows ) |
92 |
, mRasterEntries( rasterEntries ) |
93 |
, mTransformContext( transformContext ) |
94 |
{ |
95 |
} |
96 |
|
97 |
// Deprecated!
|
98 |
QgsRasterCalculator::QgsRasterCalculator( const QString &formulaString, const QString &outputFile, const QString &outputFormat, const QgsRectangle &outputExtent, int nOutputColumns, int nOutputRows, const QVector<QgsRasterCalculatorEntry> &rasterEntries ) |
99 |
: mFormulaString( formulaString ) |
100 |
, mOutputFile( outputFile ) |
101 |
, mOutputFormat( outputFormat ) |
102 |
, mOutputRectangle( outputExtent ) |
103 |
, mNumOutputColumns( nOutputColumns ) |
104 |
, mNumOutputRows( nOutputRows ) |
105 |
, mRasterEntries( rasterEntries ) |
106 |
{ |
107 |
//default to first layer's crs
|
108 |
mOutputCrs = mRasterEntries.at( 0 ).raster->crs();
|
109 |
mTransformContext = QgsProject::instance()->transformContext(); |
110 |
} |
111 |
|
112 |
|
113 |
// Deprecated!
|
114 |
QgsRasterCalculator::QgsRasterCalculator( const QString &formulaString, const QString &outputFile, const QString &outputFormat, const QgsRectangle &outputExtent, const QgsCoordinateReferenceSystem &outputCrs, int nOutputColumns, int nOutputRows, const QVector<QgsRasterCalculatorEntry> &rasterEntries ) |
115 |
: mFormulaString( formulaString ) |
116 |
, mOutputFile( outputFile ) |
117 |
, mOutputFormat( outputFormat ) |
118 |
, mOutputRectangle( outputExtent ) |
119 |
, mOutputCrs( outputCrs ) |
120 |
, mNumOutputColumns( nOutputColumns ) |
121 |
, mNumOutputRows( nOutputRows ) |
122 |
, mRasterEntries( rasterEntries ) |
123 |
, mTransformContext( QgsProject::instance()->transformContext() ) |
124 |
{ |
125 |
} |
126 |
|
127 |
QgsRasterCalculator::Result QgsRasterCalculator::processCalculation( QgsFeedback *feedback ) |
128 |
{ |
129 |
mLastError.clear(); |
130 |
|
131 |
//prepare search string / tree
|
132 |
std::unique_ptr<QgsRasterCalcNode> calcNode( QgsRasterCalcNode::parseRasterCalcString( mFormulaString, mLastError ) ); |
133 |
if ( !calcNode )
|
134 |
{ |
135 |
//error
|
136 |
return ParserError;
|
137 |
} |
138 |
|
139 |
// Check input layers and bands
|
140 |
for ( const auto &entry : std::as_const( mRasterEntries ) ) |
141 |
{ |
142 |
if ( !entry.raster ) // no raster layer in entry |
143 |
{ |
144 |
mLastError = QObject::tr( "No raster layer for entry %1" ).arg( entry.ref );
|
145 |
return InputLayerError;
|
146 |
} |
147 |
if ( entry.bandNumber <= 0 || entry.bandNumber > entry.raster->bandCount() ) |
148 |
{ |
149 |
mLastError = QObject::tr( "Band number %1 is not valid for entry %2" ).arg( entry.bandNumber ).arg( entry.ref );
|
150 |
return BandError;
|
151 |
} |
152 |
} |
153 |
|
154 |
// Check if we need to read the raster as a whole (which is memory inefficient
|
155 |
// and not interruptible by the user) by checking if any raster matrix nodes are
|
156 |
// in the expression
|
157 |
bool requiresMatrix = !calcNode->findNodes( QgsRasterCalcNode::Type::tMatrix ).isEmpty();
|
158 |
|
159 |
#ifdef HAVE_OPENCL
|
160 |
// Check for matrix nodes, GPU implementation does not support them
|
161 |
if ( QgsOpenClUtils::enabled() && QgsOpenClUtils::available() && !requiresMatrix )
|
162 |
{ |
163 |
return processCalculationGPU( std::move( calcNode ), feedback );
|
164 |
} |
165 |
#endif
|
166 |
|
167 |
//open output dataset for writing
|
168 |
GDALDriverH outputDriver = openOutputDriver(); |
169 |
if ( !outputDriver )
|
170 |
{ |
171 |
mLastError = QObject::tr( "Could not obtain driver for %1" ).arg( mOutputFormat );
|
172 |
return CreateOutputError;
|
173 |
} |
174 |
|
175 |
gdal::dataset_unique_ptr outputDataset( openOutputFile( outputDriver ) ); |
176 |
if ( !outputDataset )
|
177 |
{ |
178 |
mLastError = QObject::tr( "Could not create output %1" ).arg( mOutputFile );
|
179 |
return CreateOutputError;
|
180 |
} |
181 |
|
182 |
GDALSetProjection( outputDataset.get(), mOutputCrs.toWkt( Qgis::CrsWktVariant::PreferredGdal ).toLocal8Bit().data() ); |
183 |
GDALRasterBandH outputRasterBand = GDALGetRasterBand( outputDataset.get(), 1 );
|
184 |
|
185 |
float outputNodataValue = -FLT_MAX;
|
186 |
GDALSetRasterNoDataValue( outputRasterBand, outputNodataValue ); |
187 |
|
188 |
|
189 |
// Take the fast route (process one line at a time) if we can
|
190 |
if ( !requiresMatrix )
|
191 |
{ |
192 |
// Map of raster names -> blocks
|
193 |
std::map<QString, std::unique_ptr<QgsRasterBlock>> inputBlocks; |
194 |
std::map<QString, QgsRasterCalculatorEntry> uniqueRasterEntries; |
195 |
const QList<const QgsRasterCalcNode *> rasterRefNodes = calcNode->findNodes( QgsRasterCalcNode::Type::tRasterRef ); |
196 |
for ( const QgsRasterCalcNode *r : rasterRefNodes ) |
197 |
{ |
198 |
QString layerRef( r->toString().remove( 0, 1 ) ); |
199 |
layerRef.chop( 1 );
|
200 |
if ( !inputBlocks.count( layerRef ) )
|
201 |
{ |
202 |
for ( const QgsRasterCalculatorEntry &ref : std::as_const( mRasterEntries ) ) |
203 |
{ |
204 |
if ( ref.ref == layerRef )
|
205 |
{ |
206 |
uniqueRasterEntries[layerRef] = ref; |
207 |
inputBlocks[layerRef] = std::make_unique<QgsRasterBlock>(); |
208 |
} |
209 |
} |
210 |
} |
211 |
} |
212 |
|
213 |
//read / write line by line
|
214 |
QMap<QString, QgsRasterBlock *> _rasterData; |
215 |
// Cast to float
|
216 |
std::vector<float> castedResult( static_cast<size_t>( mNumOutputColumns ), 0 ); |
217 |
auto rowHeight = mOutputRectangle.height() / mNumOutputRows;
|
218 |
for ( size_t row = 0; row < static_cast<size_t>( mNumOutputRows ); ++row ) |
219 |
{ |
220 |
if ( feedback )
|
221 |
{ |
222 |
feedback->setProgress( 100.0 * static_cast<double>( row ) / mNumOutputRows ); |
223 |
} |
224 |
|
225 |
if ( feedback && feedback->isCanceled() )
|
226 |
{ |
227 |
break;
|
228 |
} |
229 |
|
230 |
// Calculates the rect for a single row read
|
231 |
QgsRectangle rect( mOutputRectangle ); |
232 |
rect.setYMaximum( rect.yMaximum() - rowHeight * row ); |
233 |
rect.setYMinimum( rect.yMaximum() - rowHeight ); |
234 |
|
235 |
// Read rows into input blocks
|
236 |
for ( auto &layerRef : inputBlocks ) |
237 |
{ |
238 |
QgsRasterCalculatorEntry ref = uniqueRasterEntries[layerRef.first]; |
239 |
if ( ref.raster->crs() != mOutputCrs )
|
240 |
{ |
241 |
QgsRasterProjector proj; |
242 |
proj.setCrs( ref.raster->crs(), mOutputCrs, mTransformContext ); |
243 |
proj.setInput( ref.raster->dataProvider() ); |
244 |
proj.setPrecision( QgsRasterProjector::Exact ); |
245 |
layerRef.second.reset( proj.block( ref.bandNumber, rect, mNumOutputColumns, 1 ) );
|
246 |
} |
247 |
else
|
248 |
{ |
249 |
layerRef.second.reset( ref.raster->dataProvider()->block( ref.bandNumber, rect, mNumOutputColumns, 1 ) );
|
250 |
} |
251 |
} |
252 |
|
253 |
// 1 row X mNumOutputColumns matrix
|
254 |
QgsRasterMatrix resultMatrix( mNumOutputColumns, 1, nullptr, outputNodataValue );
|
255 |
|
256 |
_rasterData.clear(); |
257 |
for ( const auto &layerRef : inputBlocks ) |
258 |
{ |
259 |
_rasterData.insert( layerRef.first, layerRef.second.get() ); |
260 |
} |
261 |
|
262 |
if ( calcNode->calculate( _rasterData, resultMatrix, 0 ) ) |
263 |
{ |
264 |
std::copy( resultMatrix.data(), resultMatrix.data() + mNumOutputColumns, castedResult.begin() ); |
265 |
if ( GDALRasterIO( outputRasterBand, GF_Write, 0, row, mNumOutputColumns, 1, castedResult.data(), mNumOutputColumns, 1, GDT_Float32, 0, 0 ) != CE_None ) |
266 |
{ |
267 |
QgsDebugError( QStringLiteral( "RasterIO error!" ) );
|
268 |
} |
269 |
} |
270 |
else
|
271 |
{ |
272 |
//delete the dataset without closing (because it is faster)
|
273 |
gdal::fast_delete_and_close( outputDataset, outputDriver, mOutputFile ); |
274 |
return CalculationError;
|
275 |
} |
276 |
} |
277 |
|
278 |
if ( feedback )
|
279 |
{ |
280 |
feedback->setProgress( 100.0 ); |
281 |
} |
282 |
} |
283 |
else // Original code (memory inefficient route) |
284 |
{ |
285 |
QMap<QString, QgsRasterBlock *> inputBlocks; |
286 |
QVector<QgsRasterCalculatorEntry>::const_iterator it = mRasterEntries.constBegin(); |
287 |
for ( ; it != mRasterEntries.constEnd(); ++it )
|
288 |
{ |
289 |
std::unique_ptr<QgsRasterBlock> block; |
290 |
// if crs transform needed
|
291 |
if ( it->raster->crs() != mOutputCrs )
|
292 |
{ |
293 |
QgsRasterProjector proj; |
294 |
proj.setCrs( it->raster->crs(), mOutputCrs, it->raster->transformContext() ); |
295 |
proj.setInput( it->raster->dataProvider() ); |
296 |
proj.setPrecision( QgsRasterProjector::Exact ); |
297 |
|
298 |
QgsRasterBlockFeedback *rasterBlockFeedback = new QgsRasterBlockFeedback();
|
299 |
QObject::connect( feedback, &QgsFeedback::canceled, rasterBlockFeedback, &QgsRasterBlockFeedback::cancel ); |
300 |
block.reset( proj.block( it->bandNumber, mOutputRectangle, mNumOutputColumns, mNumOutputRows, rasterBlockFeedback ) ); |
301 |
if ( rasterBlockFeedback->isCanceled() )
|
302 |
{ |
303 |
qDeleteAll( inputBlocks ); |
304 |
return Canceled;
|
305 |
} |
306 |
} |
307 |
else
|
308 |
{ |
309 |
block.reset( it->raster->dataProvider()->block( it->bandNumber, mOutputRectangle, mNumOutputColumns, mNumOutputRows ) ); |
310 |
} |
311 |
if ( block->isEmpty() )
|
312 |
{ |
313 |
mLastError = QObject::tr( "Could not allocate required memory for %1" ).arg( it->ref );
|
314 |
qDeleteAll( inputBlocks ); |
315 |
return MemoryError;
|
316 |
} |
317 |
inputBlocks.insert( it->ref, block.release() ); |
318 |
} |
319 |
|
320 |
QgsRasterMatrix resultMatrix; |
321 |
resultMatrix.setNodataValue( outputNodataValue ); |
322 |
|
323 |
//read / write line by line
|
324 |
for ( int i = 0; i < mNumOutputRows; ++i ) |
325 |
{ |
326 |
if ( feedback )
|
327 |
{ |
328 |
feedback->setProgress( 100.0 * static_cast<double>( i ) / mNumOutputRows ); |
329 |
} |
330 |
|
331 |
if ( feedback && feedback->isCanceled() )
|
332 |
{ |
333 |
break;
|
334 |
} |
335 |
|
336 |
if ( calcNode->calculate( inputBlocks, resultMatrix, i ) )
|
337 |
{ |
338 |
bool resultIsNumber = resultMatrix.isNumber();
|
339 |
float *calcData = new float[mNumOutputColumns]; |
340 |
|
341 |
for ( int j = 0; j < mNumOutputColumns; ++j ) |
342 |
{ |
343 |
calcData[j] = ( float ) ( resultIsNumber ? resultMatrix.number() : resultMatrix.data()[j] );
|
344 |
} |
345 |
|
346 |
//write scanline to the dataset
|
347 |
if ( GDALRasterIO( outputRasterBand, GF_Write, 0, i, mNumOutputColumns, 1, calcData, mNumOutputColumns, 1, GDT_Float32, 0, 0 ) != CE_None ) |
348 |
{ |
349 |
QgsDebugError( QStringLiteral( "RasterIO error!" ) );
|
350 |
} |
351 |
|
352 |
delete[] calcData;
|
353 |
} |
354 |
else
|
355 |
{ |
356 |
qDeleteAll( inputBlocks ); |
357 |
inputBlocks.clear(); |
358 |
gdal::fast_delete_and_close( outputDataset, outputDriver, mOutputFile ); |
359 |
return CalculationError;
|
360 |
} |
361 |
} |
362 |
|
363 |
if ( feedback )
|
364 |
{ |
365 |
feedback->setProgress( 100.0 ); |
366 |
} |
367 |
|
368 |
//close datasets and release memory
|
369 |
calcNode.reset(); |
370 |
qDeleteAll( inputBlocks ); |
371 |
inputBlocks.clear(); |
372 |
} |
373 |
|
374 |
if ( feedback && feedback->isCanceled() )
|
375 |
{ |
376 |
//delete the dataset without closing (because it is faster)
|
377 |
gdal::fast_delete_and_close( outputDataset, outputDriver, mOutputFile ); |
378 |
return Canceled;
|
379 |
} |
380 |
|
381 |
GDALComputeRasterStatistics( outputRasterBand, true, nullptr, nullptr, nullptr, nullptr, GdalProgressCallback, feedback );
|
382 |
|
383 |
return Success;
|
384 |
} |
385 |
|
386 |
#ifdef HAVE_OPENCL
|
387 |
QgsRasterCalculator::Result QgsRasterCalculator::processCalculationGPU( std::unique_ptr<QgsRasterCalcNode> calcNode, QgsFeedback *feedback ) |
388 |
{ |
389 |
QString cExpression( calcNode->toString( true ) );
|
390 |
|
391 |
QList<const QgsRasterCalcNode *> nodeList( calcNode->findNodes( QgsRasterCalcNode::Type::tRasterRef ) );
|
392 |
QSet<QString> capturedTexts; |
393 |
for ( const auto &r : std::as_const( nodeList ) ) |
394 |
{ |
395 |
QString s( r->toString().remove( 0, 1 ) ); |
396 |
s.chop( 1 );
|
397 |
capturedTexts.insert( s ); |
398 |
} |
399 |
|
400 |
// Extract all references
|
401 |
struct LayerRef
|
402 |
{ |
403 |
QString name; |
404 |
int band;
|
405 |
QgsRasterLayer *layer = nullptr; |
406 |
QString varName; |
407 |
QString typeName; |
408 |
size_t index; |
409 |
size_t bufferSize; |
410 |
size_t dataSize; |
411 |
}; |
412 |
|
413 |
// Collects all layers, band, name, varName and size information
|
414 |
std::vector<LayerRef> inputRefs; |
415 |
size_t refCounter = 0;
|
416 |
for ( const auto &r : capturedTexts ) |
417 |
{ |
418 |
if ( r.startsWith( '"' ) ) |
419 |
continue;
|
420 |
QStringList parts( r.split( '@' ) );
|
421 |
if ( parts.count() != 2 ) |
422 |
continue;
|
423 |
bool ok = false; |
424 |
LayerRef entry; |
425 |
entry.name = r; |
426 |
entry.band = parts[1].toInt( &ok );
|
427 |
for ( const auto &ref : std::as_const( mRasterEntries ) ) |
428 |
{ |
429 |
if ( ref.ref == entry.name )
|
430 |
entry.layer = ref.raster; |
431 |
} |
432 |
if ( !( entry.layer && entry.layer->dataProvider() && ok ) )
|
433 |
continue;
|
434 |
entry.dataSize = entry.layer->dataProvider()->dataTypeSize( entry.band ); |
435 |
switch ( entry.layer->dataProvider()->dataType( entry.band ) )
|
436 |
{ |
437 |
case Qgis::DataType::Byte:
|
438 |
entry.typeName = QStringLiteral( "unsigned char" );
|
439 |
break;
|
440 |
case Qgis::DataType::Int8:
|
441 |
entry.typeName = QStringLiteral( "signed char" );
|
442 |
break;
|
443 |
case Qgis::DataType::UInt16:
|
444 |
entry.typeName = QStringLiteral( "unsigned int" );
|
445 |
break;
|
446 |
case Qgis::DataType::Int16:
|
447 |
entry.typeName = QStringLiteral( "short" );
|
448 |
break;
|
449 |
case Qgis::DataType::UInt32:
|
450 |
entry.typeName = QStringLiteral( "unsigned int" );
|
451 |
break;
|
452 |
case Qgis::DataType::Int32:
|
453 |
entry.typeName = QStringLiteral( "int" );
|
454 |
break;
|
455 |
case Qgis::DataType::Float32:
|
456 |
entry.typeName = QStringLiteral( "float" );
|
457 |
break;
|
458 |
// FIXME: not sure all OpenCL implementations support double
|
459 |
// maybe safer to fall back to the CPU implementation
|
460 |
// after a compatibility check
|
461 |
case Qgis::DataType::Float64:
|
462 |
entry.typeName = QStringLiteral( "double" );
|
463 |
break;
|
464 |
case Qgis::DataType::CInt16:
|
465 |
case Qgis::DataType::CInt32:
|
466 |
case Qgis::DataType::CFloat32:
|
467 |
case Qgis::DataType::CFloat64:
|
468 |
case Qgis::DataType::ARGB32:
|
469 |
case Qgis::DataType::ARGB32_Premultiplied:
|
470 |
case Qgis::DataType::UnknownDataType:
|
471 |
return BandError;
|
472 |
} |
473 |
entry.bufferSize = entry.dataSize * mNumOutputColumns; |
474 |
entry.index = refCounter; |
475 |
entry.varName = QStringLiteral( "input_raster_%1_band_%2" )
|
476 |
.arg( refCounter++ ) |
477 |
.arg( entry.band ); |
478 |
inputRefs.push_back( entry ); |
479 |
} |
480 |
|
481 |
// May throw an openCL exception
|
482 |
try
|
483 |
{ |
484 |
// Prepare context and queue
|
485 |
cl::Context ctx( QgsOpenClUtils::context() ); |
486 |
cl::CommandQueue queue( QgsOpenClUtils::commandQueue() ); |
487 |
|
488 |
// Create the C expression
|
489 |
std::vector<cl::Buffer> inputBuffers; |
490 |
inputBuffers.reserve( inputRefs.size() ); |
491 |
QStringList inputArgs; |
492 |
for ( const auto &ref : inputRefs ) |
493 |
{ |
494 |
cExpression.replace( QStringLiteral( "\"%1\"" ).arg( ref.name ), QStringLiteral( "%1[i]" ).arg( ref.varName ) ); |
495 |
inputArgs.append( QStringLiteral( "__global %1 *%2" )
|
496 |
.arg( ref.typeName, ref.varName ) ); |
497 |
inputBuffers.push_back( cl::Buffer( ctx, CL_MEM_READ_ONLY, ref.bufferSize, nullptr, nullptr ) ); |
498 |
} |
499 |
|
500 |
//qDebug() << cExpression;
|
501 |
|
502 |
// Create the program
|
503 |
QString programTemplate( R"CL(
|
504 |
// Inputs:
|
505 |
##INPUT_DESC##
|
506 |
// Expression: ##EXPRESSION_ORIGINAL##
|
507 |
__kernel void rasterCalculator( ##INPUT##
|
508 |
__global float *resultLine
|
509 |
)
|
510 |
{
|
511 |
// Get the index of the current element
|
512 |
const int i = get_global_id(0);
|
513 |
// Check for nodata in input
|
514 |
if ( ##INPUT_NODATA_CHECK## )
|
515 |
resultLine[i] = -FLT_MAX;
|
516 |
// Expression
|
517 |
else
|
518 |
resultLine[i] = ##EXPRESSION##;
|
519 |
}
|
520 |
)CL" );
|
521 |
|
522 |
QStringList inputDesc; |
523 |
QStringList inputNoDataCheck; |
524 |
for ( const auto &ref : inputRefs ) |
525 |
{ |
526 |
inputDesc.append( QStringLiteral( " // %1 = %2" ).arg( ref.varName, ref.name ) );
|
527 |
if ( ref.layer->dataProvider()->sourceHasNoDataValue( ref.band ) )
|
528 |
{ |
529 |
inputNoDataCheck.append( QStringLiteral( "(float) %1[i] == (float) %2" ).arg( ref.varName, QString::number( ref.layer->dataProvider()->sourceNoDataValue( ref.band ), 'g', 10 ) ) ); |
530 |
} |
531 |
} |
532 |
|
533 |
programTemplate = programTemplate.replace( QLatin1String( "##INPUT_NODATA_CHECK##" ), inputNoDataCheck.isEmpty() ? QStringLiteral( "false" ) : inputNoDataCheck.join( QLatin1String( " || " ) ) ); |
534 |
programTemplate = programTemplate.replace( QLatin1String( "##INPUT_DESC##" ), inputDesc.join( '\n' ) ); |
535 |
programTemplate = programTemplate.replace( QLatin1String( "##INPUT##" ), !inputArgs.isEmpty() ? ( inputArgs.join( ',' ).append( ',' ) ) : QChar( ' ' ) ); |
536 |
programTemplate = programTemplate.replace( QLatin1String( "##EXPRESSION##" ), cExpression );
|
537 |
programTemplate = programTemplate.replace( QLatin1String( "##EXPRESSION_ORIGINAL##" ), calcNode->toString() );
|
538 |
|
539 |
//qDebug() << programTemplate;
|
540 |
|
541 |
// Create a program from the kernel source
|
542 |
cl::Program program( QgsOpenClUtils::buildProgram( programTemplate, QgsOpenClUtils::ExceptionBehavior::Throw ) ); |
543 |
|
544 |
// Create the buffers, output is float32 (4 bytes)
|
545 |
// We assume size of float = 4 because that's the size used by OpenCL and IEEE 754
|
546 |
Q_ASSERT( sizeof( float ) == 4 ); |
547 |
std::size_t resultBufferSize( 4 * static_cast<size_t>( mNumOutputColumns ) ); |
548 |
cl::Buffer resultLineBuffer( ctx, CL_MEM_WRITE_ONLY, resultBufferSize, nullptr, nullptr ); |
549 |
|
550 |
auto kernel = cl::Kernel( program, "rasterCalculator" ); |
551 |
|
552 |
for ( unsigned int i = 0; i < inputBuffers.size(); i++ ) |
553 |
{ |
554 |
kernel.setArg( i, inputBuffers.at( i ) ); |
555 |
} |
556 |
kernel.setArg( static_cast<unsigned int>( inputBuffers.size() ), resultLineBuffer ); |
557 |
|
558 |
QgsOpenClUtils::CPLAllocator<float> resultLine( static_cast<size_t>( mNumOutputColumns ) ); |
559 |
|
560 |
//open output dataset for writing
|
561 |
GDALDriverH outputDriver = openOutputDriver(); |
562 |
if ( !outputDriver )
|
563 |
{ |
564 |
mLastError = QObject::tr( "Could not obtain driver for %1" ).arg( mOutputFormat );
|
565 |
return CreateOutputError;
|
566 |
} |
567 |
|
568 |
gdal::dataset_unique_ptr outputDataset( openOutputFile( outputDriver ) ); |
569 |
if ( !outputDataset )
|
570 |
{ |
571 |
mLastError = QObject::tr( "Could not create output %1" ).arg( mOutputFile );
|
572 |
return CreateOutputError;
|
573 |
} |
574 |
|
575 |
GDALSetProjection( outputDataset.get(), mOutputCrs.toWkt( Qgis::CrsWktVariant::PreferredGdal ).toLocal8Bit().data() ); |
576 |
|
577 |
|
578 |
GDALRasterBandH outputRasterBand = GDALGetRasterBand( outputDataset.get(), 1 );
|
579 |
if ( !outputRasterBand )
|
580 |
return BandError;
|
581 |
|
582 |
const float outputNodataValue = -FLT_MAX; |
583 |
GDALSetRasterNoDataValue( outputRasterBand, outputNodataValue ); |
584 |
|
585 |
// Input block (buffer)
|
586 |
std::unique_ptr<QgsRasterBlock> block; |
587 |
|
588 |
// Run kernel on all scanlines
|
589 |
auto rowHeight = mOutputRectangle.height() / mNumOutputRows;
|
590 |
for ( int line = 0; line < mNumOutputRows; line++ ) |
591 |
{ |
592 |
if ( feedback && feedback->isCanceled() )
|
593 |
{ |
594 |
break;
|
595 |
} |
596 |
|
597 |
if ( feedback )
|
598 |
{ |
599 |
feedback->setProgress( 100.0 * static_cast<double>( line ) / mNumOutputRows ); |
600 |
} |
601 |
|
602 |
// Read lines from rasters into the buffers
|
603 |
for ( const auto &ref : inputRefs ) |
604 |
{ |
605 |
// Read one row
|
606 |
QgsRectangle rect( mOutputRectangle ); |
607 |
rect.setYMaximum( rect.yMaximum() - rowHeight * line ); |
608 |
rect.setYMinimum( rect.yMaximum() - rowHeight ); |
609 |
|
610 |
// TODO: check if this is too slow
|
611 |
// if crs transform needed
|
612 |
if ( ref.layer->crs() != mOutputCrs )
|
613 |
{ |
614 |
QgsRasterProjector proj; |
615 |
proj.setCrs( ref.layer->crs(), mOutputCrs, ref.layer->transformContext() ); |
616 |
proj.setInput( ref.layer->dataProvider() ); |
617 |
proj.setPrecision( QgsRasterProjector::Exact ); |
618 |
block.reset( proj.block( ref.band, rect, mNumOutputColumns, 1 ) );
|
619 |
} |
620 |
else
|
621 |
{ |
622 |
block.reset( ref.layer->dataProvider()->block( ref.band, rect, mNumOutputColumns, 1 ) );
|
623 |
} |
624 |
|
625 |
//for ( int i = 0; i < mNumOutputColumns; i++ )
|
626 |
// qDebug() << "Input: " << line << i << ref.varName << " = " << block->value( 0, i );
|
627 |
//qDebug() << "Writing buffer " << ref.index;
|
628 |
|
629 |
Q_ASSERT( ref.bufferSize == static_cast<size_t>( block->data().size() ) );
|
630 |
queue.enqueueWriteBuffer( inputBuffers[ref.index], CL_TRUE, 0, ref.bufferSize, block->bits() );
|
631 |
} |
632 |
// Run the kernel
|
633 |
queue.enqueueNDRangeKernel( |
634 |
kernel, |
635 |
0,
|
636 |
cl::NDRange( mNumOutputColumns ) |
637 |
); |
638 |
|
639 |
// Write the result
|
640 |
queue.enqueueReadBuffer( resultLineBuffer, CL_TRUE, 0, resultBufferSize, resultLine.get() );
|
641 |
|
642 |
//for ( int i = 0; i < mNumOutputColumns; i++ )
|
643 |
// qDebug() << "Output: " << line << i << " = " << resultLine[i];
|
644 |
|
645 |
if ( GDALRasterIO( outputRasterBand, GF_Write, 0, line, mNumOutputColumns, 1, resultLine.get(), mNumOutputColumns, 1, GDT_Float32, 0, 0 ) != CE_None ) |
646 |
{ |
647 |
return CreateOutputError;
|
648 |
} |
649 |
} |
650 |
|
651 |
if ( feedback && feedback->isCanceled() )
|
652 |
{ |
653 |
//delete the dataset without closing (because it is faster)
|
654 |
gdal::fast_delete_and_close( outputDataset, outputDriver, mOutputFile ); |
655 |
return Canceled;
|
656 |
} |
657 |
|
658 |
inputBuffers.clear(); |
659 |
|
660 |
GDALComputeRasterStatistics( outputRasterBand, true, nullptr, nullptr, nullptr, nullptr, GdalProgressCallback, feedback );
|
661 |
} |
662 |
catch ( cl::Error &e )
|
663 |
{ |
664 |
mLastError = e.what(); |
665 |
return CreateOutputError;
|
666 |
} |
667 |
|
668 |
return Success;
|
669 |
} |
670 |
#endif
|
671 |
|
672 |
GDALDriverH QgsRasterCalculator::openOutputDriver() |
673 |
{ |
674 |
//open driver
|
675 |
GDALDriverH outputDriver = GDALGetDriverByName( mOutputFormat.toLocal8Bit().data() ); |
676 |
|
677 |
if ( !outputDriver )
|
678 |
{ |
679 |
return outputDriver; //return nullptr, driver does not exist |
680 |
} |
681 |
|
682 |
if ( !QgsGdalUtils::supportsRasterCreate( outputDriver ) )
|
683 |
{ |
684 |
return nullptr; //driver exist, but it does not support the create operation |
685 |
} |
686 |
|
687 |
return outputDriver;
|
688 |
} |
689 |
|
690 |
gdal::dataset_unique_ptr QgsRasterCalculator::openOutputFile( GDALDriverH outputDriver ) |
691 |
{ |
692 |
//open output file
|
693 |
char **papszOptions = nullptr;
|
694 |
gdal::dataset_unique_ptr outputDataset( GDALCreate( outputDriver, mOutputFile.toUtf8().constData(), mNumOutputColumns, mNumOutputRows, 1, GDT_Float32, papszOptions ) );
|
695 |
if ( !outputDataset )
|
696 |
{ |
697 |
return nullptr;
|
698 |
} |
699 |
|
700 |
//assign georef information
|
701 |
double geotransform[6]; |
702 |
outputGeoTransform( geotransform ); |
703 |
GDALSetGeoTransform( outputDataset.get(), geotransform ); |
704 |
|
705 |
return outputDataset;
|
706 |
} |
707 |
|
708 |
void QgsRasterCalculator::outputGeoTransform( double *transform ) const |
709 |
{ |
710 |
transform[0] = mOutputRectangle.xMinimum();
|
711 |
transform[1] = mOutputRectangle.width() / mNumOutputColumns;
|
712 |
transform[2] = 0; |
713 |
transform[3] = mOutputRectangle.yMaximum();
|
714 |
transform[4] = 0; |
715 |
transform[5] = -mOutputRectangle.height() / mNumOutputRows;
|
716 |
} |
717 |
|
718 |
QString QgsRasterCalculator::lastError() const
|
719 |
{ |
720 |
return mLastError;
|
721 |
} |
722 |
|
723 |
QVector<QgsRasterCalculatorEntry> QgsRasterCalculatorEntry::rasterEntries() |
724 |
{ |
725 |
QVector<QgsRasterCalculatorEntry> availableEntries; |
726 |
const QMap<QString, QgsMapLayer *> &layers = QgsProject::instance()->mapLayers();
|
727 |
|
728 |
auto uniqueRasterBandIdentifier = [&]( QgsRasterCalculatorEntry &entry ) -> bool { |
729 |
unsigned int i( 1 ); |
730 |
entry.ref = QStringLiteral( "%1@%2" ).arg( entry.raster->name() ).arg( entry.bandNumber );
|
731 |
while ( true ) |
732 |
{ |
733 |
bool unique( true ); |
734 |
for ( const auto &ref : std::as_const( availableEntries ) ) |
735 |
{ |
736 |
// Safety belt
|
737 |
if ( !( entry.raster && ref.raster ) )
|
738 |
continue;
|
739 |
// Check if is another band of the same raster
|
740 |
if ( ref.raster->publicSource() == entry.raster->publicSource() )
|
741 |
{ |
742 |
if ( ref.bandNumber != entry.bandNumber )
|
743 |
{ |
744 |
continue;
|
745 |
} |
746 |
else // a layer with the same data source was already added to the list |
747 |
{ |
748 |
return false; |
749 |
} |
750 |
} |
751 |
// If same name but different source
|
752 |
if ( ref.ref == entry.ref )
|
753 |
{ |
754 |
unique = false;
|
755 |
entry.ref = QStringLiteral( "%1_%2@%3" ).arg( entry.raster->name() ).arg( i++ ).arg( entry.bandNumber );
|
756 |
} |
757 |
} |
758 |
if ( unique )
|
759 |
return true; |
760 |
} |
761 |
}; |
762 |
|
763 |
QMap<QString, QgsMapLayer *>::const_iterator layerIt = layers.constBegin(); |
764 |
for ( ; layerIt != layers.constEnd(); ++layerIt )
|
765 |
{ |
766 |
QgsRasterLayer *rlayer = qobject_cast<QgsRasterLayer *>( layerIt.value() ); |
767 |
if ( rlayer && rlayer->dataProvider() && ( rlayer->dataProvider()->capabilities() & Qgis::RasterInterfaceCapability::Size ) )
|
768 |
{ |
769 |
//get number of bands
|
770 |
for ( int i = 0; i < rlayer->bandCount(); ++i ) |
771 |
{ |
772 |
QgsRasterCalculatorEntry entry; |
773 |
entry.raster = rlayer; |
774 |
entry.bandNumber = i + 1;
|
775 |
if ( !uniqueRasterBandIdentifier( entry ) )
|
776 |
break;
|
777 |
availableEntries.push_back( entry ); |
778 |
} |
779 |
} |
780 |
} |
781 |
return availableEntries;
|
782 |
} |