Coverage Report

Created: 2026-09-14 06:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/alg/gdaltransformer.cpp
Line
Count
Source
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
    double dfPixelSize =
1100
0
        dfDiagonalDist / sqrt(static_cast<double>(nInXSize) * nInXSize +
1101
0
                              static_cast<double>(nInYSize) * nInYSize);
1102
1103
0
    double dfPixels = (dfMaxXOut - dfMinXOut) / dfPixelSize;
1104
0
    double dfLines = (dfMaxYOut - dfMinYOut) / dfPixelSize;
1105
1106
0
    const int knIntMaxMinusOne = std::numeric_limits<int>::max() - 1;
1107
0
    if (dfPixels > knIntMaxMinusOne && dfLines <= dfPixels)
1108
0
    {
1109
0
        dfPixels = knIntMaxMinusOne;
1110
0
        dfPixelSize = (dfMaxXOut - dfMinXOut) / dfPixels;
1111
0
        dfLines = (dfMaxYOut - dfMinYOut) / dfPixelSize;
1112
0
    }
1113
0
    else if (dfLines > knIntMaxMinusOne)
1114
0
    {
1115
0
        dfLines = knIntMaxMinusOne;
1116
0
        dfPixelSize = (dfMaxYOut - dfMinYOut) / dfLines;
1117
0
        dfPixels = (dfMaxXOut - dfMinXOut) / dfPixelSize;
1118
0
    }
1119
1120
0
    if (dfPixels > knIntMaxMinusOne || dfLines > knIntMaxMinusOne)
1121
0
    {
1122
0
        CPLError(CE_Failure, CPLE_AppDefined,
1123
0
                 "Computed dimensions are too big : %.0f x %.0f",
1124
0
                 dfPixels + 0.5, dfLines + 0.5);
1125
1126
0
        CPLFree(padfX);
1127
0
        CPLFree(padfXRevert);
1128
0
        CPLFree(pabSuccess);
1129
1130
0
        return CE_Failure;
1131
0
    }
1132
1133
0
    if ((nOptions & GDAL_SWO_ROUND_UP_SIZE) != 0)
1134
0
    {
1135
0
        constexpr double EPS = 1e-5;
1136
0
        *pnPixels = static_cast<int>(std::ceil(dfPixels - EPS));
1137
0
        *pnLines = static_cast<int>(std::ceil(dfLines - EPS));
1138
0
    }
1139
0
    else
1140
0
    {
1141
0
        *pnPixels = static_cast<int>(dfPixels + 0.5);
1142
0
        *pnLines = static_cast<int>(dfLines + 0.5);
1143
0
    }
1144
1145
0
    double dfPixelSizeX = dfPixelSize;
1146
0
    double dfPixelSizeY = dfPixelSize;
1147
1148
0
    const double adfRatioArray[] = {0.000, 0.001, 0.010, 0.100, 1.000};
1149
1150
    /* -------------------------------------------------------------------- */
1151
    /*      Check that the right border is not completely out of source     */
1152
    /*      image. If so, adjust the x pixel size a bit in the hope it will */
1153
    /*      fit.                                                            */
1154
    /* -------------------------------------------------------------------- */
1155
0
    for (const auto &dfRatio : adfRatioArray)
1156
0
    {
1157
0
        const double dfTryPixelSizeX =
1158
0
            dfPixelSizeX - dfPixelSizeX * dfRatio / *pnPixels;
1159
0
        double adfExtent[4] = {dfMinXOut, dfMaxYOut - (*pnLines) * dfPixelSizeY,
1160
0
                               dfMinXOut + (*pnPixels) * dfTryPixelSizeX,
1161
0
                               dfMaxYOut};
1162
0
        if (!GDALSuggestedWarpOutput2_MustAdjustForRightBorder(
1163
0
                pfnTransformer, pTransformArg, adfExtent, *pnPixels, *pnLines,
1164
0
                dfTryPixelSizeX, dfPixelSizeY))
1165
0
        {
1166
0
            dfPixelSizeX = dfTryPixelSizeX;
1167
0
            break;
1168
0
        }
1169
0
    }
1170
1171
    /* -------------------------------------------------------------------- */
1172
    /*      Check that the bottom border is not completely out of source    */
1173
    /*      image. If so, adjust the y pixel size a bit in the hope it will */
1174
    /*      fit.                                                            */
1175
    /* -------------------------------------------------------------------- */
1176
0
    for (const auto &dfRatio : adfRatioArray)
1177
0
    {
1178
0
        const double dfTryPixelSizeY =
1179
0
            dfPixelSizeY - dfPixelSizeY * dfRatio / *pnLines;
1180
0
        double adfExtent[4] = {
1181
0
            dfMinXOut, dfMaxYOut - (*pnLines) * dfTryPixelSizeY,
1182
0
            dfMinXOut + (*pnPixels) * dfPixelSizeX, dfMaxYOut};
1183
0
        if (!GDALSuggestedWarpOutput2_MustAdjustForBottomBorder(
1184
0
                pfnTransformer, pTransformArg, adfExtent, *pnPixels, *pnLines,
1185
0
                dfPixelSizeX, dfTryPixelSizeY))
1186
0
        {
1187
0
            dfPixelSizeY = dfTryPixelSizeY;
1188
0
            break;
1189
0
        }
1190
0
    }
1191
1192
    /* -------------------------------------------------------------------- */
1193
    /*      Recompute some bounds so that all return values are consistent  */
1194
    /* -------------------------------------------------------------------- */
1195
0
    double dfMaxXOutNew = dfMinXOut + (*pnPixels) * dfPixelSizeX;
1196
0
    if (bIsGeographicCoordsDeg &&
1197
0
        ((dfMaxXOut <= 180 && dfMaxXOutNew > 180) || dfMaxXOut == 180))
1198
0
    {
1199
0
        dfMaxXOut = 180;
1200
0
        dfPixelSizeX = (dfMaxXOut - dfMinXOut) / *pnPixels;
1201
0
    }
1202
0
    else
1203
0
    {
1204
0
        dfMaxXOut = dfMaxXOutNew;
1205
0
    }
1206
1207
0
    double dfMinYOutNew = dfMaxYOut - (*pnLines) * dfPixelSizeY;
1208
0
    if (bIsGeographicCoordsDeg && dfMinYOut >= -90 && dfMinYOutNew < -90)
1209
0
    {
1210
0
        dfMinYOut = -90;
1211
0
        dfPixelSizeY = (dfMaxYOut - dfMinYOut) / *pnLines;
1212
0
    }
1213
0
    else
1214
0
    {
1215
0
        dfMinYOut = dfMinYOutNew;
1216
0
    }
1217
1218
    /* -------------------------------------------------------------------- */
1219
    /*      Return raw extents.                                             */
1220
    /* -------------------------------------------------------------------- */
1221
0
    padfExtent[0] = dfMinXOut;
1222
0
    padfExtent[1] = dfMinYOut;
1223
0
    padfExtent[2] = dfMaxXOut;
1224
0
    padfExtent[3] = dfMaxYOut;
1225
1226
    /* -------------------------------------------------------------------- */
1227
    /*      Set the output geotransform.                                    */
1228
    /* -------------------------------------------------------------------- */
1229
0
    padfGeoTransformOut[0] = dfMinXOut;
1230
0
    padfGeoTransformOut[1] = dfPixelSizeX;
1231
0
    padfGeoTransformOut[2] = 0.0;
1232
0
    padfGeoTransformOut[3] = dfMaxYOut;
1233
0
    padfGeoTransformOut[4] = 0.0;
1234
0
    padfGeoTransformOut[5] = -dfPixelSizeY;
1235
1236
0
    CPLFree(padfX);
1237
0
    CPLFree(padfXRevert);
1238
0
    CPLFree(pabSuccess);
1239
1240
0
    return CE_None;
1241
0
}
1242
1243
/************************************************************************/
1244
/*                   GetCurrentCheckWithInvertPROJ()                    */
1245
/************************************************************************/
1246
1247
static bool GetCurrentCheckWithInvertPROJ()
1248
0
{
1249
0
    return CPLTestBool(CPLGetConfigOption("CHECK_WITH_INVERT_PROJ", "NO"));
1250
0
}
1251
1252
/************************************************************************/
1253
/*              GDALCreateGenImgProjTransformerInternal()               */
1254
/************************************************************************/
1255
1256
static void *GDALCreateSimilarGenImgProjTransformer(void *hTransformArg,
1257
                                                    double dfRatioX,
1258
                                                    double dfRatioY);
1259
1260
static GDALGenImgProjTransformInfo *GDALCreateGenImgProjTransformerInternal()
1261
0
{
1262
    /* -------------------------------------------------------------------- */
1263
    /*      Initialize the transform info.                                  */
1264
    /* -------------------------------------------------------------------- */
1265
0
    GDALGenImgProjTransformInfo *psInfo =
1266
0
        static_cast<GDALGenImgProjTransformInfo *>(
1267
0
            CPLCalloc(sizeof(GDALGenImgProjTransformInfo), 1));
1268
1269
0
    memcpy(psInfo->sTI.abySignature, GDAL_GTI2_SIGNATURE,
1270
0
           strlen(GDAL_GTI2_SIGNATURE));
1271
0
    psInfo->sTI.pszClassName = GDAL_GEN_IMG_TRANSFORMER_CLASS_NAME;
1272
0
    psInfo->sTI.pfnTransform = GDALGenImgProjTransform;
1273
0
    psInfo->sTI.pfnCleanup = GDALDestroyGenImgProjTransformer;
1274
0
    psInfo->sTI.pfnSerialize = GDALSerializeGenImgProjTransformer;
1275
0
    psInfo->sTI.pfnCreateSimilar = GDALCreateSimilarGenImgProjTransformer;
1276
1277
0
    psInfo->bCheckWithInvertPROJ = GetCurrentCheckWithInvertPROJ();
1278
0
    psInfo->bHasCustomTransformationPipeline = false;
1279
1280
0
    return psInfo;
1281
0
}
1282
1283
/************************************************************************/
1284
/*               GDALCreateSimilarGenImgProjTransformer()               */
1285
/************************************************************************/
1286
1287
static void *GDALCreateSimilarGenImgProjTransformer(void *hTransformArg,
1288
                                                    double dfRatioX,
1289
                                                    double dfRatioY)
1290
0
{
1291
0
    VALIDATE_POINTER1(hTransformArg, "GDALCreateSimilarGenImgProjTransformer",
1292
0
                      nullptr);
1293
1294
0
    GDALGenImgProjTransformInfo *psInfo =
1295
0
        static_cast<GDALGenImgProjTransformInfo *>(hTransformArg);
1296
1297
0
    GDALGenImgProjTransformInfo *psClonedInfo =
1298
0
        GDALCreateGenImgProjTransformerInternal();
1299
1300
0
    memcpy(psClonedInfo, psInfo, sizeof(GDALGenImgProjTransformInfo));
1301
1302
0
    psClonedInfo->bCheckWithInvertPROJ = GetCurrentCheckWithInvertPROJ();
1303
1304
0
    if (psClonedInfo->sSrcParams.pTransformArg)
1305
0
        psClonedInfo->sSrcParams.pTransformArg = GDALCreateSimilarTransformer(
1306
0
            psInfo->sSrcParams.pTransformArg, dfRatioX, dfRatioY);
1307
0
    else if (dfRatioX != 1.0 || dfRatioY != 1.0)
1308
0
    {
1309
0
        if (psClonedInfo->sSrcParams.adfGeoTransform[2] == 0.0 &&
1310
0
            psClonedInfo->sSrcParams.adfGeoTransform[4] == 0.0)
1311
0
        {
1312
0
            psClonedInfo->sSrcParams.adfGeoTransform[1] *= dfRatioX;
1313
0
            psClonedInfo->sSrcParams.adfGeoTransform[5] *= dfRatioY;
1314
0
        }
1315
0
        else
1316
0
        {
1317
            // If the x and y ratios are not equal, then we cannot really
1318
            // compute a geotransform.
1319
0
            psClonedInfo->sSrcParams.adfGeoTransform[1] *= dfRatioX;
1320
0
            psClonedInfo->sSrcParams.adfGeoTransform[2] *= dfRatioX;
1321
0
            psClonedInfo->sSrcParams.adfGeoTransform[4] *= dfRatioX;
1322
0
            psClonedInfo->sSrcParams.adfGeoTransform[5] *= dfRatioX;
1323
0
        }
1324
0
        if (!GDALInvGeoTransform(psClonedInfo->sSrcParams.adfGeoTransform,
1325
0
                                 psClonedInfo->sSrcParams.adfInvGeoTransform))
1326
0
        {
1327
0
            CPLError(CE_Failure, CPLE_AppDefined, "Cannot invert geotransform");
1328
0
            GDALDestroyGenImgProjTransformer(psClonedInfo);
1329
0
            return nullptr;
1330
0
        }
1331
0
    }
1332
1333
0
    if (psClonedInfo->pReprojectArg)
1334
0
        psClonedInfo->pReprojectArg =
1335
0
            GDALCloneTransformer(psInfo->pReprojectArg);
1336
1337
0
    if (psClonedInfo->sDstParams.pTransformArg)
1338
0
        psClonedInfo->sDstParams.pTransformArg =
1339
0
            GDALCloneTransformer(psInfo->sDstParams.pTransformArg);
1340
1341
0
    return psClonedInfo;
1342
0
}
1343
1344
/************************************************************************/
1345
/*                  GDALCreateGenImgProjTransformer()                   */
1346
/************************************************************************/
1347
1348
/**
1349
 * Create image to image transformer.
1350
 *
1351
 * This function creates a transformation object that maps from pixel/line
1352
 * coordinates on one image to pixel/line coordinates on another image.  The
1353
 * images may potentially be georeferenced in different coordinate systems,
1354
 * and may used GCPs to map between their pixel/line coordinates and
1355
 * georeferenced coordinates (as opposed to the default assumption that their
1356
 * geotransform should be used).
1357
 *
1358
 * This transformer potentially performs three concatenated transformations.
1359
 *
1360
 * The first stage is from source image pixel/line coordinates to source
1361
 * image georeferenced coordinates, and may be done using the geotransform,
1362
 * or if not defined using a polynomial model derived from GCPs.  If GCPs
1363
 * are used this stage is accomplished using GDALGCPTransform().
1364
 *
1365
 * The second stage is to change projections from the source coordinate system
1366
 * to the destination coordinate system, assuming they differ.  This is
1367
 * accomplished internally using GDALReprojectionTransform().
1368
 *
1369
 * The third stage is converting from destination image georeferenced
1370
 * coordinates to destination image coordinates.  This is done using the
1371
 * destination image geotransform, or if not available, using a polynomial
1372
 * model derived from GCPs. If GCPs are used this stage is accomplished using
1373
 * GDALGCPTransform().  This stage is skipped if hDstDS is NULL when the
1374
 * transformation is created.
1375
 *
1376
 * @param hSrcDS source dataset, or NULL.
1377
 * @param pszSrcWKT the coordinate system for the source dataset.  If NULL,
1378
 * it will be read from the dataset itself.
1379
 * @param hDstDS destination dataset (or NULL).
1380
 * @param pszDstWKT the coordinate system for the destination dataset.  If
1381
 * NULL, and hDstDS not NULL, it will be read from the destination dataset.
1382
 * @param bGCPUseOK TRUE if GCPs should be used if the geotransform is not
1383
 * available on the source dataset (not destination).
1384
 * @param dfGCPErrorThreshold ignored/deprecated.
1385
 * @param nOrder the maximum order to use for GCP derived polynomials if
1386
 * possible.  Use 0 to autoselect, or -1 for thin plate splines.
1387
 *
1388
 * @return handle suitable for use GDALGenImgProjTransform(), and to be
1389
 * deallocated with GDALDestroyGenImgProjTransformer().
1390
 */
1391
1392
void *GDALCreateGenImgProjTransformer(GDALDatasetH hSrcDS,
1393
                                      const char *pszSrcWKT,
1394
                                      GDALDatasetH hDstDS,
1395
                                      const char *pszDstWKT, int bGCPUseOK,
1396
                                      CPL_UNUSED double dfGCPErrorThreshold,
1397
                                      int nOrder)
1398
0
{
1399
0
    char **papszOptions = nullptr;
1400
1401
0
    if (pszSrcWKT != nullptr)
1402
0
        papszOptions = CSLSetNameValue(papszOptions, "SRC_SRS", pszSrcWKT);
1403
0
    if (pszDstWKT != nullptr)
1404
0
        papszOptions = CSLSetNameValue(papszOptions, "DST_SRS", pszDstWKT);
1405
0
    if (!bGCPUseOK)
1406
0
        papszOptions = CSLSetNameValue(papszOptions, "GCPS_OK", "FALSE");
1407
0
    if (nOrder != 0)
1408
0
        papszOptions = CSLSetNameValue(papszOptions, "MAX_GCP_ORDER",
1409
0
                                       CPLString().Printf("%d", nOrder));
1410
1411
0
    void *pRet = GDALCreateGenImgProjTransformer2(hSrcDS, hDstDS, papszOptions);
1412
0
    CSLDestroy(papszOptions);
1413
1414
0
    return pRet;
1415
0
}
1416
1417
/************************************************************************/
1418
/*                          InsertCenterLong()                          */
1419
/*                                                                      */
1420
/*      Insert a CENTER_LONG Extension entry on a GEOGCS to indicate    */
1421
/*      the center longitude of the dataset for wrapping purposes.      */
1422
/************************************************************************/
1423
1424
static void InsertCenterLong(GDALDatasetH hDS, const OGRSpatialReference *poSRS,
1425
                             const OGRSpatialReference *poDstSRS,
1426
                             const char *pszTargetExtent,
1427
                             CPLStringList &aosOptions)
1428
1429
0
{
1430
0
    if (!poSRS->IsGeographic() || std::fabs(poSRS->GetAngularUnits() -
1431
0
                                            CPLAtof(SRS_UA_DEGREE_CONV)) > 1e-9)
1432
0
    {
1433
0
        return;
1434
0
    }
1435
1436
0
    if (poSRS->GetExtension(nullptr, "CENTER_LONG"))
1437
0
        return;
1438
1439
    /* -------------------------------------------------------------------- */
1440
    /*      For now we only do this if we have a geotransform since         */
1441
    /*      other forms require a bunch of extra work.                      */
1442
    /* -------------------------------------------------------------------- */
1443
0
    double adfGeoTransform[6] = {};
1444
1445
0
    if (GDALGetGeoTransform(hDS, adfGeoTransform) != CE_None)
1446
0
        return;
1447
1448
    /* -------------------------------------------------------------------- */
1449
    /*      Compute min/max longitude based on testing the four corners.    */
1450
    /* -------------------------------------------------------------------- */
1451
0
    const int nXSize = GDALGetRasterXSize(hDS);
1452
0
    const int nYSize = GDALGetRasterYSize(hDS);
1453
1454
0
    const double dfMinLong =
1455
0
        std::min(std::min(adfGeoTransform[0] + 0 * adfGeoTransform[1] +
1456
0
                              0 * adfGeoTransform[2],
1457
0
                          adfGeoTransform[0] + nXSize * adfGeoTransform[1] +
1458
0
                              0 * adfGeoTransform[2]),
1459
0
                 std::min(adfGeoTransform[0] + 0 * adfGeoTransform[1] +
1460
0
                              nYSize * adfGeoTransform[2],
1461
0
                          adfGeoTransform[0] + nXSize * adfGeoTransform[1] +
1462
0
                              nYSize * adfGeoTransform[2]));
1463
0
    const double dfMaxLong =
1464
0
        std::max(std::max(adfGeoTransform[0] + 0 * adfGeoTransform[1] +
1465
0
                              0 * adfGeoTransform[2],
1466
0
                          adfGeoTransform[0] + nXSize * adfGeoTransform[1] +
1467
0
                              0 * adfGeoTransform[2]),
1468
0
                 std::max(adfGeoTransform[0] + 0 * adfGeoTransform[1] +
1469
0
                              nYSize * adfGeoTransform[2],
1470
0
                          adfGeoTransform[0] + nXSize * adfGeoTransform[1] +
1471
0
                              nYSize * adfGeoTransform[2]));
1472
1473
    // If the raster covers more than 360 degree, give up,
1474
    // except is the target SRS is geographic and crossing the antimeridian
1475
0
    if (dfMaxLong - dfMinLong > 360.0)
1476
0
    {
1477
0
        const CPLStringList aosTE(CSLTokenizeString2(pszTargetExtent, ",", 0));
1478
0
        if (aosTE.size() == 4 && poDstSRS->IsGeographic() &&
1479
0
            std::fabs(poDstSRS->GetAngularUnits() -
1480
0
                      CPLAtof(SRS_UA_DEGREE_CONV)) <= 1e-9 &&
1481
0
            ((CPLAtof(aosTE[0]) >= -179 && CPLAtof(aosTE[0]) < 180 &&
1482
0
              CPLAtof(aosTE[2]) > 180) ||
1483
0
             (CPLAtof(aosTE[0]) < -180 && CPLAtof(aosTE[2]) > -180 &&
1484
0
              CPLAtof(aosTE[2]) <= 179)))
1485
0
        {
1486
            // insert CENTER_LONG
1487
0
        }
1488
0
        else
1489
0
        {
1490
0
            return;
1491
0
        }
1492
0
    }
1493
1494
    /* -------------------------------------------------------------------- */
1495
    /*      Insert center long.                                             */
1496
    /* -------------------------------------------------------------------- */
1497
0
    const double dfCenterLong = (dfMaxLong + dfMinLong) / 2.0;
1498
0
    aosOptions.SetNameValue("CENTER_LONG", CPLSPrintf("%g", dfCenterLong));
1499
0
}
1500
1501
/************************************************************************/
1502
/*                     GDALComputeAreaOfInterest()                      */
1503
/************************************************************************/
1504
1505
bool GDALComputeAreaOfInterest(const OGRSpatialReference *poSRS,
1506
                               double adfGT[6], int nXSize, int nYSize,
1507
                               double &dfWestLongitudeDeg,
1508
                               double &dfSouthLatitudeDeg,
1509
                               double &dfEastLongitudeDeg,
1510
                               double &dfNorthLatitudeDeg)
1511
0
{
1512
0
    bool ret = false;
1513
1514
0
    if (!poSRS)
1515
0
        return false;
1516
1517
0
    OGRSpatialReference oSrcSRSHoriz(*poSRS);
1518
0
    if (oSrcSRSHoriz.IsCompound())
1519
0
    {
1520
0
        oSrcSRSHoriz.StripVertical();
1521
0
    }
1522
1523
0
    OGRSpatialReference *poGeog = oSrcSRSHoriz.CloneGeogCS();
1524
0
    if (poGeog)
1525
0
    {
1526
0
        poGeog->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
1527
0
        poGeog->SetAngularUnits(SRS_UA_DEGREE, CPLAtof(SRS_UA_DEGREE_CONV));
1528
1529
0
        auto poCT = OGRCreateCoordinateTransformation(&oSrcSRSHoriz, poGeog);
1530
0
        if (poCT)
1531
0
        {
1532
0
            poCT->SetEmitErrors(false);
1533
1534
0
            double x[4], y[4];
1535
0
            x[0] = adfGT[0];
1536
0
            y[0] = adfGT[3];
1537
0
            x[1] = adfGT[0] + nXSize * adfGT[1];
1538
0
            y[1] = adfGT[3];
1539
0
            x[2] = adfGT[0];
1540
0
            y[2] = adfGT[3] + nYSize * adfGT[5];
1541
0
            x[3] = x[1];
1542
0
            y[3] = y[2];
1543
0
            int validity[4] = {false, false, false, false};
1544
0
            poCT->Transform(4, x, y, nullptr, validity);
1545
0
            dfWestLongitudeDeg = std::numeric_limits<double>::max();
1546
0
            dfSouthLatitudeDeg = std::numeric_limits<double>::max();
1547
0
            dfEastLongitudeDeg = -std::numeric_limits<double>::max();
1548
0
            dfNorthLatitudeDeg = -std::numeric_limits<double>::max();
1549
0
            for (int i = 0; i < 4; i++)
1550
0
            {
1551
0
                if (validity[i])
1552
0
                {
1553
0
                    ret = true;
1554
0
                    dfWestLongitudeDeg = std::min(dfWestLongitudeDeg, x[i]);
1555
0
                    dfSouthLatitudeDeg = std::min(dfSouthLatitudeDeg, y[i]);
1556
0
                    dfEastLongitudeDeg = std::max(dfEastLongitudeDeg, x[i]);
1557
0
                    dfNorthLatitudeDeg = std::max(dfNorthLatitudeDeg, y[i]);
1558
0
                }
1559
0
            }
1560
0
            if (validity[0] && validity[1] && x[0] > x[1])
1561
0
            {
1562
0
                dfWestLongitudeDeg = x[0];
1563
0
                dfEastLongitudeDeg = x[1];
1564
0
            }
1565
0
            if (ret && std::fabs(dfWestLongitudeDeg) <= 180 &&
1566
0
                std::fabs(dfEastLongitudeDeg) <= 180 &&
1567
0
                std::fabs(dfSouthLatitudeDeg) <= 90 &&
1568
0
                std::fabs(dfNorthLatitudeDeg) <= 90)
1569
0
            {
1570
0
                CPLDebug("GDAL", "Computing area of interest: %g, %g, %g, %g",
1571
0
                         dfWestLongitudeDeg, dfSouthLatitudeDeg,
1572
0
                         dfEastLongitudeDeg, dfNorthLatitudeDeg);
1573
0
            }
1574
0
            else
1575
0
            {
1576
0
                CPLDebug("GDAL", "Could not compute area of interest");
1577
0
                dfWestLongitudeDeg = 0;
1578
0
                dfSouthLatitudeDeg = 0;
1579
0
                dfEastLongitudeDeg = 0;
1580
0
                dfNorthLatitudeDeg = 0;
1581
0
            }
1582
0
            OGRCoordinateTransformation::DestroyCT(poCT);
1583
0
        }
1584
1585
0
        delete poGeog;
1586
0
    }
1587
1588
0
    return ret;
1589
0
}
1590
1591
bool GDALComputeAreaOfInterest(const OGRSpatialReference *poSRS, double dfX1,
1592
                               double dfY1, double dfX2, double dfY2,
1593
                               double &dfWestLongitudeDeg,
1594
                               double &dfSouthLatitudeDeg,
1595
                               double &dfEastLongitudeDeg,
1596
                               double &dfNorthLatitudeDeg)
