Coverage Report

Created: 2025-12-03 08:24

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/frmts/raw/gtxdataset.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  Vertical Datum Transformation
4
 * Purpose:  Implementation of NOAA .gtx vertical datum shift file format.
5
 * Author:   Frank Warmerdam, warmerdam@pobox.com
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2010, Frank Warmerdam
9
 * Copyright (c) 2010-2013, Even Rouault <even dot rouault at spatialys.com>
10
 *
11
 * SPDX-License-Identifier: MIT
12
 ****************************************************************************/
13
14
#include "cpl_string.h"
15
#include "gdal_frmts.h"
16
#include "gdal_priv.h"
17
#include "ogr_srs_api.h"
18
#include "rawdataset.h"
19
20
#include <algorithm>
21
#include <limits>
22
23
/**
24
25
NOAA .GTX Vertical Datum Grid Shift Format
26
27
All values are bigendian
28
29
Header
30
------
31
32
float64  latitude_of_origin
33
float64  longitude_of_origin (0-360)
34
float64  cell size (x?y?)
35
float64  cell size (x?y?)
36
int32    length in pixels
37
int32    width in pixels
38
39
Data
40
----
41
42
float32  * width in pixels * length in pixels
43
44
Values are an offset in meters between two vertical datums.
45
46
**/
47
48
/************************************************************************/
49
/* ==================================================================== */
50
/*                              GTXDataset                              */
51
/* ==================================================================== */
52
/************************************************************************/
53
54
class GTXDataset final : public RawDataset
55
{
56
    VSILFILE *fpImage = nullptr;  // image data file.
57
58
    OGRSpatialReference m_oSRS{};
59
    GDALGeoTransform m_gt{};
60
61
    CPL_DISALLOW_COPY_ASSIGN(GTXDataset)
62
63
    CPLErr Close() override;
64
65
  public:
66
    GTXDataset()
67
53
    {
68
53
        m_oSRS.SetFromUserInput(SRS_WKT_WGS84_LAT_LONG);
69
53
        m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
70
53
    }
71
72
    ~GTXDataset() override;
73
74
    CPLErr GetGeoTransform(GDALGeoTransform &gt) const override;
75
    CPLErr SetGeoTransform(const GDALGeoTransform &gt) override;
76
77
    const OGRSpatialReference *GetSpatialRef() const override
78
0
    {
79
0
        return &m_oSRS;
80
0
    }
81
82
    static GDALDataset *Open(GDALOpenInfo *);
83
    static int Identify(GDALOpenInfo *);
84
    static GDALDataset *Create(const char *pszFilename, int nXSize, int nYSize,
85
                               int nBands, GDALDataType eType,
86
                               char **papszOptions);
87
};
88
89
/************************************************************************/
90
/* ==================================================================== */
91
/*                           GTXRasterBand                              */
92
/* ==================================================================== */
93
/************************************************************************/
94
95
class GTXRasterBand final : public RawRasterBand
96
{
97
    CPL_DISALLOW_COPY_ASSIGN(GTXRasterBand)
98
99
  public:
100
    GTXRasterBand(GDALDataset *poDS, int nBand, VSILFILE *fpRaw,
101
                  vsi_l_offset nImgOffset, int nPixelOffset, int nLineOffset,
102
                  GDALDataType eDataType, int bNativeOrder);
103
104
    ~GTXRasterBand() override;
105
106
    double GetNoDataValue(int *pbSuccess = nullptr) override;
107
};
108
109
/************************************************************************/
110
/*                            GTXRasterBand()                           */
111
/************************************************************************/
112
113
GTXRasterBand::GTXRasterBand(GDALDataset *poDSIn, int nBandIn,
114
                             VSILFILE *fpRawIn, vsi_l_offset nImgOffsetIn,
115
                             int nPixelOffsetIn, int nLineOffsetIn,
116
                             GDALDataType eDataTypeIn, int bNativeOrderIn)
