Coverage Report

Created: 2025-06-13 06:29

/src/gdal/alg/gdal_homography.cpp
Line
Count
Source (jump to first uncovered line)
1
/******************************************************************************
2
 *
3
 * Project:  Homography Transformer
4
 * Author:   Nathan Olson
5
 *
6
 ******************************************************************************
7
 * Copyright (c) 2025, Nathan Olson <nathanmolson at gmail dot com>
8
 *
9
 * SPDX-License-Identifier: MIT
10
 ****************************************************************************/
11
12
#include "cpl_port.h"
13
14
#include <stdlib.h>
15
#include <string.h>
16
#include <algorithm>
17
18
#include "cpl_atomic_ops.h"
19
#include "cpl_error.h"
20
#include "cpl_string.h"
21
#include "gdal.h"
22
#include "gdal_alg.h"
23
#include "gdallinearsystem.h"
24
25
CPL_C_START
26
CPLXMLNode *GDALSerializeHomographyTransformer(void *pTransformArg);
27
void *GDALDeserializeHomographyTransformer(CPLXMLNode *psTree);
28
CPL_C_END
29
30
struct HomographyTransformInfo
31
{
32
    GDALTransformerInfo sTI{};
33
34
    double padfForward[9]{};
35
    double padfReverse[9]{};
36
37
    volatile int nRefCount{};
38
};
39
40
/************************************************************************/
41
/*               GDALCreateSimilarHomographyTransformer()               */
42
/************************************************************************/
43
44
static void *GDALCreateSimilarHomographyTransformer(void *hTransformArg,
45
                                                    double dfRatioX,
46
                                                    double dfRatioY)
47
0
{
48
0
    VALIDATE_POINTER1(hTransformArg, "GDALCreateSimilarHomographyTransformer",
49
0
                      nullptr);
50
51
0
    HomographyTransformInfo *psInfo =
52
0
        static_cast<HomographyTransformInfo *>(hTransformArg);
53
54
0
    if (dfRatioX == 1.0 && dfRatioY == 1.0)
55
0
    {
56
        // We can just use a ref count, since using the source transformation
57
        // is thread-safe.
58
0
        CPLAtomicInc(&(psInfo->nRefCount));
59
0
    }
60
0
    else
61
0
    {
62
0
        double homography[9];
63
0
        for (int i = 0; i < 3; i++)
64
0
        {
65
0
            homography[3 * i + 1] = psInfo->padfForward[3 * i + 1] / dfRatioX;
66
0
            homography[3 * i + 2] = psInfo->padfForward[3 * i + 2] / dfRatioY;
67
0
            homography[3 * i] = psInfo->padfForward[3 * i];
68
0
        }
69
0
        psInfo = static_cast<HomographyTransformInfo *>(
70
0
            GDALCreateHomographyTransformer(homography));
71
0
    }
72
73
0
    return psInfo;
74
0
}
75
76
/************************************************************************/
77
/*                   GDALCreateHomographyTransformer()                  */
78
/************************************************************************/
79
80
/**
81
 * Create Homography transformer from GCPs.
82
 *
83
 * Homography Transformers are serializable.
84
 *
85
 * @param adfHomography the forward homography.
86
 *
87
 * @return the transform argument or NULL if creation fails.
88
 */
