1 /***************************************************************************
2 qgsimagewarper.cpp
3 --------------------------------------
4 Date : Sun Sep 16 12:03:14 AKDT 2007
5 Copyright : (C) 2007 by Gary E. Sherman
6 Email : sherman at mrcc dot 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 <cmath>
17 #include <cstdio>
18
19 #include <cpl_conv.h>
20 #include <cpl_string.h>
21 #include <gdal.h>
22 #include <gdalwarper.h>
23 #include <ogr_spatialref.h>
24
25 #include <QFile>
26 #include <QProgressDialog>
27
28 #include "qgsimagewarper.h"
29 #include "qgsgeoreftransform.h"
30 #include "qgslogger.h"
31 #include "qgsogrutils.h"
32
33 bool QgsImageWarper::sWarpCanceled = false;
34
QgsImageWarper(QWidget * parent)35 QgsImageWarper::QgsImageWarper( QWidget *parent )
36 : mParent( parent )
37 {
38 }
39
openSrcDSAndGetWarpOpt(const QString & input,ResamplingMethod resampling,const GDALTransformerFunc & pfnTransform,gdal::dataset_unique_ptr & hSrcDS,gdal::warp_options_unique_ptr & psWarpOptions)40 bool QgsImageWarper::openSrcDSAndGetWarpOpt( const QString &input, ResamplingMethod resampling,
41 const GDALTransformerFunc &pfnTransform,
42 gdal::dataset_unique_ptr &hSrcDS, gdal::warp_options_unique_ptr &psWarpOptions )
43 {
44 // Open input file
45 GDALAllRegister();
46 hSrcDS.reset( GDALOpen( input.toUtf8().constData(), GA_ReadOnly ) );
47 if ( !hSrcDS )
48 return false;
49
50 // Setup warp options.
51 psWarpOptions.reset( GDALCreateWarpOptions() );
52 psWarpOptions->hSrcDS = hSrcDS.get();
53 psWarpOptions->nBandCount = GDALGetRasterCount( hSrcDS.get() );
54 psWarpOptions->panSrcBands =
55 ( int * ) CPLMalloc( sizeof( int ) * psWarpOptions->nBandCount );
56 psWarpOptions->panDstBands =
57 ( int * ) CPLMalloc( sizeof( int ) * psWarpOptions->nBandCount );
58 for ( int i = 0; i < psWarpOptions->nBandCount; ++i )
59 {
60 psWarpOptions->panSrcBands[i] = i + 1;
61 psWarpOptions->panDstBands[i] = i + 1;
62 }
63 psWarpOptions->pfnProgress = GDALTermProgress;
64 psWarpOptions->pfnTransformer = pfnTransform;
65 psWarpOptions->eResampleAlg = toGDALResampleAlg( resampling );
66
67 return true;
68 }
69
createDestinationDataset(const QString & outputName,GDALDatasetH hSrcDS,gdal::dataset_unique_ptr & hDstDS,uint resX,uint resY,double * adfGeoTransform,bool useZeroAsTrans,const QString & compression,const QgsCoordinateReferenceSystem & crs)70 bool QgsImageWarper::createDestinationDataset( const QString &outputName, GDALDatasetH hSrcDS, gdal::dataset_unique_ptr &hDstDS,
71 uint resX, uint resY, double *adfGeoTransform, bool useZeroAsTrans,
72 const QString &compression, const QgsCoordinateReferenceSystem &crs )
73 {
74 // create the output file
75 GDALDriverH driver = GDALGetDriverByName( "GTiff" );
76 if ( !driver )
77 {
78 return false;
79 }
80 char **papszOptions = nullptr;
81 papszOptions = CSLSetNameValue( papszOptions, "COMPRESS", compression.toLatin1() );
82 hDstDS.reset( GDALCreate( driver,
83 outputName.toUtf8().constData(), resX, resY,
84 GDALGetRasterCount( hSrcDS ),
85 GDALGetRasterDataType( GDALGetRasterBand( hSrcDS, 1 ) ),
86 papszOptions ) );
87 if ( !hDstDS )
88 {
89 return false;
90 }
91
92 if ( CE_None != GDALSetGeoTransform( hDstDS.get(), adfGeoTransform ) )
93 {
94 return false;
95 }
96
97 if ( crs.isValid() )
98 {
99 OGRSpatialReference oTargetSRS;
100 #if PROJ_VERSION_MAJOR>=6
101 oTargetSRS.importFromWkt( crs.toWkt( QgsCoordinateReferenceSystem::WKT_PREFERRED_GDAL ).toUtf8().data() );
102 #else
103 oTargetSRS.importFromProj4( crs.toProj().toLatin1().data() );
104 #endif
105
106 char *wkt = nullptr;
107 OGRErr err = oTargetSRS.exportToWkt( &wkt );
108 if ( err != CE_None || GDALSetProjection( hDstDS.get(), wkt ) != CE_None )
109 {
110 CPLFree( wkt );
111 return false;
112 }
113 CPLFree( wkt );
114 }
115
116 for ( int i = 0; i < GDALGetRasterCount( hSrcDS ); ++i )
117 {
118 GDALRasterBandH hSrcBand = GDALGetRasterBand( hSrcDS, i + 1 );
119 GDALRasterBandH hDstBand = GDALGetRasterBand( hDstDS.get(), i + 1 );
120 GDALColorTableH cTable = GDALGetRasterColorTable( hSrcBand );
121 GDALSetRasterColorInterpretation( hDstBand, GDALGetRasterColorInterpretation( hSrcBand ) );
122 if ( cTable )
123 {
124 GDALSetRasterColorTable( hDstBand, cTable );
125 }
126
127 int success;
128 double noData = GDALGetRasterNoDataValue( hSrcBand, &success );
129 if ( success )
130 {
131 GDALSetRasterNoDataValue( hDstBand, noData );
132 }
133 else if ( useZeroAsTrans )
134 {
135 GDALSetRasterNoDataValue( hDstBand, 0 );
136 }
137 }
138
139 return true;
140 }
141
warpFile(const QString & input,const QString & output,const QgsGeorefTransform & georefTransform,ResamplingMethod resampling,bool useZeroAsTrans,const QString & compression,const QgsCoordinateReferenceSystem & crs,double destResX,double destResY)142 int QgsImageWarper::warpFile( const QString &input,
143 const QString &output,
144 const QgsGeorefTransform &georefTransform,
145 ResamplingMethod resampling,
146 bool useZeroAsTrans,
147 const QString &compression,
148 const QgsCoordinateReferenceSystem &crs,
149 double destResX, double destResY )
150 {
151 if ( !georefTransform.parametersInitialized() )
152 return false;
153
154 CPLErr eErr;
155 gdal::dataset_unique_ptr hSrcDS;
156 gdal::dataset_unique_ptr hDstDS;
157 gdal::warp_options_unique_ptr psWarpOptions;
158 if ( !openSrcDSAndGetWarpOpt( input, resampling, georefTransform.GDALTransformer(), hSrcDS, psWarpOptions ) )
159 {
160 // TODO: be verbose about failures
161 return false;
162 }
163
164 double adfGeoTransform[6];
165 int destPixels, destLines;
166 eErr = GDALSuggestedWarpOutput( hSrcDS.get(), georefTransform.GDALTransformer(),
167 georefTransform.GDALTransformerArgs(),
168 adfGeoTransform, &destPixels, &destLines );
169 if ( eErr != CE_None )
170 {
171 return false;
172 }
173
174 // If specified, override the suggested resolution with user values
175 if ( destResX != 0.0 || destResY != 0.0 )
176 {
177 // If only one scale has been specified, fill in the other from the GDAL suggestion
178 if ( destResX == 0.0 )
179 destResX = adfGeoTransform[1];
180 if ( destResY == 0.0 )
181 destResY = adfGeoTransform[5];
182
183 // Make sure user-specified coordinate system has canonical orientation
184 if ( destResX < 0.0 )
185 destResX = -destResX;
186 if ( destResY > 0.0 )
187 destResY = -destResY;
188
189 // Assert that the north-up convention is fulfilled by GDALSuggestedWarpOutput (should always be the case)
190 // Asserts are bad as they just crash out, changed to just return false. TS
191 if ( adfGeoTransform[0] <= 0.0 || adfGeoTransform[5] >= 0.0 )
192 {
193 QgsDebugMsg( QStringLiteral( "Image is not north up after GDALSuggestedWarpOutput, bailing out." ) );
194 return false;
195 }
196 // Find suggested output image extent (in georeferenced units)
197 double minX = adfGeoTransform[0];
198 double maxX = adfGeoTransform[0] + adfGeoTransform[1] * destPixels;
199 double maxY = adfGeoTransform[3];
200 double minY = adfGeoTransform[3] + adfGeoTransform[5] * destLines;
201
202 // Update line and pixel count to match extent at user-specified resolution
203 destPixels = ( int )( ( ( maxX - minX ) / destResX ) + 0.5 );
204 destLines = ( int )( ( ( minY - maxY ) / destResY ) + 0.5 );
205 adfGeoTransform[0] = minX;
206 adfGeoTransform[3] = maxY;
207 adfGeoTransform[1] = destResX;
208 adfGeoTransform[5] = destResY;
209 }
210
211 if ( !createDestinationDataset( output, hSrcDS.get(), hDstDS, destPixels, destLines,
212 adfGeoTransform, useZeroAsTrans, compression,
213 crs ) )
214 {
215 return false;
216 }
217
218 // Create a QT progress dialog
219 QProgressDialog *progressDialog = new QProgressDialog( mParent );
220 progressDialog->setWindowTitle( tr( "Progress Indication" ) );
221 progressDialog->setRange( 0, 100 );
222 progressDialog->setAutoClose( true );
223 progressDialog->setModal( true );
224 progressDialog->setMinimumDuration( 0 );
225
226 // Set GDAL callbacks for the progress dialog
227 psWarpOptions->pProgressArg = createWarpProgressArg( progressDialog );
228 psWarpOptions->pfnProgress = updateWarpProgress;
229
230 psWarpOptions->hSrcDS = hSrcDS.get();
231 psWarpOptions->hDstDS = hDstDS.get();
232
233 // Create a transformer which transforms from source to destination pixels (and vice versa)
234 psWarpOptions->pfnTransformer = GeoToPixelTransform;
235 psWarpOptions->pTransformerArg = addGeoToPixelTransform( georefTransform.GDALTransformer(),
236 georefTransform.GDALTransformerArgs(),
237 adfGeoTransform );
238
239 // Initialize and execute the warp operation.
240 GDALWarpOperation oOperation;
241 oOperation.Initialize( psWarpOptions.get() );
242
243 progressDialog->show();
244 progressDialog->raise();
245 progressDialog->activateWindow();
246
247 eErr = oOperation.ChunkAndWarpImage( 0, 0, destPixels, destLines );
248 // eErr = oOperation.ChunkAndWarpMulti(0, 0, destPixels, destLines);
249
250 destroyGeoToPixelTransform( psWarpOptions->pTransformerArg );
251 delete progressDialog;
252 return sWarpCanceled ? -1 : eErr == CE_None ? 1 : 0;
253 }
254
255
addGeoToPixelTransform(GDALTransformerFunc GDALTransformer,void * GDALTransformerArg,double * padfGeotransform) const256 void *QgsImageWarper::addGeoToPixelTransform( GDALTransformerFunc GDALTransformer, void *GDALTransformerArg, double *padfGeotransform ) const
257 {
258 TransformChain *chain = new TransformChain;
259 chain->GDALTransformer = GDALTransformer;
260 chain->GDALTransformerArg = GDALTransformerArg;
261 memcpy( chain->adfGeotransform, padfGeotransform, sizeof( double ) * 6 );
262 // TODO: In reality we don't require the full homogeneous matrix, so GeoToPixelTransform and matrix inversion could
263 // be optimized for simple scale+offset if there's the need (e.g. for numerical or performance reasons).
264 if ( !GDALInvGeoTransform( chain->adfGeotransform, chain->adfInvGeotransform ) )
265 {
266 // Error handling if inversion fails - although the inverse transform is not needed for warp operation
267 delete chain;
268 return nullptr;
269 }
270 return ( void * )chain;
271 }
272
destroyGeoToPixelTransform(void * GeoToPixelTransformArg) const273 void QgsImageWarper::destroyGeoToPixelTransform( void *GeoToPixelTransformArg ) const
274 {
275 delete static_cast<TransformChain *>( GeoToPixelTransformArg );
276 }
277
GeoToPixelTransform(void * pTransformerArg,int bDstToSrc,int nPointCount,double * x,double * y,double * z,int * panSuccess)278 int QgsImageWarper::GeoToPixelTransform( void *pTransformerArg, int bDstToSrc, int nPointCount,
279 double *x, double *y, double *z, int *panSuccess )
280 {
281 TransformChain *chain = static_cast<TransformChain *>( pTransformerArg );
282 if ( !chain )
283 {
284 return false;
285 }
286
287 if ( !bDstToSrc )
288 {
289 // Transform to georeferenced coordinates
290 if ( !chain->GDALTransformer( chain->GDALTransformerArg, bDstToSrc, nPointCount, x, y, z, panSuccess ) )
291 {
292 return false;
293 }
294 // Transform from georeferenced to pixel/line
295 for ( int i = 0; i < nPointCount; ++i )
296 {
297 if ( !panSuccess[i] )
298 continue;
299 double xP = x[i];
300 double yP = y[i];
301 x[i] = chain->adfInvGeotransform[0] + xP * chain->adfInvGeotransform[1] + yP * chain->adfInvGeotransform[2];
302 y[i] = chain->adfInvGeotransform[3] + xP * chain->adfInvGeotransform[4] + yP * chain->adfInvGeotransform[5];
303 }
304 }
305 else
306 {
307 // Transform from pixel/line to georeferenced coordinates
308 for ( int i = 0; i < nPointCount; ++i )
309 {
310 double P = x[i];
311 double L = y[i];
312 x[i] = chain->adfGeotransform[0] + P * chain->adfGeotransform[1] + L * chain->adfGeotransform[2];
313 y[i] = chain->adfGeotransform[3] + P * chain->adfGeotransform[4] + L * chain->adfGeotransform[5];
314 }
315 // Transform from georeferenced coordinates to source pixel/line
316 if ( !chain->GDALTransformer( chain->GDALTransformerArg, bDstToSrc, nPointCount, x, y, z, panSuccess ) )
317 {
318 return false;
319 }
320 }
321 return true;
322 }
323
createWarpProgressArg(QProgressDialog * progressDialog) const324 void *QgsImageWarper::createWarpProgressArg( QProgressDialog *progressDialog ) const
325 {
326 return ( void * )progressDialog;
327 }
328
updateWarpProgress(double dfComplete,const char * pszMessage,void * pProgressArg)329 int CPL_STDCALL QgsImageWarper::updateWarpProgress( double dfComplete, const char *pszMessage, void *pProgressArg )
330 {
331 Q_UNUSED( pszMessage )
332 QProgressDialog *progress = static_cast<QProgressDialog *>( pProgressArg );
333 progress->setValue( std::min( 100u, ( uint )( dfComplete * 100.0 ) ) );
334 qApp->processEvents();
335 // TODO: call QEventLoop manually to make "cancel" button more responsive
336 if ( progress->wasCanceled() )
337 {
338 sWarpCanceled = true;
339 return false;
340 }
341
342 sWarpCanceled = false;
343 return true;
344 }
345
toGDALResampleAlg(const QgsImageWarper::ResamplingMethod method) const346 GDALResampleAlg QgsImageWarper::toGDALResampleAlg( const QgsImageWarper::ResamplingMethod method ) const
347 {
348 switch ( method )
349 {
350 case NearestNeighbour:
351 return GRA_NearestNeighbour;
352 case Bilinear:
353 return GRA_Bilinear;
354 case Cubic:
355 return GRA_Cubic;
356 case CubicSpline:
357 return GRA_CubicSpline;
358 case Lanczos:
359 return GRA_Lanczos;
360 default:
361 break;
362 };
363
364 //avoid warning
365 return GRA_NearestNeighbour;
366 }
367