Coverage Report

Created: 2025-08-11 09:23

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