Coverage Report

Created: 2025-06-13 06:29

/src/gdal/alg/gdaltransformer.cpp
Line
Count
Source (jump to first uncovered line)
1
/******************************************************************************
2
 *
3
 * Project:  Mapinfo Image Warper
4
 * Purpose:  Implementation of one or more GDALTrasformerFunc types, including
5
 *           the GenImgProj (general image reprojector) transformer.
6
 * Author:   Frank Warmerdam, warmerdam@pobox.com
7
 *
8
 ******************************************************************************
9
 * Copyright (c) 2002, i3 - information integration and imaging
10
 *                          Fort Collin, CO
11
 * Copyright (c) 2008-2013, Even Rouault <even dot rouault at spatialys.com>
12
 * Copyright (c) 2021, CLS
13
 *
14
 * SPDX-License-Identifier: MIT
15
 ****************************************************************************/
16
17
#include "cpl_port.h"
18
#include "gdal_alg.h"
19
#include "gdal_alg_priv.h"
20
21
#include <climits>
22
#include <cmath>
23
#include <cstddef>
24
#include <cstdlib>
25
#include <cstring>
26
27
#include <algorithm>
28
#include <limits>
29
#include <utility>
30
31
#include "cpl_conv.h"
32
#include "cpl_error.h"
33
#include "cpl_list.h"
34
#include "cpl_minixml.h"
35
#include "cpl_multiproc.h"
36
#include "cpl_string.h"
37
#include "cpl_vsi.h"
38
#include "gdal.h"
39
#include "gdal_priv.h"
40
#include "ogr_core.h"
41
#include "ogr_spatialref.h"
42
#include "ogr_srs_api.h"
43
44
CPL_C_START
45
void *GDALDeserializeGCPTransformer(CPLXMLNode *psTree);
46
void *GDALDeserializeTPSTransformer(CPLXMLNode *psTree);
47
void *GDALDeserializeGeoLocTransformer(CPLXMLNode *psTree);
48
void *GDALDeserializeRPCTransformer(CPLXMLNode *psTree);
49
void *GDALDeserializeHomographyTransformer(CPLXMLNode *psTree);
50
CPL_C_END
51
52
static CPLXMLNode *GDALSerializeReprojectionTransformer(void *pTransformArg);
53
static void *GDALDeserializeReprojectionTransformer(CPLXMLNode *psTree);
54
55
static CPLXMLNode *GDALSerializeGenImgProjTransformer(void *pTransformArg);
56
static void *GDALDeserializeGenImgProjTransformer(CPLXMLNode *psTree);
57
58
static void *GDALCreateApproxTransformer2(GDALTransformerFunc pfnRawTransformer,
59
                                          void *pRawTransformerArg,
60
                                          double dfMaxErrorForward,
61
                                          double dfMaxErrorReverse);
62
63
/************************************************************************/
64
/*                            GDALIsTransformer()                       */
65
/************************************************************************/
66
67
bool GDALIsTransformer(void *hTransformerArg, const char *pszClassName)
68
0
{
69
0
    if (!hTransformerArg)
70
0
        return false;
71
    // All transformers should have a GDALTransformerInfo member as their first members
72
0
    GDALTransformerInfo *psInfo =
73
0
        static_cast<GDALTransformerInfo *>(hTransformerArg);
74
0
    return memcmp(psInfo->abySignature, GDAL_GTI2_SIGNATURE,
75
0
                  strlen(GDAL_GTI2_SIGNATURE)) == 0 &&
76
0
           strcmp(psInfo->pszClassName, pszClassName) == 0;
77
0
}
78
79
/************************************************************************/
80
/*                          GDALTransformFunc                           */
81
/*                                                                      */
82
/*      Documentation for GDALTransformFunc typedef.                    */
83
/************************************************************************/
84
85
/*!
86
87
\typedef typedef int (*GDALTransformerFunc)( void *pTransformerArg, int
88
bDstToSrc, int nPointCount, double *x, double *y, double *z, int *panSuccess );
89
90
Generic signature for spatial point transformers.
91
92
This function signature is used for a variety of functions that accept
93
passed in functions used to transform point locations between two coordinate
94
spaces.
95
96
The GDALCreateGenImgProjTransformer(), GDALCreateReprojectionTransformerEx(),
97
GDALCreateGCPTransformer() and GDALCreateApproxTransformer() functions can
98
be used to prepare argument data for some built-in transformers.  As well,
99
applications can implement their own transformers to the following signature.
100
101
\code
102
typedef int
103
(*GDALTransformerFunc)( void *pTransformerArg,
104
                        int bDstToSrc, int nPointCount,
105
                        double *x, double *y, double *z, int *panSuccess );
106
\endcode
107
108
@param pTransformerArg application supplied callback data used by the
109
transformer.
110
111
@param bDstToSrc if TRUE the transformation will be from the destination
112
coordinate space to the source coordinate system, otherwise the transformation
113
will be from the source coordinate system to the destination coordinate system.
114
115
@param nPointCount number of points in the x, y and z arrays.
116
117
@param[in,out] x input X coordinates.  Results returned in same array.
118
119
@param[in,out] y input Y coordinates.  Results returned in same array.
120
121
@param[in,out] z input Z coordinates.  Results returned in same array.
122
123
@param[out] panSuccess array of ints in which success (TRUE) or failure (FALSE)
124
flags are returned for the translation of each point. Must not be NULL.
125
126
@return TRUE if all points have been successfully transformed (changed in 3.11,
127
previously was TRUE if some points have been successfully transformed)
128
129
*/
130
131
/************************************************************************/
132
/*                      GDALSuggestedWarpOutput()                       */
133
/************************************************************************/
134
135
/**
136
 * Suggest output file size.
137
 *
138
 * This function is used to suggest the size, and georeferenced extents
139
 * appropriate given the indicated transformation and input file.  It walks
140
 * the edges of the input file (approximately 20 sample points along each
141
 * edge) transforming into output coordinates in order to get an extents box.
142
 *
143
 * Then a resolution is computed with the intent that the length of the
144
 * distance from the top left corner of the output imagery to the bottom right
145
 * corner would represent the same number of pixels as in the source image.
146
 * Note that if the image is somewhat rotated the diagonal taken isn't of the
147
 * whole output bounding rectangle, but instead of the locations where the
148
 * top/left and bottom/right corners transform.  The output pixel size is
149
 * always square.  This is intended to approximately preserve the resolution
150
 * of the input data in the output file.
151
 *
152
 * The values returned in padfGeoTransformOut, pnPixels and pnLines are
153
 * the suggested number of pixels and lines for the output file, and the
154
 * geotransform relating those pixels to the output georeferenced coordinates.
155
 *
156
 * The trickiest part of using the function is ensuring that the
157
 * transformer created is from source file pixel/line coordinates to
158
 * output file georeferenced coordinates.  This can be accomplished with
159
 * GDALCreateGenImgProjTransformer() by passing a NULL for the hDstDS.
160
 *
161
 * @param hSrcDS the input image (it is assumed the whole input image is
162
 * being transformed).
163
 * @param pfnTransformer the transformer function.
164
 * @param pTransformArg the callback data for the transformer function.
165
 * @param padfGeoTransformOut the array of six doubles in which the suggested
166
 * geotransform is returned.
167
 * @param pnPixels int in which the suggest pixel width of output is returned.
168
 * @param pnLines int in which the suggest pixel height of output is returned.
169
 *
170
 * @return CE_None if successful or CE_Failure otherwise.
171
 */
172
173
CPLErr CPL_STDCALL GDALSuggestedWarpOutput(GDALDatasetH hSrcDS,
174
                                           GDALTransformerFunc pfnTransformer,
175
                                           void *pTransformArg,
176
                                           double *padfGeoTransformOut,
177
                                           int *pnPixels, int *pnLines)
178
179
0
{
180
0
    VALIDATE_POINTER1(hSrcDS, "GDALSuggestedWarpOutput", CE_Failure);
181
182
0
    double adfExtent[4] = {};
183
184
0
    return GDALSuggestedWarpOutput2(hSrcDS, pfnTransformer, pTransformArg,
185
0
                                    padfGeoTransformOut, pnPixels, pnLines,
186
0
                                    adfExtent, 0);
187
0
}
188
189
static bool GDALSuggestedWarpOutput2_MustAdjustForRightBorder(
190
    GDALTransformerFunc pfnTransformer, void *pTransformArg, double *padfExtent,
191
    int /* nPixels*/, int nLines, double dfPixelSizeX, double dfPixelSizeY)
192
0
{
193
0
    double adfX[21] = {};
194
0
    double adfY[21] = {};
195
196
0
    const double dfMaxXOut = padfExtent[2];
197
0
    const double dfMaxYOut = padfExtent[3];
198
199
    // Take 20 steps.
200
0
    int nSamplePoints = 0;
201
0
    for (double dfRatio = 0.0; dfRatio <= 1.01; dfRatio += 0.05)
202
0
    {
203
        // Ensure we end exactly at the end.
204
0
        if (dfRatio > 0.99)
205
0
            dfRatio = 1.0;
206
207
        // Along right.
208
0
        adfX[nSamplePoints] = dfMaxXOut;
209
0
        adfY[nSamplePoints] = dfMaxYOut - dfPixelSizeY * dfRatio * nLines;
210
0
        nSamplePoints++;
211
0
    }
212
0
    double adfZ[21] = {};
213
214
0
    int abSuccess[21] = {};
215
216
0
    pfnTransformer(pTransformArg, TRUE, nSamplePoints, adfX, adfY, adfZ,
217
0
                   abSuccess);
218
219
0
    int abSuccess2[21] = {};
220
221
0
    pfnTransformer(pTransformArg, FALSE, nSamplePoints, adfX, adfY, adfZ,
222
0
                   abSuccess2);
223
224
0
    nSamplePoints = 0;
225
0
    int nBadCount = 0;
226
0
    for (double dfRatio = 0.0; dfRatio <= 1.01; dfRatio += 0.05)
227
0
    {
228
0
        const double expected_x = dfMaxXOut;
229
0
        const double expected_y = dfMaxYOut - dfPixelSizeY * dfRatio * nLines;
230
0
        if (!abSuccess[nSamplePoints] || !abSuccess2[nSamplePoints] ||
231
0
            fabs(adfX[nSamplePoints] - expected_x) > dfPixelSizeX ||
232
0
            fabs(adfY[nSamplePoints] - expected_y) > dfPixelSizeY)
233
0
        {
234
0
            nBadCount++;
235
0
        }
236
0
        nSamplePoints++;
237
0
    }
238
239
0
    return nBadCount == nSamplePoints;
240
0
}
241
242
static bool GDALSuggestedWarpOutput2_MustAdjustForBottomBorder(
243
    GDALTransformerFunc pfnTransformer, void *pTransformArg, double *padfExtent,
244
    int nPixels, int /* nLines */, double dfPixelSizeX, double dfPixelSizeY)
245
0
{
246
0
    double adfX[21] = {};
247
0
    double adfY[21] = {};
248
249
0
    const double dfMinXOut = padfExtent[0];
250
0
    const double dfMinYOut = padfExtent[1];
251
252
    // Take 20 steps.
253
0
    int nSamplePoints = 0;
254
0
    for (double dfRatio = 0.0; dfRatio <= 1.01; dfRatio += 0.05)
255
0
    {
256
        // Ensure we end exactly at the end.
257
0
        if (dfRatio > 0.99)
258
0
            dfRatio = 1.0;
259
260
        // Along right.
261
0
        adfX[nSamplePoints] = dfMinXOut + dfPixelSizeX * dfRatio * nPixels;
262
0
        adfY[nSamplePoints] = dfMinYOut;
263
0
        nSamplePoints++;
264
0
    }
265
0
    double adfZ[21] = {};
266
267
0
    int abSuccess[21] = {};
268
269
0
    pfnTransformer(pTransformArg, TRUE, nSamplePoints, adfX, adfY, adfZ,
270
0
                   abSuccess);
271
272
0
    int abSuccess2[21] = {};
273
274
0
    pfnTransformer(pTransformArg, FALSE, nSamplePoints, adfX, adfY, adfZ,
275
0
                   abSuccess2);
276
277
0
    nSamplePoints = 0;
278
0
    int nBadCount = 0;
279
0
    for (double dfRatio = 0.0; dfRatio <= 1.01; dfRatio += 0.05)
280
0
    {
281
0
        const double expected_x = dfMinXOut + dfPixelSizeX * dfRatio * nPixels;
282
0
        const double expected_y = dfMinYOut;
283
0
        if (!abSuccess[nSamplePoints] || !abSuccess2[nSamplePoints] ||
284
0
            fabs(adfX[nSamplePoints] - expected_x) > dfPixelSizeX ||
285
0
            fabs(adfY[nSamplePoints] - expected_y) > dfPixelSizeY)
286
0
        {
287
0
            nBadCount++;
288
0
        }
289
0
        nSamplePoints++;
290
0
    }
291
292
0
    return nBadCount == nSamplePoints;
293
0
}
294
295
/************************************************************************/
296
/*                      GDALSuggestedWarpOutput2()                      */
297
/************************************************************************/
298
299
/**
300
 * Suggest output file size.
301
 *
302
 * This function is used to suggest the size, and georeferenced extents
303
 * appropriate given the indicated transformation and input file.  It walks
304
 * the edges of the input file (approximately 20 sample points along each
305
 * edge) transforming into output coordinates in order to get an extents box.
306
 *
307
 * Then a resolution is computed with the intent that the length of the
308
 * distance from the top left corner of the output imagery to the bottom right
309
 * corner would represent the same number of pixels as in the source image.
310
 * Note that if the image is somewhat rotated the diagonal taken isn't of the
311
 * whole output bounding rectangle, but instead of the locations where the
312
 * top/left and bottom/right corners transform.  The output pixel size is
313
 * always square.  This is intended to approximately preserve the resolution
314
 * of the input data in the output file.
315
 *
316
 * The values returned in padfGeoTransformOut, pnPixels and pnLines are
317
 * the suggested number of pixels and lines for the output file, and the
318
 * geotransform relating those pixels to the output georeferenced coordinates.
319
 *
320
 * The trickiest part of using the function is ensuring that the
321
 * transformer created is from source file pixel/line coordinates to
322
 * output file georeferenced coordinates.  This can be accomplished with
323
 * GDALCreateGenImgProjTransformer() by passing a NULL for the hDstDS.
324
 *
325
 * @param hSrcDS the input image (it is assumed the whole input image is
326
 * being transformed).
327
 * @param pfnTransformer the transformer function.
328
 * @param pTransformArg the callback data for the transformer function.
329
 * @param padfGeoTransformOut the array of six doubles in which the suggested
330
 * geotransform is returned.
331
 * @param pnPixels int in which the suggest pixel width of output is returned.
332
 * @param pnLines int in which the suggest pixel height of output is returned.
333
 * @param padfExtent Four entry array to return extents as (xmin, ymin, xmax,
334
 * ymax).
335
 * @param nOptions Options flags. Zero or GDAL_SWO_ROUND_UP_SIZE  to ask *pnPixels
336
 * and *pnLines to be rounded up instead of being rounded to the closes integer, or
337
 * GDAL_SWO_FORCE_SQUARE_PIXEL to indicate that the generated pixel size is a square.
338
 *
339
 * @return CE_None if successful or CE_Failure otherwise.
340
 */
341
342
CPLErr CPL_STDCALL GDALSuggestedWarpOutput2(GDALDatasetH hSrcDS,
343
                                            GDALTransformerFunc pfnTransformer,
344
                                            void *pTransformArg,
345
                                            double *padfGeoTransformOut,
346
                                            int *pnPixels, int *pnLines,
347
                                            double *padfExtent, int nOptions)
