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