• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

OSGeo / gdal / 15885686134

25 Jun 2025 07:44PM UTC coverage: 71.084%. Remained the same
15885686134

push

github

rouault
gdal_priv.h: fix C++11 compatibility

573814 of 807237 relevant lines covered (71.08%)

250621.56 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

84.3
/frmts/raw/ntv2dataset.cpp
1
/******************************************************************************
2
 *
3
 * Project:  Horizontal Datum Formats
4
 * Purpose:  Implementation of NTv2 datum shift format used in Canada, France,
5
 *           Australia and elsewhere.
6
 * Author:   Frank Warmerdam, warmerdam@pobox.com
7
 * Financial Support: i-cubed (http://www.i-cubed.com)
8
 *
9
 ******************************************************************************
10
 * Copyright (c) 2010, Frank Warmerdam
11
 * Copyright (c) 2010-2012, Even Rouault <even dot rouault at spatialys.com>
12
 *
13
 * SPDX-License-Identifier: MIT
14
 ****************************************************************************/
15

16
// TODO(schwehr): There are a lot of magic numbers in this driver that should
17
// be changed to constants and documented.
18

19
#include "cpl_string.h"
20
#include "gdal_frmts.h"
21
#include "ogr_srs_api.h"
22
#include "rawdataset.h"
23

24
#include <algorithm>
25

26
// Format documentation: https://github.com/Esri/ntv2-file-routines
27
// Original archived specification:
28
// https://web.archive.org/web/20091227232322/http://www.mgs.gov.on.ca/stdprodconsume/groups/content/@mgs/@iandit/documents/resourcelist/stel02_047447.pdf
29

30
/**
31
 * The header for the file, and each grid consists of 11 16byte records.
32
 * The first half is an ASCII label, and the second half is the value
33
 * often in a little endian int or float.
34
 *
35
 * Example:
36

37
00000000  4e 55 4d 5f 4f 52 45 43  0b 00 00 00 00 00 00 00  |NUM_OREC........|
38
00000010  4e 55 4d 5f 53 52 45 43  0b 00 00 00 00 00 00 00  |NUM_SREC........|
39
00000020  4e 55 4d 5f 46 49 4c 45  01 00 00 00 00 00 00 00  |NUM_FILE........|
40
00000030  47 53 5f 54 59 50 45 20  53 45 43 4f 4e 44 53 20  |GS_TYPE SECONDS |
41
00000040  56 45 52 53 49 4f 4e 20  49 47 4e 30 37 5f 30 31  |VERSION IGN07_01|
42
00000050  53 59 53 54 45 4d 5f 46  4e 54 46 20 20 20 20 20  |SYSTEM_FNTF     |
43
00000060  53 59 53 54 45 4d 5f 54  52 47 46 39 33 20 20 20  |SYSTEM_TRGF93   |
44
00000070  4d 41 4a 4f 52 5f 46 20  cd cc cc 4c c2 54 58 41  |MAJOR_F ...L.TXA|
45
00000080  4d 49 4e 4f 52 5f 46 20  00 00 00 c0 88 3f 58 41  |MINOR_F .....?XA|
46
00000090  4d 41 4a 4f 52 5f 54 20  00 00 00 40 a6 54 58 41  |MAJOR_T ...@.TXA|
47
000000a0  4d 49 4e 4f 52 5f 54 20  27 e0 1a 14 c4 3f 58 41  |MINOR_T '....?XA|
48
000000b0  53 55 42 5f 4e 41 4d 45  46 52 41 4e 43 45 20 20  |SUB_NAMEFRANCE  |
49
000000c0  50 41 52 45 4e 54 20 20  4e 4f 4e 45 20 20 20 20  |PARENT  NONE    |
50
000000d0  43 52 45 41 54 45 44 20  33 31 2f 31 30 2f 30 37  |CREATED 31/10/07|
51
000000e0  55 50 44 41 54 45 44 20  20 20 20 20 20 20 20 20  |UPDATED         |
52
000000f0  53 5f 4c 41 54 20 20 20  00 00 00 00 80 04 02 41  |S_LAT   .......A|
53
00000100  4e 5f 4c 41 54 20 20 20  00 00 00 00 00 da 06 41  |N_LAT   .......A|
54
00000110  45 5f 4c 4f 4e 47 20 20  00 00 00 00 00 94 e1 c0  |E_LONG  ........|
55
00000120  57 5f 4c 4f 4e 47 20 20  00 00 00 00 00 56 d3 40  |W_LONG  .....V.@|
56
00000130  4c 41 54 5f 49 4e 43 20  00 00 00 00 00 80 76 40  |LAT_INC ......v@|
57
00000140  4c 4f 4e 47 5f 49 4e 43  00 00 00 00 00 80 76 40  |LONG_INC......v@|
58
00000150  47 53 5f 43 4f 55 4e 54  a4 43 00 00 00 00 00 00  |GS_COUNT.C......|
59
00000160  94 f7 c1 3e 70 ee a3 3f  2a c7 84 3d ff 42 af 3d  |...>p..?*..=.B.=|
60

61
the actual grid data is a raster with 4 float32 bands (lat offset, long
62
offset, lat error, long error).  The offset values are in arc seconds.
63
The grid is flipped in the x and y axis from our usual GDAL orientation.
64
That is, the first pixel is the south east corner with scanlines going
65
east to west, and rows from south to north.  As a GDAL dataset we represent
66
these both in the more conventional orientation.
67
 */
