LCOV - code coverage report
Current view: top level - frmts/gsg - gsbgdataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 330 444 74.3 %
Date: 2026-06-17 21:44:33 Functions: 20 22 90.9 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GDAL
       4             :  * Purpose:  Implements the Golden Software Binary Grid Format.
       5             :  * Author:   Kevin Locke, kwl7@cornell.edu
       6             :  *           (Based largely on aaigriddataset.cpp by Frank Warmerdam)
       7             :  *
       8             :  ******************************************************************************
       9             :  * Copyright (c) 2006, Kevin Locke <kwl7@cornell.edu>
      10             :  * Copyright (c) 2008-2012, Even Rouault <even dot rouault at spatialys.com>
      11             :  *
      12             :  * SPDX-License-Identifier: MIT
      13             :  ****************************************************************************/
      14             : 
      15             : #include "cpl_conv.h"
      16             : 
      17             : #include <assert.h>
      18             : #include <float.h>
      19             : #include <limits.h>
      20             : #include <limits>
      21             : 
      22             : #include "gdal_frmts.h"
      23             : #include "gdal_pam.h"
      24             : #include "gdal_driver.h"
      25             : #include "gdal_drivermanager.h"
      26             : #include "gdal_openinfo.h"
      27             : #include "gdal_cpp_functions.h"
      28             : 
      29             : /************************************************************************/
      30             : /* ==================================================================== */
      31             : /*                              GSBGDataset                             */
      32             : /* ==================================================================== */
      33             : /************************************************************************/
      34             : 
      35             : class GSBGRasterBand;
      36             : 
      37             : class GSBGDataset final : public GDALPamDataset
      38             : {
      39             :     friend class GSBGRasterBand;
      40             : 
      41             :     static const float fNODATA_VALUE;
      42             :     static const size_t nHEADER_SIZE;
      43             : 
      44             :     static CPLErr WriteHeader(VSILFILE *fp, int nXSize, int nYSize,
      45             :                               double dfMinX, double dfMaxX, double dfMinY,
      46             :                               double dfMaxY, double dfMinZ, double dfMaxZ);
      47             : 
      48             :     VSILFILE *fp = nullptr;
      49             : 
      50             :   public:
      51          45 :     GSBGDataset() = default;
      52             : 
      53             :     ~GSBGDataset() override;
      54             : 
      55             :     static int Identify(GDALOpenInfo *);
      56             :     static GDALDataset *Open(GDALOpenInfo *);
      57             :     static GDALPamDataset *OpenPAM(GDALOpenInfo *);
      58             :     static GDALDataset *Create(const char *pszFilename, int nXSize, int nYSize,
      59             :                                int nBands, GDALDataType eType, CSLConstList);
      60             :     static GDALDataset *CreateCopy(const char *pszFilename,
      61             :                                    GDALDataset *poSrcDS, int bStrict,
      62             :                                    CSLConstList papszOptions,
      63             :                                    GDALProgressFunc pfnProgress,
      64             :                                    void *pProgressData);
      65             : 
      66             :     CPLErr GetGeoTransform(GDALGeoTransform &gt) const override;
      67             :     CPLErr SetGeoTransform(const GDALGeoTransform &gt) override;
      68             : };
      69             : 
      70             : /* NOTE:  This is not mentioned in the spec, but Surfer 8 uses this value */
      71             : /* 0x7effffee (Little Endian: eeffff7e) */
      72             : const float GSBGDataset::fNODATA_VALUE = 1.701410009187828e+38f;
      73             : 
      74             : const size_t GSBGDataset::nHEADER_SIZE = 56;
      75             : 
      76             : /************************************************************************/
      77             : /* ==================================================================== */
      78             : /*                            GSBGRasterBand                            */
      79             : /* ==================================================================== */
      80             : /************************************************************************/
      81             : 
      82             : class GSBGRasterBand final : public GDALPamRasterBand
      83             : {
      84             :     friend class GSBGDataset;
      85             : 
      86             :     double dfMinX;
      87             :     double dfMaxX;
      88             :     double dfMinY;
      89             :     double dfMaxY;
      90             :     double dfMinZ;
      91             :     double dfMaxZ;
      92             : 
      93             :     float *pafRowMinZ;
      94             :     float *pafRowMaxZ;
      95             :     int nMinZRow;
      96             :     int nMaxZRow;
      97             : 
      98             :     CPLErr ScanForMinMaxZ();
      99             : 
     100             :   public:
     101             :     GSBGRasterBand(GSBGDataset *, int);
     102             :     ~GSBGRasterBand() override;
     103             : 
     104             :     CPLErr IReadBlock(int, int, void *) override;
     105             :     CPLErr IWriteBlock(int, int, void *) override;
     106             : 
     107             :     double GetNoDataValue(int *pbSuccess = nullptr) override;
     108             :     double GetMinimum(int *pbSuccess = nullptr) override;
     109             :     double GetMaximum(int *pbSuccess = nullptr) override;
     110             : };
     111             : 
     112             : /************************************************************************/
     113             : /*                           GSBGRasterBand()                           */
     114             : /************************************************************************/
     115             : 
     116          45 : GSBGRasterBand::GSBGRasterBand(GSBGDataset *poDSIn, int nBandIn)
     117             :     : dfMinX(0.0), dfMaxX(0.0), dfMinY(0.0), dfMaxY(0.0), dfMinZ(0.0),
     118             :       dfMaxZ(0.0), pafRowMinZ(nullptr), pafRowMaxZ(nullptr), nMinZRow(-1),
     119          45 :       nMaxZRow(-1)
     120             : {
     121          45 :     this->poDS = poDSIn;
     122          45 :     this->nBand = nBandIn;
     123             : 
     124          45 :     eDataType = GDT_Float32;
     125             : 
     126          45 :     nBlockXSize = poDS->GetRasterXSize();
     127          45 :     nBlockYSize = 1;
     128          45 : }
     129             : 
     130             : /************************************************************************/
     131             : /*                          ~GSBGRasterBand()                           */
     132             : /************************************************************************/
     133             : 
     134          90 : GSBGRasterBand::~GSBGRasterBand()
     135             : 
     136             : {
     137          45 :     if (pafRowMinZ != nullptr)
     138           1 :         CPLFree(pafRowMinZ);
     139          45 :     if (pafRowMaxZ != nullptr)
     140           1 :         CPLFree(pafRowMaxZ);
     141          90 : }
     142             : 
     143             : /************************************************************************/
     144             : /*                           ScanForMinMaxZ()                           */
     145             : /************************************************************************/
     146             : 
     147           1 : CPLErr GSBGRasterBand::ScanForMinMaxZ()
     148             : 
     149             : {
     150           1 :     float *pafRowVals = (float *)VSI_MALLOC2_VERBOSE(nRasterXSize, 4);
     151             : 
     152           1 :     if (pafRowVals == nullptr)
     153             :     {
     154           0 :         return CE_Failure;
     155             :     }
     156             : 
     157           1 :     double dfNewMinZ = std::numeric_limits<double>::max();
     158           1 :     double dfNewMaxZ = std::numeric_limits<double>::lowest();
     159           1 :     int nNewMinZRow = 0;
     160           1 :     int nNewMaxZRow = 0;
     161             : 
     162             :     /* Since we have to scan, lets calc. statistics too */
     163           1 :     double dfSum = 0.0;
     164           1 :     double dfSum2 = 0.0;
     165           1 :     unsigned long nValuesRead = 0;
     166          21 :     for (int iRow = 0; iRow < nRasterYSize; iRow++)
     167             :     {
     168          20 :         CPLErr eErr = IReadBlock(0, iRow, pafRowVals);
     169          20 :         if (eErr != CE_None)
     170             :         {
     171           0 :             VSIFree(pafRowVals);
     172           0 :             return CE_Failure;
     173             :         }
     174             : 
     175          20 :         pafRowMinZ[iRow] = std::numeric_limits<float>::max();
     176          20 :         pafRowMaxZ[iRow] = std::numeric_limits<float>::lowest();
     177         420 :         for (int iCol = 0; iCol < nRasterXSize; iCol++)
     178             :         {
     179         400 :             if (pafRowVals[iCol] == GSBGDataset::fNODATA_VALUE)
     180         400 :                 continue;
     181             : 
     182           0 :             if (pafRowVals[iCol] < pafRowMinZ[iRow])
     183           0 :                 pafRowMinZ[iRow] = pafRowVals[iCol];
     184             : 
     185           0 :             if (pafRowVals[iCol] > pafRowMinZ[iRow])
     186           0 :                 pafRowMaxZ[iRow] = pafRowVals[iCol];
     187             : 
     188           0 :             dfSum += pafRowVals[iCol];
     189           0 :             dfSum2 += static_cast<double>(pafRowVals[iCol]) * pafRowVals[iCol];
     190           0 :             nValuesRead++;
     191             :         }
     192             : 
     193          20 :         if (pafRowMinZ[iRow] < dfNewMinZ)
     194             :         {
     195           1 :             dfNewMinZ = pafRowMinZ[iRow];
     196           1 :             nNewMinZRow = iRow;
     197             :         }
     198             : 
     199          20 :         if (pafRowMaxZ[iRow] > dfNewMaxZ)
     200             :         {
     201           1 :             dfNewMaxZ = pafRowMaxZ[iRow];
     202           1 :             nNewMaxZRow = iRow;
     203             :         }
     204             :     }
     205             : 
     206           1 :     VSIFree(pafRowVals);
     207             : 
     208           1 :     if (nValuesRead == 0)
     209             :     {
     210           1 :         dfMinZ = 0.0;
     211           1 :         dfMaxZ = 0.0;
     212           1 :         nMinZRow = 0;
     213           1 :         nMaxZRow = 0;
     214           1 :         return CE_None;
     215             :     }
     216             : 
     217           0 :     dfMinZ = dfNewMinZ;
     218           0 :     dfMaxZ = dfNewMaxZ;
     219           0 :     nMinZRow = nNewMinZRow;
     220           0 :     nMaxZRow = nNewMaxZRow;
     221             : 
     222           0 :     double dfMean = dfSum / nValuesRead;
     223           0 :     double dfStdDev = sqrt((dfSum2 / nValuesRead) - (dfMean * dfMean));
     224           0 :     SetStatistics(dfMinZ, dfMaxZ, dfMean, dfStdDev);
     225             : 
     226           0 :     return CE_None;
     227             : }
     228             : 
     229             : /************************************************************************/
     230             : /*                             IReadBlock()                             */
     231             : /************************************************************************/
     232             : 
     233         140 : CPLErr GSBGRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff, void *pImage)
     234             : 
     235             : {
     236         140 :     if (nBlockYOff < 0 || nBlockYOff > nRasterYSize - 1 || nBlockXOff != 0)
     237           0 :         return CE_Failure;
     238             : 
     239         140 :     GSBGDataset *poGDS = cpl::down_cast<GSBGDataset *>(poDS);
     240         280 :     if (VSIFSeekL(poGDS->fp,
     241         140 :                   GSBGDataset::nHEADER_SIZE +
     242         140 :                       4 * static_cast<vsi_l_offset>(nRasterXSize) *
     243         140 :                           (nRasterYSize - nBlockYOff - 1),
     244         140 :                   SEEK_SET) != 0)
     245             :     {
     246           0 :         CPLError(CE_Failure, CPLE_FileIO,
     247             :                  "Unable to seek to beginning of grid row.");
     248           0 :         return CE_Failure;
     249             :     }
     250             : 
     251         140 :     if (VSIFReadL(pImage, sizeof(float), nBlockXSize, poGDS->fp) !=
     252         140 :         static_cast<unsigned>(nBlockXSize))
     253             :     {
     254           0 :         CPLError(CE_Failure, CPLE_FileIO,
     255             :                  "Unable to read block from grid file.");
     256           0 :         return CE_Failure;
     257             :     }
     258             : 
     259             : #ifdef CPL_MSB
     260             :     float *pfImage = (float *)pImage;
     261             :     for (int iPixel = 0; iPixel < nBlockXSize; iPixel++)
     262             :     {
     263             :         CPL_LSBPTR32(pfImage + iPixel);
     264             :     }
     265             : #endif
     266             : 
     267         140 :     return CE_None;
     268             : }
     269             : 
     270             : /************************************************************************/
     271             : /*                            IWriteBlock()                             */
     272             : /************************************************************************/
     273             : 
     274          20 : CPLErr GSBGRasterBand::IWriteBlock(int nBlockXOff, int nBlockYOff, void *pImage)
     275             : 
     276             : {
     277          20 :     if (eAccess == GA_ReadOnly)
     278             :     {
     279           0 :         CPLError(CE_Failure, CPLE_NoWriteAccess,
     280             :                  "Unable to write block, dataset opened read only.");
     281           0 :         return CE_Failure;
     282             :     }
     283             : 
     284          20 :     if (nBlockYOff < 0 || nBlockYOff > nRasterYSize - 1 || nBlockXOff != 0)
     285           0 :         return CE_Failure;
     286             : 
     287          20 :     GSBGDataset *poGDS = cpl::down_cast<GSBGDataset *>(poDS);
     288             : 
     289          20 :     if (pafRowMinZ == nullptr || pafRowMaxZ == nullptr || nMinZRow < 0 ||
     290          19 :         nMaxZRow < 0)
     291             :     {
     292           1 :         pafRowMinZ = (float *)VSI_MALLOC2_VERBOSE(nRasterYSize, sizeof(float));
     293           1 :         if (pafRowMinZ == nullptr)
     294             :         {
     295           0 :             return CE_Failure;
     296             :         }
     297             : 
     298           1 :         pafRowMaxZ = (float *)VSI_MALLOC2_VERBOSE(nRasterYSize, sizeof(float));
     299           1 :         if (pafRowMaxZ == nullptr)
     300             :         {
     301           0 :             VSIFree(pafRowMinZ);
     302           0 :             pafRowMinZ = nullptr;
     303           0 :             return CE_Failure;
     304             :         }
     305             : 
     306           1 :         CPLErr eErr = ScanForMinMaxZ();
     307           1 :         if (eErr != CE_None)
     308           0 :             return eErr;
     309             :     }
     310             : 
     311          40 :     if (VSIFSeekL(poGDS->fp,
     312          20 :                   GSBGDataset::nHEADER_SIZE +
     313          20 :                       static_cast<vsi_l_offset>(4) * nRasterXSize *
     314          20 :                           (nRasterYSize - nBlockYOff - 1),
     315          20 :                   SEEK_SET) != 0)
     316             :     {
     317           0 :         CPLError(CE_Failure, CPLE_FileIO,
     318             :                  "Unable to seek to beginning of grid row.");
     319           0 :         return CE_Failure;
     320             :     }
     321             : 
     322          20 :     float *pfImage = (float *)pImage;
     323          20 :     pafRowMinZ[nBlockYOff] = std::numeric_limits<float>::max();
     324          20 :     pafRowMaxZ[nBlockYOff] = std::numeric_limits<float>::lowest();
     325         420 :     for (int iPixel = 0; iPixel < nBlockXSize; iPixel++)
     326             :     {
     327         400 :         if (pfImage[iPixel] != GSBGDataset::fNODATA_VALUE)
     328             :         {
     329         400 :             if (pfImage[iPixel] < pafRowMinZ[nBlockYOff])
     330          78 :                 pafRowMinZ[nBlockYOff] = pfImage[iPixel];
     331             : 
     332         400 :             if (pfImage[iPixel] > pafRowMaxZ[nBlockYOff])
     333          43 :                 pafRowMaxZ[nBlockYOff] = pfImage[iPixel];
     334             :         }
     335             : 
     336         400 :         CPL_LSBPTR32(pfImage + iPixel);
     337             :     }
     338             : 
     339          20 :     if (VSIFWriteL(pImage, sizeof(float), nBlockXSize, poGDS->fp) !=
     340          20 :         static_cast<unsigned>(nBlockXSize))
     341             :     {
     342           0 :         CPLError(CE_Failure, CPLE_FileIO,
     343             :                  "Unable to write block to grid file.");
     344           0 :         return CE_Failure;
     345             :     }
     346             : 
     347             :     /* Update min/max Z values as appropriate */
     348          20 :     bool bHeaderNeedsUpdate = false;
     349          20 :     if (nMinZRow == nBlockYOff && pafRowMinZ[nBlockYOff] > dfMinZ)
     350             :     {
     351           1 :         double dfNewMinZ = std::numeric_limits<double>::max();
     352          21 :         for (int iRow = 0; iRow < nRasterYSize; iRow++)
     353             :         {
     354          20 :             if (pafRowMinZ[iRow] < dfNewMinZ)
     355             :             {
     356           1 :                 dfNewMinZ = pafRowMinZ[iRow];
     357           1 :                 nMinZRow = iRow;
     358             :             }
     359             :         }
     360             : 
     361           1 :         if (dfNewMinZ != dfMinZ)
     362             :         {
     363           1 :             dfMinZ = dfNewMinZ;
     364           1 :             bHeaderNeedsUpdate = true;
     365             :         }
     366             :     }
     367             : 
     368          20 :     if (nMaxZRow == nBlockYOff && pafRowMaxZ[nBlockYOff] < dfMaxZ)
     369             :     {
     370           0 :         double dfNewMaxZ = std::numeric_limits<double>::lowest();
     371           0 :         for (int iRow = 0; iRow < nRasterYSize; iRow++)
     372             :         {
     373           0 :             if (pafRowMaxZ[iRow] > dfNewMaxZ)
     374             :             {
     375           0 :                 dfNewMaxZ = pafRowMaxZ[iRow];
     376           0 :                 nMaxZRow = iRow;
     377             :             }
     378             :         }
     379             : 
     380           0 :         if (dfNewMaxZ != dfMaxZ)
     381             :         {
     382           0 :             dfMaxZ = dfNewMaxZ;
     383           0 :             bHeaderNeedsUpdate = true;
     384             :         }
     385             :     }
     386             : 
     387          20 :     if (pafRowMinZ[nBlockYOff] < dfMinZ || pafRowMaxZ[nBlockYOff] > dfMaxZ)
     388             :     {
     389           6 :         if (pafRowMinZ[nBlockYOff] < dfMinZ)
     390             :         {
     391           3 :             dfMinZ = pafRowMinZ[nBlockYOff];
     392           3 :             nMinZRow = nBlockYOff;
     393             :         }
     394             : 
     395           6 :         if (pafRowMaxZ[nBlockYOff] > dfMaxZ)
     396             :         {
     397           4 :             dfMaxZ = pafRowMaxZ[nBlockYOff];
     398           4 :             nMaxZRow = nBlockYOff;
     399             :         }
     400             : 
     401           6 :         bHeaderNeedsUpdate = true;
     402             :     }
     403             : 
     404          20 :     if (bHeaderNeedsUpdate && dfMaxZ > dfMinZ)
     405             :     {
     406             :         CPLErr eErr =
     407           6 :             poGDS->WriteHeader(poGDS->fp, nRasterXSize, nRasterYSize, dfMinX,
     408             :                                dfMaxX, dfMinY, dfMaxY, dfMinZ, dfMaxZ);
     409           6 :         return eErr;
     410             :     }
     411             : 
     412          14 :     return CE_None;
     413             : }
     414             : 
     415             : /************************************************************************/
     416             : /*                           GetNoDataValue()                           */
     417             : /************************************************************************/
     418             : 
     419          27 : double GSBGRasterBand::GetNoDataValue(int *pbSuccess)
     420             : {
     421          27 :     if (pbSuccess)
     422          21 :         *pbSuccess = TRUE;
     423             : 
     424          27 :     return GSBGDataset::fNODATA_VALUE;
     425             : }
     426             : 
     427             : /************************************************************************/
     428             : /*                             GetMinimum()                             */
     429             : /************************************************************************/
     430             : 
     431           0 : double GSBGRasterBand::GetMinimum(int *pbSuccess)
     432             : {
     433           0 :     if (pbSuccess)
     434           0 :         *pbSuccess = TRUE;
     435             : 
     436           0 :     return dfMinZ;
     437             : }
     438             : 
     439             : /************************************************************************/
     440             : /*                             GetMaximum()                             */
     441             : /************************************************************************/
     442             : 
     443           0 : double GSBGRasterBand::GetMaximum(int *pbSuccess)
     444             : {
     445           0 :     if (pbSuccess)
     446           0 :         *pbSuccess = TRUE;
     447             : 
     448           0 :     return dfMaxZ;
     449             : }
     450             : 
     451             : /************************************************************************/
     452             : /* ==================================================================== */
     453             : /*                              GSBGDataset                             */
     454             : /* ==================================================================== */
     455             : /************************************************************************/
     456             : 
     457          90 : GSBGDataset::~GSBGDataset()
     458             : 
     459             : {
     460          45 :     FlushCache(true);
     461          45 :     if (fp != nullptr)
     462          45 :         VSIFCloseL(fp);
     463          90 : }
     464             : 
     465             : /************************************************************************/
     466             : /*                              Identify()                              */
     467             : /************************************************************************/
     468             : 
     469       64201 : int GSBGDataset::Identify(GDALOpenInfo *poOpenInfo)
     470             : 
     471             : {
     472             :     /* Check for signature */
     473       64201 :     if (poOpenInfo->nHeaderBytes < 4 ||
     474        6250 :         !STARTS_WITH_CI((const char *)poOpenInfo->pabyHeader, "DSBB"))
     475             :     {
     476       64136 :         return FALSE;
     477             :     }
     478             : 
     479          65 :     return TRUE;
     480             : }
     481             : 
     482             : /************************************************************************/
     483             : /*                                Open()                                */
     484             : /************************************************************************/
     485             : 
     486          33 : GDALDataset *GSBGDataset::Open(GDALOpenInfo *poOpenInfo)
     487             : {
     488          33 :     return OpenPAM(poOpenInfo);
     489             : }
     490             : 
     491          45 : GDALPamDataset *GSBGDataset::OpenPAM(GDALOpenInfo *poOpenInfo)
     492             : 
     493             : {
     494          45 :     if (!Identify(poOpenInfo) || poOpenInfo->fpL == nullptr)
     495             :     {
     496           0 :         return nullptr;
     497             :     }
     498             : 
     499             :     /* -------------------------------------------------------------------- */
     500             :     /*      Create a corresponding GDALDataset.                             */
     501             :     /* -------------------------------------------------------------------- */
     502          90 :     auto poDS = std::make_unique<GSBGDataset>();
     503             : 
     504          45 :     poDS->eAccess = poOpenInfo->eAccess;
     505          45 :     poDS->fp = poOpenInfo->fpL;
     506          45 :     poOpenInfo->fpL = nullptr;
     507             : 
     508             :     /* -------------------------------------------------------------------- */
     509             :     /*      Read the header.                                                */
     510             :     /* -------------------------------------------------------------------- */
     511          45 :     if (VSIFSeekL(poDS->fp, 4, SEEK_SET) != 0)
     512             :     {
     513           0 :         CPLError(CE_Failure, CPLE_FileIO,
     514             :                  "Unable to seek to start of grid file header.");
     515           0 :         return nullptr;
     516             :     }
     517             : 
     518             :     /* Parse number of X axis grid rows */
     519             :     GInt16 nTemp;
     520          45 :     if (VSIFReadL((void *)&nTemp, 2, 1, poDS->fp) != 1)
     521             :     {
     522           0 :         CPLError(CE_Failure, CPLE_FileIO, "Unable to read raster X size.");
     523           0 :         return nullptr;
     524             :     }
     525          45 :     poDS->nRasterXSize = CPL_LSBWORD16(nTemp);
     526             : 
     527          45 :     if (VSIFReadL((void *)&nTemp, 2, 1, poDS->fp) != 1)
     528             :     {
     529           0 :         CPLError(CE_Failure, CPLE_FileIO, "Unable to read raster Y size.");
     530           0 :         return nullptr;
     531             :     }
     532          45 :     poDS->nRasterYSize = CPL_LSBWORD16(nTemp);
     533             : 
     534          45 :     if (!GDALCheckDatasetDimensions(poDS->nRasterXSize, poDS->nRasterYSize))
     535             :     {
     536           0 :         return nullptr;
     537             :     }
     538             : 
     539             :     /* -------------------------------------------------------------------- */
     540             :     /*      Create band information objects.                                */
     541             :     /* -------------------------------------------------------------------- */
     542          45 :     GSBGRasterBand *poBand = new GSBGRasterBand(poDS.get(), 1);
     543          45 :     poDS->SetBand(1, poBand);
     544             : 
     545             :     double dfTemp;
     546          45 :     if (VSIFReadL((void *)&dfTemp, 8, 1, poDS->fp) != 1)
     547             :     {
     548           0 :         CPLError(CE_Failure, CPLE_FileIO, "Unable to read minimum X value.");
     549           0 :         return nullptr;
     550             :     }
     551          45 :     CPL_LSBPTR64(&dfTemp);
     552          45 :     poBand->dfMinX = dfTemp;
     553             : 
     554          45 :     if (VSIFReadL((void *)&dfTemp, 8, 1, poDS->fp) != 1)
     555             :     {
     556           0 :         CPLError(CE_Failure, CPLE_FileIO, "Unable to read maximum X value.");
     557           0 :         return nullptr;
     558             :     }
     559          45 :     CPL_LSBPTR64(&dfTemp);
     560          45 :     poBand->dfMaxX = dfTemp;
     561             : 
     562          45 :     if (VSIFReadL((void *)&dfTemp, 8, 1, poDS->fp) != 1)
     563             :     {
     564           0 :         CPLError(CE_Failure, CPLE_FileIO, "Unable to read minimum Y value.");
     565           0 :         return nullptr;
     566             :     }
     567          45 :     CPL_LSBPTR64(&dfTemp);
     568          45 :     poBand->dfMinY = dfTemp;
     569             : 
     570          45 :     if (VSIFReadL((void *)&dfTemp, 8, 1, poDS->fp) != 1)
     571             :     {
     572           0 :         CPLError(CE_Failure, CPLE_FileIO, "Unable to read maximum Y value.");
     573           0 :         return nullptr;
     574             :     }
     575          45 :     CPL_LSBPTR64(&dfTemp);
     576          45 :     poBand->dfMaxY = dfTemp;
     577             : 
     578          45 :     if (VSIFReadL((void *)&dfTemp, 8, 1, poDS->fp) != 1)
     579             :     {
     580           0 :         CPLError(CE_Failure, CPLE_FileIO, "Unable to read minimum Z value.");
     581           0 :         return nullptr;
     582             :     }
     583          45 :     CPL_LSBPTR64(&dfTemp);
     584          45 :     poBand->dfMinZ = dfTemp;
     585             : 
     586          45 :     if (VSIFReadL((void *)&dfTemp, 8, 1, poDS->fp) != 1)
     587             :     {
     588           0 :         CPLError(CE_Failure, CPLE_FileIO, "Unable to read maximum Z value.");
     589           0 :         return nullptr;
     590             :     }
     591          45 :     CPL_LSBPTR64(&dfTemp);
     592          45 :     poBand->dfMaxZ = dfTemp;
     593             : 
     594             :     /* -------------------------------------------------------------------- */
     595             :     /*      Initialize any PAM information.                                 */
     596             :     /* -------------------------------------------------------------------- */
     597          45 :     poDS->SetDescription(poOpenInfo->pszFilename);
     598          45 :     poDS->TryLoadXML();
     599             : 
     600             :     /* -------------------------------------------------------------------- */
     601             :     /*      Check for external overviews.                                   */
     602             :     /* -------------------------------------------------------------------- */
     603          90 :     poDS->oOvManager.Initialize(poDS.get(), poOpenInfo->pszFilename,
     604          45 :                                 poOpenInfo->GetSiblingFiles());
     605             : 
     606          45 :     return poDS.release();
     607             : }
     608             : 
     609             : /************************************************************************/
     610             : /*                          GetGeoTransform()                           */
     611             : /************************************************************************/
     612             : 
     613          29 : CPLErr GSBGDataset::GetGeoTransform(GDALGeoTransform &gt) const
     614             : {
     615             :     const GSBGRasterBand *poGRB =
     616          29 :         cpl::down_cast<const GSBGRasterBand *>(GetRasterBand(1));
     617             : 
     618             :     /* check if we have a PAM GeoTransform stored */
     619          29 :     CPLPushErrorHandler(CPLQuietErrorHandler);
     620          29 :     CPLErr eErr = GDALPamDataset::GetGeoTransform(gt);
     621          29 :     CPLPopErrorHandler();
     622             : 
     623          29 :     if (eErr == CE_None)
     624           0 :         return CE_None;
     625             : 
     626          29 :     if (nRasterXSize == 1 || nRasterYSize == 1)
     627           0 :         return CE_Failure;
     628             : 
     629             :     /* calculate pixel size first */
     630          29 :     gt.xscale = (poGRB->dfMaxX - poGRB->dfMinX) / (nRasterXSize - 1);
     631          29 :     gt.yscale = (poGRB->dfMinY - poGRB->dfMaxY) / (nRasterYSize - 1);
     632             : 
     633             :     /* then calculate image origin */
     634          29 :     gt.xorig = poGRB->dfMinX - gt.xscale / 2;
     635          29 :     gt.yorig = poGRB->dfMaxY - gt.yscale / 2;
     636             : 
     637             :     /* tilt/rotation does not supported by the GS grids */
     638          29 :     gt.yrot = 0.0;
     639          29 :     gt.xrot = 0.0;
     640             : 
     641          29 :     return CE_None;
     642             : }
     643             : 
     644             : /************************************************************************/
     645             : /*                          SetGeoTransform()                           */
     646             : /************************************************************************/
     647             : 
     648          12 : CPLErr GSBGDataset::SetGeoTransform(const GDALGeoTransform &gt)
     649             : {
     650          12 :     if (eAccess == GA_ReadOnly)
     651             :     {
     652           0 :         CPLError(CE_Failure, CPLE_NoWriteAccess,
     653             :                  "Unable to set GeoTransform, dataset opened read only.");
     654           0 :         return CE_Failure;
     655             :     }
     656             : 
     657          12 :     GSBGRasterBand *poGRB = cpl::down_cast<GSBGRasterBand *>(GetRasterBand(1));
     658             : 
     659             :     /* non-zero transform 2 or 4 or negative 1 or 5 not supported natively */
     660             :     // CPLErr eErr = CE_None;
     661             :     /*if( gt.xrot != 0.0 || gt.yrot != 0.0
     662             :         || gt.xscale < 0.0 || gt.yscale < 0.0 )
     663             :         eErr = GDALPamDataset::SetGeoTransform( gt );
     664             : 
     665             :     if( eErr != CE_None )
     666             :         return eErr;*/
     667             : 
     668          12 :     double dfMinX = gt.xorig + gt.xscale / 2;
     669          12 :     double dfMaxX = gt.xscale * (nRasterXSize - 0.5) + gt.xorig;
     670          12 :     double dfMinY = gt.yscale * (nRasterYSize - 0.5) + gt.yorig;
     671          12 :     double dfMaxY = gt.yorig + gt.yscale / 2;
     672             : 
     673             :     CPLErr eErr =
     674          12 :         WriteHeader(fp, poGRB->nRasterXSize, poGRB->nRasterYSize, dfMinX,
     675             :                     dfMaxX, dfMinY, dfMaxY, poGRB->dfMinZ, poGRB->dfMaxZ);
     676             : 
     677          12 :     if (eErr == CE_None)
     678             :     {
     679          12 :         poGRB->dfMinX = dfMinX;
     680          12 :         poGRB->dfMaxX = dfMaxX;
     681          12 :         poGRB->dfMinY = dfMinY;
     682          12 :         poGRB->dfMaxY = dfMaxY;
     683             :     }
     684             : 
     685          12 :     return eErr;
     686             : }
     687             : 
     688             : /************************************************************************/
     689             : /*                            WriteHeader()                             */
     690             : /************************************************************************/
     691             : 
     692          65 : CPLErr GSBGDataset::WriteHeader(VSILFILE *fp, int nXSize, int nYSize,
     693             :                                 double dfMinX, double dfMaxX, double dfMinY,
     694             :                                 double dfMaxY, double dfMinZ, double dfMaxZ)
     695             : 
     696             : {
     697          65 :     if (VSIFSeekL(fp, 0, SEEK_SET) != 0)
     698             :     {
     699           0 :         CPLError(CE_Failure, CPLE_FileIO,
     700             :                  "Unable to seek to start of grid file.");
     701           0 :         return CE_Failure;
     702             :     }
     703             : 
     704          65 :     if (VSIFWriteL((void *)"DSBB", 1, 4, fp) != 4)
     705             :     {
     706           1 :         CPLError(CE_Failure, CPLE_FileIO,
     707             :                  "Unable to write signature to grid file.");
     708           1 :         return CE_Failure;
     709             :     }
     710             : 
     711          64 :     assert(nXSize >= 0 && nXSize <= std::numeric_limits<int16_t>::max());
     712          64 :     GInt16 nTemp = CPL_LSBWORD16(static_cast<int16_t>(nXSize));
     713          64 :     if (VSIFWriteL((void *)&nTemp, 2, 1, fp) != 1)
     714             :     {
     715           0 :         CPLError(CE_Failure, CPLE_FileIO,
     716             :                  "Unable to write raster X size to grid file.");
     717           0 :         return CE_Failure;
     718             :     }
     719             : 
     720          64 :     assert(nYSize >= 0 && nYSize <= std::numeric_limits<int16_t>::max());
     721          64 :     nTemp = CPL_LSBWORD16(static_cast<int16_t>(nYSize));
     722          64 :     if (VSIFWriteL((void *)&nTemp, 2, 1, fp) != 1)
     723             :     {
     724           0 :         CPLError(CE_Failure, CPLE_FileIO,
     725             :                  "Unable to write raster Y size to grid file.");
     726           0 :         return CE_Failure;
     727             :     }
     728             : 
     729          64 :     double dfTemp = dfMinX;
     730          64 :     CPL_LSBPTR64(&dfTemp);
     731          64 :     if (VSIFWriteL((void *)&dfTemp, 8, 1, fp) != 1)
     732             :     {
     733           0 :         CPLError(CE_Failure, CPLE_FileIO,
     734             :                  "Unable to write minimum X value to grid file.");
     735           0 :         return CE_Failure;
     736             :     }
     737             : 
     738          64 :     dfTemp = dfMaxX;
     739          64 :     CPL_LSBPTR64(&dfTemp);
     740          64 :     if (VSIFWriteL((void *)&dfTemp, 8, 1, fp) != 1)
     741             :     {
     742           0 :         CPLError(CE_Failure, CPLE_FileIO,
     743             :                  "Unable to write maximum X value to grid file.");
     744           0 :         return CE_Failure;
     745             :     }
     746             : 
     747          64 :     dfTemp = dfMinY;
     748          64 :     CPL_LSBPTR64(&dfTemp);
     749          64 :     if (VSIFWriteL((void *)&dfTemp, 8, 1, fp) != 1)
     750             :     {
     751           0 :         CPLError(CE_Failure, CPLE_FileIO,
     752             :                  "Unable to write minimum Y value to grid file.");
     753           0 :         return CE_Failure;
     754             :     }
     755             : 
     756          64 :     dfTemp = dfMaxY;
     757          64 :     CPL_LSBPTR64(&dfTemp);
     758          64 :     if (VSIFWriteL((void *)&dfTemp, 8, 1, fp) != 1)
     759             :     {
     760           0 :         CPLError(CE_Failure, CPLE_FileIO,
     761             :                  "Unable to write maximum Y value to grid file.");
     762           0 :         return CE_Failure;
     763             :     }
     764             : 
     765          64 :     dfTemp = dfMinZ;
     766          64 :     CPL_LSBPTR64(&dfTemp);
     767          64 :     if (VSIFWriteL((void *)&dfTemp, 8, 1, fp) != 1)
     768             :     {
     769           1 :         CPLError(CE_Failure, CPLE_FileIO,
     770             :                  "Unable to write minimum Z value to grid file.");
     771           1 :         return CE_Failure;
     772             :     }
     773             : 
     774          63 :     dfTemp = dfMaxZ;
     775          63 :     CPL_LSBPTR64(&dfTemp);
     776          63 :     if (VSIFWriteL((void *)&dfTemp, 8, 1, fp) != 1)
     777             :     {
     778           0 :         CPLError(CE_Failure, CPLE_FileIO,
     779             :                  "Unable to write maximum Z value to grid file.");
     780           0 :         return CE_Failure;
     781             :     }
     782             : 
     783          63 :     return CE_None;
     784             : }
     785             : 
     786             : /************************************************************************/
     787             : /*                        GSBGCreateCheckDims()                         */
     788             : /************************************************************************/
     789             : 
     790          66 : static bool GSBGCreateCheckDims(int nXSize, int nYSize)
     791             : {
     792          66 :     if (nXSize <= 1 || nYSize <= 1)
     793             :     {
     794           4 :         CPLError(CE_Failure, CPLE_IllegalArg,
     795             :                  "Unable to create grid, both X and Y size must be "
     796             :                  "larger or equal to 2.");
     797           4 :         return false;
     798             :     }
     799         122 :     if (nXSize > std::numeric_limits<short>::max() ||
     800          60 :         nYSize > std::numeric_limits<short>::max())
     801             :     {
     802           4 :         CPLError(CE_Failure, CPLE_IllegalArg,
     803             :                  "Unable to create grid, Golden Software Binary Grid format "
     804             :                  "only supports sizes up to %dx%d.  %dx%d not supported.",
     805           4 :                  std::numeric_limits<short>::max(),
     806           4 :                  std::numeric_limits<short>::max(), nXSize, nYSize);
     807           4 :         return false;
     808             :     }
     809          58 :     return true;
     810             : }
     811             : 
     812             : /************************************************************************/
     813             : /*                               Create()                               */
     814             : /************************************************************************/
     815             : 
     816          37 : GDALDataset *GSBGDataset::Create(const char *pszFilename, int nXSize,
     817             :                                  int nYSize, int /* nBands */,
     818             :                                  GDALDataType eType, CSLConstList)
     819             : {
     820          37 :     if (!GSBGCreateCheckDims(nXSize, nYSize))
     821             :     {
     822           4 :         return nullptr;
     823             :     }
     824             : 
     825          33 :     if (eType != GDT_UInt8 && eType != GDT_Float32 && eType != GDT_UInt16 &&
     826             :         eType != GDT_Int16)
     827             :     {
     828          20 :         CPLError(CE_Failure, CPLE_AppDefined,
     829             :                  "Golden Software Binary Grid only supports Byte, Int16, "
     830             :                  "Uint16, and Float32 datatypes.  Unable to create with "
     831             :                  "type %s.\n",
     832             :                  GDALGetDataTypeName(eType));
     833             : 
     834          20 :         return nullptr;
     835             :     }
     836             : 
     837          13 :     VSILFILE *fp = VSIFOpenL(pszFilename, "w+b");
     838             : 
     839          13 :     if (fp == nullptr)
     840             :     {
     841           0 :         CPLError(CE_Failure, CPLE_OpenFailed,
     842             :                  "Attempt to create file '%s' failed.\n", pszFilename);
     843           0 :         return nullptr;
     844             :     }
     845             : 
     846             :     CPLErr eErr =
     847          13 :         WriteHeader(fp, nXSize, nYSize, 0.0, nXSize, 0.0, nYSize, 0.0, 0.0);
     848          13 :     if (eErr != CE_None)
     849             :     {
     850           0 :         VSIFCloseL(fp);
     851           0 :         return nullptr;
     852             :     }
     853             : 
     854          13 :     float fVal = fNODATA_VALUE;
     855          13 :     CPL_LSBPTR32(&fVal);
     856        1233 :     for (int iRow = 0; iRow < nYSize; iRow++)
     857             :     {
     858      121620 :         for (int iCol = 0; iCol < nXSize; iCol++)
     859             :         {
     860      120400 :             if (VSIFWriteL((void *)&fVal, 4, 1, fp) != 1)
     861             :             {
     862           0 :                 VSIFCloseL(fp);
     863           0 :                 CPLError(CE_Failure, CPLE_FileIO,
     864             :                          "Unable to write grid cell.  Disk full?");
     865           0 :                 return nullptr;
     866             :             }
     867             :         }
     868             :     }
     869             : 
     870          13 :     VSIFCloseL(fp);
     871             : 
     872          26 :     GDALOpenInfo oOpenInfo(pszFilename, GA_Update);
     873          13 :     return Open(&oOpenInfo);
     874             : }
     875             : 
     876             : /************************************************************************/
     877             : /*                             CreateCopy()                             */
     878             : /************************************************************************/
     879             : 
     880          34 : GDALDataset *GSBGDataset::CreateCopy(const char *pszFilename,
     881             :                                      GDALDataset *poSrcDS, int bStrict,
     882             :                                      CPL_UNUSED CSLConstList papszOptions,
     883             :                                      GDALProgressFunc pfnProgress,
     884             :                                      void *pProgressData)
     885             : {
     886          34 :     if (pfnProgress == nullptr)
     887           0 :         pfnProgress = GDALDummyProgress;
     888             : 
     889          34 :     int nBands = poSrcDS->GetRasterCount();
     890          34 :     if (nBands == 0)
     891             :     {
     892           1 :         CPLError(
     893             :             CE_Failure, CPLE_NotSupported,
     894             :             "GSBG driver does not support source datasets with zero bands.");
     895           1 :         return nullptr;
     896             :     }
     897          33 :     else if (nBands > 1)
     898             :     {
     899           4 :         if (bStrict)
     900             :         {
     901           4 :             CPLError(CE_Failure, CPLE_NotSupported,
     902             :                      "Unable to create copy, Golden Software Binary Grid "
     903             :                      "format only supports one raster band.");
     904           4 :             return nullptr;
     905             :         }
     906             :         else
     907           0 :             CPLError(CE_Warning, CPLE_NotSupported,
     908             :                      "Golden Software Binary Grid format only supports one "
     909             :                      "raster band, first band will be copied.");
     910             :     }
     911             : 
     912          29 :     GDALRasterBand *poSrcBand = poSrcDS->GetRasterBand(1);
     913          29 :     if (!GSBGCreateCheckDims(poSrcBand->GetXSize(), poSrcBand->GetYSize()))
     914             :     {
     915           4 :         return nullptr;
     916             :     }
     917             : 
     918          25 :     if (!pfnProgress(0.0, nullptr, pProgressData))
     919             :     {
     920           0 :         CPLError(CE_Failure, CPLE_UserInterrupt, "User terminated");
     921           0 :         return nullptr;
     922             :     }
     923             : 
     924          25 :     VSILFILE *fp = VSIFOpenL(pszFilename, "w+b");
     925             : 
     926          25 :     if (fp == nullptr)
     927             :     {
     928           3 :         CPLError(CE_Failure, CPLE_OpenFailed,
     929             :                  "Attempt to create file '%s' failed.\n", pszFilename);
     930           3 :         return nullptr;
     931             :     }
     932             : 
     933          22 :     const int nXSize = poSrcBand->GetXSize();
     934          22 :     const int nYSize = poSrcBand->GetYSize();
     935          22 :     GDALGeoTransform gt;
     936          22 :     poSrcDS->GetGeoTransform(gt);
     937             : 
     938          22 :     double dfMinX = gt.xorig + gt.xscale / 2;
     939          22 :     double dfMaxX = gt.xscale * (nXSize - 0.5) + gt.xorig;
     940          22 :     double dfMinY = gt.yscale * (nYSize - 0.5) + gt.yorig;
     941          22 :     double dfMaxY = gt.yorig + gt.yscale / 2;
     942          22 :     CPLErr eErr = WriteHeader(fp, nXSize, nYSize, dfMinX, dfMaxX, dfMinY,
     943             :                               dfMaxY, 0.0, 0.0);
     944             : 
     945          22 :     if (eErr != CE_None)
     946             :     {
     947           2 :         VSIFCloseL(fp);
     948           2 :         return nullptr;
     949             :     }
     950             : 
     951             :     /* -------------------------------------------------------------------- */
     952             :     /*      Copy band data.                                                 */
     953             :     /* -------------------------------------------------------------------- */
     954          20 :     float *pfData = (float *)VSI_MALLOC2_VERBOSE(nXSize, sizeof(float));
     955          20 :     if (pfData == nullptr)
     956             :     {
     957           0 :         VSIFCloseL(fp);
     958           0 :         return nullptr;
     959             :     }
     960             : 
     961             :     int bSrcHasNDValue;
     962          20 :     float fSrcNoDataValue = (float)poSrcBand->GetNoDataValue(&bSrcHasNDValue);
     963          20 :     double dfMinZ = std::numeric_limits<double>::max();
     964          20 :     double dfMaxZ = std::numeric_limits<double>::lowest();
     965         185 :     for (int iRow = nYSize - 1; iRow >= 0; iRow--)
     966             :     {
     967         173 :         eErr = poSrcBand->RasterIO(GF_Read, 0, iRow, nXSize, 1, pfData, nXSize,
     968             :                                    1, GDT_Float32, 0, 0, nullptr);
     969             : 
     970         173 :         if (eErr != CE_None)
     971             :         {
     972           0 :             VSIFCloseL(fp);
     973           0 :             VSIFree(pfData);
     974           0 :             return nullptr;
     975             :         }
     976             : 
     977        2103 :         for (int iCol = 0; iCol < nXSize; iCol++)
     978             :         {
     979        1930 :             if (bSrcHasNDValue && pfData[iCol] == fSrcNoDataValue)
     980             :             {
     981           0 :                 pfData[iCol] = fNODATA_VALUE;
     982             :             }
     983             :             else
     984             :             {
     985        1930 :                 if (pfData[iCol] > dfMaxZ)
     986          22 :                     dfMaxZ = pfData[iCol];
     987             : 
     988        1930 :                 if (pfData[iCol] < dfMinZ)
     989          27 :                     dfMinZ = pfData[iCol];
     990             :             }
     991             : 
     992        1930 :             CPL_LSBPTR32(pfData + iCol);
     993             :         }
     994             : 
     995         173 :         if (VSIFWriteL((void *)pfData, 4, nXSize, fp) !=
     996         173 :             static_cast<unsigned>(nXSize))
     997             :         {
     998           8 :             VSIFCloseL(fp);
     999           8 :             VSIFree(pfData);
    1000           8 :             CPLError(CE_Failure, CPLE_FileIO,
    1001             :                      "Unable to write grid row. Disk full?");
    1002           8 :             return nullptr;
    1003             :         }
    1004             : 
    1005         165 :         if (!pfnProgress(static_cast<double>(nYSize - iRow) / nYSize, nullptr,
    1006             :                          pProgressData))
    1007             :         {
    1008           0 :             VSIFCloseL(fp);
    1009           0 :             VSIFree(pfData);
    1010           0 :             CPLError(CE_Failure, CPLE_UserInterrupt, "User terminated");
    1011           0 :             return nullptr;
    1012             :         }
    1013             :     }
    1014             : 
    1015          12 :     VSIFree(pfData);
    1016             : 
    1017             :     /* write out the min and max values */
    1018          12 :     eErr = WriteHeader(fp, nXSize, nYSize, dfMinX, dfMaxX, dfMinY, dfMaxY,
    1019             :                        dfMinZ, dfMaxZ);
    1020             : 
    1021          12 :     if (eErr != CE_None)
    1022             :     {
    1023           0 :         VSIFCloseL(fp);
    1024           0 :         return nullptr;
    1025             :     }
    1026             : 
    1027          12 :     VSIFCloseL(fp);
    1028             : 
    1029          12 :     GDALOpenInfo oOpenInfo(pszFilename, GA_Update);
    1030          12 :     GDALPamDataset *poDS = OpenPAM(&oOpenInfo);
    1031          12 :     if (poDS)
    1032             :     {
    1033          12 :         poDS->CloneInfo(poSrcDS, GCIF_PAM_DEFAULT);
    1034             :     }
    1035          12 :     return poDS;
    1036             : }
    1037             : 
    1038             : /************************************************************************/
    1039             : /*                         GDALRegister_GSBG()                          */
    1040             : /************************************************************************/
    1041             : 
    1042        2135 : void GDALRegister_GSBG()
    1043             : 
    1044             : {
    1045        2135 :     if (GDALGetDriverByName("GSBG") != nullptr)
    1046         263 :         return;
    1047             : 
    1048        1872 :     GDALDriver *poDriver = new GDALDriver();
    1049             : 
    1050        1872 :     poDriver->SetDescription("GSBG");
    1051        1872 :     poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
    1052        1872 :     poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
    1053        1872 :                               "Golden Software Binary Grid (.grd)");
    1054        1872 :     poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/gsbg.html");
    1055        1872 :     poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "grd");
    1056        1872 :     poDriver->SetMetadataItem(GDAL_DMD_CREATIONDATATYPES,
    1057        1872 :                               "Byte Int16 UInt16 Float32");
    1058        1872 :     poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
    1059             : 
    1060        1872 :     poDriver->pfnIdentify = GSBGDataset::Identify;
    1061        1872 :     poDriver->pfnOpen = GSBGDataset::Open;
    1062        1872 :     poDriver->pfnCreate = GSBGDataset::Create;
    1063        1872 :     poDriver->pfnCreateCopy = GSBGDataset::CreateCopy;
    1064             : 
    1065        1872 :     GetGDALDriverManager()->RegisterDriver(poDriver);
    1066             : }

Generated by: LCOV version 1.14