LCOV - code coverage report
Current view: top level - alg - gdal_rpc.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 742 905 82.0 %
Date: 2025-01-18 12:42:00 Functions: 15 19 78.9 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  Image Warper
       4             :  * Purpose:  Implements a rational polynomial (RPC) based transformer.
       5             :  * Author:   Frank Warmerdam, warmerdam@pobox.com
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2003, Frank Warmerdam <warmerdam@pobox.com>
       9             :  * Copyright (c) 2009-2013, Even Rouault <even dot rouault at spatialys.com>
      10             :  *
      11             :  * SPDX-License-Identifier: MIT
      12             :  ****************************************************************************/
      13             : 
      14             : #include "cpl_port.h"
      15             : #include "gdal_alg.h"
      16             : 
      17             : #include <cmath>
      18             : #include <cstddef>
      19             : #include <cstdlib>
      20             : #include <cstring>
      21             : 
      22             : #include <algorithm>
      23             : #include <limits>
      24             : #include <string>
      25             : 
      26             : #include "cpl_conv.h"
      27             : #include "cpl_error.h"
      28             : #include "cpl_mem_cache.h"
      29             : #include "cpl_minixml.h"
      30             : #include "cpl_string.h"
      31             : #include "cpl_vsi.h"
      32             : #include "gdal.h"
      33             : #include "gdal_interpolateatpoint.h"
      34             : #include "gdal_mdreader.h"
      35             : #include "gdal_alg_priv.h"
      36             : #include "gdal_priv.h"
      37             : 
      38             : #ifdef USE_NEON_OPTIMIZATIONS
      39             : #define USE_SSE2
      40             : #elif defined(__x86_64) || defined(_M_X64)
      41             : #define USE_SSE2
      42             : #endif
      43             : 
      44             : #ifdef USE_SSE2
      45             : #include "gdalsse_priv.h"
      46             : #define USE_SSE2_OPTIM
      47             : #endif
      48             : 
      49             : #include "ogr_api.h"
      50             : #include "ogr_geometry.h"
      51             : #include "ogr_spatialref.h"
      52             : #include "ogr_srs_api.h"
      53             : #include "gdalresamplingkernels.h"
      54             : 
      55             : // #define DEBUG_VERBOSE_EXTRACT_DEM
      56             : 
      57             : CPL_C_START
      58             : CPLXMLNode *GDALSerializeRPCTransformer(void *pTransformArg);
      59             : void *GDALDeserializeRPCTransformer(CPLXMLNode *psTree);
      60             : CPL_C_END
      61             : 
      62             : constexpr int MAX_ABS_VALUE_WARNINGS = 20;
      63             : constexpr double DEFAULT_PIX_ERR_THRESHOLD = 0.1;
      64             : 
      65             : /************************************************************************/
      66             : /*                            RPCInfoToMD()                             */
      67             : /*                                                                      */
      68             : /*      Turn an RPCInfo structure back into its metadata format.        */
      69             : /************************************************************************/
      70             : 
      71           0 : char **RPCInfoV1ToMD(GDALRPCInfoV1 *psRPCInfo)
      72             : 
      73             : {
      74             :     GDALRPCInfoV2 sRPCInfo;
      75           0 :     memcpy(&sRPCInfo, psRPCInfo, sizeof(GDALRPCInfoV1));
      76           0 :     sRPCInfo.dfERR_BIAS = std::numeric_limits<double>::quiet_NaN();
      77           0 :     sRPCInfo.dfERR_RAND = std::numeric_limits<double>::quiet_NaN();
      78           0 :     return RPCInfoV2ToMD(&sRPCInfo);
      79             : }
      80             : 
      81           1 : char **RPCInfoV2ToMD(GDALRPCInfoV2 *psRPCInfo)
      82             : 
      83             : {
      84           1 :     char **papszMD = nullptr;
      85           2 :     CPLString osField, osMultiField;
      86             : 
      87           1 :     if (!std::isnan(psRPCInfo->dfERR_BIAS))
      88             :     {
      89           1 :         osField.Printf("%.15g", psRPCInfo->dfERR_BIAS);
      90           1 :         papszMD = CSLSetNameValue(papszMD, RPC_ERR_BIAS, osField);
      91             :     }
      92             : 
      93           1 :     if (!std::isnan(psRPCInfo->dfERR_RAND))
      94             :     {
      95           1 :         osField.Printf("%.15g", psRPCInfo->dfERR_RAND);
      96           1 :         papszMD = CSLSetNameValue(papszMD, RPC_ERR_RAND, osField);
      97             :     }
      98             : 
      99           1 :     osField.Printf("%.15g", psRPCInfo->dfLINE_OFF);
     100           1 :     papszMD = CSLSetNameValue(papszMD, RPC_LINE_OFF, osField);
     101             : 
     102           1 :     osField.Printf("%.15g", psRPCInfo->dfSAMP_OFF);
     103           1 :     papszMD = CSLSetNameValue(papszMD, RPC_SAMP_OFF, osField);
     104             : 
     105           1 :     osField.Printf("%.15g", psRPCInfo->dfLAT_OFF);
     106           1 :     papszMD = CSLSetNameValue(papszMD, RPC_LAT_OFF, osField);
     107             : 
     108           1 :     osField.Printf("%.15g", psRPCInfo->dfLONG_OFF);
     109           1 :     papszMD = CSLSetNameValue(papszMD, RPC_LONG_OFF, osField);
     110             : 
     111           1 :     osField.Printf("%.15g", psRPCInfo->dfHEIGHT_OFF);
     112           1 :     papszMD = CSLSetNameValue(papszMD, RPC_HEIGHT_OFF, osField);
     113             : 
     114           1 :     osField.Printf("%.15g", psRPCInfo->dfLINE_SCALE);
     115           1 :     papszMD = CSLSetNameValue(papszMD, RPC_LINE_SCALE, osField);
     116             : 
     117           1 :     osField.Printf("%.15g", psRPCInfo->dfSAMP_SCALE);
     118           1 :     papszMD = CSLSetNameValue(papszMD, RPC_SAMP_SCALE, osField);
     119             : 
     120           1 :     osField.Printf("%.15g", psRPCInfo->dfLAT_SCALE);
     121           1 :     papszMD = CSLSetNameValue(papszMD, RPC_LAT_SCALE, osField);
     122             : 
     123           1 :     osField.Printf("%.15g", psRPCInfo->dfLONG_SCALE);
     124           1 :     papszMD = CSLSetNameValue(papszMD, RPC_LONG_SCALE, osField);
     125             : 
     126           1 :     osField.Printf("%.15g", psRPCInfo->dfHEIGHT_SCALE);
     127           1 :     papszMD = CSLSetNameValue(papszMD, RPC_HEIGHT_SCALE, osField);
     128             : 
     129           1 :     osField.Printf("%.15g", psRPCInfo->dfMIN_LONG);
     130           1 :     papszMD = CSLSetNameValue(papszMD, RPC_MIN_LONG, osField);
     131             : 
     132           1 :     osField.Printf("%.15g", psRPCInfo->dfMIN_LAT);
     133           1 :     papszMD = CSLSetNameValue(papszMD, RPC_MIN_LAT, osField);
     134             : 
     135           1 :     osField.Printf("%.15g", psRPCInfo->dfMAX_LONG);
     136           1 :     papszMD = CSLSetNameValue(papszMD, RPC_MAX_LONG, osField);
     137             : 
     138           1 :     osField.Printf("%.15g", psRPCInfo->dfMAX_LAT);
     139           1 :     papszMD = CSLSetNameValue(papszMD, RPC_MAX_LAT, osField);
     140             : 
     141          21 :     for (int i = 0; i < 20; i++)
     142             :     {
     143          20 :         osField.Printf("%.15g", psRPCInfo->adfLINE_NUM_COEFF[i]);
     144          20 :         if (i > 0)
     145          19 :             osMultiField += " ";
     146             :         else
     147           1 :             osMultiField = "";
     148          20 :         osMultiField += osField;
     149             :     }
     150           1 :     papszMD = CSLSetNameValue(papszMD, "LINE_NUM_COEFF", osMultiField);
     151             : 
     152          21 :     for (int i = 0; i < 20; i++)
     153             :     {
     154          20 :         osField.Printf("%.15g", psRPCInfo->adfLINE_DEN_COEFF[i]);
     155          20 :         if (i > 0)
     156          19 :             osMultiField += " ";
     157             :         else
     158           1 :             osMultiField = "";
     159          20 :         osMultiField += osField;
     160             :     }
     161           1 :     papszMD = CSLSetNameValue(papszMD, "LINE_DEN_COEFF", osMultiField);
     162             : 
     163          21 :     for (int i = 0; i < 20; i++)
     164             :     {
     165          20 :         osField.Printf("%.15g", psRPCInfo->adfSAMP_NUM_COEFF[i]);
     166          20 :         if (i > 0)
     167          19 :             osMultiField += " ";
     168             :         else
     169           1 :             osMultiField = "";
     170          20 :         osMultiField += osField;
     171             :     }
     172           1 :     papszMD = CSLSetNameValue(papszMD, "SAMP_NUM_COEFF", osMultiField);
     173             : 
     174          21 :     for (int i = 0; i < 20; i++)
     175             :     {
     176          20 :         osField.Printf("%.15g", psRPCInfo->adfSAMP_DEN_COEFF[i]);
     177          20 :         if (i > 0)
     178          19 :             osMultiField += " ";
     179             :         else
     180           1 :             osMultiField = "";
     181          20 :         osMultiField += osField;
     182             :     }
     183           1 :     papszMD = CSLSetNameValue(papszMD, "SAMP_DEN_COEFF", osMultiField);
     184             : 
     185           2 :     return papszMD;
     186             : }
     187             : 
     188             : /************************************************************************/
     189             : /*                          RPCComputeTerms()                           */
     190             : /************************************************************************/
     191             : 
     192      460269 : static void RPCComputeTerms(double dfLong, double dfLat, double dfHeight,
     193             :                             double *padfTerms)
     194             : 
     195             : {
     196      460269 :     padfTerms[0] = 1.0;
     197      460269 :     padfTerms[1] = dfLong;
     198      460269 :     padfTerms[2] = dfLat;
     199      460269 :     padfTerms[3] = dfHeight;
     200      460269 :     padfTerms[4] = dfLong * dfLat;
     201      460269 :     padfTerms[5] = dfLong * dfHeight;
     202      460269 :     padfTerms[6] = dfLat * dfHeight;
     203      460269 :     padfTerms[7] = dfLong * dfLong;
     204      460269 :     padfTerms[8] = dfLat * dfLat;
     205      460269 :     padfTerms[9] = dfHeight * dfHeight;
     206             : 
     207      460269 :     padfTerms[10] = dfLong * dfLat * dfHeight;
     208      460269 :     padfTerms[11] = dfLong * dfLong * dfLong;
     209      460269 :     padfTerms[12] = dfLong * dfLat * dfLat;
     210      460269 :     padfTerms[13] = dfLong * dfHeight * dfHeight;
     211      460269 :     padfTerms[14] = dfLong * dfLong * dfLat;
     212      460269 :     padfTerms[15] = dfLat * dfLat * dfLat;
     213      460269 :     padfTerms[16] = dfLat * dfHeight * dfHeight;
     214      460269 :     padfTerms[17] = dfLong * dfLong * dfHeight;
     215      460269 :     padfTerms[18] = dfLat * dfLat * dfHeight;
     216      460269 :     padfTerms[19] = dfHeight * dfHeight * dfHeight;
     217      460269 : }
     218             : 
     219             : /************************************************************************/
     220             : /* ==================================================================== */
     221             : /*                           GDALRPCTransformer                         */
     222             : /* ==================================================================== */
     223             : /************************************************************************/
     224             : 
     225             : /*! DEM Resampling Algorithm */
     226             : typedef enum
     227             : {
     228             :     /*! Nearest neighbour (select on one input pixel) */ DRA_NearestNeighbour =
     229             :         0,
     230             :     /*! Bilinear (2x2 kernel) */ DRA_Bilinear = 1,
     231             :     /*! Cubic Convolution Approximation (4x4 kernel) */ DRA_CubicSpline = 2
     232             : } DEMResampleAlg;
     233             : 
     234             : typedef struct
     235             : {
     236             : 
     237             :     GDALTransformerInfo sTI;
     238             : 
     239             :     GDALRPCInfoV2 sRPC;
     240             : 
     241             :     double adfPLToLatLongGeoTransform[6];
     242             :     double dfRefZ;
     243             : 
     244             :     int bReversed;
     245             : 
     246             :     double dfPixErrThreshold;
     247             : 
     248             :     double dfHeightOffset;
     249             : 
     250             :     double dfHeightScale;
     251             : 
     252             :     char *pszDEMPath;
     253             : 
     254             :     DEMResampleAlg eResampleAlg;
     255             : 
     256             :     int bHasDEMMissingValue;
     257             :     double dfDEMMissingValue;
     258             :     char *pszDEMSRS;
     259             :     int bApplyDEMVDatumShift;
     260             : 
     261             :     GDALDataset *poDS;
     262             :     // the key is (nYBlock << 32) | nXBlock)
     263             :     lru11::Cache<uint64_t, std::shared_ptr<std::vector<double>>> *poCacheDEM;
     264             : 
     265             :     OGRCoordinateTransformation *poCT;
     266             : 
     267             :     int nMaxIterations;
     268             : 
     269             :     double adfDEMGeoTransform[6];
     270             :     double adfDEMReverseGeoTransform[6];
     271             : 
     272             : #ifdef USE_SSE2_OPTIM
     273             :     double adfDoubles[20 * 4 + 1];
     274             :     // LINE_NUM_COEFF, LINE_DEN_COEFF, SAMP_NUM_COEFF and then SAMP_DEN_COEFF.
     275             :     double *padfCoeffs;
     276             : #endif
     277             : 
     278             :     bool bRPCInverseVerbose;
     279             :     char *pszRPCInverseLog;
     280             : 
     281             :     char *pszRPCFootprint;
     282             :     OGRGeometry *poRPCFootprintGeom;
     283             :     OGRPreparedGeometry *poRPCFootprintPreparedGeom;
     284             : 
     285             : } GDALRPCTransformInfo;
     286             : 
     287             : static bool GDALRPCOpenDEM(GDALRPCTransformInfo *psTransform);
     288             : 
     289             : /************************************************************************/
     290             : /*                            RPCEvaluate()                             */
     291             : /************************************************************************/
     292             : #ifdef USE_SSE2_OPTIM
     293             : 
     294      460269 : static void RPCEvaluate4(const double *padfTerms, const double *padfCoefs,
     295             :                          double &dfSum1, double &dfSum2, double &dfSum3,
     296             :                          double &dfSum4)
     297             : 
     298             : {
     299      460269 :     XMMReg2Double sum1 = XMMReg2Double::Zero();
     300      460269 :     XMMReg2Double sum2 = XMMReg2Double::Zero();
     301      460269 :     XMMReg2Double sum3 = XMMReg2Double::Zero();
     302      460269 :     XMMReg2Double sum4 = XMMReg2Double::Zero();
     303     5062960 :     for (int i = 0; i < 20; i += 2)
     304             :     {
     305             :         const XMMReg2Double terms =
     306     4602690 :             XMMReg2Double::Load2ValAligned(padfTerms + i);
     307             : 
     308             :         // LINE_NUM_COEFF.
     309             :         const XMMReg2Double coefs1 =
     310     4602690 :             XMMReg2Double::Load2ValAligned(padfCoefs + i);
     311             : 
     312             :         // LINE_DEN_COEFF.
     313             :         const XMMReg2Double coefs2 =
     314     4602690 :             XMMReg2Double::Load2ValAligned(padfCoefs + i + 20);
     315             : 
     316             :         // SAMP_NUM_COEFF.
     317             :         const XMMReg2Double coefs3 =
     318     4602690 :             XMMReg2Double::Load2ValAligned(padfCoefs + i + 40);
     319             : 
     320             :         // SAMP_DEN_COEFF.
     321             :         const XMMReg2Double coefs4 =
     322     4602690 :             XMMReg2Double::Load2ValAligned(padfCoefs + i + 60);
     323             : 
     324     4602690 :         sum1 += terms * coefs1;
     325     4602690 :         sum2 += terms * coefs2;
     326     4602690 :         sum3 += terms * coefs3;
     327     4602690 :         sum4 += terms * coefs4;
     328             :     }
     329      460269 :     dfSum1 = sum1.GetHorizSum();
     330      460269 :     dfSum2 = sum2.GetHorizSum();
     331      460269 :     dfSum3 = sum3.GetHorizSum();
     332      460269 :     dfSum4 = sum4.GetHorizSum();
     333      460269 : }
     334             : 
     335             : #else
     336             : 
     337             : static double RPCEvaluate(const double *padfTerms, const double *padfCoefs)
     338             : 
     339             : {
     340             :     double dfSum1 = 0.0;
     341             :     double dfSum2 = 0.0;
     342             : 
     343             :     for (int i = 0; i < 20; i += 2)
     344             :     {
     345             :         dfSum1 += padfTerms[i] * padfCoefs[i];
     346             :         dfSum2 += padfTerms[i + 1] * padfCoefs[i + 1];
     347             :     }
     348             : 
     349             :     return dfSum1 + dfSum2;
     350             : }
     351             : 
     352             : #endif
     353             : 
     354             : /************************************************************************/
     355             : /*                         RPCTransformPoint()                          */
     356             : /************************************************************************/
     357             : 
     358      460269 : static void RPCTransformPoint(const GDALRPCTransformInfo *psRPCTransformInfo,
     359             :                               double dfLong, double dfLat, double dfHeight,
     360             :                               double *pdfPixel, double *pdfLine)
     361             : 
     362             : {
     363      460269 :     double adfTermsWithMargin[20 + 1] = {};
     364             :     // Make padfTerms aligned on 16-byte boundary for SSE2 aligned loads.
     365      460269 :     double *padfTerms =
     366      460269 :         adfTermsWithMargin +
     367             :         (reinterpret_cast<GUIntptr_t>(adfTermsWithMargin) % 16) / 8;
     368             : 
     369             :     // Avoid dateline issues.
     370      460269 :     double diffLong = dfLong - psRPCTransformInfo->sRPC.dfLONG_OFF;
     371      460269 :     if (diffLong < -270)
     372             :     {
     373           2 :         diffLong += 360;
     374             :     }
     375      460267 :     else if (diffLong > 270)
     376             :     {
     377           6 :         diffLong -= 360;
     378             :     }
     379             : 
     380      460269 :     const double dfNormalizedLong =
     381      460269 :         diffLong / psRPCTransformInfo->sRPC.dfLONG_SCALE;
     382      460269 :     const double dfNormalizedLat =
     383      460269 :         (dfLat - psRPCTransformInfo->sRPC.dfLAT_OFF) /
     384      460269 :         psRPCTransformInfo->sRPC.dfLAT_SCALE;
     385      460269 :     const double dfNormalizedHeight =
     386      460269 :         (dfHeight - psRPCTransformInfo->sRPC.dfHEIGHT_OFF) /
     387      460269 :         psRPCTransformInfo->sRPC.dfHEIGHT_SCALE;
     388             : 
     389             :     // The absolute values of the 3 above normalized values are supposed to be
     390             :     // below 1. Warn (as debug message) if it is not the case. We allow for some
     391             :     // margin above 1 (1.5, somewhat arbitrary chosen) before warning.
     392             :     static int nCountWarningsAboutAboveOneNormalizedValues = 0;
     393      460269 :     if (nCountWarningsAboutAboveOneNormalizedValues < MAX_ABS_VALUE_WARNINGS)
     394             :     {
     395       99804 :         bool bWarned = false;
     396       99804 :         if (fabs(dfNormalizedLong) > 1.5)
     397             :         {
     398          31 :             bWarned = true;
     399          31 :             CPLDebug(
     400             :                 "RPC",
     401             :                 "Normalized %s for (lon,lat,height)=(%f,%f,%f) is %f, "
     402             :                 "i.e. with an absolute value of > 1, which may cause numeric "
     403             :                 "stability problems",
     404             :                 "longitude", dfLong, dfLat, dfHeight, dfNormalizedLong);
     405             :         }
     406       99804 :         if (fabs(dfNormalizedLat) > 1.5)
     407             :         {
     408          21 :             bWarned = true;
     409          21 :             CPLDebug(
     410             :                 "RPC",
     411             :                 "Normalized %s for (lon,lat,height)=(%f,%f,%f) is %f, "
     412             :                 "ie with an absolute value of > 1, which may cause numeric "
     413             :                 "stability problems",
     414             :                 "latitude", dfLong, dfLat, dfHeight, dfNormalizedLat);
     415             :         }
     416       99804 :         if (fabs(dfNormalizedHeight) > 1.5)
     417             :         {
     418          39 :             bWarned = true;
     419          39 :             CPLDebug(
     420             :                 "RPC",
     421             :                 "Normalized %s for (lon,lat,height)=(%f,%f,%f) is %f, "
     422             :                 "i.e. with an absolute value of > 1, which may cause numeric "
     423             :                 "stability problems",
     424             :                 "height", dfLong, dfLat, dfHeight, dfNormalizedHeight);
     425             :         }
     426       99804 :         if (bWarned)
     427             :         {
     428             :             // Limit the number of warnings.
     429          60 :             nCountWarningsAboutAboveOneNormalizedValues++;
     430          60 :             if (nCountWarningsAboutAboveOneNormalizedValues ==
     431             :                 MAX_ABS_VALUE_WARNINGS)
     432             :             {
     433           3 :                 CPLDebug("RPC", "No more such debug warnings will be emitted");
     434             :             }
     435             :         }
     436             :     }
     437             : 
     438      460269 :     RPCComputeTerms(dfNormalizedLong, dfNormalizedLat, dfNormalizedHeight,
     439             :                     padfTerms);
     440             : 
     441             : #ifdef USE_SSE2_OPTIM
     442      460269 :     double dfSampNum = 0.0;
     443      460269 :     double dfSampDen = 0.0;
     444      460269 :     double dfLineNum = 0.0;
     445      460269 :     double dfLineDen = 0.0;
     446      460269 :     RPCEvaluate4(padfTerms, psRPCTransformInfo->padfCoeffs, dfLineNum,
     447             :                  dfLineDen, dfSampNum, dfSampDen);
     448      460269 :     const double dfResultX = dfSampNum / dfSampDen;
     449      460269 :     const double dfResultY = dfLineNum / dfLineDen;
     450             : #else
     451             :     const double dfResultX =
     452             :         RPCEvaluate(padfTerms, psRPCTransformInfo->sRPC.adfSAMP_NUM_COEFF) /
     453             :         RPCEvaluate(padfTerms, psRPCTransformInfo->sRPC.adfSAMP_DEN_COEFF);
     454             : 
     455             :     const double dfResultY =
     456             :         RPCEvaluate(padfTerms, psRPCTransformInfo->sRPC.adfLINE_NUM_COEFF) /
     457             :         RPCEvaluate(padfTerms, psRPCTransformInfo->sRPC.adfLINE_DEN_COEFF);
     458             : #endif
     459             : 
     460             :     // RPCs are using the center of upper left pixel = 0,0 convention
     461             :     // convert to top left corner = 0,0 convention used in GDAL.
     462      460269 :     *pdfPixel = dfResultX * psRPCTransformInfo->sRPC.dfSAMP_SCALE +
     463      460269 :                 psRPCTransformInfo->sRPC.dfSAMP_OFF + 0.5;
     464      460269 :     *pdfLine = dfResultY * psRPCTransformInfo->sRPC.dfLINE_SCALE +
     465      460269 :                psRPCTransformInfo->sRPC.dfLINE_OFF + 0.5;
     466      460269 : }
     467             : 
     468             : /************************************************************************/
     469             : /*                     GDALSerializeRPCDEMResample()                    */
     470             : /************************************************************************/
     471             : 
     472           0 : static const char *GDALSerializeRPCDEMResample(DEMResampleAlg eResampleAlg)
     473             : {
     474           0 :     switch (eResampleAlg)
     475             :     {
     476           0 :         case DRA_NearestNeighbour:
     477           0 :             return "near";
     478           0 :         case DRA_CubicSpline:
     479           0 :             return "cubic";
     480           0 :         default:
     481             :         case DRA_Bilinear:
     482           0 :             return "bilinear";
     483             :     }
     484             : }
     485             : 
     486             : /************************************************************************/
     487             : /*                   GDALCreateSimilarRPCTransformer()                  */
     488             : /************************************************************************/
     489             : 
     490           1 : static void *GDALCreateSimilarRPCTransformer(void *hTransformArg,
     491             :                                              double dfRatioX, double dfRatioY)
     492             : {
     493           1 :     VALIDATE_POINTER1(hTransformArg, "GDALCreateSimilarRPCTransformer",
     494             :                       nullptr);
     495             : 
     496           1 :     GDALRPCTransformInfo *psInfo =
     497             :         static_cast<GDALRPCTransformInfo *>(hTransformArg);
     498             : 
     499             :     GDALRPCInfoV2 sRPC;
     500           1 :     memcpy(&sRPC, &(psInfo->sRPC), sizeof(GDALRPCInfoV2));
     501             : 
     502           1 :     if (dfRatioX != 1.0 || dfRatioY != 1.0)
     503             :     {
     504           1 :         sRPC.dfLINE_OFF /= dfRatioY;
     505           1 :         sRPC.dfLINE_SCALE /= dfRatioY;
     506           1 :         sRPC.dfSAMP_OFF /= dfRatioX;
     507           1 :         sRPC.dfSAMP_SCALE /= dfRatioX;
     508             :     }
     509             : 
     510           1 :     char **papszOptions = nullptr;
     511           1 :     papszOptions = CSLSetNameValue(papszOptions, "RPC_HEIGHT",
     512             :                                    CPLSPrintf("%.17g", psInfo->dfHeightOffset));
     513           1 :     papszOptions = CSLSetNameValue(papszOptions, "RPC_HEIGHT_SCALE",
     514             :                                    CPLSPrintf("%.17g", psInfo->dfHeightScale));
     515           1 :     if (psInfo->pszDEMPath != nullptr)
     516             :     {
     517             :         papszOptions =
     518           0 :             CSLSetNameValue(papszOptions, "RPC_DEM", psInfo->pszDEMPath);
     519             :         papszOptions =
     520           0 :             CSLSetNameValue(papszOptions, "RPC_DEMINTERPOLATION",
     521             :                             GDALSerializeRPCDEMResample(psInfo->eResampleAlg));
     522           0 :         if (psInfo->bHasDEMMissingValue)
     523             :             papszOptions =
     524           0 :                 CSLSetNameValue(papszOptions, "RPC_DEM_MISSING_VALUE",
     525             :                                 CPLSPrintf("%.17g", psInfo->dfDEMMissingValue));
     526             :         papszOptions =
     527           0 :             CSLSetNameValue(papszOptions, "RPC_DEM_APPLY_VDATUM_SHIFT",
     528           0 :                             (psInfo->bApplyDEMVDatumShift) ? "TRUE" : "FALSE");
     529             :     }
     530           1 :     papszOptions = CSLSetNameValue(papszOptions, "RPC_MAX_ITERATIONS",
     531             :                                    CPLSPrintf("%d", psInfo->nMaxIterations));
     532             : 
     533             :     GDALRPCTransformInfo *psNewInfo =
     534           1 :         static_cast<GDALRPCTransformInfo *>(GDALCreateRPCTransformerV2(
     535             :             &sRPC, psInfo->bReversed, psInfo->dfPixErrThreshold, papszOptions));
     536           1 :     CSLDestroy(papszOptions);
     537             : 
     538           1 :     return psNewInfo;
     539             : }
     540             : 
     541             : /************************************************************************/
     542             : /*                      GDALRPCGetHeightAtLongLat()                     */
     543             : /************************************************************************/
     544             : 
     545             : static int GDALRPCGetDEMHeight(GDALRPCTransformInfo *psTransform,
     546             :                                const double dfXIn, const double dfYIn,
     547             :                                double *pdfDEMH);
     548             : 
     549      304118 : static bool GDALRPCGetHeightAtLongLat(GDALRPCTransformInfo *psTransform,
     550             :                                       const double dfXIn, const double dfYIn,
     551             :                                       double *pdfHeight,
     552             :                                       double *pdfDEMPixel = nullptr,
     553             :                                       double *pdfDEMLine = nullptr)
     554             : {
     555      304118 :     double dfVDatumShift = 0.0;
     556      304118 :     double dfDEMH = 0.0;
     557      304118 :     if (psTransform->poDS)
     558             :     {
     559      296262 :         double dfX = 0.0;
     560      296262 :         double dfY = 0.0;
     561      296262 :         double dfXTemp = dfXIn;
     562      296262 :         double dfYTemp = dfYIn;
     563             :         // Check if dem is not in WGS84 and transform points padfX[i], padfY[i].
     564      296262 :         if (psTransform->poCT)
     565             :         {
     566      189912 :             double dfZ = 0.0;
     567      189912 :             if (!psTransform->poCT->Transform(1, &dfXTemp, &dfYTemp, &dfZ))
     568             :             {
     569           0 :                 return false;
     570             :             }
     571             : 
     572             :             // We must take the opposite since poCT transforms from
     573             :             // WGS84 to geoid. And we are going to do the reverse:
     574             :             // take an elevation over the geoid and transforms it to WGS84.
     575      189912 :             if (psTransform->bApplyDEMVDatumShift)
     576      189912 :                 dfVDatumShift = -dfZ;
     577             :         }
     578             : 
     579      296262 :         bool bRetried = false;
     580      296270 :     retry:
     581      296270 :         GDALApplyGeoTransform(psTransform->adfDEMReverseGeoTransform, dfXTemp,
     582             :                               dfYTemp, &dfX, &dfY);
     583      296270 :         if (pdfDEMPixel)
     584       43197 :             *pdfDEMPixel = dfX;
     585      296270 :         if (pdfDEMLine)
     586       43197 :             *pdfDEMLine = dfY;
     587             : 
     588      296270 :         if (!GDALRPCGetDEMHeight(psTransform, dfX, dfY, &dfDEMH))
     589             :         {
     590             :             // Try to handle the case where the DEM is in LL WGS84 and spans
     591             :             // over [-180,180], (or very close to it ), presumably with much
     592             :             // hole in the middle if using VRT, and the longitude goes beyond
     593             :             // that interval.
     594       44616 :             if (!bRetried && psTransform->poCT == nullptr &&
     595       16677 :                 (dfXIn >= 180.0 || dfXIn <= -180.0))
     596             :             {
     597           8 :                 const int nRasterXSize = psTransform->poDS->GetRasterXSize();
     598           8 :                 const double dfMinDEMLong = psTransform->adfDEMGeoTransform[0];
     599           8 :                 const double dfMaxDEMLong =
     600           8 :                     psTransform->adfDEMGeoTransform[0] +
     601           8 :                     nRasterXSize * psTransform->adfDEMGeoTransform[1];
     602           8 :                 if (fabs(dfMinDEMLong - -180) < 0.1 &&
     603           8 :                     fabs(dfMaxDEMLong - 180) < 0.1)
     604             :                 {
     605           8 :                     if (dfXIn >= 180)
     606             :                     {
     607           4 :                         dfXTemp = dfXIn - 360;
     608           4 :                         dfYTemp = dfYIn;
     609             :                     }
     610             :                     else
     611             :                     {
     612           4 :                         dfXTemp = dfXIn + 360;
     613           4 :                         dfYTemp = dfYIn;
     614             :                     }
     615           8 :                     bRetried = true;
     616           8 :                     goto retry;
     617             :                 }
     618             :             }
     619             : 
     620       44608 :             if (psTransform->bHasDEMMissingValue)
     621       11832 :                 dfDEMH = psTransform->dfDEMMissingValue;
     622             :             else
     623             :             {
     624       32776 :                 return false;
     625             :             }
     626             :         }
     627             : #ifdef DEBUG_VERBOSE_EXTRACT_DEM
     628             :         CPLDebug("RPC_DEM", "X=%f, Y=%f -> Z=%f", dfX, dfY, dfDEMH);
     629             : #endif
     630             :     }
     631             : 
     632      271342 :     *pdfHeight = dfVDatumShift + (psTransform->dfHeightOffset +
     633      271342 :                                   dfDEMH * psTransform->dfHeightScale);
     634      271342 :     return true;
     635             : }
     636             : 
     637             : /************************************************************************/
     638             : /*                      GDALCreateRPCTransformer()                      */
     639             : /************************************************************************/
     640             : 
     641           0 : void *GDALCreateRPCTransformerV1(GDALRPCInfoV1 *psRPCInfo, int bReversed,
     642             :                                  double dfPixErrThreshold, char **papszOptions)
     643             : 
     644             : {
     645             :     GDALRPCInfoV2 sRPCInfo;
     646           0 :     memcpy(&sRPCInfo, psRPCInfo, sizeof(GDALRPCInfoV1));
     647           0 :     sRPCInfo.dfERR_BIAS = std::numeric_limits<double>::quiet_NaN();
     648           0 :     sRPCInfo.dfERR_RAND = std::numeric_limits<double>::quiet_NaN();
     649           0 :     return GDALCreateRPCTransformerV2(&sRPCInfo, bReversed, dfPixErrThreshold,
     650           0 :                                       papszOptions);
     651             : }
     652             : 
     653             : /**
     654             :  * Create an RPC based transformer.
     655             :  *
     656             :  * The geometric sensor model describing the physical relationship between
     657             :  * image coordinates and ground coordinates is known as a Rigorous Projection
     658             :  * Model. A Rigorous Projection Model expresses the mapping of the image space
     659             :  * coordinates of rows and columns (r,c) onto the object space reference
     660             :  * surface geodetic coordinates (long, lat, height).
     661             :  *
     662             :  * A RPC supports a generic description of the Rigorous Projection Models. The
     663             :  * approximation used by GDAL (RPC00) is a set of rational polynomials
     664             :  * expressing the normalized row and column values, (rn , cn), as a function of
     665             :  * normalized geodetic latitude, longitude, and height, (P, L, H), given a
     666             :  * set of normalized polynomial coefficients (LINE_NUM_COEF_n, LINE_DEN_COEF_n,
     667             :  * SAMP_NUM_COEF_n, SAMP_DEN_COEF_n). Normalized values, rather than actual
     668             :  * values are used in order to minimize introduction of errors during the
     669             :  * calculations. The transformation between row and column values (r,c), and
     670             :  * normalized row and column values (rn, cn), and between the geodetic
     671             :  * latitude, longitude, and height and normalized geodetic latitude,
     672             :  * longitude, and height (P, L, H), is defined by a set of normalizing
     673             :  * translations (offsets) and scales that ensure all values are contained in
     674             :  * the range -1 to +1.
     675             :  *
     676             :  * This function creates a GDALTransformFunc compatible transformer
     677             :  * for going between image pixel/line and long/lat/height coordinates
     678             :  * using RPCs.  The RPCs are provided in a GDALRPCInfo structure which is
     679             :  * normally read from metadata using GDALExtractRPCInfo().
     680             :  *
     681             :  * GDAL RPC Metadata has the following entries (also described in GDAL RFC 22
     682             :  * and the GeoTIFF RPC document http://geotiff.maptools.org/rpc_prop.html .
     683             :  *
     684             :  * <ul>
     685             :  * <li>ERR_BIAS: Error - Bias. The RMS bias error in meters per horizontal axis
     686             :  * of all points in the image (-1.0 if unknown)
     687             :  * <li>ERR_RAND: Error - Random. RMS random error in meters per horizontal axis
     688             :  * of each point in the image (-1.0 if unknown)
     689             :  * <li>LINE_OFF: Line Offset
     690             :  * <li>SAMP_OFF: Sample Offset
     691             :  * <li>LAT_OFF: Geodetic Latitude Offset
     692             :  * <li>LONG_OFF: Geodetic Longitude Offset
     693             :  * <li>HEIGHT_OFF: Geodetic Height Offset
     694             :  * <li>LINE_SCALE: Line Scale
     695             :  * <li>SAMP_SCALE: Sample Scale
     696             :  * <li>LAT_SCALE: Geodetic Latitude Scale
     697             :  * <li>LONG_SCALE: Geodetic Longitude Scale
     698             :  * <li>HEIGHT_SCALE: Geodetic Height Scale
     699             : 
     700             :  * <li>LINE_NUM_COEFF (1-20): Line Numerator Coefficients. Twenty coefficients
     701             :  * for the polynomial in the Numerator of the rn equation. (space separated)
     702             :  * <li>LINE_DEN_COEFF (1-20): Line Denominator Coefficients. Twenty coefficients
     703             :  * for the polynomial in the Denominator of the rn equation. (space separated)
     704             :  * <li>SAMP_NUM_COEFF (1-20): Sample Numerator Coefficients. Twenty coefficients
     705             :  * for the polynomial in the Numerator of the cn equation. (space separated)
     706             :  * <li>SAMP_DEN_COEFF (1-20): Sample Denominator Coefficients. Twenty
     707             :  * coefficients for the polynomial in the Denominator of the cn equation. (space
     708             :  * separated)
     709             :  * </ul>
     710             :  *
     711             :  * The transformer normally maps from pixel/line/height to long/lat/height space
     712             :  * as a forward transformation though in RPC terms that would be considered
     713             :  * an inverse transformation (and is solved by iterative approximation using
     714             :  * long/lat/height to pixel/line transformations).  The default direction can
     715             :  * be reversed by passing bReversed=TRUE.
     716             :  *
     717             :  * The iterative solution of pixel/line
     718             :  * to lat/long/height is currently run for up to 10 iterations or until
     719             :  * the apparent error is less than dfPixErrThreshold pixels.  Passing zero
     720             :  * will not avoid all error, but will cause the operation to run for the maximum
     721             :  * number of iterations.
     722             :  *
     723             :  * Starting with GDAL 2.1, debugging of the RPC inverse transformer can be done
     724             :  * by setting the RPC_INVERSE_VERBOSE configuration option to YES (in which case
     725             :  * extra debug information will be displayed in the "RPC" debug category, so
     726             :  * requiring CPL_DEBUG to be also set) and/or by setting RPC_INVERSE_LOG to a
     727             :  * filename that will contain the content of iterations (this last option only
     728             :  * makes sense when debugging point by point, since each time
     729             :  * RPCInverseTransformPoint() is called, the file is rewritten).
     730             :  *
     731             :  * Additional options to the transformer can be supplied in papszOptions.
     732             :  *
     733             :  * Options:
     734             :  *
     735             :  * <ul>
     736             :  * <li> RPC_HEIGHT: a fixed height offset to be applied to all points passed
     737             :  * in.  In this situation the Z passed into the transformation function is
     738             :  * assumed to be height above ground, and the RPC_HEIGHT is assumed to be
     739             :  * an average height above sea level for ground in the target scene.</li>
     740             :  *
     741             :  * <li> RPC_HEIGHT_SCALE: a factor used to multiply heights above ground.
     742             :  * Useful when elevation offsets of the DEM are not expressed in meters.</li>
     743             :  *
     744             :  * <li> RPC_DEM: the name of a GDAL dataset (a DEM file typically) used to
     745             :  * extract elevation offsets from. In this situation the Z passed into the
     746             :  * transformation function is assumed to be height above ground. This option
     747             :  * should be used in replacement of RPC_HEIGHT to provide a way of defining
     748             :  * a non uniform ground for the target scene</li>
     749             :  *
     750             :  * <li> RPC_DEMINTERPOLATION: the DEM interpolation ("near", "bilinear" or
     751             :  "cubic").
     752             :  *      Default is "bilinear".</li>
     753             :  *
     754             :  * <li> RPC_DEM_MISSING_VALUE: value of DEM height that must be used in case
     755             :  * the DEM has nodata value at the sampling point, or if its extent does not
     756             :  * cover the requested coordinate. When not specified, missing values will cause
     757             :  * a failed transform.</li>
     758             :  *
     759             :  * <li> RPC_DEM_SRS: (GDAL >= 3.2) WKT SRS, or any string recognized by
     760             :  * OGRSpatialReference::SetFromUserInput(), to be used as an override for DEM
     761             :  SRS.
     762             :  * Useful if DEM SRS does not have an explicit vertical component. </li>
     763             :  *
     764             :  * <li> RPC_DEM_APPLY_VDATUM_SHIFT: whether the vertical component of a compound
     765             :  * SRS for the DEM should be used (when it is present). This is useful so as to
     766             :  * be able to transform the "raw" values from the DEM expressed with respect to
     767             :  * a geoid to the heights with respect to the WGS84 ellipsoid. When this is
     768             :  * enabled, the GTIFF_REPORT_COMPD_CS configuration option will be also set
     769             :  * temporarily so as to get the vertical information from GeoTIFF
     770             :  * files. Defaults to TRUE. (GDAL >= 2.1.0)</li>
     771             :  *
     772             :  * <li> RPC_PIXEL_ERROR_THRESHOLD: overrides the dfPixErrThreshold parameter, ie
     773             :   the error (measured in pixels) allowed in the
     774             :  * iterative solution of pixel/line to lat/long computations (the other way
     775             :  * is always exact given the equations).  (GDAL >= 2.1.0)</li>
     776             :  *
     777             :  * <li> RPC_MAX_ITERATIONS: maximum number of iterations allowed in the
     778             :  * iterative solution of pixel/line to lat/long computations. Default value is
     779             :  * 10 in the absence of a DEM, or 20 if there is a DEM.  (GDAL >= 2.1.0)</li>
     780             :  *
     781             :  * <li> RPC_FOOTPRINT: WKT or GeoJSON polygon (in long / lat coordinate space)
     782             :  * with a validity footprint for the RPC. Any coordinate transformation that
     783             :  * goes from or arrive outside this footprint will be considered invalid. This
     784             :  * is useful in situations where the RPC values become highly unstable outside
     785             :  * of the area on which they have been computed for, potentially leading to
     786             :  * undesirable "echoes" / false positives. This requires GDAL to be built
     787             :  against
     788             :  * GEOS.</li>
     789             :  *
     790             :  * </ul>
     791             :  *
     792             :  * @param psRPCInfo Definition of the RPC parameters.
     793             :  *
     794             :  * @param bReversed If true "forward" transformation will be lat/long to
     795             :  * pixel/line instead of the normal pixel/line to lat/long.
     796             :  *
     797             :  * @param dfPixErrThreshold the error (measured in pixels) allowed in the
     798             :  * iterative solution of pixel/line to lat/long computations (the other way
     799             :  * is always exact given the equations). Starting with GDAL 2.1, this may also
     800             :  * be set through the RPC_PIXEL_ERROR_THRESHOLD transformer option.
     801             :  * If a negative or null value is provided, then this defaults to 0.1 pixel.
     802             :  *
     803             :  * @param papszOptions Other transformer options (i.e. RPC_HEIGHT=z).
     804             :  *
     805             :  * @return transformer callback data (deallocate with GDALDestroyTransformer()).
     806             :  */
     807             : 
     808          46 : void *GDALCreateRPCTransformerV2(const GDALRPCInfoV2 *psRPCInfo, int bReversed,
     809             :                                  double dfPixErrThreshold, char **papszOptions)
     810             : 
     811             : {
     812             :     /* -------------------------------------------------------------------- */
     813             :     /*      Initialize core info.                                           */
     814             :     /* -------------------------------------------------------------------- */
     815             :     GDALRPCTransformInfo *psTransform = static_cast<GDALRPCTransformInfo *>(
     816          46 :         CPLCalloc(sizeof(GDALRPCTransformInfo), 1));
     817             : 
     818          46 :     memcpy(&(psTransform->sRPC), psRPCInfo, sizeof(GDALRPCInfoV2));
     819          46 :     psTransform->bReversed = bReversed;
     820             :     const char *pszPixErrThreshold =
     821          46 :         CSLFetchNameValue(papszOptions, "RPC_PIXEL_ERROR_THRESHOLD");
     822          46 :     if (pszPixErrThreshold != nullptr)
     823           1 :         psTransform->dfPixErrThreshold = CPLAtof(pszPixErrThreshold);
     824          45 :     else if (dfPixErrThreshold > 0)
     825           1 :         psTransform->dfPixErrThreshold = dfPixErrThreshold;
     826             :     else
     827          44 :         psTransform->dfPixErrThreshold = DEFAULT_PIX_ERR_THRESHOLD;
     828          46 :     psTransform->dfHeightOffset = 0.0;
     829          46 :     psTransform->dfHeightScale = 1.0;
     830             : 
     831          46 :     memcpy(psTransform->sTI.abySignature, GDAL_GTI2_SIGNATURE,
     832             :            strlen(GDAL_GTI2_SIGNATURE));
     833          46 :     psTransform->sTI.pszClassName = GDAL_RPC_TRANSFORMER_CLASS_NAME;
     834          46 :     psTransform->sTI.pfnTransform = GDALRPCTransform;
     835          46 :     psTransform->sTI.pfnCleanup = GDALDestroyRPCTransformer;
     836          46 :     psTransform->sTI.pfnSerialize = GDALSerializeRPCTransformer;
     837          46 :     psTransform->sTI.pfnCreateSimilar = GDALCreateSimilarRPCTransformer;
     838             : 
     839             : #ifdef USE_SSE2_OPTIM
     840             :     // Make sure padfCoeffs is aligned on a 16-byte boundary for SSE2 aligned
     841             :     // loads.
     842          46 :     psTransform->padfCoeffs =
     843          46 :         psTransform->adfDoubles +
     844          46 :         (reinterpret_cast<GUIntptr_t>(psTransform->adfDoubles) % 16) / 8;
     845          46 :     memcpy(psTransform->padfCoeffs, psRPCInfo->adfLINE_NUM_COEFF,
     846             :            20 * sizeof(double));
     847          46 :     memcpy(psTransform->padfCoeffs + 20, psRPCInfo->adfLINE_DEN_COEFF,
     848             :            20 * sizeof(double));
     849          46 :     memcpy(psTransform->padfCoeffs + 40, psRPCInfo->adfSAMP_NUM_COEFF,
     850             :            20 * sizeof(double));
     851          46 :     memcpy(psTransform->padfCoeffs + 60, psRPCInfo->adfSAMP_DEN_COEFF,
     852             :            20 * sizeof(double));
     853             : #endif
     854             : 
     855             :     /* -------------------------------------------------------------------- */
     856             :     /*      Do we have a "average height" that we want to consider all      */
     857             :     /*      elevations to be relative to?                                   */
     858             :     /* -------------------------------------------------------------------- */
     859          46 :     const char *pszHeight = CSLFetchNameValue(papszOptions, "RPC_HEIGHT");
     860          46 :     if (pszHeight != nullptr)
     861           6 :         psTransform->dfHeightOffset = CPLAtof(pszHeight);
     862             : 
     863             :     /* -------------------------------------------------------------------- */
     864             :     /*                       The "height scale"                             */
     865             :     /* -------------------------------------------------------------------- */
     866             :     const char *pszHeightScale =
     867          46 :         CSLFetchNameValue(papszOptions, "RPC_HEIGHT_SCALE");
     868          46 :     if (pszHeightScale != nullptr)
     869           7 :         psTransform->dfHeightScale = CPLAtof(pszHeightScale);
     870             : 
     871             :     /* -------------------------------------------------------------------- */
     872             :     /*                       The DEM file name                              */
     873             :     /* -------------------------------------------------------------------- */
     874          46 :     const char *pszDEMPath = CSLFetchNameValue(papszOptions, "RPC_DEM");
     875          46 :     if (pszDEMPath != nullptr)
     876             :     {
     877          33 :         psTransform->pszDEMPath = CPLStrdup(pszDEMPath);
     878             :     }
     879             : 
     880             :     /* -------------------------------------------------------------------- */
     881             :     /*                      The DEM interpolation                           */
     882             :     /* -------------------------------------------------------------------- */
     883             :     const char *pszDEMInterpolation =
     884          46 :         CSLFetchNameValueDef(papszOptions, "RPC_DEMINTERPOLATION", "bilinear");
     885          46 :     if (EQUAL(pszDEMInterpolation, "near"))
     886             :     {
     887           3 :         psTransform->eResampleAlg = DRA_NearestNeighbour;
     888             :     }
     889          43 :     else if (EQUAL(pszDEMInterpolation, "bilinear"))
     890             :     {
     891          40 :         psTransform->eResampleAlg = DRA_Bilinear;
     892             :     }
     893           3 :     else if (EQUAL(pszDEMInterpolation, "cubic"))
     894             :     {
     895           3 :         psTransform->eResampleAlg = DRA_CubicSpline;
     896             :     }
     897             :     else
     898             :     {
     899           0 :         CPLDebug("RPC", "Unknown interpolation %s. Defaulting to bilinear",
     900             :                  pszDEMInterpolation);
     901           0 :         psTransform->eResampleAlg = DRA_Bilinear;
     902             :     }
     903             : 
     904             :     /* -------------------------------------------------------------------- */
     905             :     /*                       The DEM missing value                          */
     906             :     /* -------------------------------------------------------------------- */
     907             :     const char *pszDEMMissingValue =
     908          46 :         CSLFetchNameValue(papszOptions, "RPC_DEM_MISSING_VALUE");
     909          46 :     if (pszDEMMissingValue != nullptr)
     910             :     {
     911           6 :         psTransform->bHasDEMMissingValue = TRUE;
     912           6 :         psTransform->dfDEMMissingValue = CPLAtof(pszDEMMissingValue);
     913             :     }
     914             : 
     915             :     /* -------------------------------------------------------------------- */
     916             :     /*                        The DEM SRS override                          */
     917             :     /* -------------------------------------------------------------------- */
     918          46 :     const char *pszDEMSRS = CSLFetchNameValue(papszOptions, "RPC_DEM_SRS");
     919          46 :     if (pszDEMSRS != nullptr)
     920             :     {
     921           1 :         psTransform->pszDEMSRS = CPLStrdup(pszDEMSRS);
     922             :     }
     923             : 
     924             :     /* -------------------------------------------------------------------- */
     925             :     /*      Whether to apply vdatum shift                                   */
     926             :     /* -------------------------------------------------------------------- */
     927          46 :     psTransform->bApplyDEMVDatumShift =
     928          46 :         CPLFetchBool(papszOptions, "RPC_DEM_APPLY_VDATUM_SHIFT", true);
     929             : 
     930          46 :     psTransform->nMaxIterations =
     931          46 :         atoi(CSLFetchNameValueDef(papszOptions, "RPC_MAX_ITERATIONS", "0"));
     932             : 
     933             :     /* -------------------------------------------------------------------- */
     934             :     /*      Debug                                                           */
     935             :     /* -------------------------------------------------------------------- */
     936          46 :     psTransform->bRPCInverseVerbose =
     937          46 :         CPLTestBool(CPLGetConfigOption("RPC_INVERSE_VERBOSE", "NO"));
     938             :     const char *pszRPCInverseLog =
     939          46 :         CPLGetConfigOption("RPC_INVERSE_LOG", nullptr);
     940          46 :     if (pszRPCInverseLog != nullptr)
     941           1 :         psTransform->pszRPCInverseLog = CPLStrdup(pszRPCInverseLog);
     942             : 
     943             :     /* -------------------------------------------------------------------- */
     944             :     /*      Footprint                                                       */
     945             :     /* -------------------------------------------------------------------- */
     946          46 :     const char *pszFootprint = CSLFetchNameValue(papszOptions, "RPC_FOOTPRINT");
     947          46 :     if (pszFootprint != nullptr)
     948             :     {
     949           1 :         psTransform->pszRPCFootprint = CPLStrdup(pszFootprint);
     950           1 :         if (pszFootprint[0] == '{')
     951             :         {
     952           0 :             psTransform->poRPCFootprintGeom =
     953           0 :                 OGRGeometryFactory::createFromGeoJson(pszFootprint);
     954             :         }
     955             :         else
     956             :         {
     957           1 :             OGRGeometryFactory::createFromWkt(
     958             :                 pszFootprint, nullptr, &(psTransform->poRPCFootprintGeom));
     959             :         }
     960           1 :         if (psTransform->poRPCFootprintGeom)
     961             :         {
     962           1 :             if (OGRHasPreparedGeometrySupport())
     963             :             {
     964           1 :                 psTransform->poRPCFootprintPreparedGeom =
     965           1 :                     OGRCreatePreparedGeometry(
     966             :                         OGRGeometry::ToHandle(psTransform->poRPCFootprintGeom));
     967             :             }
     968             :             else
     969             :             {
     970           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
     971             :                          "GEOS not available. RPC_FOOTPRINT will be ignored");
     972             :             }
     973             :         }
     974             :     }
     975             : 
     976             :     /* -------------------------------------------------------------------- */
     977             :     /*      Open DEM if needed.                                             */
     978             :     /* -------------------------------------------------------------------- */
     979             : 
     980          46 :     if (psTransform->pszDEMPath != nullptr && !GDALRPCOpenDEM(psTransform))
     981             :     {
     982           1 :         GDALDestroyRPCTransformer(psTransform);
     983           1 :         return nullptr;
     984             :     }
     985             : 
     986             :     /* -------------------------------------------------------------------- */
     987             :     /*      Establish a reference point for calcualating an affine          */
     988             :     /*      geotransform approximate transformation.                        */
     989             :     /* -------------------------------------------------------------------- */
     990          45 :     double adfGTFromLL[6] = {};
     991          45 :     double dfRefPixel = -1.0;
     992          45 :     double dfRefLine = -1.0;
     993          45 :     double dfRefLong = 0.0;
     994          45 :     double dfRefLat = 0.0;
     995             : 
     996          45 :     if (psRPCInfo->dfMIN_LONG != -180 || psRPCInfo->dfMAX_LONG != 180)
     997             :     {
     998           1 :         dfRefLong = (psRPCInfo->dfMIN_LONG + psRPCInfo->dfMAX_LONG) * 0.5;
     999           1 :         dfRefLat = (psRPCInfo->dfMIN_LAT + psRPCInfo->dfMAX_LAT) * 0.5;
    1000             : 
    1001           1 :         double dfX = dfRefLong;
    1002           1 :         double dfY = dfRefLat;
    1003           1 :         double dfZ = 0.0;
    1004           1 :         int nSuccess = 0;
    1005             :         // Try with DEM first.
    1006           1 :         if (GDALRPCTransform(psTransform, !(psTransform->bReversed), 1, &dfX,
    1007           2 :                              &dfY, &dfZ, &nSuccess) &&
    1008           1 :             nSuccess)
    1009             :         {
    1010           1 :             dfRefPixel = dfX;
    1011           1 :             dfRefLine = dfY;
    1012             :         }
    1013             :         else
    1014             :         {
    1015           0 :             RPCTransformPoint(psTransform, dfRefLong, dfRefLat, 0.0,
    1016             :                               &dfRefPixel, &dfRefLine);
    1017             :         }
    1018             :     }
    1019             : 
    1020             :     // Try with scale and offset if we don't can't use bounds or
    1021             :     // the results seem daft.
    1022          45 :     if (dfRefPixel < 0.0 || dfRefLine < 0.0 || dfRefPixel > 100000 ||
    1023           1 :         dfRefLine > 100000)
    1024             :     {
    1025          44 :         dfRefLong = psRPCInfo->dfLONG_OFF;
    1026          44 :         dfRefLat = psRPCInfo->dfLAT_OFF;
    1027             : 
    1028          44 :         double dfX = dfRefLong;
    1029          44 :         double dfY = dfRefLat;
    1030          44 :         double dfZ = 0.0;
    1031          44 :         int nSuccess = 0;
    1032             :         // Try with DEM first.
    1033          44 :         if (GDALRPCTransform(psTransform, !(psTransform->bReversed), 1, &dfX,
    1034          88 :                              &dfY, &dfZ, &nSuccess) &&
    1035          44 :             nSuccess)
    1036             :         {
    1037          28 :             dfRefPixel = dfX;
    1038          28 :             dfRefLine = dfY;
    1039             :         }
    1040             :         else
    1041             :         {
    1042          16 :             RPCTransformPoint(psTransform, dfRefLong, dfRefLat, 0.0,
    1043             :                               &dfRefPixel, &dfRefLine);
    1044             :         }
    1045             :     }
    1046             : 
    1047          45 :     psTransform->dfRefZ = 0.0;
    1048          45 :     GDALRPCGetHeightAtLongLat(psTransform, dfRefLong, dfRefLat,
    1049             :                               &psTransform->dfRefZ);
    1050             : 
    1051             :     /* -------------------------------------------------------------------- */
    1052             :     /*      Transform nearby locations to establish affine direction        */
    1053             :     /*      vectors.                                                        */
    1054             :     /* -------------------------------------------------------------------- */
    1055          45 :     double dfRefPixelDelta = 0.0;
    1056          45 :     double dfRefLineDelta = 0.0;
    1057          45 :     double dfLLDelta = 0.0001;
    1058             : 
    1059          45 :     RPCTransformPoint(psTransform, dfRefLong + dfLLDelta, dfRefLat,
    1060             :                       psTransform->dfRefZ, &dfRefPixelDelta, &dfRefLineDelta);
    1061          45 :     adfGTFromLL[1] = (dfRefPixelDelta - dfRefPixel) / dfLLDelta;
    1062          45 :     adfGTFromLL[4] = (dfRefLineDelta - dfRefLine) / dfLLDelta;
    1063             : 
    1064          45 :     RPCTransformPoint(psTransform, dfRefLong, dfRefLat + dfLLDelta,
    1065             :                       psTransform->dfRefZ, &dfRefPixelDelta, &dfRefLineDelta);
    1066          45 :     adfGTFromLL[2] = (dfRefPixelDelta - dfRefPixel) / dfLLDelta;
    1067          45 :     adfGTFromLL[5] = (dfRefLineDelta - dfRefLine) / dfLLDelta;
    1068             : 
    1069          45 :     adfGTFromLL[0] =
    1070          45 :         dfRefPixel - adfGTFromLL[1] * dfRefLong - adfGTFromLL[2] * dfRefLat;
    1071          45 :     adfGTFromLL[3] =
    1072          45 :         dfRefLine - adfGTFromLL[4] * dfRefLong - adfGTFromLL[5] * dfRefLat;
    1073             : 
    1074          45 :     if (!GDALInvGeoTransform(adfGTFromLL,
    1075          45 :                              psTransform->adfPLToLatLongGeoTransform))
    1076             :     {
    1077           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot invert geotransform");
    1078           0 :         GDALDestroyRPCTransformer(psTransform);
    1079           0 :         return nullptr;
    1080             :     }
    1081             : 
    1082          45 :     return psTransform;
    1083             : }
    1084             : 
    1085             : /************************************************************************/
    1086             : /*                 GDALDestroyReprojectionTransformer()                 */
    1087             : /************************************************************************/
    1088             : 
    1089             : /** Destroy RPC transformer */
    1090          46 : void GDALDestroyRPCTransformer(void *pTransformAlg)
    1091             : 
    1092             : {
    1093          46 :     if (pTransformAlg == nullptr)
    1094           0 :         return;
    1095             : 
    1096          46 :     GDALRPCTransformInfo *psTransform =
    1097             :         static_cast<GDALRPCTransformInfo *>(pTransformAlg);
    1098             : 
    1099          46 :     CPLFree(psTransform->pszDEMPath);
    1100          46 :     CPLFree(psTransform->pszDEMSRS);
    1101             : 
    1102          46 :     if (psTransform->poDS)
    1103          32 :         GDALClose(psTransform->poDS);
    1104          46 :     delete psTransform->poCacheDEM;
    1105          46 :     if (psTransform->poCT)
    1106          11 :         OCTDestroyCoordinateTransformation(
    1107          11 :             reinterpret_cast<OGRCoordinateTransformationH>(psTransform->poCT));
    1108          46 :     CPLFree(psTransform->pszRPCInverseLog);
    1109             : 
    1110          46 :     CPLFree(psTransform->pszRPCFootprint);
    1111          46 :     delete psTransform->poRPCFootprintGeom;
    1112          46 :     OGRDestroyPreparedGeometry(psTransform->poRPCFootprintPreparedGeom);
    1113             : 
    1114          46 :     CPLFree(pTransformAlg);
    1115             : }
    1116             : 
    1117             : /************************************************************************/
    1118             : /*                      RPCInverseTransformPoint()                      */
    1119             : /************************************************************************/
    1120             : 
    1121       13614 : static bool RPCInverseTransformPoint(GDALRPCTransformInfo *psTransform,
    1122             :                                      double dfPixel, double dfLine,
    1123             :                                      double dfUserHeight, double *pdfLong,
    1124             :                                      double *pdfLat)
    1125             : 
    1126             : {
    1127             :     // Memo:
    1128             :     // Known to work with 40 iterations with DEM on all points (int coord and
    1129             :     // +0.5,+0.5 shift) of flock1.20160216_041050_0905.tif, especially on (0,0).
    1130             : 
    1131             :     /* -------------------------------------------------------------------- */
    1132             :     /*      Compute an initial approximation based on linear                */
    1133             :     /*      interpolation from our reference point.                         */
    1134             :     /* -------------------------------------------------------------------- */
    1135       13614 :     double dfResultX = psTransform->adfPLToLatLongGeoTransform[0] +
    1136       13614 :                        psTransform->adfPLToLatLongGeoTransform[1] * dfPixel +
    1137       13614 :                        psTransform->adfPLToLatLongGeoTransform[2] * dfLine;
    1138             : 
    1139       13614 :     double dfResultY = psTransform->adfPLToLatLongGeoTransform[3] +
    1140       13614 :                        psTransform->adfPLToLatLongGeoTransform[4] * dfPixel +
    1141       13614 :                        psTransform->adfPLToLatLongGeoTransform[5] * dfLine;
    1142             : 
    1143       13614 :     if (psTransform->bRPCInverseVerbose)
    1144             :     {
    1145           1 :         CPLDebug("RPC", "Computing inverse transform for (pixel,line)=(%f,%f)",
    1146             :                  dfPixel, dfLine);
    1147             :     }
    1148       13614 :     VSILFILE *fpLog = nullptr;
    1149       13614 :     if (psTransform->pszRPCInverseLog)
    1150             :     {
    1151           1 :         fpLog = VSIFOpenL(
    1152           2 :             CPLResetExtensionSafe(psTransform->pszRPCInverseLog, "csvt")
    1153             :                 .c_str(),
    1154             :             "wb");
    1155           1 :         if (fpLog != nullptr)
    1156             :         {
    1157           1 :             VSIFPrintfL(fpLog, "Integer,Real,Real,Real,String,Real,Real\n");
    1158           1 :             VSIFCloseL(fpLog);
    1159             :         }
    1160           1 :         fpLog = VSIFOpenL(psTransform->pszRPCInverseLog, "wb");
    1161           1 :         if (fpLog != nullptr)
    1162           1 :             VSIFPrintfL(
    1163             :                 fpLog,
    1164             :                 "iter,long,lat,height,WKT,error_pixel_x,error_pixel_y\n");
    1165             :     }
    1166             : 
    1167             :     /* -------------------------------------------------------------------- */
    1168             :     /*      Now iterate, trying to find a closer LL location that will      */
    1169             :     /*      back transform to the indicated pixel and line.                 */
    1170             :     /* -------------------------------------------------------------------- */
    1171       13614 :     double dfPixelDeltaX = 0.0;
    1172       13614 :     double dfPixelDeltaY = 0.0;
    1173       13614 :     double dfLastResultX = 0.0;
    1174       13614 :     double dfLastResultY = 0.0;
    1175       13614 :     double dfLastPixelDeltaX = 0.0;
    1176       13614 :     double dfLastPixelDeltaY = 0.0;
    1177       13614 :     bool bLastPixelDeltaValid = false;
    1178       27228 :     const int nMaxIterations = (psTransform->nMaxIterations > 0)
    1179       26237 :                                    ? psTransform->nMaxIterations
    1180       12623 :                                : (psTransform->poDS != nullptr) ? 20
    1181             :                                                                 : 10;
    1182       13614 :     int nCountConsecutiveErrorBelow2 = 0;
    1183             : 
    1184       13614 :     int iIter = 0;  // Used after for.
    1185       47918 :     for (; iIter < nMaxIterations; iIter++)
    1186             :     {
    1187       47388 :         double dfBackPixel = 0.0;
    1188       47388 :         double dfBackLine = 0.0;
    1189             : 
    1190             :         // Update DEMH.
    1191       47388 :         double dfDEMH = 0.0;
    1192       47388 :         double dfDEMPixel = 0.0;
    1193       47388 :         double dfDEMLine = 0.0;
    1194       47388 :         if (!GDALRPCGetHeightAtLongLat(psTransform, dfResultX, dfResultY,
    1195             :                                        &dfDEMH, &dfDEMPixel, &dfDEMLine))
    1196             :         {
    1197       20334 :             if (psTransform->poDS)
    1198             :             {
    1199       20334 :                 CPLDebug("RPC", "DEM (pixel, line) = (%g, %g)", dfDEMPixel,
    1200             :                          dfDEMLine);
    1201             :             }
    1202             : 
    1203             :             // The first time, the guess might be completely out of the
    1204             :             // validity of the DEM, so pickup the "reference Z" as the
    1205             :             // first guess or the closest point of the DEM by snapping to it.
    1206       20334 :             if (iIter == 0)
    1207             :             {
    1208       10262 :                 bool bUseRefZ = true;
    1209       10262 :                 if (psTransform->poDS)
    1210             :                 {
    1211       10262 :                     if (dfDEMPixel >= psTransform->poDS->GetRasterXSize())
    1212        4311 :                         dfDEMPixel = psTransform->poDS->GetRasterXSize() - 0.5;
    1213        5951 :                     else if (dfDEMPixel < 0)
    1214        1385 :                         dfDEMPixel = 0.5;
    1215       10262 :                     if (dfDEMLine >= psTransform->poDS->GetRasterYSize())
    1216          21 :                         dfDEMLine = psTransform->poDS->GetRasterYSize() - 0.5;
    1217       10241 :                     else if (dfDEMPixel < 0)
    1218           0 :                         dfDEMPixel = 0.5;
    1219       10262 :                     if (GDALRPCGetDEMHeight(psTransform, dfDEMPixel, dfDEMLine,
    1220       10262 :                                             &dfDEMH))
    1221             :                     {
    1222        1900 :                         bUseRefZ = false;
    1223        1900 :                         CPLDebug("RPC",
    1224             :                                  "Iteration %d for (pixel, line) = (%g, %g): "
    1225             :                                  "No elevation value at %.15g %.15g. "
    1226             :                                  "Using elevation %g at DEM (pixel, line) = "
    1227             :                                  "(%g, %g) (snapping to boundaries) instead",
    1228             :                                  iIter, dfPixel, dfLine, dfResultX, dfResultY,
    1229             :                                  dfDEMH, dfDEMPixel, dfDEMLine);
    1230             :                     }
    1231             :                 }
    1232       10262 :                 if (bUseRefZ)
    1233             :                 {
    1234        8362 :                     dfDEMH = psTransform->dfRefZ;
    1235        8362 :                     CPLDebug("RPC",
    1236             :                              "Iteration %d for (pixel, line) = (%g, %g): "
    1237             :                              "No elevation value at %.15g %.15g. "
    1238             :                              "Using elevation %g of reference point instead",
    1239             :                              iIter, dfPixel, dfLine, dfResultX, dfResultY,
    1240             :                              dfDEMH);
    1241             :                 }
    1242             :             }
    1243             :             else
    1244             :             {
    1245       10072 :                 CPLDebug("RPC",
    1246             :                          "Iteration %d for (pixel, line) = (%g, %g): "
    1247             :                          "No elevation value at %.15g %.15g. Erroring out",
    1248             :                          iIter, dfPixel, dfLine, dfResultX, dfResultY);
    1249       10072 :                 if (fpLog)
    1250           0 :                     VSIFCloseL(fpLog);
    1251       10072 :                 return false;
    1252             :             }
    1253             :         }
    1254             : 
    1255       37316 :         RPCTransformPoint(psTransform, dfResultX, dfResultY,
    1256             :                           dfUserHeight + dfDEMH, &dfBackPixel, &dfBackLine);
    1257             : 
    1258       37316 :         dfPixelDeltaX = dfBackPixel - dfPixel;
    1259       37316 :         dfPixelDeltaY = dfBackLine - dfLine;
    1260             : 
    1261       37316 :         if (psTransform->bRPCInverseVerbose)
    1262             :         {
    1263           8 :             CPLDebug("RPC",
    1264             :                      "Iter %d: dfPixelDeltaX=%.02f, dfPixelDeltaY=%.02f, "
    1265             :                      "long=%f, lat=%f, height=%f",
    1266             :                      iIter, dfPixelDeltaX, dfPixelDeltaY, dfResultX, dfResultY,
    1267             :                      dfUserHeight + dfDEMH);
    1268             :         }
    1269       37316 :         if (fpLog != nullptr)
    1270             :         {
    1271           8 :             VSIFPrintfL(fpLog,
    1272             :                         "%d,%.12f,%.12f,%f,\"POINT(%.12f %.12f)\",%f,%f\n",
    1273             :                         iIter, dfResultX, dfResultY, dfUserHeight + dfDEMH,
    1274             :                         dfResultX, dfResultY, dfPixelDeltaX, dfPixelDeltaY);
    1275             :         }
    1276             : 
    1277             :         const double dfError =
    1278       37316 :             std::max(std::abs(dfPixelDeltaX), std::abs(dfPixelDeltaY));
    1279       37316 :         if (dfError < psTransform->dfPixErrThreshold)
    1280             :         {
    1281        3012 :             iIter = -1;
    1282        3012 :             if (psTransform->bRPCInverseVerbose)
    1283             :             {
    1284           1 :                 CPLDebug("RPC", "Converged!");
    1285             :             }
    1286        3012 :             break;
    1287             :         }
    1288       34304 :         else if (psTransform->poDS != nullptr && bLastPixelDeltaValid &&
    1289       21058 :                  dfPixelDeltaX * dfLastPixelDeltaX < 0 &&
    1290         106 :                  dfPixelDeltaY * dfLastPixelDeltaY < 0)
    1291             :         {
    1292             :             // When there is a DEM, if the error changes sign, we might
    1293             :             // oscillate forever, so take a mean position as a new guess.
    1294          40 :             if (psTransform->bRPCInverseVerbose)
    1295             :             {
    1296           1 :                 CPLDebug("RPC",
    1297             :                          "Oscillation detected. "
    1298             :                          "Taking mean of 2 previous results as new guess");
    1299             :             }
    1300          40 :             dfResultX = (fabs(dfPixelDeltaX) * dfLastResultX +
    1301          40 :                          fabs(dfLastPixelDeltaX) * dfResultX) /
    1302          40 :                         (fabs(dfPixelDeltaX) + fabs(dfLastPixelDeltaX));
    1303          40 :             dfResultY = (fabs(dfPixelDeltaY) * dfLastResultY +
    1304          40 :                          fabs(dfLastPixelDeltaY) * dfResultY) /
    1305          40 :                         (fabs(dfPixelDeltaY) + fabs(dfLastPixelDeltaY));
    1306          40 :             bLastPixelDeltaValid = false;
    1307          40 :             nCountConsecutiveErrorBelow2 = 0;
    1308          40 :             continue;
    1309             :         }
    1310             : 
    1311       34264 :         double dfBoostFactor = 1.0;
    1312       34264 :         if (psTransform->poDS != nullptr && nCountConsecutiveErrorBelow2 >= 5 &&
    1313             :             dfError < 2)
    1314             :         {
    1315             :             // When there is a DEM, if we remain below a given threshold
    1316             :             // (somewhat arbitrarily set to 2 pixels) for some time, apply a
    1317             :             // "boost factor" for the new guessed result, in the hope we will go
    1318             :             // out of the somewhat current stuck situation.
    1319          13 :             dfBoostFactor = 10;
    1320          13 :             if (psTransform->bRPCInverseVerbose)
    1321             :             {
    1322           1 :                 CPLDebug("RPC", "Applying boost factor 10");
    1323             :             }
    1324             :         }
    1325             : 
    1326       34264 :         if (dfError < 2)
    1327        1340 :             nCountConsecutiveErrorBelow2++;
    1328             :         else
    1329       32924 :             nCountConsecutiveErrorBelow2 = 0;
    1330             : 
    1331       34264 :         const double dfNewResultX =
    1332       34264 :             dfResultX -
    1333       34264 :             (dfPixelDeltaX * psTransform->adfPLToLatLongGeoTransform[1] *
    1334             :              dfBoostFactor) -
    1335       34264 :             (dfPixelDeltaY * psTransform->adfPLToLatLongGeoTransform[2] *
    1336             :              dfBoostFactor);
    1337       34264 :         const double dfNewResultY =
    1338       34264 :             dfResultY -
    1339       34264 :             (dfPixelDeltaX * psTransform->adfPLToLatLongGeoTransform[4] *
    1340             :              dfBoostFactor) -
    1341       34264 :             (dfPixelDeltaY * psTransform->adfPLToLatLongGeoTransform[5] *
    1342             :              dfBoostFactor);
    1343             : 
    1344       34264 :         dfLastResultX = dfResultX;
    1345       34264 :         dfLastResultY = dfResultY;
    1346       34264 :         dfResultX = dfNewResultX;
    1347       34264 :         dfResultY = dfNewResultY;
    1348       34264 :         dfLastPixelDeltaX = dfPixelDeltaX;
    1349       34264 :         dfLastPixelDeltaY = dfPixelDeltaY;
    1350       34264 :         bLastPixelDeltaValid = true;
    1351             :     }
    1352        3542 :     if (fpLog != nullptr)
    1353           1 :         VSIFCloseL(fpLog);
    1354             : 
    1355        3542 :     if (iIter != -1)
    1356             :     {
    1357         530 :         CPLDebug("RPC", "Failed Iterations %d: Got: %.16g,%.16g  Offset=%g,%g",
    1358             :                  iIter, dfResultX, dfResultY, dfPixelDeltaX, dfPixelDeltaY);
    1359         530 :         return false;
    1360             :     }
    1361             : 
    1362        3012 :     *pdfLong = dfResultX;
    1363        3012 :     *pdfLat = dfResultY;
    1364        3012 :     return true;
    1365             : }
    1366             : 
    1367             : /************************************************************************/
    1368             : /*                        GDALRPCGetDEMHeight()                         */
    1369             : /************************************************************************/
    1370             : 
    1371      306532 : static int GDALRPCGetDEMHeight(GDALRPCTransformInfo *psTransform,
    1372             :                                const double dfXIn, const double dfYIn,
    1373             :                                double *pdfDEMH)
    1374             : {
    1375      306532 :     GDALRIOResampleAlg eResample = GDALRIOResampleAlg::GRIORA_NearestNeighbour;
    1376      306532 :     switch (psTransform->eResampleAlg)
    1377             :     {
    1378          14 :         case DEMResampleAlg::DRA_NearestNeighbour:
    1379          14 :             eResample = GDALRIOResampleAlg::GRIORA_NearestNeighbour;
    1380          14 :             break;
    1381      306504 :         case DEMResampleAlg::DRA_Bilinear:
    1382      306504 :             eResample = GDALRIOResampleAlg::GRIORA_Bilinear;
    1383      306504 :             break;
    1384          14 :         case DEMResampleAlg::DRA_CubicSpline:
    1385          14 :             eResample = GDALRIOResampleAlg::GRIORA_CubicSpline;
    1386          14 :             break;
    1387             :     }
    1388             : 
    1389      306532 :     std::unique_ptr<DoublePointsCache> cacheDEM{psTransform->poCacheDEM};
    1390             :     int res =
    1391      306532 :         GDALInterpolateAtPoint(psTransform->poDS->GetRasterBand(1), eResample,
    1392      306532 :                                cacheDEM, dfXIn, dfYIn, pdfDEMH, nullptr);
    1393      306532 :     psTransform->poCacheDEM = cacheDEM.release();
    1394      613064 :     return res;
    1395             : }
    1396             : 
    1397             : /************************************************************************/
    1398             : /*                           RPCIsValidLongLat()                        */
    1399             : /************************************************************************/
    1400             : 
    1401      547280 : static bool RPCIsValidLongLat(const GDALRPCTransformInfo *psTransform,
    1402             :                               double dfLong, double dfLat)
    1403             : {
    1404      547280 :     if (!psTransform->poRPCFootprintPreparedGeom)
    1405      427167 :         return true;
    1406             : 
    1407      240226 :     OGRPoint p(dfLong, dfLat);
    1408      240226 :     return CPL_TO_BOOL(OGRPreparedGeometryContains(
    1409      240226 :         psTransform->poRPCFootprintPreparedGeom, OGRGeometry::ToHandle(&p)));
    1410             : }
    1411             : 
    1412             : /************************************************************************/
    1413             : /*                    GDALRPCTransformWholeLineWithDEM()                */
    1414             : /************************************************************************/
    1415             : 
    1416             : static int
    1417        2192 : GDALRPCTransformWholeLineWithDEM(const GDALRPCTransformInfo *psTransform,
    1418             :                                  int nPointCount, double *padfX, double *padfY,
    1419             :                                  double *padfZ, int *panSuccess, int nXLeft,
    1420             :                                  int nXWidth, int nYTop, int nYHeight)
    1421             : {
    1422             :     double *padfDEMBuffer = static_cast<double *>(
    1423        2192 :         VSI_MALLOC3_VERBOSE(sizeof(double), nXWidth, nYHeight));
    1424        2192 :     if (padfDEMBuffer == nullptr)
    1425             :     {
    1426           0 :         for (int i = 0; i < nPointCount; i++)
    1427           0 :             panSuccess[i] = FALSE;
    1428           0 :         return FALSE;
    1429             :     }
    1430        2192 :     CPLErr eErr = psTransform->poDS->GetRasterBand(1)->RasterIO(
    1431             :         GF_Read, nXLeft, nYTop, nXWidth, nYHeight, padfDEMBuffer, nXWidth,
    1432             :         nYHeight, GDT_Float64, 0, 0, nullptr);
    1433        2192 :     if (eErr != CE_None)
    1434             :     {
    1435           0 :         for (int i = 0; i < nPointCount; i++)
    1436           0 :             panSuccess[i] = FALSE;
    1437           0 :         VSIFree(padfDEMBuffer);
    1438           0 :         return FALSE;
    1439             :     }
    1440             : 
    1441        2192 :     int bGotNoDataValue = FALSE;
    1442             :     const double dfNoDataValue =
    1443        2192 :         psTransform->poDS->GetRasterBand(1)->GetNoDataValue(&bGotNoDataValue);
    1444             : 
    1445             :     // dfY in pixel center convention.
    1446        2192 :     const double dfY = psTransform->adfDEMReverseGeoTransform[3] +
    1447        2192 :                        padfY[0] * psTransform->adfDEMReverseGeoTransform[5] -
    1448             :                        0.5;
    1449        2192 :     const int nY = static_cast<int>(dfY);
    1450        2192 :     const double dfDeltaY = dfY - nY;
    1451             : 
    1452      180781 :     for (int i = 0; i < nPointCount; i++)
    1453             :     {
    1454      178589 :         if (padfX[i] == HUGE_VAL)
    1455           0 :             continue;
    1456             : 
    1457      178589 :         double dfDEMH = 0.0;
    1458      178589 :         const double dfZ_i = padfZ ? padfZ[i] : 0.0;
    1459             : 
    1460      178589 :         if (psTransform->eResampleAlg == DRA_CubicSpline)
    1461             :         {
    1462             :             // dfX in pixel center convention.
    1463          10 :             const double dfX =
    1464          10 :                 psTransform->adfDEMReverseGeoTransform[0] +
    1465          10 :                 padfX[i] * psTransform->adfDEMReverseGeoTransform[1] - 0.5;
    1466          10 :             const int nX = static_cast<int>(dfX);
    1467          10 :             const double dfDeltaX = dfX - nX;
    1468             : 
    1469          10 :             const int nXNew = nX - 1;
    1470             : 
    1471          10 :             double dfSumH = 0.0;
    1472          10 :             double dfSumWeight = 0.0;
    1473          50 :             for (int k_i = 0; k_i < 4; k_i++)
    1474             :             {
    1475             :                 // Loop across the X axis.
    1476         200 :                 for (int k_j = 0; k_j < 4; k_j++)
    1477             :                 {
    1478             :                     // Calculate the weight for the specified pixel according
    1479             :                     // to the bicubic b-spline kernel we're using for
    1480             :                     // interpolation.
    1481         160 :                     const int dKernIndX = k_j - 1;
    1482         160 :                     const int dKernIndY = k_i - 1;
    1483             :                     const double dfPixelWeight =
    1484         160 :                         CubicSplineKernel(dKernIndX - dfDeltaX) *
    1485         160 :                         CubicSplineKernel(dKernIndY - dfDeltaY);
    1486             : 
    1487             :                     // Create a sum of all values
    1488             :                     // adjusted for the pixel's calculated weight.
    1489         160 :                     const double dfElev =
    1490         160 :                         padfDEMBuffer[k_i * nXWidth + nXNew - nXLeft + k_j];
    1491         160 :                     if (bGotNoDataValue &&
    1492         160 :                         ARE_REAL_EQUAL(dfNoDataValue, dfElev))
    1493           0 :                         continue;
    1494             : 
    1495         160 :                     dfSumH += dfElev * dfPixelWeight;
    1496         160 :                     dfSumWeight += dfPixelWeight;
    1497             :                 }
    1498             :             }
    1499          10 :             if (dfSumWeight == 0.0)
    1500             :             {
    1501           0 :                 if (psTransform->bHasDEMMissingValue)
    1502           0 :                     dfDEMH = psTransform->dfDEMMissingValue;
    1503             :                 else
    1504             :                 {
    1505           0 :                     panSuccess[i] = FALSE;
    1506           0 :                     continue;
    1507             :                 }
    1508             :             }
    1509             :             else
    1510          10 :                 dfDEMH = dfSumH / dfSumWeight;
    1511             :         }
    1512      178579 :         else if (psTransform->eResampleAlg == DRA_Bilinear)
    1513             :         {
    1514             :             // dfX in pixel center convention.
    1515      178569 :             const double dfX =
    1516      178569 :                 psTransform->adfDEMReverseGeoTransform[0] +
    1517      178569 :                 padfX[i] * psTransform->adfDEMReverseGeoTransform[1] - 0.5;
    1518      178569 :             const int nX = static_cast<int>(dfX);
    1519      178569 :             const double dfDeltaX = dfX - nX;
    1520             : 
    1521             :             // Bilinear interpolation.
    1522      178569 :             double adfElevData[4] = {};
    1523      178569 :             memcpy(adfElevData, padfDEMBuffer + nX - nXLeft,
    1524             :                    2 * sizeof(double));
    1525      178569 :             memcpy(adfElevData + 2, padfDEMBuffer + nXWidth + nX - nXLeft,
    1526             :                    2 * sizeof(double));
    1527             : 
    1528      178569 :             int bFoundNoDataElev = FALSE;
    1529      178569 :             if (bGotNoDataValue)
    1530             :             {
    1531       46350 :                 int k_valid_sample = -1;
    1532      231750 :                 for (int k_i = 0; k_i < 4; k_i++)
    1533             :                 {
    1534      185400 :                     if (ARE_REAL_EQUAL(dfNoDataValue, adfElevData[k_i]))
    1535             :                     {
    1536           0 :                         bFoundNoDataElev = TRUE;
    1537             :                     }
    1538      185400 :                     else if (k_valid_sample < 0)
    1539             :                     {
    1540       46350 :                         k_valid_sample = k_i;
    1541             :                     }
    1542             :                 }
    1543       46350 :                 if (bFoundNoDataElev)
    1544             :                 {
    1545           0 :                     if (k_valid_sample >= 0)
    1546             :                     {
    1547           0 :                         if (!RPCIsValidLongLat(psTransform, padfX[i], padfY[i]))
    1548             :                         {
    1549           0 :                             panSuccess[i] = FALSE;
    1550           0 :                             padfX[i] = HUGE_VAL;
    1551           0 :                             padfY[i] = HUGE_VAL;
    1552           0 :                             continue;
    1553             :                         }
    1554           0 :                         dfDEMH = adfElevData[k_valid_sample];
    1555           0 :                         RPCTransformPoint(
    1556           0 :                             psTransform, padfX[i], padfY[i],
    1557           0 :                             dfZ_i + (psTransform->dfHeightOffset + dfDEMH) *
    1558           0 :                                         psTransform->dfHeightScale,
    1559           0 :                             padfX + i, padfY + i);
    1560             : 
    1561           0 :                         panSuccess[i] = TRUE;
    1562           0 :                         continue;
    1563             :                     }
    1564           0 :                     else if (psTransform->bHasDEMMissingValue)
    1565             :                     {
    1566           0 :                         if (!RPCIsValidLongLat(psTransform, padfX[i], padfY[i]))
    1567             :                         {
    1568           0 :                             panSuccess[i] = FALSE;
    1569           0 :                             padfX[i] = HUGE_VAL;
    1570           0 :                             padfY[i] = HUGE_VAL;
    1571           0 :                             continue;
    1572             :                         }
    1573           0 :                         dfDEMH = psTransform->dfDEMMissingValue;
    1574           0 :                         RPCTransformPoint(
    1575           0 :                             psTransform, padfX[i], padfY[i],
    1576           0 :                             dfZ_i + (psTransform->dfHeightOffset + dfDEMH) *
    1577           0 :                                         psTransform->dfHeightScale,
    1578           0 :                             padfX + i, padfY + i);
    1579             : 
    1580           0 :                         panSuccess[i] = TRUE;
    1581           0 :                         continue;
    1582             :                     }
    1583             :                     else
    1584             :                     {
    1585           0 :                         panSuccess[i] = FALSE;
    1586           0 :                         padfX[i] = HUGE_VAL;
    1587           0 :                         padfY[i] = HUGE_VAL;
    1588           0 :                         continue;
    1589             :                     }
    1590             :                 }
    1591             :             }
    1592      178569 :             const double dfDeltaX1 = 1.0 - dfDeltaX;
    1593      178569 :             const double dfDeltaY1 = 1.0 - dfDeltaY;
    1594             : 
    1595      178569 :             const double dfXZ1 =
    1596      178569 :                 adfElevData[0] * dfDeltaX1 + adfElevData[1] * dfDeltaX;
    1597      178569 :             const double dfXZ2 =
    1598      178569 :                 adfElevData[2] * dfDeltaX1 + adfElevData[3] * dfDeltaX;
    1599      178569 :             const double dfYZ = dfXZ1 * dfDeltaY1 + dfXZ2 * dfDeltaY;
    1600      178569 :             dfDEMH = dfYZ;
    1601             :         }
    1602             :         else
    1603             :         {
    1604          10 :             const double dfX =
    1605          10 :                 psTransform->adfDEMReverseGeoTransform[0] +
    1606          10 :                 padfX[i] * psTransform->adfDEMReverseGeoTransform[1];
    1607          10 :             const int nX = int(dfX);
    1608             : 
    1609          10 :             dfDEMH = padfDEMBuffer[nX - nXLeft];
    1610          10 :             if (bGotNoDataValue && ARE_REAL_EQUAL(dfNoDataValue, dfDEMH))
    1611             :             {
    1612           0 :                 if (psTransform->bHasDEMMissingValue)
    1613           0 :                     dfDEMH = psTransform->dfDEMMissingValue;
    1614             :                 else
    1615             :                 {
    1616           0 :                     panSuccess[i] = FALSE;
    1617           0 :                     padfX[i] = HUGE_VAL;
    1618           0 :                     padfY[i] = HUGE_VAL;
    1619           0 :                     continue;
    1620             :                 }
    1621             :             }
    1622             :         }
    1623             : 
    1624      178589 :         if (!RPCIsValidLongLat(psTransform, padfX[i], padfY[i]))
    1625             :         {
    1626           0 :             panSuccess[i] = FALSE;
    1627           0 :             padfX[i] = HUGE_VAL;
    1628           0 :             padfY[i] = HUGE_VAL;
    1629           0 :             continue;
    1630             :         }
    1631      178589 :         RPCTransformPoint(psTransform, padfX[i], padfY[i],
    1632      178589 :                           dfZ_i + (psTransform->dfHeightOffset + dfDEMH) *
    1633      178589 :                                       psTransform->dfHeightScale,
    1634      178589 :                           padfX + i, padfY + i);
    1635             : 
    1636      178589 :         panSuccess[i] = TRUE;
    1637             :     }
    1638             : 
    1639        2192 :     VSIFree(padfDEMBuffer);
    1640             : 
    1641        2192 :     return TRUE;
    1642             : }
    1643             : 
    1644             : /************************************************************************/
    1645             : /*                           GDALRPCOpenDEM()                           */
    1646             : /************************************************************************/
    1647             : 
    1648          33 : static bool GDALRPCOpenDEM(GDALRPCTransformInfo *psTransform)
    1649             : {
    1650          33 :     CPLAssert(psTransform->pszDEMPath != nullptr);
    1651             : 
    1652          33 :     bool bIsValid = false;
    1653             : 
    1654          66 :     CPLString osPrevValueConfigOption;
    1655          33 :     if (psTransform->bApplyDEMVDatumShift)
    1656             :     {
    1657             :         osPrevValueConfigOption =
    1658          32 :             CPLGetThreadLocalConfigOption("GTIFF_REPORT_COMPD_CS", "");
    1659          32 :         CPLSetThreadLocalConfigOption("GTIFF_REPORT_COMPD_CS", "YES");
    1660             :     }
    1661          33 :     CPLConfigOptionSetter oSetter("CPL_ALLOW_VSISTDIN", "NO", true);
    1662          33 :     psTransform->poDS =
    1663          33 :         GDALDataset::FromHandle(GDALOpen(psTransform->pszDEMPath, GA_ReadOnly));
    1664          65 :     if (psTransform->poDS != nullptr &&
    1665          32 :         psTransform->poDS->GetRasterCount() >= 1)
    1666             :     {
    1667          64 :         OGRSpatialReference oDEMSRS;
    1668          32 :         if (psTransform->pszDEMSRS != nullptr)
    1669             :         {
    1670           1 :             oDEMSRS.SetFromUserInput(psTransform->pszDEMSRS);
    1671           1 :             oDEMSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    1672             :         }
    1673             : 
    1674          32 :         auto poDSSpaRefSrc = psTransform->pszDEMSRS != nullptr
    1675          32 :                                  ? &oDEMSRS
    1676          31 :                                  : psTransform->poDS->GetSpatialRef();
    1677          32 :         if (poDSSpaRefSrc)
    1678             :         {
    1679          32 :             auto poDSSpaRef = poDSSpaRefSrc->Clone();
    1680             : 
    1681          32 :             if (!psTransform->bApplyDEMVDatumShift)
    1682           1 :                 poDSSpaRef->StripVertical();
    1683             : 
    1684          32 :             auto wkt_EPSG_4979 =
    1685             :                 "GEODCRS[\"WGS 84\",\n"
    1686             :                 "    DATUM[\"World Geodetic System 1984\",\n"
    1687             :                 "        ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
    1688             :                 "            LENGTHUNIT[\"metre\",1]]],\n"
    1689             :                 "    PRIMEM[\"Greenwich\",0,\n"
    1690             :                 "        ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
    1691             :                 "    CS[ellipsoidal,3],\n"
    1692             :                 "        AXIS[\"geodetic latitude (Lat)\",north,\n"
    1693             :                 "            ORDER[1],\n"
    1694             :                 "            ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
    1695             :                 "        AXIS[\"geodetic longitude (Lon)\",east,\n"
    1696             :                 "            ORDER[2],\n"
    1697             :                 "            ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
    1698             :                 "        AXIS[\"ellipsoidal height (h)\",up,\n"
    1699             :                 "            ORDER[3],\n"
    1700             :                 "            LENGTHUNIT[\"metre\",1]],\n"
    1701             :                 "    AREA[\"World (by country)\"],\n"
    1702             :                 "    BBOX[-90,-180,90,180],\n"
    1703             :                 "    ID[\"EPSG\",4979]]";
    1704             :             OGRSpatialReference *poWGSSpaRef = new OGRSpatialReference(
    1705          32 :                 poDSSpaRef->IsCompound() ? wkt_EPSG_4979
    1706          32 :                                          : SRS_WKT_WGS84_LAT_LONG);
    1707          32 :             poWGSSpaRef->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    1708             : 
    1709          32 :             if (!poWGSSpaRef->IsSame(poDSSpaRef))
    1710          12 :                 psTransform->poCT =
    1711          12 :                     OGRCreateCoordinateTransformation(poWGSSpaRef, poDSSpaRef);
    1712             : 
    1713          32 :             if (psTransform->poCT != nullptr && !poDSSpaRef->IsCompound())
    1714             :             {
    1715             :                 // Empiric attempt to guess if the coordinate transformation
    1716             :                 // to WGS84 is a no-op. For example for NED13 datasets in
    1717             :                 // NAD83.
    1718          10 :                 double adfX[] = {-179.0, 179.0, 179.0, -179.0, 0.0, 0.0};
    1719          10 :                 double adfY[] = {89.0, 89.0, -89.0, -89.0, 0.0, 0.0};
    1720          10 :                 double adfZ[] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
    1721             : 
    1722             :                 // Also test with a "reference point" from the RPC values.
    1723          10 :                 double dfRefLong = 0.0;
    1724          10 :                 double dfRefLat = 0.0;
    1725          10 :                 if (psTransform->sRPC.dfMIN_LONG != -180 ||
    1726          10 :                     psTransform->sRPC.dfMAX_LONG != 180)
    1727             :                 {
    1728           0 :                     dfRefLong = (psTransform->sRPC.dfMIN_LONG +
    1729           0 :                                  psTransform->sRPC.dfMAX_LONG) *
    1730             :                                 0.5;
    1731           0 :                     dfRefLat = (psTransform->sRPC.dfMIN_LAT +
    1732           0 :                                 psTransform->sRPC.dfMAX_LAT) *
    1733             :                                0.5;
    1734             :                 }
    1735             :                 else
    1736             :                 {
    1737          10 :                     dfRefLong = psTransform->sRPC.dfLONG_OFF;
    1738          10 :                     dfRefLat = psTransform->sRPC.dfLAT_OFF;
    1739             :                 }
    1740          10 :                 adfX[5] = dfRefLong;
    1741          10 :                 adfY[5] = dfRefLat;
    1742             : 
    1743          10 :                 if (psTransform->poCT->Transform(6, adfX, adfY, adfZ) &&
    1744          10 :                     fabs(adfX[0] - -179.0) < 1.0e-12 &&
    1745           1 :                     fabs(adfY[0] - 89.0) < 1.0e-12 &&
    1746           1 :                     fabs(adfX[1] - 179.0) < 1.0e-12 &&
    1747           1 :                     fabs(adfY[1] - 89.0) < 1.0e-12 &&
    1748           1 :                     fabs(adfX[2] - 179.0) < 1.0e-12 &&
    1749           1 :                     fabs(adfY[2] - -89.0) < 1.0e-12 &&
    1750           1 :                     fabs(adfX[3] - -179.0) < 1.0e-12 &&
    1751           1 :                     fabs(adfY[3] - -89.0) < 1.0e-12 &&
    1752           1 :                     fabs(adfX[4] - 0.0) < 1.0e-12 &&
    1753           1 :                     fabs(adfY[4] - 0.0) < 1.0e-12 &&
    1754          21 :                     fabs(adfX[5] - dfRefLong) < 1.0e-12 &&
    1755           1 :                     fabs(adfY[5] - dfRefLat) < 1.0e-12)
    1756             :                 {
    1757           1 :                     CPLDebug("RPC",
    1758             :                              "Short-circuiting coordinate transformation "
    1759             :                              "from DEM SRS to WGS 84 due to apparent nop");
    1760           1 :                     delete psTransform->poCT;
    1761           1 :                     psTransform->poCT = nullptr;
    1762             :                 }
    1763             :             }
    1764             : 
    1765          32 :             delete poWGSSpaRef;
    1766          32 :             delete poDSSpaRef;
    1767             :         }
    1768             : 
    1769          96 :         if (psTransform->poDS->GetGeoTransform(
    1770          64 :                 psTransform->adfDEMGeoTransform) == CE_None &&
    1771          32 :             GDALInvGeoTransform(psTransform->adfDEMGeoTransform,
    1772          32 :                                 psTransform->adfDEMReverseGeoTransform))
    1773             :         {
    1774          32 :             bIsValid = true;
    1775             :         }
    1776             :     }
    1777             : 
    1778          33 :     if (psTransform->bApplyDEMVDatumShift)
    1779             :     {
    1780          32 :         CPLSetThreadLocalConfigOption("GTIFF_REPORT_COMPD_CS",
    1781          32 :                                       !osPrevValueConfigOption.empty()
    1782           0 :                                           ? osPrevValueConfigOption.c_str()
    1783             :                                           : nullptr);
    1784             :     }
    1785             : 
    1786          66 :     return bIsValid;
    1787             : }
    1788             : 
    1789             : /************************************************************************/
    1790             : /*                          GDALRPCTransform()                          */
    1791             : /************************************************************************/
    1792             : 
    1793             : /** RPC transform */
    1794        9386 : int GDALRPCTransform(void *pTransformArg, int bDstToSrc, int nPointCount,
    1795             :                      double *padfX, double *padfY, double *padfZ,
    1796             :                      int *panSuccess)
    1797             : 
    1798             : {
    1799        9386 :     VALIDATE_POINTER1(pTransformArg, "GDALRPCTransform", 0);
    1800             : 
    1801        9386 :     GDALRPCTransformInfo *psTransform =
    1802             :         static_cast<GDALRPCTransformInfo *>(pTransformArg);
    1803             : 
    1804        9386 :     if (psTransform->bReversed)
    1805           0 :         bDstToSrc = !bDstToSrc;
    1806             : 
    1807             :     /* -------------------------------------------------------------------- */
    1808             :     /*      The simple case is transforming from lat/long to pixel/line.    */
    1809             :     /*      Just apply the equations directly.                              */
    1810             :     /* -------------------------------------------------------------------- */
    1811        9386 :     if (bDstToSrc)
    1812             :     {
    1813             :         // Optimization to avoid doing too many picking in DEM in the particular
    1814             :         // case where each point to transform is on a single line of the DEM.
    1815             :         // To make it simple and fast we check that all input latitudes are
    1816             :         // identical, that the DEM is in WGS84 geodetic and that it has no
    1817             :         // rotation.  Such case is for example triggered when doing gdalwarp
    1818             :         // with a target SRS of EPSG:4326 or EPSG:3857.
    1819        3473 :         if (nPointCount >= 10 && psTransform->poDS != nullptr &&
    1820        3446 :             psTransform->poCT == nullptr &&
    1821        2884 :             padfY[0] == padfY[nPointCount - 1] &&
    1822        2826 :             padfY[0] == padfY[nPointCount / 2] &&
    1823        2822 :             psTransform->adfDEMReverseGeoTransform[1] > 0.0 &&
    1824        2822 :             psTransform->adfDEMReverseGeoTransform[2] == 0.0 &&
    1825       13948 :             psTransform->adfDEMReverseGeoTransform[4] == 0.0 &&
    1826        2822 :             CPLTestBool(CPLGetConfigOption("GDAL_RPC_DEM_OPTIM", "YES")))
    1827             :         {
    1828        2822 :             bool bUseOptimized = true;
    1829        2822 :             double dfMinX = padfX[0];
    1830        2822 :             double dfMaxX = padfX[0];
    1831      327074 :             for (int i = 1; i < nPointCount; i++)
    1832             :             {
    1833      324252 :                 if (padfY[i] != padfY[0])
    1834             :                 {
    1835           0 :                     bUseOptimized = false;
    1836           0 :                     break;
    1837             :                 }
    1838      324252 :                 if (padfX[i] < dfMinX)
    1839           0 :                     dfMinX = padfX[i];
    1840      324252 :                 if (padfX[i] > dfMaxX)
    1841      324225 :                     dfMaxX = padfX[i];
    1842             :             }
    1843        2822 :             if (bUseOptimized)
    1844             :             {
    1845        2822 :                 double dfX1 = 0.0;
    1846        2822 :                 double dfY1 = 0.0;
    1847        2822 :                 double dfX2 = 0.0;
    1848        2822 :                 double dfY2 = 0.0;
    1849        2822 :                 GDALApplyGeoTransform(psTransform->adfDEMReverseGeoTransform,
    1850             :                                       dfMinX, padfY[0], &dfX1, &dfY1);
    1851        2822 :                 GDALApplyGeoTransform(psTransform->adfDEMReverseGeoTransform,
    1852             :                                       dfMaxX, padfY[0], &dfX2, &dfY2);
    1853             : 
    1854             :                 // Convert to center of pixel convention for reading the image
    1855             :                 // data.
    1856        2822 :                 if (psTransform->eResampleAlg != DRA_NearestNeighbour)
    1857             :                 {
    1858        2821 :                     dfX1 -= 0.5;
    1859        2821 :                     dfY1 -= 0.5;
    1860        2821 :                     dfX2 -= 0.5;
    1861             :                     // dfY2 -= 0.5;
    1862             :                 }
    1863        2822 :                 int nXLeft = static_cast<int>(floor(dfX1));
    1864        2822 :                 int nXRight = static_cast<int>(floor(dfX2));
    1865        2822 :                 int nXWidth = nXRight - nXLeft + 1;
    1866        2822 :                 int nYTop = static_cast<int>(floor(dfY1));
    1867             :                 int nYHeight;
    1868        2822 :                 if (psTransform->eResampleAlg == DRA_CubicSpline)
    1869             :                 {
    1870           1 :                     nXLeft--;
    1871           1 :                     nXWidth += 3;
    1872           1 :                     nYTop--;
    1873           1 :                     nYHeight = 4;
    1874             :                 }
    1875        2821 :                 else if (psTransform->eResampleAlg == DRA_Bilinear)
    1876             :                 {
    1877        2820 :                     nXWidth++;
    1878        2820 :                     nYHeight = 2;
    1879             :                 }
    1880             :                 else
    1881             :                 {
    1882           1 :                     nYHeight = 1;
    1883             :                 }
    1884        5022 :                 if (nXLeft >= 0 &&
    1885        2200 :                     nXLeft + nXWidth <= psTransform->poDS->GetRasterXSize() &&
    1886        5022 :                     nYTop >= 0 &&
    1887        2200 :                     nYTop + nYHeight <= psTransform->poDS->GetRasterYSize())
    1888             :                 {
    1889             :                     static bool bOnce = false;
    1890        2192 :                     if (!bOnce)
    1891             :                     {
    1892           2 :                         bOnce = true;
    1893           2 :                         CPLDebug("RPC",
    1894             :                                  "Using GDALRPCTransformWholeLineWithDEM");
    1895             :                     }
    1896        2192 :                     return GDALRPCTransformWholeLineWithDEM(
    1897             :                         psTransform, nPointCount, padfX, padfY, padfZ,
    1898        2192 :                         panSuccess, nXLeft, nXWidth, nYTop, nYHeight);
    1899             :                 }
    1900             :             }
    1901             :         }
    1902             : 
    1903      371140 :         for (int i = 0; i < nPointCount; i++)
    1904             :         {
    1905      365679 :             if (!RPCIsValidLongLat(psTransform, padfX[i], padfY[i]))
    1906             :             {
    1907      108994 :                 panSuccess[i] = FALSE;
    1908      108994 :                 padfX[i] = HUGE_VAL;
    1909      108994 :                 padfY[i] = HUGE_VAL;
    1910      121421 :                 continue;
    1911             :             }
    1912      256685 :             double dfHeight = 0.0;
    1913      256685 :             if (!GDALRPCGetHeightAtLongLat(psTransform, padfX[i], padfY[i],
    1914             :                                            &dfHeight))
    1915             :             {
    1916       12427 :                 panSuccess[i] = FALSE;
    1917       12427 :                 padfX[i] = HUGE_VAL;
    1918       12427 :                 padfY[i] = HUGE_VAL;
    1919       12427 :                 continue;
    1920             :             }
    1921             : 
    1922      244258 :             RPCTransformPoint(psTransform, padfX[i], padfY[i],
    1923      244258 :                               (padfZ ? padfZ[i] : 0.0) + dfHeight, padfX + i,
    1924      244258 :                               padfY + i);
    1925      244258 :             panSuccess[i] = TRUE;
    1926             :         }
    1927             : 
    1928        5461 :         return TRUE;
    1929             :     }
    1930             : 
    1931        1733 :     if (padfZ == nullptr)
    1932             :     {
    1933           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    1934             :                  "Z array should be provided for reverse RPC computation");
    1935           0 :         return FALSE;
    1936             :     }
    1937             : 
    1938             :     /* -------------------------------------------------------------------- */
    1939             :     /*      Compute the inverse (pixel/line/height to lat/long).  This      */
    1940             :     /*      function uses an iterative method from an initial linear        */
    1941             :     /*      approximation.                                                  */
    1942             :     /* -------------------------------------------------------------------- */
    1943       15347 :     for (int i = 0; i < nPointCount; i++)
    1944             :     {
    1945       13614 :         double dfResultX = 0.0;
    1946       13614 :         double dfResultY = 0.0;
    1947             : 
    1948       13614 :         if (!RPCInverseTransformPoint(psTransform, padfX[i], padfY[i], padfZ[i],
    1949             :                                       &dfResultX, &dfResultY))
    1950             :         {
    1951       10602 :             panSuccess[i] = FALSE;
    1952       10602 :             padfX[i] = HUGE_VAL;
    1953       10602 :             padfY[i] = HUGE_VAL;
    1954       10602 :             continue;
    1955             :         }
    1956        3012 :         if (!RPCIsValidLongLat(psTransform, padfX[i], padfY[i]))
    1957             :         {
    1958           0 :             panSuccess[i] = FALSE;
    1959           0 :             padfX[i] = HUGE_VAL;
    1960           0 :             padfY[i] = HUGE_VAL;
    1961           0 :             continue;
    1962             :         }
    1963             : 
    1964        3012 :         padfX[i] = dfResultX;
    1965        3012 :         padfY[i] = dfResultY;
    1966             : 
    1967        3012 :         panSuccess[i] = TRUE;
    1968             :     }
    1969             : 
    1970        1733 :     return TRUE;
    1971             : }
    1972             : 
    1973             : /************************************************************************/
    1974             : /*                    GDALSerializeRPCTransformer()                     */
    1975             : /************************************************************************/
    1976             : 
    1977           1 : CPLXMLNode *GDALSerializeRPCTransformer(void *pTransformArg)
    1978             : 
    1979             : {
    1980           1 :     VALIDATE_POINTER1(pTransformArg, "GDALSerializeRPCTransformer", nullptr);
    1981             : 
    1982           1 :     GDALRPCTransformInfo *psInfo =
    1983             :         static_cast<GDALRPCTransformInfo *>(pTransformArg);
    1984             : 
    1985             :     CPLXMLNode *psTree =
    1986           1 :         CPLCreateXMLNode(nullptr, CXT_Element, "RPCTransformer");
    1987             : 
    1988             :     /* -------------------------------------------------------------------- */
    1989             :     /*      Serialize bReversed.                                            */
    1990             :     /* -------------------------------------------------------------------- */
    1991           1 :     CPLCreateXMLElementAndValue(psTree, "Reversed",
    1992           2 :                                 CPLString().Printf("%d", psInfo->bReversed));
    1993             : 
    1994             :     /* -------------------------------------------------------------------- */
    1995             :     /*      Serialize Height Offset.                                        */
    1996             :     /* -------------------------------------------------------------------- */
    1997           1 :     CPLCreateXMLElementAndValue(
    1998             :         psTree, "HeightOffset",
    1999           2 :         CPLString().Printf("%.15g", psInfo->dfHeightOffset));
    2000             : 
    2001             :     /* -------------------------------------------------------------------- */
    2002             :     /*      Serialize Height Scale.                                         */
    2003             :     /* -------------------------------------------------------------------- */
    2004           1 :     if (psInfo->dfHeightScale != 1.0)
    2005           0 :         CPLCreateXMLElementAndValue(
    2006             :             psTree, "HeightScale",
    2007           0 :             CPLString().Printf("%.15g", psInfo->dfHeightScale));
    2008             : 
    2009             :     /* -------------------------------------------------------------------- */
    2010             :     /*      Serialize DEM path.                                             */
    2011             :     /* -------------------------------------------------------------------- */
    2012           1 :     if (psInfo->pszDEMPath != nullptr)
    2013             :     {
    2014           0 :         CPLCreateXMLElementAndValue(
    2015           0 :             psTree, "DEMPath", CPLString().Printf("%s", psInfo->pszDEMPath));
    2016             : 
    2017             :         /* --------------------------------------------------------------------
    2018             :          */
    2019             :         /*      Serialize DEM interpolation */
    2020             :         /* --------------------------------------------------------------------
    2021             :          */
    2022           0 :         CPLCreateXMLElementAndValue(
    2023             :             psTree, "DEMInterpolation",
    2024             :             GDALSerializeRPCDEMResample(psInfo->eResampleAlg));
    2025             : 
    2026           0 :         if (psInfo->bHasDEMMissingValue)
    2027             :         {
    2028           0 :             CPLCreateXMLElementAndValue(
    2029             :                 psTree, "DEMMissingValue",
    2030             :                 CPLSPrintf("%.17g", psInfo->dfDEMMissingValue));
    2031             :         }
    2032             : 
    2033           0 :         CPLCreateXMLElementAndValue(psTree, "DEMApplyVDatumShift",
    2034           0 :                                     psInfo->bApplyDEMVDatumShift ? "true"
    2035             :                                                                  : "false");
    2036             : 
    2037             :         /* --------------------------------------------------------------------
    2038             :          */
    2039             :         /*      Serialize DEM SRS */
    2040             :         /* --------------------------------------------------------------------
    2041             :          */
    2042           0 :         if (psInfo->pszDEMSRS != nullptr)
    2043             :         {
    2044           0 :             CPLCreateXMLElementAndValue(psTree, "DEMSRS", psInfo->pszDEMSRS);
    2045             :         }
    2046             :     }
    2047             : 
    2048             :     /* -------------------------------------------------------------------- */
    2049             :     /*      Serialize pixel error threshold.                                */
    2050             :     /* -------------------------------------------------------------------- */
    2051           1 :     CPLCreateXMLElementAndValue(
    2052             :         psTree, "PixErrThreshold",
    2053           2 :         CPLString().Printf("%.15g", psInfo->dfPixErrThreshold));
    2054             : 
    2055             :     /* -------------------------------------------------------------------- */
    2056             :     /*      RPC metadata.                                                   */
    2057             :     /* -------------------------------------------------------------------- */
    2058           1 :     char **papszMD = RPCInfoV2ToMD(&(psInfo->sRPC));
    2059           1 :     CPLXMLNode *psMD = CPLCreateXMLNode(psTree, CXT_Element, "Metadata");
    2060             : 
    2061          21 :     for (int i = 0; papszMD != nullptr && papszMD[i] != nullptr; i++)
    2062             :     {
    2063          20 :         char *pszKey = nullptr;
    2064             : 
    2065          20 :         const char *pszRawValue = CPLParseNameValue(papszMD[i], &pszKey);
    2066             : 
    2067          20 :         CPLXMLNode *psMDI = CPLCreateXMLNode(psMD, CXT_Element, "MDI");
    2068          20 :         CPLSetXMLValue(psMDI, "#key", pszKey);
    2069          20 :         CPLCreateXMLNode(psMDI, CXT_Text, pszRawValue);
    2070             : 
    2071          20 :         CPLFree(pszKey);
    2072             :     }
    2073             : 
    2074           1 :     CSLDestroy(papszMD);
    2075             : 
    2076           1 :     return psTree;
    2077             : }
    2078             : 
    2079             : /************************************************************************/
    2080             : /*                   GDALDeserializeRPCTransformer()                    */
    2081             : /************************************************************************/
    2082             : 
    2083           0 : void *GDALDeserializeRPCTransformer(CPLXMLNode *psTree)
    2084             : 
    2085             : {
    2086           0 :     char **papszOptions = nullptr;
    2087             : 
    2088             :     /* -------------------------------------------------------------------- */
    2089             :     /*      Collect metadata.                                               */
    2090             :     /* -------------------------------------------------------------------- */
    2091           0 :     CPLXMLNode *psMetadata = CPLGetXMLNode(psTree, "Metadata");
    2092             : 
    2093           0 :     if (psMetadata == nullptr || psMetadata->eType != CXT_Element ||
    2094           0 :         !EQUAL(psMetadata->pszValue, "Metadata"))
    2095           0 :         return nullptr;
    2096             : 
    2097           0 :     char **papszMD = nullptr;
    2098           0 :     for (CPLXMLNode *psMDI = psMetadata->psChild; psMDI != nullptr;
    2099           0 :          psMDI = psMDI->psNext)
    2100             :     {
    2101           0 :         if (!EQUAL(psMDI->pszValue, "MDI") || psMDI->eType != CXT_Element ||
    2102           0 :             psMDI->psChild == nullptr || psMDI->psChild->psNext == nullptr ||
    2103           0 :             psMDI->psChild->eType != CXT_Attribute ||
    2104           0 :             psMDI->psChild->psChild == nullptr)
    2105           0 :             continue;
    2106             : 
    2107           0 :         papszMD = CSLSetNameValue(papszMD, psMDI->psChild->psChild->pszValue,
    2108           0 :                                   psMDI->psChild->psNext->pszValue);
    2109             :     }
    2110             : 
    2111             :     GDALRPCInfoV2 sRPC;
    2112           0 :     if (!GDALExtractRPCInfoV2(papszMD, &sRPC))
    2113             :     {
    2114           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    2115             :                  "Failed to reconstitute RPC transformer.");
    2116           0 :         CSLDestroy(papszMD);
    2117           0 :         return nullptr;
    2118             :     }
    2119             : 
    2120           0 :     CSLDestroy(papszMD);
    2121             : 
    2122             :     /* -------------------------------------------------------------------- */
    2123             :     /*      Get other flags.                                                */
    2124             :     /* -------------------------------------------------------------------- */
    2125           0 :     const int bReversed = atoi(CPLGetXMLValue(psTree, "Reversed", "0"));
    2126             : 
    2127             :     const double dfPixErrThreshold =
    2128           0 :         CPLAtof(CPLGetXMLValue(psTree, "PixErrThreshold",
    2129             :                                CPLSPrintf("%f", DEFAULT_PIX_ERR_THRESHOLD)));
    2130             : 
    2131           0 :     papszOptions = CSLSetNameValue(papszOptions, "RPC_HEIGHT",
    2132             :                                    CPLGetXMLValue(psTree, "HeightOffset", "0"));
    2133           0 :     papszOptions = CSLSetNameValue(papszOptions, "RPC_HEIGHT_SCALE",
    2134             :                                    CPLGetXMLValue(psTree, "HeightScale", "1"));
    2135           0 :     const char *pszDEMPath = CPLGetXMLValue(psTree, "DEMPath", nullptr);
    2136           0 :     if (pszDEMPath != nullptr)
    2137           0 :         papszOptions = CSLSetNameValue(papszOptions, "RPC_DEM", pszDEMPath);
    2138             : 
    2139             :     const char *pszDEMInterpolation =
    2140           0 :         CPLGetXMLValue(psTree, "DEMInterpolation", "bilinear");
    2141           0 :     if (pszDEMInterpolation != nullptr)
    2142           0 :         papszOptions = CSLSetNameValue(papszOptions, "RPC_DEMINTERPOLATION",
    2143             :                                        pszDEMInterpolation);
    2144             : 
    2145             :     const char *pszDEMMissingValue =
    2146           0 :         CPLGetXMLValue(psTree, "DEMMissingValue", nullptr);
    2147           0 :     if (pszDEMMissingValue != nullptr)
    2148           0 :         papszOptions = CSLSetNameValue(papszOptions, "RPC_DEM_MISSING_VALUE",
    2149             :                                        pszDEMMissingValue);
    2150             : 
    2151             :     const char *pszDEMApplyVDatumShift =
    2152           0 :         CPLGetXMLValue(psTree, "DEMApplyVDatumShift", nullptr);
    2153           0 :     if (pszDEMApplyVDatumShift != nullptr)
    2154           0 :         papszOptions = CSLSetNameValue(
    2155             :             papszOptions, "RPC_DEM_APPLY_VDATUM_SHIFT", pszDEMApplyVDatumShift);
    2156           0 :     const char *pszDEMSRS = CPLGetXMLValue(psTree, "DEMSRS", nullptr);
    2157           0 :     if (pszDEMSRS != nullptr)
    2158           0 :         papszOptions = CSLSetNameValue(papszOptions, "RPC_DEM_SRS", pszDEMSRS);
    2159             : 
    2160             :     /* -------------------------------------------------------------------- */
    2161             :     /*      Generate transformation.                                        */
    2162             :     /* -------------------------------------------------------------------- */
    2163           0 :     void *pResult = GDALCreateRPCTransformerV2(&sRPC, bReversed,
    2164             :                                                dfPixErrThreshold, papszOptions);
    2165             : 
    2166           0 :     CSLDestroy(papszOptions);
    2167             : 
    2168           0 :     return pResult;
    2169             : }

Generated by: LCOV version 1.14