Coverage Report

Created: 2025-06-13 06:29

/src/gdal/alg/gdal_crs.cpp
Line
Count
Source (jump to first uncovered line)
1
/******************************************************************************
2
 *
3
 * Project:  Mapinfo Image Warper
4
 * Purpose:  Implementation of the GDALTransformer wrapper around CRS.C
5
 functions
6
 *           to build a polynomial transformation based on ground control
7
 *           points.
8
 * Author:   Frank Warmerdam, warmerdam@pobox.com
9
 *
10
 ***************************************************************************
11
12
    CRS.C - Center for Remote Sensing rectification routines
13
14
    Written By: Brian J. Buckley
15
16
            At: The Center for Remote Sensing
17
                Michigan State University
18
                302 Berkey Hall
19
                East Lansing, MI  48824
20
                (517)353-7195
21
22
    Written: 12/19/91
23
24
    Last Update: 12/26/91 Brian J. Buckley
25
    Last Update:  1/24/92 Brian J. Buckley
26
      Added printout of trnfile. Triggered by BDEBUG.
27
    Last Update:  1/27/92 Brian J. Buckley
28
      Fixed bug so that only the active control points were used.
29
    Last Update:  6/29/2011 C. F. Stallmann & R. van den Dool (South African
30
 National Space Agency) GCP refinement added
31
32
    Copyright (c) 1992, Michigan State University
33
 * Copyright (c) 2008-2013, Even Rouault <even dot rouault at spatialys.com>
34
35
    Permission is hereby granted, free of charge, to any person obtaining a
36
    copy of this software and associated documentation files (the "Software"),
37
    to deal in the Software without restriction, including without limitation
38
    the rights to use, copy, modify, merge, publish, distribute, sublicense,
39
    and/or sell copies of the Software, and to permit persons to whom the
40
    Software is furnished to do so, subject to the following conditions:
41
42
    The above copyright notice and this permission notice shall be included
43
    in all copies or substantial portions of the Software.
44
45
    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
46
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
47
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
48
    THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
49
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
50
    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
51
    DEALINGS IN THE SOFTWARE.
52
53
 ****************************************************************************/
54
55
#include "gdal_alg.h"
56
#include "gdal_priv.h"
57
#include "cpl_conv.h"
58
#include "cpl_minixml.h"
59
#include "cpl_string.h"
60
#include "cpl_atomic_ops.h"
61
62
#include <math.h>
63
#include <stdlib.h>
64
#include <string.h>
65
66
0
#define MAXORDER 3
67
68
namespace
69
{
70
struct Control_Points
71
{
72
    int count;
73
    double *e1;
74
    double *n1;
75
    double *e2;
76
    double *n2;
77
    int *status;
78
};
79
}  // namespace
80
81
struct GCPTransformInfo
82
{
83
    GDALTransformerInfo sTI{};
84
85
    double adfToGeoX[20]{};
86
    double adfToGeoY[20]{};
87
88
    double adfFromGeoX[20]{};
89
    double adfFromGeoY[20]{};
90
    double x1_mean{};
91
    double y1_mean{};
92
    double x2_mean{};
93
    double y2_mean{};
94
    int nOrder{};
95
    int bReversed{};
96
97
    std::vector<gdal::GCP> asGCPs{};
98
    int bRefine{};
99
    int nMinimumGcps{};
100
    double dfTolerance{};
101
102
    volatile int nRefCount{};
103
};
104
105
CPL_C_START
106
CPLXMLNode *GDALSerializeGCPTransformer(void *pTransformArg);
107
void *GDALDeserializeGCPTransformer(CPLXMLNode *psTree);
108
CPL_C_END
109
110
/* crs.c */
111
static int CRS_georef(double, double, double *, double *, double[], double[],
112
                      int);
113
static int CRS_compute_georef_equations(GCPTransformInfo *psInfo,
114
                                        struct Control_Points *, double[],
115
                                        double[], double[], double[], int);
116
static int remove_outliers(GCPTransformInfo *);
117
118
0
#define MSUCCESS 1     /* SUCCESS */
119
0
#define MNPTERR 0      /* NOT ENOUGH POINTS */
120
0
#define MUNSOLVABLE -1 /* NOT SOLVABLE */
121
0
#define MMEMERR -2     /* NOT ENOUGH MEMORY */
122
0
#define MPARMERR -3    /* PARAMETER ERROR */
123
0
#define MINTERR -4     /* INTERNAL ERROR */
124
125
static const char *const CRS_error_message[] = {
126
    "Failed to compute GCP transform: Not enough points available",
127
    "Failed to compute GCP transform: Transform is not solvable",
128
    "Failed to compute GCP transform: Not enough memory",
129
    "Failed to compute GCP transform: Parameter error",
130
    "Failed to compute GCP transform: Internal error"};
131
132
/************************************************************************/
133
/*                   GDALCreateSimilarGCPTransformer()                  */
134
/************************************************************************/
135
136
static void *GDALCreateSimilarGCPTransformer(void *hTransformArg,
137
                                             double dfRatioX, double dfRatioY)
138
0
{
139
0
    GCPTransformInfo *psInfo = static_cast<GCPTransformInfo *>(hTransformArg);
140
141
0
    VALIDATE_POINTER1(hTransformArg, "GDALCreateSimilarGCPTransformer",
142
0
                      nullptr);
143
144
0
    if (dfRatioX == 1.0 && dfRatioY == 1.0)
145
0
    {
146
        /* We can just use a ref count, since using the source transformation */
147
        /* is thread-safe */
148
0
        CPLAtomicInc(&(psInfo->nRefCount));
149
0
    }
150
0
    else
151
0
    {
152
0
        auto newGCPs = psInfo->asGCPs;
153
0
        for (auto &gcp : newGCPs)
154
0
        {
155
0
            gcp.Pixel() /= dfRatioX;
156
0
            gcp.Line() /= dfRatioY;
157
0
        }
158
        /* As remove_outliers modifies the provided GCPs we don't need to
159
         * reapply it */
160
0
        psInfo = static_cast<GCPTransformInfo *>(GDALCreateGCPTransformer(
161
0
            static_cast<int>(newGCPs.size()), gdal::GCP::c_ptr(newGCPs),
162
0
            psInfo->nOrder, psInfo->bReversed));
163
0
    }
164
165
0
    return psInfo;
166
0
}
167
168
/************************************************************************/
169
/*                      GDALCreateGCPTransformer()                      */
170
/************************************************************************/
171
172
static void *GDALCreateGCPTransformerEx(int nGCPCount,
173
                                        const GDAL_GCP *pasGCPList,
174
                                        int nReqOrder, bool bReversed,
175
                                        bool bRefine, double dfTolerance,
176
                                        int nMinimumGcps)
