1 /***************************************************************************
2     qgsgeoreftransform.cpp - Encapsulates GCP-based parameter estimation and
3     reprojection for different transformation models.
4      --------------------------------------
5     Date                 : 18-Feb-2009
6     Copyright            : (c) 2009 by Manuel Massing
7     Email                : m.massing at warped-space.de
8  ***************************************************************************
9  *                                                                         *
10  *   This program is free software; you can redistribute it and/or modify  *
11  *   it under the terms of the GNU General Public License as published by  *
12  *   the Free Software Foundation; either version 2 of the License, or     *
13  *   (at your option) any later version.                                   *
14  *                                                                         *
15  ***************************************************************************/
16 
17 #include "qgsgeoreftransform.h"
18 
19 #include <gdal.h>
20 #include <gdal_alg.h>
21 
22 #include "qgsleastsquares.h"
23 
24 #include <cmath>
25 
26 #include <cassert>
27 #include <limits>
28 
29 /**
30  * A simple transform which is paremetrized by a translation and anistotropic scale.
31  */
32 class QgsLinearGeorefTransform : public QgsGeorefTransformInterface
33 {
34   public:
35     QgsLinearGeorefTransform() = default;
36 
37     bool getOriginScale( QgsPointXY &origin, double &scaleX, double &scaleY ) const;
38 
39     bool updateParametersFromGCPs( const QVector<QgsPointXY> &mapCoords, const QVector<QgsPointXY> &pixelCoords ) override;
40     int getMinimumGCPCount() const override;
41 
GDALTransformer() const42     GDALTransformerFunc  GDALTransformer()     const override { return QgsLinearGeorefTransform::linear_transform; }
GDALTransformerArgs() const43     void                *GDALTransformerArgs() const override { return ( void * )&mParameters; }
44   private:
45     struct LinearParameters
46     {
47       QgsPointXY origin;
48       double scaleX, scaleY;
49     } mParameters;
50 
51     static int linear_transform( void *pTransformerArg, int bDstToSrc, int nPointCount,
52                                  double *x, double *y, double *z, int *panSuccess );
53 };
54 
55 /**
56  * 2-dimensional helmert transform, parametrised by isotropic scale, rotation angle and translation.
57  */
58 class QgsHelmertGeorefTransform : public QgsGeorefTransformInterface
59 {
60   public:
61     QgsHelmertGeorefTransform() = default;
62     struct HelmertParameters
63     {
64       QgsPointXY origin;
65       double   scale;
66       double   angle;
67     };
68 
69     bool getOriginScaleRotation( QgsPointXY &origin, double &scale, double &rotation ) const;
70     bool updateParametersFromGCPs( const QVector<QgsPointXY> &mapCoords, const QVector<QgsPointXY> &pixelCoords ) override;
71     int getMinimumGCPCount() const override;
72 
73     GDALTransformerFunc  GDALTransformer()     const override;
74     void                *GDALTransformerArgs() const override;
75   private:
76     HelmertParameters mHelmertParameters;
77 
78     static int helmert_transform( void *pTransformerArg, int bDstToSrc, int nPointCount,
79                                   double *x, double *y, double *z, int *panSuccess );
80 };
81 
82 /**
83  * Interface to gdal thin plate splines and 1st/2nd/3rd order polynomials.
84  */
85 class QgsGDALGeorefTransform : public QgsGeorefTransformInterface
86 {
87   public:
88     QgsGDALGeorefTransform( bool useTPS, unsigned int polynomialOrder );
89     ~QgsGDALGeorefTransform() override;
90 
91     bool updateParametersFromGCPs( const QVector<QgsPointXY> &mapCoords, const QVector<QgsPointXY> &pixelCoords ) override;
92     int getMinimumGCPCount() const override;
93 
94     GDALTransformerFunc  GDALTransformer()     const override;
95     void                *GDALTransformerArgs() const override;
96   private:
97     void destroy_gdal_args();
98 
99     const int  mPolynomialOrder;
100     const bool mIsTPSTransform;
101 
102     GDALTransformerFunc  mGDALTransformer;
103     void                *mGDALTransformerArgs = nullptr;
104 };
105 
106 /**
107  * A planar projective transform, expressed by a homography.
108  *
109  * Implements model fitting which minimizes algebraic error using total least squares.
110  */
111 class QgsProjectiveGeorefTransform : public QgsGeorefTransformInterface
112 {
113   public:
QgsProjectiveGeorefTransform()114     QgsProjectiveGeorefTransform() : mParameters() {}
115 
116     bool updateParametersFromGCPs( const QVector<QgsPointXY> &mapCoords, const QVector<QgsPointXY> &pixelCoords ) override;
117     int getMinimumGCPCount() const override;
118 
GDALTransformer() const119     GDALTransformerFunc  GDALTransformer()     const override { return QgsProjectiveGeorefTransform::projective_transform; }
GDALTransformerArgs() const120     void                *GDALTransformerArgs() const override { return ( void * )&mParameters; }
121   private:
122     struct ProjectiveParameters
123     {
124       double H[9];        // Homography
125       double Hinv[9];     // Inverted homography
126       bool   hasInverse;  // Could the inverted homography be calculated?
127     } mParameters;
128 
129     static int projective_transform( void *pTransformerArg, int bDstToSrc, int nPointCount,
130                                      double *x, double *y, double *z, int *panSuccess );
131 };
132 
133 
QgsGeorefTransform(const QgsGeorefTransform & other)134 QgsGeorefTransform::QgsGeorefTransform( const QgsGeorefTransform &other )
135 {
136   mTransformParametrisation = InvalidTransform;
137   mGeorefTransformImplementation = nullptr;
138   selectTransformParametrisation( other.mTransformParametrisation );
139 }
140 
QgsGeorefTransform(TransformParametrisation parametrisation)141 QgsGeorefTransform::QgsGeorefTransform( TransformParametrisation parametrisation )
142 {
143   mTransformParametrisation = InvalidTransform;
144   mGeorefTransformImplementation = nullptr;
145   selectTransformParametrisation( parametrisation );
146 }
147 
QgsGeorefTransform()148 QgsGeorefTransform::QgsGeorefTransform()
149 {
150   mTransformParametrisation = InvalidTransform;
151   mGeorefTransformImplementation = nullptr;
152   mParametersInitialized = false;
153 }
154 
~QgsGeorefTransform()155 QgsGeorefTransform::~QgsGeorefTransform()
156 {
157   delete mGeorefTransformImplementation;
158 }
159 
transformParametrisation() const160 QgsGeorefTransform::TransformParametrisation QgsGeorefTransform::transformParametrisation() const
161 {
162   return mTransformParametrisation;
163 }
164 
selectTransformParametrisation(TransformParametrisation parametrisation)165 void QgsGeorefTransform::selectTransformParametrisation( TransformParametrisation parametrisation )
166 {
167   if ( parametrisation != mTransformParametrisation )
168   {
169     delete mGeorefTransformImplementation;
170     mGeorefTransformImplementation = QgsGeorefTransform::createImplementation( parametrisation );
171     mParametersInitialized = false;
172     mTransformParametrisation = parametrisation;
173   }
174 }
175 
setRasterChangeCoords(const QString & fileRaster)176 void QgsGeorefTransform::setRasterChangeCoords( const QString &fileRaster )
177 {
178   mRasterChangeCoords.setRaster( fileRaster );
179 }
180 
providesAccurateInverseTransformation() const181 bool QgsGeorefTransform::providesAccurateInverseTransformation() const
182 {
183   return ( mTransformParametrisation == Linear
184            || mTransformParametrisation == Helmert
185            || mTransformParametrisation == PolynomialOrder1 );
186 }
187 
parametersInitialized() const188 bool QgsGeorefTransform::parametersInitialized() const
189 {
190   return mParametersInitialized;
191 }
192 
updateParametersFromGCPs(const QVector<QgsPointXY> & mapCoords,const QVector<QgsPointXY> & pixelCoords)193 bool QgsGeorefTransform::updateParametersFromGCPs( const QVector<QgsPointXY> &mapCoords, const QVector<QgsPointXY> &pixelCoords )
194 {
195   if ( !mGeorefTransformImplementation )
196   {
197     return false;
198   }
199   if ( mapCoords.size() != pixelCoords.size() ) // Defensive sanity check
200   {
201     throw ( std::domain_error( "Internal error: GCP mapping is not one-to-one" ) );
202   }
203   if ( mapCoords.size() < getMinimumGCPCount() )
204   {
205     return false;
206   }
207   if ( mRasterChangeCoords.hasCrs() )
208   {
209     QVector<QgsPointXY> pixelCoordsCorrect = mRasterChangeCoords.getPixelCoords( pixelCoords );
210     mParametersInitialized = mGeorefTransformImplementation->updateParametersFromGCPs( mapCoords, pixelCoordsCorrect );
211     pixelCoordsCorrect.clear();
212   }
213   else
214   {
215     mParametersInitialized = mGeorefTransformImplementation->updateParametersFromGCPs( mapCoords, pixelCoords );
216   }
217   return mParametersInitialized;
218 }
219 
getMinimumGCPCount() const220 int QgsGeorefTransform::getMinimumGCPCount() const
221 {
222   return mGeorefTransformImplementation ? mGeorefTransformImplementation->getMinimumGCPCount() : 0;
223 }
224 
GDALTransformer() const225 GDALTransformerFunc QgsGeorefTransform::GDALTransformer() const
226 {
227   return mGeorefTransformImplementation ? mGeorefTransformImplementation->GDALTransformer() : nullptr;
228 }
229 
GDALTransformerArgs() const230 void *QgsGeorefTransform::GDALTransformerArgs() const
231 {
232   return mGeorefTransformImplementation ? mGeorefTransformImplementation->GDALTransformerArgs() : nullptr;
233 }
234 
createImplementation(TransformParametrisation parametrisation)235 QgsGeorefTransformInterface *QgsGeorefTransform::createImplementation( TransformParametrisation parametrisation )
236 {
237   switch ( parametrisation )
238   {
239     case Linear:
240       return new QgsLinearGeorefTransform;
241     case Helmert:
242       return new QgsHelmertGeorefTransform;
243     case PolynomialOrder1:
244       return new QgsGDALGeorefTransform( false, 1 );
245     case PolynomialOrder2:
246       return new QgsGDALGeorefTransform( false, 2 );
247     case PolynomialOrder3:
248       return new QgsGDALGeorefTransform( false, 3 );
249     case ThinPlateSpline:
250       return new QgsGDALGeorefTransform( true, 0 );
251     case Projective:
252       return new QgsProjectiveGeorefTransform;
253     default:
254       return nullptr;
255   }
256 }
257 
transformRasterToWorld(const QgsPointXY & raster,QgsPointXY & world)258 bool QgsGeorefTransform::transformRasterToWorld( const QgsPointXY &raster, QgsPointXY &world )
259 {
260   // flip y coordinate due to different CS orientation
261   QgsPointXY raster_flipped( raster.x(), -raster.y() );
262   return gdal_transform( raster_flipped, world, 0 );
263 }
264 
transformWorldToRaster(const QgsPointXY & world,QgsPointXY & raster)265 bool QgsGeorefTransform::transformWorldToRaster( const QgsPointXY &world, QgsPointXY &raster )
266 {
267   bool success = gdal_transform( world, raster, 1 );
268   // flip y coordinate due to different CS orientation
269   raster.setY( -raster.y() );
270   return success;
271 }
272 
transform(const QgsPointXY & src,QgsPointXY & dst,bool rasterToWorld)273 bool QgsGeorefTransform::transform( const QgsPointXY &src, QgsPointXY &dst, bool rasterToWorld )
274 {
275   return rasterToWorld ? transformRasterToWorld( src, dst ) : transformWorldToRaster( src, dst );
276 }
277 
getLinearOriginScale(QgsPointXY & origin,double & scaleX,double & scaleY) const278 bool QgsGeorefTransform::getLinearOriginScale( QgsPointXY &origin, double &scaleX, double &scaleY ) const
279 {
280   if ( transformParametrisation() != Linear )
281   {
282     return false;
283   }
284   if ( !mGeorefTransformImplementation || !parametersInitialized() )
285   {
286     return false;
287   }
288   QgsLinearGeorefTransform *transform = dynamic_cast<QgsLinearGeorefTransform *>( mGeorefTransformImplementation );
289   return transform && transform->getOriginScale( origin, scaleX, scaleY );
290 }
291 
getOriginScaleRotation(QgsPointXY & origin,double & scaleX,double & scaleY,double & rotation) const292 bool QgsGeorefTransform::getOriginScaleRotation( QgsPointXY &origin, double &scaleX, double &scaleY, double &rotation ) const
293 {
294 
295   if ( mTransformParametrisation == Linear )
296   {
297     rotation = 0.0;
298     QgsLinearGeorefTransform *transform = dynamic_cast<QgsLinearGeorefTransform *>( mGeorefTransformImplementation );
299     return transform && transform->getOriginScale( origin, scaleX, scaleY );
300   }
301   else if ( mTransformParametrisation == Helmert )
302   {
303     double scale;
304     QgsHelmertGeorefTransform *transform = dynamic_cast<QgsHelmertGeorefTransform *>( mGeorefTransformImplementation );
305     if ( !transform || ! transform->getOriginScaleRotation( origin, scale, rotation ) )
306     {
307       return false;
308     }
309     scaleX = scale;
310     scaleY = scale;
311     return true;
312   }
313   return false;
314 }
315 
316 
gdal_transform(const QgsPointXY & src,QgsPointXY & dst,int dstToSrc) const317 bool QgsGeorefTransform::gdal_transform( const QgsPointXY &src, QgsPointXY &dst, int dstToSrc ) const
318 {
319   GDALTransformerFunc t = GDALTransformer();
320   // Fail if no transformer function was returned
321   if ( !t )
322     return false;
323 
324   // Copy the source coordinate for inplace transform
325   double x = src.x();
326   double y = src.y();
327   double z = 0.0;
328   int success = 0;
329 
330   // Call GDAL transform function
331   ( *t )( GDALTransformerArgs(), dstToSrc, 1,  &x, &y, &z, &success );
332   if ( !success )
333     return false;
334 
335   dst.setX( x );
336   dst.setY( y );
337   return true;
338 }
339 
340 
getOriginScale(QgsPointXY & origin,double & scaleX,double & scaleY) const341 bool QgsLinearGeorefTransform::getOriginScale( QgsPointXY &origin, double &scaleX, double &scaleY ) const
342 {
343   origin = mParameters.origin;
344   scaleX = mParameters.scaleX;
345   scaleY = mParameters.scaleY;
346   return true;
347 }
348 
updateParametersFromGCPs(const QVector<QgsPointXY> & mapCoords,const QVector<QgsPointXY> & pixelCoords)349 bool QgsLinearGeorefTransform::updateParametersFromGCPs( const QVector<QgsPointXY> &mapCoords, const QVector<QgsPointXY> &pixelCoords )
350 {
351   if ( mapCoords.size() < getMinimumGCPCount() )
352     return false;
353   QgsLeastSquares::linear( mapCoords, pixelCoords, mParameters.origin, mParameters.scaleX, mParameters.scaleY );
354   return true;
355 }
356 
getMinimumGCPCount() const357 int QgsLinearGeorefTransform::getMinimumGCPCount() const
358 {
359   return 2;
360 }
361 
linear_transform(void * pTransformerArg,int bDstToSrc,int nPointCount,double * x,double * y,double * z,int * panSuccess)362 int QgsLinearGeorefTransform::linear_transform( void *pTransformerArg, int bDstToSrc, int nPointCount,
363     double *x, double *y, double *z, int *panSuccess )
364 {
365   Q_UNUSED( z )
366   LinearParameters *t = static_cast<LinearParameters *>( pTransformerArg );
367   if ( !t )
368     return false;
369 
370   if ( !bDstToSrc )
371   {
372     for ( int i = 0; i < nPointCount; ++i )
373     {
374       x[i] = x[i] * t->scaleX + t->origin.x();
375       y[i] = -y[i] * t->scaleY + t->origin.y();
376       panSuccess[i] = true;
377     }
378   }
379   else
380   {
381     // Guard against division by zero
382     if ( std::fabs( t->scaleX ) < std::numeric_limits<double>::epsilon() ||
383          std::fabs( t->scaleY ) < std::numeric_limits<double>::epsilon() )
384     {
385       for ( int i = 0; i < nPointCount; ++i )
386       {
387         panSuccess[i] = false;
388       }
389       return false;
390     }
391     for ( int i = 0; i < nPointCount; ++i )
392     {
393       x[i] = ( x[i] - t->origin.x() ) / t->scaleX;
394       y[i] = ( y[i] - t->origin.y() ) / ( -t->scaleY );
395       panSuccess[i] = true;
396     }
397   }
398 
399   return true;
400 }
401 
updateParametersFromGCPs(const QVector<QgsPointXY> & mapCoords,const QVector<QgsPointXY> & pixelCoords)402 bool QgsHelmertGeorefTransform::updateParametersFromGCPs( const QVector<QgsPointXY> &mapCoords, const QVector<QgsPointXY> &pixelCoords )
403 {
404   if ( mapCoords.size() < getMinimumGCPCount() )
405     return false;
406 
407   QgsLeastSquares::helmert( mapCoords, pixelCoords, mHelmertParameters.origin, mHelmertParameters.scale, mHelmertParameters.angle );
408   return true;
409 }
410 
getMinimumGCPCount() const411 int QgsHelmertGeorefTransform::getMinimumGCPCount() const
412 {
413   return 2;
414 }
415 
416 
GDALTransformer() const417 GDALTransformerFunc QgsHelmertGeorefTransform::GDALTransformer() const
418 {
419   return QgsHelmertGeorefTransform::helmert_transform;
420 }
421 
GDALTransformerArgs() const422 void *QgsHelmertGeorefTransform::GDALTransformerArgs() const
423 {
424   return ( void * )&mHelmertParameters;
425 }
426 
getOriginScaleRotation(QgsPointXY & origin,double & scale,double & rotation) const427 bool QgsHelmertGeorefTransform::getOriginScaleRotation( QgsPointXY &origin, double &scale, double &rotation ) const
428 {
429   origin = mHelmertParameters.origin;
430   scale = mHelmertParameters.scale;
431   rotation = mHelmertParameters.angle;
432   return true;
433 }
434 
helmert_transform(void * pTransformerArg,int bDstToSrc,int nPointCount,double * x,double * y,double * z,int * panSuccess)435 int QgsHelmertGeorefTransform::helmert_transform( void *pTransformerArg, int bDstToSrc, int nPointCount,
436     double *x, double *y, double *z, int *panSuccess )
437 {
438   Q_UNUSED( z )
439   HelmertParameters *t = static_cast<HelmertParameters *>( pTransformerArg );
440   if ( !t )
441     return false;
442 
443   double a = std::cos( t->angle ), b = std::sin( t->angle ), x0 = t->origin.x(), y0 = t->origin.y(), s = t->scale;
444   if ( !bDstToSrc )
445   {
446     a *= s;
447     b *= s;
448     for ( int i = 0; i < nPointCount; ++i )
449     {
450       double xT = x[i], yT = y[i];
451       // Because rotation parameters where estimated in a CS with negative y-axis ^= down.
452       // we need to apply the rotation matrix and a change of base:
453       // |cos a,-sin a| |1, 0|   | std::cos a,  std::sin a|
454       // |sin a, std::cos a| |0,-1| = | std::sin a, -cos a|
455       x[i] = x0 + ( a * xT + b * yT );
456       y[i] = y0 + ( b * xT - a * yT );
457       panSuccess[i] = true;
458     }
459   }
460   else
461   {
462     // Guard against division by zero
463     if ( std::fabs( s ) < std::numeric_limits<double>::epsilon() )
464     {
465       for ( int i = 0; i < nPointCount; ++i )
466       {
467         panSuccess[i] = false;
468       }
469       return false;
470     }
471     a /= s;
472     b /= s;
473     for ( int i = 0; i < nPointCount; ++i )
474     {
475       double xT = x[i], yT = y[i];
476       xT -= x0;
477       yT -= y0;
478       // | std::cos a,  std::sin a |^-1   |cos a,  std::sin a|
479       // | std::sin a, -cos a |    = |sin a, -cos a|
480       x[i] = a * xT + b * yT;
481       y[i] = b * xT - a * yT;
482       panSuccess[i] = true;
483     }
484   }
485   return true;
486 }
487 
QgsGDALGeorefTransform(bool useTPS,unsigned int polynomialOrder)488 QgsGDALGeorefTransform::QgsGDALGeorefTransform( bool useTPS, unsigned int polynomialOrder )
489   : mPolynomialOrder( std::min( 3u, polynomialOrder ) )
490   , mIsTPSTransform( useTPS )
491 {
492   mGDALTransformer     = nullptr;
493   mGDALTransformerArgs = nullptr;
494 }
495 
~QgsGDALGeorefTransform()496 QgsGDALGeorefTransform::~QgsGDALGeorefTransform()
497 {
498   destroy_gdal_args();
499 }
500 
updateParametersFromGCPs(const QVector<QgsPointXY> & mapCoords,const QVector<QgsPointXY> & pixelCoords)501 bool QgsGDALGeorefTransform::updateParametersFromGCPs( const QVector<QgsPointXY> &mapCoords, const QVector<QgsPointXY> &pixelCoords )
502 {
503   assert( mapCoords.size() == pixelCoords.size() );
504   if ( mapCoords.size() != pixelCoords.size() )
505     return false;
506   int n = mapCoords.size();
507 
508   GDAL_GCP *GCPList = new GDAL_GCP[n];
509   for ( int i = 0; i < n; i++ )
510   {
511     GCPList[i].pszId = new char[20];
512     snprintf( GCPList[i].pszId, 19, "gcp%i", i );
513     GCPList[i].pszInfo = nullptr;
514     GCPList[i].dfGCPPixel = pixelCoords[i].x();
515     GCPList[i].dfGCPLine  = -pixelCoords[i].y();
516     GCPList[i].dfGCPX = mapCoords[i].x();
517     GCPList[i].dfGCPY = mapCoords[i].y();
518     GCPList[i].dfGCPZ = 0;
519   }
520   destroy_gdal_args();
521 
522   if ( mIsTPSTransform )
523     mGDALTransformerArgs = GDALCreateTPSTransformer( n, GCPList, false );
524   else
525     mGDALTransformerArgs = GDALCreateGCPTransformer( n, GCPList, mPolynomialOrder, false );
526 
527   for ( int i = 0; i < n; i++ )
528   {
529     delete [] GCPList[i].pszId;
530   }
531   delete [] GCPList;
532 
533   return nullptr != mGDALTransformerArgs;
534 }
535 
getMinimumGCPCount() const536 int QgsGDALGeorefTransform::getMinimumGCPCount() const
537 {
538   if ( mIsTPSTransform )
539     return 1;
540   else
541     return ( ( mPolynomialOrder + 2 ) * ( mPolynomialOrder + 1 ) ) / 2;
542 }
543 
GDALTransformer() const544 GDALTransformerFunc QgsGDALGeorefTransform::GDALTransformer() const
545 {
546   // Fail if no arguments were calculated through updateParametersFromGCP
547   if ( !mGDALTransformerArgs )
548     return nullptr;
549 
550   if ( mIsTPSTransform )
551     return GDALTPSTransform;
552   else
553     return GDALGCPTransform;
554 }
555 
GDALTransformerArgs() const556 void *QgsGDALGeorefTransform::GDALTransformerArgs() const
557 {
558   return mGDALTransformerArgs;
559 }
560 
destroy_gdal_args()561 void QgsGDALGeorefTransform::destroy_gdal_args()
562 {
563   if ( mGDALTransformerArgs )
564   {
565     if ( mIsTPSTransform )
566       GDALDestroyTPSTransformer( mGDALTransformerArgs );
567     else
568       GDALDestroyGCPTransformer( mGDALTransformerArgs );
569   }
570 }
571 
updateParametersFromGCPs(const QVector<QgsPointXY> & mapCoords,const QVector<QgsPointXY> & pixelCoords)572 bool QgsProjectiveGeorefTransform::updateParametersFromGCPs( const QVector<QgsPointXY> &mapCoords, const QVector<QgsPointXY> &pixelCoords )
573 {
574   if ( mapCoords.size() < getMinimumGCPCount() )
575     return false;
576 
577   // HACK: flip y coordinates, because georeferencer and gdal use different conventions
578   QVector<QgsPointXY> flippedPixelCoords;
579   flippedPixelCoords.reserve( pixelCoords.size() );
580   for ( const QgsPointXY &coord : pixelCoords )
581   {
582     flippedPixelCoords << QgsPointXY( coord.x(), -coord.y() );
583   }
584 
585   QgsLeastSquares::projective( mapCoords, flippedPixelCoords, mParameters.H );
586 
587   // Invert the homography matrix using adjoint matrix
588   double *H = mParameters.H;
589 
590   double adjoint[9];
591   adjoint[0] = H[4] * H[8] - H[5] * H[7];
592   adjoint[1] = -H[1] * H[8] + H[2] * H[7];
593   adjoint[2] = H[1] * H[5] - H[2] * H[4];
594 
595   adjoint[3] = -H[3] * H[8] + H[5] * H[6];
596   adjoint[4] = H[0] * H[8] - H[2] * H[6];
597   adjoint[5] = -H[0] * H[5] + H[2] * H[3];
598 
599   adjoint[6] = H[3] * H[7] - H[4] * H[6];
600   adjoint[7] = -H[0] * H[7] + H[1] * H[6];
601   adjoint[8] = H[0] * H[4] - H[1] * H[3];
602 
603   double det = H[0] * adjoint[0] + H[3] * adjoint[1] + H[6] * adjoint[2];
604 
605   if ( std::fabs( det ) < 1024.0 * std::numeric_limits<double>::epsilon() )
606   {
607     mParameters.hasInverse = false;
608   }
609   else
610   {
611     mParameters.hasInverse = true;
612     double oo_det = 1.0 / det;
613     for ( int i = 0; i < 9; i++ )
614     {
615       mParameters.Hinv[i] = adjoint[i] * oo_det;
616     }
617   }
618   return true;
619 }
620 
getMinimumGCPCount() const621 int QgsProjectiveGeorefTransform::getMinimumGCPCount() const
622 {
623   return 4;
624 }
625 
projective_transform(void * pTransformerArg,int bDstToSrc,int nPointCount,double * x,double * y,double * z,int * panSuccess)626 int QgsProjectiveGeorefTransform::projective_transform( void *pTransformerArg, int bDstToSrc, int nPointCount,
627     double *x, double *y, double *z, int *panSuccess )
628 {
629   Q_UNUSED( z )
630   ProjectiveParameters *t = static_cast<ProjectiveParameters *>( pTransformerArg );
631   if ( !t )
632     return false;
633 
634   double *H = nullptr;
635   if ( !bDstToSrc )
636   {
637     H = t->H;
638   }
639   else
640   {
641     if ( !t->hasInverse )
642     {
643       for ( int i = 0; i < nPointCount; ++i )
644       {
645         panSuccess[i] = false;
646       }
647       return false;
648     }
649     H = t->Hinv;
650   }
651 
652 
653   for ( int i = 0; i < nPointCount; ++i )
654   {
655     double Z = x[i] * H[6] + y[i] * H[7] + H[8];
656     // Projects to infinity?
657     if ( std::fabs( Z ) < 1024.0 * std::numeric_limits<double>::epsilon() )
658     {
659       panSuccess[i] = false;
660       continue;
661     }
662     double X = ( x[i] * H[0] + y[i] * H[1] + H[2] ) / Z;
663     double Y = ( x[i] * H[3] + y[i] * H[4] + H[5] ) / Z;
664 
665     x[i] = X;
666     y[i] = Y;
667 
668     panSuccess[i] = true;
669   }
670 
671   return true;
672 }
673