89
90
void *GDALCreateHomographyTransformer(double adfHomography[9])
91
0
{
92
    /* -------------------------------------------------------------------- */
93
    /*      Allocate transform info.                                        */
94
    /* -------------------------------------------------------------------- */
95
0
    HomographyTransformInfo *psInfo = new HomographyTransformInfo();
96
97
0
    memcpy(psInfo->sTI.abySignature, GDAL_GTI2_SIGNATURE,
98
0
           strlen(GDAL_GTI2_SIGNATURE));
99
0
    psInfo->sTI.pszClassName = "GDALHomographyTransformer";
100
0
    psInfo->sTI.pfnTransform = GDALHomographyTransform;
101
0
    psInfo->sTI.pfnCleanup = GDALDestroyHomographyTransformer;
102
0
    psInfo->sTI.pfnSerialize = GDALSerializeHomographyTransformer;
103
0
    psInfo->sTI.pfnCreateSimilar = GDALCreateSimilarHomographyTransformer;
104
105
0
    psInfo->nRefCount = 1;
106
107
0
    memcpy(psInfo->padfForward, adfHomography, 9 * sizeof(double));
108
0
    if (GDALInvHomography(psInfo->padfForward, psInfo->padfReverse))
109
0
    {
110
0
        return psInfo;
111
0
    }
112
113
0
    CPLError(CE_Failure, CPLE_AppDefined,
114
0
             "GDALCreateHomographyTransformer() failed, because "
115
0
             "GDALInvHomography() failed");
116
0
    GDALDestroyHomographyTransformer(psInfo);
117
0
    return nullptr;
118
0
}
119
120
/************************************************************************/
121
/*                        GDALGCPsToHomography()                        */
122
/************************************************************************/
123
124
/**
125
 * \brief Generate Homography from GCPs.
126
 *
127
 * Given a set of GCPs perform least squares fit as a homography.
128
 *
129
 * A minimum of four GCPs are required to uniquely define a homography.
130
 * If there are less than four GCPs, GDALGCPsToGeoTransform() is used to
131
 * compute the transform.
132
 *
133
 * @param nGCPCount the number of GCPs being passed in.
134
 * @param pasGCPList the list of GCP structures.
135
 * @param padfHomography the nine double array in which the homography
136
 * will be returned.
137
 *
138
 * @return TRUE on success or FALSE if there aren't enough points to prepare a
139
 * homography, or pathological geometry is detected
140
 */
141
int GDALGCPsToHomography(int nGCPCount, const GDAL_GCP *pasGCPList,
142
                         double *padfHomography)
