LCOV - code coverage report
Current view: top level - frmts/gsg - gsbgdataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 320 446 71.7 %
Date: 2024-11-21 22:18:42 Functions: 18 20 90.0 %

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

Generated by: LCOV version 1.14