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