1597
0
{
1598
0
    bool ret = false;
1599
1600
0
    if (!poSRS)
1601
0
        return false;
1602
1603
0
    OGRSpatialReference oSrcSRSHoriz(*poSRS);
1604
0
    if (oSrcSRSHoriz.IsCompound())
1605
0
    {
1606
0
        oSrcSRSHoriz.StripVertical();
1607
0
    }
1608
1609
0
    OGRSpatialReference *poGeog = oSrcSRSHoriz.CloneGeogCS();
1610
0
    if (poGeog)
1611
0
    {
1612
0
        poGeog->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
1613
1614
0
        auto poCT = OGRCreateCoordinateTransformation(&oSrcSRSHoriz, poGeog);
1615
0
        if (poCT)
1616
0
        {
1617
0
            double x[4], y[4];
1618
0
            x[0] = dfX1;
1619
0
            y[0] = dfY1;
1620
0
            x[1] = dfX2;
1621
0
            y[1] = dfY1;
1622
0
            x[2] = dfX1;
1623
0
            y[2] = dfY2;
1624
0
            x[3] = dfX2;
1625
0
            y[3] = dfY2;
1626
0
            int validity[4] = {false, false, false, false};
1627
0
            poCT->Transform(4, x, y, nullptr, validity);
1628
0
            dfWestLongitudeDeg = std::numeric_limits<double>::max();
1629
0
            dfSouthLatitudeDeg = std::numeric_limits<double>::max();
1630
0
            dfEastLongitudeDeg = -std::numeric_limits<double>::max();
1631
0
            dfNorthLatitudeDeg = -std::numeric_limits<double>::max();
1632
0
            for (int i = 0; i < 4; i++)
1633
0
            {
1634
0
                if (validity[i])
1635
0
                {
1636
0
                    ret = true;
1637
0
                    dfWestLongitudeDeg = std::min(dfWestLongitudeDeg, x[i]);
1638
0
                    dfSouthLatitudeDeg = std::min(dfSouthLatitudeDeg, y[i]);
1639
0
                    dfEastLongitudeDeg = std::max(dfEastLongitudeDeg, x[i]);
1640
0
                    dfNorthLatitudeDeg = std::max(dfNorthLatitudeDeg, y[i]);
1641
0
                }
1642
0
            }
1643
0
            if (validity[0] && validity[1] && (dfX1 - dfX2) * (x[0] - x[1]) < 0)
1644
0
            {
1645
0
                dfWestLongitudeDeg = x[0];
1646
0
                dfEastLongitudeDeg = x[1];
1647
0
            }
1648
0
            if (ret)
1649
0
            {
1650
0
                CPLDebug("GDAL", "Computing area of interest: %g, %g, %g, %g",
1651
0
                         dfWestLongitudeDeg, dfSouthLatitudeDeg,
1652
0
                         dfEastLongitudeDeg, dfNorthLatitudeDeg);
1653
0
            }
1654
0
            else
1655
0
            {
1656
0
                CPLDebug("GDAL", "Could not compute area of interest");
1657
0
                dfWestLongitudeDeg = 0;
1658
0
                dfSouthLatitudeDeg = 0;
1659
0
                dfEastLongitudeDeg = 0;
1660
0
                dfNorthLatitudeDeg = 0;
1661
0
            }
1662
0
            delete poCT;
1663
0
        }
1664
1665
0
        delete poGeog;
1666
0
    }
1667
1668
0
    return ret;
1669
0
}
1670
1671
/************************************************************************/
1672
/*                     GDALGCPAntimeridianUnwrap()                      */
1673
/************************************************************************/
1674
1675
/* Deal with discontinuties of dfGCPX longitudes around the anti-meridian.
1676
 * Cf https://github.com/OSGeo/gdal/issues/8371
1677
 */
1678
static void GDALGCPAntimeridianUnwrap(int nGCPCount, GDAL_GCP *pasGCPList,
1679
                                      const OGRSpatialReference &oSRS,
1680
                                      CSLConstList papszOptions)
1681
0
{
1682
0
    const char *pszGCPAntimeridianUnwrap =
1683
0
        CSLFetchNameValueDef(papszOptions, "GCP_ANTIMERIDIAN_UNWRAP", "AUTO");
1684
0
    const bool bForced = EQUAL(pszGCPAntimeridianUnwrap, "YES") ||
1685
0
                         EQUAL(pszGCPAntimeridianUnwrap, "ON") ||
1686
0
                         EQUAL(pszGCPAntimeridianUnwrap, "TRUE") ||
1687
0
                         EQUAL(pszGCPAntimeridianUnwrap, "1");
1688
0
    if (bForced || (!oSRS.IsEmpty() && oSRS.IsGeographic() &&
1689
0
                    fabs(oSRS.GetAngularUnits(nullptr) -
1690
0
                         CPLAtof(SRS_UA_DEGREE_CONV)) < 1e-8 &&
1691
0
                    EQUAL(pszGCPAntimeridianUnwrap, "AUTO")))
1692
0
    {
1693
0
        if (!bForced)
1694
0
        {
1695
            // Proceed to unwrapping only if the longitudes are within
1696
            // [-180, -170] or [170, 180]
1697
0
            for (int i = 0; i < nGCPCount; ++i)
1698
0
            {
1699
0
                const double dfLongAbs = fabs(pasGCPList[i].dfGCPX);
1700
0
                if (dfLongAbs > 180 || dfLongAbs < 170)
1701
0
                {
1702
0
                    return;
1703
0
                }
1704
0
            }
1705
0
        }
1706
1707
0
        bool bDone = false;
1708
0
        for (int i = 0; i < nGCPCount; ++i)
1709
0
        {
1710
0
            if (pasGCPList[i].dfGCPX < 0)
1711
0
            {
1712
0
                if (!bDone)
1713
0
                {
1714
0
                    bDone = true;
1715
0
                    CPLDebug("WARP", "GCP longitude unwrapping");
1716
0
                }
1717
0
                pasGCPList[i].dfGCPX += 360;
1718
0
            }
1719
0
        }
1720
0
    }
1721
0
}
1722
1723
/************************************************************************/
1724
/*               GDALGetGenImgProjTranformerOptionList()                */
1725
/************************************************************************/
1726
1727
/** Return a XML string describing options accepted by
1728
 * GDALCreateGenImgProjTransformer2().
1729
 *
1730
 * @since 3.11
1731
 */
1732
const char *GDALGetGenImgProjTranformerOptionList(void)
1733
0
{
1734
0
    return "<OptionList>"
1735
0
           "<Option name='SRC_SRS' type='string' description='WKT SRS, or any "
1736
0
           "string recognized by OGRSpatialReference::SetFromUserInput(), to "
1737
0
           "be used as an override for CRS of input dataset'/>"
1738
0
           "<Option name='DST_SRS' type='string' description='WKT SRS, or any "
1739
0
           "string recognized by OGRSpatialReference::SetFromUserInput(), to "
1740
0
           "be used as an override for CRS of output dataset'/>"
1741
0
           "<Option name='PROMOTE_TO_3D' type='boolean' description='"
1742
0
           "Whether to promote SRC_SRS / DST_SRS to 3D.' "
1743
0
           "default='NO'/>"
1744
0
           "<Option name='COORDINATE_OPERATION' type='string' description='"
1745
0
           "Coordinate operation, as a PROJ or WKT string, used as an override "
1746
0
           "over the normally computed pipeline. The pipeline must take into "
1747
0
           "account the axis order of the source and target SRS.'/>"
1748
0
           "<Option name='ALLOW_BALLPARK' type='boolean' description='"
1749
0
           "Whether ballpark coordinate operations are allowed.' "
1750
0
           "default='YES'/>"
1751
0
           "<Option name='ONLY_BEST' type='string-select' "
1752
0
           "description='"
1753
0
           "By default (at least in the PROJ 9.x series), PROJ may use "
1754
0
           "coordinate operations that are not the \"best\" if resources "
1755
0
           "(typically grids) needed to use them are missing. It will then "
1756
0
           "fallback to other coordinate operations that have a lesser "
1757
0
           "accuracy, for example using Helmert transformations, or in the "
1758
0
           "absence of such operations, to ones with potential very rough "
1759
0
           " accuracy, using \"ballpark\" transformations (see "
1760
0
           "https://proj.org/glossary.html). "
1761
0
           "When calling this method with YES, PROJ will only consider the "
1762
0
           "\"best\" operation, and error out (at Transform() time) if they "
1763
0
           "cannot be used. This method may be used together with "
1764
0
           "ALLOW_BALLPARK=NO to only allow best operations that have a known "
1765
0
           "accuracy. Note that this method has no effect on PROJ versions "
1766
0
           "before 9.2. The default value for this option can be also set with "
1767
0
           "the PROJ_ONLY_BEST_DEFAULT environment variable, or with the "
1768
0
           "\"only_best_default\" setting of proj.ini. Setting "
1769
0
           "ONLY_BEST=YES/NO overrides such default value' default='AUTO'>"
1770
0
           "  <Value>AUTO</Value>"
1771
0
           "  <Value>YES</Value>"
1772
0
           "  <Value>NO</Value>"
1773
0
           "</Option>"
1774
0
           "<Option name='COORDINATE_EPOCH' type='float' description='"
1775
0
           "Coordinate epoch, expressed as a decimal year. Useful for "
1776
0
           "time-dependent coordinate operations.'/>"
1777
0
           "<Option name='SRC_COORDINATE_EPOCH' type='float' description='"
1778
0
           "Coordinate epoch of source CRS, expressed as a decimal year. "
1779
0
           "Useful for time-dependent coordinate operations.'/>"
1780
0
           "<Option name='DST_COORDINATE_EPOCH' type='float' description='"
1781
0
           "Coordinate epoch of target CRS, expressed as a decimal year. "
1782
0
           "Useful for time-dependent coordinate operations.'/>"
1783
0
           "<Option name='GCPS_OK' type='boolean' description='"
1784
0
           "Allow use of GCPs.' default='YES'/>"
1785
0
           "<Option name='REFINE_MINIMUM_GCPS' type='int' description='"
1786
0
           "The minimum amount of GCPs that should be available after the "
1787
0
           "refinement'/>"
1788
0
           "<Option name='REFINE_TOLERANCE' type='float' description='"
1789
0
           "The tolerance that specifies when a GCP will be eliminated.'/>"
1790
0
           "<Option name='MAX_GCP_ORDER' type='int' description='"
1791
0
           "The maximum order to use for GCP derived polynomials if possible. "
1792
0
           "The default is to autoselect based on the number of GCPs. A value "
1793
0
           "of -1 triggers use of Thin Plate Spline instead of polynomials.'/>"
1794
0
           "<Option name='GCP_ANTIMERIDIAN_UNWRAP' type='string-select' "
1795
0
           "description='"
1796
0
           "Whether to \"unwrap\" longitudes of ground control points that "
1797
0
           "span the antimeridian. For datasets with GCPs in "
1798
0
           "longitude/latitude coordinate space spanning the antimeridian, "
1799
0
           "longitudes will have a discontinuity on +/- 180 deg, and will "
1800
0
           "result in a subset of the GCPs with longitude in the [-180,-170] "
1801
0
           "range and another subset in [170, 180]. By default (AUTO), that "
1802
0
           "situation will be detected and longitudes in [-180,-170] will be "
1803
0
           "shifted to [180, 190] to get a continuous set. This option can be "
1804
0
           "set to YES to force that behavior (useful if no SRS information is "
1805
0
           "available), or to NO to disable it.' default='AUTO'>"
1806
0
           "  <Value>AUTO</Value>"
1807
0
           "  <Value>YES</Value>"
1808
0
           "  <Value>NO</Value>"
1809
0
           "</Option>"
1810
0
           "<Option name='SRC_METHOD' alias='METHOD' type='string-select' "
1811
0
           "description='"
1812
0
           "Force only one geolocation method to be considered on the source "
1813
0
           "dataset. Will be used for pixel/line to georef transformation on "
1814
0
           "the source dataset. NO_GEOTRANSFORM can be used to specify the "
1815
0
           "identity geotransform (ungeoreferenced image)'>"
1816
0
           "  <Value>GEOTRANSFORM</Value>"
1817
0
           "  <Value>GCP_POLYNOMIAL</Value>"
1818
0
           "  <Value>GCP_TPS</Value>"
1819
0
           "  <Value>GCP_HOMOGRAPHY</Value>"
1820
0
           "  <Value>GEOLOC_ARRAY</Value>"
1821
0
           "  <Value>RPC</Value>"
1822
0
           "  <Value>NO_GEOTRANSFORM</Value>"
1823
0
           "</Option>"
1824
0
           "<Option name='DST_METHOD' type='string-select' description='"
1825
0
           "Force only one geolocation method to be considered on the target "
1826
0
           "dataset. Will be used for pixel/line to georef transformation on "
1827
0
           "the targe dataset. NO_GEOTRANSFORM can be used to specify the "
1828
0
           "identity geotransform (ungeoreferenced image)'>"
1829
0
           "  <Value>GEOTRANSFORM</Value>"
1830
0
           "  <Value>GCP_POLYNOMIAL</Value>"
1831
0
           "  <Value>GCP_TPS</Value>"
1832
0
           "  <Value>GCP_HOMOGRAPHY</Value>"
1833
0
           "  <Value>GEOLOC_ARRAY</Value>"
1834
0
           "  <Value>RPC</Value>"
1835
0
           "  <Value>NO_GEOTRANSFORM</Value>"
1836
0
           "</Option>"
1837
0
           "<Option name='RPC_HEIGHT' type='float' description='"
1838
0
           "A fixed height to be used with RPC calculations. If RPC_HEIGHT and "
1839
0
           "RPC_DEM are not specified but that the RPC metadata domain contains"
1840
0
           " a HEIGHT_DEFAULT item (for example, the DIMAP driver may fill it),"
1841
0
           "this value will be used as the RPC_HEIGHT. Otherwise, if none of "
1842
0
           "RPC_HEIGHT and RPC_DEM are specified as transformer options and "
1843
0
           "if HEIGHT_DEFAULT is no available, a height of 0 will be used.'/>"
1844
0
           "<Option name='RPC_DEM' type='string' description='"
1845
0
           "Name of a GDAL dataset (a DEM file typically) used to extract "
1846
0
           "elevation offsets from. In this situation the Z passed into the "
1847
0
           "transformation function is assumed to be height above ground. "
1848
0
           "This option should be used in replacement of RPC_HEIGHT to provide "
1849
0
           "a way of defining a non uniform ground for the target scene.'/>"
1850
0
           "<Option name='RPC_HEIGHT_SCALE' type='float' description='"
1851
0
           "Factor used to multiply heights above ground. Useful when "
1852
0
           "elevation offsets of the DEM are not expressed in meters.'/>"
1853
0
           "<Option name='RPC_DEMINTERPOLATION' type='string-select' "
1854
0
           "description='DEM interpolation method' default='BILINEAR'>"
1855
0
           "  <Value>NEAR</Value>"
1856
0
           "  <Value>BILINEAR</Value>"
1857
0
           "  <Value>CUBIC</Value>"
1858
0
           "</Option>"
1859
0
           "<Option name='RPC_DEM_MISSING_VALUE' type='float' description='"
1860
0
           "Value of DEM height that must be used in case the DEM has nodata "
1861
0
           "value at the sampling point, or if its extent does not cover the "
1862
0
           "requested coordinate. When not specified, missing values will "
1863
0
           "cause a failed transform.'/>"
1864
0
           "<Option name='RPC_DEM_SRS' type='string' description='"
1865
0
           "WKT SRS, or any string recognized by "
1866
0
           "OGRSpatialReference::SetFromUserInput(), to be used as an "
1867
0
           "override for DEM SRS. Useful if DEM SRS does not have an explicit "
1868
0
           "vertical component.'/>"
1869
0
           "<Option name='RPC_DEM_APPLY_VDATUM_SHIFT' type='boolean' "
1870
0
           "description='"
1871
0
           "Whether the vertical component of a compound SRS for the DEM "
1872
0
           "should be used (when it is present). This is useful so as to "
1873
0
           "be able to transform the raw values from the DEM expressed with "
1874
0
           "respect to a geoid to the heights with respect to the WGS84 "
1875
0
           "ellipsoid. When this is enabled, the GTIFF_REPORT_COMPD_CS "
1876
0
           "configuration option will be also set temporarily so as to get "
1877
0
           "the vertical information from GeoTIFF files.' default='YES'/>"
1878
0
           "<Option name='RPC_PIXEL_ERROR_THRESHOLD' type='float' description='"
1879
0
           "Overrides the dfPixErrThreshold parameter, i.e. the error "
1880
0
           "(measured in pixels) allowed in the iterative solution of "
1881
0
           "pixel/line to lat/long computations (the other way is always "
1882
0
           "exact given the equations).'/>"
1883
0
           "<Option name='RPC_MAX_ITERATIONS' type='int' description='"
1884
0
           "Maximum number of iterations allowed in the iterative solution of "
1885
0
           "pixel/line to lat/long computations. Default value is 10 in the "
1886
0
           "absence of a DEM, or 20 if there is a DEM.'/>"
1887
0
           "<Option name='RPC_FOOTPRINT' type='string' description='"
1888
0
           "WKT or GeoJSON polygon (in long / lat coordinate space) with a "
1889
0
           "validity footprint for the RPC. Any coordinate transformation that "
1890
0
           "goes from or arrive outside this footprint will be considered "
1891
0
           "invalid. This* is useful in situations where the RPC values become "
1892
0
           "highly unstable outside of the area on which they have been "
1893
0
           "computed for, potentially leading to undesirable \"echoes\" / "
1894
0
           "false positives. This requires GDAL to be built against GEOS..'/>"
1895
0
           "<Option name='RPC_MAX_ITERATIONS' type='int' description='"
1896
0
           "Maximum number of iterations allowed in the iterative solution of "
1897
0
           "pixel/line to lat/long computations. Default value is 10 in the "
1898
0
           "absence of a DEM, or 20 if there is a DEM.'/>"
1899
0
           "<Option name='INSERT_CENTER_LONG' type='boolean' description='"
1900
0
           "May be set to FALSE to disable setting up a CENTER_LONG value on "
1901
0
           "the coordinate system to rewrap things around the center of the "
1902
0
           "image.' default='YES'/>"
1903
0
           "<Option name='SRC_APPROX_ERROR_IN_SRS_UNIT' type='float' "
1904
0
           "description='"
1905
0
           "Use an approximate transformer for the source transformer. Must be "
1906
0
           "defined together with SRC_APPROX_ERROR_IN_PIXEL to be taken into "
1907
0
           "account.'/>"
1908
0
           "<Option name='SRC_APPROX_ERROR_IN_PIXEL' type='float' "
1909
0
           "description='"
1910
0
           "Use an approximate transformer for the source transformer. Must be "
1911
0
           "defined together with SRC_APPROX_ERROR_IN_SRS_UNIT to be taken "
1912
0
           "into "
1913
0
           "account.'/>"
1914
0
           "<Option name='DST_APPROX_ERROR_IN_SRS_UNIT' type='float' "
1915
0
           "description='"
1916
0
           "Use an approximate transformer for the target transformer. Must be "
1917
0
           "defined together with DST_APPROX_ERROR_IN_PIXEL to be taken into "
1918
0
           "account.'/>"
1919
0
           "<Option name='DST_APPROX_ERROR_IN_PIXEL' type='float' "
1920
0
           "description='"
1921
0
           "Use an approximate transformer for the target transformer. Must be "
1922
0
           "defined together with DST_APPROX_ERROR_IN_SRS_UNIT to be taken "
1923
0
           "into "
1924
0
           "account.'/>"
1925
0
           "<Option name='REPROJECTION_APPROX_ERROR_IN_SRC_SRS_UNIT' "
1926
0
           "type='float' "
1927
0
           "description='"
1928
0
           "Use an approximate transformer for the coordinate reprojection. "
1929
0
           "Must be used together with "
1930
0
           "REPROJECTION_APPROX_ERROR_IN_DST_SRS_UNIT to be taken into "
1931
0
           "account.'/>"
1932
0
           "<Option name='REPROJECTION_APPROX_ERROR_IN_DST_SRS_UNIT' "
1933
0
           "type='float' "
1934
0
           "description='"
1935
0
           "Use an approximate transformer for the coordinate reprojection. "
1936
0
           "Must be used together with "
1937
0
           "REPROJECTION_APPROX_ERROR_IN_SRC_SRS_UNIT to be taken into "
1938
0
           "account.'/>"
1939
0
           "<Option name='AREA_OF_INTEREST' type='string' "
1940
0
           "description='"
1941
0
           "Area of interest, as "
1942
0
           "west_lon_deg,south_lat_deg,east_lon_deg,north_lat_deg, used to "
1943
0
           "compute the best coordinate operation between the source and "
1944
0
           "target SRS. If not specified, the bounding box of the source "
1945
0
           "raster will be used.'/>"
1946
0
           "<Option name='TARGET_EXTENT' type='string' "
1947
0
           "description='Target extent as minx,miny,maxx,maxy expressed in "
1948
0
           "target SRS.'/>"
1949
0
           "<Option name='GEOLOC_BACKMAP_OVERSAMPLE_FACTOR' type='float' "
1950
0
           "min='0.1' max='2' description='"
1951
0
           "Oversample factor used to derive the size of the \"backmap\" used "
1952
0
           "for geolocation array transformers.' default='1.3'/>"
1953
0
           "<Option name='GEOLOC_USE_TEMP_DATASETS' type='boolean' "
1954
0
           "description='"
1955
0
           "Whether temporary GeoTIFF datasets should be used to store the "
1956
0
           "backmap. The default is NO, that is to use in-memory arrays, "
1957
0
           "unless the number of pixels of the geolocation array is greater "
1958
0
           "than 16 megapixels.' default='NO'/>"
1959
0
           "<Option name='GEOLOC_ARRAY' alias='SRC_GEOLOC_ARRAY' type='string' "
1960
0
           "description='"
1961
0
           "Name of a GDAL dataset containing a geolocation array and "
1962
0
           "associated metadata. This is an alternative to having geolocation "
1963
0
           "information described in the GEOLOCATION metadata domain of the "
1964
0
           "source dataset. The dataset specified may have a GEOLOCATION "
1965
0
           "metadata domain containing appropriate metadata, however default "
1966
0
           "values are assigned for all omitted items. X_BAND defaults to 1 "
1967
0
           "and Y_BAND to 2, however the dataset must contain exactly 2 bands. "
1968
0
           "PIXEL_OFFSET and LINE_OFFSET default to 0. PIXEL_STEP and "
1969
0
           "LINE_STEP default to the ratio of the width/height of the source "
1970
0
           "dataset divided by the with/height of the geolocation array. "
1971
0
           "SRS defaults to the spatial reference system of the geolocation "
1972
0
           "array dataset, if set, otherwise WGS84 is used. "
1973
0
           "GEOREFERENCING_CONVENTION is selected from the main metadata "
1974
0
           "domain if it is omitted from the GEOLOCATION domain, and if not "
1975
0
           "available TOP_LEFT_CORNER is assigned as a default. "
1976
0
           "If GEOLOC_ARRAY is set SRC_METHOD defaults to GEOLOC_ARRAY.'/>"
1977
0
           "<Option name='DST_GEOLOC_ARRAY' type='string' "
1978
0
           "description='"
1979
0
           "Name of a GDAL dataset that contains at least 2 bands with the X "
1980
0
           "and Y geolocation bands. This is an alternative to having "
1981
0
           "geolocation information described in the GEOLOCATION metadata "
1982
0
           "domain of the destination dataset. See SRC_GEOLOC_ARRAY "
1983
0
           "description for details, assumptions, and defaults. If this "
1984
0
           "option is set, DST_METHOD=GEOLOC_ARRAY will be assumed if not "
1985
0
           "set.'/>"
1986
0
           "<Option name='GEOLOC_NORMALIZE_LONGITUDE_MINUS_180_PLUS_180' "
1987
0
           "type='boolean' "
1988
0
           "description='"
1989
0
           "Force geolocation longitudes into -180,180 when longitude/latitude "
1990
0
           "is the coordinate system of the geolocation arrays' default='NO'>"
1991
0
           "  <Value>YES</Value>"
1992
0
           "  <Value>NO</Value>"
1993
0
           "</Option>"
1994
0
           "<Option name='NUM_THREADS' type='string' "
1995
0
           "description='Number of threads to use'/>"
1996
0
           "</OptionList>";
1997
0
}
1998
1999
/************************************************************************/
2000
/*                  GDALCreateGenImgProjTransformer2()                  */
2001
/************************************************************************/
2002
2003
/* clang-format off */
2004
/**
2005
 * Create image to image transformer.
2006
 *
2007
 * This function creates a transformation object that maps from pixel/line
2008
 * coordinates on one image to pixel/line coordinates on another image.  The
2009
 * images may potentially be georeferenced in different coordinate systems,
2010
 * and may used GCPs to map between their pixel/line coordinates and
2011
 * georeferenced coordinates (as opposed to the default assumption that their
2012
 * geotransform should be used).
2013
 *
2014
 * This transformer potentially performs three concatenated transformations.
2015
 *
2016
 * The first stage is from source image pixel/line coordinates to source
2017
 * image georeferenced coordinates, and may be done using the geotransform,
2018
 * or if not defined using a polynomial model derived from GCPs.  If GCPs
2019
 * are used this stage is accomplished using GDALGCPTransform().
2020
 *
2021
 * The second stage is to change projections from the source coordinate system
2022
 * to the destination coordinate system, assuming they differ.  This is
2023
 * accomplished internally using GDALReprojectionTransform().
2024
 *
2025
 * The third stage is converting from destination image georeferenced
2026
 * coordinates to destination image coordinates.  This is done using the
2027
 * destination image geotransform, or if not available, using a polynomial
2028
 * model derived from GCPs. If GCPs are used this stage is accomplished using
2029
 * GDALGCPTransform().  This stage is skipped if hDstDS is NULL when the
2030
 * transformation is created.
2031
 *
2032
 * Supported Options (specified with the -to switch of gdalwarp for example):
2033
 * <ul>
2034
 * <li> SRC_SRS: WKT SRS, or any string recognized by
2035
 * OGRSpatialReference::SetFromUserInput(), to be used as an override for
2036
 * hSrcDS.</li>
2037
 * <li> DST_SRS: WKT SRS, or any string recognized by
2038
 * OGRSpatialReference::SetFromUserInput(),  to be used as an override for
2039
 * hDstDS.
2040
 * </li>
2041
 * <li>PROMOTE_TO_3D=YES/NO: whether to promote SRC_SRS / DST_SRS to 3D.
2042
 * Default is NO</li>
2043
 * <li> COORDINATE_OPERATION: (GDAL &gt;= 3.0) Coordinate operation, as
2044
 * a PROJ or WKT string, used as an override over the normally computed
2045
 * pipeline. The pipeline must take into account the axis order of the source
2046
 * and target SRS.
2047
 * </li>
2048
 * <li> ALLOW_BALLPARK=YES/NO: (GDAL &gt;= 3.11) Whether ballpark coordinate
2049
 * operations are allowed. Defaults to YES.</li>
2050
 * <li> ONLY_BEST=YES/NO/AUTO: (GDAL &gt;= 3.11) By default (at least in the
2051
 * PROJ 9.x series), PROJ may use coordinate
2052
 * operations that are not the "best" if resources (typically grids) needed
2053
 * to use them are missing. It will then fallback to other coordinate operations
2054
 * that have a lesser accuracy, for example using Helmert transformations,
2055
 * or in the absence of such operations, to ones with potential very rough
2056
 * accuracy, using "ballpark" transformations
2057
 * (see https://proj.org/glossary.html).
2058
 * When calling this method with YES, PROJ will only consider the
2059
 * "best" operation, and error out (at Transform() time) if they cannot be
2060
 * used.
2061
 * This method may be used together with ALLOW_BALLPARK=NO to
2062
 * only allow best operations that have a known accuracy.
2063
 * Note that this method has no effect on PROJ versions before 9.2.
2064
 * The default value for this option can be also set with the
2065
 * PROJ_ONLY_BEST_DEFAULT environment variable, or with the "only_best_default"
2066
 * setting of proj.ini. Calling SetOnlyBest() overrides such default value.</li>
2067
 * <li> COORDINATE_EPOCH: (GDAL &gt;= 3.0) Coordinate epoch,
2068
 * expressed as a decimal year. Useful for time-dependent coordinate operations.
2069
 * </li>
2070
 * <li> SRC_COORDINATE_EPOCH: (GDAL &gt;= 3.4) Coordinate epoch of source CRS,
2071
 * expressed as a decimal year. Useful for time-dependent coordinate operations.
2072
 * </li>
2073
 * <li> DST_COORDINATE_EPOCH: (GDAL &gt;= 3.4) Coordinate epoch of target CRS,
2074
 * expressed as a decimal year. Useful for time-dependent coordinate operations.
2075
 * </li>
2076
 * <li> GCPS_OK: If false, GCPs will not be used, default is TRUE.
2077
 * </li>
2078
 * <li> REFINE_MINIMUM_GCPS: The minimum amount of GCPs that should be available
2079
 * after the refinement.
2080
 * </li>
2081
 * <li> REFINE_TOLERANCE: The tolerance that specifies when a GCP will be
2082
 * eliminated.
2083
 * </li>
2084
 * <li> MAX_GCP_ORDER: the maximum order to use for GCP derived polynomials if
2085
 * possible.  The default is to autoselect based on the number of GCPs.
2086
 * A value of -1 triggers use of Thin Plate Spline instead of polynomials if
2087
 * SRC_METHOD/DST_METHOD is not specified.
2088
 * </li>
2089
 * <li>GCP_ANTIMERIDIAN_UNWRAP=AUTO/YES/NO. (GDAL &gt;= 3.8) Whether to
2090
 * "unwrap" longitudes of ground control points that span the antimeridian.
2091
 * For datasets with GCPs in longitude/latitude coordinate space spanning the
2092
 * antimeridian, longitudes will have a discontinuity on +/- 180 deg, and
2093
 * will result in a subset of the GCPs with longitude in the [-180,-170] range
2094
 * and another subset in [170, 180]. By default (AUTO), that situation will be
2095
 * detected and longitudes in [-180,-170] will be shifted to [180, 190] to get
2096
 * a continuous set. This option can be set to YES to force that behavior
2097
 * (useful if no SRS information is available), or to NO to disable it.
2098
 * </li>
2099
 * <li> SRC_METHOD: may have a value which is one of GEOTRANSFORM, GCP_HOMOGRAPHY,
2100
 * GCP_POLYNOMIAL, GCP_TPS, GEOLOC_ARRAY, RPC to force only one geolocation
2101
 * method to be considered on the source dataset. Will be used for pixel/line
2102
 * to georef transformation on the source dataset. NO_GEOTRANSFORM can be
2103
 * used to specify the identity geotransform (ungeoreferenced image)
2104
 * Note that using GCP_TPS with more than a few thousand GCPs requires significant RAM usage
2105
 * (at least numGCPs * numGCPs * 8 bytes) and processing time.
2106
 * </li>
2107
 * <li> DST_METHOD: may have a value which is one of GEOTRANSFORM,
2108
 * GCP_POLYNOMIAL, GCP_HOMOGRAPHY, GCP_TPS, GEOLOC_ARRAY (added in 3.5), RPC to
2109
 * force only one
2110
 * geolocation method to be considered on the target dataset.  Will be used for
2111
 * pixel/line to georef transformation on the destination dataset.
2112
 * NO_GEOTRANSFORM can be used to specify the identity geotransform
2113
 * (ungeoreferenced image)
2114
 * Note that using GCP_TPS with more than a few thousand GCPs requires significant RAM usage
2115
 * (at least numGCPs * numGCPs * 8 bytes) and processing time.
2116
 * </li>
2117
 * <li> RPC_HEIGHT: A fixed height to be used with RPC
2118
 * calculations. If RPC_HEIGHT and RPC_DEM are not specified but that the RPC
2119
 * metadata domain contains a HEIGHT_DEFAULT item (for example, the DIMAP driver
2120
 * may fill it), this value will be used as the RPC_HEIGHT. Otherwise, if none
2121
 * of RPC_HEIGHT and RPC_DEM are specified as transformer
2122
 * options and if HEIGHT_DEFAULT is no available, a height of 0 will be used.
2123
 * </li>
2124
 * <li> RPC_DEM: The name of a DEM file to be used with RPC
2125
 * calculations. See GDALCreateRPCTransformerV2() for more details.
2126
 * </li>
2127
 * <li> Other RPC related options. See GDALCreateRPCTransformerV2()
2128
 * </li>
2129
 * <li>
2130
 * INSERT_CENTER_LONG: May be set to FALSE to disable setting up a CENTER_LONG
2131
 * value on the coordinate system to rewrap things around the center of the
2132
 * image.
2133
 * </li>
2134
 * <li> SRC_APPROX_ERROR_IN_SRS_UNIT=err_threshold_in_SRS_units. (GDAL
2135
 * &gt;= 2.2) Use an approximate transformer for the source transformer. Must be
2136
 * defined together with SRC_APPROX_ERROR_IN_PIXEL to be taken into account.
2137
 * </li>
2138
 * <li> SRC_APPROX_ERROR_IN_PIXEL=err_threshold_in_pixel. (GDAL &gt;= 2.2) Use
2139
 * an approximate transformer for the source transformer.. Must be defined
2140
 * together with SRC_APPROX_ERROR_IN_SRS_UNIT to be taken into account.
2141
 * </li>
2142
 * <li>
2143
 * DST_APPROX_ERROR_IN_SRS_UNIT=err_threshold_in_SRS_units. (GDAL &gt;= 2.2) Use
2144
 * an approximate transformer for the destination transformer. Must be defined
2145
 * together with DST_APPROX_ERROR_IN_PIXEL to be taken into account.
2146
 * </li>
2147
 * <li>
2148
 * DST_APPROX_ERROR_IN_PIXEL=err_threshold_in_pixel. (GDAL &gt;= 2.2) Use an
2149
 * approximate transformer for the destination transformer. Must be defined
2150
 * together with DST_APPROX_ERROR_IN_SRS_UNIT to be taken into account.
2151
 * </li>
2152
 * <li>
2153
 * REPROJECTION_APPROX_ERROR_IN_SRC_SRS_UNIT=err_threshold_in_src_SRS_units.
2154
 * (GDAL &gt;= 2.2) Use an approximate transformer for the coordinate
2155
 * reprojection. Must be used together with
2156
 * REPROJECTION_APPROX_ERROR_IN_DST_SRS_UNIT to be taken into account.
2157
 * </li>
2158
 * <li>
2159
 * REPROJECTION_APPROX_ERROR_IN_DST_SRS_UNIT=err_threshold_in_dst_SRS_units.
2160
 * (GDAL &gt;= 2.2) Use an approximate transformer for the coordinate
2161
 * reprojection. Must be used together with
2162
 * REPROJECTION_APPROX_ERROR_IN_SRC_SRS_UNIT to be taken into account.
2163
 * </li>
2164
 * <li>
2165
 * AREA_OF_INTEREST=west_lon_deg,south_lat_deg,east_lon_deg,north_lat_deg. (GDAL
2166
 * &gt;= 3.0) Area of interest, used to compute the best coordinate operation
2167
 * between the source and target SRS. If not specified, the bounding box of the
2168
 * source raster will be used.
2169
 * </li>
2170
 * <li> GEOLOC_BACKMAP_OVERSAMPLE_FACTOR=[0.1,2]. (GDAL &gt;= 3.5) Oversample
2171
 * factor used to derive the size of the "backmap" used for geolocation array
2172
 * transformers. Default value is 1.3.
2173
 * </li>
2174
 * <li> GEOLOC_USE_TEMP_DATASETS=YES/NO.
2175
 * (GDAL &gt;= 3.5) Whether temporary GeoTIFF datasets should be used to store
2176
 * the backmap. The default is NO, that is to use in-memory arrays, unless the
2177
 * number of pixels of the geolocation array is greater than 16 megapixels.
2178
 * </li>
2179
 * <li>
2180
 * GEOLOC_ARRAY/SRC_GEOLOC_ARRAY=filename. (GDAL &gt;= 3.5.2) Name of a GDAL
2181
 * dataset containing a geolocation array and associated metadata. This is an
2182
 * alternative to having geolocation information described in the GEOLOCATION
2183
 * metadata domain of the source dataset. The dataset specified may have a
2184
 * GEOLOCATION metadata domain containing appropriate metadata, however default
2185
 * values are assigned for all omitted items. X_BAND defaults to 1 and Y_BAND to
2186
 * 2, however the dataset must contain exactly 2 bands. PIXEL_OFFSET and
2187
 * LINE_OFFSET default to 0. PIXEL_STEP and LINE_STEP default to the ratio of
2188
 * the width/height of the source dataset divided by the with/height of the
2189
 * geolocation array. SRS defaults to the geolocation array dataset's spatial
2190
 * reference system if set, otherwise WGS84 is used.
2191
 * GEOREFERENCING_CONVENTION is selected from the main metadata domain if it
2192
 * is omitted from the GEOLOCATION domain, and if not available
2193
 * TOP_LEFT_CORNER is assigned as a default.
2194
 * If GEOLOC_ARRAY is set SRC_METHOD
2195
 * defaults to GEOLOC_ARRAY.
2196
 * </li>
2197
 * <li>DST_GEOLOC_ARRAY=filename. (GDAL &gt;= 3.5.2) Name of a
2198
 * GDAL dataset that contains at least 2 bands with the X and Y geolocation
2199
 * bands. This is an alternative to having geolocation information described in
2200
 * the GEOLOCATION metadata domain of the destination dataset. See
2201
 * SRC_GEOLOC_ARRAY description for details, assumptions, and defaults. If this
2202
 * option is set, DST_METHOD=GEOLOC_ARRAY will be assumed if not set.
2203
 * </li>
2204
 * <li>GEOLOC_NORMALIZE_LONGITUDE_MINUS_180_PLUS_180=YES/NO. (GDAL &gt;= 3.12.0)
2205
 * Whether to force geolocation longitudes into -180,180 when longitude/latitude is
2206
 * the coordinate system of the geolocation arrays. The default is to enable this mode
2207
 * when the values in the geolocation array are in the -180,180, otherwise NO.
2208
 * </li>
2209
 * </ul>
2210
 *
2211
 * The use case for the *_APPROX_ERROR_* options is when defining an approximate
2212
 * transformer on top of the GenImgProjTransformer globally is not practical.
2213
 * Such a use case is when the source dataset has RPC with a RPC DEM. In such
2214
 * case we don't want to use the approximate transformer on the RPC
2215
 * transformation, as the RPC DEM generally involves non-linearities that the
2216
 * approximate transformer will not detect. In such case, we must a
2217
 * non-approximated GenImgProjTransformer, but it might be worthwhile to use
2218
 * approximate sub- transformers, for example on coordinate reprojection. For
2219
 * example if warping from a source dataset with RPC to a destination dataset
2220
 * with a UTM projection, since the inverse UTM transformation is rather costly.
2221
 * In which case, one can use the REPROJECTION_APPROX_ERROR_IN_SRC_SRS_UNIT and
2222
 * REPROJECTION_APPROX_ERROR_IN_DST_SRS_UNIT options.
2223
 *
2224
 * The list of supported options can also be programmatically obtained with
2225
 * GDALGetGenImgProjTranformerOptionList().
2226
 *
2227
 * @param hSrcDS source dataset, or NULL.
2228
 * @param hDstDS destination dataset (or NULL).
2229
 * @param papszOptions NULL-terminated list of string options (or NULL).
2230
 *
2231
 * @return handle suitable for use GDALGenImgProjTransform(), and to be
2232
 * deallocated with GDALDestroyGenImgProjTransformer() or NULL on failure.
2233
 */