143
0
{
144
0
    if (nGCPCount < 4)
145
0
    {
146
0
        padfHomography[6] = 1.0;
147
0
        padfHomography[7] = 0.0;
148
0
        padfHomography[8] = 0.0;
149
0
        return GDALGCPsToGeoTransform(nGCPCount, pasGCPList, padfHomography,
150
0
                                      FALSE);
151
0
    }
152
153
    /* -------------------------------------------------------------------- */
154
    /*      Compute source and destination ranges so we can normalize       */
155
    /*      the values to make the least squares computation more stable.   */
156
    /* -------------------------------------------------------------------- */
157
0
    double min_pixel = pasGCPList[0].dfGCPPixel;
158
0
    double max_pixel = pasGCPList[0].dfGCPPixel;
159
0
    double min_line = pasGCPList[0].dfGCPLine;
160
0
    double max_line = pasGCPList[0].dfGCPLine;
161
0
    double min_geox = pasGCPList[0].dfGCPX;
162
0
    double max_geox = pasGCPList[0].dfGCPX;
163
0
    double min_geoy = pasGCPList[0].dfGCPY;
164
0
    double max_geoy = pasGCPList[0].dfGCPY;
165
166
0
    for (int i = 1; i < nGCPCount; ++i)
167
0
    {
168
0
        min_pixel = std::min(min_pixel, pasGCPList[i].dfGCPPixel);
169
0
        max_pixel = std::max(max_pixel, pasGCPList[i].dfGCPPixel);
170
0
        min_line = std::min(min_line, pasGCPList[i].dfGCPLine);
171
0
        max_line = std::max(max_line, pasGCPList[i].dfGCPLine);
172
0
        min_geox = std::min(min_geox, pasGCPList[i].dfGCPX);
173
0
        max_geox = std::max(max_geox, pasGCPList[i].dfGCPX);
174
0
        min_geoy = std::min(min_geoy, pasGCPList[i].dfGCPY);
175
0
        max_geoy = std::max(max_geoy, pasGCPList[i].dfGCPY);
176
0
    }
177
178
0
    constexpr double EPSILON = 1.0e-12;
179
180
0
    if (std::abs(max_pixel - min_pixel) < EPSILON ||
181
0
        std::abs(max_line - min_line) < EPSILON ||
182
0
        std::abs(max_geox - min_geox) < EPSILON ||
183
0
        std::abs(max_geoy - min_geoy) < EPSILON)
184
0
    {
185
0
        CPLError(CE_Failure, CPLE_AppDefined,
186
0
                 "GDALGCPsToHomography() failed: GCPs degenerate in at least "
187
0
                 "one dimension.");
188
0
        return FALSE;
189
0
    }
190
191
0
    double pl_normalize[9], geo_normalize[9];
192
193
0
    pl_normalize[0] = -min_pixel / (max_pixel - min_pixel);
194
0
    pl_normalize[1] = 1.0 / (max_pixel - min_pixel);
195
0
    pl_normalize[2] = 0.0;
196
0
    pl_normalize[3] = -min_line / (max_line - min_line);
197
0
    pl_normalize[4] = 0.0;
198
0
    pl_normalize[5] = 1.0 / (max_line - min_line);
199
0
    pl_normalize[6] = 1.0;
200
0
    pl_normalize[7] = 0.0;
201
0
    pl_normalize[8] = 0.0;
202
203
0
    geo_normalize[0] = -min_geox / (max_geox - min_geox);
204
0
    geo_normalize[1] = 1.0 / (max_geox - min_geox);
205
0
    geo_normalize[2] = 0.0;
206
0
    geo_normalize[3] = -min_geoy / (max_geoy - min_geoy);
207
0
    geo_normalize[4] = 0.0;
208
0
    geo_normalize[5] = 1.0 / (max_geoy - min_geoy);
209
0
    geo_normalize[6] = 1.0;
210
0
    geo_normalize[7] = 0.0;
211
0
    geo_normalize[8] = 0.0;
212
213
0
    double inv_geo_normalize[9] = {0.0};
214
0
    if (!GDALInvHomography(geo_normalize, inv_geo_normalize))
215
0
    {
216
0
        CPLError(CE_Failure, CPLE_AppDefined,
217
0
                 "GDALGCPsToHomography() failed: GDALInvHomography() failed");
218
0
        return FALSE;
219
0
    }
220
221
    /* -------------------------------------------------------------------- */
222
    /* Calculate the best fit homography following                          */
223
    /* https://www.cs.unc.edu/~ronisen/teaching/fall_2023/pdf_slides/       */
224
    /* lecture9_transformation.pdf                                          */
225
    /* Since rank(AtA) = rank(8) = 8, append an additional equation         */
226
    /* h_normalized[6] = 1 to fully define the solution.                    */
227
    /* -------------------------------------------------------------------- */
228
0
    GDALMatrix AtA(9, 9);
229
0
    GDALMatrix rhs(9, 1);
230
0
    rhs(6, 0) = 1;
231
0
    AtA(6, 6) = 1;
232
233
0
    for (int i = 0; i < nGCPCount; ++i)
234
0
    {
235
0
        double pixel, line, geox, geoy;
236
237
0
        if (!GDALApplyHomography(pl_normalize, pasGCPList[i].dfGCPPixel,
238
0
                                 pasGCPList[i].dfGCPLine, &pixel, &line) ||
239
0
            !GDALApplyHomography(geo_normalize, pasGCPList[i].dfGCPX,
240
0
                                 pasGCPList[i].dfGCPY, &geox, &geoy))
241
0
        {
242
0
            CPLError(CE_Failure, CPLE_AppDefined,
243
0
                     "GDALGCPsToHomography() failed: GDALApplyHomography() "
244
0
                     "failed on GCP %d.",
245
0
                     i);
246
0
            return FALSE;
247
0
        }
248
249
0
        double Ax[] = {1, pixel, line,          0,           0,
250
0
                       0, -geox, -geox * pixel, -geox * line};
251
0
        double Ay[] = {0,           0, 0, 1, pixel, line, -geoy, -geoy * pixel,
252
0
                       -geoy * line};
253
0
        int j, k;
254
        // Populate the lower triangle of symmetric AtA matrix
255
0
        for (j = 0; j < 9; j++)
256
0
        {
257
0
            for (k = j; k < 9; k++)
258
0
            {
259
0
                AtA(j, k) += Ax[j] * Ax[k] + Ay[j] * Ay[k];
260
0
            }
261
0
        }
262
0
    }
263
    // Populate the upper triangle of symmetric AtA matrix
264
0
    for (int j = 0; j < 9; j++)
265
0
    {
266
0
        for (int k = 0; k < j; k++)
267
0
        {
268
0
            AtA(j, k) = AtA(k, j);
269
0
        }
270
0
    }
271
272
0
    GDALMatrix h_normalized(9, 1);
273
0
    if (!GDALLinearSystemSolve(AtA, rhs, h_normalized))
274
0
    {
275
0
        CPLError(
276
0
            CE_Failure, CPLE_AppDefined,
277
0
            "GDALGCPsToHomography() failed: GDALLinearSystemSolve() failed");
278
0
        return FALSE;
279
0
    }
280
0
    if (std::abs(h_normalized(6, 0)) < 1.0e-15)
281
0
    {
282
0
        CPLError(CE_Failure, CPLE_AppDefined,
283
0
                 "GDALGCPsToHomography() failed: h_normalized(6, 0) not zero");
284
0
        return FALSE;
285
0
    }
286
287
    /* -------------------------------------------------------------------- */
288
    /* Check that the homography maps the unit square to a convex           */
289
    /* quadrilateral.                                                       */
290
    /* -------------------------------------------------------------------- */
291
    // First, use the normalized homography to make the corners of the unit
292
    // square to normalized geo coordinates
293
0
    double x[4] = {0, 1, 1, 0};
294
0
    double y[4] = {0, 0, 1, 1};
295
0
    for (int i = 0; i < 4; i++)
296
0
    {
297
0
        if (!GDALApplyHomography(h_normalized.data(), x[i], y[i], &x[i], &y[i]))
298
0
        {
299
0
            return FALSE;
300
0
        }
301
0
    }
302
    // Next, compute the vector from the top-left corner to each corner
303
0
    for (int i = 3; i >= 0; i--)
304
0
    {
305
0
        x[i] -= x[0];
306
0
        y[i] -= y[0];
307
0
    }
308
    // Finally, check that "v2" (<x[2], y[2]>, the vector from top-left to
309
    // bottom-right corner) is between v1 and v3, by checking that the
310
    // vector cross product (v1 x v2) has the same sign as (v2 x v3)
311
0
    double cross12 = x[1] * y[2] - x[2] * y[1];
312
0
    double cross23 = x[2] * y[3] - x[3] * y[2];
313
0
    if (cross12 * cross23 <= 0.0)
314
0
    {
315
0
        CPLError(CE_Failure, CPLE_AppDefined,
316
0
                 "GDALGCPsToHomography() failed: cross12 * cross23 <= 0.0");
317
0
        return FALSE;
318
0
    }
319
320
    /* -------------------------------------------------------------------- */
321
    /*      Compose the resulting transformation with the normalization     */
322
    /*      homographies.                                                   */
323
    /* -------------------------------------------------------------------- */
324
0
    double h1p2[9] = {0.0};
325
326
0
    GDALComposeHomographies(pl_normalize, h_normalized.data(), h1p2);
327
0
    GDALComposeHomographies(h1p2, inv_geo_normalize, padfHomography);
328
329
0
    return TRUE;
330
0
}
331
332
/************************************************************************/
333
/*                      GDALComposeHomographies()                       */
334
/************************************************************************/
335
336
/**
337
 * \brief Compose two homographies.
338
 *
339
 * The resulting homography is the equivalent to padfH1 and then padfH2
340
 * being applied to a point.
341
 *
342
 * @param padfH1 the first homography, nine values.
343
 * @param padfH2 the second homography, nine values.
344
 * @param padfHOut the output homography, nine values, may safely be the same
345
 * array as padfH1 or padfH2.
346
 */
