QGIS API Documentation 4.1.0-Master (60fea48833c)
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
73{}
74
75
76// Deprecated!
78 const QgsRasterPipe *pipe, int nCols, int nRows, const QgsRectangle &outputExtent, const QgsCoordinateReferenceSystem &crs, QgsRasterBlockFeedback *feedback
79)
80{
81 return writeRaster( pipe, nCols, nRows, outputExtent, crs, ( pipe && pipe->provider() ) ? pipe->provider()->transformContext() : QgsCoordinateTransformContext(), feedback );
82}
83
85 const QgsRasterPipe *pipe, int nCols, int nRows, const QgsRectangle &outputExtent, const QgsCoordinateReferenceSystem &crs, const QgsCoordinateTransformContext &transformContext, QgsRasterBlockFeedback *feedback
86)
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(
163 const QgsRasterPipe *pipe,
164 QgsRasterIterator *iter,
165 int nCols,
166 int nRows,
167 const QgsRectangle &outputExtent,
169 const QgsCoordinateTransformContext &transformContext,
170 QgsRasterBlockFeedback *feedback
171)
172{
173 QgsDebugMsgLevel( u"Entered"_s, 4 );
174 if ( !iter )
175 {
177 }
178
179 const QgsRasterInterface *iface = pipe->last();
180 if ( !iface )
181 {
183 }
184
185 QgsRasterDataProvider *srcProvider = const_cast<QgsRasterDataProvider *>( dynamic_cast<const QgsRasterDataProvider *>( iface->sourceInput() ) );
186 if ( !srcProvider )
187 {
188 QgsDebugError( u"Cannot get source data provider"_s );
190 }
191
192 iter->setMaximumTileWidth( mMaxTileWidth );
193 iter->setMaximumTileHeight( mMaxTileHeight );
194
195 const int nBands = iface->bandCount();
196 if ( nBands < 1 )
197 {
199 }
200
201
202 //check if all the bands have the same data type size, otherwise we cannot write it to the provider
203 //(at least not with the current interface)
204 const int dataTypeSize = QgsRasterBlock::typeSize( srcProvider->sourceDataType( 1 ) );
205 for ( int i = 2; i <= nBands; ++i )
206 {
207 if ( QgsRasterBlock::typeSize( srcProvider->sourceDataType( 1 ) ) != dataTypeSize )
208 {
210 }
211 }
212
213 // Output data type - source data type is preferred but it may happen that we need
214 // to set 'no data' value (which was not set on source data) if output extent
215 // is larger than source extent (with or without reprojection) and there is no 'free'
216 // (not used) value available
217 QList<bool> destHasNoDataValueList;
218 QList<double> destNoDataValueList;
219 QList<Qgis::DataType> destDataTypeList;
220 destDataTypeList.reserve( nBands );
221 destHasNoDataValueList.reserve( nBands );
222 destNoDataValueList.reserve( nBands );
223
224 const bool isGpkgOutput = mOutputProviderKey == "gdal" && mOutputFormat.compare( "gpkg"_L1, Qt::CaseInsensitive ) == 0;
225 double pixelSize;
226 double geoTransform[6];
227 globalOutputParameters( outputExtent, nCols, nRows, geoTransform, pixelSize );
228 const auto srcProviderExtent( srcProvider->extent() );
229
230 for ( int bandNo = 1; bandNo <= nBands; bandNo++ )
231 {
232 QgsRasterNuller *nuller = pipe->nuller();
233
234 const bool srcHasNoDataValue = srcProvider->sourceHasNoDataValue( bandNo );
235 bool destHasNoDataValue = false;
236 double destNoDataValue = std::numeric_limits<double>::quiet_NaN();
237 const Qgis::DataType srcDataType = srcProvider->sourceDataType( bandNo );
238 Qgis::DataType destDataType = srcDataType;
239 // TODO: verify what happens/should happen if srcNoDataValue is disabled by setUseSrcNoDataValue
240 QgsDebugMsgLevel( u"srcHasNoDataValue = %1 srcNoDataValue = %2"_s.arg( srcHasNoDataValue ).arg( srcProvider->sourceNoDataValue( bandNo ) ), 4 );
241 if ( srcHasNoDataValue )
242 {
243 // If source has no data value, it is used by provider
244 destNoDataValue = srcProvider->sourceNoDataValue( bandNo );
245 destHasNoDataValue = true;
246 }
247 else if ( nuller && !nuller->noData( bandNo ).isEmpty() )
248 {
249 // Use one user defined no data value
250 destNoDataValue = nuller->noData( bandNo ).value( 0 ).min();
251 destHasNoDataValue = true;
252 }
253 // GeoPackage does not support nodata for Byte output, and does not
254 // support non-Byte multiband output, so do not take the risk of an accidental
255 // data type promotion.
256 else if ( !( isGpkgOutput && destDataType == Qgis::DataType::Byte ) )
257 {
258 // Verify if we really need no data value, i.e.
259 QgsRectangle outputExtentInSrcCrs = outputExtent;
260 QgsRasterProjector *projector = pipe->projector();
261 if ( projector && projector->destinationCrs() != projector->sourceCrs() )
262 {
263 QgsCoordinateTransform ct( projector->destinationCrs(), projector->sourceCrs(), transformContext );
264 ct.setBallparkTransformsAreAppropriate( true );
265 outputExtentInSrcCrs = ct.transformBoundingBox( outputExtent );
266 }
267 if ( !srcProviderExtent.contains( outputExtentInSrcCrs ) &&
268 ( std::fabs( srcProviderExtent.xMinimum() - outputExtentInSrcCrs.xMinimum() ) > geoTransform[1] / 2 ||
269 std::fabs( srcProviderExtent.xMaximum() - outputExtentInSrcCrs.xMaximum() ) > geoTransform[1] / 2 ||
270 std::fabs( srcProviderExtent.yMinimum() - outputExtentInSrcCrs.yMinimum() ) > std::fabs( geoTransform[5] ) / 2 ||
271 std::fabs( srcProviderExtent.yMaximum() - outputExtentInSrcCrs.yMaximum() ) > std::fabs( geoTransform[5] ) / 2 ) )
272 {
273 // Destination extent is (at least partially) outside of source extent, we need destination no data values
274 // Get src sample statistics (estimation from sample)
275 const QgsRasterBandStats stats = srcProvider->bandStatistics( bandNo, Qgis::RasterBandStatistic::Min | Qgis::RasterBandStatistic::Max, outputExtentInSrcCrs, 250000 );
276
277 // Test if we have free (not used) values
278 const double typeMinValue = QgsContrastEnhancement::minimumValuePossible( srcDataType );
279 const double typeMaxValue = QgsContrastEnhancement::maximumValuePossible( srcDataType );
280 if ( stats.minimumValue > typeMinValue )
281 {
282 destNoDataValue = typeMinValue;
283 }
284 else if ( stats.maximumValue < typeMaxValue )
285 {
286 destNoDataValue = typeMaxValue;
287 }
288 else
289 {
290 // We have to use wider type
291 destDataType = QgsRasterBlock::typeWithNoDataValue( destDataType, &destNoDataValue );
292 }
293 destHasNoDataValue = true;
294 }
295 }
296
297 if ( nuller && destHasNoDataValue )
298 {
299 nuller->setOutputNoDataValue( bandNo, destNoDataValue );
300 }
301
302 QgsDebugMsgLevel( u"bandNo = %1 destDataType = %2 destHasNoDataValue = %3 destNoDataValue = %4"_s.arg( bandNo ).arg( qgsEnumValueToKey( destDataType ) ).arg( destHasNoDataValue ).arg( destNoDataValue ), 4 );
303 destDataTypeList.append( destDataType );
304 destHasNoDataValueList.append( destHasNoDataValue );
305 destNoDataValueList.append( destNoDataValue );
306 }
307
308 Qgis::DataType destDataType = destDataTypeList.value( 0 );
309 // Currently write API supports one output type for dataset only -> find the widest
310 for ( int i = 1; i < nBands; i++ )
311 {
312 if ( destDataTypeList.value( i ) > destDataType )
313 {
314 destDataType = destDataTypeList.value( i );
315 // no data value may be left per band (for future)
316 }
317 }
318
320 for ( int attempt = 0; attempt < 2; attempt++ )
321 {
322 //create destProvider for whole dataset here
323 // initOutput() returns 0 in tile mode!
324 std::unique_ptr<QgsRasterDataProvider> destProvider( initOutput( nCols, nRows, crs, geoTransform, nBands, destDataType, destHasNoDataValueList, destNoDataValueList ) );
325 if ( !mTiledMode )
326 {
327 if ( !destProvider )
328 {
330 }
331 if ( !destProvider->isValid() )
332 {
333 if ( feedback && !destProvider->error().isEmpty() )
334 {
335 feedback->appendError( destProvider->error().summary() );
336 }
338 }
339 if ( nCols != destProvider->xSize() || nRows != destProvider->ySize() )
340 {
341 QgsDebugError( u"Created raster does not have requested dimensions"_s );
342 if ( feedback )
343 {
344 feedback->appendError( QObject::tr( "Created raster does not have requested dimensions" ) );
345 }
347 }
348 if ( nBands != destProvider->bandCount() )
349 {
350 QgsDebugError( u"Created raster does not have requested band count"_s );
351 if ( feedback )
352 {
353 feedback->appendError( QObject::tr( "Created raster does not have requested band count" ) );
354 }
356 }
357 if ( nBands )
358 {
359 // Some driver like GS7BG may accept Byte as requested data type,
360 // but actually return a driver with Float64...
361 destDataType = destProvider->dataType( 1 );
362 }
363 }
364
365 error = writeDataRaster( pipe, iter, nCols, nRows, outputExtent, crs, destDataType, destHasNoDataValueList, destNoDataValueList, destProvider, feedback );
366
367 if ( attempt == 0 && error == Qgis::RasterFileWriterResult::NoDataConflict )
368 {
369 // The value used for no data was found in source data, we must use wider data type
370 if ( destProvider ) // no tiles
371 {
372 destProvider->remove();
373 destProvider.reset();
374 }
375 else // VRT
376 {
377 // TODO: remove created VRT
378 }
379
380 // But we don't know which band -> wider all
381 for ( int i = 0; i < nBands; i++ )
382 {
383 double destNoDataValue;
384 const Qgis::DataType destDataType = QgsRasterBlock::typeWithNoDataValue( destDataTypeList.value( i ), &destNoDataValue );
385 destDataTypeList.replace( i, destDataType );
386 destNoDataValueList.replace( i, destNoDataValue );
387 }
388 destDataType = destDataTypeList.value( 0 );
389
390 // Try again
391 }
392 else
393 {
394 break;
395 }
396 }
397
398 return error;
399}
400
401static int qgsDivRoundUp( int a, int b )
402{
403 return a / b + ( ( ( a % b ) != 0 ) ? 1 : 0 );
404}
405
406Qgis::RasterFileWriterResult QgsRasterFileWriter::writeDataRaster(
407 const QgsRasterPipe *pipe,
408 QgsRasterIterator *iter,
409 int nCols,
410 int nRows,
411 const QgsRectangle &outputExtent,
413 Qgis::DataType destDataType,
414 const QList<bool> &destHasNoDataValueList,
415 const QList<double> &destNoDataValueList,
416 std::unique_ptr<QgsRasterDataProvider> &destProvider,
417 QgsRasterBlockFeedback *feedback
418)
419{
420 Q_UNUSED( pipe )
421 Q_UNUSED( destHasNoDataValueList )
422 QgsDebugMsgLevel( u"Entered"_s, 4 );
423
424 const QgsRasterInterface *iface = iter->input();
425 const QgsRasterDataProvider *srcProvider = dynamic_cast<const QgsRasterDataProvider *>( iface->sourceInput() );
426 const int nBands = iface->bandCount();
427 QgsDebugMsgLevel( u"nBands = %1"_s.arg( nBands ), 4 );
428
429 //Get output map units per pixel
430 int iterLeft = 0;
431 int iterTop = 0;
432 int iterCols = 0;
433 int iterRows = 0;
434
435 std::vector< std::unique_ptr<QgsRasterBlock> > blockList;
436 std::vector< std::unique_ptr<QgsRasterBlock> > destBlockList;
437
438 blockList.resize( nBands );
439 destBlockList.resize( nBands );
440
441 for ( int i = 1; i <= nBands; ++i )
442 {
443 iter->startRasterRead( i, nCols, nRows, outputExtent, feedback );
444 if ( destProvider && destHasNoDataValueList.value( i - 1 ) ) // no tiles
445 {
446 destProvider->setNoDataValue( i, destNoDataValueList.value( i - 1 ) );
447 }
448 }
449
450 int nParts = 0;
451 int fileIndex = 0;
452 if ( feedback )
453 {
454 const int nPartsX = qgsDivRoundUp( nCols, iter->maximumTileWidth() );
455 const int nPartsY = qgsDivRoundUp( nRows, iter->maximumTileHeight() );
456 nParts = nPartsX * nPartsY;
457 }
458
459 const bool hasReportsDuringClose = destProvider && destProvider->hasReportsDuringClose();
460
461 // hmm why is there a for(;;) here ..
462 // not good coding practice IMHO, it might be better to use [ for() and break ] or [ while (test) ]
463 Q_FOREVER
464 {
465 bool done = false;
466 for ( int i = 1; i <= nBands && !done; ++i )
467 {
468 QgsRasterBlock *block = nullptr;
469 if ( !iter->readNextRasterPart( i, iterCols, iterRows, &block, iterLeft, iterTop ) )
470 {
471 // No more parts, create VRT and return
472 if ( mTiledMode )
473 {
474 const QString vrtFilePath( mOutputUrl + '/' + vrtFileName() );
475 writeVRT( vrtFilePath );
476 if ( mBuildPyramidsFlag == Qgis::RasterBuildPyramidOption::Yes )
477 {
478 if ( !buildPyramids( vrtFilePath ) )
479 {
481 }
482 }
483 }
484 else
485 {
486 if ( mBuildPyramidsFlag == Qgis::RasterBuildPyramidOption::Yes &&
487 // Pyramid creation is done by the driver itself
488 mOutputFormat != "COG"_L1 )
489 {
490 if ( !buildPyramids( mOutputUrl, destProvider.get() ) )
491 {
493 }
494 }
495 }
496
497 QgsDebugMsgLevel( u"Done"_s, 4 );
498 done = true;
499 }
500 blockList[i - 1].reset( block );
501 // TODO: verify if NoDataConflict happened, to do that we need the whole pipe or nuller interface
502 }
503 if ( done )
504 {
505 break;
506 }
507
508 if ( feedback && fileIndex < ( nParts - 1 ) )
509 {
510 const double maxProgress = hasReportsDuringClose ? 50.0 : 100.0;
511 feedback->setProgress( maxProgress * fileIndex / static_cast< double >( nParts ) );
512 if ( feedback->isCanceled() )
513 {
514 break;
515 }
516 }
517
518 // It may happen that internal data type (dataType) is wider than destDataType
519 for ( int i = 1; i <= nBands; ++i )
520 {
521 if ( srcProvider && srcProvider->dataType( i ) == destDataType )
522 {
523 // nothing
524 }
525 else
526 {
527 // TODO: this conversion should go to QgsRasterDataProvider::write with additional input data type param
528 blockList[i - 1]->convert( destDataType );
529 }
530 destBlockList[i - 1] = std::move( blockList[i - 1] );
531 }
532
533 if ( mTiledMode ) //write to file
534 {
535 std::unique_ptr< QgsRasterDataProvider > partDestProvider( createPartProvider( outputExtent, nCols, iterCols, iterRows, iterLeft, iterTop, mOutputUrl, fileIndex, nBands, destDataType, crs ) );
536
537 if ( !partDestProvider || !partDestProvider->isValid() )
538 {
540 }
541
542 //write data to output file. todo: loop over the data list
543 for ( int i = 1; i <= nBands; ++i )
544 {
545 if ( destHasNoDataValueList.value( i - 1 ) )
546 {
547 partDestProvider->setNoDataValue( i, destNoDataValueList.value( i - 1 ) );
548 }
549 if ( destBlockList[i - 1]->isEmpty() )
550 continue;
551
552 if ( !partDestProvider->write( destBlockList[i - 1]->constBits( 0 ), i, iterCols, iterRows, 0, 0 ) )
553 {
555 }
556 addToVRT( partFileName( fileIndex ), i, iterCols, iterRows, iterLeft, iterTop );
557 }
558 }
559 else if ( destProvider )
560 {
561 //loop over data
562 for ( int i = 1; i <= nBands; ++i )
563 {
564 if ( destBlockList[i - 1]->isEmpty() )
565 continue;
566
567 if ( !destProvider->write( destBlockList[i - 1]->constBits( 0 ), i, iterCols, iterRows, iterLeft, iterTop ) )
568 {
570 }
571 }
572 }
573 ++fileIndex;
574 }
575
576 // If the provider can report progress during closing (typically when generating COG files),
577 // report it, making feedback report percentage in the [50, 100] range.
578 if ( feedback && destProvider && hasReportsDuringClose )
579 {
580 std::unique_ptr<QgsFeedback> scaledFeedback( QgsFeedback::createScaledFeedback( feedback, 50.0, 100.0 ) );
581 if ( !destProvider->closeWithProgress( scaledFeedback.get() ) )
582 {
583 destProvider->remove();
584 destProvider.reset();
586 }
587 }
588
589 QgsDebugMsgLevel( u"Done"_s, 4 );
591}
592
593Qgis::RasterFileWriterResult QgsRasterFileWriter::writeImageRaster(
594 QgsRasterIterator *iter, int nCols, int nRows, const QgsRectangle &outputExtent, const QgsCoordinateReferenceSystem &crs, QgsRasterBlockFeedback *feedback
595)
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, nCols, iterCols, iterRows, iterLeft, iterTop, mOutputUrl, fileIndex, 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, feedback, [feedback]( double progress ) { feedback->setProgress( 50.0 + progress * 0.5 ); } );
785 QObject::connect( &closingProgress, &QgsFeedback::canceled, feedback, &QgsFeedback::cancel );
786 }
787
788 if ( !destProvider->closeWithProgress( feedback ? &closingProgress : nullptr ) )
789 {
790 destProvider->remove();
791 destProvider.reset();
793 }
794 }
795
796 destProvider.reset();
797
798 if ( mBuildPyramidsFlag == Qgis::RasterBuildPyramidOption::Yes &&
799 // Pyramid creation is done by the driver itself
800 mOutputFormat != "COG"_L1 )
801 {
802 if ( !buildPyramids( mOutputUrl ) )
804 }
805 }
807}
808
809void QgsRasterFileWriter::addToVRT( const QString &filename, int band, int xSize, int ySize, int xOffset, int yOffset )
810{
811 QDomElement bandElem = mVRTBands.value( band - 1 );
812
813 QDomElement simpleSourceElem = mVRTDocument.createElement( u"SimpleSource"_s );
814
815 //SourceFilename
816 QDomElement sourceFilenameElem = mVRTDocument.createElement( u"SourceFilename"_s );
817 sourceFilenameElem.setAttribute( u"relativeToVRT"_s, u"1"_s );
818 const QDomText sourceFilenameText = mVRTDocument.createTextNode( filename );
819 sourceFilenameElem.appendChild( sourceFilenameText );
820 simpleSourceElem.appendChild( sourceFilenameElem );
821
822 //SourceBand
823 QDomElement sourceBandElem = mVRTDocument.createElement( u"SourceBand"_s );
824 const QDomText sourceBandText = mVRTDocument.createTextNode( QString::number( band ) );
825 sourceBandElem.appendChild( sourceBandText );
826 simpleSourceElem.appendChild( sourceBandElem );
827
828 //SourceProperties
829 QDomElement sourcePropertiesElem = mVRTDocument.createElement( u"SourceProperties"_s );
830 sourcePropertiesElem.setAttribute( u"RasterXSize"_s, xSize );
831 sourcePropertiesElem.setAttribute( u"RasterYSize"_s, ySize );
832 sourcePropertiesElem.setAttribute( u"BlockXSize"_s, xSize );
833 sourcePropertiesElem.setAttribute( u"BlockYSize"_s, ySize );
834 sourcePropertiesElem.setAttribute( u"DataType"_s, u"Byte"_s );
835 simpleSourceElem.appendChild( sourcePropertiesElem );
836
837 //SrcRect
838 QDomElement srcRectElem = mVRTDocument.createElement( u"SrcRect"_s );
839 srcRectElem.setAttribute( u"xOff"_s, u"0"_s );
840 srcRectElem.setAttribute( u"yOff"_s, u"0"_s );
841 srcRectElem.setAttribute( u"xSize"_s, xSize );
842 srcRectElem.setAttribute( u"ySize"_s, ySize );
843 simpleSourceElem.appendChild( srcRectElem );
844
845 //DstRect
846 QDomElement dstRectElem = mVRTDocument.createElement( u"DstRect"_s );
847 dstRectElem.setAttribute( u"xOff"_s, xOffset );
848 dstRectElem.setAttribute( u"yOff"_s, yOffset );
849 dstRectElem.setAttribute( u"xSize"_s, xSize );
850 dstRectElem.setAttribute( u"ySize"_s, ySize );
851 simpleSourceElem.appendChild( dstRectElem );
852
853 bandElem.appendChild( simpleSourceElem );
854}
855
856bool QgsRasterFileWriter::buildPyramids( const QString &filename, QgsRasterDataProvider *destProviderIn )
857{
858 QgsDebugMsgLevel( "filename = " + filename, 4 );
859 // open new dataProvider so we can build pyramids with it
860 const QgsDataProvider::ProviderOptions providerOptions;
861 QgsRasterDataProvider *destProvider = destProviderIn;
862 if ( !destProvider )
863 {
864 destProvider = qobject_cast< QgsRasterDataProvider * >( QgsProviderRegistry::instance()->createProvider( mOutputProviderKey, filename, providerOptions ) );
865 if ( !destProvider || !destProvider->isValid() )
866 {
867 delete destProvider;
868 return false;
869 }
870 }
871
872 // TODO progress report
873 // TODO test mTiledMode - not tested b/c segfault at line # 289
874 // connect( provider, &FIXME::progressUpdate, mPyramidProgress, &FIXME::setValue );
875 QList< QgsRasterPyramid> myPyramidList;
876 if ( !mPyramidsList.isEmpty() )
877 myPyramidList = destProvider->buildPyramidList( mPyramidsList );
878 for ( int myCounterInt = 0; myCounterInt < myPyramidList.count(); myCounterInt++ )
879 {
880 myPyramidList[myCounterInt].setBuild( true );
881 }
882
884 u"building pyramids : %1 pyramids, %2 resampling, %3 format, %4 options"_s.arg( myPyramidList.count() )
885 .arg( mPyramidsResampling )
886 .arg( qgsEnumValueToKey( mPyramidsFormat ) )
887 .arg( mPyramidsConfigOptions.count() ),
888 4
889 );
890 // QApplication::setOverrideCursor( Qt::WaitCursor );
891 const QString res = destProvider->buildPyramids( myPyramidList, mPyramidsResampling, mPyramidsFormat, mPyramidsConfigOptions );
892 // QApplication::restoreOverrideCursor();
893
894 // TODO put this in provider or elsewhere
895 if ( !res.isNull() )
896 {
897 QString message;
898 if ( res == "ERROR_WRITE_ACCESS"_L1 )
899 {
900 message = QObject::tr( "Write access denied. Adjust the file permissions and try again." );
901 }
902 else if ( res == "ERROR_WRITE_FORMAT"_L1 )
903 {
904 message = QObject::tr(
905 "The file was not writable. Some formats do not "
906 "support pyramid overviews. Consult the GDAL documentation if in doubt."
907 );
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(
949 int xSize, int ySize, const QgsCoordinateReferenceSystem &crs, double *geoTransform, Qgis::DataType type, const QList<bool> &destHasNoDataValueList, const QList<double> &destNoDataValueList
950)
951{
952 mVRTDocument.clear();
953 QDomElement VRTDatasetElem = mVRTDocument.createElement( u"VRTDataset"_s );
954
955 //xsize / ysize
956 VRTDatasetElem.setAttribute( u"rasterXSize"_s, xSize );
957 VRTDatasetElem.setAttribute( u"rasterYSize"_s, ySize );
958 mVRTDocument.appendChild( VRTDatasetElem );
959
960 //CRS
961 QDomElement SRSElem = mVRTDocument.createElement( u"SRS"_s );
962 const QDomText crsText = mVRTDocument.createTextNode( crs.toWkt() );
963 SRSElem.appendChild( crsText );
964 VRTDatasetElem.appendChild( SRSElem );
965
966 //geotransform
967 if ( geoTransform )
968 {
969 QDomElement geoTransformElem = mVRTDocument.createElement( u"GeoTransform"_s );
970 const QString geoTransformString = QString::number( geoTransform[0], 'f', 6 )
971 + ", "
972 + QString::number( geoTransform[1] )
973 + ", "
974 + QString::number( geoTransform[2] )
975 + ", "
976 + QString::number( geoTransform[3], 'f', 6 )
977 + ", "
978 + QString::number( geoTransform[4] )
979 + ", "
980 + QString::number( geoTransform[5] );
981 const QDomText geoTransformText = mVRTDocument.createTextNode( geoTransformString );
982 geoTransformElem.appendChild( geoTransformText );
983 VRTDatasetElem.appendChild( geoTransformElem );
984 }
985
986 int nBands;
987 if ( mMode == Qgis::RasterExportType::Raw )
988 {
989 nBands = mInput->bandCount();
990 }
991 else
992 {
993 nBands = 4;
994 }
995
996 QStringList colorInterp;
997 colorInterp << u"Red"_s << u"Green"_s << u"Blue"_s << u"Alpha"_s;
998
999 QMap<Qgis::DataType, QString> dataTypes;
1000 dataTypes.insert( Qgis::DataType::Byte, u"Byte"_s );
1001 dataTypes.insert( Qgis::DataType::Int8, u"Int8"_s );
1002 dataTypes.insert( Qgis::DataType::UInt16, u"UInt16"_s );
1003 dataTypes.insert( Qgis::DataType::Int16, u"Int16"_s );
1004 dataTypes.insert( Qgis::DataType::UInt32, u"Int32"_s );
1005 dataTypes.insert( Qgis::DataType::Float32, u"Float32"_s );
1006 dataTypes.insert( Qgis::DataType::Float64, u"Float64"_s );
1007 dataTypes.insert( Qgis::DataType::CInt16, u"CInt16"_s );
1008 dataTypes.insert( Qgis::DataType::CInt32, u"CInt32"_s );
1009 dataTypes.insert( Qgis::DataType::CFloat32, u"CFloat32"_s );
1010 dataTypes.insert( Qgis::DataType::CFloat64, u"CFloat64"_s );
1011
1012 for ( int i = 1; i <= nBands; i++ )
1013 {
1014 QDomElement VRTBand = mVRTDocument.createElement( u"VRTRasterBand"_s );
1015
1016 VRTBand.setAttribute( u"band"_s, QString::number( i ) );
1017 const QString dataType = dataTypes.value( type );
1018 VRTBand.setAttribute( u"dataType"_s, dataType );
1019
1021 {
1022 VRTBand.setAttribute( u"dataType"_s, u"Byte"_s );
1023 QDomElement colorInterpElement = mVRTDocument.createElement( u"ColorInterp"_s );
1024 const QDomText interpText = mVRTDocument.createTextNode( colorInterp.value( i - 1 ) );
1025 colorInterpElement.appendChild( interpText );
1026 VRTBand.appendChild( colorInterpElement );
1027 }
1028
1029 if ( !destHasNoDataValueList.isEmpty() && destHasNoDataValueList.value( i - 1 ) )
1030 {
1031 VRTBand.setAttribute( u"NoDataValue"_s, QString::number( destNoDataValueList.value( i - 1 ) ) );
1032 }
1033
1034 mVRTBands.append( VRTBand );
1035 VRTDatasetElem.appendChild( VRTBand );
1036 }
1037}
1038
1039bool QgsRasterFileWriter::writeVRT( const QString &file )
1040{
1041 QFile outputFile( file );
1042 if ( !outputFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
1043 {
1044 return false;
1045 }
1046
1047 QTextStream outStream( &outputFile );
1048 mVRTDocument.save( outStream, 2 );
1049 return true;
1050}
1051
1052QgsRasterDataProvider *QgsRasterFileWriter::createPartProvider(
1053 const QgsRectangle &extent, int nCols, int iterCols, int iterRows, int iterLeft, int iterTop, const QString &outputUrl, int fileIndex, int nBands, Qgis::DataType type, const QgsCoordinateReferenceSystem &crs
1054)
1055{
1056 const double mup = extent.width() / nCols;
1057 const double mapLeft = extent.xMinimum() + iterLeft * mup;
1058 const double mapRight = mapLeft + mup * iterCols;
1059 const double mapTop = extent.yMaximum() - iterTop * mup;
1060 const double mapBottom = mapTop - iterRows * mup;
1061 const QgsRectangle mapRect( mapLeft, mapBottom, mapRight, mapTop );
1062
1063 const QString outputFile = outputUrl + '/' + partFileName( fileIndex );
1064
1065 //geotransform
1066 double geoTransform[6];
1067 geoTransform[0] = mapRect.xMinimum();
1068 geoTransform[1] = mup;
1069 geoTransform[2] = 0.0;
1070 geoTransform[3] = mapRect.yMaximum();
1071 geoTransform[4] = 0.0;
1072 geoTransform[5] = -mup;
1073
1074 // perhaps we need a separate creationOptions for tiles ?
1075
1076 QgsRasterDataProvider *destProvider = QgsRasterDataProvider::create( mOutputProviderKey, outputFile, mOutputFormat, nBands, type, iterCols, iterRows, geoTransform, crs, mCreationOptions );
1077
1078 // TODO: return provider and report error
1079 return destProvider;
1080}
1081
1082void QgsRasterFileWriter::setOutputFormat( const QString &format )
1083{
1084 mOutputFormat = format;
1085 if ( !mBuildPyramidsFlagSet && format == "COG"_L1 )
1086 {
1088 }
1089}
1090
1092{
1093 mBuildPyramidsFlag = flag;
1094 mBuildPyramidsFlagSet = true;
1095}
1096
1097QgsRasterDataProvider *QgsRasterFileWriter::initOutput(
1098 int nCols, int nRows, const QgsCoordinateReferenceSystem &crs, double *geoTransform, int nBands, Qgis::DataType type, const QList<bool> &destHasNoDataValueList, const QList<double> &destNoDataValueList
1099)
1100{
1101 if ( mTiledMode )
1102 {
1103 createVRT( nCols, nRows, crs, geoTransform, type, destHasNoDataValueList, destNoDataValueList );
1104 return nullptr;
1105 }
1106 else
1107 {
1108#if 0
1109 // TODO enable "use existing", has no effect for now, because using Create() in gdal provider
1110 // should this belong in provider? should also test that source provider is gdal
1111 if ( mBuildPyramidsFlag == -4 && mOutputProviderKey == "gdal" && mOutputFormat.compare( "gtiff"_L1, Qt::CaseInsensitive ) == 0 )
1112 mCreationOptions << "COPY_SRC_OVERVIEWS=YES";
1113#endif
1114 QStringList creationOptions( mCreationOptions );
1115 if ( mOutputFormat == "COG"_L1 )
1116 {
1117 if ( mBuildPyramidsFlag == Qgis::RasterBuildPyramidOption::No )
1118 creationOptions << u"OVERVIEWS=NO"_s;
1119 else
1120 {
1121 creationOptions << u"OVERVIEW_RESAMPLING="_s + mPyramidsResampling;
1122 for ( const QString &opt : std::as_const( mPyramidsConfigOptions ) )
1123 {
1124 const std::string optStr( opt.toStdString() );
1125 char *key = nullptr;
1126 const char *value = CPLParseNameValue( optStr.c_str(), &key );
1127 if ( key && value )
1128 {
1129 if ( EQUAL( key, "JPEG_QUALITY_OVERVIEW" ) )
1130 {
1131 creationOptions << u"OVERVIEW_QUALITY="_s + value;
1132 }
1133 else if ( EQUAL( key, "COMPRESS_OVERVIEW" ) )
1134 {
1135 creationOptions << u"OVERVIEW_COMPRESS="_s + value;
1136 }
1137 else if ( EQUAL( key, "PREDICTOR_OVERVIEW" ) )
1138 {
1139 creationOptions << u"OVERVIEW_PREDICTOR="_s + value;
1140 }
1141 }
1142 CPLFree( key );
1143 }
1144 }
1145 }
1146
1147 QgsRasterDataProvider *destProvider = QgsRasterDataProvider::create( mOutputProviderKey, mOutputUrl, mOutputFormat, nBands, type, nCols, nRows, geoTransform, crs, creationOptions );
1148
1149 if ( !destProvider )
1150 {
1151 QgsDebugError( u"No provider created"_s );
1152 }
1153
1154 return destProvider;
1155 }
1156}
1157
1158void QgsRasterFileWriter::globalOutputParameters( const QgsRectangle &extent, int nCols, int &nRows, double *geoTransform, double &pixelSize )
1159{
1160 pixelSize = extent.width() / nCols;
1161
1162 //calculate nRows automatically for providers without exact resolution
1163 if ( nRows < 0 )
1164 {
1165 nRows = static_cast< double >( nCols ) / extent.width() * extent.height() + 0.5; //NOLINT
1166 }
1167 geoTransform[0] = extent.xMinimum();
1168 geoTransform[1] = pixelSize;
1169 geoTransform[2] = 0.0;
1170 geoTransform[3] = extent.yMaximum();
1171 geoTransform[4] = 0.0;
1172 geoTransform[5] = -( extent.height() / nRows );
1173}
1174
1175QString QgsRasterFileWriter::partFileName( int fileIndex )
1176{
1177 // .tif for now
1178 const QFileInfo outputInfo( mOutputUrl );
1179 return u"%1.%2.tif"_s.arg( outputInfo.fileName() ).arg( fileIndex );
1180}
1181
1182QString QgsRasterFileWriter::vrtFileName()
1183{
1184 const QFileInfo outputInfo( mOutputUrl );
1185 return u"%1.vrt"_s.arg( outputInfo.fileName() );
1186}
1187
1188QString QgsRasterFileWriter::driverForExtension( const QString &extension )
1189{
1190 QString ext = extension.trimmed();
1191 if ( ext.isEmpty() )
1192 return QString();
1193
1194 if ( ext.startsWith( '.' ) )
1195 ext.remove( 0, 1 );
1196
1197 if ( ext.compare( "tif"_L1, Qt::CaseInsensitive ) == 0 || ext.compare( "tiff"_L1, Qt::CaseInsensitive ) == 0 )
1198 {
1199 // Be robust to GDAL drivers potentially recognizing the tif/tiff extensions
1200 // but being registered before the GTiff one.
1201 // Cf https://github.com/qgis/QGIS/issues/59112
1202 if ( GDALGetDriverByName( "GTiff" ) )
1203 return "GTiff";
1204 }
1205
1206 GDALAllRegister();
1207 int const drvCount = GDALGetDriverCount();
1208
1209 for ( int i = 0; i < drvCount; ++i )
1210 {
1211 GDALDriverH drv = GDALGetDriver( i );
1212 if ( drv )
1213 {
1214 CSLConstList driverMetadata = GDALGetMetadata( drv, nullptr );
1215 if ( CSLFetchBoolean( driverMetadata, GDAL_DCAP_RASTER, false ) )
1216 {
1217 QString drvName = GDALGetDriverShortName( drv );
1218 const QStringList driverExtensions = QString( GDALGetMetadataItem( drv, GDAL_DMD_EXTENSIONS, nullptr ) ).split( ' ' );
1219
1220 const auto constDriverExtensions = driverExtensions;
1221 for ( const QString &driver : constDriverExtensions )
1222 {
1223 if ( driver.compare( ext, Qt::CaseInsensitive ) == 0 )
1224 return drvName;
1225 }
1226 }
1227 }
1228 }
1229 return QString();
1230}
1231
1232QStringList QgsRasterFileWriter::extensionsForFormat( const QString &format )
1233{
1234 GDALDriverH drv = GDALGetDriverByName( format.toLocal8Bit().data() );
1235 if ( drv )
1236 {
1237 CSLConstList driverMetadata = GDALGetMetadata( drv, nullptr );
1238 if ( CSLFetchBoolean( driverMetadata, GDAL_DCAP_RASTER, false ) )
1239 {
1240 return QString( GDALGetMetadataItem( drv, GDAL_DMD_EXTENSIONS, nullptr ) ).split( ' ' );
1241 }
1242 }
1243 return QStringList();
1244}
1245
1246QString QgsRasterFileWriter::filterForDriver( const QString &driverName )
1247{
1248 GDALDriverH drv = GDALGetDriverByName( driverName.toLocal8Bit().data() );
1249 if ( drv )
1250 {
1251 const QString drvName = GDALGetDriverLongName( drv );
1252 const QString extensionsString = QString( GDALGetMetadataItem( drv, GDAL_DMD_EXTENSIONS, nullptr ) );
1253 if ( extensionsString.isEmpty() )
1254 {
1255 return QString();
1256 }
1257 const QStringList extensions = extensionsString.split( ' ' );
1258 QString filter = drvName + " (";
1259 for ( const QString &ext : extensions )
1260 {
1261 filter.append( u"*.%1 *.%2 "_s.arg( ext.toLower(), ext.toUpper() ) );
1262 }
1263 filter = filter.trimmed().append( u")"_s );
1264 return filter;
1265 }
1266
1267 return QString();
1268}
1269
1270QList< QgsRasterFileWriter::FilterFormatDetails > QgsRasterFileWriter::supportedFiltersAndFormats( RasterFormatOptions options )
1271{
1272 static QReadWriteLock sFilterLock;
1273 static QMap< RasterFormatOptions, QList< QgsRasterFileWriter::FilterFormatDetails > > sFilters;
1274
1275 QgsReadWriteLocker locker( sFilterLock, QgsReadWriteLocker::Read );
1276
1277 const auto it = sFilters.constFind( options );
1278 if ( it != sFilters.constEnd() )
1279 return it.value();
1280
1281 GDALAllRegister();
1282 int const drvCount = GDALGetDriverCount();
1283
1285 QList< QgsRasterFileWriter::FilterFormatDetails > results;
1286
1287 FilterFormatDetails tifFormat;
1288
1289 for ( int i = 0; i < drvCount; ++i )
1290 {
1291 GDALDriverH drv = GDALGetDriver( i );
1292 if ( drv )
1293 {
1295 {
1296 const QString drvName = GDALGetDriverShortName( drv );
1297 const QString filterString = filterForDriver( drvName );
1298 if ( filterString.isEmpty() )
1299 continue;
1300
1301 FilterFormatDetails details;
1302 details.driverName = drvName;
1303 details.filterString = filterString;
1304
1305 if ( options & SortRecommended )
1306 {
1307 if ( drvName == "GTiff"_L1 )
1308 {
1309 tifFormat = details;
1310 continue;
1311 }
1312 }
1313
1314 results << details;
1315 }
1316 }
1317 }
1318
1319 std::sort( results.begin(), results.end(), []( const FilterFormatDetails &a, const FilterFormatDetails &b ) -> bool { return a.driverName < b.driverName; } );
1320
1321 if ( options & SortRecommended )
1322 {
1323 if ( !tifFormat.filterString.isEmpty() )
1324 {
1325 results.insert( 0, tifFormat );
1326 }
1327 }
1328
1329 sFilters.insert( options, results );
1330
1331 return results;
1332}
1333
1335{
1336 const auto formats = supportedFiltersAndFormats( options );
1337 QSet< QString > extensions;
1338
1339 const thread_local QRegularExpression rx( u"\\*\\.([a-zA-Z0-9]*)"_s );
1340
1341 for ( const FilterFormatDetails &format : formats )
1342 {
1343 const QString ext = format.filterString;
1344 const QRegularExpressionMatch match = rx.match( ext );
1345 if ( !match.hasMatch() )
1346 continue;
1347
1348 const QString matched = match.captured( 1 );
1349 extensions.insert( matched );
1350 }
1351
1352 QStringList extensionList( extensions.constBegin(), extensions.constEnd() );
1353
1354 std::sort( extensionList.begin(), extensionList.end(), [options]( const QString &a, const QString &b ) -> bool {
1355 if ( options & SortRecommended )
1356 {
1357 if ( a == "tif"_L1 )
1358 return true;
1359 else if ( b == "tif"_L1 )
1360 return false;
1361 if ( a == "tiff"_L1 )
1362 return true;
1363 else if ( b == "tiff"_L1 )
1364 return false;
1365 if ( a == "gpkg"_L1 )
1366 return true;
1367 else if ( b == "gpkg"_L1 )
1368 return false;
1369 }
1370
1371 return a.toLower().localeAwareCompare( b.toLower() ) < 0;
1372 } );
1373
1374 return extensionList;
1375}
RasterFileWriterResult
Raster file export results.
Definition qgis.h:1721
@ Canceled
Writing was manually canceled.
Definition qgis.h:1728
@ NoDataConflict
Internal error if a value used for 'no data' was found in input.
Definition qgis.h:1727
@ WriteError
Write error.
Definition qgis.h:1726
@ Success
Successful export.
Definition qgis.h:1722
@ CreateDatasourceError
Data source creation error.
Definition qgis.h:1725
@ DestinationProviderError
Destination data provider error.
Definition qgis.h:1724
@ SourceProviderError
Source data provider error.
Definition qgis.h:1723
DataType
Raster data types.
Definition qgis.h:393
@ CInt32
Complex Int32.
Definition qgis.h:404
@ Float32
Thirty two bit floating point (float).
Definition qgis.h:401
@ CFloat64
Complex Float64.
Definition qgis.h:406
@ Int16
Sixteen bit signed integer (qint16).
Definition qgis.h:398
@ ARGB32_Premultiplied
Color, alpha, red, green, blue, 4 bytes the same as QImage::Format_ARGB32_Premultiplied.
Definition qgis.h:408
@ Int8
Eight bit signed integer (qint8) (added in QGIS 3.30).
Definition qgis.h:396
@ UInt16
Sixteen bit unsigned integer (quint16).
Definition qgis.h:397
@ Byte
Eight bit unsigned integer (quint8).
Definition qgis.h:395
@ ARGB32
Color, alpha, red, green, blue, 4 bytes the same as QImage::Format_ARGB32.
Definition qgis.h:407
@ Float64
Sixty four bit floating point (double).
Definition qgis.h:402
@ CFloat32
Complex Float32.
Definition qgis.h:405
@ CInt16
Complex Int16.
Definition qgis.h:403
@ UInt32
Thirty two bit unsigned integer (quint32).
Definition qgis.h:399
RasterBuildPyramidOption
Raster pyramid building options.
Definition qgis.h:4974
@ RenderedImage
Rendered image.
Definition qgis.h:1710
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:56
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:65
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(), Qgis::StringFormat format=Qgis::StringFormat::PlainText)
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:7157
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:7485
#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.