117
34
    : RawRasterBand(poDSIn, nBandIn, fpRawIn, nImgOffsetIn, nPixelOffsetIn,
118
34
                    nLineOffsetIn, eDataTypeIn, bNativeOrderIn,
119
34
                    RawRasterBand::OwnFP::NO)
120
34
{
121
34
}
122
123
/************************************************************************/
124
/*                           ~GTXRasterBand()                           */
125
/************************************************************************/
126
127
GTXRasterBand::~GTXRasterBand()
128
34
{
129
34
}
130
131
/************************************************************************/
132
/*                           GetNoDataValue()                           */
133
/************************************************************************/
134
135
double GTXRasterBand::GetNoDataValue(int *pbSuccess)
136
0
{
137
0
    if (pbSuccess)
138
0
        *pbSuccess = TRUE;
139
0
    int bSuccess = FALSE;
140
0
    double dfNoData = GDALPamRasterBand::GetNoDataValue(&bSuccess);
141
0
    if (bSuccess)
142
0
    {
143
0
        return dfNoData;
144
0
    }
145
0
    return -88.8888;
146
0
}
147
148
/************************************************************************/
149
/* ==================================================================== */
150
/*                              GTXDataset                              */
151
/* ==================================================================== */
152
/************************************************************************/
153
154
/************************************************************************/
155
/*                            ~GTXDataset()                             */
156
/************************************************************************/
157
158
GTXDataset::~GTXDataset()
159
160
53
{
161
53
    GTXDataset::Close();
162
53
}
163
164
/************************************************************************/
165
/*                              Close()                                 */
166
/************************************************************************/
167
168
CPLErr GTXDataset::Close()
169
53
{
170
53
    CPLErr eErr = CE_None;
171
53
    if (nOpenFlags != OPEN_FLAGS_CLOSED)
172
53
    {
173
53
        if (GTXDataset::FlushCache(true) != CE_None)
174
0
            eErr = CE_Failure;
175
176
53
        if (fpImage)
177
53
        {
178
53
            if (VSIFCloseL(fpImage) != 0)
179
0
            {
180
0
                CPLError(CE_Failure, CPLE_FileIO, "I/O error");
181
0
                eErr = CE_Failure;
182
0
            }
183
53
        }
184
185
53
        if (GDALPamDataset::Close() != CE_None)
186
0
            eErr = CE_Failure;
187
53
    }
188
53
    return eErr;
189
53
}
190
191
/************************************************************************/
192
/*                              Identify()                              */
193
/************************************************************************/
194
195
int GTXDataset::Identify(GDALOpenInfo *poOpenInfo)
196
197
560k
{
198
560k
    if (poOpenInfo->nHeaderBytes < 40)
199
509k
        return FALSE;
200
201
51.6k
    if (!poOpenInfo->IsExtensionEqualToCI("gtx"))
202
51.5k
        return FALSE;
203
204
106
    return TRUE;
205
51.6k
}
206
207
/************************************************************************/
208
/*                                Open()                                */
209
/************************************************************************/
210
211
GDALDataset *GTXDataset::Open(GDALOpenInfo *poOpenInfo)
212
213
53
{
214
53
    if (!Identify(poOpenInfo) || poOpenInfo->fpL == nullptr)
215
0
        return nullptr;
216
217
    /* -------------------------------------------------------------------- */
218
    /*      Create a corresponding GDALDataset.                             */
219
    /* -------------------------------------------------------------------- */
220
53
    auto poDS = std::make_unique<GTXDataset>();
221
222
53
    poDS->eAccess = poOpenInfo->eAccess;
223
53
    std::swap(poDS->fpImage, poOpenInfo->fpL);
224
225
    /* -------------------------------------------------------------------- */
226
    /*      Read the header.                                                */
227
    /* -------------------------------------------------------------------- */
228
53
    double gt[6] = {0};
229
53
    CPL_IGNORE_RET_VAL(VSIFReadL(&gt[3], 8, 1, poDS->fpImage));
230
53
    CPL_IGNORE_RET_VAL(VSIFReadL(&gt[0], 8, 1, poDS->fpImage));
231
53
    CPL_IGNORE_RET_VAL(VSIFReadL(&gt[5], 8, 1, poDS->fpImage));
232
53
    CPL_IGNORE_RET_VAL(VSIFReadL(&gt[1], 8, 1, poDS->fpImage));
233
234
53
    CPL_IGNORE_RET_VAL(VSIFReadL(&(poDS->nRasterYSize), 4, 1, poDS->fpImage));
235
53
    CPL_IGNORE_RET_VAL(VSIFReadL(&(poDS->nRasterXSize), 4, 1, poDS->fpImage));
236
237
53
    CPL_MSBPTR32(&(poDS->nRasterYSize));
238
53
    CPL_MSBPTR32(&(poDS->nRasterXSize));
239
240
53
    CPL_MSBPTR64(&gt[0]);
241
53
    CPL_MSBPTR64(&gt[1]);
242
53
    CPL_MSBPTR64(&gt[3]);
243
53
    CPL_MSBPTR64(&gt[5]);
244
245
53
    poDS->m_gt = GDALGeoTransform(gt);
246
53
    poDS->m_gt[3] +=
247
53
        poDS->m_gt[5] * (static_cast<double>(poDS->nRasterYSize) - 1);
248
249
53
    poDS->m_gt[0] -= poDS->m_gt[1] * 0.5;
250
53
    poDS->m_gt[3] += poDS->m_gt[5] * 0.5;
251
252
53
    poDS->m_gt[5] *= -1;
253
254
53
    if (CPLFetchBool(poOpenInfo->papszOpenOptions,
255
53
                     "SHIFT_ORIGIN_IN_MINUS_180_PLUS_180", false))
256
0
    {
257
0
        if (poDS->m_gt[0] < -180.0 - poDS->m_gt[1])
258
0
            poDS->m_gt[0] += 360.0;
259
0
        else if (poDS->m_gt[0] > 180.0)
260
0
            poDS->m_gt[0] -= 360.0;
261
0
    }
262
263
53
    if (!GDALCheckDatasetDimensions(poDS->nRasterXSize, poDS->nRasterYSize) ||
264
49
        static_cast<vsi_l_offset>(poDS->nRasterXSize) * poDS->nRasterYSize >
265
49
            std::numeric_limits<vsi_l_offset>::max() / sizeof(double))
266
8
    {
267
8
        return nullptr;
268
8
    }
269
270
    /* -------------------------------------------------------------------- */
271
    /*      Guess the data type. Since October 1, 2009, it should be        */
272
    /*      Float32. Before it was double.                                  */
273
    /* -------------------------------------------------------------------- */
274
45
    CPL_IGNORE_RET_VAL(VSIFSeekL(poDS->fpImage, 0, SEEK_END));
275
45
    const vsi_l_offset nSize = VSIFTellL(poDS->fpImage);
276
277
45
    GDALDataType eDT = GDT_Float32;
278
45
    if (nSize - 40 == sizeof(double) *
279
45
                          static_cast<vsi_l_offset>(poDS->nRasterXSize) *
280
45
                          poDS->nRasterYSize)
281
0
        eDT = GDT_Float64;
282
45
    const int nDTSize = GDALGetDataTypeSizeBytes(eDT);
283
45
    if (nDTSize <= 0 || poDS->nRasterXSize > INT_MAX / nDTSize)
284
11
    {
285
11
        return nullptr;
286
11
    }
287
288
    /* -------------------------------------------------------------------- */
289
    /*      Create band information object.                                 */
290
    /* -------------------------------------------------------------------- */
291
34
    auto poBand = std::make_unique<GTXRasterBand>(
292
34
        poDS.get(), 1, poDS->fpImage,
293
34
        static_cast<vsi_l_offset>(poDS->nRasterYSize - 1) * poDS->nRasterXSize *
294
34
                nDTSize +
295
34
            40,
296
34
        nDTSize, poDS->nRasterXSize * -nDTSize, eDT, !CPL_IS_LSB);
297
34
    if (!poBand->IsValid())
298
0
        return nullptr;
299
34
    poDS->SetBand(1, std::move(poBand));
300
301
    /* -------------------------------------------------------------------- */
302
    /*      Initialize any PAM information.                                 */
303
    /* -------------------------------------------------------------------- */
304
34
    poDS->SetDescription(poOpenInfo->pszFilename);
305
34
    poDS->TryLoadXML();
306
307
    /* -------------------------------------------------------------------- */
308
    /*      Check for overviews.                                            */
309
    /* -------------------------------------------------------------------- */
310
34
    poDS->oOvManager.Initialize(poDS.get(), poOpenInfo->pszFilename);
311
312
34
    return poDS.release();
313
34
}
314
315
/************************************************************************/
316
/*                          GetGeoTransform()                           */
317
/************************************************************************/
318
319
CPLErr GTXDataset::GetGeoTransform(GDALGeoTransform &gt) const
320
321
0
{
322
0
    gt = m_gt;
323
0
    return CE_None;
324
0
}
325
326
/************************************************************************/
327
/*                          SetGeoTransform()                           */
328
/************************************************************************/
329
330
CPLErr GTXDataset::SetGeoTransform(const GDALGeoTransform &gt)
331
0
{
332
0
    if (gt[2] != 0.0 || gt[4] != 0.0)
333
0
    {
334
0
        CPLError(CE_Failure, CPLE_AppDefined,
335
0
                 "Attempt to write skewed or rotated geotransform to gtx.");
336
0
        return CE_Failure;
337
0
    }
338
339
0
    m_gt = gt;
340
341
0
    const double dfXOrigin = m_gt[0] + 0.5 * m_gt[1];
342
0
    const double dfYOrigin = m_gt[3] + (nRasterYSize - 0.5) * m_gt[5];
343
0
    const double dfWidth = m_gt[1];
344
0
    const double dfHeight = -m_gt[5];
345
346
0
    unsigned char header[32] = {'\0'};
347
0
    memcpy(header + 0, &dfYOrigin, 8);
348
0
    CPL_MSBPTR64(header + 0);
349
350
0
    memcpy(header + 8, &dfXOrigin, 8);
351
0
    CPL_MSBPTR64(header + 8);
352
353
0
    memcpy(header + 16, &dfHeight, 8);
354
0
    CPL_MSBPTR64(header + 16);
355
356
0
    memcpy(header + 24, &dfWidth, 8);
357
0
    CPL_MSBPTR64(header + 24);
358
359
0
    if (VSIFSeekL(fpImage, SEEK_SET, 0) != 0 ||
360
0
        VSIFWriteL(header, 32, 1, fpImage) != 1)
361
0
    {
362
0
        CPLError(CE_Failure, CPLE_AppDefined,
363
0
                 "Attempt to write geotransform header to GTX failed.");
364
0
        return CE_Failure;
365
0
    }
366
367
0
    return CE_None;
368
0
}
369
370
/************************************************************************/
371
/*                               Create()                               */
372
/************************************************************************/
373
374
GDALDataset *GTXDataset::Create(const char *pszFilename, int nXSize, int nYSize,
375
                                int /* nBands */, GDALDataType eType,
376
                                char ** /* papszOptions */)
