Statistics
| Branch: | Tag: | Revision:

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
}