Coverage Report

Created: 2025-11-16 06:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/alg/gdalapplyverticalshiftgrid.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  GDAL algorithms
4
 * Purpose:  Apply vertical shift grid
5
 * Author:   Even Rouault, even.rouault at spatialys.com
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2017, Even Rouault <even.rouault at spatialys.com>
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#include "cpl_string.h"
14
#include "gdal.h"
15
#include "gdal_alg.h"
16
#include "gdal_alg_priv.h"
17
#include "gdal_priv.h"
18
#include "gdal_utils.h"
19
#include "gdalwarper.h"
20
#include "vrtdataset.h"
21
#include "ogr_spatialref.h"
22
23
#include "proj.h"
24
25
#include <cmath>
26
#include <limits>
27
28
/************************************************************************/
29
/*                        GDALApplyVSGDataset                           */
30
/************************************************************************/
31
32
class GDALApplyVSGDataset final : public GDALDataset
33
{
34
    friend class GDALApplyVSGRasterBand;
35
36
    GDALDataset *m_poSrcDataset = nullptr;
37
    GDALDataset *m_poReprojectedGrid = nullptr;
38
    bool m_bInverse = false;
39
    float m_fSrcUnitToMeter = 0.0f;
40
    float m_fDstUnitToMeter = 0.0f;
41
42
    CPL_DISALLOW_COPY_ASSIGN(GDALApplyVSGDataset)
43
44
  public:
45
    GDALApplyVSGDataset(GDALDataset *poSrcDataset,
46
                        GDALDataset *poReprojectedGrid, GDALDataType eDT,
47
                        bool bInverse, float fSrcUnitToMeter,
48
                        float fDstUnitToMeter, int nBlockSize);
49
    ~GDALApplyVSGDataset() override;
50
51
    int CloseDependentDatasets() override;
52
53
    CPLErr GetGeoTransform(GDALGeoTransform &gt) const override;
54
    const OGRSpatialReference *GetSpatialRef() const override;
55
56
    bool IsInitOK();
57
};
58
59
/************************************************************************/
60
/*                       GDALApplyVSGRasterBand                         */
61
/************************************************************************/
62
63
class GDALApplyVSGRasterBand final : public GDALRasterBand
64
{
65
    friend class GDALApplyVSGDataset;
66
67
    float *m_pafSrcData = nullptr;
68
    float *m_pafGridData = nullptr;
69
70
    CPL_DISALLOW_COPY_ASSIGN(GDALApplyVSGRasterBand)
71
72
  public:
73
    GDALApplyVSGRasterBand(GDALDataType eDT, int nBlockSize);
74
    ~GDALApplyVSGRasterBand() override;
75
76
    virtual CPLErr IReadBlock(int nBlockXOff, int nBlockYOff,
77
                              void *pData) override;
78
    double GetNoDataValue(int *pbSuccess) override;
79
};
80
81
/************************************************************************/
82
/*                        GDALApplyVSGDataset()                         */
83
/************************************************************************/
84
85
GDALApplyVSGDataset::GDALApplyVSGDataset(GDALDataset *poSrcDataset,
86
                                         GDALDataset *poReprojectedGrid,
87
                                         GDALDataType eDT, bool bInverse,
88
                                         float fSrcUnitToMeter,
89
                                         float fDstUnitToMeter, int nBlockSize)
90
0
    : m_poSrcDataset(poSrcDataset), m_poReprojectedGrid(poReprojectedGrid),
91
0
      m_bInverse(bInverse), m_fSrcUnitToMeter(fSrcUnitToMeter),
92
0
      m_fDstUnitToMeter(fDstUnitToMeter)