68

69
constexpr size_t knREGULAR_RECORD_SIZE = 16;
70
// This one is for velocity grids such as the NAD83(CRSR)v7 / NAD83v70VG.gvb
71
// which is the only example I know actually of that format variant.
72
constexpr size_t knMAX_RECORD_SIZE = 24;
73

74
/************************************************************************/
75
/* ==================================================================== */
76
/*                              NTv2Dataset                             */
77
/* ==================================================================== */
78
/************************************************************************/
79

80
class NTv2Dataset final : public RawDataset
81
{
82
  public:
83
    RawRasterBand::ByteOrder m_eByteOrder = RawRasterBand::NATIVE_BYTE_ORDER;
84
    bool m_bMustSwap = false;
85
    VSILFILE *fpImage = nullptr;  // image data file.
86

87
    size_t nRecordSize = 0;
88
    vsi_l_offset nGridOffset = 0;
89

90
    OGRSpatialReference m_oSRS{};
91
    GDALGeoTransform m_gt{};
92

93
    void CaptureMetadataItem(const char *pszItem);
94

95
    bool OpenGrid(const char *pachGridHeader, vsi_l_offset nDataStart);
96

97
    CPL_DISALLOW_COPY_ASSIGN(NTv2Dataset)
98

99
    CPLErr Close() override;
100

101
  public:
102
    NTv2Dataset();
103
    ~NTv2Dataset() override;
104

105
    CPLErr GetGeoTransform(GDALGeoTransform &gt) const override;
106

107
    const OGRSpatialReference *GetSpatialRef() const override
2✔
108
    {
109
        return &m_oSRS;
2✔
110
    }
111

112
    static GDALDataset *Open(GDALOpenInfo *);
113
    static int Identify(GDALOpenInfo *);
114
};
115

116
/************************************************************************/
117
/* ==================================================================== */
118
/*                              NTv2Dataset                             */
119
/* ==================================================================== */
120
/************************************************************************/
121

122
/************************************************************************/
123
/*                             NTv2Dataset()                          */
124
/************************************************************************/
125

126
NTv2Dataset::NTv2Dataset()
4✔
127
{
128
    m_oSRS.SetFromUserInput(SRS_WKT_WGS84_LAT_LONG);
4✔
129
    m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
4✔
130
}
4✔
131

132
/************************************************************************/
133
/*                            ~NTv2Dataset()                            */
134
/************************************************************************/
135

136
NTv2Dataset::~NTv2Dataset()
8✔
137

138
{
139
    NTv2Dataset::Close();
4✔
140
}
8✔
141

142
/************************************************************************/
143
/*                              Close()                                 */
144
/************************************************************************/
145