377
0
{
378
0
    if (eType != GDT_Float32)
379
0
    {
380
0
        CPLError(CE_Failure, CPLE_AppDefined,
381
0
                 "Attempt to create gtx file with unsupported data type '%s'.",
382
0
                 GDALGetDataTypeName(eType));
383
0
        return nullptr;
384
0
    }
385
386
0
    if (!EQUAL(CPLGetExtensionSafe(pszFilename).c_str(), "gtx"))
387
0
    {
388
0
        CPLError(CE_Failure, CPLE_AppDefined,
389
0
                 "Attempt to create gtx file with extension other than gtx.");
390
0
        return nullptr;
391
0
    }
392
393
    /* -------------------------------------------------------------------- */
394
    /*      Try to create the file.                                         */
395
    /* -------------------------------------------------------------------- */
396
0
    VSILFILE *fp = VSIFOpenL(pszFilename, "wb");
397
0
    if (fp == nullptr)
398
0
    {
399
0
        CPLError(CE_Failure, CPLE_OpenFailed,
400
0
                 "Attempt to create file `%s' failed.\n", pszFilename);
401
0
        return nullptr;
402
0
    }
403
404
    /* -------------------------------------------------------------------- */
405
    /*      Write out the header with stub georeferencing.                  */
406
    /* -------------------------------------------------------------------- */
407
408
0
    unsigned char header[40] = {'\0'};
409
0
    double dfYOrigin = 0.0;
410
0
    memcpy(header + 0, &dfYOrigin, 8);
411
0
    CPL_MSBPTR64(header + 0);
412
413
0
    double dfXOrigin = 0.0;
414
0
    memcpy(header + 8, &dfXOrigin, 8);
415
0
    CPL_MSBPTR64(header + 8);
416
417
0
    double dfYSize = 0.01;
418
0
    memcpy(header + 16, &dfYSize, 8);
419
0
    CPL_MSBPTR64(header + 16);
420
421
0
    double dfXSize = 0.01;
422
0
    memcpy(header + 24, &dfXSize, 8);
423
0
    CPL_MSBPTR64(header + 24);
424
425
0
    GInt32 nYSize32 = nYSize;
426
0
    memcpy(header + 32, &nYSize32, 4);
427
0
    CPL_MSBPTR32(header + 32);
428
429
0
    GInt32 nXSize32 = nXSize;
430
0
    memcpy(header + 36, &nXSize32, 4);
431
0
    CPL_MSBPTR32(header + 36);
432
433
0
    CPL_IGNORE_RET_VAL(VSIFWriteL(header, 40, 1, fp));
434
0
    CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
435
436
0
    return GDALDataset::FromHandle(GDALOpen(pszFilename, GA_Update));
437
0
}
438
439
/************************************************************************/
440
/*                          GDALRegister_GTX()                          */
441
/************************************************************************/
442
443
void GDALRegister_GTX()
444
445
22
{
446
22
    if (GDALGetDriverByName("GTX") != nullptr)
447
0
        return;
448
449
22
    GDALDriver *poDriver = new GDALDriver();
450
451
22
    poDriver->SetDescription("GTX");
452
22
    poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
453
22
    poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "NOAA Vertical Datum .GTX");
454
22
    poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "gtx");
455
22
    poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
456
    // poDriver->SetMetadataItem( GDAL_DMD_HELPTOPIC,
457
    //                            "frmt_various.html#GTX" );
458
22
    poDriver->SetMetadataItem(
459
22
        GDAL_DMD_OPENOPTIONLIST,
460
22
        "<OpenOptionList>"
461
22
        "   <Option name='SHIFT_ORIGIN_IN_MINUS_180_PLUS_180' type='boolean' "
462
22
        "description='Whether to apply a +/-360 deg shift to the longitude of "
463
22
        "the top left corner so that it is in the [-180,180] range' "
464
22
        "default='NO'/>"
465
22
        "</OpenOptionList>");
466
467
22
    poDriver->SetMetadataItem(GDAL_DMD_CREATIONDATATYPES, "Float32");
468
469
22
    poDriver->pfnOpen = GTXDataset::Open;
470
22
    poDriver->pfnIdentify = GTXDataset::Identify;
471
22
    poDriver->pfnCreate = GTXDataset::Create;
472
473
22
    GetGDALDriverManager()->RegisterDriver(poDriver);
474
22
}