347
348
void GDALComposeHomographies(const double *padfH1, const double *padfH2,
349
                             double *padfHOut)
350
351
0
{
352
0
    double hwrk[9] = {0.0};
353
354
0
    hwrk[1] =
355
0
        padfH2[1] * padfH1[1] + padfH2[2] * padfH1[4] + padfH2[0] * padfH1[7];
356
0
    hwrk[2] =
357
0
        padfH2[1] * padfH1[2] + padfH2[2] * padfH1[5] + padfH2[0] * padfH1[8];
358
0
    hwrk[0] =
359
0
        padfH2[1] * padfH1[0] + padfH2[2] * padfH1[3] + padfH2[0] * padfH1[6];
360
361
0
    hwrk[4] =
362
0
        padfH2[4] * padfH1[1] + padfH2[5] * padfH1[4] + padfH2[3] * padfH1[7];
363
0
    hwrk[5] =
364
0
        padfH2[4] * padfH1[2] + padfH2[5] * padfH1[5] + padfH2[3] * padfH1[8];
365
0
    hwrk[3] =
366
0
        padfH2[4] * padfH1[0] + padfH2[5] * padfH1[3] + padfH2[3] * padfH1[6];
367
368
0
    hwrk[7] =
369
0
        padfH2[7] * padfH1[1] + padfH2[8] * padfH1[4] + padfH2[6] * padfH1[7];
370
0
    hwrk[8] =
371
0
        padfH2[7] * padfH1[2] + padfH2[8] * padfH1[5] + padfH2[6] * padfH1[8];
372
0
    hwrk[6] =
373
0
        padfH2[7] * padfH1[0] + padfH2[8] * padfH1[3] + padfH2[6] * padfH1[6];
374
0
    memcpy(padfHOut, hwrk, sizeof(hwrk));
375
0
}
376
377
/************************************************************************/
378
/*                        GDALApplyHomography()                         */
379
/************************************************************************/
380
381
/**
382
 * Apply Homography to x/y coordinate.
383
 *
384
 * Applies the following computation, converting a (pixel, line) coordinate
385
 * into a georeferenced (geo_x, geo_y) location.
386
 * \code{.c}
387
 *  *pdfGeoX = (padfHomography[0] + dfPixel * padfHomography[1]
388
 *                                + dfLine  * padfHomography[2]) /
389
 *             (padfHomography[6] + dfPixel * padfHomography[7]
390
 *                                + dfLine  * padfHomography[8]);
391
 *  *pdfGeoY = (padfHomography[3] + dfPixel * padfHomography[4]
392
 *                                + dfLine  * padfHomography[5]) /
393
 *             (padfHomography[6] + dfPixel * padfHomography[7]
394
 *                                + dfLine  * padfHomography[8]);
395
 * \endcode
396
 *
397
 * @param padfHomography Nine coefficient Homography to apply.
398
 * @param dfPixel Input pixel position.
399
 * @param dfLine Input line position.
400
 * @param pdfGeoX output location where geo_x (easting/longitude)
401
 * location is placed.
402
 * @param pdfGeoY output location where geo_y (northing/latitude)
403
 * location is placed.
404
*
405
* @return TRUE on success or FALSE if failure.
406
 */