93
0
{
94
0
    m_poSrcDataset->Reference();
95
0
    m_poReprojectedGrid->Reference();
96
97
0
    nRasterXSize = poSrcDataset->GetRasterXSize();
98
0
    nRasterYSize = poSrcDataset->GetRasterYSize();
99
0
    SetBand(1, new GDALApplyVSGRasterBand(eDT, nBlockSize));
100
0
}
101
102
/************************************************************************/
103
/*                       ~GDALApplyVSGDataset()                         */
104
/************************************************************************/
105
106
GDALApplyVSGDataset::~GDALApplyVSGDataset()
107
0
{
108
0
    GDALApplyVSGDataset::CloseDependentDatasets();
109
0
}
110
111
/************************************************************************/
112
/*                     CloseDependentDatasets()                         */
113
/************************************************************************/
114
115
int GDALApplyVSGDataset::CloseDependentDatasets()
116
0
{
117
0
    bool bRet = false;
118
0
    if (m_poSrcDataset != nullptr)
119
0
    {
120
0
        if (m_poSrcDataset->ReleaseRef())
121
0
        {
122
0
            bRet = true;
123
0
        }
124
0
        m_poSrcDataset = nullptr;
125
0
    }
126
0
    if (m_poReprojectedGrid != nullptr)
127
0
    {
128
0
        if (m_poReprojectedGrid->ReleaseRef())
129
0
        {
130
0
            bRet = true;
131
0
        }
132
0
        m_poReprojectedGrid = nullptr;
133
0
    }
134
0
    return bRet;
135
0
}
136
137
/************************************************************************/
138
/*                          GetGeoTransform()                           */
139
/************************************************************************/
140
141
CPLErr GDALApplyVSGDataset::GetGeoTransform(GDALGeoTransform &gt) const
142
0
{
143
0
    return m_poSrcDataset->GetGeoTransform(gt);
144
0
}
145
146
/************************************************************************/
147
/*                          GetSpatialRef()                             */
148
/************************************************************************/
149
150
const OGRSpatialReference *GDALApplyVSGDataset::GetSpatialRef() const
151
0
{
152
0
    return m_poSrcDataset->GetSpatialRef();
153
0
}
154
155
/************************************************************************/
156
/*                             IsInitOK()                               */
157
/************************************************************************/
158
159
bool GDALApplyVSGDataset::IsInitOK()
160
0
{
161
0
    GDALApplyVSGRasterBand *poBand =
162
0
        cpl::down_cast<GDALApplyVSGRasterBand *>(GetRasterBand(1));
163
0
    return poBand->m_pafSrcData != nullptr && poBand->m_pafGridData != nullptr;
164
0
}
165
166
/************************************************************************/
167
/*                       GDALApplyVSGRasterBand()                       */
168
/************************************************************************/
169
170
GDALApplyVSGRasterBand::GDALApplyVSGRasterBand(GDALDataType eDT, int nBlockSize)
171
0
{
172
0
    eDataType = eDT;
173
0
    nBlockXSize = nBlockSize;
174
0
    nBlockYSize = nBlockSize;
175
0
    m_pafSrcData = static_cast<float *>(
176
0
        VSI_MALLOC3_VERBOSE(nBlockXSize, nBlockYSize, sizeof(float)));
177
0
    m_pafGridData = static_cast<float *>(
178
0
        VSI_MALLOC3_VERBOSE(nBlockXSize, nBlockYSize, sizeof(float)));
179
0
}
180
181
/************************************************************************/
182
/*                      ~GDALApplyVSGRasterBand()                       */
183
/************************************************************************/
184
185
GDALApplyVSGRasterBand::~GDALApplyVSGRasterBand()
186
0
{
187
0
    VSIFree(m_pafSrcData);
188
0
    VSIFree(m_pafGridData);
189
0
}
190
191
/************************************************************************/
192
/*                           GetNoDataValue()                           */
193
/************************************************************************/
194
195
double GDALApplyVSGRasterBand::GetNoDataValue(int *pbSuccess)
196
0
{
197
0
    GDALApplyVSGDataset *poGDS = cpl::down_cast<GDALApplyVSGDataset *>(poDS);
198
0
    return poGDS->m_poSrcDataset->GetRasterBand(1)->GetNoDataValue(pbSuccess);
199
0
}
200
201
/************************************************************************/
202
/*                              IReadBlock()                            */
203
/************************************************************************/
204
205
CPLErr GDALApplyVSGRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff,
206
                                          void *pData)
