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