LCOV - code coverage report
Current view: top level - alg - gdal_rpc.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 856 1025 83.5 %
Date: 2024-05-04 12:52:34 Functions: 17 21 81.0 %

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

Generated by: LCOV version 1.14