177
178
0
{
179
    // If no minimumGcp parameter was passed, we  use the default value
180
    // according to the model
181
0
    if (bRefine && nMinimumGcps == -1)
182
0
    {
183
0
        nMinimumGcps = ((nReqOrder + 1) * (nReqOrder + 2)) / 2 + 1;
184
0
    }
185
186
0
    GCPTransformInfo *psInfo = nullptr;
187
0
    double *padfGeoX = nullptr;
188
0
    double *padfGeoY = nullptr;
189
0
    double *padfRasterX = nullptr;
190
0
    double *padfRasterY = nullptr;
191
0
    int *panStatus = nullptr;
192
0
    int iGCP = 0;
193
0
    int nCRSresult = 0;
194
0
    struct Control_Points sPoints;
195
196
0
    double x1_sum = 0;
197
0
    double y1_sum = 0;
198
0
    double x2_sum = 0;
199
0
    double y2_sum = 0;
200
201
0
    memset(&sPoints, 0, sizeof(sPoints));
202
203
0
    if (nReqOrder == 0)
204
0
    {
205
0
        if (nGCPCount >= 10)
206
0
            nReqOrder = 2; /*for now we avoid 3rd order since it is unstable*/
207
0
        else if (nGCPCount >= 6)
208
0
            nReqOrder = 2;
209
0
        else
210
0
            nReqOrder = 1;
211
0
    }
212
213
0
    psInfo = new GCPTransformInfo();
214
0
    psInfo->bReversed = bReversed;
215
0
    psInfo->nOrder = nReqOrder;
216
0
    psInfo->bRefine = bRefine;
217
0
    psInfo->dfTolerance = dfTolerance;
218
0
    psInfo->nMinimumGcps = nMinimumGcps;
219
220
0
    psInfo->nRefCount = 1;
221
222
0
    psInfo->asGCPs = gdal::GCP::fromC(pasGCPList, nGCPCount);
223
0
    if (nGCPCount == 2 && nReqOrder == 1 &&
224
0
        psInfo->asGCPs[0].X() != psInfo->asGCPs[1].X() &&
225
0
        psInfo->asGCPs[0].Y() != psInfo->asGCPs[1].Y())
226
0
    {
227
        // Assumes that the 2 GCPs form opposite corners of a rectangle,
228
        // and synthetize a 3rd corner
229
0
        gdal::GCP newGCP;
230
0
        newGCP.X() = psInfo->asGCPs[1].X();
231
0
        newGCP.Y() = psInfo->asGCPs[0].Y();
232
0
        newGCP.Pixel() = psInfo->asGCPs[1].Pixel();
233
0
        newGCP.Line() = psInfo->asGCPs[0].Line();
234
0
        psInfo->asGCPs.emplace_back(std::move(newGCP));
235
236
0
        nGCPCount = 3;
237
0
        pasGCPList = gdal::GCP::c_ptr(psInfo->asGCPs);
238
0
    }
239
240
0
    memcpy(psInfo->sTI.abySignature, GDAL_GTI2_SIGNATURE,
241
0
           strlen(GDAL_GTI2_SIGNATURE));
242
0
    psInfo->sTI.pszClassName = "GDALGCPTransformer";
243
0
    psInfo->sTI.pfnTransform = GDALGCPTransform;
244
0
    psInfo->sTI.pfnCleanup = GDALDestroyGCPTransformer;
245
0
    psInfo->sTI.pfnSerialize = GDALSerializeGCPTransformer;
246
0
    psInfo->sTI.pfnCreateSimilar = GDALCreateSimilarGCPTransformer;
247
248
    /* -------------------------------------------------------------------- */
249
    /*      Compute the forward and reverse polynomials.                    */
250
    /* -------------------------------------------------------------------- */
251
252
0
    if (nGCPCount == 0)
253
0
    {
254
0
        nCRSresult = MNPTERR;
255
0
    }
256
0
    else if (bRefine)
257
0
    {
258
0
        nCRSresult = remove_outliers(psInfo);
259
0
    }
260
0
    else
261
0
    {
262
        /* --------------------------------------------------------------------
263
         */
264
        /*      Allocate and initialize the working points list. */
265
        /* --------------------------------------------------------------------
266
         */
267
0
        try
268
0
        {
269
0
            padfGeoX = new double[nGCPCount];
270
0
            padfGeoY = new double[nGCPCount];
271
0
            padfRasterX = new double[nGCPCount];
272
0
            padfRasterY = new double[nGCPCount];
273
0
            panStatus = new int[nGCPCount];
274
0
            for (iGCP = 0; iGCP < nGCPCount; iGCP++)
275
0
            {
276
0
                panStatus[iGCP] = 1;
277
0
                padfGeoX[iGCP] = pasGCPList[iGCP].dfGCPX;
278
0
                padfGeoY[iGCP] = pasGCPList[iGCP].dfGCPY;
279
0
                padfRasterX[iGCP] = pasGCPList[iGCP].dfGCPPixel;
280
0
                padfRasterY[iGCP] = pasGCPList[iGCP].dfGCPLine;
281
0
                x1_sum += pasGCPList[iGCP].dfGCPPixel;
282
0
                y1_sum += pasGCPList[iGCP].dfGCPLine;
283
0
                x2_sum += pasGCPList[iGCP].dfGCPX;
284
0
                y2_sum += pasGCPList[iGCP].dfGCPY;
285
0
            }
286
0
            psInfo->x1_mean = x1_sum / nGCPCount;
287
0
            psInfo->y1_mean = y1_sum / nGCPCount;
288
0
            psInfo->x2_mean = x2_sum / nGCPCount;
289
0
            psInfo->y2_mean = y2_sum / nGCPCount;
290
291
0
            sPoints.count = nGCPCount;
292
0
            sPoints.e1 = padfRasterX;
293
0
            sPoints.n1 = padfRasterY;
294
0
            sPoints.e2 = padfGeoX;
295
0
            sPoints.n2 = padfGeoY;
296
0
            sPoints.status = panStatus;
297
0
            nCRSresult = CRS_compute_georef_equations(
298
0
                psInfo, &sPoints, psInfo->adfToGeoX, psInfo->adfToGeoY,
299
0
                psInfo->adfFromGeoX, psInfo->adfFromGeoY, nReqOrder);
300
0
        }
301
0
        catch (const std::exception &e)
302
0
        {
303
0
            CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what());
304
0
            nCRSresult = MINTERR;
305
0
        }
306
0
        delete[] padfGeoX;
307
0
        delete[] padfGeoY;
308
0
        delete[] padfRasterX;
309
0
        delete[] padfRasterY;
310
0
        delete[] panStatus;
311
0
    }
