1 /***************************************************************************
2      testqgsgdalutils.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 #include "qgstest.h"
16 #include <QObject>
17 #include <QString>
18 #include <QStringList>
19 #include <QSettings>
20 
21 #include <gdal.h>
22 
23 #include "qgsgdalutils.h"
24 #include "qgsapplication.h"
25 #include "qgsrasterlayer.h"
26 
27 
28 class TestQgsGdalUtils: public QObject
29 {
30     Q_OBJECT
31 
32   private slots:
33     void initTestCase();// will be called before the first testfunction is executed.
34     void cleanupTestCase();// will be called after the last testfunction was executed.
35     void init();// will be called before each testfunction is executed.
36     void cleanup();// will be called after every testfunction.
37     void supportsRasterCreate();
38     void testCreateSingleBandMemoryDataset();
39     void testCreateMultiBandMemoryDataset();
40     void testCreateSingleBandTiffDataset();
41     void testResampleSingleBandRaster();
42     void testImageToDataset();
43     void testResampleImageToImage();
44     void testPathIsCheapToOpen();
45     void testVrtMatchesLayerType();
46     void testMultilayerExtensions();
47 
48   private:
49 
50     double identify( GDALDatasetH dataset, int band, int px, int py );
51 };
52 
initTestCase()53 void TestQgsGdalUtils::initTestCase()
54 {
55   QgsApplication::init();
56   QgsApplication::initQgis();
57   GDALAllRegister();
58 }
59 
cleanupTestCase()60 void TestQgsGdalUtils::cleanupTestCase()
61 {
62   QgsApplication::exitQgis();
63 }
64 
init()65 void TestQgsGdalUtils::init()
66 {
67 
68 }
69 
cleanup()70 void TestQgsGdalUtils::cleanup()
71 {
72 
73 }
74 
supportsRasterCreate()75 void TestQgsGdalUtils::supportsRasterCreate()
76 {
77   QVERIFY( QgsGdalUtils::supportsRasterCreate( GDALGetDriverByName( "GTiff" ) ) );
78   QVERIFY( QgsGdalUtils::supportsRasterCreate( GDALGetDriverByName( "GPKG" ) ) );
79 
80   // special case
81   QVERIFY( !QgsGdalUtils::supportsRasterCreate( GDALGetDriverByName( "SQLite" ) ) );
82 
83   // create-only
84   QVERIFY( !QgsGdalUtils::supportsRasterCreate( GDALGetDriverByName( "DTED" ) ) );
85 
86   // vector-only
87   QVERIFY( !QgsGdalUtils::supportsRasterCreate( GDALGetDriverByName( "ESRI Shapefile" ) ) );
88 }
89 
90 #define EPSG_4326_WKT \
91   "GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS 84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]]," \
92   "AUTHORITY[\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],UNIT[\"degree\",0.0174532925199433," \
93   "AUTHORITY[\"EPSG\",\"9122\"]],AXIS[\"Latitude\",NORTH],AXIS[\"Longitude\",EAST],AUTHORITY[\"EPSG\",\"4326\"]]"
94 
testCreateSingleBandMemoryDataset()95 void TestQgsGdalUtils::testCreateSingleBandMemoryDataset()
96 {
97   const gdal::dataset_unique_ptr ds1 = QgsGdalUtils::createSingleBandMemoryDataset( GDT_Float32, QgsRectangle( 1, 1, 21, 11 ), 40, 20, QgsCoordinateReferenceSystem( "EPSG:4326" ) );
98   QVERIFY( ds1 );
99 
100   QCOMPARE( GDALGetRasterCount( ds1.get() ), 1 );
101   QCOMPARE( GDALGetRasterXSize( ds1.get() ), 40 );
102   QCOMPARE( GDALGetRasterYSize( ds1.get() ), 20 );
103 
104   QCOMPARE( GDALGetProjectionRef( ds1.get() ),  R"""(GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AXIS["Latitude",NORTH],AXIS["Longitude",EAST],AUTHORITY["EPSG","4326"]])""" );
105   double geoTransform[6];
106   double geoTransformExpected[] = { 1, 0.5, 0, 11, 0, -0.5 };
107   QCOMPARE( GDALGetGeoTransform( ds1.get(), geoTransform ), CE_None );
108   QVERIFY( memcmp( geoTransform, geoTransformExpected, sizeof( double ) * 6 ) == 0 );
109 
110   QCOMPARE( GDALGetRasterDataType( GDALGetRasterBand( ds1.get(), 1 ) ), GDT_Float32 );
111 }
112 
testCreateMultiBandMemoryDataset()113 void TestQgsGdalUtils::testCreateMultiBandMemoryDataset()
114 {
115   const gdal::dataset_unique_ptr ds1 = QgsGdalUtils::createMultiBandMemoryDataset( GDT_Float32, 4, QgsRectangle( 1, 1, 21, 11 ), 40, 20, QgsCoordinateReferenceSystem( "EPSG:4326" ) );
116   QVERIFY( ds1 );
117 
118   QCOMPARE( GDALGetRasterCount( ds1.get() ), 4 );
119   QCOMPARE( GDALGetRasterXSize( ds1.get() ), 40 );
120   QCOMPARE( GDALGetRasterYSize( ds1.get() ), 20 );
121 
122   QCOMPARE( GDALGetProjectionRef( ds1.get() ),  R"""(GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AXIS["Latitude",NORTH],AXIS["Longitude",EAST],AUTHORITY["EPSG","4326"]])""" );
123   double geoTransform[6];
124   double geoTransformExpected[] = { 1, 0.5, 0, 11, 0, -0.5 };
125   QCOMPARE( GDALGetGeoTransform( ds1.get(), geoTransform ), CE_None );
126   QVERIFY( memcmp( geoTransform, geoTransformExpected, sizeof( double ) * 6 ) == 0 );
127 
128   QCOMPARE( GDALGetRasterDataType( GDALGetRasterBand( ds1.get(), 1 ) ), GDT_Float32 );
129   QCOMPARE( GDALGetRasterDataType( GDALGetRasterBand( ds1.get(), 2 ) ), GDT_Float32 );
130   QCOMPARE( GDALGetRasterDataType( GDALGetRasterBand( ds1.get(), 3 ) ), GDT_Float32 );
131   QCOMPARE( GDALGetRasterDataType( GDALGetRasterBand( ds1.get(), 4 ) ), GDT_Float32 );
132 }
133 
testCreateSingleBandTiffDataset()134 void TestQgsGdalUtils::testCreateSingleBandTiffDataset()
135 {
136   const QString filename = QDir::tempPath() + "/qgis_test_single_band_raster.tif";
137   QFile::remove( filename );
138   QVERIFY( !QFile::exists( filename ) );
139 
140   gdal::dataset_unique_ptr ds1 = QgsGdalUtils::createSingleBandTiffDataset( filename, GDT_Float32, QgsRectangle( 1, 1, 21, 11 ), 40, 20, QgsCoordinateReferenceSystem( "EPSG:4326" ) );
141   QVERIFY( ds1 );
142 
143   QCOMPARE( GDALGetRasterCount( ds1.get() ), 1 );
144   QCOMPARE( GDALGetRasterXSize( ds1.get() ), 40 );
145   QCOMPARE( GDALGetRasterYSize( ds1.get() ), 20 );
146 
147   QCOMPARE( GDALGetProjectionRef( ds1.get() ), R"""(GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AXIS["Latitude",NORTH],AXIS["Longitude",EAST],AUTHORITY["EPSG","4326"]])""" );
148 
149   double geoTransform[6];
150   double geoTransformExpected[] = { 1, 0.5, 0, 11, 0, -0.5 };
151   QCOMPARE( GDALGetGeoTransform( ds1.get(), geoTransform ), CE_None );
152   QVERIFY( memcmp( geoTransform, geoTransformExpected, sizeof( double ) * 6 ) == 0 );
153 
154   QCOMPARE( GDALGetRasterDataType( GDALGetRasterBand( ds1.get(), 1 ) ), GDT_Float32 );
155 
156   ds1.reset();  // makes sure the file is fully written
157 
158   QVERIFY( QFile::exists( filename ) );
159 
160   std::unique_ptr<QgsRasterLayer> layer( new QgsRasterLayer( filename, "test", "gdal" ) );
161   QVERIFY( layer->isValid() );
162   QCOMPARE( layer->extent(), QgsRectangle( 1, 1, 21, 11 ) );
163   QCOMPARE( layer->width(), 40 );
164   QCOMPARE( layer->height(), 20 );
165 
166   layer.reset();  // let's clean up before removing the file
167   QFile::remove( filename );
168 }
169 
testResampleSingleBandRaster()170 void TestQgsGdalUtils::testResampleSingleBandRaster()
171 {
172   const QString inputFilename = QString( TEST_DATA_DIR ) + "/float1-16.tif";
173   const gdal::dataset_unique_ptr srcDS( GDALOpen( inputFilename.toUtf8().constData(), GA_ReadOnly ) );
174   QVERIFY( srcDS );
175 
176   const QString outputFilename = QDir::tempPath() + "/qgis_test_float1-16_resampled.tif";
177   const QgsRectangle outputExtent( 106.25, -6.75, 106.55, -6.45 );
178   gdal::dataset_unique_ptr dstDS = QgsGdalUtils::createSingleBandTiffDataset( outputFilename, GDT_Float32, outputExtent, 2, 2, QgsCoordinateReferenceSystem( "EPSG:4326" ) );
179   QVERIFY( dstDS );
180 
181   QgsGdalUtils::resampleSingleBandRaster( srcDS.get(), dstDS.get(), GRA_NearestNeighbour, nullptr );
182   dstDS.reset();
183 
184   std::unique_ptr<QgsRasterLayer> layer( new QgsRasterLayer( outputFilename, "test", "gdal" ) );
185   QVERIFY( layer );
186   std::unique_ptr<QgsRasterBlock> block( layer->dataProvider()->block( 1, outputExtent, 2, 2 ) );
187   QVERIFY( block );
188   QCOMPARE( block->value( 0, 0 ), 6. );
189   QCOMPARE( block->value( 1, 1 ), 11. );
190 
191   layer.reset();
192   QFile::remove( outputFilename );
193 }
194 
testImageToDataset()195 void TestQgsGdalUtils::testImageToDataset()
196 {
197   const QString inputFilename = QString( TEST_DATA_DIR ) + "/rgb256x256.png";
198   QImage src = QImage( inputFilename );
199   src = src.convertToFormat( QImage::Format_ARGB32 );
200   QVERIFY( !src.isNull() );
201 
202   gdal::dataset_unique_ptr dstDS = QgsGdalUtils::imageToMemoryDataset( QImage() );
203   QVERIFY( !dstDS );
204 
205   dstDS = QgsGdalUtils::imageToMemoryDataset( src );
206   QVERIFY( dstDS );
207 
208   QCOMPARE( GDALGetRasterCount( dstDS.get() ), 4 );
209   QCOMPARE( GDALGetRasterXSize( dstDS.get() ), 256 );
210   QCOMPARE( GDALGetRasterYSize( dstDS.get() ), 256 );
211 
212   QCOMPARE( GDALGetRasterDataType( GDALGetRasterBand( dstDS.get(), 1 ) ), GDT_Byte );
213   QCOMPARE( GDALGetRasterDataType( GDALGetRasterBand( dstDS.get(), 2 ) ), GDT_Byte );
214   QCOMPARE( GDALGetRasterDataType( GDALGetRasterBand( dstDS.get(), 3 ) ), GDT_Byte );
215   QCOMPARE( GDALGetRasterDataType( GDALGetRasterBand( dstDS.get(), 4 ) ), GDT_Byte );
216 
217   QCOMPARE( identify( dstDS.get(), 1, 50, 50 ), 255.0 );
218   QCOMPARE( identify( dstDS.get(), 1, 200, 50 ), 255.0 );
219   QCOMPARE( identify( dstDS.get(), 1, 50, 200 ), 0.0 );
220   QCOMPARE( identify( dstDS.get(), 1, 200, 200 ), 0.0 );
221 
222   QCOMPARE( identify( dstDS.get(), 2, 50, 50 ), 255.0 );
223   QCOMPARE( identify( dstDS.get(), 2, 200, 50 ), 0.0 );
224   QCOMPARE( identify( dstDS.get(), 2, 50, 200 ), 255.0 );
225   QCOMPARE( identify( dstDS.get(), 2, 200, 200 ), 0.0 );
226 
227   QCOMPARE( identify( dstDS.get(), 3, 50, 50 ), 0.0 );
228   QCOMPARE( identify( dstDS.get(), 3, 200, 50 ), 0.0 );
229   QCOMPARE( identify( dstDS.get(), 3, 50, 200 ), 0.0 );
230   QCOMPARE( identify( dstDS.get(), 3, 200, 200 ), 255.0 );
231 
232   QCOMPARE( identify( dstDS.get(), 4, 50, 50 ), 255.0 );
233   QCOMPARE( identify( dstDS.get(), 4, 200, 50 ), 255.0 );
234   QCOMPARE( identify( dstDS.get(), 4, 50, 200 ), 255.0 );
235   QCOMPARE( identify( dstDS.get(), 4, 200, 200 ), 255.0 );
236 }
237 
testResampleImageToImage()238 void TestQgsGdalUtils::testResampleImageToImage()
239 {
240   const QString inputFilename = QString( TEST_DATA_DIR ) + "/rgb256x256.png";
241   QImage src = QImage( inputFilename );
242   src = src.convertToFormat( QImage::Format_ARGB32 );
243   QVERIFY( !src.isNull() );
244 
245   QImage res = QgsGdalUtils::resampleImage( QImage(), QSize( 50, 50 ), GRIORA_NearestNeighbour );
246   QVERIFY( res.isNull() );
247 
248   res = QgsGdalUtils::resampleImage( src, QSize( 50, 50 ), GRIORA_NearestNeighbour );
249   QVERIFY( !res.isNull() );
250   QCOMPARE( res.width(), 50 );
251   QCOMPARE( res.height(), 50 );
252 
253   QCOMPARE( qRed( res.pixel( 10, 10 ) ), 255 );
254   QCOMPARE( qGreen( res.pixel( 10, 10 ) ), 255 );
255   QCOMPARE( qBlue( res.pixel( 10, 10 ) ), 0 );
256   QCOMPARE( qAlpha( res.pixel( 10, 10 ) ), 255 );
257 
258   QCOMPARE( qRed( res.pixel( 40, 10 ) ), 255 );
259   QCOMPARE( qGreen( res.pixel( 40, 10 ) ), 0 );
260   QCOMPARE( qBlue( res.pixel( 40, 10 ) ), 0 );
261   QCOMPARE( qAlpha( res.pixel( 40, 10 ) ), 255 );
262 
263   QCOMPARE( qRed( res.pixel( 10, 40 ) ), 0 );
264   QCOMPARE( qGreen( res.pixel( 10, 40 ) ), 255 );
265   QCOMPARE( qBlue( res.pixel( 10, 40 ) ), 0 );
266   QCOMPARE( qAlpha( res.pixel( 10, 40 ) ), 255 );
267 
268   QCOMPARE( qRed( res.pixel( 40, 40 ) ), 0 );
269   QCOMPARE( qGreen( res.pixel( 40, 40 ) ), 0 );
270   QCOMPARE( qBlue( res.pixel( 40, 40 ) ), 255 );
271   QCOMPARE( qAlpha( res.pixel( 40, 40 ) ), 255 );
272 }
273 
testPathIsCheapToOpen()274 void TestQgsGdalUtils::testPathIsCheapToOpen()
275 {
276   // should be safe and report false paths which don't exist
277   QVERIFY( !QgsGdalUtils::pathIsCheapToOpen( "/not/a/file" ) );
278 
279   // for now, tiff aren't marked as cheap to open
280   QVERIFY( !QgsGdalUtils::pathIsCheapToOpen( QStringLiteral( TEST_DATA_DIR ) + "/big_raster.tif" ) );
281 
282   // a csv is considered cheap to open
283   QVERIFY( QgsGdalUtils::pathIsCheapToOpen( QStringLiteral( TEST_DATA_DIR ) + "/delimitedtext/test.csv" ) );
284 
285   // ... unless it's larger than the specified file size limit (500 bytes)
286   QVERIFY( !QgsGdalUtils::pathIsCheapToOpen( QStringLiteral( TEST_DATA_DIR ) + "/delimitedtext/testdms.csv", 500 ) );
287 }
288 
testVrtMatchesLayerType()289 void TestQgsGdalUtils::testVrtMatchesLayerType()
290 {
291   QVERIFY( QgsGdalUtils::vrtMatchesLayerType( QStringLiteral( TEST_DATA_DIR ) + "/raster/hub13263.vrt", QgsMapLayerType::RasterLayer ) );
292   QVERIFY( !QgsGdalUtils::vrtMatchesLayerType( QStringLiteral( TEST_DATA_DIR ) + "/raster/hub13263.vrt", QgsMapLayerType::VectorLayer ) );
293 
294   QVERIFY( !QgsGdalUtils::vrtMatchesLayerType( QStringLiteral( TEST_DATA_DIR ) + "/vector_vrt.vrt", QgsMapLayerType::RasterLayer ) );
295   QVERIFY( QgsGdalUtils::vrtMatchesLayerType( QStringLiteral( TEST_DATA_DIR ) + "/vector_vrt.vrt", QgsMapLayerType::VectorLayer ) );
296 }
297 
testMultilayerExtensions()298 void TestQgsGdalUtils::testMultilayerExtensions()
299 {
300   const QStringList extensions = QgsGdalUtils::multiLayerFileExtensions();
301   QVERIFY( extensions.contains( QStringLiteral( "gpkg" ) ) );
302   QVERIFY( extensions.contains( QStringLiteral( "sqlite" ) ) );
303   QVERIFY( extensions.contains( QStringLiteral( "db" ) ) );
304   QVERIFY( extensions.contains( QStringLiteral( "kml" ) ) );
305   QVERIFY( extensions.contains( QStringLiteral( "ods" ) ) );
306   QVERIFY( extensions.contains( QStringLiteral( "osm" ) ) );
307   QVERIFY( extensions.contains( QStringLiteral( "mdb" ) ) );
308   QVERIFY( extensions.contains( QStringLiteral( "xls" ) ) );
309   QVERIFY( extensions.contains( QStringLiteral( "xlsx" ) ) );
310   QVERIFY( extensions.contains( QStringLiteral( "gpx" ) ) );
311   QVERIFY( extensions.contains( QStringLiteral( "pdf" ) ) );
312   QVERIFY( extensions.contains( QStringLiteral( "nc" ) ) );
313   QVERIFY( extensions.contains( QStringLiteral( "gdb" ) ) );
314 }
315 
identify(GDALDatasetH dataset,int band,int px,int py)316 double TestQgsGdalUtils::identify( GDALDatasetH dataset, int band, int px, int py )
317 {
318   GDALRasterBandH hBand = GDALGetRasterBand( dataset, band );
319 
320   float *pafScanline = ( float * ) CPLMalloc( sizeof( float ) );
321   const CPLErr err = GDALRasterIO( hBand, GF_Read, px, py, 1, 1,
322                                    pafScanline, 1, 1, GDT_Float32, 0, 0 );
323   const double value = err == CE_None ? pafScanline[0] : std::numeric_limits<double>::quiet_NaN();
324   CPLFree( pafScanline );
325 
326   return value;
327 }
328 
329 QGSTEST_MAIN( TestQgsGdalUtils )
330 #include "testqgsgdalutils.moc"
331