348
0
{
349
0
    VALIDATE_POINTER1(hSrcDS, "GDALSuggestedWarpOutput2", CE_Failure);
350
351
0
    const bool bIsGDALGenImgProjTransform{
352
0
        pTransformArg &&
353
0
        GDALIsTransformer(pTransformArg, GDAL_GEN_IMG_TRANSFORMER_CLASS_NAME)};
354
355
    /* -------------------------------------------------------------------- */
356
    /*      Setup sample points all around the edge of the input raster.    */
357
    /* -------------------------------------------------------------------- */
358
0
    if (bIsGDALGenImgProjTransform)
359
0
    {
360
        // In case CHECK_WITH_INVERT_PROJ has been modified.
361
0
        GDALRefreshGenImgProjTransformer(pTransformArg);
362
0
    }
363
0
    else if (GDALIsTransformer(pTransformArg,
364
0
                               GDAL_APPROX_TRANSFORMER_CLASS_NAME))
365
0
    {
366
        // In case CHECK_WITH_INVERT_PROJ has been modified.
367
0
        GDALRefreshApproxTransformer(pTransformArg);
368
0
    }
369
370
0
    const int nInXSize = GDALGetRasterXSize(hSrcDS);
371
0
    const int nInYSize = GDALGetRasterYSize(hSrcDS);
372
373
    /* ------------------------------------------------------------- */
374
    /* Special case for warping on the same (or null) CRS.           */
375
    /* ------------------------------------------------------------- */
376
0
    if ((!nOptions || (nOptions & GDAL_SWO_FORCE_SQUARE_PIXEL) == 0) &&
377
0
        pTransformArg && bIsGDALGenImgProjTransform)
378
0
    {
379
0
        const GDALGenImgProjTransformInfo *psInfo =
380
0
            static_cast<const GDALGenImgProjTransformInfo *>(pTransformArg);
381
382
0
        if (!psInfo->sSrcParams.pTransformer &&
383
0
            !psInfo->bHasCustomTransformationPipeline &&
384
0
            !psInfo->sDstParams.pTransformer &&
385
0
            psInfo->sSrcParams.adfGeoTransform[2] == 0 &&
386
0
            psInfo->sSrcParams.adfGeoTransform[4] == 0 &&
387
0
            psInfo->sDstParams.adfGeoTransform[0] == 0 &&
388
0
            psInfo->sDstParams.adfGeoTransform[1] == 1 &&
389
0
            psInfo->sDstParams.adfGeoTransform[2] == 0 &&
390
0
            psInfo->sDstParams.adfGeoTransform[3] == 0 &&
391
0
            psInfo->sDstParams.adfGeoTransform[4] == 0 &&
392
0
            psInfo->sDstParams.adfGeoTransform[5] == 1)
393
0
        {
394
0
            const OGRSpatialReference *poSourceCRS = nullptr;
395
0
            const OGRSpatialReference *poTargetCRS = nullptr;
396
397
0
            if (psInfo->pReprojectArg)
398
0
            {
399
0
                const GDALReprojectionTransformInfo *psRTI =
400
0
                    static_cast<const GDALReprojectionTransformInfo *>(
401
0
                        psInfo->pReprojectArg);
402
0
                poSourceCRS = psRTI->poForwardTransform->GetSourceCS();
403
0
                poTargetCRS = psRTI->poForwardTransform->GetTargetCS();
404
0
            }
405
406
0
            if ((!poSourceCRS && !poTargetCRS) ||
407
0
                (poSourceCRS && poTargetCRS &&
408
0
                 poSourceCRS->IsSame(poTargetCRS)))
409
0
            {
410
411
0
                const bool bNorthUp{psInfo->sSrcParams.adfGeoTransform[5] <
412
0
                                    0.0};
413
414
0
                memcpy(padfGeoTransformOut, psInfo->sSrcParams.adfGeoTransform,
415
0
                       sizeof(double) * 6);
416
417
0
                if (!bNorthUp)
418
0
                {
419
0
                    padfGeoTransformOut[3] = padfGeoTransformOut[3] +
420
0
                                             nInYSize * padfGeoTransformOut[5];
421
0
                    padfGeoTransformOut[5] = -padfGeoTransformOut[5];
422
0
                }
423
424
0
                *pnPixels = nInXSize;
425
0
                *pnLines = nInYSize;
426
427
                // Calculate extent from hSrcDS
428
0
                if (padfExtent)
429
0
                {
430
0
                    padfExtent[0] = psInfo->sSrcParams.adfGeoTransform[0];
431
0
                    padfExtent[1] =
432
0
                        psInfo->sSrcParams.adfGeoTransform[3] +
433
0
                        nInYSize * psInfo->sSrcParams.adfGeoTransform[5];
434
0
                    padfExtent[2] =
435
0
                        psInfo->sSrcParams.adfGeoTransform[0] +
436
0
                        nInXSize * psInfo->sSrcParams.adfGeoTransform[1];
437
0
                    padfExtent[3] = psInfo->sSrcParams.adfGeoTransform[3];
438
0
                    if (!bNorthUp)
439
0
                    {
440
0
                        std::swap(padfExtent[1], padfExtent[3]);
441
0
                    }
442
0
                }
443
0
                return CE_None;
444
0
            }
445
0
        }
446
0
    }
447
448
0
    const int N_PIXELSTEP = 50;
449
0
    int nSteps = static_cast<int>(
450
0
        static_cast<double>(std::min(nInYSize, nInXSize)) / N_PIXELSTEP + 0.5);
451
0
    if (nSteps < 20)
452
0
        nSteps = 20;
453
0
    else if (nSteps > 100)
454
0
        nSteps = 100;
455
456
    // TODO(rouault): How is this goto retry supposed to work?  Added in r20537.
457
    // Does redoing the same malloc multiple times work?  If it is needed, can
458
    // it be converted to a tigher while loop around the MALLOC3s and free?  Is
459
    // the point to try with the full requested steps.  Then, if there is not
460
    // enough memory, back off and try with just 20 steps?
461
0
retry:
462
0
    int nStepsPlusOne = nSteps + 1;
463
0
    int nSampleMax = nStepsPlusOne * nStepsPlusOne;
464
465
0
    double dfStep = 1.0 / nSteps;
466
0
    double *padfY = nullptr;
467
0
    double *padfZ = nullptr;
468
0
    double *padfYRevert = nullptr;
469
0
    double *padfZRevert = nullptr;
470
471
0
    int *pabSuccess = static_cast<int *>(
472
0
        VSI_MALLOC3_VERBOSE(sizeof(int), nStepsPlusOne, nStepsPlusOne));
473
0
    double *padfX = static_cast<double *>(
474
0
        VSI_MALLOC3_VERBOSE(sizeof(double) * 3, nStepsPlusOne, nStepsPlusOne));
475
0
    double *padfXRevert = static_cast<double *>(
476
0
        VSI_MALLOC3_VERBOSE(sizeof(double) * 3, nStepsPlusOne, nStepsPlusOne));
477
0
    if (pabSuccess == nullptr || padfX == nullptr || padfXRevert == nullptr)
478
0
    {
479
0
        CPLFree(padfX);
480
0
        CPLFree(padfXRevert);
481
0
        CPLFree(pabSuccess);
482
0
        if (nSteps > 20)
483
0
        {
484
0
            nSteps = 20;
485
0
            goto retry;
486
0
        }
487
0
        return CE_Failure;
488
0
    }
489
490
0
    padfY = padfX + nSampleMax;
491
0
    padfZ = padfX + nSampleMax * 2;
492
0
    padfYRevert = padfXRevert + nSampleMax;
493
0
    padfZRevert = padfXRevert + nSampleMax * 2;
494
495
    // Take N_STEPS steps.
496
0
    for (int iStep = 0; iStep <= nSteps; iStep++)
497
0
    {
498
0
        double dfRatio = (iStep == nSteps) ? 1.0 : iStep * dfStep;
499
0
        int iStep2 = iStep;
500
501
        // Along top.
502
0
        padfX[iStep2] = dfRatio * nInXSize;
503
0
        padfY[iStep2] = 0.0;
504
0
        padfZ[iStep2] = 0.0;
505
506
        // Along bottom.
507
0
        iStep2 += nStepsPlusOne;
508
0
        padfX[iStep2] = dfRatio * nInXSize;
509
0
        padfY[iStep2] = nInYSize;
510
0
        padfZ[iStep2] = 0.0;
511
512
        // Along left.
513
0
        iStep2 += nStepsPlusOne;
514
0
        padfX[iStep2] = 0.0;
515
0
        padfY[iStep2] = dfRatio * nInYSize;
516
0
        padfZ[iStep2] = 0.0;
517
518
        // Along right.
519
0
        iStep2 += nStepsPlusOne;
520
0
        padfX[iStep2] = nInXSize;
521
0
        padfY[iStep2] = dfRatio * nInYSize;
522
0
        padfZ[iStep2] = 0.0;
523
0
    }
524
525
0
    int nSamplePoints = 4 * nStepsPlusOne;
526
527
0
    memset(pabSuccess, 1, sizeof(int) * nSampleMax);
528
529
    /* -------------------------------------------------------------------- */
530
    /*      Transform them to the output coordinate system.                 */
531
    /* -------------------------------------------------------------------- */
532
0
    {
533
0
        CPLTurnFailureIntoWarningBackuper oErrorsToWarnings{};
534
0
        pfnTransformer(pTransformArg, FALSE, nSamplePoints, padfX, padfY, padfZ,
535
0
                       pabSuccess);
536
0
    }
537
0
    constexpr int SIGN_FINAL_UNINIT = -2;
538
0
    constexpr int SIGN_FINAL_INVALID = 0;
539
0
    int iSignDiscontinuity = SIGN_FINAL_UNINIT;
540
0
    int nFailedCount = 0;
541
0
    const int iSignArray[2] = {-1, 1};
542
0
    for (int i = 0; i < nSamplePoints; i++)
543
0
    {
544
0
        if (pabSuccess[i])
545
0
        {
546
            // Fix for https://trac.osgeo.org/gdal/ticket/7243
547
            // where echo "-2050000.000 2050000.000" |
548
            //              gdaltransform -s_srs EPSG:3411 -t_srs EPSG:4326
549
            // gives "-180 63.691332898492"
550
            // but we would rather like 180
551
0
            if (iSignDiscontinuity == 1 || iSignDiscontinuity == -1)
552
0
            {
553
0
                if (!((iSignDiscontinuity * padfX[i] > 0 &&
554
0
                       iSignDiscontinuity * padfX[i] <= 180.0) ||
555
0
                      (fabs(padfX[i] - iSignDiscontinuity * -180.0) < 1e-8)))
556
0
                {
557
0
                    iSignDiscontinuity = SIGN_FINAL_INVALID;
558
0
                }
559
0
            }
560
0
            else if (iSignDiscontinuity == SIGN_FINAL_UNINIT)
561
0
            {
562
0
                for (const auto &iSign : iSignArray)
563
0
                {
564
0
                    if ((iSign * padfX[i] > 0 && iSign * padfX[i] <= 180.0) ||
565
0
                        (fabs(padfX[i] - iSign * -180.0) < 1e-8))
566
0
                    {
567
0
                        iSignDiscontinuity = iSign;
568
0
                        break;
569
0
                    }
570
0
                }
571
0
                if (iSignDiscontinuity == SIGN_FINAL_UNINIT)
572
0
                {
573
0
                    iSignDiscontinuity = SIGN_FINAL_INVALID;
574
0
                }
575
0
            }
576
0
        }
577
0
        else
578
0
        {
579
0
            nFailedCount++;
580
0
        }
581
0
    }
582
583
0
    if (iSignDiscontinuity == 1 || iSignDiscontinuity == -1)
584
0
    {
585
0
        for (int i = 0; i < nSamplePoints; i++)
586
0
        {
587
0
            if (pabSuccess[i])
588
0
            {
589
0
                if (fabs(padfX[i] - iSignDiscontinuity * -180.0) < 1e-8)
590
0
                {
591
0
                    double axTemp[2] = {iSignDiscontinuity * -180.0,
592
0
                                        iSignDiscontinuity * 180.0};
593
0
                    double ayTemp[2] = {padfY[i], padfY[i]};
594
0
                    double azTemp[2] = {padfZ[i], padfZ[i]};
595
0
                    int abSuccess[2] = {FALSE, FALSE};
596
0
                    CPLTurnFailureIntoWarningBackuper oErrorsToWarnings{};
597
0
                    if (pfnTransformer(pTransformArg, TRUE, 2, axTemp, ayTemp,
598
0
                                       azTemp, abSuccess) &&
599
0
                        fabs(axTemp[0] - axTemp[1]) < 1e-8 &&
600
0
                        fabs(ayTemp[0] - ayTemp[1]) < 1e-8)
601
0
                    {
602
0
                        padfX[i] = iSignDiscontinuity * 180.0;
603
0
                    }
604
0
                }
605
0
            }
606
0
        }
607
0
    }
608
609
    /* -------------------------------------------------------------------- */
610
    /*      Check if the computed target coordinates are revertable.        */
611
    /*      If not, try the detailed grid sampling.                         */
612
    /* -------------------------------------------------------------------- */
613
0
    if (nFailedCount)
614
0
    {
615
0
        CPLDebug("WARP", "At least one point failed after direct transform");
616
0
    }
617
0
    else
618
0
    {
619
0
        memcpy(padfXRevert, padfX, nSamplePoints * sizeof(double));
620
0
        memcpy(padfYRevert, padfY, nSamplePoints * sizeof(double));
621
0
        memcpy(padfZRevert, padfZ, nSamplePoints * sizeof(double));
622
0
        {
623
0
            CPLTurnFailureIntoWarningBackuper oErrorsToWarnings{};
624
0
            pfnTransformer(pTransformArg, TRUE, nSamplePoints, padfXRevert,
625
0
                           padfYRevert, padfZRevert, pabSuccess);
626
0
        }
627
628
0
        for (int i = 0; nFailedCount == 0 && i < nSamplePoints; i++)
629
0
        {
630
0
            if (!pabSuccess[i])
631
0
            {
632
0
                nFailedCount++;
633
0
                break;
634
0
            }
635
636
0
            double dfRatio = (i % nStepsPlusOne) * dfStep;
637
0
            if (dfRatio > 0.99)
638
0
                dfRatio = 1.0;
639
640
0
            double dfExpectedX = 0.0;
641
0
            double dfExpectedY = 0.0;
642
0
            if (i < nStepsPlusOne)
643
0
            {
644
0
                dfExpectedX = dfRatio * nInXSize;
645
0
            }
646
0
            else if (i < 2 * nStepsPlusOne)
647
0
            {
648
0
                dfExpectedX = dfRatio * nInXSize;
649
0
                dfExpectedY = nInYSize;
650
0
            }
651
0
            else if (i < 3 * nStepsPlusOne)
652
0
            {
653
0
                dfExpectedY = dfRatio * nInYSize;
654
0
            }
655
0
            else
656
0
            {
657
0
                dfExpectedX = nInXSize;
658
0
                dfExpectedY = dfRatio * nInYSize;
659
0
            }
660
661
0
            if (fabs(padfXRevert[i] - dfExpectedX) >
662
0
                    nInXSize / static_cast<double>(nSteps) ||
663
0
                fabs(padfYRevert[i] - dfExpectedY) >
664
0
                    nInYSize / static_cast<double>(nSteps))
665
0
                nFailedCount++;
666
0
        }
667
0
        if (nFailedCount != 0)
668
0
            CPLDebug("WARP",
669
0
                     "At least one point failed after revert transform");
670
0
    }
671
672
    /* -------------------------------------------------------------------- */
673
    /*      If any of the edge points failed to transform, we need to       */
674
    /*      build a fairly detailed internal grid of points instead to      */
675
    /*      help identify the area that is transformable.                   */
676
    /* -------------------------------------------------------------------- */
677
0
    if (nFailedCount)
678
0
    {
679
0
        nSamplePoints = 0;
680
681
        // Take N_STEPS steps.
682
0
        for (int iStep = 0; iStep <= nSteps; iStep++)
683
0
        {
684
0
            double dfRatio = (iStep == nSteps) ? 1.0 : iStep * dfStep;
685
686
0
            for (int iStep2 = 0; iStep2 <= nSteps; iStep2++)
687
0
            {
688
0
                const double dfRatio2 =
689
0
                    iStep2 == nSteps ? 1.0 : iStep2 * dfStep;
690
691
                // From top to bottom, from left to right.
692
0
                padfX[nSamplePoints] = dfRatio2 * nInXSize;
693
0
                padfY[nSamplePoints] = dfRatio * nInYSize;
694
0
                padfZ[nSamplePoints] = 0.0;
695
0
                nSamplePoints++;
696
0
            }
697
0
        }
698
699
0
        CPLAssert(nSamplePoints == nSampleMax);
700
701
0
        {
702
0
            CPLTurnFailureIntoWarningBackuper oErrorsToWarnings{};
703
0
            pfnTransformer(pTransformArg, FALSE, nSamplePoints, padfX, padfY,
704
0
                           padfZ, pabSuccess);
705
0
        }
706
0
    }
707
708
    /* -------------------------------------------------------------------- */
709
    /*      Collect the bounds, ignoring any failed points.                 */
710
    /* -------------------------------------------------------------------- */
711
0
    double dfMinXOut = 0.0;
712
0
    double dfMinYOut = 0.0;
713
0
    double dfMaxXOut = 0.0;
714
0
    double dfMaxYOut = 0.0;
715
0
    bool bGotInitialPoint = false;
716
717
0
    nFailedCount = 0;
718
0
    for (int i = 0; i < nSamplePoints; i++)
719
0
    {
720
0
        int x_i = 0;
721
0
        int y_i = 0;
722
723
0
        if (nSamplePoints == nSampleMax)
724
0
        {
725
0
            x_i = i % nStepsPlusOne;
726
0
            y_i = i / nStepsPlusOne;
727
0
        }
728
0
        else
729
0
        {
730
0
            if (i < 2 * nStepsPlusOne)
731
0
            {
732
0
                x_i = i % nStepsPlusOne;
733
0
                y_i = (i < nStepsPlusOne) ? 0 : nSteps;
734
0
            }
735
0
        }
736
737
0
        if (x_i > 0 && (pabSuccess[i - 1] || pabSuccess[i]))
738
0
        {
739
0
            double x_out_before = padfX[i - 1];
740
0
            double x_out_after = padfX[i];
741
0
            int nIter = 0;
742
0
            double x_in_before =
743
0
                static_cast<double>(x_i - 1) * nInXSize / nSteps;
744
0
            double x_in_after = static_cast<double>(x_i) * nInXSize / nSteps;
745
0
            int invalid_before = !(pabSuccess[i - 1]);
746
0
            int invalid_after = !(pabSuccess[i]);
747
748
            // Detect discontinuity in target coordinates when the target x
749
            // coordinates change sign. This may be a false positive when the
750
            // target tx is around 0 Dichotomic search to reduce the interval
751
            // to near the discontinuity and get a better out extent.
752
0
            while ((invalid_before || invalid_after ||
753
0
                    x_out_before * x_out_after < 0.0) &&
754
0
                   nIter < 16)
755
0
            {
756
0
                double x = (x_in_before + x_in_after) / 2.0;
757
0
                double y = static_cast<double>(y_i) * nInYSize / nSteps;
758
0
                double z = 0.0;
759
0
                int bSuccess = TRUE;
760
0
                if (pfnTransformer(pTransformArg, FALSE, 1, &x, &y, &z,
761
0
                                   &bSuccess) &&
762
0
                    bSuccess)
763
0
                {
764
0
                    if (bGotInitialPoint)
765
0
                    {
766
0
                        dfMinXOut = std::min(dfMinXOut, x);
767
0
                        dfMinYOut = std::min(dfMinYOut, y);
768
0
                        dfMaxXOut = std::max(dfMaxXOut, x);
769
0
                        dfMaxYOut = std::max(dfMaxYOut, y);
770
0
                    }
771
0
                    else
772
0
                    {
773
0
                        bGotInitialPoint = true;
774
0
                        dfMinXOut = x;
775
0
                        dfMaxXOut = x;
776
0
                        dfMinYOut = y;
777
0
                        dfMaxYOut = y;
778
0
                    }
779
780
0
                    if (invalid_before || x_out_before * x < 0)
781
0
                    {
782
0
                        invalid_after = FALSE;
783
0
                        x_in_after = (x_in_before + x_in_after) / 2.0;
784
0
                        x_out_after = x;
785
0
                    }
786
0
                    else
787
0
                    {
788
0
                        invalid_before = FALSE;
789
0
                        x_out_before = x;
790
0
                        x_in_before = (x_in_before + x_in_after) / 2.0;
791
0
                    }
792
0
                }
793
0
                else
794
0
                {
795
0
                    if (invalid_before)
796
0
                    {
797
0
                        x_in_before = (x_in_before + x_in_after) / 2.0;
798
0
                    }
799
0
                    else if (invalid_after)
800
0
                    {
801
0
                        x_in_after = (x_in_before + x_in_after) / 2.0;
802
0
                    }
803
0
                    else
804
0
                    {
805
0
                        break;
806
0
                    }
807
0
                }
808
0
                nIter++;
809
0
            }
810
0
        }
811
812
0
        if (!pabSuccess[i])
813
0
        {
814
0
            nFailedCount++;
815
0
            continue;
816
0
        }
817
818
0
        if (bGotInitialPoint)
819
0
        {
820
0
            dfMinXOut = std::min(dfMinXOut, padfX[i]);
821
0
            dfMinYOut = std::min(dfMinYOut, padfY[i]);
822
0
            dfMaxXOut = std::max(dfMaxXOut, padfX[i]);
823
0
            dfMaxYOut = std::max(dfMaxYOut, padfY[i]);
824
0
        }
825
0
        else
826
0
        {
827
0
            bGotInitialPoint = true;
828
0
            dfMinXOut = padfX[i];
829
0
            dfMaxXOut = padfX[i];
830
0
            dfMinYOut = padfY[i];
831
0
            dfMaxYOut = padfY[i];
832
0
        }
833
0
    }
834
835
0
    if (nFailedCount > nSamplePoints - 10)
836
0
    {
837
0
        CPLError(CE_Failure, CPLE_AppDefined,
838
0
                 "Too many points (%d out of %d) failed to transform, "
839
0
                 "unable to compute output bounds.",
840
0
                 nFailedCount, nSamplePoints);
841
842
0
        CPLFree(padfX);
843
0
        CPLFree(padfXRevert);
844
0
        CPLFree(pabSuccess);
845
846
0
        return CE_Failure;
847
0
    }
848
849
0
    if (nFailedCount)
850
0
        CPLDebug("GDAL",
851
0
                 "GDALSuggestedWarpOutput(): %d out of %d points failed to "
852
0
                 "transform.",
853
0
                 nFailedCount, nSamplePoints);
854
855
0
    bool bIsGeographicCoordsDeg = false;
856
0
    if (bIsGDALGenImgProjTransform)
857
0
    {
858
0
        const GDALGenImgProjTransformInfo *pGIPTI =
859
0
            static_cast<const GDALGenImgProjTransformInfo *>(pTransformArg);
860
0
        if (pGIPTI->sSrcParams.pTransformer == GDALGeoLocTransform &&
861
0
            pGIPTI->sDstParams.pTransformer == nullptr &&
862
0
            pGIPTI->sDstParams.adfGeoTransform[0] == 0 &&
863
0
            pGIPTI->sDstParams.adfGeoTransform[1] == 1 &&
864
0
            pGIPTI->sDstParams.adfGeoTransform[2] == 0 &&
865
0
            pGIPTI->sDstParams.adfGeoTransform[3] == 0 &&
866
0
            pGIPTI->sDstParams.adfGeoTransform[4] == 0 &&
867
0
            pGIPTI->sDstParams.adfGeoTransform[5] == 1)
868
0
        {
869
            /* --------------------------------------------------------------------
870
             */
871
            /*      Special case for geolocation array, to quickly find the
872
             * bounds. */
873
            /* --------------------------------------------------------------------
874
             */
875
0
            const GDALGeoLocTransformInfo *pGLTI =
876
0
                static_cast<const GDALGeoLocTransformInfo *>(
877
0
                    pGIPTI->sSrcParams.pTransformArg);
878
879
0
            if (pGIPTI->pReproject == nullptr)
880
0
            {
881
0
                const char *pszGLSRS =
882
0
                    CSLFetchNameValue(pGLTI->papszGeolocationInfo, "SRS");
883
0
                if (pszGLSRS == nullptr)
884
0
                {
885
0
                    bIsGeographicCoordsDeg = true;
886
0
                }
887
0
                else
888
0
                {
889
0
                    OGRSpatialReference oSRS;
890
0
                    if (oSRS.SetFromUserInput(pszGLSRS) == OGRERR_NONE &&
891
0
                        oSRS.IsGeographic() &&
892
0
                        std::fabs(oSRS.GetAngularUnits() -
893
0
                                  CPLAtof(SRS_UA_DEGREE_CONV)) < 1e-9)
894
0
                    {
895
0
                        bIsGeographicCoordsDeg = true;
896
0
                    }
897
0
                }
898
0
            }
899
900
0
            for (const auto &xy :
901
0
                 {std::pair<double, double>(pGLTI->dfMinX, pGLTI->dfYAtMinX),
902
0
                  std::pair<double, double>(pGLTI->dfXAtMinY, pGLTI->dfMinY),
903
0
                  std::pair<double, double>(pGLTI->dfMaxX, pGLTI->dfYAtMaxX),
904
0
                  std::pair<double, double>(pGLTI->dfXAtMaxY, pGLTI->dfMaxY)})
905
0
            {
906
0
                double x = xy.first;
907
0
                double y = xy.second;
908
0
                if (pGLTI->bSwapXY)
909
0
                {
910
0
                    std::swap(x, y);
911
0
                }
912
0
                double xOut = std::numeric_limits<double>::quiet_NaN();
913
0
                double yOut = std::numeric_limits<double>::quiet_NaN();
914
0
                if (pGIPTI->pReproject == nullptr ||
915
0
                    pGIPTI->pReproject(pGIPTI->pReprojectArg, false, 1, &x, &y,
916
0
                                       nullptr, nullptr))
917
0
                {
918
0
                    xOut = x;
919
0
                    yOut = y;
920
0
                }
921
0
                dfMinXOut = std::min(dfMinXOut, xOut);
922
0
                dfMinYOut = std::min(dfMinYOut, yOut);
923
0
                dfMaxXOut = std::max(dfMaxXOut, xOut);
924
0
                dfMaxYOut = std::max(dfMaxYOut, yOut);
925
0
            }
926
0
        }
927
0
        else if (pGIPTI->sSrcParams.pTransformer == nullptr &&
928
0
                 pGIPTI->sDstParams.pTransformer == nullptr &&
929
0
                 pGIPTI->pReproject == GDALReprojectionTransform &&
930
0
                 pGIPTI->sDstParams.adfGeoTransform[0] == 0 &&
931
0
                 pGIPTI->sDstParams.adfGeoTransform[1] == 1 &&
932
0
                 pGIPTI->sDstParams.adfGeoTransform[2] == 0 &&
933
0
                 pGIPTI->sDstParams.adfGeoTransform[3] == 0 &&
934
0
                 pGIPTI->sDstParams.adfGeoTransform[4] == 0 &&
935
0
                 pGIPTI->sDstParams.adfGeoTransform[5] == 1)
936
0
        {
937
            /* ------------------------------------------------------------- */
938
            /* Special case for warping using source geotransform and        */
939
            /* reprojection to deal with the poles.                          */
940
            /* ------------------------------------------------------------- */
941
0
            const GDALReprojectionTransformInfo *psRTI =
942
0
                static_cast<const GDALReprojectionTransformInfo *>(
943
0
                    pGIPTI->pReprojectArg);
944
0
            const OGRSpatialReference *poSourceCRS =
945
0
                psRTI->poForwardTransform->GetSourceCS();
946
0
            const OGRSpatialReference *poTargetCRS =
947
0
                psRTI->poForwardTransform->GetTargetCS();
948
0
            if (poTargetCRS != nullptr &&
949
0
                psRTI->poReverseTransform != nullptr &&
950
0
                poTargetCRS->IsGeographic() &&
951
0
                fabs(poTargetCRS->GetAngularUnits() -
952
0
                     CPLAtof(SRS_UA_DEGREE_CONV)) < 1e-9 &&
953
0
                (!poSourceCRS || !poSourceCRS->IsGeographic()))
954
0
            {
955
0
                bIsGeographicCoordsDeg = true;
956
957
0
                std::unique_ptr<CPLConfigOptionSetter> poSetter;
958
0
                if (pGIPTI->bCheckWithInvertPROJ)
959
0
                {
960
                    // CHECK_WITH_INVERT_PROJ=YES prevent reliable
961
                    // transformation of poles.
962
0
                    poSetter = std::make_unique<CPLConfigOptionSetter>(
963
0
                        "CHECK_WITH_INVERT_PROJ", "NO", false);
964
0
                    GDALRefreshGenImgProjTransformer(pTransformArg);
965
                    // GDALRefreshGenImgProjTransformer() has invalidated psRTI
966
0
                    psRTI = static_cast<const GDALReprojectionTransformInfo *>(
967
0
                        pGIPTI->pReprojectArg);
968
0
                }
969
970
0
                for (const auto &sign : iSignArray)
971
0
                {
972
0
                    double X = 0.0;
973
0
                    const double Yinit = 90.0 * sign;
974
0
                    double Y = Yinit;
975
0
                    if (psRTI->poReverseTransform->Transform(1, &X, &Y))
976
0
                    {
977
0
                        const auto invGT =
978
0
                            pGIPTI->sSrcParams.adfInvGeoTransform;
979
0
                        const double x = invGT[0] + X * invGT[1] + Y * invGT[2];
980
0
                        const double y = invGT[3] + X * invGT[4] + Y * invGT[5];
981
0
                        constexpr double EPSILON = 1e-5;
982
0
                        if (x >= -EPSILON && x <= nInXSize + EPSILON &&
983
0
                            y >= -EPSILON && y <= nInYSize + EPSILON)
984
0
                        {
985
0
                            if (psRTI->poForwardTransform->Transform(1, &X,
986
0
                                                                     &Y) &&
987
0
                                fabs(Y - Yinit) <= 1e-6)
988
0
                            {
989
0
                                bool bMinXMaxXSet = false;
990
0
                                if (poSourceCRS)
991
0
                                {
992
0
                                    const char *pszProjection =
993
0
                                        poSourceCRS->GetAttrValue("PROJECTION");
994
0
                                    if (pszProjection &&
995
0
                                        EQUAL(pszProjection,
996
0
                                              SRS_PT_ORTHOGRAPHIC))
997
0
                                    {
998
0
                                        const double dfLon0 =
999
0
                                            poSourceCRS->GetNormProjParm(
1000
0
                                                SRS_PP_CENTRAL_MERIDIAN, 0.0);
1001
0
                                        dfMinXOut = dfLon0 - 90;
1002
0
                                        dfMaxXOut = dfLon0 + 90;
1003
0
                                        bMinXMaxXSet = true;
1004
0
                                    }
1005
0
                                }
1006
0
                                if (!bMinXMaxXSet)
1007
0
                                {
1008
0
                                    dfMinXOut = -180;
1009
0
                                    dfMaxXOut = 180;
1010
0
                                }
1011
0
                                if (sign < 0)
1012
0
                                    dfMinYOut = Yinit;
1013
0
                                else
1014
0
                                    dfMaxYOut = Yinit;
1015
0
                            }
1016
0
                        }
1017
0
                    }
1018
0
                }
1019
1020
0
                if (poSetter)
1021
0
                {
1022
0
                    poSetter.reset();
1023
0
                    GDALRefreshGenImgProjTransformer(pTransformArg);
1024
0
                    pGIPTI = static_cast<const GDALGenImgProjTransformInfo *>(
1025
0
                        pTransformArg);
1026
0
                    psRTI = static_cast<const GDALReprojectionTransformInfo *>(
1027
0
                        pGIPTI->pReprojectArg);
1028
0
                    poSourceCRS = psRTI->poForwardTransform->GetSourceCS();
1029
0
                    poTargetCRS = psRTI->poForwardTransform->GetTargetCS();
1030
0
                }
1031
0
            }
1032
1033
            // Use TransformBounds() to handle more particular cases
1034
0
            if (poSourceCRS != nullptr && poTargetCRS != nullptr &&
1035
0
                pGIPTI->sSrcParams.adfGeoTransform[1] != 0 &&
1036
0
                pGIPTI->sSrcParams.adfGeoTransform[2] == 0 &&
1037
0
                pGIPTI->sSrcParams.adfGeoTransform[4] == 0 &&
1038
0
                pGIPTI->sSrcParams.adfGeoTransform[5] != 0)
1039
0
            {
1040
0
                const double dfULX = pGIPTI->sSrcParams.adfGeoTransform[0];
1041
0
                const double dfULY = pGIPTI->sSrcParams.adfGeoTransform[3];
1042
0
                const double dfLRX =
1043
0
                    dfULX + pGIPTI->sSrcParams.adfGeoTransform[1] * nInXSize;
1044
0
                const double dfLRY =
1045
0
                    dfULY + pGIPTI->sSrcParams.adfGeoTransform[5] * nInYSize;
1046
0
                const double dfMinSrcX = std::min(dfULX, dfLRX);
1047
0
                const double dfMinSrcY = std::min(dfULY, dfLRY);
1048
0
                const double dfMaxSrcX = std::max(dfULX, dfLRX);
1049
0
                const double dfMaxSrcY = std::max(dfULY, dfLRY);
1050
0
                double dfTmpMinXOut = std::numeric_limits<double>::max();
1051
0
                double dfTmpMinYOut = std::numeric_limits<double>::max();
1052
0
                double dfTmpMaxXOut = std::numeric_limits<double>::min();
1053
0
                double dfTmpMaxYOut = std::numeric_limits<double>::min();
1054
0
                if (psRTI->poForwardTransform->TransformBounds(
1055
0
                        dfMinSrcX, dfMinSrcY, dfMaxSrcX, dfMaxSrcY,
1056
0
                        &dfTmpMinXOut, &dfTmpMinYOut, &dfTmpMaxXOut,
1057
0
                        &dfTmpMaxYOut,
1058
0
                        2))  // minimum number of points as we already have a
1059
                             // logic above to sample
1060
0
                {
1061
0
                    dfMinXOut = std::min(dfMinXOut, dfTmpMinXOut);
1062
0
                    dfMinYOut = std::min(dfMinYOut, dfTmpMinYOut);
1063
0
                    dfMaxXOut = std::max(dfMaxXOut, dfTmpMaxXOut);
1064
0
                    dfMaxYOut = std::max(dfMaxYOut, dfTmpMaxYOut);
1065
0
                }
1066
0
            }
1067
0
        }
1068
0
    }
1069
1070
    /* -------------------------------------------------------------------- */
1071
    /*      Compute the distance in "georeferenced" units from the top      */
1072
    /*      corner of the transformed input image to the bottom left        */
1073
    /*      corner of the transformed input.  Use this distance to          */
1074
    /*      compute an approximate pixel size in the output                 */
1075
    /*      georeferenced coordinates.                                      */
1076
    /* -------------------------------------------------------------------- */
1077
0
    double dfDiagonalDist = 0.0;
1078
0
    double dfDeltaX = 0.0;
1079
0
    double dfDeltaY = 0.0;
1080
1081
0
    if (pabSuccess[0] && pabSuccess[nSamplePoints - 1])
1082
0
    {
1083
0
        dfDeltaX = padfX[nSamplePoints - 1] - padfX[0];
1084
0
        dfDeltaY = padfY[nSamplePoints - 1] - padfY[0];
1085
        // In some cases this can result in 0 values. See #5980
1086
        // Fallback to safer method in that case.
1087
0
    }
1088
0
    if (dfDeltaX == 0.0 || dfDeltaY == 0.0)
1089
0
    {
1090
0
        dfDeltaX = dfMaxXOut - dfMinXOut;
1091
0
        dfDeltaY = dfMaxYOut - dfMinYOut;
1092
0
    }
1093
1094
0
    dfDiagonalDist = sqrt(dfDeltaX * dfDeltaX + dfDeltaY * dfDeltaY);
1095
1096
    /* -------------------------------------------------------------------- */
1097
    /*      Compute a pixel size from this.                                 */
1098
    /* -------------------------------------------------------------------- */
1099
0
    const double dfPixelSize =
1100
0
        dfDiagonalDist / sqrt(static_cast<double>(nInXSize) * nInXSize +
1101
0
                              static_cast<double>(nInYSize) * nInYSize);
1102
1103
0
    const double dfPixels = (dfMaxXOut - dfMinXOut) / dfPixelSize;
1104
0
    const double dfLines = (dfMaxYOut - dfMinYOut) / dfPixelSize;
1105
1106
0
    const int knIntMaxMinusOne = std::numeric_limits<int>::max() - 1;
1107
0
    if (dfPixels > knIntMaxMinusOne || dfLines > knIntMaxMinusOne)
1108
0
    {
1109
0
        CPLError(CE_Failure, CPLE_AppDefined,
1110
0
                 "Computed dimensions are too big : %.0f x %.0f",
1111
0
                 dfPixels + 0.5, dfLines + 0.5);
1112
1113
0
        CPLFree(padfX);
1114
0
        CPLFree(padfXRevert);
1115
0
        CPLFree(pabSuccess);
1116
1117
0
        return CE_Failure;
1118
0
    }
1119
1120
0
    if ((nOptions & GDAL_SWO_ROUND_UP_SIZE) != 0)
1121
0
    {
1122
0
        constexpr double EPS = 1e-5;
1123
0
        *pnPixels = static_cast<int>(std::ceil(dfPixels - EPS));
1124
0
        *pnLines = static_cast<int>(std::ceil(dfLines - EPS));
1125
0
    }
1126
0
    else
1127
0
    {
1128
0
        *pnPixels = static_cast<int>(dfPixels + 0.5);
1129
0
        *pnLines = static_cast<int>(dfLines + 0.5);
1130
0
    }
1131
1132
0
    double dfPixelSizeX = dfPixelSize;
1133
0
    double dfPixelSizeY = dfPixelSize;
1134
1135
0
    const double adfRatioArray[] = {0.000, 0.001, 0.010, 0.100, 1.000};
1136
1137
    /* -------------------------------------------------------------------- */
1138
    /*      Check that the right border is not completely out of source     */
1139
    /*      image. If so, adjust the x pixel size a bit in the hope it will */
1140
    /*      fit.                                                            */
1141
    /* -------------------------------------------------------------------- */
1142
0
    for (const auto &dfRatio : adfRatioArray)
1143
0
    {
1144
0
        const double dfTryPixelSizeX =
1145
0
            dfPixelSizeX - dfPixelSizeX * dfRatio / *pnPixels;
1146
0
        double adfExtent[4] = {dfMinXOut, dfMaxYOut - (*pnLines) * dfPixelSizeY,
1147
0
                               dfMinXOut + (*pnPixels) * dfTryPixelSizeX,
1148
0
                               dfMaxYOut};
1149
0
        if (!GDALSuggestedWarpOutput2_MustAdjustForRightBorder(
1150
0
                pfnTransformer, pTransformArg, adfExtent, *pnPixels, *pnLines,
1151
0
                dfTryPixelSizeX, dfPixelSizeY))
1152
0
        {
1153
0
            dfPixelSizeX = dfTryPixelSizeX;
1154
0
            break;
1155
0
        }
1156
0
    }
1157
1158
    /* -------------------------------------------------------------------- */
1159
    /*      Check that the bottom border is not completely out of source    */
1160
    /*      image. If so, adjust the y pixel size a bit in the hope it will */
1161
    /*      fit.                                                            */
1162
    /* -------------------------------------------------------------------- */
1163
0
    for (const auto &dfRatio : adfRatioArray)
1164
0
    {
1165
0
        const double dfTryPixelSizeY =
1166
0
            dfPixelSizeY - dfPixelSizeY * dfRatio / *pnLines;
1167
0
        double adfExtent[4] = {
1168
0
            dfMinXOut, dfMaxYOut - (*pnLines) * dfTryPixelSizeY,
1169
0
            dfMinXOut + (*pnPixels) * dfPixelSizeX, dfMaxYOut};
1170
0
        if (!GDALSuggestedWarpOutput2_MustAdjustForBottomBorder(
1171
0
                pfnTransformer, pTransformArg, adfExtent, *pnPixels, *pnLines,
1172
0
                dfPixelSizeX, dfTryPixelSizeY))
1173
0
        {
1174
0
            dfPixelSizeY = dfTryPixelSizeY;
1175
0
            break;
1176
0
        }
1177
0
    }
1178
1179
    /* -------------------------------------------------------------------- */
1180
    /*      Recompute some bounds so that all return values are consistent  */
1181
    /* -------------------------------------------------------------------- */
1182
0
    double dfMaxXOutNew = dfMinXOut + (*pnPixels) * dfPixelSizeX;
1183
0
    if (bIsGeographicCoordsDeg &&
1184
0
        ((dfMaxXOut <= 180 && dfMaxXOutNew > 180) || dfMaxXOut == 180))
1185
0
    {
1186
0
        dfMaxXOut = 180;
1187
0
        dfPixelSizeX = (dfMaxXOut - dfMinXOut) / *pnPixels;
1188
0
    }
1189
0
    else
1190
0
    {
1191
0
        dfMaxXOut = dfMaxXOutNew;
1192
0
    }
1193
1194
0
    double dfMinYOutNew = dfMaxYOut - (*pnLines) * dfPixelSizeY;
1195
0
    if (bIsGeographicCoordsDeg && dfMinYOut >= -90 && dfMinYOutNew < -90)
1196
0
    {
1197
0
        dfMinYOut = -90;
1198
0
        dfPixelSizeY = (dfMaxYOut - dfMinYOut) / *pnLines;
1199
0
    }
1200
0
    else
1201
0
    {
1202
0
        dfMinYOut = dfMinYOutNew;
1203
0
    }
1204
1205
    /* -------------------------------------------------------------------- */
1206
    /*      Return raw extents.                                             */
1207
    /* -------------------------------------------------------------------- */
1208
0
    padfExtent[0] = dfMinXOut;
1209
0
    padfExtent[1] = dfMinYOut;
1210
0
    padfExtent[2] = dfMaxXOut;
1211
0
    padfExtent[3] = dfMaxYOut;
1212
1213
    /* -------------------------------------------------------------------- */
1214
    /*      Set the output geotransform.                                    */
1215
    /* -------------------------------------------------------------------- */
1216
0
    padfGeoTransformOut[0] = dfMinXOut;
1217
0
    padfGeoTransformOut[1] = dfPixelSizeX;
1218
0
    padfGeoTransformOut[2] = 0.0;
1219
0
    padfGeoTransformOut[3] = dfMaxYOut;
1220
0
    padfGeoTransformOut[4] = 0.0;
1221
0
    padfGeoTransformOut[5] = -dfPixelSizeY;
1222
1223
0
    CPLFree(padfX);
1224
0
    CPLFree(padfXRevert);
1225
0
    CPLFree(pabSuccess);
1226
1227
0
    return CE_None;
1228
0
}
1229
1230
/************************************************************************/
1231
/*                    GetCurrentCheckWithInvertPROJ()                   */
1232
/************************************************************************/
1233
1234
static bool GetCurrentCheckWithInvertPROJ()
1235
0
{
1236
0
    return CPLTestBool(CPLGetConfigOption("CHECK_WITH_INVERT_PROJ", "NO"));
1237
0
}
1238
1239
/************************************************************************/
1240
/*               GDALCreateGenImgProjTransformerInternal()              */
1241
/************************************************************************/
1242
1243
static void *GDALCreateSimilarGenImgProjTransformer(void *hTransformArg,
1244
                                                    double dfRatioX,
1245
                                                    double dfRatioY);
1246
1247
static GDALGenImgProjTransformInfo *GDALCreateGenImgProjTransformerInternal()
1248
0
{
1249
    /* -------------------------------------------------------------------- */
1250
    /*      Initialize the transform info.                                  */
1251
    /* -------------------------------------------------------------------- */
1252
0
    GDALGenImgProjTransformInfo *psInfo =
1253
0
        static_cast<GDALGenImgProjTransformInfo *>(
1254
0
            CPLCalloc(sizeof(GDALGenImgProjTransformInfo), 1));
1255
1256
0
    memcpy(psInfo->sTI.abySignature, GDAL_GTI2_SIGNATURE,
1257
0
           strlen(GDAL_GTI2_SIGNATURE));
1258
0
    psInfo->sTI.pszClassName = GDAL_GEN_IMG_TRANSFORMER_CLASS_NAME;
1259
0
    psInfo->sTI.pfnTransform = GDALGenImgProjTransform;
1260
0
    psInfo->sTI.pfnCleanup = GDALDestroyGenImgProjTransformer;
1261
0
    psInfo->sTI.pfnSerialize = GDALSerializeGenImgProjTransformer;
1262
0
    psInfo->sTI.pfnCreateSimilar = GDALCreateSimilarGenImgProjTransformer;
1263
1264
0
    psInfo->bCheckWithInvertPROJ = GetCurrentCheckWithInvertPROJ();
1265
0
    psInfo->bHasCustomTransformationPipeline = false;
1266
1267
0
    return psInfo;
1268
0
}
1269
1270
/************************************************************************/
1271
/*                GDALCreateSimilarGenImgProjTransformer()              */
1272
/************************************************************************/
1273
1274
static void *GDALCreateSimilarGenImgProjTransformer(void *hTransformArg,
1275
                                                    double dfRatioX,
1276
                                                    double dfRatioY)
1277
0
{
1278
0
    VALIDATE_POINTER1(hTransformArg, "GDALCreateSimilarGenImgProjTransformer",
1279
0
                      nullptr);
1280
1281
0
    GDALGenImgProjTransformInfo *psInfo =
1282
0
        static_cast<GDALGenImgProjTransformInfo *>(hTransformArg);
1283
1284
0
    GDALGenImgProjTransformInfo *psClonedInfo =
1285
0
        GDALCreateGenImgProjTransformerInternal();
1286
1287
0
    memcpy(psClonedInfo, psInfo, sizeof(GDALGenImgProjTransformInfo));
1288
1289
0
    psClonedInfo->bCheckWithInvertPROJ = GetCurrentCheckWithInvertPROJ();
1290
1291
0
    if (psClonedInfo->sSrcParams.pTransformArg)
1292
0
        psClonedInfo->sSrcParams.pTransformArg = GDALCreateSimilarTransformer(
1293
0
            psInfo->sSrcParams.pTransformArg, dfRatioX, dfRatioY);
1294
0
    else if (dfRatioX != 1.0 || dfRatioY != 1.0)
1295
0
    {
1296
0
        if (psClonedInfo->sSrcParams.adfGeoTransform[2] == 0.0 &&
1297
0
            psClonedInfo->sSrcParams.adfGeoTransform[4] == 0.0)
1298
0
        {
1299
0
            psClonedInfo->sSrcParams.adfGeoTransform[1] *= dfRatioX;
1300
0
            psClonedInfo->sSrcParams.adfGeoTransform[5] *= dfRatioY;
1301
0
        }
1302
0
        else
1303
0
        {
1304
            // If the x and y ratios are not equal, then we cannot really
1305
            // compute a geotransform.
1306
0
            psClonedInfo->sSrcParams.adfGeoTransform[1] *= dfRatioX;
1307
0
            psClonedInfo->sSrcParams.adfGeoTransform[2] *= dfRatioX;
1308
0
            psClonedInfo->sSrcParams.adfGeoTransform[4] *= dfRatioX;
1309
0
            psClonedInfo->sSrcParams.adfGeoTransform[5] *= dfRatioX;
1310
0
        }
1311
0
        if (!GDALInvGeoTransform(psClonedInfo->sSrcParams.adfGeoTransform,
1312
0
                                 psClonedInfo->sSrcParams.adfInvGeoTransform))
1313
0
        {
1314
0
            CPLError(CE_Failure, CPLE_AppDefined, "Cannot invert geotransform");
1315
0
            GDALDestroyGenImgProjTransformer(psClonedInfo);
1316
0
            return nullptr;
1317
0
        }
1318
0
    }
1319
1320
0
    if (psClonedInfo->pReprojectArg)
1321
0
        psClonedInfo->pReprojectArg =
1322
0
            GDALCloneTransformer(psInfo->pReprojectArg);
1323
1324
0
    if (psClonedInfo->sDstParams.pTransformArg)
1325
0
        psClonedInfo->sDstParams.pTransformArg =
1326
0
            GDALCloneTransformer(psInfo->sDstParams.pTransformArg);
1327
1328
0
    return psClonedInfo;
1329
0
}
1330
1331
/************************************************************************/
1332
/*                  GDALCreateGenImgProjTransformer()                   */
1333
/************************************************************************/
1334
1335
/**
1336
 * Create image to image transformer.
1337
 *
1338
 * This function creates a transformation object that maps from pixel/line
1339
 * coordinates on one image to pixel/line coordinates on another image.  The
1340
 * images may potentially be georeferenced in different coordinate systems,
1341
 * and may used GCPs to map between their pixel/line coordinates and
1342
 * georeferenced coordinates (as opposed to the default assumption that their
1343
 * geotransform should be used).
1344
 *
1345
 * This transformer potentially performs three concatenated transformations.
1346
 *
1347
 * The first stage is from source image pixel/line coordinates to source
1348
 * image georeferenced coordinates, and may be done using the geotransform,
1349
 * or if not defined using a polynomial model derived from GCPs.  If GCPs
1350
 * are used this stage is accomplished using GDALGCPTransform().
1351
 *
1352
 * The second stage is to change projections from the source coordinate system
1353
 * to the destination coordinate system, assuming they differ.  This is
1354
 * accomplished internally using GDALReprojectionTransform().
1355
 *
1356
 * The third stage is converting from destination image georeferenced
1357
 * coordinates to destination image coordinates.  This is done using the
1358
 * destination image geotransform, or if not available, using a polynomial
1359
 * model derived from GCPs. If GCPs are used this stage is accomplished using
1360
 * GDALGCPTransform().  This stage is skipped if hDstDS is NULL when the
1361
 * transformation is created.
1362
 *
1363
 * @param hSrcDS source dataset, or NULL.
1364
 * @param pszSrcWKT the coordinate system for the source dataset.  If NULL,
1365
 * it will be read from the dataset itself.
1366
 * @param hDstDS destination dataset (or NULL).
1367
 * @param pszDstWKT the coordinate system for the destination dataset.  If
1368
 * NULL, and hDstDS not NULL, it will be read from the destination dataset.
1369
 * @param bGCPUseOK TRUE if GCPs should be used if the geotransform is not
1370
 * available on the source dataset (not destination).
1371
 * @param dfGCPErrorThreshold ignored/deprecated.
1372
 * @param nOrder the maximum order to use for GCP derived polynomials if
1373
 * possible.  Use 0 to autoselect, or -1 for thin plate splines.
1374
 *
1375
 * @return handle suitable for use GDALGenImgProjTransform(), and to be
1376
 * deallocated with GDALDestroyGenImgProjTransformer().
1377
 */