312
313
0
    if (nCRSresult != 1)
314
0
    {
315
0
        CPLError(CE_Failure, CPLE_AppDefined, "%s",
316
0
                 CRS_error_message[-nCRSresult]);
317
0
        GDALDestroyGCPTransformer(psInfo);
318
0
        return nullptr;
319
0
    }
320
0
    else
321
0
    {
322
0
        return psInfo;
323
0
    }
324
0
}
325
326
/**
327
 * Create GCP based polynomial transformer.
328
 *
329
 * Computes least squares fit polynomials from a provided set of GCPs,
330
 * and stores the coefficients for later transformation of points between
331
 * pixel/line and georeferenced coordinates.
332
 *
333
 * The return value should be used as a TransformArg in combination with
334
 * the transformation function GDALGCPTransform which fits the
335
 * GDALTransformerFunc signature.  The returned transform argument should
336
 * be deallocated with GDALDestroyGCPTransformer when no longer needed.
337
 *
338
 * This function may fail (returning nullptr) if the provided set of GCPs
339
 * are inadequate for the requested order, the determinate is zero or they
340
 * are otherwise "ill conditioned".
341
 *
342
 * Note that 2nd order requires at least 6 GCPs, and 3rd order requires at
343
 * least 10 gcps.  If nReqOrder is 0 the highest order possible (limited to 2)
344
 * with the provided gcp count will be used.
345
 *
346
 * @param nGCPCount the number of GCPs in pasGCPList.
347
 * @param pasGCPList an array of GCPs to be used as input.
348
 * @param nReqOrder the requested polynomial order.  It should be 1, 2 or 3.
349
 * Using 3 is not recommended due to potential numeric instabilities issues.
350
 * @param bReversed set it to TRUE to compute the reversed transformation.
351
 *
352
 * @return the transform argument or nullptr if creation fails.
353
 */
354
void *GDALCreateGCPTransformer(int nGCPCount, const GDAL_GCP *pasGCPList,
355
                               int nReqOrder, int bReversed)
356
357
0
{
358
0
    return GDALCreateGCPTransformerEx(nGCPCount, pasGCPList, nReqOrder,
359
0
                                      CPL_TO_BOOL(bReversed), false, -1, -1);
360
0
}
361
362
/** Create GCP based polynomial transformer, with a tolerance threshold to
363
 * discard GCPs that transform badly.
364
 */
365
void *GDALCreateGCPRefineTransformer(int nGCPCount, const GDAL_GCP *pasGCPList,
366
                                     int nReqOrder, int bReversed,
367
                                     double dfTolerance, int nMinimumGcps)
368
369
0
{
370
0
    return GDALCreateGCPTransformerEx(nGCPCount, pasGCPList, nReqOrder,
371
0
                                      CPL_TO_BOOL(bReversed), true, dfTolerance,
372
0
                                      nMinimumGcps);
373
0
}
374
375
/************************************************************************/
376
/*                     GDALDestroyGCPTransformer()                      */
377
/************************************************************************/
378
379
/**
380
 * Destroy GCP transformer.
381
 *
382
 * This function is used to destroy information about a GCP based
383
 * polynomial transformation created with GDALCreateGCPTransformer().
384
 *
385
 * @param pTransformArg the transform arg previously returned by
386
 * GDALCreateGCPTransformer().
387
 */
388
389
void GDALDestroyGCPTransformer(void *pTransformArg)
390
391
0
{
392
0
    if (pTransformArg == nullptr)
393
0
        return;
394
395
0
    GCPTransformInfo *psInfo = static_cast<GCPTransformInfo *>(pTransformArg);
396
397
0
    if (CPLAtomicDec(&(psInfo->nRefCount)) == 0)
398
0
    {
399
0
        delete psInfo;
400
0
    }
401
0
}
402
403
/************************************************************************/
404
/*                          GDALGCPTransform()                          */
405
/************************************************************************/
406
407
/**
408
 * Transforms point based on GCP derived polynomial model.
409
 *
410
 * This function matches the GDALTransformerFunc signature, and can be
411
 * used to transform one or more points from pixel/line coordinates to
412
 * georeferenced coordinates (SrcToDst) or vice versa (DstToSrc).
413
 *
414
 * @param pTransformArg return value from GDALCreateGCPTransformer().
415
 * @param bDstToSrc TRUE if transformation is from the destination
416
 * (georeferenced) coordinates to pixel/line or FALSE when transforming
417
 * from pixel/line to georeferenced coordinates.
418
 * @param nPointCount the number of values in the x, y and z arrays.
419
 * @param x array containing the X values to be transformed.
420
 * @param y array containing the Y values to be transformed.
421
 * @param z array containing the Z values to be transformed.
422
 * @param panSuccess array in which a flag indicating success (TRUE) or
423
 * failure (FALSE) of the transformation are placed.
424
 *
425
 * @return TRUE if all points have been successfully transformed.
426
 */
427
428
int GDALGCPTransform(void *pTransformArg, int bDstToSrc, int nPointCount,
429
                     double *x, double *y, CPL_UNUSED double *z,
430
                     int *panSuccess)