146
CPLErr NTv2Dataset::Close()
8✔
147
{
148
    CPLErr eErr = CE_None;
8✔
149
    if (nOpenFlags != OPEN_FLAGS_CLOSED)
8✔
150
    {
151
        if (fpImage)
4✔
152
        {
153
            if (VSIFCloseL(fpImage) != 0)
4✔
154
            {
155
                CPLError(CE_Failure, CPLE_FileIO, "I/O error");
×
156
                eErr = CE_Failure;
×
157
            }
158
        }
159

160
        if (GDALPamDataset::Close() != CE_None)
4✔
161
            eErr = CE_Failure;
×
162
    }
163
    return eErr;
8✔
164
}
165

166
/************************************************************************/
167
/*                        SwapPtr32IfNecessary()                        */
168
/************************************************************************/
169

170
static void SwapPtr32IfNecessary(bool bMustSwap, void *ptr)
8✔
171
{
172
    if (bMustSwap)
8✔
173
    {
174
        CPL_SWAP32PTR(static_cast<GByte *>(ptr));
4✔
175
    }
176
}
8✔
177

178
/************************************************************************/
179
/*                        SwapPtr64IfNecessary()                        */
180
/************************************************************************/
181

182
static void SwapPtr64IfNecessary(bool bMustSwap, void *ptr)
40✔
183
{
184
    if (bMustSwap)
40✔
185
    {
186
        CPL_SWAP64PTR(static_cast<GByte *>(ptr));
20✔
187
    }
188
}
40✔
189

190
/************************************************************************/
191
/*                              Identify()                              */
192
/************************************************************************/
193

194
int NTv2Dataset::Identify(GDALOpenInfo *poOpenInfo)
57,079✔
195

196
{
197
    if (STARTS_WITH_CI(poOpenInfo->pszFilename, "NTv2:"))
57,079✔
198
        return TRUE;
×
199

200
    if (poOpenInfo->nHeaderBytes < 64)
57,079✔
201
        return FALSE;
53,541✔
202

203
    const char *pszHeader =
3,538✔
204
        reinterpret_cast<const char *>(poOpenInfo->pabyHeader);
205
    if (!STARTS_WITH_CI(pszHeader + 0, "NUM_OREC"))
3,538✔
206
        return FALSE;
3,530✔
207

208
    if (!STARTS_WITH_CI(pszHeader + knREGULAR_RECORD_SIZE, "NUM_SREC") &&
8✔
209
        !STARTS_WITH_CI(pszHeader + knMAX_RECORD_SIZE, "NUM_SREC"))
×
210
        return FALSE;
×
211

212
    return TRUE;
8✔
213
}
214

215
/************************************************************************/
216
/*                                Open()                                */
217
/************************************************************************/
218

219
GDALDataset *NTv2Dataset::Open(GDALOpenInfo *poOpenInfo)
4✔
220

