QGIS API Documentation 3.99.0-Master (a8882ad4560)
Loading...
Searching...
No Matches
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 "qgsrasterfilewriter.h"
16
17#include <cmath>
18#include <cpl_string.h>
19#include <gdal.h>
20#include <mutex>
21#include <typeinfo>
22
25#include "qgsgdalutils.h"
26#include "qgsmessagelog.h"
27#include "qgsproviderregistry.h"
29#include "qgsrasterinterface.h"
30#include "qgsrasteriterator.h"
31#include "qgsrasternuller.h"
32#include "qgsrasterpipe.h"
33#include "qgsrasterprojector.h"
34#include "qgsreadwritelocker.h"
35
36#include <QCoreApplication>
37#include <QProgressDialog>
38#include <QRegularExpression>
39#include <QTextStream>
40
42{
43 if ( mTiledMode )
44 return nullptr; // does not make sense with tiled mode
45
46 double pixelSize;
47 double geoTransform[6];
48 globalOutputParameters( extent, width, height, geoTransform, pixelSize );
49
50 return initOutput( width, height, crs, geoTransform, 1, dataType, QList<bool>(), QList<double>() );
51}
52
54{
55 if ( mTiledMode )
56 return nullptr; // does not make sense with tiled mode
57
58 double pixelSize;
59 double geoTransform[6];
60 globalOutputParameters( extent, width, height, geoTransform, pixelSize );
61
62 return initOutput( width, height, crs, geoTransform, nBands, dataType, QList<bool>(), QList<double>() );
63}
64
66 : mOutputUrl( outputUrl )
67{
68
69}
70
72{
73
74}
75
76
77// Deprecated!
78Qgis::RasterFileWriterResult QgsRasterFileWriter::writeRaster( const QgsRasterPipe *pipe, int nCols, int nRows, const QgsRectangle &outputExtent,
80{
81 return writeRaster( pipe, nCols, nRows, outputExtent, crs, ( pipe && pipe->provider() ) ? pipe->provider()->transformContext() : QgsCoordinateTransformContext(), feedback );
82}
83
84Qgis::RasterFileWriterResult QgsRasterFileWriter::writeRaster( const QgsRasterPipe *pipe, int nCols, int nRows, const QgsRectangle &outputExtent,
85 const QgsCoordinateReferenceSystem &crs, const QgsCoordinateTransformContext &transformContext,
86 QgsRasterBlockFeedback *feedback )
87{
88 QgsDebugMsgLevel( u"Entered"_s, 4 );
89
90 if ( !pipe )
91 {
93 }
94 mPipe = pipe;
95
96 //const QgsRasterInterface* iface = iter->input();
97 const QgsRasterInterface *iface = pipe->last();
98 if ( !iface )
99 {
101 }
102 mInput = iface;
103
104 if ( QgsRasterBlock::typeIsColor( iface->dataType( 1 ) ) )
105 {
107 }
108 else
109 {
111 }
112
113 QgsDebugMsgLevel( u"reading from %1"_s.arg( typeid( *iface ).name() ), 4 );
114
115 if ( !iface->sourceInput() )
116 {
117 QgsDebugError( u"iface->srcInput() == 0"_s );
119 }
120#ifdef QGISDEBUG
121 const QgsRasterInterface &srcInput = *iface->sourceInput();
122 QgsDebugMsgLevel( u"srcInput = %1"_s.arg( typeid( srcInput ).name() ), 4 );
123#endif
124
125 mFeedback = feedback;
126
127 QgsRasterIterator iter( pipe->last() );
128
129 //create directory for output files
130 if ( mTiledMode )
131 {
132 const QFileInfo fileInfo( mOutputUrl );
133 if ( !fileInfo.exists() )
134 {
135 const QDir dir = fileInfo.dir();
136 if ( !dir.mkdir( fileInfo.fileName() ) )
137 {
138 QgsDebugError( "Cannot create output VRT directory " + fileInfo.fileName() + " in " + dir.absolutePath() );
140 }
141 }
142 }
143
144 // Remove pre-existing overview files to avoid using those with new raster
145 QFile pyramidFile( mOutputUrl + ( mTiledMode ? ".vrt.ovr" : ".ovr" ) );
146 if ( pyramidFile.exists() )
147 pyramidFile.remove();
148 pyramidFile.setFileName( mOutputUrl + ( mTiledMode ? ".vrt.rrd" : ".rrd" ) );
149 if ( pyramidFile.exists() )
150 pyramidFile.remove();
151
153 {
154 return writeImageRaster( &iter, nCols, nRows, outputExtent, crs, feedback );
155 }
156 else
157 {
158 return writeDataRaster( pipe, &iter, nCols, nRows, outputExtent, crs, transformContext, feedback );
159 }
160}
161
162Qgis::RasterFileWriterResult QgsRasterFileWriter::writeDataRaster( const QgsRasterPipe *pipe, QgsRasterIterator *iter, int nCols, int nRows, const QgsRectangle &outputExtent,
163 const QgsCoordinateReferenceSystem &crs, const QgsCoordinateTransformContext &transformContext, QgsRasterBlockFeedback *feedback )
164{
165 QgsDebugMsgLevel( u"Entered"_s, 4 );
166 if ( !iter )
167 {
169 }
170
171 const QgsRasterInterface *iface = pipe->last();
172 if ( !iface )
173 {
175 }
176
177 QgsRasterDataProvider *srcProvider = const_cast<QgsRasterDataProvider *>( dynamic_cast<const QgsRasterDataProvider *>( iface->sourceInput() ) );
178 if ( !srcProvider )
179 {
180 QgsDebugError( u"Cannot get source data provider"_s );
182 }
183
184 iter->setMaximumTileWidth( mMaxTileWidth );
185 iter->setMaximumTileHeight( mMaxTileHeight );
186
187 const int nBands = iface->bandCount();
188 if ( nBands < 1 )
189 {
191 }
192
193
194 //check if all the bands have the same data type size, otherwise we cannot write it to the provider
195 //(at least not with the current interface)
196 const int dataTypeSize = QgsRasterBlock::typeSize( srcProvider->sourceDataType( 1 ) );
197 for ( int i = 2; i <= nBands; ++i )
198 {
199 if ( QgsRasterBlock::typeSize( srcProvider->sourceDataType( 1 ) ) != dataTypeSize )
200 {
202 }
203 }
204
205 // Output data type - source data type is preferred but it may happen that we need
206 // to set 'no data' value (which was not set on source data) if output extent
207 // is larger than source extent (with or without reprojection) and there is no 'free'
208 // (not used) value available
209 QList<bool> destHasNoDataValueList;
210 QList<double> destNoDataValueList;
211 QList<Qgis::DataType> destDataTypeList;
212 destDataTypeList.reserve( nBands );
213 destHasNoDataValueList.reserve( nBands );
214 destNoDataValueList.reserve( nBands );
215
216 const bool isGpkgOutput = mOutputProviderKey == "gdal" &&
217 mOutputFormat.compare( "gpkg"_L1, Qt::CaseInsensitive ) == 0;
218 double pixelSize;
219 double geoTransform[6];
220 globalOutputParameters( outputExtent, nCols, nRows, geoTransform, pixelSize );
221 const auto srcProviderExtent( srcProvider->extent() );
222
223 for ( int bandNo = 1; bandNo <= nBands; bandNo++ )
224 {
225 QgsRasterNuller *nuller = pipe->nuller();
226
227 const bool srcHasNoDataValue = srcProvider->sourceHasNoDataValue( bandNo );
228 bool destHasNoDataValue = false;
229 double destNoDataValue = std::numeric_limits<double>::quiet_NaN();
230 const Qgis::DataType srcDataType = srcProvider->sourceDataType( bandNo );
231 Qgis::DataType destDataType = srcDataType;
232 // TODO: verify what happens/should happen if srcNoDataValue is disabled by setUseSrcNoDataValue
233 QgsDebugMsgLevel( u"srcHasNoDataValue = %1 srcNoDataValue = %2"_s.arg( srcHasNoDataValue ).arg( srcProvider->sourceNoDataValue( bandNo ) ), 4 );
234 if ( srcHasNoDataValue )
235 {
236
237 // If source has no data value, it is used by provider
238 destNoDataValue = srcProvider->sourceNoDataValue( bandNo );
239 destHasNoDataValue = true;
240 }
241 else if ( nuller && !nuller->noData( bandNo ).isEmpty() )
242 {
243 // Use one user defined no data value
244 destNoDataValue = nuller->noData( bandNo ).value( 0 ).min();
245 destHasNoDataValue = true;
246 }
247 // GeoPackage does not support nodata for Byte output, and does not
248 // support non-Byte multiband output, so do not take the risk of an accidental
249 // data type promotion.
250 else if ( !( isGpkgOutput && destDataType == Qgis::DataType::Byte ) )
251 {
252 // Verify if we really need no data value, i.e.
253 QgsRectangle outputExtentInSrcCrs = outputExtent;
254 QgsRasterProjector *projector = pipe->projector();
255 if ( projector && projector->destinationCrs() != projector->sourceCrs() )
256 {
257 QgsCoordinateTransform ct( projector->destinationCrs(), projector->sourceCrs(), transformContext );
258 ct.setBallparkTransformsAreAppropriate( true );
259 outputExtentInSrcCrs = ct.transformBoundingBox( outputExtent );
260 }
261 if ( !srcProviderExtent.contains( outputExtentInSrcCrs ) &&
262 ( std::fabs( srcProviderExtent.xMinimum() - outputExtentInSrcCrs.xMinimum() ) > geoTransform[1] / 2 ||
263 std::fabs( srcProviderExtent.xMaximum() - outputExtentInSrcCrs.xMaximum() ) > geoTransform[1] / 2 ||
264 std::fabs( srcProviderExtent.yMinimum() - outputExtentInSrcCrs.yMinimum() ) > std::fabs( geoTransform[5] ) / 2 ||
265 std::fabs( srcProviderExtent.yMaximum() - outputExtentInSrcCrs.yMaximum() ) > std::fabs( geoTransform[5] ) / 2 ) )
266 {
267 // Destination extent is (at least partially) outside of source extent, we need destination no data values
268 // Get src sample statistics (estimation from sample)
269 const QgsRasterBandStats stats = srcProvider->bandStatistics( bandNo, Qgis::RasterBandStatistic::Min | Qgis::RasterBandStatistic::Max, outputExtentInSrcCrs, 250000 );
270
271 // Test if we have free (not used) values
272 const double typeMinValue = QgsContrastEnhancement::minimumValuePossible( srcDataType );
273 const double typeMaxValue = QgsContrastEnhancement::maximumValuePossible( srcDataType );
274 if ( stats.minimumValue > typeMinValue )
275 {
276 destNoDataValue = typeMinValue;
277 }
278 else if ( stats.maximumValue < typeMaxValue )
279 {
280 destNoDataValue = typeMaxValue;
281 }
282 else
283 {
284 // We have to use wider type
285 destDataType = QgsRasterBlock::typeWithNoDataValue( destDataType, &destNoDataValue );
286 }
287 destHasNoDataValue = true;
288 }
289 }
290
291 if ( nuller && destHasNoDataValue )
292 {
293 nuller->setOutputNoDataValue( bandNo, destNoDataValue );
294 }
295
296 QgsDebugMsgLevel( u"bandNo = %1 destDataType = %2 destHasNoDataValue = %3 destNoDataValue = %4"_s.arg( bandNo ).arg( qgsEnumValueToKey( destDataType ) ).arg( destHasNoDataValue ).arg( destNoDataValue ), 4 );
297 destDataTypeList.append( destDataType );
298 destHasNoDataValueList.append( destHasNoDataValue );
299 destNoDataValueList.append( destNoDataValue );
300 }
301
302 Qgis::DataType destDataType = destDataTypeList.value( 0 );
303 // Currently write API supports one output type for dataset only -> find the widest
304 for ( int i = 1; i < nBands; i++ )
305 {
306 if ( destDataTypeList.value( i ) > destDataType )
307 {
308 destDataType = destDataTypeList.value( i );
309 // no data value may be left per band (for future)
310 }
311 }
312
314 for ( int attempt = 0; attempt < 2; attempt ++ )
315 {
316 //create destProvider for whole dataset here
317 // initOutput() returns 0 in tile mode!
318 std::unique_ptr<QgsRasterDataProvider> destProvider(
319 initOutput( nCols, nRows, crs, geoTransform, nBands, destDataType, destHasNoDataValueList, destNoDataValueList ) );
320 if ( !mTiledMode )
321 {
322 if ( !destProvider )
323 {
325 }
326 if ( !destProvider->isValid() )
327 {
328 if ( feedback && !destProvider->error().isEmpty() )
329 {
330 feedback->appendError( destProvider->error().summary() );
331 }
333 }
334 if ( nCols != destProvider->xSize() || nRows != destProvider->ySize() )
335 {
336 QgsDebugError( u"Created raster does not have requested dimensions"_s );
337 if ( feedback )
338 {
339 feedback->appendError( QObject::tr( "Created raster does not have requested dimensions" ) );
340 }
342 }
343 if ( nBands != destProvider->bandCount() )
344 {
345 QgsDebugError( u"Created raster does not have requested band count"_s );
346 if ( feedback )
347 {
348 feedback->appendError( QObject::tr( "Created raster does not have requested band count" ) );
349 }
351 }
352 if ( nBands )
353 {
354 // Some driver like GS7BG may accept Byte as requested data type,
355 // but actually return a driver with Float64...
356 destDataType = destProvider->dataType( 1 );
357 }
358 }
359
360 error = writeDataRaster( pipe, iter, nCols, nRows, outputExtent, crs, destDataType, destHasNoDataValueList, destNoDataValueList, destProvider, feedback );
361
362 if ( attempt == 0 && error == Qgis::RasterFileWriterResult::NoDataConflict )
363 {
364 // The value used for no data was found in source data, we must use wider data type
365 if ( destProvider ) // no tiles
366 {
367 destProvider->remove();
368 destProvider.reset();
369 }
370 else // VRT
371 {
372 // TODO: remove created VRT
373 }
374
375 // But we don't know which band -> wider all
376 for ( int i = 0; i < nBands; i++ )
377 {
378 double destNoDataValue;
379 const Qgis::DataType destDataType = QgsRasterBlock::typeWithNoDataValue( destDataTypeList.value( i ), &destNoDataValue );
380 destDataTypeList.replace( i, destDataType );
381 destNoDataValueList.replace( i, destNoDataValue );
382 }
383 destDataType = destDataTypeList.value( 0 );
384
385 // Try again
386 }
387 else
388 {
389 break;
390 }
391 }
392
393 return error;
394}
395
396static int qgsDivRoundUp( int a, int b )
397{
398 return a / b + ( ( ( a % b ) != 0 ) ? 1 : 0 );
399}
400
401Qgis::RasterFileWriterResult QgsRasterFileWriter::writeDataRaster( const QgsRasterPipe *pipe,
402 QgsRasterIterator *iter,
403 int nCols, int nRows,
404 const QgsRectangle &outputExtent,
406 Qgis::DataType destDataType,
407 const QList<bool> &destHasNoDataValueList,
408 const QList<double> &destNoDataValueList,
409 std::unique_ptr<QgsRasterDataProvider> &destProvider,
410 QgsRasterBlockFeedback *feedback )
411{
412 Q_UNUSED( pipe )
413 Q_UNUSED( destHasNoDataValueList )
414 QgsDebugMsgLevel( u"Entered"_s, 4 );
415
416 const QgsRasterInterface *iface = iter->input();
417 const QgsRasterDataProvider *srcProvider = dynamic_cast<const QgsRasterDataProvider *>( iface->sourceInput() );
418 const int nBands = iface->bandCount();
419 QgsDebugMsgLevel( u"nBands = %1"_s.arg( nBands ), 4 );
420
421 //Get output map units per pixel
422 int iterLeft = 0;
423 int iterTop = 0;
424 int iterCols = 0;
425 int iterRows = 0;
426
427 std::vector< std::unique_ptr<QgsRasterBlock> > blockList;
428 std::vector< std::unique_ptr<QgsRasterBlock> > destBlockList;
429
430 blockList.resize( nBands );
431 destBlockList.resize( nBands );
432
433 for ( int i = 1; i <= nBands; ++i )
434 {
435 iter->startRasterRead( i, nCols, nRows, outputExtent, feedback );
436 if ( destProvider && destHasNoDataValueList.value( i - 1 ) ) // no tiles
437 {
438 destProvider->setNoDataValue( i, destNoDataValueList.value( i - 1 ) );
439 }
440 }
441
442 int nParts = 0;
443 int fileIndex = 0;
444 if ( feedback )
445 {
446 const int nPartsX = qgsDivRoundUp( nCols, iter->maximumTileWidth() );
447 const int nPartsY = qgsDivRoundUp( nRows, iter->maximumTileHeight() );
448 nParts = nPartsX * nPartsY;
449 }
450
451 const bool hasReportsDuringClose = destProvider && destProvider->hasReportsDuringClose();
452
453 // hmm why is there a for(;;) here ..
454 // not good coding practice IMHO, it might be better to use [ for() and break ] or [ while (test) ]
455 Q_FOREVER
456 {
457 bool done = false;
458 for ( int i = 1; i <= nBands && !done; ++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 == Qgis::RasterBuildPyramidOption::Yes )
469 {
470 if ( !buildPyramids( vrtFilePath ) )
471 {
473 }
474 }
475 }
476 else
477 {
478 if ( mBuildPyramidsFlag == Qgis::RasterBuildPyramidOption::Yes &&
479 // Pyramid creation is done by the driver itself
480 mOutputFormat != "COG"_L1 )
481 {
482 if ( !buildPyramids( mOutputUrl, destProvider.get() ) )
483 {
485 }
486 }
487 }
488
489 QgsDebugMsgLevel( u"Done"_s, 4 );
490 done = true;
491 }
492 blockList[i - 1].reset( block );
493 // TODO: verify if NoDataConflict happened, to do that we need the whole pipe or nuller interface
494 }
495 if ( done )
496 {
497 break;
498 }
499
500 if ( feedback && fileIndex < ( nParts - 1 ) )
501 {
502 const double maxProgress = hasReportsDuringClose ? 50.0 : 100.0;
503 feedback->setProgress( maxProgress * fileIndex / static_cast< double >( nParts ) );
504 if ( feedback->isCanceled() )
505 {
506 break;
507 }
508 }
509
510 // It may happen that internal data type (dataType) is wider than destDataType
511 for ( int i = 1; i <= nBands; ++i )
512 {
513 if ( srcProvider && srcProvider->dataType( i ) == destDataType )
514 {
515 // nothing
516 }
517 else
518 {
519 // TODO: this conversion should go to QgsRasterDataProvider::write with additional input data type param
520 blockList[i - 1]->convert( destDataType );
521 }
522 destBlockList[i - 1] = std::move( blockList[i - 1] );
523 }
524
525 if ( mTiledMode ) //write to file
526 {
527 std::unique_ptr< QgsRasterDataProvider > partDestProvider( createPartProvider( outputExtent,
528 nCols, iterCols, iterRows,
529 iterLeft, iterTop, mOutputUrl,
530 fileIndex, nBands, destDataType, crs ) );
531
532 if ( !partDestProvider || !partDestProvider->isValid() )
533 {
535 }
536
537 //write data to output file. todo: loop over the data list
538 for ( int i = 1; i <= nBands; ++i )
539 {
540 if ( destHasNoDataValueList.value( i - 1 ) )
541 {
542 partDestProvider->setNoDataValue( i, destNoDataValueList.value( i - 1 ) );
543 }
544 if ( destBlockList[ i - 1 ]->isEmpty() )
545 continue;
546
547 if ( !partDestProvider->write( destBlockList[i - 1]->constBits( 0 ), i, iterCols, iterRows, 0, 0 ) )
548 {
550 }
551 addToVRT( partFileName( fileIndex ), i, iterCols, iterRows, iterLeft, iterTop );
552 }
553
554 }
555 else if ( destProvider )
556 {
557 //loop over data
558 for ( int i = 1; i <= nBands; ++i )
559 {
560 if ( destBlockList[ i - 1 ]->isEmpty() )
561 continue;
562
563 if ( !destProvider->write( destBlockList[i - 1]->constBits( 0 ), i, iterCols, iterRows, iterLeft, iterTop ) )
564 {
566 }
567 }
568 }
569 ++fileIndex;
570 }
571
572 // If the provider can report progress during closing (typically when generating COG files),
573 // report it, making feedback report percentage in the [50, 100] range.
574 if ( feedback && destProvider && hasReportsDuringClose )
575 {
576 std::unique_ptr<QgsFeedback> scaledFeedback( QgsFeedback::createScaledFeedback( feedback, 50.0, 100.0 ) );
577 if ( !destProvider->closeWithProgress( scaledFeedback.get() ) )
578 {
579 destProvider->remove();
580 destProvider.reset();
581 return ( feedback && feedback->isCanceled() ) ?
584 }
585 }
586
587 QgsDebugMsgLevel( u"Done"_s, 4 );
589}
590
591Qgis::RasterFileWriterResult QgsRasterFileWriter::writeImageRaster( QgsRasterIterator *iter, int nCols, int nRows, const QgsRectangle &outputExtent,
593{
594 QgsDebugMsgLevel( u"Entered"_s, 4 );
595 if ( !iter )
596 {
598 }
599
600 const QgsRasterInterface *iface = iter->input();
601 if ( !iface )
603
604 const Qgis::DataType inputDataType = iface->dataType( 1 );
605 if ( inputDataType != Qgis::DataType::ARGB32 && inputDataType != Qgis::DataType::ARGB32_Premultiplied )
606 {
608 }
609 const bool isPremultiplied = ( inputDataType == Qgis::DataType::ARGB32_Premultiplied );
610
611 iter->setMaximumTileWidth( mMaxTileWidth );
612 iter->setMaximumTileHeight( mMaxTileHeight );
613
614 const size_t nMaxPixels = static_cast<size_t>( mMaxTileWidth ) * mMaxTileHeight;
615 std::vector<unsigned char> redData( nMaxPixels );
616 std::vector<unsigned char> greenData( nMaxPixels );
617 std::vector<unsigned char> blueData( nMaxPixels );
618 std::vector<unsigned char> alphaData( nMaxPixels );
619 int iterLeft = 0, iterTop = 0, iterCols = 0, iterRows = 0;
620 int fileIndex = 0;
621
622 //create destProvider for whole dataset here
623 double pixelSize;
624 double geoTransform[6];
625 globalOutputParameters( outputExtent, nCols, nRows, geoTransform, pixelSize );
626
627 const int nOutputBands = 4;
628 std::unique_ptr< QgsRasterDataProvider > destProvider( initOutput( nCols, nRows, crs, geoTransform, nOutputBands, Qgis::DataType::Byte ) );
629 if ( !mTiledMode )
630 {
631 if ( !destProvider )
632 {
634 }
635 if ( !destProvider->isValid() )
636 {
637 if ( feedback && !destProvider->error().isEmpty() )
638 {
639 feedback->appendError( destProvider->error().summary() );
640 }
642 }
643 if ( nCols != destProvider->xSize() || nRows != destProvider->ySize() )
644 {
645 QgsDebugError( u"Created raster does not have requested dimensions"_s );
646 if ( feedback )
647 {
648 feedback->appendError( QObject::tr( "Created raster does not have requested dimensions" ) );
649 }
651 }
652 if ( nOutputBands != destProvider->bandCount() )
653 {
654 QgsDebugError( u"Created raster does not have requested band count"_s );
655 if ( feedback )
656 {
657 feedback->appendError( QObject::tr( "Created raster does not have requested band count" ) );
658 }
660 }
661 if ( Qgis::DataType::Byte != destProvider->dataType( 1 ) )
662 {
663 QgsDebugError( u"Created raster does not have requested data type"_s );
664 if ( feedback )
665 {
666 feedback->appendError( QObject::tr( "Created raster does not have requested data type" ) );
667 }
669 }
670 }
671
672 iter->startRasterRead( 1, nCols, nRows, outputExtent, feedback );
673
674 int nParts = 0;
675 if ( feedback )
676 {
677 const int nPartsX = qgsDivRoundUp( nCols, iter->maximumTileWidth() );
678 const int nPartsY = qgsDivRoundUp( nRows, iter->maximumTileHeight() );
679 nParts = nPartsX * nPartsY;
680 }
681
682 const bool hasReportsDuringClose = !mTiledMode && destProvider && destProvider->hasReportsDuringClose();
683 const double maxProgress = hasReportsDuringClose ? 50.0 : 100.0;
684
685 std::unique_ptr< QgsRasterBlock > inputBlock;
686 while ( iter->readNextRasterPart( 1, iterCols, iterRows, inputBlock, iterLeft, iterTop ) )
687 {
688 if ( !inputBlock || inputBlock->isEmpty() )
689 {
690 continue;
691 }
692
693 if ( feedback && fileIndex < ( nParts - 1 ) )
694 {
695 feedback->setProgress( maxProgress * fileIndex / static_cast< double >( nParts ) );
696 if ( feedback->isCanceled() )
697 {
698 break;
699 }
700 }
701
702 //fill into red/green/blue/alpha channels
703 const qgssize nPixels = static_cast< qgssize >( iterCols ) * iterRows;
704 for ( qgssize i = 0; i < nPixels; ++i )
705 {
706 QRgb c = inputBlock->color( i );
707 if ( isPremultiplied )
708 {
709 c = qUnpremultiply( c );
710 }
711 redData[i] = static_cast<unsigned char>( qRed( c ) );
712 greenData[i] = static_cast<unsigned char>( qGreen( c ) );
713 blueData[i] = static_cast<unsigned char>( qBlue( c ) );
714 alphaData[i] = static_cast<unsigned char>( qAlpha( c ) );
715 }
716
717 //create output file
718 if ( mTiledMode )
719 {
720 std::unique_ptr< QgsRasterDataProvider > partDestProvider( createPartProvider( outputExtent,
721 nCols, iterCols, iterRows,
722 iterLeft, iterTop, mOutputUrl, fileIndex,
723 4, Qgis::DataType::Byte, crs ) );
724
725 if ( !partDestProvider || partDestProvider->isValid() )
726 {
728 }
729
730 //write data to output file
731 if ( !partDestProvider->write( &redData[0], 1, iterCols, iterRows, 0, 0 ) ||
732 !partDestProvider->write( &greenData[0], 2, iterCols, iterRows, 0, 0 ) ||
733 !partDestProvider->write( &blueData[0], 3, iterCols, iterRows, 0, 0 ) ||
734 !partDestProvider->write( &alphaData[0], 4, iterCols, iterRows, 0, 0 ) )
735 {
737 }
738
739 addToVRT( partFileName( fileIndex ), 1, iterCols, iterRows, iterLeft, iterTop );
740 addToVRT( partFileName( fileIndex ), 2, iterCols, iterRows, iterLeft, iterTop );
741 addToVRT( partFileName( fileIndex ), 3, iterCols, iterRows, iterLeft, iterTop );
742 addToVRT( partFileName( fileIndex ), 4, iterCols, iterRows, iterLeft, iterTop );
743 }
744 else if ( destProvider )
745 {
746 if ( !destProvider->write( &redData[0], 1, iterCols, iterRows, iterLeft, iterTop ) ||
747 !destProvider->write( &greenData[0], 2, iterCols, iterRows, iterLeft, iterTop ) ||
748 !destProvider->write( &blueData[0], 3, iterCols, iterRows, iterLeft, iterTop ) ||
749 !destProvider->write( &alphaData[0], 4, iterCols, iterRows, iterLeft, iterTop ) )
750 {
752 }
753 }
754
755 ++fileIndex;
756 }
757
758 if ( feedback )
759 {
760 feedback->setProgress( maxProgress );
761 }
762
763 if ( mTiledMode )
764 {
765 destProvider.reset();
766
767 const QString vrtFilePath( mOutputUrl + '/' + vrtFileName() );
768 writeVRT( vrtFilePath );
769 if ( mBuildPyramidsFlag == Qgis::RasterBuildPyramidOption::Yes )
770 {
771 if ( !buildPyramids( vrtFilePath ) )
773 }
774 }
775 else
776 {
777 // If the provider can report progress during closing (typically when generating COG files),
778 // report it, making feedback report percentage in the [50, 100] range.
779 if ( destProvider && hasReportsDuringClose )
780 {
781 QgsFeedback closingProgress;
782 if ( feedback )
783 {
784 QObject::connect( &closingProgress, &QgsFeedback::progressChanged,
785 feedback, [feedback]( double progress )
786 {
787 feedback->setProgress( 50.0 + progress * 0.5 );
788 } );
789 QObject::connect( &closingProgress, &QgsFeedback::canceled,
790 feedback, &QgsFeedback::cancel );
791 }
792
793 if ( !destProvider->closeWithProgress( feedback ? &closingProgress : nullptr ) )
794 {
795 destProvider->remove();
796 destProvider.reset();
797 return ( feedback && feedback->isCanceled() ) ?
800 }
801 }
802
803 destProvider.reset();
804
805 if ( mBuildPyramidsFlag == Qgis::RasterBuildPyramidOption::Yes &&
806 // Pyramid creation is done by the driver itself
807 mOutputFormat != "COG"_L1 )
808 {
809 if ( !buildPyramids( mOutputUrl ) )
811 }
812 }
814}
815
816void QgsRasterFileWriter::addToVRT( const QString &filename, int band, int xSize, int ySize, int xOffset, int yOffset )
817{
818 QDomElement bandElem = mVRTBands.value( band - 1 );
819
820 QDomElement simpleSourceElem = mVRTDocument.createElement( u"SimpleSource"_s );
821
822 //SourceFilename
823 QDomElement sourceFilenameElem = mVRTDocument.createElement( u"SourceFilename"_s );
824 sourceFilenameElem.setAttribute( u"relativeToVRT"_s, u"1"_s );
825 const QDomText sourceFilenameText = mVRTDocument.createTextNode( filename );
826 sourceFilenameElem.appendChild( sourceFilenameText );
827 simpleSourceElem.appendChild( sourceFilenameElem );
828
829 //SourceBand
830 QDomElement sourceBandElem = mVRTDocument.createElement( u"SourceBand"_s );
831 const QDomText sourceBandText = mVRTDocument.createTextNode( QString::number( band ) );
832 sourceBandElem.appendChild( sourceBandText );
833 simpleSourceElem.appendChild( sourceBandElem );
834
835 //SourceProperties
836 QDomElement sourcePropertiesElem = mVRTDocument.createElement( u"SourceProperties"_s );
837 sourcePropertiesElem.setAttribute( u"RasterXSize"_s, xSize );
838 sourcePropertiesElem.setAttribute( u"RasterYSize"_s, ySize );
839 sourcePropertiesElem.setAttribute( u"BlockXSize"_s, xSize );
840 sourcePropertiesElem.setAttribute( u"BlockYSize"_s, ySize );
841 sourcePropertiesElem.setAttribute( u"DataType"_s, u"Byte"_s );
842 simpleSourceElem.appendChild( sourcePropertiesElem );
843
844 //SrcRect
845 QDomElement srcRectElem = mVRTDocument.createElement( u"SrcRect"_s );
846 srcRectElem.setAttribute( u"xOff"_s, u"0"_s );
847 srcRectElem.setAttribute( u"yOff"_s, u"0"_s );
848 srcRectElem.setAttribute( u"xSize"_s, xSize );
849 srcRectElem.setAttribute( u"ySize"_s, ySize );
850 simpleSourceElem.appendChild( srcRectElem );
851
852 //DstRect
853 QDomElement dstRectElem = mVRTDocument.createElement( u"DstRect"_s );
854 dstRectElem.setAttribute( u"xOff"_s, xOffset );
855 dstRectElem.setAttribute( u"yOff"_s, yOffset );
856 dstRectElem.setAttribute( u"xSize"_s, xSize );
857 dstRectElem.setAttribute( u"ySize"_s, ySize );
858 simpleSourceElem.appendChild( dstRectElem );
859
860 bandElem.appendChild( simpleSourceElem );
861}
862
863bool QgsRasterFileWriter::buildPyramids( const QString &filename, QgsRasterDataProvider *destProviderIn )
864{
865 QgsDebugMsgLevel( "filename = " + filename, 4 );
866 // open new dataProvider so we can build pyramids with it
867 const QgsDataProvider::ProviderOptions providerOptions;
868 QgsRasterDataProvider *destProvider = destProviderIn;
869 if ( !destProvider )
870 {
871 destProvider = qobject_cast< QgsRasterDataProvider * >( QgsProviderRegistry::instance()->createProvider( mOutputProviderKey, filename, providerOptions ) );
872 if ( !destProvider || !destProvider->isValid() )
873 {
874 delete destProvider;
875 return false;
876 }
877 }
878
879 // TODO progress report
880 // TODO test mTiledMode - not tested b/c segfault at line # 289
881 // connect( provider, &FIXME::progressUpdate, mPyramidProgress, &FIXME::setValue );
882 QList< QgsRasterPyramid> myPyramidList;
883 if ( ! mPyramidsList.isEmpty() )
884 myPyramidList = destProvider->buildPyramidList( mPyramidsList );
885 for ( int myCounterInt = 0; myCounterInt < myPyramidList.count(); myCounterInt++ )
886 {
887 myPyramidList[myCounterInt].setBuild( true );
888 }
889
890 QgsDebugMsgLevel( u"building pyramids : %1 pyramids, %2 resampling, %3 format, %4 options"_s.arg( myPyramidList.count() ).arg( mPyramidsResampling ).arg( qgsEnumValueToKey( mPyramidsFormat ) ).arg( mPyramidsConfigOptions.count() ), 4 );
891 // QApplication::setOverrideCursor( Qt::WaitCursor );
892 const QString res = destProvider->buildPyramids( myPyramidList, mPyramidsResampling,
893 mPyramidsFormat, mPyramidsConfigOptions );
894 // QApplication::restoreOverrideCursor();
895
896 // TODO put this in provider or elsewhere
897 if ( !res.isNull() )
898 {
899 QString message;
900 if ( res == "ERROR_WRITE_ACCESS"_L1 )
901 {
902 message = QObject::tr( "Write access denied. Adjust the file permissions and try again." );
903 }
904 else if ( res == "ERROR_WRITE_FORMAT"_L1 )
905 {
906 message = QObject::tr( "The file was not writable. Some formats do not "
907 "support pyramid overviews. Consult the GDAL documentation if in doubt." );
908 }
909 else if ( res == "FAILED_NOT_SUPPORTED"_L1 )
910 {
911 message = QObject::tr( "Building pyramid overviews is not supported on this type of raster." );
912 }
913 else if ( res == "ERROR_VIRTUAL"_L1 )
914 {
915 message = QObject::tr( "Building pyramid overviews is not supported on this type of raster." );
916 }
917
918 QgsMessageLog::logMessage( message, QObject::tr( "Building Pyramids" ) );
919
920 QgsDebugMsgLevel( res + " - " + message, 4 );
921 }
922 if ( !destProviderIn )
923 delete destProvider;
924
925 return res.isNull();
926}
927
928#if 0
929int QgsRasterFileWriter::pyramidsProgress( double dfComplete, const char *pszMessage, void *pData )
930{
931 Q_UNUSED( pszMessage )
932 GDALTermProgress( dfComplete, 0, 0 );
933 QProgressDialog *progressDialog = static_cast<QProgressDialog *>( pData );
934 if ( pData && progressDialog->wasCanceled() )
935 {
936 return 0;
937 }
938
939 if ( pData )
940 {
941 progressDialog->setRange( 0, 100 );
942 progressDialog->setValue( dfComplete * 100 );
943 }
944 return 1;
945}
946#endif
947
948void QgsRasterFileWriter::createVRT( int xSize, int ySize, const QgsCoordinateReferenceSystem &crs, double *geoTransform, Qgis::DataType type, const QList<bool> &destHasNoDataValueList, const QList<double> &destNoDataValueList )
949{
950 mVRTDocument.clear();
951 QDomElement VRTDatasetElem = mVRTDocument.createElement( u"VRTDataset"_s );
952
953 //xsize / ysize
954 VRTDatasetElem.setAttribute( u"rasterXSize"_s, xSize );
955 VRTDatasetElem.setAttribute( u"rasterYSize"_s, ySize );
956 mVRTDocument.appendChild( VRTDatasetElem );
957
958 //CRS
959 QDomElement SRSElem = mVRTDocument.createElement( u"SRS"_s );
960 const QDomText crsText = mVRTDocument.createTextNode( crs.toWkt() );
961 SRSElem.appendChild( crsText );
962 VRTDatasetElem.appendChild( SRSElem );
963
964 //geotransform
965 if ( geoTransform )
966 {
967 QDomElement geoTransformElem = mVRTDocument.createElement( u"GeoTransform"_s );
968 const QString geoTransformString = QString::number( geoTransform[0], 'f', 6 ) + ", " + QString::number( geoTransform[1] ) + ", " + QString::number( geoTransform[2] ) +
969 ", " + QString::number( geoTransform[3], 'f', 6 ) + ", " + QString::number( geoTransform[4] ) + ", " + QString::number( geoTransform[5] );
970 const QDomText geoTransformText = mVRTDocument.createTextNode( geoTransformString );
971 geoTransformElem.appendChild( geoTransformText );
972 VRTDatasetElem.appendChild( geoTransformElem );
973 }
974
975 int nBands;
976 if ( mMode == Qgis::RasterExportType::Raw )
977 {
978 nBands = mInput->bandCount();
979 }
980 else
981 {
982 nBands = 4;
983 }
984
985 QStringList colorInterp;
986 colorInterp << u"Red"_s << u"Green"_s << u"Blue"_s << u"Alpha"_s;
987
988 QMap<Qgis::DataType, QString> dataTypes;
989 dataTypes.insert( Qgis::DataType::Byte, u"Byte"_s );
990 dataTypes.insert( Qgis::DataType::Int8, u"Int8"_s );
991 dataTypes.insert( Qgis::DataType::UInt16, u"UInt16"_s );
992 dataTypes.insert( Qgis::DataType::Int16, u"Int16"_s );
993 dataTypes.insert( Qgis::DataType::UInt32, u"Int32"_s );
994 dataTypes.insert( Qgis::DataType::Float32, u"Float32"_s );
995 dataTypes.insert( Qgis::DataType::Float64, u"Float64"_s );
996 dataTypes.insert( Qgis::DataType::CInt16, u"CInt16"_s );
997 dataTypes.insert( Qgis::DataType::CInt32, u"CInt32"_s );
998 dataTypes.insert( Qgis::DataType::CFloat32, u"CFloat32"_s );
999 dataTypes.insert( Qgis::DataType::CFloat64, u"CFloat64"_s );
1000
1001 for ( int i = 1; i <= nBands; i++ )
1002 {
1003 QDomElement VRTBand = mVRTDocument.createElement( u"VRTRasterBand"_s );
1004
1005 VRTBand.setAttribute( u"band"_s, QString::number( i ) );
1006 const QString dataType = dataTypes.value( type );
1007 VRTBand.setAttribute( u"dataType"_s, dataType );
1008
1010 {
1011
1012 VRTBand.setAttribute( u"dataType"_s, u"Byte"_s );
1013 QDomElement colorInterpElement = mVRTDocument.createElement( u"ColorInterp"_s );
1014 const QDomText interpText = mVRTDocument.createTextNode( colorInterp.value( i - 1 ) );
1015 colorInterpElement.appendChild( interpText );
1016 VRTBand.appendChild( colorInterpElement );
1017 }
1018
1019 if ( !destHasNoDataValueList.isEmpty() && destHasNoDataValueList.value( i - 1 ) )
1020 {
1021 VRTBand.setAttribute( u"NoDataValue"_s, QString::number( destNoDataValueList.value( i - 1 ) ) );
1022 }
1023
1024 mVRTBands.append( VRTBand );
1025 VRTDatasetElem.appendChild( VRTBand );
1026 }
1027}
1028
1029bool QgsRasterFileWriter::writeVRT( const QString &file )
1030{
1031 QFile outputFile( file );
1032 if ( ! outputFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
1033 {
1034 return false;
1035 }
1036
1037 QTextStream outStream( &outputFile );
1038 mVRTDocument.save( outStream, 2 );
1039 return true;
1040}
1041
1042QgsRasterDataProvider *QgsRasterFileWriter::createPartProvider( const QgsRectangle &extent, int nCols, int iterCols,
1043 int iterRows, int iterLeft, int iterTop, const QString &outputUrl, int fileIndex, int nBands, Qgis::DataType type,
1044 const QgsCoordinateReferenceSystem &crs )
1045{
1046 const double mup = extent.width() / nCols;
1047 const double mapLeft = extent.xMinimum() + iterLeft * mup;
1048 const double mapRight = mapLeft + mup * iterCols;
1049 const double mapTop = extent.yMaximum() - iterTop * mup;
1050 const double mapBottom = mapTop - iterRows * mup;
1051 const QgsRectangle mapRect( mapLeft, mapBottom, mapRight, mapTop );
1052
1053 const QString outputFile = outputUrl + '/' + partFileName( fileIndex );
1054
1055 //geotransform
1056 double geoTransform[6];
1057 geoTransform[0] = mapRect.xMinimum();
1058 geoTransform[1] = mup;
1059 geoTransform[2] = 0.0;
1060 geoTransform[3] = mapRect.yMaximum();
1061 geoTransform[4] = 0.0;
1062 geoTransform[5] = -mup;
1063
1064 // perhaps we need a separate creationOptions for tiles ?
1065
1066 QgsRasterDataProvider *destProvider = QgsRasterDataProvider::create( mOutputProviderKey, outputFile, mOutputFormat, nBands, type, iterCols, iterRows, geoTransform, crs, mCreationOptions );
1067
1068 // TODO: return provider and report error
1069 return destProvider;
1070}
1071
1072void QgsRasterFileWriter::setOutputFormat( const QString &format )
1073{
1074 mOutputFormat = format;
1075 if ( !mBuildPyramidsFlagSet && format == "COG"_L1 )
1076 {
1078 }
1079}
1080
1082{
1083 mBuildPyramidsFlag = flag;
1084 mBuildPyramidsFlagSet = true;
1085}
1086
1087QgsRasterDataProvider *QgsRasterFileWriter::initOutput( int nCols, int nRows, const QgsCoordinateReferenceSystem &crs,
1088 double *geoTransform, int nBands, Qgis::DataType type,
1089 const QList<bool> &destHasNoDataValueList, const QList<double> &destNoDataValueList )
1090{
1091 if ( mTiledMode )
1092 {
1093 createVRT( nCols, nRows, crs, geoTransform, type, destHasNoDataValueList, destNoDataValueList );
1094 return nullptr;
1095 }
1096 else
1097 {
1098#if 0
1099 // TODO enable "use existing", has no effect for now, because using Create() in gdal provider
1100 // should this belong in provider? should also test that source provider is gdal
1101 if ( mBuildPyramidsFlag == -4 && mOutputProviderKey == "gdal" && mOutputFormat.compare( "gtiff"_L1, Qt::CaseInsensitive ) == 0 )
1102 mCreationOptions << "COPY_SRC_OVERVIEWS=YES";
1103#endif
1104 QStringList creationOptions( mCreationOptions );
1105 if ( mOutputFormat == "COG"_L1 )
1106 {
1107 if ( mBuildPyramidsFlag == Qgis::RasterBuildPyramidOption::No )
1108 creationOptions << u"OVERVIEWS=NO"_s;
1109 else
1110 {
1111 creationOptions << u"OVERVIEW_RESAMPLING="_s + mPyramidsResampling;
1112 for ( const QString &opt : std::as_const( mPyramidsConfigOptions ) )
1113 {
1114 const std::string optStr( opt.toStdString() );
1115 char *key = nullptr;
1116 const char *value = CPLParseNameValue( optStr.c_str(), &key );
1117 if ( key && value )
1118 {
1119 if ( EQUAL( key, "JPEG_QUALITY_OVERVIEW" ) )
1120 {
1121 creationOptions << u"OVERVIEW_QUALITY="_s + value;
1122 }
1123 else if ( EQUAL( key, "COMPRESS_OVERVIEW" ) )
1124 {
1125 creationOptions << u"OVERVIEW_COMPRESS="_s + value;
1126 }
1127 else if ( EQUAL( key, "PREDICTOR_OVERVIEW" ) )
1128 {
1129 creationOptions << u"OVERVIEW_PREDICTOR="_s + value;
1130 }
1131 }
1132 CPLFree( key );
1133 }
1134 }
1135 }
1136
1137 QgsRasterDataProvider *destProvider = QgsRasterDataProvider::create( mOutputProviderKey, mOutputUrl, mOutputFormat, nBands, type, nCols, nRows, geoTransform, crs, creationOptions );
1138
1139 if ( !destProvider )
1140 {
1141 QgsDebugError( u"No provider created"_s );
1142 }
1143
1144 return destProvider;
1145 }
1146}
1147
1148void QgsRasterFileWriter::globalOutputParameters( const QgsRectangle &extent, int nCols, int &nRows,
1149 double *geoTransform, double &pixelSize )
1150{
1151 pixelSize = extent.width() / nCols;
1152
1153 //calculate nRows automatically for providers without exact resolution
1154 if ( nRows < 0 )
1155 {
1156 nRows = static_cast< double >( nCols ) / extent.width() * extent.height() + 0.5; //NOLINT
1157 }
1158 geoTransform[0] = extent.xMinimum();
1159 geoTransform[1] = pixelSize;
1160 geoTransform[2] = 0.0;
1161 geoTransform[3] = extent.yMaximum();
1162 geoTransform[4] = 0.0;
1163 geoTransform[5] = -( extent.height() / nRows );
1164}
1165
1166QString QgsRasterFileWriter::partFileName( int fileIndex )
1167{
1168 // .tif for now
1169 const QFileInfo outputInfo( mOutputUrl );
1170 return u"%1.%2.tif"_s.arg( outputInfo.fileName() ).arg( fileIndex );
1171}
1172
1173QString QgsRasterFileWriter::vrtFileName()
1174{
1175 const QFileInfo outputInfo( mOutputUrl );
1176 return u"%1.vrt"_s.arg( outputInfo.fileName() );
1177}
1178
1179QString QgsRasterFileWriter::driverForExtension( const QString &extension )
1180{
1181 QString ext = extension.trimmed();
1182 if ( ext.isEmpty() )
1183 return QString();
1184
1185 if ( ext.startsWith( '.' ) )
1186 ext.remove( 0, 1 );
1187
1188 if ( ext.compare( "tif"_L1, Qt::CaseInsensitive ) == 0 ||
1189 ext.compare( "tiff"_L1, Qt::CaseInsensitive ) == 0 )
1190 {
1191 // Be robust to GDAL drivers potentially recognizing the tif/tiff extensions
1192 // but being registered before the GTiff one.
1193 // Cf https://github.com/qgis/QGIS/issues/59112
1194 if ( GDALGetDriverByName( "GTiff" ) )
1195 return "GTiff";
1196 }
1197
1198 GDALAllRegister();
1199 int const drvCount = GDALGetDriverCount();
1200
1201 for ( int i = 0; i < drvCount; ++i )
1202 {
1203 GDALDriverH drv = GDALGetDriver( i );
1204 if ( drv )
1205 {
1206 CSLConstList driverMetadata = GDALGetMetadata( drv, nullptr );
1207 if ( CSLFetchBoolean( driverMetadata, GDAL_DCAP_RASTER, false ) )
1208 {
1209 QString drvName = GDALGetDriverShortName( drv );
1210 const QStringList driverExtensions = QString( GDALGetMetadataItem( drv, GDAL_DMD_EXTENSIONS, nullptr ) ).split( ' ' );
1211
1212 const auto constDriverExtensions = driverExtensions;
1213 for ( const QString &driver : constDriverExtensions )
1214 {
1215 if ( driver.compare( ext, Qt::CaseInsensitive ) == 0 )
1216 return drvName;
1217 }
1218 }
1219 }
1220 }
1221 return QString();
1222}
1223
1224QStringList QgsRasterFileWriter::extensionsForFormat( const QString &format )
1225{
1226 GDALDriverH drv = GDALGetDriverByName( format.toLocal8Bit().data() );
1227 if ( drv )
1228 {
1229 CSLConstList driverMetadata = GDALGetMetadata( drv, nullptr );
1230 if ( CSLFetchBoolean( driverMetadata, GDAL_DCAP_RASTER, false ) )
1231 {
1232 return QString( GDALGetMetadataItem( drv, GDAL_DMD_EXTENSIONS, nullptr ) ).split( ' ' );
1233 }
1234 }
1235 return QStringList();
1236}
1237
1238QString QgsRasterFileWriter::filterForDriver( const QString &driverName )
1239{
1240 GDALDriverH drv = GDALGetDriverByName( driverName.toLocal8Bit().data() );
1241 if ( drv )
1242 {
1243 const QString drvName = GDALGetDriverLongName( drv );
1244 const QString extensionsString = QString( GDALGetMetadataItem( drv, GDAL_DMD_EXTENSIONS, nullptr ) );
1245 if ( extensionsString.isEmpty() )
1246 {
1247 return QString();
1248 }
1249 const QStringList extensions = extensionsString.split( ' ' );
1250 QString filter = drvName + " (";
1251 for ( const QString &ext : extensions )
1252 {
1253 filter.append( u"*.%1 *.%2 "_s.arg( ext.toLower(), ext.toUpper() ) );
1254 }
1255 filter = filter.trimmed().append( u")"_s );
1256 return filter;
1257 }
1258
1259 return QString();
1260}
1261
1262QList< QgsRasterFileWriter::FilterFormatDetails > QgsRasterFileWriter::supportedFiltersAndFormats( RasterFormatOptions options )
1263{
1264 static QReadWriteLock sFilterLock;
1265 static QMap< RasterFormatOptions, QList< QgsRasterFileWriter::FilterFormatDetails > > sFilters;
1266
1267 QgsReadWriteLocker locker( sFilterLock, QgsReadWriteLocker::Read );
1268
1269 const auto it = sFilters.constFind( options );
1270 if ( it != sFilters.constEnd() )
1271 return it.value();
1272
1273 GDALAllRegister();
1274 int const drvCount = GDALGetDriverCount();
1275
1277 QList< QgsRasterFileWriter::FilterFormatDetails > results;
1278
1279 FilterFormatDetails tifFormat;
1280
1281 for ( int i = 0; i < drvCount; ++i )
1282 {
1283 GDALDriverH drv = GDALGetDriver( i );
1284 if ( drv )
1285 {
1287 {
1288 const QString drvName = GDALGetDriverShortName( drv );
1289 const QString filterString = filterForDriver( drvName );
1290 if ( filterString.isEmpty() )
1291 continue;
1292
1293 FilterFormatDetails details;
1294 details.driverName = drvName;
1295 details.filterString = filterString;
1296
1297 if ( options & SortRecommended )
1298 {
1299 if ( drvName == "GTiff"_L1 )
1300 {
1301 tifFormat = details;
1302 continue;
1303 }
1304 }
1305
1306 results << details;
1307 }
1308 }
1309 }
1310
1311 std::sort( results.begin(), results.end(), []( const FilterFormatDetails & a, const FilterFormatDetails & b ) -> bool
1312 {
1313 return a.driverName < b.driverName;
1314 } );
1315
1316 if ( options & SortRecommended )
1317 {
1318 if ( !tifFormat.filterString.isEmpty() )
1319 {
1320 results.insert( 0, tifFormat );
1321 }
1322 }
1323
1324 sFilters.insert( options, results );
1325
1326 return results;
1327}
1328
1330{
1331 const auto formats = supportedFiltersAndFormats( options );
1332 QSet< QString > extensions;
1333
1334 const thread_local QRegularExpression rx( u"\\*\\.([a-zA-Z0-9]*)"_s );
1335
1336 for ( const FilterFormatDetails &format : formats )
1337 {
1338 const QString ext = format.filterString;
1339 const QRegularExpressionMatch match = rx.match( ext );
1340 if ( !match.hasMatch() )
1341 continue;
1342
1343 const QString matched = match.captured( 1 );
1344 extensions.insert( matched );
1345 }
1346
1347 QStringList extensionList( extensions.constBegin(), extensions.constEnd() );
1348
1349 std::sort( extensionList.begin(), extensionList.end(), [options]( const QString & a, const QString & b ) -> bool
1350 {
1351 if ( options & SortRecommended )
1352 {
1353 if ( a == "tif"_L1 )
1354 return true;
1355 else if ( b == "tif"_L1 )
1356 return false;
1357 if ( a == "tiff"_L1 )
1358 return true;
1359 else if ( b == "tiff"_L1 )
1360 return false;
1361 if ( a == "gpkg"_L1 )
1362 return true;
1363 else if ( b == "gpkg"_L1 )
1364 return false;
1365 }
1366
1367 return a.toLower().localeAwareCompare( b.toLower() ) < 0;
1368 } );
1369
1370 return extensionList;
1371}
RasterFileWriterResult
Raster file export results.
Definition qgis.h:1688
@ Canceled
Writing was manually canceled.
Definition qgis.h:1695
@ NoDataConflict
Internal error if a value used for 'no data' was found in input.
Definition qgis.h:1694
@ WriteError
Write error.
Definition qgis.h:1693
@ Success
Successful export.
Definition qgis.h:1689
@ CreateDatasourceError
Data source creation error.
Definition qgis.h:1692
@ DestinationProviderError
Destination data provider error.
Definition qgis.h:1691
@ SourceProviderError
Source data provider error.
Definition qgis.h:1690
DataType
Raster data types.
Definition qgis.h:379
@ CInt32
Complex Int32.
Definition qgis.h:390
@ Float32
Thirty two bit floating point (float).
Definition qgis.h:387
@ CFloat64
Complex Float64.
Definition qgis.h:392
@ Int16
Sixteen bit signed integer (qint16).
Definition qgis.h:384
@ ARGB32_Premultiplied
Color, alpha, red, green, blue, 4 bytes the same as QImage::Format_ARGB32_Premultiplied.
Definition qgis.h:394
@ Int8
Eight bit signed integer (qint8) (added in QGIS 3.30).
Definition qgis.h:382
@ UInt16
Sixteen bit unsigned integer (quint16).
Definition qgis.h:383
@ Byte
Eight bit unsigned integer (quint8).
Definition qgis.h:381
@ ARGB32
Color, alpha, red, green, blue, 4 bytes the same as QImage::Format_ARGB32.
Definition qgis.h:393
@ Float64
Sixty four bit floating point (double).
Definition qgis.h:388
@ CFloat32
Complex Float32.
Definition qgis.h:391
@ CInt16
Complex Int16.
Definition qgis.h:389
@ UInt32
Thirty two bit unsigned integer (quint32).
Definition qgis.h:385
RasterBuildPyramidOption
Raster pyramid building options.
Definition qgis.h:4877
@ RenderedImage
Rendered image.
Definition qgis.h:1677
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.
Represents a coordinate reference system (CRS).
QString toWkt(Qgis::CrsWktVariant variant=Qgis::CrsWktVariant::Wkt1Gdal, 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.
QgsCoordinateTransformContext transformContext() const
Returns data provider coordinate transform context.
virtual bool isValid() const =0
Returns true if this is a valid layer.
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition qgsfeedback.h:55
void progressChanged(double progress)
Emitted when the feedback object reports a progress change.
void canceled()
Internal routines can connect to this signal if they use event loop.
void cancel()
Tells the internal routines that the current operation should be canceled. This should be run by the ...
void setProgress(double progress)
Sets the current progress for the feedback object.
Definition qgsfeedback.h:63
static std::unique_ptr< QgsFeedback > createScaledFeedback(QgsFeedback *parentFeedback, double startPercentage, double endPercentage)
Returns a feedback object whose [0, 100] progression range will be mapped to parentFeedback [startPer...
static bool supportsRasterCreate(GDALDriverH driver)
Reads whether a driver supports GDALCreate() for raster purposes.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true, const char *file=__builtin_FILE(), const char *function=__builtin_FUNCTION(), int line=__builtin_LINE())
Adds a message to the log instance (and creates it if necessary).
static QgsProviderRegistry * instance(const QString &pluginPath=QString())
Means of accessing canonical single instance.
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.
static int typeSize(Qgis::DataType dataType)
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 a data type is a color type.
Base class for raster data providers.
virtual bool sourceHasNoDataValue(int bandNo) const
Returns true if source band has no data value.
virtual QString buildPyramids(const QList< QgsRasterPyramid > &pyramidList, const QString &resamplingMethod="NEAREST", Qgis::RasterPyramidFormat format=Qgis::RasterPyramidFormat::GeoTiff, const QStringList &configOptions=QStringList(), QgsRasterBlockFeedback *feedback=nullptr)
Creates pyramid overviews.
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 QList< QgsRasterPyramid > buildPyramidList(const QList< int > &overviewList=QList< int >())
Returns the raster layers pyramid list.
Q_DECL_DEPRECATED Qgis::RasterFileWriterResult writeRaster(const QgsRasterPipe *pipe, int nCols, int nRows, const QgsRectangle &outputExtent, const QgsCoordinateReferenceSystem &crs, QgsRasterBlockFeedback *feedback=nullptr) SIP_DEPRECATED
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)
Constructor for QgsRasterFileWriter, writing to the specified output URL/filename.
static QStringList supportedFormatExtensions(RasterFormatOptions options=SortRecommended)
Returns a list of file extensions for supported formats.
QString outputUrl() const
Returns the output URL (filename) for the raster.
QFlags< RasterFormatOption > RasterFormatOptions
void setBuildPyramidsFlag(Qgis::RasterBuildPyramidOption flag)
Sets the pyramid building option.
QgsRasterDataProvider * createMultiBandRaster(Qgis::DataType dataType, int width, int height, const QgsRectangle &extent, const QgsCoordinateReferenceSystem &crs, int nBands) SIP_FACTORY
Create a raster file with given number of bands without initializing the pixel data.
QStringList creationOptions() const
Returns the list of data source creation options which will be used when creating the output raster f...
void setOutputFormat(const QString &format)
Sets the output format.
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...
QgsRasterDataProvider * createOneBandRaster(Qgis::DataType dataType, int width, int height, const QgsRectangle &extent, const QgsCoordinateReferenceSystem &crs) SIP_FACTORY
Create a raster file with one band without initializing the pixel data.
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.
Q_DECL_DEPRECATED QgsRasterBandStats bandStatistics(int bandNo, int stats, const QgsRectangle &extent=QgsRectangle(), int sampleSize=0, QgsRasterBlockFeedback *feedback=nullptr)
Returns the band statistics.
virtual int bandCount() const =0
Gets number of bands.
virtual const QgsRasterInterface * sourceInput() const
Gets source / raw input, the first in pipe, usually provider.
Iterator for sequentially processing raster cells.
int maximumTileWidth() const
Returns the maximum tile width returned during iteration.
const QgsRasterInterface * input() const
Returns the input raster interface which is being iterated over.
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.
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.
QgsRasterInterface * last() const
Returns last interface in the pipe.
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.
QgsRasterNuller * nuller() const
Returns the raster nuller interface, or nullptr if no raster nuller is present in the pipe.
QgsCoordinateReferenceSystem destinationCrs() const
Returns the destination CRS.
QgsCoordinateReferenceSystem sourceCrs() const
Returns the source CRS.
A convenience class that simplifies locking and unlocking QReadWriteLocks.
@ Write
Lock for write.
void changeMode(Mode mode)
Change the mode of the lock to mode.
A rectangle specified with double values.
double xMinimum
double yMinimum
double xMaximum
double yMaximum
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, bool *returnOk=nullptr)
Returns the value for the given key of an enum.
Definition qgis.h:7079
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:7411
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:63
#define QgsDebugError(str)
Definition qgslogger.h:59
Details of available filters and formats.
QString filterString
Filter string for file picker dialogs.