QGIS API Documentation 3.99.0-Master (8e76e220402)
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 <QString>
40#include <QTextStream>
41
42using namespace Qt::StringLiterals;
43
45{
46 if ( mTiledMode )
47 return nullptr; // does not make sense with tiled mode
48
49 double pixelSize;
50 double geoTransform[6];
51 globalOutputParameters( extent, width, height, geoTransform, pixelSize );
52
53 return initOutput( width, height, crs, geoTransform, 1, dataType, QList<bool>(), QList<double>() );
54}
55
57{
58 if ( mTiledMode )
59 return nullptr; // does not make sense with tiled mode
60
61 double pixelSize;
62 double geoTransform[6];
63 globalOutputParameters( extent, width, height, geoTransform, pixelSize );
64
65 return initOutput( width, height, crs, geoTransform, nBands, dataType, QList<bool>(), QList<double>() );
66}
67
69 : mOutputUrl( outputUrl )
70{
71
72}
73
75{
76
77}
78
79
80// Deprecated!
81Qgis::RasterFileWriterResult QgsRasterFileWriter::writeRaster( const QgsRasterPipe *pipe, int nCols, int nRows, const QgsRectangle &outputExtent,
83{
84 return writeRaster( pipe, nCols, nRows, outputExtent, crs, ( pipe && pipe->provider() ) ? pipe->provider()->transformContext() : QgsCoordinateTransformContext(), feedback );
85}
86
87Qgis::RasterFileWriterResult QgsRasterFileWriter::writeRaster( const QgsRasterPipe *pipe, int nCols, int nRows, const QgsRectangle &outputExtent,
88 const QgsCoordinateReferenceSystem &crs, const QgsCoordinateTransformContext &transformContext,
89 QgsRasterBlockFeedback *feedback )
90{
91 QgsDebugMsgLevel( u"Entered"_s, 4 );
92
93 if ( !pipe )
94 {
96 }
97 mPipe = pipe;
98
99 //const QgsRasterInterface* iface = iter->input();
100 const QgsRasterInterface *iface = pipe->last();
101 if ( !iface )
102 {
104 }
105 mInput = iface;
106
107 if ( QgsRasterBlock::typeIsColor( iface->dataType( 1 ) ) )
108 {
110 }
111 else
112 {
114 }
115
116 QgsDebugMsgLevel( u"reading from %1"_s.arg( typeid( *iface ).name() ), 4 );
117
118 if ( !iface->sourceInput() )
119 {
120 QgsDebugError( u"iface->srcInput() == 0"_s );
122 }
123#ifdef QGISDEBUG
124 const QgsRasterInterface &srcInput = *iface->sourceInput();
125 QgsDebugMsgLevel( u"srcInput = %1"_s.arg( typeid( srcInput ).name() ), 4 );
126#endif
127
128 mFeedback = feedback;
129
130 QgsRasterIterator iter( pipe->last() );
131
132 //create directory for output files
133 if ( mTiledMode )
134 {
135 const QFileInfo fileInfo( mOutputUrl );
136 if ( !fileInfo.exists() )
137 {
138 const QDir dir = fileInfo.dir();
139 if ( !dir.mkdir( fileInfo.fileName() ) )
140 {
141 QgsDebugError( "Cannot create output VRT directory " + fileInfo.fileName() + " in " + dir.absolutePath() );
143 }
144 }
145 }
146
147 // Remove pre-existing overview files to avoid using those with new raster
148 QFile pyramidFile( mOutputUrl + ( mTiledMode ? ".vrt.ovr" : ".ovr" ) );
149 if ( pyramidFile.exists() )
150 pyramidFile.remove();
151 pyramidFile.setFileName( mOutputUrl + ( mTiledMode ? ".vrt.rrd" : ".rrd" ) );
152 if ( pyramidFile.exists() )
153 pyramidFile.remove();
154
156 {
157 return writeImageRaster( &iter, nCols, nRows, outputExtent, crs, feedback );
158 }
159 else
160 {
161 return writeDataRaster( pipe, &iter, nCols, nRows, outputExtent, crs, transformContext, feedback );
162 }
163}
164
165Qgis::RasterFileWriterResult 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( u"Entered"_s, 4 );
169 if ( !iter )
170 {
172 }
173
174 const QgsRasterInterface *iface = pipe->last();
175 if ( !iface )
176 {
178 }
179
180 QgsRasterDataProvider *srcProvider = const_cast<QgsRasterDataProvider *>( dynamic_cast<const QgsRasterDataProvider *>( iface->sourceInput() ) );
181 if ( !srcProvider )
182 {
183 QgsDebugError( u"Cannot get source data provider"_s );
185 }
186
187 iter->setMaximumTileWidth( mMaxTileWidth );
188 iter->setMaximumTileHeight( mMaxTileHeight );
189
190 const int nBands = iface->bandCount();
191 if ( nBands < 1 )
192 {
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 {
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( "gpkg"_L1, 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( u"srcHasNoDataValue = %1 srcNoDataValue = %2"_s.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, Qgis::RasterBandStatistic::Min | Qgis::RasterBandStatistic::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( u"bandNo = %1 destDataType = %2 destHasNoDataValue = %3 destNoDataValue = %4"_s.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
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 {
328 }
329 if ( !destProvider->isValid() )
330 {
331 if ( feedback && !destProvider->error().isEmpty() )
332 {
333 feedback->appendError( destProvider->error().summary() );
334 }
336 }
337 if ( nCols != destProvider->xSize() || nRows != destProvider->ySize() )
338 {
339 QgsDebugError( u"Created raster does not have requested dimensions"_s );
340 if ( feedback )
341 {
342 feedback->appendError( QObject::tr( "Created raster does not have requested dimensions" ) );
343 }
345 }
346 if ( nBands != destProvider->bandCount() )
347 {
348 QgsDebugError( u"Created raster does not have requested band count"_s );
349 if ( feedback )
350 {
351 feedback->appendError( QObject::tr( "Created raster does not have requested band count" ) );
352 }
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, feedback );
364
365 if ( attempt == 0 && error == Qgis::RasterFileWriterResult::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
399static int qgsDivRoundUp( int a, int b )
400{
401 return a / b + ( ( ( a % b ) != 0 ) ? 1 : 0 );
402}
403
404Qgis::RasterFileWriterResult 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 std::unique_ptr<QgsRasterDataProvider> &destProvider,
413 QgsRasterBlockFeedback *feedback )
414{
415 Q_UNUSED( pipe )
416 Q_UNUSED( destHasNoDataValueList )
417 QgsDebugMsgLevel( u"Entered"_s, 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( u"nBands = %1"_s.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, feedback );
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 const bool hasReportsDuringClose = destProvider && destProvider->hasReportsDuringClose();
455
456 // hmm why is there a for(;;) here ..
457 // not good coding practice IMHO, it might be better to use [ for() and break ] or [ while (test) ]
458 Q_FOREVER
459 {
460 bool done = false;
461 for ( int i = 1; i <= nBands && !done; ++i )
462 {
463 QgsRasterBlock *block = nullptr;
464 if ( !iter->readNextRasterPart( i, iterCols, iterRows, &block, iterLeft, iterTop ) )
465 {
466 // No more parts, create VRT and return
467 if ( mTiledMode )
468 {
469 const QString vrtFilePath( mOutputUrl + '/' + vrtFileName() );
470 writeVRT( vrtFilePath );
471 if ( mBuildPyramidsFlag == Qgis::RasterBuildPyramidOption::Yes )
472 {
473 if ( !buildPyramids( vrtFilePath ) )
474 {
476 }
477 }
478 }
479 else
480 {
481 if ( mBuildPyramidsFlag == Qgis::RasterBuildPyramidOption::Yes &&
482 // Pyramid creation is done by the driver itself
483 mOutputFormat != "COG"_L1 )
484 {
485 if ( !buildPyramids( mOutputUrl, destProvider.get() ) )
486 {
488 }
489 }
490 }
491
492 QgsDebugMsgLevel( u"Done"_s, 4 );
493 done = true;
494 }
495 blockList[i - 1].reset( block );
496 // TODO: verify if NoDataConflict happened, to do that we need the whole pipe or nuller interface
497 }
498 if ( done )
499 {
500 break;
501 }
502
503 if ( feedback && fileIndex < ( nParts - 1 ) )
504 {
505 const double maxProgress = hasReportsDuringClose ? 50.0 : 100.0;
506 feedback->setProgress( maxProgress * fileIndex / static_cast< double >( nParts ) );
507 if ( feedback->isCanceled() )
508 {
509 break;
510 }
511 }
512
513 // It may happen that internal data type (dataType) is wider than destDataType
514 for ( int i = 1; i <= nBands; ++i )
515 {
516 if ( srcProvider && srcProvider->dataType( i ) == destDataType )
517 {
518 // nothing
519 }
520 else
521 {
522 // TODO: this conversion should go to QgsRasterDataProvider::write with additional input data type param
523 blockList[i - 1]->convert( destDataType );
524 }
525 destBlockList[i - 1] = std::move( blockList[i - 1] );
526 }
527
528 if ( mTiledMode ) //write to file
529 {
530 std::unique_ptr< QgsRasterDataProvider > partDestProvider( createPartProvider( outputExtent,
531 nCols, iterCols, iterRows,
532 iterLeft, iterTop, mOutputUrl,
533 fileIndex, nBands, destDataType, crs ) );
534
535 if ( !partDestProvider || !partDestProvider->isValid() )
536 {
538 }
539
540 //write data to output file. todo: loop over the data list
541 for ( int i = 1; i <= nBands; ++i )
542 {
543 if ( destHasNoDataValueList.value( i - 1 ) )
544 {
545 partDestProvider->setNoDataValue( i, destNoDataValueList.value( i - 1 ) );
546 }
547 if ( destBlockList[ i - 1 ]->isEmpty() )
548 continue;
549
550 if ( !partDestProvider->write( destBlockList[i - 1]->constBits( 0 ), i, iterCols, iterRows, 0, 0 ) )
551 {
553 }
554 addToVRT( partFileName( fileIndex ), i, iterCols, iterRows, iterLeft, iterTop );
555 }
556
557 }
558 else if ( destProvider )
559 {
560 //loop over data
561 for ( int i = 1; i <= nBands; ++i )
562 {
563 if ( destBlockList[ i - 1 ]->isEmpty() )
564 continue;
565
566 if ( !destProvider->write( destBlockList[i - 1]->constBits( 0 ), i, iterCols, iterRows, iterLeft, iterTop ) )
567 {
569 }
570 }
571 }
572 ++fileIndex;
573 }
574
575 // If the provider can report progress during closing (typically when generating COG files),
576 // report it, making feedback report percentage in the [50, 100] range.
577 if ( feedback && destProvider && hasReportsDuringClose )
578 {
579 std::unique_ptr<QgsFeedback> scaledFeedback( QgsFeedback::createScaledFeedback( feedback, 50.0, 100.0 ) );
580 if ( !destProvider->closeWithProgress( scaledFeedback.get() ) )
581 {
582 destProvider->remove();
583 destProvider.reset();
584 return ( feedback && feedback->isCanceled() ) ?
587 }
588 }
589
590 QgsDebugMsgLevel( u"Done"_s, 4 );
592}
593
594Qgis::RasterFileWriterResult QgsRasterFileWriter::writeImageRaster( QgsRasterIterator *iter, int nCols, int nRows, const QgsRectangle &outputExtent,
596{
597 QgsDebugMsgLevel( u"Entered"_s, 4 );
598 if ( !iter )
599 {
601 }
602
603 const QgsRasterInterface *iface = iter->input();
604 if ( !iface )
606
607 const Qgis::DataType inputDataType = iface->dataType( 1 );
608 if ( inputDataType != Qgis::DataType::ARGB32 && inputDataType != Qgis::DataType::ARGB32_Premultiplied )
609 {
611 }
612 const bool isPremultiplied = ( inputDataType == Qgis::DataType::ARGB32_Premultiplied );
613
614 iter->setMaximumTileWidth( mMaxTileWidth );
615 iter->setMaximumTileHeight( mMaxTileHeight );
616
617 const size_t nMaxPixels = static_cast<size_t>( mMaxTileWidth ) * mMaxTileHeight;
618 std::vector<unsigned char> redData( nMaxPixels );
619 std::vector<unsigned char> greenData( nMaxPixels );
620 std::vector<unsigned char> blueData( nMaxPixels );
621 std::vector<unsigned char> alphaData( nMaxPixels );
622 int iterLeft = 0, iterTop = 0, iterCols = 0, iterRows = 0;
623 int fileIndex = 0;
624
625 //create destProvider for whole dataset here
626 double pixelSize;
627 double geoTransform[6];
628 globalOutputParameters( outputExtent, nCols, nRows, geoTransform, pixelSize );
629
630 const int nOutputBands = 4;
631 std::unique_ptr< QgsRasterDataProvider > destProvider( initOutput( nCols, nRows, crs, geoTransform, nOutputBands, Qgis::DataType::Byte ) );
632 if ( !mTiledMode )
633 {
634 if ( !destProvider )
635 {
637 }
638 if ( !destProvider->isValid() )
639 {
640 if ( feedback && !destProvider->error().isEmpty() )
641 {
642 feedback->appendError( destProvider->error().summary() );
643 }
645 }
646 if ( nCols != destProvider->xSize() || nRows != destProvider->ySize() )
647 {
648 QgsDebugError( u"Created raster does not have requested dimensions"_s );
649 if ( feedback )
650 {
651 feedback->appendError( QObject::tr( "Created raster does not have requested dimensions" ) );
652 }
654 }
655 if ( nOutputBands != destProvider->bandCount() )
656 {
657 QgsDebugError( u"Created raster does not have requested band count"_s );
658 if ( feedback )
659 {
660 feedback->appendError( QObject::tr( "Created raster does not have requested band count" ) );
661 }
663 }
664 if ( Qgis::DataType::Byte != destProvider->dataType( 1 ) )
665 {
666 QgsDebugError( u"Created raster does not have requested data type"_s );
667 if ( feedback )
668 {
669 feedback->appendError( QObject::tr( "Created raster does not have requested data type" ) );
670 }
672 }
673 }
674
675 iter->startRasterRead( 1, nCols, nRows, outputExtent, feedback );
676
677 int nParts = 0;
678 if ( feedback )
679 {
680 const int nPartsX = qgsDivRoundUp( nCols, iter->maximumTileWidth() );
681 const int nPartsY = qgsDivRoundUp( nRows, iter->maximumTileHeight() );
682 nParts = nPartsX * nPartsY;
683 }
684
685 const bool hasReportsDuringClose = !mTiledMode && destProvider && destProvider->hasReportsDuringClose();
686 const double maxProgress = hasReportsDuringClose ? 50.0 : 100.0;
687
688 std::unique_ptr< QgsRasterBlock > inputBlock;
689 while ( iter->readNextRasterPart( 1, iterCols, iterRows, inputBlock, iterLeft, iterTop ) )
690 {
691 if ( !inputBlock || inputBlock->isEmpty() )
692 {
693 continue;
694 }
695
696 if ( feedback && fileIndex < ( nParts - 1 ) )
697 {
698 feedback->setProgress( maxProgress * fileIndex / static_cast< double >( nParts ) );
699 if ( feedback->isCanceled() )
700 {
701 break;
702 }
703 }
704
705 //fill into red/green/blue/alpha channels
706 const qgssize nPixels = static_cast< qgssize >( iterCols ) * iterRows;
707 for ( qgssize i = 0; i < nPixels; ++i )
708 {
709 QRgb c = inputBlock->color( i );
710 if ( isPremultiplied )
711 {
712 c = qUnpremultiply( c );
713 }
714 redData[i] = static_cast<unsigned char>( qRed( c ) );
715 greenData[i] = static_cast<unsigned char>( qGreen( c ) );
716 blueData[i] = static_cast<unsigned char>( qBlue( c ) );
717 alphaData[i] = static_cast<unsigned char>( qAlpha( c ) );
718 }
719
720 //create output file
721 if ( mTiledMode )
722 {
723 std::unique_ptr< QgsRasterDataProvider > partDestProvider( createPartProvider( outputExtent,
724 nCols, iterCols, iterRows,
725 iterLeft, iterTop, mOutputUrl, fileIndex,
726 4, Qgis::DataType::Byte, crs ) );
727
728 if ( !partDestProvider || partDestProvider->isValid() )
729 {
731 }
732
733 //write data to output file
734 if ( !partDestProvider->write( &redData[0], 1, iterCols, iterRows, 0, 0 ) ||
735 !partDestProvider->write( &greenData[0], 2, iterCols, iterRows, 0, 0 ) ||
736 !partDestProvider->write( &blueData[0], 3, iterCols, iterRows, 0, 0 ) ||
737 !partDestProvider->write( &alphaData[0], 4, iterCols, iterRows, 0, 0 ) )
738 {
740 }
741
742 addToVRT( partFileName( fileIndex ), 1, iterCols, iterRows, iterLeft, iterTop );
743 addToVRT( partFileName( fileIndex ), 2, iterCols, iterRows, iterLeft, iterTop );
744 addToVRT( partFileName( fileIndex ), 3, iterCols, iterRows, iterLeft, iterTop );
745 addToVRT( partFileName( fileIndex ), 4, iterCols, iterRows, iterLeft, iterTop );
746 }
747 else if ( destProvider )
748 {
749 if ( !destProvider->write( &redData[0], 1, iterCols, iterRows, iterLeft, iterTop ) ||
750 !destProvider->write( &greenData[0], 2, iterCols, iterRows, iterLeft, iterTop ) ||
751 !destProvider->write( &blueData[0], 3, iterCols, iterRows, iterLeft, iterTop ) ||
752 !destProvider->write( &alphaData[0], 4, iterCols, iterRows, iterLeft, iterTop ) )
753 {
755 }
756 }
757
758 ++fileIndex;
759 }
760
761 if ( feedback )
762 {
763 feedback->setProgress( maxProgress );
764 }
765
766 if ( mTiledMode )
767 {
768 destProvider.reset();
769
770 const QString vrtFilePath( mOutputUrl + '/' + vrtFileName() );
771 writeVRT( vrtFilePath );
772 if ( mBuildPyramidsFlag == Qgis::RasterBuildPyramidOption::Yes )
773 {
774 if ( !buildPyramids( vrtFilePath ) )
776 }
777 }
778 else
779 {
780 // If the provider can report progress during closing (typically when generating COG files),
781 // report it, making feedback report percentage in the [50, 100] range.
782 if ( destProvider && hasReportsDuringClose )
783 {
784 QgsFeedback closingProgress;
785 if ( feedback )
786 {
787 QObject::connect( &closingProgress, &QgsFeedback::progressChanged,
788 feedback, [feedback]( double progress )
789 {
790 feedback->setProgress( 50.0 + progress * 0.5 );
791 } );
792 QObject::connect( &closingProgress, &QgsFeedback::canceled,
793 feedback, &QgsFeedback::cancel );
794 }
795
796 if ( !destProvider->closeWithProgress( feedback ? &closingProgress : nullptr ) )
797 {
798 destProvider->remove();
799 destProvider.reset();
800 return ( feedback && feedback->isCanceled() ) ?
803 }
804 }
805
806 destProvider.reset();
807
808 if ( mBuildPyramidsFlag == Qgis::RasterBuildPyramidOption::Yes &&
809 // Pyramid creation is done by the driver itself
810 mOutputFormat != "COG"_L1 )
811 {
812 if ( !buildPyramids( mOutputUrl ) )
814 }
815 }
817}
818
819void QgsRasterFileWriter::addToVRT( const QString &filename, int band, int xSize, int ySize, int xOffset, int yOffset )
820{
821 QDomElement bandElem = mVRTBands.value( band - 1 );
822
823 QDomElement simpleSourceElem = mVRTDocument.createElement( u"SimpleSource"_s );
824
825 //SourceFilename
826 QDomElement sourceFilenameElem = mVRTDocument.createElement( u"SourceFilename"_s );
827 sourceFilenameElem.setAttribute( u"relativeToVRT"_s, u"1"_s );
828 const QDomText sourceFilenameText = mVRTDocument.createTextNode( filename );
829 sourceFilenameElem.appendChild( sourceFilenameText );
830 simpleSourceElem.appendChild( sourceFilenameElem );
831
832 //SourceBand
833 QDomElement sourceBandElem = mVRTDocument.createElement( u"SourceBand"_s );
834 const QDomText sourceBandText = mVRTDocument.createTextNode( QString::number( band ) );
835 sourceBandElem.appendChild( sourceBandText );
836 simpleSourceElem.appendChild( sourceBandElem );
837
838 //SourceProperties
839 QDomElement sourcePropertiesElem = mVRTDocument.createElement( u"SourceProperties"_s );
840 sourcePropertiesElem.setAttribute( u"RasterXSize"_s, xSize );
841 sourcePropertiesElem.setAttribute( u"RasterYSize"_s, ySize );
842 sourcePropertiesElem.setAttribute( u"BlockXSize"_s, xSize );
843 sourcePropertiesElem.setAttribute( u"BlockYSize"_s, ySize );
844 sourcePropertiesElem.setAttribute( u"DataType"_s, u"Byte"_s );
845 simpleSourceElem.appendChild( sourcePropertiesElem );
846
847 //SrcRect
848 QDomElement srcRectElem = mVRTDocument.createElement( u"SrcRect"_s );
849 srcRectElem.setAttribute( u"xOff"_s, u"0"_s );
850 srcRectElem.setAttribute( u"yOff"_s, u"0"_s );
851 srcRectElem.setAttribute( u"xSize"_s, xSize );
852 srcRectElem.setAttribute( u"ySize"_s, ySize );
853 simpleSourceElem.appendChild( srcRectElem );
854
855 //DstRect
856 QDomElement dstRectElem = mVRTDocument.createElement( u"DstRect"_s );
857 dstRectElem.setAttribute( u"xOff"_s, xOffset );
858 dstRectElem.setAttribute( u"yOff"_s, yOffset );
859 dstRectElem.setAttribute( u"xSize"_s, xSize );
860 dstRectElem.setAttribute( u"ySize"_s, ySize );
861 simpleSourceElem.appendChild( dstRectElem );
862
863 bandElem.appendChild( simpleSourceElem );
864}
865
866bool QgsRasterFileWriter::buildPyramids( const QString &filename, QgsRasterDataProvider *destProviderIn )
867{
868 QgsDebugMsgLevel( "filename = " + filename, 4 );
869 // open new dataProvider so we can build pyramids with it
870 const QgsDataProvider::ProviderOptions providerOptions;
871 QgsRasterDataProvider *destProvider = destProviderIn;
872 if ( !destProvider )
873 {
874 destProvider = qobject_cast< QgsRasterDataProvider * >( QgsProviderRegistry::instance()->createProvider( mOutputProviderKey, filename, providerOptions ) );
875 if ( !destProvider || !destProvider->isValid() )
876 {
877 delete destProvider;
878 return false;
879 }
880 }
881
882 // TODO progress report
883 // TODO test mTiledMode - not tested b/c segfault at line # 289
884 // connect( provider, &FIXME::progressUpdate, mPyramidProgress, &FIXME::setValue );
885 QList< QgsRasterPyramid> myPyramidList;
886 if ( ! mPyramidsList.isEmpty() )
887 myPyramidList = destProvider->buildPyramidList( mPyramidsList );
888 for ( int myCounterInt = 0; myCounterInt < myPyramidList.count(); myCounterInt++ )
889 {
890 myPyramidList[myCounterInt].setBuild( true );
891 }
892
893 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 );
894 // QApplication::setOverrideCursor( Qt::WaitCursor );
895 const QString res = destProvider->buildPyramids( myPyramidList, mPyramidsResampling,
896 mPyramidsFormat, mPyramidsConfigOptions );
897 // QApplication::restoreOverrideCursor();
898
899 // TODO put this in provider or elsewhere
900 if ( !res.isNull() )
901 {
902 QString message;
903 if ( res == "ERROR_WRITE_ACCESS"_L1 )
904 {
905 message = QObject::tr( "Write access denied. Adjust the file permissions and try again." );
906 }
907 else if ( res == "ERROR_WRITE_FORMAT"_L1 )
908 {
909 message = QObject::tr( "The file was not writable. Some formats do not "
910 "support pyramid overviews. Consult the GDAL documentation if in doubt." );
911 }
912 else if ( res == "FAILED_NOT_SUPPORTED"_L1 )
913 {
914 message = QObject::tr( "Building pyramid overviews is not supported on this type of raster." );
915 }
916 else if ( res == "ERROR_VIRTUAL"_L1 )
917 {
918 message = QObject::tr( "Building pyramid overviews is not supported on this type of raster." );
919 }
920
921 QgsMessageLog::logMessage( message, QObject::tr( "Building Pyramids" ) );
922
923 QgsDebugMsgLevel( res + " - " + message, 4 );
924 }
925 if ( !destProviderIn )
926 delete destProvider;
927
928 return res.isNull();
929}
930
931#if 0
932int QgsRasterFileWriter::pyramidsProgress( double dfComplete, const char *pszMessage, void *pData )
933{
934 Q_UNUSED( pszMessage )
935 GDALTermProgress( dfComplete, 0, 0 );
936 QProgressDialog *progressDialog = static_cast<QProgressDialog *>( pData );
937 if ( pData && progressDialog->wasCanceled() )
938 {
939 return 0;
940 }
941
942 if ( pData )
943 {
944 progressDialog->setRange( 0, 100 );
945 progressDialog->setValue( dfComplete * 100 );
946 }
947 return 1;
948}
949#endif
950
951void QgsRasterFileWriter::createVRT( int xSize, int ySize, const QgsCoordinateReferenceSystem &crs, double *geoTransform, Qgis::DataType type, const QList<bool> &destHasNoDataValueList, const QList<double> &destNoDataValueList )
952{
953 mVRTDocument.clear();
954 QDomElement VRTDatasetElem = mVRTDocument.createElement( u"VRTDataset"_s );
955
956 //xsize / ysize
957 VRTDatasetElem.setAttribute( u"rasterXSize"_s, xSize );
958 VRTDatasetElem.setAttribute( u"rasterYSize"_s, ySize );
959 mVRTDocument.appendChild( VRTDatasetElem );
960
961 //CRS
962 QDomElement SRSElem = mVRTDocument.createElement( u"SRS"_s );
963 const QDomText crsText = mVRTDocument.createTextNode( crs.toWkt() );
964 SRSElem.appendChild( crsText );
965 VRTDatasetElem.appendChild( SRSElem );
966
967 //geotransform
968 if ( geoTransform )
969 {
970 QDomElement geoTransformElem = mVRTDocument.createElement( u"GeoTransform"_s );
971 const QString geoTransformString = QString::number( geoTransform[0], 'f', 6 ) + ", " + QString::number( geoTransform[1] ) + ", " + QString::number( geoTransform[2] ) +
972 ", " + QString::number( geoTransform[3], 'f', 6 ) + ", " + QString::number( geoTransform[4] ) + ", " + QString::number( geoTransform[5] );
973 const QDomText geoTransformText = mVRTDocument.createTextNode( geoTransformString );
974 geoTransformElem.appendChild( geoTransformText );
975 VRTDatasetElem.appendChild( geoTransformElem );
976 }
977
978 int nBands;
979 if ( mMode == Qgis::RasterExportType::Raw )
980 {
981 nBands = mInput->bandCount();
982 }
983 else
984 {
985 nBands = 4;
986 }
987
988 QStringList colorInterp;
989 colorInterp << u"Red"_s << u"Green"_s << u"Blue"_s << u"Alpha"_s;
990
991 QMap<Qgis::DataType, QString> dataTypes;
992 dataTypes.insert( Qgis::DataType::Byte, u"Byte"_s );
993 dataTypes.insert( Qgis::DataType::Int8, u"Int8"_s );
994 dataTypes.insert( Qgis::DataType::UInt16, u"UInt16"_s );
995 dataTypes.insert( Qgis::DataType::Int16, u"Int16"_s );
996 dataTypes.insert( Qgis::DataType::UInt32, u"Int32"_s );
997 dataTypes.insert( Qgis::DataType::Float32, u"Float32"_s );
998 dataTypes.insert( Qgis::DataType::Float64, u"Float64"_s );
999 dataTypes.insert( Qgis::DataType::CInt16, u"CInt16"_s );
1000 dataTypes.insert( Qgis::DataType::CInt32, u"CInt32"_s );
1001 dataTypes.insert( Qgis::DataType::CFloat32, u"CFloat32"_s );
1002 dataTypes.insert( Qgis::DataType::CFloat64, u"CFloat64"_s );
1003
1004 for ( int i = 1; i <= nBands; i++ )
1005 {
1006 QDomElement VRTBand = mVRTDocument.createElement( u"VRTRasterBand"_s );
1007
1008 VRTBand.setAttribute( u"band"_s, QString::number( i ) );
1009 const QString dataType = dataTypes.value( type );
1010 VRTBand.setAttribute( u"dataType"_s, dataType );
1011
1013 {
1014
1015 VRTBand.setAttribute( u"dataType"_s, u"Byte"_s );
1016 QDomElement colorInterpElement = mVRTDocument.createElement( u"ColorInterp"_s );
1017 const QDomText interpText = mVRTDocument.createTextNode( colorInterp.value( i - 1 ) );
1018 colorInterpElement.appendChild( interpText );
1019 VRTBand.appendChild( colorInterpElement );
1020 }
1021
1022 if ( !destHasNoDataValueList.isEmpty() && destHasNoDataValueList.value( i - 1 ) )
1023 {
1024 VRTBand.setAttribute( u"NoDataValue"_s, QString::number( destNoDataValueList.value( i - 1 ) ) );
1025 }
1026
1027 mVRTBands.append( VRTBand );
1028 VRTDatasetElem.appendChild( VRTBand );
1029 }
1030}
1031
1032bool QgsRasterFileWriter::writeVRT( const QString &file )
1033{
1034 QFile outputFile( file );
1035 if ( ! outputFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
1036 {
1037 return false;
1038 }
1039
1040 QTextStream outStream( &outputFile );
1041 mVRTDocument.save( outStream, 2 );
1042 return true;
1043}
1044
1045QgsRasterDataProvider *QgsRasterFileWriter::createPartProvider( const QgsRectangle &extent, int nCols, int iterCols,
1046 int iterRows, int iterLeft, int iterTop, const QString &outputUrl, int fileIndex, int nBands, Qgis::DataType type,
1047 const QgsCoordinateReferenceSystem &crs )
1048{
1049 const double mup = extent.width() / nCols;
1050 const double mapLeft = extent.xMinimum() + iterLeft * mup;
1051 const double mapRight = mapLeft + mup * iterCols;
1052 const double mapTop = extent.yMaximum() - iterTop * mup;
1053 const double mapBottom = mapTop - iterRows * mup;
1054 const QgsRectangle mapRect( mapLeft, mapBottom, mapRight, mapTop );
1055
1056 const QString outputFile = outputUrl + '/' + partFileName( fileIndex );
1057
1058 //geotransform
1059 double geoTransform[6];
1060 geoTransform[0] = mapRect.xMinimum();
1061 geoTransform[1] = mup;
1062 geoTransform[2] = 0.0;
1063 geoTransform[3] = mapRect.yMaximum();
1064 geoTransform[4] = 0.0;
1065 geoTransform[5] = -mup;
1066
1067 // perhaps we need a separate creationOptions for tiles ?
1068
1069 QgsRasterDataProvider *destProvider = QgsRasterDataProvider::create( mOutputProviderKey, outputFile, mOutputFormat, nBands, type, iterCols, iterRows, geoTransform, crs, mCreationOptions );
1070
1071 // TODO: return provider and report error
1072 return destProvider;
1073}
1074
1075void QgsRasterFileWriter::setOutputFormat( const QString &format )
1076{
1077 mOutputFormat = format;
1078 if ( !mBuildPyramidsFlagSet && format == "COG"_L1 )
1079 {
1081 }
1082}
1083
1085{
1086 mBuildPyramidsFlag = flag;
1087 mBuildPyramidsFlagSet = true;
1088}
1089
1090QgsRasterDataProvider *QgsRasterFileWriter::initOutput( int nCols, int nRows, const QgsCoordinateReferenceSystem &crs,
1091 double *geoTransform, int nBands, Qgis::DataType type,
1092 const QList<bool> &destHasNoDataValueList, const QList<double> &destNoDataValueList )
1093{
1094 if ( mTiledMode )
1095 {
1096 createVRT( nCols, nRows, crs, geoTransform, type, destHasNoDataValueList, destNoDataValueList );
1097 return nullptr;
1098 }
1099 else
1100 {
1101#if 0
1102 // TODO enable "use existing", has no effect for now, because using Create() in gdal provider
1103 // should this belong in provider? should also test that source provider is gdal
1104 if ( mBuildPyramidsFlag == -4 && mOutputProviderKey == "gdal" && mOutputFormat.compare( "gtiff"_L1, Qt::CaseInsensitive ) == 0 )
1105 mCreationOptions << "COPY_SRC_OVERVIEWS=YES";
1106#endif
1107 QStringList creationOptions( mCreationOptions );
1108 if ( mOutputFormat == "COG"_L1 )
1109 {
1110 if ( mBuildPyramidsFlag == Qgis::RasterBuildPyramidOption::No )
1111 creationOptions << u"OVERVIEWS=NO"_s;
1112 else
1113 {
1114 creationOptions << u"OVERVIEW_RESAMPLING="_s + mPyramidsResampling;
1115 for ( const QString &opt : std::as_const( mPyramidsConfigOptions ) )
1116 {
1117 const std::string optStr( opt.toStdString() );
1118 char *key = nullptr;
1119 const char *value = CPLParseNameValue( optStr.c_str(), &key );
1120 if ( key && value )
1121 {
1122 if ( EQUAL( key, "JPEG_QUALITY_OVERVIEW" ) )
1123 {
1124 creationOptions << u"OVERVIEW_QUALITY="_s + value;
1125 }
1126 else if ( EQUAL( key, "COMPRESS_OVERVIEW" ) )
1127 {
1128 creationOptions << u"OVERVIEW_COMPRESS="_s + value;
1129 }
1130 else if ( EQUAL( key, "PREDICTOR_OVERVIEW" ) )
1131 {
1132 creationOptions << u"OVERVIEW_PREDICTOR="_s + value;
1133 }
1134 }
1135 CPLFree( key );
1136 }
1137 }
1138 }
1139
1140 QgsRasterDataProvider *destProvider = QgsRasterDataProvider::create( mOutputProviderKey, mOutputUrl, mOutputFormat, nBands, type, nCols, nRows, geoTransform, crs, creationOptions );
1141
1142 if ( !destProvider )
1143 {
1144 QgsDebugError( u"No provider created"_s );
1145 }
1146
1147 return destProvider;
1148 }
1149}
1150
1151void QgsRasterFileWriter::globalOutputParameters( const QgsRectangle &extent, int nCols, int &nRows,
1152 double *geoTransform, double &pixelSize )
1153{
1154 pixelSize = extent.width() / nCols;
1155
1156 //calculate nRows automatically for providers without exact resolution
1157 if ( nRows < 0 )
1158 {
1159 nRows = static_cast< double >( nCols ) / extent.width() * extent.height() + 0.5; //NOLINT
1160 }
1161 geoTransform[0] = extent.xMinimum();
1162 geoTransform[1] = pixelSize;
1163 geoTransform[2] = 0.0;
1164 geoTransform[3] = extent.yMaximum();
1165 geoTransform[4] = 0.0;
1166 geoTransform[5] = -( extent.height() / nRows );
1167}
1168
1169QString QgsRasterFileWriter::partFileName( int fileIndex )
1170{
1171 // .tif for now
1172 const QFileInfo outputInfo( mOutputUrl );
1173 return u"%1.%2.tif"_s.arg( outputInfo.fileName() ).arg( fileIndex );
1174}
1175
1176QString QgsRasterFileWriter::vrtFileName()
1177{
1178 const QFileInfo outputInfo( mOutputUrl );
1179 return u"%1.vrt"_s.arg( outputInfo.fileName() );
1180}
1181
1182QString QgsRasterFileWriter::driverForExtension( const QString &extension )
1183{
1184 QString ext = extension.trimmed();
1185 if ( ext.isEmpty() )
1186 return QString();
1187
1188 if ( ext.startsWith( '.' ) )
1189 ext.remove( 0, 1 );
1190
1191 if ( ext.compare( "tif"_L1, Qt::CaseInsensitive ) == 0 ||
1192 ext.compare( "tiff"_L1, Qt::CaseInsensitive ) == 0 )
1193 {
1194 // Be robust to GDAL drivers potentially recognizing the tif/tiff extensions
1195 // but being registered before the GTiff one.
1196 // Cf https://github.com/qgis/QGIS/issues/59112
1197 if ( GDALGetDriverByName( "GTiff" ) )
1198 return "GTiff";
1199 }
1200
1201 GDALAllRegister();
1202 int const drvCount = GDALGetDriverCount();
1203
1204 for ( int i = 0; i < drvCount; ++i )
1205 {
1206 GDALDriverH drv = GDALGetDriver( i );
1207 if ( drv )
1208 {
1209 CSLConstList driverMetadata = GDALGetMetadata( drv, nullptr );
1210 if ( CSLFetchBoolean( driverMetadata, GDAL_DCAP_RASTER, false ) )
1211 {
1212 QString drvName = GDALGetDriverShortName( drv );
1213 const QStringList driverExtensions = QString( GDALGetMetadataItem( drv, GDAL_DMD_EXTENSIONS, nullptr ) ).split( ' ' );
1214
1215 const auto constDriverExtensions = driverExtensions;
1216 for ( const QString &driver : constDriverExtensions )
1217 {
1218 if ( driver.compare( ext, Qt::CaseInsensitive ) == 0 )
1219 return drvName;
1220 }
1221 }
1222 }
1223 }
1224 return QString();
1225}
1226
1227QStringList QgsRasterFileWriter::extensionsForFormat( const QString &format )
1228{
1229 GDALDriverH drv = GDALGetDriverByName( format.toLocal8Bit().data() );
1230 if ( drv )
1231 {
1232 CSLConstList driverMetadata = GDALGetMetadata( drv, nullptr );
1233 if ( CSLFetchBoolean( driverMetadata, GDAL_DCAP_RASTER, false ) )
1234 {
1235 return QString( GDALGetMetadataItem( drv, GDAL_DMD_EXTENSIONS, nullptr ) ).split( ' ' );
1236 }
1237 }
1238 return QStringList();
1239}
1240
1241QString QgsRasterFileWriter::filterForDriver( const QString &driverName )
1242{
1243 GDALDriverH drv = GDALGetDriverByName( driverName.toLocal8Bit().data() );
1244 if ( drv )
1245 {
1246 const QString drvName = GDALGetDriverLongName( drv );
1247 const QString extensionsString = QString( GDALGetMetadataItem( drv, GDAL_DMD_EXTENSIONS, nullptr ) );
1248 if ( extensionsString.isEmpty() )
1249 {
1250 return QString();
1251 }
1252 const QStringList extensions = extensionsString.split( ' ' );
1253 QString filter = drvName + " (";
1254 for ( const QString &ext : extensions )
1255 {
1256 filter.append( u"*.%1 *.%2 "_s.arg( ext.toLower(), ext.toUpper() ) );
1257 }
1258 filter = filter.trimmed().append( u")"_s );
1259 return filter;
1260 }
1261
1262 return QString();
1263}
1264
1265QList< QgsRasterFileWriter::FilterFormatDetails > QgsRasterFileWriter::supportedFiltersAndFormats( RasterFormatOptions options )
1266{
1267 static QReadWriteLock sFilterLock;
1268 static QMap< RasterFormatOptions, QList< QgsRasterFileWriter::FilterFormatDetails > > sFilters;
1269
1270 QgsReadWriteLocker locker( sFilterLock, QgsReadWriteLocker::Read );
1271
1272 const auto it = sFilters.constFind( options );
1273 if ( it != sFilters.constEnd() )
1274 return it.value();
1275
1276 GDALAllRegister();
1277 int const drvCount = GDALGetDriverCount();
1278
1280 QList< QgsRasterFileWriter::FilterFormatDetails > results;
1281
1282 FilterFormatDetails tifFormat;
1283
1284 for ( int i = 0; i < drvCount; ++i )
1285 {
1286 GDALDriverH drv = GDALGetDriver( i );
1287 if ( drv )
1288 {
1290 {
1291 const QString drvName = GDALGetDriverShortName( drv );
1292 const QString filterString = filterForDriver( drvName );
1293 if ( filterString.isEmpty() )
1294 continue;
1295
1296 FilterFormatDetails details;
1297 details.driverName = drvName;
1298 details.filterString = filterString;
1299
1300 if ( options & SortRecommended )
1301 {
1302 if ( drvName == "GTiff"_L1 )
1303 {
1304 tifFormat = details;
1305 continue;
1306 }
1307 }
1308
1309 results << details;
1310 }
1311 }
1312 }
1313
1314 std::sort( results.begin(), results.end(), []( const FilterFormatDetails & a, const FilterFormatDetails & b ) -> bool
1315 {
1316 return a.driverName < b.driverName;
1317 } );
1318
1319 if ( options & SortRecommended )
1320 {
1321 if ( !tifFormat.filterString.isEmpty() )
1322 {
1323 results.insert( 0, tifFormat );
1324 }
1325 }
1326
1327 sFilters.insert( options, results );
1328
1329 return results;
1330}
1331
1333{
1334 const auto formats = supportedFiltersAndFormats( options );
1335 QSet< QString > extensions;
1336
1337 const thread_local QRegularExpression rx( u"\\*\\.([a-zA-Z0-9]*)"_s );
1338
1339 for ( const FilterFormatDetails &format : formats )
1340 {
1341 const QString ext = format.filterString;
1342 const QRegularExpressionMatch match = rx.match( ext );
1343 if ( !match.hasMatch() )
1344 continue;
1345
1346 const QString matched = match.captured( 1 );
1347 extensions.insert( matched );
1348 }
1349
1350 QStringList extensionList( extensions.constBegin(), extensions.constEnd() );
1351
1352 std::sort( extensionList.begin(), extensionList.end(), [options]( const QString & a, const QString & b ) -> bool
1353 {
1354 if ( options & SortRecommended )
1355 {
1356 if ( a == "tif"_L1 )
1357 return true;
1358 else if ( b == "tif"_L1 )
1359 return false;
1360 if ( a == "tiff"_L1 )
1361 return true;
1362 else if ( b == "tiff"_L1 )
1363 return false;
1364 if ( a == "gpkg"_L1 )
1365 return true;
1366 else if ( b == "gpkg"_L1 )
1367 return false;
1368 }
1369
1370 return a.toLower().localeAwareCompare( b.toLower() ) < 0;
1371 } );
1372
1373 return extensionList;
1374}
RasterFileWriterResult
Raster file export results.
Definition qgis.h:1700
@ Canceled
Writing was manually canceled.
Definition qgis.h:1707
@ NoDataConflict
Internal error if a value used for 'no data' was found in input.
Definition qgis.h:1706
@ WriteError
Write error.
Definition qgis.h:1705
@ Success
Successful export.
Definition qgis.h:1701
@ CreateDatasourceError
Data source creation error.
Definition qgis.h:1704
@ DestinationProviderError
Destination data provider error.
Definition qgis.h:1703
@ SourceProviderError
Source data provider error.
Definition qgis.h:1702
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:4924
@ RenderedImage
Rendered image.
Definition qgis.h:1689
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:7126
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:7458
#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.