QGIS API Documentation  3.12.1-BucureČ™ti (121cc00ff0)
qgsgdalutils.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsgdalutils.cpp
3  ----------------
4  begin : September 2018
5  copyright : (C) 2018 Even Rouault
6  email : even.rouault at spatialys.com
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 
16 #include "qgsgdalutils.h"
17 #include "qgslogger.h"
18 
19 #define CPL_SUPRESS_CPLUSPLUS //#spellok
20 #include "gdal.h"
21 #include "cpl_string.h"
22 
23 #include <QString>
24 #include <QImage>
25 
26 bool QgsGdalUtils::supportsRasterCreate( GDALDriverH driver )
27 {
28  QString driverShortName = GDALGetDriverShortName( driver );
29  if ( driverShortName == QLatin1String( "SQLite" ) )
30  {
31  // it supports Create() but only for vector side
32  return false;
33  }
34  char **driverMetadata = GDALGetMetadata( driver, nullptr );
35  return CSLFetchBoolean( driverMetadata, GDAL_DCAP_CREATE, false ) &&
36  CSLFetchBoolean( driverMetadata, GDAL_DCAP_RASTER, false );
37 }
38 
39 gdal::dataset_unique_ptr QgsGdalUtils::createSingleBandMemoryDataset( GDALDataType dataType, const QgsRectangle &extent, int width, int height, const QgsCoordinateReferenceSystem &crs )
40 {
41  return createMultiBandMemoryDataset( dataType, 1, extent, width, height, crs );
42 }
43 
44 gdal::dataset_unique_ptr QgsGdalUtils::createMultiBandMemoryDataset( GDALDataType dataType, int bands, const QgsRectangle &extent, int width, int height, const QgsCoordinateReferenceSystem &crs )
45 {
46  GDALDriverH hDriverMem = GDALGetDriverByName( "MEM" );
47  if ( !hDriverMem )
48  {
49  return gdal::dataset_unique_ptr();
50  }
51 
52  gdal::dataset_unique_ptr hSrcDS( GDALCreate( hDriverMem, "", width, height, bands, dataType, nullptr ) );
53 
54  double cellSizeX = extent.width() / width;
55  double cellSizeY = extent.height() / height;
56  double geoTransform[6];
57  geoTransform[0] = extent.xMinimum();
58  geoTransform[1] = cellSizeX;
59  geoTransform[2] = 0;
60  geoTransform[3] = extent.yMinimum() + ( cellSizeY * height );
61  geoTransform[4] = 0;
62  geoTransform[5] = -cellSizeY;
63 
64  GDALSetProjection( hSrcDS.get(), crs.toWkt( QgsCoordinateReferenceSystem::WKT2_2018 ).toLatin1().constData() );
65  GDALSetGeoTransform( hSrcDS.get(), geoTransform );
66  return hSrcDS;
67 }
68 
69 gdal::dataset_unique_ptr QgsGdalUtils::createSingleBandTiffDataset( const QString &filename, GDALDataType dataType, const QgsRectangle &extent, int width, int height, const QgsCoordinateReferenceSystem &crs )
70 {
71  double cellSizeX = extent.width() / width;
72  double cellSizeY = extent.height() / height;
73  double geoTransform[6];
74  geoTransform[0] = extent.xMinimum();
75  geoTransform[1] = cellSizeX;
76  geoTransform[2] = 0;
77  geoTransform[3] = extent.yMinimum() + ( cellSizeY * height );
78  geoTransform[4] = 0;
79  geoTransform[5] = -cellSizeY;
80 
81  GDALDriverH hDriver = GDALGetDriverByName( "GTiff" );
82  if ( !hDriver )
83  {
84  return gdal::dataset_unique_ptr();
85  }
86 
87  // Create the output file.
88  gdal::dataset_unique_ptr hDstDS( GDALCreate( hDriver, filename.toLocal8Bit().constData(), width, height, 1, dataType, nullptr ) );
89  if ( !hDstDS )
90  {
91  return gdal::dataset_unique_ptr();
92  }
93 
94  // Write out the projection definition.
95  GDALSetProjection( hDstDS.get(), crs.toWkt( QgsCoordinateReferenceSystem::WKT2_2018 ).toLatin1().constData() );
96  GDALSetGeoTransform( hDstDS.get(), geoTransform );
97  return hDstDS;
98 }
99 
101 {
102  if ( image.isNull() )
103  return nullptr;
104 
105  const QRgb *rgb = reinterpret_cast<const QRgb *>( image.constBits() );
106  GDALDriverH hDriverMem = GDALGetDriverByName( "MEM" );
107  if ( !hDriverMem )
108  {
109  return nullptr;
110  }
111  gdal::dataset_unique_ptr hSrcDS( GDALCreate( hDriverMem, "", image.width(), image.height(), 0, GDT_Byte, nullptr ) );
112 
113  char **papszOptions = QgsGdalUtils::papszFromStringList( QStringList()
114  << QStringLiteral( "PIXELOFFSET=%1" ).arg( sizeof( QRgb ) )
115  << QStringLiteral( "LINEOFFSET=%1" ).arg( image.bytesPerLine() )
116  << QStringLiteral( "DATAPOINTER=%1" ).arg( reinterpret_cast< qulonglong >( rgb ) + 2 ) );
117  GDALAddBand( hSrcDS.get(), GDT_Byte, papszOptions );
118  CSLDestroy( papszOptions );
119 
120  papszOptions = QgsGdalUtils::papszFromStringList( QStringList()
121  << QStringLiteral( "PIXELOFFSET=%1" ).arg( sizeof( QRgb ) )
122  << QStringLiteral( "LINEOFFSET=%1" ).arg( image.bytesPerLine() )
123  << QStringLiteral( "DATAPOINTER=%1" ).arg( reinterpret_cast< qulonglong >( rgb ) + 1 ) );
124  GDALAddBand( hSrcDS.get(), GDT_Byte, papszOptions );
125  CSLDestroy( papszOptions );
126 
127  papszOptions = QgsGdalUtils::papszFromStringList( QStringList()
128  << QStringLiteral( "PIXELOFFSET=%1" ).arg( sizeof( QRgb ) )
129  << QStringLiteral( "LINEOFFSET=%1" ).arg( image.bytesPerLine() )
130  << QStringLiteral( "DATAPOINTER=%1" ).arg( reinterpret_cast< qulonglong >( rgb ) ) );
131  GDALAddBand( hSrcDS.get(), GDT_Byte, papszOptions );
132  CSLDestroy( papszOptions );
133 
134  papszOptions = QgsGdalUtils::papszFromStringList( QStringList()
135  << QStringLiteral( "PIXELOFFSET=%1" ).arg( sizeof( QRgb ) )
136  << QStringLiteral( "LINEOFFSET=%1" ).arg( image.bytesPerLine() )
137  << QStringLiteral( "DATAPOINTER=%1" ).arg( reinterpret_cast< qulonglong >( rgb ) + 3 ) );
138  GDALAddBand( hSrcDS.get(), GDT_Byte, papszOptions );
139  CSLDestroy( papszOptions );
140 
141  return hSrcDS;
142 }
143 
144 void QgsGdalUtils::resampleSingleBandRaster( GDALDatasetH hSrcDS, GDALDatasetH hDstDS, GDALResampleAlg resampleAlg )
145 {
146  gdal::warp_options_unique_ptr psWarpOptions( GDALCreateWarpOptions() );
147  psWarpOptions->hSrcDS = hSrcDS;
148  psWarpOptions->hDstDS = hDstDS;
149 
150  psWarpOptions->nBandCount = 1;
151  psWarpOptions->panSrcBands = reinterpret_cast< int * >( CPLMalloc( sizeof( int ) * 1 ) );
152  psWarpOptions->panDstBands = reinterpret_cast< int * >( CPLMalloc( sizeof( int ) * 1 ) );
153  psWarpOptions->panSrcBands[0] = 1;
154  psWarpOptions->panDstBands[0] = 1;
155 
156  psWarpOptions->eResampleAlg = resampleAlg;
157 
158  // Establish reprojection transformer.
159  psWarpOptions->pTransformerArg =
160  GDALCreateGenImgProjTransformer( hSrcDS, GDALGetProjectionRef( hSrcDS ),
161  hDstDS, GDALGetProjectionRef( hDstDS ),
162  FALSE, 0.0, 1 );
163  psWarpOptions->pfnTransformer = GDALGenImgProjTransform;
164 
165  // Initialize and execute the warp operation.
166  GDALWarpOperation oOperation;
167  oOperation.Initialize( psWarpOptions.get() );
168 
169  oOperation.ChunkAndWarpImage( 0, 0, GDALGetRasterXSize( hDstDS ), GDALGetRasterYSize( hDstDS ) );
170 
171  GDALDestroyGenImgProjTransformer( psWarpOptions->pTransformerArg );
172 }
173 
174 QImage QgsGdalUtils::resampleImage( const QImage &image, QSize outputSize, GDALRIOResampleAlg resampleAlg )
175 {
177  if ( !srcDS )
178  return QImage();
179 
180  GDALRasterIOExtraArg extra;
181  INIT_RASTERIO_EXTRA_ARG( extra );
182  extra.eResampleAlg = resampleAlg;
183 
184  QImage res( outputSize, image.format() );
185  if ( res.isNull() )
186  return QImage();
187 
188  GByte *rgb = reinterpret_cast<GByte *>( res.bits() );
189 
190  CPLErr err = GDALRasterIOEx( GDALGetRasterBand( srcDS.get(), 1 ), GF_Read, 0, 0, image.width(), image.height(), rgb + 2, outputSize.width(),
191  outputSize.height(), GDT_Byte, sizeof( QRgb ), res.bytesPerLine(), &extra );
192  if ( err != CE_None )
193  {
194  QgsDebugMsg( QStringLiteral( "failed to read red band" ) );
195  return QImage();
196  }
197 
198  err = GDALRasterIOEx( GDALGetRasterBand( srcDS.get(), 2 ), GF_Read, 0, 0, image.width(), image.height(), rgb + 1, outputSize.width(),
199  outputSize.height(), GDT_Byte, sizeof( QRgb ), res.bytesPerLine(), &extra );
200  if ( err != CE_None )
201  {
202  QgsDebugMsg( QStringLiteral( "failed to read green band" ) );
203  return QImage();
204  }
205 
206  err = GDALRasterIOEx( GDALGetRasterBand( srcDS.get(), 3 ), GF_Read, 0, 0, image.width(), image.height(), rgb, outputSize.width(),
207  outputSize.height(), GDT_Byte, sizeof( QRgb ), res.bytesPerLine(), &extra );
208  if ( err != CE_None )
209  {
210  QgsDebugMsg( QStringLiteral( "failed to read blue band" ) );
211  return QImage();
212  }
213 
214  err = GDALRasterIOEx( GDALGetRasterBand( srcDS.get(), 4 ), GF_Read, 0, 0, image.width(), image.height(), rgb + 3, outputSize.width(),
215  outputSize.height(), GDT_Byte, sizeof( QRgb ), res.bytesPerLine(), &extra );
216  if ( err != CE_None )
217  {
218  QgsDebugMsg( QStringLiteral( "failed to read alpha band" ) );
219  return QImage();
220  }
221 
222  return res;
223 }
224 
225 QString QgsGdalUtils::helpCreationOptionsFormat( const QString &format )
226 {
227  QString message;
228  GDALDriverH myGdalDriver = GDALGetDriverByName( format.toLocal8Bit().constData() );
229  if ( myGdalDriver )
230  {
231  // first report details and help page
232  char **GDALmetadata = GDALGetMetadata( myGdalDriver, nullptr );
233  message += QLatin1String( "Format Details:\n" );
234  message += QStringLiteral( " Extension: %1\n" ).arg( CSLFetchNameValue( GDALmetadata, GDAL_DMD_EXTENSION ) );
235  message += QStringLiteral( " Short Name: %1" ).arg( GDALGetDriverShortName( myGdalDriver ) );
236  message += QStringLiteral( " / Long Name: %1\n" ).arg( GDALGetDriverLongName( myGdalDriver ) );
237  message += QStringLiteral( " Help page: http://www.gdal.org/%1\n\n" ).arg( CSLFetchNameValue( GDALmetadata, GDAL_DMD_HELPTOPIC ) );
238 
239  // next get creation options
240  // need to serialize xml to get newlines, should we make the basic xml prettier?
241  CPLXMLNode *psCOL = CPLParseXMLString( GDALGetMetadataItem( myGdalDriver,
242  GDAL_DMD_CREATIONOPTIONLIST, "" ) );
243  char *pszFormattedXML = CPLSerializeXMLTree( psCOL );
244  if ( pszFormattedXML )
245  message += QString( pszFormattedXML );
246  if ( psCOL )
247  CPLDestroyXMLNode( psCOL );
248  if ( pszFormattedXML )
249  CPLFree( pszFormattedXML );
250  }
251  return message;
252 }
253 
254 char **QgsGdalUtils::papszFromStringList( const QStringList &list )
255 {
256  char **papszRetList = nullptr;
257  const auto constList = list;
258  for ( const QString &elem : constList )
259  {
260  papszRetList = CSLAddString( papszRetList, elem.toLocal8Bit().constData() );
261  }
262  return papszRetList;
263 }
264 
265 QString QgsGdalUtils::validateCreationOptionsFormat( const QStringList &createOptions, const QString &format )
266 {
267  GDALDriverH myGdalDriver = GDALGetDriverByName( format.toLocal8Bit().constData() );
268  if ( ! myGdalDriver )
269  return QStringLiteral( "invalid GDAL driver" );
270 
271  char **papszOptions = papszFromStringList( createOptions );
272  // get error string?
273  int ok = GDALValidateCreationOptions( myGdalDriver, papszOptions );
274  CSLDestroy( papszOptions );
275 
276  if ( !ok )
277  return QStringLiteral( "Failed GDALValidateCreationOptions() test" );
278  return QString();
279 }
A rectangle specified with double values.
Definition: qgsrectangle.h:41
static gdal::dataset_unique_ptr createMultiBandMemoryDataset(GDALDataType dataType, int bands, const QgsRectangle &extent, int width, int height, const QgsCoordinateReferenceSystem &crs)
Creates a new multi band memory dataset with given parameters.
static gdal::dataset_unique_ptr imageToMemoryDataset(const QImage &image)
Converts an image to a GDAL memory dataset by borrowing image data.
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
const QgsCoordinateReferenceSystem & crs
std::unique_ptr< GDALWarpOptions, GDALWarpOptionsDeleter > warp_options_unique_ptr
Scoped GDAL warp options.
Definition: qgsogrutils.h:149
static bool supportsRasterCreate(GDALDriverH driver)
Reads whether a driver supports GDALCreate() for raster purposes.
static gdal::dataset_unique_ptr createSingleBandTiffDataset(const QString &filename, GDALDataType dataType, const QgsRectangle &extent, int width, int height, const QgsCoordinateReferenceSystem &crs)
Creates a new single band TIFF dataset with given parameters.
double width() const
Returns the width of the rectangle.
Definition: qgsrectangle.h:202
static char ** papszFromStringList(const QStringList &list)
Helper function.
static gdal::dataset_unique_ptr createSingleBandMemoryDataset(GDALDataType dataType, const QgsRectangle &extent, int width, int height, const QgsCoordinateReferenceSystem &crs)
Creates a new single band memory dataset with given parameters.
void * GDALDatasetH
static QString helpCreationOptionsFormat(const QString &format)
Gets creation options metadata for a given format.
Full WKT2 string, conforming to ISO 19162:2018 / OGC 18-010, with all possible nodes and new keyword ...
double yMinimum() const
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:177
This class represents a coordinate reference system (CRS).
static QString validateCreationOptionsFormat(const QStringList &createOptions, const QString &format)
Validates creation options for a given format, regardless of layer.
double xMinimum() const
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:167
std::unique_ptr< std::remove_pointer< GDALDatasetH >::type, GDALDatasetCloser > dataset_unique_ptr
Scoped GDAL dataset.
Definition: qgsogrutils.h:134
QString toWkt(WktVariant variant=WKT1_GDAL, bool multiline=false, int indentationWidth=4) const
Returns a WKT representation of this CRS.
static void resampleSingleBandRaster(GDALDatasetH hSrcDS, GDALDatasetH hDstDS, GDALResampleAlg resampleAlg)
Resamples a single band raster to the destination dataset with different resolution (and possibly wit...
double height() const
Returns the height of the rectangle.
Definition: qgsrectangle.h:209
static QImage resampleImage(const QImage &image, QSize outputSize, GDALRIOResampleAlg resampleAlg)
Resamples a QImage image using GDAL resampler.