QGIS API Documentation 3.41.0-Master (af5edcb665c)
Loading...
Searching...
No Matches
qgsgcptransformer.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsgcptransformer.cpp
3 --------------------------------------
4 Date : 18-Feb-2009
5 Copyright : (c) 2009 by Manuel Massing
6 Email : m.massing at warped-space.de
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 "qgsgcptransformer.h"
17#include "moc_qgsgcptransformer.cpp"
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
30bool QgsGcpTransformerInterface::transform( double &x, double &y, bool inverseTransform ) const
31{
32 GDALTransformerFunc t = GDALTransformer();
33 // Fail if no transformer function was returned
34 if ( !t )
35 return false;
36
37 double z = 0.0;
38 int success = 0;
39
40 // Call GDAL transform function
41 ( *t )( GDALTransformerArgs(), inverseTransform ? 1 : 0, 1, &x, &y, &z, &success );
42 if ( !success )
43 return false;
44
45 return true;
46}
47
49{
50 switch ( method )
51 {
53 return QObject::tr( "Linear" );
55 return QObject::tr( "Helmert" );
57 return QObject::tr( "Polynomial 1" );
59 return QObject::tr( "Polynomial 2" );
61 return QObject::tr( "Polynomial 3" );
63 return QObject::tr( "Thin Plate Spline (TPS)" );
65 return QObject::tr( "Projective" );
66 default:
67 return QObject::tr( "Not set" );
68 }
69}
70
93
94QgsGcpTransformerInterface *QgsGcpTransformerInterface::createFromParameters( QgsGcpTransformerInterface::TransformMethod method, const QVector<QgsPointXY> &sourceCoordinates, const QVector<QgsPointXY> &destinationCoordinates )
95{
96 std::unique_ptr<QgsGcpTransformerInterface> transformer( create( method ) );
97 if ( !transformer )
98 return nullptr;
99
100 if ( !transformer->updateParametersFromGcps( sourceCoordinates, destinationCoordinates ) )
101 return nullptr;
102
103 return transformer.release();
104}
105
106
107//
108// QgsLinearGeorefTransform
109//
110
111bool QgsLinearGeorefTransform::getOriginScale( QgsPointXY &origin, double &scaleX, double &scaleY ) const
112{
113 origin = mParameters.origin;
114 scaleX = mParameters.scaleX;
115 scaleY = mParameters.scaleY;
116 return true;
117}
118
120{
121 std::unique_ptr<QgsLinearGeorefTransform> res = std::make_unique<QgsLinearGeorefTransform>();
122 res->mParameters = mParameters;
123 return res.release();
124}
125
126bool QgsLinearGeorefTransform::updateParametersFromGcps( const QVector<QgsPointXY> &sourceCoordinates, const QVector<QgsPointXY> &destinationCoordinates, bool invertYAxis )
127{
128 if ( destinationCoordinates.size() < minimumGcpCount() )
129 return false;
130
131 mParameters.invertYAxis = invertYAxis;
132 QgsLeastSquares::linear( sourceCoordinates, destinationCoordinates, mParameters.origin, mParameters.scaleX, mParameters.scaleY );
133 return true;
134}
135
137{
138 return 2;
139}
140
142{
143 return QgsLinearGeorefTransform::linearTransform;
144}
145
147{
148 return ( void * ) &mParameters;
149}
150
155
156int QgsLinearGeorefTransform::linearTransform( void *pTransformerArg, int bDstToSrc, int nPointCount, double *x, double *y, double *z, int *panSuccess )
157{
158 Q_UNUSED( z )
159 LinearParameters *t = static_cast<LinearParameters *>( pTransformerArg );
160 if ( !t )
161 return false;
162
163 if ( !bDstToSrc )
164 {
165 for ( int i = 0; i < nPointCount; ++i )
166 {
167 x[i] = x[i] * t->scaleX + t->origin.x();
168 y[i] = ( t->invertYAxis ? -1 : 1 ) * y[i] * t->scaleY + t->origin.y();
169 panSuccess[i] = true;
170 }
171 }
172 else
173 {
174 // Guard against division by zero
175 if ( std::fabs( t->scaleX ) < std::numeric_limits<double>::epsilon() || std::fabs( t->scaleY ) < std::numeric_limits<double>::epsilon() )
176 {
177 for ( int i = 0; i < nPointCount; ++i )
178 {
179 panSuccess[i] = false;
180 }
181 return false;
182 }
183 for ( int i = 0; i < nPointCount; ++i )
184 {
185 x[i] = ( x[i] - t->origin.x() ) / t->scaleX;
186 y[i] = ( y[i] - t->origin.y() ) / ( ( t->invertYAxis ? -1 : 1 ) * t->scaleY );
187 panSuccess[i] = true;
188 }
189 }
190
191 return true;
192}
193
194//
195// QgsHelmertGeorefTransform
196//
197bool QgsHelmertGeorefTransform::updateParametersFromGcps( const QVector<QgsPointXY> &sourceCoordinates, const QVector<QgsPointXY> &destinationCoordinates, bool invertYAxis )
198{
199 if ( destinationCoordinates.size() < minimumGcpCount() )
200 return false;
201
202 mHelmertParameters.invertYAxis = invertYAxis;
203 QgsLeastSquares::helmert( sourceCoordinates, destinationCoordinates, mHelmertParameters.origin, mHelmertParameters.scale, mHelmertParameters.angle );
204 return true;
205}
206
208{
209 return 2;
210}
211
213{
214 return QgsHelmertGeorefTransform::helmertTransform;
215}
216
218{
219 return ( void * ) &mHelmertParameters;
220}
221
226
227bool QgsHelmertGeorefTransform::getOriginScaleRotation( QgsPointXY &origin, double &scale, double &rotation ) const
228{
229 origin = mHelmertParameters.origin;
230 scale = mHelmertParameters.scale;
231 rotation = mHelmertParameters.angle;
232 return true;
233}
234
236{
237 std::unique_ptr<QgsHelmertGeorefTransform> res = std::make_unique<QgsHelmertGeorefTransform>();
238 res->mHelmertParameters = mHelmertParameters;
239 return res.release();
240}
241
242int QgsHelmertGeorefTransform::helmertTransform( void *pTransformerArg, int bDstToSrc, int nPointCount, double *x, double *y, double *z, int *panSuccess )
243{
244 Q_UNUSED( z )
245 const HelmertParameters *t = static_cast<const HelmertParameters *>( pTransformerArg );
246 if ( !t )
247 return false;
248
249 double a = std::cos( t->angle );
250 double b = std::sin( t->angle );
251 const double x0 = t->origin.x();
252 const double y0 = t->origin.y();
253 const double s = t->scale;
254 if ( !bDstToSrc )
255 {
256 a *= s;
257 b *= s;
258 for ( int i = 0; i < nPointCount; ++i )
259 {
260 const double xT = x[i];
261 const double yT = y[i];
262
263 if ( t->invertYAxis )
264 {
265 // Because rotation parameters where estimated in a CS with negative y-axis ^= down.
266 // we need to apply the rotation matrix and a change of base:
267 // |cos a, -sin a | |1, 0| | cos a, sin a|
268 // |sin a, cos a | |0,-1| = | sin a, -cos a|
269 x[i] = x0 + ( a * xT + b * yT );
270 y[i] = y0 + ( b * xT - a * yT );
271 }
272 else
273 {
274 x[i] = x0 + ( a * xT - b * yT );
275 y[i] = y0 + ( b * xT + a * yT );
276 }
277 panSuccess[i] = true;
278 }
279 }
280 else
281 {
282 // Guard against division by zero
283 if ( std::fabs( s ) < std::numeric_limits<double>::epsilon() )
284 {
285 for ( int i = 0; i < nPointCount; ++i )
286 {
287 panSuccess[i] = false;
288 }
289 return false;
290 }
291 a /= s;
292 b /= s;
293 for ( int i = 0; i < nPointCount; ++i )
294 {
295 const double xT = x[i] - x0;
296 const double yT = y[i] - y0;
297 if ( t->invertYAxis )
298 {
299 // | cos a, sin a |^-1 |cos a, sin a|
300 // | sin a, -cos a | = |sin a, -cos a|
301 x[i] = a * xT + b * yT;
302 y[i] = b * xT - a * yT;
303 }
304 else
305 {
306 x[i] = a * xT + b * yT;
307 y[i] = -b * xT + a * yT;
308 }
309 panSuccess[i] = true;
310 }
311 }
312 return true;
313}
314
315//
316// QgsGDALGeorefTransform
317//
318
319QgsGDALGeorefTransform::QgsGDALGeorefTransform( bool useTPS, unsigned int polynomialOrder )
320 : mPolynomialOrder( std::min( 3u, polynomialOrder ) )
321 , mIsTPSTransform( useTPS )
322{
323}
324
326{
327 destroyGdalArgs();
328}
329
331{
332 std::unique_ptr<QgsGDALGeorefTransform> res = std::make_unique<QgsGDALGeorefTransform>( mIsTPSTransform, mPolynomialOrder );
333 res->updateParametersFromGcps( mSourceCoords, mDestCoordinates, mInvertYAxis );
334 return res.release();
335}
336
337bool QgsGDALGeorefTransform::updateParametersFromGcps( const QVector<QgsPointXY> &sourceCoordinates, const QVector<QgsPointXY> &destinationCoordinates, bool invertYAxis )
338{
339 mSourceCoords = sourceCoordinates;
340 mDestCoordinates = destinationCoordinates;
341 mInvertYAxis = invertYAxis;
342
343 assert( sourceCoordinates.size() == destinationCoordinates.size() );
344 if ( sourceCoordinates.size() != destinationCoordinates.size() )
345 return false;
346 const int n = sourceCoordinates.size();
347
348 GDAL_GCP *GCPList = new GDAL_GCP[n];
349 for ( int i = 0; i < n; i++ )
350 {
351 GCPList[i].pszId = new char[20];
352 snprintf( GCPList[i].pszId, 19, "gcp%i", i );
353 GCPList[i].pszInfo = nullptr;
354 GCPList[i].dfGCPPixel = sourceCoordinates[i].x();
355 GCPList[i].dfGCPLine = ( mInvertYAxis ? -1 : 1 ) * sourceCoordinates[i].y();
356 GCPList[i].dfGCPX = destinationCoordinates[i].x();
357 GCPList[i].dfGCPY = destinationCoordinates[i].y();
358 GCPList[i].dfGCPZ = 0;
359 }
360 destroyGdalArgs();
361
362 if ( mIsTPSTransform )
363 mGDALTransformerArgs = GDALCreateTPSTransformer( n, GCPList, false );
364 else
365 mGDALTransformerArgs = GDALCreateGCPTransformer( n, GCPList, mPolynomialOrder, false );
366
367 for ( int i = 0; i < n; i++ )
368 {
369 delete[] GCPList[i].pszId;
370 }
371 delete[] GCPList;
372
373 return nullptr != mGDALTransformerArgs;
374}
375
377{
378 if ( mIsTPSTransform )
379 return 1;
380 else
381 return ( ( mPolynomialOrder + 2 ) * ( mPolynomialOrder + 1 ) ) / 2;
382}
383
385{
386 // Fail if no arguments were calculated through updateParametersFromGCP
387 if ( !mGDALTransformerArgs )
388 return nullptr;
389
390 if ( mIsTPSTransform )
391 return GDALTPSTransform;
392 else
393 return GDALGCPTransform;
394}
395
397{
398 return mGDALTransformerArgs;
399}
400
402{
403 if ( mIsTPSTransform )
405
406 switch ( mPolynomialOrder )
407 {
408 case 1:
410 case 2:
412 case 3:
414 }
416}
417
418void QgsGDALGeorefTransform::destroyGdalArgs()
419{
420 if ( mGDALTransformerArgs )
421 {
422 if ( mIsTPSTransform )
423 GDALDestroyTPSTransformer( mGDALTransformerArgs );
424 else
425 GDALDestroyGCPTransformer( mGDALTransformerArgs );
426 }
427}
428
429//
430// QgsProjectiveGeorefTransform
431//
432
436
438{
439 std::unique_ptr<QgsProjectiveGeorefTransform> res = std::make_unique<QgsProjectiveGeorefTransform>();
440 res->mParameters = mParameters;
441 return res.release();
442}
443
444bool QgsProjectiveGeorefTransform::updateParametersFromGcps( const QVector<QgsPointXY> &sourceCoordinates, const QVector<QgsPointXY> &destinationCoordinates, bool invertYAxis )
445{
446 if ( destinationCoordinates.size() < minimumGcpCount() )
447 return false;
448
449 if ( invertYAxis )
450 {
451 // HACK: flip y coordinates, because georeferencer and gdal use different conventions
452 QVector<QgsPointXY> flippedPixelCoords;
453 flippedPixelCoords.reserve( sourceCoordinates.size() );
454 for ( const QgsPointXY &coord : sourceCoordinates )
455 {
456 flippedPixelCoords << QgsPointXY( coord.x(), -coord.y() );
457 }
458
459 QgsLeastSquares::projective( flippedPixelCoords, destinationCoordinates, mParameters.H );
460 }
461 else
462 {
463 QgsLeastSquares::projective( sourceCoordinates, destinationCoordinates, mParameters.H );
464 }
465
466 // Invert the homography matrix using adjoint matrix
467 double *H = mParameters.H;
468
469 double adjoint[9];
470 adjoint[0] = H[4] * H[8] - H[5] * H[7];
471 adjoint[1] = -H[1] * H[8] + H[2] * H[7];
472 adjoint[2] = H[1] * H[5] - H[2] * H[4];
473
474 adjoint[3] = -H[3] * H[8] + H[5] * H[6];
475 adjoint[4] = H[0] * H[8] - H[2] * H[6];
476 adjoint[5] = -H[0] * H[5] + H[2] * H[3];
477
478 adjoint[6] = H[3] * H[7] - H[4] * H[6];
479 adjoint[7] = -H[0] * H[7] + H[1] * H[6];
480 adjoint[8] = H[0] * H[4] - H[1] * H[3];
481
482 const double det = H[0] * adjoint[0] + H[3] * adjoint[1] + H[6] * adjoint[2];
483
484 if ( std::fabs( det ) < 1024.0 * std::numeric_limits<double>::epsilon() )
485 {
486 mParameters.hasInverse = false;
487 }
488 else
489 {
490 mParameters.hasInverse = true;
491 const double oo_det = 1.0 / det;
492 for ( int i = 0; i < 9; i++ )
493 {
494 mParameters.Hinv[i] = adjoint[i] * oo_det;
495 }
496 }
497 return true;
498}
499
501{
502 return 4;
503}
504
506{
507 return QgsProjectiveGeorefTransform::projectiveTransform;
508}
509
511{
512 return ( void * ) &mParameters;
513}
514
519
520int QgsProjectiveGeorefTransform::projectiveTransform( void *pTransformerArg, int bDstToSrc, int nPointCount, double *x, double *y, double *z, int *panSuccess )
521{
522 Q_UNUSED( z )
523 ProjectiveParameters *t = static_cast<ProjectiveParameters *>( pTransformerArg );
524 if ( !t )
525 return false;
526
527 double *H = nullptr;
528 if ( !bDstToSrc )
529 {
530 H = t->H;
531 }
532 else
533 {
534 if ( !t->hasInverse )
535 {
536 for ( int i = 0; i < nPointCount; ++i )
537 {
538 panSuccess[i] = false;
539 }
540 return false;
541 }
542 H = t->Hinv;
543 }
544
545
546 for ( int i = 0; i < nPointCount; ++i )
547 {
548 const double Z = x[i] * H[6] + y[i] * H[7] + H[8];
549 // Projects to infinity?
550 if ( std::fabs( Z ) < 1024.0 * std::numeric_limits<double>::epsilon() )
551 {
552 panSuccess[i] = false;
553 continue;
554 }
555 const double X = ( x[i] * H[0] + y[i] * H[1] + H[2] ) / Z;
556 const double Y = ( x[i] * H[3] + y[i] * H[4] + H[5] ) / Z;
557
558 x[i] = X;
559 y[i] = Y;
560
561 panSuccess[i] = true;
562 }
563
564 return true;
565}
Interface to gdal thin plate splines and 1st/2nd/3rd order polynomials.
int minimumGcpCount() const override
Returns the minimum number of Ground Control Points (GCPs) required for parameter fitting.
TransformMethod method() const override
Returns the transformation method.
QgsGcpTransformerInterface * clone() const override
Clones the transformer, returning a new copy of the transformer with the same parameters as this one.
QgsGDALGeorefTransform(bool useTPS, unsigned int polynomialOrder)
Constructor for QgsGDALGeorefTransform.
GDALTransformerFunc GDALTransformer() const override
Returns function pointer to the GDALTransformer function.
void * GDALTransformerArgs() const override
Returns pointer to the GDALTransformer arguments.
bool updateParametersFromGcps(const QVector< QgsPointXY > &sourceCoordinates, const QVector< QgsPointXY > &destinationCoordinates, bool invertYAxis=false) override
Fits transformation parameters using the specified Ground Control Points (GCPs) lists of source and d...
An interface for Ground Control Points (GCP) based transformations.
bool transform(double &x, double &y, bool inverseTransform=false) const
Transforms the point (x, y) from source to destination coordinates.
TransformMethod
Available transformation methods.
static QgsGcpTransformerInterface * create(TransformMethod method)
Creates a new QgsGcpTransformerInterface subclass representing the specified transform method.
static QString methodToString(TransformMethod method)
Returns a translated string representing the specified transform method.
virtual void * GDALTransformerArgs() const =0
Returns pointer to the GDALTransformer arguments.
virtual GDALTransformerFunc GDALTransformer() const =0
Returns function pointer to the GDALTransformer function.
virtual TransformMethod method() const =0
Returns the transformation method.
static QgsGcpTransformerInterface * createFromParameters(TransformMethod method, const QVector< QgsPointXY > &sourceCoordinates, const QVector< QgsPointXY > &destinationCoordinates)
Creates a new QgsGcpTransformerInterface subclass representing the specified transform method,...
2-dimensional helmert transform, parametrised by isotropic scale, rotation angle and translation.
TransformMethod method() const override
Returns the transformation method.
QgsGcpTransformerInterface * clone() const override
Clones the transformer, returning a new copy of the transformer with the same parameters as this one.
bool getOriginScaleRotation(QgsPointXY &origin, double &scale, double &rotation) const
Returns the origin, scale and rotation for the transform.
GDALTransformerFunc GDALTransformer() const override
Returns function pointer to the GDALTransformer function.
int minimumGcpCount() const override
Returns the minimum number of Ground Control Points (GCPs) required for parameter fitting.
bool updateParametersFromGcps(const QVector< QgsPointXY > &sourceCoordinates, const QVector< QgsPointXY > &destinationCoordinates, bool invertYAxis=false) override
Fits transformation parameters using the specified Ground Control Points (GCPs) lists of source and d...
void * GDALTransformerArgs() const override
Returns pointer to the GDALTransformer arguments.
static void helmert(const QVector< QgsPointXY > &sourceCoordinates, const QVector< QgsPointXY > &destinationCoordinates, QgsPointXY &origin, double &pixelSize, double &rotation)
Transforms the point at origin in-place, using a helmert transformation calculated from the list of s...
static void projective(const QVector< QgsPointXY > &sourceCoordinates, const QVector< QgsPointXY > &destinationCoordinates, double H[9])
Calculates projective parameters from the list of source and destination Ground Control Points (GCPs)...
static void linear(const QVector< QgsPointXY > &sourceCoordinates, const QVector< QgsPointXY > &destinationCoordinates, QgsPointXY &origin, double &pixelXSize, double &pixelYSize)
Transforms the point at origin in-place, using a linear transformation calculated from the list of so...
A simple transform which is parametrized by a translation and anistotropic scale.
bool getOriginScale(QgsPointXY &origin, double &scaleX, double &scaleY) const
Returns the origin and scale for the transform.
TransformMethod method() const override
Returns the transformation method.
void * GDALTransformerArgs() const override
Returns pointer to the GDALTransformer arguments.
QgsGcpTransformerInterface * clone() const override
Clones the transformer, returning a new copy of the transformer with the same parameters as this one.
bool updateParametersFromGcps(const QVector< QgsPointXY > &sourceCoordinates, const QVector< QgsPointXY > &destinationCoordinates, bool invertYAxis=false) override
Fits transformation parameters using the specified Ground Control Points (GCPs) lists of source and d...
int minimumGcpCount() const override
Returns the minimum number of Ground Control Points (GCPs) required for parameter fitting.
GDALTransformerFunc GDALTransformer() const override
Returns function pointer to the GDALTransformer function.
A class to represent a 2D point.
Definition qgspointxy.h:60
A planar projective transform, expressed by a homography.
int minimumGcpCount() const override
Returns the minimum number of Ground Control Points (GCPs) required for parameter fitting.
QgsGcpTransformerInterface * clone() const override
Clones the transformer, returning a new copy of the transformer with the same parameters as this one.
bool updateParametersFromGcps(const QVector< QgsPointXY > &sourceCoordinates, const QVector< QgsPointXY > &destinationCoordinates, bool invertYAxis=false) override
Fits transformation parameters using the specified Ground Control Points (GCPs) lists of source and d...
GDALTransformerFunc GDALTransformer() const override
Returns function pointer to the GDALTransformer function.
void * GDALTransformerArgs() const override
Returns pointer to the GDALTransformer arguments.
TransformMethod method() const override
Returns the transformation method.