207
0
{
208
0
    GDALApplyVSGDataset *poGDS = cpl::down_cast<GDALApplyVSGDataset *>(poDS);
209
210
0
    const int nXOff = nBlockXOff * nBlockXSize;
211
0
    const int nReqXSize = (nXOff > nRasterXSize - nBlockXSize)
212
0
                              ? nRasterXSize - nXOff
213
0
                              : nBlockXSize;
214
0
    const int nYOff = nBlockYOff * nBlockYSize;
215
0
    const int nReqYSize = (nYOff > nRasterYSize - nBlockYSize)
216
0
                              ? nRasterYSize - nYOff
217
0
                              : nBlockYSize;
218
219
0
    CPLErr eErr = poGDS->m_poSrcDataset->GetRasterBand(1)->RasterIO(
220
0
        GF_Read, nXOff, nYOff, nReqXSize, nReqYSize, m_pafSrcData, nReqXSize,
221
0
        nReqYSize, GDT_Float32, sizeof(float), nBlockXSize * sizeof(float),
222
0
        nullptr);
223
0
    if (eErr == CE_None)
224
0
        eErr = poGDS->m_poReprojectedGrid->GetRasterBand(1)->RasterIO(
225
0
            GF_Read, nXOff, nYOff, nReqXSize, nReqYSize, m_pafGridData,
226
0
            nReqXSize, nReqYSize, GDT_Float32, sizeof(float),
227
0
            nBlockXSize * sizeof(float), nullptr);
228
0
    if (eErr == CE_None)
229
0
    {
230
0
        const int nDTSize = GDALGetDataTypeSizeBytes(eDataType);
231
0
        int bHasNoData = FALSE;
232
0
        float fNoDataValue = static_cast<float>(GetNoDataValue(&bHasNoData));
233
0
        for (int iY = 0; iY < nReqYSize; iY++)
234
0
        {
235
0
            for (int iX = 0; iX < nReqXSize; iX++)
236
0
            {
237
0
                const float fSrcVal = m_pafSrcData[iY * nBlockXSize + iX];
238
0
                const float fGridVal = m_pafGridData[iY * nBlockXSize + iX];
239
0
                if (bHasNoData && fSrcVal == fNoDataValue)
240
0
                {
241
0
                }
242
0
                else if (std::isinf(fGridVal))
243
0
                {
244
0
                    CPLError(CE_Failure, CPLE_AppDefined,
245
0
                             "Missing vertical grid value at source (%d,%d)",
246
0
                             nXOff + iX, nYOff + iY);
247
0
                    return CE_Failure;
248
0
                }
249
0
                else if (poGDS->m_bInverse)
250
0
                {
251
0
                    m_pafSrcData[iY * nBlockXSize + iX] =
252
0
                        (fSrcVal * poGDS->m_fSrcUnitToMeter - fGridVal) /
253
0
                        poGDS->m_fDstUnitToMeter;
254
0
                }
255
0
                else
256
0
                {
257
0
                    m_pafSrcData[iY * nBlockXSize + iX] =
258
0
                        (fSrcVal * poGDS->m_fSrcUnitToMeter + fGridVal) /
259
0
                        poGDS->m_fDstUnitToMeter;
260
0
                }
261
0
            }
262
0
            GDALCopyWords(
263
0
                m_pafSrcData + iY * nBlockXSize, GDT_Float32, sizeof(float),
264
0
                static_cast<GByte *>(pData) + iY * nBlockXSize * nDTSize,
265
0
                eDataType, nDTSize, nReqXSize);
266
0
        }
267
0
    }
268
269
0
    return eErr;
270
0
}
271
272
/************************************************************************/
273
/*                      GDALApplyVerticalShiftGrid()                    */
274
/************************************************************************/
275
276
/** Apply a vertical shift grid to a source (DEM typically) dataset.
277
 *
278
 * hGridDataset will typically use WGS84 as horizontal datum (but this is
279
 * not a requirement) and its values are the values to add to go from geoid
280
 * elevations to WGS84 ellipsoidal heights.
281
 *
282
 * hGridDataset will be on-the-fly reprojected and resampled to the projection
283
 * and resolution of hSrcDataset, using bilinear resampling by default.
284
 *
285
 * Both hSrcDataset and hGridDataset must be single band datasets, and have
286
 * a valid geotransform and projection.
287
 *
288
 * On success, a reference will be taken on hSrcDataset and hGridDataset.
289
 * Reference counting semantics on the source and grid datasets should be
290
 * honoured. That is, don't just GDALClose() it, unless it was opened with
291
 * GDALOpenShared(), but rather use GDALReleaseDataset() if wanting to
292
 * immediately release the reference(s) and make the returned dataset the
293
 * owner of them.
294
 *
295
 * Valid use cases:
296
 *
297
 * \code
298
 * hSrcDataset = GDALOpen(...)
299
 * hGridDataset = GDALOpen(...)
300
 * hDstDataset = GDALApplyVerticalShiftGrid(hSrcDataset, hGridDataset, ...)
301
 * GDALReleaseDataset(hSrcDataset);
302
 * GDALReleaseDataset(hGridDataset);
303
 * if( hDstDataset )
304
 * {
305
 *     // Do things with hDstDataset
306
 *     GDALClose(hDstDataset) // will close hSrcDataset and hGridDataset
307
 * }
308
 * \endcode
309
310
 *
311
 * @param hSrcDataset source (DEM) dataset. Must not be NULL.
312
 * @param hGridDataset vertical grid shift dataset. Must not be NULL.
313
 * @param bInverse if set to FALSE, hGridDataset values will be added to
314
 *                 hSrcDataset. If set to TRUE, they will be subtracted.
315
 * @param dfSrcUnitToMeter the factor to convert values from hSrcDataset to
316
 *                         meters (1.0 if source values are in meter).
317
 * @param dfDstUnitToMeter the factor to convert shifted values from meter
318
 *                          (1.0 if output values must be in meter).
319
 * @param papszOptions list of options, or NULL. Supported options are:
320
 * <ul>
321
 * <li>RESAMPLING=NEAREST/BILINEAR/CUBIC. Defaults to BILINEAR.</li>
322
 * <li>MAX_ERROR=val. Maximum error measured in input pixels that is allowed in
323
 * approximating the transformation (0.0 for exact calculations). Defaults
324
 * to 0.125</li>
325
 * <li>DATATYPE=Byte/UInt16/Int16/Float32/Float64. Output data type. If not
326
 * specified will be the same as the one of hSrcDataset.
327
 * <li>ERROR_ON_MISSING_VERT_SHIFT=YES/NO. Whether a missing/nodata value in
328
 * hGridDataset should cause I/O requests to fail. Default is NO (in which case
329
 * 0 will be used)
330
 * <li>SRC_SRS=srs_def. Override projection on hSrcDataset;
331
 * </ul>
332
 *
333
 * @return a new dataset corresponding to hSrcDataset adjusted with
334
 * hGridDataset, or NULL. If not NULL, it must be closed with GDALClose().
335
 *
336
 * @deprecated GDAL 3.4. Will be removed in GDAL 4.0. This function was used
337
 *             by gdalwarp initially, but is no longer needed.
338
 */