407
408
int GDALApplyHomography(const double *padfHomography, double dfPixel,
409
                        double dfLine, double *pdfGeoX, double *pdfGeoY)
410
0
{
411
0
    double w = padfHomography[6] + dfPixel * padfHomography[7] +
412
0
               dfLine * padfHomography[8];
413
0
    if (std::abs(w) < 1.0e-15)
414
0
    {
415
0
        return FALSE;
416
0
    }
417
0
    double wx = padfHomography[0] + dfPixel * padfHomography[1] +
418
0
                dfLine * padfHomography[2];
419
0
    double wy = padfHomography[3] + dfPixel * padfHomography[4] +
420
0
                dfLine * padfHomography[5];
421
0
    *pdfGeoX = wx / w;
422
0
    *pdfGeoY = wy / w;
423
0
    return TRUE;
424
0
}
425
426
/************************************************************************/
427
/*                         GDALInvHomography()                          */
428
/************************************************************************/
429
430
/**
431
* Invert Homography.
432
*
433
* This function will invert a standard 3x3 set of Homography coefficients.
434
* This converts the equation from being pixel to geo to being geo to pixel.
435
*
436
* @param padfHIn Input homography (nine doubles - unaltered).
437
* @param padfHOut Output homography (nine doubles - updated).
438
*
439
* @return TRUE on success or FALSE if the equation is uninvertable.
440
*/
441
442
int GDALInvHomography(const double *padfHIn, double *padfHOut)
443
444
0
{
445
    // Special case - no rotation - to avoid computing determinant
446
    // and potential precision issues.
447
0
    if (padfHIn[2] == 0.0 && padfHIn[4] == 0.0 && padfHIn[1] != 0.0 &&
448
0
        padfHIn[5] != 0.0 && padfHIn[7] == 0.0 && padfHIn[8] == 0.0 &&
449
0
        padfHIn[6] != 0.0)
450
0
    {
451
0
        padfHOut[0] = -padfHIn[0] / padfHIn[1] / padfHIn[6];
452
0
        padfHOut[1] = 1.0 / padfHIn[1];
453
0
        padfHOut[2] = 0.0;
454
0
        padfHOut[3] = -padfHIn[3] / padfHIn[5] / padfHIn[6];
455
0
        padfHOut[4] = 0.0;
456
0
        padfHOut[5] = 1.0 / padfHIn[5];
457
0
        padfHOut[6] = 1.0 / padfHIn[6];
458
0
        padfHOut[7] = 0.0;
459
0
        padfHOut[8] = 0.0;
460
0
        return TRUE;
461
0
    }
462
463
    // Compute determinant.
464
465
0
    const double det = padfHIn[1] * padfHIn[5] * padfHIn[6] -
466
0
                       padfHIn[2] * padfHIn[4] * padfHIn[6] +
467
0
                       padfHIn[2] * padfHIn[3] * padfHIn[7] -
468
0
                       padfHIn[0] * padfHIn[5] * padfHIn[7] +
469
0
                       padfHIn[0] * padfHIn[4] * padfHIn[8] -
470
0
                       padfHIn[1] * padfHIn[3] * padfHIn[8];
471
0
    const double magnitude =
472
0
        std::max(std::max(fabs(padfHIn[1]), fabs(padfHIn[2])),
473
0
                 std::max(fabs(padfHIn[4]), fabs(padfHIn[5])));
474
475
0
    if (fabs(det) <= 1e-10 * magnitude * magnitude)
476
0
    {
477
0
        CPLError(CE_Failure, CPLE_AppDefined,
478
0
                 "GDALInvHomography() failed: null determinant");
479
0
        return FALSE;
480
0
    }
481
482
0
    const double inv_det = 1.0 / det;
483
484
    // Compute adjoint, and divide by determinant.
485
486
0
    padfHOut[1] = (padfHIn[5] * padfHIn[6] - padfHIn[3] * padfHIn[8]) * inv_det;
487
0
    padfHOut[4] = (padfHIn[3] * padfHIn[7] - padfHIn[4] * padfHIn[6]) * inv_det;
488
0
    padfHOut[7] = (padfHIn[4] * padfHIn[8] - padfHIn[5] * padfHIn[7]) * inv_det;
489
490
0
    padfHOut[2] = (padfHIn[0] * padfHIn[8] - padfHIn[2] * padfHIn[6]) * inv_det;
491
0
    padfHOut[5] = (padfHIn[1] * padfHIn[6] - padfHIn[0] * padfHIn[7]) * inv_det;
492
0
    padfHOut[8] = (padfHIn[2] * padfHIn[7] - padfHIn[1] * padfHIn[8]) * inv_det;
493
494
0
    padfHOut[0] = (padfHIn[2] * padfHIn[3] - padfHIn[0] * padfHIn[5]) * inv_det;
495
0
    padfHOut[3] = (padfHIn[0] * padfHIn[4] - padfHIn[1] * padfHIn[3]) * inv_det;
496
0
    padfHOut[6] = (padfHIn[1] * padfHIn[5] - padfHIn[2] * padfHIn[4]) * inv_det;
497
498
0
    return TRUE;
499
0
}
500
501
/************************************************************************/
502
/*               GDALCreateHomographyTransformerFromGCPs()              */
503
/************************************************************************/
504
505
/**
506
 * Create Homography transformer from GCPs.
507
 *
508
 * Homography Transformers are serializable.
509
 *
510
 * @param nGCPCount the number of GCPs in pasGCPList.
511
 * @param pasGCPList an array of GCPs to be used as input.
512
 *
513
 * @return the transform argument or NULL if creation fails.
514
 */
515
516
void *GDALCreateHomographyTransformerFromGCPs(int nGCPCount,
517
                                              const GDAL_GCP *pasGCPList)
518
0
{
519
0
    double adfHomography[9];
520
521
0
    if (GDALGCPsToHomography(nGCPCount, pasGCPList, adfHomography))
522
0
    {
523
0
        return GDALCreateHomographyTransformer(adfHomography);
524
0
    }
525
0
    return nullptr;
526
0
}
527
528
/************************************************************************/
529
/*                  GDALDestroyHomographyTransformer()                  */
530
/************************************************************************/
531
532
/**
533
 * Destroy Homography transformer.
534
 *
535
 * This function is used to destroy information about a homography
536
 * transformation created with GDALCreateHomographyTransformer().
537
 *
538
 * @param pTransformArg the transform arg previously returned by
539
 * GDALCreateHomographyTransformer().
540
 */
541
542
void GDALDestroyHomographyTransformer(void *pTransformArg)
543
544
0
{
545
0
    if (pTransformArg == nullptr)
546
0
        return;
547
548
0
    HomographyTransformInfo *psInfo =
549
0
        static_cast<HomographyTransformInfo *>(pTransformArg);
550
551
0
    if (CPLAtomicDec(&(psInfo->nRefCount)) == 0)
552
0
    {
553
0
        delete psInfo;
554
0
    }
555
0
}
556
557
/************************************************************************/
558
/*                       GDALHomographyTransform()                      */
559
/************************************************************************/
560
561
/**
562
 * Transforms point based on homography.
563
 *
564
 * This function matches the GDALTransformerFunc signature, and can be
565
 * used to transform one or more points from pixel/line coordinates to
566
 * georeferenced coordinates (SrcToDst) or vice versa (DstToSrc).
567
 *
568
 * @param pTransformArg return value from GDALCreateHomographyTransformer().
569
 * @param bDstToSrc TRUE if transformation is from the destination
570
 * (georeferenced) coordinates to pixel/line or FALSE when transforming
571
 * from pixel/line to georeferenced coordinates.
572
 * @param nPointCount the number of values in the x, y and z arrays.
573
 * @param x array containing the X values to be transformed.
574
 * @param y array containing the Y values to be transformed.
575
 * @param z array containing the Z values to be transformed.
576
 * @param panSuccess array in which a flag indicating success (TRUE) or
577
 * failure (FALSE) of the transformation are placed.
578
 *
579
 * @return TRUE if all points have been successfully transformed.
580
 */
581
582
int GDALHomographyTransform(void *pTransformArg, int bDstToSrc, int nPointCount,
583
                            double *x, double *y, CPL_UNUSED double *z,
584
                            int *panSuccess)
585
0
{
586
0
    VALIDATE_POINTER1(pTransformArg, "GDALHomographyTransform", 0);
587
588
0
    HomographyTransformInfo *psInfo =
589
0
        static_cast<HomographyTransformInfo *>(pTransformArg);
590
591
0
    double *homography = bDstToSrc ? psInfo->padfReverse : psInfo->padfForward;
592
0
    int ret = TRUE;
593
0
    for (int i = 0; i < nPointCount; i++)
594
0
    {
595
0
        double w = homography[6] + x[i] * homography[7] + y[i] * homography[8];
596
0
        if (std::abs(w) < 1.0e-15)
597
0
        {
598
0
            panSuccess[i] = FALSE;
599
0
            ret = FALSE;
600
0
        }
601
0
        else
602
0
        {
603
0
            double wx =
604
0
                homography[0] + x[i] * homography[1] + y[i] * homography[2];
605
0
            double wy =
606
0
                homography[3] + x[i] * homography[4] + y[i] * homography[5];
607
0
            x[i] = wx / w;
608
0
            y[i] = wy / w;
609
0
            panSuccess[i] = TRUE;
610
0
        }
611
0
    }
612
613
0
    return ret;
614
0
}
615
616
/************************************************************************/
617
/*                 GDALSerializeHomographyTransformer()                 */
618
/************************************************************************/
619
620
CPLXMLNode *GDALSerializeHomographyTransformer(void *pTransformArg)
621
622
0
{
623
0
    VALIDATE_POINTER1(pTransformArg, "GDALSerializeHomographyTransformer",
624
0
                      nullptr);
625
626
0
    HomographyTransformInfo *psInfo =
627
0
        static_cast<HomographyTransformInfo *>(pTransformArg);
628
629
0
    CPLXMLNode *psTree =
630
0
        CPLCreateXMLNode(nullptr, CXT_Element, "HomographyTransformer");
631
632
    /* -------------------------------------------------------------------- */
633
    /*      Attach Homography.                                              */
634
    /* -------------------------------------------------------------------- */
635
0
    char szWork[300] = {};
636
637
0
    CPLsnprintf(
638
0
        szWork, sizeof(szWork),
639
0
        "%.17g,%.17g,%.17g,%.17g,%.17g,%.17g,%.17g,%.17g,%.17g",
640
0
        psInfo->padfForward[0], psInfo->padfForward[1], psInfo->padfForward[2],
641
0
        psInfo->padfForward[3], psInfo->padfForward[4], psInfo->padfForward[5],
642
0
        psInfo->padfForward[6], psInfo->padfForward[7], psInfo->padfForward[8]);
643
0
    CPLCreateXMLElementAndValue(psTree, "Homography", szWork);
644
645
0
    return psTree;
646
0
}
647
648
/************************************************************************/
649
/*                     GDALDeserializeHomography()                      */
650
/************************************************************************/
651
652
static void GDALDeserializeHomography(const char *pszH, double adfHomography[9])
653
0
{
654
0
    CPLsscanf(pszH, "%lf,%lf,%lf,%lf,%lf,%lf,%lf,%lf,%lf", adfHomography + 0,
655
0
              adfHomography + 1, adfHomography + 2, adfHomography + 3,
656
0
              adfHomography + 4, adfHomography + 5, adfHomography + 6,
657
0
              adfHomography + 7, adfHomography + 8);
658
0
}
659
660
/************************************************************************/
661
/*                GDALDeserializeHomographyTransformer()                */
662
/************************************************************************/
663
664
void *GDALDeserializeHomographyTransformer(CPLXMLNode *psTree)
665
666
0
{
667
    /* -------------------------------------------------------------------- */
668
    /*        Homography                                                    */
669
    /* -------------------------------------------------------------------- */
670
0
    double padfForward[9];
671
0
    if (CPLGetXMLNode(psTree, "Homography") != nullptr)
672
0
    {
673
0
        GDALDeserializeHomography(CPLGetXMLValue(psTree, "Homography", ""),
674
0
                                  padfForward);
675
676
        /* -------------------------------------------------------------------- */
677
        /*      Generate transformation.                                        */
678
        /* -------------------------------------------------------------------- */
679
0
        void *pResult = GDALCreateHomographyTransformer(padfForward);
680
681
0
        return pResult;
682
0
    }
683
0
    return nullptr;
684
0
}