2234
/* clang-format on */
2235
2236
void *GDALCreateGenImgProjTransformer2(GDALDatasetH hSrcDS, GDALDatasetH hDstDS,
2237
                                       CSLConstList papszOptions)
2238
2239
0
{
2240
0
    GDALValidateOptions(nullptr, GDALGetGenImgProjTranformerOptionList(),
2241
0
                        papszOptions, "option", "transformer options");
2242
2243
0
    double dfWestLongitudeDeg = 0.0;
2244
0
    double dfSouthLatitudeDeg = 0.0;
2245
0
    double dfEastLongitudeDeg = 0.0;
2246
0
    double dfNorthLatitudeDeg = 0.0;
2247
0
    bool bHasAreaOfInterest = false;
2248
0
    if (const char *pszAreaOfInterest =
2249
0
            CSLFetchNameValue(papszOptions, "AREA_OF_INTEREST"))
2250
0
    {
2251
0
        const CPLStringList aosTokens(
2252
0
            CSLTokenizeString2(pszAreaOfInterest, ", ", 0));
2253
0
        if (aosTokens.size() == 4)
2254
0
        {
2255
0
            dfWestLongitudeDeg = CPLAtof(aosTokens[0]);
2256
0
            dfSouthLatitudeDeg = CPLAtof(aosTokens[1]);
2257
0
            dfEastLongitudeDeg = CPLAtof(aosTokens[2]);
2258
0
            dfNorthLatitudeDeg = CPLAtof(aosTokens[3]);
2259
0
            bHasAreaOfInterest = true;
2260
0
        }
2261
0
    }
2262
2263
0
    const char *pszCO = CSLFetchNameValue(papszOptions, "COORDINATE_OPERATION");
2264
2265
0
    const auto SetAxisMapping =
2266
0
        [papszOptions](OGRSpatialReference &oSRS, const char *pszPrefix)
2267
0
    {
2268
0
        const char *pszMapping = CSLFetchNameValue(
2269
0
            papszOptions, std::string(pszPrefix)
2270
0
                              .append("_DATA_AXIS_TO_SRS_AXIS_MAPPING")
2271
0
                              .c_str());
2272
0
        if (pszMapping)
2273
0
        {
2274
0
            CPLStringList aosTokens(CSLTokenizeString2(pszMapping, ",", 0));
2275
0
            std::vector<int> anMapping;
2276
0
            for (int i = 0; i < aosTokens.size(); ++i)
2277
0
                anMapping.push_back(atoi(aosTokens[i]));
2278
0
            oSRS.SetDataAxisToSRSAxisMapping(anMapping);
2279
0
        }
2280
0
        else
2281
0
        {
2282
0
            const char *pszStrategy = CSLFetchNameValueDef(
2283
0
                papszOptions,
2284
0
                std::string(pszPrefix).append("_AXIS_MAPPING_STRATEGY").c_str(),
2285
0
                "TRADITIONAL_GIS_ORDER");
2286
0
            if (EQUAL(pszStrategy, "TRADITIONAL_GIS_ORDER"))
2287
0
                oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
2288
0
            else if (EQUAL(pszStrategy, "AUTHORITY_COMPLIANT"))
2289
0
                oSRS.SetAxisMappingStrategy(OAMS_AUTHORITY_COMPLIANT);
2290
0
            else
2291
0
            {
2292
0
                CPLError(CE_Warning, CPLE_AppDefined,
2293
0
                         "Unrecognized value '%s' for %s", pszStrategy,
2294
0
                         std::string(pszPrefix)
2295
0
                             .append("_AXIS_MAPPING_STRATEGY")
2296
0
                             .c_str());
2297
0
                return false;
2298
0
            }
2299
0
        }
2300
0
        return true;
2301
0
    };
2302
2303
    /* -------------------------------------------------------------------- */
2304
    /*      Initialize the transform info.                                  */
2305
    /* -------------------------------------------------------------------- */
2306
0
    GDALGenImgProjTransformInfo *psInfo =
2307
0
        GDALCreateGenImgProjTransformerInternal();
2308
2309
0
    const auto DealWithForwardOrInverse =
2310
0
        [bHasAreaOfInterest, &dfWestLongitudeDeg, &dfSouthLatitudeDeg,
2311
0
         &dfEastLongitudeDeg, &dfNorthLatitudeDeg, pszCO, papszOptions,
2312
0
         &SetAxisMapping](GDALGenImgProjTransformPart &part, GDALDatasetH hDS,
2313
0
                          const char *pszPrefix, OGRSpatialReference &oSRS,
2314
0
                          bool &bCanUseGeoTransform)
2315
0
    {
2316
0
        const int nOrder =
2317
0
            atoi(CSLFetchNameValueDef(papszOptions, "MAX_GCP_ORDER", "0"));
2318
2319
0
        const bool bGCPUseOK =
2320
0
            CPLTestBool(CSLFetchNameValueDef(papszOptions, "GCPS_OK", "YES"));
2321
0
        const int nMinimumGcps = atoi(
2322
0
            CSLFetchNameValueDef(papszOptions, "REFINE_MINIMUM_GCPS", "-1"));
2323
2324
0
        const char *pszRefineTolerance =
2325
0
            CSLFetchNameValue(papszOptions, "REFINE_TOLERANCE");
2326
0
        const bool bRefine = pszRefineTolerance != nullptr;
2327
0
        const double dfTolerance =
2328
0
            pszRefineTolerance ? CPLAtof(pszRefineTolerance) : 0.0;
2329
2330
0
        const std::string osSRSOptionName =
2331
0
            std::string(pszPrefix).append("_SRS");
2332
0
        const char *pszSRS =
2333
0
            CSLFetchNameValue(papszOptions, osSRSOptionName.c_str());
2334
0
        if (pszSRS)
2335
0
        {
2336
0
            if (pszSRS[0] != '\0' &&
2337
0
                oSRS.SetFromUserInput(pszSRS) != OGRERR_NONE)
2338
0
            {
2339
0
                CPLError(CE_Failure, CPLE_AppDefined,
2340
0
                         "Failed to import coordinate system `%s'.", pszSRS);
2341
0
                return false;
2342
0
            }
2343
0
            if (!SetAxisMapping(oSRS, osSRSOptionName.c_str()))
2344
0
                return false;
2345
0
        }
2346
2347
0
        CSLConstList papszMD = nullptr;
2348
0
        GDALRPCInfoV2 sRPCInfo;
2349
2350
0
        bCanUseGeoTransform = false;
2351
2352
0
        const char *pszMethod = CSLFetchNameValue(
2353
0
            papszOptions, std::string(pszPrefix).append("_METHOD").c_str());
2354
0
        if (!pszMethod && EQUAL(pszPrefix, "SRC"))
2355
0
            pszMethod = CSLFetchNameValue(papszOptions, "METHOD");
2356
2357
0
        const char *pszGeolocArray = CSLFetchNameValue(
2358
0
            papszOptions,
2359
0
            std::string(pszPrefix).append("_GEOLOC_ARRAY").c_str());
2360
0
        if (!pszGeolocArray && EQUAL(pszPrefix, "SRC"))
2361
0
            pszGeolocArray = CSLFetchNameValue(papszOptions, "GEOLOC_ARRAY");
2362
0
        if (!pszMethod && pszGeolocArray != nullptr)
2363
0
            pszMethod = "GEOLOC_ARRAY";
2364
2365
        /* -------------------------------------------------------------------- */
2366
        /*      Get forward and inverse geotransform for the source image.      */
2367
        /* -------------------------------------------------------------------- */
2368
0
        if (hDS == nullptr ||
2369
0
            (pszMethod != nullptr && EQUAL(pszMethod, "NO_GEOTRANSFORM")))
2370
0
        {
2371
0
            part.adfGeoTransform[0] = 0.0;
2372
0
            part.adfGeoTransform[1] = 1.0;
2373
0
            part.adfGeoTransform[2] = 0.0;
2374
0
            part.adfGeoTransform[3] = 0.0;
2375
0
            part.adfGeoTransform[4] = 0.0;
2376
0
            part.adfGeoTransform[5] = 1.0;
2377
0
            memcpy(part.adfInvGeoTransform, part.adfGeoTransform,
2378
0
                   sizeof(double) * 6);
2379
0
        }
2380
0
        else if ((pszMethod == nullptr || EQUAL(pszMethod, "GEOTRANSFORM")) &&
2381
0
                 GDALGetGeoTransform(hDS, part.adfGeoTransform) == CE_None)
2382
0
        {
2383
0
            if (!GDALInvGeoTransform(part.adfGeoTransform,
2384
0
                                     part.adfInvGeoTransform))
2385
0
            {
2386
0
                CPLError(CE_Failure, CPLE_AppDefined,
2387
0
                         "Cannot invert geotransform");
2388
0
                return false;
2389
0
            }
2390
0
            if (pszSRS == nullptr)
2391
0
            {
2392
0
                auto hSRS = GDALGetSpatialRef(hDS);
2393
0
                if (hSRS)
2394
0
                    oSRS = *(OGRSpatialReference::FromHandle(hSRS));
2395
0
            }
2396
0
            if (EQUAL(pszPrefix, "SRC"))
2397
0
            {
2398
0
                if (!bHasAreaOfInterest && pszCO == nullptr && !oSRS.IsEmpty())
2399
0
                {
2400
0
                    GDALComputeAreaOfInterest(
2401
0
                        &oSRS, part.adfGeoTransform, GDALGetRasterXSize(hDS),
2402
0
                        GDALGetRasterYSize(hDS), dfWestLongitudeDeg,
2403
0
                        dfSouthLatitudeDeg, dfEastLongitudeDeg,
2404
0
                        dfNorthLatitudeDeg);
2405
0
                }
2406
0
                bCanUseGeoTransform = true;
2407
0
            }
2408
0
        }
2409
0
        else if (bGCPUseOK &&
2410
0
                 ((pszMethod == nullptr && GDALGetGCPCount(hDS) >= 4 &&
2411
0
                   GDALGetGCPCount(hDS) < 6) ||
2412
0
                  (pszMethod != nullptr &&
2413
0
                   EQUAL(pszMethod, "GCP_HOMOGRAPHY"))) &&
2414
0
                 GDALGetGCPCount(hDS) > 0)
2415
0
        {
2416
0
            if (!pszMethod)
2417
0
                CPLDebug("WARP", "Using GCP_HOMOGRAPHY transformer due to GCP "
2418
0
                                 "count in [4, 5]");
2419
0
            if (pszSRS == nullptr)
2420
0
            {
2421
0
                auto hSRS = GDALGetGCPSpatialRef(hDS);
2422
0
                if (hSRS)
2423
0
                    oSRS = *(OGRSpatialReference::FromHandle(hSRS));
2424
0
            }
2425
2426
0
            const auto nGCPCount = GDALGetGCPCount(hDS);
2427
0
            auto pasGCPList = GDALDuplicateGCPs(nGCPCount, GDALGetGCPs(hDS));
2428
0
            GDALGCPAntimeridianUnwrap(nGCPCount, pasGCPList, oSRS,
2429
0
                                      papszOptions);
2430
2431
0
            part.pTransformArg =
2432
0
                GDALCreateHomographyTransformerFromGCPs(nGCPCount, pasGCPList);
2433
2434
0
            GDALDeinitGCPs(nGCPCount, pasGCPList);
2435
0
            CPLFree(pasGCPList);
2436
2437
0
            if (part.pTransformArg == nullptr)
2438
0
            {
2439
0
                return false;
2440
0
            }
2441
0
            part.pTransformer = GDALHomographyTransform;
2442
0
        }
2443
0
        else if (bGCPUseOK &&
2444
0
                 ((pszMethod == nullptr && nOrder >= 0) ||
2445
0
                  (pszMethod && EQUAL(pszMethod, "GCP_POLYNOMIAL"))) &&
2446
0
                 GDALGetGCPCount(hDS) > 0)
2447
0
        {
2448
0
            const auto nGCPCount = GDALGetGCPCount(hDS);
2449
0
            const int nSanitizedOrder = std::max(0, nOrder);
2450
0
            if (!pszMethod && nSanitizedOrder == 0)
2451
0
            {
2452
0
                if (nGCPCount <= 6)
2453
0
                    CPLDebug("WARP", "Using GCP_POLYNOMIAL transformer with "
2454
0
                                     "order = 1 due to GCP count <= 6");
2455
0
                else
2456
0
                    CPLDebug("WARP", "Using GCP_POLYNOMIAL transformer with "
2457
0
                                     "order = 2 due to GCP count > 6");
2458
0
            }
2459
2460
0
            if (pszSRS == nullptr)
2461
0
            {
2462
0
                auto hSRS = GDALGetGCPSpatialRef(hDS);
2463
0
                if (hSRS)
2464
0
                    oSRS = *(OGRSpatialReference::FromHandle(hSRS));
2465
0
            }
2466
2467
0
            auto pasGCPList = GDALDuplicateGCPs(nGCPCount, GDALGetGCPs(hDS));
2468
0
            GDALGCPAntimeridianUnwrap(nGCPCount, pasGCPList, oSRS,
2469
0
                                      papszOptions);
2470
2471
0
            if (bRefine)
2472
0
            {
2473
0
                part.pTransformArg = GDALCreateGCPRefineTransformer(
2474
0
                    nGCPCount, pasGCPList, nSanitizedOrder, FALSE, dfTolerance,
2475
0
                    nMinimumGcps);
2476
0
            }
2477
0
            else
2478
0
            {
2479
0
                part.pTransformArg = GDALCreateGCPTransformer(
2480
0
                    nGCPCount, pasGCPList, nSanitizedOrder, FALSE);
2481
0
            }
2482
2483
0
            GDALDeinitGCPs(nGCPCount, pasGCPList);
2484
0
            CPLFree(pasGCPList);
2485
2486
0
            if (part.pTransformArg == nullptr)
2487
0
            {
2488
0
                return false;
2489
0
            }
2490
0
            part.pTransformer = GDALGCPTransform;
2491
0
        }
2492
2493
0
        else if (bGCPUseOK && GDALGetGCPCount(hDS) > 0 &&
2494
0
                 ((pszMethod == nullptr && nOrder < 0) ||
2495
0
                  (pszMethod && EQUAL(pszMethod, "GCP_TPS"))))
2496
0
        {
2497
0
            if (pszSRS == nullptr)
2498
0
            {
2499
0
                auto hSRS = GDALGetGCPSpatialRef(hDS);
2500
0
                if (hSRS)
2501
0
                    oSRS = *(OGRSpatialReference::FromHandle(hSRS));
2502
0
            }
2503
2504
0
            const auto nGCPCount = GDALGetGCPCount(hDS);
2505
0
            auto pasGCPList = GDALDuplicateGCPs(nGCPCount, GDALGetGCPs(hDS));
2506
0
            GDALGCPAntimeridianUnwrap(nGCPCount, pasGCPList, oSRS,
2507
0
                                      papszOptions);
2508
2509
0
            part.pTransformArg = GDALCreateTPSTransformerInt(
2510
0
                nGCPCount, pasGCPList, FALSE, papszOptions);
2511
2512
0
            GDALDeinitGCPs(nGCPCount, pasGCPList);
2513
0
            CPLFree(pasGCPList);
2514
2515
0
            if (part.pTransformArg == nullptr)
2516
0
            {
2517
0
                return false;
2518
0
            }
2519
0
            part.pTransformer = GDALTPSTransform;
2520
0
        }
2521
2522
0
        else if ((pszMethod == nullptr || EQUAL(pszMethod, GDAL_MDD_RPC)) &&
2523
0
                 (papszMD = GDALGetMetadata(hDS, GDAL_MDD_RPC)) != nullptr &&
2524
0
                 GDALExtractRPCInfoV2(papszMD, &sRPCInfo))
2525
0
        {
2526
0
            if (!pszMethod)
2527
0
                CPLDebug("WARP", "Using RPC transformer");
2528
2529
0
            CPLStringList aosOptions(papszOptions);
2530
0
            if (!CSLFetchNameValue(papszOptions, "RPC_HEIGHT") &&
2531
0
                !CSLFetchNameValue(papszOptions, "RPC_DEM"))
2532
0
            {
2533
0
                if (const char *pszHEIGHT_DEFAULT =
2534
0
                        CSLFetchNameValue(papszMD, "HEIGHT_DEFAULT"))
2535
0
                {
2536
0
                    CPLDebug("GDAL",
2537
0
                             "For %s, using RPC_HEIGHT = HEIGHT_DEFAULT = %s",
2538
0
                             pszPrefix, pszHEIGHT_DEFAULT);
2539
0
                    aosOptions.SetNameValue("RPC_HEIGHT", pszHEIGHT_DEFAULT);
2540
0
                }
2541
0
            }
2542
0
            part.pTransformArg = GDALCreateRPCTransformerV2(&sRPCInfo, FALSE, 0,
2543
0
                                                            aosOptions.List());
2544
0
            if (part.pTransformArg == nullptr)
2545
0
            {
2546
0
                return false;
2547
0
            }
2548
0
            part.pTransformer = GDALRPCTransform;
2549
0
            if (pszSRS == nullptr)
2550
0
            {
2551
0
                oSRS.SetFromUserInput(SRS_WKT_WGS84_LAT_LONG);
2552
0
                oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
2553
0
            }
2554
0
        }
2555
2556
0
        else if ((pszMethod == nullptr || EQUAL(pszMethod, "GEOLOC_ARRAY")) &&
2557
0
                 ((papszMD = GDALGetMetadata(hDS, GDAL_MDD_GEOLOCATION)) !=
2558
0
                      nullptr ||
2559
0
                  pszGeolocArray != nullptr))
2560
0
        {
2561
0
            if (!pszMethod)
2562
0
                CPLDebug("WARP", "Using geolocation array transformer");
2563
2564
0
            CPLStringList aosGeolocMD;  // keep in this scope
2565
0
            if (pszGeolocArray != nullptr)
2566
0
            {
2567
0
                if (papszMD != nullptr)
2568
0
                {
2569
0
                    CPLError(
2570
0
                        CE_Warning, CPLE_AppDefined,
2571
0
                        "Both GEOLOCATION metadata domain on the source "
2572
0
                        "dataset "
2573
0
                        "and [%s_]GEOLOC_ARRAY transformer option are set. "
2574
0
                        "Only using the later.",
2575
0
                        pszPrefix);
2576
0
                }
2577
0
                aosGeolocMD = GDALCreateGeolocationMetadata(
2578
0
                    hDS, pszGeolocArray,
2579
0
                    /* bIsSource= */ EQUAL(pszPrefix, "SRC"));
2580
0
                if (aosGeolocMD.empty())
2581
0
                {
2582
0
                    return false;
2583
0
                }
2584
0
                papszMD = aosGeolocMD.List();
2585
0
            }
2586
2587
0
            part.pTransformArg = GDALCreateGeoLocTransformerEx(
2588
0
                hDS, papszMD, FALSE, nullptr, papszOptions);
2589
0
            if (part.pTransformArg == nullptr)
2590
0
            {
2591
0
                return false;
2592
0
            }
2593
0
            part.pTransformer = GDALGeoLocTransform;
2594
0
            if (pszSRS == nullptr)
2595
0
            {
2596
0
                pszSRS = CSLFetchNameValue(papszMD, "SRS");
2597
0
                if (pszSRS)
2598
0
                {
2599
0
                    oSRS.SetFromUserInput(pszSRS);
2600
0
                    oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
2601
0
                }
2602
0
            }
2603
0
        }
2604
2605
0
        else if (pszMethod != nullptr && EQUAL(pszPrefix, "SRC"))
2606
0
        {
2607
0
            CPLError(CE_Failure, CPLE_AppDefined,
2608
0
                     "Unable to compute a %s based transformation between "
2609
0
                     "pixel/line and georeferenced coordinates for %s.",
2610
0
                     pszMethod, GDALGetDescription(hDS));
2611
2612
0
            return false;
2613
0
        }
2614
2615
0
        else
2616
0
        {
2617
0
            CPLError(CE_Failure, CPLE_AppDefined,
2618
0
                     "Unable to compute a transformation between pixel/line "
2619
0
                     "and georeferenced coordinates for %s. "
2620
0
                     "There is no affine transformation and no GCPs. "
2621
0
                     "Specify transformation option %s_METHOD=NO_GEOTRANSFORM "
2622
0
                     "to bypass this check.",
2623
0
                     GDALGetDescription(hDS), pszPrefix);
2624
2625
0
            return false;
2626
0
        }
2627
2628
        /* ---------------------------------------------------------------- */
2629
        /*      Handle optional source approximation transformer.           */
2630
        /* ---------------------------------------------------------------- */
2631
0
        if (part.pTransformer)
2632
0
        {
2633
0
            const char *pszApproxErrorFwd = CSLFetchNameValue(
2634
0
                papszOptions, std::string(pszPrefix)
2635
0
                                  .append("_APPROX_ERROR_IN_SRS_UNIT")
2636
0
                                  .c_str());
2637
0
            const char *pszApproxErrorReverse = CSLFetchNameValue(
2638
0
                papszOptions, std::string(pszPrefix)
2639
0
                                  .append("_APPROX_ERROR_IN_PIXEL")
2640
0
                                  .c_str());
2641
0
            if (pszApproxErrorFwd && pszApproxErrorReverse)
2642
0
            {
2643
0
                void *pArg = GDALCreateApproxTransformer2(
2644
0
                    part.pTransformer, part.pTransformArg,
2645
0
                    CPLAtof(pszApproxErrorFwd), CPLAtof(pszApproxErrorReverse));
2646
0
                if (pArg == nullptr)
2647
0
                {
2648
0
                    return false;
2649
0
                }
2650
0
                part.pTransformArg = pArg;
2651
0
                part.pTransformer = GDALApproxTransform;
2652
0
                GDALApproxTransformerOwnsSubtransformer(part.pTransformArg,
2653
0
                                                        TRUE);
2654
0
            }
2655
0
        }
2656
2657
0
        return true;
2658
0
    };
2659
2660
    /* -------------------------------------------------------------------- */
2661
    /*      Get forward and inverse geotransform for the source image.      */
2662
    /* -------------------------------------------------------------------- */
2663
0
    bool bCanUseSrcGeoTransform = false;
2664
0
    OGRSpatialReference oSrcSRS;
2665
0
    if (!DealWithForwardOrInverse(psInfo->sSrcParams, hSrcDS, "SRC", oSrcSRS,
2666
0
                                  bCanUseSrcGeoTransform))
2667
0
    {
2668
0
        GDALDestroyGenImgProjTransformer(psInfo);
2669
0
        return nullptr;
2670
0
    }
2671
2672
    /* -------------------------------------------------------------------- */
2673
    /*      Get forward and inverse geotransform for destination image.     */
2674
    /*      If we have no destination use a unit transform.                 */
2675
    /* -------------------------------------------------------------------- */
2676
0
    bool bIgnored = false;
2677
0
    OGRSpatialReference oDstSRS;
2678
0
    if (!DealWithForwardOrInverse(psInfo->sDstParams, hDstDS, "DST", oDstSRS,
2679
0
                                  bIgnored))
2680
0
    {
2681
0
        GDALDestroyGenImgProjTransformer(psInfo);
2682
0
        return nullptr;
2683
0
    }
2684
2685
    /* -------------------------------------------------------------------- */
2686
    /*      Setup reprojection.                                             */
2687
    /* -------------------------------------------------------------------- */
2688
2689
0
    if (CPLFetchBool(papszOptions, "@STRIP_VERT_CS", false))
2690
0
    {
2691
0
        if (oSrcSRS.IsCompound())
2692
0
        {
2693
0
            oSrcSRS.StripVertical();
2694
0
        }
2695
0
        if (oDstSRS.IsCompound())
2696
0
        {
2697
0
            oDstSRS.StripVertical();
2698
0
        }
2699
0
    }
2700
2701
0
    const bool bMayInsertCenterLong =
2702
0
        (bCanUseSrcGeoTransform && !oSrcSRS.IsEmpty() && hSrcDS &&
2703
0
         CPLFetchBool(papszOptions, "INSERT_CENTER_LONG", true));
2704
0
    const char *pszSrcCoordEpoch =
2705
0
        CSLFetchNameValue(papszOptions, "SRC_COORDINATE_EPOCH");
2706
0
    const char *pszDstCoordEpoch =
2707
0
        CSLFetchNameValue(papszOptions, "DST_COORDINATE_EPOCH");
2708
0
    if ((!oSrcSRS.IsEmpty() && !oDstSRS.IsEmpty() &&
2709
0
         (pszSrcCoordEpoch || pszDstCoordEpoch || !oSrcSRS.IsSame(&oDstSRS) ||
2710
0
          (oSrcSRS.IsGeographic() && bMayInsertCenterLong))) ||
2711
0
        pszCO)
2712
0
    {
2713
0
        CPLStringList aosOptions;
2714
2715
0
        if (bMayInsertCenterLong)
2716
0
        {
2717
0
            InsertCenterLong(hSrcDS, &oSrcSRS, &oDstSRS,
2718
0
                             CSLFetchNameValue(papszOptions, "TARGET_EXTENT"),
2719
0
                             aosOptions);
2720
0
        }
2721
2722
0
        if (CPLFetchBool(papszOptions, "PROMOTE_TO_3D", false))
2723
0
        {
2724
0
            oSrcSRS.PromoteTo3D(nullptr);
2725
0
            oDstSRS.PromoteTo3D(nullptr);
2726
0
        }
2727
2728
0
        if (!(dfWestLongitudeDeg == 0.0 && dfSouthLatitudeDeg == 0.0 &&
2729
0
              dfEastLongitudeDeg == 0.0 && dfNorthLatitudeDeg == 0.0))
2730
0
        {
2731
0
            aosOptions.SetNameValue(
2732
0
                "AREA_OF_INTEREST",
2733
0
                CPLSPrintf("%.16g,%.16g,%.16g,%.16g", dfWestLongitudeDeg,
2734
0
                           dfSouthLatitudeDeg, dfEastLongitudeDeg,
2735
0
                           dfNorthLatitudeDeg));
2736
0
        }
2737
0
        if (pszCO)
2738
0
        {
2739
0
            aosOptions.SetNameValue("COORDINATE_OPERATION", pszCO);
2740
0
        }
2741
2742
0
        const char *pszCoordEpoch =
2743
0
            CSLFetchNameValue(papszOptions, "COORDINATE_EPOCH");
2744
0
        if (pszCoordEpoch)
2745
0
        {
2746
0
            aosOptions.SetNameValue("COORDINATE_EPOCH", pszCoordEpoch);
2747
0
        }
2748
2749
0
        if (pszSrcCoordEpoch)
2750
0
        {
2751
0
            aosOptions.SetNameValue("SRC_COORDINATE_EPOCH", pszSrcCoordEpoch);
2752
0
            oSrcSRS.SetCoordinateEpoch(CPLAtof(pszSrcCoordEpoch));
2753
0
        }
2754
2755
0
        if (pszDstCoordEpoch)
2756
0
        {
2757
0
            aosOptions.SetNameValue("DST_COORDINATE_EPOCH", pszDstCoordEpoch);
2758
0
            oDstSRS.SetCoordinateEpoch(CPLAtof(pszDstCoordEpoch));
2759
0
        }
2760
2761
0
        if (const char *pszAllowBallpark =
2762
0
                CSLFetchNameValue(papszOptions, "ALLOW_BALLPARK"))
2763
0
        {
2764
0
            aosOptions.SetNameValue("ALLOW_BALLPARK", pszAllowBallpark);
2765
0
        }
2766
2767
0
        if (const char *pszOnlyBest =
2768
0
                CSLFetchNameValue(papszOptions, "ONLY_BEST"))
2769
0
        {
2770
0
            aosOptions.SetNameValue("ONLY_BEST", pszOnlyBest);
2771
0
        }
2772
2773
0
        psInfo->pReprojectArg = GDALCreateReprojectionTransformerEx(
2774
0
            !oSrcSRS.IsEmpty() ? OGRSpatialReference::ToHandle(&oSrcSRS)
2775
0
                               : nullptr,
2776
0
            !oDstSRS.IsEmpty() ? OGRSpatialReference::ToHandle(&oDstSRS)
2777
0
                               : nullptr,
2778
0
            aosOptions.List());
2779
2780
0
        if (pszCO)
2781
0
        {
2782
0
            psInfo->bHasCustomTransformationPipeline = true;
2783
0
        }
2784
2785
0
        if (psInfo->pReprojectArg == nullptr)
2786
0
        {
2787
0
            GDALDestroyGenImgProjTransformer(psInfo);
2788
0
            return nullptr;
2789
0
        }
2790
0
        psInfo->pReproject = GDALReprojectionTransform;
2791
2792
        /* --------------------------------------------------------------------
2793
         */
2794
        /*      Handle optional reprojection approximation transformer. */
2795
        /* --------------------------------------------------------------------
2796
         */
2797
0
        const char *psApproxErrorFwd = CSLFetchNameValue(
2798
0
            papszOptions, "REPROJECTION_APPROX_ERROR_IN_DST_SRS_UNIT");
2799
0
        const char *psApproxErrorReverse = CSLFetchNameValue(
2800
0
            papszOptions, "REPROJECTION_APPROX_ERROR_IN_SRC_SRS_UNIT");
2801
0
        if (psApproxErrorFwd && psApproxErrorReverse)
2802
0
        {
2803
0
            void *pArg = GDALCreateApproxTransformer2(
2804
0
                psInfo->pReproject, psInfo->pReprojectArg,
2805
0
                CPLAtof(psApproxErrorFwd), CPLAtof(psApproxErrorReverse));
2806
0
            if (pArg == nullptr)
2807
0
            {
2808
0
                GDALDestroyGenImgProjTransformer(psInfo);
2809
0
                return nullptr;
2810
0
            }
2811
0
            psInfo->pReprojectArg = pArg;
2812
0
            psInfo->pReproject = GDALApproxTransform;
2813
0
            GDALApproxTransformerOwnsSubtransformer(psInfo->pReprojectArg,
2814
0
                                                    TRUE);
2815
0
        }
2816
0
    }
2817
2818
0
    return psInfo;
2819
0
}
2820
2821
/************************************************************************/
2822
/*                  GDALRefreshGenImgProjTransformer()                  */
2823
/************************************************************************/
2824
2825
void GDALRefreshGenImgProjTransformer(void *hTransformArg)
2826
0
{
2827
0
    GDALGenImgProjTransformInfo *psInfo =
2828
0
        static_cast<GDALGenImgProjTransformInfo *>(hTransformArg);
2829
2830
0
    if (psInfo->pReprojectArg &&
2831
0
        psInfo->bCheckWithInvertPROJ != GetCurrentCheckWithInvertPROJ())
2832
0
    {
2833
0
        psInfo->bCheckWithInvertPROJ = !psInfo->bCheckWithInvertPROJ;
2834
2835
0
        CPLXMLNode *psXML =
2836
0
            GDALSerializeTransformer(psInfo->pReproject, psInfo->pReprojectArg);
2837
0
        GDALDestroyTransformer(psInfo->pReprojectArg);
2838
0
        GDALDeserializeTransformer(psXML, &psInfo->pReproject,
2839
0
                                   &psInfo->pReprojectArg);
2840
0
        CPLDestroyXMLNode(psXML);
2841
0
    }
2842
0
}
2843
2844
/************************************************************************/
2845
/*                  GDALCreateGenImgProjTransformer3()                  */
2846
/************************************************************************/
2847
2848
/**
2849
 * Create image to image transformer.
2850
 *
2851
 * This function creates a transformation object that maps from pixel/line
2852
 * coordinates on one image to pixel/line coordinates on another image.  The
2853
 * images may potentially be georeferenced in different coordinate systems,
2854
 * and may used GCPs to map between their pixel/line coordinates and
2855
 * georeferenced coordinates (as opposed to the default assumption that their
2856
 * geotransform should be used).
2857
 *
2858
 * This transformer potentially performs three concatenated transformations.
2859
 *
2860
 * The first stage is from source image pixel/line coordinates to source
2861
 * image georeferenced coordinates, and may be done using the geotransform,
2862
 * or if not defined using a polynomial model derived from GCPs.  If GCPs
2863
 * are used this stage is accomplished using GDALGCPTransform().
2864
 *
2865
 * The second stage is to change projections from the source coordinate system
2866
 * to the destination coordinate system, assuming they differ.  This is
2867
 * accomplished internally using GDALReprojectionTransform().
2868
 *
2869
 * The third stage is converting from destination image georeferenced
2870
 * coordinates to destination image coordinates.  This is done using the
2871
 * destination image geotransform, or if not available, using a polynomial
2872
 * model derived from GCPs. If GCPs are used this stage is accomplished using
2873
 * GDALGCPTransform().  This stage is skipped if hDstDS is NULL when the
2874
 * transformation is created.
2875
 *
2876
 * @param pszSrcWKT source WKT (or NULL).
2877
 * @param padfSrcGeoTransform source geotransform (or NULL).
2878
 * @param pszDstWKT destination WKT (or NULL).
2879
 * @param padfDstGeoTransform destination geotransform (or NULL).
2880
 *
2881
 * @return handle suitable for use GDALGenImgProjTransform(), and to be
2882
 * deallocated with GDALDestroyGenImgProjTransformer() or NULL on failure.
2883
 */
2884
2885
void *GDALCreateGenImgProjTransformer3(const char *pszSrcWKT,
2886
                                       const double *padfSrcGeoTransform,
2887
                                       const char *pszDstWKT,
2888
                                       const double *padfDstGeoTransform)
2889
2890
0
{
2891
0
    OGRSpatialReference oSrcSRS;
2892
0
    if (pszSrcWKT)
2893
0
    {
2894
0
        oSrcSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
2895
0
        if (pszSrcWKT[0] != '\0' &&
2896
0
            oSrcSRS.importFromWkt(pszSrcWKT) != OGRERR_NONE)
2897
0
        {
2898
0
            CPLError(CE_Failure, CPLE_AppDefined,
2899
0
                     "Failed to import coordinate system `%s'.", pszSrcWKT);
2900
0
            return nullptr;
2901
0
        }
2902
0
    }
2903
2904
0
    OGRSpatialReference oDstSRS;
2905
0
    if (pszDstWKT)
2906
0
    {
2907
0
        oDstSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
2908
0
        if (pszDstWKT[0] != '\0' &&
2909
0
            oDstSRS.importFromWkt(pszDstWKT) != OGRERR_NONE)
2910
0
        {
2911
0
            CPLError(CE_Failure, CPLE_AppDefined,
2912
0
                     "Failed to import coordinate system `%s'.", pszDstWKT);
2913
0
            return nullptr;
2914
0
        }
2915
0
    }
2916
0
    return GDALCreateGenImgProjTransformer4(
2917
0
        OGRSpatialReference::ToHandle(&oSrcSRS), padfSrcGeoTransform,
2918
0
        OGRSpatialReference::ToHandle(&oDstSRS), padfDstGeoTransform, nullptr);
2919
0
}
2920
2921
/************************************************************************/
2922
/*                  GDALCreateGenImgProjTransformer4()                  */
2923
/************************************************************************/
2924
2925
/**
2926
 * Create image to image transformer.
2927
 *
2928
 * Similar to GDALCreateGenImgProjTransformer3(), except that it takes
2929
 * OGRSpatialReferenceH objects and options.
2930
 * The options are the ones supported by GDALCreateReprojectionTransformerEx()
2931
 *
2932
 * @since GDAL 3.0
2933
 */
2934
void *GDALCreateGenImgProjTransformer4(OGRSpatialReferenceH hSrcSRS,
2935
                                       const double *padfSrcGeoTransform,
2936
                                       OGRSpatialReferenceH hDstSRS,
2937
                                       const double *padfDstGeoTransform,
2938
                                       const char *const *papszOptions)
2939
0
{
2940
    /* -------------------------------------------------------------------- */
2941
    /*      Initialize the transform info.                                  */
2942
    /* -------------------------------------------------------------------- */
2943
0
    GDALGenImgProjTransformInfo *psInfo =
2944
0
        GDALCreateGenImgProjTransformerInternal();
2945
2946
    /* -------------------------------------------------------------------- */
2947
    /*      Get forward and inverse geotransform for the source image.      */
2948
    /* -------------------------------------------------------------------- */
2949
2950
0
    const auto SetParams =
2951
0
        [](GDALGenImgProjTransformPart &part, const double *padfGT)
2952
0
    {
2953
0
        if (padfGT)
2954
0
        {
2955
0
            memcpy(part.adfGeoTransform, padfGT, sizeof(part.adfGeoTransform));
2956
0
            if (!GDALInvGeoTransform(part.adfGeoTransform,
2957
0
                                     part.adfInvGeoTransform))
2958
0
            {
2959
0
                CPLError(CE_Failure, CPLE_AppDefined,
2960
0
                         "Cannot invert geotransform");
2961
0
                return false;
2962
0
            }
2963
0
        }
2964
0
        else
2965
0
        {
2966
0
            part.adfGeoTransform[0] = 0.0;
2967
0
            part.adfGeoTransform[1] = 1.0;
2968
0
            part.adfGeoTransform[2] = 0.0;
2969
0
            part.adfGeoTransform[3] = 0.0;
2970
0
            part.adfGeoTransform[4] = 0.0;
2971
0
            part.adfGeoTransform[5] = 1.0;
2972
0
            memcpy(part.adfInvGeoTransform, part.adfGeoTransform,
2973
0
                   sizeof(double) * 6);
2974
0
        }
2975
0
        return true;
2976
0
    };
2977
2978
0
    if (!SetParams(psInfo->sSrcParams, padfSrcGeoTransform))
2979
0
    {
2980
0
        GDALDestroyGenImgProjTransformer(psInfo);
2981
0
        return nullptr;
2982
0
    }
2983
2984
    /* -------------------------------------------------------------------- */
2985
    /*      Setup reprojection.                                             */
2986
    /* -------------------------------------------------------------------- */
2987
0
    OGRSpatialReference *poSrcSRS = OGRSpatialReference::FromHandle(hSrcSRS);
2988
0
    OGRSpatialReference *poDstSRS = OGRSpatialReference::FromHandle(hDstSRS);
2989
0
    if (!poSrcSRS->IsEmpty() && !poDstSRS->IsEmpty() &&
2990
0
        !poSrcSRS->IsSame(poDstSRS))
2991
0
    {
2992
0
        psInfo->pReprojectArg =
2993
0
            GDALCreateReprojectionTransformerEx(hSrcSRS, hDstSRS, papszOptions);
2994
0
        if (psInfo->pReprojectArg == nullptr)
2995
0
        {
2996
0
            GDALDestroyGenImgProjTransformer(psInfo);
2997
0
            return nullptr;
2998
0
        }
2999
0
        psInfo->pReproject = GDALReprojectionTransform;
3000
0
    }
3001
3002
    /* -------------------------------------------------------------------- */
3003
    /*      Get forward and inverse geotransform for destination image.     */
3004
    /*      If we have no destination matrix use a unit transform.          */
3005
    /* -------------------------------------------------------------------- */
3006
0
    if (!SetParams(psInfo->sDstParams, padfDstGeoTransform))
3007
0
    {
3008
0
        GDALDestroyGenImgProjTransformer(psInfo);
3009
0
        return nullptr;
3010
0
    }
3011
3012
0
    return psInfo;
3013
0
}
3014
3015
/************************************************************************/
3016
/*            GDALSetGenImgProjTransformerDstGeoTransform()             */
3017
/************************************************************************/
3018
3019
/**
3020
 * Set GenImgProj output geotransform.
3021
 *
3022
 * Normally the "destination geotransform", or transformation between
3023
 * georeferenced output coordinates and pixel/line coordinates on the
3024
 * destination file is extracted from the destination file by
3025
 * GDALCreateGenImgProjTransformer() and stored in the GenImgProj private
3026
 * info.  However, sometimes it is inconvenient to have an output file
3027
 * handle with appropriate geotransform information when creating the
3028
 * transformation.  For these cases, this function can be used to apply
3029
 * the destination geotransform.
3030
 *
3031
 * @param hTransformArg the handle to update.
3032
 * @param padfGeoTransform the destination geotransform to apply (six doubles).
3033
 */