1378
1379
void *GDALCreateGenImgProjTransformer(GDALDatasetH hSrcDS,
1380
                                      const char *pszSrcWKT,
1381
                                      GDALDatasetH hDstDS,
1382
                                      const char *pszDstWKT, int bGCPUseOK,
1383
                                      CPL_UNUSED double dfGCPErrorThreshold,
1384
                                      int nOrder)
1385
0
{
1386
0
    char **papszOptions = nullptr;
1387
1388
0
    if (pszSrcWKT != nullptr)
1389
0
        papszOptions = CSLSetNameValue(papszOptions, "SRC_SRS", pszSrcWKT);
1390
0
    if (pszDstWKT != nullptr)
1391
0
        papszOptions = CSLSetNameValue(papszOptions, "DST_SRS", pszDstWKT);
1392
0
    if (!bGCPUseOK)
1393
0
        papszOptions = CSLSetNameValue(papszOptions, "GCPS_OK", "FALSE");
1394
0
    if (nOrder != 0)
1395
0
        papszOptions = CSLSetNameValue(papszOptions, "MAX_GCP_ORDER",
1396
0
                                       CPLString().Printf("%d", nOrder));
1397
1398
0
    void *pRet = GDALCreateGenImgProjTransformer2(hSrcDS, hDstDS, papszOptions);
1399
0
    CSLDestroy(papszOptions);
1400
1401
0
    return pRet;
1402
0
}
1403
1404
/************************************************************************/
1405
/*                          InsertCenterLong()                          */
1406
/*                                                                      */
1407
/*      Insert a CENTER_LONG Extension entry on a GEOGCS to indicate    */
1408
/*      the center longitude of the dataset for wrapping purposes.      */
1409
/************************************************************************/
1410
1411
static void InsertCenterLong(GDALDatasetH hDS, OGRSpatialReference *poSRS,
1412
                             CPLStringList &aosOptions)
1413
1414
0
{
1415
0
    if (!poSRS->IsGeographic() || std::fabs(poSRS->GetAngularUnits() -
1416
0
                                            CPLAtof(SRS_UA_DEGREE_CONV)) > 1e-9)
1417
0
    {
1418
0
        return;
1419
0
    }
1420
1421
0
    if (poSRS->GetExtension(nullptr, "CENTER_LONG"))
1422
0
        return;
1423
1424
    /* -------------------------------------------------------------------- */
1425
    /*      For now we only do this if we have a geotransform since         */
1426
    /*      other forms require a bunch of extra work.                      */
1427
    /* -------------------------------------------------------------------- */
1428
0
    double adfGeoTransform[6] = {};
1429
1430
0
    if (GDALGetGeoTransform(hDS, adfGeoTransform) != CE_None)
1431
0
        return;
1432
1433
    /* -------------------------------------------------------------------- */
1434
    /*      Compute min/max longitude based on testing the four corners.    */
1435
    /* -------------------------------------------------------------------- */
1436
0
    const int nXSize = GDALGetRasterXSize(hDS);
1437
0
    const int nYSize = GDALGetRasterYSize(hDS);
1438
1439
0
    const double dfMinLong =
1440
0
        std::min(std::min(adfGeoTransform[0] + 0 * adfGeoTransform[1] +
1441
0
                              0 * adfGeoTransform[2],
1442
0
                          adfGeoTransform[0] + nXSize * adfGeoTransform[1] +
1443
0
                              0 * adfGeoTransform[2]),
1444
0
                 std::min(adfGeoTransform[0] + 0 * adfGeoTransform[1] +
1445
0
                              nYSize * adfGeoTransform[2],
1446
0
                          adfGeoTransform[0] + nXSize * adfGeoTransform[1] +
1447
0
                              nYSize * adfGeoTransform[2]));
1448
0
    const double dfMaxLong =
1449
0
        std::max(std::max(adfGeoTransform[0] + 0 * adfGeoTransform[1] +
1450
0
                              0 * adfGeoTransform[2],
1451
0
                          adfGeoTransform[0] + nXSize * adfGeoTransform[1] +
1452
0
                              0 * adfGeoTransform[2]),
1453
0
                 std::max(adfGeoTransform[0] + 0 * adfGeoTransform[1] +
1454
0
                              nYSize * adfGeoTransform[2],
1455
0
                          adfGeoTransform[0] + nXSize * adfGeoTransform[1] +
1456
0
                              nYSize * adfGeoTransform[2]));
1457
1458
0
    const double dfEpsilon =
1459
0
        std::max(std::fabs(adfGeoTransform[1]), std::fabs(adfGeoTransform[2]));
1460
    // If the raster covers more than 360 degree (allow an extra pixel),
1461
    // give up
1462
0
    constexpr double RELATIVE_EPSILON = 0.05;  // for numeric precision issues
1463
0
    if (dfMaxLong - dfMinLong > 360.0 + dfEpsilon * (1 + RELATIVE_EPSILON))
1464
0
        return;
1465
1466
    /* -------------------------------------------------------------------- */
1467
    /*      Insert center long.                                             */
1468
    /* -------------------------------------------------------------------- */
1469
0
    const double dfCenterLong = (dfMaxLong + dfMinLong) / 2.0;
1470
0
    aosOptions.SetNameValue("CENTER_LONG", CPLSPrintf("%g", dfCenterLong));
1471
0
}
1472
1473
/************************************************************************/
1474
/*                      GDALComputeAreaOfInterest()                     */
1475
/************************************************************************/
1476
1477
bool GDALComputeAreaOfInterest(OGRSpatialReference *poSRS, double adfGT[6],
1478
                               int nXSize, int nYSize,
1479
                               double &dfWestLongitudeDeg,
1480
                               double &dfSouthLatitudeDeg,
1481
                               double &dfEastLongitudeDeg,
1482
                               double &dfNorthLatitudeDeg)
1483
0
{
1484
0
    bool ret = false;
1485
1486
0
    if (!poSRS)
1487
0
        return false;
1488
1489
0
    OGRSpatialReference oSrcSRSHoriz(*poSRS);
1490
0
    if (oSrcSRSHoriz.IsCompound())
1491
0
    {
1492
0
        oSrcSRSHoriz.StripVertical();
1493
0
    }
1494
1495
0
    OGRSpatialReference *poGeog = oSrcSRSHoriz.CloneGeogCS();
1496
0
    if (poGeog)
1497
0
    {
1498
0
        poGeog->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
1499
0
        poGeog->SetAngularUnits(SRS_UA_DEGREE, CPLAtof(SRS_UA_DEGREE_CONV));
1500
1501
0
        auto poCT = OGRCreateCoordinateTransformation(&oSrcSRSHoriz, poGeog);
1502
0
        if (poCT)
1503
0
        {
1504
0
            poCT->SetEmitErrors(false);
1505
1506
0
            double x[4], y[4];
1507
0
            x[0] = adfGT[0];
1508
0
            y[0] = adfGT[3];
1509
0
            x[1] = adfGT[0] + nXSize * adfGT[1];
1510
0
            y[1] = adfGT[3];
1511
0
            x[2] = adfGT[0];
1512
0
            y[2] = adfGT[3] + nYSize * adfGT[5];
1513
0
            x[3] = x[1];
1514
0
            y[3] = y[2];
1515
0
            int validity[4] = {false, false, false, false};
1516
0
            poCT->Transform(4, x, y, nullptr, validity);
1517
0
            dfWestLongitudeDeg = std::numeric_limits<double>::max();
1518
0
            dfSouthLatitudeDeg = std::numeric_limits<double>::max();
1519
0
            dfEastLongitudeDeg = -std::numeric_limits<double>::max();
1520
0
            dfNorthLatitudeDeg = -std::numeric_limits<double>::max();
1521
0
            for (int i = 0; i < 4; i++)
1522
0
            {
1523
0
                if (validity[i])
1524
0
                {
1525
0
                    ret = true;
1526
0
                    dfWestLongitudeDeg = std::min(dfWestLongitudeDeg, x[i]);
1527
0
                    dfSouthLatitudeDeg = std::min(dfSouthLatitudeDeg, y[i]);
1528
0
                    dfEastLongitudeDeg = std::max(dfEastLongitudeDeg, x[i]);
1529
0
                    dfNorthLatitudeDeg = std::max(dfNorthLatitudeDeg, y[i]);
1530
0
                }
1531
0
            }
1532
0
            if (validity[0] && validity[1] && x[0] > x[1])
1533
0
            {
1534
0
                dfWestLongitudeDeg = x[0];
1535
0
                dfEastLongitudeDeg = x[1];
1536
0
            }
1537
0
            if (ret && std::fabs(dfWestLongitudeDeg) <= 180 &&
1538
0
                std::fabs(dfEastLongitudeDeg) <= 180 &&
1539
0
                std::fabs(dfSouthLatitudeDeg) <= 90 &&
1540
0
                std::fabs(dfNorthLatitudeDeg) <= 90)
1541
0
            {
1542
0
                CPLDebug("GDAL", "Computing area of interest: %g, %g, %g, %g",
1543
0
                         dfWestLongitudeDeg, dfSouthLatitudeDeg,
1544
0
                         dfEastLongitudeDeg, dfNorthLatitudeDeg);
1545
0
            }
1546
0
            else
1547
0
            {
1548
0
                CPLDebug("GDAL", "Could not compute area of interest");
1549
0
                dfWestLongitudeDeg = 0;
1550
0
                dfSouthLatitudeDeg = 0;
1551
0
                dfEastLongitudeDeg = 0;
1552
0
                dfNorthLatitudeDeg = 0;
1553
0
            }
1554
0
            OGRCoordinateTransformation::DestroyCT(poCT);
1555
0
        }
1556
1557
0
        delete poGeog;
1558
0
    }
1559
1560
0
    return ret;
1561
0
}
1562
1563
bool GDALComputeAreaOfInterest(OGRSpatialReference *poSRS, double dfX1,
1564
                               double dfY1, double dfX2, double dfY2,
1565
                               double &dfWestLongitudeDeg,
1566
                               double &dfSouthLatitudeDeg,
1567
                               double &dfEastLongitudeDeg,
1568
                               double &dfNorthLatitudeDeg)
1569
0
{
1570
0
    bool ret = false;
1571
1572
0
    if (!poSRS)
1573
0
        return false;
1574
1575
0
    OGRSpatialReference oSrcSRSHoriz(*poSRS);
1576
0
    if (oSrcSRSHoriz.IsCompound())
1577
0
    {
1578
0
        oSrcSRSHoriz.StripVertical();
1579
0
    }
1580
1581
0
    OGRSpatialReference *poGeog = oSrcSRSHoriz.CloneGeogCS();
1582
0
    if (poGeog)
1583
0
    {
1584
0
        poGeog->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
1585
1586
0
        auto poCT = OGRCreateCoordinateTransformation(&oSrcSRSHoriz, poGeog);
1587
0
        if (poCT)
1588
0
        {
1589
0
            double x[4], y[4];
1590
0
            x[0] = dfX1;
1591
0
            y[0] = dfY1;
1592
0
            x[1] = dfX2;
1593
0
            y[1] = dfY1;
1594
0
            x[2] = dfX1;
1595
0
            y[2] = dfY2;
1596
0
            x[3] = dfX2;
1597
0
            y[3] = dfY2;
1598
0
            int validity[4] = {false, false, false, false};
1599
0
            poCT->Transform(4, x, y, nullptr, validity);
1600
0
            dfWestLongitudeDeg = std::numeric_limits<double>::max();
1601
0
            dfSouthLatitudeDeg = std::numeric_limits<double>::max();
1602
0
            dfEastLongitudeDeg = -std::numeric_limits<double>::max();
1603
0
            dfNorthLatitudeDeg = -std::numeric_limits<double>::max();
1604
0
            for (int i = 0; i < 4; i++)
1605
0
            {
1606
0
                if (validity[i])
1607
0
                {
1608
0
                    ret = true;
1609
0
                    dfWestLongitudeDeg = std::min(dfWestLongitudeDeg, x[i]);
1610
0
                    dfSouthLatitudeDeg = std::min(dfSouthLatitudeDeg, y[i]);
1611
0
                    dfEastLongitudeDeg = std::max(dfEastLongitudeDeg, x[i]);
1612
0
                    dfNorthLatitudeDeg = std::max(dfNorthLatitudeDeg, y[i]);
1613
0
                }
1614
0
            }
1615
0
            if (validity[0] && validity[1] && (dfX1 - dfX2) * (x[0] - x[1]) < 0)
1616
0
            {
1617
0
                dfWestLongitudeDeg = x[0];
1618
0
                dfEastLongitudeDeg = x[1];
1619
0
            }
1620
0
            if (ret)
1621
0
            {
1622
0
                CPLDebug("GDAL", "Computing area of interest: %g, %g, %g, %g",
1623
0
                         dfWestLongitudeDeg, dfSouthLatitudeDeg,
1624
0
                         dfEastLongitudeDeg, dfNorthLatitudeDeg);
1625
0
            }
1626
0
            else
1627
0
            {
1628
0
                CPLDebug("GDAL", "Could not compute area of interest");
1629
0
                dfWestLongitudeDeg = 0;
1630
0
                dfSouthLatitudeDeg = 0;
1631
0
                dfEastLongitudeDeg = 0;
1632
0
                dfNorthLatitudeDeg = 0;
1633
0
            }
1634
0
            delete poCT;
1635
0
        }
1636
1637
0
        delete poGeog;
1638
0
    }
1639
1640
0
    return ret;
1641
0
}
1642
1643
/************************************************************************/
1644
/*                    GDALGCPAntimeridianUnwrap()                       */
1645
/************************************************************************/
1646
1647
/* Deal with discontinuties of dfGCPX longitudes around the anti-meridian.
1648
 * Cf https://github.com/OSGeo/gdal/issues/8371
1649
 */
1650
static void GDALGCPAntimeridianUnwrap(int nGCPCount, GDAL_GCP *pasGCPList,
1651
                                      const OGRSpatialReference &oSRS,
1652
                                      CSLConstList papszOptions)
1653
0
{
1654
0
    const char *pszGCPAntimeridianUnwrap =
1655
0
        CSLFetchNameValueDef(papszOptions, "GCP_ANTIMERIDIAN_UNWRAP", "AUTO");
1656
0
    const bool bForced = EQUAL(pszGCPAntimeridianUnwrap, "YES") ||
1657
0
                         EQUAL(pszGCPAntimeridianUnwrap, "ON") ||
1658
0
                         EQUAL(pszGCPAntimeridianUnwrap, "TRUE") ||
1659
0
                         EQUAL(pszGCPAntimeridianUnwrap, "1");
1660
0
    if (bForced || (!oSRS.IsEmpty() && oSRS.IsGeographic() &&
1661
0
                    fabs(oSRS.GetAngularUnits(nullptr) -
1662
0
                         CPLAtof(SRS_UA_DEGREE_CONV)) < 1e-8 &&
1663
0
                    EQUAL(pszGCPAntimeridianUnwrap, "AUTO")))
1664
0
    {
1665
0
        if (!bForced)
1666
0
        {
1667
            // Proceed to unwrapping only if the longitudes are within
1668
            // [-180, -170] or [170, 180]
1669
0
            for (int i = 0; i < nGCPCount; ++i)
1670
0
            {
1671
0
                const double dfLongAbs = fabs(pasGCPList[i].dfGCPX);
1672
0
                if (dfLongAbs > 180 || dfLongAbs < 170)
1673
0
                {
1674
0
                    return;
1675
0
                }
1676
0
            }
1677
0
        }
1678
1679
0
        bool bDone = false;
1680
0
        for (int i = 0; i < nGCPCount; ++i)
1681
0
        {
1682
0
            if (pasGCPList[i].dfGCPX < 0)
1683
0
            {
1684
0
                if (!bDone)
1685
0
                {
1686
0
                    bDone = true;
1687
0
                    CPLDebug("WARP", "GCP longitude unwrapping");
1688
0
                }
1689
0
                pasGCPList[i].dfGCPX += 360;
1690
0
            }
1691
0
        }
1692
0
    }
1693
0
}
1694
1695
/************************************************************************/
1696
/*              GDALGetGenImgProjTranformerOptionList()                 */
1697
/************************************************************************/
1698
1699
/** Return a XML string describing options accepted by
1700
 * GDALCreateGenImgProjTransformer2().
1701
 *
1702
 * @since 3.11
1703
 */
