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