3034
3035
void GDALSetGenImgProjTransformerDstGeoTransform(void *hTransformArg,
3036
                                                 const double *padfGeoTransform)
3037
3038
0
{
3039
0
    VALIDATE_POINTER0(hTransformArg,
3040
0
                      "GDALSetGenImgProjTransformerDstGeoTransform");
3041
3042
0
    GDALGenImgProjTransformInfo *psInfo =
3043
0
        static_cast<GDALGenImgProjTransformInfo *>(hTransformArg);
3044
3045
0
    memcpy(psInfo->sDstParams.adfGeoTransform, padfGeoTransform,
3046
0
           sizeof(double) * 6);
3047
0
    if (!GDALInvGeoTransform(psInfo->sDstParams.adfGeoTransform,
3048
0
                             psInfo->sDstParams.adfInvGeoTransform))
3049
0
    {
3050
0
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot invert geotransform");
3051
0
    }
3052
0
}
3053
3054
/************************************************************************/
3055
/*                  GDALDestroyGenImgProjTransformer()                  */
3056
/************************************************************************/
3057
3058
/**
3059
 * GenImgProjTransformer deallocator.
3060
 *
3061
 * This function is used to deallocate the handle created with
3062
 * GDALCreateGenImgProjTransformer().
3063
 *
3064
 * @param hTransformArg the handle to deallocate.
3065
 */
3066
3067
void GDALDestroyGenImgProjTransformer(void *hTransformArg)
3068
3069
0
{
3070
0
    if (hTransformArg == nullptr)
3071
0
        return;
3072
3073
0
    GDALGenImgProjTransformInfo *psInfo =
3074
0
        static_cast<GDALGenImgProjTransformInfo *>(hTransformArg);
3075
3076
0
    if (psInfo->sSrcParams.pTransformArg != nullptr)
3077
0
        GDALDestroyTransformer(psInfo->sSrcParams.pTransformArg);
3078
3079
0
    if (psInfo->sDstParams.pTransformArg != nullptr)
3080
0
        GDALDestroyTransformer(psInfo->sDstParams.pTransformArg);
3081
3082
0
    if (psInfo->pReprojectArg != nullptr)
3083
0
        GDALDestroyTransformer(psInfo->pReprojectArg);
3084
3085
0
    CPLFree(psInfo);
3086
0
}
3087
3088
/************************************************************************/
3089
/*                      GDALGenImgProjTransform()                       */
3090
/************************************************************************/
3091
3092
/**
3093
 * Perform general image reprojection transformation.
3094
 *
3095
 * Actually performs the transformation setup in
3096
 * GDALCreateGenImgProjTransformer().  This function matches the signature
3097
 * required by the GDALTransformerFunc(), and more details on the arguments
3098
 * can be found in that topic.
3099
 */
3100
3101
#ifdef DEBUG_APPROX_TRANSFORMER
3102
int countGDALGenImgProjTransform = 0;
3103
#endif
3104
3105
int GDALGenImgProjTransform(void *pTransformArgIn, int bDstToSrc,
3106
                            int nPointCount, double *padfX, double *padfY,
3107
                            double *padfZ, int *panSuccess)
3108
0
{
3109
    // Sanity check (see issue GH #13498)
3110
0
    if (nullptr == pTransformArgIn)
3111
0
        return FALSE;
3112
3113
0
    GDALGenImgProjTransformInfo *psInfo =
3114
0
        static_cast<GDALGenImgProjTransformInfo *>(pTransformArgIn);
3115
3116
#ifdef DEBUG_APPROX_TRANSFORMER
3117
    CPLAssert(nPointCount > 0);
3118
    countGDALGenImgProjTransform += nPointCount;
3119
#endif
3120
3121
0
    for (int i = 0; i < nPointCount; i++)
3122
0
    {
3123
0
        panSuccess[i] = (padfX[i] != HUGE_VAL && padfY[i] != HUGE_VAL);
3124
0
    }
3125
3126
0
    int ret = TRUE;
3127
3128
    /* -------------------------------------------------------------------- */
3129
    /*      Convert from src (dst) pixel/line to src (dst)                  */
3130
    /*      georeferenced coordinates.                                      */
3131
    /* -------------------------------------------------------------------- */
3132
0
    {
3133
0
        const auto params = bDstToSrc ? psInfo->sDstParams : psInfo->sSrcParams;
3134
0
        const double *padfGeoTransform = params.adfGeoTransform;
3135
0
        void *pTransformArg = params.pTransformArg;
3136
0
        GDALTransformerFunc pTransformer = params.pTransformer;
3137
3138
0
        if (pTransformArg != nullptr)
3139
0
        {
3140
0
            if (!pTransformer(pTransformArg, FALSE, nPointCount, padfX, padfY,
3141
0
                              padfZ, panSuccess))
3142
0
                ret = FALSE;
3143
0
        }
3144
0
        else
3145
0
        {
3146
0
            for (int i = 0; i < nPointCount; i++)
3147
0
            {
3148
0
                if (!panSuccess[i])
3149
0
                    continue;
3150
3151
0
                const double dfNewX = padfGeoTransform[0] +
3152
0
                                      padfX[i] * padfGeoTransform[1] +
3153
0
                                      padfY[i] * padfGeoTransform[2];
3154
0
                const double dfNewY = padfGeoTransform[3] +
3155
0
                                      padfX[i] * padfGeoTransform[4] +
3156
0
                                      padfY[i] * padfGeoTransform[5];
3157
3158
0
                padfX[i] = dfNewX;
3159
0
                padfY[i] = dfNewY;
3160
0
            }
3161
0
        }
3162
0
    }
3163
3164
    /* -------------------------------------------------------------------- */
3165
    /*      Reproject if needed.                                            */
3166
    /* -------------------------------------------------------------------- */
3167
0
    if (psInfo->pReprojectArg)
3168
0
    {
3169
0
        if (!psInfo->pReproject(psInfo->pReprojectArg, bDstToSrc, nPointCount,
3170
0
                                padfX, padfY, padfZ, panSuccess))
3171
0
            ret = FALSE;
3172
0
    }
3173
3174
    /* -------------------------------------------------------------------- */
3175
    /*      Convert dst (src) georef coordinates back to pixel/line.        */
3176
    /* -------------------------------------------------------------------- */
3177
0
    {
3178
0
        const auto params = bDstToSrc ? psInfo->sSrcParams : psInfo->sDstParams;
3179
0
        const double *padfInvGeoTransform = params.adfInvGeoTransform;
3180
0
        void *pTransformArg = params.pTransformArg;
3181
0
        GDALTransformerFunc pTransformer = params.pTransformer;
3182
3183
0
        if (pTransformArg != nullptr)
3184
0
        {
3185
0
            if (!pTransformer(pTransformArg, TRUE, nPointCount, padfX, padfY,
3186
0
                              padfZ, panSuccess))
3187
0
                ret = FALSE;
3188
0
        }
3189
0
        else
3190
0
        {
3191
0
            for (int i = 0; i < nPointCount; i++)
3192
0
            {
3193
0
                if (!panSuccess[i])
3194
0
                    continue;
3195
3196
0
                const double dfNewX = padfInvGeoTransform[0] +
3197
0
                                      padfX[i] * padfInvGeoTransform[1] +
3198
0
                                      padfY[i] * padfInvGeoTransform[2];
3199
0
                const double dfNewY = padfInvGeoTransform[3] +
3200
0
                                      padfX[i] * padfInvGeoTransform[4] +
3201
0
                                      padfY[i] * padfInvGeoTransform[5];
3202
3203
0
                padfX[i] = dfNewX;
3204
0
                padfY[i] = dfNewY;
3205
0
            }
3206
0
        }
3207
0
    }
3208
3209
0
    return ret;
3210
0
}
3211
3212
/************************************************************************/
3213
/*           GDALTransformLonLatToDestGenImgProjTransformer()           */
3214
/************************************************************************/
3215
3216
int GDALTransformLonLatToDestGenImgProjTransformer(void *hTransformArg,
3217
                                                   double *pdfX, double *pdfY)
3218
0
{
3219
0
    GDALGenImgProjTransformInfo *psInfo =
3220
0
        static_cast<GDALGenImgProjTransformInfo *>(hTransformArg);
3221
3222
0
    if (psInfo->pReprojectArg == nullptr ||
3223
0
        psInfo->pReproject != GDALReprojectionTransform)
3224
0
        return false;
3225
3226
0
    GDALReprojectionTransformInfo *psReprojInfo =
3227
0
        static_cast<GDALReprojectionTransformInfo *>(psInfo->pReprojectArg);
3228
0
    if (psReprojInfo->poForwardTransform == nullptr ||
3229
0
        psReprojInfo->poForwardTransform->GetSourceCS() == nullptr)
3230
0
        return false;
3231
3232
0
    double z = 0;
3233
0
    int success = true;
3234
0
    auto poSourceCRS = psReprojInfo->poForwardTransform->GetSourceCS();
3235
0
    if (poSourceCRS->IsGeographic() &&
3236
0
        std::fabs(poSourceCRS->GetAngularUnits() -
3237
0
                  CPLAtof(SRS_UA_DEGREE_CONV)) < 1e-9)
3238
0
    {
3239
        // Optimization to avoid creating a OGRCoordinateTransformation
3240
0
        OGRAxisOrientation eSourceFirstAxisOrient = OAO_Other;
3241
0
        poSourceCRS->GetAxis(nullptr, 0, &eSourceFirstAxisOrient);
3242
0
        const auto &mapping = poSourceCRS->GetDataAxisToSRSAxisMapping();
3243
0
        if ((mapping[0] == 2 && eSourceFirstAxisOrient == OAO_East) ||
3244
0
            (mapping[0] == 1 && eSourceFirstAxisOrient != OAO_East))
3245
0
        {
3246
0
            std::swap(*pdfX, *pdfY);
3247
0
        }
3248
0
    }
3249
0
    else
3250
0
    {
3251
0
        auto poLongLat =
3252
0
            std::unique_ptr<OGRSpatialReference>(poSourceCRS->CloneGeogCS());
3253
0
        if (poLongLat == nullptr)
3254
0
            return false;
3255
0
        poLongLat->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
3256
3257
0
        const bool bCurrentCheckWithInvertProj =
3258
0
            GetCurrentCheckWithInvertPROJ();
3259
0
        if (!bCurrentCheckWithInvertProj)
3260
0
            CPLSetThreadLocalConfigOption("CHECK_WITH_INVERT_PROJ", "YES");
3261
0
        auto poCT = std::unique_ptr<OGRCoordinateTransformation>(
3262
0
            OGRCreateCoordinateTransformation(poLongLat.get(), poSourceCRS));
3263
0
        if (!bCurrentCheckWithInvertProj)
3264
0
            CPLSetThreadLocalConfigOption("CHECK_WITH_INVERT_PROJ", nullptr);
3265
0
        if (poCT == nullptr)
3266
0
            return false;
3267
3268
0
        poCT->SetEmitErrors(false);
3269
0
        if (!poCT->Transform(1, pdfX, pdfY))
3270
0
            return false;
3271
3272
0
        if (!psInfo->pReproject(psInfo->pReprojectArg, false, 1, pdfX, pdfY, &z,
3273
0
                                &success) ||
3274
0
            !success)
3275
0
        {
3276
0
            return false;
3277
0
        }
3278
0
    }
3279
3280
0
    double *padfGeoTransform = psInfo->sDstParams.adfInvGeoTransform;
3281
0
    void *pTransformArg = psInfo->sDstParams.pTransformArg;
3282
0
    GDALTransformerFunc pTransformer = psInfo->sDstParams.pTransformer;
3283
0
    if (pTransformArg != nullptr)
3284
0
    {
3285
0
        if (!pTransformer(pTransformArg, TRUE, 1, pdfX, pdfY, &z, &success) ||
3286
0
            !success)
3287
0
        {
3288
0
            return false;
3289
0
        }
3290
0
    }
3291
0
    else
3292
0
    {
3293
0
        const double dfNewX = padfGeoTransform[0] +
3294
0
                              pdfX[0] * padfGeoTransform[1] +
3295
0
                              pdfY[0] * padfGeoTransform[2];
3296
0
        const double dfNewY = padfGeoTransform[3] +
3297
0
                              pdfX[0] * padfGeoTransform[4] +
3298
0
                              pdfY[0] * padfGeoTransform[5];
3299
3300
0
        pdfX[0] = dfNewX;
3301
0
        pdfY[0] = dfNewY;
3302
0
    }
3303
3304
0
    return true;
3305
0
}
3306
3307
/************************************************************************/
3308
/*                 GDALSerializeGenImgProjTransformer()                 */
3309
/************************************************************************/
3310
3311
static CPLXMLNode *GDALSerializeGenImgProjTransformer(void *pTransformArg)
3312
3313
0
{
3314
0
    GDALGenImgProjTransformInfo *psInfo =
3315
0
        static_cast<GDALGenImgProjTransformInfo *>(pTransformArg);
3316
3317
0
    CPLXMLNode *psTree =
3318
0
        CPLCreateXMLNode(nullptr, CXT_Element, "GenImgProjTransformer");
3319
3320
0
    const auto SerializePart =
3321
0
        [psTree](const char *pszPrefix, const GDALGenImgProjTransformPart &part)
3322
0
    {
3323
0
        char szWork[200] = {};
3324
3325
        /* ------------------------------------------------------------- */
3326
        /*      Handle transformation.                                   */
3327
        /* ------------------------------------------------------------- */
3328
0
        if (part.pTransformArg != nullptr)
3329
0
        {
3330
0
            CPLXMLNode *psTransformer =
3331
0
                GDALSerializeTransformer(part.pTransformer, part.pTransformArg);
3332
0
            if (psTransformer != nullptr)
3333
0
            {
3334
0
                CPLXMLNode *psTransformerContainer = CPLCreateXMLNode(
3335
0
                    psTree, CXT_Element,
3336
0
                    CPLSPrintf("%s%s", pszPrefix, psTransformer->pszValue));
3337
3338
0
                CPLAddXMLChild(psTransformerContainer, psTransformer);
3339
0
            }
3340
0
        }
3341
3342
        /* ------------------------------------------------------------- */
3343
        /*      Handle geotransforms.                                    */
3344
        /* ------------------------------------------------------------- */
3345
0
        else
3346
0
        {
3347
0
            CPLsnprintf(szWork, sizeof(szWork),
3348
0
                        "%.17g,%.17g,%.17g,%.17g,%.17g,%.17g",
3349
0
                        part.adfGeoTransform[0], part.adfGeoTransform[1],
3350
0
                        part.adfGeoTransform[2], part.adfGeoTransform[3],
3351
0
                        part.adfGeoTransform[4], part.adfGeoTransform[5]);
3352
0
            CPLCreateXMLElementAndValue(
3353
0
                psTree, CPLSPrintf("%sGeoTransform", pszPrefix), szWork);
3354
3355
0
            CPLsnprintf(szWork, sizeof(szWork),
3356
0
                        "%.17g,%.17g,%.17g,%.17g,%.17g,%.17g",
3357
0
                        part.adfInvGeoTransform[0], part.adfInvGeoTransform[1],
3358
0
                        part.adfInvGeoTransform[2], part.adfInvGeoTransform[3],
3359
0
                        part.adfInvGeoTransform[4], part.adfInvGeoTransform[5]);
3360
0
            CPLCreateXMLElementAndValue(
3361
0
                psTree, CPLSPrintf("%sInvGeoTransform", pszPrefix), szWork);
3362
0
        }
3363
0
    };
3364
3365
0
    SerializePart("Src", psInfo->sSrcParams);
3366
0
    SerializePart("Dst", psInfo->sDstParams);
3367
3368
    /* -------------------------------------------------------------------- */
3369
    /*      Do we have a reprojection transformer?                          */
3370
    /* -------------------------------------------------------------------- */
3371
0
    if (psInfo->pReprojectArg != nullptr)
3372
0
    {
3373
3374
0
        CPLXMLNode *psTransformerContainer =
3375
0
            CPLCreateXMLNode(psTree, CXT_Element, "ReprojectTransformer");
3376
3377
0
        CPLXMLNode *psTransformer =
3378
0
            GDALSerializeTransformer(psInfo->pReproject, psInfo->pReprojectArg);
3379
0
        if (psTransformer != nullptr)
3380
0
            CPLAddXMLChild(psTransformerContainer, psTransformer);
3381
0
    }
3382
3383
0
    return psTree;
3384
0
}
3385
3386
/************************************************************************/
3387
/*                    GDALDeserializeGeoTransform()                     */
3388
/************************************************************************/
3389
3390
static void GDALDeserializeGeoTransform(const char *pszGT,
3391
                                        double adfGeoTransform[6])
3392
0
{
3393
0
    CPLsscanf(pszGT, "%lf,%lf,%lf,%lf,%lf,%lf", adfGeoTransform + 0,
3394
0
              adfGeoTransform + 1, adfGeoTransform + 2, adfGeoTransform + 3,
3395
0
              adfGeoTransform + 4, adfGeoTransform + 5);
3396
0
}
3397
3398
/************************************************************************/
3399
/*                GDALDeserializeGenImgProjTransformer()                */
3400
/************************************************************************/
3401
3402
void *GDALDeserializeGenImgProjTransformer(CPLXMLNode *psTree)
3403
3404
0
{
3405
    /* -------------------------------------------------------------------- */
3406
    /*      Initialize the transform info.                                  */
3407
    /* -------------------------------------------------------------------- */
3408
0
    GDALGenImgProjTransformInfo *psInfo =
3409
0
        GDALCreateGenImgProjTransformerInternal();
3410
3411
0
    const auto DeserializePart =
3412
0
        [psTree](const char *pszPrefix, GDALGenImgProjTransformPart &part)
3413
0
    {
3414
        /* ----------------------------------------------------------------- */
3415
        /*      Geotransform                                                 */
3416
        /* ----------------------------------------------------------------- */
3417
0
        if (const auto psGTNode =
3418
0
                CPLGetXMLNode(psTree, CPLSPrintf("%sGeoTransform", pszPrefix)))
3419
0
        {
3420
0
            GDALDeserializeGeoTransform(CPLGetXMLValue(psGTNode, "", ""),
3421
0
                                        part.adfGeoTransform);
3422
3423
0
            if (const auto psInvGTNode = CPLGetXMLNode(
3424
0
                    psTree, CPLSPrintf("%sInvGeoTransform", pszPrefix)))
3425
0
            {
3426
0
                GDALDeserializeGeoTransform(CPLGetXMLValue(psInvGTNode, "", ""),
3427
0
                                            part.adfInvGeoTransform);
3428
0
            }
3429
0
            else
3430
0
            {
3431
0
                if (!GDALInvGeoTransform(part.adfGeoTransform,
3432
0
                                         part.adfInvGeoTransform))
3433
0
                {
3434
0
                    CPLError(CE_Failure, CPLE_AppDefined,
3435
0
                             "Cannot invert geotransform");
3436
0
                }
3437
0
            }
3438
0
        }
3439
3440
        /* ---------------------------------------------------------------- */
3441
        /*      Transform                                                   */
3442
        /* ---------------------------------------------------------------- */
3443
0
        else
3444
0
        {
3445
0
            for (CPLXMLNode *psIter = psTree->psChild; psIter != nullptr;
3446
0
                 psIter = psIter->psNext)
3447
0
            {
3448
0
                if (psIter->eType == CXT_Element &&
3449
0
                    STARTS_WITH_CI(psIter->pszValue, pszPrefix))
3450
0
                {
3451
0
                    GDALDeserializeTransformer(psIter->psChild,
3452
0
                                               &part.pTransformer,
3453
0
                                               &part.pTransformArg);
3454
0
                    break;
3455
0
                }
3456
0
            }
3457
0
        }
3458
0
    };
3459
3460
0
    DeserializePart("Src", psInfo->sSrcParams);
3461
0
    DeserializePart("Dst", psInfo->sDstParams);
3462
3463
    /* -------------------------------------------------------------------- */
3464
    /*      Reproject transformer                                           */
3465
    /* -------------------------------------------------------------------- */
3466
0
    CPLXMLNode *psSubtree = CPLGetXMLNode(psTree, "ReprojectTransformer");
3467
0
    if (psSubtree != nullptr && psSubtree->psChild != nullptr)
3468
0
    {
3469
0
        GDALDeserializeTransformer(psSubtree->psChild, &psInfo->pReproject,
3470
0
                                   &psInfo->pReprojectArg);
3471
0
    }
3472
3473
0
    return psInfo;
3474
0
}
3475
3476
/************************************************************************/
3477
/*                 GDALCreateReprojectionTransformer()                  */
3478
/************************************************************************/
3479
3480
/**
3481
 * Create reprojection transformer.
3482
 *
3483
 * Creates a callback data structure suitable for use with
3484
 * GDALReprojectionTransformation() to represent a transformation from
3485
 * one geographic or projected coordinate system to another.  On input
3486
 * the coordinate systems are described in OpenGIS WKT format.
3487
 *
3488
 * Internally the OGRCoordinateTransformation object is used to implement
3489
 * the reprojection.
3490
 *
3491
 * @param pszSrcWKT the coordinate system for the source coordinate system.
3492
 * @param pszDstWKT the coordinate system for the destination coordinate
3493
 * system.
3494
 *
3495
 * @return Handle for use with GDALReprojectionTransform(), or NULL if the
3496
 * system fails to initialize the reprojection.
3497
 **/
3498
3499
void *GDALCreateReprojectionTransformer(const char *pszSrcWKT,
3500
                                        const char *pszDstWKT)
3501
3502
0
{
3503
    /* -------------------------------------------------------------------- */
3504
    /*      Ingest the SRS definitions.                                     */
3505
    /* -------------------------------------------------------------------- */
3506
0
    OGRSpatialReference oSrcSRS;
3507
0
    oSrcSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
3508
0
    if (oSrcSRS.importFromWkt(pszSrcWKT) != OGRERR_NONE)
3509
0
    {
3510
0
        CPLError(CE_Failure, CPLE_AppDefined,
3511
0
                 "Failed to import coordinate system `%s'.", pszSrcWKT);
3512
0
        return nullptr;
3513
0
    }
3514
3515
0
    OGRSpatialReference oDstSRS;
3516
0
    oDstSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
3517
0
    if (oDstSRS.importFromWkt(pszDstWKT) != OGRERR_NONE)
3518
0
    {
3519
0
        CPLError(CE_Failure, CPLE_AppDefined,
3520
0
                 "Failed to import coordinate system `%s'.", pszSrcWKT);
3521
0
        return nullptr;
3522
0
    }
3523
3524
0
    return GDALCreateReprojectionTransformerEx(
3525
0
        OGRSpatialReference::ToHandle(&oSrcSRS),
3526
0
        OGRSpatialReference::ToHandle(&oDstSRS), nullptr);
3527
0
}
3528
3529
/************************************************************************/
3530
/*                GDALCreateReprojectionTransformerEx()                 */
3531
/************************************************************************/
3532
3533
/**
3534
 * Create reprojection transformer.
3535
 *
3536
 * Creates a callback data structure suitable for use with
3537
 * GDALReprojectionTransformation() to represent a transformation from
3538
 * one geographic or projected coordinate system to another.
3539
 *
3540
 * Internally the OGRCoordinateTransformation object is used to implement
3541
 * the reprojection.
3542
 *
3543
 * @param hSrcSRS the coordinate system for the source coordinate system.
3544
 * @param hDstSRS the coordinate system for the destination coordinate
3545
 * system.
3546
 * @param papszOptions NULL-terminated list of options, or NULL. Currently
3547
 * supported options are:
3548
 * <ul>
3549
 * <li>AREA_OF_INTEREST=west_long,south_lat,east_long,north_lat: Values in
3550
 * degrees. longitudes in [-180,180], latitudes in [-90,90].</li>
3551
 * <li>COORDINATE_OPERATION=string: PROJ or WKT string representing a
3552
 * coordinate operation, overriding the default computed transformation.</li>
3553
 * <li>COORDINATE_EPOCH=decimal_year: Coordinate epoch, expressed as a
3554
 * decimal year. Useful for time-dependent coordinate operations.</li>
3555
 * <li> SRC_COORDINATE_EPOCH: (GDAL &gt;= 3.4) Coordinate epoch of source CRS,
3556
 * expressed as a decimal year. Useful for time-dependent coordinate
3557
 *operations.</li>
3558
 * <li> DST_COORDINATE_EPOCH: (GDAL &gt;= 3.4) Coordinate epoch
3559
 *of target CRS, expressed as a decimal year. Useful for time-dependent
3560
 *coordinate operations.</li>
3561
 * <li> ALLOW_BALLPARK=YES/NO: (GDAL &gt;= 3.11) Whether ballpark coordinate
3562
 * operations are allowed. Defaults to YES.</li>
3563
 * <li> ONLY_BEST=YES/NO/AUTO: (GDAL &gt;= 3.11) By default (at least in the
3564
 * PROJ 9.x series), PROJ may use coordinate
3565
 * operations that are not the "best" if resources (typically grids) needed
3566
 * to use them are missing. It will then fallback to other coordinate operations
3567
 * that have a lesser accuracy, for example using Helmert transformations,
3568
 * or in the absence of such operations, to ones with potential very rough
3569
 * accuracy, using "ballpark" transformations
3570
 * (see https://proj.org/glossary.html).
3571
 * When calling this method with YES, PROJ will only consider the
3572
 * "best" operation, and error out (at Transform() time) if they cannot be
3573
 * used.
3574
 * This method may be used together with ALLOW_BALLPARK=NO to
3575
 * only allow best operations that have a known accuracy.
3576
 * Note that this method has no effect on PROJ versions before 9.2.
3577
 * The default value for this option can be also set with the
3578
 * PROJ_ONLY_BEST_DEFAULT environment variable, or with the "only_best_default"
3579
 * setting of proj.ini. Calling SetOnlyBest() overrides such default value.</li>
3580
 * </ul>
3581
 *
3582
 * @return Handle for use with GDALReprojectionTransform(), or NULL if the
3583
 * system fails to initialize the reprojection.
3584
 *
3585
 * @since GDAL 3.0
3586
 **/
3587
3588
void *GDALCreateReprojectionTransformerEx(OGRSpatialReferenceH hSrcSRS,
3589
                                          OGRSpatialReferenceH hDstSRS,
3590
                                          const char *const *papszOptions)
3591
0
{
3592
0
    OGRSpatialReference *poSrcSRS = OGRSpatialReference::FromHandle(hSrcSRS);
3593
0
    OGRSpatialReference *poDstSRS = OGRSpatialReference::FromHandle(hDstSRS);
3594
3595
    /* -------------------------------------------------------------------- */
3596
    /*      Build the forward coordinate transformation.                    */
3597
    /* -------------------------------------------------------------------- */
3598
0
    double dfWestLongitudeDeg = 0.0;
3599
0
    double dfSouthLatitudeDeg = 0.0;
3600
0
    double dfEastLongitudeDeg = 0.0;
3601
0
    double dfNorthLatitudeDeg = 0.0;
3602
0
    const char *pszBBOX = CSLFetchNameValue(papszOptions, "AREA_OF_INTEREST");
3603
0
    if (pszBBOX)
3604
0
    {
3605
0
        char **papszTokens = CSLTokenizeString2(pszBBOX, ",", 0);
3606
0
        if (CSLCount(papszTokens) == 4)
3607
0
        {
3608
0
            dfWestLongitudeDeg = CPLAtof(papszTokens[0]);
3609
0
            dfSouthLatitudeDeg = CPLAtof(papszTokens[1]);
3610
0
            dfEastLongitudeDeg = CPLAtof(papszTokens[2]);
3611
0
            dfNorthLatitudeDeg = CPLAtof(papszTokens[3]);
3612
0
        }
3613
0
        CSLDestroy(papszTokens);
3614
0
    }
3615
0
    const char *pszCO = CSLFetchNameValue(papszOptions, "COORDINATE_OPERATION");
3616
3617
0
    OGRCoordinateTransformationOptions optionsFwd;
3618
0
    if (!(dfWestLongitudeDeg == 0.0 && dfSouthLatitudeDeg == 0.0 &&
3619
0
          dfEastLongitudeDeg == 0.0 && dfNorthLatitudeDeg == 0.0))
3620
0
    {
3621
0
        optionsFwd.SetAreaOfInterest(dfWestLongitudeDeg, dfSouthLatitudeDeg,
3622
0
                                     dfEastLongitudeDeg, dfNorthLatitudeDeg);
3623
0
    }
3624
0
    if (pszCO)
3625
0
    {
3626
0
        optionsFwd.SetCoordinateOperation(pszCO, false);
3627
0
    }
3628
3629
0
    const char *pszCENTER_LONG = CSLFetchNameValue(papszOptions, "CENTER_LONG");
3630
0
    if (pszCENTER_LONG)
3631
0
    {
3632
0
        optionsFwd.SetSourceCenterLong(CPLAtof(pszCENTER_LONG));
3633
0
    }
3634
3635
0
    optionsFwd.SetBallparkAllowed(CPLTestBool(
3636
0
        CSLFetchNameValueDef(papszOptions, "ALLOW_BALLPARK", "YES")));
3637
3638
0
    const char *pszOnlyBest =
3639
0
        CSLFetchNameValueDef(papszOptions, "ONLY_BEST", "AUTO");
3640
0
    if (!EQUAL(pszOnlyBest, "AUTO"))
3641
0
    {
3642
0
        optionsFwd.SetOnlyBest(CPLTestBool(pszOnlyBest));
3643
0
    }
3644
3645
0
    OGRCoordinateTransformation *poForwardTransform =
3646
0
        OGRCreateCoordinateTransformation(poSrcSRS, poDstSRS, optionsFwd);
3647
3648
0
    if (poForwardTransform == nullptr)
3649
        // OGRCreateCoordinateTransformation() will report errors on its own.
3650
0
        return nullptr;
3651
3652
0
    poForwardTransform->SetEmitErrors(false);
3653
3654
    /* -------------------------------------------------------------------- */
3655
    /*      Create a structure to hold the transform info, and also         */
3656
    /*      build reverse transform.  We assume that if the forward         */
3657
    /*      transform can be created, then so can the reverse one.          */
3658
    /* -------------------------------------------------------------------- */
3659
0
    GDALReprojectionTransformInfo *psInfo = new GDALReprojectionTransformInfo();
3660
3661
0
    psInfo->papszOptions = CSLDuplicate(papszOptions);
3662
0
    psInfo->poForwardTransform = poForwardTransform;
3663
0
    psInfo->dfTime = CPLAtof(CSLFetchNameValueDef(
3664
0
        papszOptions, "COORDINATE_EPOCH",
3665
0
        CSLFetchNameValueDef(
3666
0
            papszOptions, "DST_COORDINATE_EPOCH",
3667
0
            CSLFetchNameValueDef(papszOptions, "SRC_COORDINATE_EPOCH", "0"))));
3668
0
    psInfo->poReverseTransform = poForwardTransform->GetInverse();
3669
3670
0
    if (psInfo->poReverseTransform)
3671
0
        psInfo->poReverseTransform->SetEmitErrors(false);
3672
3673
0
    memcpy(psInfo->sTI.abySignature, GDAL_GTI2_SIGNATURE,
3674
0
           strlen(GDAL_GTI2_SIGNATURE));
3675
0
    psInfo->sTI.pszClassName = GDAL_REPROJECTION_TRANSFORMER_CLASS_NAME;
3676
0
    psInfo->sTI.pfnTransform = GDALReprojectionTransform;
3677
0
    psInfo->sTI.pfnCleanup = GDALDestroyReprojectionTransformer;
3678
0
    psInfo->sTI.pfnSerialize = GDALSerializeReprojectionTransformer;
3679
3680
0
    return psInfo;
3681
0
}
3682
3683
/************************************************************************/
3684
/*                 GDALDestroyReprojectionTransformer()                 */
3685
/************************************************************************/
3686
3687
/**
3688
 * Destroy reprojection transformation.
3689
 *
3690
 * @param pTransformArg the transformation handle returned by
3691
 * GDALCreateReprojectionTransformer().
3692
 */
3693
3694
void GDALDestroyReprojectionTransformer(void *pTransformArg)
3695
3696
0
{
3697
0
    if (pTransformArg == nullptr)
3698
0
        return;
3699
3700
0
    GDALReprojectionTransformInfo *psInfo =
3701
0
        static_cast<GDALReprojectionTransformInfo *>(pTransformArg);
3702
3703
0
    if (psInfo->poForwardTransform)
3704
0
        OGRCoordinateTransformation::DestroyCT(psInfo->poForwardTransform);
3705
3706
0
    if (psInfo->poReverseTransform)
3707
0
        OGRCoordinateTransformation::DestroyCT(psInfo->poReverseTransform);
3708
3709
0
    CSLDestroy(psInfo->papszOptions);
3710
3711
0
    delete psInfo;
3712
0
}
3713
3714
/************************************************************************/
3715
/*                     GDALReprojectionTransform()                      */
3716
/************************************************************************/
3717
3718
/**
3719
 * Perform reprojection transformation.
3720
 *
3721
 * Actually performs the reprojection transformation described in
3722
 * GDALCreateReprojectionTransformer().  This function matches the
3723
 * GDALTransformerFunc() signature.  Details of the arguments are described
3724
 * there.
3725
 */
3726
3727
int GDALReprojectionTransform(void *pTransformArg, int bDstToSrc,
3728
                              int nPointCount, double *padfX, double *padfY,
3729
                              double *padfZ, int *panSuccess)
3730
3731
0
{
3732
0
    GDALReprojectionTransformInfo *psInfo =
3733
0
        static_cast<GDALReprojectionTransformInfo *>(pTransformArg);
3734
0
    int bSuccess;
3735
3736
0
    std::vector<double> adfTime;
3737
0
    double *padfT = nullptr;
3738
0
    if (psInfo->dfTime != 0.0 && nPointCount > 0)
3739
0
    {
3740
0
        adfTime.resize(nPointCount, psInfo->dfTime);
3741
0
        padfT = &adfTime[0];
3742
0
    }
3743
3744
0
    if (bDstToSrc)
3745
0
    {
3746
0
        if (psInfo->poReverseTransform == nullptr)
3747
0
        {
3748
0
            CPLError(
3749
0
                CE_Failure, CPLE_AppDefined,
3750
0
                "Inverse coordinate transformation cannot be instantiated");
3751
0
            if (panSuccess)
3752
0
            {
3753
0
                for (int i = 0; i < nPointCount; i++)
3754
0
                    panSuccess[i] = FALSE;
3755
0
            }
3756
0
            bSuccess = false;
3757
0
        }
3758
0
        else
3759
0
        {
3760
0
            bSuccess = psInfo->poReverseTransform->Transform(
3761
0
                nPointCount, padfX, padfY, padfZ, padfT, panSuccess);
3762
0
        }
3763
0
    }
3764
0
    else
3765
0
        bSuccess = psInfo->poForwardTransform->Transform(
3766
0
            nPointCount, padfX, padfY, padfZ, padfT, panSuccess);
3767
3768
0
    return bSuccess;
3769
0
}
3770
3771
/************************************************************************/
3772
/*                GDALSerializeReprojectionTransformer()                */
3773
/************************************************************************/
3774
3775
static CPLXMLNode *GDALSerializeReprojectionTransformer(void *pTransformArg)
3776
3777
0
{
3778
0
    CPLXMLNode *psTree;
3779
0
    GDALReprojectionTransformInfo *psInfo =
3780
0
        static_cast<GDALReprojectionTransformInfo *>(pTransformArg);
3781
3782
0
    psTree = CPLCreateXMLNode(nullptr, CXT_Element, "ReprojectionTransformer");
3783
3784
    /* -------------------------------------------------------------------- */
3785
    /*      Handle SourceCS.                                                */
3786
    /* -------------------------------------------------------------------- */
3787
0
    const auto ExportToWkt = [](const OGRSpatialReference *poSRS)
3788
0
    {
3789
        // Try first in WKT1 for backward compat
3790
0
        {
3791
0
            char *pszWKT = nullptr;
3792
0
            const char *const apszOptions[] = {"FORMAT=WKT1", nullptr};
3793
0
            CPLErrorHandlerPusher oHandler(CPLQuietErrorHandler);
3794
0
            CPLErrorStateBackuper oBackuper;
3795
0
            if (poSRS->exportToWkt(&pszWKT, apszOptions) == OGRERR_NONE)
3796
0
            {
3797
0
                std::string osRet(pszWKT);
3798
0
                CPLFree(pszWKT);
3799
0
                return osRet;
3800
0
            }
3801
0
            CPLFree(pszWKT);
3802
0
        }
3803
3804
0
        char *pszWKT = nullptr;
3805
0
        const char *const apszOptions[] = {"FORMAT=WKT2_2019", nullptr};
3806
0
        if (poSRS->exportToWkt(&pszWKT, apszOptions) == OGRERR_NONE)
3807
0
        {
3808
0
            std::string osRet(pszWKT);
3809
0
            CPLFree(pszWKT);
3810
0
            return osRet;
3811
0
        }
3812
0
        CPLFree(pszWKT);
3813
0
        return std::string();
3814
0
    };
3815
3816
0
    auto poSRS = psInfo->poForwardTransform->GetSourceCS();
3817
0
    if (poSRS)
3818
0
    {
3819
0
        const auto osWKT = ExportToWkt(poSRS);
3820
0
        CPLCreateXMLElementAndValue(psTree, "SourceSRS", osWKT.c_str());
3821
0
    }
3822
3823
    /* -------------------------------------------------------------------- */
3824
    /*      Handle DestinationCS.                                           */
3825
    /* -------------------------------------------------------------------- */
3826
0
    poSRS = psInfo->poForwardTransform->GetTargetCS();
3827
0
    if (poSRS)
3828
0
    {
3829
0
        const auto osWKT = ExportToWkt(poSRS);
3830
0
        CPLCreateXMLElementAndValue(psTree, "TargetSRS", osWKT.c_str());
3831
0
    }
3832
3833
    /* -------------------------------------------------------------------- */
3834
    /*      Serialize options.                                              */
3835
    /* -------------------------------------------------------------------- */
3836
0
    if (psInfo->papszOptions)
3837
0
    {
3838
0
        CPLXMLNode *psOptions =
3839
0
            CPLCreateXMLNode(psTree, CXT_Element, "Options");
3840
0
        for (auto iter = psInfo->papszOptions; *iter != nullptr; ++iter)
3841
0
        {
3842
0
            char *pszKey = nullptr;
3843
0
            const char *pszValue = CPLParseNameValue(*iter, &pszKey);
3844
0
            if (pszKey && pszValue)
3845
0
            {
3846
0
                auto elt =
3847
0
                    CPLCreateXMLElementAndValue(psOptions, "Option", pszValue);
3848
0
                CPLAddXMLAttributeAndValue(elt, "key", pszKey);
3849
0
            }
3850
0
            CPLFree(pszKey);
3851
0
        }
3852
0
    }
3853
3854
0
    return psTree;
3855
0
}
3856
3857
/************************************************************************/
3858
/*               GDALDeserializeReprojectionTransformer()               */
3859
/************************************************************************/
3860
3861
static void *GDALDeserializeReprojectionTransformer(CPLXMLNode *psTree)
3862
3863
0
{
3864
0
    const char *pszSourceSRS = CPLGetXMLValue(psTree, "SourceSRS", nullptr);
3865
0
    const char *pszTargetSRS = CPLGetXMLValue(psTree, "TargetSRS", nullptr);
3866
0
    char *pszSourceWKT = nullptr, *pszTargetWKT = nullptr;
3867
0
    void *pResult = nullptr;
3868
3869
0
    OGRSpatialReference oSrcSRS;
3870
0
    OGRSpatialReference oDstSRS;
3871
3872
0
    oSrcSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
3873
0
    oDstSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
3874
0
    if (pszSourceSRS != nullptr)
3875
0
    {
3876
0
        oSrcSRS.SetFromUserInput(pszSourceSRS);
3877
0
    }
3878
3879
0
    if (pszTargetSRS != nullptr)
3880
0
    {
3881
0
        oDstSRS.SetFromUserInput(pszTargetSRS);
3882
0
    }
3883
3884
0
    CPLStringList aosList;
3885
0
    const CPLXMLNode *psOptions = CPLGetXMLNode(psTree, "Options");
3886
0
    if (psOptions)
3887
0
    {
3888
0
        for (auto iter = psOptions->psChild; iter; iter = iter->psNext)
3889
0
        {
3890
0
            if (iter->eType == CXT_Element &&
3891
0
                strcmp(iter->pszValue, "Option") == 0)
3892
0
            {
3893
0
                const char *pszKey = CPLGetXMLValue(iter, "key", nullptr);
3894
0
                const char *pszValue = CPLGetXMLValue(iter, nullptr, nullptr);
3895
0
                if (pszKey && pszValue)
3896
0
                {
3897
0
                    aosList.SetNameValue(pszKey, pszValue);
3898
0
                }
3899
0
            }
3900
0
        }
3901
0
    }
3902
3903
0
    pResult = GDALCreateReprojectionTransformerEx(
3904
0
        !oSrcSRS.IsEmpty() ? OGRSpatialReference::ToHandle(&oSrcSRS) : nullptr,
3905
0
        !oDstSRS.IsEmpty() ? OGRSpatialReference::ToHandle(&oDstSRS) : nullptr,
3906
0
        aosList.List());
3907
3908
0
    CPLFree(pszSourceWKT);
3909
0
    CPLFree(pszTargetWKT);
3910
3911
0
    return pResult;
3912
0
}
3913
3914
/************************************************************************/
3915
/* ==================================================================== */
3916
/*      Approximate transformer.                                        */
3917
/* ==================================================================== */
3918
/************************************************************************/
3919
3920
/************************************************************************/
3921
/*                 GDALCreateSimilarApproxTransformer()                 */
3922
/************************************************************************/
3923
3924
static void *GDALCreateSimilarApproxTransformer(void *hTransformArg,
3925
                                                double dfSrcRatioX,
3926
                                                double dfSrcRatioY)
3927
0
{
3928
0
    VALIDATE_POINTER1(hTransformArg, "GDALCreateSimilarApproxTransformer",
3929
0
                      nullptr);
3930
3931
0
    GDALApproxTransformInfo *psInfo =
3932
0
        static_cast<GDALApproxTransformInfo *>(hTransformArg);
3933
3934
0
    void *pBaseCBData = GDALCreateSimilarTransformer(psInfo->pBaseCBData,
3935
0
                                                     dfSrcRatioX, dfSrcRatioY);
3936
0
    if (pBaseCBData == nullptr)
3937
0
    {
3938
0
        return nullptr;
3939
0
    }
3940
3941
0
    GDALApproxTransformInfo *psClonedInfo =
3942
0
        static_cast<GDALApproxTransformInfo *>(GDALCreateApproxTransformer2(
3943
0
            psInfo->pfnBaseTransformer, pBaseCBData, psInfo->dfMaxErrorForward,
3944
0
            psInfo->dfMaxErrorReverse));
3945
0
    psClonedInfo->bOwnSubtransformer = TRUE;
3946
3947
0
    return psClonedInfo;
3948
0
}
3949
3950
/************************************************************************/
3951
/*                   GDALSerializeApproxTransformer()                   */
3952
/************************************************************************/
3953
3954
static CPLXMLNode *GDALSerializeApproxTransformer(void *pTransformArg)
3955
3956
0
{
3957
0
    CPLXMLNode *psTree;
3958
0
    GDALApproxTransformInfo *psInfo =
3959
0
        static_cast<GDALApproxTransformInfo *>(pTransformArg);
3960
3961
0
    psTree = CPLCreateXMLNode(nullptr, CXT_Element, "ApproxTransformer");
3962
3963
    /* -------------------------------------------------------------------- */
3964
    /*      Attach max error.                                               */
3965
    /* -------------------------------------------------------------------- */
3966
0
    if (psInfo->dfMaxErrorForward == psInfo->dfMaxErrorReverse)
3967
0
    {
3968
0
        CPLCreateXMLElementAndValue(
3969
0
            psTree, "MaxError",
3970
0
            CPLString().Printf("%g", psInfo->dfMaxErrorForward));
3971
0
    }
3972
0
    else
3973
0
    {
3974
0
        CPLCreateXMLElementAndValue(
3975
0
            psTree, "MaxErrorForward",
3976
0
            CPLString().Printf("%g", psInfo->dfMaxErrorForward));
3977
0
        CPLCreateXMLElementAndValue(
3978
0
            psTree, "MaxErrorReverse",
3979
0
            CPLString().Printf("%g", psInfo->dfMaxErrorReverse));
3980
0
    }
3981
3982
    /* -------------------------------------------------------------------- */
3983
    /*      Capture underlying transformer.                                 */
3984
    /* -------------------------------------------------------------------- */
3985
0
    CPLXMLNode *psTransformerContainer =
3986
0
        CPLCreateXMLNode(psTree, CXT_Element, "BaseTransformer");
3987
3988
0
    CPLXMLNode *psTransformer = GDALSerializeTransformer(
3989
0
        psInfo->pfnBaseTransformer, psInfo->pBaseCBData);
3990
0
    if (psTransformer != nullptr)
3991
0
        CPLAddXMLChild(psTransformerContainer, psTransformer);
3992
3993
0
    return psTree;
3994
0
}
3995
3996
/************************************************************************/
3997
/*                    GDALCreateApproxTransformer()                     */
3998
/************************************************************************/
3999
4000
/**
4001
 * Create an approximating transformer.
4002
 *
4003
 * This function creates a context for an approximated transformer.  Basically
4004
 * a high precision transformer is supplied as input and internally linear
4005
 * approximations are computed to generate results to within a defined
4006
 * precision.
4007
 *
4008
 * The approximation is actually done at the point where GDALApproxTransform()
4009
 * calls are made, and depend on the assumption that they are roughly linear.
4010
 * The first and last point passed in must be the extreme values and the
4011
 * intermediate values should describe a curve between the end points.  The
4012
 * approximator transforms and centers using the approximate transformer, and
4013
 * then compares the true middle transformed value to a linear approximation
4014
 * based on the end points.  If the error is within the supplied threshold then
4015
 * the end points are used to linearly approximate all the values otherwise the
4016
 * input points are split into two smaller sets, and the function is recursively
4017
 * called until a sufficiently small set of points is found that the linear
4018
 * approximation is OK, or that all the points are exactly computed.
4019
 *
4020
 * This function is very suitable for approximating transformation results
4021
 * from output pixel/line space to input coordinates for warpers that operate
4022
 * on one input scanline at a time.  Care should be taken using it in other
4023
 * circumstances as little internal validation is done in order to keep things
4024
 * fast.
4025
 *
4026
 * @param pfnBaseTransformer the high precision transformer which should be
4027
 * approximated.
4028
 * @param pBaseTransformArg the callback argument for the high precision
4029
 * transformer.
4030
 * @param dfMaxError the maximum cartesian error in the "output" space that
4031
 * is to be accepted in the linear approximation, evaluated as a Manhattan
4032
 * distance.
4033
 *
4034
 * @return callback pointer suitable for use with GDALApproxTransform().  It
4035
 * should be deallocated with GDALDestroyApproxTransformer().
4036
 */
4037
4038
void *GDALCreateApproxTransformer(GDALTransformerFunc pfnBaseTransformer,
4039
                                  void *pBaseTransformArg, double dfMaxError)
4040
4041
0
{
4042
0
    return GDALCreateApproxTransformer2(pfnBaseTransformer, pBaseTransformArg,
4043
0
                                        dfMaxError, dfMaxError);
4044
0
}
4045
4046
static void *
4047
GDALCreateApproxTransformer2(GDALTransformerFunc pfnBaseTransformer,
4048
                             void *pBaseTransformArg, double dfMaxErrorForward,
4049
                             double dfMaxErrorReverse)
4050
4051
0
{
4052
0
    GDALApproxTransformInfo *psATInfo = new GDALApproxTransformInfo;
4053
0
    psATInfo->pfnBaseTransformer = pfnBaseTransformer;
4054
0
    psATInfo->pBaseCBData = pBaseTransformArg;
4055
0
    psATInfo->dfMaxErrorForward = dfMaxErrorForward;
4056
0
    psATInfo->dfMaxErrorReverse = dfMaxErrorReverse;
4057
0
    psATInfo->bOwnSubtransformer = FALSE;
4058
4059
0
    memcpy(psATInfo->sTI.abySignature, GDAL_GTI2_SIGNATURE,
4060
0
           strlen(GDAL_GTI2_SIGNATURE));
4061
0
    psATInfo->sTI.pszClassName = GDAL_APPROX_TRANSFORMER_CLASS_NAME;
4062
0
    psATInfo->sTI.pfnTransform = GDALApproxTransform;
4063
0
    psATInfo->sTI.pfnCleanup = GDALDestroyApproxTransformer;
4064
0
    psATInfo->sTI.pfnSerialize = GDALSerializeApproxTransformer;
4065
0
    psATInfo->sTI.pfnCreateSimilar = GDALCreateSimilarApproxTransformer;
4066
4067
0
    return psATInfo;
4068
0
}
4069
4070
/************************************************************************/
4071
/*              GDALApproxTransformerOwnsSubtransformer()               */
4072
/************************************************************************/
4073
4074
/** Set bOwnSubtransformer flag */
4075
void GDALApproxTransformerOwnsSubtransformer(void *pCBData, int bOwnFlag)
4076
4077
0
{
4078
0
    GDALApproxTransformInfo *psATInfo =
4079
0
        static_cast<GDALApproxTransformInfo *>(pCBData);
4080
4081
0
    psATInfo->bOwnSubtransformer = bOwnFlag;
4082
0
}
4083
4084
/************************************************************************/
4085
/*                    GDALDestroyApproxTransformer()                    */
4086
/************************************************************************/
4087
4088
/**
4089
 * Cleanup approximate transformer.
4090
 *
4091
 * Deallocates the resources allocated by GDALCreateApproxTransformer().
4092
 *
4093
 * @param pCBData callback data originally returned by
4094
 * GDALCreateApproxTransformer().
4095
 */
4096
4097
void GDALDestroyApproxTransformer(void *pCBData)
4098
4099
0
{
4100
0
    if (pCBData == nullptr)
4101
0
        return;
4102
4103
0
    GDALApproxTransformInfo *psATInfo =
4104
0
        static_cast<GDALApproxTransformInfo *>(pCBData);
4105
4106
0
    if (psATInfo->bOwnSubtransformer)
4107
0
        GDALDestroyTransformer(psATInfo->pBaseCBData);
4108
4109
0
    delete psATInfo;
4110
0
}
4111
4112
/************************************************************************/
4113
/*                    GDALRefreshApproxTransformer()                    */
4114
/************************************************************************/
4115
4116
void GDALRefreshApproxTransformer(void *hTransformArg)
4117
0
{
4118
0
    GDALApproxTransformInfo *psInfo =
4119
0
        static_cast<GDALApproxTransformInfo *>(hTransformArg);
4120
4121
0
    if (GDALIsTransformer(psInfo->pBaseCBData,
4122
0
                          GDAL_GEN_IMG_TRANSFORMER_CLASS_NAME))
4123
0
    {
4124
0
        GDALRefreshGenImgProjTransformer(psInfo->pBaseCBData);
4125
0
    }
4126
0
}
4127
4128
/************************************************************************/
4129
/*                    GDALApproxTransformInternal()                     */
4130
/************************************************************************/
4131
4132
static int GDALApproxTransformInternal(void *pCBData, int bDstToSrc,
4133
                                       int nPoints, double *x, double *y,
4134
                                       double *z, int *panSuccess,
4135
                                       // SME = Start, Middle, End.
4136
                                       const double xSMETransformed[3],
4137
                                       const double ySMETransformed[3],
4138
                                       const double zSMETransformed[3])
4139
0
{
4140
0
    GDALApproxTransformInfo *psATInfo =
4141
0
        static_cast<GDALApproxTransformInfo *>(pCBData);
4142
0
    const int nMiddle = (nPoints - 1) / 2;
4143
4144
#ifdef notdef_sanify_check
4145
    {
4146
        double x2[3] = {x[0], x[nMiddle], x[nPoints - 1]};
4147
        double y2[3] = {y[0], y[nMiddle], y[nPoints - 1]};
4148
        double z2[3] = {z[0], z[nMiddle], z[nPoints - 1]};
4149
        int anSuccess2[3] = {};
4150
4151
        const int bSuccess = psATInfo->pfnBaseTransformer(
4152
            psATInfo->pBaseCBData, bDstToSrc, 3, x2, y2, z2, anSuccess2);
4153
        CPLAssert(bSuccess);
4154
        CPLAssert(anSuccess2[0]);
4155
        CPLAssert(anSuccess2[1]);
4156
        CPLAssert(anSuccess2[2]);
4157
        CPLAssert(x2[0] == xSMETransformed[0]);
4158
        CPLAssert(y2[0] == ySMETransformed[0]);
4159
        CPLAssert(z2[0] == zSMETransformed[0]);
4160
        CPLAssert(x2[1] == xSMETransformed[1]);
4161
        CPLAssert(y2[1] == ySMETransformed[1]);
4162
        CPLAssert(z2[1] == zSMETransformed[1]);
4163
        CPLAssert(x2[2] == xSMETransformed[2]);
4164
        CPLAssert(y2[2] == ySMETransformed[2]);
4165
        CPLAssert(z2[2] == zSMETransformed[2]);
4166
    }
4167
#endif
4168
4169
#ifdef DEBUG_APPROX_TRANSFORMER
4170
    fprintf(stderr, "start (%.3f,%.3f) -> (%.3f,%.3f)\n", /*ok*/
4171
            x[0], y[0], xSMETransformed[0], ySMETransformed[0]);
4172
    fprintf(stderr, "middle (%.3f,%.3f) -> (%.3f,%.3f)\n", /*ok*/
4173
            x[nMiddle], y[nMiddle], xSMETransformed[1], ySMETransformed[1]);
4174
    fprintf(stderr, "end (%.3f,%.3f) -> (%.3f,%.3f)\n", /*ok*/
4175
            x[nPoints - 1], y[nPoints - 1], xSMETransformed[2],
4176
            ySMETransformed[2]);
4177
#endif
4178
4179
    /* -------------------------------------------------------------------- */
4180
    /*      Is the error at the middle acceptable relative to an            */
4181
    /*      interpolation of the middle position?                           */
4182
    /* -------------------------------------------------------------------- */
4183
0
    const double dfDeltaX =
4184
0
        (xSMETransformed[2] - xSMETransformed[0]) / (x[nPoints - 1] - x[0]);
4185
0
    const double dfDeltaY =
4186
0
        (ySMETransformed[2] - ySMETransformed[0]) / (x[nPoints - 1] - x[0]);
4187
0
    const double dfDeltaZ =
4188
0
        (zSMETransformed[2] - zSMETransformed[0]) / (x[nPoints - 1] - x[0]);
4189
4190
0
    const double dfError =
4191
0
        fabs((xSMETransformed[0] + dfDeltaX * (x[nMiddle] - x[0])) -
4192
0
             xSMETransformed[1]) +
4193
0
        fabs((ySMETransformed[0] + dfDeltaY * (x[nMiddle] - x[0])) -
4194
0
             ySMETransformed[1]);
4195
4196
0
    const double dfMaxError =
4197
0
        (bDstToSrc) ? psATInfo->dfMaxErrorReverse : psATInfo->dfMaxErrorForward;
4198
0
    if (dfError > dfMaxError)
4199
0
    {
4200
#if DEBUG_VERBOSE
4201
        CPLDebug("GDAL",
4202
                 "ApproxTransformer - "
4203
                 "error %g over threshold %g, subdivide %d points.",
4204
                 dfError, dfMaxError, nPoints);
4205
#endif
4206
4207
0
        double xMiddle[3] = {x[(nMiddle - 1) / 2], x[nMiddle - 1],
4208
0
                             x[nMiddle + (nPoints - nMiddle - 1) / 2]};
4209
0
        double yMiddle[3] = {y[(nMiddle - 1) / 2], y[nMiddle - 1],
4210
0
                             y[nMiddle + (nPoints - nMiddle - 1) / 2]};
4211
0
        double zMiddle[3] = {z[(nMiddle - 1) / 2], z[nMiddle - 1],
4212
0
                             z[nMiddle + (nPoints - nMiddle - 1) / 2]};
4213
4214
0
        const bool bUseBaseTransformForHalf1 =
4215
0
            nMiddle <= 5 || y[0] != y[nMiddle - 1] ||
4216
0
            y[0] != y[(nMiddle - 1) / 2] || x[0] == x[nMiddle - 1] ||
4217
0
            x[0] == x[(nMiddle - 1) / 2];
4218
0
        const bool bUseBaseTransformForHalf2 =
4219
0
            nPoints - nMiddle <= 5 || y[nMiddle] != y[nPoints - 1] ||
4220
0
            y[nMiddle] != y[nMiddle + (nPoints - nMiddle - 1) / 2] ||
4221
0
            x[nMiddle] == x[nPoints - 1] ||
4222
0
            x[nMiddle] == x[nMiddle + (nPoints - nMiddle - 1) / 2];
4223
4224
0
        int anSuccess2[3] = {};
4225
0
        int bSuccess = FALSE;
4226
0
        if (!bUseBaseTransformForHalf1 && !bUseBaseTransformForHalf2)
4227
0
            bSuccess = psATInfo->pfnBaseTransformer(
4228
0
                psATInfo->pBaseCBData, bDstToSrc, 3, xMiddle, yMiddle, zMiddle,
4229
0
                anSuccess2);
4230
0
        else if (!bUseBaseTransformForHalf1)
4231
0
        {
4232
0
            bSuccess = psATInfo->pfnBaseTransformer(
4233
0
                psATInfo->pBaseCBData, bDstToSrc, 2, xMiddle, yMiddle, zMiddle,
4234
0
                anSuccess2);
4235
0
            anSuccess2[2] = TRUE;
4236
0
        }
4237
0
        else if (!bUseBaseTransformForHalf2)
4238
0
        {
4239
0
            bSuccess = psATInfo->pfnBaseTransformer(
4240
0
                psATInfo->pBaseCBData, bDstToSrc, 1, xMiddle + 2, yMiddle + 2,
4241
0
                zMiddle + 2, anSuccess2 + 2);
4242
0
            anSuccess2[0] = TRUE;
4243
0
            anSuccess2[1] = TRUE;
4244
0
        }
4245
4246
0
        if (!bSuccess || !anSuccess2[0] || !anSuccess2[1] || !anSuccess2[2])
4247
0
        {
4248
0
            bSuccess = psATInfo->pfnBaseTransformer(
4249
0
                psATInfo->pBaseCBData, bDstToSrc, nMiddle - 1, x + 1, y + 1,
4250
0
                z + 1, panSuccess + 1);
4251
0
            bSuccess &= psATInfo->pfnBaseTransformer(
4252
0
                psATInfo->pBaseCBData, bDstToSrc, nPoints - nMiddle - 2,
4253
0
                x + nMiddle + 1, y + nMiddle + 1, z + nMiddle + 1,
4254
0
                panSuccess + nMiddle + 1);
4255
4256
0
            x[0] = xSMETransformed[0];
4257
0
            y[0] = ySMETransformed[0];
4258
0
            z[0] = zSMETransformed[0];
4259
0
            panSuccess[0] = TRUE;
4260
0
            x[nMiddle] = xSMETransformed[1];
4261
0
            y[nMiddle] = ySMETransformed[1];
4262
0
            z[nMiddle] = zSMETransformed[1];
4263
0
            panSuccess[nMiddle] = TRUE;
4264
0
            x[nPoints - 1] = xSMETransformed[2];
4265
0
            y[nPoints - 1] = ySMETransformed[2];
4266
0
            z[nPoints - 1] = zSMETransformed[2];
4267
0
            panSuccess[nPoints - 1] = TRUE;
4268
0
            return bSuccess;
4269
0
        }
4270
4271
0
        double x2[3] = {};
4272
0
        double y2[3] = {};
4273
0
        double z2[3] = {};
4274
0
        if (!bUseBaseTransformForHalf1)
4275
0
        {
4276
0
            x2[0] = xSMETransformed[0];
4277
0
            y2[0] = ySMETransformed[0];
4278
0
            z2[0] = zSMETransformed[0];
4279
0
            x2[1] = xMiddle[0];
4280
0
            y2[1] = yMiddle[0];
4281
0
            z2[1] = zMiddle[0];
4282
0
            x2[2] = xMiddle[1];
4283
0
            y2[2] = yMiddle[1];
4284
0
            z2[2] = zMiddle[1];
4285
4286
0
            bSuccess = GDALApproxTransformInternal(
4287
0
                psATInfo, bDstToSrc, nMiddle, x, y, z, panSuccess, x2, y2, z2);
4288
0
        }
4289
0
        else
4290
0
        {
4291
0
            bSuccess = psATInfo->pfnBaseTransformer(
4292
0
                psATInfo->pBaseCBData, bDstToSrc, nMiddle - 1, x + 1, y + 1,
4293
0
                z + 1, panSuccess + 1);
4294
0
            x[0] = xSMETransformed[0];
4295
0
            y[0] = ySMETransformed[0];
4296
0
            z[0] = zSMETransformed[0];
4297
0
            panSuccess[0] = TRUE;
4298
0
        }
4299
4300
0
        if (!bSuccess)
4301
0
            return FALSE;
4302
4303
0
        if (!bUseBaseTransformForHalf2)
4304
0
        {
4305
0
            x2[0] = xSMETransformed[1];
4306
0
            y2[0] = ySMETransformed[1];
4307
0
            z2[0] = zSMETransformed[1];
4308
0
            x2[1] = xMiddle[2];
4309
0
            y2[1] = yMiddle[2];
4310
0
            z2[1] = zMiddle[2];
4311
0
            x2[2] = xSMETransformed[2];
4312
0
            y2[2] = ySMETransformed[2];
4313
0
            z2[2] = zSMETransformed[2];
4314
4315
0
            bSuccess = GDALApproxTransformInternal(
4316
0
                psATInfo, bDstToSrc, nPoints - nMiddle, x + nMiddle,
4317
0
                y + nMiddle, z + nMiddle, panSuccess + nMiddle, x2, y2, z2);
4318
0
        }
4319
0
        else
4320
0
        {
4321
0
            bSuccess = psATInfo->pfnBaseTransformer(
4322
0
                psATInfo->pBaseCBData, bDstToSrc, nPoints - nMiddle - 2,
4323
0
                x + nMiddle + 1, y + nMiddle + 1, z + nMiddle + 1,
4324
0
                panSuccess + nMiddle + 1);
4325
4326
0
            x[nMiddle] = xSMETransformed[1];
4327
0
            y[nMiddle] = ySMETransformed[1];
4328
0
            z[nMiddle] = zSMETransformed[1];
4329
0
            panSuccess[nMiddle] = TRUE;
4330
0
            x[nPoints - 1] = xSMETransformed[2];
4331
0
            y[nPoints - 1] = ySMETransformed[2];
4332
0
            z[nPoints - 1] = zSMETransformed[2];
4333
0
            panSuccess[nPoints - 1] = TRUE;
4334
0
        }
4335
4336
0
        if (!bSuccess)
4337
0
            return FALSE;
4338
4339
0
        return TRUE;
4340
0
    }
4341
4342
    /* -------------------------------------------------------------------- */
4343
    /*      Error is OK since this is just used to compute output bounds    */
4344
    /*      of newly created file for gdalwarper.  So just use affine       */
4345
    /*      approximation of the reverse transform.  Eventually we          */
4346
    /*      should implement iterative searching to find a result within    */
4347
    /*      our error threshold.                                            */
4348
    /*      NOTE: the above comment is not true: gdalwarp uses approximator */
4349
    /*      also to compute the source pixel of each target pixel.          */
4350
    /* -------------------------------------------------------------------- */
4351
0
    for (int i = nPoints - 1; i >= 0; i--)
4352
0
    {
4353
#ifdef check_error
4354
        double xtemp = x[i];
4355
        double ytemp = y[i];
4356
        double ztemp = z[i];
4357
        double x_ori = xtemp;
4358
        double y_ori = ytemp;
4359
        int btemp = FALSE;
4360
        psATInfo->pfnBaseTransformer(psATInfo->pBaseCBData, bDstToSrc, 1,
4361
                                     &xtemp, &ytemp, &ztemp, &btemp);
4362
#endif
4363
0
        const double dfDist = (x[i] - x[0]);
4364
0
        x[i] = xSMETransformed[0] + dfDeltaX * dfDist;
4365
0
        y[i] = ySMETransformed[0] + dfDeltaY * dfDist;
4366
0
        z[i] = zSMETransformed[0] + dfDeltaZ * dfDist;
4367
#ifdef check_error
4368
        const double dfError2 = fabs(x[i] - xtemp) + fabs(y[i] - ytemp);
4369
        if (dfError2 > 4 /*10 * dfMaxError*/)
4370
        {
4371
            /*ok*/ printf("Error = %f on (%f, %f)\n", dfError2, x_ori, y_ori);
4372
        }
4373
#endif
4374
0
        panSuccess[i] = TRUE;
4375
0
    }
4376
4377
0
    return TRUE;
4378
0
}
4379
4380
/************************************************************************/
4381
/*                        GDALApproxTransform()                         */
4382
/************************************************************************/
4383
4384
/**
4385
 * Perform approximate transformation.
4386
 *
4387
 * Actually performs the approximate transformation described in
4388
 * GDALCreateApproxTransformer().  This function matches the
4389
 * GDALTransformerFunc() signature.  Details of the arguments are described
4390
 * there.
4391
 */
4392
4393
int GDALApproxTransform(void *pCBData, int bDstToSrc, int nPoints, double *x,
4394
                        double *y, double *z, int *panSuccess)
4395
4396
0
{
4397
0
    GDALApproxTransformInfo *psATInfo =
4398
0
        static_cast<GDALApproxTransformInfo *>(pCBData);
4399
0
    double x2[3] = {};
4400
0
    double y2[3] = {};
4401
0
    double z2[3] = {};
4402
0
    int anSuccess2[3] = {};
4403
0
    int bSuccess;
4404
4405
0
    const int nMiddle = (nPoints - 1) / 2;
4406
4407
    /* -------------------------------------------------------------------- */
4408
    /*      Bail if our preconditions are not met, or if error is not       */
4409
    /*      acceptable.                                                     */
4410
    /* -------------------------------------------------------------------- */
4411
0
    int bRet = FALSE;
4412
0
    if (y[0] != y[nPoints - 1] || y[0] != y[nMiddle] ||
4413
0
        x[0] == x[nPoints - 1] || x[0] == x[nMiddle] ||
4414
0
        (psATInfo->dfMaxErrorForward == 0.0 &&
4415
0
         psATInfo->dfMaxErrorReverse == 0.0) ||
4416
0
        nPoints <= 5)
4417
0
    {
4418
0
        bRet = psATInfo->pfnBaseTransformer(psATInfo->pBaseCBData, bDstToSrc,
4419
0
                                            nPoints, x, y, z, panSuccess);
4420
0
        goto end;
4421
0
    }
4422
4423
    /* -------------------------------------------------------------------- */
4424
    /*      Transform first, last and middle point.                         */
4425
    /* -------------------------------------------------------------------- */
4426
0
    x2[0] = x[0];
4427
0
    y2[0] = y[0];
4428
0
    z2[0] = z[0];
4429
0
    x2[1] = x[nMiddle];
4430
0
    y2[1] = y[nMiddle];
4431
0
    z2[1] = z[nMiddle];
4432
0
    x2[2] = x[nPoints - 1];
4433
0
    y2[2] = y[nPoints - 1];
4434
0
    z2[2] = z[nPoints - 1];
4435
4436
0
    bSuccess = psATInfo->pfnBaseTransformer(psATInfo->pBaseCBData, bDstToSrc, 3,
4437
0
                                            x2, y2, z2, anSuccess2);
4438
0
    if (!bSuccess || !anSuccess2[0] || !anSuccess2[1] || !anSuccess2[2])
4439
0
    {
4440
0
        bRet = psATInfo->pfnBaseTransformer(psATInfo->pBaseCBData, bDstToSrc,
4441
0
                                            nPoints, x, y, z, panSuccess);
4442
0
        goto end;
4443
0
    }
4444
4445
0
    bRet = GDALApproxTransformInternal(pCBData, bDstToSrc, nPoints, x, y, z,
4446
0
                                       panSuccess, x2, y2, z2);
4447
4448
0
end:
4449
#ifdef DEBUG_APPROX_TRANSFORMER
4450
    for (int i = 0; i < nPoints; i++)
4451
        fprintf(stderr, "[%d] (%.10f,%.10f) %d\n", /*ok*/
4452
                i, x[i], y[i], panSuccess[i]);
4453
#endif
4454
4455
0
    return bRet;
4456
0
}
4457
4458
/************************************************************************/
4459
/*                  GDALDeserializeApproxTransformer()                  */
4460
/************************************************************************/
4461
4462
static void *GDALDeserializeApproxTransformer(CPLXMLNode *psTree)
4463
4464
0
{
4465
0
    double dfMaxErrorForward = 0.25;
4466
0
    double dfMaxErrorReverse = 0.25;
4467
0
    const char *pszMaxError = CPLGetXMLValue(psTree, "MaxError", nullptr);
4468
0
    if (pszMaxError != nullptr)
4469
0
    {
4470
0
        dfMaxErrorForward = CPLAtof(pszMaxError);
4471
0
        dfMaxErrorReverse = dfMaxErrorForward;
4472
0
    }
4473
0
    const char *pszMaxErrorForward =
4474
0
        CPLGetXMLValue(psTree, "MaxErrorForward", nullptr);
4475
0
    if (pszMaxErrorForward != nullptr)
4476
0
    {
4477
0
        dfMaxErrorForward = CPLAtof(pszMaxErrorForward);
4478
0
    }
4479
0
    const char *pszMaxErrorReverse =
4480
0
        CPLGetXMLValue(psTree, "MaxErrorReverse", nullptr);
4481
0
    if (pszMaxErrorReverse != nullptr)
4482
0
    {
4483
0
        dfMaxErrorReverse = CPLAtof(pszMaxErrorReverse);
4484
0
    }
4485
4486
0
    GDALTransformerFunc pfnBaseTransform = nullptr;
4487
0
    void *pBaseCBData = nullptr;
4488
4489
0
    CPLXMLNode *psContainer = CPLGetXMLNode(psTree, "BaseTransformer");
4490
4491
0
    if (psContainer != nullptr && psContainer->psChild != nullptr)
4492
0
    {
4493
0
        GDALDeserializeTransformer(psContainer->psChild, &pfnBaseTransform,
4494
0
                                   &pBaseCBData);
4495
0
    }
4496
4497
0
    if (pfnBaseTransform == nullptr)
4498
0
    {
4499
0
        CPLError(CE_Failure, CPLE_AppDefined,
4500
0
                 "Cannot get base transform for approx transformer.");
4501
0
        return nullptr;
4502
0
    }
4503
4504
0
    void *pApproxCBData = GDALCreateApproxTransformer2(
4505
0
        pfnBaseTransform, pBaseCBData, dfMaxErrorForward, dfMaxErrorReverse);
4506
0
    GDALApproxTransformerOwnsSubtransformer(pApproxCBData, TRUE);
4507
4508
0
    return pApproxCBData;
4509
0
}
4510
4511
/************************************************************************/
4512
/*             GDALTransformLonLatToDestApproxTransformer()             */
4513
/************************************************************************/
4514
4515
int GDALTransformLonLatToDestApproxTransformer(void *hTransformArg,
4516
                                               double *pdfX, double *pdfY)
4517
0
{
4518
0
    GDALApproxTransformInfo *psInfo =
4519
0
        static_cast<GDALApproxTransformInfo *>(hTransformArg);
4520
4521
0
    if (GDALIsTransformer(psInfo->pBaseCBData,
4522
0
                          GDAL_GEN_IMG_TRANSFORMER_CLASS_NAME))
4523
0
    {
4524
0
        return GDALTransformLonLatToDestGenImgProjTransformer(
4525
0
            psInfo->pBaseCBData, pdfX, pdfY);
4526
0
    }
4527
0
    return false;
4528
0
}
4529
4530
/************************************************************************/
4531
/*                       GDALApplyGeoTransform()                        */
4532
/************************************************************************/
4533
4534
/**
4535
 * Apply GeoTransform to x/y coordinate.
4536
 *
4537
 * Applies the following computation, converting a (pixel, line) coordinate
4538
 * into a georeferenced (geo_x, geo_y) location.
4539
 * \code{.c}
4540
 *  *pdfGeoX = padfGeoTransform[0] + dfPixel * padfGeoTransform[1]
4541
 *                                 + dfLine  * padfGeoTransform[2];
4542
 *  *pdfGeoY = padfGeoTransform[3] + dfPixel * padfGeoTransform[4]
4543
 *                                 + dfLine  * padfGeoTransform[5];
4544
 * \endcode
4545
 *
4546
 * @param padfGeoTransform Six coefficient GeoTransform to apply.
4547
 * @param dfPixel Input pixel position.
4548
 * @param dfLine Input line position.
4549
 * @param pdfGeoX output location where geo_x (easting/longitude)
4550
 * location is placed.
4551
 * @param pdfGeoY output location where geo_y (northing/latitude)
4552
 * location is placed.
4553
 */
4554
4555
void CPL_STDCALL GDALApplyGeoTransform(const double *padfGeoTransform,
4556
                                       double dfPixel, double dfLine,
4557
                                       double *pdfGeoX, double *pdfGeoY)
4558
0
{
4559
0
    *pdfGeoX = padfGeoTransform[0] + dfPixel * padfGeoTransform[1] +
4560
0
               dfLine * padfGeoTransform[2];
4561
0
    *pdfGeoY = padfGeoTransform[3] + dfPixel * padfGeoTransform[4] +
4562
0
               dfLine * padfGeoTransform[5];
4563
0
}
4564
4565
/************************************************************************/
4566
/*                        GDALInvGeoTransform()                         */
4567
/************************************************************************/
4568
4569
/**
4570
 * Invert Geotransform.
4571
 *
4572
 * This function will invert a standard 3x2 set of GeoTransform coefficients.
4573
 * This converts the equation from being pixel to geo to being geo to pixel.
4574
 *
4575
 * @param gt_in Input geotransform (six doubles - unaltered).
4576
 * @param gt_out Output geotransform (six doubles - updated).
4577
 *
4578
 * @return TRUE on success or FALSE if the equation is uninvertable.
4579
 */
4580
4581
int CPL_STDCALL GDALInvGeoTransform(const double *gt_in, double *gt_out)
4582
4583
0
{
4584
    // Special case - no rotation - to avoid computing determinate
4585
    // and potential precision issues.
4586
0
    if (gt_in[2] == 0.0 && gt_in[4] == 0.0 && gt_in[1] != 0.0 &&
4587
0
        gt_in[5] != 0.0)
4588
0
    {
4589
        /*X = gt_in[0] + x * gt_in[1]
4590
          Y = gt_in[3] + y * gt_in[5]
4591
          -->
4592
          x = -gt_in[0] / gt_in[1] + (1 / gt_in[1]) * X
4593
          y = -gt_in[3] / gt_in[5] + (1 / gt_in[5]) * Y
4594
        */
4595
0
        gt_out[0] = -gt_in[0] / gt_in[1];
4596
0
        gt_out[1] = 1.0 / gt_in[1];
4597
0
        gt_out[2] = 0.0;
4598
0
        gt_out[3] = -gt_in[3] / gt_in[5];
4599
0
        gt_out[4] = 0.0;
4600
0
        gt_out[5] = 1.0 / gt_in[5];
4601
0
        return 1;
4602
0
    }
4603
4604
    // Assume a 3rd row that is [1 0 0].
4605
4606
    // Compute determinate.
4607
4608
0
    const double det = gt_in[1] * gt_in[5] - gt_in[2] * gt_in[4];
4609
0
    const double magnitude = std::max(std::max(fabs(gt_in[1]), fabs(gt_in[2])),
4610
0
                                      std::max(fabs(gt_in[4]), fabs(gt_in[5])));
4611
4612
0
    if (fabs(det) <= 1e-10 * magnitude * magnitude)
4613
0
        return 0;
4614
4615
0
    const double inv_det = 1.0 / det;
4616
4617
    // Compute adjoint, and divide by determinate.
4618
4619
0
    gt_out[1] = gt_in[5] * inv_det;
4620
0
    gt_out[4] = -gt_in[4] * inv_det;
4621
4622
0
    gt_out[2] = -gt_in[2] * inv_det;
4623
0
    gt_out[5] = gt_in[1] * inv_det;
4624
4625
0
    gt_out[0] = (gt_in[2] * gt_in[3] - gt_in[0] * gt_in[5]) * inv_det;
4626
0
    gt_out[3] = (-gt_in[1] * gt_in[3] + gt_in[0] * gt_in[4]) * inv_det;
4627
4628
0
    return 1;
4629
0
}
4630
4631
/************************************************************************/
4632
/*                      GDALSerializeTransformer()                      */
4633
/************************************************************************/
4634
4635
CPLXMLNode *GDALSerializeTransformer(GDALTransformerFunc /* pfnFunc */,
4636
                                     void *pTransformArg)