1704
const char *GDALGetGenImgProjTranformerOptionList(void)
1705
0
{
1706
0
    return "<OptionList>"
1707
0
           "<Option name='SRC_SRS' type='string' description='WKT SRS, or any "
1708
0
           "string recognized by OGRSpatialReference::SetFromUserInput(), to "
1709
0
           "be used as an override for CRS of input dataset'/>"
1710
0
           "<Option name='DST_SRS' type='string' description='WKT SRS, or any "
1711
0
           "string recognized by OGRSpatialReference::SetFromUserInput(), to "
1712
0
           "be used as an override for CRS of output dataset'/>"
1713
0
           "<Option name='PROMOTE_TO_3D' type='boolean' description='"
1714
0
           "Whether to promote SRC_SRS / DST_SRS to 3D.' "
1715
0
           "default='NO'/>"
1716
0
           "<Option name='COORDINATE_OPERATION' type='string' description='"
1717
0
           "Coordinate operation, as a PROJ or WKT string, used as an override "
1718
0
           "over the normally computed pipeline. The pipeline must take into "
1719
0
           "account the axis order of the source and target SRS.'/>"
1720
0
           "<Option name='ALLOW_BALLPARK' type='boolean' description='"
1721
0
           "Whether ballpark coordinate operations are allowed.' "
1722
0
           "default='YES'/>"
1723
0
           "<Option name='ONLY_BEST' type='string-select' "
1724
0
           "description='"
1725
0
           "By default (at least in the PROJ 9.x series), PROJ may use "
1726
0
           "coordinate operations that are not the \"best\" if resources "
1727
0
           "(typically grids) needed to use them are missing. It will then "
1728
0
           "fallback to other coordinate operations that have a lesser "
1729
0
           "accuracy, for example using Helmert transformations, or in the "
1730
0
           "absence of such operations, to ones with potential very rough "
1731
0
           " accuracy, using \"ballpark\" transformations (see "
1732
0
           "https://proj.org/glossary.html). "
1733
0
           "When calling this method with YES, PROJ will only consider the "
1734
0
           "\"best\" operation, and error out (at Transform() time) if they "
1735
0
           "cannot be used. This method may be used together with "
1736
0
           "ALLOW_BALLPARK=NO to only allow best operations that have a known "
1737
0
           "accuracy. Note that this method has no effect on PROJ versions "
1738
0
           "before 9.2. The default value for this option can be also set with "
1739
0
           "the PROJ_ONLY_BEST_DEFAULT environment variable, or with the "
1740
0
           "\"only_best_default\" setting of proj.ini. Setting "
1741
0
           "ONLY_BEST=YES/NO overrides such default value' default='AUTO'>"
1742
0
           "  <Value>AUTO</Value>"
1743
0
           "  <Value>YES</Value>"
1744
0
           "  <Value>NO</Value>"
1745
0
           "</Option>"
1746
0
           "<Option name='COORDINATE_EPOCH' type='float' description='"
1747
0
           "Coordinate epoch, expressed as a decimal year. Useful for "
1748
0
           "time-dependent coordinate operations.'/>"
1749
0
           "<Option name='SRC_COORDINATE_EPOCH' type='float' description='"
1750
0
           "Coordinate epoch of source CRS, expressed as a decimal year. "
1751
0
           "Useful for time-dependent coordinate operations.'/>"
1752
0
           "<Option name='DST_COORDINATE_EPOCH' type='float' description='"
1753
0
           "Coordinate epoch of target CRS, expressed as a decimal year. "
1754
0
           "Useful for time-dependent coordinate operations.'/>"
1755
0
           "<Option name='GCPS_OK' type='boolean' description='"
1756
0
           "Allow use of GCPs.' default='YES'/>"
1757
0
           "<Option name='REFINE_MINIMUM_GCPS' type='int' description='"
1758
0
           "The minimum amount of GCPs that should be available after the "
1759
0
           "refinement'/>"
1760
0
           "<Option name='REFINE_TOLERANCE' type='float' description='"
1761
0
           "The tolerance that specifies when a GCP will be eliminated.'/>"
1762
0
           "<Option name='MAX_GCP_ORDER' type='int' description='"
1763
0
           "The maximum order to use for GCP derived polynomials if possible. "
1764
0
           "The default is to autoselect based on the number of GCPs. A value "
1765
0
           "of -1 triggers use of Thin Plate Spline instead of polynomials.'/>"
1766
0
           "<Option name='GCP_ANTIMERIDIAN_UNWRAP' type='string-select' "
1767
0
           "description='"
1768
0
           "Whether to \"unwrap\" longitudes of ground control points that "
1769
0
           "span the antimeridian. For datasets with GCPs in "
1770
0
           "longitude/latitude coordinate space spanning the antimeridian, "
1771
0
           "longitudes will have a discontinuity on +/- 180 deg, and will "
1772
0
           "result in a subset of the GCPs with longitude in the [-180,-170] "
1773
0
           "range and another subset in [170, 180]. By default (AUTO), that "
1774
0
           "situation will be detected and longitudes in [-180,-170] will be "
1775
0
           "shifted to [180, 190] to get a continuous set. This option can be "
1776
0
           "set to YES to force that behavior (useful if no SRS information is "
1777
0
           "available), or to NO to disable it.' default='AUTO'>"
1778
0
           "  <Value>AUTO</Value>"
1779
0
           "  <Value>YES</Value>"
1780
0
           "  <Value>NO</Value>"
1781
0
           "</Option>"
1782
0
           "<Option name='SRC_METHOD' alias='METHOD' type='string-select' "
1783
0
           "description='"
1784
0
           "Force only one geolocation method to be considered on the source "
1785
0
           "dataset. Will be used for pixel/line to georef transformation on "
1786
0
           "the source dataset. NO_GEOTRANSFORM can be used to specify the "
1787
0
           "identity geotransform (ungeoreferenced image)'>"
1788
0
           "  <Value>GEOTRANSFORM</Value>"
1789
0
           "  <Value>GCP_POLYNOMIAL</Value>"
1790
0
           "  <Value>GCP_TPS</Value>"
1791
0
           "  <Value>GCP_HOMOGRAPHY</Value>"
1792
0
           "  <Value>GEOLOC_ARRAY</Value>"
1793
0
           "  <Value>RPC</Value>"
1794
0
           "  <Value>NO_GEOTRANSFORM</Value>"
1795
0
           "</Option>"
1796
0
           "<Option name='DST_METHOD' type='string-select' description='"
1797
0
           "Force only one geolocation method to be considered on the target "
1798
0
           "dataset. Will be used for pixel/line to georef transformation on "
1799
0
           "the targe dataset. NO_GEOTRANSFORM can be used to specify the "
1800
0
           "identity geotransform (ungeoreferenced image)'>"
1801
0
           "  <Value>GEOTRANSFORM</Value>"
1802
0
           "  <Value>GCP_POLYNOMIAL</Value>"
1803
0
           "  <Value>GCP_TPS</Value>"
1804
0
           "  <Value>GCP_HOMOGRAPHY</Value>"
1805
0
           "  <Value>GEOLOC_ARRAY</Value>"
1806
0
           "  <Value>RPC</Value>"
1807
0
           "  <Value>NO_GEOTRANSFORM</Value>"
1808
0
           "</Option>"
1809
0
           "<Option name='RPC_HEIGHT' type='float' description='"
1810
0
           "A fixed height to be used with RPC calculations. If RPC_HEIGHT and "
1811
0
           "RPC_DEM are not specified but that the RPC metadata domain contains"
1812
0
           " a HEIGHT_DEFAULT item (for example, the DIMAP driver may fill it),"
1813
0
           "this value will be used as the RPC_HEIGHT. Otherwise, if none of "
1814
0
           "RPC_HEIGHT and RPC_DEM are specified as transformer options and "
1815
0
           "if HEIGHT_DEFAULT is no available, a height of 0 will be used.'/>"
1816
0
           "<Option name='RPC_DEM' type='string' description='"
1817
0
           "Name of a GDAL dataset (a DEM file typically) used to extract "
1818
0
           "elevation offsets from. In this situation the Z passed into the "
1819
0
           "transformation function is assumed to be height above ground. "
1820
0
           "This option should be used in replacement of RPC_HEIGHT to provide "
1821
0
           "a way of defining a non uniform ground for the target scene.'/>"
1822
0
           "<Option name='RPC_HEIGHT_SCALE' type='float' description='"
1823
0
           "Factor used to multiply heights above ground. Useful when "
1824
0
           "elevation offsets of the DEM are not expressed in meters.'/>"
1825
0
           "<Option name='RPC_DEMINTERPOLATION' type='string-select' "
1826
0
           "description='DEM interpolation method' default='BILINEAR'>"
1827
0
           "  <Value>NEAR</Value>"
1828
0
           "  <Value>BILINEAR</Value>"
1829
0
           "  <Value>CUBIC</Value>"
1830
0
           "</Option>"
1831
0
           "<Option name='RPC_DEM_MISSING_VALUE' type='float' description='"
1832
0
           "Value of DEM height that must be used in case the DEM has nodata "
1833
0
           "value at the sampling point, or if its extent does not cover the "
1834
0
           "requested coordinate. When not specified, missing values will "
1835
0
           "cause a failed transform.'/>"
1836
0
           "<Option name='RPC_DEM_SRS' type='string' description='"
1837
0
           "WKT SRS, or any string recognized by "
1838
0
           "OGRSpatialReference::SetFromUserInput(), to be used as an "
1839
0
           "override for DEM SRS. Useful if DEM SRS does not have an explicit "
1840
0
           "vertical component.'/>"
1841
0
           "<Option name='RPC_DEM_APPLY_VDATUM_SHIFT' type='boolean' "
1842
0
           "description='"
1843
0
           "Whether the vertical component of a compound SRS for the DEM "
1844
0
           "should be used (when it is present). This is useful so as to "
1845
0
           "be able to transform the raw values from the DEM expressed with "
1846
0
           "respect to a geoid to the heights with respect to the WGS84 "
1847
0
           "ellipsoid. When this is enabled, the GTIFF_REPORT_COMPD_CS "
1848
0
           "configuration option will be also set temporarily so as to get "
1849
0
           "the vertical information from GeoTIFF files.' default='YES'/>"
1850
0
           "<Option name='RPC_PIXEL_ERROR_THRESHOLD' type='float' description='"
1851
0
           "Overrides the dfPixErrThreshold parameter, i.e. the error "
1852
0
           "(measured in pixels) allowed in the iterative solution of "
1853
0
           "pixel/line to lat/long computations (the other way is always "
1854
0
           "exact given the equations).'/>"
1855
0
           "<Option name='RPC_MAX_ITERATIONS' type='int' description='"
1856
0
           "Maximum number of iterations allowed in the iterative solution of "
1857
0
           "pixel/line to lat/long computations. Default value is 10 in the "
1858
0
           "absence of a DEM, or 20 if there is a DEM.'/>"
1859
0
           "<Option name='RPC_FOOTPRINT' type='string' description='"
1860
0
           "WKT or GeoJSON polygon (in long / lat coordinate space) with a "
1861
0
           "validity footprint for the RPC. Any coordinate transformation that "
1862
0
           "goes from or arrive outside this footprint will be considered "
1863
0
           "invalid. This* is useful in situations where the RPC values become "
1864
0
           "highly unstable outside of the area on which they have been "
1865
0
           "computed for, potentially leading to undesirable \"echoes\" / "
1866
0
           "false positives. This requires GDAL to be built against GEOS..'/>"
1867
0
           "<Option name='RPC_MAX_ITERATIONS' type='int' description='"
1868
0
           "Maximum number of iterations allowed in the iterative solution of "
1869
0
           "pixel/line to lat/long computations. Default value is 10 in the "
1870
0
           "absence of a DEM, or 20 if there is a DEM.'/>"
1871
0
           "<Option name='INSERT_CENTER_LONG' type='boolean' description='"
1872
0
           "May be set to FALSE to disable setting up a CENTER_LONG value on "
1873
0
           "the coordinate system to rewrap things around the center of the "
1874
0
           "image.' default='YES'/>"
1875
0
           "<Option name='SRC_APPROX_ERROR_IN_SRS_UNIT' type='float' "
1876
0
           "description='"
1877
0
           "Use an approximate transformer for the source transformer. Must be "
1878
0
           "defined together with SRC_APPROX_ERROR_IN_PIXEL to be taken into "
1879
0
           "account.'/>"
1880
0
           "<Option name='SRC_APPROX_ERROR_IN_PIXEL' type='float' "
1881
0
           "description='"
1882
0
           "Use an approximate transformer for the source transformer. Must be "
1883
0
           "defined together with SRC_APPROX_ERROR_IN_SRS_UNIT to be taken "
1884
0
           "into "
1885
0
           "account.'/>"
1886
0
           "<Option name='DST_APPROX_ERROR_IN_SRS_UNIT' type='float' "
1887
0
           "description='"
1888
0
           "Use an approximate transformer for the target transformer. Must be "
1889
0
           "defined together with DST_APPROX_ERROR_IN_PIXEL to be taken into "
1890
0
           "account.'/>"
1891
0
           "<Option name='DST_APPROX_ERROR_IN_PIXEL' type='float' "
1892
0
           "description='"
1893
0
           "Use an approximate transformer for the target transformer. Must be "
1894
0
           "defined together with DST_APPROX_ERROR_IN_SRS_UNIT to be taken "
1895
0
           "into "
1896
0
           "account.'/>"
1897
0
           "<Option name='REPROJECTION_APPROX_ERROR_IN_SRC_SRS_UNIT' "
1898
0
           "type='float' "
1899
0
           "description='"
1900
0
           "Use an approximate transformer for the coordinate reprojection. "
1901
0
           "Must be used together with "
1902
0
           "REPROJECTION_APPROX_ERROR_IN_DST_SRS_UNIT to be taken into "
1903
0
           "account.'/>"
1904
0
           "<Option name='REPROJECTION_APPROX_ERROR_IN_DST_SRS_UNIT' "
1905
0
           "type='float' "
1906
0
           "description='"
1907
0
           "Use an approximate transformer for the coordinate reprojection. "
1908
0
           "Must be used together with "
1909
0
           "REPROJECTION_APPROX_ERROR_IN_SRC_SRS_UNIT to be taken into "
1910
0
           "account.'/>"
1911
0
           "<Option name='AREA_OF_INTEREST' type='string' "
1912
0
           "description='"
1913
0
           "Area of interest, as "
1914
0
           "west_lon_deg,south_lat_deg,east_lon_deg,north_lat_deg, used to "
1915
0
           "compute the best coordinate operation between the source and "
1916
0
           "target SRS. If not specified, the bounding box of the source "
1917
0
           "raster will be used.'/>"
1918
0
           "<Option name='GEOLOC_BACKMAP_OVERSAMPLE_FACTOR' type='float' "
1919
0
           "min='0.1' max='2' description='"
1920
0
           "Oversample factor used to derive the size of the \"backmap\" used "
1921
0
           "for geolocation array transformers.' default='1.3'/>"
1922
0
           "<Option name='GEOLOC_USE_TEMP_DATASETS' type='boolean' "
1923
0
           "description='"
1924
0
           "Whether temporary GeoTIFF datasets should be used to store the "
1925
0
           "backmap. The default is NO, that is to use in-memory arrays, "
1926
0
           "unless the number of pixels of the geolocation array is greater "
1927
0
           "than 16 megapixels.' default='NO'/>"
1928
0
           "<Option name='GEOLOC_ARRAY' alias='SRC_GEOLOC_ARRAY' type='string' "
1929
0
           "description='"
1930
0
           "Name of a GDAL dataset containing a geolocation array and "
1931
0
           "associated metadata. This is an alternative to having geolocation "
1932
0
           "information described in the GEOLOCATION metadata domain of the "
1933
0
           "source dataset. The dataset specified may have a GEOLOCATION "
1934
0
           "metadata domain containing appropriate metadata, however default "
1935
0
           "values are assigned for all omitted items. X_BAND defaults to 1 "
1936
0
           "and Y_BAND to 2, however the dataset must contain exactly 2 bands. "
1937
0
           "PIXEL_OFFSET and LINE_OFFSET default to 0. PIXEL_STEP and "
1938
0
           "LINE_STEP default to the ratio of the width/height of the source "
1939
0
           "dataset divided by the with/height of the geolocation array. "
1940
0
           "SRS defaults to the spatial reference system of the geolocation "
1941
0
           "array dataset, if set, otherwise WGS84 is used. "
1942
0
           "GEOREFERENCING_CONVENTION is selected from the main metadata "
1943
0
           "domain if it is omitted from the GEOLOCATION domain, and if not "
1944
0
           "available TOP_LEFT_CORNER is assigned as a default. "
1945
0
           "If GEOLOC_ARRAY is set SRC_METHOD defaults to GEOLOC_ARRAY.'/>"
1946
0
           "<Option name='DST_GEOLOC_ARRAY' type='string' "
1947
0
           "description='"
1948
0
           "Name of a GDAL dataset that contains at least 2 bands with the X "
1949
0
           "and Y geolocation bands. This is an alternative to having "
1950
0
           "geolocation information described in the GEOLOCATION metadata "
1951
0
           "domain of the destination dataset. See SRC_GEOLOC_ARRAY "
1952
0
           "description for details, assumptions, and defaults. If this "
1953
0
           "option is set, DST_METHOD=GEOLOC_ARRAY will be assumed if not "
1954
0
           "set.'/>"
1955
0
           "<Option name='NUM_THREADS' type='string' "
1956
0
           "description='Number of threads to use'/>"
1957
0
           "</OptionList>";
1958
0
}
1959
1960
/************************************************************************/
1961
/*                  GDALCreateGenImgProjTransformer2()                  */
1962
/************************************************************************/
1963
1964
/* clang-format off */
1965
/**
1966
 * Create image to image transformer.
1967
 *
1968
 * This function creates a transformation object that maps from pixel/line
1969
 * coordinates on one image to pixel/line coordinates on another image.  The
1970
 * images may potentially be georeferenced in different coordinate systems,
1971
 * and may used GCPs to map between their pixel/line coordinates and
1972
 * georeferenced coordinates (as opposed to the default assumption that their
1973
 * geotransform should be used).
1974
 *
1975
 * This transformer potentially performs three concatenated transformations.
1976
 *
1977
 * The first stage is from source image pixel/line coordinates to source
1978
 * image georeferenced coordinates, and may be done using the geotransform,
1979
 * or if not defined using a polynomial model derived from GCPs.  If GCPs
1980
 * are used this stage is accomplished using GDALGCPTransform().
1981
 *
1982
 * The second stage is to change projections from the source coordinate system
1983
 * to the destination coordinate system, assuming they differ.  This is
1984
 * accomplished internally using GDALReprojectionTransform().
1985
 *
1986
 * The third stage is converting from destination image georeferenced
1987
 * coordinates to destination image coordinates.  This is done using the
1988
 * destination image geotransform, or if not available, using a polynomial
1989
 * model derived from GCPs. If GCPs are used this stage is accomplished using
1990
 * GDALGCPTransform().  This stage is skipped if hDstDS is NULL when the
1991
 * transformation is created.
1992
 *
1993
 * Supported Options (specified with the -to switch of gdalwarp for example):
1994
 * <ul>
1995
 * <li> SRC_SRS: WKT SRS, or any string recognized by
1996
 * OGRSpatialReference::SetFromUserInput(), to be used as an override for
1997
 * hSrcDS.</li>
1998
 * <li> DST_SRS: WKT SRS, or any string recognized by
1999
 * OGRSpatialReference::SetFromUserInput(),  to be used as an override for
2000
 * hDstDS.
2001
 * </li>
2002
 * <li>PROMOTE_TO_3D=YES/NO: whether to promote SRC_SRS / DST_SRS to 3D.
2003
 * Default is NO</li>
2004
 * <li> COORDINATE_OPERATION: (GDAL &gt;= 3.0) Coordinate operation, as
2005
 * a PROJ or WKT string, used as an override over the normally computed
2006
 * pipeline. The pipeline must take into account the axis order of the source
2007
 * and target SRS.
2008
 * </li>
2009
 * <li> ALLOW_BALLPARK=YES/NO: (GDAL &gt;= 3.11) Whether ballpark coordinate
2010
 * operations are allowed. Defaults to YES.</li>
2011
 * <li> ONLY_BEST=YES/NO/AUTO: (GDAL &gt;= 3.11) By default (at least in the
2012
 * PROJ 9.x series), PROJ may use coordinate
2013
 * operations that are not the "best" if resources (typically grids) needed
2014
 * to use them are missing. It will then fallback to other coordinate operations
2015
 * that have a lesser accuracy, for example using Helmert transformations,
2016
 * or in the absence of such operations, to ones with potential very rought
2017
 * accuracy, using "ballpark" transformations
2018
 * (see https://proj.org/glossary.html).
2019
 * When calling this method with YES, PROJ will only consider the
2020
 * "best" operation, and error out (at Transform() time) if they cannot be
2021
 * used.
2022
 * This method may be used together with ALLOW_BALLPARK=NO to
2023
 * only allow best operations that have a known accuracy.
2024
 * Note that this method has no effect on PROJ versions before 9.2.
2025
 * The default value for this option can be also set with the
2026
 * PROJ_ONLY_BEST_DEFAULT environment variable, or with the "only_best_default"
2027
 * setting of proj.ini. Calling SetOnlyBest() overrides such default value.</li>
2028
 * <li> COORDINATE_EPOCH: (GDAL &gt;= 3.0) Coordinate epoch,
2029
 * expressed as a decimal year. Useful for time-dependent coordinate operations.
2030
 * </li>
2031
 * <li> SRC_COORDINATE_EPOCH: (GDAL &gt;= 3.4) Coordinate epoch of source CRS,
2032
 * expressed as a decimal year. Useful for time-dependent coordinate operations.
2033
 * </li>
2034
 * <li> DST_COORDINATE_EPOCH: (GDAL &gt;= 3.4) Coordinate epoch of target CRS,
2035
 * expressed as a decimal year. Useful for time-dependent coordinate operations.
2036
 * </li>
2037
 * <li> GCPS_OK: If false, GCPs will not be used, default is TRUE.
2038
 * </li>
2039
 * <li> REFINE_MINIMUM_GCPS: The minimum amount of GCPs that should be available
2040
 * after the refinement.
2041
 * </li>
2042
 * <li> REFINE_TOLERANCE: The tolerance that specifies when a GCP will be
2043
 * eliminated.
2044
 * </li>
2045
 * <li> MAX_GCP_ORDER: the maximum order to use for GCP derived polynomials if
2046
 * possible.  The default is to autoselect based on the number of GCPs.
2047
 * A value of -1 triggers use of Thin Plate Spline instead of polynomials.
2048
 * </li>
2049
 * <li>GCP_ANTIMERIDIAN_UNWRAP=AUTO/YES/NO. (GDAL &gt;= 3.8) Whether to
2050
 * "unwrap" longitudes of ground control points that span the antimeridian.
2051
 * For datasets with GCPs in longitude/latitude coordinate space spanning the
2052
 * antimeridian, longitudes will have a discontinuity on +/- 180 deg, and
2053
 * will result in a subset of the GCPs with longitude in the [-180,-170] range
2054
 * and another subset in [170, 180]. By default (AUTO), that situation will be
2055
 * detected and longitudes in [-180,-170] will be shifted to [180, 190] to get
2056
 * a continuous set. This option can be set to YES to force that behavior
2057
 * (useful if no SRS information is available), or to NO to disable it.
2058
 * </li>
2059
 * <li> SRC_METHOD: may have a value which is one of GEOTRANSFORM, GCP_HOMOGRAPHY,
2060
 * GCP_POLYNOMIAL, GCP_TPS, GEOLOC_ARRAY, RPC to force only one geolocation
2061
 * method to be considered on the source dataset. Will be used for pixel/line
2062
 * to georef transformation on the source dataset. NO_GEOTRANSFORM can be
2063
 * used to specify the identity geotransform (ungeoreferenced image)
2064
 * </li>
2065
 * <li> DST_METHOD: may have a value which is one of GEOTRANSFORM,
2066
 * GCP_POLYNOMIAL, GCP_HOMOGRAPHY, GCP_TPS, GEOLOC_ARRAY (added in 3.5), RPC to
2067
 * force only one
2068
 * geolocation method to be considered on the target dataset.  Will be used for
2069
 * pixel/line to georef transformation on the destination dataset.
2070
 * NO_GEOTRANSFORM can be used to specify the identity geotransform
2071
 * (ungeoreferenced image)
2072
 * </li>
2073
 * <li> RPC_HEIGHT: A fixed height to be used with RPC
2074
 * calculations. If RPC_HEIGHT and RPC_DEM are not specified but that the RPC
2075
 * metadata domain contains a HEIGHT_DEFAULT item (for example, the DIMAP driver
2076
 * may fill it), this value will be used as the RPC_HEIGHT. Otherwise, if none
2077
 * of RPC_HEIGHT and RPC_DEM are specified as transformer
2078
 * options and if HEIGHT_DEFAULT is no available, a height of 0 will be used.
2079
 * </li>
2080
 * <li> RPC_DEM: The name of a DEM file to be used with RPC
2081
 * calculations. See GDALCreateRPCTransformerV2() for more details.
2082
 * </li>
2083
 * <li> Other RPC related options. See GDALCreateRPCTransformerV2()
2084
 * </li>
2085
 * <li>
2086
 * INSERT_CENTER_LONG: May be set to FALSE to disable setting up a CENTER_LONG
2087
 * value on the coordinate system to rewrap things around the center of the
2088
 * image.
2089
 * </li>
2090
 * <li> SRC_APPROX_ERROR_IN_SRS_UNIT=err_threshold_in_SRS_units. (GDAL
2091
 * &gt;= 2.2) Use an approximate transformer for the source transformer. Must be
2092
 * defined together with SRC_APPROX_ERROR_IN_PIXEL to be taken into account.
2093
 * </li>
2094
 * <li> SRC_APPROX_ERROR_IN_PIXEL=err_threshold_in_pixel. (GDAL &gt;= 2.2) Use
2095
 * an approximate transformer for the source transformer.. Must be defined
2096
 * together with SRC_APPROX_ERROR_IN_SRS_UNIT to be taken into account.
2097
 * </li>
2098
 * <li>
2099
 * DST_APPROX_ERROR_IN_SRS_UNIT=err_threshold_in_SRS_units. (GDAL &gt;= 2.2) Use
2100
 * an approximate transformer for the destination transformer. Must be defined
2101
 * together with DST_APPROX_ERROR_IN_PIXEL to be taken into account.
2102
 * </li>
2103
 * <li>
2104
 * DST_APPROX_ERROR_IN_PIXEL=err_threshold_in_pixel. (GDAL &gt;= 2.2) Use an
2105
 * approximate transformer for the destination transformer. Must be defined
2106
 * together with DST_APPROX_ERROR_IN_SRS_UNIT to be taken into account.
2107
 * </li>
2108
 * <li>
2109
 * REPROJECTION_APPROX_ERROR_IN_SRC_SRS_UNIT=err_threshold_in_src_SRS_units.
2110
 * (GDAL &gt;= 2.2) Use an approximate transformer for the coordinate
2111
 * reprojection. Must be used together with
2112
 * REPROJECTION_APPROX_ERROR_IN_DST_SRS_UNIT to be taken into account.
2113
 * </li>
2114
 * <li>
2115
 * REPROJECTION_APPROX_ERROR_IN_DST_SRS_UNIT=err_threshold_in_dst_SRS_units.
2116
 * (GDAL &gt;= 2.2) Use an approximate transformer for the coordinate
2117
 * reprojection. Must be used together with
2118
 * REPROJECTION_APPROX_ERROR_IN_SRC_SRS_UNIT to be taken into account.
2119
 * </li>
2120
 * <li>
2121
 * AREA_OF_INTEREST=west_lon_deg,south_lat_deg,east_lon_deg,north_lat_deg. (GDAL
2122
 * &gt;= 3.0) Area of interest, used to compute the best coordinate operation
2123
 * between the source and target SRS. If not specified, the bounding box of the
2124
 * source raster will be used.
2125
 * </li>
2126
 * <li> GEOLOC_BACKMAP_OVERSAMPLE_FACTOR=[0.1,2]. (GDAL &gt;= 3.5) Oversample
2127
 * factor used to derive the size of the "backmap" used for geolocation array
2128
 * transformers. Default value is 1.3.
2129
 * </li>
2130
 * <li> GEOLOC_USE_TEMP_DATASETS=YES/NO.
2131
 * (GDAL &gt;= 3.5) Whether temporary GeoTIFF datasets should be used to store
2132
 * the backmap. The default is NO, that is to use in-memory arrays, unless the
2133
 * number of pixels of the geolocation array is greater than 16 megapixels.
2134
 * </li>
2135
 * <li>
2136
 * GEOLOC_ARRAY/SRC_GEOLOC_ARRAY=filename. (GDAL &gt;= 3.5.2) Name of a GDAL
2137
 * dataset containing a geolocation array and associated metadata. This is an
2138
 * alternative to having geolocation information described in the GEOLOCATION
2139
 * metadata domain of the source dataset. The dataset specified may have a
2140
 * GEOLOCATION metadata domain containing appropriate metadata, however default
2141
 * values are assigned for all omitted items. X_BAND defaults to 1 and Y_BAND to
2142
 * 2, however the dataset must contain exactly 2 bands. PIXEL_OFFSET and
2143
 * LINE_OFFSET default to 0. PIXEL_STEP and LINE_STEP default to the ratio of
2144
 * the width/height of the source dataset divided by the with/height of the
2145
 * geolocation array. SRS defaults to the geolocation array dataset's spatial
2146
 * reference system if set, otherwise WGS84 is used.
2147
 * GEOREFERENCING_CONVENTION is selected from the main metadata domain if it
2148
 * is omitted from the GEOLOCATION domain, and if not available
2149
 * TOP_LEFT_CORNER is assigned as a default.
2150
 * If GEOLOC_ARRAY is set SRC_METHOD
2151
 * defaults to GEOLOC_ARRAY.
2152
 * </li>
2153
 * <li>DST_GEOLOC_ARRAY=filename. (GDAL &gt;= 3.5.2) Name of a
2154
 * GDAL dataset that contains at least 2 bands with the X and Y geolocation
2155
 * bands. This is an alternative to having geolocation information described in
2156
 * the GEOLOCATION metadata domain of the destination dataset. See
2157
 * SRC_GEOLOC_ARRAY description for details, assumptions, and defaults. If this
2158
 * option is set, DST_METHOD=GEOLOC_ARRAY will be assumed if not set.
2159
 * </li>
2160
 * </ul>
2161
 *
2162
 * The use case for the *_APPROX_ERROR_* options is when defining an approximate
2163
 * transformer on top of the GenImgProjTransformer globally is not practical.
2164
 * Such a use case is when the source dataset has RPC with a RPC DEM. In such
2165
 * case we don't want to use the approximate transformer on the RPC
2166
 * transformation, as the RPC DEM generally involves non-linearities that the
2167
 * approximate transformer will not detect. In such case, we must a
2168
 * non-approximated GenImgProjTransformer, but it might be worthwhile to use
2169
 * approximate sub- transformers, for example on coordinate reprojection. For
2170
 * example if warping from a source dataset with RPC to a destination dataset
2171
 * with a UTM projection, since the inverse UTM transformation is rather costly.
2172
 * In which case, one can use the REPROJECTION_APPROX_ERROR_IN_SRC_SRS_UNIT and
2173
 * REPROJECTION_APPROX_ERROR_IN_DST_SRS_UNIT options.
2174
 *
2175
 * The list of supported options can also be programmatically obtained with
2176
 * GDALGetGenImgProjTranformerOptionList().
2177
 *
2178
 * @param hSrcDS source dataset, or NULL.
2179
 * @param hDstDS destination dataset (or NULL).
2180
 * @param papszOptions NULL-terminated list of string options (or NULL).
2181
 *
2182
 * @return handle suitable for use GDALGenImgProjTransform(), and to be
2183
 * deallocated with GDALDestroyGenImgProjTransformer() or NULL on failure.
2184
 */
2185
/* clang-format on */
2186
2187
void *GDALCreateGenImgProjTransformer2(GDALDatasetH hSrcDS, GDALDatasetH hDstDS,
2188
                                       CSLConstList papszOptions)
