1 /***************************************************************************
2     qgsgcptransformer.h
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 #ifndef QGSGCPTRANSFORMER_H
17 #define QGSGCPTRANSFORMER_H
18 
19 #include <gdal_alg.h>
20 #include "qgspoint.h"
21 #include "qgis_analysis.h"
22 #include "qgis_sip.h"
23 
24 /**
25  * \ingroup analysis
26  * \brief An interface for Ground Control Points (GCP) based transformations.
27  *
28  * QgsGcpTransformerInterface implementations are able to transform point locations
29  * based on a transformation method and a list of GCPs.
30  *
31  * \since QGIS 3.20
32 */
33 class ANALYSIS_EXPORT QgsGcpTransformerInterface SIP_ABSTRACT
34 {
35   public:
36 
37     /**
38      * Available transformation methods.
39      */
40     enum class TransformMethod : int
41     {
42       Linear, //!< Linear transform
43       Helmert, //!< Helmert transform
44       PolynomialOrder1, //!< Polynomial order 1
45       PolynomialOrder2, //!< Polyonmial order 2
46       PolynomialOrder3, //!< Polynomial order
47       ThinPlateSpline, //!< Thin plate splines
48       Projective, //!< Projective
49       InvalidTransform = 65535 //!< Invalid transform
50     };
51 
52     //! Constructor for QgsGcpTransformerInterface
53     QgsGcpTransformerInterface() = default;
54 
55     virtual ~QgsGcpTransformerInterface() = default;
56 
57     //! QgsGcpTransformerInterface cannot be copied - use clone() instead.
58     QgsGcpTransformerInterface( const QgsGcpTransformerInterface &other ) = delete;
59 
60     //! QgsGcpTransformerInterface cannot be copied - use clone() instead.
61     QgsGcpTransformerInterface &operator=( const QgsGcpTransformerInterface &other ) = delete;
62 
63     /**
64      * Clones the transformer, returning a new copy of the transformer with the same
65      * parameters as this one.
66      *
67      * Caller takes ownership of the returned object.
68      */
69     virtual QgsGcpTransformerInterface *clone() const = 0 SIP_FACTORY;
70 
71     /**
72      * Fits transformation parameters using the specified Ground Control Points (GCPs) lists of source and destination coordinates.
73      *
74      * If \a invertYAxis is set to TRUE then the y-axis of source coordinates will be inverted, e.g. to allow for transformation of raster layers
75      * with ascending top-to-bottom vertical axis coordinates.
76      *
77      * \returns TRUE on success, FALSE on failure
78      */
79     virtual bool updateParametersFromGcps( const QVector<QgsPointXY> &sourceCoordinates, const QVector<QgsPointXY> &destinationCoordinates, bool invertYAxis = false ) SIP_THROW( QgsNotSupportedException ) = 0;
80 
81     /**
82      * Returns the minimum number of Ground Control Points (GCPs) required for parameter fitting.
83      */
84     virtual int minimumGcpCount() const = 0;
85 
86     /**
87      * Returns the transformation method.
88      */
89     virtual TransformMethod method() const = 0;
90 
91     /**
92      * Transforms the point (\a x, \a y) from source to destination coordinates.
93      *
94      * If \a inverseTransform is set to TRUE, the point will be transformed from the destination to the source.
95      *
96      * \returns TRUE if transformation was successful.
97      */
98     bool transform( double &x SIP_INOUT, double &y SIP_INOUT, bool inverseTransform = false ) const;
99 
100     /**
101      * Returns a translated string representing the specified transform \a method.
102      */
103     static QString methodToString( TransformMethod method );
104 
105     /**
106      * Creates a new QgsGcpTransformerInterface subclass representing the specified transform \a method.
107      *
108      * Caller takes ownership of the returned object.
109      */
110     static QgsGcpTransformerInterface *create( TransformMethod method ) SIP_FACTORY;
111 
112     /**
113      * Creates a new QgsGcpTransformerInterface subclass representing the specified transform \a method, initialized
114      * using the given lists of source and destination coordinates.
115      *
116      * If the parameters cannot be fit to a transform NULLPTR will be returned.
117      *
118      * Caller takes ownership of the returned object.
119      */
120     static QgsGcpTransformerInterface *createFromParameters( TransformMethod method, const QVector<QgsPointXY> &sourceCoordinates, const QVector<QgsPointXY> &destinationCoordinates ) SIP_THROW( QgsNotSupportedException ) SIP_FACTORY;
121 
122 #ifndef SIP_RUN
123 
124     /**
125      * Returns function pointer to the GDALTransformer function.
126      */
127     virtual GDALTransformerFunc GDALTransformer() const = 0;
128 
129     /**
130      * Returns pointer to the GDALTransformer arguments.
131      */
132     virtual void *GDALTransformerArgs() const = 0;
133 #endif
134 
135   private:
136 
137 #ifdef SIP_RUN
138     QgsGcpTransformerInterface( const QgsGcpTransformerInterface &other )
139 #endif
140 };
141 
142 /**
143  * \brief A simple transform which is parametrized by a translation and anistotropic scale.
144  * \ingroup analysis
145  * \note Not available in Python bindings
146  * \since QGIS 3.20
147  */
148 class ANALYSIS_EXPORT QgsLinearGeorefTransform : public QgsGcpTransformerInterface SIP_SKIP
149 {
150   public:
151 
152     //! Constructor for QgsLinearGeorefTransform
153     QgsLinearGeorefTransform() = default;
154 
155     /**
156      * Returns the origin and scale for the transform.
157      */
158     bool getOriginScale( QgsPointXY &origin, double &scaleX, double &scaleY ) const;
159 
160     QgsGcpTransformerInterface *clone() const override;
161     bool updateParametersFromGcps( const QVector<QgsPointXY> &sourceCoordinates, const QVector<QgsPointXY> &destinationCoordinates, bool invertYAxis = false ) override;
162     int minimumGcpCount() const override;
163     GDALTransformerFunc GDALTransformer() const override;
164     void *GDALTransformerArgs() const override;
165     TransformMethod method() const override;
166 
167   private:
168     struct LinearParameters
169     {
170       QgsPointXY origin;
171       double scaleX = 1;
172       double scaleY = 1;
173       bool invertYAxis = false;
174     } mParameters;
175 
176     static int linearTransform( void *pTransformerArg, int bDstToSrc, int nPointCount,
177                                 double *x, double *y, double *z, int *panSuccess );
178 
179 };
180 
181 /**
182  * \brief 2-dimensional helmert transform, parametrised by isotropic scale, rotation angle and translation.
183  * \ingroup analysis
184  * \note Not available in Python bindings
185  * \since QGIS 3.20
186  */
187 class ANALYSIS_EXPORT QgsHelmertGeorefTransform : public QgsGcpTransformerInterface SIP_SKIP
188 {
189   public:
190 
191     //! Constructor for QgsHelmertGeorefTransform
192     QgsHelmertGeorefTransform() = default;
193 
194     /**
195      * Returns the origin, scale and rotation for the transform.
196      */
197     bool getOriginScaleRotation( QgsPointXY &origin, double &scale, double &rotation ) const;
198 
199     QgsGcpTransformerInterface *clone() const override;
200     bool updateParametersFromGcps( const QVector<QgsPointXY> &sourceCoordinates, const QVector<QgsPointXY> &destinationCoordinates, bool invertYAxis = false ) override;
201     int minimumGcpCount() const override;
202     GDALTransformerFunc GDALTransformer() const override;
203     void *GDALTransformerArgs() const override;
204     TransformMethod method() const override;
205 
206   private:
207 
208     struct HelmertParameters
209     {
210       QgsPointXY origin;
211       double scale;
212       double angle;
213       bool invertYAxis = false;
214     };
215     HelmertParameters mHelmertParameters;
216 
217     static int helmertTransform( void *pTransformerArg, int bDstToSrc, int nPointCount,
218                                  double *x, double *y, double *z, int *panSuccess );
219 
220 };
221 
222 /**
223  * \brief Interface to gdal thin plate splines and 1st/2nd/3rd order polynomials.
224  * \ingroup analysis
225  * \note Not available in Python bindings
226  * \since QGIS 3.20
227  */
228 class ANALYSIS_EXPORT QgsGDALGeorefTransform : public QgsGcpTransformerInterface SIP_SKIP
229 {
230   public:
231 
232     //! Constructor for QgsGDALGeorefTransform
233     QgsGDALGeorefTransform( bool useTPS, unsigned int polynomialOrder );
234     ~QgsGDALGeorefTransform() override;
235 
236     QgsGcpTransformerInterface *clone() const override;
237     bool updateParametersFromGcps( const QVector<QgsPointXY> &sourceCoordinates, const QVector<QgsPointXY> &destinationCoordinates, bool invertYAxis = false ) override;
238     int minimumGcpCount() const override;
239     GDALTransformerFunc GDALTransformer() const override;
240     void *GDALTransformerArgs() const override;
241     TransformMethod method() const override;
242 
243   private:
244     void destroyGdalArgs();
245 
246     QVector<QgsPointXY> mSourceCoords;
247     QVector<QgsPointXY> mDestCoordinates;
248     bool mInvertYAxis = false;
249 
250     const int mPolynomialOrder;
251     const bool mIsTPSTransform;
252 
253     void *mGDALTransformerArgs = nullptr;
254 
255 };
256 
257 /**
258  * \brief A planar projective transform, expressed by a homography.
259  *
260  * Implements model fitting which minimizes algebraic error using total least squares.
261  *
262  * \ingroup analysis
263  * \note Not available in Python bindings
264  * \since QGIS 3.20
265  */
266 class ANALYSIS_EXPORT QgsProjectiveGeorefTransform : public QgsGcpTransformerInterface SIP_SKIP
267 {
268   public:
269 
270     //! Constructor for QgsProjectiveGeorefTransform
271     QgsProjectiveGeorefTransform();
272 
273     QgsGcpTransformerInterface *clone() const override;
274     bool updateParametersFromGcps( const QVector<QgsPointXY> &sourceCoordinates, const QVector<QgsPointXY> &destinationCoordinates, bool invertYAxis = false ) override;
275     int minimumGcpCount() const override;
276     GDALTransformerFunc GDALTransformer() const override;
277     void *GDALTransformerArgs() const override;
278     TransformMethod method() const override;
279 
280   private:
281     struct ProjectiveParameters
282     {
283       double H[9];        // Homography
284       double Hinv[9];     // Inverted homography
285       bool hasInverse;  // Could the inverted homography be calculated?
286     } mParameters;
287 
288     static int projectiveTransform( void *pTransformerArg, int bDstToSrc, int nPointCount,
289                                     double *x, double *y, double *z, int *panSuccess );
290 
291 };
292 
293 #endif //QGSGCPTRANSFORMER_H
294