221
{
222
    if (!Identify(poOpenInfo) || poOpenInfo->eAccess == GA_Update)
4✔
223
        return nullptr;
×
224

225
    /* -------------------------------------------------------------------- */
226
    /*      Are we targeting a particular grid?                             */
227
    /* -------------------------------------------------------------------- */
228
    CPLString osFilename;
8✔
229
    int iTargetGrid = -1;
4✔
230

231
    if (STARTS_WITH_CI(poOpenInfo->pszFilename, "NTv2:"))
4✔
232
    {
233
        const char *pszRest = poOpenInfo->pszFilename + 5;
×
234

235
        iTargetGrid = atoi(pszRest);
×
236
        while (*pszRest != '\0' && *pszRest != ':')
×
237
            pszRest++;
×
238

239
        if (*pszRest == ':')
×
240
            pszRest++;
×
241

242
        osFilename = pszRest;
×
243
    }
244
    else
245
    {
246
        osFilename = poOpenInfo->pszFilename;
4✔
247
    }
248

249
    /* -------------------------------------------------------------------- */
250
    /*      Create a corresponding GDALDataset.                             */
251
    /* -------------------------------------------------------------------- */
252
    auto poDS = std::make_unique<NTv2Dataset>();
8✔
253
    poDS->eAccess = poOpenInfo->eAccess;
4✔
254

255
    /* -------------------------------------------------------------------- */
256
    /*      Open the file.                                                  */
257
    /* -------------------------------------------------------------------- */
258
    if (poOpenInfo->eAccess == GA_ReadOnly)
4✔
259
        poDS->fpImage = VSIFOpenL(osFilename, "rb");
4✔
260
    else
261
        poDS->fpImage = VSIFOpenL(osFilename, "rb+");
×
262

263
    if (poDS->fpImage == nullptr)
4✔
264
    {
265
        return nullptr;
×
266
    }
267

268
    /* -------------------------------------------------------------------- */
269
    /*      Read the file header.                                           */
270
    /* -------------------------------------------------------------------- */
271
    char achHeader[11 * knMAX_RECORD_SIZE] = {0};
4✔
272

273
    if (VSIFSeekL(poDS->fpImage, 0, SEEK_SET) != 0 ||
8✔
274
        VSIFReadL(achHeader, 1, 64, poDS->fpImage) != 64)
4✔
275
    {
276
        return nullptr;
×
277
    }
278

279
    poDS->nRecordSize =
4✔
280
        STARTS_WITH_CI(achHeader + knMAX_RECORD_SIZE, "NUM_SREC")
4✔
281
            ? knMAX_RECORD_SIZE
4✔
282
            : knREGULAR_RECORD_SIZE;
283
    if (VSIFReadL(achHeader + 64, 1, 11 * poDS->nRecordSize - 64,
4✔
284
                  poDS->fpImage) != 11 * poDS->nRecordSize - 64)
8✔
285
    {
286
        return nullptr;
×
287
    }
288

289
    const bool bIsLE = achHeader[8] == 11 && achHeader[9] == 0 &&
2✔
290
                       achHeader[10] == 0 && achHeader[11] == 0;
6✔
291
    const bool bIsBE = achHeader[8] == 0 && achHeader[9] == 0 &&
2✔
292
                       achHeader[10] == 0 && achHeader[11] == 11;
6✔
293
    if (!bIsLE && !bIsBE)
4✔
294
    {
295
        return nullptr;
×
296
    }
297
    poDS->m_eByteOrder = bIsLE ? RawRasterBand::ByteOrder::ORDER_LITTLE_ENDIAN
4✔
298
                               : RawRasterBand::ByteOrder::ORDER_BIG_ENDIAN;
299
    poDS->m_bMustSwap = poDS->m_eByteOrder != RawRasterBand::NATIVE_BYTE_ORDER;
4✔
300

301
    SwapPtr32IfNecessary(poDS->m_bMustSwap,
4✔
302
                         achHeader + 2 * poDS->nRecordSize + 8);
4✔
303
    GInt32 nSubFileCount = 0;
4✔
304
    memcpy(&nSubFileCount, achHeader + 2 * poDS->nRecordSize + 8, 4);
4✔
305
    if (nSubFileCount <= 0 || nSubFileCount >= 1024)
4✔
306
    {
307
        CPLError(CE_Failure, CPLE_AppDefined, "Invalid value for NUM_FILE : %d",
×
308
                 nSubFileCount);
309
        return nullptr;
×
310
    }
311

312
    poDS->CaptureMetadataItem(achHeader + 3 * poDS->nRecordSize);
4✔
313
    poDS->CaptureMetadataItem(achHeader + 4 * poDS->nRecordSize);
4✔
314
    poDS->CaptureMetadataItem(achHeader + 5 * poDS->nRecordSize);
4✔
315
    poDS->CaptureMetadataItem(achHeader + 6 * poDS->nRecordSize);
4✔
316

317
    double dfValue = 0.0;
4✔
318
    memcpy(&dfValue, achHeader + 7 * poDS->nRecordSize + 8, 8);
4✔
319
    SwapPtr64IfNecessary(poDS->m_bMustSwap, &dfValue);
4✔
320
    CPLString osFValue;
8✔
321
    osFValue.Printf("%.15g", dfValue);
4✔
322
    poDS->SetMetadataItem("MAJOR_F", osFValue);
4✔
323

324
    memcpy(&dfValue, achHeader + 8 * poDS->nRecordSize + 8, 8);
4✔
325
    SwapPtr64IfNecessary(poDS->m_bMustSwap, &dfValue);
4✔
326
    osFValue.Printf("%.15g", dfValue);
4✔
327
    poDS->SetMetadataItem("MINOR_F", osFValue);
4✔
328

329
    memcpy(&dfValue, achHeader + 9 * poDS->nRecordSize + 8, 8);
4✔
330
    SwapPtr64IfNecessary(poDS->m_bMustSwap, &dfValue);
4✔
331
    osFValue.Printf("%.15g", dfValue);
4✔
332
    poDS->SetMetadataItem("MAJOR_T", osFValue);
4✔
333

334
    memcpy(&dfValue, achHeader + 10 * poDS->nRecordSize + 8, 8);
4✔
335
    SwapPtr64IfNecessary(poDS->m_bMustSwap, &dfValue);
4✔
336
    osFValue.Printf("%.15g", dfValue);
4✔
337
    poDS->SetMetadataItem("MINOR_T", osFValue);
4✔
338

339
    /* ==================================================================== */
340
    /*      Loop over grids.                                                */
341
    /* ==================================================================== */
342
    vsi_l_offset nGridOffset = 11 * poDS->nRecordSize;
4✔
343

344
    for (int iGrid = 0; iGrid < nSubFileCount; iGrid++)
8✔
345
    {
346
        if (VSIFSeekL(poDS->fpImage, nGridOffset, SEEK_SET) < 0 ||
8✔
347
            VSIFReadL(achHeader, 11, poDS->nRecordSize, poDS->fpImage) !=
4✔
348
                poDS->nRecordSize)
4✔
349
        {
350
            CPLError(CE_Failure, CPLE_AppDefined,
×
351
                     "Cannot read header for subfile %d", iGrid);
352
            return nullptr;
×
353
        }
354

355
        for (int i = 4; i <= 9; i++)
28✔
356
            SwapPtr64IfNecessary(poDS->m_bMustSwap,
24✔
357
                                 achHeader + i * poDS->nRecordSize + 8);
24✔
358

359
        SwapPtr32IfNecessary(poDS->m_bMustSwap,
4✔
360
                             achHeader + 10 * poDS->nRecordSize + 8);
4✔
361

362
        GUInt32 nGSCount = 0;
4✔
363
        memcpy(&nGSCount, achHeader + 10 * poDS->nRecordSize + 8, 4);
4✔
364

365
        CPLString osSubName;
4✔
366
        osSubName.assign(achHeader + 8, 8);
4✔
367
        osSubName.Trim();
4✔
368

369
        // If this is our target grid, open it as a dataset.
370
        if (iTargetGrid == iGrid || (iTargetGrid == -1 && iGrid == 0))
4✔
371
        {
372
            if (!poDS->OpenGrid(achHeader, nGridOffset))
4✔
373
            {
374
                return nullptr;
×
375
            }
376
        }
377

378
        // If we are opening the file as a whole, list subdatasets.
379
        if (iTargetGrid == -1)
4✔
380
        {
381
            CPLString osKey;
8✔
382
            CPLString osValue;
8✔
383
            osKey.Printf("SUBDATASET_%d_NAME", iGrid);
4✔
384
            osValue.Printf("NTv2:%d:%s", iGrid, osFilename.c_str());
4✔
385
            poDS->SetMetadataItem(osKey, osValue, "SUBDATASETS");
4✔
386

387
            osKey.Printf("SUBDATASET_%d_DESC", iGrid);
4✔
388
            osValue.Printf("%s", osSubName.c_str());
4✔
389
            poDS->SetMetadataItem(osKey, osValue, "SUBDATASETS");
4✔
390
        }
391

392
        nGridOffset +=
4✔
393
            (11 + static_cast<vsi_l_offset>(nGSCount)) * poDS->nRecordSize;
4✔
394
    }
395

396
    /* -------------------------------------------------------------------- */
397
    /*      Initialize any PAM information.                                 */
398
    /* -------------------------------------------------------------------- */
399
    poDS->SetDescription(poOpenInfo->pszFilename);
4✔
400
    poDS->TryLoadXML();
4✔
401

402
    /* -------------------------------------------------------------------- */
403
    /*      Check for overviews.                                            */
404
    /* -------------------------------------------------------------------- */
405
    poDS->oOvManager.Initialize(poDS.get(), poOpenInfo->pszFilename);
4✔
406

407
    return poDS.release();
4✔
408
}
409