2189
2190
0
{
2191
0
    GDALValidateOptions(GDALGetGenImgProjTranformerOptionList(), papszOptions,
2192
0
                        "option", "transformer options");
2193
2194
0
    double dfWestLongitudeDeg = 0.0;
2195
0
    double dfSouthLatitudeDeg = 0.0;
2196
0
    double dfEastLongitudeDeg = 0.0;
2197
0
    double dfNorthLatitudeDeg = 0.0;
2198
0
    bool bHasAreaOfInterest = false;
2199
0
    if (const char *pszAreaOfInterest =
2200
0
            CSLFetchNameValue(papszOptions, "AREA_OF_INTEREST"))
2201
0
    {
2202
0
        const CPLStringList aosTokens(
2203
0
            CSLTokenizeString2(pszAreaOfInterest, ", ", 0));
2204
0
        if (aosTokens.size() == 4)
2205
0
        {
2206
0
            dfWestLongitudeDeg = CPLAtof(aosTokens[0]);
2207
0
            dfSouthLatitudeDeg = CPLAtof(aosTokens[1]);
2208
0
            dfEastLongitudeDeg = CPLAtof(aosTokens[2]);
2209
0
            dfNorthLatitudeDeg = CPLAtof(aosTokens[3]);
2210
0
            bHasAreaOfInterest = true;
2211
0
        }
2212
0
    }
2213
2214
0
    const char *pszCO = CSLFetchNameValue(papszOptions, "COORDINATE_OPERATION");
2215
2216
0
    const auto SetAxisMapping =
2217
0
        [papszOptions](OGRSpatialReference &oSRS, const char *pszPrefix)
2218
0
    {
2219
0
        const char *pszMapping = CSLFetchNameValue(
2220
0
            papszOptions, std::string(pszPrefix)
2221
0
                              .append("_DATA_AXIS_TO_SRS_AXIS_MAPPING")
2222
0
                              .c_str());
2223
0
        if (pszMapping)
2224
0
        {
2225
0
            CPLStringList aosTokens(CSLTokenizeString2(pszMapping, ",", 0));
2226
0
            std::vector<int> anMapping;
2227
0
            for (int i = 0; i < aosTokens.size(); ++i)
2228
0
                anMapping.push_back(atoi(aosTokens[i]));
2229
0
            oSRS.SetDataAxisToSRSAxisMapping(anMapping);
2230
0
        }
2231
0
        else
2232
0
        {
2233
0
            const char *pszStrategy = CSLFetchNameValueDef(
2234
0
                papszOptions,
2235
0
                std::string(pszPrefix).append("_AXIS_MAPPING_STRATEGY").c_str(),
2236
0
                "TRADITIONAL_GIS_ORDER");
2237
0
            if (EQUAL(pszStrategy, "TRADITIONAL_GIS_ORDER"))
2238
0
                oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
2239
0
            else if (EQUAL(pszStrategy, "AUTHORITY_COMPLIANT"))
2240
0
                oSRS.SetAxisMappingStrategy(OAMS_AUTHORITY_COMPLIANT);
2241
0
            else
2242
0
            {
2243
0
                CPLError(CE_Warning, CPLE_AppDefined,
2244
0
                         "Unrecognized value '%s' for %s", pszStrategy,
2245
0
                         std::string(pszPrefix)
2246
0
                             .append("_AXIS_MAPPING_STRATEGY")
2247
0
                             .c_str());
2248
0
                return false;
2249
0
            }
2250
0
        }
2251
0
        return true;
2252
0
    };
2253
2254
    /* -------------------------------------------------------------------- */
2255
    /*      Initialize the transform info.                                  */
2256
    /* -------------------------------------------------------------------- */
2257
0
    GDALGenImgProjTransformInfo *psInfo =
2258
0
        GDALCreateGenImgProjTransformerInternal();
2259
2260
0
    const auto DealWithForwardOrInverse =
2261
0
        [bHasAreaOfInterest, &dfWestLongitudeDeg, &dfSouthLatitudeDeg,
2262
0
         &dfEastLongitudeDeg, &dfNorthLatitudeDeg, pszCO, papszOptions,
2263
0
         &SetAxisMapping](GDALGenImgProjTransformPart &part, GDALDatasetH hDS,
2264
0
                          const char *pszPrefix, OGRSpatialReference &oSRS,
2265
0
                          bool &bCanUseGeoTransform)
2266
0
    {
2267
0
        const int nOrder =
2268
0
            atoi(CSLFetchNameValueDef(papszOptions, "MAX_GCP_ORDER", "0"));
2269
2270
0
        const bool bGCPUseOK =
2271
0
            CPLTestBool(CSLFetchNameValueDef(papszOptions, "GCPS_OK", "YES"));
2272
0
        const int nMinimumGcps = atoi(
2273
0
            CSLFetchNameValueDef(papszOptions, "REFINE_MINIMUM_GCPS", "-1"));
2274
2275
0
        const char *pszRefineTolerance =
2276
0
            CSLFetchNameValue(papszOptions, "REFINE_TOLERANCE");
2277
0
        const bool bRefine = pszRefineTolerance != nullptr;
2278
0
        const double dfTolerance =
2279
0
            pszRefineTolerance ? CPLAtof(pszRefineTolerance) : 0.0;
2280
2281
0
        const std::string osSRSOptionName =
2282
0
            std::string(pszPrefix).append("_SRS");
2283
0
        const char *pszSRS =
2284
0
            CSLFetchNameValue(papszOptions, osSRSOptionName.c_str());
2285
0
        if (pszSRS)
2286
0
        {
2287
0
            if (pszSRS[0] != '\0' &&
2288
0
                oSRS.SetFromUserInput(pszSRS) != OGRERR_NONE)
2289
0
            {
2290
0
                CPLError(CE_Failure, CPLE_AppDefined,
2291
0
                         "Failed to import coordinate system `%s'.", pszSRS);
2292
0
                return false;
2293
0
            }
2294
0
            if (!SetAxisMapping(oSRS, osSRSOptionName.c_str()))
2295
0
                return false;
2296
0
        }
2297
2298
0
        CSLConstList papszMD = nullptr;
2299
0
        GDALRPCInfoV2 sRPCInfo;
2300
2301
0
        bCanUseGeoTransform = false;
2302
2303
0
        const char *pszMethod = CSLFetchNameValue(
2304
0
            papszOptions, std::string(pszPrefix).append("_METHOD").c_str());
2305
0
        if (!pszMethod && EQUAL(pszPrefix, "SRC"))
2306
0
            pszMethod = CSLFetchNameValue(papszOptions, "METHOD");
2307
2308
0
        const char *pszGeolocArray = CSLFetchNameValue(
2309
0
            papszOptions,
2310
0
            std::string(pszPrefix).append("_GEOLOC_ARRAY").c_str());
2311
0
        if (!pszGeolocArray && EQUAL(pszPrefix, "SRC"))
2312
0
            pszGeolocArray = CSLFetchNameValue(papszOptions, "GEOLOC_ARRAY");
2313
0
        if (!pszMethod && pszGeolocArray != nullptr)
2314
0
            pszMethod = "GEOLOC_ARRAY";
2315
2316
        /* -------------------------------------------------------------------- */
2317
        /*      Get forward and inverse geotransform for the source image.      */
2318
        /* -------------------------------------------------------------------- */
2319
0
        if (hDS == nullptr ||
2320
0
            (pszMethod != nullptr && EQUAL(pszMethod, "NO_GEOTRANSFORM")))
2321
0
        {
2322
0
            part.adfGeoTransform[0] = 0.0;
2323
0
            part.adfGeoTransform[1] = 1.0;
2324
0
            part.adfGeoTransform[2] = 0.0;
2325
0
            part.adfGeoTransform[3] = 0.0;
2326
0
            part.adfGeoTransform[4] = 0.0;
2327
0
            part.adfGeoTransform[5] = 1.0;
2328
0
            memcpy(part.adfInvGeoTransform, part.adfGeoTransform,
2329
0
                   sizeof(double) * 6);
2330
0
        }
2331
0
        else if ((pszMethod == nullptr || EQUAL(pszMethod, "GEOTRANSFORM")) &&
2332
0
                 GDALGetGeoTransform(hDS, part.adfGeoTransform) == CE_None)
2333
0
        {
2334
0
            if (!GDALInvGeoTransform(part.adfGeoTransform,
2335
0
                                     part.adfInvGeoTransform))
2336
0
            {
2337
0
                CPLError(CE_Failure, CPLE_AppDefined,
2338
0
                         "Cannot invert geotransform");
2339
0
                return false;
2340
0
            }
2341
0
            if (pszSRS == nullptr)
2342
0
            {
2343
0
                auto hSRS = GDALGetSpatialRef(hDS);
2344
0
                if (hSRS)
2345
0
                    oSRS = *(OGRSpatialReference::FromHandle(hSRS));
2346
0
            }
2347
0
            if (EQUAL(pszPrefix, "SRC"))
2348
0
            {
2349
0
                if (!bHasAreaOfInterest && pszCO == nullptr && !oSRS.IsEmpty())
2350
0
                {
2351
0
                    GDALComputeAreaOfInterest(
2352
0
                        &oSRS, part.adfGeoTransform, GDALGetRasterXSize(hDS),
2353
0
                        GDALGetRasterYSize(hDS), dfWestLongitudeDeg,
2354
0
                        dfSouthLatitudeDeg, dfEastLongitudeDeg,
2355
0
                        dfNorthLatitudeDeg);
2356
0
                }
2357
0
                bCanUseGeoTransform = true;
2358
0
            }
2359
0
        }
2360
0
        else if (bGCPUseOK &&
2361
0
                 ((pszMethod == nullptr && GDALGetGCPCount(hDS) >= 4 &&
2362
0
                   GDALGetGCPCount(hDS) < 6) ||
2363
0
                  (pszMethod != nullptr &&
2364
0
                   EQUAL(pszMethod, "GCP_HOMOGRAPHY"))) &&
2365
0
                 GDALGetGCPCount(hDS) > 0)
2366
0
        {
2367
0
            if (pszSRS == nullptr)
2368
0
            {
2369
0
                auto hSRS = GDALGetGCPSpatialRef(hDS);
2370
0
                if (hSRS)
2371
0
                    oSRS = *(OGRSpatialReference::FromHandle(hSRS));
2372
0
            }
2373
2374
0
            const auto nGCPCount = GDALGetGCPCount(hDS);
2375
0
            auto pasGCPList = GDALDuplicateGCPs(nGCPCount, GDALGetGCPs(hDS));
2376
0
            GDALGCPAntimeridianUnwrap(nGCPCount, pasGCPList, oSRS,
2377
0
                                      papszOptions);
2378
2379
0
            part.pTransformArg =
2380
0
                GDALCreateHomographyTransformerFromGCPs(nGCPCount, pasGCPList);
2381
2382
0
            GDALDeinitGCPs(nGCPCount, pasGCPList);
2383
0
            CPLFree(pasGCPList);
2384
2385
0
            if (part.pTransformArg == nullptr)
2386
0
            {
2387
0
                return false;
2388
0
            }
2389
0
            part.pTransformer = GDALHomographyTransform;
2390
0
        }
2391
0
        else if (bGCPUseOK &&
2392
0
                 (pszMethod == nullptr || EQUAL(pszMethod, "GCP_POLYNOMIAL")) &&
2393
0
                 GDALGetGCPCount(hDS) > 0 && nOrder >= 0)
2394
0
        {
2395
0
            if (pszSRS == nullptr)
2396
0
            {
2397
0
                auto hSRS = GDALGetGCPSpatialRef(hDS);
2398
0
                if (hSRS)
2399
0
                    oSRS = *(OGRSpatialReference::FromHandle(hSRS));
2400
0
            }
2401
2402
0
            const auto nGCPCount = GDALGetGCPCount(hDS);
2403
0
            auto pasGCPList = GDALDuplicateGCPs(nGCPCount, GDALGetGCPs(hDS));
2404
0
            GDALGCPAntimeridianUnwrap(nGCPCount, pasGCPList, oSRS,
2405
0
                                      papszOptions);
2406
2407
0
            if (bRefine)
2408
0
            {
2409
0
                part.pTransformArg = GDALCreateGCPRefineTransformer(
2410
0
                    nGCPCount, pasGCPList, nOrder, FALSE, dfTolerance,
2411
0
                    nMinimumGcps);
2412
0
            }
2413
0
            else
2414
0
            {
2415
0
                part.pTransformArg = GDALCreateGCPTransformer(
2416
0
                    nGCPCount, pasGCPList, nOrder, FALSE);
2417
0
            }
2418
2419
0
            GDALDeinitGCPs(nGCPCount, pasGCPList);
2420
0
            CPLFree(pasGCPList);
2421
2422
0
            if (part.pTransformArg == nullptr)
2423
0
            {
2424
0
                return false;
2425
0
            }
2426
0
            part.pTransformer = GDALGCPTransform;
2427
0
        }
2428
2429
0
        else if (bGCPUseOK && GDALGetGCPCount(hDS) > 0 && nOrder <= 0 &&
2430
0
                 (pszMethod == nullptr || EQUAL(pszMethod, "GCP_TPS")))
2431
0
        {
2432
0
            if (pszSRS == nullptr)
2433
0
            {
2434
0
                auto hSRS = GDALGetGCPSpatialRef(hDS);
2435
0
                if (hSRS)
2436
0
                    oSRS = *(OGRSpatialReference::FromHandle(hSRS));
2437
0
            }
2438
2439
0
            const auto nGCPCount = GDALGetGCPCount(hDS);
2440
0
            auto pasGCPList = GDALDuplicateGCPs(nGCPCount, GDALGetGCPs(hDS));
2441
0
            GDALGCPAntimeridianUnwrap(nGCPCount, pasGCPList, oSRS,
2442
0
                                      papszOptions);
2443
2444
0
            part.pTransformArg = GDALCreateTPSTransformerInt(
2445
0
                nGCPCount, pasGCPList, FALSE, papszOptions);
2446
2447
0
            GDALDeinitGCPs(nGCPCount, pasGCPList);
2448
0
            CPLFree(pasGCPList);
2449
2450
0
            if (part.pTransformArg == nullptr)
2451
0
            {
2452
0
                return false;
2453
0
            }
2454
0
            part.pTransformer = GDALTPSTransform;
2455
0
        }
2456
2457
0
        else if ((pszMethod == nullptr || EQUAL(pszMethod, "RPC")) &&
2458
0
                 (papszMD = GDALGetMetadata(hDS, "RPC")) != nullptr &&
2459
0
                 GDALExtractRPCInfoV2(papszMD, &sRPCInfo))
2460
0
        {
2461
0
            CPLStringList aosOptions(papszOptions);
2462
0
            if (!CSLFetchNameValue(papszOptions, "RPC_HEIGHT") &&
2463
0
                !CSLFetchNameValue(papszOptions, "RPC_DEM"))
2464
0
            {
2465
0
                if (const char *pszHEIGHT_DEFAULT =
2466
0
                        CSLFetchNameValue(papszMD, "HEIGHT_DEFAULT"))
2467
0
                {
2468
0
                    CPLDebug("GDAL",
2469
0
                             "For %s, using RPC_HEIGHT = HEIGHT_DEFAULT = %s",
2470
0
                             pszPrefix, pszHEIGHT_DEFAULT);
2471
0
                    aosOptions.SetNameValue("RPC_HEIGHT", pszHEIGHT_DEFAULT);
2472
0
                }
2473
0
            }
2474
0
            part.pTransformArg = GDALCreateRPCTransformerV2(&sRPCInfo, FALSE, 0,
2475
0
                                                            aosOptions.List());
2476
0
            if (part.pTransformArg == nullptr)
2477
0
            {
2478
0
                return false;
2479
0
            }
2480
0
            part.pTransformer = GDALRPCTransform;
2481
0
            if (pszSRS == nullptr)
2482
0
            {
2483
0
                oSRS.SetFromUserInput(SRS_WKT_WGS84_LAT_LONG);
2484
0
                oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
2485
0
            }
2486
0
        }
2487
2488
0
        else if ((pszMethod == nullptr || EQUAL(pszMethod, "GEOLOC_ARRAY")) &&
2489
0
                 ((papszMD = GDALGetMetadata(hDS, "GEOLOCATION")) != nullptr ||
2490
0
                  pszGeolocArray != nullptr))
2491
0
        {
2492
0
            CPLStringList aosGeolocMD;  // keep in this scope
2493
0
            if (pszGeolocArray != nullptr)
2494
0
            {
2495
0
                if (papszMD != nullptr)
2496
0
                {
2497
0
                    CPLError(
2498
0
                        CE_Warning, CPLE_AppDefined,
2499
0
                        "Both GEOLOCATION metadata domain on the source "
2500
0
                        "dataset "
2501
0
                        "and [%s_]GEOLOC_ARRAY transformer option are set. "
2502
0
                        "Only using the later.",
2503
0
                        pszPrefix);
2504
0
                }
2505
0
                aosGeolocMD = GDALCreateGeolocationMetadata(
2506
0
                    hDS, pszGeolocArray,
2507
0
                    /* bIsSource= */ EQUAL(pszPrefix, "SRC"));
2508
0
                if (aosGeolocMD.empty())
2509
0
                {
2510
0
                    return false;
2511
0
                }
2512
0
                papszMD = aosGeolocMD.List();
2513
0
            }
2514
2515
0
            part.pTransformArg = GDALCreateGeoLocTransformerEx(
2516
0
                hDS, papszMD, FALSE, nullptr, papszOptions);
2517
0
            if (part.pTransformArg == nullptr)
2518
0
            {
2519
0
                return false;
2520
0
            }
2521
0
            part.pTransformer = GDALGeoLocTransform;
2522
0
            if (pszSRS == nullptr)
2523
0
            {
2524
0
                pszSRS = CSLFetchNameValue(papszMD, "SRS");
2525
0
                if (pszSRS)
2526
0
                {
2527
0
                    oSRS.SetFromUserInput(pszSRS);
2528
0
                    oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
2529
0
                }
2530
0
            }
2531
0
        }
2532
2533
0
        else if (pszMethod != nullptr && EQUAL(pszPrefix, "SRC"))
2534
0
        {
2535
0
            CPLError(CE_Failure, CPLE_AppDefined,
2536
0
                     "Unable to compute a %s based transformation between "
2537
0
                     "pixel/line and georeferenced coordinates for %s.",
2538
0
                     pszMethod, GDALGetDescription(hDS));
2539
2540
0
            return false;
2541
0
        }
2542
2543
0
        else
2544
0
        {
2545
0
            CPLError(CE_Failure, CPLE_AppDefined,
2546
0
                     "Unable to compute a transformation between pixel/line "
2547
0
                     "and georeferenced coordinates for %s. "
2548
0
                     "There is no affine transformation and no GCPs. "
2549
0
                     "Specify transformation option %s_METHOD=NO_GEOTRANSFORM "
2550
0
                     "to bypass this check.",
2551
0
                     GDALGetDescription(hDS), pszPrefix);
2552
2553
0
            return false;
2554
0
        }
2555
2556
        /* ---------------------------------------------------------------- */
2557
        /*      Handle optional source approximation transformer.           */
2558
        /* ---------------------------------------------------------------- */
2559
0
        if (part.pTransformer)
2560
0
        {
2561
0
            const char *pszApproxErrorFwd = CSLFetchNameValue(
2562
0
                papszOptions, std::string(pszPrefix)
2563
0
                                  .append("_APPROX_ERROR_IN_SRS_UNIT")
2564
0
                                  .c_str());
2565
0
            const char *pszApproxErrorReverse = CSLFetchNameValue(
2566
0
                papszOptions, std::string(pszPrefix)
2567
0
                                  .append("_APPROX_ERROR_IN_PIXEL")
2568
0
                                  .c_str());
2569
0
            if (pszApproxErrorFwd && pszApproxErrorReverse)
2570
0
            {
2571
0
                void *pArg = GDALCreateApproxTransformer2(
2572
0
                    part.pTransformer, part.pTransformArg,
2573
0
                    CPLAtof(pszApproxErrorFwd), CPLAtof(pszApproxErrorReverse));
2574
0
                if (pArg == nullptr)
2575
0
                {
2576
0
                    return false;
2577
0
                }
2578
0
                part.pTransformArg = pArg;
2579
0
                part.pTransformer = GDALApproxTransform;
2580
0
                GDALApproxTransformerOwnsSubtransformer(part.pTransformArg,
2581
0
                                                        TRUE);
2582
0
            }
2583
0
        }
2584
2585
0
        return true;
2586
0
    };
2587
2588
    /* -------------------------------------------------------------------- */
2589
    /*      Get forward and inverse geotransform for the source image.      */
2590
    /* -------------------------------------------------------------------- */
2591
0
    bool bCanUseSrcGeoTransform = false;
2592
0
    OGRSpatialReference oSrcSRS;
2593
0
    if (!DealWithForwardOrInverse(psInfo->sSrcParams, hSrcDS, "SRC", oSrcSRS,
2594
0
                                  bCanUseSrcGeoTransform))
2595
0
    {
2596
0
        GDALDestroyGenImgProjTransformer(psInfo);
2597
0
        return nullptr;
2598
0
    }
2599
2600
    /* -------------------------------------------------------------------- */
2601
    /*      Get forward and inverse geotransform for destination image.     */
2602
    /*      If we have no destination use a unit transform.                 */
2603
    /* -------------------------------------------------------------------- */
2604
0
    bool bIgnored = false;
2605
0
    OGRSpatialReference oDstSRS;
2606
0
    if (!DealWithForwardOrInverse(psInfo->sDstParams, hDstDS, "DST", oDstSRS,
2607
0
                                  bIgnored))
2608
0
    {
2609
0
        GDALDestroyGenImgProjTransformer(psInfo);
2610
0
        return nullptr;
2611
0
    }
2612
2613
    /* -------------------------------------------------------------------- */
2614
    /*      Setup reprojection.                                             */
2615
    /* -------------------------------------------------------------------- */
2616
2617
0
    if (CPLFetchBool(papszOptions, "STRIP_VERT_CS", false))
2618
0
    {
2619
0
        if (oSrcSRS.IsCompound())
2620
0
        {
2621
0
            oSrcSRS.StripVertical();
2622
0
        }
2623
0
        if (oDstSRS.IsCompound())
2624
0
        {
2625
0
            oDstSRS.StripVertical();
2626
0
        }
2627
0
    }
2628
2629
0
    const bool bMayInsertCenterLong =
2630
0
        (bCanUseSrcGeoTransform && !oSrcSRS.IsEmpty() && hSrcDS &&
2631
0
         CPLFetchBool(papszOptions, "INSERT_CENTER_LONG", true));
2632
0
    const char *pszSrcCoordEpoch =
2633
0
        CSLFetchNameValue(papszOptions, "SRC_COORDINATE_EPOCH");
2634
0
    const char *pszDstCoordEpoch =
2635
0
        CSLFetchNameValue(papszOptions, "DST_COORDINATE_EPOCH");
2636
0
    if ((!oSrcSRS.IsEmpty() && !oDstSRS.IsEmpty() &&
2637
0
         (pszSrcCoordEpoch || pszDstCoordEpoch || !oSrcSRS.IsSame(&oDstSRS) ||
2638
0
          (oSrcSRS.IsGeographic() && bMayInsertCenterLong))) ||
2639
0
        pszCO)
2640
0
    {
2641
0
        CPLStringList aosOptions;
2642
2643
0
        if (bMayInsertCenterLong)
2644
0
        {
2645
0
            InsertCenterLong(hSrcDS, &oSrcSRS, aosOptions);
2646
0
        }
2647
2648
0
        if (CPLFetchBool(papszOptions, "PROMOTE_TO_3D", false))
2649
0
        {
2650
0
            oSrcSRS.PromoteTo3D(nullptr);
2651
0
            oDstSRS.PromoteTo3D(nullptr);
2652
0
        }
2653
2654
0
        if (!(dfWestLongitudeDeg == 0.0 && dfSouthLatitudeDeg == 0.0 &&
2655
0
              dfEastLongitudeDeg == 0.0 && dfNorthLatitudeDeg == 0.0))
2656
0
        {
2657
0
            aosOptions.SetNameValue(
2658
0
                "AREA_OF_INTEREST",
2659
0
                CPLSPrintf("%.16g,%.16g,%.16g,%.16g", dfWestLongitudeDeg,
2660
0
                           dfSouthLatitudeDeg, dfEastLongitudeDeg,
2661
0
                           dfNorthLatitudeDeg));
2662
0
        }
2663
0
        if (pszCO)
2664
0
        {
2665
0
            aosOptions.SetNameValue("COORDINATE_OPERATION", pszCO);
2666
0
        }
2667
2668
0
        const char *pszCoordEpoch =
2669
0
            CSLFetchNameValue(papszOptions, "COORDINATE_EPOCH");
2670
0
        if (pszCoordEpoch)
2671
0
        {
2672
0
            aosOptions.SetNameValue("COORDINATE_EPOCH", pszCoordEpoch);
2673
0
        }
2674
2675
0
        if (pszSrcCoordEpoch)
2676
0
        {
2677
0
            aosOptions.SetNameValue("SRC_COORDINATE_EPOCH", pszSrcCoordEpoch);
2678
0
            oSrcSRS.SetCoordinateEpoch(CPLAtof(pszSrcCoordEpoch));
2679
0
        }
2680
2681
0
        if (pszDstCoordEpoch)
2682
0
        {
2683
0
            aosOptions.SetNameValue("DST_COORDINATE_EPOCH", pszDstCoordEpoch);
2684
0
            oDstSRS.SetCoordinateEpoch(CPLAtof(pszDstCoordEpoch));
2685
0
        }
2686
2687
0
        if (const char *pszAllowBallpark =
2688
0
                CSLFetchNameValue(papszOptions, "ALLOW_BALLPARK"))
2689
0
        {
2690
0
            aosOptions.SetNameValue("ALLOW_BALLPARK", pszAllowBallpark);
2691
0
        }
2692
2693
0
        if (const char *pszOnlyBest =
2694
0
                CSLFetchNameValue(papszOptions, "ONLY_BEST"))
2695
0
        {
2696
0
            aosOptions.SetNameValue("ONLY_BEST", pszOnlyBest);
2697
0
        }
2698
2699
0
        psInfo->pReprojectArg = GDALCreateReprojectionTransformerEx(
2700
0
            !oSrcSRS.IsEmpty() ? OGRSpatialReference::ToHandle(&oSrcSRS)
2701
0
                               : nullptr,
2702
0
            !oDstSRS.IsEmpty() ? OGRSpatialReference::ToHandle(&oDstSRS)
2703
0
                               : nullptr,
2704
0
            aosOptions.List());
2705
2706
0
        if (pszCO)
2707
0
        {
2708
0
            psInfo->bHasCustomTransformationPipeline = true;
2709
0
        }
2710
2711
0
        if (psInfo->pReprojectArg == nullptr)
2712
0
        {
2713
0
            GDALDestroyGenImgProjTransformer(psInfo);
2714
0
            return nullptr;
2715
0
        }
2716
0
        psInfo->pReproject = GDALReprojectionTransform;
2717
2718
        /* --------------------------------------------------------------------
2719
         */
2720
        /*      Handle optional reprojection approximation transformer. */
2721
        /* --------------------------------------------------------------------
2722
         */
2723
0
        const char *psApproxErrorFwd = CSLFetchNameValue(
2724
0
            papszOptions, "REPROJECTION_APPROX_ERROR_IN_DST_SRS_UNIT");
2725
0
        const char *psApproxErrorReverse = CSLFetchNameValue(
2726
0
            papszOptions, "REPROJECTION_APPROX_ERROR_IN_SRC_SRS_UNIT");
2727
0
        if (psApproxErrorFwd && psApproxErrorReverse)
2728
0
        {
2729
0
            void *pArg = GDALCreateApproxTransformer2(
2730
0
                psInfo->pReproject, psInfo->pReprojectArg,
2731
0
                CPLAtof(psApproxErrorFwd), CPLAtof(psApproxErrorReverse));
2732
0
            if (pArg == nullptr)
2733
0
            {
2734
0
                GDALDestroyGenImgProjTransformer(psInfo);
2735
0
                return nullptr;
2736
0
            }
2737
0
            psInfo->pReprojectArg = pArg;
2738
0
            psInfo->pReproject = GDALApproxTransform;
2739
0
            GDALApproxTransformerOwnsSubtransformer(psInfo->pReprojectArg,
2740
0
                                                    TRUE);
2741
0
        }
2742
0
    }
2743
2744
0
    return psInfo;
2745
0
}
2746
2747
/************************************************************************/
2748
/*                  GDALRefreshGenImgProjTransformer()                  */
2749
/************************************************************************/
2750
2751
void GDALRefreshGenImgProjTransformer(void *hTransformArg)
2752
0
{
2753
0
    GDALGenImgProjTransformInfo *psInfo =
2754
0
        static_cast<GDALGenImgProjTransformInfo *>(hTransformArg);
2755
2756
0
    if (psInfo->pReprojectArg &&
2757
0
        psInfo->bCheckWithInvertPROJ != GetCurrentCheckWithInvertPROJ())
2758
0
    {
2759
0
        psInfo->bCheckWithInvertPROJ = !psInfo->bCheckWithInvertPROJ;
2760
2761
0
        CPLXMLNode *psXML =
2762
0
            GDALSerializeTransformer(psInfo->pReproject, psInfo->pReprojectArg);
2763
0
        GDALDestroyTransformer(psInfo->pReprojectArg);
2764
0
        GDALDeserializeTransformer(psXML, &psInfo->pReproject,
2765
0
                                   &psInfo->pReprojectArg);
2766
0
        CPLDestroyXMLNode(psXML);
2767
0
    }
2768
0
}
2769
2770
/************************************************************************/
2771
/*                  GDALCreateGenImgProjTransformer3()                  */
2772
/************************************************************************/
2773
2774
/**
2775
 * Create image to image transformer.
2776
 *
2777
 * This function creates a transformation object that maps from pixel/line
2778
 * coordinates on one image to pixel/line coordinates on another image.  The
2779
 * images may potentially be georeferenced in different coordinate systems,
2780
 * and may used GCPs to map between their pixel/line coordinates and
2781
 * georeferenced coordinates (as opposed to the default assumption that their
2782
 * geotransform should be used).
2783
 *
2784
 * This transformer potentially performs three concatenated transformations.
2785
 *
2786
 * The first stage is from source image pixel/line coordinates to source
2787
 * image georeferenced coordinates, and may be done using the geotransform,
2788
 * or if not defined using a polynomial model derived from GCPs.  If GCPs
2789
 * are used this stage is accomplished using GDALGCPTransform().
2790
 *
2791
 * The second stage is to change projections from the source coordinate system
2792
 * to the destination coordinate system, assuming they differ.  This is
2793
 * accomplished internally using GDALReprojectionTransform().
2794
 *
2795
 * The third stage is converting from destination image georeferenced
2796
 * coordinates to destination image coordinates.  This is done using the
2797
 * destination image geotransform, or if not available, using a polynomial
2798
 * model derived from GCPs. If GCPs are used this stage is accomplished using
2799
 * GDALGCPTransform().  This stage is skipped if hDstDS is NULL when the
2800
 * transformation is created.
2801
 *
2802
 * @param pszSrcWKT source WKT (or NULL).
2803
 * @param padfSrcGeoTransform source geotransform (or NULL).
2804
 * @param pszDstWKT destination WKT (or NULL).
2805
 * @param padfDstGeoTransform destination geotransform (or NULL).
2806
 *
2807
 * @return handle suitable for use GDALGenImgProjTransform(), and to be
2808
 * deallocated with GDALDestroyGenImgProjTransformer() or NULL on failure.
2809
 */
2810
2811
void *GDALCreateGenImgProjTransformer3(const char *pszSrcWKT,
2812
                                       const double *padfSrcGeoTransform,
2813
                                       const char *pszDstWKT,
2814
                                       const double *padfDstGeoTransform)
2815
2816
0
{
2817
0
    OGRSpatialReference oSrcSRS;
2818
0
    if (pszSrcWKT)
2819
0
    {
2820
0
        oSrcSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
2821
0
        if (pszSrcWKT[0] != '\0' &&
2822
0
            oSrcSRS.importFromWkt(pszSrcWKT) != OGRERR_NONE)
2823
0
        {
2824
0
            CPLError(CE_Failure, CPLE_AppDefined,
2825
0
                     "Failed to import coordinate system `%s'.", pszSrcWKT);
2826
0
            return nullptr;
2827
0
        }
2828
0
    }
2829
2830
0
    OGRSpatialReference oDstSRS;
2831
0
    if (pszDstWKT)
2832
0
    {
2833
0
        oDstSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
2834
0
        if (pszDstWKT[0] != '\0' &&
2835
0
            oDstSRS.importFromWkt(pszDstWKT) != OGRERR_NONE)
2836
0
        {
2837
0
            CPLError(CE_Failure, CPLE_AppDefined,
2838
0
                     "Failed to import coordinate system `%s'.", pszDstWKT);
2839
0
            return nullptr;
2840
0
        }
2841
0
    }
2842
0
    return GDALCreateGenImgProjTransformer4(
2843
0
        OGRSpatialReference::ToHandle(&oSrcSRS), padfSrcGeoTransform,
2844
0
        OGRSpatialReference::ToHandle(&oDstSRS), padfDstGeoTransform, nullptr);
2845
0
}
2846
2847
/************************************************************************/
2848
/*                  GDALCreateGenImgProjTransformer4()                  */
2849
/************************************************************************/
2850
2851
/**
2852
 * Create image to image transformer.
2853
 *
2854
 * Similar to GDALCreateGenImgProjTransformer3(), except that it takes
2855
 * OGRSpatialReferenceH objects and options.
2856
 * The options are the ones supported by GDALCreateReprojectionTransformerEx()
2857
 *
2858
 * @since GDAL 3.0
2859
 */
2860
void *GDALCreateGenImgProjTransformer4(OGRSpatialReferenceH hSrcSRS,
2861
                                       const double *padfSrcGeoTransform,
2862
                                       OGRSpatialReferenceH hDstSRS,
2863
                                       const double *padfDstGeoTransform,
2864
                                       const char *const *papszOptions)
2865
0
{
2866
    /* -------------------------------------------------------------------- */
2867
    /*      Initialize the transform info.                                  */
2868
    /* -------------------------------------------------------------------- */
2869
0
    GDALGenImgProjTransformInfo *psInfo =
2870
0
        GDALCreateGenImgProjTransformerInternal();
2871
2872
    /* -------------------------------------------------------------------- */
2873
    /*      Get forward and inverse geotransform for the source image.      */
2874
    /* -------------------------------------------------------------------- */
2875
2876
0
    const auto SetParams =
2877
0
        [](GDALGenImgProjTransformPart &part, const double *padfGT)
2878
0
    {
2879
0
        if (padfGT)
2880
0
        {
2881
0
            memcpy(part.adfGeoTransform, padfGT, sizeof(part.adfGeoTransform));
2882
0
            if (!GDALInvGeoTransform(part.adfGeoTransform,
2883
0
                                     part.adfInvGeoTransform))
2884
0
            {
2885
0
                CPLError(CE_Failure, CPLE_AppDefined,
2886
0
                         "Cannot invert geotransform");
2887
0
                return false;
2888
0
            }
2889
0
        }
2890
0
        else
2891
0
        {
2892
0
            part.adfGeoTransform[0] = 0.0;
2893
0
            part.adfGeoTransform[1] = 1.0;
2894
0
            part.adfGeoTransform[2] = 0.0;
2895
0
            part.adfGeoTransform[3] = 0.0;
2896
0
            part.adfGeoTransform[4] = 0.0;
2897
0
            part.adfGeoTransform[5] = 1.0;
2898
0
            memcpy(part.adfInvGeoTransform, part.adfGeoTransform,
2899
0
                   sizeof(double) * 6);
2900
0
        }
2901
0
        return true;
2902
0
    };
2903
2904
0
    if (!SetParams(psInfo->sSrcParams, padfSrcGeoTransform))
2905
0
    {
2906
0
        GDALDestroyGenImgProjTransformer(psInfo);
2907
0
        return nullptr;
2908
0
    }
2909
2910
    /* -------------------------------------------------------------------- */
2911
    /*      Setup reprojection.                                             */
2912
    /* -------------------------------------------------------------------- */
2913
0
    OGRSpatialReference *poSrcSRS = OGRSpatialReference::FromHandle(hSrcSRS);
2914
0
    OGRSpatialReference *poDstSRS = OGRSpatialReference::FromHandle(hDstSRS);
2915
0
    if (!poSrcSRS->IsEmpty() && !poDstSRS->IsEmpty() &&
2916
0
        !poSrcSRS->IsSame(poDstSRS))
2917
0
    {
2918
0
        psInfo->pReprojectArg =
2919
0
            GDALCreateReprojectionTransformerEx(hSrcSRS, hDstSRS, papszOptions);
2920
0
        if (psInfo->pReprojectArg == nullptr)
2921
0
        {
2922
0
            GDALDestroyGenImgProjTransformer(psInfo);
2923
0
            return nullptr;
2924
0
        }
2925
0
        psInfo->pReproject = GDALReprojectionTransform;
2926
0
    }
2927
2928
    /* -------------------------------------------------------------------- */
2929
    /*      Get forward and inverse geotransform for destination image.     */
2930
    /*      If we have no destination matrix use a unit transform.          */
2931
    /* -------------------------------------------------------------------- */
2932
0
    if (!SetParams(psInfo->sDstParams, padfDstGeoTransform))
2933
0
    {
2934
0
        GDALDestroyGenImgProjTransformer(psInfo);
2935
0
        return nullptr;
2936
0
    }
2937
2938
0
    return psInfo;
2939
0
}
2940
2941
/************************************************************************/
2942
/*            GDALSetGenImgProjTransformerDstGeoTransform()             */
2943
/************************************************************************/
2944
2945
/**
2946
 * Set GenImgProj output geotransform.
2947
 *
2948
 * Normally the "destination geotransform", or transformation between
2949
 * georeferenced output coordinates and pixel/line coordinates on the
2950
 * destination file is extracted from the destination file by
2951
 * GDALCreateGenImgProjTransformer() and stored in the GenImgProj private
2952
 * info.  However, sometimes it is inconvenient to have an output file
2953
 * handle with appropriate geotransform information when creating the
2954
 * transformation.  For these cases, this function can be used to apply
2955
 * the destination geotransform.
2956
 *
2957
 * @param hTransformArg the handle to update.
2958
 * @param padfGeoTransform the destination geotransform to apply (six doubles).
2959
 */
2960
2961
void GDALSetGenImgProjTransformerDstGeoTransform(void *hTransformArg,
2962
                                                 const double *padfGeoTransform)
2963
2964
0
{
2965
0
    VALIDATE_POINTER0(hTransformArg,
2966
0
                      "GDALSetGenImgProjTransformerDstGeoTransform");
2967
2968
0
    GDALGenImgProjTransformInfo *psInfo =
2969
0
        static_cast<GDALGenImgProjTransformInfo *>(hTransformArg);
2970
2971
0
    memcpy(psInfo->sDstParams.adfGeoTransform, padfGeoTransform,
2972
0
           sizeof(double) * 6);
2973
0
    if (!GDALInvGeoTransform(psInfo->sDstParams.adfGeoTransform,
2974
0
                             psInfo->sDstParams.adfInvGeoTransform))
2975
0
    {
2976
0
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot invert geotransform");
2977
0
    }
2978
0
}
2979
2980
/************************************************************************/
2981
/*                  GDALDestroyGenImgProjTransformer()                  */
2982
/************************************************************************/
2983
2984
/**
2985
 * GenImgProjTransformer deallocator.
2986
 *
2987
 * This function is used to deallocate the handle created with
2988
 * GDALCreateGenImgProjTransformer().
2989
 *
2990
 * @param hTransformArg the handle to deallocate.
2991
 */
2992
2993
void GDALDestroyGenImgProjTransformer(void *hTransformArg)
2994
2995
0
{
2996
0
    if (hTransformArg == nullptr)
2997
0
        return;
2998
2999
0
    GDALGenImgProjTransformInfo *psInfo =
3000
0
        static_cast<GDALGenImgProjTransformInfo *>(hTransformArg);
3001
3002
0
    if (psInfo->sSrcParams.pTransformArg != nullptr)
3003
0
        GDALDestroyTransformer(psInfo->sSrcParams.pTransformArg);
3004
3005
0
    if (psInfo->sDstParams.pTransformArg != nullptr)
3006
0
        GDALDestroyTransformer(psInfo->sDstParams.pTransformArg);
3007
3008
0
    if (psInfo->pReprojectArg != nullptr)
3009
0
        GDALDestroyTransformer(psInfo->pReprojectArg);
3010
3011
0
    CPLFree(psInfo);
3012
0
}
3013
3014
/************************************************************************/
3015
/*                      GDALGenImgProjTransform()                       */
3016
/************************************************************************/
3017
3018
/**
3019
 * Perform general image reprojection transformation.
3020
 *
3021
 * Actually performs the transformation setup in
3022
 * GDALCreateGenImgProjTransformer().  This function matches the signature
3023
 * required by the GDALTransformerFunc(), and more details on the arguments
3024
 * can be found in that topic.
3025
 */
3026
3027
#ifdef DEBUG_APPROX_TRANSFORMER
3028
int countGDALGenImgProjTransform = 0;
3029
#endif
3030
3031
int GDALGenImgProjTransform(void *pTransformArgIn, int bDstToSrc,
3032
                            int nPointCount, double *padfX, double *padfY,
3033
                            double *padfZ, int *panSuccess)
3034
0
{
3035
0
    GDALGenImgProjTransformInfo *psInfo =
3036
0
        static_cast<GDALGenImgProjTransformInfo *>(pTransformArgIn);
3037
3038
#ifdef DEBUG_APPROX_TRANSFORMER
3039
    CPLAssert(nPointCount > 0);
3040
    countGDALGenImgProjTransform += nPointCount;
3041
#endif
3042
3043
0
    for (int i = 0; i < nPointCount; i++)
3044
0
    {
3045
0
        panSuccess[i] = (padfX[i] != HUGE_VAL && padfY[i] != HUGE_VAL);
3046
0
    }
3047
3048
0
    int ret = TRUE;
3049
3050
    /* -------------------------------------------------------------------- */
3051
    /*      Convert from src (dst) pixel/line to src (dst)                  */
3052
    /*      georeferenced coordinates.                                      */
3053
    /* -------------------------------------------------------------------- */
3054
0
    {
3055
0
        const auto params = bDstToSrc ? psInfo->sDstParams : psInfo->sSrcParams;
3056
0
        const double *padfGeoTransform = params.adfGeoTransform;
3057
0
        void *pTransformArg = params.pTransformArg;
3058
0
        GDALTransformerFunc pTransformer = params.pTransformer;
3059
3060
0
        if (pTransformArg != nullptr)
3061
0
        {
3062
0
            if (!pTransformer(pTransformArg, FALSE, nPointCount, padfX, padfY,
3063
0
                              padfZ, panSuccess))
3064
0
                ret = FALSE;
3065
0
        }
3066
0
        else
3067
0
        {
3068
0
            for (int i = 0; i < nPointCount; i++)
3069
0
            {
3070
0
                if (!panSuccess[i])
3071
0
                    continue;
3072
3073
0
                const double dfNewX = padfGeoTransform[0] +
3074
0
                                      padfX[i] * padfGeoTransform[1] +
3075
0
                                      padfY[i] * padfGeoTransform[2];
3076
0
                const double dfNewY = padfGeoTransform[3] +
3077
0
                                      padfX[i] * padfGeoTransform[4] +
3078
0
                                      padfY[i] * padfGeoTransform[5];
3079
3080
0
                padfX[i] = dfNewX;
3081
0
                padfY[i] = dfNewY;
3082
0
            }
3083
0
        }
3084
0
    }
3085
3086
    /* -------------------------------------------------------------------- */
3087
    /*      Reproject if needed.                                            */
3088
    /* -------------------------------------------------------------------- */
3089
0
    if (psInfo->pReprojectArg)
3090
0
    {
3091
0
        if (!psInfo->pReproject(psInfo->pReprojectArg, bDstToSrc, nPointCount,
3092
0
                                padfX, padfY, padfZ, panSuccess))
3093
0
            ret = FALSE;
3094
0
    }
3095
3096
    /* -------------------------------------------------------------------- */
3097
    /*      Convert dst (src) georef coordinates back to pixel/line.        */
3098
    /* -------------------------------------------------------------------- */
3099
0
    {
3100
0
        const auto params = bDstToSrc ? psInfo->sSrcParams : psInfo->sDstParams;
3101
0
        const double *padfInvGeoTransform = params.adfInvGeoTransform;
3102
0
        void *pTransformArg = params.pTransformArg;
3103
0
        GDALTransformerFunc pTransformer = params.pTransformer;
3104
3105
0
        if (pTransformArg != nullptr)
3106
0
        {
3107
0
            if (!pTransformer(pTransformArg, TRUE, nPointCount, padfX, padfY,
3108
0
                              padfZ, panSuccess))
3109
0
                ret = FALSE;
3110
0
        }
3111
0
        else
3112
0
        {
3113
0
            for (int i = 0; i < nPointCount; i++)
3114
0
            {
3115
0
                if (!panSuccess[i])
3116
0
                    continue;
3117
3118
0
                const double dfNewX = padfInvGeoTransform[0] +
3119
0
                                      padfX[i] * padfInvGeoTransform[1] +
3120
0
                                      padfY[i] * padfInvGeoTransform[2];
3121
0
                const double dfNewY = padfInvGeoTransform[3] +
3122
0
                                      padfX[i] * padfInvGeoTransform[4] +
3123
0
                                      padfY[i] * padfInvGeoTransform[5];
3124
3125
0
                padfX[i] = dfNewX;
3126
0
                padfY[i] = dfNewY;
3127
0
            }
3128
0
        }
3129
0
    }
3130
3131
0
    return ret;
3132
0
}
3133
3134
/************************************************************************/
3135
/*              GDALTransformLonLatToDestGenImgProjTransformer()        */
3136
/************************************************************************/
3137
3138
int GDALTransformLonLatToDestGenImgProjTransformer(void *hTransformArg,
3139
                                                   double *pdfX, double *pdfY)
