LCOV - code coverage report
Current view: top level - frmts/raw - ntv2dataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 453 489 92.6 %
Date: 2024-05-03 15:49:35 Functions: 16 16 100.0 %

          Line data    Source code
       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             :  * Permission is hereby granted, free of charge, to any person obtaining a
      14             :  * copy of this software and associated documentation files (the "Software"),
      15             :  * to deal in the Software without restriction, including without limitation
      16             :  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
      17             :  * and/or sell copies of the Software, and to permit persons to whom the
      18             :  * Software is furnished to do so, subject to the following conditions:
      19             :  *
      20             :  * The above copyright notice and this permission notice shall be included
      21             :  * in all copies or substantial portions of the Software.
      22             :  *
      23             :  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
      24             :  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      25             :  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
      26             :  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
      27             :  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
      28             :  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
      29             :  * DEALINGS IN THE SOFTWARE.
      30             :  ****************************************************************************/
      31             : 
      32             : // TODO(schwehr): There are a lot of magic numbers in this driver that should
      33             : // be changed to constants and documented.
      34             : 
      35             : #include "cpl_string.h"
      36             : #include "gdal_frmts.h"
      37             : #include "ogr_srs_api.h"
      38             : #include "rawdataset.h"
      39             : 
      40             : #include <algorithm>
      41             : 
      42             : // Format documentation: https://github.com/Esri/ntv2-file-routines
      43             : // Original archived specification:
      44             : // https://web.archive.org/web/20091227232322/http://www.mgs.gov.on.ca/stdprodconsume/groups/content/@mgs/@iandit/documents/resourcelist/stel02_047447.pdf
      45             : 
      46             : /**
      47             :  * The header for the file, and each grid consists of 11 16byte records.
      48             :  * The first half is an ASCII label, and the second half is the value
      49             :  * often in a little endian int or float.
      50             :  *
      51             :  * Example:
      52             : 
      53             : 00000000  4e 55 4d 5f 4f 52 45 43  0b 00 00 00 00 00 00 00  |NUM_OREC........|
      54             : 00000010  4e 55 4d 5f 53 52 45 43  0b 00 00 00 00 00 00 00  |NUM_SREC........|
      55             : 00000020  4e 55 4d 5f 46 49 4c 45  01 00 00 00 00 00 00 00  |NUM_FILE........|
      56             : 00000030  47 53 5f 54 59 50 45 20  53 45 43 4f 4e 44 53 20  |GS_TYPE SECONDS |
      57             : 00000040  56 45 52 53 49 4f 4e 20  49 47 4e 30 37 5f 30 31  |VERSION IGN07_01|
      58             : 00000050  53 59 53 54 45 4d 5f 46  4e 54 46 20 20 20 20 20  |SYSTEM_FNTF     |
      59             : 00000060  53 59 53 54 45 4d 5f 54  52 47 46 39 33 20 20 20  |SYSTEM_TRGF93   |
      60             : 00000070  4d 41 4a 4f 52 5f 46 20  cd cc cc 4c c2 54 58 41  |MAJOR_F ...L.TXA|
      61             : 00000080  4d 49 4e 4f 52 5f 46 20  00 00 00 c0 88 3f 58 41  |MINOR_F .....?XA|
      62             : 00000090  4d 41 4a 4f 52 5f 54 20  00 00 00 40 a6 54 58 41  |MAJOR_T ...@.TXA|
      63             : 000000a0  4d 49 4e 4f 52 5f 54 20  27 e0 1a 14 c4 3f 58 41  |MINOR_T '....?XA|
      64             : 000000b0  53 55 42 5f 4e 41 4d 45  46 52 41 4e 43 45 20 20  |SUB_NAMEFRANCE  |
      65             : 000000c0  50 41 52 45 4e 54 20 20  4e 4f 4e 45 20 20 20 20  |PARENT  NONE    |
      66             : 000000d0  43 52 45 41 54 45 44 20  33 31 2f 31 30 2f 30 37  |CREATED 31/10/07|
      67             : 000000e0  55 50 44 41 54 45 44 20  20 20 20 20 20 20 20 20  |UPDATED         |
      68             : 000000f0  53 5f 4c 41 54 20 20 20  00 00 00 00 80 04 02 41  |S_LAT   .......A|
      69             : 00000100  4e 5f 4c 41 54 20 20 20  00 00 00 00 00 da 06 41  |N_LAT   .......A|
      70             : 00000110  45 5f 4c 4f 4e 47 20 20  00 00 00 00 00 94 e1 c0  |E_LONG  ........|
      71             : 00000120  57 5f 4c 4f 4e 47 20 20  00 00 00 00 00 56 d3 40  |W_LONG  .....V.@|
      72             : 00000130  4c 41 54 5f 49 4e 43 20  00 00 00 00 00 80 76 40  |LAT_INC ......v@|
      73             : 00000140  4c 4f 4e 47 5f 49 4e 43  00 00 00 00 00 80 76 40  |LONG_INC......v@|
      74             : 00000150  47 53 5f 43 4f 55 4e 54  a4 43 00 00 00 00 00 00  |GS_COUNT.C......|
      75             : 00000160  94 f7 c1 3e 70 ee a3 3f  2a c7 84 3d ff 42 af 3d  |...>p..?*..=.B.=|
      76             : 
      77             : the actual grid data is a raster with 4 float32 bands (lat offset, long
      78             : offset, lat error, long error).  The offset values are in arc seconds.
      79             : The grid is flipped in the x and y axis from our usual GDAL orientation.
      80             : That is, the first pixel is the south east corner with scanlines going
      81             : east to west, and rows from south to north.  As a GDAL dataset we represent
      82             : these both in the more conventional orientation.
      83             :  */
      84             : 
      85             : constexpr size_t knREGULAR_RECORD_SIZE = 16;
      86             : // This one is for velocity grids such as the NAD83(CRSR)v7 / NAD83v70VG.gvb
      87             : // which is the only example I know actually of that format variant.
      88             : constexpr size_t knMAX_RECORD_SIZE = 24;
      89             : 
      90             : /************************************************************************/
      91             : /* ==================================================================== */
      92             : /*                              NTv2Dataset                             */
      93             : /* ==================================================================== */
      94             : /************************************************************************/
      95             : 
      96             : class NTv2Dataset final : public RawDataset
      97             : {
      98             :   public:
      99             :     RawRasterBand::ByteOrder m_eByteOrder = RawRasterBand::NATIVE_BYTE_ORDER;
     100             :     bool m_bMustSwap = false;
     101             :     VSILFILE *fpImage = nullptr;  // image data file.
     102             : 
     103             :     size_t nRecordSize = 0;
     104             :     vsi_l_offset nGridOffset = 0;
     105             : 
     106             :     OGRSpatialReference m_oSRS{};
     107             :     double adfGeoTransform[6];
     108             : 
     109             :     void CaptureMetadataItem(const char *pszItem);
     110             : 
     111             :     bool OpenGrid(const char *pachGridHeader, vsi_l_offset nDataStart);
     112             : 
     113             :     CPL_DISALLOW_COPY_ASSIGN(NTv2Dataset)
     114             : 
     115             :     CPLErr Close() override;
     116             : 
     117             :   public:
     118             :     NTv2Dataset();
     119             :     ~NTv2Dataset() override;
     120             : 
     121             :     CPLErr SetGeoTransform(double *padfTransform) override;
     122             :     CPLErr GetGeoTransform(double *padfTransform) override;
     123             : 
     124           6 :     const OGRSpatialReference *GetSpatialRef() const override
     125             :     {
     126           6 :         return &m_oSRS;
     127             :     }
     128             : 
     129             :     CPLErr FlushCache(bool bAtClosing) override;
     130             : 
     131             :     static GDALDataset *Open(GDALOpenInfo *);
     132             :     static int Identify(GDALOpenInfo *);
     133             :     static GDALDataset *Create(const char *pszFilename, int nXSize, int nYSize,
     134             :                                int nBandsIn, GDALDataType eType,
     135             :                                char **papszOptions);
     136             : };
     137             : 
     138             : /************************************************************************/
     139             : /* ==================================================================== */
     140             : /*                              NTv2Dataset                             */
     141             : /* ==================================================================== */
     142             : /************************************************************************/
     143             : 
     144             : /************************************************************************/
     145             : /*                             NTv2Dataset()                          */
     146             : /************************************************************************/
     147             : 
     148          22 : NTv2Dataset::NTv2Dataset()
     149             : {
     150          22 :     m_oSRS.SetFromUserInput(SRS_WKT_WGS84_LAT_LONG);
     151          22 :     m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     152             : 
     153          22 :     adfGeoTransform[0] = 0.0;
     154          22 :     adfGeoTransform[1] = 0.0;  // TODO(schwehr): Should this be 1.0?
     155          22 :     adfGeoTransform[2] = 0.0;
     156          22 :     adfGeoTransform[3] = 0.0;
     157          22 :     adfGeoTransform[4] = 0.0;
     158          22 :     adfGeoTransform[5] = 0.0;  // TODO(schwehr): Should this be 1.0?
     159          22 : }
     160             : 
     161             : /************************************************************************/
     162             : /*                            ~NTv2Dataset()                            */
     163             : /************************************************************************/
     164             : 
     165          44 : NTv2Dataset::~NTv2Dataset()
     166             : 
     167             : {
     168          22 :     NTv2Dataset::Close();
     169          44 : }
     170             : 
     171             : /************************************************************************/
     172             : /*                              Close()                                 */
     173             : /************************************************************************/
     174             : 
     175          44 : CPLErr NTv2Dataset::Close()
     176             : {
     177          44 :     CPLErr eErr = CE_None;
     178          44 :     if (nOpenFlags != OPEN_FLAGS_CLOSED)
     179             :     {
     180          22 :         if (NTv2Dataset::FlushCache(true) != CE_None)
     181           0 :             eErr = CE_Failure;
     182             : 
     183          22 :         if (fpImage)
     184             :         {
     185          22 :             if (VSIFCloseL(fpImage) != 0)
     186             :             {
     187           0 :                 CPLError(CE_Failure, CPLE_FileIO, "I/O error");
     188           0 :                 eErr = CE_Failure;
     189             :             }
     190             :         }
     191             : 
     192          22 :         if (GDALPamDataset::Close() != CE_None)
     193           0 :             eErr = CE_Failure;
     194             :     }
     195          44 :     return eErr;
     196             : }
     197             : 
     198             : /************************************************************************/
     199             : /*                        SwapPtr32IfNecessary()                        */
     200             : /************************************************************************/
     201             : 
     202          78 : static void SwapPtr32IfNecessary(bool bMustSwap, void *ptr)
     203             : {
     204          78 :     if (bMustSwap)
     205             :     {
     206          35 :         CPL_SWAP32PTR(static_cast<GByte *>(ptr));
     207             :     }
     208          78 : }
     209             : 
     210             : /************************************************************************/
     211             : /*                        SwapPtr64IfNecessary()                        */
     212             : /************************************************************************/
     213             : 
     214         326 : static void SwapPtr64IfNecessary(bool bMustSwap, void *ptr)
     215             : {
     216         326 :     if (bMustSwap)
     217             :     {
     218         143 :         CPL_SWAP64PTR(static_cast<GByte *>(ptr));
     219             :     }
     220         326 : }
     221             : 
     222             : /************************************************************************/
     223             : /*                             FlushCache()                             */
     224             : /************************************************************************/
     225             : 
     226          23 : CPLErr NTv2Dataset::FlushCache(bool bAtClosing)
     227             : 
     228             : {
     229             :     /* -------------------------------------------------------------------- */
     230             :     /*      Nothing to do in readonly mode, or if nothing seems to have     */
     231             :     /*      changed metadata wise.                                          */
     232             :     /* -------------------------------------------------------------------- */
     233          23 :     if (eAccess != GA_Update || !(GetPamFlags() & GPF_DIRTY))
     234             :     {
     235          19 :         return RawDataset::FlushCache(bAtClosing);
     236             :     }
     237             : 
     238             :     /* -------------------------------------------------------------------- */
     239             :     /*      Load grid and file headers.                                     */
     240             :     /* -------------------------------------------------------------------- */
     241           4 :     const int nRecords = 11;
     242           4 :     char achFileHeader[nRecords * knMAX_RECORD_SIZE] = {'\0'};
     243           4 :     char achGridHeader[nRecords * knMAX_RECORD_SIZE] = {'\0'};
     244             : 
     245           4 :     bool bOK = VSIFSeekL(fpImage, 0, SEEK_SET) == 0;
     246           4 :     bOK &=
     247           4 :         VSIFReadL(achFileHeader, nRecords, nRecordSize, fpImage) == nRecordSize;
     248             : 
     249           4 :     bOK &= VSIFSeekL(fpImage, nGridOffset, SEEK_SET) == 0;
     250           4 :     bOK &=
     251           4 :         VSIFReadL(achGridHeader, nRecords, nRecordSize, fpImage) == nRecordSize;
     252             : 
     253             :     /* -------------------------------------------------------------------- */
     254             :     /*      Update the grid, and file headers with any available            */
     255             :     /*      metadata.  If all available metadata is recognised then mark    */
     256             :     /*      things "clean" from a PAM point of view.                        */
     257             :     /* -------------------------------------------------------------------- */
     258           4 :     char **papszMD = GetMetadata();
     259           4 :     bool bSomeLeftOver = false;
     260             : 
     261          52 :     for (int i = 0; papszMD != nullptr && papszMD[i] != nullptr; i++)
     262             :     {
     263          48 :         const size_t nMinLen = 8;
     264          48 :         char *pszKey = nullptr;
     265          48 :         const char *pszValue = CPLParseNameValue(papszMD[i], &pszKey);
     266          48 :         if (pszKey == nullptr)
     267           0 :             continue;
     268             : 
     269          48 :         if (EQUAL(pszKey, "GS_TYPE"))
     270             :         {
     271           4 :             memcpy(achFileHeader + 3 * nRecordSize + 8, "        ", 8);
     272           4 :             memcpy(achFileHeader + 3 * nRecordSize + 8, pszValue,
     273           4 :                    std::min(nMinLen, strlen(pszValue)));
     274             :         }
     275          44 :         else if (EQUAL(pszKey, "VERSION"))
     276             :         {
     277           4 :             memcpy(achFileHeader + 4 * nRecordSize + 8, "        ", 8);
     278           4 :             memcpy(achFileHeader + 4 * nRecordSize + 8, pszValue,
     279           4 :                    std::min(nMinLen, strlen(pszValue)));
     280             :         }
     281          40 :         else if (EQUAL(pszKey, "SYSTEM_F"))
     282             :         {
     283           4 :             memcpy(achFileHeader + 5 * nRecordSize + 8, "        ", 8);
     284           4 :             memcpy(achFileHeader + 5 * nRecordSize + 8, pszValue,
     285           4 :                    std::min(nMinLen, strlen(pszValue)));
     286             :         }
     287          36 :         else if (EQUAL(pszKey, "SYSTEM_T"))
     288             :         {
     289           4 :             memcpy(achFileHeader + 6 * nRecordSize + 8, "        ", 8);
     290           4 :             memcpy(achFileHeader + 6 * nRecordSize + 8, pszValue,
     291           4 :                    std::min(nMinLen, strlen(pszValue)));
     292             :         }
     293          32 :         else if (EQUAL(pszKey, "MAJOR_F"))
     294             :         {
     295           4 :             double dfValue = CPLAtof(pszValue);
     296           4 :             SwapPtr64IfNecessary(m_bMustSwap, &dfValue);
     297           4 :             memcpy(achFileHeader + 7 * nRecordSize + 8, &dfValue, 8);
     298             :         }
     299          28 :         else if (EQUAL(pszKey, "MINOR_F"))
     300             :         {
     301           4 :             double dfValue = CPLAtof(pszValue);
     302           4 :             SwapPtr64IfNecessary(m_bMustSwap, &dfValue);
     303           4 :             memcpy(achFileHeader + 8 * nRecordSize + 8, &dfValue, 8);
     304             :         }
     305          24 :         else if (EQUAL(pszKey, "MAJOR_T"))
     306             :         {
     307           4 :             double dfValue = CPLAtof(pszValue);
     308           4 :             SwapPtr64IfNecessary(m_bMustSwap, &dfValue);
     309           4 :             memcpy(achFileHeader + 9 * nRecordSize + 8, &dfValue, 8);
     310             :         }
     311          20 :         else if (EQUAL(pszKey, "MINOR_T"))
     312             :         {
     313           4 :             double dfValue = CPLAtof(pszValue);
     314           4 :             SwapPtr64IfNecessary(m_bMustSwap, &dfValue);
     315           4 :             memcpy(achFileHeader + 10 * nRecordSize + 8, &dfValue, 8);
     316             :         }
     317          16 :         else if (EQUAL(pszKey, "SUB_NAME"))
     318             :         {
     319           4 :             memcpy(achGridHeader + /*0*nRecordSize+*/ 8, "        ", 8);
     320           4 :             memcpy(achGridHeader + /*0*nRecordSize+*/ 8, pszValue,
     321           4 :                    std::min(nMinLen, strlen(pszValue)));
     322             :         }
     323          12 :         else if (EQUAL(pszKey, "PARENT"))
     324             :         {
     325           4 :             memcpy(achGridHeader + 1 * nRecordSize + 8, "        ", 8);
     326           4 :             memcpy(achGridHeader + 1 * nRecordSize + 8, pszValue,
     327           4 :                    std::min(nMinLen, strlen(pszValue)));
     328             :         }
     329           8 :         else if (EQUAL(pszKey, "CREATED"))
     330             :         {
     331           4 :             memcpy(achGridHeader + 2 * nRecordSize + 8, "        ", 8);
     332           4 :             memcpy(achGridHeader + 2 * nRecordSize + 8, pszValue,
     333           4 :                    std::min(nMinLen, strlen(pszValue)));
     334             :         }
     335           4 :         else if (EQUAL(pszKey, "UPDATED"))
     336             :         {
     337           4 :             memcpy(achGridHeader + 3 * nRecordSize + 8, "        ", 8);
     338           4 :             memcpy(achGridHeader + 3 * nRecordSize + 8, pszValue,
     339           4 :                    std::min(nMinLen, strlen(pszValue)));
     340             :         }
     341             :         else
     342             :         {
     343           0 :             bSomeLeftOver = true;
     344             :         }
     345             : 
     346          48 :         CPLFree(pszKey);
     347             :     }
     348             : 
     349             :     /* -------------------------------------------------------------------- */
     350             :     /*      Load grid and file headers.                                     */
     351             :     /* -------------------------------------------------------------------- */
     352           4 :     bOK &= VSIFSeekL(fpImage, 0, SEEK_SET) == 0;
     353           4 :     bOK &= VSIFWriteL(achFileHeader, nRecords, nRecordSize, fpImage) ==
     354           4 :            nRecordSize;
     355             : 
     356           4 :     bOK &= VSIFSeekL(fpImage, nGridOffset, SEEK_SET) == 0;
     357           4 :     bOK &= VSIFWriteL(achGridHeader, nRecords, nRecordSize, fpImage) ==
     358           4 :            nRecordSize;
     359             : 
     360             :     /* -------------------------------------------------------------------- */
     361             :     /*      Clear flags if we got everything, then let pam and below do     */
     362             :     /*      their flushing.                                                 */
     363             :     /* -------------------------------------------------------------------- */
     364           4 :     if (!bSomeLeftOver)
     365           4 :         SetPamFlags(GetPamFlags() & (~GPF_DIRTY));
     366             : 
     367           4 :     if (RawDataset::FlushCache(bAtClosing) != CE_None)
     368           0 :         bOK = false;
     369           4 :     return bOK ? CE_None : CE_Failure;
     370             : }
     371             : 
     372             : /************************************************************************/
     373             : /*                              Identify()                              */
     374             : /************************************************************************/
     375             : 
     376       48793 : int NTv2Dataset::Identify(GDALOpenInfo *poOpenInfo)
     377             : 
     378             : {
     379       48793 :     if (STARTS_WITH_CI(poOpenInfo->pszFilename, "NTv2:"))
     380           8 :         return TRUE;
     381             : 
     382       48785 :     if (poOpenInfo->nHeaderBytes < 64)
     383       45539 :         return FALSE;
     384             : 
     385        3246 :     const char *pszHeader =
     386             :         reinterpret_cast<const char *>(poOpenInfo->pabyHeader);
     387        3246 :     if (!STARTS_WITH_CI(pszHeader + 0, "NUM_OREC"))
     388        3209 :         return FALSE;
     389             : 
     390          37 :     if (!STARTS_WITH_CI(pszHeader + knREGULAR_RECORD_SIZE, "NUM_SREC") &&
     391           0 :         !STARTS_WITH_CI(pszHeader + knMAX_RECORD_SIZE, "NUM_SREC"))
     392           0 :         return FALSE;
     393             : 
     394          37 :     return TRUE;
     395             : }
     396             : 
     397             : /************************************************************************/
     398             : /*                                Open()                                */
     399             : /************************************************************************/
     400             : 
     401          22 : GDALDataset *NTv2Dataset::Open(GDALOpenInfo *poOpenInfo)
     402             : 
     403             : {
     404          22 :     if (!Identify(poOpenInfo))
     405           0 :         return nullptr;
     406             : 
     407             :     /* -------------------------------------------------------------------- */
     408             :     /*      Are we targeting a particular grid?                             */
     409             :     /* -------------------------------------------------------------------- */
     410          44 :     CPLString osFilename;
     411          22 :     int iTargetGrid = -1;
     412             : 
     413          22 :     if (STARTS_WITH_CI(poOpenInfo->pszFilename, "NTv2:"))
     414             :     {
     415           4 :         const char *pszRest = poOpenInfo->pszFilename + 5;
     416             : 
     417           4 :         iTargetGrid = atoi(pszRest);
     418           8 :         while (*pszRest != '\0' && *pszRest != ':')
     419           4 :             pszRest++;
     420             : 
     421           4 :         if (*pszRest == ':')
     422           4 :             pszRest++;
     423             : 
     424           4 :         osFilename = pszRest;
     425             :     }
     426             :     else
     427             :     {
     428          18 :         osFilename = poOpenInfo->pszFilename;
     429             :     }
     430             : 
     431             :     /* -------------------------------------------------------------------- */
     432             :     /*      Create a corresponding GDALDataset.                             */
     433             :     /* -------------------------------------------------------------------- */
     434          44 :     auto poDS = std::make_unique<NTv2Dataset>();
     435          22 :     poDS->eAccess = poOpenInfo->eAccess;
     436             : 
     437             :     /* -------------------------------------------------------------------- */
     438             :     /*      Open the file.                                                  */
     439             :     /* -------------------------------------------------------------------- */
     440          22 :     if (poOpenInfo->eAccess == GA_ReadOnly)
     441          16 :         poDS->fpImage = VSIFOpenL(osFilename, "rb");
     442             :     else
     443           6 :         poDS->fpImage = VSIFOpenL(osFilename, "rb+");
     444             : 
     445          22 :     if (poDS->fpImage == nullptr)
     446             :     {
     447           0 :         return nullptr;
     448             :     }
     449             : 
     450             :     /* -------------------------------------------------------------------- */
     451             :     /*      Read the file header.                                           */
     452             :     /* -------------------------------------------------------------------- */
     453          22 :     char achHeader[11 * knMAX_RECORD_SIZE] = {0};
     454             : 
     455          44 :     if (VSIFSeekL(poDS->fpImage, 0, SEEK_SET) != 0 ||
     456          22 :         VSIFReadL(achHeader, 1, 64, poDS->fpImage) != 64)
     457             :     {
     458           0 :         return nullptr;
     459             :     }
     460             : 
     461          22 :     poDS->nRecordSize =
     462          22 :         STARTS_WITH_CI(achHeader + knMAX_RECORD_SIZE, "NUM_SREC")
     463          22 :             ? knMAX_RECORD_SIZE
     464             :             : knREGULAR_RECORD_SIZE;
     465          22 :     if (VSIFReadL(achHeader + 64, 1, 11 * poDS->nRecordSize - 64,
     466          44 :                   poDS->fpImage) != 11 * poDS->nRecordSize - 64)
     467             :     {
     468           0 :         return nullptr;
     469             :     }
     470             : 
     471          13 :     const bool bIsLE = achHeader[8] == 11 && achHeader[9] == 0 &&
     472          35 :                        achHeader[10] == 0 && achHeader[11] == 0;
     473           9 :     const bool bIsBE = achHeader[8] == 0 && achHeader[9] == 0 &&
     474          31 :                        achHeader[10] == 0 && achHeader[11] == 11;
     475          22 :     if (!bIsLE && !bIsBE)
     476             :     {
     477           0 :         return nullptr;
     478             :     }
     479          22 :     poDS->m_eByteOrder = bIsLE ? RawRasterBand::ByteOrder::ORDER_LITTLE_ENDIAN
     480             :                                : RawRasterBand::ByteOrder::ORDER_BIG_ENDIAN;
     481          22 :     poDS->m_bMustSwap = poDS->m_eByteOrder != RawRasterBand::NATIVE_BYTE_ORDER;
     482             : 
     483          22 :     SwapPtr32IfNecessary(poDS->m_bMustSwap,
     484          22 :                          achHeader + 2 * poDS->nRecordSize + 8);
     485          22 :     GInt32 nSubFileCount = 0;
     486          22 :     memcpy(&nSubFileCount, achHeader + 2 * poDS->nRecordSize + 8, 4);
     487          22 :     if (nSubFileCount <= 0 || nSubFileCount >= 1024)
     488             :     {
     489           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid value for NUM_FILE : %d",
     490             :                  nSubFileCount);
     491           0 :         return nullptr;
     492             :     }
     493             : 
     494          22 :     poDS->CaptureMetadataItem(achHeader + 3 * poDS->nRecordSize);
     495          22 :     poDS->CaptureMetadataItem(achHeader + 4 * poDS->nRecordSize);
     496          22 :     poDS->CaptureMetadataItem(achHeader + 5 * poDS->nRecordSize);
     497          22 :     poDS->CaptureMetadataItem(achHeader + 6 * poDS->nRecordSize);
     498             : 
     499          22 :     double dfValue = 0.0;
     500          22 :     memcpy(&dfValue, achHeader + 7 * poDS->nRecordSize + 8, 8);
     501          22 :     SwapPtr64IfNecessary(poDS->m_bMustSwap, &dfValue);
     502          44 :     CPLString osFValue;
     503          22 :     osFValue.Printf("%.15g", dfValue);
     504          22 :     poDS->SetMetadataItem("MAJOR_F", osFValue);
     505             : 
     506          22 :     memcpy(&dfValue, achHeader + 8 * poDS->nRecordSize + 8, 8);
     507          22 :     SwapPtr64IfNecessary(poDS->m_bMustSwap, &dfValue);
     508          22 :     osFValue.Printf("%.15g", dfValue);
     509          22 :     poDS->SetMetadataItem("MINOR_F", osFValue);
     510             : 
     511          22 :     memcpy(&dfValue, achHeader + 9 * poDS->nRecordSize + 8, 8);
     512          22 :     SwapPtr64IfNecessary(poDS->m_bMustSwap, &dfValue);
     513          22 :     osFValue.Printf("%.15g", dfValue);
     514          22 :     poDS->SetMetadataItem("MAJOR_T", osFValue);
     515             : 
     516          22 :     memcpy(&dfValue, achHeader + 10 * poDS->nRecordSize + 8, 8);
     517          22 :     SwapPtr64IfNecessary(poDS->m_bMustSwap, &dfValue);
     518          22 :     osFValue.Printf("%.15g", dfValue);
     519          22 :     poDS->SetMetadataItem("MINOR_T", osFValue);
     520             : 
     521             :     /* ==================================================================== */
     522             :     /*      Loop over grids.                                                */
     523             :     /* ==================================================================== */
     524          22 :     vsi_l_offset nGridOffset = 11 * poDS->nRecordSize;
     525             : 
     526          50 :     for (int iGrid = 0; iGrid < nSubFileCount; iGrid++)
     527             :     {
     528          56 :         if (VSIFSeekL(poDS->fpImage, nGridOffset, SEEK_SET) < 0 ||
     529          28 :             VSIFReadL(achHeader, 11, poDS->nRecordSize, poDS->fpImage) !=
     530          28 :                 poDS->nRecordSize)
     531             :         {
     532           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     533             :                      "Cannot read header for subfile %d", iGrid);
     534           0 :             return nullptr;
     535             :         }
     536             : 
     537         196 :         for (int i = 4; i <= 9; i++)
     538         168 :             SwapPtr64IfNecessary(poDS->m_bMustSwap,
     539         168 :                                  achHeader + i * poDS->nRecordSize + 8);
     540             : 
     541          28 :         SwapPtr32IfNecessary(poDS->m_bMustSwap,
     542          28 :                              achHeader + 10 * poDS->nRecordSize + 8);
     543             : 
     544          28 :         GUInt32 nGSCount = 0;
     545          28 :         memcpy(&nGSCount, achHeader + 10 * poDS->nRecordSize + 8, 4);
     546             : 
     547          28 :         CPLString osSubName;
     548          28 :         osSubName.assign(achHeader + 8, 8);
     549          28 :         osSubName.Trim();
     550             : 
     551             :         // If this is our target grid, open it as a dataset.
     552          28 :         if (iTargetGrid == iGrid || (iTargetGrid == -1 && iGrid == 0))
     553             :         {
     554          22 :             if (!poDS->OpenGrid(achHeader, nGridOffset))
     555             :             {
     556           0 :                 return nullptr;
     557             :             }
     558             :         }
     559             : 
     560             :         // If we are opening the file as a whole, list subdatasets.
     561          28 :         if (iTargetGrid == -1)
     562             :         {
     563          40 :             CPLString osKey;
     564          40 :             CPLString osValue;
     565          20 :             osKey.Printf("SUBDATASET_%d_NAME", iGrid);
     566          20 :             osValue.Printf("NTv2:%d:%s", iGrid, osFilename.c_str());
     567          20 :             poDS->SetMetadataItem(osKey, osValue, "SUBDATASETS");
     568             : 
     569          20 :             osKey.Printf("SUBDATASET_%d_DESC", iGrid);
     570          20 :             osValue.Printf("%s", osSubName.c_str());
     571          20 :             poDS->SetMetadataItem(osKey, osValue, "SUBDATASETS");
     572             :         }
     573             : 
     574          28 :         nGridOffset +=
     575          28 :             (11 + static_cast<vsi_l_offset>(nGSCount)) * poDS->nRecordSize;
     576             :     }
     577             : 
     578             :     /* -------------------------------------------------------------------- */
     579             :     /*      Initialize any PAM information.                                 */
     580             :     /* -------------------------------------------------------------------- */
     581          22 :     poDS->SetDescription(poOpenInfo->pszFilename);
     582          22 :     poDS->TryLoadXML();
     583             : 
     584             :     /* -------------------------------------------------------------------- */
     585             :     /*      Check for overviews.                                            */
     586             :     /* -------------------------------------------------------------------- */
     587          22 :     poDS->oOvManager.Initialize(poDS.get(), poOpenInfo->pszFilename);
     588             : 
     589          22 :     return poDS.release();
     590             : }
     591             : 
     592             : /************************************************************************/
     593             : /*                              OpenGrid()                              */
     594             : /*                                                                      */
     595             : /*      Note that the caller will already have byte swapped needed      */
     596             : /*      portions of the header.                                         */
     597             : /************************************************************************/
     598             : 
     599          22 : bool NTv2Dataset::OpenGrid(const char *pachHeader, vsi_l_offset nGridOffsetIn)
     600             : 
     601             : {
     602          22 :     nGridOffset = nGridOffsetIn;
     603             : 
     604             :     /* -------------------------------------------------------------------- */
     605             :     /*      Read the grid header.                                           */
     606             :     /* -------------------------------------------------------------------- */
     607          22 :     CaptureMetadataItem(pachHeader + 0 * nRecordSize);
     608          22 :     CaptureMetadataItem(pachHeader + 1 * nRecordSize);
     609          22 :     CaptureMetadataItem(pachHeader + 2 * nRecordSize);
     610          22 :     CaptureMetadataItem(pachHeader + 3 * nRecordSize);
     611             : 
     612             :     double s_lat, n_lat, e_long, w_long, lat_inc, long_inc;
     613          22 :     memcpy(&s_lat, pachHeader + 4 * nRecordSize + 8, 8);
     614          22 :     memcpy(&n_lat, pachHeader + 5 * nRecordSize + 8, 8);
     615          22 :     memcpy(&e_long, pachHeader + 6 * nRecordSize + 8, 8);
     616          22 :     memcpy(&w_long, pachHeader + 7 * nRecordSize + 8, 8);
     617          22 :     memcpy(&lat_inc, pachHeader + 8 * nRecordSize + 8, 8);
     618          22 :     memcpy(&long_inc, pachHeader + 9 * nRecordSize + 8, 8);
     619             : 
     620          22 :     e_long *= -1;
     621          22 :     w_long *= -1;
     622             : 
     623          22 :     if (long_inc == 0.0 || lat_inc == 0.0)
     624           0 :         return false;
     625          22 :     const double dfXSize = floor((e_long - w_long) / long_inc + 1.5);
     626          22 :     const double dfYSize = floor((n_lat - s_lat) / lat_inc + 1.5);
     627          22 :     if (!(dfXSize >= 0 && dfXSize < INT_MAX) ||
     628          22 :         !(dfYSize >= 0 && dfYSize < INT_MAX))
     629           0 :         return false;
     630          22 :     nRasterXSize = static_cast<int>(dfXSize);
     631          22 :     nRasterYSize = static_cast<int>(dfYSize);
     632             : 
     633          22 :     const int l_nBands = nRecordSize == knREGULAR_RECORD_SIZE ? 4 : 6;
     634          22 :     const int nPixelSize = l_nBands * 4;
     635             : 
     636          22 :     if (!GDALCheckDatasetDimensions(nRasterXSize, nRasterYSize))
     637           0 :         return false;
     638          22 :     if (nRasterXSize > INT_MAX / nPixelSize)
     639           0 :         return false;
     640             : 
     641             :     /* -------------------------------------------------------------------- */
     642             :     /*      Create band information object.                                 */
     643             :     /*                                                                      */
     644             :     /*      We use unusual offsets to remap from bottom to top, to top      */
     645             :     /*      to bottom orientation, and also to remap east to west, to       */
     646             :     /*      west to east.                                                   */
     647             :     /* -------------------------------------------------------------------- */
     648         110 :     for (int iBand = 0; iBand < l_nBands; iBand++)
     649             :     {
     650             :         auto poBand = RawRasterBand::Create(
     651             :             this, iBand + 1, fpImage,
     652          88 :             nGridOffset + 4 * iBand + 11 * nRecordSize +
     653          88 :                 static_cast<vsi_l_offset>(nRasterXSize - 1) * nPixelSize +
     654          88 :                 static_cast<vsi_l_offset>(nRasterYSize - 1) * nPixelSize *
     655          88 :                     nRasterXSize,
     656          88 :             -nPixelSize, -nPixelSize * nRasterXSize, GDT_Float32, m_eByteOrder,
     657          88 :             RawRasterBand::OwnFP::NO);
     658          88 :         if (!poBand)
     659           0 :             return false;
     660          88 :         SetBand(iBand + 1, std::move(poBand));
     661             :     }
     662             : 
     663          22 :     if (l_nBands == 4)
     664             :     {
     665          22 :         GetRasterBand(1)->SetDescription("Latitude Offset (arc seconds)");
     666          22 :         GetRasterBand(2)->SetDescription("Longitude Offset (arc seconds)");
     667          22 :         GetRasterBand(2)->SetMetadataItem("positive_value", "west");
     668          22 :         GetRasterBand(3)->SetDescription("Latitude Error");
     669          22 :         GetRasterBand(4)->SetDescription("Longitude Error");
     670             :     }
     671             :     else
     672             :     {
     673             :         // A bit surprising that the order is easting, northing here, contrary
     674             :         // to the classic NTv2 order.... Verified on NAD83v70VG.gvb
     675             :         // (https://webapp.geod.nrcan.gc.ca/geod/process/download-helper.php?file_id=NAD83v70VG)
     676             :         // against the TRX software
     677             :         // (https://webapp.geod.nrcan.gc.ca/geod/process/download-helper.php?file_id=trx)
     678             :         // https://webapp.geod.nrcan.gc.ca/geod/tools-outils/nad83-docs.php
     679             :         // Unfortunately I couldn't find an official documentation of the format
     680             :         // !
     681           0 :         GetRasterBand(1)->SetDescription("East velocity (mm/year)");
     682           0 :         GetRasterBand(2)->SetDescription("North velocity (mm/year)");
     683           0 :         GetRasterBand(3)->SetDescription("Up velocity (mm/year)");
     684           0 :         GetRasterBand(4)->SetDescription("East velocity Error (mm/year)");
     685           0 :         GetRasterBand(5)->SetDescription("North velocity Error (mm/year)");
     686           0 :         GetRasterBand(6)->SetDescription("Up velocity Error (mm/year)");
     687             :     }
     688             : 
     689             :     /* -------------------------------------------------------------------- */
     690             :     /*      Setup georeferencing.                                           */
     691             :     /* -------------------------------------------------------------------- */
     692          22 :     adfGeoTransform[0] = (w_long - long_inc * 0.5) / 3600.0;
     693          22 :     adfGeoTransform[1] = long_inc / 3600.0;
     694          22 :     adfGeoTransform[2] = 0.0;
     695          22 :     adfGeoTransform[3] = (n_lat + lat_inc * 0.5) / 3600.0;
     696          22 :     adfGeoTransform[4] = 0.0;
     697          22 :     adfGeoTransform[5] = (-1 * lat_inc) / 3600.0;
     698             : 
     699          22 :     return true;
     700             : }
     701             : 
     702             : /************************************************************************/
     703             : /*                        CaptureMetadataItem()                         */
     704             : /************************************************************************/
     705             : 
     706         176 : void NTv2Dataset::CaptureMetadataItem(const char *pszItem)
     707             : 
     708             : {
     709         352 :     CPLString osKey;
     710         352 :     CPLString osValue;
     711             : 
     712         176 :     osKey.assign(pszItem, 8);
     713         176 :     osValue.assign(pszItem + 8, 8);
     714             : 
     715         176 :     SetMetadataItem(osKey.Trim(), osValue.Trim());
     716         176 : }
     717             : 
     718             : /************************************************************************/
     719             : /*                          GetGeoTransform()                           */
     720             : /************************************************************************/
     721             : 
     722           8 : CPLErr NTv2Dataset::GetGeoTransform(double *padfTransform)
     723             : 
     724             : {
     725           8 :     memcpy(padfTransform, adfGeoTransform, sizeof(double) * 6);
     726           8 :     return CE_None;
     727             : }
     728             : 
     729             : /************************************************************************/
     730             : /*                          SetGeoTransform()                           */
     731             : /************************************************************************/
     732             : 
     733           4 : CPLErr NTv2Dataset::SetGeoTransform(double *padfTransform)
     734             : 
     735             : {
     736           4 :     if (eAccess == GA_ReadOnly)
     737             :     {
     738           0 :         CPLError(CE_Failure, CPLE_NoWriteAccess,
     739             :                  "Unable to update geotransform on readonly file.");
     740           0 :         return CE_Failure;
     741             :     }
     742             : 
     743           4 :     if (padfTransform[2] != 0.0 || padfTransform[4] != 0.0)
     744             :     {
     745           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     746             :                  "Rotated and sheared geotransforms not supported for NTv2.");
     747           0 :         return CE_Failure;
     748             :     }
     749             : 
     750           4 :     memcpy(adfGeoTransform, padfTransform, sizeof(double) * 6);
     751             : 
     752             :     /* -------------------------------------------------------------------- */
     753             :     /*      Update grid header.                                             */
     754             :     /* -------------------------------------------------------------------- */
     755           4 :     char achHeader[11 * knMAX_RECORD_SIZE] = {'\0'};
     756             : 
     757             :     // read grid header
     758           4 :     CPL_IGNORE_RET_VAL(VSIFSeekL(fpImage, nGridOffset, SEEK_SET));
     759           4 :     CPL_IGNORE_RET_VAL(VSIFReadL(achHeader, 11, nRecordSize, fpImage));
     760             : 
     761             :     // S_LAT
     762           4 :     double dfValue =
     763           4 :         3600 * (adfGeoTransform[3] + (nRasterYSize - 0.5) * adfGeoTransform[5]);
     764           4 :     SwapPtr64IfNecessary(m_bMustSwap, &dfValue);
     765           4 :     memcpy(achHeader + 4 * nRecordSize + 8, &dfValue, 8);
     766             : 
     767             :     // N_LAT
     768           4 :     dfValue = 3600 * (adfGeoTransform[3] + 0.5 * adfGeoTransform[5]);
     769           4 :     SwapPtr64IfNecessary(m_bMustSwap, &dfValue);
     770           4 :     memcpy(achHeader + 5 * nRecordSize + 8, &dfValue, 8);
     771             : 
     772             :     // E_LONG
     773           4 :     dfValue = -3600 *
     774           4 :               (adfGeoTransform[0] + (nRasterXSize - 0.5) * adfGeoTransform[1]);
     775           4 :     SwapPtr64IfNecessary(m_bMustSwap, &dfValue);
     776           4 :     memcpy(achHeader + 6 * nRecordSize + 8, &dfValue, 8);
     777             : 
     778             :     // W_LONG
     779           4 :     dfValue = -3600 * (adfGeoTransform[0] + 0.5 * adfGeoTransform[1]);
     780           4 :     SwapPtr64IfNecessary(m_bMustSwap, &dfValue);
     781           4 :     memcpy(achHeader + 7 * nRecordSize + 8, &dfValue, 8);
     782             : 
     783             :     // LAT_INC
     784           4 :     dfValue = -3600 * adfGeoTransform[5];
     785           4 :     SwapPtr64IfNecessary(m_bMustSwap, &dfValue);
     786           4 :     memcpy(achHeader + 8 * nRecordSize + 8, &dfValue, 8);
     787             : 
     788             :     // LONG_INC
     789           4 :     dfValue = 3600 * adfGeoTransform[1];
     790           4 :     SwapPtr64IfNecessary(m_bMustSwap, &dfValue);
     791           4 :     memcpy(achHeader + 9 * nRecordSize + 8, &dfValue, 8);
     792             : 
     793             :     // write grid header.
     794           4 :     CPL_IGNORE_RET_VAL(VSIFSeekL(fpImage, nGridOffset, SEEK_SET));
     795           4 :     CPL_IGNORE_RET_VAL(VSIFWriteL(achHeader, 11, nRecordSize, fpImage));
     796             : 
     797           4 :     return CE_None;
     798             : }
     799             : 
     800             : /************************************************************************/
     801             : /*                               Create()                               */
     802             : /************************************************************************/
     803             : 
     804          57 : GDALDataset *NTv2Dataset::Create(const char *pszFilename, int nXSize,
     805             :                                  int nYSize, int nBandsIn, GDALDataType eType,
     806             :                                  char **papszOptions)
     807             : {
     808          57 :     if (eType != GDT_Float32)
     809             :     {
     810          44 :         CPLError(CE_Failure, CPLE_AppDefined,
     811             :                  "Attempt to create NTv2 file with unsupported data type '%s'.",
     812             :                  GDALGetDataTypeName(eType));
     813          44 :         return nullptr;
     814             :     }
     815          13 :     if (nBandsIn != 4)
     816             :     {
     817           5 :         CPLError(CE_Failure, CPLE_AppDefined,
     818             :                  "Attempt to create NTv2 file with unsupported "
     819             :                  "band number '%d'.",
     820             :                  nBandsIn);
     821           5 :         return nullptr;
     822             :     }
     823             : 
     824             :     /* -------------------------------------------------------------------- */
     825             :     /*      Are we extending an existing file?                              */
     826             :     /* -------------------------------------------------------------------- */
     827           8 :     const bool bAppend = CPLFetchBool(papszOptions, "APPEND_SUBDATASET", false);
     828             : 
     829             :     /* -------------------------------------------------------------------- */
     830             :     /*      Try to open or create file.                                     */
     831             :     /* -------------------------------------------------------------------- */
     832           8 :     VSILFILE *fp = nullptr;
     833           8 :     if (bAppend)
     834           3 :         fp = VSIFOpenL(pszFilename, "rb+");
     835             :     else
     836           5 :         fp = VSIFOpenL(pszFilename, "wb");
     837             : 
     838           8 :     if (fp == nullptr)
     839             :     {
     840           2 :         CPLError(CE_Failure, CPLE_OpenFailed,
     841             :                  "Attempt to open/create file `%s' failed.\n", pszFilename);
     842           2 :         return nullptr;
     843             :     }
     844             : 
     845             :     /* -------------------------------------------------------------------- */
     846             :     /*      Create a file level header if we are creating new.              */
     847             :     /* -------------------------------------------------------------------- */
     848           6 :     char achHeader[11 * 16] = {'\0'};
     849           6 :     const char *pszValue = nullptr;
     850           6 :     GUInt32 nNumFile = 1;
     851           6 :     bool bMustSwap = false;
     852           6 :     bool bIsLE = false;
     853             : 
     854           6 :     if (!bAppend)
     855             :     {
     856           4 :         memset(achHeader, 0, sizeof(achHeader));
     857             : 
     858           4 :         bIsLE =
     859           4 :             EQUAL(CSLFetchNameValueDef(papszOptions, "ENDIANNESS", "LE"), "LE");
     860             : #ifdef CPL_LSB
     861           4 :         bMustSwap = !bIsLE;
     862             : #else
     863             :         bMustSwap = bIsLE;
     864             : #endif
     865             : 
     866           4 :         memcpy(achHeader + 0 * 16, "NUM_OREC", 8);
     867           4 :         int nNumOrec = 11;
     868           4 :         SwapPtr32IfNecessary(bMustSwap, &nNumOrec);
     869           4 :         memcpy(achHeader + 0 * 16 + 8, &nNumOrec, 4);
     870             : 
     871           4 :         memcpy(achHeader + 1 * 16, "NUM_SREC", 8);
     872           4 :         int nNumSrec = 11;
     873           4 :         SwapPtr32IfNecessary(bMustSwap, &nNumSrec);
     874           4 :         memcpy(achHeader + 1 * 16 + 8, &nNumSrec, 4);
     875             : 
     876           4 :         memcpy(achHeader + 2 * 16, "NUM_FILE", 8);
     877           4 :         SwapPtr32IfNecessary(bMustSwap, &nNumFile);
     878           4 :         memcpy(achHeader + 2 * 16 + 8, &nNumFile, 4);
     879           4 :         SwapPtr32IfNecessary(bMustSwap, &nNumFile);
     880             : 
     881           4 :         const size_t nMinLen = 16;
     882           4 :         memcpy(achHeader + 3 * 16, "GS_TYPE         ", 16);
     883           4 :         pszValue = CSLFetchNameValueDef(papszOptions, "GS_TYPE", "SECONDS");
     884           4 :         memcpy(achHeader + 3 * 16 + 8, pszValue,
     885           4 :                std::min(nMinLen, strlen(pszValue)));
     886             : 
     887           4 :         memcpy(achHeader + 4 * 16, "VERSION         ", 16);
     888           4 :         pszValue = CSLFetchNameValueDef(papszOptions, "VERSION", "");
     889           4 :         memcpy(achHeader + 4 * 16 + 8, pszValue,
     890           4 :                std::min(nMinLen, strlen(pszValue)));
     891             : 
     892           4 :         memcpy(achHeader + 5 * 16, "SYSTEM_F        ", 16);
     893           4 :         pszValue = CSLFetchNameValueDef(papszOptions, "SYSTEM_F", "");
     894           4 :         memcpy(achHeader + 5 * 16 + 8, pszValue,
     895           4 :                std::min(nMinLen, strlen(pszValue)));
     896             : 
     897           4 :         memcpy(achHeader + 6 * 16, "SYSTEM_T        ", 16);
     898           4 :         pszValue = CSLFetchNameValueDef(papszOptions, "SYSTEM_T", "");
     899           4 :         memcpy(achHeader + 6 * 16 + 8, pszValue,
     900           4 :                std::min(nMinLen, strlen(pszValue)));
     901             : 
     902           4 :         memcpy(achHeader + 7 * 16, "MAJOR_F ", 8);
     903           4 :         memcpy(achHeader + 8 * 16, "MINOR_F ", 8);
     904           4 :         memcpy(achHeader + 9 * 16, "MAJOR_T ", 8);
     905           4 :         memcpy(achHeader + 10 * 16, "MINOR_T ", 8);
     906             : 
     907           4 :         CPL_IGNORE_RET_VAL(VSIFWriteL(achHeader, 1, sizeof(achHeader), fp));
     908             :     }
     909             : 
     910             :     /* -------------------------------------------------------------------- */
     911             :     /*      Otherwise update the header with an increased subfile count,    */
     912             :     /*      and advanced to the last record of the file.                    */
     913             :     /* -------------------------------------------------------------------- */
     914             :     else
     915             :     {
     916           2 :         CPL_IGNORE_RET_VAL(VSIFSeekL(fp, 0, SEEK_SET));
     917           2 :         CPL_IGNORE_RET_VAL(VSIFReadL(achHeader, 1, 16, fp));
     918             : 
     919           3 :         bIsLE = achHeader[8] == 11 && achHeader[9] == 0 && achHeader[10] == 0 &&
     920           1 :                 achHeader[11] == 0;
     921           1 :         const bool bIsBE = achHeader[8] == 0 && achHeader[9] == 0 &&
     922           3 :                            achHeader[10] == 0 && achHeader[11] == 11;
     923           2 :         if (!bIsLE && !bIsBE)
     924             :         {
     925           0 :             VSIFCloseL(fp);
     926           0 :             return nullptr;
     927             :         }
     928             : #ifdef CPL_LSB
     929           2 :         bMustSwap = bIsBE;
     930             : #else
     931             :         bMustSwap = bIsLE;
     932             : #endif
     933             : 
     934           2 :         CPL_IGNORE_RET_VAL(VSIFSeekL(fp, 2 * 16 + 8, SEEK_SET));
     935           2 :         CPL_IGNORE_RET_VAL(VSIFReadL(&nNumFile, 1, 4, fp));
     936           2 :         SwapPtr32IfNecessary(bMustSwap, &nNumFile);
     937             : 
     938           2 :         nNumFile++;
     939             : 
     940           2 :         SwapPtr32IfNecessary(bMustSwap, &nNumFile);
     941           2 :         CPL_IGNORE_RET_VAL(VSIFSeekL(fp, 2 * 16 + 8, SEEK_SET));
     942           2 :         CPL_IGNORE_RET_VAL(VSIFWriteL(&nNumFile, 1, 4, fp));
     943           2 :         SwapPtr32IfNecessary(bMustSwap, &nNumFile);
     944             : 
     945           2 :         CPL_IGNORE_RET_VAL(VSIFSeekL(fp, 0, SEEK_END));
     946           2 :         const vsi_l_offset nEnd = VSIFTellL(fp);
     947           2 :         CPL_IGNORE_RET_VAL(VSIFSeekL(fp, nEnd - 16, SEEK_SET));
     948             :     }
     949             : 
     950             :     /* -------------------------------------------------------------------- */
     951             :     /*      Write the grid header.                                          */
     952             :     /* -------------------------------------------------------------------- */
     953           6 :     memset(achHeader, 0, sizeof(achHeader));
     954             : 
     955           6 :     const size_t nMinLen = 16;
     956             : 
     957           6 :     memcpy(achHeader + 0 * 16, "SUB_NAME        ", 16);
     958           6 :     pszValue = CSLFetchNameValueDef(papszOptions, "SUB_NAME", "");
     959           6 :     memcpy(achHeader + 0 * 16 + 8, pszValue,
     960           6 :            std::min(nMinLen, strlen(pszValue)));
     961             : 
     962           6 :     memcpy(achHeader + 1 * 16, "PARENT          ", 16);
     963           6 :     pszValue = CSLFetchNameValueDef(papszOptions, "PARENT", "NONE");
     964           6 :     memcpy(achHeader + 1 * 16 + 8, pszValue,
     965           6 :            std::min(nMinLen, strlen(pszValue)));
     966             : 
     967           6 :     memcpy(achHeader + 2 * 16, "CREATED         ", 16);
     968           6 :     pszValue = CSLFetchNameValueDef(papszOptions, "CREATED", "");
     969           6 :     memcpy(achHeader + 2 * 16 + 8, pszValue,
     970           6 :            std::min(nMinLen, strlen(pszValue)));
     971             : 
     972           6 :     memcpy(achHeader + 3 * 16, "UPDATED         ", 16);
     973           6 :     pszValue = CSLFetchNameValueDef(papszOptions, "UPDATED", "");
     974           6 :     memcpy(achHeader + 3 * 16 + 8, pszValue,
     975           6 :            std::min(nMinLen, strlen(pszValue)));
     976             : 
     977             :     double dfValue;
     978             : 
     979           6 :     memcpy(achHeader + 4 * 16, "S_LAT   ", 8);
     980           6 :     dfValue = 0;
     981           6 :     SwapPtr64IfNecessary(bMustSwap, &dfValue);
     982           6 :     memcpy(achHeader + 4 * 16 + 8, &dfValue, 8);
     983             : 
     984           6 :     memcpy(achHeader + 5 * 16, "N_LAT   ", 8);
     985           6 :     dfValue = nYSize - 1;
     986           6 :     SwapPtr64IfNecessary(bMustSwap, &dfValue);
     987           6 :     memcpy(achHeader + 5 * 16 + 8, &dfValue, 8);
     988             : 
     989           6 :     memcpy(achHeader + 6 * 16, "E_LONG  ", 8);
     990           6 :     dfValue = -1 * (nXSize - 1);
     991           6 :     SwapPtr64IfNecessary(bMustSwap, &dfValue);
     992           6 :     memcpy(achHeader + 6 * 16 + 8, &dfValue, 8);
     993             : 
     994           6 :     memcpy(achHeader + 7 * 16, "W_LONG  ", 8);
     995           6 :     dfValue = 0;
     996           6 :     SwapPtr64IfNecessary(bMustSwap, &dfValue);
     997           6 :     memcpy(achHeader + 7 * 16 + 8, &dfValue, 8);
     998             : 
     999           6 :     memcpy(achHeader + 8 * 16, "LAT_INC ", 8);
    1000           6 :     dfValue = 1;
    1001           6 :     SwapPtr64IfNecessary(bMustSwap, &dfValue);
    1002           6 :     memcpy(achHeader + 8 * 16 + 8, &dfValue, 8);
    1003             : 
    1004           6 :     memcpy(achHeader + 9 * 16, "LONG_INC", 8);
    1005           6 :     memcpy(achHeader + 9 * 16 + 8, &dfValue, 8);
    1006             : 
    1007           6 :     memcpy(achHeader + 10 * 16, "GS_COUNT", 8);
    1008           6 :     GUInt32 nGSCount = nXSize * nYSize;
    1009           6 :     SwapPtr32IfNecessary(bMustSwap, &nGSCount);
    1010           6 :     memcpy(achHeader + 10 * 16 + 8, &nGSCount, 4);
    1011             : 
    1012           6 :     CPL_IGNORE_RET_VAL(VSIFWriteL(achHeader, 1, sizeof(achHeader), fp));
    1013             : 
    1014             :     /* -------------------------------------------------------------------- */
    1015             :     /*      Write zeroed grid data.                                         */
    1016             :     /* -------------------------------------------------------------------- */
    1017           6 :     memset(achHeader, 0, 16);
    1018             : 
    1019             :     // Use -1 (0x000080bf) as the default error value.
    1020           6 :     memset(achHeader + ((bIsLE) ? 10 : 9), 0x80, 1);
    1021           6 :     memset(achHeader + ((bIsLE) ? 11 : 8), 0xbf, 1);
    1022           6 :     memset(achHeader + ((bIsLE) ? 14 : 13), 0x80, 1);
    1023           6 :     memset(achHeader + ((bIsLE) ? 15 : 12), 0xbf, 1);
    1024             : 
    1025          24 :     for (int i = 0; i < nXSize * nYSize; i++)
    1026          18 :         CPL_IGNORE_RET_VAL(VSIFWriteL(achHeader, 1, 16, fp));
    1027             : 
    1028             :     /* -------------------------------------------------------------------- */
    1029             :     /*      Write the end record.                                           */
    1030             :     /* -------------------------------------------------------------------- */
    1031           6 :     memcpy(achHeader, "END     ", 8);
    1032           6 :     memset(achHeader + 8, 0, 8);
    1033           6 :     CPL_IGNORE_RET_VAL(VSIFWriteL(achHeader, 1, 16, fp));
    1034           6 :     CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
    1035             : 
    1036           6 :     if (nNumFile == 1)
    1037           4 :         return GDALDataset::FromHandle(GDALOpen(pszFilename, GA_Update));
    1038             : 
    1039           4 :     CPLString osSubDSName;
    1040           2 :     osSubDSName.Printf("NTv2:%d:%s", nNumFile - 1, pszFilename);
    1041           2 :     return GDALDataset::FromHandle(GDALOpen(osSubDSName, GA_Update));
    1042             : }
    1043             : 
    1044             : /************************************************************************/
    1045             : /*                         GDALRegister_NTv2()                          */
    1046             : /************************************************************************/
    1047             : 
    1048        1511 : void GDALRegister_NTv2()
    1049             : 
    1050             : {
    1051        1511 :     if (GDALGetDriverByName("NTv2") != nullptr)
    1052         295 :         return;
    1053             : 
    1054        1216 :     GDALDriver *poDriver = new GDALDriver();
    1055             : 
    1056        1216 :     poDriver->SetDescription("NTv2");
    1057        1216 :     poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
    1058        1216 :     poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "NTv2 Datum Grid Shift");
    1059        1216 :     poDriver->SetMetadataItem(GDAL_DMD_EXTENSIONS, "gsb gvb");
    1060        1216 :     poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
    1061        1216 :     poDriver->SetMetadataItem(GDAL_DMD_SUBDATASETS, "YES");
    1062        1216 :     poDriver->SetMetadataItem(GDAL_DMD_CREATIONDATATYPES, "Float32");
    1063             : 
    1064        1216 :     poDriver->pfnOpen = NTv2Dataset::Open;
    1065        1216 :     poDriver->pfnIdentify = NTv2Dataset::Identify;
    1066        1216 :     poDriver->pfnCreate = NTv2Dataset::Create;
    1067             : 
    1068        1216 :     GetGDALDriverManager()->RegisterDriver(poDriver);
    1069             : }

Generated by: LCOV version 1.14