LCOV - code coverage report
Current view: top level - alg - gdaltransformer.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 1700 1921 88.5 %
Date: 2026-09-09 22:24:20 Functions: 62 66 93.9 %

          Line data    Source code
       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       17084 : bool GDALIsTransformer(void *hTransformerArg, const char *pszClassName)
      68             : {
      69       17084 :     if (!hTransformerArg)
      70         816 :         return false;
      71             :     // All transformers should have a GDALTransformerInfo member as their first members
      72       16268 :     GDALTransformerInfo *psInfo =
      73             :         static_cast<GDALTransformerInfo *>(hTransformerArg);
      74       16268 :     return memcmp(psInfo->abySignature, GDAL_GTI2_SIGNATURE,
      75       31642 :                   strlen(GDAL_GTI2_SIGNATURE)) == 0 &&
      76       31642 :            strcmp(psInfo->pszClassName, pszClassName) == 0;
      77             : }
      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          39 : CPLErr CPL_STDCALL GDALSuggestedWarpOutput(GDALDatasetH hSrcDS,
     174             :                                            GDALTransformerFunc pfnTransformer,
     175             :                                            void *pTransformArg,
     176             :                                            double *padfGeoTransformOut,
     177             :                                            int *pnPixels, int *pnLines)
     178             : 
     179             : {
     180          39 :     VALIDATE_POINTER1(hSrcDS, "GDALSuggestedWarpOutput", CE_Failure);
     181             : 
     182          39 :     double adfExtent[4] = {};
     183             : 
     184          39 :     return GDALSuggestedWarpOutput2(hSrcDS, pfnTransformer, pTransformArg,
     185             :                                     padfGeoTransformOut, pnPixels, pnLines,
     186          39 :                                     adfExtent, 0);
     187             : }
     188             : 
     189         717 : static bool GDALSuggestedWarpOutput2_MustAdjustForRightBorder(
     190             :     GDALTransformerFunc pfnTransformer, void *pTransformArg, double *padfExtent,
     191             :     int /* nPixels*/, int nLines, double dfPixelSizeX, double dfPixelSizeY)
     192             : {
     193         717 :     double adfX[21] = {};
     194         717 :     double adfY[21] = {};
     195             : 
     196         717 :     const double dfMaxXOut = padfExtent[2];
     197         717 :     const double dfMaxYOut = padfExtent[3];
     198             : 
     199             :     // Take 20 steps.
     200         717 :     int nSamplePoints = 0;
     201       15774 :     for (double dfRatio = 0.0; dfRatio <= 1.01; dfRatio += 0.05)
     202             :     {
     203             :         // Ensure we end exactly at the end.
     204       15057 :         if (dfRatio > 0.99)
     205         717 :             dfRatio = 1.0;
     206             : 
     207             :         // Along right.
     208       15057 :         adfX[nSamplePoints] = dfMaxXOut;
     209       15057 :         adfY[nSamplePoints] = dfMaxYOut - dfPixelSizeY * dfRatio * nLines;
     210       15057 :         nSamplePoints++;
     211             :     }
     212         717 :     double adfZ[21] = {};
     213             : 
     214         717 :     int abSuccess[21] = {};
     215             : 
     216         717 :     pfnTransformer(pTransformArg, TRUE, nSamplePoints, adfX, adfY, adfZ,
     217             :                    abSuccess);
     218             : 
     219         717 :     int abSuccess2[21] = {};
     220             : 
     221         717 :     pfnTransformer(pTransformArg, FALSE, nSamplePoints, adfX, adfY, adfZ,
     222             :                    abSuccess2);
     223             : 
     224         717 :     nSamplePoints = 0;
     225         717 :     int nBadCount = 0;
     226       15774 :     for (double dfRatio = 0.0; dfRatio <= 1.01; dfRatio += 0.05)
     227             :     {
     228       15057 :         const double expected_x = dfMaxXOut;
     229       15057 :         const double expected_y = dfMaxYOut - dfPixelSizeY * dfRatio * nLines;
     230       15057 :         if (!abSuccess[nSamplePoints] || !abSuccess2[nSamplePoints] ||
     231       12053 :             fabs(adfX[nSamplePoints] - expected_x) > dfPixelSizeX ||
     232       10679 :             fabs(adfY[nSamplePoints] - expected_y) > dfPixelSizeY)
     233             :         {
     234        4378 :             nBadCount++;
     235             :         }
     236       15057 :         nSamplePoints++;
     237             :     }
     238             : 
     239         717 :     return nBadCount == nSamplePoints;
     240             : }
     241             : 
     242         618 : static bool GDALSuggestedWarpOutput2_MustAdjustForBottomBorder(
     243             :     GDALTransformerFunc pfnTransformer, void *pTransformArg, double *padfExtent,
     244             :     int nPixels, int /* nLines */, double dfPixelSizeX, double dfPixelSizeY)
     245             : {
     246         618 :     double adfX[21] = {};
     247         618 :     double adfY[21] = {};
     248             : 
     249         618 :     const double dfMinXOut = padfExtent[0];
     250         618 :     const double dfMinYOut = padfExtent[1];
     251             : 
     252             :     // Take 20 steps.
     253         618 :     int nSamplePoints = 0;
     254       13596 :     for (double dfRatio = 0.0; dfRatio <= 1.01; dfRatio += 0.05)
     255             :     {
     256             :         // Ensure we end exactly at the end.
     257       12978 :         if (dfRatio > 0.99)
     258         618 :             dfRatio = 1.0;
     259             : 
     260             :         // Along right.
     261       12978 :         adfX[nSamplePoints] = dfMinXOut + dfPixelSizeX * dfRatio * nPixels;
     262       12978 :         adfY[nSamplePoints] = dfMinYOut;
     263       12978 :         nSamplePoints++;
     264             :     }
     265         618 :     double adfZ[21] = {};
     266             : 
     267         618 :     int abSuccess[21] = {};
     268             : 
     269         618 :     pfnTransformer(pTransformArg, TRUE, nSamplePoints, adfX, adfY, adfZ,
     270             :                    abSuccess);
     271             : 
     272         618 :     int abSuccess2[21] = {};
     273             : 
     274         618 :     pfnTransformer(pTransformArg, FALSE, nSamplePoints, adfX, adfY, adfZ,
     275             :                    abSuccess2);
     276             : 
     277         618 :     nSamplePoints = 0;
     278         618 :     int nBadCount = 0;
     279       13596 :     for (double dfRatio = 0.0; dfRatio <= 1.01; dfRatio += 0.05)
     280             :     {
     281       12978 :         const double expected_x = dfMinXOut + dfPixelSizeX * dfRatio * nPixels;
     282       12978 :         const double expected_y = dfMinYOut;
     283       12978 :         if (!abSuccess[nSamplePoints] || !abSuccess2[nSamplePoints] ||
     284       10868 :             fabs(adfX[nSamplePoints] - expected_x) > dfPixelSizeX ||
     285       10579 :             fabs(adfY[nSamplePoints] - expected_y) > dfPixelSizeY)
     286             :         {
     287        2399 :             nBadCount++;
     288             :         }
     289       12978 :         nSamplePoints++;
     290             :     }
     291             : 
     292         618 :     return nBadCount == nSamplePoints;
     293             : }
     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        1341 : 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             : {
     349        1341 :     VALIDATE_POINTER1(hSrcDS, "GDALSuggestedWarpOutput2", CE_Failure);
     350             : 
     351             :     const bool bIsGDALGenImgProjTransform{
     352        2682 :         pTransformArg &&
     353        1341 :         GDALIsTransformer(pTransformArg, GDAL_GEN_IMG_TRANSFORMER_CLASS_NAME)};
     354             : 
     355             :     /* -------------------------------------------------------------------- */
     356             :     /*      Setup sample points all around the edge of the input raster.    */
     357             :     /* -------------------------------------------------------------------- */
     358        1341 :     if (bIsGDALGenImgProjTransform)
     359             :     {
     360             :         // In case CHECK_WITH_INVERT_PROJ has been modified.
     361        1341 :         GDALRefreshGenImgProjTransformer(pTransformArg);
     362             :     }
     363           0 :     else if (GDALIsTransformer(pTransformArg,
     364             :                                GDAL_APPROX_TRANSFORMER_CLASS_NAME))
     365             :     {
     366             :         // In case CHECK_WITH_INVERT_PROJ has been modified.
     367           0 :         GDALRefreshApproxTransformer(pTransformArg);
     368             :     }
     369             : 
     370        1341 :     const int nInXSize = GDALGetRasterXSize(hSrcDS);
     371        1341 :     const int nInYSize = GDALGetRasterYSize(hSrcDS);
     372             : 
     373             :     /* ------------------------------------------------------------- */
     374             :     /* Special case for warping on the same (or null) CRS.           */
     375             :     /* ------------------------------------------------------------- */
     376        1341 :     if ((!nOptions || (nOptions & GDAL_SWO_FORCE_SQUARE_PIXEL) == 0) &&
     377        1340 :         pTransformArg && bIsGDALGenImgProjTransform)
     378             :     {
     379        1340 :         const GDALGenImgProjTransformInfo *psInfo =
     380             :             static_cast<const GDALGenImgProjTransformInfo *>(pTransformArg);
     381             : 
     382        1340 :         if (!psInfo->sSrcParams.pTransformer &&
     383        1256 :             !psInfo->bHasCustomTransformationPipeline &&
     384        1252 :             !psInfo->sDstParams.pTransformer &&
     385        1252 :             psInfo->sSrcParams.adfGeoTransform[2] == 0 &&
     386        1250 :             psInfo->sSrcParams.adfGeoTransform[4] == 0 &&
     387        1250 :             psInfo->sDstParams.adfGeoTransform[0] == 0 &&
     388        1238 :             psInfo->sDstParams.adfGeoTransform[1] == 1 &&
     389        1238 :             psInfo->sDstParams.adfGeoTransform[2] == 0 &&
     390        1238 :             psInfo->sDstParams.adfGeoTransform[3] == 0 &&
     391        1238 :             psInfo->sDstParams.adfGeoTransform[4] == 0 &&
     392        1238 :             psInfo->sDstParams.adfGeoTransform[5] == 1)
     393             :         {
     394        1238 :             const OGRSpatialReference *poSourceCRS = nullptr;
     395        1238 :             const OGRSpatialReference *poTargetCRS = nullptr;
     396             : 
     397        1238 :             if (psInfo->pReprojectArg)
     398             :             {
     399         811 :                 const GDALReprojectionTransformInfo *psRTI =
     400             :                     static_cast<const GDALReprojectionTransformInfo *>(
     401             :                         psInfo->pReprojectArg);
     402         811 :                 poSourceCRS = psRTI->poForwardTransform->GetSourceCS();
     403         811 :                 poTargetCRS = psRTI->poForwardTransform->GetTargetCS();
     404             :             }
     405             : 
     406        2049 :             if ((!poSourceCRS && !poTargetCRS) ||
     407         811 :                 (poSourceCRS && poTargetCRS &&
     408         811 :                  poSourceCRS->IsSame(poTargetCRS)))
     409             :             {
     410             : 
     411         788 :                 const bool bNorthUp{psInfo->sSrcParams.adfGeoTransform[5] <
     412             :                                     0.0};
     413             : 
     414         788 :                 memcpy(padfGeoTransformOut, psInfo->sSrcParams.adfGeoTransform,
     415             :                        sizeof(double) * 6);
     416             : 
     417         788 :                 if (!bNorthUp)
     418             :                 {
     419         150 :                     padfGeoTransformOut[3] = padfGeoTransformOut[3] +
     420         150 :                                              nInYSize * padfGeoTransformOut[5];
     421         150 :                     padfGeoTransformOut[5] = -padfGeoTransformOut[5];
     422             :                 }
     423             : 
     424         788 :                 *pnPixels = nInXSize;
     425         788 :                 *pnLines = nInYSize;
     426             : 
     427             :                 // Calculate extent from hSrcDS
     428         788 :                 if (padfExtent)
     429             :                 {
     430         788 :                     padfExtent[0] = psInfo->sSrcParams.adfGeoTransform[0];
     431         788 :                     padfExtent[1] =
     432         788 :                         psInfo->sSrcParams.adfGeoTransform[3] +
     433         788 :                         nInYSize * psInfo->sSrcParams.adfGeoTransform[5];
     434         788 :                     padfExtent[2] =
     435         788 :                         psInfo->sSrcParams.adfGeoTransform[0] +
     436         788 :                         nInXSize * psInfo->sSrcParams.adfGeoTransform[1];
     437         788 :                     padfExtent[3] = psInfo->sSrcParams.adfGeoTransform[3];
     438         788 :                     if (!bNorthUp)
     439             :                     {
     440         150 :                         std::swap(padfExtent[1], padfExtent[3]);
     441             :                     }
     442             :                 }
     443         788 :                 return CE_None;
     444             :             }
     445             :         }
     446             :     }
     447             : 
     448         553 :     const int N_PIXELSTEP = 50;
     449             :     int nSteps = static_cast<int>(
     450         553 :         static_cast<double>(std::min(nInYSize, nInXSize)) / N_PIXELSTEP + 0.5);
     451         553 :     if (nSteps < 20)
     452         524 :         nSteps = 20;
     453          29 :     else if (nSteps > 100)
     454          14 :         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         553 : retry:
     462         553 :     int nStepsPlusOne = nSteps + 1;
     463         553 :     int nSampleMax = nStepsPlusOne * nStepsPlusOne;
     464             : 
     465         553 :     double dfStep = 1.0 / nSteps;
     466         553 :     double *padfY = nullptr;
     467         553 :     double *padfZ = nullptr;
     468         553 :     double *padfYRevert = nullptr;
     469         553 :     double *padfZRevert = nullptr;
     470             : 
     471             :     int *pabSuccess = static_cast<int *>(
     472         553 :         VSI_MALLOC3_VERBOSE(sizeof(int), nStepsPlusOne, nStepsPlusOne));
     473             :     double *padfX = static_cast<double *>(
     474         553 :         VSI_MALLOC3_VERBOSE(sizeof(double) * 3, nStepsPlusOne, nStepsPlusOne));
     475             :     double *padfXRevert = static_cast<double *>(
     476         553 :         VSI_MALLOC3_VERBOSE(sizeof(double) * 3, nStepsPlusOne, nStepsPlusOne));
     477         553 :     if (pabSuccess == nullptr || padfX == nullptr || padfXRevert == nullptr)
     478             :     {
     479           0 :         CPLFree(padfX);
     480           0 :         CPLFree(padfXRevert);
     481           0 :         CPLFree(pabSuccess);
     482           0 :         if (nSteps > 20)
     483             :         {
     484           0 :             nSteps = 20;
     485           0 :             goto retry;
     486             :         }
     487           0 :         return CE_Failure;
     488             :     }
     489             : 
     490         553 :     padfY = padfX + nSampleMax;
     491         553 :     padfZ = padfX + nSampleMax * 2;
     492         553 :     padfYRevert = padfXRevert + nSampleMax;
     493         553 :     padfZRevert = padfXRevert + nSampleMax * 2;
     494             : 
     495             :     // Take N_STEPS steps.
     496       13389 :     for (int iStep = 0; iStep <= nSteps; iStep++)
     497             :     {
     498       12836 :         double dfRatio = (iStep == nSteps) ? 1.0 : iStep * dfStep;
     499       12836 :         int iStep2 = iStep;
     500             : 
     501             :         // Along top.
     502       12836 :         padfX[iStep2] = dfRatio * nInXSize;
     503       12836 :         padfY[iStep2] = 0.0;
     504       12836 :         padfZ[iStep2] = 0.0;
     505             : 
     506             :         // Along bottom.
     507       12836 :         iStep2 += nStepsPlusOne;
     508       12836 :         padfX[iStep2] = dfRatio * nInXSize;
     509       12836 :         padfY[iStep2] = nInYSize;
     510       12836 :         padfZ[iStep2] = 0.0;
     511             : 
     512             :         // Along left.
     513       12836 :         iStep2 += nStepsPlusOne;
     514       12836 :         padfX[iStep2] = 0.0;
     515       12836 :         padfY[iStep2] = dfRatio * nInYSize;
     516       12836 :         padfZ[iStep2] = 0.0;
     517             : 
     518             :         // Along right.
     519       12836 :         iStep2 += nStepsPlusOne;
     520       12836 :         padfX[iStep2] = nInXSize;
     521       12836 :         padfY[iStep2] = dfRatio * nInYSize;
     522       12836 :         padfZ[iStep2] = 0.0;
     523             :     }
     524             : 
     525         553 :     int nSamplePoints = 4 * nStepsPlusOne;
     526             : 
     527         553 :     memset(pabSuccess, 1, sizeof(int) * nSampleMax);
     528             : 
     529             :     /* -------------------------------------------------------------------- */
     530             :     /*      Transform them to the output coordinate system.                 */
     531             :     /* -------------------------------------------------------------------- */
     532             :     {
     533        1106 :         CPLTurnFailureIntoWarningBackuper oErrorsToWarnings{};
     534         553 :         pfnTransformer(pTransformArg, FALSE, nSamplePoints, padfX, padfY, padfZ,
     535             :                        pabSuccess);
     536             :     }
     537         553 :     constexpr int SIGN_FINAL_UNINIT = -2;
     538         553 :     constexpr int SIGN_FINAL_INVALID = 0;
     539         553 :     int iSignDiscontinuity = SIGN_FINAL_UNINIT;
     540         553 :     int nFailedCount = 0;
     541         553 :     const int iSignArray[2] = {-1, 1};
     542       51897 :     for (int i = 0; i < nSamplePoints; i++)
     543             :     {
     544       51344 :         if (pabSuccess[i])
     545             :         {
     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       46340 :             if (iSignDiscontinuity == 1 || iSignDiscontinuity == -1)
     552             :             {
     553       13672 :                 if (!((iSignDiscontinuity * padfX[i] > 0 &&
     554       13588 :                        iSignDiscontinuity * padfX[i] <= 180.0) ||
     555          85 :                       (fabs(padfX[i] - iSignDiscontinuity * -180.0) < 1e-8)))
     556             :                 {
     557          62 :                     iSignDiscontinuity = SIGN_FINAL_INVALID;
     558             :                 }
     559             :             }
     560       32668 :             else if (iSignDiscontinuity == SIGN_FINAL_UNINIT)
     561             :             {
     562        1305 :                 for (const auto &iSign : iSignArray)
     563             :                 {
     564         957 :                     if ((iSign * padfX[i] > 0 && iSign * padfX[i] <= 180.0) ||
     565         754 :                         (fabs(padfX[i] - iSign * -180.0) < 1e-8))
     566             :                     {
     567         203 :                         iSignDiscontinuity = iSign;
     568         203 :                         break;
     569             :                     }
     570             :                 }
     571         551 :                 if (iSignDiscontinuity == SIGN_FINAL_UNINIT)
     572             :                 {
     573         348 :                     iSignDiscontinuity = SIGN_FINAL_INVALID;
     574             :                 }
     575             :             }
     576             :         }
     577             :         else
     578             :         {
     579        5004 :             nFailedCount++;
     580             :         }
     581             :     }
     582             : 
     583         553 :     if (iSignDiscontinuity == 1 || iSignDiscontinuity == -1)
     584             :     {
     585       13953 :         for (int i = 0; i < nSamplePoints; i++)
     586             :         {
     587       13812 :             if (pabSuccess[i])
     588             :             {
     589       13195 :                 if (fabs(padfX[i] - iSignDiscontinuity * -180.0) < 1e-8)
     590             :                 {
     591           2 :                     double axTemp[2] = {iSignDiscontinuity * -180.0,
     592           2 :                                         iSignDiscontinuity * 180.0};
     593           2 :                     double ayTemp[2] = {padfY[i], padfY[i]};
     594           2 :                     double azTemp[2] = {padfZ[i], padfZ[i]};
     595           2 :                     int abSuccess[2] = {FALSE, FALSE};
     596           4 :                     CPLTurnFailureIntoWarningBackuper oErrorsToWarnings{};
     597           2 :                     if (pfnTransformer(pTransformArg, TRUE, 2, axTemp, ayTemp,
     598           2 :                                        azTemp, abSuccess) &&
     599           4 :                         fabs(axTemp[0] - axTemp[1]) < 1e-8 &&
     600           2 :                         fabs(ayTemp[0] - ayTemp[1]) < 1e-8)
     601             :                     {
     602           2 :                         padfX[i] = iSignDiscontinuity * 180.0;
     603             :                     }
     604             :                 }
     605             :             }
     606             :         }
     607             :     }
     608             : 
     609             :     /* -------------------------------------------------------------------- */
     610             :     /*      Check if the computed target coordinates are revertable.        */
     611             :     /*      If not, try the detailed grid sampling.                         */
     612             :     /* -------------------------------------------------------------------- */
     613         553 :     if (nFailedCount)
     614             :     {
     615         140 :         CPLDebug("WARP", "At least one point failed after direct transform");
     616             :     }
     617             :     else
     618             :     {
     619         413 :         memcpy(padfXRevert, padfX, nSamplePoints * sizeof(double));
     620         413 :         memcpy(padfYRevert, padfY, nSamplePoints * sizeof(double));
     621         413 :         memcpy(padfZRevert, padfZ, nSamplePoints * sizeof(double));
     622             :         {
     623         826 :             CPLTurnFailureIntoWarningBackuper oErrorsToWarnings{};
     624         413 :             pfnTransformer(pTransformArg, TRUE, nSamplePoints, padfXRevert,
     625             :                            padfYRevert, padfZRevert, pabSuccess);
     626             :         }
     627             : 
     628       37665 :         for (int i = 0; nFailedCount == 0 && i < nSamplePoints; i++)
     629             :         {
     630       37272 :             if (!pabSuccess[i])
     631             :             {
     632          20 :                 nFailedCount++;
     633          20 :                 break;
     634             :             }
     635             : 
     636       37252 :             double dfRatio = (i % nStepsPlusOne) * dfStep;
     637       37252 :             if (dfRatio > 0.99)
     638        1562 :                 dfRatio = 1.0;
     639             : 
     640       37252 :             double dfExpectedX = 0.0;
     641       37252 :             double dfExpectedY = 0.0;
     642       37252 :             if (i < nStepsPlusOne)
     643             :             {
     644        9500 :                 dfExpectedX = dfRatio * nInXSize;
     645             :             }
     646       27752 :             else if (i < 2 * nStepsPlusOne)
     647             :             {
     648        9404 :                 dfExpectedX = dfRatio * nInXSize;
     649        9404 :                 dfExpectedY = nInYSize;
     650             :             }
     651       18348 :             else if (i < 3 * nStepsPlusOne)
     652             :             {
     653        9236 :                 dfExpectedY = dfRatio * nInYSize;
     654             :             }
     655             :             else
     656             :             {
     657        9112 :                 dfExpectedX = nInXSize;
     658        9112 :                 dfExpectedY = dfRatio * nInYSize;
     659             :             }
     660             : 
     661       37252 :             if (fabs(padfXRevert[i] - dfExpectedX) >
     662       37252 :                     nInXSize / static_cast<double>(nSteps) ||
     663       37240 :                 fabs(padfYRevert[i] - dfExpectedY) >
     664       37240 :                     nInYSize / static_cast<double>(nSteps))
     665          12 :                 nFailedCount++;
     666             :         }
     667         413 :         if (nFailedCount != 0)
     668          32 :             CPLDebug("WARP",
     669             :                      "At least one point failed after revert transform");
     670             :     }
     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         553 :     if (nFailedCount)
     678             :     {
     679         172 :         nSamplePoints = 0;
     680             : 
     681             :         // Take N_STEPS steps.
     682        3938 :         for (int iStep = 0; iStep <= nSteps; iStep++)
     683             :         {
     684        3766 :             double dfRatio = (iStep == nSteps) ? 1.0 : iStep * dfStep;
     685             : 
     686       96378 :             for (int iStep2 = 0; iStep2 <= nSteps; iStep2++)
     687             :             {
     688       92612 :                 const double dfRatio2 =
     689       92612 :                     iStep2 == nSteps ? 1.0 : iStep2 * dfStep;
     690             : 
     691             :                 // From top to bottom, from left to right.
     692       92612 :                 padfX[nSamplePoints] = dfRatio2 * nInXSize;
     693       92612 :                 padfY[nSamplePoints] = dfRatio * nInYSize;
     694       92612 :                 padfZ[nSamplePoints] = 0.0;
     695       92612 :                 nSamplePoints++;
     696             :             }
     697             :         }
     698             : 
     699         172 :         CPLAssert(nSamplePoints == nSampleMax);
     700             : 
     701             :         {
     702         344 :             CPLTurnFailureIntoWarningBackuper oErrorsToWarnings{};
     703         172 :             pfnTransformer(pTransformArg, FALSE, nSamplePoints, padfX, padfY,
     704             :                            padfZ, pabSuccess);
     705             :         }
     706             :     }
     707             : 
     708             :     /* -------------------------------------------------------------------- */
     709             :     /*      Collect the bounds, ignoring any failed points.                 */
     710             :     /* -------------------------------------------------------------------- */
     711         553 :     double dfMinXOut = 0.0;
     712         553 :     double dfMinYOut = 0.0;
     713         553 :     double dfMaxXOut = 0.0;
     714         553 :     double dfMaxYOut = 0.0;
     715         553 :     bool bGotInitialPoint = false;
     716             : 
     717         553 :     nFailedCount = 0;
     718      129445 :     for (int i = 0; i < nSamplePoints; i++)
     719             :     {
     720      128892 :         int x_i = 0;
     721      128892 :         int y_i = 0;
     722             : 
     723      128892 :         if (nSamplePoints == nSampleMax)
     724             :         {
     725       92612 :             x_i = i % nStepsPlusOne;
     726       92612 :             y_i = i / nStepsPlusOne;
     727             :         }
     728             :         else
     729             :         {
     730       36280 :             if (i < 2 * nStepsPlusOne)
     731             :             {
     732       18140 :                 x_i = i % nStepsPlusOne;
     733       18140 :                 y_i = (i < nStepsPlusOne) ? 0 : nSteps;
     734             :             }
     735             :         }
     736             : 
     737      128892 :         if (x_i > 0 && (pabSuccess[i - 1] || pabSuccess[i]))
     738             :         {
     739       92725 :             double x_out_before = padfX[i - 1];
     740       92725 :             double x_out_after = padfX[i];
     741       92725 :             int nIter = 0;
     742       92725 :             double x_in_before =
     743       92725 :                 static_cast<double>(x_i - 1) * nInXSize / nSteps;
     744       92725 :             double x_in_after = static_cast<double>(x_i) * nInXSize / nSteps;
     745       92725 :             int invalid_before = !(pabSuccess[i - 1]);
     746       92725 :             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      111330 :             while ((invalid_before || invalid_after ||
     753      230327 :                     x_out_before * x_out_after < 0.0) &&
     754             :                    nIter < 16)
     755             :             {
     756       26272 :                 double x = (x_in_before + x_in_after) / 2.0;
     757       26272 :                 double y = static_cast<double>(y_i) * nInYSize / nSteps;
     758       26272 :                 double z = 0.0;
     759       26272 :                 int bSuccess = TRUE;
     760       26272 :                 if (pfnTransformer(pTransformArg, FALSE, 1, &x, &y, &z,
     761       45734 :                                    &bSuccess) &&
     762       19462 :                     bSuccess)
     763             :                 {
     764       19462 :                     if (bGotInitialPoint)
     765             :                     {
     766       19442 :                         dfMinXOut = std::min(dfMinXOut, x);
     767       19442 :                         dfMinYOut = std::min(dfMinYOut, y);
     768       19442 :                         dfMaxXOut = std::max(dfMaxXOut, x);
     769       19442 :                         dfMaxYOut = std::max(dfMaxYOut, y);
     770             :                     }
     771             :                     else
     772             :                     {
     773          20 :                         bGotInitialPoint = true;
     774          20 :                         dfMinXOut = x;
     775          20 :                         dfMaxXOut = x;
     776          20 :                         dfMinYOut = y;
     777          20 :                         dfMaxYOut = y;
     778             :                     }
     779             : 
     780       19462 :                     if (invalid_before || x_out_before * x < 0)
     781             :                     {
     782       10301 :                         invalid_after = FALSE;
     783       10301 :                         x_in_after = (x_in_before + x_in_after) / 2.0;
     784       10301 :                         x_out_after = x;
     785             :                     }
     786             :                     else
     787             :                     {
     788        9161 :                         invalid_before = FALSE;
     789        9161 :                         x_out_before = x;
     790        9161 :                         x_in_before = (x_in_before + x_in_after) / 2.0;
     791             :                     }
     792             :                 }
     793             :                 else
     794             :                 {
     795        6810 :                     if (invalid_before)
     796             :                     {
     797        3402 :                         x_in_before = (x_in_before + x_in_after) / 2.0;
     798             :                     }
     799        3408 :                     else if (invalid_after)
     800             :                     {
     801        3408 :                         x_in_after = (x_in_before + x_in_after) / 2.0;
     802             :                     }
     803             :                     else
     804             :                     {
     805           0 :                         break;
     806             :                     }
     807             :                 }
     808       26272 :                 nIter++;
     809             :             }
     810             :         }
     811             : 
     812      128892 :         if (!pabSuccess[i])
     813             :         {
     814       14579 :             nFailedCount++;
     815       14579 :             continue;
     816             :         }
     817             : 
     818      114313 :         if (bGotInitialPoint)
     819             :         {
     820      113782 :             dfMinXOut = std::min(dfMinXOut, padfX[i]);
     821      113782 :             dfMinYOut = std::min(dfMinYOut, padfY[i]);
     822      113782 :             dfMaxXOut = std::max(dfMaxXOut, padfX[i]);
     823      113782 :             dfMaxYOut = std::max(dfMaxYOut, padfY[i]);
     824             :         }
     825             :         else
     826             :         {
     827         531 :             bGotInitialPoint = true;
     828         531 :             dfMinXOut = padfX[i];
     829         531 :             dfMaxXOut = padfX[i];
     830         531 :             dfMinYOut = padfY[i];
     831         531 :             dfMaxYOut = padfY[i];
     832             :         }
     833             :     }
     834             : 
     835         553 :     if (nFailedCount > nSamplePoints - 10)
     836             :     {
     837           5 :         CPLError(CE_Failure, CPLE_AppDefined,
     838             :                  "Too many points (%d out of %d) failed to transform, "
     839             :                  "unable to compute output bounds.",
     840             :                  nFailedCount, nSamplePoints);
     841             : 
     842           5 :         CPLFree(padfX);
     843           5 :         CPLFree(padfXRevert);
     844           5 :         CPLFree(pabSuccess);
     845             : 
     846           5 :         return CE_Failure;
     847             :     }
     848             : 
     849         548 :     if (nFailedCount)
     850         136 :         CPLDebug("GDAL",
     851             :                  "GDALSuggestedWarpOutput(): %d out of %d points failed to "
     852             :                  "transform.",
     853             :                  nFailedCount, nSamplePoints);
     854             : 
     855         548 :     bool bIsGeographicCoordsDeg = false;
     856         548 :     if (bIsGDALGenImgProjTransform)
     857             :     {
     858         548 :         const GDALGenImgProjTransformInfo *pGIPTI =
     859             :             static_cast<const GDALGenImgProjTransformInfo *>(pTransformArg);
     860         548 :         if (pGIPTI->sSrcParams.pTransformer == GDALGeoLocTransform &&
     861          36 :             pGIPTI->sDstParams.pTransformer == nullptr &&
     862          36 :             pGIPTI->sDstParams.adfGeoTransform[0] == 0 &&
     863          34 :             pGIPTI->sDstParams.adfGeoTransform[1] == 1 &&
     864          34 :             pGIPTI->sDstParams.adfGeoTransform[2] == 0 &&
     865          34 :             pGIPTI->sDstParams.adfGeoTransform[3] == 0 &&
     866          34 :             pGIPTI->sDstParams.adfGeoTransform[4] == 0 &&
     867          34 :             pGIPTI->sDstParams.adfGeoTransform[5] == 1)
     868             :         {
     869             :             /* --------------------------------------------------------------------
     870             :              */
     871             :             /*      Special case for geolocation array, to quickly find the
     872             :              * bounds. */
     873             :             /* --------------------------------------------------------------------
     874             :              */
     875          34 :             const GDALGeoLocTransformInfo *pGLTI =
     876             :                 static_cast<const GDALGeoLocTransformInfo *>(
     877             :                     pGIPTI->sSrcParams.pTransformArg);
     878             : 
     879          34 :             if (pGIPTI->pReproject == nullptr)
     880             :             {
     881             :                 const char *pszGLSRS =
     882          28 :                     CSLFetchNameValue(pGLTI->papszGeolocationInfo, "SRS");
     883          28 :                 if (pszGLSRS == nullptr)
     884             :                 {
     885           6 :                     bIsGeographicCoordsDeg = true;
     886             :                 }
     887             :                 else
     888             :                 {
     889          44 :                     OGRSpatialReference oSRS;
     890          22 :                     if (oSRS.SetFromUserInput(pszGLSRS) == OGRERR_NONE &&
     891          42 :                         oSRS.IsGeographic() &&
     892          20 :                         std::fabs(oSRS.GetAngularUnits() -
     893          20 :                                   CPLAtof(SRS_UA_DEGREE_CONV)) < 1e-9)
     894             :                     {
     895          20 :                         bIsGeographicCoordsDeg = true;
     896             :                     }
     897             :                 }
     898             :             }
     899             : 
     900         272 :             for (const auto &xy :
     901          34 :                  {std::pair<double, double>(pGLTI->dfMinX, pGLTI->dfYAtMinX),
     902          34 :                   std::pair<double, double>(pGLTI->dfXAtMinY, pGLTI->dfMinY),
     903          34 :                   std::pair<double, double>(pGLTI->dfMaxX, pGLTI->dfYAtMaxX),
     904         170 :                   std::pair<double, double>(pGLTI->dfXAtMaxY, pGLTI->dfMaxY)})
     905             :             {
     906         136 :                 double x = xy.first;
     907         136 :                 double y = xy.second;
     908         136 :                 if (pGLTI->bSwapXY)
     909             :                 {
     910           4 :                     std::swap(x, y);
     911             :                 }
     912         136 :                 double xOut = std::numeric_limits<double>::quiet_NaN();
     913         136 :                 double yOut = std::numeric_limits<double>::quiet_NaN();
     914         160 :                 if (pGIPTI->pReproject == nullptr ||
     915          24 :                     pGIPTI->pReproject(pGIPTI->pReprojectArg, false, 1, &x, &y,
     916             :                                        nullptr, nullptr))
     917             :                 {
     918         136 :                     xOut = x;
     919         136 :                     yOut = y;
     920             :                 }
     921         136 :                 dfMinXOut = std::min(dfMinXOut, xOut);
     922         136 :                 dfMinYOut = std::min(dfMinYOut, yOut);
     923         136 :                 dfMaxXOut = std::max(dfMaxXOut, xOut);
     924         136 :                 dfMaxYOut = std::max(dfMaxYOut, yOut);
     925          34 :             }
     926             :         }
     927         514 :         else if (pGIPTI->sSrcParams.pTransformer == nullptr &&
     928         468 :                  pGIPTI->sDstParams.pTransformer == nullptr &&
     929         468 :                  pGIPTI->pReproject == GDALReprojectionTransform &&
     930         455 :                  pGIPTI->sDstParams.adfGeoTransform[0] == 0 &&
     931         453 :                  pGIPTI->sDstParams.adfGeoTransform[1] == 1 &&
     932         453 :                  pGIPTI->sDstParams.adfGeoTransform[2] == 0 &&
     933         453 :                  pGIPTI->sDstParams.adfGeoTransform[3] == 0 &&
     934         453 :                  pGIPTI->sDstParams.adfGeoTransform[4] == 0 &&
     935         453 :                  pGIPTI->sDstParams.adfGeoTransform[5] == 1)
     936             :         {
     937             :             /* ------------------------------------------------------------- */
     938             :             /* Special case for warping using source geotransform and        */
     939             :             /* reprojection to deal with the poles.                          */
     940             :             /* ------------------------------------------------------------- */
     941         453 :             const GDALReprojectionTransformInfo *psRTI =
     942             :                 static_cast<const GDALReprojectionTransformInfo *>(
     943             :                     pGIPTI->pReprojectArg);
     944             :             const OGRSpatialReference *poSourceCRS =
     945         453 :                 psRTI->poForwardTransform->GetSourceCS();
     946             :             const OGRSpatialReference *poTargetCRS =
     947         453 :                 psRTI->poForwardTransform->GetTargetCS();
     948         905 :             if (poTargetCRS != nullptr &&
     949         452 :                 psRTI->poReverseTransform != nullptr &&
     950         452 :                 poTargetCRS->IsGeographic() &&
     951         158 :                 fabs(poTargetCRS->GetAngularUnits() -
     952        1063 :                      CPLAtof(SRS_UA_DEGREE_CONV)) < 1e-9 &&
     953         158 :                 (!poSourceCRS || !poSourceCRS->IsGeographic()))
     954             :             {
     955         107 :                 bIsGeographicCoordsDeg = true;
     956             : 
     957         107 :                 std::unique_ptr<CPLConfigOptionSetter> poSetter;
     958         107 :                 if (pGIPTI->bCheckWithInvertPROJ)
     959             :                 {
     960             :                     // CHECK_WITH_INVERT_PROJ=YES prevent reliable
     961             :                     // transformation of poles.
     962           4 :                     poSetter = std::make_unique<CPLConfigOptionSetter>(
     963           4 :                         "CHECK_WITH_INVERT_PROJ", "NO", false);
     964           4 :                     GDALRefreshGenImgProjTransformer(pTransformArg);
     965             :                     // GDALRefreshGenImgProjTransformer() has invalidated psRTI
     966           4 :                     psRTI = static_cast<const GDALReprojectionTransformInfo *>(
     967             :                         pGIPTI->pReprojectArg);
     968             :                 }
     969             : 
     970         321 :                 for (const auto &sign : iSignArray)
     971             :                 {
     972         214 :                     double X = 0.0;
     973         214 :                     const double Yinit = 90.0 * sign;
     974         214 :                     double Y = Yinit;
     975         214 :                     if (psRTI->poReverseTransform->Transform(1, &X, &Y))
     976             :                     {
     977         132 :                         const auto invGT =
     978             :                             pGIPTI->sSrcParams.adfInvGeoTransform;
     979         132 :                         const double x = invGT[0] + X * invGT[1] + Y * invGT[2];
     980         132 :                         const double y = invGT[3] + X * invGT[4] + Y * invGT[5];
     981         132 :                         constexpr double EPSILON = 1e-5;
     982         132 :                         if (x >= -EPSILON && x <= nInXSize + EPSILON &&
     983          27 :                             y >= -EPSILON && y <= nInYSize + EPSILON)
     984             :                         {
     985           6 :                             if (psRTI->poForwardTransform->Transform(1, &X,
     986          12 :                                                                      &Y) &&
     987           6 :                                 fabs(Y - Yinit) <= 1e-6)
     988             :                             {
     989           6 :                                 bool bMinXMaxXSet = false;
     990           6 :                                 if (poSourceCRS)
     991             :                                 {
     992             :                                     const char *pszProjection =
     993           6 :                                         poSourceCRS->GetAttrValue("PROJECTION");
     994           6 :                                     if (pszProjection &&
     995           6 :                                         EQUAL(pszProjection,
     996             :                                               SRS_PT_ORTHOGRAPHIC))
     997             :                                     {
     998             :                                         const double dfLon0 =
     999           4 :                                             poSourceCRS->GetNormProjParm(
    1000             :                                                 SRS_PP_CENTRAL_MERIDIAN, 0.0);
    1001           4 :                                         dfMinXOut = dfLon0 - 90;
    1002           4 :                                         dfMaxXOut = dfLon0 + 90;
    1003           4 :                                         bMinXMaxXSet = true;
    1004             :                                     }
    1005             :                                 }
    1006           6 :                                 if (!bMinXMaxXSet)
    1007             :                                 {
    1008           2 :                                     dfMinXOut = -180;
    1009           2 :                                     dfMaxXOut = 180;
    1010             :                                 }
    1011           6 :                                 if (sign < 0)
    1012           2 :                                     dfMinYOut = Yinit;
    1013             :                                 else
    1014           4 :                                     dfMaxYOut = Yinit;
    1015             :                             }
    1016             :                         }
    1017             :                     }
    1018             :                 }
    1019             : 
    1020         107 :                 if (poSetter)
    1021             :                 {
    1022           4 :                     poSetter.reset();
    1023           4 :                     GDALRefreshGenImgProjTransformer(pTransformArg);
    1024           4 :                     pGIPTI = static_cast<const GDALGenImgProjTransformInfo *>(
    1025             :                         pTransformArg);
    1026           4 :                     psRTI = static_cast<const GDALReprojectionTransformInfo *>(
    1027             :                         pGIPTI->pReprojectArg);
    1028           4 :                     poSourceCRS = psRTI->poForwardTransform->GetSourceCS();
    1029           4 :                     poTargetCRS = psRTI->poForwardTransform->GetTargetCS();
    1030             :                 }
    1031             :             }
    1032             : 
    1033             :             // Use TransformBounds() to handle more particular cases
    1034         453 :             if (poSourceCRS != nullptr && poTargetCRS != nullptr &&
    1035         452 :                 pGIPTI->sSrcParams.adfGeoTransform[1] != 0 &&
    1036         452 :                 pGIPTI->sSrcParams.adfGeoTransform[2] == 0 &&
    1037         452 :                 pGIPTI->sSrcParams.adfGeoTransform[4] == 0 &&
    1038         452 :                 pGIPTI->sSrcParams.adfGeoTransform[5] != 0)
    1039             :             {
    1040         452 :                 const double dfULX = pGIPTI->sSrcParams.adfGeoTransform[0];
    1041         452 :                 const double dfULY = pGIPTI->sSrcParams.adfGeoTransform[3];
    1042         452 :                 const double dfLRX =
    1043         452 :                     dfULX + pGIPTI->sSrcParams.adfGeoTransform[1] * nInXSize;
    1044         452 :                 const double dfLRY =
    1045         452 :                     dfULY + pGIPTI->sSrcParams.adfGeoTransform[5] * nInYSize;
    1046         452 :                 const double dfMinSrcX = std::min(dfULX, dfLRX);
    1047         452 :                 const double dfMinSrcY = std::min(dfULY, dfLRY);
    1048         452 :                 const double dfMaxSrcX = std::max(dfULX, dfLRX);
    1049         452 :                 const double dfMaxSrcY = std::max(dfULY, dfLRY);
    1050         452 :                 double dfTmpMinXOut = std::numeric_limits<double>::max();
    1051         452 :                 double dfTmpMinYOut = std::numeric_limits<double>::max();
    1052         452 :                 double dfTmpMaxXOut = std::numeric_limits<double>::min();
    1053         452 :                 double dfTmpMaxYOut = std::numeric_limits<double>::min();
    1054         904 :                 if (psRTI->poForwardTransform->TransformBounds(
    1055             :                         dfMinSrcX, dfMinSrcY, dfMaxSrcX, dfMaxSrcY,
    1056             :                         &dfTmpMinXOut, &dfTmpMinYOut, &dfTmpMaxXOut,
    1057             :                         &dfTmpMaxYOut,
    1058         452 :                         2))  // minimum number of points as we already have a
    1059             :                              // logic above to sample
    1060             :                 {
    1061         445 :                     dfMinXOut = std::min(dfMinXOut, dfTmpMinXOut);
    1062         445 :                     dfMinYOut = std::min(dfMinYOut, dfTmpMinYOut);
    1063         445 :                     dfMaxXOut = std::max(dfMaxXOut, dfTmpMaxXOut);
    1064         445 :                     dfMaxYOut = std::max(dfMaxYOut, dfTmpMaxYOut);
    1065             :                 }
    1066             :             }
    1067             :         }
    1068             :     }
    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         548 :     double dfDiagonalDist = 0.0;
    1078         548 :     double dfDeltaX = 0.0;
    1079         548 :     double dfDeltaY = 0.0;
    1080             : 
    1081         548 :     if (pabSuccess[0] && pabSuccess[nSamplePoints - 1])
    1082             :     {
    1083         417 :         dfDeltaX = padfX[nSamplePoints - 1] - padfX[0];
    1084         417 :         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             :     }
    1088         548 :     if (dfDeltaX == 0.0 || dfDeltaY == 0.0)
    1089             :     {
    1090         139 :         dfDeltaX = dfMaxXOut - dfMinXOut;
    1091         139 :         dfDeltaY = dfMaxYOut - dfMinYOut;
    1092             :     }
    1093             : 
    1094         548 :     dfDiagonalDist = sqrt(dfDeltaX * dfDeltaX + dfDeltaY * dfDeltaY);
    1095             : 
    1096             :     /* -------------------------------------------------------------------- */
    1097             :     /*      Compute a pixel size from this.                                 */
    1098             :     /* -------------------------------------------------------------------- */
    1099             :     double dfPixelSize =
    1100         548 :         dfDiagonalDist / sqrt(static_cast<double>(nInXSize) * nInXSize +
    1101         548 :                               static_cast<double>(nInYSize) * nInYSize);
    1102             : 
    1103         548 :     double dfPixels = (dfMaxXOut - dfMinXOut) / dfPixelSize;
    1104         548 :     double dfLines = (dfMaxYOut - dfMinYOut) / dfPixelSize;
    1105             : 
    1106         548 :     const int knIntMaxMinusOne = std::numeric_limits<int>::max() - 1;
    1107         548 :     if (dfPixels > knIntMaxMinusOne && dfLines <= dfPixels)
    1108             :     {
    1109           0 :         dfPixels = knIntMaxMinusOne;
    1110           0 :         dfPixelSize = (dfMaxXOut - dfMinXOut) / dfPixels;
    1111           0 :         dfLines = (dfMaxYOut - dfMinYOut) / dfPixelSize;
    1112             :     }
    1113         548 :     else if (dfLines > knIntMaxMinusOne)
    1114             :     {
    1115           2 :         dfLines = knIntMaxMinusOne;
    1116           2 :         dfPixelSize = (dfMaxYOut - dfMinYOut) / dfLines;
    1117           2 :         dfPixels = (dfMaxXOut - dfMinXOut) / dfPixelSize;
    1118             :     }
    1119             : 
    1120         548 :     if (dfPixels > knIntMaxMinusOne || dfLines > knIntMaxMinusOne)
    1121             :     {
    1122           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    1123             :                  "Computed dimensions are too big : %.0f x %.0f",
    1124             :                  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             :     }
    1132             : 
    1133         548 :     if ((nOptions & GDAL_SWO_ROUND_UP_SIZE) != 0)
    1134             :     {
    1135           8 :         constexpr double EPS = 1e-5;
    1136           8 :         *pnPixels = static_cast<int>(std::ceil(dfPixels - EPS));
    1137           8 :         *pnLines = static_cast<int>(std::ceil(dfLines - EPS));
    1138             :     }
    1139             :     else
    1140             :     {
    1141         540 :         *pnPixels = static_cast<int>(dfPixels + 0.5);
    1142         540 :         *pnLines = static_cast<int>(dfLines + 0.5);
    1143             :     }
    1144             : 
    1145         548 :     double dfPixelSizeX = dfPixelSize;
    1146         548 :     double dfPixelSizeY = dfPixelSize;
    1147             : 
    1148         548 :     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         725 :     for (const auto &dfRatio : adfRatioArray)
    1156             :     {
    1157         717 :         const double dfTryPixelSizeX =
    1158         717 :             dfPixelSizeX - dfPixelSizeX * dfRatio / *pnPixels;
    1159         717 :         double adfExtent[4] = {dfMinXOut, dfMaxYOut - (*pnLines) * dfPixelSizeY,
    1160         717 :                                dfMinXOut + (*pnPixels) * dfTryPixelSizeX,
    1161         717 :                                dfMaxYOut};
    1162         717 :         if (!GDALSuggestedWarpOutput2_MustAdjustForRightBorder(
    1163             :                 pfnTransformer, pTransformArg, adfExtent, *pnPixels, *pnLines,
    1164             :                 dfTryPixelSizeX, dfPixelSizeY))
    1165             :         {
    1166         540 :             dfPixelSizeX = dfTryPixelSizeX;
    1167         540 :             break;
    1168             :         }
    1169             :     }
    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         628 :     for (const auto &dfRatio : adfRatioArray)
    1177             :     {
    1178         618 :         const double dfTryPixelSizeY =
    1179         618 :             dfPixelSizeY - dfPixelSizeY * dfRatio / *pnLines;
    1180             :         double adfExtent[4] = {
    1181         618 :             dfMinXOut, dfMaxYOut - (*pnLines) * dfTryPixelSizeY,
    1182         618 :             dfMinXOut + (*pnPixels) * dfPixelSizeX, dfMaxYOut};
    1183         618 :         if (!GDALSuggestedWarpOutput2_MustAdjustForBottomBorder(
    1184             :                 pfnTransformer, pTransformArg, adfExtent, *pnPixels, *pnLines,
    1185             :                 dfPixelSizeX, dfTryPixelSizeY))
    1186             :         {
    1187         538 :             dfPixelSizeY = dfTryPixelSizeY;
    1188         538 :             break;
    1189             :         }
    1190             :     }
    1191             : 
    1192             :     /* -------------------------------------------------------------------- */
    1193             :     /*      Recompute some bounds so that all return values are consistent  */
    1194             :     /* -------------------------------------------------------------------- */
    1195         548 :     double dfMaxXOutNew = dfMinXOut + (*pnPixels) * dfPixelSizeX;
    1196         548 :     if (bIsGeographicCoordsDeg &&
    1197         133 :         ((dfMaxXOut <= 180 && dfMaxXOutNew > 180) || dfMaxXOut == 180))
    1198             :     {
    1199           3 :         dfMaxXOut = 180;
    1200           3 :         dfPixelSizeX = (dfMaxXOut - dfMinXOut) / *pnPixels;
    1201             :     }
    1202             :     else
    1203             :     {
    1204         545 :         dfMaxXOut = dfMaxXOutNew;
    1205             :     }
    1206             : 
    1207         548 :     double dfMinYOutNew = dfMaxYOut - (*pnLines) * dfPixelSizeY;
    1208         548 :     if (bIsGeographicCoordsDeg && dfMinYOut >= -90 && dfMinYOutNew < -90)
    1209             :     {
    1210           0 :         dfMinYOut = -90;
    1211           0 :         dfPixelSizeY = (dfMaxYOut - dfMinYOut) / *pnLines;
    1212             :     }
    1213             :     else
    1214             :     {
    1215         548 :         dfMinYOut = dfMinYOutNew;
    1216             :     }
    1217             : 
    1218             :     /* -------------------------------------------------------------------- */
    1219             :     /*      Return raw extents.                                             */
    1220             :     /* -------------------------------------------------------------------- */
    1221         548 :     padfExtent[0] = dfMinXOut;
    1222         548 :     padfExtent[1] = dfMinYOut;
    1223         548 :     padfExtent[2] = dfMaxXOut;
    1224         548 :     padfExtent[3] = dfMaxYOut;
    1225             : 
    1226             :     /* -------------------------------------------------------------------- */
    1227             :     /*      Set the output geotransform.                                    */
    1228             :     /* -------------------------------------------------------------------- */
    1229         548 :     padfGeoTransformOut[0] = dfMinXOut;
    1230         548 :     padfGeoTransformOut[1] = dfPixelSizeX;
    1231         548 :     padfGeoTransformOut[2] = 0.0;
    1232         548 :     padfGeoTransformOut[3] = dfMaxYOut;
    1233         548 :     padfGeoTransformOut[4] = 0.0;
    1234         548 :     padfGeoTransformOut[5] = -dfPixelSizeY;
    1235             : 
    1236         548 :     CPLFree(padfX);
    1237         548 :     CPLFree(padfXRevert);
    1238         548 :     CPLFree(pabSuccess);
    1239             : 
    1240         548 :     return CE_None;
    1241             : }
    1242             : 
    1243             : /************************************************************************/
    1244             : /*                   GetCurrentCheckWithInvertPROJ()                    */
    1245             : /************************************************************************/
    1246             : 
    1247        3971 : static bool GetCurrentCheckWithInvertPROJ()
    1248             : {
    1249        3971 :     return CPLTestBool(CPLGetConfigOption("CHECK_WITH_INVERT_PROJ", "NO"));
    1250             : }
    1251             : 
    1252             : /************************************************************************/
    1253             : /*              GDALCreateGenImgProjTransformerInternal()               */
    1254             : /************************************************************************/
    1255             : 
    1256             : static void *GDALCreateSimilarGenImgProjTransformer(void *hTransformArg,
    1257             :                                                     double dfRatioX,
    1258             :                                                     double dfRatioY);
    1259             : 
    1260        2569 : static GDALGenImgProjTransformInfo *GDALCreateGenImgProjTransformerInternal()
    1261             : {
    1262             :     /* -------------------------------------------------------------------- */
    1263             :     /*      Initialize the transform info.                                  */
    1264             :     /* -------------------------------------------------------------------- */
    1265             :     GDALGenImgProjTransformInfo *psInfo =
    1266             :         static_cast<GDALGenImgProjTransformInfo *>(
    1267        2569 :             CPLCalloc(sizeof(GDALGenImgProjTransformInfo), 1));
    1268             : 
    1269        2569 :     memcpy(psInfo->sTI.abySignature, GDAL_GTI2_SIGNATURE,
    1270             :            strlen(GDAL_GTI2_SIGNATURE));
    1271        2569 :     psInfo->sTI.pszClassName = GDAL_GEN_IMG_TRANSFORMER_CLASS_NAME;
    1272        2569 :     psInfo->sTI.pfnTransform = GDALGenImgProjTransform;
    1273        2569 :     psInfo->sTI.pfnCleanup = GDALDestroyGenImgProjTransformer;
    1274        2569 :     psInfo->sTI.pfnSerialize = GDALSerializeGenImgProjTransformer;
    1275        2569 :     psInfo->sTI.pfnCreateSimilar = GDALCreateSimilarGenImgProjTransformer;
    1276             : 
    1277        2569 :     psInfo->bCheckWithInvertPROJ = GetCurrentCheckWithInvertPROJ();
    1278        2569 :     psInfo->bHasCustomTransformationPipeline = false;
    1279             : 
    1280        2569 :     return psInfo;
    1281             : }
    1282             : 
    1283             : /************************************************************************/
    1284             : /*               GDALCreateSimilarGenImgProjTransformer()               */
    1285             : /************************************************************************/
    1286             : 
    1287          61 : static void *GDALCreateSimilarGenImgProjTransformer(void *hTransformArg,
    1288             :                                                     double dfRatioX,
    1289             :                                                     double dfRatioY)
    1290             : {
    1291          61 :     VALIDATE_POINTER1(hTransformArg, "GDALCreateSimilarGenImgProjTransformer",
    1292             :                       nullptr);
    1293             : 
    1294          61 :     GDALGenImgProjTransformInfo *psInfo =
    1295             :         static_cast<GDALGenImgProjTransformInfo *>(hTransformArg);
    1296             : 
    1297             :     GDALGenImgProjTransformInfo *psClonedInfo =
    1298          61 :         GDALCreateGenImgProjTransformerInternal();
    1299             : 
    1300          61 :     memcpy(psClonedInfo, psInfo, sizeof(GDALGenImgProjTransformInfo));
    1301             : 
    1302          61 :     psClonedInfo->bCheckWithInvertPROJ = GetCurrentCheckWithInvertPROJ();
    1303             : 
    1304          61 :     if (psClonedInfo->sSrcParams.pTransformArg)
    1305           8 :         psClonedInfo->sSrcParams.pTransformArg = GDALCreateSimilarTransformer(
    1306             :             psInfo->sSrcParams.pTransformArg, dfRatioX, dfRatioY);
    1307          53 :     else if (dfRatioX != 1.0 || dfRatioY != 1.0)
    1308             :     {
    1309          10 :         if (psClonedInfo->sSrcParams.adfGeoTransform[2] == 0.0 &&
    1310          10 :             psClonedInfo->sSrcParams.adfGeoTransform[4] == 0.0)
    1311             :         {
    1312          10 :             psClonedInfo->sSrcParams.adfGeoTransform[1] *= dfRatioX;
    1313          10 :             psClonedInfo->sSrcParams.adfGeoTransform[5] *= dfRatioY;
    1314             :         }
    1315             :         else
    1316             :         {
    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             :         }
    1324          10 :         if (!GDALInvGeoTransform(psClonedInfo->sSrcParams.adfGeoTransform,
    1325          10 :                                  psClonedInfo->sSrcParams.adfInvGeoTransform))
    1326             :         {
    1327           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Cannot invert geotransform");
    1328           0 :             GDALDestroyGenImgProjTransformer(psClonedInfo);
    1329           0 :             return nullptr;
    1330             :         }
    1331             :     }
    1332             : 
    1333          61 :     if (psClonedInfo->pReprojectArg)
    1334          39 :         psClonedInfo->pReprojectArg =
    1335          39 :             GDALCloneTransformer(psInfo->pReprojectArg);
    1336             : 
    1337          61 :     if (psClonedInfo->sDstParams.pTransformArg)
    1338           0 :         psClonedInfo->sDstParams.pTransformArg =
    1339           0 :             GDALCloneTransformer(psInfo->sDstParams.pTransformArg);
    1340             : 
    1341          61 :     return psClonedInfo;
    1342             : }
    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          52 : 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             : {
    1399          52 :     char **papszOptions = nullptr;
    1400             : 
    1401          52 :     if (pszSrcWKT != nullptr)
    1402           3 :         papszOptions = CSLSetNameValue(papszOptions, "SRC_SRS", pszSrcWKT);
    1403          52 :     if (pszDstWKT != nullptr)
    1404           3 :         papszOptions = CSLSetNameValue(papszOptions, "DST_SRS", pszDstWKT);
    1405          52 :     if (!bGCPUseOK)
    1406           0 :         papszOptions = CSLSetNameValue(papszOptions, "GCPS_OK", "FALSE");
    1407          52 :     if (nOrder != 0)
    1408           0 :         papszOptions = CSLSetNameValue(papszOptions, "MAX_GCP_ORDER",
    1409           0 :                                        CPLString().Printf("%d", nOrder));
    1410             : 
    1411          52 :     void *pRet = GDALCreateGenImgProjTransformer2(hSrcDS, hDstDS, papszOptions);
    1412          52 :     CSLDestroy(papszOptions);
    1413             : 
    1414          52 :     return pRet;
    1415             : }
    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        1112 : static void InsertCenterLong(GDALDatasetH hDS, const OGRSpatialReference *poSRS,
    1425             :                              const OGRSpatialReference *poDstSRS,
    1426             :                              const char *pszTargetExtent,
    1427             :                              CPLStringList &aosOptions)
    1428             : 
    1429             : {
    1430        1941 :     if (!poSRS->IsGeographic() || std::fabs(poSRS->GetAngularUnits() -
    1431         829 :                                             CPLAtof(SRS_UA_DEGREE_CONV)) > 1e-9)
    1432             :     {
    1433         286 :         return;
    1434             :     }
    1435             : 
    1436         828 :     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         828 :     double adfGeoTransform[6] = {};
    1444             : 
    1445         828 :     if (GDALGetGeoTransform(hDS, adfGeoTransform) != CE_None)
    1446           0 :         return;
    1447             : 
    1448             :     /* -------------------------------------------------------------------- */
    1449             :     /*      Compute min/max longitude based on testing the four corners.    */
    1450             :     /* -------------------------------------------------------------------- */
    1451         828 :     const int nXSize = GDALGetRasterXSize(hDS);
    1452         828 :     const int nYSize = GDALGetRasterYSize(hDS);
    1453             : 
    1454             :     const double dfMinLong =
    1455        1656 :         std::min(std::min(adfGeoTransform[0] + 0 * adfGeoTransform[1] +
    1456         828 :                               0 * adfGeoTransform[2],
    1457        1656 :                           adfGeoTransform[0] + nXSize * adfGeoTransform[1] +
    1458         828 :                               0 * adfGeoTransform[2]),
    1459        1656 :                  std::min(adfGeoTransform[0] + 0 * adfGeoTransform[1] +
    1460         828 :                               nYSize * adfGeoTransform[2],
    1461        1656 :                           adfGeoTransform[0] + nXSize * adfGeoTransform[1] +
    1462         828 :                               nYSize * adfGeoTransform[2]));
    1463             :     const double dfMaxLong =
    1464        1656 :         std::max(std::max(adfGeoTransform[0] + 0 * adfGeoTransform[1] +
    1465         828 :                               0 * adfGeoTransform[2],
    1466        1656 :                           adfGeoTransform[0] + nXSize * adfGeoTransform[1] +
    1467         828 :                               0 * adfGeoTransform[2]),
    1468        1656 :                  std::max(adfGeoTransform[0] + 0 * adfGeoTransform[1] +
    1469         828 :                               nYSize * adfGeoTransform[2],
    1470        1656 :                           adfGeoTransform[0] + nXSize * adfGeoTransform[1] +
    1471         828 :                               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         828 :     if (dfMaxLong - dfMinLong > 360.0)
    1476             :     {
    1477           3 :         const CPLStringList aosTE(CSLTokenizeString2(pszTargetExtent, ",", 0));
    1478           6 :         if (aosTE.size() == 4 && poDstSRS->IsGeographic() &&
    1479           1 :             std::fabs(poDstSRS->GetAngularUnits() -
    1480           7 :                       CPLAtof(SRS_UA_DEGREE_CONV)) <= 1e-9 &&
    1481           1 :             ((CPLAtof(aosTE[0]) >= -179 && CPLAtof(aosTE[0]) < 180 &&
    1482           1 :               CPLAtof(aosTE[2]) > 180) ||
    1483           0 :              (CPLAtof(aosTE[0]) < -180 && CPLAtof(aosTE[2]) > -180 &&
    1484           0 :               CPLAtof(aosTE[2]) <= 179)))
    1485             :         {
    1486             :             // insert CENTER_LONG
    1487             :         }
    1488             :         else
    1489             :         {
    1490           2 :             return;
    1491             :         }
    1492             :     }
    1493             : 
    1494             :     /* -------------------------------------------------------------------- */
    1495             :     /*      Insert center long.                                             */
    1496             :     /* -------------------------------------------------------------------- */
    1497         826 :     const double dfCenterLong = (dfMaxLong + dfMinLong) / 2.0;
    1498         826 :     aosOptions.SetNameValue("CENTER_LONG", CPLSPrintf("%g", dfCenterLong));
    1499             : }
    1500             : 
    1501             : /************************************************************************/
    1502             : /*                     GDALComputeAreaOfInterest()                      */
    1503             : /************************************************************************/
    1504             : 
    1505        1553 : 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             : {
    1512        1553 :     bool ret = false;
    1513             : 
    1514        1553 :     if (!poSRS)
    1515           0 :         return false;
    1516             : 
    1517        1553 :     OGRSpatialReference oSrcSRSHoriz(*poSRS);
    1518        1553 :     if (oSrcSRSHoriz.IsCompound())
    1519             :     {
    1520          19 :         oSrcSRSHoriz.StripVertical();
    1521             :     }
    1522             : 
    1523        1553 :     OGRSpatialReference *poGeog = oSrcSRSHoriz.CloneGeogCS();
    1524        1553 :     if (poGeog)
    1525             :     {
    1526        1553 :         poGeog->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    1527        1553 :         poGeog->SetAngularUnits(SRS_UA_DEGREE, CPLAtof(SRS_UA_DEGREE_CONV));
    1528             : 
    1529        1553 :         auto poCT = OGRCreateCoordinateTransformation(&oSrcSRSHoriz, poGeog);
    1530        1553 :         if (poCT)
    1531             :         {
    1532        1553 :             poCT->SetEmitErrors(false);
    1533             : 
    1534             :             double x[4], y[4];
    1535        1553 :             x[0] = adfGT[0];
    1536        1553 :             y[0] = adfGT[3];
    1537        1553 :             x[1] = adfGT[0] + nXSize * adfGT[1];
    1538        1553 :             y[1] = adfGT[3];
    1539        1553 :             x[2] = adfGT[0];
    1540        1553 :             y[2] = adfGT[3] + nYSize * adfGT[5];
    1541        1553 :             x[3] = x[1];
    1542        1553 :             y[3] = y[2];
    1543        1553 :             int validity[4] = {false, false, false, false};
    1544        1553 :             poCT->Transform(4, x, y, nullptr, validity);
    1545        1553 :             dfWestLongitudeDeg = std::numeric_limits<double>::max();
    1546        1553 :             dfSouthLatitudeDeg = std::numeric_limits<double>::max();
    1547        1553 :             dfEastLongitudeDeg = -std::numeric_limits<double>::max();
    1548        1553 :             dfNorthLatitudeDeg = -std::numeric_limits<double>::max();
    1549        7765 :             for (int i = 0; i < 4; i++)
    1550             :             {
    1551        6212 :                 if (validity[i])
    1552             :                 {
    1553        6196 :                     ret = true;
    1554        6196 :                     dfWestLongitudeDeg = std::min(dfWestLongitudeDeg, x[i]);
    1555        6196 :                     dfSouthLatitudeDeg = std::min(dfSouthLatitudeDeg, y[i]);
    1556        6196 :                     dfEastLongitudeDeg = std::max(dfEastLongitudeDeg, x[i]);
    1557        6196 :                     dfNorthLatitudeDeg = std::max(dfNorthLatitudeDeg, y[i]);
    1558             :                 }
    1559             :             }
    1560        1553 :             if (validity[0] && validity[1] && x[0] > x[1])
    1561             :             {
    1562          13 :                 dfWestLongitudeDeg = x[0];
    1563          13 :                 dfEastLongitudeDeg = x[1];
    1564             :             }
    1565        1553 :             if (ret && std::fabs(dfWestLongitudeDeg) <= 180 &&
    1566        1546 :                 std::fabs(dfEastLongitudeDeg) <= 180 &&
    1567        1542 :                 std::fabs(dfSouthLatitudeDeg) <= 90 &&
    1568        1538 :                 std::fabs(dfNorthLatitudeDeg) <= 90)
    1569             :             {
    1570        1538 :                 CPLDebug("GDAL", "Computing area of interest: %g, %g, %g, %g",
    1571             :                          dfWestLongitudeDeg, dfSouthLatitudeDeg,
    1572             :                          dfEastLongitudeDeg, dfNorthLatitudeDeg);
    1573             :             }
    1574             :             else
    1575             :             {
    1576          15 :                 CPLDebug("GDAL", "Could not compute area of interest");
    1577          15 :                 dfWestLongitudeDeg = 0;
    1578          15 :                 dfSouthLatitudeDeg = 0;
    1579          15 :                 dfEastLongitudeDeg = 0;
    1580          15 :                 dfNorthLatitudeDeg = 0;
    1581             :             }
    1582        1553 :             OGRCoordinateTransformation::DestroyCT(poCT);
    1583             :         }
    1584             : 
    1585        1553 :         delete poGeog;
    1586             :     }
    1587             : 
    1588        1553 :     return ret;
    1589             : }
    1590             : 
    1591           5 : 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             : {
    1598           5 :     bool ret = false;
    1599             : 
    1600           5 :     if (!poSRS)
    1601           0 :         return false;
    1602             : 
    1603           5 :     OGRSpatialReference oSrcSRSHoriz(*poSRS);
    1604           5 :     if (oSrcSRSHoriz.IsCompound())
    1605             :     {
    1606           0 :         oSrcSRSHoriz.StripVertical();
    1607             :     }
    1608             : 
    1609           5 :     OGRSpatialReference *poGeog = oSrcSRSHoriz.CloneGeogCS();
    1610           5 :     if (poGeog)
    1611             :     {
    1612           5 :         poGeog->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    1613             : 
    1614           5 :         auto poCT = OGRCreateCoordinateTransformation(&oSrcSRSHoriz, poGeog);
    1615           5 :         if (poCT)
    1616             :         {
    1617             :             double x[4], y[4];
    1618           5 :             x[0] = dfX1;
    1619           5 :             y[0] = dfY1;
    1620           5 :             x[1] = dfX2;
    1621           5 :             y[1] = dfY1;
    1622           5 :             x[2] = dfX1;
    1623           5 :             y[2] = dfY2;
    1624           5 :             x[3] = dfX2;
    1625           5 :             y[3] = dfY2;
    1626           5 :             int validity[4] = {false, false, false, false};
    1627           5 :             poCT->Transform(4, x, y, nullptr, validity);
    1628           5 :             dfWestLongitudeDeg = std::numeric_limits<double>::max();
    1629           5 :             dfSouthLatitudeDeg = std::numeric_limits<double>::max();
    1630           5 :             dfEastLongitudeDeg = -std::numeric_limits<double>::max();
    1631           5 :             dfNorthLatitudeDeg = -std::numeric_limits<double>::max();
    1632          25 :             for (int i = 0; i < 4; i++)
    1633             :             {
    1634          20 :                 if (validity[i])
    1635             :                 {
    1636          20 :                     ret = true;
    1637          20 :                     dfWestLongitudeDeg = std::min(dfWestLongitudeDeg, x[i]);
    1638          20 :                     dfSouthLatitudeDeg = std::min(dfSouthLatitudeDeg, y[i]);
    1639          20 :                     dfEastLongitudeDeg = std::max(dfEastLongitudeDeg, x[i]);
    1640          20 :                     dfNorthLatitudeDeg = std::max(dfNorthLatitudeDeg, y[i]);
    1641             :                 }
    1642             :             }
    1643           5 :             if (validity[0] && validity[1] && (dfX1 - dfX2) * (x[0] - x[1]) < 0)
    1644             :             {
    1645           0 :                 dfWestLongitudeDeg = x[0];
    1646           0 :                 dfEastLongitudeDeg = x[1];
    1647             :             }
    1648           5 :             if (ret)
    1649             :             {
    1650           5 :                 CPLDebug("GDAL", "Computing area of interest: %g, %g, %g, %g",
    1651             :                          dfWestLongitudeDeg, dfSouthLatitudeDeg,
    1652             :                          dfEastLongitudeDeg, dfNorthLatitudeDeg);
    1653             :             }
    1654             :             else
    1655             :             {
    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             :             }
    1662           5 :             delete poCT;
    1663             :         }
    1664             : 
    1665           5 :         delete poGeog;
    1666             :     }
    1667             : 
    1668           5 :     return ret;
    1669             : }
    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          44 : static void GDALGCPAntimeridianUnwrap(int nGCPCount, GDAL_GCP *pasGCPList,
    1679             :                                       const OGRSpatialReference &oSRS,
    1680             :                                       CSLConstList papszOptions)
    1681             : {
    1682             :     const char *pszGCPAntimeridianUnwrap =
    1683          44 :         CSLFetchNameValueDef(papszOptions, "GCP_ANTIMERIDIAN_UNWRAP", "AUTO");
    1684         131 :     const bool bForced = EQUAL(pszGCPAntimeridianUnwrap, "YES") ||
    1685          43 :                          EQUAL(pszGCPAntimeridianUnwrap, "ON") ||
    1686         130 :                          EQUAL(pszGCPAntimeridianUnwrap, "TRUE") ||
    1687          43 :                          EQUAL(pszGCPAntimeridianUnwrap, "1");
    1688          49 :     if (bForced || (!oSRS.IsEmpty() && oSRS.IsGeographic() &&
    1689           5 :                     fabs(oSRS.GetAngularUnits(nullptr) -
    1690           5 :                          CPLAtof(SRS_UA_DEGREE_CONV)) < 1e-8 &&
    1691           5 :                     EQUAL(pszGCPAntimeridianUnwrap, "AUTO")))
    1692             :     {
    1693           5 :         if (!bForced)
    1694             :         {
    1695             :             // Proceed to unwrapping only if the longitudes are within
    1696             :             // [-180, -170] or [170, 180]
    1697         424 :             for (int i = 0; i < nGCPCount; ++i)
    1698             :             {
    1699         422 :                 const double dfLongAbs = fabs(pasGCPList[i].dfGCPX);
    1700         422 :                 if (dfLongAbs > 180 || dfLongAbs < 170)
    1701             :                 {
    1702           2 :                     return;
    1703             :                 }
    1704             :             }
    1705             :         }
    1706             : 
    1707           3 :         bool bDone = false;
    1708         633 :         for (int i = 0; i < nGCPCount; ++i)
    1709             :         {
    1710         630 :             if (pasGCPList[i].dfGCPX < 0)
    1711             :             {
    1712          48 :                 if (!bDone)
    1713             :                 {
    1714           3 :                     bDone = true;
    1715           3 :                     CPLDebug("WARP", "GCP longitude unwrapping");
    1716             :                 }
    1717          48 :                 pasGCPList[i].dfGCPX += 360;
    1718             :             }
    1719             :         }
    1720             :     }
    1721             : }
    1722             : 
    1723             : /************************************************************************/
    1724             : /*               GDALGetGenImgProjTranformerOptionList()                */
    1725             : /************************************************************************/
    1726             : 
    1727             : /** Return a XML string describing options accepted by
    1728             :  * GDALCreateGenImgProjTransformer2().
    1729             :  *
    1730             :  * @since 3.11
    1731             :  */
    1732        2346 : const char *GDALGetGenImgProjTranformerOptionList(void)
    1733             : {
    1734             :     return "<OptionList>"
    1735             :            "<Option name='SRC_SRS' type='string' description='WKT SRS, or any "
    1736             :            "string recognized by OGRSpatialReference::SetFromUserInput(), to "
    1737             :            "be used as an override for CRS of input dataset'/>"
    1738             :            "<Option name='DST_SRS' type='string' description='WKT SRS, or any "
    1739             :            "string recognized by OGRSpatialReference::SetFromUserInput(), to "
    1740             :            "be used as an override for CRS of output dataset'/>"
    1741             :            "<Option name='PROMOTE_TO_3D' type='boolean' description='"
    1742             :            "Whether to promote SRC_SRS / DST_SRS to 3D.' "
    1743             :            "default='NO'/>"
    1744             :            "<Option name='COORDINATE_OPERATION' type='string' description='"
    1745             :            "Coordinate operation, as a PROJ or WKT string, used as an override "
    1746             :            "over the normally computed pipeline. The pipeline must take into "
    1747             :            "account the axis order of the source and target SRS.'/>"
    1748             :            "<Option name='ALLOW_BALLPARK' type='boolean' description='"
    1749             :            "Whether ballpark coordinate operations are allowed.' "
    1750             :            "default='YES'/>"
    1751             :            "<Option name='ONLY_BEST' type='string-select' "
    1752             :            "description='"
    1753             :            "By default (at least in the PROJ 9.x series), PROJ may use "
    1754             :            "coordinate operations that are not the \"best\" if resources "
    1755             :            "(typically grids) needed to use them are missing. It will then "
    1756             :            "fallback to other coordinate operations that have a lesser "
    1757             :            "accuracy, for example using Helmert transformations, or in the "
    1758             :            "absence of such operations, to ones with potential very rough "
    1759             :            " accuracy, using \"ballpark\" transformations (see "
    1760             :            "https://proj.org/glossary.html). "
    1761             :            "When calling this method with YES, PROJ will only consider the "
    1762             :            "\"best\" operation, and error out (at Transform() time) if they "
    1763             :            "cannot be used. This method may be used together with "
    1764             :            "ALLOW_BALLPARK=NO to only allow best operations that have a known "
    1765             :            "accuracy. Note that this method has no effect on PROJ versions "
    1766             :            "before 9.2. The default value for this option can be also set with "
    1767             :            "the PROJ_ONLY_BEST_DEFAULT environment variable, or with the "
    1768             :            "\"only_best_default\" setting of proj.ini. Setting "
    1769             :            "ONLY_BEST=YES/NO overrides such default value' default='AUTO'>"
    1770             :            "  <Value>AUTO</Value>"
    1771             :            "  <Value>YES</Value>"
    1772             :            "  <Value>NO</Value>"
    1773             :            "</Option>"
    1774             :            "<Option name='COORDINATE_EPOCH' type='float' description='"
    1775             :            "Coordinate epoch, expressed as a decimal year. Useful for "
    1776             :            "time-dependent coordinate operations.'/>"
    1777             :            "<Option name='SRC_COORDINATE_EPOCH' type='float' description='"
    1778             :            "Coordinate epoch of source CRS, expressed as a decimal year. "
    1779             :            "Useful for time-dependent coordinate operations.'/>"
    1780             :            "<Option name='DST_COORDINATE_EPOCH' type='float' description='"
    1781             :            "Coordinate epoch of target CRS, expressed as a decimal year. "
    1782             :            "Useful for time-dependent coordinate operations.'/>"
    1783             :            "<Option name='GCPS_OK' type='boolean' description='"
    1784             :            "Allow use of GCPs.' default='YES'/>"
    1785             :            "<Option name='REFINE_MINIMUM_GCPS' type='int' description='"
    1786             :            "The minimum amount of GCPs that should be available after the "
    1787             :            "refinement'/>"
    1788             :            "<Option name='REFINE_TOLERANCE' type='float' description='"
    1789             :            "The tolerance that specifies when a GCP will be eliminated.'/>"
    1790             :            "<Option name='MAX_GCP_ORDER' type='int' description='"
    1791             :            "The maximum order to use for GCP derived polynomials if possible. "
    1792             :            "The default is to autoselect based on the number of GCPs. A value "
    1793             :            "of -1 triggers use of Thin Plate Spline instead of polynomials.'/>"
    1794             :            "<Option name='GCP_ANTIMERIDIAN_UNWRAP' type='string-select' "
    1795             :            "description='"
    1796             :            "Whether to \"unwrap\" longitudes of ground control points that "
    1797             :            "span the antimeridian. For datasets with GCPs in "
    1798             :            "longitude/latitude coordinate space spanning the antimeridian, "
    1799             :            "longitudes will have a discontinuity on +/- 180 deg, and will "
    1800             :            "result in a subset of the GCPs with longitude in the [-180,-170] "
    1801             :            "range and another subset in [170, 180]. By default (AUTO), that "
    1802             :            "situation will be detected and longitudes in [-180,-170] will be "
    1803             :            "shifted to [180, 190] to get a continuous set. This option can be "
    1804             :            "set to YES to force that behavior (useful if no SRS information is "
    1805             :            "available), or to NO to disable it.' default='AUTO'>"
    1806             :            "  <Value>AUTO</Value>"
    1807             :            "  <Value>YES</Value>"
    1808             :            "  <Value>NO</Value>"
    1809             :            "</Option>"
    1810             :            "<Option name='SRC_METHOD' alias='METHOD' type='string-select' "
    1811             :            "description='"
    1812             :            "Force only one geolocation method to be considered on the source "
    1813             :            "dataset. Will be used for pixel/line to georef transformation on "
    1814             :            "the source dataset. NO_GEOTRANSFORM can be used to specify the "
    1815             :            "identity geotransform (ungeoreferenced image)'>"
    1816             :            "  <Value>GEOTRANSFORM</Value>"
    1817             :            "  <Value>GCP_POLYNOMIAL</Value>"
    1818             :            "  <Value>GCP_TPS</Value>"
    1819             :            "  <Value>GCP_HOMOGRAPHY</Value>"
    1820             :            "  <Value>GEOLOC_ARRAY</Value>"
    1821             :            "  <Value>RPC</Value>"
    1822             :            "  <Value>NO_GEOTRANSFORM</Value>"
    1823             :            "</Option>"
    1824             :            "<Option name='DST_METHOD' type='string-select' description='"
    1825             :            "Force only one geolocation method to be considered on the target "
    1826             :            "dataset. Will be used for pixel/line to georef transformation on "
    1827             :            "the targe dataset. NO_GEOTRANSFORM can be used to specify the "
    1828             :            "identity geotransform (ungeoreferenced image)'>"
    1829             :            "  <Value>GEOTRANSFORM</Value>"
    1830             :            "  <Value>GCP_POLYNOMIAL</Value>"
    1831             :            "  <Value>GCP_TPS</Value>"
    1832             :            "  <Value>GCP_HOMOGRAPHY</Value>"
    1833             :            "  <Value>GEOLOC_ARRAY</Value>"
    1834             :            "  <Value>RPC</Value>"
    1835             :            "  <Value>NO_GEOTRANSFORM</Value>"
    1836             :            "</Option>"
    1837             :            "<Option name='RPC_HEIGHT' type='float' description='"
    1838             :            "A fixed height to be used with RPC calculations. If RPC_HEIGHT and "
    1839             :            "RPC_DEM are not specified but that the RPC metadata domain contains"
    1840             :            " a HEIGHT_DEFAULT item (for example, the DIMAP driver may fill it),"
    1841             :            "this value will be used as the RPC_HEIGHT. Otherwise, if none of "
    1842             :            "RPC_HEIGHT and RPC_DEM are specified as transformer options and "
    1843             :            "if HEIGHT_DEFAULT is no available, a height of 0 will be used.'/>"
    1844             :            "<Option name='RPC_DEM' type='string' description='"
    1845             :            "Name of a GDAL dataset (a DEM file typically) used to extract "
    1846             :            "elevation offsets from. In this situation the Z passed into the "
    1847             :            "transformation function is assumed to be height above ground. "
    1848             :            "This option should be used in replacement of RPC_HEIGHT to provide "
    1849             :            "a way of defining a non uniform ground for the target scene.'/>"
    1850             :            "<Option name='RPC_HEIGHT_SCALE' type='float' description='"
    1851             :            "Factor used to multiply heights above ground. Useful when "
    1852             :            "elevation offsets of the DEM are not expressed in meters.'/>"
    1853             :            "<Option name='RPC_DEMINTERPOLATION' type='string-select' "
    1854             :            "description='DEM interpolation method' default='BILINEAR'>"
    1855             :            "  <Value>NEAR</Value>"
    1856             :            "  <Value>BILINEAR</Value>"
    1857             :            "  <Value>CUBIC</Value>"
    1858             :            "</Option>"
    1859             :            "<Option name='RPC_DEM_MISSING_VALUE' type='float' description='"
    1860             :            "Value of DEM height that must be used in case the DEM has nodata "
    1861             :            "value at the sampling point, or if its extent does not cover the "
    1862             :            "requested coordinate. When not specified, missing values will "
    1863             :            "cause a failed transform.'/>"
    1864             :            "<Option name='RPC_DEM_SRS' type='string' description='"
    1865             :            "WKT SRS, or any string recognized by "
    1866             :            "OGRSpatialReference::SetFromUserInput(), to be used as an "
    1867             :            "override for DEM SRS. Useful if DEM SRS does not have an explicit "
    1868             :            "vertical component.'/>"
    1869             :            "<Option name='RPC_DEM_APPLY_VDATUM_SHIFT' type='boolean' "
    1870             :            "description='"
    1871             :            "Whether the vertical component of a compound SRS for the DEM "
    1872             :            "should be used (when it is present). This is useful so as to "
    1873             :            "be able to transform the raw values from the DEM expressed with "
    1874             :            "respect to a geoid to the heights with respect to the WGS84 "
    1875             :            "ellipsoid. When this is enabled, the GTIFF_REPORT_COMPD_CS "
    1876             :            "configuration option will be also set temporarily so as to get "
    1877             :            "the vertical information from GeoTIFF files.' default='YES'/>"
    1878             :            "<Option name='RPC_PIXEL_ERROR_THRESHOLD' type='float' description='"
    1879             :            "Overrides the dfPixErrThreshold parameter, i.e. the error "
    1880             :            "(measured in pixels) allowed in the iterative solution of "
    1881             :            "pixel/line to lat/long computations (the other way is always "
    1882             :            "exact given the equations).'/>"
    1883             :            "<Option name='RPC_MAX_ITERATIONS' type='int' description='"
    1884             :            "Maximum number of iterations allowed in the iterative solution of "
    1885             :            "pixel/line to lat/long computations. Default value is 10 in the "
    1886             :            "absence of a DEM, or 20 if there is a DEM.'/>"
    1887             :            "<Option name='RPC_FOOTPRINT' type='string' description='"
    1888             :            "WKT or GeoJSON polygon (in long / lat coordinate space) with a "
    1889             :            "validity footprint for the RPC. Any coordinate transformation that "
    1890             :            "goes from or arrive outside this footprint will be considered "
    1891             :            "invalid. This* is useful in situations where the RPC values become "
    1892             :            "highly unstable outside of the area on which they have been "
    1893             :            "computed for, potentially leading to undesirable \"echoes\" / "
    1894             :            "false positives. This requires GDAL to be built against GEOS..'/>"
    1895             :            "<Option name='RPC_MAX_ITERATIONS' type='int' description='"
    1896             :            "Maximum number of iterations allowed in the iterative solution of "
    1897             :            "pixel/line to lat/long computations. Default value is 10 in the "
    1898             :            "absence of a DEM, or 20 if there is a DEM.'/>"
    1899             :            "<Option name='INSERT_CENTER_LONG' type='boolean' description='"
    1900             :            "May be set to FALSE to disable setting up a CENTER_LONG value on "
    1901             :            "the coordinate system to rewrap things around the center of the "
    1902             :            "image.' default='YES'/>"
    1903             :            "<Option name='SRC_APPROX_ERROR_IN_SRS_UNIT' type='float' "
    1904             :            "description='"
    1905             :            "Use an approximate transformer for the source transformer. Must be "
    1906             :            "defined together with SRC_APPROX_ERROR_IN_PIXEL to be taken into "
    1907             :            "account.'/>"
    1908             :            "<Option name='SRC_APPROX_ERROR_IN_PIXEL' type='float' "
    1909             :            "description='"
    1910             :            "Use an approximate transformer for the source transformer. Must be "
    1911             :            "defined together with SRC_APPROX_ERROR_IN_SRS_UNIT to be taken "
    1912             :            "into "
    1913             :            "account.'/>"
    1914             :            "<Option name='DST_APPROX_ERROR_IN_SRS_UNIT' type='float' "
    1915             :            "description='"
    1916             :            "Use an approximate transformer for the target transformer. Must be "
    1917             :            "defined together with DST_APPROX_ERROR_IN_PIXEL to be taken into "
    1918             :            "account.'/>"
    1919             :            "<Option name='DST_APPROX_ERROR_IN_PIXEL' type='float' "
    1920             :            "description='"
    1921             :            "Use an approximate transformer for the target transformer. Must be "
    1922             :            "defined together with DST_APPROX_ERROR_IN_SRS_UNIT to be taken "
    1923             :            "into "
    1924             :            "account.'/>"
    1925             :            "<Option name='REPROJECTION_APPROX_ERROR_IN_SRC_SRS_UNIT' "
    1926             :            "type='float' "
    1927             :            "description='"
    1928             :            "Use an approximate transformer for the coordinate reprojection. "
    1929             :            "Must be used together with "
    1930             :            "REPROJECTION_APPROX_ERROR_IN_DST_SRS_UNIT to be taken into "
    1931             :            "account.'/>"
    1932             :            "<Option name='REPROJECTION_APPROX_ERROR_IN_DST_SRS_UNIT' "
    1933             :            "type='float' "
    1934             :            "description='"
    1935             :            "Use an approximate transformer for the coordinate reprojection. "
    1936             :            "Must be used together with "
    1937             :            "REPROJECTION_APPROX_ERROR_IN_SRC_SRS_UNIT to be taken into "
    1938             :            "account.'/>"
    1939             :            "<Option name='AREA_OF_INTEREST' type='string' "
    1940             :            "description='"
    1941             :            "Area of interest, as "
    1942             :            "west_lon_deg,south_lat_deg,east_lon_deg,north_lat_deg, used to "
    1943             :            "compute the best coordinate operation between the source and "
    1944             :            "target SRS. If not specified, the bounding box of the source "
    1945             :            "raster will be used.'/>"
    1946             :            "<Option name='TARGET_EXTENT' type='string' "
    1947             :            "description='Target extent as minx,miny,maxx,maxy expressed in "
    1948             :            "target SRS.'/>"
    1949             :            "<Option name='GEOLOC_BACKMAP_OVERSAMPLE_FACTOR' type='float' "
    1950             :            "min='0.1' max='2' description='"
    1951             :            "Oversample factor used to derive the size of the \"backmap\" used "
    1952             :            "for geolocation array transformers.' default='1.3'/>"
    1953             :            "<Option name='GEOLOC_USE_TEMP_DATASETS' type='boolean' "
    1954             :            "description='"
    1955             :            "Whether temporary GeoTIFF datasets should be used to store the "
    1956             :            "backmap. The default is NO, that is to use in-memory arrays, "
    1957             :            "unless the number of pixels of the geolocation array is greater "
    1958             :            "than 16 megapixels.' default='NO'/>"
    1959             :            "<Option name='GEOLOC_ARRAY' alias='SRC_GEOLOC_ARRAY' type='string' "
    1960             :            "description='"
    1961             :            "Name of a GDAL dataset containing a geolocation array and "
    1962             :            "associated metadata. This is an alternative to having geolocation "
    1963             :            "information described in the GEOLOCATION metadata domain of the "
    1964             :            "source dataset. The dataset specified may have a GEOLOCATION "
    1965             :            "metadata domain containing appropriate metadata, however default "
    1966             :            "values are assigned for all omitted items. X_BAND defaults to 1 "
    1967             :            "and Y_BAND to 2, however the dataset must contain exactly 2 bands. "
    1968             :            "PIXEL_OFFSET and LINE_OFFSET default to 0. PIXEL_STEP and "
    1969             :            "LINE_STEP default to the ratio of the width/height of the source "
    1970             :            "dataset divided by the with/height of the geolocation array. "
    1971             :            "SRS defaults to the spatial reference system of the geolocation "
    1972             :            "array dataset, if set, otherwise WGS84 is used. "
    1973             :            "GEOREFERENCING_CONVENTION is selected from the main metadata "
    1974             :            "domain if it is omitted from the GEOLOCATION domain, and if not "
    1975             :            "available TOP_LEFT_CORNER is assigned as a default. "
    1976             :            "If GEOLOC_ARRAY is set SRC_METHOD defaults to GEOLOC_ARRAY.'/>"
    1977             :            "<Option name='DST_GEOLOC_ARRAY' type='string' "
    1978             :            "description='"
    1979             :            "Name of a GDAL dataset that contains at least 2 bands with the X "
    1980             :            "and Y geolocation bands. This is an alternative to having "
    1981             :            "geolocation information described in the GEOLOCATION metadata "
    1982             :            "domain of the destination dataset. See SRC_GEOLOC_ARRAY "
    1983             :            "description for details, assumptions, and defaults. If this "
    1984             :            "option is set, DST_METHOD=GEOLOC_ARRAY will be assumed if not "
    1985             :            "set.'/>"
    1986             :            "<Option name='GEOLOC_NORMALIZE_LONGITUDE_MINUS_180_PLUS_180' "
    1987             :            "type='boolean' "
    1988             :            "description='"
    1989             :            "Force geolocation longitudes into -180,180 when longitude/latitude "
    1990             :            "is the coordinate system of the geolocation arrays' default='NO'>"
    1991             :            "  <Value>YES</Value>"
    1992             :            "  <Value>NO</Value>"
    1993             :            "</Option>"
    1994             :            "<Option name='NUM_THREADS' type='string' "
    1995             :            "description='Number of threads to use'/>"
    1996        2346 :            "</OptionList>";
    1997             : }
    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        2345 : void *GDALCreateGenImgProjTransformer2(GDALDatasetH hSrcDS, GDALDatasetH hDstDS,
    2237             :                                        CSLConstList papszOptions)
    2238             : 
    2239             : {
    2240        2345 :     GDALValidateOptions(nullptr, GDALGetGenImgProjTranformerOptionList(),
    2241             :                         papszOptions, "option", "transformer options");
    2242             : 
    2243        2345 :     double dfWestLongitudeDeg = 0.0;
    2244        2345 :     double dfSouthLatitudeDeg = 0.0;
    2245        2345 :     double dfEastLongitudeDeg = 0.0;
    2246        2345 :     double dfNorthLatitudeDeg = 0.0;
    2247        2345 :     bool bHasAreaOfInterest = false;
    2248        2345 :     if (const char *pszAreaOfInterest =
    2249        2345 :             CSLFetchNameValue(papszOptions, "AREA_OF_INTEREST"))
    2250             :     {
    2251             :         const CPLStringList aosTokens(
    2252           0 :             CSLTokenizeString2(pszAreaOfInterest, ", ", 0));
    2253           0 :         if (aosTokens.size() == 4)
    2254             :         {
    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             :         }
    2261             :     }
    2262             : 
    2263        2345 :     const char *pszCO = CSLFetchNameValue(papszOptions, "COORDINATE_OPERATION");
    2264             : 
    2265             :     const auto SetAxisMapping =
    2266        4633 :         [papszOptions](OGRSpatialReference &oSRS, const char *pszPrefix)
    2267             :     {
    2268        1545 :         const char *pszMapping = CSLFetchNameValue(
    2269        3090 :             papszOptions, std::string(pszPrefix)
    2270        1545 :                               .append("_DATA_AXIS_TO_SRS_AXIS_MAPPING")
    2271             :                               .c_str());
    2272        1545 :         if (pszMapping)
    2273             :         {
    2274           4 :             CPLStringList aosTokens(CSLTokenizeString2(pszMapping, ",", 0));
    2275           4 :             std::vector<int> anMapping;
    2276           6 :             for (int i = 0; i < aosTokens.size(); ++i)
    2277           4 :                 anMapping.push_back(atoi(aosTokens[i]));
    2278           2 :             oSRS.SetDataAxisToSRSAxisMapping(anMapping);
    2279             :         }
    2280             :         else
    2281             :         {
    2282        1543 :             const char *pszStrategy = CSLFetchNameValueDef(
    2283             :                 papszOptions,
    2284        3086 :                 std::string(pszPrefix).append("_AXIS_MAPPING_STRATEGY").c_str(),
    2285             :                 "TRADITIONAL_GIS_ORDER");
    2286        1543 :             if (EQUAL(pszStrategy, "TRADITIONAL_GIS_ORDER"))
    2287        1542 :                 oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    2288           1 :             else if (EQUAL(pszStrategy, "AUTHORITY_COMPLIANT"))
    2289           1 :                 oSRS.SetAxisMappingStrategy(OAMS_AUTHORITY_COMPLIANT);
    2290             :             else
    2291             :             {
    2292           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    2293             :                          "Unrecognized value '%s' for %s", pszStrategy,
    2294           0 :                          std::string(pszPrefix)
    2295           0 :                              .append("_AXIS_MAPPING_STRATEGY")
    2296             :                              .c_str());
    2297           0 :                 return false;
    2298             :             }
    2299             :         }
    2300        1545 :         return true;
    2301        2345 :     };
    2302             : 
    2303             :     /* -------------------------------------------------------------------- */
    2304             :     /*      Initialize the transform info.                                  */
    2305             :     /* -------------------------------------------------------------------- */
    2306             :     GDALGenImgProjTransformInfo *psInfo =
    2307        2345 :         GDALCreateGenImgProjTransformerInternal();
    2308             : 
    2309             :     const auto DealWithForwardOrInverse =
    2310        4678 :         [bHasAreaOfInterest, &dfWestLongitudeDeg, &dfSouthLatitudeDeg,
    2311             :          &dfEastLongitudeDeg, &dfNorthLatitudeDeg, pszCO, papszOptions,
    2312             :          &SetAxisMapping](GDALGenImgProjTransformPart &part, GDALDatasetH hDS,
    2313             :                           const char *pszPrefix, OGRSpatialReference &oSRS,
    2314       44692 :                           bool &bCanUseGeoTransform)
    2315             :     {
    2316             :         const int nOrder =
    2317        4678 :             atoi(CSLFetchNameValueDef(papszOptions, "MAX_GCP_ORDER", "0"));
    2318             : 
    2319             :         const bool bGCPUseOK =
    2320        4678 :             CPLTestBool(CSLFetchNameValueDef(papszOptions, "GCPS_OK", "YES"));
    2321        4678 :         const int nMinimumGcps = atoi(
    2322             :             CSLFetchNameValueDef(papszOptions, "REFINE_MINIMUM_GCPS", "-1"));
    2323             : 
    2324             :         const char *pszRefineTolerance =
    2325        4678 :             CSLFetchNameValue(papszOptions, "REFINE_TOLERANCE");
    2326        4678 :         const bool bRefine = pszRefineTolerance != nullptr;
    2327             :         const double dfTolerance =
    2328        4678 :             pszRefineTolerance ? CPLAtof(pszRefineTolerance) : 0.0;
    2329             : 
    2330             :         const std::string osSRSOptionName =
    2331       14034 :             std::string(pszPrefix).append("_SRS");
    2332             :         const char *pszSRS =
    2333        4678 :             CSLFetchNameValue(papszOptions, osSRSOptionName.c_str());
    2334        4678 :         if (pszSRS)
    2335             :         {
    2336        3086 :             if (pszSRS[0] != '\0' &&
    2337        1541 :                 oSRS.SetFromUserInput(pszSRS) != OGRERR_NONE)
    2338             :             {
    2339           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    2340             :                          "Failed to import coordinate system `%s'.", pszSRS);
    2341           0 :                 return false;
    2342             :             }
    2343        1545 :             if (!SetAxisMapping(oSRS, osSRSOptionName.c_str()))
    2344           0 :                 return false;
    2345             :         }
    2346             : 
    2347        4678 :         CSLConstList papszMD = nullptr;
    2348             :         GDALRPCInfoV2 sRPCInfo;
    2349             : 
    2350        4678 :         bCanUseGeoTransform = false;
    2351             : 
    2352        4678 :         const char *pszMethod = CSLFetchNameValue(
    2353        9356 :             papszOptions, std::string(pszPrefix).append("_METHOD").c_str());
    2354        4678 :         if (!pszMethod && EQUAL(pszPrefix, "SRC"))
    2355        2305 :             pszMethod = CSLFetchNameValue(papszOptions, "METHOD");
    2356             : 
    2357        4678 :         const char *pszGeolocArray = CSLFetchNameValue(
    2358             :             papszOptions,
    2359        9356 :             std::string(pszPrefix).append("_GEOLOC_ARRAY").c_str());
    2360        4678 :         if (!pszGeolocArray && EQUAL(pszPrefix, "SRC"))
    2361        2344 :             pszGeolocArray = CSLFetchNameValue(papszOptions, "GEOLOC_ARRAY");
    2362        4678 :         if (!pszMethod && pszGeolocArray != nullptr)
    2363           9 :             pszMethod = "GEOLOC_ARRAY";
    2364             : 
    2365             :         /* -------------------------------------------------------------------- */
    2366             :         /*      Get forward and inverse geotransform for the source image.      */
    2367             :         /* -------------------------------------------------------------------- */
    2368        4678 :         if (hDS == nullptr ||
    2369         101 :             (pszMethod != nullptr && EQUAL(pszMethod, "NO_GEOTRANSFORM")))
    2370             :         {
    2371        1911 :             part.adfGeoTransform[0] = 0.0;
    2372        1911 :             part.adfGeoTransform[1] = 1.0;
    2373        1911 :             part.adfGeoTransform[2] = 0.0;
    2374        1911 :             part.adfGeoTransform[3] = 0.0;
    2375        1911 :             part.adfGeoTransform[4] = 0.0;
    2376        1911 :             part.adfGeoTransform[5] = 1.0;
    2377        1911 :             memcpy(part.adfInvGeoTransform, part.adfGeoTransform,
    2378             :                    sizeof(double) * 6);
    2379             :         }
    2380        5463 :         else if ((pszMethod == nullptr || EQUAL(pszMethod, "GEOTRANSFORM")) &&
    2381        2696 :                  GDALGetGeoTransform(hDS, part.adfGeoTransform) == CE_None)
    2382             :         {
    2383        2615 :             if (!GDALInvGeoTransform(part.adfGeoTransform,
    2384        2615 :                                      part.adfInvGeoTransform))
    2385             :             {
    2386           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    2387             :                          "Cannot invert geotransform");
    2388           0 :                 return false;
    2389             :             }
    2390        2615 :             if (pszSRS == nullptr)
    2391             :             {
    2392        2329 :                 auto hSRS = GDALGetSpatialRef(hDS);
    2393        2329 :                 if (hSRS)
    2394        1706 :                     oSRS = *(OGRSpatialReference::FromHandle(hSRS));
    2395             :             }
    2396        2615 :             if (EQUAL(pszPrefix, "SRC"))
    2397             :             {
    2398        1849 :                 if (!bHasAreaOfInterest && pszCO == nullptr && !oSRS.IsEmpty())
    2399             :                 {
    2400        1537 :                     GDALComputeAreaOfInterest(
    2401        1537 :                         &oSRS, part.adfGeoTransform, GDALGetRasterXSize(hDS),
    2402             :                         GDALGetRasterYSize(hDS), dfWestLongitudeDeg,
    2403             :                         dfSouthLatitudeDeg, dfEastLongitudeDeg,
    2404             :                         dfNorthLatitudeDeg);
    2405             :                 }
    2406        1849 :                 bCanUseGeoTransform = true;
    2407             :             }
    2408             :         }
    2409         152 :         else if (bGCPUseOK &&
    2410          81 :                  ((pszMethod == nullptr && GDALGetGCPCount(hDS) >= 4 &&
    2411         152 :                    GDALGetGCPCount(hDS) < 6) ||
    2412          71 :                   (pszMethod != nullptr &&
    2413         305 :                    EQUAL(pszMethod, "GCP_HOMOGRAPHY"))) &&
    2414          24 :                  GDALGetGCPCount(hDS) > 0)
    2415             :         {
    2416          24 :             if (!pszMethod)
    2417          23 :                 CPLDebug("WARP", "Using GCP_HOMOGRAPHY transformer due to GCP "
    2418             :                                  "count in [4, 5]");
    2419          24 :             if (pszSRS == nullptr)
    2420             :             {
    2421          24 :                 auto hSRS = GDALGetGCPSpatialRef(hDS);
    2422          24 :                 if (hSRS)
    2423          21 :                     oSRS = *(OGRSpatialReference::FromHandle(hSRS));
    2424             :             }
    2425             : 
    2426          24 :             const auto nGCPCount = GDALGetGCPCount(hDS);
    2427          24 :             auto pasGCPList = GDALDuplicateGCPs(nGCPCount, GDALGetGCPs(hDS));
    2428          24 :             GDALGCPAntimeridianUnwrap(nGCPCount, pasGCPList, oSRS,
    2429             :                                       papszOptions);
    2430             : 
    2431          24 :             part.pTransformArg =
    2432          24 :                 GDALCreateHomographyTransformerFromGCPs(nGCPCount, pasGCPList);
    2433             : 
    2434          24 :             GDALDeinitGCPs(nGCPCount, pasGCPList);
    2435          24 :             CPLFree(pasGCPList);
    2436             : 
    2437          24 :             if (part.pTransformArg == nullptr)
    2438             :             {
    2439           0 :                 return false;
    2440             :             }
    2441          24 :             part.pTransformer = GDALHomographyTransform;
    2442             :         }
    2443         128 :         else if (bGCPUseOK &&
    2444         128 :                  ((pszMethod == nullptr && nOrder >= 0) ||
    2445         260 :                   (pszMethod && EQUAL(pszMethod, "GCP_POLYNOMIAL"))) &&
    2446          62 :                  GDALGetGCPCount(hDS) > 0)
    2447             :         {
    2448           9 :             const auto nGCPCount = GDALGetGCPCount(hDS);
    2449           9 :             const int nSanitizedOrder = std::max(0, nOrder);
    2450           9 :             if (!pszMethod && nSanitizedOrder == 0)
    2451             :             {
    2452           5 :                 if (nGCPCount <= 6)
    2453           1 :                     CPLDebug("WARP", "Using GCP_POLYNOMIAL transformer with "
    2454             :                                      "order = 1 due to GCP count <= 6");
    2455             :                 else
    2456           4 :                     CPLDebug("WARP", "Using GCP_POLYNOMIAL transformer with "
    2457             :                                      "order = 2 due to GCP count > 6");
    2458             :             }
    2459             : 
    2460           9 :             if (pszSRS == nullptr)
    2461             :             {
    2462           9 :                 auto hSRS = GDALGetGCPSpatialRef(hDS);
    2463           9 :                 if (hSRS)
    2464           9 :                     oSRS = *(OGRSpatialReference::FromHandle(hSRS));
    2465             :             }
    2466             : 
    2467           9 :             auto pasGCPList = GDALDuplicateGCPs(nGCPCount, GDALGetGCPs(hDS));
    2468           9 :             GDALGCPAntimeridianUnwrap(nGCPCount, pasGCPList, oSRS,
    2469             :                                       papszOptions);
    2470             : 
    2471           9 :             if (bRefine)
    2472             :             {
    2473           0 :                 part.pTransformArg = GDALCreateGCPRefineTransformer(
    2474             :                     nGCPCount, pasGCPList, nSanitizedOrder, FALSE, dfTolerance,
    2475             :                     nMinimumGcps);
    2476             :             }
    2477             :             else
    2478             :             {
    2479           9 :                 part.pTransformArg = GDALCreateGCPTransformer(
    2480             :                     nGCPCount, pasGCPList, nSanitizedOrder, FALSE);
    2481             :             }
    2482             : 
    2483           9 :             GDALDeinitGCPs(nGCPCount, pasGCPList);
    2484           9 :             CPLFree(pasGCPList);
    2485             : 
    2486           9 :             if (part.pTransformArg == nullptr)
    2487             :             {
    2488           0 :                 return false;
    2489             :             }
    2490           9 :             part.pTransformer = GDALGCPTransform;
    2491             :         }
    2492             : 
    2493         130 :         else if (bGCPUseOK && GDALGetGCPCount(hDS) > 0 &&
    2494          11 :                  ((pszMethod == nullptr && nOrder < 0) ||
    2495          11 :                   (pszMethod && EQUAL(pszMethod, "GCP_TPS"))))
    2496             :         {
    2497          11 :             if (pszSRS == nullptr)
    2498             :             {
    2499          11 :                 auto hSRS = GDALGetGCPSpatialRef(hDS);
    2500          11 :                 if (hSRS)
    2501          10 :                     oSRS = *(OGRSpatialReference::FromHandle(hSRS));
    2502             :             }
    2503             : 
    2504          11 :             const auto nGCPCount = GDALGetGCPCount(hDS);
    2505          11 :             auto pasGCPList = GDALDuplicateGCPs(nGCPCount, GDALGetGCPs(hDS));
    2506          11 :             GDALGCPAntimeridianUnwrap(nGCPCount, pasGCPList, oSRS,
    2507             :                                       papszOptions);
    2508             : 
    2509          11 :             part.pTransformArg = GDALCreateTPSTransformerInt(
    2510             :                 nGCPCount, pasGCPList, FALSE, papszOptions);
    2511             : 
    2512          11 :             GDALDeinitGCPs(nGCPCount, pasGCPList);
    2513          11 :             CPLFree(pasGCPList);
    2514             : 
    2515          11 :             if (part.pTransformArg == nullptr)
    2516             :             {
    2517           2 :                 return false;
    2518             :             }
    2519           9 :             part.pTransformer = GDALTPSTransform;
    2520             :         }
    2521             : 
    2522          55 :         else if ((pszMethod == nullptr || EQUAL(pszMethod, GDAL_MDD_RPC)) &&
    2523         209 :                  (papszMD = GDALGetMetadata(hDS, GDAL_MDD_RPC)) != nullptr &&
    2524          46 :                  GDALExtractRPCInfoV2(papszMD, &sRPCInfo))
    2525             :         {
    2526          46 :             if (!pszMethod)
    2527          16 :                 CPLDebug("WARP", "Using RPC transformer");
    2528             : 
    2529          46 :             CPLStringList aosOptions(papszOptions);
    2530          87 :             if (!CSLFetchNameValue(papszOptions, "RPC_HEIGHT") &&
    2531          41 :                 !CSLFetchNameValue(papszOptions, "RPC_DEM"))
    2532             :             {
    2533           8 :                 if (const char *pszHEIGHT_DEFAULT =
    2534           8 :                         CSLFetchNameValue(papszMD, "HEIGHT_DEFAULT"))
    2535             :                 {
    2536           1 :                     CPLDebug("GDAL",
    2537             :                              "For %s, using RPC_HEIGHT = HEIGHT_DEFAULT = %s",
    2538             :                              pszPrefix, pszHEIGHT_DEFAULT);
    2539           1 :                     aosOptions.SetNameValue("RPC_HEIGHT", pszHEIGHT_DEFAULT);
    2540             :                 }
    2541             :             }
    2542          46 :             part.pTransformArg = GDALCreateRPCTransformerV2(&sRPCInfo, FALSE, 0,
    2543          46 :                                                             aosOptions.List());
    2544          46 :             if (part.pTransformArg == nullptr)
    2545             :             {
    2546           1 :                 return false;
    2547             :             }
    2548          45 :             part.pTransformer = GDALRPCTransform;
    2549          45 :             if (pszSRS == nullptr)
    2550             :             {
    2551          45 :                 oSRS.SetFromUserInput(SRS_WKT_WGS84_LAT_LONG);
    2552          45 :                 oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    2553             :             }
    2554             :         }
    2555             : 
    2556         123 :         else if ((pszMethod == nullptr || EQUAL(pszMethod, "GEOLOC_ARRAY")) &&
    2557          61 :                  ((papszMD = GDALGetMetadata(hDS, GDAL_MDD_GEOLOCATION)) !=
    2558          15 :                       nullptr ||
    2559             :                   pszGeolocArray != nullptr))
    2560             :         {
    2561          56 :             if (!pszMethod)
    2562          32 :                 CPLDebug("WARP", "Using geolocation array transformer");
    2563             : 
    2564          56 :             CPLStringList aosGeolocMD;  // keep in this scope
    2565          56 :             if (pszGeolocArray != nullptr)
    2566             :             {
    2567          10 :                 if (papszMD != nullptr)
    2568             :                 {
    2569           0 :                     CPLError(
    2570             :                         CE_Warning, CPLE_AppDefined,
    2571             :                         "Both GEOLOCATION metadata domain on the source "
    2572             :                         "dataset "
    2573             :                         "and [%s_]GEOLOC_ARRAY transformer option are set. "
    2574             :                         "Only using the later.",
    2575             :                         pszPrefix);
    2576             :                 }
    2577          10 :                 aosGeolocMD = GDALCreateGeolocationMetadata(
    2578             :                     hDS, pszGeolocArray,
    2579          10 :                     /* bIsSource= */ EQUAL(pszPrefix, "SRC"));
    2580          10 :                 if (aosGeolocMD.empty())
    2581             :                 {
    2582           3 :                     return false;
    2583             :                 }
    2584           7 :                 papszMD = aosGeolocMD.List();
    2585             :             }
    2586             : 
    2587          53 :             part.pTransformArg = GDALCreateGeoLocTransformerEx(
    2588             :                 hDS, papszMD, FALSE, nullptr, papszOptions);
    2589          53 :             if (part.pTransformArg == nullptr)
    2590             :             {
    2591           2 :                 return false;
    2592             :             }
    2593          51 :             part.pTransformer = GDALGeoLocTransform;
    2594          51 :             if (pszSRS == nullptr)
    2595             :             {
    2596          51 :                 pszSRS = CSLFetchNameValue(papszMD, "SRS");
    2597          51 :                 if (pszSRS)
    2598             :                 {
    2599          48 :                     oSRS.SetFromUserInput(pszSRS);
    2600          48 :                     oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    2601             :                 }
    2602             :             }
    2603             :         }
    2604             : 
    2605           6 :         else if (pszMethod != nullptr && EQUAL(pszPrefix, "SRC"))
    2606             :         {
    2607           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    2608             :                      "Unable to compute a %s based transformation between "
    2609             :                      "pixel/line and georeferenced coordinates for %s.",
    2610             :                      pszMethod, GDALGetDescription(hDS));
    2611             : 
    2612           1 :             return false;
    2613             :         }
    2614             : 
    2615             :         else
    2616             :         {
    2617           5 :             CPLError(CE_Failure, CPLE_AppDefined,
    2618             :                      "Unable to compute a transformation between pixel/line "
    2619             :                      "and georeferenced coordinates for %s. "
    2620             :                      "There is no affine transformation and no GCPs. "
    2621             :                      "Specify transformation option %s_METHOD=NO_GEOTRANSFORM "
    2622             :                      "to bypass this check.",
    2623             :                      GDALGetDescription(hDS), pszPrefix);
    2624             : 
    2625           5 :             return false;
    2626             :         }
    2627             : 
    2628             :         /* ---------------------------------------------------------------- */
    2629             :         /*      Handle optional source approximation transformer.           */
    2630             :         /* ---------------------------------------------------------------- */
    2631        4664 :         if (part.pTransformer)
    2632             :         {
    2633         138 :             const char *pszApproxErrorFwd = CSLFetchNameValue(
    2634         276 :                 papszOptions, std::string(pszPrefix)
    2635         138 :                                   .append("_APPROX_ERROR_IN_SRS_UNIT")
    2636             :                                   .c_str());
    2637         138 :             const char *pszApproxErrorReverse = CSLFetchNameValue(
    2638         276 :                 papszOptions, std::string(pszPrefix)
    2639         138 :                                   .append("_APPROX_ERROR_IN_PIXEL")
    2640             :                                   .c_str());
    2641         138 :             if (pszApproxErrorFwd && pszApproxErrorReverse)
    2642             :             {
    2643           1 :                 void *pArg = GDALCreateApproxTransformer2(
    2644             :                     part.pTransformer, part.pTransformArg,
    2645             :                     CPLAtof(pszApproxErrorFwd), CPLAtof(pszApproxErrorReverse));
    2646           1 :                 if (pArg == nullptr)
    2647             :                 {
    2648           0 :                     return false;
    2649             :                 }
    2650           1 :                 part.pTransformArg = pArg;
    2651           1 :                 part.pTransformer = GDALApproxTransform;
    2652           1 :                 GDALApproxTransformerOwnsSubtransformer(part.pTransformArg,
    2653             :                                                         TRUE);
    2654             :             }
    2655             :         }
    2656             : 
    2657        4664 :         return true;
    2658        2345 :     };
    2659             : 
    2660             :     /* -------------------------------------------------------------------- */
    2661             :     /*      Get forward and inverse geotransform for the source image.      */
    2662             :     /* -------------------------------------------------------------------- */
    2663        2345 :     bool bCanUseSrcGeoTransform = false;
    2664        4690 :     OGRSpatialReference oSrcSRS;
    2665        2345 :     if (!DealWithForwardOrInverse(psInfo->sSrcParams, hSrcDS, "SRC", oSrcSRS,
    2666             :                                   bCanUseSrcGeoTransform))
    2667             :     {
    2668          12 :         GDALDestroyGenImgProjTransformer(psInfo);
    2669          12 :         return nullptr;
    2670             :     }
    2671             : 
    2672             :     /* -------------------------------------------------------------------- */
    2673             :     /*      Get forward and inverse geotransform for destination image.     */
    2674             :     /*      If we have no destination use a unit transform.                 */
    2675             :     /* -------------------------------------------------------------------- */
    2676        2333 :     bool bIgnored = false;
    2677        4666 :     OGRSpatialReference oDstSRS;
    2678        2333 :     if (!DealWithForwardOrInverse(psInfo->sDstParams, hDstDS, "DST", oDstSRS,
    2679             :                                   bIgnored))
    2680             :     {
    2681           2 :         GDALDestroyGenImgProjTransformer(psInfo);
    2682           2 :         return nullptr;
    2683             :     }
    2684             : 
    2685             :     /* -------------------------------------------------------------------- */
    2686             :     /*      Setup reprojection.                                             */
    2687             :     /* -------------------------------------------------------------------- */
    2688             : 
    2689        2331 :     if (CPLFetchBool(papszOptions, "@STRIP_VERT_CS", false))
    2690             :     {
    2691           1 :         if (oSrcSRS.IsCompound())
    2692             :         {
    2693           1 :             oSrcSRS.StripVertical();
    2694             :         }
    2695           1 :         if (oDstSRS.IsCompound())
    2696             :         {
    2697           0 :             oDstSRS.StripVertical();
    2698             :         }
    2699             :     }
    2700             : 
    2701             :     const bool bMayInsertCenterLong =
    2702        3870 :         (bCanUseSrcGeoTransform && !oSrcSRS.IsEmpty() && hSrcDS &&
    2703        1539 :          CPLFetchBool(papszOptions, "INSERT_CENTER_LONG", true));
    2704             :     const char *pszSrcCoordEpoch =
    2705        2331 :         CSLFetchNameValue(papszOptions, "SRC_COORDINATE_EPOCH");
    2706             :     const char *pszDstCoordEpoch =
    2707        2331 :         CSLFetchNameValue(papszOptions, "DST_COORDINATE_EPOCH");
    2708        4038 :     if ((!oSrcSRS.IsEmpty() && !oDstSRS.IsEmpty() &&
    2709        1587 :          (pszSrcCoordEpoch || pszDstCoordEpoch || !oSrcSRS.IsSame(&oDstSRS) ||
    2710        4038 :           (oSrcSRS.IsGeographic() && bMayInsertCenterLong))) ||
    2711             :         pszCO)
    2712             :     {
    2713        1131 :         CPLStringList aosOptions;
    2714             : 
    2715        1131 :         if (bMayInsertCenterLong)
    2716             :         {
    2717        1112 :             InsertCenterLong(hSrcDS, &oSrcSRS, &oDstSRS,
    2718             :                              CSLFetchNameValue(papszOptions, "TARGET_EXTENT"),
    2719             :                              aosOptions);
    2720             :         }
    2721             : 
    2722        1131 :         if (CPLFetchBool(papszOptions, "PROMOTE_TO_3D", false))
    2723             :         {
    2724          20 :             oSrcSRS.PromoteTo3D(nullptr);
    2725          20 :             oDstSRS.PromoteTo3D(nullptr);
    2726             :         }
    2727             : 
    2728        1131 :         if (!(dfWestLongitudeDeg == 0.0 && dfSouthLatitudeDeg == 0.0 &&
    2729          40 :               dfEastLongitudeDeg == 0.0 && dfNorthLatitudeDeg == 0.0))
    2730             :         {
    2731             :             aosOptions.SetNameValue(
    2732             :                 "AREA_OF_INTEREST",
    2733             :                 CPLSPrintf("%.16g,%.16g,%.16g,%.16g", dfWestLongitudeDeg,
    2734             :                            dfSouthLatitudeDeg, dfEastLongitudeDeg,
    2735        1097 :                            dfNorthLatitudeDeg));
    2736             :         }
    2737        1131 :         if (pszCO)
    2738             :         {
    2739           7 :             aosOptions.SetNameValue("COORDINATE_OPERATION", pszCO);
    2740             :         }
    2741             : 
    2742             :         const char *pszCoordEpoch =
    2743        1131 :             CSLFetchNameValue(papszOptions, "COORDINATE_EPOCH");
    2744        1131 :         if (pszCoordEpoch)
    2745             :         {
    2746           1 :             aosOptions.SetNameValue("COORDINATE_EPOCH", pszCoordEpoch);
    2747             :         }
    2748             : 
    2749        1131 :         if (pszSrcCoordEpoch)
    2750             :         {
    2751           0 :             aosOptions.SetNameValue("SRC_COORDINATE_EPOCH", pszSrcCoordEpoch);
    2752           0 :             oSrcSRS.SetCoordinateEpoch(CPLAtof(pszSrcCoordEpoch));
    2753             :         }
    2754             : 
    2755        1131 :         if (pszDstCoordEpoch)
    2756             :         {
    2757           0 :             aosOptions.SetNameValue("DST_COORDINATE_EPOCH", pszDstCoordEpoch);
    2758           0 :             oDstSRS.SetCoordinateEpoch(CPLAtof(pszDstCoordEpoch));
    2759             :         }
    2760             : 
    2761        1131 :         if (const char *pszAllowBallpark =
    2762        1131 :                 CSLFetchNameValue(papszOptions, "ALLOW_BALLPARK"))
    2763             :         {
    2764           2 :             aosOptions.SetNameValue("ALLOW_BALLPARK", pszAllowBallpark);
    2765             :         }
    2766             : 
    2767        1131 :         if (const char *pszOnlyBest =
    2768        1131 :                 CSLFetchNameValue(papszOptions, "ONLY_BEST"))
    2769             :         {
    2770           1 :             aosOptions.SetNameValue("ONLY_BEST", pszOnlyBest);
    2771             :         }
    2772             : 
    2773        1135 :         psInfo->pReprojectArg = GDALCreateReprojectionTransformerEx(
    2774        1131 :             !oSrcSRS.IsEmpty() ? OGRSpatialReference::ToHandle(&oSrcSRS)
    2775             :                                : nullptr,
    2776        1131 :             !oDstSRS.IsEmpty() ? OGRSpatialReference::ToHandle(&oDstSRS)
    2777             :                                : nullptr,
    2778        1131 :             aosOptions.List());
    2779             : 
    2780        1131 :         if (pszCO)
    2781             :         {
    2782           7 :             psInfo->bHasCustomTransformationPipeline = true;
    2783             :         }
    2784             : 
    2785        1131 :         if (psInfo->pReprojectArg == nullptr)
    2786             :         {
    2787           5 :             GDALDestroyGenImgProjTransformer(psInfo);
    2788           5 :             return nullptr;
    2789             :         }
    2790        1126 :         psInfo->pReproject = GDALReprojectionTransform;
    2791             : 
    2792             :         /* --------------------------------------------------------------------
    2793             :          */
    2794             :         /*      Handle optional reprojection approximation transformer. */
    2795             :         /* --------------------------------------------------------------------
    2796             :          */
    2797        1126 :         const char *psApproxErrorFwd = CSLFetchNameValue(
    2798             :             papszOptions, "REPROJECTION_APPROX_ERROR_IN_DST_SRS_UNIT");
    2799        1126 :         const char *psApproxErrorReverse = CSLFetchNameValue(
    2800             :             papszOptions, "REPROJECTION_APPROX_ERROR_IN_SRC_SRS_UNIT");
    2801        1126 :         if (psApproxErrorFwd && psApproxErrorReverse)
    2802             :         {
    2803           1 :             void *pArg = GDALCreateApproxTransformer2(
    2804             :                 psInfo->pReproject, psInfo->pReprojectArg,
    2805             :                 CPLAtof(psApproxErrorFwd), CPLAtof(psApproxErrorReverse));
    2806           1 :             if (pArg == nullptr)
    2807             :             {
    2808           0 :                 GDALDestroyGenImgProjTransformer(psInfo);
    2809           0 :                 return nullptr;
    2810             :             }
    2811           1 :             psInfo->pReprojectArg = pArg;
    2812           1 :             psInfo->pReproject = GDALApproxTransform;
    2813           1 :             GDALApproxTransformerOwnsSubtransformer(psInfo->pReprojectArg,
    2814             :                                                     TRUE);
    2815             :         }
    2816             :     }
    2817             : 
    2818        2326 :     return psInfo;
    2819             : }
    2820             : 
    2821             : /************************************************************************/
    2822             : /*                  GDALRefreshGenImgProjTransformer()                  */
    2823             : /************************************************************************/
    2824             : 
    2825        1395 : void GDALRefreshGenImgProjTransformer(void *hTransformArg)
    2826             : {
    2827        1395 :     GDALGenImgProjTransformInfo *psInfo =
    2828             :         static_cast<GDALGenImgProjTransformInfo *>(hTransformArg);
    2829             : 
    2830        2276 :     if (psInfo->pReprojectArg &&
    2831         881 :         psInfo->bCheckWithInvertPROJ != GetCurrentCheckWithInvertPROJ())
    2832             :     {
    2833          72 :         psInfo->bCheckWithInvertPROJ = !psInfo->bCheckWithInvertPROJ;
    2834             : 
    2835             :         CPLXMLNode *psXML =
    2836          72 :             GDALSerializeTransformer(psInfo->pReproject, psInfo->pReprojectArg);
    2837          72 :         GDALDestroyTransformer(psInfo->pReprojectArg);
    2838          72 :         GDALDeserializeTransformer(psXML, &psInfo->pReproject,
    2839             :                                    &psInfo->pReprojectArg);
    2840          72 :         CPLDestroyXMLNode(psXML);
    2841             :     }
    2842        1395 : }
    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           0 : void *GDALCreateGenImgProjTransformer3(const char *pszSrcWKT,
    2886             :                                        const double *padfSrcGeoTransform,
    2887             :                                        const char *pszDstWKT,
    2888             :                                        const double *padfDstGeoTransform)
    2889             : 
    2890             : {
    2891           0 :     OGRSpatialReference oSrcSRS;
    2892           0 :     if (pszSrcWKT)
    2893             :     {
    2894           0 :         oSrcSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    2895           0 :         if (pszSrcWKT[0] != '\0' &&
    2896           0 :             oSrcSRS.importFromWkt(pszSrcWKT) != OGRERR_NONE)
    2897             :         {
    2898           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    2899             :                      "Failed to import coordinate system `%s'.", pszSrcWKT);
    2900           0 :             return nullptr;
    2901             :         }
    2902             :     }
    2903             : 
    2904           0 :     OGRSpatialReference oDstSRS;
    2905           0 :     if (pszDstWKT)
    2906             :     {
    2907           0 :         oDstSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    2908           0 :         if (pszDstWKT[0] != '\0' &&
    2909           0 :             oDstSRS.importFromWkt(pszDstWKT) != OGRERR_NONE)
    2910             :         {
    2911           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    2912             :                      "Failed to import coordinate system `%s'.", pszDstWKT);
    2913           0 :             return nullptr;
    2914             :         }
    2915             :     }
    2916           0 :     return GDALCreateGenImgProjTransformer4(
    2917             :         OGRSpatialReference::ToHandle(&oSrcSRS), padfSrcGeoTransform,
    2918           0 :         OGRSpatialReference::ToHandle(&oDstSRS), padfDstGeoTransform, nullptr);
    2919             : }
    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          16 : void *GDALCreateGenImgProjTransformer4(OGRSpatialReferenceH hSrcSRS,
    2935             :                                        const double *padfSrcGeoTransform,
    2936             :                                        OGRSpatialReferenceH hDstSRS,
    2937             :                                        const double *padfDstGeoTransform,
    2938             :                                        const char *const *papszOptions)
    2939             : {
    2940             :     /* -------------------------------------------------------------------- */
    2941             :     /*      Initialize the transform info.                                  */
    2942             :     /* -------------------------------------------------------------------- */
    2943             :     GDALGenImgProjTransformInfo *psInfo =
    2944          16 :         GDALCreateGenImgProjTransformerInternal();
    2945             : 
    2946             :     /* -------------------------------------------------------------------- */
    2947             :     /*      Get forward and inverse geotransform for the source image.      */
    2948             :     /* -------------------------------------------------------------------- */
    2949             : 
    2950             :     const auto SetParams =
    2951          30 :         [](GDALGenImgProjTransformPart &part, const double *padfGT)
    2952             :     {
    2953          30 :         if (padfGT)
    2954             :         {
    2955          30 :             memcpy(part.adfGeoTransform, padfGT, sizeof(part.adfGeoTransform));
    2956          30 :             if (!GDALInvGeoTransform(part.adfGeoTransform,
    2957          30 :                                      part.adfInvGeoTransform))
    2958             :             {
    2959           2 :                 CPLError(CE_Failure, CPLE_AppDefined,
    2960             :                          "Cannot invert geotransform");
    2961           2 :                 return false;
    2962             :             }
    2963             :         }
    2964             :         else
    2965             :         {
    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             :                    sizeof(double) * 6);
    2974             :         }
    2975          28 :         return true;
    2976             :     };
    2977             : 
    2978          16 :     if (!SetParams(psInfo->sSrcParams, padfSrcGeoTransform))
    2979             :     {
    2980           1 :         GDALDestroyGenImgProjTransformer(psInfo);
    2981           1 :         return nullptr;
    2982             :     }
    2983             : 
    2984             :     /* -------------------------------------------------------------------- */
    2985             :     /*      Setup reprojection.                                             */
    2986             :     /* -------------------------------------------------------------------- */
    2987          15 :     OGRSpatialReference *poSrcSRS = OGRSpatialReference::FromHandle(hSrcSRS);
    2988          15 :     OGRSpatialReference *poDstSRS = OGRSpatialReference::FromHandle(hDstSRS);
    2989          30 :     if (!poSrcSRS->IsEmpty() && !poDstSRS->IsEmpty() &&
    2990          15 :         !poSrcSRS->IsSame(poDstSRS))
    2991             :     {
    2992           4 :         psInfo->pReprojectArg =
    2993           4 :             GDALCreateReprojectionTransformerEx(hSrcSRS, hDstSRS, papszOptions);
    2994           4 :         if (psInfo->pReprojectArg == nullptr)
    2995             :         {
    2996           1 :             GDALDestroyGenImgProjTransformer(psInfo);
    2997           1 :             return nullptr;
    2998             :         }
    2999           3 :         psInfo->pReproject = GDALReprojectionTransform;
    3000             :     }
    3001             : 
    3002             :     /* -------------------------------------------------------------------- */
    3003             :     /*      Get forward and inverse geotransform for destination image.     */
    3004             :     /*      If we have no destination matrix use a unit transform.          */
    3005             :     /* -------------------------------------------------------------------- */
    3006          14 :     if (!SetParams(psInfo->sDstParams, padfDstGeoTransform))
    3007             :     {
    3008           1 :         GDALDestroyGenImgProjTransformer(psInfo);
    3009           1 :         return nullptr;
    3010             :     }
    3011             : 
    3012          13 :     return psInfo;
    3013             : }
    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        1100 : void GDALSetGenImgProjTransformerDstGeoTransform(void *hTransformArg,
    3036             :                                                  const double *padfGeoTransform)
    3037             : 
    3038             : {
    3039        1100 :     VALIDATE_POINTER0(hTransformArg,
    3040             :                       "GDALSetGenImgProjTransformerDstGeoTransform");
    3041             : 
    3042        1100 :     GDALGenImgProjTransformInfo *psInfo =
    3043             :         static_cast<GDALGenImgProjTransformInfo *>(hTransformArg);
    3044             : 
    3045        1100 :     memcpy(psInfo->sDstParams.adfGeoTransform, padfGeoTransform,
    3046             :            sizeof(double) * 6);
    3047        1100 :     if (!GDALInvGeoTransform(psInfo->sDstParams.adfGeoTransform,
    3048        1100 :                              psInfo->sDstParams.adfInvGeoTransform))
    3049             :     {
    3050           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot invert geotransform");
    3051             :     }
    3052             : }
    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        2569 : void GDALDestroyGenImgProjTransformer(void *hTransformArg)
    3068             : 
    3069             : {
    3070        2569 :     if (hTransformArg == nullptr)
    3071           0 :         return;
    3072             : 
    3073        2569 :     GDALGenImgProjTransformInfo *psInfo =
    3074             :         static_cast<GDALGenImgProjTransformInfo *>(hTransformArg);
    3075             : 
    3076        2569 :     if (psInfo->sSrcParams.pTransformArg != nullptr)
    3077         152 :         GDALDestroyTransformer(psInfo->sSrcParams.pTransformArg);
    3078             : 
    3079        2569 :     if (psInfo->sDstParams.pTransformArg != nullptr)
    3080           7 :         GDALDestroyTransformer(psInfo->sDstParams.pTransformArg);
    3081             : 
    3082        2569 :     if (psInfo->pReprojectArg != nullptr)
    3083        1214 :         GDALDestroyTransformer(psInfo->pReprojectArg);
    3084             : 
    3085        2569 :     CPLFree(psInfo);
    3086             : }
    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    11989600 : int GDALGenImgProjTransform(void *pTransformArgIn, int bDstToSrc,
    3106             :                             int nPointCount, double *padfX, double *padfY,
    3107             :                             double *padfZ, int *panSuccess)
    3108             : {
    3109             :     // Sanity check (see issue GH #13498)
    3110    11989600 :     if (nullptr == pTransformArgIn)
    3111           0 :         return FALSE;
    3112             : 
    3113    11989600 :     GDALGenImgProjTransformInfo *psInfo =
    3114             :         static_cast<GDALGenImgProjTransformInfo *>(pTransformArgIn);
    3115             : 
    3116             : #ifdef DEBUG_APPROX_TRANSFORMER
    3117             :     CPLAssert(nPointCount > 0);
    3118             :     countGDALGenImgProjTransform += nPointCount;
    3119             : #endif
    3120             : 
    3121    41393600 :     for (int i = 0; i < nPointCount; i++)
    3122             :     {
    3123    29404000 :         panSuccess[i] = (padfX[i] != HUGE_VAL && padfY[i] != HUGE_VAL);
    3124             :     }
    3125             : 
    3126    11989600 :     int ret = TRUE;
    3127             : 
    3128             :     /* -------------------------------------------------------------------- */
    3129             :     /*      Convert from src (dst) pixel/line to src (dst)                  */
    3130             :     /*      georeferenced coordinates.                                      */
    3131             :     /* -------------------------------------------------------------------- */
    3132             :     {
    3133    11989600 :         const auto params = bDstToSrc ? psInfo->sDstParams : psInfo->sSrcParams;
    3134    11989600 :         const double *padfGeoTransform = params.adfGeoTransform;
    3135    11989600 :         void *pTransformArg = params.pTransformArg;
    3136    11989600 :         GDALTransformerFunc pTransformer = params.pTransformer;
    3137             : 
    3138    11989600 :         if (pTransformArg != nullptr)
    3139             :         {
    3140       42554 :             if (!pTransformer(pTransformArg, FALSE, nPointCount, padfX, padfY,
    3141             :                               padfZ, panSuccess))
    3142        1795 :                 ret = FALSE;
    3143             :         }
    3144             :         else
    3145             :         {
    3146    41263400 :             for (int i = 0; i < nPointCount; i++)
    3147             :             {
    3148    29316300 :                 if (!panSuccess[i])
    3149        2071 :                     continue;
    3150             : 
    3151    29314300 :                 const double dfNewX = padfGeoTransform[0] +
    3152    29314300 :                                       padfX[i] * padfGeoTransform[1] +
    3153    29314300 :                                       padfY[i] * padfGeoTransform[2];
    3154    29314300 :                 const double dfNewY = padfGeoTransform[3] +
    3155    29314300 :                                       padfX[i] * padfGeoTransform[4] +
    3156    29314300 :                                       padfY[i] * padfGeoTransform[5];
    3157             : 
    3158    29314300 :                 padfX[i] = dfNewX;
    3159    29314300 :                 padfY[i] = dfNewY;
    3160             :             }
    3161             :         }
    3162             :     }
    3163             : 
    3164             :     /* -------------------------------------------------------------------- */
    3165             :     /*      Reproject if needed.                                            */
    3166             :     /* -------------------------------------------------------------------- */
    3167    11989600 :     if (psInfo->pReprojectArg)
    3168             :     {
    3169    11492500 :         if (!psInfo->pReproject(psInfo->pReprojectArg, bDstToSrc, nPointCount,
    3170             :                                 padfX, padfY, padfZ, panSuccess))
    3171       20369 :             ret = FALSE;
    3172             :     }
    3173             : 
    3174             :     /* -------------------------------------------------------------------- */
    3175             :     /*      Convert dst (src) georef coordinates back to pixel/line.        */
    3176             :     /* -------------------------------------------------------------------- */
    3177             :     {
    3178    11989600 :         const auto params = bDstToSrc ? psInfo->sSrcParams : psInfo->sDstParams;
    3179    11989600 :         const double *padfInvGeoTransform = params.adfInvGeoTransform;
    3180    11989600 :         void *pTransformArg = params.pTransformArg;
    3181    11989600 :         GDALTransformerFunc pTransformer = params.pTransformer;
    3182             : 
    3183    11989600 :         if (pTransformArg != nullptr)
    3184             :         {
    3185       53369 :             if (!pTransformer(pTransformArg, TRUE, nPointCount, padfX, padfY,
    3186             :                               padfZ, panSuccess))
    3187        2011 :                 ret = FALSE;
    3188             :         }
    3189             :         else
    3190             :         {
    3191    40426800 :             for (int i = 0; i < nPointCount; i++)
    3192             :             {
    3193    28490600 :                 if (!panSuccess[i])
    3194     3560710 :                     continue;
    3195             : 
    3196    24929900 :                 const double dfNewX = padfInvGeoTransform[0] +
    3197    24929900 :                                       padfX[i] * padfInvGeoTransform[1] +
    3198    24929900 :                                       padfY[i] * padfInvGeoTransform[2];
    3199    24929900 :                 const double dfNewY = padfInvGeoTransform[3] +
    3200    24929900 :                                       padfX[i] * padfInvGeoTransform[4] +
    3201    24929900 :                                       padfY[i] * padfInvGeoTransform[5];
    3202             : 
    3203    24929900 :                 padfX[i] = dfNewX;
    3204    24929900 :                 padfY[i] = dfNewY;
    3205             :             }
    3206             :         }
    3207             :     }
    3208             : 
    3209    11989600 :     return ret;
    3210             : }
    3211             : 
    3212             : /************************************************************************/
    3213             : /*           GDALTransformLonLatToDestGenImgProjTransformer()           */
    3214             : /************************************************************************/
    3215             : 
    3216        3362 : int GDALTransformLonLatToDestGenImgProjTransformer(void *hTransformArg,
    3217             :                                                    double *pdfX, double *pdfY)
    3218             : {
    3219        3362 :     GDALGenImgProjTransformInfo *psInfo =
    3220             :         static_cast<GDALGenImgProjTransformInfo *>(hTransformArg);
    3221             : 
    3222        3362 :     if (psInfo->pReprojectArg == nullptr ||
    3223        1844 :         psInfo->pReproject != GDALReprojectionTransform)
    3224        1522 :         return false;
    3225             : 
    3226        1840 :     GDALReprojectionTransformInfo *psReprojInfo =
    3227             :         static_cast<GDALReprojectionTransformInfo *>(psInfo->pReprojectArg);
    3228        3680 :     if (psReprojInfo->poForwardTransform == nullptr ||
    3229        1840 :         psReprojInfo->poForwardTransform->GetSourceCS() == nullptr)
    3230           2 :         return false;
    3231             : 
    3232        1838 :     double z = 0;
    3233        1838 :     int success = true;
    3234        1838 :     auto poSourceCRS = psReprojInfo->poForwardTransform->GetSourceCS();
    3235        3218 :     if (poSourceCRS->IsGeographic() &&
    3236        1380 :         std::fabs(poSourceCRS->GetAngularUnits() -
    3237        1380 :                   CPLAtof(SRS_UA_DEGREE_CONV)) < 1e-9)
    3238             :     {
    3239             :         // Optimization to avoid creating a OGRCoordinateTransformation
    3240        1378 :         OGRAxisOrientation eSourceFirstAxisOrient = OAO_Other;
    3241        1378 :         poSourceCRS->GetAxis(nullptr, 0, &eSourceFirstAxisOrient);
    3242        1378 :         const auto &mapping = poSourceCRS->GetDataAxisToSRSAxisMapping();
    3243        2756 :         if ((mapping[0] == 2 && eSourceFirstAxisOrient == OAO_East) ||
    3244        1378 :             (mapping[0] == 1 && eSourceFirstAxisOrient != OAO_East))
    3245             :         {
    3246           6 :             std::swap(*pdfX, *pdfY);
    3247             :         }
    3248             :     }
    3249             :     else
    3250             :     {
    3251             :         auto poLongLat =
    3252         460 :             std::unique_ptr<OGRSpatialReference>(poSourceCRS->CloneGeogCS());
    3253         460 :         if (poLongLat == nullptr)
    3254           0 :             return false;
    3255         460 :         poLongLat->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    3256             : 
    3257             :         const bool bCurrentCheckWithInvertProj =
    3258         460 :             GetCurrentCheckWithInvertPROJ();
    3259         460 :         if (!bCurrentCheckWithInvertProj)
    3260         460 :             CPLSetThreadLocalConfigOption("CHECK_WITH_INVERT_PROJ", "YES");
    3261             :         auto poCT = std::unique_ptr<OGRCoordinateTransformation>(
    3262         460 :             OGRCreateCoordinateTransformation(poLongLat.get(), poSourceCRS));
    3263         460 :         if (!bCurrentCheckWithInvertProj)
    3264         460 :             CPLSetThreadLocalConfigOption("CHECK_WITH_INVERT_PROJ", nullptr);
    3265         460 :         if (poCT == nullptr)
    3266           0 :             return false;
    3267             : 
    3268         460 :         poCT->SetEmitErrors(false);
    3269         460 :         if (!poCT->Transform(1, pdfX, pdfY))
    3270           4 :             return false;
    3271             : 
    3272         456 :         if (!psInfo->pReproject(psInfo->pReprojectArg, false, 1, pdfX, pdfY, &z,
    3273         714 :                                 &success) ||
    3274         258 :             !success)
    3275             :         {
    3276         198 :             return false;
    3277             :         }
    3278             :     }
    3279             : 
    3280        1636 :     double *padfGeoTransform = psInfo->sDstParams.adfInvGeoTransform;
    3281        1636 :     void *pTransformArg = psInfo->sDstParams.pTransformArg;
    3282        1636 :     GDALTransformerFunc pTransformer = psInfo->sDstParams.pTransformer;
    3283        1636 :     if (pTransformArg != nullptr)
    3284             :     {
    3285           4 :         if (!pTransformer(pTransformArg, TRUE, 1, pdfX, pdfY, &z, &success) ||
    3286           0 :             !success)
    3287             :         {
    3288           4 :             return false;
    3289             :         }
    3290             :     }
    3291             :     else
    3292             :     {
    3293        1632 :         const double dfNewX = padfGeoTransform[0] +
    3294        1632 :                               pdfX[0] * padfGeoTransform[1] +
    3295        1632 :                               pdfY[0] * padfGeoTransform[2];
    3296        1632 :         const double dfNewY = padfGeoTransform[3] +
    3297        1632 :                               pdfX[0] * padfGeoTransform[4] +
    3298        1632 :                               pdfY[0] * padfGeoTransform[5];
    3299             : 
    3300        1632 :         pdfX[0] = dfNewX;
    3301        1632 :         pdfY[0] = dfNewY;
    3302             :     }
    3303             : 
    3304        1632 :     return true;
    3305             : }
    3306             : 
    3307             : /************************************************************************/
    3308             : /*                 GDALSerializeGenImgProjTransformer()                 */
    3309             : /************************************************************************/
    3310             : 
    3311          42 : static CPLXMLNode *GDALSerializeGenImgProjTransformer(void *pTransformArg)
    3312             : 
    3313             : {
    3314          42 :     GDALGenImgProjTransformInfo *psInfo =
    3315             :         static_cast<GDALGenImgProjTransformInfo *>(pTransformArg);
    3316             : 
    3317             :     CPLXMLNode *psTree =
    3318          42 :         CPLCreateXMLNode(nullptr, CXT_Element, "GenImgProjTransformer");
    3319             : 
    3320             :     const auto SerializePart =
    3321         241 :         [psTree](const char *pszPrefix, const GDALGenImgProjTransformPart &part)
    3322             :     {
    3323          84 :         char szWork[200] = {};
    3324             : 
    3325             :         /* ------------------------------------------------------------- */
    3326             :         /*      Handle transformation.                                   */
    3327             :         /* ------------------------------------------------------------- */
    3328          84 :         if (part.pTransformArg != nullptr)
    3329             :         {
    3330             :             CPLXMLNode *psTransformer =
    3331          11 :                 GDALSerializeTransformer(part.pTransformer, part.pTransformArg);
    3332          11 :             if (psTransformer != nullptr)
    3333             :             {
    3334          11 :                 CPLXMLNode *psTransformerContainer = CPLCreateXMLNode(
    3335             :                     psTree, CXT_Element,
    3336             :                     CPLSPrintf("%s%s", pszPrefix, psTransformer->pszValue));
    3337             : 
    3338          11 :                 CPLAddXMLChild(psTransformerContainer, psTransformer);
    3339             :             }
    3340             :         }
    3341             : 
    3342             :         /* ------------------------------------------------------------- */
    3343             :         /*      Handle geotransforms.                                    */
    3344             :         /* ------------------------------------------------------------- */
    3345             :         else
    3346             :         {
    3347          73 :             CPLsnprintf(szWork, sizeof(szWork),
    3348             :                         "%.17g,%.17g,%.17g,%.17g,%.17g,%.17g",
    3349          73 :                         part.adfGeoTransform[0], part.adfGeoTransform[1],
    3350          73 :                         part.adfGeoTransform[2], part.adfGeoTransform[3],
    3351          73 :                         part.adfGeoTransform[4], part.adfGeoTransform[5]);
    3352          73 :             CPLCreateXMLElementAndValue(
    3353             :                 psTree, CPLSPrintf("%sGeoTransform", pszPrefix), szWork);
    3354             : 
    3355          73 :             CPLsnprintf(szWork, sizeof(szWork),
    3356             :                         "%.17g,%.17g,%.17g,%.17g,%.17g,%.17g",
    3357          73 :                         part.adfInvGeoTransform[0], part.adfInvGeoTransform[1],
    3358          73 :                         part.adfInvGeoTransform[2], part.adfInvGeoTransform[3],
    3359          73 :                         part.adfInvGeoTransform[4], part.adfInvGeoTransform[5]);
    3360          73 :             CPLCreateXMLElementAndValue(
    3361             :                 psTree, CPLSPrintf("%sInvGeoTransform", pszPrefix), szWork);
    3362             :         }
    3363          84 :     };
    3364             : 
    3365          42 :     SerializePart("Src", psInfo->sSrcParams);
    3366          42 :     SerializePart("Dst", psInfo->sDstParams);
    3367             : 
    3368             :     /* -------------------------------------------------------------------- */
    3369             :     /*      Do we have a reprojection transformer?                          */
    3370             :     /* -------------------------------------------------------------------- */
    3371          42 :     if (psInfo->pReprojectArg != nullptr)
    3372             :     {
    3373             : 
    3374             :         CPLXMLNode *psTransformerContainer =
    3375          15 :             CPLCreateXMLNode(psTree, CXT_Element, "ReprojectTransformer");
    3376             : 
    3377             :         CPLXMLNode *psTransformer =
    3378          15 :             GDALSerializeTransformer(psInfo->pReproject, psInfo->pReprojectArg);
    3379          15 :         if (psTransformer != nullptr)
    3380          15 :             CPLAddXMLChild(psTransformerContainer, psTransformer);
    3381             :     }
    3382             : 
    3383          42 :     return psTree;
    3384             : }
    3385             : 
    3386             : /************************************************************************/
    3387             : /*                    GDALDeserializeGeoTransform()                     */
    3388             : /************************************************************************/
    3389             : 
    3390         537 : static void GDALDeserializeGeoTransform(const char *pszGT,
    3391             :                                         double adfGeoTransform[6])
    3392             : {
    3393         537 :     CPLsscanf(pszGT, "%lf,%lf,%lf,%lf,%lf,%lf", adfGeoTransform + 0,
    3394             :               adfGeoTransform + 1, adfGeoTransform + 2, adfGeoTransform + 3,
    3395             :               adfGeoTransform + 4, adfGeoTransform + 5);
    3396         537 : }
    3397             : 
    3398             : /************************************************************************/
    3399             : /*                GDALDeserializeGenImgProjTransformer()                */
    3400             : /************************************************************************/
    3401             : 
    3402         147 : void *GDALDeserializeGenImgProjTransformer(CPLXMLNode *psTree)
    3403             : 
    3404             : {
    3405             :     /* -------------------------------------------------------------------- */
    3406             :     /*      Initialize the transform info.                                  */
    3407             :     /* -------------------------------------------------------------------- */
    3408             :     GDALGenImgProjTransformInfo *psInfo =
    3409         147 :         GDALCreateGenImgProjTransformerInternal();
    3410             : 
    3411             :     const auto DeserializePart =
    3412         882 :         [psTree](const char *pszPrefix, GDALGenImgProjTransformPart &part)
    3413             :     {
    3414             :         /* ----------------------------------------------------------------- */
    3415             :         /*      Geotransform                                                 */
    3416             :         /* ----------------------------------------------------------------- */
    3417         294 :         if (const auto psGTNode =
    3418         294 :                 CPLGetXMLNode(psTree, CPLSPrintf("%sGeoTransform", pszPrefix)))
    3419             :         {
    3420         281 :             GDALDeserializeGeoTransform(CPLGetXMLValue(psGTNode, "", ""),
    3421         281 :                                         part.adfGeoTransform);
    3422             : 
    3423         281 :             if (const auto psInvGTNode = CPLGetXMLNode(
    3424             :                     psTree, CPLSPrintf("%sInvGeoTransform", pszPrefix)))
    3425             :             {
    3426         256 :                 GDALDeserializeGeoTransform(CPLGetXMLValue(psInvGTNode, "", ""),
    3427         256 :                                             part.adfInvGeoTransform);
    3428             :             }
    3429             :             else
    3430             :             {
    3431          25 :                 if (!GDALInvGeoTransform(part.adfGeoTransform,
    3432          25 :                                          part.adfInvGeoTransform))
    3433             :                 {
    3434           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    3435             :                              "Cannot invert geotransform");
    3436             :                 }
    3437             :             }
    3438             :         }
    3439             : 
    3440             :         /* ---------------------------------------------------------------- */
    3441             :         /*      Transform                                                   */
    3442             :         /* ---------------------------------------------------------------- */
    3443             :         else
    3444             :         {
    3445          13 :             for (CPLXMLNode *psIter = psTree->psChild; psIter != nullptr;
    3446           0 :                  psIter = psIter->psNext)
    3447             :             {
    3448          13 :                 if (psIter->eType == CXT_Element &&
    3449          13 :                     STARTS_WITH_CI(psIter->pszValue, pszPrefix))
    3450             :                 {
    3451          13 :                     GDALDeserializeTransformer(psIter->psChild,
    3452             :                                                &part.pTransformer,
    3453             :                                                &part.pTransformArg);
    3454          13 :                     break;
    3455             :                 }
    3456             :             }
    3457             :         }
    3458         441 :     };
    3459             : 
    3460         147 :     DeserializePart("Src", psInfo->sSrcParams);
    3461         147 :     DeserializePart("Dst", psInfo->sDstParams);
    3462             : 
    3463             :     /* -------------------------------------------------------------------- */
    3464             :     /*      Reproject transformer                                           */
    3465             :     /* -------------------------------------------------------------------- */
    3466         147 :     CPLXMLNode *psSubtree = CPLGetXMLNode(psTree, "ReprojectTransformer");
    3467         147 :     if (psSubtree != nullptr && psSubtree->psChild != nullptr)
    3468             :     {
    3469          46 :         GDALDeserializeTransformer(psSubtree->psChild, &psInfo->pReproject,
    3470             :                                    &psInfo->pReprojectArg);
    3471             :     }
    3472             : 
    3473         147 :     return psInfo;
    3474             : }
    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           0 : void *GDALCreateReprojectionTransformer(const char *pszSrcWKT,
    3500             :                                         const char *pszDstWKT)
    3501             : 
    3502             : {
    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             :     {
    3510           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    3511             :                  "Failed to import coordinate system `%s'.", pszSrcWKT);
    3512           0 :         return nullptr;
    3513             :     }
    3514             : 
    3515           0 :     OGRSpatialReference oDstSRS;
    3516           0 :     oDstSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    3517           0 :     if (oDstSRS.importFromWkt(pszDstWKT) != OGRERR_NONE)
    3518             :     {
    3519           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    3520             :                  "Failed to import coordinate system `%s'.", pszSrcWKT);
    3521           0 :         return nullptr;
    3522             :     }
    3523             : 
    3524           0 :     return GDALCreateReprojectionTransformerEx(
    3525             :         OGRSpatialReference::ToHandle(&oSrcSRS),
    3526           0 :         OGRSpatialReference::ToHandle(&oDstSRS), nullptr);
    3527             : }
    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        1292 : void *GDALCreateReprojectionTransformerEx(OGRSpatialReferenceH hSrcSRS,
    3589             :                                           OGRSpatialReferenceH hDstSRS,
    3590             :                                           const char *const *papszOptions)
    3591             : {
    3592        1292 :     OGRSpatialReference *poSrcSRS = OGRSpatialReference::FromHandle(hSrcSRS);
    3593        1292 :     OGRSpatialReference *poDstSRS = OGRSpatialReference::FromHandle(hDstSRS);
    3594             : 
    3595             :     /* -------------------------------------------------------------------- */
    3596             :     /*      Build the forward coordinate transformation.                    */
    3597             :     /* -------------------------------------------------------------------- */
    3598        1292 :     double dfWestLongitudeDeg = 0.0;
    3599        1292 :     double dfSouthLatitudeDeg = 0.0;
    3600        1292 :     double dfEastLongitudeDeg = 0.0;
    3601        1292 :     double dfNorthLatitudeDeg = 0.0;
    3602        1292 :     const char *pszBBOX = CSLFetchNameValue(papszOptions, "AREA_OF_INTEREST");
    3603        1292 :     if (pszBBOX)
    3604             :     {
    3605        1221 :         char **papszTokens = CSLTokenizeString2(pszBBOX, ",", 0);
    3606        1221 :         if (CSLCount(papszTokens) == 4)
    3607             :         {
    3608        1221 :             dfWestLongitudeDeg = CPLAtof(papszTokens[0]);
    3609        1221 :             dfSouthLatitudeDeg = CPLAtof(papszTokens[1]);
    3610        1221 :             dfEastLongitudeDeg = CPLAtof(papszTokens[2]);
    3611        1221 :             dfNorthLatitudeDeg = CPLAtof(papszTokens[3]);
    3612             :         }
    3613        1221 :         CSLDestroy(papszTokens);
    3614             :     }
    3615        1292 :     const char *pszCO = CSLFetchNameValue(papszOptions, "COORDINATE_OPERATION");
    3616             : 
    3617        2584 :     OGRCoordinateTransformationOptions optionsFwd;
    3618        1292 :     if (!(dfWestLongitudeDeg == 0.0 && dfSouthLatitudeDeg == 0.0 &&
    3619             :           dfEastLongitudeDeg == 0.0 && dfNorthLatitudeDeg == 0.0))
    3620             :     {
    3621        1221 :         optionsFwd.SetAreaOfInterest(dfWestLongitudeDeg, dfSouthLatitudeDeg,
    3622             :                                      dfEastLongitudeDeg, dfNorthLatitudeDeg);
    3623             :     }
    3624        1292 :     if (pszCO)
    3625             :     {
    3626           7 :         optionsFwd.SetCoordinateOperation(pszCO, false);
    3627             :     }
    3628             : 
    3629        1292 :     const char *pszCENTER_LONG = CSLFetchNameValue(papszOptions, "CENTER_LONG");
    3630        1292 :     if (pszCENTER_LONG)
    3631             :     {
    3632         882 :         optionsFwd.SetSourceCenterLong(CPLAtof(pszCENTER_LONG));
    3633             :     }
    3634             : 
    3635        1292 :     optionsFwd.SetBallparkAllowed(CPLTestBool(
    3636             :         CSLFetchNameValueDef(papszOptions, "ALLOW_BALLPARK", "YES")));
    3637             : 
    3638             :     const char *pszOnlyBest =
    3639        1292 :         CSLFetchNameValueDef(papszOptions, "ONLY_BEST", "AUTO");
    3640        1292 :     if (!EQUAL(pszOnlyBest, "AUTO"))
    3641             :     {
    3642           1 :         optionsFwd.SetOnlyBest(CPLTestBool(pszOnlyBest));
    3643             :     }
    3644             : 
    3645             :     OGRCoordinateTransformation *poForwardTransform =
    3646        1292 :         OGRCreateCoordinateTransformation(poSrcSRS, poDstSRS, optionsFwd);
    3647             : 
    3648        1292 :     if (poForwardTransform == nullptr)
    3649             :         // OGRCreateCoordinateTransformation() will report errors on its own.
    3650           6 :         return nullptr;
    3651             : 
    3652        1286 :     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        1286 :     GDALReprojectionTransformInfo *psInfo = new GDALReprojectionTransformInfo();
    3660             : 
    3661        1286 :     psInfo->papszOptions = CSLDuplicate(papszOptions);
    3662        1286 :     psInfo->poForwardTransform = poForwardTransform;
    3663        1286 :     psInfo->dfTime = CPLAtof(CSLFetchNameValueDef(
    3664             :         papszOptions, "COORDINATE_EPOCH",
    3665             :         CSLFetchNameValueDef(
    3666             :             papszOptions, "DST_COORDINATE_EPOCH",
    3667             :             CSLFetchNameValueDef(papszOptions, "SRC_COORDINATE_EPOCH", "0"))));
    3668        1286 :     psInfo->poReverseTransform = poForwardTransform->GetInverse();
    3669             : 
    3670        1286 :     if (psInfo->poReverseTransform)
    3671        1286 :         psInfo->poReverseTransform->SetEmitErrors(false);
    3672             : 
    3673        1286 :     memcpy(psInfo->sTI.abySignature, GDAL_GTI2_SIGNATURE,
    3674             :            strlen(GDAL_GTI2_SIGNATURE));
    3675        1286 :     psInfo->sTI.pszClassName = GDAL_REPROJECTION_TRANSFORMER_CLASS_NAME;
    3676        1286 :     psInfo->sTI.pfnTransform = GDALReprojectionTransform;
    3677        1286 :     psInfo->sTI.pfnCleanup = GDALDestroyReprojectionTransformer;
    3678        1286 :     psInfo->sTI.pfnSerialize = GDALSerializeReprojectionTransformer;
    3679             : 
    3680        1286 :     return psInfo;
    3681             : }
    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        1286 : void GDALDestroyReprojectionTransformer(void *pTransformArg)
    3695             : 
    3696             : {
    3697        1286 :     if (pTransformArg == nullptr)
    3698           0 :         return;
    3699             : 
    3700        1286 :     GDALReprojectionTransformInfo *psInfo =
    3701             :         static_cast<GDALReprojectionTransformInfo *>(pTransformArg);
    3702             : 
    3703        1286 :     if (psInfo->poForwardTransform)
    3704        1286 :         OGRCoordinateTransformation::DestroyCT(psInfo->poForwardTransform);
    3705             : 
    3706        1286 :     if (psInfo->poReverseTransform)
    3707        1286 :         OGRCoordinateTransformation::DestroyCT(psInfo->poReverseTransform);
    3708             : 
    3709        1286 :     CSLDestroy(psInfo->papszOptions);
    3710             : 
    3711        1286 :     delete psInfo;
    3712             : }
    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    11493000 : int GDALReprojectionTransform(void *pTransformArg, int bDstToSrc,
    3728             :                               int nPointCount, double *padfX, double *padfY,
    3729             :                               double *padfZ, int *panSuccess)
    3730             : 
    3731             : {
    3732    11493000 :     GDALReprojectionTransformInfo *psInfo =
    3733             :         static_cast<GDALReprojectionTransformInfo *>(pTransformArg);
    3734             :     int bSuccess;
    3735             : 
    3736    11493000 :     std::vector<double> adfTime;
    3737    11493000 :     double *padfT = nullptr;
    3738    11493000 :     if (psInfo->dfTime != 0.0 && nPointCount > 0)
    3739             :     {
    3740           1 :         adfTime.resize(nPointCount, psInfo->dfTime);
    3741           1 :         padfT = &adfTime[0];
    3742             :     }
    3743             : 
    3744    11493000 :     if (bDstToSrc)
    3745             :     {
    3746    11216500 :         if (psInfo->poReverseTransform == nullptr)
    3747             :         {
    3748           0 :             CPLError(
    3749             :                 CE_Failure, CPLE_AppDefined,
    3750             :                 "Inverse coordinate transformation cannot be instantiated");
    3751           0 :             if (panSuccess)
    3752             :             {
    3753           0 :                 for (int i = 0; i < nPointCount; i++)
    3754           0 :                     panSuccess[i] = FALSE;
    3755             :             }
    3756           0 :             bSuccess = false;
    3757             :         }
    3758             :         else
    3759             :         {
    3760    11216500 :             bSuccess = psInfo->poReverseTransform->Transform(
    3761    11216500 :                 nPointCount, padfX, padfY, padfZ, padfT, panSuccess);
    3762             :         }
    3763             :     }
    3764             :     else
    3765      276480 :         bSuccess = psInfo->poForwardTransform->Transform(
    3766      276480 :             nPointCount, padfX, padfY, padfZ, padfT, panSuccess);
    3767             : 
    3768    22985900 :     return bSuccess;
    3769             : }
    3770             : 
    3771             : /************************************************************************/
    3772             : /*                GDALSerializeReprojectionTransformer()                */
    3773             : /************************************************************************/
    3774             : 
    3775         126 : static CPLXMLNode *GDALSerializeReprojectionTransformer(void *pTransformArg)
    3776             : 
    3777             : {
    3778             :     CPLXMLNode *psTree;
    3779         126 :     GDALReprojectionTransformInfo *psInfo =
    3780             :         static_cast<GDALReprojectionTransformInfo *>(pTransformArg);
    3781             : 
    3782         126 :     psTree = CPLCreateXMLNode(nullptr, CXT_Element, "ReprojectionTransformer");
    3783             : 
    3784             :     /* -------------------------------------------------------------------- */
    3785             :     /*      Handle SourceCS.                                                */
    3786             :     /* -------------------------------------------------------------------- */
    3787         252 :     const auto ExportToWkt = [](const OGRSpatialReference *poSRS)
    3788             :     {
    3789             :         // Try first in WKT1 for backward compat
    3790             :         {
    3791         252 :             char *pszWKT = nullptr;
    3792         252 :             const char *const apszOptions[] = {"FORMAT=WKT1", nullptr};
    3793         252 :             CPLErrorHandlerPusher oHandler(CPLQuietErrorHandler);
    3794         252 :             CPLErrorStateBackuper oBackuper;
    3795         252 :             if (poSRS->exportToWkt(&pszWKT, apszOptions) == OGRERR_NONE)
    3796             :             {
    3797         502 :                 std::string osRet(pszWKT);
    3798         251 :                 CPLFree(pszWKT);
    3799         251 :                 return osRet;
    3800             :             }
    3801           1 :             CPLFree(pszWKT);
    3802             :         }
    3803             : 
    3804           1 :         char *pszWKT = nullptr;
    3805           1 :         const char *const apszOptions[] = {"FORMAT=WKT2_2019", nullptr};
    3806           1 :         if (poSRS->exportToWkt(&pszWKT, apszOptions) == OGRERR_NONE)
    3807             :         {
    3808           2 :             std::string osRet(pszWKT);
    3809           1 :             CPLFree(pszWKT);
    3810           1 :             return osRet;
    3811             :         }
    3812           0 :         CPLFree(pszWKT);
    3813           0 :         return std::string();
    3814             :     };
    3815             : 
    3816         126 :     auto poSRS = psInfo->poForwardTransform->GetSourceCS();
    3817         126 :     if (poSRS)
    3818             :     {
    3819         252 :         const auto osWKT = ExportToWkt(poSRS);
    3820         126 :         CPLCreateXMLElementAndValue(psTree, "SourceSRS", osWKT.c_str());
    3821             :     }
    3822             : 
    3823             :     /* -------------------------------------------------------------------- */
    3824             :     /*      Handle DestinationCS.                                           */
    3825             :     /* -------------------------------------------------------------------- */
    3826         126 :     poSRS = psInfo->poForwardTransform->GetTargetCS();
    3827         126 :     if (poSRS)
    3828             :     {
    3829         252 :         const auto osWKT = ExportToWkt(poSRS);
    3830         126 :         CPLCreateXMLElementAndValue(psTree, "TargetSRS", osWKT.c_str());
    3831             :     }
    3832             : 
    3833             :     /* -------------------------------------------------------------------- */
    3834             :     /*      Serialize options.                                              */
    3835             :     /* -------------------------------------------------------------------- */
    3836         126 :     if (psInfo->papszOptions)
    3837             :     {
    3838             :         CPLXMLNode *psOptions =
    3839         112 :             CPLCreateXMLNode(psTree, CXT_Element, "Options");
    3840         272 :         for (auto iter = psInfo->papszOptions; *iter != nullptr; ++iter)
    3841             :         {
    3842         160 :             char *pszKey = nullptr;
    3843         160 :             const char *pszValue = CPLParseNameValue(*iter, &pszKey);
    3844         160 :             if (pszKey && pszValue)
    3845             :             {
    3846             :                 auto elt =
    3847         160 :                     CPLCreateXMLElementAndValue(psOptions, "Option", pszValue);
    3848         160 :                 CPLAddXMLAttributeAndValue(elt, "key", pszKey);
    3849             :             }
    3850         160 :             CPLFree(pszKey);
    3851             :         }
    3852             :     }
    3853             : 
    3854         126 :     return psTree;
    3855             : }
    3856             : 
    3857             : /************************************************************************/
    3858             : /*               GDALDeserializeReprojectionTransformer()               */
    3859             : /************************************************************************/
    3860             : 
    3861         157 : static void *GDALDeserializeReprojectionTransformer(CPLXMLNode *psTree)
    3862             : 
    3863             : {
    3864         157 :     const char *pszSourceSRS = CPLGetXMLValue(psTree, "SourceSRS", nullptr);
    3865         157 :     const char *pszTargetSRS = CPLGetXMLValue(psTree, "TargetSRS", nullptr);
    3866         157 :     char *pszSourceWKT = nullptr, *pszTargetWKT = nullptr;
    3867         157 :     void *pResult = nullptr;
    3868             : 
    3869         314 :     OGRSpatialReference oSrcSRS;
    3870         314 :     OGRSpatialReference oDstSRS;
    3871             : 
    3872         157 :     oSrcSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    3873         157 :     oDstSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    3874         157 :     if (pszSourceSRS != nullptr)
    3875             :     {
    3876         157 :         oSrcSRS.SetFromUserInput(pszSourceSRS);
    3877             :     }
    3878             : 
    3879         157 :     if (pszTargetSRS != nullptr)
    3880             :     {
    3881         157 :         oDstSRS.SetFromUserInput(pszTargetSRS);
    3882             :     }
    3883             : 
    3884         157 :     CPLStringList aosList;
    3885         157 :     const CPLXMLNode *psOptions = CPLGetXMLNode(psTree, "Options");
    3886         157 :     if (psOptions)
    3887             :     {
    3888         297 :         for (auto iter = psOptions->psChild; iter; iter = iter->psNext)
    3889             :         {
    3890         176 :             if (iter->eType == CXT_Element &&
    3891         176 :                 strcmp(iter->pszValue, "Option") == 0)
    3892             :             {
    3893         176 :                 const char *pszKey = CPLGetXMLValue(iter, "key", nullptr);
    3894         176 :                 const char *pszValue = CPLGetXMLValue(iter, nullptr, nullptr);
    3895         176 :                 if (pszKey && pszValue)
    3896             :                 {
    3897         176 :                     aosList.SetNameValue(pszKey, pszValue);
    3898             :                 }
    3899             :             }
    3900             :         }
    3901             :     }
    3902             : 
    3903         157 :     pResult = GDALCreateReprojectionTransformerEx(
    3904         157 :         !oSrcSRS.IsEmpty() ? OGRSpatialReference::ToHandle(&oSrcSRS) : nullptr,
    3905         157 :         !oDstSRS.IsEmpty() ? OGRSpatialReference::ToHandle(&oDstSRS) : nullptr,
    3906         157 :         aosList.List());
    3907             : 
    3908         157 :     CPLFree(pszSourceWKT);
    3909         157 :     CPLFree(pszTargetWKT);
    3910             : 
    3911         314 :     return pResult;
    3912             : }
    3913             : 
    3914             : /************************************************************************/
    3915             : /* ==================================================================== */
    3916             : /*      Approximate transformer.                                        */
    3917             : /* ==================================================================== */
    3918             : /************************************************************************/
    3919             : 
    3920             : /************************************************************************/
    3921             : /*                 GDALCreateSimilarApproxTransformer()                 */
    3922             : /************************************************************************/
    3923             : 
    3924          46 : static void *GDALCreateSimilarApproxTransformer(void *hTransformArg,
    3925             :                                                 double dfSrcRatioX,
    3926             :                                                 double dfSrcRatioY)
    3927             : {
    3928          46 :     VALIDATE_POINTER1(hTransformArg, "GDALCreateSimilarApproxTransformer",
    3929             :                       nullptr);
    3930             : 
    3931          46 :     GDALApproxTransformInfo *psInfo =
    3932             :         static_cast<GDALApproxTransformInfo *>(hTransformArg);
    3933             : 
    3934          46 :     void *pBaseCBData = GDALCreateSimilarTransformer(psInfo->pBaseCBData,
    3935             :                                                      dfSrcRatioX, dfSrcRatioY);
    3936          46 :     if (pBaseCBData == nullptr)
    3937             :     {
    3938           0 :         return nullptr;
    3939             :     }
    3940             : 
    3941             :     GDALApproxTransformInfo *psClonedInfo =
    3942          46 :         static_cast<GDALApproxTransformInfo *>(GDALCreateApproxTransformer2(
    3943             :             psInfo->pfnBaseTransformer, pBaseCBData, psInfo->dfMaxErrorForward,
    3944             :             psInfo->dfMaxErrorReverse));
    3945          46 :     psClonedInfo->bOwnSubtransformer = TRUE;
    3946             : 
    3947          46 :     return psClonedInfo;
    3948             : }
    3949             : 
    3950             : /************************************************************************/
    3951             : /*                   GDALSerializeApproxTransformer()                   */
    3952             : /************************************************************************/
    3953             : 
    3954          27 : static CPLXMLNode *GDALSerializeApproxTransformer(void *pTransformArg)
    3955             : 
    3956             : {
    3957             :     CPLXMLNode *psTree;
    3958          27 :     GDALApproxTransformInfo *psInfo =
    3959             :         static_cast<GDALApproxTransformInfo *>(pTransformArg);
    3960             : 
    3961          27 :     psTree = CPLCreateXMLNode(nullptr, CXT_Element, "ApproxTransformer");
    3962             : 
    3963             :     /* -------------------------------------------------------------------- */
    3964             :     /*      Attach max error.                                               */
    3965             :     /* -------------------------------------------------------------------- */
    3966          27 :     if (psInfo->dfMaxErrorForward == psInfo->dfMaxErrorReverse)
    3967             :     {
    3968          25 :         CPLCreateXMLElementAndValue(
    3969             :             psTree, "MaxError",
    3970          50 :             CPLString().Printf("%g", psInfo->dfMaxErrorForward));
    3971             :     }
    3972             :     else
    3973             :     {
    3974           2 :         CPLCreateXMLElementAndValue(
    3975             :             psTree, "MaxErrorForward",
    3976           4 :             CPLString().Printf("%g", psInfo->dfMaxErrorForward));
    3977           2 :         CPLCreateXMLElementAndValue(
    3978             :             psTree, "MaxErrorReverse",
    3979           4 :             CPLString().Printf("%g", psInfo->dfMaxErrorReverse));
    3980             :     }
    3981             : 
    3982             :     /* -------------------------------------------------------------------- */
    3983             :     /*      Capture underlying transformer.                                 */
    3984             :     /* -------------------------------------------------------------------- */
    3985             :     CPLXMLNode *psTransformerContainer =
    3986          27 :         CPLCreateXMLNode(psTree, CXT_Element, "BaseTransformer");
    3987             : 
    3988          27 :     CPLXMLNode *psTransformer = GDALSerializeTransformer(
    3989             :         psInfo->pfnBaseTransformer, psInfo->pBaseCBData);
    3990          27 :     if (psTransformer != nullptr)
    3991          27 :         CPLAddXMLChild(psTransformerContainer, psTransformer);
    3992             : 
    3993          27 :     return psTree;
    3994             : }
    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        1387 : void *GDALCreateApproxTransformer(GDALTransformerFunc pfnBaseTransformer,
    4039             :                                   void *pBaseTransformArg, double dfMaxError)
    4040             : 
    4041             : {
    4042        1387 :     return GDALCreateApproxTransformer2(pfnBaseTransformer, pBaseTransformArg,
    4043        1387 :                                         dfMaxError, dfMaxError);
    4044             : }
    4045             : 
    4046             : static void *
    4047        1542 : GDALCreateApproxTransformer2(GDALTransformerFunc pfnBaseTransformer,
    4048             :                              void *pBaseTransformArg, double dfMaxErrorForward,
    4049             :                              double dfMaxErrorReverse)
    4050             : 
    4051             : {
    4052        1542 :     GDALApproxTransformInfo *psATInfo = new GDALApproxTransformInfo;
    4053        1542 :     psATInfo->pfnBaseTransformer = pfnBaseTransformer;
    4054        1542 :     psATInfo->pBaseCBData = pBaseTransformArg;
    4055        1542 :     psATInfo->dfMaxErrorForward = dfMaxErrorForward;
    4056        1542 :     psATInfo->dfMaxErrorReverse = dfMaxErrorReverse;
    4057        1542 :     psATInfo->bOwnSubtransformer = FALSE;
    4058             : 
    4059        1542 :     memcpy(psATInfo->sTI.abySignature, GDAL_GTI2_SIGNATURE,
    4060             :            strlen(GDAL_GTI2_SIGNATURE));
    4061        1542 :     psATInfo->sTI.pszClassName = GDAL_APPROX_TRANSFORMER_CLASS_NAME;
    4062        1542 :     psATInfo->sTI.pfnTransform = GDALApproxTransform;
    4063        1542 :     psATInfo->sTI.pfnCleanup = GDALDestroyApproxTransformer;
    4064        1542 :     psATInfo->sTI.pfnSerialize = GDALSerializeApproxTransformer;
    4065        1542 :     psATInfo->sTI.pfnCreateSimilar = GDALCreateSimilarApproxTransformer;
    4066             : 
    4067        1542 :     return psATInfo;
    4068             : }
    4069             : 
    4070             : /************************************************************************/
    4071             : /*              GDALApproxTransformerOwnsSubtransformer()               */
    4072             : /************************************************************************/
    4073             : 
    4074             : /** Set bOwnSubtransformer flag */
    4075        1493 : void GDALApproxTransformerOwnsSubtransformer(void *pCBData, int bOwnFlag)
    4076             : 
    4077             : {
    4078        1493 :     GDALApproxTransformInfo *psATInfo =
    4079             :         static_cast<GDALApproxTransformInfo *>(pCBData);
    4080             : 
    4081        1493 :     psATInfo->bOwnSubtransformer = bOwnFlag;
    4082        1493 : }
    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        1542 : void GDALDestroyApproxTransformer(void *pCBData)
    4098             : 
    4099             : {
    4100        1542 :     if (pCBData == nullptr)
    4101           0 :         return;
    4102             : 
    4103        1542 :     GDALApproxTransformInfo *psATInfo =
    4104             :         static_cast<GDALApproxTransformInfo *>(pCBData);
    4105             : 
    4106        1542 :     if (psATInfo->bOwnSubtransformer)
    4107        1539 :         GDALDestroyTransformer(psATInfo->pBaseCBData);
    4108             : 
    4109        1542 :     delete psATInfo;
    4110             : }
    4111             : 
    4112             : /************************************************************************/
    4113             : /*                    GDALRefreshApproxTransformer()                    */
    4114             : /************************************************************************/
    4115             : 
    4116          46 : void GDALRefreshApproxTransformer(void *hTransformArg)
    4117             : {
    4118          46 :     GDALApproxTransformInfo *psInfo =
    4119             :         static_cast<GDALApproxTransformInfo *>(hTransformArg);
    4120             : 
    4121          46 :     if (GDALIsTransformer(psInfo->pBaseCBData,
    4122             :                           GDAL_GEN_IMG_TRANSFORMER_CLASS_NAME))
    4123             :     {
    4124          46 :         GDALRefreshGenImgProjTransformer(psInfo->pBaseCBData);
    4125             :     }
    4126          46 : }
    4127             : 
    4128             : /************************************************************************/
    4129             : /*                    GDALApproxTransformInternal()                     */
    4130             : /************************************************************************/
    4131             : 
    4132     1756560 : 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             : {
    4140     1756560 :     GDALApproxTransformInfo *psATInfo =
    4141             :         static_cast<GDALApproxTransformInfo *>(pCBData);
    4142     1756560 :     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     1756560 :     const double dfDeltaX =
    4184     1756560 :         (xSMETransformed[2] - xSMETransformed[0]) / (x[nPoints - 1] - x[0]);
    4185     1756560 :     const double dfDeltaY =
    4186     1756560 :         (ySMETransformed[2] - ySMETransformed[0]) / (x[nPoints - 1] - x[0]);
    4187     1756560 :     const double dfDeltaZ =
    4188     1756560 :         (zSMETransformed[2] - zSMETransformed[0]) / (x[nPoints - 1] - x[0]);
    4189             : 
    4190     1756560 :     const double dfError =
    4191     1756560 :         fabs((xSMETransformed[0] + dfDeltaX * (x[nMiddle] - x[0])) -
    4192     1756560 :              xSMETransformed[1]) +
    4193     1756560 :         fabs((ySMETransformed[0] + dfDeltaY * (x[nMiddle] - x[0])) -
    4194     1756560 :              ySMETransformed[1]);
    4195             : 
    4196     1756560 :     const double dfMaxError =
    4197     1756560 :         (bDstToSrc) ? psATInfo->dfMaxErrorReverse : psATInfo->dfMaxErrorForward;
    4198     1756560 :     if (dfError > dfMaxError)
    4199             :     {
    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      656578 :         double xMiddle[3] = {x[(nMiddle - 1) / 2], x[nMiddle - 1],
    4208      656578 :                              x[nMiddle + (nPoints - nMiddle - 1) / 2]};
    4209      656578 :         double yMiddle[3] = {y[(nMiddle - 1) / 2], y[nMiddle - 1],
    4210      656578 :                              y[nMiddle + (nPoints - nMiddle - 1) / 2]};
    4211      656578 :         double zMiddle[3] = {z[(nMiddle - 1) / 2], z[nMiddle - 1],
    4212      656578 :                              z[nMiddle + (nPoints - nMiddle - 1) / 2]};
    4213             : 
    4214      656578 :         const bool bUseBaseTransformForHalf1 =
    4215      525184 :             nMiddle <= 5 || y[0] != y[nMiddle - 1] ||
    4216     1706950 :             y[0] != y[(nMiddle - 1) / 2] || x[0] == x[nMiddle - 1] ||
    4217      525184 :             x[0] == x[(nMiddle - 1) / 2];
    4218      656578 :         const bool bUseBaseTransformForHalf2 =
    4219      539319 :             nPoints - nMiddle <= 5 || y[nMiddle] != y[nPoints - 1] ||
    4220      539319 :             y[nMiddle] != y[nMiddle + (nPoints - nMiddle - 1) / 2] ||
    4221     1735220 :             x[nMiddle] == x[nPoints - 1] ||
    4222      539319 :             x[nMiddle] == x[nMiddle + (nPoints - nMiddle - 1) / 2];
    4223             : 
    4224      656578 :         int anSuccess2[3] = {};
    4225      656578 :         int bSuccess = FALSE;
    4226      656578 :         if (!bUseBaseTransformForHalf1 && !bUseBaseTransformForHalf2)
    4227      525184 :             bSuccess = psATInfo->pfnBaseTransformer(
    4228             :                 psATInfo->pBaseCBData, bDstToSrc, 3, xMiddle, yMiddle, zMiddle,
    4229             :                 anSuccess2);
    4230      131394 :         else if (!bUseBaseTransformForHalf1)
    4231             :         {
    4232           0 :             bSuccess = psATInfo->pfnBaseTransformer(
    4233             :                 psATInfo->pBaseCBData, bDstToSrc, 2, xMiddle, yMiddle, zMiddle,
    4234             :                 anSuccess2);
    4235           0 :             anSuccess2[2] = TRUE;
    4236             :         }
    4237      131394 :         else if (!bUseBaseTransformForHalf2)
    4238             :         {
    4239       14135 :             bSuccess = psATInfo->pfnBaseTransformer(
    4240             :                 psATInfo->pBaseCBData, bDstToSrc, 1, xMiddle + 2, yMiddle + 2,
    4241             :                 zMiddle + 2, anSuccess2 + 2);
    4242       14135 :             anSuccess2[0] = TRUE;
    4243       14135 :             anSuccess2[1] = TRUE;
    4244             :         }
    4245             : 
    4246      656578 :         if (!bSuccess || !anSuccess2[0] || !anSuccess2[1] || !anSuccess2[2])
    4247             :         {
    4248      117275 :             bSuccess = psATInfo->pfnBaseTransformer(
    4249             :                 psATInfo->pBaseCBData, bDstToSrc, nMiddle - 1, x + 1, y + 1,
    4250             :                 z + 1, panSuccess + 1);
    4251      234550 :             bSuccess &= psATInfo->pfnBaseTransformer(
    4252      117275 :                 psATInfo->pBaseCBData, bDstToSrc, nPoints - nMiddle - 2,
    4253      117275 :                 x + nMiddle + 1, y + nMiddle + 1, z + nMiddle + 1,
    4254      117275 :                 panSuccess + nMiddle + 1);
    4255             : 
    4256      117275 :             x[0] = xSMETransformed[0];
    4257      117275 :             y[0] = ySMETransformed[0];
    4258      117275 :             z[0] = zSMETransformed[0];
    4259      117275 :             panSuccess[0] = TRUE;
    4260      117275 :             x[nMiddle] = xSMETransformed[1];
    4261      117275 :             y[nMiddle] = ySMETransformed[1];
    4262      117275 :             z[nMiddle] = zSMETransformed[1];
    4263      117275 :             panSuccess[nMiddle] = TRUE;
    4264      117275 :             x[nPoints - 1] = xSMETransformed[2];
    4265      117275 :             y[nPoints - 1] = ySMETransformed[2];
    4266      117275 :             z[nPoints - 1] = zSMETransformed[2];
    4267      117275 :             panSuccess[nPoints - 1] = TRUE;
    4268      117275 :             return bSuccess;
    4269             :         }
    4270             : 
    4271      539303 :         double x2[3] = {};
    4272      539303 :         double y2[3] = {};
    4273      539303 :         double z2[3] = {};
    4274      539303 :         if (!bUseBaseTransformForHalf1)
    4275             :         {
    4276      525168 :             x2[0] = xSMETransformed[0];
    4277      525168 :             y2[0] = ySMETransformed[0];
    4278      525168 :             z2[0] = zSMETransformed[0];
    4279      525168 :             x2[1] = xMiddle[0];
    4280      525168 :             y2[1] = yMiddle[0];
    4281      525168 :             z2[1] = zMiddle[0];
    4282      525168 :             x2[2] = xMiddle[1];
    4283      525168 :             y2[2] = yMiddle[1];
    4284      525168 :             z2[2] = zMiddle[1];
    4285             : 
    4286      525168 :             bSuccess = GDALApproxTransformInternal(
    4287             :                 psATInfo, bDstToSrc, nMiddle, x, y, z, panSuccess, x2, y2, z2);
    4288             :         }
    4289             :         else
    4290             :         {
    4291       14135 :             bSuccess = psATInfo->pfnBaseTransformer(
    4292             :                 psATInfo->pBaseCBData, bDstToSrc, nMiddle - 1, x + 1, y + 1,
    4293             :                 z + 1, panSuccess + 1);
    4294       14135 :             x[0] = xSMETransformed[0];
    4295       14135 :             y[0] = ySMETransformed[0];
    4296       14135 :             z[0] = zSMETransformed[0];
    4297       14135 :             panSuccess[0] = TRUE;
    4298             :         }
    4299             : 
    4300      539303 :         if (!bSuccess)
    4301          24 :             return FALSE;
    4302             : 
    4303      539279 :         if (!bUseBaseTransformForHalf2)
    4304             :         {
    4305      539279 :             x2[0] = xSMETransformed[1];
    4306      539279 :             y2[0] = ySMETransformed[1];
    4307      539279 :             z2[0] = zSMETransformed[1];
    4308      539279 :             x2[1] = xMiddle[2];
    4309      539279 :             y2[1] = yMiddle[2];
    4310      539279 :             z2[1] = zMiddle[2];
    4311      539279 :             x2[2] = xSMETransformed[2];
    4312      539279 :             y2[2] = ySMETransformed[2];
    4313      539279 :             z2[2] = zSMETransformed[2];
    4314             : 
    4315      539279 :             bSuccess = GDALApproxTransformInternal(
    4316      539279 :                 psATInfo, bDstToSrc, nPoints - nMiddle, x + nMiddle,
    4317      539279 :                 y + nMiddle, z + nMiddle, panSuccess + nMiddle, x2, y2, z2);
    4318             :         }
    4319             :         else
    4320             :         {
    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             :         }
    4335             : 
    4336      539279 :         if (!bSuccess)
    4337           2 :             return FALSE;
    4338             : 
    4339      539277 :         return TRUE;
    4340             :     }
    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   260086000 :     for (int i = nPoints - 1; i >= 0; i--)
    4352             :     {
    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   258986000 :         const double dfDist = (x[i] - x[0]);
    4364   258986000 :         x[i] = xSMETransformed[0] + dfDeltaX * dfDist;
    4365   258986000 :         y[i] = ySMETransformed[0] + dfDeltaY * dfDist;
    4366   258986000 :         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   258986000 :         panSuccess[i] = TRUE;
    4375             :     }
    4376             : 
    4377     1099980 :     return TRUE;
    4378             : }
    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    10317800 : int GDALApproxTransform(void *pCBData, int bDstToSrc, int nPoints, double *x,
    4394             :                         double *y, double *z, int *panSuccess)
    4395             : 
    4396             : {
    4397    10317800 :     GDALApproxTransformInfo *psATInfo =
    4398             :         static_cast<GDALApproxTransformInfo *>(pCBData);
    4399    10317800 :     double x2[3] = {};
    4400    10317800 :     double y2[3] = {};
    4401    10317800 :     double z2[3] = {};
    4402    10317800 :     int anSuccess2[3] = {};
    4403             :     int bSuccess;
    4404             : 
    4405    10317800 :     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    10317800 :     int bRet = FALSE;
    4412    10317800 :     if (y[0] != y[nPoints - 1] || y[0] != y[nMiddle] ||
    4413    10301400 :         x[0] == x[nPoints - 1] || x[0] == x[nMiddle] ||
    4414      698445 :         (psATInfo->dfMaxErrorForward == 0.0 &&
    4415      698445 :          psATInfo->dfMaxErrorReverse == 0.0) ||
    4416             :         nPoints <= 5)
    4417             :     {
    4418     9620170 :         bRet = psATInfo->pfnBaseTransformer(psATInfo->pBaseCBData, bDstToSrc,
    4419             :                                             nPoints, x, y, z, panSuccess);
    4420     9620170 :         goto end;
    4421             :     }
    4422             : 
    4423             :     /* -------------------------------------------------------------------- */
    4424             :     /*      Transform first, last and middle point.                         */
    4425             :     /* -------------------------------------------------------------------- */
    4426      697668 :     x2[0] = x[0];
    4427      697668 :     y2[0] = y[0];
    4428      697668 :     z2[0] = z[0];
    4429      697668 :     x2[1] = x[nMiddle];
    4430      697668 :     y2[1] = y[nMiddle];
    4431      697668 :     z2[1] = z[nMiddle];
    4432      697668 :     x2[2] = x[nPoints - 1];
    4433      697668 :     y2[2] = y[nPoints - 1];
    4434      697668 :     z2[2] = z[nPoints - 1];
    4435             : 
    4436      697668 :     bSuccess = psATInfo->pfnBaseTransformer(psATInfo->pBaseCBData, bDstToSrc, 3,
    4437             :                                             x2, y2, z2, anSuccess2);
    4438      697668 :     if (!bSuccess || !anSuccess2[0] || !anSuccess2[1] || !anSuccess2[2])
    4439             :     {
    4440        5560 :         bRet = psATInfo->pfnBaseTransformer(psATInfo->pBaseCBData, bDstToSrc,
    4441             :                                             nPoints, x, y, z, panSuccess);
    4442        5560 :         goto end;
    4443             :     }
    4444             : 
    4445      692108 :     bRet = GDALApproxTransformInternal(pCBData, bDstToSrc, nPoints, x, y, z,
    4446             :                                        panSuccess, x2, y2, z2);
    4447             : 
    4448    10317800 : 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    10317800 :     return bRet;
    4456             : }
    4457             : 
    4458             : /************************************************************************/
    4459             : /*                  GDALDeserializeApproxTransformer()                  */
    4460             : /************************************************************************/
    4461             : 
    4462         107 : static void *GDALDeserializeApproxTransformer(CPLXMLNode *psTree)
    4463             : 
    4464             : {
    4465         107 :     double dfMaxErrorForward = 0.25;
    4466         107 :     double dfMaxErrorReverse = 0.25;
    4467         107 :     const char *pszMaxError = CPLGetXMLValue(psTree, "MaxError", nullptr);
    4468         107 :     if (pszMaxError != nullptr)
    4469             :     {
    4470         105 :         dfMaxErrorForward = CPLAtof(pszMaxError);
    4471         105 :         dfMaxErrorReverse = dfMaxErrorForward;
    4472             :     }
    4473             :     const char *pszMaxErrorForward =
    4474         107 :         CPLGetXMLValue(psTree, "MaxErrorForward", nullptr);
    4475         107 :     if (pszMaxErrorForward != nullptr)
    4476             :     {
    4477           2 :         dfMaxErrorForward = CPLAtof(pszMaxErrorForward);
    4478             :     }
    4479             :     const char *pszMaxErrorReverse =
    4480         107 :         CPLGetXMLValue(psTree, "MaxErrorReverse", nullptr);
    4481         107 :     if (pszMaxErrorReverse != nullptr)
    4482             :     {
    4483           2 :         dfMaxErrorReverse = CPLAtof(pszMaxErrorReverse);
    4484             :     }
    4485             : 
    4486         107 :     GDALTransformerFunc pfnBaseTransform = nullptr;
    4487         107 :     void *pBaseCBData = nullptr;
    4488             : 
    4489         107 :     CPLXMLNode *psContainer = CPLGetXMLNode(psTree, "BaseTransformer");
    4490             : 
    4491         107 :     if (psContainer != nullptr && psContainer->psChild != nullptr)
    4492             :     {
    4493         107 :         GDALDeserializeTransformer(psContainer->psChild, &pfnBaseTransform,
    4494             :                                    &pBaseCBData);
    4495             :     }
    4496             : 
    4497         107 :     if (pfnBaseTransform == nullptr)
    4498             :     {
    4499           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    4500             :                  "Cannot get base transform for approx transformer.");
    4501           0 :         return nullptr;
    4502             :     }
    4503             : 
    4504         107 :     void *pApproxCBData = GDALCreateApproxTransformer2(
    4505             :         pfnBaseTransform, pBaseCBData, dfMaxErrorForward, dfMaxErrorReverse);
    4506         107 :     GDALApproxTransformerOwnsSubtransformer(pApproxCBData, TRUE);
    4507             : 
    4508         107 :     return pApproxCBData;
    4509             : }
    4510             : 
    4511             : /************************************************************************/
    4512             : /*             GDALTransformLonLatToDestApproxTransformer()             */
    4513             : /************************************************************************/
    4514             : 
    4515        3048 : int GDALTransformLonLatToDestApproxTransformer(void *hTransformArg,
    4516             :                                                double *pdfX, double *pdfY)
    4517             : {
    4518        3048 :     GDALApproxTransformInfo *psInfo =
    4519             :         static_cast<GDALApproxTransformInfo *>(hTransformArg);
    4520             : 
    4521        3048 :     if (GDALIsTransformer(psInfo->pBaseCBData,
    4522             :                           GDAL_GEN_IMG_TRANSFORMER_CLASS_NAME))
    4523             :     {
    4524        3048 :         return GDALTransformLonLatToDestGenImgProjTransformer(
    4525        3048 :             psInfo->pBaseCBData, pdfX, pdfY);
    4526             :     }
    4527           0 :     return false;
    4528             : }
    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      429564 : void CPL_STDCALL GDALApplyGeoTransform(const double *padfGeoTransform,
    4556             :                                        double dfPixel, double dfLine,
    4557             :                                        double *pdfGeoX, double *pdfGeoY)
    4558             : {
    4559      429564 :     *pdfGeoX = padfGeoTransform[0] + dfPixel * padfGeoTransform[1] +
    4560      429564 :                dfLine * padfGeoTransform[2];
    4561      429564 :     *pdfGeoY = padfGeoTransform[3] + dfPixel * padfGeoTransform[4] +
    4562      429564 :                dfLine * padfGeoTransform[5];
    4563      429564 : }
    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        4429 : int CPL_STDCALL GDALInvGeoTransform(const double *gt_in, double *gt_out)
    4582             : 
    4583             : {
    4584             :     // Special case - no rotation - to avoid computing determinate
    4585             :     // and potential precision issues.
    4586        4429 :     if (gt_in[2] == 0.0 && gt_in[4] == 0.0 && gt_in[1] != 0.0 &&
    4587        4369 :         gt_in[5] != 0.0)
    4588             :     {
    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        4369 :         gt_out[0] = -gt_in[0] / gt_in[1];
    4596        4369 :         gt_out[1] = 1.0 / gt_in[1];
    4597        4369 :         gt_out[2] = 0.0;
    4598        4369 :         gt_out[3] = -gt_in[3] / gt_in[5];
    4599        4369 :         gt_out[4] = 0.0;
    4600        4369 :         gt_out[5] = 1.0 / gt_in[5];
    4601        4369 :         return 1;
    4602             :     }
    4603             : 
    4604             :     // Assume a 3rd row that is [1 0 0].
    4605             : 
    4606             :     // Compute determinate.
    4607             : 
    4608          60 :     const double det = gt_in[1] * gt_in[5] - gt_in[2] * gt_in[4];
    4609         120 :     const double magnitude = std::max(std::max(fabs(gt_in[1]), fabs(gt_in[2])),
    4610          60 :                                       std::max(fabs(gt_in[4]), fabs(gt_in[5])));
    4611             : 
    4612          60 :     if (fabs(det) <= 1e-10 * magnitude * magnitude)
    4613           9 :         return 0;
    4614             : 
    4615          51 :     const double inv_det = 1.0 / det;
    4616             : 
    4617             :     // Compute adjoint, and divide by determinate.
    4618             : 
    4619          51 :     gt_out[1] = gt_in[5] * inv_det;
    4620          51 :     gt_out[4] = -gt_in[4] * inv_det;
    4621             : 
    4622          51 :     gt_out[2] = -gt_in[2] * inv_det;
    4623          51 :     gt_out[5] = gt_in[1] * inv_det;
    4624             : 
    4625          51 :     gt_out[0] = (gt_in[2] * gt_in[3] - gt_in[0] * gt_in[5]) * inv_det;
    4626          51 :     gt_out[3] = (-gt_in[1] * gt_in[3] + gt_in[0] * gt_in[4]) * inv_det;
    4627             : 
    4628          51 :     return 1;
    4629             : }
    4630             : 
    4631             : /************************************************************************/
    4632             : /*                      GDALSerializeTransformer()                      */
    4633             : /************************************************************************/
    4634             : 
    4635         167 : CPLXMLNode *GDALSerializeTransformer(GDALTransformerFunc /* pfnFunc */,
    4636             :                                      void *pTransformArg)
    4637             : {
    4638         167 :     VALIDATE_POINTER1(pTransformArg, "GDALSerializeTransformer", nullptr);
    4639             : 
    4640         167 :     GDALTransformerInfo *psInfo =
    4641             :         static_cast<GDALTransformerInfo *>(pTransformArg);
    4642             : 
    4643         167 :     if (psInfo == nullptr || memcmp(psInfo->abySignature, GDAL_GTI2_SIGNATURE,
    4644             :                                     strlen(GDAL_GTI2_SIGNATURE)) != 0)
    4645             :     {
    4646           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    4647             :                  "Attempt to serialize non-GTI2 transformer.");
    4648           0 :         return nullptr;
    4649             :     }
    4650         167 :     else if (psInfo->pfnSerialize == nullptr)
    4651             :     {
    4652           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    4653             :                  "No serialization function available for this transformer.");
    4654           0 :         return nullptr;
    4655             :     }
    4656             : 
    4657         167 :     return psInfo->pfnSerialize(pTransformArg);
    4658             : }
    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           0 : void *GDALRegisterTransformDeserializer(
    4675             :     const char *pszTransformName, GDALTransformerFunc pfnTransformerFunc,
    4676             :     GDALTransformDeserializeFunc pfnDeserializeFunc)
    4677             : {
    4678             :     TransformDeserializerInfo *psInfo =
    4679             :         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             : }
    4690             : 
    4691             : /************************************************************************/
    4692             : /*                GDALUnregisterTransformDeserializer()                 */
    4693             : /************************************************************************/
    4694             : 
    4695           0 : void GDALUnregisterTransformDeserializer(void *pData)
    4696             : {
    4697           0 :     CPLMutexHolderD(&hDeserializerMutex);
    4698           0 :     CPLList *psList = psListDeserializer;
    4699           0 :     CPLList *psLast = nullptr;
    4700           0 :     while (psList)
    4701             :     {
    4702           0 :         if (psList->pData == pData)
    4703             :         {
    4704           0 :             TransformDeserializerInfo *psInfo =
    4705             :                 static_cast<TransformDeserializerInfo *>(pData);
    4706           0 :             CPLFree(psInfo->pszTransformName);
    4707           0 :             CPLFree(pData);
    4708           0 :             if (psLast)
    4709           0 :                 psLast->psNext = psList->psNext;
    4710             :             else
    4711           0 :                 psListDeserializer = nullptr;
    4712           0 :             CPLFree(psList);
    4713           0 :             break;
    4714             :         }
    4715           0 :         psLast = psList;
    4716           0 :         psList = psList->psNext;
    4717             :     }
    4718           0 : }
    4719             : 
    4720             : /************************************************************************/
    4721             : /*                GDALUnregisterTransformDeserializer()                 */
    4722             : /************************************************************************/
    4723             : 
    4724        1227 : void GDALCleanupTransformDeserializerMutex()
    4725             : {
    4726        1227 :     if (hDeserializerMutex != nullptr)
    4727             :     {
    4728           0 :         CPLDestroyMutex(hDeserializerMutex);
    4729           0 :         hDeserializerMutex = nullptr;
    4730             :     }
    4731        1227 : }
    4732             : 
    4733             : /************************************************************************/
    4734             : /*                     GDALDeserializeTransformer()                     */
    4735             : /************************************************************************/
    4736             : 
    4737         424 : CPLErr GDALDeserializeTransformer(CPLXMLNode *psTree,
    4738             :                                   GDALTransformerFunc *ppfnFunc,
    4739             :                                   void **ppTransformArg)
    4740             : 
    4741             : {
    4742         424 :     *ppfnFunc = nullptr;
    4743         424 :     *ppTransformArg = nullptr;
    4744             : 
    4745         424 :     CPLErrorReset();
    4746             : 
    4747         424 :     if (psTree == nullptr || psTree->eType != CXT_Element)
    4748           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    4749             :                  "Malformed element in GDALDeserializeTransformer");
    4750         424 :     else if (EQUAL(psTree->pszValue, "GenImgProjTransformer"))
    4751             :     {
    4752         147 :         *ppfnFunc = GDALGenImgProjTransform;
    4753         147 :         *ppTransformArg = GDALDeserializeGenImgProjTransformer(psTree);
    4754             :     }
    4755         277 :     else if (EQUAL(psTree->pszValue, "ReprojectionTransformer"))
    4756             :     {
    4757         157 :         *ppfnFunc = GDALReprojectionTransform;
    4758         157 :         *ppTransformArg = GDALDeserializeReprojectionTransformer(psTree);
    4759             :     }
    4760         120 :     else if (EQUAL(psTree->pszValue, "GCPTransformer"))
    4761             :     {
    4762           5 :         *ppfnFunc = GDALGCPTransform;
    4763           5 :         *ppTransformArg = GDALDeserializeGCPTransformer(psTree);
    4764             :     }
    4765         115 :     else if (EQUAL(psTree->pszValue, "TPSTransformer"))
    4766             :     {
    4767           3 :         *ppfnFunc = GDALTPSTransform;
    4768           3 :         *ppTransformArg = GDALDeserializeTPSTransformer(psTree);
    4769             :     }
    4770         112 :     else if (EQUAL(psTree->pszValue, "GeoLocTransformer"))
    4771             :     {
    4772           1 :         *ppfnFunc = GDALGeoLocTransform;
    4773           1 :         *ppTransformArg = GDALDeserializeGeoLocTransformer(psTree);
    4774             :     }
    4775         111 :     else if (EQUAL(psTree->pszValue, "RPCTransformer"))
    4776             :     {
    4777           0 :         *ppfnFunc = GDALRPCTransform;
    4778           0 :         *ppTransformArg = GDALDeserializeRPCTransformer(psTree);
    4779             :     }
    4780         111 :     else if (EQUAL(psTree->pszValue, "ApproxTransformer"))
    4781             :     {
    4782         107 :         *ppfnFunc = GDALApproxTransform;
    4783         107 :         *ppTransformArg = GDALDeserializeApproxTransformer(psTree);
    4784             :     }
    4785           4 :     else if (EQUAL(psTree->pszValue, "HomographyTransformer"))
    4786             :     {
    4787           4 :         *ppfnFunc = GDALHomographyTransform;
    4788           4 :         *ppTransformArg = GDALDeserializeHomographyTransformer(psTree);
    4789             :     }
    4790             :     else
    4791             :     {
    4792           0 :         GDALTransformDeserializeFunc pfnDeserializeFunc = nullptr;
    4793             :         {
    4794           0 :             CPLMutexHolderD(&hDeserializerMutex);
    4795           0 :             CPLList *psList = psListDeserializer;
    4796           0 :             while (psList)
    4797             :             {
    4798           0 :                 TransformDeserializerInfo *psInfo =
    4799             :                     static_cast<TransformDeserializerInfo *>(psList->pData);
    4800           0 :                 if (strcmp(psInfo->pszTransformName, psTree->pszValue) == 0)
    4801             :                 {
    4802           0 :                     *ppfnFunc = psInfo->pfnTransformerFunc;
    4803           0 :                     pfnDeserializeFunc = psInfo->pfnDeserializeFunc;
    4804           0 :                     break;
    4805             :                 }
    4806           0 :                 psList = psList->psNext;
    4807             :             }
    4808             :         }
    4809             : 
    4810           0 :         if (pfnDeserializeFunc != nullptr)
    4811             :         {
    4812           0 :             *ppTransformArg = pfnDeserializeFunc(psTree);
    4813             :         }
    4814             :         else
    4815             :         {
    4816           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    4817             :                      "Unrecognized element '%s' GDALDeserializeTransformer",
    4818             :                      psTree->pszValue);
    4819             :         }
    4820             :     }
    4821             : 
    4822         424 :     return CPLGetLastErrorType();
    4823             : }
    4824             : 
    4825             : /************************************************************************/
    4826             : /*                       GDALDestroyTransformer()                       */
    4827             : /************************************************************************/
    4828             : 
    4829        5373 : void GDALDestroyTransformer(void *pTransformArg)
    4830             : 
    4831             : {
    4832        5373 :     if (pTransformArg == nullptr)
    4833           0 :         return;
    4834             : 
    4835        5373 :     GDALTransformerInfo *psInfo =
    4836             :         static_cast<GDALTransformerInfo *>(pTransformArg);
    4837             : 
    4838        5373 :     if (memcmp(psInfo->abySignature, GDAL_GTI2_SIGNATURE,
    4839             :                strlen(GDAL_GTI2_SIGNATURE)) != 0)
    4840             :     {
    4841           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    4842             :                  "Attempt to destroy non-GTI2 transformer.");
    4843           0 :         return;
    4844             :     }
    4845             : 
    4846        5373 :     psInfo->pfnCleanup(pTransformArg);
    4847             : }
    4848             : 
    4849             : /************************************************************************/
    4850             : /*                         GDALUseTransformer()                         */
    4851             : /************************************************************************/
    4852             : 
    4853        8683 : int GDALUseTransformer(void *pTransformArg, int bDstToSrc, int nPointCount,
    4854             :                        double *x, double *y, double *z, int *panSuccess)
    4855             : {
    4856        8683 :     GDALTransformerInfo *psInfo =
    4857             :         static_cast<GDALTransformerInfo *>(pTransformArg);
    4858             : 
    4859        8683 :     if (psInfo == nullptr || memcmp(psInfo->abySignature, GDAL_GTI2_SIGNATURE,
    4860             :                                     strlen(GDAL_GTI2_SIGNATURE)) != 0)
    4861             :     {
    4862           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    4863             :                  "Attempt to use non-GTI2 transformer.");
    4864           0 :         return FALSE;
    4865             :     }
    4866             : 
    4867        8683 :     return psInfo->pfnTransform(pTransformArg, bDstToSrc, nPointCount, x, y, z,
    4868        8683 :                                 panSuccess);
    4869             : }
    4870             : 
    4871             : /************************************************************************/
    4872             : /*                        GDALCloneTransformer()                        */
    4873             : /************************************************************************/
    4874             : 
    4875          79 : void *GDALCloneTransformer(void *pTransformArg)
    4876             : {
    4877          79 :     GDALTransformerInfo *psInfo =
    4878             :         static_cast<GDALTransformerInfo *>(pTransformArg);
    4879             : 
    4880          79 :     if (psInfo == nullptr || memcmp(psInfo->abySignature, GDAL_GTI2_SIGNATURE,
    4881             :                                     strlen(GDAL_GTI2_SIGNATURE)) != 0)
    4882             :     {
    4883           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    4884             :                  "Attempt to clone non-GTI2 transformer.");
    4885           0 :         return nullptr;
    4886             :     }
    4887             : 
    4888          79 :     if (psInfo->pfnCreateSimilar != nullptr)
    4889             :     {
    4890          40 :         return psInfo->pfnCreateSimilar(psInfo, 1.0, 1.0);
    4891             :     }
    4892             : 
    4893          39 :     if (psInfo->pfnSerialize == nullptr)
    4894             :     {
    4895           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    4896             :                  "No serialization function available for this transformer.");
    4897           0 :         return nullptr;
    4898             :     }
    4899             : 
    4900          39 :     CPLXMLNode *pSerialized = psInfo->pfnSerialize(pTransformArg);
    4901          39 :     if (pSerialized == nullptr)
    4902           0 :         return nullptr;
    4903          39 :     GDALTransformerFunc pfnTransformer = nullptr;
    4904          39 :     void *pClonedTransformArg = nullptr;
    4905          39 :     if (GDALDeserializeTransformer(pSerialized, &pfnTransformer,
    4906          39 :                                    &pClonedTransformArg) != CE_None)
    4907             :     {
    4908           0 :         CPLDestroyXMLNode(pSerialized);
    4909           0 :         CPLFree(pClonedTransformArg);
    4910           0 :         return nullptr;
    4911             :     }
    4912             : 
    4913          39 :     CPLDestroyXMLNode(pSerialized);
    4914          39 :     return pClonedTransformArg;
    4915             : }
    4916             : 
    4917             : /************************************************************************/
    4918             : /*                    GDALCreateSimilarTransformer()                    */
    4919             : /************************************************************************/
    4920             : 
    4921          75 : void *GDALCreateSimilarTransformer(void *pTransformArg, double dfRatioX,
    4922             :                                    double dfRatioY)
    4923             : {
    4924          75 :     GDALTransformerInfo *psInfo =
    4925             :         static_cast<GDALTransformerInfo *>(pTransformArg);
    4926             : 
    4927          75 :     if (psInfo == nullptr || memcmp(psInfo->abySignature, GDAL_GTI2_SIGNATURE,
    4928             :                                     strlen(GDAL_GTI2_SIGNATURE)) != 0)
    4929             :     {
    4930           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    4931             :                  "Attempt to call CreateSimilar on a non-GTI2 transformer.");
    4932           0 :         return nullptr;
    4933             :     }
    4934             : 
    4935          75 :     if (psInfo->pfnCreateSimilar == nullptr)
    4936             :     {
    4937           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    4938             :                  "No CreateSimilar function available for this transformer.");
    4939           0 :         return nullptr;
    4940             :     }
    4941             : 
    4942          75 :     return psInfo->pfnCreateSimilar(psInfo, dfRatioX, dfRatioY);
    4943             : }
    4944             : 
    4945             : /************************************************************************/
    4946             : /*                     GetGenImgProjTransformInfo()                     */
    4947             : /************************************************************************/
    4948             : 
    4949          46 : static GDALTransformerInfo *GetGenImgProjTransformInfo(const char *pszFunc,
    4950             :                                                        void *pTransformArg)
    4951             : {
    4952          46 :     GDALTransformerInfo *psInfo =
    4953             :         static_cast<GDALTransformerInfo *>(pTransformArg);
    4954             : 
    4955          46 :     if (psInfo == nullptr || memcmp(psInfo->abySignature, GDAL_GTI2_SIGNATURE,
    4956             :                                     strlen(GDAL_GTI2_SIGNATURE)) != 0)
    4957             :     {
    4958           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    4959             :                  "Attempt to call %s on "
    4960             :                  "a non-GTI2 transformer.",
    4961             :                  pszFunc);
    4962           0 :         return nullptr;
    4963             :     }
    4964             : 
    4965          46 :     if (EQUAL(psInfo->pszClassName, GDAL_APPROX_TRANSFORMER_CLASS_NAME))
    4966             :     {
    4967          16 :         GDALApproxTransformInfo *psATInfo =
    4968             :             static_cast<GDALApproxTransformInfo *>(pTransformArg);
    4969          16 :         psInfo = static_cast<GDALTransformerInfo *>(psATInfo->pBaseCBData);
    4970             : 
    4971          16 :         if (psInfo == nullptr ||
    4972          16 :             memcmp(psInfo->abySignature, GDAL_GTI2_SIGNATURE,
    4973             :                    strlen(GDAL_GTI2_SIGNATURE)) != 0)
    4974             :         {
    4975           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    4976             :                      "Attempt to call %s on "
    4977             :                      "a non-GTI2 transformer.",
    4978             :                      pszFunc);
    4979           0 :             return nullptr;
    4980             :         }
    4981             :     }
    4982             : 
    4983          46 :     if (EQUAL(psInfo->pszClassName, GDAL_GEN_IMG_TRANSFORMER_CLASS_NAME))
    4984             :     {
    4985          46 :         return psInfo;
    4986             :     }
    4987             : 
    4988           0 :     return nullptr;
    4989             : }
    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          23 : void GDALSetTransformerDstGeoTransform(void *pTransformArg,
    5015             :                                        const double *padfGeoTransform)
    5016             : {
    5017          23 :     VALIDATE_POINTER0(pTransformArg, "GDALSetTransformerDstGeoTransform");
    5018             : 
    5019          23 :     GDALTransformerInfo *psInfo = GetGenImgProjTransformInfo(
    5020             :         "GDALSetTransformerDstGeoTransform", pTransformArg);
    5021          23 :     if (psInfo)
    5022             :     {
    5023          23 :         GDALSetGenImgProjTransformerDstGeoTransform(psInfo, padfGeoTransform);
    5024             :     }
    5025             : }
    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          23 : void GDALGetTransformerDstGeoTransform(void *pTransformArg,
    5040             :                                        double *padfGeoTransform)
    5041             : {
    5042          23 :     VALIDATE_POINTER0(pTransformArg, "GDALGetTransformerDstGeoTransform");
    5043             : 
    5044          23 :     GDALTransformerInfo *psInfo = GetGenImgProjTransformInfo(
    5045             :         "GDALGetTransformerDstGeoTransform", pTransformArg);
    5046          23 :     if (psInfo)
    5047             :     {
    5048          23 :         GDALGenImgProjTransformInfo *psGenImgProjInfo =
    5049             :             reinterpret_cast<GDALGenImgProjTransformInfo *>(psInfo);
    5050             : 
    5051          23 :         memcpy(padfGeoTransform, psGenImgProjInfo->sDstParams.adfGeoTransform,
    5052             :                sizeof(double) * 6);
    5053             :     }
    5054             : }
    5055             : 
    5056             : /************************************************************************/
    5057             : /*            GDALTransformIsTranslationOnPixelBoundaries()             */
    5058             : /************************************************************************/
    5059             : 
    5060        1834 : bool GDALTransformIsTranslationOnPixelBoundaries(GDALTransformerFunc,
    5061             :                                                  void *pTransformerArg)
    5062             : {
    5063        1834 :     if (GDALIsTransformer(pTransformerArg, GDAL_APPROX_TRANSFORMER_CLASS_NAME))
    5064             :     {
    5065        1524 :         const auto *pApproxInfo =
    5066             :             static_cast<const GDALApproxTransformInfo *>(pTransformerArg);
    5067        1524 :         pTransformerArg = pApproxInfo->pBaseCBData;
    5068             :     }
    5069        1834 :     if (GDALIsTransformer(pTransformerArg, GDAL_GEN_IMG_TRANSFORMER_CLASS_NAME))
    5070             :     {
    5071        1681 :         const auto *pGenImgpProjInfo =
    5072             :             static_cast<GDALGenImgProjTransformInfo *>(pTransformerArg);
    5073         562 :         const auto IsCloseToInteger = [](double dfVal)
    5074         562 :         { return std::fabs(dfVal - std::round(dfVal)) <= 1e-6; };
    5075        3278 :         return pGenImgpProjInfo->sSrcParams.pTransformArg == nullptr &&
    5076        1597 :                pGenImgpProjInfo->sDstParams.pTransformArg == nullptr &&
    5077        1595 :                pGenImgpProjInfo->pReproject == nullptr &&
    5078         686 :                pGenImgpProjInfo->sSrcParams.adfGeoTransform[1] ==
    5079         686 :                    pGenImgpProjInfo->sDstParams.adfGeoTransform[1] &&
    5080         417 :                pGenImgpProjInfo->sSrcParams.adfGeoTransform[5] ==
    5081         417 :                    pGenImgpProjInfo->sDstParams.adfGeoTransform[5] &&
    5082         299 :                pGenImgpProjInfo->sSrcParams.adfGeoTransform[2] ==
    5083         299 :                    pGenImgpProjInfo->sDstParams.adfGeoTransform[2] &&
    5084         299 :                pGenImgpProjInfo->sSrcParams.adfGeoTransform[4] ==
    5085         598 :                    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         299 :                IsCloseToInteger(
    5090         299 :                    pGenImgpProjInfo->sSrcParams.adfInvGeoTransform[0] +
    5091         299 :                    pGenImgpProjInfo->sDstParams.adfGeoTransform[0] *
    5092         299 :                        pGenImgpProjInfo->sSrcParams.adfInvGeoTransform[1] +
    5093         299 :                    pGenImgpProjInfo->sDstParams.adfGeoTransform[3] *
    5094        3577 :                        pGenImgpProjInfo->sSrcParams.adfInvGeoTransform[2]) &&
    5095         263 :                IsCloseToInteger(
    5096         263 :                    pGenImgpProjInfo->sSrcParams.adfInvGeoTransform[3] +
    5097         263 :                    pGenImgpProjInfo->sDstParams.adfGeoTransform[0] *
    5098         263 :                        pGenImgpProjInfo->sSrcParams.adfInvGeoTransform[4] +
    5099         263 :                    pGenImgpProjInfo->sDstParams.adfGeoTransform[3] *
    5100        1944 :                        pGenImgpProjInfo->sSrcParams.adfInvGeoTransform[5]);
    5101             :     }
    5102         153 :     return false;
    5103             : }
    5104             : 
    5105             : /************************************************************************/
    5106             : /*                  GDALTransformIsAffineNoRotation()                   */
    5107             : /************************************************************************/
    5108             : 
    5109          19 : bool GDALTransformIsAffineNoRotation(GDALTransformerFunc, void *pTransformerArg)
    5110             : {
    5111          19 :     if (GDALIsTransformer(pTransformerArg, GDAL_APPROX_TRANSFORMER_CLASS_NAME))
    5112             :     {
    5113          19 :         const auto *pApproxInfo =
    5114             :             static_cast<const GDALApproxTransformInfo *>(pTransformerArg);
    5115          19 :         pTransformerArg = pApproxInfo->pBaseCBData;
    5116             :     }
    5117          19 :     if (GDALIsTransformer(pTransformerArg, GDAL_GEN_IMG_TRANSFORMER_CLASS_NAME))
    5118             :     {
    5119          19 :         const auto *pGenImgpProjInfo =
    5120             :             static_cast<GDALGenImgProjTransformInfo *>(pTransformerArg);
    5121          38 :         return pGenImgpProjInfo->sSrcParams.pTransformArg == nullptr &&
    5122          19 :                pGenImgpProjInfo->sDstParams.pTransformArg == nullptr &&
    5123          19 :                pGenImgpProjInfo->pReproject == nullptr &&
    5124           9 :                pGenImgpProjInfo->sSrcParams.adfGeoTransform[2] == 0 &&
    5125           9 :                pGenImgpProjInfo->sSrcParams.adfGeoTransform[4] == 0 &&
    5126          47 :                pGenImgpProjInfo->sDstParams.adfGeoTransform[2] == 0 &&
    5127          28 :                pGenImgpProjInfo->sDstParams.adfGeoTransform[4] == 0;
    5128             :     }
    5129           0 :     return false;
    5130             : }
    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           2 : bool GDALTransformHasFastClone(void *pTransformerArg)
    5141             : {
    5142           2 :     if (GDALIsTransformer(pTransformerArg, GDAL_APPROX_TRANSFORMER_CLASS_NAME))
    5143             :     {
    5144           1 :         const auto *pApproxInfo =
    5145             :             static_cast<const GDALApproxTransformInfo *>(pTransformerArg);
    5146           1 :         pTransformerArg = pApproxInfo->pBaseCBData;
    5147             :         // Fallback to next lines
    5148             :     }
    5149             : 
    5150           2 :     if (GDALIsTransformer(pTransformerArg, GDAL_GEN_IMG_TRANSFORMER_CLASS_NAME))
    5151             :     {
    5152           2 :         const auto *pGenImgpProjInfo =
    5153             :             static_cast<GDALGenImgProjTransformInfo *>(pTransformerArg);
    5154           2 :         return (pGenImgpProjInfo->sSrcParams.pTransformArg == nullptr ||
    5155           0 :                 GDALTransformHasFastClone(
    5156           4 :                     pGenImgpProjInfo->sSrcParams.pTransformArg)) &&
    5157           2 :                (pGenImgpProjInfo->sDstParams.pTransformArg == nullptr ||
    5158           0 :                 GDALTransformHasFastClone(
    5159           2 :                     pGenImgpProjInfo->sDstParams.pTransformArg));
    5160             :     }
    5161           0 :     else if (GDALIsTransformer(pTransformerArg,
    5162             :                                GDAL_RPC_TRANSFORMER_CLASS_NAME))
    5163             :     {
    5164           0 :         return true;
    5165             :     }
    5166             :     else
    5167             :     {
    5168           0 :         return false;
    5169             :     }
    5170             : }

Generated by: LCOV version 1.14