QGIS API Documentation 3.99.0-Master (2fe06baccd8)
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
18#include <cassert>
19#include <cmath>
20#include <gdal.h>
21#include <gdal_alg.h>
22#include <limits>
23
24#include "qgsleastsquares.h"
25
26#include "moc_qgsgcptransformer.cpp"
27
28bool QgsGcpTransformerInterface::transform( double &x, double &y, bool inverseTransform ) const
29{
30 GDALTransformerFunc t = GDALTransformer();
31 // Fail if no transformer function was returned
32 if ( !t )
33 return false;
34
35 double z = 0.0;
36 int success = 0;
37
38 // Call GDAL transform function
39 ( *t )( GDALTransformerArgs(), inverseTransform ? 1 : 0, 1, &x, &y, &z, &success );
40 if ( !success )
41 return false;
42
43 return true;
44}
45
47{
48 switch ( method )
49 {
51 return QObject::tr( "Linear" );
53 return QObject::tr( "Helmert" );
55 return QObject::tr( "Polynomial 1" );
57 return QObject::tr( "Polynomial 2" );
59 return QObject::tr( "Polynomial 3" );
61 return QObject::tr( "Thin Plate Spline (TPS)" );
63 return QObject::tr( "Projective" );
64 default:
65 return QObject::tr( "Not set" );
66 }
67}
68
91
92QgsGcpTransformerInterface *QgsGcpTransformerInterface::createFromParameters( QgsGcpTransformerInterface::TransformMethod method, const QVector<QgsPointXY> &sourceCoordinates, const QVector<QgsPointXY> &destinationCoordinates )
93{
94 std::unique_ptr<QgsGcpTransformerInterface> transformer( create( method ) );
95 if ( !transformer )
96 return nullptr;
97
98 if ( !transformer->updateParametersFromGcps( sourceCoordinates, destinationCoordinates ) )
99 return nullptr;
100
101 return transformer.release();
102}
103
104
105//
106// QgsLinearGeorefTransform
107//
108
109bool QgsLinearGeorefTransform::getOriginScale( QgsPointXY &origin, double &scaleX, double &scaleY ) const
110{
111 origin = mParameters.origin;
112 scaleX = mParameters.scaleX;
113 scaleY = mParameters.scaleY;
114 return true;
115}
116
118{
119 auto res = std::make_unique<QgsLinearGeorefTransform>();
120 res->mParameters = mParameters;
121 return res.release();
122}
123
124bool QgsLinearGeorefTransform::updateParametersFromGcps( const QVector<QgsPointXY> &sourceCoordinates, const QVector<QgsPointXY> &destinationCoordinates, bool invertYAxis )
125{
126 if ( destinationCoordinates.size() < minimumGcpCount() )
127 return false;
128
129 mParameters.invertYAxis = invertYAxis;
130 QgsLeastSquares::linear( sourceCoordinates, destinationCoordinates, mParameters.origin, mParameters.scaleX, mParameters.scaleY );
131 return true;
132}
133
135{
136 return 2;
137}
138
140{
141 return QgsLinearGeorefTransform::linearTransform;
142}
143
145{
146 return ( void * ) &mParameters;
147}
148
153
154int QgsLinearGeorefTransform::linearTransform( void *pTransformerArg, int bDstToSrc, int nPointCount, double *x, double *y, double *z, int *panSuccess )
155{
156 Q_UNUSED( z )
157 LinearParameters *t = static_cast<LinearParameters *>( pTransformerArg );
158 if ( !t )
159 return false;
160
161 if ( !bDstToSrc )
162 {
163 for ( int i = 0; i < nPointCount; ++i )
164 {
165 x[i] = x[i] * t->scaleX + t->origin.x();
166 y[i] = ( t->invertYAxis ? -1 : 1 ) * y[i] * t->scaleY + t->origin.y();
167 panSuccess[i] = true;
168 }
169 }
170 else
171 {
172 // Guard against division by zero
173 if ( std::fabs( t->scaleX ) < std::numeric_limits<double>::epsilon() || std::fabs( t->scaleY ) < std::numeric_limits<double>::epsilon() )
174 {
175 for ( int i = 0; i < nPointCount; ++i )
176 {
177 panSuccess[i] = false;
178 }
179 return false;
180 }
181 for ( int i = 0; i < nPointCount; ++i )
182 {
183 x[i] = ( x[i] - t->origin.x() ) / t->scaleX;
184 y[i] = ( y[i] - t->origin.y() ) / ( ( t->invertYAxis ? -1 : 1 ) * t->scaleY );
185 panSuccess[i] = true;
186 }
187 }
188
189 return true;
190}
191
192//
193// QgsHelmertGeorefTransform
194//
195bool QgsHelmertGeorefTransform::updateParametersFromGcps( const QVector<QgsPointXY> &sourceCoordinates, const QVector<QgsPointXY> &destinationCoordinates, bool invertYAxis )
196{
197 if ( destinationCoordinates.size() < minimumGcpCount() )
198 return false;
199
200 mHelmertParameters.invertYAxis = invertYAxis;
201 QgsLeastSquares::helmert( sourceCoordinates, destinationCoordinates, mHelmertParameters.origin, mHelmertParameters.scale, mHelmertParameters.angle );
202 return true;
203}
204
206{
207 return 2;
208}
209
211{
212 return QgsHelmertGeorefTransform::helmertTransform;
213}
214
216{
217 return ( void * ) &mHelmertParameters;
218}
219
224
225bool QgsHelmertGeorefTransform::getOriginScaleRotation( QgsPointXY &origin, double &scale, double &rotation ) const
226{
227 origin = mHelmertParameters.origin;
228 scale = mHelmertParameters.scale;
229 rotation = mHelmertParameters.angle;
230 return true;
231}
232
234{
235 auto res = std::make_unique<QgsHelmertGeorefTransform>();
236 res->mHelmertParameters = mHelmertParameters;
237 return res.release();
238}
239
240int QgsHelmertGeorefTransform::helmertTransform( void *pTransformerArg, int bDstToSrc, int nPointCount, double *x, double *y, double *z, int *panSuccess )
241{
242 Q_UNUSED( z )
243 const HelmertParameters *t = static_cast<const HelmertParameters *>( pTransformerArg );
244 if ( !t )
245 return false;
246
247 double a = std::cos( t->angle );
248 double b = std::sin( t->angle );
249 const double x0 = t->origin.x();
250 const double y0 = t->origin.y();
251 const double s = t->scale;
252 if ( !bDstToSrc )
253 {
254 a *= s;
255 b *= s;
256 for ( int i = 0; i < nPointCount; ++i )
257 {
258 const double xT = x[i];
259 const double yT = y[i];
260
261 if ( t->invertYAxis )
262 {
263 // Because rotation parameters where estimated in a CS with negative y-axis ^= down.
264 // we need to apply the rotation matrix and a change of base:
265 // |cos a, -sin a | |1, 0| | cos a, sin a|
266 // |sin a, cos a | |0,-1| = | sin a, -cos a|
267 x[i] = x0 + ( a * xT + b * yT );
268 y[i] = y0 + ( b * xT - a * yT );
269 }
270 else
271 {
272 x[i] = x0 + ( a * xT - b * yT );
273 y[i] = y0 + ( b * xT + a * yT );
274 }
275 panSuccess[i] = true;
276 }
277 }
278 else
279 {
280 // Guard against division by zero
281 if ( std::fabs( s ) < std::numeric_limits<double>::epsilon() )
282 {
283 for ( int i = 0; i < nPointCount; ++i )
284 {
285 panSuccess[i] = false;
286 }
287 return false;
288 }
289 a /= s;
290 b /= s;
291 for ( int i = 0; i < nPointCount; ++i )
292 {
293 const double xT = x[i] - x0;
294 const double yT = y[i] - y0;
295 if ( t->invertYAxis )
296 {
297 // | cos a, sin a |^-1 |cos a, sin a|
298 // | sin a, -cos a | = |sin a, -cos a|
299 x[i] = a * xT + b * yT;
300 y[i] = b * xT - a * yT;
301 }
302 else
303 {
304 x[i] = a * xT + b * yT;
305 y[i] = -b * xT + a * yT;
306 }
307 panSuccess[i] = true;
308 }
309 }
310 return true;
311}
312
313//
314// QgsGDALGeorefTransform
315//
316
317QgsGDALGeorefTransform::QgsGDALGeorefTransform( bool useTPS, unsigned int polynomialOrder )
318 : mPolynomialOrder( std::min( 3u, polynomialOrder ) )
319 , mIsTPSTransform( useTPS )
320{
321}
322
324{
325 destroyGdalArgs();
326}
327
329{
330 auto res = std::make_unique<QgsGDALGeorefTransform>( mIsTPSTransform, mPolynomialOrder );
331 res->updateParametersFromGcps( mSourceCoords, mDestCoordinates, mInvertYAxis );
332 return res.release();
333}
334
335bool QgsGDALGeorefTransform::updateParametersFromGcps( const QVector<QgsPointXY> &sourceCoordinates, const QVector<QgsPointXY> &destinationCoordinates, bool invertYAxis )
336{
337 mSourceCoords = sourceCoordinates;
338 mDestCoordinates = destinationCoordinates;
339 mInvertYAxis = invertYAxis;
340
341 assert( sourceCoordinates.size() == destinationCoordinates.size() );
342 if ( sourceCoordinates.size() != destinationCoordinates.size() )
343 return false;
344 const int n = sourceCoordinates.size();
345
346 GDAL_GCP *GCPList = new GDAL_GCP[n];
347 for ( int i = 0; i < n; i++ )
348 {
349 GCPList[i].pszId = new char[20];
350 snprintf( GCPList[i].pszId, 19, "gcp%i", i );
351 GCPList[i].pszInfo = nullptr;
352 GCPList[i].dfGCPPixel = sourceCoordinates[i].x();
353 GCPList[i].dfGCPLine = ( mInvertYAxis ? -1 : 1 ) * sourceCoordinates[i].y();
354 GCPList[i].dfGCPX = destinationCoordinates[i].x();
355 GCPList[i].dfGCPY = destinationCoordinates[i].y();
356 GCPList[i].dfGCPZ = 0;
357 }
358 destroyGdalArgs();
359
360 if ( mIsTPSTransform )
361 mGDALTransformerArgs = GDALCreateTPSTransformer( n, GCPList, false );
362 else
363 mGDALTransformerArgs = GDALCreateGCPTransformer( n, GCPList, mPolynomialOrder, false );
364
365 for ( int i = 0; i < n; i++ )
366 {
367 delete[] GCPList[i].pszId;
368 }
369 delete[] GCPList;
370
371 return nullptr != mGDALTransformerArgs;
372}
373
375{
376 if ( mIsTPSTransform )
377 return 1;
378 else
379 return ( ( mPolynomialOrder + 2 ) * ( mPolynomialOrder + 1 ) ) / 2;
380}
381
383{
384 // Fail if no arguments were calculated through updateParametersFromGCP
385 if ( !mGDALTransformerArgs )
386 return nullptr;
387
388 if ( mIsTPSTransform )
389 return GDALTPSTransform;
390 else
391 return GDALGCPTransform;
392}
393
395{
396 return mGDALTransformerArgs;
397}
398
400{
401 if ( mIsTPSTransform )
403
404 switch ( mPolynomialOrder )
405 {
406 case 1:
408 case 2:
410 case 3:
412 }
414}
415
416void QgsGDALGeorefTransform::destroyGdalArgs()
417{
418 if ( mGDALTransformerArgs )
419 {
420 if ( mIsTPSTransform )
421 GDALDestroyTPSTransformer( mGDALTransformerArgs );
422 else
423 GDALDestroyGCPTransformer( mGDALTransformerArgs );
424 }
425}
426
427//
428// QgsProjectiveGeorefTransform
429//
430
434
436{
437 auto res = std::make_unique<QgsProjectiveGeorefTransform>();
438 res->mParameters = mParameters;
439 return res.release();
440}
441
442bool QgsProjectiveGeorefTransform::updateParametersFromGcps( const QVector<QgsPointXY> &sourceCoordinates, const QVector<QgsPointXY> &destinationCoordinates, bool invertYAxis )
443{
444 if ( destinationCoordinates.size() < minimumGcpCount() )
445 return false;
446
447 if ( invertYAxis )
448 {
449 // HACK: flip y coordinates, because georeferencer and gdal use different conventions
450 QVector<QgsPointXY> flippedPixelCoords;
451 flippedPixelCoords.reserve( sourceCoordinates.size() );
452 for ( const QgsPointXY &coord : sourceCoordinates )
453 {
454 flippedPixelCoords << QgsPointXY( coord.x(), -coord.y() );
455 }
456
457 QgsLeastSquares::projective( flippedPixelCoords, destinationCoordinates, mParameters.H );
458 }
459 else
460 {
461 QgsLeastSquares::projective( sourceCoordinates, destinationCoordinates, mParameters.H );
462 }
463
464 // Invert the homography matrix using adjoint matrix
465 double *H = mParameters.H;
466
467 double adjoint[9];
468 adjoint[0] = H[4] * H[8] - H[5] * H[7];
469 adjoint[1] = -H[1] * H[8] + H[2] * H[7];
470 adjoint[2] = H[1] * H[5] - H[2] * H[4];
471
472 adjoint[3] = -H[3] * H[8] + H[5] * H[6];
473 adjoint[4] = H[0] * H[8] - H[2] * H[6];
474 adjoint[5] = -H[0] * H[5] + H[2] * H[3];
475
476 adjoint[6] = H[3] * H[7] - H[4] * H[6];
477 adjoint[7] = -H[0] * H[7] + H[1] * H[6];
478 adjoint[8] = H[0] * H[4] - H[1] * H[3];
479
480 const double det = H[0] * adjoint[0] + H[3] * adjoint[1] + H[6] * adjoint[2];
481
482 if ( std::fabs( det ) < 1024.0 * std::numeric_limits<double>::epsilon() )
483 {
484 mParameters.hasInverse = false;
485 }
486 else
487 {
488 mParameters.hasInverse = true;
489 const double oo_det = 1.0 / det;
490 for ( int i = 0; i < 9; i++ )
491 {
492 mParameters.Hinv[i] = adjoint[i] * oo_det;
493 }
494 }
495 return true;
496}
497
499{
500 return 4;
501}
502
504{
505 return QgsProjectiveGeorefTransform::projectiveTransform;
506}
507
509{
510 return ( void * ) &mParameters;
511}
512
517
518int QgsProjectiveGeorefTransform::projectiveTransform( void *pTransformerArg, int bDstToSrc, int nPointCount, double *x, double *y, double *z, int *panSuccess )
519{
520 Q_UNUSED( z )
521 ProjectiveParameters *t = static_cast<ProjectiveParameters *>( pTransformerArg );
522 if ( !t )
523 return false;
524
525 double *H = nullptr;
526 if ( !bDstToSrc )
527 {
528 H = t->H;
529 }
530 else
531 {
532 if ( !t->hasInverse )
533 {
534 for ( int i = 0; i < nPointCount; ++i )
535 {
536 panSuccess[i] = false;
537 }
538 return false;
539 }
540 H = t->Hinv;
541 }
542
543
544 for ( int i = 0; i < nPointCount; ++i )
545 {
546 const double Z = x[i] * H[6] + y[i] * H[7] + H[8];
547 // Projects to infinity?
548 if ( std::fabs( Z ) < 1024.0 * std::numeric_limits<double>::epsilon() )
549 {
550 panSuccess[i] = false;
551 continue;
552 }
553 const double X = ( x[i] * H[0] + y[i] * H[1] + H[2] ) / Z;
554 const double Y = ( x[i] * H[3] + y[i] * H[4] + H[5] ) / Z;
555
556 x[i] = X;
557 y[i] = Y;
558
559 panSuccess[i] = true;
560 }
561
562 return true;
563}
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...
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.
QgsGcpTransformerInterface()=default
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.
Represents 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.