LCOV - code coverage report
Current view: top level - alg - gdaltransformer.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 1672 1988 84.1 %
Date: 2025-01-18 12:42:00 Functions: 57 61 93.4 %

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

Generated by: LCOV version 1.14