410
/************************************************************************/
411
/*                              OpenGrid()                              */
412
/*                                                                      */
413
/*      Note that the caller will already have byte swapped needed      */
414
/*      portions of the header.                                         */
415
/************************************************************************/
416

417
bool NTv2Dataset::OpenGrid(const char *pachHeader, vsi_l_offset nGridOffsetIn)
4✔
418

419
{
420
    nGridOffset = nGridOffsetIn;
4✔
421

422
    /* -------------------------------------------------------------------- */
423
    /*      Read the grid header.                                           */
424
    /* -------------------------------------------------------------------- */
425
    CaptureMetadataItem(pachHeader + 0 * nRecordSize);
4✔
426
    CaptureMetadataItem(pachHeader + 1 * nRecordSize);
4✔
427
    CaptureMetadataItem(pachHeader + 2 * nRecordSize);
4✔
428
    CaptureMetadataItem(pachHeader + 3 * nRecordSize);
4✔
429

430
    double s_lat, n_lat, e_long, w_long, lat_inc, long_inc;
431
    memcpy(&s_lat, pachHeader + 4 * nRecordSize + 8, 8);
4✔
432
    memcpy(&n_lat, pachHeader + 5 * nRecordSize + 8, 8);
4✔
433
    memcpy(&e_long, pachHeader + 6 * nRecordSize + 8, 8);
4✔
434
    memcpy(&w_long, pachHeader + 7 * nRecordSize + 8, 8);
4✔
435
    memcpy(&lat_inc, pachHeader + 8 * nRecordSize + 8, 8);
4✔
436
    memcpy(&long_inc, pachHeader + 9 * nRecordSize + 8, 8);
4✔
437

438
    e_long *= -1;
4✔
439
    w_long *= -1;
4✔
440

441
    if (long_inc == 0.0 || lat_inc == 0.0)
4✔
442
        return false;
×
443
    const double dfXSize = floor((e_long - w_long) / long_inc + 1.5);
4✔
444
    const double dfYSize = floor((n_lat - s_lat) / lat_inc + 1.5);
4✔
445
    if (!(dfXSize >= 0 && dfXSize < INT_MAX) ||
4✔
446
        !(dfYSize >= 0 && dfYSize < INT_MAX))
4✔
447
        return false;
×
448
    nRasterXSize = static_cast<int>(dfXSize);
4✔
449
    nRasterYSize = static_cast<int>(dfYSize);
4✔
450

451
    const int l_nBands = nRecordSize == knREGULAR_RECORD_SIZE ? 4 : 6;
4✔
452
    const int nPixelSize = l_nBands * 4;
4✔
453

454
    if (!GDALCheckDatasetDimensions(nRasterXSize, nRasterYSize))
4✔
455
        return false;
×
456
    if (nRasterXSize > INT_MAX / nPixelSize)
4✔
457
        return false;
×
458

459
    /* -------------------------------------------------------------------- */
460
    /*      Create band information object.                                 */
461
    /*                                                                      */
462
    /*      We use unusual offsets to remap from bottom to top, to top      */
463
    /*      to bottom orientation, and also to remap east to west, to       */
464
    /*      west to east.                                                   */
465
    /* -------------------------------------------------------------------- */
466
    for (int iBand = 0; iBand < l_nBands; iBand++)
20✔
467
    {
468
        auto poBand = RawRasterBand::Create(
469
            this, iBand + 1, fpImage,
470
            nGridOffset + 4 * iBand + 11 * nRecordSize +
16✔
471
                static_cast<vsi_l_offset>(nRasterXSize - 1) * nPixelSize +
16✔
472
                static_cast<vsi_l_offset>(nRasterYSize - 1) * nPixelSize *
16✔
473
                    nRasterXSize,
16✔
474
            -nPixelSize, -nPixelSize * nRasterXSize, GDT_Float32, m_eByteOrder,
16✔
475
            RawRasterBand::OwnFP::NO);
16✔
476
        if (!poBand)
16✔
477
            return false;
×
478
        SetBand(iBand + 1, std::move(poBand));
16✔
479
    }
480

481
    if (l_nBands == 4)
4✔
482
    {
483
        GetRasterBand(1)->SetDescription("Latitude Offset (arc seconds)");
4✔
484
        GetRasterBand(2)->SetDescription("Longitude Offset (arc seconds)");
4✔
485
        GetRasterBand(2)->SetMetadataItem("positive_value", "west");
4✔
486
        GetRasterBand(3)->SetDescription("Latitude Error");
4✔
487
        GetRasterBand(4)->SetDescription("Longitude Error");
4✔
488
    }
489
    else
490
    {
491
        // A bit surprising that the order is easting, northing here, contrary
492
        // to the classic NTv2 order.... Verified on NAD83v70VG.gvb
493
        // (https://webapp.geod.nrcan.gc.ca/geod/process/download-helper.php?file_id=NAD83v70VG)
494
        // against the TRX software
495
        // (https://webapp.geod.nrcan.gc.ca/geod/process/download-helper.php?file_id=trx)
496
        // https://webapp.geod.nrcan.gc.ca/geod/tools-outils/nad83-docs.php
497
        // Unfortunately I couldn't find an official documentation of the format
498
        // !
499
        GetRasterBand(1)->SetDescription("East velocity (mm/year)");
×
500
        GetRasterBand(2)->SetDescription("North velocity (mm/year)");
×
501
        GetRasterBand(3)->SetDescription("Up velocity (mm/year)");
×
502
        GetRasterBand(4)->SetDescription("East velocity Error (mm/year)");
×
503
        GetRasterBand(5)->SetDescription("North velocity Error (mm/year)");
×
504
        GetRasterBand(6)->SetDescription("Up velocity Error (mm/year)");
×
505
    }
506

507
    /* -------------------------------------------------------------------- */
508
    /*      Setup georeferencing.                                           */
509
    /* -------------------------------------------------------------------- */
510
    m_gt[0] = (w_long - long_inc * 0.5) / 3600.0;
4✔
511
    m_gt[1] = long_inc / 3600.0;
4✔
512
    m_gt[2] = 0.0;
4✔
513
    m_gt[3] = (n_lat + lat_inc * 0.5) / 3600.0;
4✔
514
    m_gt[4] = 0.0;
4✔
515
    m_gt[5] = (-1 * lat_inc) / 3600.0;
4✔
516

517
    return true;
4✔
518
}
519