431
432
0
{
433
0
    int i = 0;
434
0
    GCPTransformInfo *psInfo = static_cast<GCPTransformInfo *>(pTransformArg);
435
436
0
    if (psInfo->bReversed)
437
0
        bDstToSrc = !bDstToSrc;
438
439
0
    int bRet = TRUE;
440
0
    for (i = 0; i < nPointCount; i++)
441
0
    {
442
0
        if (x[i] == HUGE_VAL || y[i] == HUGE_VAL)
443
0
        {
444
0
            bRet = FALSE;
445
0
            panSuccess[i] = FALSE;
446
0
            continue;
447
0
        }
448
449
0
        if (bDstToSrc)
450
0
        {
451
0
            CRS_georef(x[i] - psInfo->x2_mean, y[i] - psInfo->y2_mean, x + i,
452
0
                       y + i, psInfo->adfFromGeoX, psInfo->adfFromGeoY,
453
0
                       psInfo->nOrder);
454
0
        }
455
0
        else
456
0
        {
457
0
            CRS_georef(x[i] - psInfo->x1_mean, y[i] - psInfo->y1_mean, x + i,
458
0
                       y + i, psInfo->adfToGeoX, psInfo->adfToGeoY,
459
0
                       psInfo->nOrder);
460
0
        }
461
0
        panSuccess[i] = TRUE;
462
0
    }
463
464
0
    return bRet;
465
0
}
466
467
/************************************************************************/
468
/*                    GDALSerializeGCPTransformer()                     */
469
/************************************************************************/
470
471
CPLXMLNode *GDALSerializeGCPTransformer(void *pTransformArg)
472
473
0
{
474
0
    CPLXMLNode *psTree = nullptr;
475
0
    GCPTransformInfo *psInfo = static_cast<GCPTransformInfo *>(pTransformArg);
476
477
0
    VALIDATE_POINTER1(pTransformArg, "GDALSerializeGCPTransformer", nullptr);
478
479
0
    psTree = CPLCreateXMLNode(nullptr, CXT_Element, "GCPTransformer");
480
481
    /* -------------------------------------------------------------------- */
482
    /*      Serialize Order and bReversed.                                  */
483
    /* -------------------------------------------------------------------- */
484
0
    CPLCreateXMLElementAndValue(psTree, "Order",
485
0
                                CPLSPrintf("%d", psInfo->nOrder));
486
487
0
    CPLCreateXMLElementAndValue(psTree, "Reversed",
488
0
                                CPLSPrintf("%d", psInfo->bReversed));
489
490
0
    if (psInfo->bRefine)
491
0
    {
492
0
        CPLCreateXMLElementAndValue(psTree, "Refine",
493
0
                                    CPLSPrintf("%d", psInfo->bRefine));
494
495
0
        CPLCreateXMLElementAndValue(psTree, "MinimumGcps",
496
0
                                    CPLSPrintf("%d", psInfo->nMinimumGcps));
497
498
0
        CPLCreateXMLElementAndValue(psTree, "Tolerance",
499
0
                                    CPLSPrintf("%f", psInfo->dfTolerance));
500
0
    }
501
502
    /* -------------------------------------------------------------------- */
503
    /*     Attach GCP List.                                                 */
504
    /* -------------------------------------------------------------------- */
505
0
    if (!psInfo->asGCPs.empty())
506
0
    {
507
0
        if (psInfo->bRefine)
508
0
        {
509
0
            remove_outliers(psInfo);
510
0
        }
511
512
0
        GDALSerializeGCPListToXML(psTree, psInfo->asGCPs, nullptr);
513
0
    }
514
515
0
    return psTree;
516
0
}
517
518
/************************************************************************/
519
/*               GDALDeserializeReprojectionTransformer()               */
520
/************************************************************************/
521
522
void *GDALDeserializeGCPTransformer(CPLXMLNode *psTree)
523
524
0
{
525
0
    std::vector<gdal::GCP> asGCPs;
526
0
    void *pResult = nullptr;
527
0
    int nReqOrder = 0;
528
0
    int bReversed = 0;
529
0
    int bRefine = 0;
530
0
    int nMinimumGcps = 0;
531
0
    double dfTolerance = 0.0;
532
533
    /* -------------------------------------------------------------------- */
534
    /*      Check for GCPs.                                                 */
535
    /* -------------------------------------------------------------------- */
536
0
    CPLXMLNode *psGCPList = CPLGetXMLNode(psTree, "GCPList");
537
538
0
    if (psGCPList != nullptr)
539
0
    {
540
0
        GDALDeserializeGCPListFromXML(psGCPList, asGCPs, nullptr);
541
0
    }
542
543
    /* -------------------------------------------------------------------- */
544
    /*      Get other flags.                                                */
545
    /* -------------------------------------------------------------------- */
546
0
    nReqOrder = atoi(CPLGetXMLValue(psTree, "Order", "3"));
547
0
    bReversed = atoi(CPLGetXMLValue(psTree, "Reversed", "0"));
548
0
    bRefine = atoi(CPLGetXMLValue(psTree, "Refine", "0"));
549
0
    nMinimumGcps = atoi(CPLGetXMLValue(psTree, "MinimumGcps", "6"));
550
0
    dfTolerance = CPLAtof(CPLGetXMLValue(psTree, "Tolerance", "1.0"));
551
552
    /* -------------------------------------------------------------------- */
553
    /*      Generate transformation.                                        */
554
    /* -------------------------------------------------------------------- */
555
0
    if (bRefine)
556
0
    {
557
0
        pResult = GDALCreateGCPRefineTransformer(
558
0
            static_cast<int>(asGCPs.size()), gdal::GCP::c_ptr(asGCPs),
559
0
            nReqOrder, bReversed, dfTolerance, nMinimumGcps);
560
0
    }
561
0
    else
562
0
    {
563
0
        pResult = GDALCreateGCPTransformer(static_cast<int>(asGCPs.size()),
564
0
                                           gdal::GCP::c_ptr(asGCPs), nReqOrder,
565
0
                                           bReversed);
566
0
    }
567
568
0
    return pResult;
569
0
}
570
571
/************************************************************************/
572
/* ==================================================================== */
573
/*      Everything below this point derived from the CRS.C from GRASS.  */
574
/* ==================================================================== */
575
/************************************************************************/
576
577
/* STRUCTURE FOR USE INTERNALLY WITH THESE FUNCTIONS.  THESE FUNCTIONS EXPECT
578
   SQUARE MATRICES SO ONLY ONE VARIABLE IS GIVEN (N) FOR THE MATRIX SIZE */
579
580
struct MATRIX
581
{
582
    int n; /* SIZE OF THIS MATRIX (N x N) */
583
    double *v;
584
};
585
586
/* CALCULATE OFFSET INTO ARRAY BASED ON R/C */
587
588
0
#define M(row, col) m->v[(((row)-1) * (m->n)) + (col)-1]
589
590
/***************************************************************************/
591
/*
592
    FUNCTION PROTOTYPES FOR STATIC (INTERNAL) FUNCTIONS
593
*/
594
/***************************************************************************/
595
596
static int calccoef(struct Control_Points *, double, double, double *, double *,
597
                    int);
598
static int calcls(struct Control_Points *, struct MATRIX *, double, double,
599
                  double *, double *, double *, double *);
600
static int exactdet(struct Control_Points *, struct MATRIX *, double, double,
601
                    double *, double *, double *, double *);