3140
0
{
3141
0
    GDALGenImgProjTransformInfo *psInfo =
3142
0
        static_cast<GDALGenImgProjTransformInfo *>(hTransformArg);
3143
3144
0
    if (psInfo->pReprojectArg == nullptr ||
3145
0
        psInfo->pReproject != GDALReprojectionTransform)
3146
0
        return false;
3147
3148
0
    GDALReprojectionTransformInfo *psReprojInfo =
3149
0
        static_cast<GDALReprojectionTransformInfo *>(psInfo->pReprojectArg);
3150
0
    if (psReprojInfo->poForwardTransform == nullptr ||
3151
0
        psReprojInfo->poForwardTransform->GetSourceCS() == nullptr)
3152
0
        return false;
3153
3154
0
    double z = 0;
3155
0
    int success = true;
3156
0
    auto poSourceCRS = psReprojInfo->poForwardTransform->GetSourceCS();
3157
0
    if (poSourceCRS->IsGeographic() &&
3158
0
        std::fabs(poSourceCRS->GetAngularUnits() -
3159
0
                  CPLAtof(SRS_UA_DEGREE_CONV)) < 1e-9)
3160
0
    {
3161
        // Optimization to avoid creating a OGRCoordinateTransformation
3162
0
        OGRAxisOrientation eSourceFirstAxisOrient = OAO_Other;
3163
0
        poSourceCRS->GetAxis(nullptr, 0, &eSourceFirstAxisOrient);
3164
0
        const auto &mapping = poSourceCRS->GetDataAxisToSRSAxisMapping();
3165
0
        if ((mapping[0] == 2 && eSourceFirstAxisOrient == OAO_East) ||
3166
0
            (mapping[0] == 1 && eSourceFirstAxisOrient != OAO_East))
3167
0
        {
3168
0
            std::swap(*pdfX, *pdfY);
3169
0
        }
3170
0
    }
3171
0
    else
3172
0
    {
3173
0
        auto poLongLat =
3174
0
            std::unique_ptr<OGRSpatialReference>(poSourceCRS->CloneGeogCS());
3175
0
        if (poLongLat == nullptr)
3176
0
            return false;
3177
0
        poLongLat->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
3178
3179
0
        const bool bCurrentCheckWithInvertProj =
3180
0
            GetCurrentCheckWithInvertPROJ();
3181
0
        if (!bCurrentCheckWithInvertProj)
3182
0
            CPLSetThreadLocalConfigOption("CHECK_WITH_INVERT_PROJ", "YES");
3183
0
        auto poCT = std::unique_ptr<OGRCoordinateTransformation>(
3184
0
            OGRCreateCoordinateTransformation(poLongLat.get(), poSourceCRS));
3185
0
        if (!bCurrentCheckWithInvertProj)
3186
0
            CPLSetThreadLocalConfigOption("CHECK_WITH_INVERT_PROJ", nullptr);
3187
0
        if (poCT == nullptr)
3188
0
            return false;
3189
3190
0
        poCT->SetEmitErrors(false);
3191
0
        if (!poCT->Transform(1, pdfX, pdfY))
3192
0
            return false;
3193
3194
0
        if (!psInfo->pReproject(psInfo->pReprojectArg, false, 1, pdfX, pdfY, &z,
3195
0
                                &success) ||
3196
0
            !success)
3197
0
        {
3198
0
            return false;
3199
0
        }
3200
0
    }
3201
3202
0
    double *padfGeoTransform = psInfo->sDstParams.adfInvGeoTransform;
3203
0
    void *pTransformArg = psInfo->sDstParams.pTransformArg;
3204
0
    GDALTransformerFunc pTransformer = psInfo->sDstParams.pTransformer;
3205
0
    if (pTransformArg != nullptr)
3206
0
    {
3207
0
        if (!pTransformer(pTransformArg, TRUE, 1, pdfX, pdfY, &z, &success) ||
3208
0
            !success)
3209
0
        {
3210
0
            return false;
3211
0
        }
3212
0
    }
3213
0
    else
3214
0
    {
3215
0
        const double dfNewX = padfGeoTransform[0] +
3216
0
                              pdfX[0] * padfGeoTransform[1] +
3217
0
                              pdfY[0] * padfGeoTransform[2];
3218
0
        const double dfNewY = padfGeoTransform[3] +
3219
0
                              pdfX[0] * padfGeoTransform[4] +
3220
0
                              pdfY[0] * padfGeoTransform[5];
3221
3222
0
        pdfX[0] = dfNewX;
3223
0
        pdfY[0] = dfNewY;
3224
0
    }
3225
3226
0
    return true;
3227
0
}
3228
3229
/************************************************************************/
3230
/*                 GDALSerializeGenImgProjTransformer()                 */
3231
/************************************************************************/
3232
3233
static CPLXMLNode *GDALSerializeGenImgProjTransformer(void *pTransformArg)
3234
3235
0
{
3236
0
    GDALGenImgProjTransformInfo *psInfo =
3237
0
        static_cast<GDALGenImgProjTransformInfo *>(pTransformArg);
3238
3239
0
    CPLXMLNode *psTree =
3240
0
        CPLCreateXMLNode(nullptr, CXT_Element, "GenImgProjTransformer");
3241
3242
0
    const auto SerializePart =
3243
0
        [psTree](const char *pszPrefix, const GDALGenImgProjTransformPart &part)
3244
0
    {
3245
0
        char szWork[200] = {};
3246
3247
        /* ------------------------------------------------------------- */
3248
        /*      Handle transformation.                                   */
3249
        /* ------------------------------------------------------------- */
3250
0
        if (part.pTransformArg != nullptr)
3251
0
        {
3252
0
            CPLXMLNode *psTransformer =
3253
0
                GDALSerializeTransformer(part.pTransformer, part.pTransformArg);
3254
0
            if (psTransformer != nullptr)
3255
0
            {
3256
0
                CPLXMLNode *psTransformerContainer = CPLCreateXMLNode(
3257
0
                    psTree, CXT_Element,
3258
0
                    CPLSPrintf("%s%s", pszPrefix, psTransformer->pszValue));
3259
3260
0
                CPLAddXMLChild(psTransformerContainer, psTransformer);
3261
0
            }
3262
0
        }
3263
3264
        /* ------------------------------------------------------------- */
3265
        /*      Handle geotransforms.                                    */
3266
        /* ------------------------------------------------------------- */
3267
0
        else
3268
0
        {
3269
0
            CPLsnprintf(szWork, sizeof(szWork),
3270
0
                        "%.17g,%.17g,%.17g,%.17g,%.17g,%.17g",
3271
0
                        part.adfGeoTransform[0], part.adfGeoTransform[1],
3272
0
                        part.adfGeoTransform[2], part.adfGeoTransform[3],
3273
0
                        part.adfGeoTransform[4], part.adfGeoTransform[5]);
3274
0
            CPLCreateXMLElementAndValue(
3275
0
                psTree, CPLSPrintf("%sGeoTransform", pszPrefix), szWork);
3276
3277
0
            CPLsnprintf(szWork, sizeof(szWork),
3278
0
                        "%.17g,%.17g,%.17g,%.17g,%.17g,%.17g",
3279
0
                        part.adfInvGeoTransform[0], part.adfInvGeoTransform[1],
3280
0
                        part.adfInvGeoTransform[2], part.adfInvGeoTransform[3],
3281
0
                        part.adfInvGeoTransform[4], part.adfInvGeoTransform[5]);
3282
0
            CPLCreateXMLElementAndValue(
3283
0
                psTree, CPLSPrintf("%sInvGeoTransform", pszPrefix), szWork);
3284
0
        }
3285
0
    };
3286
3287
0
    SerializePart("Src", psInfo->sSrcParams);
3288
0
    SerializePart("Dst", psInfo->sDstParams);
3289
3290
    /* -------------------------------------------------------------------- */
3291
    /*      Do we have a reprojection transformer?                          */
3292
    /* -------------------------------------------------------------------- */
3293
0
    if (psInfo->pReprojectArg != nullptr)
3294
0
    {
3295
3296
0
        CPLXMLNode *psTransformerContainer =
3297
0
            CPLCreateXMLNode(psTree, CXT_Element, "ReprojectTransformer");
3298
3299
0
        CPLXMLNode *psTransformer =
3300
0
            GDALSerializeTransformer(psInfo->pReproject, psInfo->pReprojectArg);
3301
0
        if (psTransformer != nullptr)
3302
0
            CPLAddXMLChild(psTransformerContainer, psTransformer);
3303
0
    }
3304
3305
0
    return psTree;
3306
0
}
3307
3308
/************************************************************************/
3309
/*                    GDALDeserializeGeoTransform()                     */
3310
/************************************************************************/
3311
3312
static void GDALDeserializeGeoTransform(const char *pszGT,
3313
                                        double adfGeoTransform[6])
3314
0
{
3315
0
    CPLsscanf(pszGT, "%lf,%lf,%lf,%lf,%lf,%lf", adfGeoTransform + 0,
3316
0
              adfGeoTransform + 1, adfGeoTransform + 2, adfGeoTransform + 3,
3317
0
              adfGeoTransform + 4, adfGeoTransform + 5);
3318
0
}
3319
3320
/************************************************************************/
3321
/*                GDALDeserializeGenImgProjTransformer()                */
3322
/************************************************************************/
3323
3324
void *GDALDeserializeGenImgProjTransformer(CPLXMLNode *psTree)
3325
3326
0
{
3327
    /* -------------------------------------------------------------------- */
3328
    /*      Initialize the transform info.                                  */
3329
    /* -------------------------------------------------------------------- */
3330
0
    GDALGenImgProjTransformInfo *psInfo =
3331
0
        GDALCreateGenImgProjTransformerInternal();
3332
3333
0
    const auto DeserializePart =
3334
0
        [psTree](const char *pszPrefix, GDALGenImgProjTransformPart &part)
3335
0
    {
3336
        /* ----------------------------------------------------------------- */
3337
        /*      Geotransform                                                 */
3338
        /* ----------------------------------------------------------------- */
3339
0
        if (const auto psGTNode =
3340
0
                CPLGetXMLNode(psTree, CPLSPrintf("%sGeoTransform", pszPrefix)))
3341
0
        {
3342
0
            GDALDeserializeGeoTransform(CPLGetXMLValue(psGTNode, "", ""),
3343
0
                                        part.adfGeoTransform);
3344
3345
0
            if (const auto psInvGTNode = CPLGetXMLNode(
3346
0
                    psTree, CPLSPrintf("%sInvGeoTransform", pszPrefix)))
3347
0
            {
3348
0
                GDALDeserializeGeoTransform(CPLGetXMLValue(psInvGTNode, "", ""),
3349
0
                                            part.adfInvGeoTransform);
3350
0
            }
3351
0
            else
3352
0
            {
3353
0
                if (!GDALInvGeoTransform(part.adfGeoTransform,
3354
0
                                         part.adfInvGeoTransform))
3355
0
                {
3356
0
                    CPLError(CE_Failure, CPLE_AppDefined,
3357
0
                             "Cannot invert geotransform");
3358
0
                }
3359
0
            }
3360
0
        }
3361
3362
        /* ---------------------------------------------------------------- */
3363
        /*      Transform                                                   */
3364
        /* ---------------------------------------------------------------- */
3365
0
        else
3366
0
        {
3367
0
            for (CPLXMLNode *psIter = psTree->psChild; psIter != nullptr;
3368
0
                 psIter = psIter->psNext)
3369
0
            {
3370
0
                if (psIter->eType == CXT_Element &&
3371
0
                    STARTS_WITH_CI(psIter->pszValue, pszPrefix))
3372
0
                {
3373
0
                    GDALDeserializeTransformer(psIter->psChild,
3374
0
                                               &part.pTransformer,
3375
0
                                               &part.pTransformArg);
3376
0
                    break;
3377
0
                }
3378
0
            }
3379
0
        }
3380
0
    };
3381
3382
0
    DeserializePart("Src", psInfo->sSrcParams);
3383
0
    DeserializePart("Dst", psInfo->sDstParams);
3384
3385
    /* -------------------------------------------------------------------- */
3386
    /*      Reproject transformer                                           */
3387
    /* -------------------------------------------------------------------- */
3388
0
    CPLXMLNode *psSubtree = CPLGetXMLNode(psTree, "ReprojectTransformer");
3389
0
    if (psSubtree != nullptr && psSubtree->psChild != nullptr)
3390
0
    {
3391
0
        GDALDeserializeTransformer(psSubtree->psChild, &psInfo->pReproject,
3392
0
                                   &psInfo->pReprojectArg);
3393
0
    }
3394
3395
0
    return psInfo;
3396
0
}
3397
3398
/************************************************************************/
3399
/*                 GDALCreateReprojectionTransformer()                  */
3400
/************************************************************************/
3401
3402
/**
3403
 * Create reprojection transformer.
3404
 *
3405
 * Creates a callback data structure suitable for use with
3406
 * GDALReprojectionTransformation() to represent a transformation from
3407
 * one geographic or projected coordinate system to another.  On input
3408
 * the coordinate systems are described in OpenGIS WKT format.
3409
 *
3410
 * Internally the OGRCoordinateTransformation object is used to implement
3411
 * the reprojection.
3412
 *
3413
 * @param pszSrcWKT the coordinate system for the source coordinate system.
3414
 * @param pszDstWKT the coordinate system for the destination coordinate
3415
 * system.
3416
 *
3417
 * @return Handle for use with GDALReprojectionTransform(), or NULL if the
3418
 * system fails to initialize the reprojection.
3419
 **/
3420
3421
void *GDALCreateReprojectionTransformer(const char *pszSrcWKT,
3422
                                        const char *pszDstWKT)
3423
3424
0
{
3425
    /* -------------------------------------------------------------------- */
3426
    /*      Ingest the SRS definitions.                                     */
3427
    /* -------------------------------------------------------------------- */
3428
0
    OGRSpatialReference oSrcSRS;
3429
0
    oSrcSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
3430
0
    if (oSrcSRS.importFromWkt(pszSrcWKT) != OGRERR_NONE)
3431
0
    {
3432
0
        CPLError(CE_Failure, CPLE_AppDefined,
3433
0
                 "Failed to import coordinate system `%s'.", pszSrcWKT);
3434
0
        return nullptr;
3435
0
    }
3436
3437
0
    OGRSpatialReference oDstSRS;
3438
0
    oDstSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
3439
0
    if (oDstSRS.importFromWkt(pszDstWKT) != OGRERR_NONE)
3440
0
    {
3441
0
        CPLError(CE_Failure, CPLE_AppDefined,
3442
0
                 "Failed to import coordinate system `%s'.", pszSrcWKT);
3443
0
        return nullptr;
3444
0
    }
3445
3446
0
    return GDALCreateReprojectionTransformerEx(
3447
0
        OGRSpatialReference::ToHandle(&oSrcSRS),
3448
0
        OGRSpatialReference::ToHandle(&oDstSRS), nullptr);
3449
0
}
3450
3451
/************************************************************************/
3452
/*                 GDALCreateReprojectionTransformerEx()                */
3453
/************************************************************************/
3454
3455
/**
3456
 * Create reprojection transformer.
3457
 *
3458
 * Creates a callback data structure suitable for use with
3459
 * GDALReprojectionTransformation() to represent a transformation from
3460
 * one geographic or projected coordinate system to another.
3461
 *
3462
 * Internally the OGRCoordinateTransformation object is used to implement
3463
 * the reprojection.
3464
 *
3465
 * @param hSrcSRS the coordinate system for the source coordinate system.
3466
 * @param hDstSRS the coordinate system for the destination coordinate
3467
 * system.
3468
 * @param papszOptions NULL-terminated list of options, or NULL. Currently
3469
 * supported options are:
3470
 * <ul>
3471
 * <li>AREA_OF_INTEREST=west_long,south_lat,east_long,north_lat: Values in
3472
 * degrees. longitudes in [-180,180], latitudes in [-90,90].</li>
3473
 * <li>COORDINATE_OPERATION=string: PROJ or WKT string representing a
3474
 * coordinate operation, overriding the default computed transformation.</li>
3475
 * <li>COORDINATE_EPOCH=decimal_year: Coordinate epoch, expressed as a
3476
 * decimal year. Useful for time-dependent coordinate operations.</li>
3477
 * <li> SRC_COORDINATE_EPOCH: (GDAL &gt;= 3.4) Coordinate epoch of source CRS,
3478
 * expressed as a decimal year. Useful for time-dependent coordinate
3479
 *operations.</li>
3480
 * <li> DST_COORDINATE_EPOCH: (GDAL &gt;= 3.4) Coordinate epoch
3481
 *of target CRS, expressed as a decimal year. Useful for time-dependent
3482
 *coordinate operations.</li>
3483
 * <li> ALLOW_BALLPARK=YES/NO: (GDAL &gt;= 3.11) Whether ballpark coordinate
3484
 * operations are allowed. Defaults to YES.</li>
3485
 * <li> ONLY_BEST=YES/NO/AUTO: (GDAL &gt;= 3.11) By default (at least in the
3486
 * PROJ 9.x series), PROJ may use coordinate
3487
 * operations that are not the "best" if resources (typically grids) needed
3488
 * to use them are missing. It will then fallback to other coordinate operations
3489
 * that have a lesser accuracy, for example using Helmert transformations,
3490
 * or in the absence of such operations, to ones with potential very rought
3491
 * accuracy, using "ballpark" transformations
3492
 * (see https://proj.org/glossary.html).
3493
 * When calling this method with YES, PROJ will only consider the
3494
 * "best" operation, and error out (at Transform() time) if they cannot be
3495
 * used.
3496
 * This method may be used together with ALLOW_BALLPARK=NO to
3497
 * only allow best operations that have a known accuracy.
3498
 * Note that this method has no effect on PROJ versions before 9.2.
3499
 * The default value for this option can be also set with the
3500
 * PROJ_ONLY_BEST_DEFAULT environment variable, or with the "only_best_default"
3501
 * setting of proj.ini. Calling SetOnlyBest() overrides such default value.</li>
3502
 * </ul>
3503
 *
3504
 * @return Handle for use with GDALReprojectionTransform(), or NULL if the
3505
 * system fails to initialize the reprojection.
3506
 *
3507
 * @since GDAL 3.0
3508
 **/
3509
3510
void *GDALCreateReprojectionTransformerEx(OGRSpatialReferenceH hSrcSRS,
3511
                                          OGRSpatialReferenceH hDstSRS,
3512
                                          const char *const *papszOptions)
3513
0
{
3514
0
    OGRSpatialReference *poSrcSRS = OGRSpatialReference::FromHandle(hSrcSRS);
3515
0
    OGRSpatialReference *poDstSRS = OGRSpatialReference::FromHandle(hDstSRS);
3516
3517
    /* -------------------------------------------------------------------- */
3518
    /*      Build the forward coordinate transformation.                    */
3519
    /* -------------------------------------------------------------------- */
3520
0
    double dfWestLongitudeDeg = 0.0;
3521
0
    double dfSouthLatitudeDeg = 0.0;
3522
0
    double dfEastLongitudeDeg = 0.0;
3523
0
    double dfNorthLatitudeDeg = 0.0;
3524
0
    const char *pszBBOX = CSLFetchNameValue(papszOptions, "AREA_OF_INTEREST");
3525
0
    if (pszBBOX)
3526
0
    {
3527
0
        char **papszTokens = CSLTokenizeString2(pszBBOX, ",", 0);
3528
0
        if (CSLCount(papszTokens) == 4)
3529
0
        {
3530
0
            dfWestLongitudeDeg = CPLAtof(papszTokens[0]);
3531
0
            dfSouthLatitudeDeg = CPLAtof(papszTokens[1]);
3532
0
            dfEastLongitudeDeg = CPLAtof(papszTokens[2]);
3533
0
            dfNorthLatitudeDeg = CPLAtof(papszTokens[3]);
3534
0
        }
3535
0
        CSLDestroy(papszTokens);
3536
0
    }
3537
0
    const char *pszCO = CSLFetchNameValue(papszOptions, "COORDINATE_OPERATION");
3538
3539
0
    OGRCoordinateTransformationOptions optionsFwd;
3540
0
    if (!(dfWestLongitudeDeg == 0.0 && dfSouthLatitudeDeg == 0.0 &&
3541
0
          dfEastLongitudeDeg == 0.0 && dfNorthLatitudeDeg == 0.0))
3542
0
    {
3543
0
        optionsFwd.SetAreaOfInterest(dfWestLongitudeDeg, dfSouthLatitudeDeg,
3544
0
                                     dfEastLongitudeDeg, dfNorthLatitudeDeg);
3545
0
    }
3546
0
    if (pszCO)
3547
0
    {
3548
0
        optionsFwd.SetCoordinateOperation(pszCO, false);
3549
0
    }
3550
3551
0
    const char *pszCENTER_LONG = CSLFetchNameValue(papszOptions, "CENTER_LONG");
3552
0
    if (pszCENTER_LONG)
3553
0
    {
3554
0
        optionsFwd.SetSourceCenterLong(CPLAtof(pszCENTER_LONG));
3555
0
    }
3556
3557
0
    optionsFwd.SetBallparkAllowed(CPLTestBool(
3558
0
        CSLFetchNameValueDef(papszOptions, "ALLOW_BALLPARK", "YES")));
3559
3560
0
    const char *pszOnlyBest =
3561
0
        CSLFetchNameValueDef(papszOptions, "ONLY_BEST", "AUTO");
3562
0
    if (!EQUAL(pszOnlyBest, "AUTO"))
3563
0
    {
3564
0
        optionsFwd.SetOnlyBest(CPLTestBool(pszOnlyBest));
3565
0
    }
3566
3567
0
    OGRCoordinateTransformation *poForwardTransform =
3568
0
        OGRCreateCoordinateTransformation(poSrcSRS, poDstSRS, optionsFwd);
3569
3570
0
    if (poForwardTransform == nullptr)
3571
        // OGRCreateCoordinateTransformation() will report errors on its own.
3572
0
        return nullptr;
3573
3574
0
    poForwardTransform->SetEmitErrors(false);
3575
3576
    /* -------------------------------------------------------------------- */
3577
    /*      Create a structure to hold the transform info, and also         */
3578
    /*      build reverse transform.  We assume that if the forward         */
3579
    /*      transform can be created, then so can the reverse one.          */
3580
    /* -------------------------------------------------------------------- */
3581
0
    GDALReprojectionTransformInfo *psInfo = new GDALReprojectionTransformInfo();
3582
3583
0
    psInfo->papszOptions = CSLDuplicate(papszOptions);
3584
0
    psInfo->poForwardTransform = poForwardTransform;
3585
0
    psInfo->dfTime = CPLAtof(CSLFetchNameValueDef(
3586
0
        papszOptions, "COORDINATE_EPOCH",
3587
0
        CSLFetchNameValueDef(
3588
0
            papszOptions, "DST_COORDINATE_EPOCH",
3589
0
            CSLFetchNameValueDef(papszOptions, "SRC_COORDINATE_EPOCH", "0"))));
3590
0
    psInfo->poReverseTransform = poForwardTransform->GetInverse();
3591
3592
0
    if (psInfo->poReverseTransform)
3593
0
        psInfo->poReverseTransform->SetEmitErrors(false);
3594
3595
0
    memcpy(psInfo->sTI.abySignature, GDAL_GTI2_SIGNATURE,
3596
0
           strlen(GDAL_GTI2_SIGNATURE));
3597
0
    psInfo->sTI.pszClassName = GDAL_REPROJECTION_TRANSFORMER_CLASS_NAME;
3598
0
    psInfo->sTI.pfnTransform = GDALReprojectionTransform;
3599
0
    psInfo->sTI.pfnCleanup = GDALDestroyReprojectionTransformer;
3600
0
    psInfo->sTI.pfnSerialize = GDALSerializeReprojectionTransformer;
3601
3602
0
    return psInfo;
3603
0
}
3604
3605
/************************************************************************/
3606
/*                 GDALDestroyReprojectionTransformer()                 */
3607
/************************************************************************/
3608
3609
/**
3610
 * Destroy reprojection transformation.
3611
 *
3612
 * @param pTransformArg the transformation handle returned by
3613
 * GDALCreateReprojectionTransformer().
3614
 */
3615
3616
void GDALDestroyReprojectionTransformer(void *pTransformArg)
3617
3618
0
{
3619
0
    if (pTransformArg == nullptr)
3620
0
        return;
3621
3622
0
    GDALReprojectionTransformInfo *psInfo =
3623
0
        static_cast<GDALReprojectionTransformInfo *>(pTransformArg);
3624
3625
0
    if (psInfo->poForwardTransform)
3626
0
        OGRCoordinateTransformation::DestroyCT(psInfo->poForwardTransform);
3627
3628
0
    if (psInfo->poReverseTransform)
3629
0
        OGRCoordinateTransformation::DestroyCT(psInfo->poReverseTransform);
3630
3631
0
    CSLDestroy(psInfo->papszOptions);
3632
3633
0
    delete psInfo;
3634
0
}
3635
3636
/************************************************************************/
3637
/*                     GDALReprojectionTransform()                      */
3638
/************************************************************************/
3639
3640
/**
3641
 * Perform reprojection transformation.
3642
 *
3643
 * Actually performs the reprojection transformation described in
3644
 * GDALCreateReprojectionTransformer().  This function matches the
3645
 * GDALTransformerFunc() signature.  Details of the arguments are described
3646
 * there.
3647
 */
3648
3649
int GDALReprojectionTransform(void *pTransformArg, int bDstToSrc,
3650
                              int nPointCount, double *padfX, double *padfY,
3651
                              double *padfZ, int *panSuccess)
3652
3653
0
{
3654
0
    GDALReprojectionTransformInfo *psInfo =
3655
0
        static_cast<GDALReprojectionTransformInfo *>(pTransformArg);
3656
0
    int bSuccess;
3657
3658
0
    std::vector<double> adfTime;
3659
0
    double *padfT = nullptr;
3660
0
    if (psInfo->dfTime != 0.0 && nPointCount > 0)
3661
0
    {
3662
0
        adfTime.resize(nPointCount, psInfo->dfTime);
3663
0
        padfT = &adfTime[0];
3664
0
    }
3665
3666
0
    if (bDstToSrc)
3667
0
    {
3668
0
        if (psInfo->poReverseTransform == nullptr)
3669
0
        {
3670
0
            CPLError(
3671
0
                CE_Failure, CPLE_AppDefined,
3672
0
                "Inverse coordinate transformation cannot be instantiated");
3673
0
            if (panSuccess)
3674
0
            {
3675
0
                for (int i = 0; i < nPointCount; i++)
3676
0
                    panSuccess[i] = FALSE;
3677
0
            }
3678
0
            bSuccess = false;
3679
0
        }
3680
0
        else
3681
0
        {
3682
0
            bSuccess = psInfo->poReverseTransform->Transform(
3683
0
                nPointCount, padfX, padfY, padfZ, padfT, panSuccess);
3684
0
        }
3685
0
    }
3686
0
    else
3687
0
        bSuccess = psInfo->poForwardTransform->Transform(
3688
0
            nPointCount, padfX, padfY, padfZ, padfT, panSuccess);
3689
3690
0
    return bSuccess;
3691
0
}
3692
3693
/************************************************************************/
3694
/*                GDALSerializeReprojectionTransformer()                */
3695
/************************************************************************/
3696
3697
static CPLXMLNode *GDALSerializeReprojectionTransformer(void *pTransformArg)
3698
3699
0
{
3700
0
    CPLXMLNode *psTree;
3701
0
    GDALReprojectionTransformInfo *psInfo =
3702
0
        static_cast<GDALReprojectionTransformInfo *>(pTransformArg);
3703
3704
0
    psTree = CPLCreateXMLNode(nullptr, CXT_Element, "ReprojectionTransformer");
3705
3706
    /* -------------------------------------------------------------------- */
3707
    /*      Handle SourceCS.                                                */
3708
    /* -------------------------------------------------------------------- */
3709
0
    const auto ExportToWkt = [](const OGRSpatialReference *poSRS)
3710
0
    {
3711
        // Try first in WKT1 for backward compat
3712
0
        {
3713
0
            char *pszWKT = nullptr;
3714
0
            const char *const apszOptions[] = {"FORMAT=WKT1", nullptr};
3715
0
            CPLErrorHandlerPusher oHandler(CPLQuietErrorHandler);
3716
0
            CPLErrorStateBackuper oBackuper;
3717
0
            if (poSRS->exportToWkt(&pszWKT, apszOptions) == OGRERR_NONE)
3718
0
            {
3719
0
                std::string osRet(pszWKT);
3720
0
                CPLFree(pszWKT);
3721
0
                return osRet;
3722
0
            }
3723
0
            CPLFree(pszWKT);
3724
0
        }
3725
3726
0
        char *pszWKT = nullptr;
3727
0
        const char *const apszOptions[] = {"FORMAT=WKT2_2019", nullptr};
3728
0
        if (poSRS->exportToWkt(&pszWKT, apszOptions) == OGRERR_NONE)
3729
0
        {
3730
0
            std::string osRet(pszWKT);
3731
0
            CPLFree(pszWKT);
3732
0
            return osRet;
3733
0
        }
3734
0
        CPLFree(pszWKT);
3735
0
        return std::string();
3736
0
    };
3737
3738
0
    auto poSRS = psInfo->poForwardTransform->GetSourceCS();
3739
0
    if (poSRS)
3740
0
    {
3741
0
        const auto osWKT = ExportToWkt(poSRS);
3742
0
        CPLCreateXMLElementAndValue(psTree, "SourceSRS", osWKT.c_str());
3743
0
    }
3744
3745
    /* -------------------------------------------------------------------- */
3746
    /*      Handle DestinationCS.                                           */
3747
    /* -------------------------------------------------------------------- */
3748
0
    poSRS = psInfo->poForwardTransform->GetTargetCS();
3749
0
    if (poSRS)
3750
0
    {
3751
0
        const auto osWKT = ExportToWkt(poSRS);
3752
0
        CPLCreateXMLElementAndValue(psTree, "TargetSRS", osWKT.c_str());
3753
0
    }
3754
3755
    /* -------------------------------------------------------------------- */
3756
    /*      Serialize options.                                              */
3757
    /* -------------------------------------------------------------------- */
3758
0
    if (psInfo->papszOptions)
3759
0
    {
3760
0
        CPLXMLNode *psOptions =
3761
0
            CPLCreateXMLNode(psTree, CXT_Element, "Options");
3762
0
        for (auto iter = psInfo->papszOptions; *iter != nullptr; ++iter)
3763
0
        {
3764
0
            char *pszKey = nullptr;
3765
0
            const char *pszValue = CPLParseNameValue(*iter, &pszKey);
3766
0
            if (pszKey && pszValue)
3767
0
            {
3768
0
                auto elt =
3769
0
                    CPLCreateXMLElementAndValue(psOptions, "Option", pszValue);
3770
0
                CPLAddXMLAttributeAndValue(elt, "key", pszKey);
3771
0
            }
3772
0
            CPLFree(pszKey);
3773
0
        }
3774
0
    }
3775
3776
0
    return psTree;
3777
0
}
3778
3779
/************************************************************************/
3780
/*               GDALDeserializeReprojectionTransformer()               */
3781
/************************************************************************/
3782
3783
static void *GDALDeserializeReprojectionTransformer(CPLXMLNode *psTree)
3784
3785
0
{
3786
0
    const char *pszSourceSRS = CPLGetXMLValue(psTree, "SourceSRS", nullptr);
3787
0
    const char *pszTargetSRS = CPLGetXMLValue(psTree, "TargetSRS", nullptr);
3788
0
    char *pszSourceWKT = nullptr, *pszTargetWKT = nullptr;
3789
0
    void *pResult = nullptr;
3790
3791
0
    OGRSpatialReference oSrcSRS;
3792
0
    OGRSpatialReference oDstSRS;
3793
3794
0
    oSrcSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
3795
0
    oDstSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
3796
0
    if (pszSourceSRS != nullptr)
3797
0
    {
3798
0
        oSrcSRS.SetFromUserInput(pszSourceSRS);
3799
0
    }
3800
3801
0
    if (pszTargetSRS != nullptr)
3802
0
    {
3803
0
        oDstSRS.SetFromUserInput(pszTargetSRS);
3804
0
    }
3805
3806
0
    CPLStringList aosList;
3807
0
    const CPLXMLNode *psOptions = CPLGetXMLNode(psTree, "Options");
3808
0
    if (psOptions)
3809
0
    {
3810
0
        for (auto iter = psOptions->psChild; iter; iter = iter->psNext)
3811
0
        {
3812
0
            if (iter->eType == CXT_Element &&
3813
0
                strcmp(iter->pszValue, "Option") == 0)
3814
0
            {
3815
0
                const char *pszKey = CPLGetXMLValue(iter, "key", nullptr);
3816
0
                const char *pszValue = CPLGetXMLValue(iter, nullptr, nullptr);
3817
0
                if (pszKey && pszValue)
3818
0
                {
3819
0
                    aosList.SetNameValue(pszKey, pszValue);
3820
0
                }
3821
0
            }
3822
0
        }
3823
0
    }
3824
3825
0
    pResult = GDALCreateReprojectionTransformerEx(
3826
0
        !oSrcSRS.IsEmpty() ? OGRSpatialReference::ToHandle(&oSrcSRS) : nullptr,
3827
0
        !oDstSRS.IsEmpty() ? OGRSpatialReference::ToHandle(&oDstSRS) : nullptr,
3828
0
        aosList.List());
3829
3830
0
    CPLFree(pszSourceWKT);
3831
0
    CPLFree(pszTargetWKT);
3832
3833
0
    return pResult;
3834
0
}
3835
3836
/************************************************************************/
3837
/* ==================================================================== */
3838
/*      Approximate transformer.                                        */
3839
/* ==================================================================== */
3840
/************************************************************************/
3841
3842
/************************************************************************/
3843
/*                  GDALCreateSimilarApproxTransformer()                */
3844
/************************************************************************/
3845
3846
static void *GDALCreateSimilarApproxTransformer(void *hTransformArg,
3847
                                                double dfSrcRatioX,
3848
                                                double dfSrcRatioY)