4637
0
{
4638
0
    VALIDATE_POINTER1(pTransformArg, "GDALSerializeTransformer", nullptr);
4639
4640
0
    GDALTransformerInfo *psInfo =
4641
0
        static_cast<GDALTransformerInfo *>(pTransformArg);
4642
4643
0
    if (psInfo == nullptr || memcmp(psInfo->abySignature, GDAL_GTI2_SIGNATURE,
4644
0
                                    strlen(GDAL_GTI2_SIGNATURE)) != 0)
4645
0
    {
4646
0
        CPLError(CE_Failure, CPLE_AppDefined,
4647
0
                 "Attempt to serialize non-GTI2 transformer.");
4648
0
        return nullptr;
4649
0
    }
4650
0
    else if (psInfo->pfnSerialize == nullptr)
4651
0
    {
4652
0
        CPLError(CE_Failure, CPLE_AppDefined,
4653
0
                 "No serialization function available for this transformer.");
4654
0
        return nullptr;
4655
0
    }
4656
4657
0
    return psInfo->pfnSerialize(pTransformArg);
4658
0
}
4659
4660
/************************************************************************/
4661
/*                 GDALRegisterTransformDeserializer()                  */
4662
/************************************************************************/
4663
4664
static CPLList *psListDeserializer = nullptr;
4665
static CPLMutex *hDeserializerMutex = nullptr;
4666
4667
typedef struct
4668
{
4669
    char *pszTransformName;
4670
    GDALTransformerFunc pfnTransformerFunc;
4671
    GDALTransformDeserializeFunc pfnDeserializeFunc;
4672
} TransformDeserializerInfo;
4673
4674
void *GDALRegisterTransformDeserializer(
4675
    const char *pszTransformName, GDALTransformerFunc pfnTransformerFunc,
4676
    GDALTransformDeserializeFunc pfnDeserializeFunc)
4677
0
{
4678
0
    TransformDeserializerInfo *psInfo =
4679
0
        static_cast<TransformDeserializerInfo *>(
4680
0
            CPLMalloc(sizeof(TransformDeserializerInfo)));
4681
0
    psInfo->pszTransformName = CPLStrdup(pszTransformName);
4682
0
    psInfo->pfnTransformerFunc = pfnTransformerFunc;
4683
0
    psInfo->pfnDeserializeFunc = pfnDeserializeFunc;
4684
4685
0
    CPLMutexHolderD(&hDeserializerMutex);
4686
0
    psListDeserializer = CPLListInsert(psListDeserializer, psInfo, 0);
4687
4688
0
    return psInfo;
4689
0
}
4690
4691
/************************************************************************/
4692
/*                GDALUnregisterTransformDeserializer()                 */
4693
/************************************************************************/
4694
4695
void GDALUnregisterTransformDeserializer(void *pData)
4696
0
{
4697
0
    CPLMutexHolderD(&hDeserializerMutex);
4698
0
    CPLList *psList = psListDeserializer;
4699
0
    CPLList *psLast = nullptr;
4700
0
    while (psList)
4701
0
    {
4702
0
        if (psList->pData == pData)
4703
0
        {
4704
0
            TransformDeserializerInfo *psInfo =
4705
0
                static_cast<TransformDeserializerInfo *>(pData);
4706
0
            CPLFree(psInfo->pszTransformName);
4707
0
            CPLFree(pData);
4708
0
            if (psLast)
4709
0
                psLast->psNext = psList->psNext;
4710
0
            else
4711
0
                psListDeserializer = nullptr;
4712
0
            CPLFree(psList);
4713
0
            break;
4714
0
        }
4715
0
        psLast = psList;
4716
0
        psList = psList->psNext;
4717
0
    }
4718
0
}
4719
4720
/************************************************************************/
4721
/*                GDALUnregisterTransformDeserializer()                 */
4722
/************************************************************************/
4723
4724
void GDALCleanupTransformDeserializerMutex()
4725
0
{
4726
0
    if (hDeserializerMutex != nullptr)
4727
0
    {
4728
0
        CPLDestroyMutex(hDeserializerMutex);
4729
0
        hDeserializerMutex = nullptr;
4730
0
    }
4731
0
}
4732
4733
/************************************************************************/
4734
/*                     GDALDeserializeTransformer()                     */
4735
/************************************************************************/
4736
4737
CPLErr GDALDeserializeTransformer(CPLXMLNode *psTree,
4738
                                  GDALTransformerFunc *ppfnFunc,
4739
                                  void **ppTransformArg)
4740
4741
0
{
4742
0
    *ppfnFunc = nullptr;
4743
0
    *ppTransformArg = nullptr;
4744
4745
0
    CPLErrorReset();
4746
4747
0
    if (psTree == nullptr || psTree->eType != CXT_Element)
4748
0
        CPLError(CE_Failure, CPLE_AppDefined,
4749
0
                 "Malformed element in GDALDeserializeTransformer");
4750
0
    else if (EQUAL(psTree->pszValue, "GenImgProjTransformer"))
4751
0
    {
4752
0
        *ppfnFunc = GDALGenImgProjTransform;
4753
0
        *ppTransformArg = GDALDeserializeGenImgProjTransformer(psTree);
4754
0
    }
4755
0
    else if (EQUAL(psTree->pszValue, "ReprojectionTransformer"))
4756
0
    {
4757
0
        *ppfnFunc = GDALReprojectionTransform;
4758
0
        *ppTransformArg = GDALDeserializeReprojectionTransformer(psTree);
4759
0
    }
4760
0
    else if (EQUAL(psTree->pszValue, "GCPTransformer"))
4761
0
    {
4762
0
        *ppfnFunc = GDALGCPTransform;
4763
0
        *ppTransformArg = GDALDeserializeGCPTransformer(psTree);
4764
0
    }
4765
0
    else if (EQUAL(psTree->pszValue, "TPSTransformer"))
4766
0
    {
4767
0
        *ppfnFunc = GDALTPSTransform;
4768
0
        *ppTransformArg = GDALDeserializeTPSTransformer(psTree);
4769
0
    }
4770
0
    else if (EQUAL(psTree->pszValue, "GeoLocTransformer"))
4771
0
    {
4772
0
        *ppfnFunc = GDALGeoLocTransform;
4773
0
        *ppTransformArg = GDALDeserializeGeoLocTransformer(psTree);
4774
0
    }
4775
0
    else if (EQUAL(psTree->pszValue, "RPCTransformer"))
4776
0
    {
4777
0
        *ppfnFunc = GDALRPCTransform;
4778
0
        *ppTransformArg = GDALDeserializeRPCTransformer(psTree);
4779
0
    }
4780
0
    else if (EQUAL(psTree->pszValue, "ApproxTransformer"))
4781
0
    {
4782
0
        *ppfnFunc = GDALApproxTransform;
4783
0
        *ppTransformArg = GDALDeserializeApproxTransformer(psTree);
4784
0
    }
4785
0
    else if (EQUAL(psTree->pszValue, "HomographyTransformer"))
4786
0
    {
4787
0
        *ppfnFunc = GDALHomographyTransform;
4788
0
        *ppTransformArg = GDALDeserializeHomographyTransformer(psTree);
4789
0
    }
4790
0
    else
4791
0
    {
4792
0
        GDALTransformDeserializeFunc pfnDeserializeFunc = nullptr;
4793
0
        {
4794
0
            CPLMutexHolderD(&hDeserializerMutex);
4795
0
            CPLList *psList = psListDeserializer;
4796
0
            while (psList)
4797
0
            {
4798
0
                TransformDeserializerInfo *psInfo =
4799
0
                    static_cast<TransformDeserializerInfo *>(psList->pData);
4800
0
                if (strcmp(psInfo->pszTransformName, psTree->pszValue) == 0)
4801
0
                {
4802
0
                    *ppfnFunc = psInfo->pfnTransformerFunc;
4803
0
                    pfnDeserializeFunc = psInfo->pfnDeserializeFunc;
4804
0
                    break;
4805
0
                }
4806
0
                psList = psList->psNext;
4807
0
            }
4808
0
        }
4809
4810
0
        if (pfnDeserializeFunc != nullptr)
4811
0
        {
4812
0
            *ppTransformArg = pfnDeserializeFunc(psTree);
4813
0
        }
4814
0
        else
4815
0
        {
4816
0
            CPLError(CE_Failure, CPLE_AppDefined,
4817
0
                     "Unrecognized element '%s' GDALDeserializeTransformer",
4818
0
                     psTree->pszValue);
4819
0
        }
4820
0
    }
4821
4822
0
    return CPLGetLastErrorType();
4823
0
}
4824
4825
/************************************************************************/
4826
/*                       GDALDestroyTransformer()                       */
4827
/************************************************************************/
4828
4829
void GDALDestroyTransformer(void *pTransformArg)
4830
4831
0
{
4832
0
    if (pTransformArg == nullptr)
4833
0
        return;
4834
4835
0
    GDALTransformerInfo *psInfo =
4836
0
        static_cast<GDALTransformerInfo *>(pTransformArg);
4837
4838
0
    if (memcmp(psInfo->abySignature, GDAL_GTI2_SIGNATURE,
4839
0
               strlen(GDAL_GTI2_SIGNATURE)) != 0)
4840
0
    {
4841
0
        CPLError(CE_Failure, CPLE_AppDefined,
4842
0
                 "Attempt to destroy non-GTI2 transformer.");
4843
0
        return;
4844
0
    }
4845
4846
0
    psInfo->pfnCleanup(pTransformArg);
4847
0
}
4848
4849
/************************************************************************/
4850
/*                         GDALUseTransformer()                         */
4851
/************************************************************************/
4852
4853
int GDALUseTransformer(void *pTransformArg, int bDstToSrc, int nPointCount,
4854
                       double *x, double *y, double *z, int *panSuccess)
4855
0
{
4856
0
    GDALTransformerInfo *psInfo =
4857
0
        static_cast<GDALTransformerInfo *>(pTransformArg);
4858
4859
0
    if (psInfo == nullptr || memcmp(psInfo->abySignature, GDAL_GTI2_SIGNATURE,
4860
0
                                    strlen(GDAL_GTI2_SIGNATURE)) != 0)
4861
0
    {
4862
0
        CPLError(CE_Failure, CPLE_AppDefined,
4863
0
                 "Attempt to use non-GTI2 transformer.");
4864
0
        return FALSE;
4865
0
    }
4866
4867
0
    return psInfo->pfnTransform(pTransformArg, bDstToSrc, nPointCount, x, y, z,
4868
0
                                panSuccess);
4869
0
}
4870
4871
/************************************************************************/
4872
/*                        GDALCloneTransformer()                        */
4873
/************************************************************************/
4874
4875
void *GDALCloneTransformer(void *pTransformArg)
4876
0
{
4877
0
    GDALTransformerInfo *psInfo =
4878
0
        static_cast<GDALTransformerInfo *>(pTransformArg);
4879
4880
0
    if (psInfo == nullptr || memcmp(psInfo->abySignature, GDAL_GTI2_SIGNATURE,
4881
0
                                    strlen(GDAL_GTI2_SIGNATURE)) != 0)
4882
0
    {
4883
0
        CPLError(CE_Failure, CPLE_AppDefined,
4884
0
                 "Attempt to clone non-GTI2 transformer.");
4885
0
        return nullptr;
4886
0
    }
4887
4888
0
    if (psInfo->pfnCreateSimilar != nullptr)
4889
0
    {
4890
0
        return psInfo->pfnCreateSimilar(psInfo, 1.0, 1.0);
4891
0
    }
4892
4893
0
    if (psInfo->pfnSerialize == nullptr)
4894
0
    {
4895
0
        CPLError(CE_Failure, CPLE_AppDefined,
4896
0
                 "No serialization function available for this transformer.");
4897
0
        return nullptr;
4898
0
    }
4899
4900
0
    CPLXMLNode *pSerialized = psInfo->pfnSerialize(pTransformArg);
4901
0
    if (pSerialized == nullptr)
4902
0
        return nullptr;
4903
0
    GDALTransformerFunc pfnTransformer = nullptr;
4904
0
    void *pClonedTransformArg = nullptr;
4905
0
    if (GDALDeserializeTransformer(pSerialized, &pfnTransformer,
4906
0
                                   &pClonedTransformArg) != CE_None)
4907
0
    {
4908
0
        CPLDestroyXMLNode(pSerialized);
4909
0
        CPLFree(pClonedTransformArg);
4910
0
        return nullptr;
4911
0
    }
4912
4913
0
    CPLDestroyXMLNode(pSerialized);
4914
0
    return pClonedTransformArg;
4915
0
}
4916
4917
/************************************************************************/
4918
/*                    GDALCreateSimilarTransformer()                    */
4919
/************************************************************************/
4920
4921
void *GDALCreateSimilarTransformer(void *pTransformArg, double dfRatioX,
4922
                                   double dfRatioY)
4923
0
{
4924
0
    GDALTransformerInfo *psInfo =
4925
0
        static_cast<GDALTransformerInfo *>(pTransformArg);
4926
4927
0
    if (psInfo == nullptr || memcmp(psInfo->abySignature, GDAL_GTI2_SIGNATURE,
4928
0
                                    strlen(GDAL_GTI2_SIGNATURE)) != 0)
4929
0
    {
4930
0
        CPLError(CE_Failure, CPLE_AppDefined,
4931
0
                 "Attempt to call CreateSimilar on a non-GTI2 transformer.");
4932
0
        return nullptr;
4933
0
    }
4934
4935
0
    if (psInfo->pfnCreateSimilar == nullptr)
4936
0
    {
4937
0
        CPLError(CE_Failure, CPLE_AppDefined,
4938
0
                 "No CreateSimilar function available for this transformer.");
4939
0
        return nullptr;
4940
0
    }
4941
4942
0
    return psInfo->pfnCreateSimilar(psInfo, dfRatioX, dfRatioY);
4943
0
}
4944
4945
/************************************************************************/
4946
/*                     GetGenImgProjTransformInfo()                     */
4947
/************************************************************************/
4948
4949
static GDALTransformerInfo *GetGenImgProjTransformInfo(const char *pszFunc,
4950
                                                       void *pTransformArg)
4951
0
{
4952
0
    GDALTransformerInfo *psInfo =
4953
0
        static_cast<GDALTransformerInfo *>(pTransformArg);
4954
4955
0
    if (psInfo == nullptr || memcmp(psInfo->abySignature, GDAL_GTI2_SIGNATURE,
4956
0
                                    strlen(GDAL_GTI2_SIGNATURE)) != 0)
4957
0
    {
4958
0
        CPLError(CE_Failure, CPLE_AppDefined,
4959
0
                 "Attempt to call %s on "
4960
0
                 "a non-GTI2 transformer.",
4961
0
                 pszFunc);
4962
0
        return nullptr;
4963
0
    }
4964
4965
0
    if (EQUAL(psInfo->pszClassName, GDAL_APPROX_TRANSFORMER_CLASS_NAME))
4966
0
    {
4967
0
        GDALApproxTransformInfo *psATInfo =
4968
0
            static_cast<GDALApproxTransformInfo *>(pTransformArg);
4969
0
        psInfo = static_cast<GDALTransformerInfo *>(psATInfo->pBaseCBData);
4970
4971
0
        if (psInfo == nullptr ||
4972
0
            memcmp(psInfo->abySignature, GDAL_GTI2_SIGNATURE,
4973
0
                   strlen(GDAL_GTI2_SIGNATURE)) != 0)
4974
0
        {
4975
0
            CPLError(CE_Failure, CPLE_AppDefined,
4976
0
                     "Attempt to call %s on "
4977
0
                     "a non-GTI2 transformer.",
4978
0
                     pszFunc);
4979
0
            return nullptr;
4980
0
        }
4981
0
    }
4982
4983
0
    if (EQUAL(psInfo->pszClassName, GDAL_GEN_IMG_TRANSFORMER_CLASS_NAME))
4984
0
    {
4985
0
        return psInfo;
4986
0
    }
4987
4988
0
    return nullptr;
4989
0
}
4990
4991
/************************************************************************/
4992
/*                 GDALSetTransformerDstGeoTransform()                  */
4993
/************************************************************************/
4994
4995
/**
4996
 * Set ApproxTransformer or GenImgProj output geotransform.
4997
 *
4998
 * This is a layer above GDALSetGenImgProjTransformerDstGeoTransform() that
4999
 * checks that the passed hTransformArg is compatible.
5000
 *
5001
 * Normally the "destination geotransform", or transformation between
5002
 * georeferenced output coordinates and pixel/line coordinates on the
5003
 * destination file is extracted from the destination file by
5004
 * GDALCreateGenImgProjTransformer() and stored in the GenImgProj private
5005
 * info.  However, sometimes it is inconvenient to have an output file
5006
 * handle with appropriate geotransform information when creating the
5007
 * transformation.  For these cases, this function can be used to apply
5008
 * the destination geotransform.
5009
 *
5010
 * @param pTransformArg the handle to update.
5011
 * @param padfGeoTransform the destination geotransform to apply (six doubles).
5012
 */
5013
5014
void GDALSetTransformerDstGeoTransform(void *pTransformArg,
5015
                                       const double *padfGeoTransform)
5016
0
{
5017
0
    VALIDATE_POINTER0(pTransformArg, "GDALSetTransformerDstGeoTransform");
5018
5019
0
    GDALTransformerInfo *psInfo = GetGenImgProjTransformInfo(
5020
0
        "GDALSetTransformerDstGeoTransform", pTransformArg);
5021
0
    if (psInfo)
5022
0
    {
5023
0
        GDALSetGenImgProjTransformerDstGeoTransform(psInfo, padfGeoTransform);
5024
0
    }
5025
0
}
5026
5027
/************************************************************************/
5028
/*                 GDALGetTransformerDstGeoTransform()                  */
5029
/************************************************************************/
5030
5031
/**
5032
 * Get ApproxTransformer or GenImgProj output geotransform.
5033
 *
5034
 * @param pTransformArg transformer handle.
5035
 * @param padfGeoTransform (output) the destination geotransform to return (six
5036
 * doubles).
5037
 */
5038
5039
void GDALGetTransformerDstGeoTransform(void *pTransformArg,
5040
                                       double *padfGeoTransform)
5041
0
{
5042
0
    VALIDATE_POINTER0(pTransformArg, "GDALGetTransformerDstGeoTransform");
5043
5044
0
    GDALTransformerInfo *psInfo = GetGenImgProjTransformInfo(
5045
0
        "GDALGetTransformerDstGeoTransform", pTransformArg);
5046
0
    if (psInfo)
5047
0
    {
5048
0
        GDALGenImgProjTransformInfo *psGenImgProjInfo =
5049
0
            reinterpret_cast<GDALGenImgProjTransformInfo *>(psInfo);
5050
5051
0
        memcpy(padfGeoTransform, psGenImgProjInfo->sDstParams.adfGeoTransform,
5052
0
               sizeof(double) * 6);
5053
0
    }
5054
0
}
5055
5056
/************************************************************************/
5057
/*            GDALTransformIsTranslationOnPixelBoundaries()             */
5058
/************************************************************************/
5059
5060
bool GDALTransformIsTranslationOnPixelBoundaries(GDALTransformerFunc,
5061
                                                 void *pTransformerArg)
5062
0
{
5063
0
    if (GDALIsTransformer(pTransformerArg, GDAL_APPROX_TRANSFORMER_CLASS_NAME))
5064
0
    {
5065
0
        const auto *pApproxInfo =
5066
0
            static_cast<const GDALApproxTransformInfo *>(pTransformerArg);
5067
0
        pTransformerArg = pApproxInfo->pBaseCBData;
5068
0
    }
5069
0
    if (GDALIsTransformer(pTransformerArg, GDAL_GEN_IMG_TRANSFORMER_CLASS_NAME))
5070
0
    {
5071
0
        const auto *pGenImgpProjInfo =
5072
0
            static_cast<GDALGenImgProjTransformInfo *>(pTransformerArg);
5073
0
        const auto IsCloseToInteger = [](double dfVal)
5074
0
        { return std::fabs(dfVal - std::round(dfVal)) <= 1e-6; };
5075
0
        return pGenImgpProjInfo->sSrcParams.pTransformArg == nullptr &&
5076
0
               pGenImgpProjInfo->sDstParams.pTransformArg == nullptr &&
5077
0
               pGenImgpProjInfo->pReproject == nullptr &&
5078
0
               pGenImgpProjInfo->sSrcParams.adfGeoTransform[1] ==
5079
0
                   pGenImgpProjInfo->sDstParams.adfGeoTransform[1] &&
5080
0
               pGenImgpProjInfo->sSrcParams.adfGeoTransform[5] ==
5081
0
                   pGenImgpProjInfo->sDstParams.adfGeoTransform[5] &&
5082
0
               pGenImgpProjInfo->sSrcParams.adfGeoTransform[2] ==
5083
0
                   pGenImgpProjInfo->sDstParams.adfGeoTransform[2] &&
5084
0
               pGenImgpProjInfo->sSrcParams.adfGeoTransform[4] ==
5085
0
                   pGenImgpProjInfo->sDstParams.adfGeoTransform[4] &&
5086
               // Check that the georeferenced origin of the destination
5087
               // geotransform is close to be an integer value when transformed
5088
               // to source image coordinates
5089
0
               IsCloseToInteger(
5090
0
                   pGenImgpProjInfo->sSrcParams.adfInvGeoTransform[0] +
5091
0
                   pGenImgpProjInfo->sDstParams.adfGeoTransform[0] *
5092
0
                       pGenImgpProjInfo->sSrcParams.adfInvGeoTransform[1] +
5093
0
                   pGenImgpProjInfo->sDstParams.adfGeoTransform[3] *
5094
0
                       pGenImgpProjInfo->sSrcParams.adfInvGeoTransform[2]) &&
5095
0
               IsCloseToInteger(
5096
0
                   pGenImgpProjInfo->sSrcParams.adfInvGeoTransform[3] +
5097
0
                   pGenImgpProjInfo->sDstParams.adfGeoTransform[0] *
5098
0
                       pGenImgpProjInfo->sSrcParams.adfInvGeoTransform[4] +
5099
0
                   pGenImgpProjInfo->sDstParams.adfGeoTransform[3] *
5100
0
                       pGenImgpProjInfo->sSrcParams.adfInvGeoTransform[5]);
5101
0
    }
5102
0
    return false;
5103
0
}
5104
5105
/************************************************************************/
5106
/*                  GDALTransformIsAffineNoRotation()                   */
5107
/************************************************************************/
5108
5109
bool GDALTransformIsAffineNoRotation(GDALTransformerFunc, void *pTransformerArg)
5110
0
{
5111
0
    if (GDALIsTransformer(pTransformerArg, GDAL_APPROX_TRANSFORMER_CLASS_NAME))
5112
0
    {
5113
0
        const auto *pApproxInfo =
5114
0
            static_cast<const GDALApproxTransformInfo *>(pTransformerArg);
5115
0
        pTransformerArg = pApproxInfo->pBaseCBData;
5116
0
    }
5117
0
    if (GDALIsTransformer(pTransformerArg, GDAL_GEN_IMG_TRANSFORMER_CLASS_NAME))
5118
0
    {
5119
0
        const auto *pGenImgpProjInfo =
5120
0
            static_cast<GDALGenImgProjTransformInfo *>(pTransformerArg);
5121
0
        return pGenImgpProjInfo->sSrcParams.pTransformArg == nullptr &&
5122
0
               pGenImgpProjInfo->sDstParams.pTransformArg == nullptr &&
5123
0
               pGenImgpProjInfo->pReproject == nullptr &&
5124
0
               pGenImgpProjInfo->sSrcParams.adfGeoTransform[2] == 0 &&
5125
0
               pGenImgpProjInfo->sSrcParams.adfGeoTransform[4] == 0 &&
5126
0
               pGenImgpProjInfo->sDstParams.adfGeoTransform[2] == 0 &&
5127
0
               pGenImgpProjInfo->sDstParams.adfGeoTransform[4] == 0;
5128
0
    }
5129
0
    return false;
5130
0
}
5131
5132
/************************************************************************/
5133
/*                     GDALTransformHasFastClone()                      */
5134
/************************************************************************/
5135
5136
/** Returns whether GDALCloneTransformer() on this transformer is
5137
 * "fast"
5138
 * Counter-examples are GCPs or TPSs transformers.
5139
 */
5140
bool GDALTransformHasFastClone(void *pTransformerArg)
5141
0
{
5142
0
    if (GDALIsTransformer(pTransformerArg, GDAL_APPROX_TRANSFORMER_CLASS_NAME))
5143
0
    {
5144
0
        const auto *pApproxInfo =
5145
0
            static_cast<const GDALApproxTransformInfo *>(pTransformerArg);
5146
0
        pTransformerArg = pApproxInfo->pBaseCBData;
5147
        // Fallback to next lines
5148
0
    }
5149
5150
0
    if (GDALIsTransformer(pTransformerArg, GDAL_GEN_IMG_TRANSFORMER_CLASS_NAME))
5151
0
    {
5152
0
        const auto *pGenImgpProjInfo =
5153
0
            static_cast<GDALGenImgProjTransformInfo *>(pTransformerArg);
5154
0
        return (pGenImgpProjInfo->sSrcParams.pTransformArg == nullptr ||
5155
0
                GDALTransformHasFastClone(
5156
0
                    pGenImgpProjInfo->sSrcParams.pTransformArg)) &&
5157
0
               (pGenImgpProjInfo->sDstParams.pTransformArg == nullptr ||
5158
0
                GDALTransformHasFastClone(
5159
0
                    pGenImgpProjInfo->sDstParams.pTransformArg));
5160
0
    }
5161
0
    else if (GDALIsTransformer(pTransformerArg,
5162
0
                               GDAL_RPC_TRANSFORMER_CLASS_NAME))
5163
0
    {
5164
0
        return true;
5165
0
    }
5166
0
    else
5167
0
    {
5168
0
        return false;
5169
0
    }
5170
0
}