339
GDALDatasetH GDALApplyVerticalShiftGrid(GDALDatasetH hSrcDataset,
340
                                        GDALDatasetH hGridDataset, int bInverse,
341
                                        double dfSrcUnitToMeter,
342
                                        double dfDstUnitToMeter,
343
                                        const char *const *papszOptions)
344
0
{
345
0
    VALIDATE_POINTER1(hSrcDataset, "GDALApplyVerticalShiftGrid", nullptr);
346
0
    VALIDATE_POINTER1(hGridDataset, "GDALApplyVerticalShiftGrid", nullptr);
347
348
0
    GDALGeoTransform srcGT;
349
0
    if (GDALDataset::FromHandle(hSrcDataset)->GetGeoTransform(srcGT) != CE_None)
350
0
    {
351
0
        CPLError(CE_Failure, CPLE_NotSupported,
352
0
                 "Source dataset has no geotransform.");
353
0
        return nullptr;
354
0
    }
355
0
    const char *pszSrcProjection = CSLFetchNameValue(papszOptions, "SRC_SRS");
356
0
    OGRSpatialReference oSrcSRS;
357
0
    if (pszSrcProjection != nullptr && pszSrcProjection[0] != '\0')
358
0
    {
359
0
        oSrcSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
360
0
        oSrcSRS.SetFromUserInput(pszSrcProjection);
361
0
    }
362
0
    else
363
0
    {
364
0
        auto poSRS = GDALDataset::FromHandle(hSrcDataset)->GetSpatialRef();
365
0
        if (poSRS)
366
0
            oSrcSRS = *poSRS;
367
0
    }
368
369
0
    if (oSrcSRS.IsCompound())
370
0
    {
371
0
        oSrcSRS.StripVertical();
372
0
    }
373
374
0
    if (oSrcSRS.IsEmpty())
375
0
    {
376
0
        CPLError(CE_Failure, CPLE_NotSupported,
377
0
                 "Source dataset has no projection.");
378
0
        return nullptr;
379
0
    }
380
0
    if (GDALGetRasterCount(hSrcDataset) != 1)
381
0
    {
382
0
        CPLError(CE_Failure, CPLE_NotSupported,
383
0
                 "Only single band source dataset is supported.");
384
0
        return nullptr;
385
0
    }
386
387
0
    GDALGeoTransform gridGT;
388
0
    if (GDALDataset::FromHandle(hGridDataset)->GetGeoTransform(gridGT) !=
389
0
        CE_None)
390
0
    {
391
0
        CPLError(CE_Failure, CPLE_NotSupported,
392
0
                 "Grid dataset has no geotransform.");
393
0
        return nullptr;
394
0
    }
395
396
0
    OGRSpatialReferenceH hGridSRS = GDALGetSpatialRef(hGridDataset);
397
0
    if (hGridSRS == nullptr)
398
0
    {
399
0
        CPLError(CE_Failure, CPLE_NotSupported,
400
0
                 "Grid dataset has no projection.");
401
0
        return nullptr;
402
0
    }
403
0
    if (GDALGetRasterCount(hGridDataset) != 1)
404
0
    {
405
0
        CPLError(CE_Failure, CPLE_NotSupported,
406
0
                 "Only single band grid dataset is supported.");
407
0
        return nullptr;
408
0
    }
409
410
0
    GDALDataType eDT = GDALGetRasterDataType(GDALGetRasterBand(hSrcDataset, 1));
411
0
    const char *pszDataType = CSLFetchNameValue(papszOptions, "DATATYPE");
412
0
    if (pszDataType)
413
0
        eDT = GDALGetDataTypeByName(pszDataType);
414
0
    if (eDT == GDT_Unknown)
415
0
    {
416
0
        CPLError(CE_Failure, CPLE_NotSupported, "Invalid DATATYPE=%s",
417
0
                 pszDataType);
418
0
        return nullptr;
419
0
    }
420
421
0
    const int nSrcXSize = GDALGetRasterXSize(hSrcDataset);
422
0
    const int nSrcYSize = GDALGetRasterYSize(hSrcDataset);
423
424
0
    double dfWestLongitudeDeg = 0.0;
425
0
    double dfSouthLatitudeDeg = 0.0;
426
0
    double dfEastLongitudeDeg = 0.0;
427
0
    double dfNorthLatitudeDeg = 0.0;
428
0
    GDALComputeAreaOfInterest(&oSrcSRS, srcGT.data(), nSrcXSize, nSrcYSize,
429
0
                              dfWestLongitudeDeg, dfSouthLatitudeDeg,
430
0
                              dfEastLongitudeDeg, dfNorthLatitudeDeg);
431
432
0
    CPLStringList aosOptions;
433
0
    if (!(dfWestLongitudeDeg == 0.0 && dfSouthLatitudeDeg == 0.0 &&
434
0
          dfEastLongitudeDeg == 0.0 && dfNorthLatitudeDeg == 0.0))
435
0
    {
436
0
        aosOptions.SetNameValue(
437
0
            "AREA_OF_INTEREST",
438
0
            CPLSPrintf("%.16g,%.16g,%.16g,%.16g", dfWestLongitudeDeg,
439
0
                       dfSouthLatitudeDeg, dfEastLongitudeDeg,
440
0
                       dfNorthLatitudeDeg));
441
0
    }
442
0
    void *hTransform = GDALCreateGenImgProjTransformer4(
443
0
        hGridSRS, gridGT.data(), OGRSpatialReference::ToHandle(&oSrcSRS),
444
0
        srcGT.data(), aosOptions.List());
445
0
    if (hTransform == nullptr)
446
0
        return nullptr;
447
0
    GDALWarpOptions *psWO = GDALCreateWarpOptions();
448
0
    psWO->hSrcDS = hGridDataset;
449
0
    psWO->eResampleAlg = GRA_Bilinear;
450
0
    const char *pszResampling = CSLFetchNameValue(papszOptions, "RESAMPLING");
451
0
    if (pszResampling)
452
0
    {
453
0
        if (EQUAL(pszResampling, "NEAREST"))
454
0
            psWO->eResampleAlg = GRA_NearestNeighbour;
455
0
        else if (EQUAL(pszResampling, "BILINEAR"))
456
0
            psWO->eResampleAlg = GRA_Bilinear;
457
0
        else if (EQUAL(pszResampling, "CUBIC"))
458
0
            psWO->eResampleAlg = GRA_Cubic;
459
0
    }
460
0
    psWO->eWorkingDataType = GDT_Float32;
461
0
    int bHasNoData = FALSE;
462
0
    const double dfSrcNoData = GDALGetRasterNoDataValue(
463
0
        GDALGetRasterBand(hGridDataset, 1), &bHasNoData);
464
0
    if (bHasNoData)
465
0
    {
466
0
        psWO->padfSrcNoDataReal =
467
0
            static_cast<double *>(CPLMalloc(sizeof(double)));
468
0
        psWO->padfSrcNoDataReal[0] = dfSrcNoData;
469
0
    }
470
471
0
    psWO->padfDstNoDataReal = static_cast<double *>(CPLMalloc(sizeof(double)));
472
0
    const bool bErrorOnMissingShift =
473
0
        CPLFetchBool(papszOptions, "ERROR_ON_MISSING_VERT_SHIFT", false);
474
0
    psWO->padfDstNoDataReal[0] =
475
0
        (bErrorOnMissingShift)
476
0
            ? static_cast<double>(-std::numeric_limits<float>::infinity())
477
0
            : 0.0;
478
0
    psWO->papszWarpOptions =
479
0
        CSLSetNameValue(psWO->papszWarpOptions, "INIT_DEST", "NO_DATA");
480
481
0
    psWO->pfnTransformer = GDALGenImgProjTransform;
482
0
    psWO->pTransformerArg = hTransform;
483
0
    const double dfMaxError =
484
0
        CPLAtof(CSLFetchNameValueDef(papszOptions, "MAX_ERROR", "0.125"));
485
0
    if (dfMaxError > 0.0)
486
0
    {
487
0
        psWO->pTransformerArg = GDALCreateApproxTransformer(
488
0
            psWO->pfnTransformer, psWO->pTransformerArg, dfMaxError);
489
0
        psWO->pfnTransformer = GDALApproxTransform;
490
0
        GDALApproxTransformerOwnsSubtransformer(psWO->pTransformerArg, TRUE);
491
0
    }
492
0
    psWO->nBandCount = 1;
493
0
    psWO->panSrcBands = static_cast<int *>(CPLMalloc(sizeof(int)));
494
0
    psWO->panSrcBands[0] = 1;
495
0
    psWO->panDstBands = static_cast<int *>(CPLMalloc(sizeof(int)));
496
0
    psWO->panDstBands[0] = 1;
497
498
0
    VRTWarpedDataset *poReprojectedGrid =
499
0
        new VRTWarpedDataset(nSrcXSize, nSrcYSize);
500
    // This takes a reference on hGridDataset
501
0
    CPLErr eErr = poReprojectedGrid->Initialize(psWO);
502
0
    CPLAssert(eErr == CE_None);
503
0
    CPL_IGNORE_RET_VAL(eErr);
504
0
    GDALDestroyWarpOptions(psWO);
505
0
    poReprojectedGrid->SetGeoTransform(srcGT);
506
0
    poReprojectedGrid->AddBand(GDT_Float32, nullptr);
507
508
0
    GDALApplyVSGDataset *poOutDS = new GDALApplyVSGDataset(
509
0
        GDALDataset::FromHandle(hSrcDataset), poReprojectedGrid, eDT,
510
0
        CPL_TO_BOOL(bInverse), static_cast<float>(dfSrcUnitToMeter),
511
0
        static_cast<float>(dfDstUnitToMeter),
512
        // Undocumented option. For testing only
513
0
        atoi(CSLFetchNameValueDef(papszOptions, "BLOCKSIZE", "256")));
514
515
0
    poReprojectedGrid->ReleaseRef();
516
517
0
    if (!poOutDS->IsInitOK())
518
0
    {
519
0
        delete poOutDS;
520
0
        return nullptr;
521
0
    }
522
0
    poOutDS->SetDescription(GDALGetDescription(hSrcDataset));
523
0
    return reinterpret_cast<GDALDatasetH>(poOutDS);
524
0
}
525
526
/************************************************************************/
527
/*                           GetProj4Filename()                         */
528
/************************************************************************/
529
530
static CPLString GetProj4Filename(const char *pszFilename)
531
0
{
532
0
    CPLString osFilename;
533
534
    /* or fixed path: /name, ./name or ../name */
535
0
    if (!CPLIsFilenameRelative(pszFilename) || *pszFilename == '.')
536
0
    {
537
0
        return pszFilename;
538
0
    }
539
540
0
    PJ_GRID_INFO info = proj_grid_info(pszFilename);
541
0
    if (info.filename[0])
542
0
    {
543
0
        osFilename = info.filename;
544
0
    }
545
546
0
    return osFilename;
547
0
}
548
549
/************************************************************************/
550
/*                       GDALOpenVerticalShiftGrid()                    */
551
/************************************************************************/
552
553
/** Load proj.4 geoidgrids as GDAL dataset
554
 *
555
 * @param pszProj4Geoidgrids Value of proj.4 geoidgrids parameter.
556
 * @param pbError If not NULL, the pointed value will be set to TRUE if an
557
 *                error occurred.
558
 *
559
 * @return a dataset. If not NULL, it must be closed with GDALClose().
560
 *
561
 * @deprecated GDAL 3.4. Will be removed in GDAL 4.0. This function was used
562
 *             by gdalwarp initially, but is no longer needed.
563
 */