520
/************************************************************************/
521
/*                        CaptureMetadataItem()                         */
522
/************************************************************************/
523

524
void NTv2Dataset::CaptureMetadataItem(const char *pszItem)
32✔
525

526
{
527
    CPLString osKey;
64✔
528
    CPLString osValue;
64✔
529

530
    osKey.assign(pszItem, 8);
32✔
531
    osValue.assign(pszItem + 8, 8);
32✔
532

533
    SetMetadataItem(osKey.Trim(), osValue.Trim());
32✔
534
}
32✔
535

536
/************************************************************************/
537
/*                          GetGeoTransform()                           */
538
/************************************************************************/
539

540
CPLErr NTv2Dataset::GetGeoTransform(GDALGeoTransform &gt) const
2✔
541

542
{
543
    gt = m_gt;
2✔
544
    return CE_None;
2✔
545
}
546

547
/************************************************************************/
548
/*                         GDALRegister_NTv2()                          */
549
/************************************************************************/
550

551
void GDALRegister_NTv2()
1,911✔
552

553
{
554
    if (GDALGetDriverByName("NTv2") != nullptr)
1,911✔
555
        return;
282✔
556

557
    GDALDriver *poDriver = new GDALDriver();
1,629✔
558

559
    poDriver->SetDescription("NTv2");
1,629✔
560
    poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
1,629✔
561
    poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "NTv2 Datum Grid Shift");
1,629✔
562
    poDriver->SetMetadataItem(GDAL_DMD_EXTENSIONS, "gsb gvb");
1,629✔
563
    poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
1,629✔
564
    poDriver->SetMetadataItem(GDAL_DMD_SUBDATASETS, "YES");
1,629✔
565

566
    poDriver->pfnOpen = NTv2Dataset::Open;
1,629✔
567
    poDriver->pfnIdentify = NTv2Dataset::Identify;
1,629✔
568

569
    GetGDALDriverManager()->RegisterDriver(poDriver);
1,629✔
570
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc