Coverage Report

Created: 2026-07-25 06:50

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