bool QgsRasterLayerRenderer::render()
{
  if ( !mRasterViewPort )
    return true; // outside of layer extent - nothing to do

  //R->draw( mPainter, mRasterViewPort, &mMapToPixel );

  QTime time;
  time.start();
  //
  //
  // The goal here is to make as many decisions as possible early on (outside of the rendering loop)
  // so that we can maximise performance of the rendering process. So now we check which drawing
  // procedure to use :
  //

  QgsRasterProjector *projector = mPipe->projector();

  // TODO add a method to interface to get provider and get provider
  // params in QgsRasterProjector
  if ( projector )
  {
    projector->setCRS( mRasterViewPort->mSrcCRS, mRasterViewPort->mDestCRS );
  }

  // Drawer to pipe?
  QgsRasterIterator iterator( mPipe->last() );
  QgsRasterDrawer drawer( &iterator );
  drawer.draw( mPainter, mRasterViewPort, mMapToPixel );

  QgsDebugMsg( QString( "total raster draw time (ms):     %1" ).arg( time.elapsed(), 5 ) );

  return true;
}
Beispiel #2
0
QByteArray* QgsWCSServer::getCoverage()
{
  QStringList wcsLayersId = mConfigParser->wcsLayers();

  QList<QgsMapLayer*> layerList;

  QStringList mErrors = QStringList();

  //defining coverage name
  QString coveName = "";
  //read COVERAGE
  QMap<QString, QString>::const_iterator cove_name_it = mParameters.find( "COVERAGE" );
  if ( cove_name_it != mParameters.end() )
  {
    coveName = cove_name_it.value();
  }
  if ( coveName == "" )
  {
    QMap<QString, QString>::const_iterator cove_name_it = mParameters.find( "IDENTIFIER" );
    if ( cove_name_it != mParameters.end() )
    {
      coveName = cove_name_it.value();
    }
  }

  if ( coveName == "" )
  {
    mErrors << QString( "COVERAGE is mandatory" );
  }

  layerList = mConfigParser->mapLayerFromCoverage( coveName );
  if ( layerList.size() < 1 )
  {
    mErrors << QString( "The layer for the COVERAGE '%1' is not found" ).arg( coveName );
  }

  bool conversionSuccess;
  // BBOX
  bool bboxOk = false;
  double minx = 0.0, miny = 0.0, maxx = 0.0, maxy = 0.0;
  // WIDTh and HEIGHT
  int width = 0, height = 0;
  // CRS
  QString crs = "";

  // read BBOX
  QMap<QString, QString>::const_iterator bbIt = mParameters.find( "BBOX" );
  if ( bbIt == mParameters.end() )
  {
    minx = 0; miny = 0; maxx = 0; maxy = 0;
  }
  else
  {
    bboxOk = true;
    QString bbString = bbIt.value();
    minx = bbString.section( ",", 0, 0 ).toDouble( &conversionSuccess );
    if ( !conversionSuccess ) {bboxOk = false;}
    miny = bbString.section( ",", 1, 1 ).toDouble( &conversionSuccess );
    if ( !conversionSuccess ) {bboxOk = false;}
    maxx = bbString.section( ",", 2, 2 ).toDouble( &conversionSuccess );
    if ( !conversionSuccess ) {bboxOk = false;}
    maxy = bbString.section( ",", 3, 3 ).toDouble( &conversionSuccess );
    if ( !conversionSuccess ) {bboxOk = false;}
  }
  if ( !bboxOk )
  {
    mErrors << QString( "The BBOX is mandatory and has to be xx.xxx,yy.yyy,xx.xxx,yy.yyy" );
  }

  // read WIDTH
  width = mParameters.value( "WIDTH", "0" ).toInt( &conversionSuccess );
  if ( !conversionSuccess )
    width = 0;
  // read HEIGHT
  height = mParameters.value( "HEIGHT", "0" ).toInt( &conversionSuccess );
  if ( !conversionSuccess )
  {
    height = 0;
  }

  if ( width < 0 || height < 0 )
  {
    mErrors << QString( "The WIDTH and HEIGHT are mandatory and have to be integer" );
  }

  crs = mParameters.value( "CRS", "" );
  if ( crs == "" )
  {
    mErrors << QString( "The CRS is mandatory" );
  }

  if ( mErrors.count() != 0 )
  {
    throw QgsMapServiceException( "RequestNotWellFormed", mErrors.join( ". " ) );
  }

  QgsCoordinateReferenceSystem requestCRS = QgsCRSCache::instance()->crsByAuthId( crs );
  if ( !requestCRS.isValid() )
  {
    mErrors << QString( "Could not create request CRS" );
    throw QgsMapServiceException( "RequestNotWellFormed", mErrors.join( ". " ) );
  }

  QgsRectangle rect( minx, miny, maxx, maxy );

  QgsMapLayer* layer = layerList.at( 0 );
  QgsRasterLayer* rLayer = dynamic_cast<QgsRasterLayer*>( layer );
  if ( rLayer && wcsLayersId.contains( rLayer->id() ) )
  {
    // RESPONSE_CRS
    QgsCoordinateReferenceSystem responseCRS = rLayer->crs();
    crs = mParameters.value( "RESPONSE_CRS", "" );
    if ( crs != "" )
    {
      responseCRS = QgsCRSCache::instance()->crsByAuthId( crs );
      if ( !responseCRS.isValid() )
      {
        responseCRS = rLayer->crs();
      }
    }

    // transform rect
    if ( requestCRS != rLayer->crs() )
    {
      QgsCoordinateTransform t( requestCRS, rLayer->crs() );
      rect = t.transformBoundingBox( rect );
    }

    QTemporaryFile tempFile;
    tempFile.open();
    QgsRasterFileWriter fileWriter( tempFile.fileName() );

    // clone pipe/provider
    QgsRasterPipe* pipe = new QgsRasterPipe();
    if ( !pipe->set( rLayer->dataProvider()->clone() ) )
    {
      mErrors << QString( "Cannot set pipe provider" );
      throw QgsMapServiceException( "RequestNotWellFormed", mErrors.join( ". " ) );
    }

    // add projector if necessary
    if ( responseCRS != rLayer->crs() )
    {
      QgsRasterProjector * projector = new QgsRasterProjector;
      projector->setCRS( rLayer->crs(), responseCRS );
      if ( !pipe->insert( 2, projector ) )
      {
        mErrors << QString( "Cannot set pipe projector" );
        throw QgsMapServiceException( "RequestNotWellFormed", mErrors.join( ". " ) );
      }
    }

    QgsRasterFileWriter::WriterError err = fileWriter.writeRaster( pipe, width, height, rect, responseCRS );
    if ( err != QgsRasterFileWriter::NoError )
    {
      mErrors << QString( "Cannot write raster error code: %1" ).arg( err );
      throw QgsMapServiceException( "RequestNotWellFormed", mErrors.join( ". " ) );
    }
    delete pipe;
    QByteArray* ba = 0;
    ba = new QByteArray();
    *ba = tempFile.readAll();

    return ba;
  }
  return 0;
}
int QgsRasterCalculator::processCalculation( QProgressDialog* p )
{
  //prepare search string / tree
  QString errorString;
  QgsRasterCalcNode* calcNode = QgsRasterCalcNode::parseRasterCalcString( mFormulaString, errorString );
  if ( !calcNode )
  {
    //error
    return static_cast<int>( ParserError );
  }

  QMap< QString, QgsRasterBlock* > inputBlocks;
  QVector<QgsRasterCalculatorEntry>::const_iterator it = mRasterEntries.constBegin();
  for ( ; it != mRasterEntries.constEnd(); ++it )
  {
    if ( !it->raster ) // no raster layer in entry
    {
      delete calcNode;
      qDeleteAll( inputBlocks );
      return static_cast< int >( InputLayerError );
    }

    QgsRasterBlock* block = nullptr;
    // if crs transform needed
    if ( it->raster->crs() != mOutputCrs )
    {
      QgsRasterProjector proj;
      proj.setCRS( it->raster->crs(), mOutputCrs );
      proj.setInput( it->raster->dataProvider() );
      proj.setPrecision( QgsRasterProjector::Exact );

      block = proj.block( it->bandNumber, mOutputRectangle, mNumOutputColumns, mNumOutputRows );
    }
    else
    {
      block = it->raster->dataProvider()->block( it->bandNumber, mOutputRectangle, mNumOutputColumns, mNumOutputRows );
    }
    if ( block->isEmpty() )
    {
      delete block;
      delete calcNode;
      qDeleteAll( inputBlocks );
      return static_cast<int>( MemoryError );
    }
    inputBlocks.insert( it->ref, block );
  }

  //open output dataset for writing
  GDALDriverH outputDriver = openOutputDriver();
  if ( !outputDriver )
  {
    return static_cast< int >( CreateOutputError );
  }

  GDALDatasetH outputDataset = openOutputFile( outputDriver );
  GDALSetProjection( outputDataset, mOutputCrs.toWkt().toLocal8Bit().data() );
  GDALRasterBandH outputRasterBand = GDALGetRasterBand( outputDataset, 1 );

  float outputNodataValue = -FLT_MAX;
  GDALSetRasterNoDataValue( outputRasterBand, outputNodataValue );

  if ( p )
  {
    p->setMaximum( mNumOutputRows );
  }

  QgsRasterMatrix resultMatrix;
  resultMatrix.setNodataValue( outputNodataValue );

  //read / write line by line
  for ( int i = 0; i < mNumOutputRows; ++i )
  {
    if ( p )
    {
      p->setValue( i );
    }

    if ( p && p->wasCanceled() )
    {
      break;
    }

    if ( calcNode->calculate( inputBlocks, resultMatrix, i ) )
    {
      bool resultIsNumber = resultMatrix.isNumber();
      float* calcData = new float[mNumOutputColumns];

      for ( int j = 0; j < mNumOutputColumns; ++j )
      {
        calcData[j] = ( float )( resultIsNumber ? resultMatrix.number() : resultMatrix.data()[j] );
      }

      //write scanline to the dataset
      if ( GDALRasterIO( outputRasterBand, GF_Write, 0, i, mNumOutputColumns, 1, calcData, mNumOutputColumns, 1, GDT_Float32, 0, 0 ) != CE_None )
      {
        qWarning( "RasterIO error!" );
      }

      delete[] calcData;
    }

  }

  if ( p )
  {
    p->setValue( mNumOutputRows );
  }

  //close datasets and release memory
  delete calcNode;
  qDeleteAll( inputBlocks );
  inputBlocks.clear();

  if ( p && p->wasCanceled() )
  {
    //delete the dataset without closing (because it is faster)
    GDALDeleteDataset( outputDriver, TO8F( mOutputFile ) );
    return static_cast< int >( Cancelled );
  }
  GDALClose( outputDataset );

  return static_cast< int >( Success );
}
Beispiel #4
0
QgsRasterCalculator::Result QgsRasterCalculator::processCalculation( QgsFeedback *feedback )
{
  mLastError.clear();

  //prepare search string / tree
  std::unique_ptr< QgsRasterCalcNode > calcNode( QgsRasterCalcNode::parseRasterCalcString( mFormulaString, mLastError ) );
  if ( !calcNode )
  {
    //error
    return ParserError;
  }

  // Check input layers and bands
  for ( const auto &entry : qgis::as_const( mRasterEntries ) )
  {
    if ( !entry.raster ) // no raster layer in entry
    {
      mLastError = QObject::tr( "No raster layer for entry %1" ).arg( entry.ref );
      return InputLayerError;
    }
    if ( entry.bandNumber <= 0 || entry.bandNumber > entry.raster->bandCount() )
    {
      mLastError = QObject::tr( "Band number %1 is not valid for entry %2" ).arg( entry.bandNumber ).arg( entry.ref );
      return BandError;
    }
  }

#ifdef HAVE_OPENCL
  // Check for matrix nodes, GPU implementation does not support them
  QList<const QgsRasterCalcNode *> nodeList;
  if ( QgsOpenClUtils::enabled() && QgsOpenClUtils::available() && calcNode->findNodes( QgsRasterCalcNode::Type::tMatrix ).isEmpty() )
    return processCalculationGPU( std::move( calcNode ), feedback );
#endif

  //open output dataset for writing
  GDALDriverH outputDriver = openOutputDriver();
  if ( !outputDriver )
  {
    mLastError = QObject::tr( "Could not obtain driver for %1" ).arg( mOutputFormat );
    return CreateOutputError;
  }

  gdal::dataset_unique_ptr outputDataset( openOutputFile( outputDriver ) );
  if ( !outputDataset )
  {
    mLastError = QObject::tr( "Could not create output %1" ).arg( mOutputFile );
    return CreateOutputError;
  }

  GDALSetProjection( outputDataset.get(), mOutputCrs.toWkt().toLocal8Bit().data() );
  GDALRasterBandH outputRasterBand = GDALGetRasterBand( outputDataset.get(), 1 );

  float outputNodataValue = -FLT_MAX;
  GDALSetRasterNoDataValue( outputRasterBand, outputNodataValue );

  // Check if we need to read the raster as a whole (which is memory inefficient
  // and not interruptable by the user) by checking if any raster matrix nodes are
  // in the expression
  bool requiresMatrix = ! calcNode->findNodes( QgsRasterCalcNode::Type::tMatrix ).isEmpty();

  // Take the fast route (process one line at a time) if we can
  if ( ! requiresMatrix )
  {
    // Map of raster names -> blocks
    std::map<QString, std::unique_ptr<QgsRasterBlock>> inputBlocks;
    std::map<QString, QgsRasterCalculatorEntry> uniqueRasterEntries;
    for ( const auto &r : calcNode->findNodes( QgsRasterCalcNode::Type::tRasterRef ) )
    {
      QString layerRef( r->toString().remove( 0, 1 ) );
      layerRef.chop( 1 );
      if ( ! inputBlocks.count( layerRef ) )
      {
        for ( const auto &ref : mRasterEntries )
        {
          if ( ref.ref == layerRef )
          {
            uniqueRasterEntries[layerRef] = ref;
            inputBlocks[layerRef ] = qgis::make_unique<QgsRasterBlock>();
          }
        }
      }
    }

    //read / write line by line
    QMap<QString, QgsRasterBlock * > _rasterData;
    // Cast to float
    std::vector<float> castedResult;
    castedResult.reserve( static_cast<size_t>( mNumOutputColumns ) );
    auto rowHeight = mOutputRectangle.height() / mNumOutputRows;
    for ( size_t row = 0; row < static_cast<size_t>( mNumOutputRows ); ++row )
    {
      if ( feedback )
      {
        feedback->setProgress( 100.0 * static_cast< double >( row ) / mNumOutputRows );
      }

      if ( feedback && feedback->isCanceled() )
      {
        break;
      }

      // Calculates the rect for a single row read
      QgsRectangle rect( mOutputRectangle );
      rect.setYMaximum( rect.yMaximum() - rowHeight * row );
      rect.setYMinimum( rect.yMaximum() - rowHeight );

      // Read rows into input blocks
      for ( auto &layerRef : inputBlocks )
      {
        QgsRasterCalculatorEntry ref = uniqueRasterEntries[layerRef.first];
        if ( uniqueRasterEntries[layerRef.first].raster->crs() != mOutputCrs )
        {
          QgsRasterProjector proj;
          proj.setCrs( ref.raster->crs(), mOutputCrs );
          proj.setInput( ref.raster->dataProvider() );
          proj.setPrecision( QgsRasterProjector::Exact );
          layerRef.second.reset( proj.block( ref.bandNumber, rect, mNumOutputColumns, 1 ) );
        }
        else
        {
          inputBlocks[layerRef.first].reset( ref.raster->dataProvider()->block( ref.bandNumber, rect, mNumOutputColumns, 1 ) );
        }
      }

      QgsRasterMatrix resultMatrix;
      resultMatrix.setNodataValue( outputNodataValue );

      _rasterData.clear();
      for ( const auto &layerRef : inputBlocks )
      {
        _rasterData.insert( layerRef.first, inputBlocks[layerRef.first].get() );
      }

      if ( calcNode->calculate( _rasterData, resultMatrix, 0 ) )
      {
        // write scanline to the dataset
        for ( size_t i = 0; i < static_cast<size_t>( mNumOutputColumns ); i++ )
        {
          castedResult[i] = static_cast<float>( resultMatrix.data()[i] );
        }
        if ( GDALRasterIO( outputRasterBand, GF_Write, 0, row, mNumOutputColumns, 1, castedResult.data(), mNumOutputColumns, 1, GDT_Float32, 0, 0 ) != CE_None )
        {
          QgsDebugMsg( QStringLiteral( "RasterIO error!" ) );
        }
      }
    }

    if ( feedback )
    {
      feedback->setProgress( 100.0 );
    }
  }
  else  // Original code (memory inefficient route)
  {
    QMap< QString, QgsRasterBlock * > inputBlocks;
    QVector<QgsRasterCalculatorEntry>::const_iterator it = mRasterEntries.constBegin();
    for ( ; it != mRasterEntries.constEnd(); ++it )
    {

      std::unique_ptr< QgsRasterBlock > block;
      // if crs transform needed
      if ( it->raster->crs() != mOutputCrs )
      {
        QgsRasterProjector proj;
        proj.setCrs( it->raster->crs(), mOutputCrs );
        proj.setInput( it->raster->dataProvider() );
        proj.setPrecision( QgsRasterProjector::Exact );

        QgsRasterBlockFeedback *rasterBlockFeedback = new QgsRasterBlockFeedback();
        QObject::connect( feedback, &QgsFeedback::canceled, rasterBlockFeedback, &QgsRasterBlockFeedback::cancel );
        block.reset( proj.block( it->bandNumber, mOutputRectangle, mNumOutputColumns, mNumOutputRows, rasterBlockFeedback ) );
        if ( rasterBlockFeedback->isCanceled() )
        {
          qDeleteAll( inputBlocks );
          return Canceled;
        }
      }
      else
      {
        block.reset( it->raster->dataProvider()->block( it->bandNumber, mOutputRectangle, mNumOutputColumns, mNumOutputRows ) );
      }
      if ( block->isEmpty() )
      {
        mLastError = QObject::tr( "Could not allocate required memory for %1" ).arg( it->ref );
        qDeleteAll( inputBlocks );
        return MemoryError;
      }
      inputBlocks.insert( it->ref, block.release() );
    }

    QgsRasterMatrix resultMatrix;
    resultMatrix.setNodataValue( outputNodataValue );

    //read / write line by line
    for ( int i = 0; i < mNumOutputRows; ++i )
    {
      if ( feedback )
      {
        feedback->setProgress( 100.0 * static_cast< double >( i ) / mNumOutputRows );
      }

      if ( feedback && feedback->isCanceled() )
      {
        break;
      }

      if ( calcNode->calculate( inputBlocks, resultMatrix, i ) )
      {
        bool resultIsNumber = resultMatrix.isNumber();
        float *calcData = new float[mNumOutputColumns];

        for ( int j = 0; j < mNumOutputColumns; ++j )
        {
          calcData[j] = ( float )( resultIsNumber ? resultMatrix.number() : resultMatrix.data()[j] );
        }

        //write scanline to the dataset
        if ( GDALRasterIO( outputRasterBand, GF_Write, 0, i, mNumOutputColumns, 1, calcData, mNumOutputColumns, 1, GDT_Float32, 0, 0 ) != CE_None )
        {
          QgsDebugMsg( QStringLiteral( "RasterIO error!" ) );
        }

        delete[] calcData;
      }

    }

    if ( feedback )
    {
      feedback->setProgress( 100.0 );
    }

    //close datasets and release memory
    calcNode.reset();
    qDeleteAll( inputBlocks );
    inputBlocks.clear();

  }

  if ( feedback && feedback->isCanceled() )
  {
    //delete the dataset without closing (because it is faster)
    gdal::fast_delete_and_close( outputDataset, outputDriver, mOutputFile );
    return Canceled;
  }
  return Success;
}
Beispiel #5
0
QgsRasterCalculator::Result QgsRasterCalculator::processCalculationGPU( std::unique_ptr< QgsRasterCalcNode > calcNode, QgsFeedback *feedback )
{

  QString cExpression( calcNode->toString( true ) );

  QList<const QgsRasterCalcNode *> nodeList( calcNode->findNodes( QgsRasterCalcNode::Type::tRasterRef ) );
  QSet<QString> capturedTexts;
  for ( const auto &r : qgis::as_const( nodeList ) )
  {
    QString s( r->toString().remove( 0, 1 ) );
    s.chop( 1 );
    capturedTexts.insert( s );
  }

  // Extract all references
  struct LayerRef
  {
    QString name;
    int band;
    QgsRasterLayer *layer = nullptr;
    QString varName;
    QString typeName;
    size_t index;
    size_t bufferSize;
    size_t dataSize;
  };

  // Collects all layers, band, name, varName and size information
  std::vector<LayerRef> inputRefs;
  size_t refCounter = 0;
  for ( const auto &r : capturedTexts )
  {
    if ( r.startsWith( '"' ) )
      continue;
    QStringList parts( r.split( '@' ) );
    if ( parts.count() != 2 )
      continue;
    bool ok = false;
    LayerRef entry;
    entry.name = r;
    entry.band = parts[1].toInt( &ok );
    for ( const auto &ref : mRasterEntries )
    {
      if ( ref.ref == entry.name )
        entry.layer = ref.raster;
    }
    if ( !( entry.layer && entry.layer->dataProvider() && ok ) )
      continue;
    entry.dataSize = entry.layer->dataProvider()->dataTypeSize( entry.band );
    switch ( entry.layer->dataProvider()->dataType( entry.band ) )
    {
      case Qgis::DataType::Byte:
        entry.typeName = QStringLiteral( "unsigned char" );
        break;
      case Qgis::DataType::UInt16:
        entry.typeName = QStringLiteral( "unsigned int" );
        break;
      case Qgis::DataType::Int16:
        entry.typeName = QStringLiteral( "short" );
        break;
      case Qgis::DataType::UInt32:
        entry.typeName = QStringLiteral( "unsigned int" );
        break;
      case Qgis::DataType::Int32:
        entry.typeName = QStringLiteral( "int" );
        break;
      case Qgis::DataType::Float32:
        entry.typeName = QStringLiteral( "float" );
        break;
      // FIXME: not sure all OpenCL implementations support double
      //        maybe safer to fall back to the CPU implementation
      //        after a compatibility check
      case Qgis::DataType::Float64:
        entry.typeName = QStringLiteral( "double" );
        break;
      default:
        return BandError;
    }
    entry.bufferSize = entry.dataSize * mNumOutputColumns;
    entry.index = refCounter;
    entry.varName = QStringLiteral( "input_raster_%1_band_%2" )
                    .arg( refCounter++ )
                    .arg( entry.band );
    inputRefs.push_back( entry );
  }

  // Prepare context and queue
  cl::Context ctx( QgsOpenClUtils::context() );
  cl::CommandQueue queue( QgsOpenClUtils::commandQueue() );

  // Create the C expression
  std::vector<cl::Buffer> inputBuffers;
  inputBuffers.reserve( inputRefs.size() );
  QStringList inputArgs;
  for ( const auto &ref : inputRefs )
  {
    cExpression.replace( QStringLiteral( "\"%1\"" ).arg( ref.name ), QStringLiteral( "%1[i]" ).arg( ref.varName ) );
    inputArgs.append( QStringLiteral( "__global %1 *%2" )
                      .arg( ref.typeName )
                      .arg( ref.varName ) );
    inputBuffers.push_back( cl::Buffer( ctx, CL_MEM_READ_ONLY, ref.bufferSize, nullptr, nullptr ) );
  }

  //qDebug() << cExpression;

  // Create the program
  QString programTemplate( R"CL(
  // Inputs:
  ##INPUT_DESC##
  // Expression: ##EXPRESSION_ORIGINAL##
  __kernel void rasterCalculator( ##INPUT##
                            __global float *resultLine
                          )
  {
    // Get the index of the current element
    const int i = get_global_id(0);
    // Expression
    resultLine[i] = ##EXPRESSION##;
  }
  )CL" );

  QStringList inputDesc;
  for ( const auto &ref : inputRefs )
  {
    inputDesc.append( QStringLiteral( "  // %1 = %2" ).arg( ref.varName ).arg( ref.name ) );
  }
  programTemplate = programTemplate.replace( QStringLiteral( "##INPUT_DESC##" ), inputDesc.join( '\n' ) );
  programTemplate = programTemplate.replace( QStringLiteral( "##INPUT##" ), inputArgs.length() ? ( inputArgs.join( ',' ).append( ',' ) ) : QChar( ' ' ) );
  programTemplate = programTemplate.replace( QStringLiteral( "##EXPRESSION##" ), cExpression );
  programTemplate = programTemplate.replace( QStringLiteral( "##EXPRESSION_ORIGINAL##" ), calcNode->toString( ) );

  // qDebug() << programTemplate;

  // Create a program from the kernel source
  cl::Program program( QgsOpenClUtils::buildProgram( programTemplate, QgsOpenClUtils::ExceptionBehavior::Throw ) );

  // Create the buffers, output is float32 (4 bytes)
  // We assume size of float = 4 because that's the size used by OpenCL and IEEE 754
  Q_ASSERT( sizeof( float ) == 4 );
  std::size_t resultBufferSize( 4 * static_cast<size_t>( mNumOutputColumns ) );
  cl::Buffer resultLineBuffer( ctx, CL_MEM_WRITE_ONLY,
                               resultBufferSize, nullptr, nullptr );

  auto kernel = cl::Kernel( program, "rasterCalculator" );

  for ( unsigned int i = 0; i < inputBuffers.size() ; i++ )
  {
    kernel.setArg( i, inputBuffers.at( i ) );
  }
  kernel.setArg( static_cast<unsigned int>( inputBuffers.size() ), resultLineBuffer );

  QgsOpenClUtils::CPLAllocator<float> resultLine( static_cast<size_t>( mNumOutputColumns ) );

  //open output dataset for writing
  GDALDriverH outputDriver = openOutputDriver();
  if ( !outputDriver )
  {
    mLastError = QObject::tr( "Could not obtain driver for %1" ).arg( mOutputFormat );
    return CreateOutputError;
  }

  gdal::dataset_unique_ptr outputDataset( openOutputFile( outputDriver ) );
  if ( !outputDataset )
  {
    mLastError = QObject::tr( "Could not create output %1" ).arg( mOutputFile );
    return CreateOutputError;
  }

  GDALSetProjection( outputDataset.get(), mOutputCrs.toWkt().toLocal8Bit().data() );

  GDALRasterBandH outputRasterBand = GDALGetRasterBand( outputDataset.get(), 1 );
  if ( !outputRasterBand )
    return BandError;

  // Input block (buffer)
  std::unique_ptr<QgsRasterBlock> block;

  // Run kernel on all scanlines
  auto rowHeight = mOutputRectangle.height() / mNumOutputRows;
  for ( int line = 0; line < mNumOutputRows; line++ )
  {
    if ( feedback && feedback->isCanceled() )
    {
      break;
    }

    if ( feedback )
    {
      feedback->setProgress( 100.0 * static_cast< double >( line ) / mNumOutputRows );
    }

    // Read lines from rasters into the buffers
    for ( const auto &ref : inputRefs )
    {
      // Read one row
      QgsRectangle rect( mOutputRectangle );
      rect.setYMaximum( rect.yMaximum() - rowHeight * line );
      rect.setYMinimum( rect.yMaximum() - rowHeight );

      // TODO: check if this is too slow
      // if crs transform needed
      if ( ref.layer->crs() != mOutputCrs )
      {
        QgsRasterProjector proj;
        proj.setCrs( ref.layer->crs(), mOutputCrs );
        proj.setInput( ref.layer->dataProvider() );
        proj.setPrecision( QgsRasterProjector::Exact );
        block.reset( proj.block( ref.band, rect, mNumOutputColumns, 1 ) );
      }
      else
      {
        block.reset( ref.layer->dataProvider()->block( ref.band, rect, mNumOutputColumns, 1 ) );
      }

      //for ( int i = 0; i < mNumOutputColumns; i++ )
      //  qDebug() << "Input: " << line << i << ref.varName << " = " << block->value( 0, i );
      //qDebug() << "Writing buffer " << ref.index;

      Q_ASSERT( ref.bufferSize == static_cast<size_t>( block->data().size( ) ) );
      queue.enqueueWriteBuffer( inputBuffers[ref.index], CL_TRUE, 0,
                                ref.bufferSize, block->bits() );

    }
    // Run the kernel
    queue.enqueueNDRangeKernel(
      kernel,
      0,
      cl::NDRange( mNumOutputColumns )
    );

    // Write the result
    queue.enqueueReadBuffer( resultLineBuffer, CL_TRUE, 0,
                             resultBufferSize, resultLine.get() );

    //for ( int i = 0; i < mNumOutputColumns; i++ )
    //  qDebug() << "Output: " << line << i << " = " << resultLine[i];

    if ( GDALRasterIO( outputRasterBand, GF_Write, 0, line, mNumOutputColumns, 1, resultLine.get(), mNumOutputColumns, 1, GDT_Float32, 0, 0 ) != CE_None )
    {
      return CreateOutputError;
    }
  }

  if ( feedback && feedback->isCanceled() )
  {
    //delete the dataset without closing (because it is faster)
    gdal::fast_delete_and_close( outputDataset, outputDriver, mOutputFile );
    return Canceled;
  }

  inputBuffers.clear();

  return Success;
}
QgsRasterFileWriter::WriterError QgsRasterFileWriter::writeDataRaster( const QgsRasterPipe* pipe, QgsRasterIterator* iter, int nCols, int nRows, const QgsRectangle& outputExtent,
    const QgsCoordinateReferenceSystem& crs, QProgressDialog* progressDialog )
{
  QgsDebugMsg( "Entered" );
  if ( !iter )
  {
    return SourceProviderError;
  }

  const QgsRasterInterface* iface = pipe->last();
  if ( !iface )
  {
    return SourceProviderError;
  }

  QgsRasterDataProvider* srcProvider = const_cast<QgsRasterDataProvider*>( dynamic_cast<const QgsRasterDataProvider*>( iface->srcInput() ) );
  if ( !srcProvider )
  {
    QgsDebugMsg( "Cannot get source data provider" );
    return SourceProviderError;
  }

  iter->setMaximumTileWidth( mMaxTileWidth );
  iter->setMaximumTileHeight( mMaxTileHeight );

  int nBands = iface->bandCount();
  if ( nBands < 1 )
  {
    return SourceProviderError;
  }


  //check if all the bands have the same data type size, otherwise we cannot write it to the provider
  //(at least not with the current interface)
  int dataTypeSize = QgsRasterBlock::typeSize( srcProvider->srcDataType( 1 ) );
  for ( int i = 2; i <= nBands; ++i )
  {
    if ( QgsRasterBlock::typeSize( srcProvider->srcDataType( 1 ) ) != dataTypeSize )
    {
      return DestProviderError;
    }
  }

  // Output data type - source data type is preferred but it may happen that we need
  // to set 'no data' value (which was not set on source data) if output extent
  // is larger than source extent (with or without reprojection) and there is no 'free'
  // (not used) value available
  QList<bool> destHasNoDataValueList;
  QList<double> destNoDataValueList;
  QList<QGis::DataType> destDataTypeList;
  for ( int bandNo = 1; bandNo <= nBands; bandNo++ )
  {
    QgsRasterNuller *nuller = pipe->nuller();

    bool srcHasNoDataValue = srcProvider->srcHasNoDataValue( bandNo );
    bool destHasNoDataValue = false;
    double destNoDataValue = std::numeric_limits<double>::quiet_NaN();
    QGis::DataType destDataType = srcProvider->srcDataType( bandNo );
    // TODO: verify what happens/should happen if srcNoDataValue is disabled by setUseSrcNoDataValue
    QgsDebugMsg( QString( "srcHasNoDataValue = %1 srcNoDataValue = %2" ).arg( srcHasNoDataValue ).arg( srcProvider->srcNoDataValue( bandNo ) ) );
    if ( srcHasNoDataValue )
    {

      // If source has no data value, it is used by provider
      destNoDataValue = srcProvider->srcNoDataValue( bandNo );
      destHasNoDataValue = true;
    }
    else if ( nuller && nuller->noData( bandNo ).size() > 0 )
    {
      // Use one user defined no data value
      destNoDataValue = nuller->noData( bandNo ).value( 0 ).min();
      destHasNoDataValue = true;
    }
    else
    {
      // Verify if we realy need no data value, i.e.
      QgsRectangle srcExtent = outputExtent;
      QgsRasterProjector *projector = pipe->projector();
      if ( projector && projector->destCrs() != projector->srcCrs() )
      {
        QgsCoordinateTransform ct( projector->destCrs(), projector->srcCrs() );
        srcExtent = ct.transformBoundingBox( outputExtent );
      }
      if ( !srcProvider->extent().contains( srcExtent ) )
      {
        // Destination extent is larger than source extent, we need destination no data values
        // Get src sample statistics (estimation from sample)
        QgsRasterBandStats stats = srcProvider->bandStatistics( bandNo, QgsRasterBandStats::Min | QgsRasterBandStats::Max, srcExtent, 250000 );

        // Test if we have free (not used) values
        double typeMinValue = QgsContrastEnhancement::maximumValuePossible(( QGis::DataType )srcProvider->srcDataType( bandNo ) );
        double typeMaxValue = QgsContrastEnhancement::maximumValuePossible(( QGis::DataType )srcProvider->srcDataType( bandNo ) );
        if ( stats.minimumValue > typeMinValue )
        {
          destNoDataValue = typeMinValue;
        }
        else if ( stats.maximumValue < typeMaxValue )
        {
          destNoDataValue = typeMaxValue;
        }
        else
        {
          // We have to use wider type
          destDataType = QgsRasterBlock::typeWithNoDataValue( destDataType, &destNoDataValue );
        }
        destHasNoDataValue = true;
      }
    }

    if ( nuller && destHasNoDataValue )
    {
      nuller->setOutputNoDataValue( bandNo, destNoDataValue );
    }

    QgsDebugMsg( QString( "bandNo = %1 destDataType = %2 destHasNoDataValue = %3 destNoDataValue = %4" ).arg( bandNo ).arg( destDataType ).arg( destHasNoDataValue ).arg( destNoDataValue ) );
    destDataTypeList.append( destDataType );
    destHasNoDataValueList.append( destHasNoDataValue );
    destNoDataValueList.append( destNoDataValue );
  }

  QGis::DataType destDataType = destDataTypeList.value( 0 );
  // Currently write API supports one output type for dataset only -> find the widest
  for ( int i = 1; i < nBands; i++ )
  {
    if ( destDataTypeList.value( i ) > destDataType )
    {
      destDataType = destDataTypeList.value( i );
      // no data value may be left per band (for future)
    }
  }

  //create destProvider for whole dataset here
  QgsRasterDataProvider* destProvider = 0;
  double pixelSize;
  double geoTransform[6];
  globalOutputParameters( outputExtent, nCols, nRows, geoTransform, pixelSize );

  // initOutput() returns 0 in tile mode!
  destProvider = initOutput( nCols, nRows, crs, geoTransform, nBands, destDataType, destHasNoDataValueList, destNoDataValueList );

  WriterError error = writeDataRaster( pipe, iter, nCols, nRows, outputExtent, crs, destDataType, destHasNoDataValueList, destNoDataValueList, destProvider, progressDialog );

  if ( error == NoDataConflict )
  {
    // The value used for no data was found in source data, we must use wider data type
    if ( destProvider ) // no tiles
    {
      destProvider->remove();
      delete destProvider;
      destProvider = 0;
    }
    else // VRT
    {
      // TODO: remove created VRT
    }

    // But we don't know which band -> wider all
    for ( int i = 0; i < nBands; i++ )
    {
      double destNoDataValue;
      QGis::DataType destDataType = QgsRasterBlock::typeWithNoDataValue( destDataTypeList.value( i ), &destNoDataValue );
      destDataTypeList.replace( i, destDataType );
      destNoDataValueList.replace( i, destNoDataValue );
    }
    destDataType =  destDataTypeList.value( 0 );

    // Try again
    destProvider = initOutput( nCols, nRows, crs, geoTransform, nBands, destDataType, destHasNoDataValueList, destNoDataValueList );
    error = writeDataRaster( pipe, iter, nCols, nRows, outputExtent, crs, destDataType, destHasNoDataValueList, destNoDataValueList, destProvider, progressDialog );
  }

  if ( destProvider )
    delete destProvider;

  return error;
}