602
static int solvemat(struct MATRIX *, double *, double *, double *, double *);
603
static double term(int, double, double);
604
605
/***************************************************************************/
606
/*
607
    TRANSFORM A SINGLE COORDINATE PAIR.
608
*/
609
/***************************************************************************/
610
611
static int
612
CRS_georef(double e1,  /* EASTINGS TO BE TRANSFORMED */
613
           double n1,  /* NORTHINGS TO BE TRANSFORMED */
614
           double *e,  /* EASTINGS TO BE TRANSFORMED */
615
           double *n,  /* NORTHINGS TO BE TRANSFORMED */
616
           double E[], /* EASTING COEFFICIENTS */
617
           double N[], /* NORTHING COEFFICIENTS */
618
           int order /* ORDER OF TRANSFORMATION TO BE PERFORMED, MUST MATCH THE
619
                     ORDER USED TO CALCULATE THE COEFFICIENTS */
620
)
621
0
{
622
0
    double e3 = 0.0;
623
0
    double e2n = 0.0;
624
0
    double en2 = 0.0;
625
0
    double n3 = 0.0;
626
0
    double e2 = 0.0;
627
0
    double en = 0.0;
628
0
    double n2 = 0.0;
629
630
0
    switch (order)
631
0
    {
632
0
        case 1:
633
634
0
            *e = E[0] + E[1] * e1 + E[2] * n1;
635
0
            *n = N[0] + N[1] * e1 + N[2] * n1;
636
0
            break;
637
638
0
        case 2:
639
640
0
            e2 = e1 * e1;
641
0
            n2 = n1 * n1;
642
0
            en = e1 * n1;
643
644
0
            *e = E[0] + E[1] * e1 + E[2] * n1 + E[3] * e2 + E[4] * en +
645
0
                 E[5] * n2;
646
0
            *n = N[0] + N[1] * e1 + N[2] * n1 + N[3] * e2 + N[4] * en +
647
0
                 N[5] * n2;
648
0
            break;
649
650
0
        case 3:
651
652
0
            e2 = e1 * e1;
653
0
            en = e1 * n1;
654
0
            n2 = n1 * n1;
655
0
            e3 = e1 * e2;
656
0
            e2n = e2 * n1;
657
0
            en2 = e1 * n2;
658
0
            n3 = n1 * n2;
659
660
0
            *e = E[0] + E[1] * e1 + E[2] * n1 + E[3] * e2 + E[4] * en +
661
0
                 E[5] * n2 + E[6] * e3 + E[7] * e2n + E[8] * en2 + E[9] * n3;
662
0
            *n = N[0] + N[1] * e1 + N[2] * n1 + N[3] * e2 + N[4] * en +
663
0
                 N[5] * n2 + N[6] * e3 + N[7] * e2n + N[8] * en2 + N[9] * n3;
664
0
            break;
665
666
0
        default:
667
668
0
            return (MPARMERR);
669
0
    }
670
671
0
    return (MSUCCESS);
672
0
}
673
674
/***************************************************************************/
675
/*
676
    COMPUTE THE GEOREFFERENCING COEFFICIENTS BASED ON A SET OF CONTROL POINTS
677
*/
678
/***************************************************************************/
679
680
static int CRS_compute_georef_equations(GCPTransformInfo *psInfo,
681
                                        struct Control_Points *cp, double E12[],
682
                                        double N12[], double E21[],
683
                                        double N21[], int order)
684
0
{
685
0
    double *tempptr = nullptr;
686
0
    int status = 0;
687
688
0
    if (order < 1 || order > MAXORDER)
689
0
        return (MPARMERR);
690
691
    /* CALCULATE THE FORWARD TRANSFORMATION COEFFICIENTS */
692
693
0
    status = calccoef(cp, psInfo->x1_mean, psInfo->y1_mean, E12, N12, order);
694
0
    if (status != MSUCCESS)
695
0
        return (status);
696
697
    /* SWITCH THE 1 AND 2 EASTING AND NORTHING ARRAYS */
698
699
0
    tempptr = cp->e1;
700
0
    cp->e1 = cp->e2;
701
0
    cp->e2 = tempptr;
702
0
    tempptr = cp->n1;
703
0
    cp->n1 = cp->n2;
704
0
    cp->n2 = tempptr;
705
706
    /* CALCULATE THE BACKWARD TRANSFORMATION COEFFICIENTS */
707
708
0
    status = calccoef(cp, psInfo->x2_mean, psInfo->y2_mean, E21, N21, order);
709
710
    /* SWITCH THE 1 AND 2 EASTING AND NORTHING ARRAYS BACK */
711
712
0
    tempptr = cp->e1;
713
0
    cp->e1 = cp->e2;
714
0
    cp->e2 = tempptr;
715
0
    tempptr = cp->n1;
716
0
    cp->n1 = cp->n2;
717
0
    cp->n2 = tempptr;
718
719
0
    return (status);
720
0
}
721
722
/***************************************************************************/
723
/*
724
    COMPUTE THE GEOREFFERENCING COEFFICIENTS BASED ON A SET OF CONTROL POINTS
725
*/
726
/***************************************************************************/
727
728
static int calccoef(struct Control_Points *cp, double x_mean, double y_mean,
729
                    double E[], double N[], int order)