3849
0
{
3850
0
    VALIDATE_POINTER1(hTransformArg, "GDALCreateSimilarApproxTransformer",
3851
0
                      nullptr);
3852
3853
0
    GDALApproxTransformInfo *psInfo =
3854
0
        static_cast<GDALApproxTransformInfo *>(hTransformArg);
3855
3856
0
    void *pBaseCBData = GDALCreateSimilarTransformer(psInfo->pBaseCBData,
3857
0
                                                     dfSrcRatioX, dfSrcRatioY);
3858
0
    if (pBaseCBData == nullptr)
3859
0
    {
3860
0
        return nullptr;
3861
0
    }
3862
3863
0
    GDALApproxTransformInfo *psClonedInfo =
3864
0
        static_cast<GDALApproxTransformInfo *>(GDALCreateApproxTransformer2(
3865
0
            psInfo->pfnBaseTransformer, pBaseCBData, psInfo->dfMaxErrorForward,
3866
0
            psInfo->dfMaxErrorReverse));
3867
0
    psClonedInfo->bOwnSubtransformer = TRUE;
3868
3869
0
    return psClonedInfo;
3870
0
}
3871
3872
/************************************************************************/
3873
/*                   GDALSerializeApproxTransformer()                   */
3874
/************************************************************************/
3875
3876
static CPLXMLNode *GDALSerializeApproxTransformer(void *pTransformArg)
3877
3878
0
{
3879
0
    CPLXMLNode *psTree;
3880
0
    GDALApproxTransformInfo *psInfo =
3881
0
        static_cast<GDALApproxTransformInfo *>(pTransformArg);
3882
3883
0
    psTree = CPLCreateXMLNode(nullptr, CXT_Element, "ApproxTransformer");
3884
3885
    /* -------------------------------------------------------------------- */
3886
    /*      Attach max error.                                               */
3887
    /* -------------------------------------------------------------------- */
3888
0
    if (psInfo->dfMaxErrorForward == psInfo->dfMaxErrorReverse)
3889
0
    {
3890
0
        CPLCreateXMLElementAndValue(
3891
0
            psTree, "MaxError",
3892
0
            CPLString().Printf("%g", psInfo->dfMaxErrorForward));
3893
0
    }
3894
0
    else
3895
0
    {
3896
0
        CPLCreateXMLElementAndValue(
3897
0
            psTree, "MaxErrorForward",
3898
0
            CPLString().Printf("%g", psInfo->dfMaxErrorForward));
3899
0
        CPLCreateXMLElementAndValue(
3900
0
            psTree, "MaxErrorReverse",
3901
0
            CPLString().Printf("%g", psInfo->dfMaxErrorReverse));
3902
0
    }
3903
3904
    /* -------------------------------------------------------------------- */
3905
    /*      Capture underlying transformer.                                 */
3906
    /* -------------------------------------------------------------------- */
3907
0
    CPLXMLNode *psTransformerContainer =
3908
0
        CPLCreateXMLNode(psTree, CXT_Element, "BaseTransformer");
3909
3910
0
    CPLXMLNode *psTransformer = GDALSerializeTransformer(
3911
0
        psInfo->pfnBaseTransformer, psInfo->pBaseCBData);
3912
0
    if (psTransformer != nullptr)
3913
0
        CPLAddXMLChild(psTransformerContainer, psTransformer);
3914
3915
0
    return psTree;
3916
0
}
3917
3918
/************************************************************************/
3919
/*                    GDALCreateApproxTransformer()                     */
3920
/************************************************************************/
3921
3922
/**
3923
 * Create an approximating transformer.
3924
 *
3925
 * This function creates a context for an approximated transformer.  Basically
3926
 * a high precision transformer is supplied as input and internally linear
3927
 * approximations are computed to generate results to within a defined
3928
 * precision.
3929
 *
3930
 * The approximation is actually done at the point where GDALApproxTransform()
3931
 * calls are made, and depend on the assumption that they are roughly linear.
3932
 * The first and last point passed in must be the extreme values and the
3933
 * intermediate values should describe a curve between the end points.  The
3934
 * approximator transforms and centers using the approximate transformer, and
3935
 * then compares the true middle transformed value to a linear approximation
3936
 * based on the end points.  If the error is within the supplied threshold then
3937
 * the end points are used to linearly approximate all the values otherwise the
3938
 * input points are split into two smaller sets, and the function is recursively
3939
 * called until a sufficiently small set of points is found that the linear
3940
 * approximation is OK, or that all the points are exactly computed.
3941
 *
3942
 * This function is very suitable for approximating transformation results
3943
 * from output pixel/line space to input coordinates for warpers that operate
3944
 * on one input scanline at a time.  Care should be taken using it in other
3945
 * circumstances as little internal validation is done in order to keep things
3946
 * fast.
3947
 *
3948
 * @param pfnBaseTransformer the high precision transformer which should be
3949
 * approximated.
3950
 * @param pBaseTransformArg the callback argument for the high precision
3951
 * transformer.
3952
 * @param dfMaxError the maximum cartesian error in the "output" space that
3953
 * is to be accepted in the linear approximation, evaluated as a Manhattan
3954
 * distance.
3955
 *
3956
 * @return callback pointer suitable for use with GDALApproxTransform().  It
3957
 * should be deallocated with GDALDestroyApproxTransformer().
3958
 */
3959
3960
void *GDALCreateApproxTransformer(GDALTransformerFunc pfnBaseTransformer,
3961
                                  void *pBaseTransformArg, double dfMaxError)
3962
3963
0
{
3964
0
    return GDALCreateApproxTransformer2(pfnBaseTransformer, pBaseTransformArg,
3965
0
                                        dfMaxError, dfMaxError);
3966
0
}
3967
3968
static void *
3969
GDALCreateApproxTransformer2(GDALTransformerFunc pfnBaseTransformer,
3970
                             void *pBaseTransformArg, double dfMaxErrorForward,
3971
                             double dfMaxErrorReverse)
3972
3973
0
{
3974
0
    GDALApproxTransformInfo *psATInfo = new GDALApproxTransformInfo;
3975
0
    psATInfo->pfnBaseTransformer = pfnBaseTransformer;
3976
0
    psATInfo->pBaseCBData = pBaseTransformArg;
3977
0
    psATInfo->dfMaxErrorForward = dfMaxErrorForward;
3978
0
    psATInfo->dfMaxErrorReverse = dfMaxErrorReverse;
3979
0
    psATInfo->bOwnSubtransformer = FALSE;
3980
3981
0
    memcpy(psATInfo->sTI.abySignature, GDAL_GTI2_SIGNATURE,
3982
0
           strlen(GDAL_GTI2_SIGNATURE));
3983
0
    psATInfo->sTI.pszClassName = GDAL_APPROX_TRANSFORMER_CLASS_NAME;
3984
0
    psATInfo->sTI.pfnTransform = GDALApproxTransform;
3985
0
    psATInfo->sTI.pfnCleanup = GDALDestroyApproxTransformer;
3986
0
    psATInfo->sTI.pfnSerialize = GDALSerializeApproxTransformer;
3987
0
    psATInfo->sTI.pfnCreateSimilar = GDALCreateSimilarApproxTransformer;
3988
3989
0
    return psATInfo;
3990
0
}
3991
3992
/************************************************************************/
3993
/*              GDALApproxTransformerOwnsSubtransformer()               */
3994
/************************************************************************/
3995
3996
/** Set bOwnSubtransformer flag */
3997
void GDALApproxTransformerOwnsSubtransformer(void *pCBData, int bOwnFlag)
3998
3999
0
{
4000
0
    GDALApproxTransformInfo *psATInfo =
4001
0
        static_cast<GDALApproxTransformInfo *>(pCBData);
4002
4003
0
    psATInfo->bOwnSubtransformer = bOwnFlag;
4004
0
}
4005
4006
/************************************************************************/
4007
/*                    GDALDestroyApproxTransformer()                    */
4008
/************************************************************************/
4009
4010
/**
4011
 * Cleanup approximate transformer.
4012
 *
4013
 * Deallocates the resources allocated by GDALCreateApproxTransformer().
4014
 *
4015
 * @param pCBData callback data originally returned by
4016
 * GDALCreateApproxTransformer().
4017
 */
4018
4019
void GDALDestroyApproxTransformer(void *pCBData)
4020
4021
0
{
4022
0
    if (pCBData == nullptr)
4023
0
        return;
4024
4025
0
    GDALApproxTransformInfo *psATInfo =
4026
0
        static_cast<GDALApproxTransformInfo *>(pCBData);
4027
4028
0
    if (psATInfo->bOwnSubtransformer)
4029
0
        GDALDestroyTransformer(psATInfo->pBaseCBData);
4030
4031
0
    delete psATInfo;
4032
0
}
4033
4034
/************************************************************************/
4035
/*                  GDALRefreshApproxTransformer()                      */
4036
/************************************************************************/
4037
4038
void GDALRefreshApproxTransformer(void *hTransformArg)
4039
0
{
4040
0
    GDALApproxTransformInfo *psInfo =
4041
0
        static_cast<GDALApproxTransformInfo *>(hTransformArg);
4042
4043
0
    if (GDALIsTransformer(psInfo->pBaseCBData,
4044
0
                          GDAL_GEN_IMG_TRANSFORMER_CLASS_NAME))
4045
0
    {
4046
0
        GDALRefreshGenImgProjTransformer(psInfo->pBaseCBData);
4047
0
    }
4048
0
}
4049
4050
/************************************************************************/
4051
/*                      GDALApproxTransformInternal()                   */
4052
/************************************************************************/
4053
4054
static int GDALApproxTransformInternal(void *pCBData, int bDstToSrc,
4055
                                       int nPoints, double *x, double *y,
4056
                                       double *z, int *panSuccess,
4057
                                       // SME = Start, Middle, End.
4058
                                       const double xSMETransformed[3],
4059
                                       const double ySMETransformed[3],
4060
                                       const double zSMETransformed[3])
4061
0
{
4062
0
    GDALApproxTransformInfo *psATInfo =
4063
0
        static_cast<GDALApproxTransformInfo *>(pCBData);
4064
0
    const int nMiddle = (nPoints - 1) / 2;
4065
4066
#ifdef notdef_sanify_check
4067
    {
4068
        double x2[3] = {x[0], x[nMiddle], x[nPoints - 1]};
4069
        double y2[3] = {y[0], y[nMiddle], y[nPoints - 1]};
4070
        double z2[3] = {z[0], z[nMiddle], z[nPoints - 1]};
4071
        int anSuccess2[3] = {};
4072
4073
        const int bSuccess = psATInfo->pfnBaseTransformer(
4074
            psATInfo->pBaseCBData, bDstToSrc, 3, x2, y2, z2, anSuccess2);
4075
        CPLAssert(bSuccess);
4076
        CPLAssert(anSuccess2[0]);
4077
        CPLAssert(anSuccess2[1]);
4078
        CPLAssert(anSuccess2[2]);
4079
        CPLAssert(x2[0] == xSMETransformed[0]);
4080
        CPLAssert(y2[0] == ySMETransformed[0]);
4081
        CPLAssert(z2[0] == zSMETransformed[0]);
4082
        CPLAssert(x2[1] == xSMETransformed[1]);
4083
        CPLAssert(y2[1] == ySMETransformed[1]);
4084
        CPLAssert(z2[1] == zSMETransformed[1]);
4085
        CPLAssert(x2[2] == xSMETransformed[2]);
4086
        CPLAssert(y2[2] == ySMETransformed[2]);
4087
        CPLAssert(z2[2] == zSMETransformed[2]);
4088
    }
4089
#endif
4090
4091
#ifdef DEBUG_APPROX_TRANSFORMER
4092
    fprintf(stderr, "start (%.3f,%.3f) -> (%.3f,%.3f)\n", /*ok*/
4093
            x[0], y[0], xSMETransformed[0], ySMETransformed[0]);
4094
    fprintf(stderr, "middle (%.3f,%.3f) -> (%.3f,%.3f)\n", /*ok*/
4095
            x[nMiddle], y[nMiddle], xSMETransformed[1], ySMETransformed[1]);
4096
    fprintf(stderr, "end (%.3f,%.3f) -> (%.3f,%.3f)\n", /*ok*/
4097
            x[nPoints - 1], y[nPoints - 1], xSMETransformed[2],
4098
            ySMETransformed[2]);
4099
#endif
4100
4101
    /* -------------------------------------------------------------------- */
4102
    /*      Is the error at the middle acceptable relative to an            */
4103
    /*      interpolation of the middle position?                           */
4104
    /* -------------------------------------------------------------------- */
4105
0
    const double dfDeltaX =
4106
0
        (xSMETransformed[2] - xSMETransformed[0]) / (x[nPoints - 1] - x[0]);
4107
0
    const double dfDeltaY =
4108
0
        (ySMETransformed[2] - ySMETransformed[0]) / (x[nPoints - 1] - x[0]);
4109
0
    const double dfDeltaZ =
4110
0
        (zSMETransformed[2] - zSMETransformed[0]) / (x[nPoints - 1] - x[0]);
4111
4112
0
    const double dfError =
4113
0
        fabs((xSMETransformed[0] + dfDeltaX * (x[nMiddle] - x[0])) -
4114
0
             xSMETransformed[1]) +
4115
0
        fabs((ySMETransformed[0] + dfDeltaY * (x[nMiddle] - x[0])) -
4116
0
             ySMETransformed[1]);
4117
4118
0
    const double dfMaxError =
4119
0
        (bDstToSrc) ? psATInfo->dfMaxErrorReverse : psATInfo->dfMaxErrorForward;
4120
0
    if (dfError > dfMaxError)
4121
0
    {
4122
#if DEBUG_VERBOSE
4123
        CPLDebug("GDAL",
4124
                 "ApproxTransformer - "
4125
                 "error %g over threshold %g, subdivide %d points.",
4126
                 dfError, dfMaxError, nPoints);
4127
#endif
4128
4129
0
        double xMiddle[3] = {x[(nMiddle - 1) / 2], x[nMiddle - 1],
4130
0
                             x[nMiddle + (nPoints - nMiddle - 1) / 2]};
4131
0
        double yMiddle[3] = {y[(nMiddle - 1) / 2], y[nMiddle - 1],
4132
0
                             y[nMiddle + (nPoints - nMiddle - 1) / 2]};
4133
0
        double zMiddle[3] = {z[(nMiddle - 1) / 2], z[nMiddle - 1],
4134
0
                             z[nMiddle + (nPoints - nMiddle - 1) / 2]};
4135
4136
0
        const bool bUseBaseTransformForHalf1 =
4137
0
            nMiddle <= 5 || y[0] != y[nMiddle - 1] ||
4138
0
            y[0] != y[(nMiddle - 1) / 2] || x[0] == x[nMiddle - 1] ||
4139
0
            x[0] == x[(nMiddle - 1) / 2];
4140
0
        const bool bUseBaseTransformForHalf2 =
4141
0
            nPoints - nMiddle <= 5 || y[nMiddle] != y[nPoints - 1] ||
4142
0
            y[nMiddle] != y[nMiddle + (nPoints - nMiddle - 1) / 2] ||
4143
0
            x[nMiddle] == x[nPoints - 1] ||
4144
0
            x[nMiddle] == x[nMiddle + (nPoints - nMiddle - 1) / 2];
4145
4146
0
        int anSuccess2[3] = {};
4147
0
        int bSuccess = FALSE;
4148
0
        if (!bUseBaseTransformForHalf1 && !bUseBaseTransformForHalf2)
4149
0
            bSuccess = psATInfo->pfnBaseTransformer(
4150
0
                psATInfo->pBaseCBData, bDstToSrc, 3, xMiddle, yMiddle, zMiddle,
4151
0
                anSuccess2);
4152
0
        else if (!bUseBaseTransformForHalf1)
4153
0
        {
4154
0
            bSuccess = psATInfo->pfnBaseTransformer(
4155
0
                psATInfo->pBaseCBData, bDstToSrc, 2, xMiddle, yMiddle, zMiddle,
4156
0
                anSuccess2);
4157
0
            anSuccess2[2] = TRUE;
4158
0
        }
4159
0
        else if (!bUseBaseTransformForHalf2)
4160
0
        {
4161
0
            bSuccess = psATInfo->pfnBaseTransformer(
4162
0
                psATInfo->pBaseCBData, bDstToSrc, 1, xMiddle + 2, yMiddle + 2,
4163
0
                zMiddle + 2, anSuccess2 + 2);
4164
0
            anSuccess2[0] = TRUE;
4165
0
            anSuccess2[1] = TRUE;
4166
0
        }
4167
4168
0
        if (!bSuccess || !anSuccess2[0] || !anSuccess2[1] || !anSuccess2[2])
4169
0
        {
4170
0
            bSuccess = psATInfo->pfnBaseTransformer(
4171
0
                psATInfo->pBaseCBData, bDstToSrc, nMiddle - 1, x + 1, y + 1,
4172
0
                z + 1, panSuccess + 1);
4173
0
            bSuccess &= psATInfo->pfnBaseTransformer(
4174
0
                psATInfo->pBaseCBData, bDstToSrc, nPoints - nMiddle - 2,
4175
0
                x + nMiddle + 1, y + nMiddle + 1, z + nMiddle + 1,
4176
0
                panSuccess + nMiddle + 1);
4177
4178
0
            x[0] = xSMETransformed[0];
4179
0
            y[0] = ySMETransformed[0];
4180
0
            z[0] = zSMETransformed[0];
4181
0
            panSuccess[0] = TRUE;
4182
0
            x[nMiddle] = xSMETransformed[1];
4183
0
            y[nMiddle] = ySMETransformed[1];
4184
0
            z[nMiddle] = zSMETransformed[1];
4185
0
            panSuccess[nMiddle] = TRUE;
4186
0
            x[nPoints - 1] = xSMETransformed[2];
4187
0
            y[nPoints - 1] = ySMETransformed[2];
4188
0
            z[nPoints - 1] = zSMETransformed[2];
4189
0
            panSuccess[nPoints - 1] = TRUE;
4190
0
            return bSuccess;
4191
0
        }
4192
4193
0
        double x2[3] = {};
4194
0
        double y2[3] = {};
4195
0
        double z2[3] = {};
4196
0
        if (!bUseBaseTransformForHalf1)
4197
0
        {
4198
0
            x2[0] = xSMETransformed[0];
4199
0
            y2[0] = ySMETransformed[0];
4200
0
            z2[0] = zSMETransformed[0];
4201
0
            x2[1] = xMiddle[0];
4202
0
            y2[1] = yMiddle[0];
4203
0
            z2[1] = zMiddle[0];
4204
0
            x2[2] = xMiddle[1];
4205
0
            y2[2] = yMiddle[1];
4206
0
            z2[2] = zMiddle[1];
4207
4208
0
            bSuccess = GDALApproxTransformInternal(
4209
0
                psATInfo, bDstToSrc, nMiddle, x, y, z, panSuccess, x2, y2, z2);
4210
0
        }
4211
0
        else
4212
0
        {
4213
0
            bSuccess = psATInfo->pfnBaseTransformer(
4214
0
                psATInfo->pBaseCBData, bDstToSrc, nMiddle - 1, x + 1, y + 1,
4215
0
                z + 1, panSuccess + 1);
4216
0
            x[0] = xSMETransformed[0];
4217
0
            y[0] = ySMETransformed[0];
4218
0
            z[0] = zSMETransformed[0];
4219
0
            panSuccess[0] = TRUE;
4220
0
        }
4221
4222
0
        if (!bSuccess)
4223
0
            return FALSE;
4224
4225
0
        if (!bUseBaseTransformForHalf2)
4226
0
        {
4227
0
            x2[0] = xSMETransformed[1];
4228
0
            y2[0] = ySMETransformed[1];
4229
0
            z2[0] = zSMETransformed[1];
4230
0
            x2[1] = xMiddle[2];
4231
0
            y2[1] = yMiddle[2];
4232
0
            z2[1] = zMiddle[2];
4233
0
            x2[2] = xSMETransformed[2];
4234
0
            y2[2] = ySMETransformed[2];
4235
0
            z2[2] = zSMETransformed[2];
4236
4237
0
            bSuccess = GDALApproxTransformInternal(
4238
0
                psATInfo, bDstToSrc, nPoints - nMiddle, x + nMiddle,
4239
0
                y + nMiddle, z + nMiddle, panSuccess + nMiddle, x2, y2, z2);
4240
0
        }
4241
0
        else
4242
0
        {
4243
0
            bSuccess = psATInfo->pfnBaseTransformer(
4244
0
                psATInfo->pBaseCBData, bDstToSrc, nPoints - nMiddle - 2,
4245
0
                x + nMiddle + 1, y + nMiddle + 1, z + nMiddle + 1,
4246
0
                panSuccess + nMiddle + 1);
4247
4248
0
            x[nMiddle] = xSMETransformed[1];
4249
0
            y[nMiddle] = ySMETransformed[1];
4250
0
            z[nMiddle] = zSMETransformed[1];
4251
0
            panSuccess[nMiddle] = TRUE;
4252
0
            x[nPoints - 1] = xSMETransformed[2];
4253
0
            y[nPoints - 1] = ySMETransformed[2];
4254
0
            z[nPoints - 1] = zSMETransformed[2];
4255
0
            panSuccess[nPoints - 1] = TRUE;
4256
0
        }
4257
4258
0
        if (!bSuccess)
4259
0
            return FALSE;
4260
4261
0
        return TRUE;
4262
0
    }
4263
4264
    /* -------------------------------------------------------------------- */
4265
    /*      Error is OK since this is just used to compute output bounds    */
4266
    /*      of newly created file for gdalwarper.  So just use affine       */
4267
    /*      approximation of the reverse transform.  Eventually we          */
4268
    /*      should implement iterative searching to find a result within    */
4269
    /*      our error threshold.                                            */
4270
    /*      NOTE: the above comment is not true: gdalwarp uses approximator */
4271
    /*      also to compute the source pixel of each target pixel.          */
4272
    /* -------------------------------------------------------------------- */
4273
0
    for (int i = nPoints - 1; i >= 0; i--)
4274
0
    {
4275
#ifdef check_error
4276
        double xtemp = x[i];
4277
        double ytemp = y[i];
4278
        double ztemp = z[i];
4279
        double x_ori = xtemp;
4280
        double y_ori = ytemp;
4281
        int btemp = FALSE;
4282
        psATInfo->pfnBaseTransformer(psATInfo->pBaseCBData, bDstToSrc, 1,
4283
                                     &xtemp, &ytemp, &ztemp, &btemp);
4284
#endif
4285
0
        const double dfDist = (x[i] - x[0]);
4286
0
        x[i] = xSMETransformed[0] + dfDeltaX * dfDist;
4287
0
        y[i] = ySMETransformed[0] + dfDeltaY * dfDist;
4288
0
        z[i] = zSMETransformed[0] + dfDeltaZ * dfDist;
4289
#ifdef check_error
4290
        const double dfError2 = fabs(x[i] - xtemp) + fabs(y[i] - ytemp);
4291
        if (dfError2 > 4 /*10 * dfMaxError*/)
4292
        {
4293
            /*ok*/ printf("Error = %f on (%f, %f)\n", dfError2, x_ori, y_ori);
4294
        }
4295
#endif
4296
0
        panSuccess[i] = TRUE;
4297
0
    }
4298
4299
0
    return TRUE;
4300
0
}
4301
4302
/************************************************************************/
4303
/*                        GDALApproxTransform()                         */
4304
/************************************************************************/
4305
4306
/**
4307
 * Perform approximate transformation.
4308
 *
4309
 * Actually performs the approximate transformation described in
4310
 * GDALCreateApproxTransformer().  This function matches the
4311
 * GDALTransformerFunc() signature.  Details of the arguments are described
4312
 * there.
4313
 */
4314
4315
int GDALApproxTransform(void *pCBData, int bDstToSrc, int nPoints, double *x,
4316
                        double *y, double *z, int *panSuccess)
4317
4318
0
{
4319
0
    GDALApproxTransformInfo *psATInfo =
4320
0
        static_cast<GDALApproxTransformInfo *>(pCBData);
4321
0
    double x2[3] = {};
4322
0
    double y2[3] = {};
4323
0
    double z2[3] = {};
4324
0
    int anSuccess2[3] = {};
4325
0
    int bSuccess;
4326
4327
0
    const int nMiddle = (nPoints - 1) / 2;
4328
4329
    /* -------------------------------------------------------------------- */
4330
    /*      Bail if our preconditions are not met, or if error is not       */
4331
    /*      acceptable.                                                     */
4332
    /* -------------------------------------------------------------------- */
4333
0
    int bRet = FALSE;
4334
0
    if (y[0] != y[nPoints - 1] || y[0] != y[nMiddle] ||
4335
0
        x[0] == x[nPoints - 1] || x[0] == x[nMiddle] ||
4336
0
        (psATInfo->dfMaxErrorForward == 0.0 &&
4337
0
         psATInfo->dfMaxErrorReverse == 0.0) ||
4338
0
        nPoints <= 5)
4339
0
    {
4340
0
        bRet = psATInfo->pfnBaseTransformer(psATInfo->pBaseCBData, bDstToSrc,
4341
0
                                            nPoints, x, y, z, panSuccess);
4342
0
        goto end;
4343
0
    }
4344
4345
    /* -------------------------------------------------------------------- */
4346
    /*      Transform first, last and middle point.                         */
4347
    /* -------------------------------------------------------------------- */
4348
0
    x2[0] = x[0];
4349
0
    y2[0] = y[0];
4350
0
    z2[0] = z[0];
4351
0
    x2[1] = x[nMiddle];
4352
0
    y2[1] = y[nMiddle];
4353
0
    z2[1] = z[nMiddle];
4354
0
    x2[2] = x[nPoints - 1];
4355
0
    y2[2] = y[nPoints - 1];
4356
0
    z2[2] = z[nPoints - 1];
4357
4358
0
    bSuccess = psATInfo->pfnBaseTransformer(psATInfo->pBaseCBData, bDstToSrc, 3,
4359
0
                                            x2, y2, z2, anSuccess2);
4360
0
    if (!bSuccess || !anSuccess2[0] || !anSuccess2[1] || !anSuccess2[2])
4361
0
    {
4362
0
        bRet = psATInfo->pfnBaseTransformer(psATInfo->pBaseCBData, bDstToSrc,
4363
0
                                            nPoints, x, y, z, panSuccess);
4364
0
        goto end;
4365
0
    }
4366
4367
0
    bRet = GDALApproxTransformInternal(pCBData, bDstToSrc, nPoints, x, y, z,
4368
0
                                       panSuccess, x2, y2, z2);
4369
4370
0
end:
4371
#ifdef DEBUG_APPROX_TRANSFORMER
4372
    for (int i = 0; i < nPoints; i++)
4373
        fprintf(stderr, "[%d] (%.10f,%.10f) %d\n", /*ok*/
4374
                i, x[i], y[i], panSuccess[i]);
4375
#endif
4376
4377
0
    return bRet;
4378
0
}
4379
4380
/************************************************************************/
4381
/*                  GDALDeserializeApproxTransformer()                  */
4382
/************************************************************************/
4383
4384
static void *GDALDeserializeApproxTransformer(CPLXMLNode *psTree)
4385
4386
0
{
4387
0
    double dfMaxErrorForward = 0.25;
4388
0
    double dfMaxErrorReverse = 0.25;
4389
0
    const char *pszMaxError = CPLGetXMLValue(psTree, "MaxError", nullptr);
4390
0
    if (pszMaxError != nullptr)
4391
0
    {
4392
0
        dfMaxErrorForward = CPLAtof(pszMaxError);
4393
0
        dfMaxErrorReverse = dfMaxErrorForward;
4394
0
    }
4395
0
    const char *pszMaxErrorForward =
4396
0
        CPLGetXMLValue(psTree, "MaxErrorForward", nullptr);
4397
0
    if (pszMaxErrorForward != nullptr)
4398
0
    {
4399
0
        dfMaxErrorForward = CPLAtof(pszMaxErrorForward);
4400
0
    }
4401
0
    const char *pszMaxErrorReverse =
4402
0
        CPLGetXMLValue(psTree, "MaxErrorReverse", nullptr);
4403
0
    if (pszMaxErrorReverse != nullptr)
4404
0
    {
4405
0
        dfMaxErrorReverse = CPLAtof(pszMaxErrorReverse);
4406
0
    }
4407
4408
0
    GDALTransformerFunc pfnBaseTransform = nullptr;
4409
0
    void *pBaseCBData = nullptr;
4410
4411
0
    CPLXMLNode *psContainer = CPLGetXMLNode(psTree, "BaseTransformer");
4412
4413
0
    if (psContainer != nullptr && psContainer->psChild != nullptr)
4414
0
    {
4415
0
        GDALDeserializeTransformer(psContainer->psChild, &pfnBaseTransform,
4416
0
                                   &pBaseCBData);
4417
0
    }
4418
4419
0
    if (pfnBaseTransform == nullptr)
4420
0
    {
4421
0
        CPLError(CE_Failure, CPLE_AppDefined,
4422
0
                 "Cannot get base transform for approx transformer.");
4423
0
        return nullptr;
4424
0
    }
4425
4426
0
    void *pApproxCBData = GDALCreateApproxTransformer2(
4427
0
        pfnBaseTransform, pBaseCBData, dfMaxErrorForward, dfMaxErrorReverse);
4428
0
    GDALApproxTransformerOwnsSubtransformer(pApproxCBData, TRUE);
4429
4430
0
    return pApproxCBData;
4431
0
}
4432
4433
/************************************************************************/
4434
/*                 GDALTransformLonLatToDestApproxTransformer()         */
4435
/************************************************************************/
4436
4437
int GDALTransformLonLatToDestApproxTransformer(void *hTransformArg,
4438
                                               double *pdfX, double *pdfY)
