QGIS API Documentation  3.24.2-Tisler (13c1a02865)
qgsrasterfilewriter.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsrasterfilewriter.cpp
3  ---------------------
4  begin : July 2012
5  copyright : (C) 2012 by Marco Hugentobler
6  email : marco dot hugentobler at sourcepole dot ch
7  ***************************************************************************
8  * *
9  * This program is free software; you can redistribute it and/or modify *
10  * it under the terms of the GNU General Public License as published by *
11  * the Free Software Foundation; either version 2 of the License, or *
12  * (at your option) any later version. *
13  * *
14  ***************************************************************************/
15 #include <typeinfo>
16 
17 #include "qgsgdalutils.h"
18 #include "qgsrasterfilewriter.h"
19 #include "qgscoordinatetransform.h"
20 #include "qgsproviderregistry.h"
21 #include "qgsrasterinterface.h"
22 #include "qgsrasteriterator.h"
23 #include "qgsrasterlayer.h"
24 #include "qgsrasterprojector.h"
25 #include "qgsrasterdataprovider.h"
26 #include "qgsrasternuller.h"
27 #include "qgsreadwritelocker.h"
28 #include "qgsrasterpipe.h"
29 
30 #include <QCoreApplication>
31 #include <QProgressDialog>
32 #include <QTextStream>
33 #include <QMessageBox>
34 #include <QRegularExpression>
35 
36 #include <cmath>
37 
38 #include <gdal.h>
39 #include <cpl_string.h>
40 #include <mutex>
41 
43 {
44  if ( mTiledMode )
45  return nullptr; // does not make sense with tiled mode
46 
47  double pixelSize;
48  double geoTransform[6];
49  globalOutputParameters( extent, width, height, geoTransform, pixelSize );
50 
51  return initOutput( width, height, crs, geoTransform, 1, dataType, QList<bool>(), QList<double>() );
52 }
53 
55 {
56  if ( mTiledMode )
57  return nullptr; // does not make sense with tiled mode
58 
59  double pixelSize;
60  double geoTransform[6];
61  globalOutputParameters( extent, width, height, geoTransform, pixelSize );
62 
63  return initOutput( width, height, crs, geoTransform, nBands, dataType, QList<bool>(), QList<double>() );
64 }
65 
66 QgsRasterFileWriter::QgsRasterFileWriter( const QString &outputUrl )
67  : mOutputUrl( outputUrl )
68 {
69 
70 }
71 
72 QgsRasterFileWriter::QgsRasterFileWriter()
73 {
74 
75 }
76 
77 
78 // Deprecated!
79 QgsRasterFileWriter::WriterError QgsRasterFileWriter::writeRaster( const QgsRasterPipe *pipe, int nCols, int nRows, const QgsRectangle &outputExtent,
81 {
82  return writeRaster( pipe, nCols, nRows, outputExtent, crs, ( pipe && pipe->provider() ) ? pipe->provider()->transformContext() : QgsCoordinateTransformContext(), feedback );
83 }
84 
85 QgsRasterFileWriter::WriterError QgsRasterFileWriter::writeRaster( const QgsRasterPipe *pipe, int nCols, int nRows, const QgsRectangle &outputExtent,
86  const QgsCoordinateReferenceSystem &crs, const QgsCoordinateTransformContext &transformContext,
87  QgsRasterBlockFeedback *feedback )
88 {
89  QgsDebugMsgLevel( QStringLiteral( "Entered" ), 4 );
90 
91  if ( !pipe )
92  {
93  return SourceProviderError;
94  }
95  mPipe = pipe;
96 
97  //const QgsRasterInterface* iface = iter->input();
98  const QgsRasterInterface *iface = pipe->last();
99  if ( !iface )
100  {
101  return SourceProviderError;
102  }
103  mInput = iface;
104 
105  if ( QgsRasterBlock::typeIsColor( iface->dataType( 1 ) ) )
106  {
107  mMode = Image;
108  }
109  else
110  {
111  mMode = Raw;
112  }
113 
114  QgsDebugMsgLevel( QStringLiteral( "reading from %1" ).arg( typeid( *iface ).name() ), 4 );
115 
116  if ( !iface->sourceInput() )
117  {
118  QgsDebugMsg( QStringLiteral( "iface->srcInput() == 0" ) );
119  return SourceProviderError;
120  }
121 #ifdef QGISDEBUG
122  const QgsRasterInterface &srcInput = *iface->sourceInput();
123  QgsDebugMsgLevel( QStringLiteral( "srcInput = %1" ).arg( typeid( srcInput ).name() ), 4 );
124 #endif
125 
126  mFeedback = feedback;
127 
128  QgsRasterIterator iter( pipe->last() );
129 
130  //create directory for output files
131  if ( mTiledMode )
132  {
133  const QFileInfo fileInfo( mOutputUrl );
134  if ( !fileInfo.exists() )
135  {
136  const QDir dir = fileInfo.dir();
137  if ( !dir.mkdir( fileInfo.fileName() ) )
138  {
139  QgsDebugMsg( "Cannot create output VRT directory " + fileInfo.fileName() + " in " + dir.absolutePath() );
140  return CreateDatasourceError;
141  }
142  }
143  }
144 
145  // Remove pre-existing overview files to avoid using those with new raster
146  QFile pyramidFile( mOutputUrl + ( mTiledMode ? ".vrt.ovr" : ".ovr" ) );
147  if ( pyramidFile.exists() )
148  pyramidFile.remove();
149  pyramidFile.setFileName( mOutputUrl + ( mTiledMode ? ".vrt.rrd" : ".rrd" ) );
150  if ( pyramidFile.exists() )
151  pyramidFile.remove();
152 
153  if ( mMode == Image )
154  {
155  const WriterError e = writeImageRaster( &iter, nCols, nRows, outputExtent, crs, feedback );
156  return e;
157  }
158  else
159  {
160  const WriterError e = writeDataRaster( pipe, &iter, nCols, nRows, outputExtent, crs, transformContext, feedback );
161  return e;
162  }
163 }
164 
165 QgsRasterFileWriter::WriterError QgsRasterFileWriter::writeDataRaster( const QgsRasterPipe *pipe, QgsRasterIterator *iter, int nCols, int nRows, const QgsRectangle &outputExtent,
166  const QgsCoordinateReferenceSystem &crs, const QgsCoordinateTransformContext &transformContext, QgsRasterBlockFeedback *feedback )
167 {
168  QgsDebugMsgLevel( QStringLiteral( "Entered" ), 4 );
169  if ( !iter )
170  {
171  return SourceProviderError;
172  }
173 
174  const QgsRasterInterface *iface = pipe->last();
175  if ( !iface )
176  {
177  return SourceProviderError;
178  }
179 
180  QgsRasterDataProvider *srcProvider = const_cast<QgsRasterDataProvider *>( dynamic_cast<const QgsRasterDataProvider *>( iface->sourceInput() ) );
181  if ( !srcProvider )
182  {
183  QgsDebugMsg( QStringLiteral( "Cannot get source data provider" ) );
184  return SourceProviderError;
185  }
186 
187  iter->setMaximumTileWidth( mMaxTileWidth );
188  iter->setMaximumTileHeight( mMaxTileHeight );
189 
190  const int nBands = iface->bandCount();
191  if ( nBands < 1 )
192  {
193  return SourceProviderError;
194  }
195 
196 
197  //check if all the bands have the same data type size, otherwise we cannot write it to the provider
198  //(at least not with the current interface)
199  const int dataTypeSize = QgsRasterBlock::typeSize( srcProvider->sourceDataType( 1 ) );
200  for ( int i = 2; i <= nBands; ++i )
201  {
202  if ( QgsRasterBlock::typeSize( srcProvider->sourceDataType( 1 ) ) != dataTypeSize )
203  {
204  return DestProviderError;
205  }
206  }
207 
208  // Output data type - source data type is preferred but it may happen that we need
209  // to set 'no data' value (which was not set on source data) if output extent
210  // is larger than source extent (with or without reprojection) and there is no 'free'
211  // (not used) value available
212  QList<bool> destHasNoDataValueList;
213  QList<double> destNoDataValueList;
214  QList<Qgis::DataType> destDataTypeList;
215  destDataTypeList.reserve( nBands );
216  destHasNoDataValueList.reserve( nBands );
217  destNoDataValueList.reserve( nBands );
218 
219  const bool isGpkgOutput = mOutputProviderKey == "gdal" &&
220  mOutputFormat.compare( QLatin1String( "gpkg" ), Qt::CaseInsensitive ) == 0;
221  double pixelSize;
222  double geoTransform[6];
223  globalOutputParameters( outputExtent, nCols, nRows, geoTransform, pixelSize );
224  const auto srcProviderExtent( srcProvider->extent() );
225 
226  for ( int bandNo = 1; bandNo <= nBands; bandNo++ )
227  {
228  QgsRasterNuller *nuller = pipe->nuller();
229 
230  const bool srcHasNoDataValue = srcProvider->sourceHasNoDataValue( bandNo );
231  bool destHasNoDataValue = false;
232  double destNoDataValue = std::numeric_limits<double>::quiet_NaN();
233  const Qgis::DataType srcDataType = srcProvider->sourceDataType( bandNo );
234  Qgis::DataType destDataType = srcDataType;
235  // TODO: verify what happens/should happen if srcNoDataValue is disabled by setUseSrcNoDataValue
236  QgsDebugMsgLevel( QStringLiteral( "srcHasNoDataValue = %1 srcNoDataValue = %2" ).arg( srcHasNoDataValue ).arg( srcProvider->sourceNoDataValue( bandNo ) ), 4 );
237  if ( srcHasNoDataValue )
238  {
239 
240  // If source has no data value, it is used by provider
241  destNoDataValue = srcProvider->sourceNoDataValue( bandNo );
242  destHasNoDataValue = true;
243  }
244  else if ( nuller && !nuller->noData( bandNo ).isEmpty() )
245  {
246  // Use one user defined no data value
247  destNoDataValue = nuller->noData( bandNo ).value( 0 ).min();
248  destHasNoDataValue = true;
249  }
250  // GeoPackage does not support nodata for Byte output, and does not
251  // support non-Byte multiband output, so do not take the risk of an accidental
252  // data type promotion.
253  else if ( !( isGpkgOutput && destDataType == Qgis::DataType::Byte ) )
254  {
255  // Verify if we really need no data value, i.e.
256  QgsRectangle outputExtentInSrcCrs = outputExtent;
257  QgsRasterProjector *projector = pipe->projector();
258  if ( projector && projector->destinationCrs() != projector->sourceCrs() )
259  {
260  QgsCoordinateTransform ct( projector->destinationCrs(), projector->sourceCrs(), transformContext );
261  ct.setBallparkTransformsAreAppropriate( true );
262  outputExtentInSrcCrs = ct.transformBoundingBox( outputExtent );
263  }
264  if ( !srcProviderExtent.contains( outputExtentInSrcCrs ) &&
265  ( std::fabs( srcProviderExtent.xMinimum() - outputExtentInSrcCrs.xMinimum() ) > geoTransform[1] / 2 ||
266  std::fabs( srcProviderExtent.xMaximum() - outputExtentInSrcCrs.xMaximum() ) > geoTransform[1] / 2 ||
267  std::fabs( srcProviderExtent.yMinimum() - outputExtentInSrcCrs.yMinimum() ) > std::fabs( geoTransform[5] ) / 2 ||
268  std::fabs( srcProviderExtent.yMaximum() - outputExtentInSrcCrs.yMaximum() ) > std::fabs( geoTransform[5] ) / 2 ) )
269  {
270  // Destination extent is (at least partially) outside of source extent, we need destination no data values
271  // Get src sample statistics (estimation from sample)
272  const QgsRasterBandStats stats = srcProvider->bandStatistics( bandNo, QgsRasterBandStats::Min | QgsRasterBandStats::Max, outputExtentInSrcCrs, 250000 );
273 
274  // Test if we have free (not used) values
275  const double typeMinValue = QgsContrastEnhancement::minimumValuePossible( srcDataType );
276  const double typeMaxValue = QgsContrastEnhancement::maximumValuePossible( srcDataType );
277  if ( stats.minimumValue > typeMinValue )
278  {
279  destNoDataValue = typeMinValue;
280  }
281  else if ( stats.maximumValue < typeMaxValue )
282  {
283  destNoDataValue = typeMaxValue;
284  }
285  else
286  {
287  // We have to use wider type
288  destDataType = QgsRasterBlock::typeWithNoDataValue( destDataType, &destNoDataValue );
289  }
290  destHasNoDataValue = true;
291  }
292  }
293 
294  if ( nuller && destHasNoDataValue )
295  {
296  nuller->setOutputNoDataValue( bandNo, destNoDataValue );
297  }
298 
299  QgsDebugMsgLevel( QStringLiteral( "bandNo = %1 destDataType = %2 destHasNoDataValue = %3 destNoDataValue = %4" ).arg( bandNo ).arg( qgsEnumValueToKey( destDataType ) ).arg( destHasNoDataValue ).arg( destNoDataValue ), 4 );
300  destDataTypeList.append( destDataType );
301  destHasNoDataValueList.append( destHasNoDataValue );
302  destNoDataValueList.append( destNoDataValue );
303  }
304 
305  Qgis::DataType destDataType = destDataTypeList.value( 0 );
306  // Currently write API supports one output type for dataset only -> find the widest
307  for ( int i = 1; i < nBands; i++ )
308  {
309  if ( destDataTypeList.value( i ) > destDataType )
310  {
311  destDataType = destDataTypeList.value( i );
312  // no data value may be left per band (for future)
313  }
314  }
315 
316  WriterError error;
317  for ( int attempt = 0; attempt < 2; attempt ++ )
318  {
319  //create destProvider for whole dataset here
320  // initOutput() returns 0 in tile mode!
321  std::unique_ptr<QgsRasterDataProvider> destProvider(
322  initOutput( nCols, nRows, crs, geoTransform, nBands, destDataType, destHasNoDataValueList, destNoDataValueList ) );
323  if ( !mTiledMode )
324  {
325  if ( !destProvider )
326  {
327  return CreateDatasourceError;
328  }
329  if ( !destProvider->isValid() )
330  {
331  if ( feedback && !destProvider->error().isEmpty() )
332  {
333  feedback->appendError( destProvider->error().summary() );
334  }
335  return CreateDatasourceError;
336  }
337  if ( nCols != destProvider->xSize() || nRows != destProvider->ySize() )
338  {
339  QgsDebugMsg( QStringLiteral( "Created raster does not have requested dimensions" ) );
340  if ( feedback )
341  {
342  feedback->appendError( QObject::tr( "Created raster does not have requested dimensions" ) );
343  }
344  return CreateDatasourceError;
345  }
346  if ( nBands != destProvider->bandCount() )
347  {
348  QgsDebugMsg( QStringLiteral( "Created raster does not have requested band count" ) );
349  if ( feedback )
350  {
351  feedback->appendError( QObject::tr( "Created raster does not have requested band count" ) );
352  }
353  return CreateDatasourceError;
354  }
355  if ( nBands )
356  {
357  // Some driver like GS7BG may accept Byte as requested data type,
358  // but actually return a driver with Float64...
359  destDataType = destProvider->dataType( 1 );
360  }
361  }
362 
363  error = writeDataRaster( pipe, iter, nCols, nRows, outputExtent, crs, destDataType, destHasNoDataValueList, destNoDataValueList, destProvider.get(), feedback );
364 
365  if ( attempt == 0 && error == NoDataConflict )
366  {
367  // The value used for no data was found in source data, we must use wider data type
368  if ( destProvider ) // no tiles
369  {
370  destProvider->remove();
371  destProvider.reset();
372  }
373  else // VRT
374  {
375  // TODO: remove created VRT
376  }
377 
378  // But we don't know which band -> wider all
379  for ( int i = 0; i < nBands; i++ )
380  {
381  double destNoDataValue;
382  const Qgis::DataType destDataType = QgsRasterBlock::typeWithNoDataValue( destDataTypeList.value( i ), &destNoDataValue );
383  destDataTypeList.replace( i, destDataType );
384  destNoDataValueList.replace( i, destNoDataValue );
385  }
386  destDataType = destDataTypeList.value( 0 );
387 
388  // Try again
389  }
390  else
391  {
392  break;
393  }
394  }
395 
396  return error;
397 }
398 
399 static int qgsDivRoundUp( int a, int b )
400 {
401  return a / b + ( ( ( a % b ) != 0 ) ? 1 : 0 );
402 }
403 
404 QgsRasterFileWriter::WriterError QgsRasterFileWriter::writeDataRaster( const QgsRasterPipe *pipe,
405  QgsRasterIterator *iter,
406  int nCols, int nRows,
407  const QgsRectangle &outputExtent,
409  Qgis::DataType destDataType,
410  const QList<bool> &destHasNoDataValueList,
411  const QList<double> &destNoDataValueList,
412  QgsRasterDataProvider *destProvider,
413  QgsRasterBlockFeedback *feedback )
414 {
415  Q_UNUSED( pipe )
416  Q_UNUSED( destHasNoDataValueList )
417  QgsDebugMsgLevel( QStringLiteral( "Entered" ), 4 );
418 
419  const QgsRasterInterface *iface = iter->input();
420  const QgsRasterDataProvider *srcProvider = dynamic_cast<const QgsRasterDataProvider *>( iface->sourceInput() );
421  const int nBands = iface->bandCount();
422  QgsDebugMsgLevel( QStringLiteral( "nBands = %1" ).arg( nBands ), 4 );
423 
424  //Get output map units per pixel
425  int iterLeft = 0;
426  int iterTop = 0;
427  int iterCols = 0;
428  int iterRows = 0;
429 
430  std::vector< std::unique_ptr<QgsRasterBlock> > blockList;
431  std::vector< std::unique_ptr<QgsRasterBlock> > destBlockList;
432 
433  blockList.resize( nBands );
434  destBlockList.resize( nBands );
435 
436  for ( int i = 1; i <= nBands; ++i )
437  {
438  iter->startRasterRead( i, nCols, nRows, outputExtent );
439  if ( destProvider && destHasNoDataValueList.value( i - 1 ) ) // no tiles
440  {
441  destProvider->setNoDataValue( i, destNoDataValueList.value( i - 1 ) );
442  }
443  }
444 
445  int nParts = 0;
446  int fileIndex = 0;
447  if ( feedback )
448  {
449  const int nPartsX = qgsDivRoundUp( nCols, iter->maximumTileWidth() );
450  const int nPartsY = qgsDivRoundUp( nRows, iter->maximumTileHeight() );
451  nParts = nPartsX * nPartsY;
452  }
453 
454  // hmm why is there a for(;;) here ..
455  // not good coding practice IMHO, it might be better to use [ for() and break ] or [ while (test) ]
456  Q_FOREVER
457  {
458  for ( int i = 1; i <= nBands; ++i )
459  {
460  QgsRasterBlock *block = nullptr;
461  if ( !iter->readNextRasterPart( i, iterCols, iterRows, &block, iterLeft, iterTop ) )
462  {
463  // No more parts, create VRT and return
464  if ( mTiledMode )
465  {
466  const QString vrtFilePath( mOutputUrl + '/' + vrtFileName() );
467  writeVRT( vrtFilePath );
468  if ( mBuildPyramidsFlag == QgsRaster::PyramidsFlagYes )
469  {
470  buildPyramids( vrtFilePath );
471  }
472  }
473  else
474  {
475  if ( mBuildPyramidsFlag == QgsRaster::PyramidsFlagYes )
476  {
477  buildPyramids( mOutputUrl, destProvider );
478  }
479  }
480 
481  QgsDebugMsgLevel( QStringLiteral( "Done" ), 4 );
482  return NoError; //reached last tile, bail out
483  }
484  blockList[i - 1].reset( block );
485  // TODO: verify if NoDataConflict happened, to do that we need the whole pipe or nuller interface
486  }
487 
488  if ( feedback && fileIndex < ( nParts - 1 ) )
489  {
490  feedback->setProgress( 100.0 * fileIndex / static_cast< double >( nParts ) );
491  if ( feedback->isCanceled() )
492  {
493  break;
494  }
495  }
496 
497  // It may happen that internal data type (dataType) is wider than destDataType
498  for ( int i = 1; i <= nBands; ++i )
499  {
500  if ( srcProvider && srcProvider->dataType( i ) == destDataType )
501  {
502  // nothing
503  }
504  else
505  {
506  // TODO: this conversion should go to QgsRasterDataProvider::write with additional input data type param
507  blockList[i - 1]->convert( destDataType );
508  }
509  destBlockList[i - 1] = std::move( blockList[i - 1] );
510  }
511 
512  if ( mTiledMode ) //write to file
513  {
514  std::unique_ptr< QgsRasterDataProvider > partDestProvider( createPartProvider( outputExtent,
515  nCols, iterCols, iterRows,
516  iterLeft, iterTop, mOutputUrl,
517  fileIndex, nBands, destDataType, crs ) );
518 
519  if ( !partDestProvider || !partDestProvider->isValid() )
520  {
521  return DestProviderError;
522  }
523 
524  //write data to output file. todo: loop over the data list
525  for ( int i = 1; i <= nBands; ++i )
526  {
527  if ( destHasNoDataValueList.value( i - 1 ) )
528  {
529  partDestProvider->setNoDataValue( i, destNoDataValueList.value( i - 1 ) );
530  }
531  if ( destBlockList[ i - 1 ]->isEmpty() )
532  continue;
533 
534  if ( !partDestProvider->write( destBlockList[i - 1]->bits( 0 ), i, iterCols, iterRows, 0, 0 ) )
535  {
536  return WriteError;
537  }
538  addToVRT( partFileName( fileIndex ), i, iterCols, iterRows, iterLeft, iterTop );
539  }
540 
541  }
542  else if ( destProvider )
543  {
544  //loop over data
545  for ( int i = 1; i <= nBands; ++i )
546  {
547  if ( destBlockList[ i - 1 ]->isEmpty() )
548  continue;
549 
550  if ( !destProvider->write( destBlockList[i - 1]->bits( 0 ), i, iterCols, iterRows, iterLeft, iterTop ) )
551  {
552  return WriteError;
553  }
554  }
555  }
556  ++fileIndex;
557  }
558 
559  QgsDebugMsgLevel( QStringLiteral( "Done" ), 4 );
560  return ( feedback && feedback->isCanceled() ) ? WriteCanceled : NoError;
561 }
562 
563 QgsRasterFileWriter::WriterError QgsRasterFileWriter::writeImageRaster( QgsRasterIterator *iter, int nCols, int nRows, const QgsRectangle &outputExtent,
565 {
566  QgsDebugMsgLevel( QStringLiteral( "Entered" ), 4 );
567  if ( !iter )
568  {
569  return SourceProviderError;
570  }
571 
572  const QgsRasterInterface *iface = iter->input();
573  if ( !iface )
574  return SourceProviderError;
575 
576  const Qgis::DataType inputDataType = iface->dataType( 1 );
577  if ( inputDataType != Qgis::DataType::ARGB32 && inputDataType != Qgis::DataType::ARGB32_Premultiplied )
578  {
579  return SourceProviderError;
580  }
581  const bool isPremultiplied = ( inputDataType == Qgis::DataType::ARGB32_Premultiplied );
582 
583  iter->setMaximumTileWidth( mMaxTileWidth );
584  iter->setMaximumTileHeight( mMaxTileHeight );
585 
586  const size_t nMaxPixels = static_cast<size_t>( mMaxTileWidth ) * mMaxTileHeight;
587  std::vector<unsigned char> redData( nMaxPixels );
588  std::vector<unsigned char> greenData( nMaxPixels );
589  std::vector<unsigned char> blueData( nMaxPixels );
590  std::vector<unsigned char> alphaData( nMaxPixels );
591  int iterLeft = 0, iterTop = 0, iterCols = 0, iterRows = 0;
592  int fileIndex = 0;
593 
594  //create destProvider for whole dataset here
595  double pixelSize;
596  double geoTransform[6];
597  globalOutputParameters( outputExtent, nCols, nRows, geoTransform, pixelSize );
598 
599  const int nOutputBands = 4;
600  std::unique_ptr< QgsRasterDataProvider > destProvider( initOutput( nCols, nRows, crs, geoTransform, nOutputBands, Qgis::DataType::Byte ) );
601  if ( !mTiledMode )
602  {
603  if ( !destProvider )
604  {
605  return CreateDatasourceError;
606  }
607  if ( !destProvider->isValid() )
608  {
609  if ( feedback && !destProvider->error().isEmpty() )
610  {
611  feedback->appendError( destProvider->error().summary() );
612  }
613  return CreateDatasourceError;
614  }
615  if ( nCols != destProvider->xSize() || nRows != destProvider->ySize() )
616  {
617  QgsDebugMsg( QStringLiteral( "Created raster does not have requested dimensions" ) );
618  if ( feedback )
619  {
620  feedback->appendError( QObject::tr( "Created raster does not have requested dimensions" ) );
621  }
622  return CreateDatasourceError;
623  }
624  if ( nOutputBands != destProvider->bandCount() )
625  {
626  QgsDebugMsg( QStringLiteral( "Created raster does not have requested band count" ) );
627  if ( feedback )
628  {
629  feedback->appendError( QObject::tr( "Created raster does not have requested band count" ) );
630  }
631  return CreateDatasourceError;
632  }
633  if ( Qgis::DataType::Byte != destProvider->dataType( 1 ) )
634  {
635  QgsDebugMsg( QStringLiteral( "Created raster does not have requested data type" ) );
636  if ( feedback )
637  {
638  feedback->appendError( QObject::tr( "Created raster does not have requested data type" ) );
639  }
640  return CreateDatasourceError;
641  }
642  }
643 
644  iter->startRasterRead( 1, nCols, nRows, outputExtent, feedback );
645 
646  int nParts = 0;
647  if ( feedback )
648  {
649  const int nPartsX = qgsDivRoundUp( nCols, iter->maximumTileWidth() );
650  const int nPartsY = qgsDivRoundUp( nRows, iter->maximumTileHeight() );
651  nParts = nPartsX * nPartsY;
652  }
653 
654  std::unique_ptr< QgsRasterBlock > inputBlock;
655  while ( iter->readNextRasterPart( 1, iterCols, iterRows, inputBlock, iterLeft, iterTop ) )
656  {
657  if ( !inputBlock || inputBlock->isEmpty() )
658  {
659  continue;
660  }
661 
662  if ( feedback && fileIndex < ( nParts - 1 ) )
663  {
664  feedback->setProgress( 100.0 * fileIndex / static_cast< double >( nParts ) );
665  if ( feedback->isCanceled() )
666  {
667  break;
668  }
669  }
670 
671  //fill into red/green/blue/alpha channels
672  const qgssize nPixels = static_cast< qgssize >( iterCols ) * iterRows;
673  for ( qgssize i = 0; i < nPixels; ++i )
674  {
675  QRgb c = inputBlock->color( i );
676  if ( isPremultiplied )
677  {
678  c = qUnpremultiply( c );
679  }
680  redData[i] = static_cast<unsigned char>( qRed( c ) );
681  greenData[i] = static_cast<unsigned char>( qGreen( c ) );
682  blueData[i] = static_cast<unsigned char>( qBlue( c ) );
683  alphaData[i] = static_cast<unsigned char>( qAlpha( c ) );
684  }
685 
686  //create output file
687  if ( mTiledMode )
688  {
689  std::unique_ptr< QgsRasterDataProvider > partDestProvider( createPartProvider( outputExtent,
690  nCols, iterCols, iterRows,
691  iterLeft, iterTop, mOutputUrl, fileIndex,
692  4, Qgis::DataType::Byte, crs ) );
693 
694  if ( !partDestProvider || partDestProvider->isValid() )
695  {
696  return DestProviderError;
697  }
698 
699  //write data to output file
700  if ( !partDestProvider->write( &redData[0], 1, iterCols, iterRows, 0, 0 ) ||
701  !partDestProvider->write( &greenData[0], 2, iterCols, iterRows, 0, 0 ) ||
702  !partDestProvider->write( &blueData[0], 3, iterCols, iterRows, 0, 0 ) ||
703  !partDestProvider->write( &alphaData[0], 4, iterCols, iterRows, 0, 0 ) )
704  {
705  return WriteError;
706  }
707 
708  addToVRT( partFileName( fileIndex ), 1, iterCols, iterRows, iterLeft, iterTop );
709  addToVRT( partFileName( fileIndex ), 2, iterCols, iterRows, iterLeft, iterTop );
710  addToVRT( partFileName( fileIndex ), 3, iterCols, iterRows, iterLeft, iterTop );
711  addToVRT( partFileName( fileIndex ), 4, iterCols, iterRows, iterLeft, iterTop );
712  }
713  else if ( destProvider )
714  {
715  if ( !destProvider->write( &redData[0], 1, iterCols, iterRows, iterLeft, iterTop ) ||
716  !destProvider->write( &greenData[0], 2, iterCols, iterRows, iterLeft, iterTop ) ||
717  !destProvider->write( &blueData[0], 3, iterCols, iterRows, iterLeft, iterTop ) ||
718  !destProvider->write( &alphaData[0], 4, iterCols, iterRows, iterLeft, iterTop ) )
719  {
720  return WriteError;
721  }
722  }
723 
724  ++fileIndex;
725  }
726  destProvider.reset();
727 
728  if ( feedback )
729  {
730  feedback->setProgress( 100.0 );
731  }
732 
733  if ( mTiledMode )
734  {
735  const QString vrtFilePath( mOutputUrl + '/' + vrtFileName() );
736  writeVRT( vrtFilePath );
737  if ( mBuildPyramidsFlag == QgsRaster::PyramidsFlagYes )
738  {
739  buildPyramids( vrtFilePath );
740  }
741  }
742  else
743  {
744  if ( mBuildPyramidsFlag == QgsRaster::PyramidsFlagYes )
745  {
746  buildPyramids( mOutputUrl );
747  }
748  }
749  return ( feedback && feedback->isCanceled() ) ? WriteCanceled : NoError;
750 }
751 
752 void QgsRasterFileWriter::addToVRT( const QString &filename, int band, int xSize, int ySize, int xOffset, int yOffset )
753 {
754  QDomElement bandElem = mVRTBands.value( band - 1 );
755 
756  QDomElement simpleSourceElem = mVRTDocument.createElement( QStringLiteral( "SimpleSource" ) );
757 
758  //SourceFilename
759  QDomElement sourceFilenameElem = mVRTDocument.createElement( QStringLiteral( "SourceFilename" ) );
760  sourceFilenameElem.setAttribute( QStringLiteral( "relativeToVRT" ), QStringLiteral( "1" ) );
761  const QDomText sourceFilenameText = mVRTDocument.createTextNode( filename );
762  sourceFilenameElem.appendChild( sourceFilenameText );
763  simpleSourceElem.appendChild( sourceFilenameElem );
764 
765  //SourceBand
766  QDomElement sourceBandElem = mVRTDocument.createElement( QStringLiteral( "SourceBand" ) );
767  const QDomText sourceBandText = mVRTDocument.createTextNode( QString::number( band ) );
768  sourceBandElem.appendChild( sourceBandText );
769  simpleSourceElem.appendChild( sourceBandElem );
770 
771  //SourceProperties
772  QDomElement sourcePropertiesElem = mVRTDocument.createElement( QStringLiteral( "SourceProperties" ) );
773  sourcePropertiesElem.setAttribute( QStringLiteral( "RasterXSize" ), xSize );
774  sourcePropertiesElem.setAttribute( QStringLiteral( "RasterYSize" ), ySize );
775  sourcePropertiesElem.setAttribute( QStringLiteral( "BlockXSize" ), xSize );
776  sourcePropertiesElem.setAttribute( QStringLiteral( "BlockYSize" ), ySize );
777  sourcePropertiesElem.setAttribute( QStringLiteral( "DataType" ), QStringLiteral( "Byte" ) );
778  simpleSourceElem.appendChild( sourcePropertiesElem );
779 
780  //SrcRect
781  QDomElement srcRectElem = mVRTDocument.createElement( QStringLiteral( "SrcRect" ) );
782  srcRectElem.setAttribute( QStringLiteral( "xOff" ), QStringLiteral( "0" ) );
783  srcRectElem.setAttribute( QStringLiteral( "yOff" ), QStringLiteral( "0" ) );
784  srcRectElem.setAttribute( QStringLiteral( "xSize" ), xSize );
785  srcRectElem.setAttribute( QStringLiteral( "ySize" ), ySize );
786  simpleSourceElem.appendChild( srcRectElem );
787 
788  //DstRect
789  QDomElement dstRectElem = mVRTDocument.createElement( QStringLiteral( "DstRect" ) );
790  dstRectElem.setAttribute( QStringLiteral( "xOff" ), xOffset );
791  dstRectElem.setAttribute( QStringLiteral( "yOff" ), yOffset );
792  dstRectElem.setAttribute( QStringLiteral( "xSize" ), xSize );
793  dstRectElem.setAttribute( QStringLiteral( "ySize" ), ySize );
794  simpleSourceElem.appendChild( dstRectElem );
795 
796  bandElem.appendChild( simpleSourceElem );
797 }
798 
799 void QgsRasterFileWriter::buildPyramids( const QString &filename, QgsRasterDataProvider *destProviderIn )
800 {
801  QgsDebugMsgLevel( "filename = " + filename, 4 );
802  // open new dataProvider so we can build pyramids with it
803  const QgsDataProvider::ProviderOptions providerOptions;
804  QgsRasterDataProvider *destProvider = destProviderIn;
805  if ( !destProvider )
806  {
807  destProvider = qobject_cast< QgsRasterDataProvider * >( QgsProviderRegistry::instance()->createProvider( mOutputProviderKey, filename, providerOptions ) );
808  if ( !destProvider || !destProvider->isValid() )
809  {
810  delete destProvider;
811  return;
812  }
813  }
814 
815  // TODO progress report
816  // TODO test mTiledMode - not tested b/c segfault at line # 289
817  // connect( provider, SIGNAL( progressUpdate( int ) ), mPyramidProgress, SLOT( setValue( int ) ) );
818  QList< QgsRasterPyramid> myPyramidList;
819  if ( ! mPyramidsList.isEmpty() )
820  myPyramidList = destProvider->buildPyramidList( mPyramidsList );
821  for ( int myCounterInt = 0; myCounterInt < myPyramidList.count(); myCounterInt++ )
822  {
823  myPyramidList[myCounterInt].setBuild( true );
824  }
825 
826  QgsDebugMsgLevel( QStringLiteral( "building pyramids : %1 pyramids, %2 resampling, %3 format, %4 options" ).arg( myPyramidList.count() ).arg( mPyramidsResampling ).arg( mPyramidsFormat ).arg( mPyramidsConfigOptions.count() ), 4 );
827  // QApplication::setOverrideCursor( Qt::WaitCursor );
828  const QString res = destProvider->buildPyramids( myPyramidList, mPyramidsResampling,
829  mPyramidsFormat, mPyramidsConfigOptions );
830  // QApplication::restoreOverrideCursor();
831 
832  // TODO put this in provider or elsewhere
833  if ( !res.isNull() )
834  {
835  QString title, message;
836  if ( res == QLatin1String( "ERROR_WRITE_ACCESS" ) )
837  {
838  title = QObject::tr( "Building Pyramids" );
839  message = QObject::tr( "Write access denied. Adjust the file permissions and try again." );
840  }
841  else if ( res == QLatin1String( "ERROR_WRITE_FORMAT" ) )
842  {
843  title = QObject::tr( "Building Pyramids" );
844  message = QObject::tr( "The file was not writable. Some formats do not "
845  "support pyramid overviews. Consult the GDAL documentation if in doubt." );
846  }
847  else if ( res == QLatin1String( "FAILED_NOT_SUPPORTED" ) )
848  {
849  title = QObject::tr( "Building Pyramids" );
850  message = QObject::tr( "Building pyramid overviews is not supported on this type of raster." );
851  }
852  else if ( res == QLatin1String( "ERROR_VIRTUAL" ) )
853  {
854  title = QObject::tr( "Building Pyramids" );
855  message = QObject::tr( "Building pyramid overviews is not supported on this type of raster." );
856  }
857  QMessageBox::warning( nullptr, title, message );
858  QgsDebugMsgLevel( res + " - " + message, 4 );
859  }
860  if ( !destProviderIn )
861  delete destProvider;
862 }
863 
864 #if 0
865 int QgsRasterFileWriter::pyramidsProgress( double dfComplete, const char *pszMessage, void *pData )
866 {
867  Q_UNUSED( pszMessage )
868  GDALTermProgress( dfComplete, 0, 0 );
869  QProgressDialog *progressDialog = static_cast<QProgressDialog *>( pData );
870  if ( pData && progressDialog->wasCanceled() )
871  {
872  return 0;
873  }
874 
875  if ( pData )
876  {
877  progressDialog->setRange( 0, 100 );
878  progressDialog->setValue( dfComplete * 100 );
879  }
880  return 1;
881 }
882 #endif
883 
884 void QgsRasterFileWriter::createVRT( int xSize, int ySize, const QgsCoordinateReferenceSystem &crs, double *geoTransform, Qgis::DataType type, const QList<bool> &destHasNoDataValueList, const QList<double> &destNoDataValueList )
885 {
886  mVRTDocument.clear();
887  QDomElement VRTDatasetElem = mVRTDocument.createElement( QStringLiteral( "VRTDataset" ) );
888 
889  //xsize / ysize
890  VRTDatasetElem.setAttribute( QStringLiteral( "rasterXSize" ), xSize );
891  VRTDatasetElem.setAttribute( QStringLiteral( "rasterYSize" ), ySize );
892  mVRTDocument.appendChild( VRTDatasetElem );
893 
894  //CRS
895  QDomElement SRSElem = mVRTDocument.createElement( QStringLiteral( "SRS" ) );
896  const QDomText crsText = mVRTDocument.createTextNode( crs.toWkt() );
897  SRSElem.appendChild( crsText );
898  VRTDatasetElem.appendChild( SRSElem );
899 
900  //geotransform
901  if ( geoTransform )
902  {
903  QDomElement geoTransformElem = mVRTDocument.createElement( QStringLiteral( "GeoTransform" ) );
904  const QString geoTransformString = QString::number( geoTransform[0], 'f', 6 ) + ", " + QString::number( geoTransform[1] ) + ", " + QString::number( geoTransform[2] ) +
905  ", " + QString::number( geoTransform[3], 'f', 6 ) + ", " + QString::number( geoTransform[4] ) + ", " + QString::number( geoTransform[5] );
906  const QDomText geoTransformText = mVRTDocument.createTextNode( geoTransformString );
907  geoTransformElem.appendChild( geoTransformText );
908  VRTDatasetElem.appendChild( geoTransformElem );
909  }
910 
911  int nBands;
912  if ( mMode == Raw )
913  {
914  nBands = mInput->bandCount();
915  }
916  else
917  {
918  nBands = 4;
919  }
920 
921  QStringList colorInterp;
922  colorInterp << QStringLiteral( "Red" ) << QStringLiteral( "Green" ) << QStringLiteral( "Blue" ) << QStringLiteral( "Alpha" );
923 
924  QMap<Qgis::DataType, QString> dataTypes;
925  dataTypes.insert( Qgis::DataType::Byte, QStringLiteral( "Byte" ) );
926  dataTypes.insert( Qgis::DataType::UInt16, QStringLiteral( "UInt16" ) );
927  dataTypes.insert( Qgis::DataType::Int16, QStringLiteral( "Int16" ) );
928  dataTypes.insert( Qgis::DataType::UInt32, QStringLiteral( "Int32" ) );
929  dataTypes.insert( Qgis::DataType::Float32, QStringLiteral( "Float32" ) );
930  dataTypes.insert( Qgis::DataType::Float64, QStringLiteral( "Float64" ) );
931  dataTypes.insert( Qgis::DataType::CInt16, QStringLiteral( "CInt16" ) );
932  dataTypes.insert( Qgis::DataType::CInt32, QStringLiteral( "CInt32" ) );
933  dataTypes.insert( Qgis::DataType::CFloat32, QStringLiteral( "CFloat32" ) );
934  dataTypes.insert( Qgis::DataType::CFloat64, QStringLiteral( "CFloat64" ) );
935 
936  for ( int i = 1; i <= nBands; i++ )
937  {
938  QDomElement VRTBand = mVRTDocument.createElement( QStringLiteral( "VRTRasterBand" ) );
939 
940  VRTBand.setAttribute( QStringLiteral( "band" ), QString::number( i ) );
941  const QString dataType = dataTypes.value( type );
942  VRTBand.setAttribute( QStringLiteral( "dataType" ), dataType );
943 
944  if ( mMode == Image )
945  {
946  VRTBand.setAttribute( QStringLiteral( "dataType" ), QStringLiteral( "Byte" ) );
947  QDomElement colorInterpElement = mVRTDocument.createElement( QStringLiteral( "ColorInterp" ) );
948  const QDomText interpText = mVRTDocument.createTextNode( colorInterp.value( i - 1 ) );
949  colorInterpElement.appendChild( interpText );
950  VRTBand.appendChild( colorInterpElement );
951  }
952 
953  if ( !destHasNoDataValueList.isEmpty() && destHasNoDataValueList.value( i - 1 ) )
954  {
955  VRTBand.setAttribute( QStringLiteral( "NoDataValue" ), QString::number( destNoDataValueList.value( i - 1 ) ) );
956  }
957 
958  mVRTBands.append( VRTBand );
959  VRTDatasetElem.appendChild( VRTBand );
960  }
961 }
962 
963 bool QgsRasterFileWriter::writeVRT( const QString &file )
964 {
965  QFile outputFile( file );
966  if ( ! outputFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
967  {
968  return false;
969  }
970 
971  QTextStream outStream( &outputFile );
972  mVRTDocument.save( outStream, 2 );
973  return true;
974 }
975 
976 QgsRasterDataProvider *QgsRasterFileWriter::createPartProvider( const QgsRectangle &extent, int nCols, int iterCols,
977  int iterRows, int iterLeft, int iterTop, const QString &outputUrl, int fileIndex, int nBands, Qgis::DataType type,
979 {
980  const double mup = extent.width() / nCols;
981  const double mapLeft = extent.xMinimum() + iterLeft * mup;
982  const double mapRight = mapLeft + mup * iterCols;
983  const double mapTop = extent.yMaximum() - iterTop * mup;
984  const double mapBottom = mapTop - iterRows * mup;
985  const QgsRectangle mapRect( mapLeft, mapBottom, mapRight, mapTop );
986 
987  const QString outputFile = outputUrl + '/' + partFileName( fileIndex );
988 
989  //geotransform
990  double geoTransform[6];
991  geoTransform[0] = mapRect.xMinimum();
992  geoTransform[1] = mup;
993  geoTransform[2] = 0.0;
994  geoTransform[3] = mapRect.yMaximum();
995  geoTransform[4] = 0.0;
996  geoTransform[5] = -mup;
997 
998  // perhaps we need a separate createOptions for tiles ?
999 
1000  QgsRasterDataProvider *destProvider = QgsRasterDataProvider::create( mOutputProviderKey, outputFile, mOutputFormat, nBands, type, iterCols, iterRows, geoTransform, crs, mCreateOptions );
1001 
1002  // TODO: return provider and report error
1003  return destProvider;
1004 }
1005 
1006 QgsRasterDataProvider *QgsRasterFileWriter::initOutput( int nCols, int nRows, const QgsCoordinateReferenceSystem &crs,
1007  double *geoTransform, int nBands, Qgis::DataType type,
1008  const QList<bool> &destHasNoDataValueList, const QList<double> &destNoDataValueList )
1009 {
1010  if ( mTiledMode )
1011  {
1012  createVRT( nCols, nRows, crs, geoTransform, type, destHasNoDataValueList, destNoDataValueList );
1013  return nullptr;
1014  }
1015  else
1016  {
1017 #if 0
1018  // TODO enable "use existing", has no effect for now, because using Create() in gdal provider
1019  // should this belong in provider? should also test that source provider is gdal
1020  if ( mBuildPyramidsFlag == -4 && mOutputProviderKey == "gdal" && mOutputFormat.compare( QLatin1String( "gtiff" ), Qt::CaseInsensitive ) == 0 )
1021  mCreateOptions << "COPY_SRC_OVERVIEWS=YES";
1022 #endif
1023 
1024  QgsRasterDataProvider *destProvider = QgsRasterDataProvider::create( mOutputProviderKey, mOutputUrl, mOutputFormat, nBands, type, nCols, nRows, geoTransform, crs, mCreateOptions );
1025 
1026  if ( !destProvider )
1027  {
1028  QgsDebugMsg( QStringLiteral( "No provider created" ) );
1029  }
1030 
1031  return destProvider;
1032  }
1033 }
1034 
1035 void QgsRasterFileWriter::globalOutputParameters( const QgsRectangle &extent, int nCols, int &nRows,
1036  double *geoTransform, double &pixelSize )
1037 {
1038  pixelSize = extent.width() / nCols;
1039 
1040  //calculate nRows automatically for providers without exact resolution
1041  if ( nRows < 0 )
1042  {
1043  nRows = static_cast< double >( nCols ) / extent.width() * extent.height() + 0.5; //NOLINT
1044  }
1045  geoTransform[0] = extent.xMinimum();
1046  geoTransform[1] = pixelSize;
1047  geoTransform[2] = 0.0;
1048  geoTransform[3] = extent.yMaximum();
1049  geoTransform[4] = 0.0;
1050  geoTransform[5] = -( extent.height() / nRows );
1051 }
1052 
1053 QString QgsRasterFileWriter::partFileName( int fileIndex )
1054 {
1055  // .tif for now
1056  const QFileInfo outputInfo( mOutputUrl );
1057  return QStringLiteral( "%1.%2.tif" ).arg( outputInfo.fileName() ).arg( fileIndex );
1058 }
1059 
1060 QString QgsRasterFileWriter::vrtFileName()
1061 {
1062  const QFileInfo outputInfo( mOutputUrl );
1063  return QStringLiteral( "%1.vrt" ).arg( outputInfo.fileName() );
1064 }
1065 
1066 QString QgsRasterFileWriter::driverForExtension( const QString &extension )
1067 {
1068  QString ext = extension.trimmed();
1069  if ( ext.isEmpty() )
1070  return QString();
1071 
1072  if ( ext.startsWith( '.' ) )
1073  ext.remove( 0, 1 );
1074 
1075  GDALAllRegister();
1076  int const drvCount = GDALGetDriverCount();
1077 
1078  for ( int i = 0; i < drvCount; ++i )
1079  {
1080  GDALDriverH drv = GDALGetDriver( i );
1081  if ( drv )
1082  {
1083  char **driverMetadata = GDALGetMetadata( drv, nullptr );
1084  if ( CSLFetchBoolean( driverMetadata, GDAL_DCAP_RASTER, false ) )
1085  {
1086  QString drvName = GDALGetDriverShortName( drv );
1087  const QStringList driverExtensions = QString( GDALGetMetadataItem( drv, GDAL_DMD_EXTENSIONS, nullptr ) ).split( ' ' );
1088 
1089  const auto constDriverExtensions = driverExtensions;
1090  for ( const QString &driver : constDriverExtensions )
1091  {
1092  if ( driver.compare( ext, Qt::CaseInsensitive ) == 0 )
1093  return drvName;
1094  }
1095  }
1096  }
1097  }
1098  return QString();
1099 }
1100 
1101 QStringList QgsRasterFileWriter::extensionsForFormat( const QString &format )
1102 {
1103  GDALDriverH drv = GDALGetDriverByName( format.toLocal8Bit().data() );
1104  if ( drv )
1105  {
1106  char **driverMetadata = GDALGetMetadata( drv, nullptr );
1107  if ( CSLFetchBoolean( driverMetadata, GDAL_DCAP_RASTER, false ) )
1108  {
1109  return QString( GDALGetMetadataItem( drv, GDAL_DMD_EXTENSIONS, nullptr ) ).split( ' ' );
1110  }
1111  }
1112  return QStringList();
1113 }
1114 
1115 QString QgsRasterFileWriter::filterForDriver( const QString &driverName )
1116 {
1117  GDALDriverH drv = GDALGetDriverByName( driverName.toLocal8Bit().data() );
1118  if ( drv )
1119  {
1120  const QString drvName = GDALGetDriverLongName( drv );
1121  const QString extensionsString = QString( GDALGetMetadataItem( drv, GDAL_DMD_EXTENSIONS, nullptr ) );
1122  if ( extensionsString.isEmpty() )
1123  {
1124  return QString();
1125  }
1126  const QStringList extensions = extensionsString.split( ' ' );
1127  QString filter = drvName + " (";
1128  for ( const QString &ext : extensions )
1129  {
1130  filter.append( QStringLiteral( "*.%1 *.%2 " ).arg( ext.toLower(), ext.toUpper() ) );
1131  }
1132  filter = filter.trimmed().append( QStringLiteral( ")" ) );
1133  return filter;
1134  }
1135 
1136  return QString();
1137 }
1138 
1139 QList< QgsRasterFileWriter::FilterFormatDetails > QgsRasterFileWriter::supportedFiltersAndFormats( RasterFormatOptions options )
1140 {
1141  static QReadWriteLock sFilterLock;
1142  static QMap< RasterFormatOptions, QList< QgsRasterFileWriter::FilterFormatDetails > > sFilters;
1143 
1144  QgsReadWriteLocker locker( sFilterLock, QgsReadWriteLocker::Read );
1145 
1146  const auto it = sFilters.constFind( options );
1147  if ( it != sFilters.constEnd() )
1148  return it.value();
1149 
1150  GDALAllRegister();
1151  int const drvCount = GDALGetDriverCount();
1152 
1154  QList< QgsRasterFileWriter::FilterFormatDetails > results;
1155 
1156  FilterFormatDetails tifFormat;
1157 
1158  for ( int i = 0; i < drvCount; ++i )
1159  {
1160  GDALDriverH drv = GDALGetDriver( i );
1161  if ( drv )
1162  {
1164  {
1165  const QString drvName = GDALGetDriverShortName( drv );
1166  const QString filterString = filterForDriver( drvName );
1167  if ( filterString.isEmpty() )
1168  continue;
1169 
1170  FilterFormatDetails details;
1171  details.driverName = drvName;
1172  details.filterString = filterString;
1173 
1174  if ( options & SortRecommended )
1175  {
1176  if ( drvName == QLatin1String( "GTiff" ) )
1177  {
1178  tifFormat = details;
1179  continue;
1180  }
1181  }
1182 
1183  results << details;
1184  }
1185  }
1186  }
1187 
1188  std::sort( results.begin(), results.end(), []( const FilterFormatDetails & a, const FilterFormatDetails & b ) -> bool
1189  {
1190  return a.driverName < b.driverName;
1191  } );
1192 
1193  if ( options & SortRecommended )
1194  {
1195  if ( !tifFormat.filterString.isEmpty() )
1196  {
1197  results.insert( 0, tifFormat );
1198  }
1199  }
1200 
1201  sFilters.insert( options, results );
1202 
1203  return results;
1204 }
1205 
1206 QStringList QgsRasterFileWriter::supportedFormatExtensions( const RasterFormatOptions options )
1207 {
1208  const auto formats = supportedFiltersAndFormats( options );
1209  QStringList extensions;
1210 
1211  const QRegularExpression rx( QStringLiteral( "\\*\\.([a-zA-Z0-9]*)" ) );
1212 
1213  for ( const FilterFormatDetails &format : formats )
1214  {
1215  const QString ext = format.filterString;
1216  const QRegularExpressionMatch match = rx.match( ext );
1217  if ( !match.hasMatch() )
1218  continue;
1219 
1220  const QString matched = match.captured( 1 );
1221  extensions << matched;
1222  }
1223  return extensions;
1224 }
DataType
Raster data types.
Definition: qgis.h:121
@ CInt32
Complex Int32.
@ Float32
Thirty two bit floating point (float)
@ CFloat64
Complex Float64.
@ Int16
Sixteen bit signed integer (qint16)
@ ARGB32_Premultiplied
Color, alpha, red, green, blue, 4 bytes the same as QImage::Format_ARGB32_Premultiplied.
@ UInt16
Sixteen bit unsigned integer (quint16)
@ Byte
Eight bit unsigned integer (quint8)
@ ARGB32
Color, alpha, red, green, blue, 4 bytes the same as QImage::Format_ARGB32.
@ Float64
Sixty four bit floating point (double)
@ CFloat32
Complex Float32.
@ CInt16
Complex Int16.
@ UInt32
Thirty two bit unsigned integer (quint32)
static double maximumValuePossible(Qgis::DataType dataType)
Helper function that returns the maximum possible value for a data type.
static double minimumValuePossible(Qgis::DataType dataType)
Helper function that returns the minimum possible value for a data type.
This class represents a coordinate reference system (CRS).
QString toWkt(WktVariant variant=WKT1_GDAL, bool multiline=false, int indentationWidth=4) const
Returns a WKT representation of this CRS.
Contains information about the context in which a coordinate transform is executed.
Class for doing transforms between two map coordinate systems.
QgsCoordinateTransformContext transformContext() const
Returns data provider coordinate transform context.
virtual bool isValid() const =0
Returns true if this is a valid layer.
virtual QgsError error() const
Gets current status error.
bool isEmpty() const
Test if any error is set.
Definition: qgserror.h:111
QString summary() const
Short error description, usually the first error in chain, the real error.
Definition: qgserror.cpp:129
bool isCanceled() const SIP_HOLDGIL
Tells whether the operation has been canceled already.
Definition: qgsfeedback.h:54
void setProgress(double progress)
Sets the current progress for the feedback object.
Definition: qgsfeedback.h:63
static bool supportsRasterCreate(GDALDriverH driver)
Reads whether a driver supports GDALCreate() for raster purposes.
static QgsProviderRegistry * instance(const QString &pluginPath=QString())
Means of accessing canonical single instance.
The RasterBandStats struct is a container for statistics about a single raster band.
double minimumValue
The minimum cell value in the raster band.
double maximumValue
The maximum cell value in the raster band.
Feedback object tailored for raster block reading.
void appendError(const QString &error)
Appends an error message to the stored list of errors.
Raster data container.
static int typeSize(Qgis::DataType dataType) SIP_HOLDGIL
Returns the size in bytes for the specified dataType.
static Qgis::DataType typeWithNoDataValue(Qgis::DataType dataType, double *noDataValue)
For given data type returns wider type and sets no data value.
static bool typeIsColor(Qgis::DataType type)
Returns true if data type is color.
Base class for raster data providers.
virtual QString buildPyramids(const QList< QgsRasterPyramid > &pyramidList, const QString &resamplingMethod="NEAREST", QgsRaster::RasterPyramidsFormat format=QgsRaster::PyramidsGTiff, const QStringList &configOptions=QStringList(), QgsRasterBlockFeedback *feedback=nullptr)
Creates pyramid overviews.
virtual bool sourceHasNoDataValue(int bandNo) const
Returns true if source band has no data value.
virtual bool setNoDataValue(int bandNo, double noDataValue)
Set no data value on created dataset.
virtual QList< QgsRasterPyramid > buildPyramidList(const QList< int > &overviewList=QList< int >())
Returns the raster layers pyramid list.
Qgis::DataType sourceDataType(int bandNo) const override=0
Returns source data type for the band specified by number, source data type may be shorter than dataT...
virtual double sourceNoDataValue(int bandNo) const
Value representing no data value.
QgsRectangle extent() const override=0
Returns the extent of the layer.
Qgis::DataType dataType(int bandNo) const override=0
Returns data type for the band specified by number.
static QgsRasterDataProvider * create(const QString &providerKey, const QString &uri, const QString &format, int nBands, Qgis::DataType type, int width, int height, double *geoTransform, const QgsCoordinateReferenceSystem &crs, const QStringList &createOptions=QStringList())
Creates a new dataset with mDataSourceURI.
virtual bool write(void *data, int band, int width, int height, int xOffset, int yOffset)
Writes into the provider datasource.
Q_DECL_DEPRECATED WriterError writeRaster(const QgsRasterPipe *pipe, int nCols, int nRows, const QgsRectangle &outputExtent, const QgsCoordinateReferenceSystem &crs, QgsRasterBlockFeedback *feedback=nullptr)
Write raster file.
static QStringList extensionsForFormat(const QString &format)
Returns a list of known file extensions for the given GDAL driver format.
static QString filterForDriver(const QString &driverName)
Creates a filter for an GDAL driver key.
static QString driverForExtension(const QString &extension)
Returns the GDAL driver name for a specified file extension.
QgsRasterFileWriter(const QString &outputUrl)
static QStringList supportedFormatExtensions(RasterFormatOptions options=SortRecommended)
Returns a list of file extensions for supported formats.
QgsRasterDataProvider * createMultiBandRaster(Qgis::DataType dataType, int width, int height, const QgsRectangle &extent, const QgsCoordinateReferenceSystem &crs, int nBands)
Create a raster file with given number of bands without initializing the pixel data.
QString outputUrl() const
Returns the output URL for the raster.
@ WriteCanceled
Writing was manually canceled.
@ NoDataConflict
Internal error if a value used for 'no data' was found in input.
QgsRasterDataProvider * createOneBandRaster(Qgis::DataType dataType, int width, int height, const QgsRectangle &extent, const QgsCoordinateReferenceSystem &crs)
Create a raster file with one band without initializing the pixel data.
@ Image
Rendered image.
@ SortRecommended
Use recommended sort order, with extremely commonly used formats listed first.
static QList< QgsRasterFileWriter::FilterFormatDetails > supportedFiltersAndFormats(RasterFormatOptions options=SortRecommended)
Returns a list or pairs, with format filter string as first element and GDAL format key as second ele...
Base class for processing filters like renderers, reprojector, resampler etc.
virtual Qgis::DataType dataType(int bandNo) const =0
Returns data type for the band specified by number.
virtual int xSize() const
Gets raster size.
virtual const QgsRasterInterface * sourceInput() const
Gets source / raw input, the first in pipe, usually provider.
virtual int bandCount() const =0
Gets number of bands.
virtual QgsRasterBandStats bandStatistics(int bandNo, int stats=QgsRasterBandStats::All, const QgsRectangle &extent=QgsRectangle(), int sampleSize=0, QgsRasterBlockFeedback *feedback=nullptr)
Returns the band statistics.
virtual int ySize() const
Iterator for sequentially processing raster cells.
const QgsRasterInterface * input() const
Returns the input raster interface which is being iterated over.
int maximumTileWidth() const
Returns the maximum tile width returned during iteration.
void setMaximumTileWidth(int w)
Sets the maximum tile width returned during iteration.
bool readNextRasterPart(int bandNumber, int &nCols, int &nRows, QgsRasterBlock **block, int &topLeftCol, int &topLeftRow)
Fetches next part of raster data, caller takes ownership of the block and caller should delete the bl...
int maximumTileHeight() const
Returns the minimum tile width returned during iteration.
void startRasterRead(int bandNumber, qgssize nCols, qgssize nRows, const QgsRectangle &extent, QgsRasterBlockFeedback *feedback=nullptr)
Start reading of raster band.
void setMaximumTileHeight(int h)
Sets the minimum tile height returned during iteration.
Raster pipe that deals with null values.
void setOutputNoDataValue(int bandNo, double noData)
Sets the output no data value.
QgsRasterRangeList noData(int bandNo) const
Contains a pipeline of raster interfaces for sequential raster processing.
Definition: qgsrasterpipe.h:50
QgsRasterDataProvider * provider() const
Returns the data provider interface, or nullptr if no data provider is present in the pipe.
QgsRasterProjector * projector() const
Returns the projector interface, or nullptr if no projector is present in the pipe.
QgsRasterInterface * last() const
Returns last interface in the pipe.
QgsRasterNuller * nuller() const
Returns the raster nuller interface, or nullptr if no raster nuller is present in the pipe.
QgsRasterProjector implements approximate projection support for it calculates grid of points in sour...
QgsCoordinateReferenceSystem destinationCrs() const
Returns the destination CRS.
QgsCoordinateReferenceSystem sourceCrs() const
Returns the source CRS.
@ PyramidsFlagYes
Definition: qgsraster.h:77
The QgsReadWriteLocker class is a convenience class that simplifies locking and unlocking QReadWriteL...
@ Write
Lock for write.
@ Read
Lock for read.
void changeMode(Mode mode)
Change the mode of the lock to mode.
A rectangle specified with double values.
Definition: qgsrectangle.h:42
double yMaximum() const SIP_HOLDGIL
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:193
double xMaximum() const SIP_HOLDGIL
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:183
double xMinimum() const SIP_HOLDGIL
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:188
double yMinimum() const SIP_HOLDGIL
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:198
double height() const SIP_HOLDGIL
Returns the height of the rectangle.
Definition: qgsrectangle.h:230
double width() const SIP_HOLDGIL
Returns the width of the rectangle.
Definition: qgsrectangle.h:223
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
QString qgsEnumValueToKey(const T &value)
Returns the value for the given key of an enum.
Definition: qgis.h:1737
unsigned long long qgssize
Qgssize is used instead of size_t, because size_t is stdlib type, unknown by SIP, and it would be har...
Definition: qgis.h:2036
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
const QgsCoordinateReferenceSystem & crs
Setting options for creating vector data providers.
Details of available filters and formats.
QString filterString
Filter string for file picker dialogs.