730
0
{
731
0
    struct MATRIX m;
732
0
    double *a = nullptr;
733
0
    double *b = nullptr;
734
0
    int numactive = 0; /* NUMBER OF ACTIVE CONTROL POINTS */
735
0
    int status = 0;
736
0
    int i = 0;
737
738
0
    memset(&m, 0, sizeof(m));
739
740
    /* CALCULATE THE NUMBER OF VALID CONTROL POINTS */
741
742
0
    for (i = numactive = 0; i < cp->count; i++)
743
0
    {
744
0
        if (cp->status[i] > 0)
745
0
            numactive++;
746
0
    }
747
748
    /* CALCULATE THE MINIMUM NUMBER OF CONTROL POINTS NEEDED TO DETERMINE
749
       A TRANSFORMATION OF THIS ORDER */
750
751
0
    m.n = ((order + 1) * (order + 2)) / 2;
752
753
0
    if (numactive < m.n)
754
0
        return (MNPTERR);
755
756
    /* INITIALIZE MATRIX */
757
758
0
    m.v = static_cast<double *>(
759
0
        VSICalloc(cpl::fits_on<int>(m.n * m.n), sizeof(double)));
760
0
    if (m.v == nullptr)
761
0
    {
762
0
        return (MMEMERR);
763
0
    }
764
0
    a = static_cast<double *>(VSICalloc(m.n, sizeof(double)));
765
0
    if (a == nullptr)
766
0
    {
767
0
        CPLFree(m.v);
768
0
        return (MMEMERR);
769
0
    }
770
0
    b = static_cast<double *>(VSICalloc(m.n, sizeof(double)));
771
0
    if (b == nullptr)
772
0
    {
773
0
        CPLFree(m.v);
774
0
        CPLFree(a);
775
0
        return (MMEMERR);
776
0
    }
777
778
0
    if (numactive == m.n)
779
0
        status = exactdet(cp, &m, x_mean, y_mean, a, b, E, N);
780
0
    else
781
0
        status = calcls(cp, &m, x_mean, y_mean, a, b, E, N);
782
783
0
    CPLFree(m.v);
784
0
    CPLFree(a);
785
0
    CPLFree(b);
786
787
0
    return (status);
788
0
}
789
790
/***************************************************************************/
791
/*
792
    CALCULATE THE TRANSFORMATION COEFFICIENTS WITH EXACTLY THE MINIMUM
793
    NUMBER OF CONTROL POINTS REQUIRED FOR THIS TRANSFORMATION.
794
*/
795
/***************************************************************************/
796
797
static int exactdet(struct Control_Points *cp, struct MATRIX *m, double x_mean,
798
                    double y_mean, double a[], double b[],
799
                    double E[], /* EASTING COEFFICIENTS */
800
                    double N[]  /* NORTHING COEFFICIENTS */
801
)
802
0
{
803
0
    int currow = 1;
804
805
0
    for (int pntnow = 0; pntnow < cp->count; pntnow++)
806
0
    {
807
0
        if (cp->status[pntnow] > 0)
808
0
        {
809
            /* POPULATE MATRIX M */
810
811
0
            for (int j = 1; j <= m->n; j++)
812
0
            {
813
0
                M(currow, j) =
814
0
                    term(j, cp->e1[pntnow] - x_mean, cp->n1[pntnow] - y_mean);
815
0
            }
816
817
            /* POPULATE MATRIX A AND B */
818
819
0
            a[currow - 1] = cp->e2[pntnow];
820
0
            b[currow - 1] = cp->n2[pntnow];
821
822
0
            currow++;
823
0
        }
824
0
    }
825
826
0
    if (currow - 1 != m->n)
827
0
        return (MINTERR);
828
829
0
    return (solvemat(m, a, b, E, N));
830
0
}
831
832
/***************************************************************************/
833
/*
834
    CALCULATE THE TRANSFORMATION COEFFICIENTS WITH MORE THAN THE MINIMUM
835
    NUMBER OF CONTROL POINTS REQUIRED FOR THIS TRANSFORMATION.  THIS
836
    ROUTINE USES THE LEAST SQUARES METHOD TO COMPUTE THE COEFFICIENTS.
837
*/
838
/***************************************************************************/
839
840
static int calcls(struct Control_Points *cp, struct MATRIX *m, double x_mean,
841
                  double y_mean, double a[], double b[],
842
                  double E[], /* EASTING COEFFICIENTS */
843
                  double N[]  /* NORTHING COEFFICIENTS */
844
)
845
0
{
846
0
    int numactive = 0;
847
848
    /* INITIALIZE THE UPPER HALF OF THE MATRIX AND THE TWO COLUMN VECTORS */
849
850
0
    for (int i = 1; i <= m->n; i++)
851
0
    {
852
0
        for (int j = i; j <= m->n; j++)
853
0
            M(i, j) = 0.0;
854
0
        a[i - 1] = b[i - 1] = 0.0;
855
0
    }
856
857
    /* SUM THE UPPER HALF OF THE MATRIX AND THE COLUMN VECTORS ACCORDING TO
858
       THE LEAST SQUARES METHOD OF SOLVING OVER DETERMINED SYSTEMS */
859
860
0
    for (int n = 0; n < cp->count; n++)
861
0
    {
862
0
        if (cp->status[n] > 0)
863
0
        {
864
0
            numactive++;
865
0
            for (int i = 1; i <= m->n; i++)
866
0
            {
867
0
                for (int j = i; j <= m->n; j++)
868
0
                    M(i, j) += term(i, cp->e1[n] - x_mean, cp->n1[n] - y_mean) *
869
0
                               term(j, cp->e1[n] - x_mean, cp->n1[n] - y_mean);
870
871
0
                a[i - 1] +=
872
0
                    cp->e2[n] * term(i, cp->e1[n] - x_mean, cp->n1[n] - y_mean);
873
0
                b[i - 1] +=
874
0
                    cp->n2[n] * term(i, cp->e1[n] - x_mean, cp->n1[n] - y_mean);
875
0
            }
876
0
        }
877
0
    }
878
879
0
    if (numactive <= m->n)
880
0
        return (MINTERR);
881
882
    /* TRANSPOSE VALUES IN UPPER HALF OF M TO OTHER HALF */
883
884
0
    for (int i = 2; i <= m->n; i++)
885
0
    {
886
0
        for (int j = 1; j < i; j++)
887
0
            M(i, j) = M(j, i);
888
0
    }
889
890
0
    return (solvemat(m, a, b, E, N));
891
0
}
892
893
/***************************************************************************/
894
/*
895
    CALCULATE THE X/Y TERM BASED ON THE TERM NUMBER
896
897
ORDER\TERM   1    2    3    4    5    6    7    8    9   10
898
  1        e0n0 e1n0 e0n1
899
  2        e0n0 e1n0 e0n1 e2n0 e1n1 e0n2
900
  3        e0n0 e1n0 e0n1 e2n0 e1n1 e0n2 e3n0 e2n1 e1n2 e0n3
901
*/
902
/***************************************************************************/
903
904
static double term(int nTerm, double e, double n)
905
0
{
906
0
    switch (nTerm)
907
0
    {
908
0
        case 1:
909
0
            return (1.0);
910
0
        case 2:
911
0
            return (e);
912
0
        case 3:
913
0
            return (n);
914
0
        case 4:
915
0
            return ((e * e));
916
0
        case 5:
917
0
            return ((e * n));
918
0
        case 6:
919
0
            return ((n * n));
920
0
        case 7:
921
0
            return ((e * e * e));
922
0
        case 8:
923
0
            return ((e * e * n));
924
0
        case 9:
925
0
            return ((e * n * n));
926
0
        case 10:
927
0
            return ((n * n * n));
928
0
    }
929
0
    return 0.0;
930
0
}
931
932
/***************************************************************************/
933
/*
934
    SOLVE FOR THE 'E' AND 'N' COEFFICIENTS BY USING A SOMEWHAT MODIFIED
935
    GAUSSIAN ELIMINATION METHOD.
936
937
    | M11 M12 ... M1n | | E0   |   | a0   |
938
    | M21 M22 ... M2n | | E1   | = | a1   |
939
    |  .   .   .   .  | | .    |   | .    |
940
    | Mn1 Mn2 ... Mnn | | En-1 |   | an-1 |
941
942
    and
943
944
    | M11 M12 ... M1n | | N0   |   | b0   |
945
    | M21 M22 ... M2n | | N1   | = | b1   |
946
    |  .   .   .   .  | | .    |   | .    |
947
    | Mn1 Mn2 ... Mnn | | Nn-1 |   | bn-1 |
948
*/
949
/***************************************************************************/
950
951
static int solvemat(struct MATRIX *m, double a[], double b[], double E[],
952
                    double N[])