4439
0
{
4440
0
    GDALApproxTransformInfo *psInfo =
4441
0
        static_cast<GDALApproxTransformInfo *>(hTransformArg);
4442
4443
0
    if (GDALIsTransformer(psInfo->pBaseCBData,
4444
0
                          GDAL_GEN_IMG_TRANSFORMER_CLASS_NAME))
4445
0
    {
4446
0
        return GDALTransformLonLatToDestGenImgProjTransformer(
4447
0
            psInfo->pBaseCBData, pdfX, pdfY);
4448
0
    }
4449
0
    return false;
4450
0
}
4451
4452
/************************************************************************/
4453
/*                       GDALApplyGeoTransform()                        */
4454
/************************************************************************/
4455
4456
/**
4457
 * Apply GeoTransform to x/y coordinate.
4458
 *
4459
 * Applies the following computation, converting a (pixel, line) coordinate
4460
 * into a georeferenced (geo_x, geo_y) location.
4461
 * \code{.c}
4462
 *  *pdfGeoX = padfGeoTransform[0] + dfPixel * padfGeoTransform[1]
4463
 *                                 + dfLine  * padfGeoTransform[2];
4464
 *  *pdfGeoY = padfGeoTransform[3] + dfPixel * padfGeoTransform[4]
4465
 *                                 + dfLine  * padfGeoTransform[5];
4466
 * \endcode
4467
 *
4468
 * @param padfGeoTransform Six coefficient GeoTransform to apply.
4469
 * @param dfPixel Input pixel position.
4470
 * @param dfLine Input line position.
4471
 * @param pdfGeoX output location where geo_x (easting/longitude)
4472
 * location is placed.
4473
 * @param pdfGeoY output location where geo_y (northing/latitude)
4474
 * location is placed.
4475
 */
4476
4477
void CPL_STDCALL GDALApplyGeoTransform(const double *padfGeoTransform,
4478
                                       double dfPixel, double dfLine,
4479
                                       double *pdfGeoX, double *pdfGeoY)
4480
0
{
4481
0
    *pdfGeoX = padfGeoTransform[0] + dfPixel * padfGeoTransform[1] +
4482
0
               dfLine * padfGeoTransform[2];
4483
0
    *pdfGeoY = padfGeoTransform[3] + dfPixel * padfGeoTransform[4] +
4484
0
               dfLine * padfGeoTransform[5];
4485
0
}
4486
4487
/************************************************************************/
4488
/*                        GDALInvGeoTransform()                         */
4489
/************************************************************************/
4490
4491
/**
4492
 * Invert Geotransform.
4493
 *
4494
 * This function will invert a standard 3x2 set of GeoTransform coefficients.
4495
 * This converts the equation from being pixel to geo to being geo to pixel.
4496
 *
4497
 * @param gt_in Input geotransform (six doubles - unaltered).
4498
 * @param gt_out Output geotransform (six doubles - updated).
4499
 *
4500
 * @return TRUE on success or FALSE if the equation is uninvertable.
4501
 */
4502
4503
int CPL_STDCALL GDALInvGeoTransform(const double *gt_in, double *gt_out)
4504
4505
0
{
4506
    // Special case - no rotation - to avoid computing determinate
4507
    // and potential precision issues.
4508
0
    if (gt_in[2] == 0.0 && gt_in[4] == 0.0 && gt_in[1] != 0.0 &&
4509
0
        gt_in[5] != 0.0)
4510
0
    {
4511
        /*X = gt_in[0] + x * gt_in[1]
4512
          Y = gt_in[3] + y * gt_in[5]
4513
          -->
4514
          x = -gt_in[0] / gt_in[1] + (1 / gt_in[1]) * X
4515
          y = -gt_in[3] / gt_in[5] + (1 / gt_in[5]) * Y
4516
        */
4517
0
        gt_out[0] = -gt_in[0] / gt_in[1];
4518
0
        gt_out[1] = 1.0 / gt_in[1];
4519
0
        gt_out[2] = 0.0;
4520
0
        gt_out[3] = -gt_in[3] / gt_in[5];
4521
0
        gt_out[4] = 0.0;
4522
0
        gt_out[5] = 1.0 / gt_in[5];
4523
0
        return 1;
4524
0
    }
4525
4526
    // Assume a 3rd row that is [1 0 0].
4527
4528
    // Compute determinate.
4529
4530
0
    const double det = gt_in[1] * gt_in[5] - gt_in[2] * gt_in[4];
4531
0
    const double magnitude = std::max(std::max(fabs(gt_in[1]), fabs(gt_in[2])),
4532
0
                                      std::max(fabs(gt_in[4]), fabs(gt_in[5])));
4533
4534
0
    if (fabs(det) <= 1e-10 * magnitude * magnitude)
4535
0
        return 0;
4536
4537
0
    const double inv_det = 1.0 / det;
4538
4539
    // Compute adjoint, and divide by determinate.
4540
4541
0
    gt_out[1] = gt_in[5] * inv_det;
4542
0
    gt_out[4] = -gt_in[4] * inv_det;
4543
4544
0
    gt_out[2] = -gt_in[2] * inv_det;
4545
0
    gt_out[5] = gt_in[1] * inv_det;
4546
4547
0
    gt_out[0] = (gt_in[2] * gt_in[3] - gt_in[0] * gt_in[5]) * inv_det;
4548
0
    gt_out[3] = (-gt_in[1] * gt_in[3] + gt_in[0] * gt_in[4]) * inv_det;
4549
4550
0
    return 1;
4551
0
}
4552
4553
/************************************************************************/
4554
/*                      GDALSerializeTransformer()                      */
4555
/************************************************************************/
4556
4557
CPLXMLNode *GDALSerializeTransformer(GDALTransformerFunc /* pfnFunc */,
4558
                                     void *pTransformArg)
4559
0
{
4560
0
    VALIDATE_POINTER1(pTransformArg, "GDALSerializeTransformer", nullptr);
4561
4562
0
    GDALTransformerInfo *psInfo =
4563
0
        static_cast<GDALTransformerInfo *>(pTransformArg);
4564
4565
0
    if (psInfo == nullptr || memcmp(psInfo->abySignature, GDAL_GTI2_SIGNATURE,
4566
0
                                    strlen(GDAL_GTI2_SIGNATURE)) != 0)
4567
0
    {
4568
0
        CPLError(CE_Failure, CPLE_AppDefined,
4569
0
                 "Attempt to serialize non-GTI2 transformer.");
4570
0
        return nullptr;
4571
0
    }
4572
0
    else if (psInfo->pfnSerialize == nullptr)
4573
0
    {
4574
0
        CPLError(CE_Failure, CPLE_AppDefined,
4575
0
                 "No serialization function available for this transformer.");
4576
0
        return nullptr;
4577
0
    }
4578
4579
0
    return psInfo->pfnSerialize(pTransformArg);
4580
0
}
4581
4582
/************************************************************************/
4583
/*                  GDALRegisterTransformDeserializer()                 */
4584
/************************************************************************/
4585
4586
static CPLList *psListDeserializer = nullptr;
4587
static CPLMutex *hDeserializerMutex = nullptr;
4588
4589
typedef struct
4590
{
4591
    char *pszTransformName;
4592
    GDALTransformerFunc pfnTransformerFunc;
4593
    GDALTransformDeserializeFunc pfnDeserializeFunc;
4594
} TransformDeserializerInfo;
4595
4596
void *GDALRegisterTransformDeserializer(
4597
    const char *pszTransformName, GDALTransformerFunc pfnTransformerFunc,
4598
    GDALTransformDeserializeFunc pfnDeserializeFunc)
4599
0
{
4600
0
    TransformDeserializerInfo *psInfo =
4601
0
        static_cast<TransformDeserializerInfo *>(
4602
0
            CPLMalloc(sizeof(TransformDeserializerInfo)));
4603
0
    psInfo->pszTransformName = CPLStrdup(pszTransformName);
4604
0
    psInfo->pfnTransformerFunc = pfnTransformerFunc;
4605
0
    psInfo->pfnDeserializeFunc = pfnDeserializeFunc;
4606
4607
0
    CPLMutexHolderD(&hDeserializerMutex);
4608
0
    psListDeserializer = CPLListInsert(psListDeserializer, psInfo, 0);
4609
4610
0
    return psInfo;
4611
0
}
4612
4613
/************************************************************************/
4614
/*                GDALUnregisterTransformDeserializer()                 */
4615
/************************************************************************/
4616
4617
void GDALUnregisterTransformDeserializer(void *pData)
4618
0
{
4619
0
    CPLMutexHolderD(&hDeserializerMutex);
4620
0
    CPLList *psList = psListDeserializer;
4621
0
    CPLList *psLast = nullptr;
4622
0
    while (psList)
4623
0
    {
4624
0
        if (psList->pData == pData)
4625
0
        {
4626
0
            TransformDeserializerInfo *psInfo =
4627
0
                static_cast<TransformDeserializerInfo *>(pData);
4628
0
            CPLFree(psInfo->pszTransformName);
4629
0
            CPLFree(pData);
4630
0
            if (psLast)
4631
0
                psLast->psNext = psList->psNext;
4632
0
            else
4633
0
                psListDeserializer = nullptr;
4634
0
            CPLFree(psList);
4635
0
            break;
4636
0
        }
4637
0
        psLast = psList;
4638
0
        psList = psList->psNext;
4639
0
    }
4640
0
}
4641
4642
/************************************************************************/
4643
/*                GDALUnregisterTransformDeserializer()                 */
4644
/************************************************************************/
4645
4646
void GDALCleanupTransformDeserializerMutex()
4647
0
{
4648
0
    if (hDeserializerMutex != nullptr)
4649
0
    {
4650
0
        CPLDestroyMutex(hDeserializerMutex);
4651
0
        hDeserializerMutex = nullptr;
4652
0
    }
4653
0
}
4654
4655
/************************************************************************/
4656
/*                     GDALDeserializeTransformer()                     */
4657
/************************************************************************/
4658
4659
CPLErr GDALDeserializeTransformer(CPLXMLNode *psTree,
4660
                                  GDALTransformerFunc *ppfnFunc,
4661
                                  void **ppTransformArg)
4662
4663
0
{
4664
0
    *ppfnFunc = nullptr;
4665
0
    *ppTransformArg = nullptr;
4666
4667
0
    CPLErrorReset();
4668
4669
0
    if (psTree == nullptr || psTree->eType != CXT_Element)
4670
0
        CPLError(CE_Failure, CPLE_AppDefined,
4671
0
                 "Malformed element in GDALDeserializeTransformer");
4672
0
    else if (EQUAL(psTree->pszValue, "GenImgProjTransformer"))
4673
0
    {
4674
0
        *ppfnFunc = GDALGenImgProjTransform;
4675
0
        *ppTransformArg = GDALDeserializeGenImgProjTransformer(psTree);
4676
0
    }
4677
0
    else if (EQUAL(psTree->pszValue, "ReprojectionTransformer"))
4678
0
    {
4679
0
        *ppfnFunc = GDALReprojectionTransform;
4680
0
        *ppTransformArg = GDALDeserializeReprojectionTransformer(psTree);
4681
0
    }
4682
0
    else if (EQUAL(psTree->pszValue, "GCPTransformer"))
4683
0
    {
4684
0
        *ppfnFunc = GDALGCPTransform;
4685
0
        *ppTransformArg = GDALDeserializeGCPTransformer(psTree);
4686
0
    }
4687
0
    else if (EQUAL(psTree->pszValue, "TPSTransformer"))
4688
0
    {
4689
0
        *ppfnFunc = GDALTPSTransform;
4690
0
        *ppTransformArg = GDALDeserializeTPSTransformer(psTree);
4691
0
    }
4692
0
    else if (EQUAL(psTree->pszValue, "GeoLocTransformer"))
4693
0
    {
4694
0
        *ppfnFunc = GDALGeoLocTransform;
4695
0
        *ppTransformArg = GDALDeserializeGeoLocTransformer(psTree);
4696
0
    }
4697
0
    else if (EQUAL(psTree->pszValue, "RPCTransformer"))
4698
0
    {
4699
0
        *ppfnFunc = GDALRPCTransform;
4700
0
        *ppTransformArg = GDALDeserializeRPCTransformer(psTree);
4701
0
    }
4702
0
    else if (EQUAL(psTree->pszValue, "ApproxTransformer"))
4703
0
    {
4704
0
        *ppfnFunc = GDALApproxTransform;
4705
0
        *ppTransformArg = GDALDeserializeApproxTransformer(psTree);
4706
0
    }
4707
0
    else if (EQUAL(psTree->pszValue, "HomographyTransformer"))
4708
0
    {
4709
0
        *ppfnFunc = GDALHomographyTransform;
4710
0
        *ppTransformArg = GDALDeserializeHomographyTransformer(psTree);
4711
0
    }
4712
0
    else
4713
0
    {
4714
0
        GDALTransformDeserializeFunc pfnDeserializeFunc = nullptr;
4715
0
        {
4716
0
            CPLMutexHolderD(&hDeserializerMutex);
4717
0
            CPLList *psList = psListDeserializer;
4718
0
            while (psList)
4719
0
            {
4720
0
                TransformDeserializerInfo *psInfo =
4721
0
                    static_cast<TransformDeserializerInfo *>(psList->pData);
4722
0
                if (strcmp(psInfo->pszTransformName, psTree->pszValue) == 0)
4723
0
                {
4724
0
                    *ppfnFunc = psInfo->pfnTransformerFunc;
4725
0
                    pfnDeserializeFunc = psInfo->pfnDeserializeFunc;
4726
0
                    break;
4727
0
                }
4728
0
                psList = psList->psNext;
4729
0
            }
4730
0
        }
4731
4732
0
        if (pfnDeserializeFunc != nullptr)
4733
0
        {
4734
0
            *ppTransformArg = pfnDeserializeFunc(psTree);
4735
0
        }
4736
0
        else
4737
0
        {
4738
0
            CPLError(CE_Failure, CPLE_AppDefined,
4739
0
                     "Unrecognized element '%s' GDALDeserializeTransformer",
4740
0
                     psTree->pszValue);
4741
0
        }
4742
0
    }
4743
4744
0
    return CPLGetLastErrorType();
4745
0
}
4746
4747
/************************************************************************/
4748
/*                       GDALDestroyTransformer()                       */
4749
/************************************************************************/
4750
4751
void GDALDestroyTransformer(void *pTransformArg)
4752
4753
0
{
4754
0
    if (pTransformArg == nullptr)
4755
0
        return;
4756
4757
0
    GDALTransformerInfo *psInfo =
4758
0
        static_cast<GDALTransformerInfo *>(pTransformArg);
4759
4760
0
    if (memcmp(psInfo->abySignature, GDAL_GTI2_SIGNATURE,
4761
0
               strlen(GDAL_GTI2_SIGNATURE)) != 0)
4762
0
    {
4763
0
        CPLError(CE_Failure, CPLE_AppDefined,
4764
0
                 "Attempt to destroy non-GTI2 transformer.");
4765
0
        return;
4766
0
    }
4767
4768
0
    psInfo->pfnCleanup(pTransformArg);
4769
0
}
4770
4771
/************************************************************************/
4772
/*                         GDALUseTransformer()                         */
4773
/************************************************************************/
4774
4775
int GDALUseTransformer(void *pTransformArg, int bDstToSrc, int nPointCount,
4776
                       double *x, double *y, double *z, int *panSuccess)
4777
0
{
4778
0
    GDALTransformerInfo *psInfo =
4779
0
        static_cast<GDALTransformerInfo *>(pTransformArg);
4780
4781
0
    if (psInfo == nullptr || memcmp(psInfo->abySignature, GDAL_GTI2_SIGNATURE,
4782
0
                                    strlen(GDAL_GTI2_SIGNATURE)) != 0)
4783
0
    {
4784
0
        CPLError(CE_Failure, CPLE_AppDefined,
4785
0
                 "Attempt to use non-GTI2 transformer.");
4786
0
        return FALSE;
4787
0
    }
4788
4789
0
    return psInfo->pfnTransform(pTransformArg, bDstToSrc, nPointCount, x, y, z,
4790
0
                                panSuccess);
4791
0
}
4792
4793
/************************************************************************/
4794
/*                        GDALCloneTransformer()                        */
4795
/************************************************************************/
4796
4797
void *GDALCloneTransformer(void *pTransformArg)
4798
0
{
4799
0
    GDALTransformerInfo *psInfo =
4800
0
        static_cast<GDALTransformerInfo *>(pTransformArg);
4801
4802
0
    if (psInfo == nullptr || memcmp(psInfo->abySignature, GDAL_GTI2_SIGNATURE,
4803
0
                                    strlen(GDAL_GTI2_SIGNATURE)) != 0)
4804
0
    {
4805
0
        CPLError(CE_Failure, CPLE_AppDefined,
4806
0
                 "Attempt to clone non-GTI2 transformer.");
4807
0
        return nullptr;
4808
0
    }
4809
4810
0
    if (psInfo->pfnCreateSimilar != nullptr)
4811
0
    {
4812
0
        return psInfo->pfnCreateSimilar(psInfo, 1.0, 1.0);
4813
0
    }
4814
4815
0
    if (psInfo->pfnSerialize == nullptr)
4816
0
    {
4817
0
        CPLError(CE_Failure, CPLE_AppDefined,
4818
0
                 "No serialization function available for this transformer.");
4819
0
        return nullptr;
4820
0
    }
4821
4822
0
    CPLXMLNode *pSerialized = psInfo->pfnSerialize(pTransformArg);
4823
0
    if (pSerialized == nullptr)
4824
0
        return nullptr;
4825
0
    GDALTransformerFunc pfnTransformer = nullptr;
4826
0
    void *pClonedTransformArg = nullptr;
4827
0
    if (GDALDeserializeTransformer(pSerialized, &pfnTransformer,
4828
0
                                   &pClonedTransformArg) != CE_None)
4829
0
    {
4830
0
        CPLDestroyXMLNode(pSerialized);
4831
0
        CPLFree(pClonedTransformArg);
4832
0
        return nullptr;
4833
0
    }
4834
4835
0
    CPLDestroyXMLNode(pSerialized);
4836
0
    return pClonedTransformArg;
4837
0
}
4838
4839
/************************************************************************/
4840
/*                   GDALCreateSimilarTransformer()                     */
4841
/************************************************************************/
4842
4843
void *GDALCreateSimilarTransformer(void *pTransformArg, double dfRatioX,
4844
                                   double dfRatioY)
4845
0
{
4846
0
    GDALTransformerInfo *psInfo =
4847
0
        static_cast<GDALTransformerInfo *>(pTransformArg);
4848
4849
0
    if (psInfo == nullptr || memcmp(psInfo->abySignature, GDAL_GTI2_SIGNATURE,
4850
0
                                    strlen(GDAL_GTI2_SIGNATURE)) != 0)
4851
0
    {
4852
0
        CPLError(CE_Failure, CPLE_AppDefined,
4853
0
                 "Attempt to call CreateSimilar on a non-GTI2 transformer.");
4854
0
        return nullptr;
4855
0
    }
4856
4857
0
    if (psInfo->pfnCreateSimilar == nullptr)
4858
0
    {
4859
0
        CPLError(CE_Failure, CPLE_AppDefined,
4860
0
                 "No CreateSimilar function available for this transformer.");
4861
0
        return nullptr;
4862
0
    }
4863
4864
0
    return psInfo->pfnCreateSimilar(psInfo, dfRatioX, dfRatioY);
4865
0
}
4866
4867
/************************************************************************/
4868
/*                      GetGenImgProjTransformInfo()                    */
4869
/************************************************************************/
4870
4871
static GDALTransformerInfo *GetGenImgProjTransformInfo(const char *pszFunc,
4872
                                                       void *pTransformArg)
4873
0
{
4874
0
    GDALTransformerInfo *psInfo =
4875
0
        static_cast<GDALTransformerInfo *>(pTransformArg);
4876
4877
0
    if (psInfo == nullptr || memcmp(psInfo->abySignature, GDAL_GTI2_SIGNATURE,
4878
0
                                    strlen(GDAL_GTI2_SIGNATURE)) != 0)
4879
0
    {
4880
0
        CPLError(CE_Failure, CPLE_AppDefined,
4881
0
                 "Attempt to call %s on "
4882
0
                 "a non-GTI2 transformer.",
4883
0
                 pszFunc);
4884
0
        return nullptr;
4885
0
    }
4886
4887
0
    if (EQUAL(psInfo->pszClassName, GDAL_APPROX_TRANSFORMER_CLASS_NAME))
4888
0
    {
4889
0
        GDALApproxTransformInfo *psATInfo =
4890
0
            static_cast<GDALApproxTransformInfo *>(pTransformArg);
4891
0
        psInfo = static_cast<GDALTransformerInfo *>(psATInfo->pBaseCBData);
4892
4893
0
        if (psInfo == nullptr ||
4894
0
            memcmp(psInfo->abySignature, GDAL_GTI2_SIGNATURE,
4895
0
                   strlen(GDAL_GTI2_SIGNATURE)) != 0)
4896
0
        {
4897
0
            CPLError(CE_Failure, CPLE_AppDefined,
4898
0
                     "Attempt to call %s on "
4899
0
                     "a non-GTI2 transformer.",
4900
0
                     pszFunc);
4901
0
            return nullptr;
4902
0
        }
4903
0
    }
4904
4905
0
    if (EQUAL(psInfo->pszClassName, GDAL_GEN_IMG_TRANSFORMER_CLASS_NAME))
4906
0
    {
4907
0
        return psInfo;
4908
0
    }
4909
4910
0
    return nullptr;
4911
0
}
4912
4913
/************************************************************************/
4914
/*                 GDALSetTransformerDstGeoTransform()                  */
4915
/************************************************************************/
4916
4917
/**
4918
 * Set ApproxTransformer or GenImgProj output geotransform.
4919
 *
4920
 * This is a layer above GDALSetGenImgProjTransformerDstGeoTransform() that
4921
 * checks that the passed hTransformArg is compatible.
4922
 *
4923
 * Normally the "destination geotransform", or transformation between
4924
 * georeferenced output coordinates and pixel/line coordinates on the
4925
 * destination file is extracted from the destination file by
4926
 * GDALCreateGenImgProjTransformer() and stored in the GenImgProj private
4927
 * info.  However, sometimes it is inconvenient to have an output file
4928
 * handle with appropriate geotransform information when creating the
4929
 * transformation.  For these cases, this function can be used to apply
4930
 * the destination geotransform.
4931
 *
4932
 * @param pTransformArg the handle to update.
4933
 * @param padfGeoTransform the destination geotransform to apply (six doubles).
4934
 */
4935
4936
void GDALSetTransformerDstGeoTransform(void *pTransformArg,
4937
                                       const double *padfGeoTransform)
4938
0
{
4939
0
    VALIDATE_POINTER0(pTransformArg, "GDALSetTransformerDstGeoTransform");
4940
4941
0
    GDALTransformerInfo *psInfo = GetGenImgProjTransformInfo(
4942
0
        "GDALSetTransformerDstGeoTransform", pTransformArg);
4943
0
    if (psInfo)
4944
0
    {
4945
0
        GDALSetGenImgProjTransformerDstGeoTransform(psInfo, padfGeoTransform);
4946
0
    }
4947
0
}
4948
4949
/************************************************************************/
4950
/*                 GDALGetTransformerDstGeoTransform()                  */
4951
/************************************************************************/
4952
4953
/**
4954
 * Get ApproxTransformer or GenImgProj output geotransform.
4955
 *
4956
 * @param pTransformArg transformer handle.
4957
 * @param padfGeoTransform (output) the destination geotransform to return (six
4958
 * doubles).
4959
 */
4960
4961
void GDALGetTransformerDstGeoTransform(void *pTransformArg,
4962
                                       double *padfGeoTransform)
4963
0
{
4964
0
    VALIDATE_POINTER0(pTransformArg, "GDALGetTransformerDstGeoTransform");
4965
4966
0
    GDALTransformerInfo *psInfo = GetGenImgProjTransformInfo(
4967
0
        "GDALGetTransformerDstGeoTransform", pTransformArg);
4968
0
    if (psInfo)
4969
0
    {
4970
0
        GDALGenImgProjTransformInfo *psGenImgProjInfo =
4971
0
            reinterpret_cast<GDALGenImgProjTransformInfo *>(psInfo);
4972
4973
0
        memcpy(padfGeoTransform, psGenImgProjInfo->sDstParams.adfGeoTransform,
4974
0
               sizeof(double) * 6);
4975
0
    }
4976
0
}
4977
4978
/************************************************************************/
4979
/*            GDALTransformIsTranslationOnPixelBoundaries()             */
4980
/************************************************************************/
4981
4982
bool GDALTransformIsTranslationOnPixelBoundaries(GDALTransformerFunc,
4983
                                                 void *pTransformerArg)
4984
0
{
4985
0
    if (GDALIsTransformer(pTransformerArg, GDAL_APPROX_TRANSFORMER_CLASS_NAME))
4986
0
    {
4987
0
        const auto *pApproxInfo =
4988
0
            static_cast<const GDALApproxTransformInfo *>(pTransformerArg);
4989
0
        pTransformerArg = pApproxInfo->pBaseCBData;
4990
0
    }
4991
0
    if (GDALIsTransformer(pTransformerArg, GDAL_GEN_IMG_TRANSFORMER_CLASS_NAME))
4992
0
    {
4993
0
        const auto *pGenImgpProjInfo =
4994
0
            static_cast<GDALGenImgProjTransformInfo *>(pTransformerArg);
4995
0
        const auto IsCloseToInteger = [](double dfVal)
4996
0
        { return std::fabs(dfVal - std::round(dfVal)) <= 1e-6; };
4997
0
        return pGenImgpProjInfo->sSrcParams.pTransformArg == nullptr &&
4998
0
               pGenImgpProjInfo->sDstParams.pTransformArg == nullptr &&
4999
0
               pGenImgpProjInfo->pReproject == nullptr &&
5000
0
               pGenImgpProjInfo->sSrcParams.adfGeoTransform[1] ==
5001
0
                   pGenImgpProjInfo->sDstParams.adfGeoTransform[1] &&
5002
0
               pGenImgpProjInfo->sSrcParams.adfGeoTransform[5] ==
5003
0
                   pGenImgpProjInfo->sDstParams.adfGeoTransform[5] &&
5004
0
               pGenImgpProjInfo->sSrcParams.adfGeoTransform[2] ==
5005
0
                   pGenImgpProjInfo->sDstParams.adfGeoTransform[2] &&
5006
0
               pGenImgpProjInfo->sSrcParams.adfGeoTransform[4] ==
5007
0
                   pGenImgpProjInfo->sDstParams.adfGeoTransform[4] &&
5008
               // Check that the georeferenced origin of the destination
5009
               // geotransform is close to be an integer value when transformed
5010
               // to source image coordinates
5011
0
               IsCloseToInteger(
5012
0
                   pGenImgpProjInfo->sSrcParams.adfInvGeoTransform[0] +
5013
0
                   pGenImgpProjInfo->sDstParams.adfGeoTransform[0] *
5014
0
                       pGenImgpProjInfo->sSrcParams.adfInvGeoTransform[1] +
5015
0
                   pGenImgpProjInfo->sDstParams.adfGeoTransform[3] *
5016
0
                       pGenImgpProjInfo->sSrcParams.adfInvGeoTransform[2]) &&
5017
0
               IsCloseToInteger(
5018
0
                   pGenImgpProjInfo->sSrcParams.adfInvGeoTransform[3] +
5019
0
                   pGenImgpProjInfo->sDstParams.adfGeoTransform[0] *
5020
0
                       pGenImgpProjInfo->sSrcParams.adfInvGeoTransform[4] +
5021
0
                   pGenImgpProjInfo->sDstParams.adfGeoTransform[3] *
5022
0
                       pGenImgpProjInfo->sSrcParams.adfInvGeoTransform[5]);
5023
0
    }
5024
0
    return false;
5025
0
}
5026
5027
/************************************************************************/
5028
/*                   GDALTransformIsAffineNoRotation()                  */
5029
/************************************************************************/
5030
5031
bool GDALTransformIsAffineNoRotation(GDALTransformerFunc, void *pTransformerArg)
5032
0
{
5033
0
    if (GDALIsTransformer(pTransformerArg, GDAL_APPROX_TRANSFORMER_CLASS_NAME))
5034
0
    {
5035
0
        const auto *pApproxInfo =
5036
0
            static_cast<const GDALApproxTransformInfo *>(pTransformerArg);
5037
0
        pTransformerArg = pApproxInfo->pBaseCBData;
5038
0
    }
5039
0
    if (GDALIsTransformer(pTransformerArg, GDAL_GEN_IMG_TRANSFORMER_CLASS_NAME))
5040
0
    {
5041
0
        const auto *pGenImgpProjInfo =
5042
0
            static_cast<GDALGenImgProjTransformInfo *>(pTransformerArg);
5043
0
        return pGenImgpProjInfo->sSrcParams.pTransformArg == nullptr &&
5044
0
               pGenImgpProjInfo->sDstParams.pTransformArg == nullptr &&
5045
0
               pGenImgpProjInfo->pReproject == nullptr &&
5046
0
               pGenImgpProjInfo->sSrcParams.adfGeoTransform[2] == 0 &&
5047
0
               pGenImgpProjInfo->sSrcParams.adfGeoTransform[4] == 0 &&
5048
0
               pGenImgpProjInfo->sDstParams.adfGeoTransform[2] == 0 &&
5049
0
               pGenImgpProjInfo->sDstParams.adfGeoTransform[4] == 0;
5050
0
    }
5051
0
    return false;
5052
0
}
5053
5054
/************************************************************************/
5055
/*                        GDALTransformHasFastClone()                   */
5056
/************************************************************************/
5057
5058
/** Returns whether GDALCloneTransformer() on this transformer is
5059
 * "fast"
5060
 * Counter-examples are GCPs or TPSs transformers.
5061
 */
5062
bool GDALTransformHasFastClone(void *pTransformerArg)
5063
0
{
5064
0
    if (GDALIsTransformer(pTransformerArg, GDAL_APPROX_TRANSFORMER_CLASS_NAME))
5065
0
    {
5066
0
        const auto *pApproxInfo =
5067
0
            static_cast<const GDALApproxTransformInfo *>(pTransformerArg);
5068
0
        pTransformerArg = pApproxInfo->pBaseCBData;
5069
        // Fallback to next lines
5070
0
    }
5071
5072
0
    if (GDALIsTransformer(pTransformerArg, GDAL_GEN_IMG_TRANSFORMER_CLASS_NAME))
5073
0
    {
5074
0
        const auto *pGenImgpProjInfo =
5075
0
            static_cast<GDALGenImgProjTransformInfo *>(pTransformerArg);
5076
0
        return (pGenImgpProjInfo->sSrcParams.pTransformArg == nullptr ||
5077
0
                GDALTransformHasFastClone(
5078
0
                    pGenImgpProjInfo->sSrcParams.pTransformArg)) &&
5079
0
               (pGenImgpProjInfo->sDstParams.pTransformArg == nullptr ||
5080
0
                GDALTransformHasFastClone(
5081
0
                    pGenImgpProjInfo->sDstParams.pTransformArg));
5082
0
    }
5083
0
    else if (GDALIsTransformer(pTransformerArg,
5084
0
                               GDAL_RPC_TRANSFORMER_CLASS_NAME))
5085
0
    {
5086
0
        return true;
5087
0
    }
5088
0
    else
5089
0
    {
5090
0
        return false;
5091
0
    }
5092
0
}