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

Generated by: LCOV version 1.14