953
0
{
954
0
    for (int i = 1; i <= m->n; i++)
955
0
    {
956
0
        int j = i;
957
958
        /* find row with largest magnitude value for pivot value */
959
960
0
        double pivot =
961
0
            M(i, j); /* ACTUAL VALUE OF THE LARGEST PIVOT CANDIDATE */
962
0
        int imark = i;
963
0
        for (int i2 = i + 1; i2 <= m->n; i2++)
964
0
        {
965
0
            if (fabs(M(i2, j)) > fabs(pivot))
966
0
            {
967
0
                pivot = M(i2, j);
968
0
                imark = i2;
969
0
            }
970
0
        }
971
972
        /* if the pivot is very small then the points are nearly co-linear */
973
        /* co-linear points result in an undefined matrix, and nearly */
974
        /* co-linear points results in a solution with rounding error */
975
976
0
        if (pivot == 0.0)
977
0
            return (MUNSOLVABLE);
978
979
        /* if row with highest pivot is not the current row, switch them */
980
981
0
        if (imark != i)
982
0
        {
983
0
            for (int j2 = 1; j2 <= m->n; j2++)
984
0
            {
985
0
                std::swap(M(imark, j2), M(i, j2));
986
0
            }
987
988
0
            std::swap(a[imark - 1], a[i - 1]);
989
0
            std::swap(b[imark - 1], b[i - 1]);
990
0
        }
991
992
        /* compute zeros above and below the pivot, and compute
993
           values for the rest of the row as well */
994
995
0
        for (int i2 = 1; i2 <= m->n; i2++)
996
0
        {
997
0
            if (i2 != i)
998
0
            {
999
0
                const double factor = M(i2, j) / pivot;
1000
0
                for (int j2 = j; j2 <= m->n; j2++)
1001
0
                    M(i2, j2) -= factor * M(i, j2);
1002
0
                a[i2 - 1] -= factor * a[i - 1];
1003
0
                b[i2 - 1] -= factor * b[i - 1];
1004
0
            }
1005
0
        }
1006
0
    }
1007
1008
    /* SINCE ALL OTHER VALUES IN THE MATRIX ARE ZERO NOW, CALCULATE THE
1009
       COEFFICIENTS BY DIVIDING THE COLUMN VECTORS BY THE DIAGONAL VALUES. */
1010
1011
0
    for (int i = 1; i <= m->n; i++)
1012
0
    {
1013
0
        E[i - 1] = a[i - 1] / M(i, i);
1014
0
        N[i - 1] = b[i - 1] / M(i, i);
1015
0
    }
1016
1017
0
    return (MSUCCESS);
1018
0
}
1019
1020
/***************************************************************************/
1021
/*
1022
  DETECTS THE WORST OUTLIER IN THE GCP LIST AND RETURNS THE INDEX OF THE
1023
  OUTLIER.
1024
1025
  THE WORST OUTLIER IS CALCULATED BASED ON THE CONTROL POINTS, COEFFICIENTS
1026
  AND THE ALLOWED TOLERANCE:
1027
1028
  sampleAdj = a0 + a1*sample + a2*line + a3*line*sample
1029
  lineAdj = b0 + b1*sample + b2*line + b3*line*sample
1030
1031
  WHERE sampleAdj AND lineAdj ARE CORRELATED GCPS
1032
1033
  [residualSample] = [A1][sampleCoefficients] - [b1]
1034
  [residualLine] = [A2][lineCoefficients] - [b2]
1035
1036
  sampleResidual^2 = sum( [residualSample]^2 )
1037
  lineResidual^2 = sum( [lineSample]^2 )
1038
1039
  residuals(i) = squareRoot( residualSample(i)^2 + residualLine(i)^2 )
1040
1041
  THE GCP WITH THE GREATEST DISTANCE residual(i) GREATER THAN THE TOLERANCE WILL
1042
  CONSIDERED THE WORST OUTLIER.
1043
1044
  IF NO OUTLIER CAN BE FOUND, -1 WILL BE RETURNED.
1045
*/
1046
/***************************************************************************/
1047
static int worst_outlier(struct Control_Points *cp, double x_mean,
1048
                         double y_mean, int nOrder, double E[], double N[],
1049
                         double dfTolerance)