564
GDALDatasetH GDALOpenVerticalShiftGrid(const char *pszProj4Geoidgrids,
565
                                       int *pbError)
566
0
{
567
0
    char **papszGrids = CSLTokenizeString2(pszProj4Geoidgrids, ",", 0);
568
0
    const int nGridCount = CSLCount(papszGrids);
569
0
    if (nGridCount == 1)
570
0
    {
571
0
        CSLDestroy(papszGrids);
572
573
0
        bool bMissingOk = false;
574
0
        if (*pszProj4Geoidgrids == '@')
575
0
        {
576
0
            pszProj4Geoidgrids++;
577
0
            bMissingOk = true;
578
0
        }
579
0
        const CPLString osFilename(GetProj4Filename(pszProj4Geoidgrids));
580
0
        const char *const papszOpenOptions[] = {
581
0
            "@SHIFT_ORIGIN_IN_MINUS_180_PLUS_180=YES", nullptr};
582
0
        GDALDatasetH hDS =
583
0
            GDALOpenEx(osFilename, 0, nullptr, papszOpenOptions, nullptr);
584
0
        if (hDS == nullptr)
585
0
        {
586
0
            CPLDebug("GDAL", "Cannot find file corresponding to %s",
587
0
                     pszProj4Geoidgrids);
588
0
        }
589
0
        if (pbError)
590
0
            *pbError = (!bMissingOk && hDS == nullptr);
591
0
        return hDS;
592
0
    }
593
594
0
    CPLStringList aosFilenames;
595
0
    for (int i = nGridCount - 1; i >= 0; i--)
596
0
    {
597
0
        const char *pszName = papszGrids[i];
598
0
        bool bMissingOk = false;
599
0
        if (*pszName == '@')
600
0
        {
601
0
            pszName++;
602
0
            bMissingOk = true;
603
0
        }
604
0
        const CPLString osFilename(GetProj4Filename(pszName));
605
0
        VSIStatBufL sStat;
606
0
        if (osFilename.empty() || VSIStatL(osFilename, &sStat) != 0)
607
0
        {
608
0
            CPLDebug("GDAL", "Cannot find file corresponding to %s", pszName);
609
0
            if (!bMissingOk)
610
0
            {
611
0
                if (pbError)
612
0
                    *pbError = true;
613
0
                CSLDestroy(papszGrids);
614
0
                return nullptr;
615
0
            }
616
0
        }
617
0
        else
618
0
        {
619
0
            aosFilenames.AddString(osFilename);
620
0
        }
621
0
    }
622
623
0
    CSLDestroy(papszGrids);
624
625
0
    if (aosFilenames.empty())
626
0
    {
627
0
        if (pbError)
628
0
            *pbError = false;
629
0
        return nullptr;
630
0
    }
631
632
0
    char **papszArgv = nullptr;
633
0
    papszArgv = CSLAddString(papszArgv, "-resolution");
634
0
    papszArgv = CSLAddString(papszArgv, "highest");
635
0
    papszArgv = CSLAddString(papszArgv, "-vrtnodata");
636
0
    papszArgv = CSLAddString(papszArgv, "-inf");
637
0
    papszArgv = CSLAddString(papszArgv, "-oo");
638
0
    papszArgv =
639
0
        CSLAddString(papszArgv, "@SHIFT_ORIGIN_IN_MINUS_180_PLUS_180=YES");
640
0
    GDALBuildVRTOptions *psOptions = GDALBuildVRTOptionsNew(papszArgv, nullptr);
641
0
    CSLDestroy(papszArgv);
642
0
    GDALDatasetH hDS = GDALBuildVRT("", aosFilenames.size(), nullptr,
643
0
                                    aosFilenames.List(), psOptions, nullptr);
644
0
    GDALBuildVRTOptionsFree(psOptions);
645
0
    if (pbError)
646
0
        *pbError = hDS != nullptr;
647
0
    return hDS;
648
0
}