1050
0
{
1051
    // double dfSampleResidual = 0.0;
1052
    // double dfLineResidual = 0.0;
1053
0
    double *padfResiduals =
1054
0
        static_cast<double *>(CPLCalloc(sizeof(double), cp->count));
1055
1056
0
    for (int nI = 0; nI < cp->count; nI++)
1057
0
    {
1058
0
        double dfSampleRes = 0.0;
1059
0
        double dfLineRes = 0.0;
1060
0
        CRS_georef(cp->e1[nI] - x_mean, cp->n1[nI] - y_mean, &dfSampleRes,
1061
0
                   &dfLineRes, E, N, nOrder);
1062
0
        dfSampleRes -= cp->e2[nI];
1063
0
        dfLineRes -= cp->n2[nI];
1064
        // dfSampleResidual += dfSampleRes*dfSampleRes;
1065
        // dfLineResidual += dfLineRes*dfLineRes;
1066
1067
0
        padfResiduals[nI] =
1068
0
            sqrt(dfSampleRes * dfSampleRes + dfLineRes * dfLineRes);
1069
0
    }
1070
1071
0
    int nIndex = -1;
1072
0
    double dfDifference = -1.0;
1073
0
    for (int nI = 0; nI < cp->count; nI++)
1074
0
    {
1075
0
        double dfCurrentDifference = padfResiduals[nI];
1076
0
        if (fabs(dfCurrentDifference) < 1.19209290E-07F /*FLT_EPSILON*/)
1077
0
        {
1078
0
            dfCurrentDifference = 0.0;
1079
0
        }
1080
0
        if (dfCurrentDifference > dfDifference &&
1081
0
            dfCurrentDifference >= dfTolerance)
1082
0
        {
1083
0
            dfDifference = dfCurrentDifference;
1084
0
            nIndex = nI;
1085
0
        }
1086
0
    }
1087
0
    CPLFree(padfResiduals);
1088
0
    return nIndex;
1089
0
}
1090
1091
/***************************************************************************/
1092
/*
1093
  REMOVES THE WORST OUTLIERS ITERATIVELY UNTIL THE MINIMUM NUMBER OF GCPS
1094
  ARE REACHED OR NO OUTLIERS CAN BE DETECTED.
1095
1096
  1. WE CALCULATE THE COEFFICIENTS FOR ALL THE GCPS.
1097
  2. THE GCP LIST WILL BE SCANNED TO DETERMINE THE WORST OUTLIER USING
1098
     THE CALCULATED COEFFICIENTS.
1099
  3. THE WORST OUTLIER WILL BE REMOVED FROM THE GCP LIST.
1100
  4. THE COEFFICIENTS WILL BE RECALCULATED WITHOUT THE WORST OUTLIER.
1101
  5. STEP 1 TO 4 ARE EXECUTED UNTIL THE MINIMUM NUMBER OF GCPS WERE REACHED
1102
     OR IF NO GCP IS CONSIDERED AN OUTLIER WITH THE PASSED TOLERANCE.
1103
*/
1104
/***************************************************************************/
1105
static int remove_outliers(GCPTransformInfo *psInfo)
1106
0
{
1107
0
    double *padfGeoX = nullptr;
1108
0
    double *padfGeoY = nullptr;
1109
0
    double *padfRasterX = nullptr;
1110
0
    double *padfRasterY = nullptr;
1111
0
    int *panStatus = nullptr;
1112
0
    int nCRSresult = 0;
1113
0
    int nGCPCount = 0;
1114
0
    int nMinimumGcps = 0;
1115
0
    int nReqOrder = 0;
1116
0
    double dfTolerance = 0;
1117
0
    struct Control_Points sPoints;
1118
1119
0
    double x1_sum = 0;
1120
0
    double y1_sum = 0;
1121
0
    double x2_sum = 0;
1122
0
    double y2_sum = 0;
1123
0
    memset(&sPoints, 0, sizeof(sPoints));
1124
1125
0
    nGCPCount = static_cast<int>(psInfo->asGCPs.size());
1126
0
    nMinimumGcps = psInfo->nMinimumGcps;
1127
0
    nReqOrder = psInfo->nOrder;
1128
0
    dfTolerance = psInfo->dfTolerance;
1129
1130
0
    try
1131
0
    {
1132
0
        padfGeoX = new double[nGCPCount];
1133
0
        padfGeoY = new double[nGCPCount];
1134
0
        padfRasterX = new double[nGCPCount];
1135
0
        padfRasterY = new double[nGCPCount];
1136
0
        panStatus = new int[nGCPCount];
1137
1138
0
        for (int nI = 0; nI < nGCPCount; nI++)
1139
0
        {
1140
0
            panStatus[nI] = 1;
1141
0
            padfGeoX[nI] = psInfo->asGCPs[nI].X();
1142
0
            padfGeoY[nI] = psInfo->asGCPs[nI].Y();
1143
0
            padfRasterX[nI] = psInfo->asGCPs[nI].Pixel();
1144
0
            padfRasterY[nI] = psInfo->asGCPs[nI].Line();
1145
0
            x1_sum += psInfo->asGCPs[nI].Pixel();
1146
0
            y1_sum += psInfo->asGCPs[nI].Line();
1147
0
            x2_sum += psInfo->asGCPs[nI].X();
1148
0
            y2_sum += psInfo->asGCPs[nI].Y();
1149
0
        }
1150
0
        psInfo->x1_mean = x1_sum / nGCPCount;
1151
0
        psInfo->y1_mean = y1_sum / nGCPCount;
1152
0
        psInfo->x2_mean = x2_sum / nGCPCount;
1153
0
        psInfo->y2_mean = y2_sum / nGCPCount;
1154
1155
0
        sPoints.count = nGCPCount;
1156
0
        sPoints.e1 = padfRasterX;
1157
0
        sPoints.n1 = padfRasterY;
1158
0
        sPoints.e2 = padfGeoX;
1159
0
        sPoints.n2 = padfGeoY;
1160
0
        sPoints.status = panStatus;
1161
1162
0
        nCRSresult = CRS_compute_georef_equations(
1163
0
            psInfo, &sPoints, psInfo->adfToGeoX, psInfo->adfToGeoY,
1164
0
            psInfo->adfFromGeoX, psInfo->adfFromGeoY, nReqOrder);
1165
1166
0
        while (sPoints.count > nMinimumGcps)
1167
0
        {
1168
0
            int nIndex = worst_outlier(
1169
0
                &sPoints, psInfo->x1_mean, psInfo->y1_mean, psInfo->nOrder,
1170
0
                psInfo->adfToGeoX, psInfo->adfToGeoY, dfTolerance);
1171
1172
            // If no outliers were detected, stop the GCP elimination
1173
0
            if (nIndex == -1)
1174
0
            {
1175
0
                break;
1176
0
            }
1177
1178
0
            for (int nI = nIndex; nI < sPoints.count - 1; nI++)
1179
0
            {
1180
0
                sPoints.e1[nI] = sPoints.e1[nI + 1];
1181
0
                sPoints.n1[nI] = sPoints.n1[nI + 1];
1182
0
                sPoints.e2[nI] = sPoints.e2[nI + 1];
1183
0
                sPoints.n2[nI] = sPoints.n2[nI + 1];
1184
0
                psInfo->asGCPs[nI].SetId(psInfo->asGCPs[nI + 1].Id());
1185
0
                psInfo->asGCPs[nI].SetInfo(psInfo->asGCPs[nI + 1].Info());
1186
0
            }
1187
1188
0
            sPoints.count = sPoints.count - 1;
1189
1190
0
            nCRSresult = CRS_compute_georef_equations(
1191
0
                psInfo, &sPoints, psInfo->adfToGeoX, psInfo->adfToGeoY,
1192
0
                psInfo->adfFromGeoX, psInfo->adfFromGeoY, nReqOrder);
1193
0
        }
1194
1195
0
        for (int nI = 0; nI < sPoints.count; nI++)
1196
0
        {
1197
0
            psInfo->asGCPs[nI].X() = sPoints.e2[nI];
1198
0
            psInfo->asGCPs[nI].Y() = sPoints.n2[nI];
1199
0
            psInfo->asGCPs[nI].Pixel() = sPoints.e1[nI];
1200
0
            psInfo->asGCPs[nI].Line() = sPoints.n1[nI];
1201
0
        }
1202
0
        psInfo->asGCPs.resize(sPoints.count);
1203
0
    }
1204
0
    catch (const std::exception &e)
1205
0
    {
1206
0
        CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what());
1207
0
        nCRSresult = MINTERR;
1208
0
    }
1209
0
    delete[] padfGeoX;
1210
0
    delete[] padfGeoY;
1211
0
    delete[] padfRasterX;
1212
0
    delete[] padfRasterY;
1213
0
    delete[] panStatus;
1214
1215
0
    return nCRSresult;
1216
0
}