LCOV - code coverage report
Current view: top level - frmts/gsg - gsagdataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 326 774 42.1 %
Date: 2026-06-19 21:24:00 Functions: 16 22 72.7 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GDAL
       4             :  * Purpose:  Implements the Golden Software ASCII 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             : #include <sstream>
      22             : 
      23             : #include "gdal_frmts.h"
      24             : #include "gdal_pam.h"
      25             : #include "gdal_driver.h"
      26             : #include "gdal_drivermanager.h"
      27             : #include "gdal_openinfo.h"
      28             : #include "gdal_cpp_functions.h"
      29             : 
      30             : /************************************************************************/
      31             : /* ==================================================================== */
      32             : /*                              GSAGDataset                             */
      33             : /* ==================================================================== */
      34             : /************************************************************************/
      35             : 
      36             : class GSAGRasterBand;
      37             : 
      38             : class GSAGDataset final : public GDALPamDataset
      39             : {
      40             :     friend class GSAGRasterBand;
      41             : 
      42             :     static const double dfNODATA_VALUE;
      43             :     static const int nFIELD_PRECISION;
      44             :     static const size_t nMAX_HEADER_SIZE;
      45             : 
      46             :     static CPLErr ShiftFileContents(VSILFILE *, vsi_l_offset, int,
      47             :                                     const char *);
      48             : 
      49             :     VSILFILE *fp;
      50             :     size_t nMinMaxZOffset;
      51             :     char szEOL[3];
      52             : 
      53             :     CPLErr UpdateHeader();
      54             : 
      55             :   public:
      56             :     explicit GSAGDataset(const char *pszEOL = "\x0D\x0A");
      57             :     ~GSAGDataset() override;
      58             : 
      59             :     static int Identify(GDALOpenInfo *);
      60             :     static GDALDataset *Open(GDALOpenInfo *);
      61             :     static GDALPamDataset *OpenPAM(GDALOpenInfo *);
      62             :     static GDALDataset *CreateCopy(const char *pszFilename,
      63             :                                    GDALDataset *poSrcDS, int bStrict,
      64             :                                    CSLConstList papszOptions,
      65             :                                    GDALProgressFunc pfnProgress,
      66             :                                    void *pProgressData);
      67             : 
      68             :     CPLErr GetGeoTransform(GDALGeoTransform &gt) const override;
      69             :     CPLErr SetGeoTransform(const GDALGeoTransform &gt) override;
      70             : };
      71             : 
      72             : /* NOTE:  This is not mentioned in the spec, but Surfer 8 uses this value */
      73             : const double GSAGDataset::dfNODATA_VALUE = 1.70141E+38;
      74             : 
      75             : const int GSAGDataset::nFIELD_PRECISION = 14;
      76             : const size_t GSAGDataset::nMAX_HEADER_SIZE = 200;
      77             : 
      78             : /************************************************************************/
      79             : /* ==================================================================== */
      80             : /*                            GSAGRasterBand                            */
      81             : /* ==================================================================== */
      82             : /************************************************************************/
      83             : 
      84             : class GSAGRasterBand final : public GDALPamRasterBand
      85             : {
      86             :     friend class GSAGDataset;
      87             : 
      88             :     double dfMinX;
      89             :     double dfMaxX;
      90             :     double dfMinY;
      91             :     double dfMaxY;
      92             :     double dfMinZ;
      93             :     double dfMaxZ;
      94             : 
      95             :     vsi_l_offset *panLineOffset;
      96             :     int nLastReadLine;
      97             :     size_t nMaxLineSize;
      98             : 
      99             :     double *padfRowMinZ;
     100             :     double *padfRowMaxZ;
     101             :     int nMinZRow;
     102             :     int nMaxZRow;
     103             : 
     104             :     CPLErr ScanForMinMaxZ();
     105             : 
     106             :   public:
     107             :     GSAGRasterBand(GSAGDataset *, int, vsi_l_offset);
     108             :     ~GSAGRasterBand() override;
     109             : 
     110             :     CPLErr IReadBlock(int, int, void *) override;
     111             :     CPLErr IWriteBlock(int, int, void *) override;
     112             : 
     113             :     double GetNoDataValue(int *pbSuccess = nullptr) override;
     114             :     double GetMinimum(int *pbSuccess = nullptr) override;
     115             :     double GetMaximum(int *pbSuccess = nullptr) override;
     116             : };
     117             : 
     118             : /************************************************************************/
     119             : /*                            AlmostEqual()                             */
     120             : /* This function is needed because in release mode "1.70141E+38" is not */
     121             : /* parsed as 1.70141E+38 in the last bit of the mantissa.               */
     122             : /* See http://gcc.gnu.org/ml/gcc/2003-08/msg01195.html for some         */
     123             : /* explanation.                                                         */
     124             : /************************************************************************/
     125             : 
     126         400 : static bool AlmostEqual(double dfVal1, double dfVal2)
     127             : 
     128             : {
     129         400 :     const double dfTOLERANCE = 0.0000000001;
     130         400 :     if (dfVal1 == 0.0 || dfVal2 == 0.0)
     131           0 :         return fabs(dfVal1 - dfVal2) < dfTOLERANCE;
     132         400 :     return fabs((dfVal1 - dfVal2) / dfVal1) < dfTOLERANCE;
     133             : }
     134             : 
     135             : /************************************************************************/
     136             : /*                           GSAGRasterBand()                           */
     137             : /************************************************************************/
     138             : 
     139          17 : GSAGRasterBand::GSAGRasterBand(GSAGDataset *poDSIn, int nBandIn,
     140          17 :                                vsi_l_offset nDataStart)
     141             :     : dfMinX(0.0), dfMaxX(0.0), dfMinY(0.0), dfMaxY(0.0), dfMinZ(0.0),
     142          17 :       dfMaxZ(0.0), panLineOffset(nullptr), nLastReadLine(poDSIn->nRasterYSize),
     143             :       nMaxLineSize(128), padfRowMinZ(nullptr), padfRowMaxZ(nullptr),
     144          17 :       nMinZRow(-1), nMaxZRow(-1)
     145             : {
     146          17 :     poDS = poDSIn;
     147          17 :     nBand = nBandIn;
     148             : 
     149          17 :     eDataType = GDT_Float64;
     150             : 
     151          17 :     nBlockXSize = poDS->GetRasterXSize();
     152          17 :     nBlockYSize = 1;
     153             : 
     154          17 :     if (poDSIn->nRasterYSize > 1000000)
     155             :     {
     156             :         // Sanity check to avoid excessive memory allocations
     157           0 :         VSIFSeekL(poDSIn->fp, 0, SEEK_END);
     158           0 :         vsi_l_offset nFileSize = VSIFTellL(poDSIn->fp);
     159           0 :         if (static_cast<vsi_l_offset>(poDSIn->nRasterYSize) > nFileSize)
     160             :         {
     161           0 :             CPLError(CE_Failure, CPLE_FileIO, "Truncated file");
     162           0 :             return;
     163             :         }
     164             :     }
     165          17 :     panLineOffset = static_cast<vsi_l_offset *>(
     166          17 :         VSI_CALLOC_VERBOSE(poDSIn->nRasterYSize + 1, sizeof(vsi_l_offset)));
     167          17 :     if (panLineOffset == nullptr)
     168             :     {
     169           0 :         return;
     170             :     }
     171             : 
     172          17 :     panLineOffset[poDSIn->nRasterYSize - 1] = nDataStart;
     173             : }
     174             : 
     175             : /************************************************************************/
     176             : /*                          ~GSAGRasterBand()                           */
     177             : /************************************************************************/
     178             : 
     179          34 : GSAGRasterBand::~GSAGRasterBand()
     180             : {
     181          17 :     CPLFree(panLineOffset);
     182          17 :     CPLFree(padfRowMinZ);
     183          17 :     CPLFree(padfRowMaxZ);
     184          34 : }
     185             : 
     186             : /************************************************************************/
     187             : /*                           ScanForMinMaxZ()                           */
     188             : /************************************************************************/
     189             : 
     190           0 : CPLErr GSAGRasterBand::ScanForMinMaxZ()
     191             : 
     192             : {
     193             :     double *padfRowValues =
     194           0 :         (double *)VSI_MALLOC2_VERBOSE(nBlockXSize, sizeof(double));
     195           0 :     if (padfRowValues == nullptr)
     196             :     {
     197           0 :         return CE_Failure;
     198             :     }
     199             : 
     200           0 :     double dfNewMinZ = std::numeric_limits<double>::max();
     201           0 :     double dfNewMaxZ = std::numeric_limits<double>::lowest();
     202           0 :     int nNewMinZRow = 0;
     203           0 :     int nNewMaxZRow = 0;
     204             : 
     205             :     /* Since we have to scan, lets calc. statistics too */
     206           0 :     double dfSum = 0.0;
     207           0 :     double dfSum2 = 0.0;
     208           0 :     unsigned long nValuesRead = 0;
     209           0 :     for (int iRow = 0; iRow < nRasterYSize; iRow++)
     210             :     {
     211           0 :         CPLErr eErr = IReadBlock(0, iRow, padfRowValues);
     212           0 :         if (eErr != CE_None)
     213             :         {
     214           0 :             VSIFree(padfRowValues);
     215           0 :             return eErr;
     216             :         }
     217             : 
     218           0 :         padfRowMinZ[iRow] = std::numeric_limits<double>::max();
     219           0 :         padfRowMaxZ[iRow] = std::numeric_limits<double>::lowest();
     220           0 :         for (int iCell = 0; iCell < nRasterXSize; iCell++)
     221             :         {
     222           0 :             if (AlmostEqual(padfRowValues[iCell], GSAGDataset::dfNODATA_VALUE))
     223           0 :                 continue;
     224             : 
     225           0 :             if (padfRowValues[iCell] < padfRowMinZ[iRow])
     226           0 :                 padfRowMinZ[iRow] = padfRowValues[iCell];
     227             : 
     228           0 :             if (padfRowValues[iCell] > padfRowMaxZ[iRow])
     229           0 :                 padfRowMaxZ[iRow] = padfRowValues[iCell];
     230             : 
     231           0 :             dfSum += padfRowValues[iCell];
     232           0 :             dfSum2 += padfRowValues[iCell] * padfRowValues[iCell];
     233           0 :             nValuesRead++;
     234             :         }
     235             : 
     236           0 :         if (padfRowMinZ[iRow] < dfNewMinZ)
     237             :         {
     238           0 :             dfNewMinZ = padfRowMinZ[iRow];
     239           0 :             nNewMinZRow = iRow;
     240             :         }
     241             : 
     242           0 :         if (padfRowMaxZ[iRow] > dfNewMaxZ)
     243             :         {
     244           0 :             dfNewMaxZ = padfRowMaxZ[iRow];
     245           0 :             nNewMaxZRow = iRow;
     246             :         }
     247             :     }
     248             : 
     249           0 :     VSIFree(padfRowValues);
     250             : 
     251           0 :     if (nValuesRead == 0)
     252             :     {
     253           0 :         dfMinZ = 0.0;
     254           0 :         dfMaxZ = 0.0;
     255           0 :         nMinZRow = 0;
     256           0 :         nMaxZRow = 0;
     257           0 :         return CE_None;
     258             :     }
     259             : 
     260           0 :     dfMinZ = dfNewMinZ;
     261           0 :     dfMaxZ = dfNewMaxZ;
     262           0 :     nMinZRow = nNewMinZRow;
     263           0 :     nMaxZRow = nNewMaxZRow;
     264             : 
     265           0 :     double dfMean = dfSum / nValuesRead;
     266           0 :     double dfStdDev = sqrt((dfSum2 / nValuesRead) - (dfMean * dfMean));
     267           0 :     SetStatistics(dfMinZ, dfMaxZ, dfMean, dfStdDev);
     268             : 
     269           0 :     return CE_None;
     270             : }
     271             : 
     272             : /************************************************************************/
     273             : /*                             IReadBlock()                             */
     274             : /************************************************************************/
     275             : 
     276         156 : CPLErr GSAGRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff, void *pImage)
     277             : {
     278         156 :     GSAGDataset *poGDS = cpl::down_cast<GSAGDataset *>(poDS);
     279             : 
     280         156 :     double *pdfImage = static_cast<double *>(pImage);
     281             : 
     282         156 :     if (nBlockYOff < 0 || nBlockYOff > nRasterYSize - 1 || nBlockXOff != 0)
     283           0 :         return CE_Failure;
     284             : 
     285         156 :     if (panLineOffset[nBlockYOff] == 0)
     286             :     {
     287             :         // Discover the last read block
     288          80 :         for (int iFoundLine = nLastReadLine - 1; iFoundLine > nBlockYOff;
     289             :              iFoundLine--)
     290             :         {
     291          76 :             if (IReadBlock(nBlockXOff, iFoundLine, nullptr) != CE_None)
     292           0 :                 return CE_Failure;
     293             :         }
     294             :     }
     295             : 
     296         156 :     if (panLineOffset[nBlockYOff] == 0)
     297           0 :         return CE_Failure;
     298         156 :     if (VSIFSeekL(poGDS->fp, panLineOffset[nBlockYOff], SEEK_SET) != 0)
     299             :     {
     300           0 :         CPLError(CE_Failure, CPLE_FileIO,
     301             :                  "Can't seek to offset %ld to read grid row %d.",
     302           0 :                  (long)panLineOffset[nBlockYOff], nBlockYOff);
     303           0 :         return CE_Failure;
     304             :     }
     305             : 
     306         156 :     size_t nLineBufSize = nMaxLineSize;
     307             :     /* If we know the offsets, we can just read line directly */
     308         156 :     if ((nBlockYOff > 0) && (panLineOffset[nBlockYOff - 1] != 0))
     309             :     {
     310          76 :         assert(panLineOffset[nBlockYOff - 1] > panLineOffset[nBlockYOff]);
     311          76 :         nLineBufSize = (size_t)(panLineOffset[nBlockYOff - 1] -
     312          76 :                                 panLineOffset[nBlockYOff] + 1);
     313             :     }
     314             : 
     315         156 :     char *szLineBuf = (char *)VSI_MALLOC_VERBOSE(nLineBufSize);
     316         156 :     if (szLineBuf == nullptr)
     317             :     {
     318           0 :         return CE_Failure;
     319             :     }
     320             : 
     321         156 :     size_t nCharsRead = VSIFReadL(szLineBuf, 1, nLineBufSize - 1, poGDS->fp);
     322         156 :     if (nCharsRead == 0)
     323             :     {
     324           0 :         VSIFree(szLineBuf);
     325           0 :         CPLError(CE_Failure, CPLE_FileIO,
     326             :                  "Can't read grid row %d at offset %ld.\n", nBlockYOff,
     327           0 :                  (long)panLineOffset[nBlockYOff]);
     328           0 :         return CE_Failure;
     329             :     }
     330         156 :     szLineBuf[nCharsRead] = '\0';
     331             : 
     332         156 :     size_t nCharsExamined = 0;
     333         156 :     char *szStart = szLineBuf;
     334         156 :     char *szEnd = szStart;
     335        3276 :     for (int iCell = 0; iCell < nBlockXSize; szStart = szEnd)
     336             :     {
     337        6396 :         while (isspace((unsigned char)*szStart))
     338        3276 :             szStart++;
     339             : 
     340        3120 :         double dfValue = CPLStrtod(szStart, &szEnd);
     341        3120 :         if (szStart == szEnd)
     342             :         {
     343             :             /* No number found */
     344           0 :             if (*szStart == '.')
     345             :             {
     346           0 :                 CPLError(CE_Failure, CPLE_FileIO,
     347             :                          "Unexpected value in grid row %d (expected floating "
     348             :                          "point value, found \"%s\").\n",
     349             :                          nBlockYOff, szStart);
     350           0 :                 VSIFree(szLineBuf);
     351           0 :                 return CE_Failure;
     352             :             }
     353             : 
     354             :             /* Check if this was an expected failure */
     355             : 
     356             :             /* Found sign at end of input, seek back to re-read it */
     357           0 :             bool bOnlySign = false;
     358           0 :             if ((*szStart == '-' || *szStart == '+') &&
     359           0 :                 static_cast<size_t>(szStart + 1 - szLineBuf) == nCharsRead)
     360             :             {
     361           0 :                 if (VSIFSeekL(poGDS->fp, VSIFTellL(poGDS->fp) - 1, SEEK_SET) !=
     362             :                     0)
     363             :                 {
     364           0 :                     VSIFree(szLineBuf);
     365           0 :                     CPLError(CE_Failure, CPLE_FileIO,
     366             :                              "Unable to seek in grid row %d "
     367             :                              "(offset %ld, seek %d).\n",
     368           0 :                              nBlockYOff, (long)VSIFTellL(poGDS->fp), -1);
     369             : 
     370           0 :                     return CE_Failure;
     371             :                 }
     372           0 :                 bOnlySign = true;
     373             :             }
     374           0 :             else if (*szStart != '\0')
     375             :             {
     376             :                 // cppcheck-suppress redundantAssignment
     377           0 :                 szEnd = szStart;
     378           0 :                 while (!isspace((unsigned char)*szEnd) && *szEnd != '\0')
     379           0 :                     szEnd++;
     380           0 :                 char cOldEnd = *szEnd;
     381           0 :                 *szEnd = '\0';
     382             : 
     383           0 :                 CPLError(CE_Warning, CPLE_FileIO,
     384             :                          "Unexpected value in grid row %d (expected floating "
     385             :                          "point value, found \"%s\").\n",
     386             :                          nBlockYOff, szStart);
     387             : 
     388           0 :                 *szEnd = cOldEnd;
     389             : 
     390           0 :                 szEnd = szStart;
     391           0 :                 while (!isdigit(static_cast<unsigned char>(*szEnd)) &&
     392           0 :                        *szEnd != '.' && *szEnd != '\0')
     393           0 :                     szEnd++;
     394             : 
     395           0 :                 continue;
     396             :             }
     397           0 :             else if (static_cast<size_t>(szStart - szLineBuf) != nCharsRead)
     398             :             {
     399           0 :                 CPLError(CE_Warning, CPLE_FileIO,
     400             :                          "Unexpected ASCII null-character in grid row %d at "
     401             :                          "offset %ld.\n",
     402             :                          nBlockYOff, (long)(szStart - szLineBuf));
     403             : 
     404           0 :                 while (*szStart == '\0' &&
     405           0 :                        static_cast<size_t>(szStart - szLineBuf) < nCharsRead)
     406           0 :                     szStart++;
     407             : 
     408           0 :                 szEnd = szStart;
     409           0 :                 continue;
     410             :             }
     411             : 
     412           0 :             nCharsExamined += szStart - szLineBuf;
     413           0 :             nCharsRead = VSIFReadL(szLineBuf, 1, nLineBufSize - 1, poGDS->fp);
     414           0 :             if (nCharsRead == 0 || (bOnlySign && nCharsRead == 1))
     415             :             {
     416           0 :                 VSIFree(szLineBuf);
     417           0 :                 CPLError(CE_Failure, CPLE_FileIO,
     418             :                          "Can't read portion of grid row %d at offset %ld.",
     419           0 :                          nBlockYOff, (long)panLineOffset[nBlockYOff]);
     420           0 :                 return CE_Failure;
     421             :             }
     422           0 :             szLineBuf[nCharsRead] = '\0';
     423           0 :             szEnd = szLineBuf;
     424           0 :             continue;
     425             :         }
     426        3120 :         else if (*szEnd == '\0' || (*szEnd == '.' && *(szEnd + 1) == '\0') ||
     427        3120 :                  (*szEnd == '-' && *(szEnd + 1) == '\0') ||
     428        3120 :                  (*szEnd == '+' && *(szEnd + 1) == '\0') ||
     429        3120 :                  (*szEnd == 'E' && *(szEnd + 1) == '\0') ||
     430        3120 :                  (*szEnd == 'E' && *(szEnd + 1) == '-' &&
     431           0 :                   *(szEnd + 2) == '\0') ||
     432        3120 :                  (*szEnd == 'E' && *(szEnd + 1) == '+' &&
     433           0 :                   *(szEnd + 2) == '\0') ||
     434        3120 :                  (*szEnd == 'e' && *(szEnd + 1) == '\0') ||
     435        3120 :                  (*szEnd == 'e' && *(szEnd + 1) == '-' &&
     436           0 :                   *(szEnd + 2) == '\0') ||
     437        3120 :                  (*szEnd == 'e' && *(szEnd + 1) == '+' && *(szEnd + 2) == '\0'))
     438             :         {
     439             :             /* Number was interrupted by a nul character */
     440           0 :             while (*szEnd != '\0')
     441           0 :                 szEnd++;
     442             : 
     443           0 :             if (static_cast<size_t>(szEnd - szLineBuf) != nCharsRead)
     444             :             {
     445           0 :                 CPLError(CE_Warning, CPLE_FileIO,
     446             :                          "Unexpected ASCII null-character in grid row %d at "
     447             :                          "offset %ld.\n",
     448             :                          nBlockYOff, (long)(szEnd - szLineBuf));
     449             : 
     450           0 :                 while (*szEnd == '\0' &&
     451           0 :                        static_cast<size_t>(szEnd - szLineBuf) < nCharsRead)
     452           0 :                     szEnd++;
     453             : 
     454           0 :                 continue;
     455             :             }
     456             : 
     457             :             /* End of buffer, could be interrupting a number */
     458           0 :             if (VSIFSeekL(poGDS->fp,
     459           0 :                           static_cast<vsi_l_offset>(VSIFTellL(poGDS->fp) +
     460           0 :                                                     szStart - szEnd),
     461           0 :                           SEEK_SET) != 0)
     462             :             {
     463           0 :                 VSIFree(szLineBuf);
     464           0 :                 CPLError(CE_Failure, CPLE_FileIO,
     465             :                          "Unable to seek in grid row %d (offset %ld, seek %d)"
     466             :                          ".\n",
     467           0 :                          nBlockYOff, (long)VSIFTellL(poGDS->fp),
     468           0 :                          (int)(szStart - szEnd));
     469             : 
     470           0 :                 return CE_Failure;
     471             :             }
     472           0 :             nCharsExamined += szStart - szLineBuf;
     473           0 :             nCharsRead = VSIFReadL(szLineBuf, 1, nLineBufSize - 1, poGDS->fp);
     474           0 :             szLineBuf[nCharsRead] = '\0';
     475             : 
     476           0 :             if (nCharsRead == 0)
     477             :             {
     478           0 :                 VSIFree(szLineBuf);
     479           0 :                 CPLError(CE_Failure, CPLE_FileIO,
     480             :                          "Can't read portion of grid row %d at offset %ld.",
     481           0 :                          nBlockYOff, (long)panLineOffset[nBlockYOff]);
     482           0 :                 return CE_Failure;
     483             :             }
     484           0 :             else if (nCharsRead > static_cast<size_t>(szEnd - szStart))
     485             :             {
     486             :                 /* Read new data, this was not really the end */
     487           0 :                 szEnd = szLineBuf;
     488           0 :                 continue;
     489             :             }
     490             : 
     491             :             /* This is really the last value and has no tailing newline */
     492           0 :             szEnd = szLineBuf + nCharsRead;
     493             :         }
     494             : 
     495        3120 :         if (pdfImage != nullptr)
     496             :         {
     497        1600 :             *(pdfImage + iCell) = dfValue;
     498             :         }
     499             : 
     500        3120 :         iCell++;
     501             :     }
     502             : 
     503         312 :     while (*szEnd == ' ')
     504         156 :         szEnd++;
     505             : 
     506         156 :     if (*szEnd != '\0' && *szEnd != poGDS->szEOL[0])
     507           0 :         CPLDebug("GSAG",
     508             :                  "Grid row %d does not end with a newline.  "
     509             :                  "Possible skew.\n",
     510             :                  nBlockYOff);
     511             : 
     512         830 :     while (isspace((unsigned char)*szEnd))
     513         674 :         szEnd++;
     514             : 
     515         156 :     nCharsExamined += szEnd - szLineBuf;
     516             : 
     517         156 :     if (nCharsExamined >= nMaxLineSize)
     518           0 :         nMaxLineSize = nCharsExamined + 1;
     519             : 
     520         156 :     if (nBlockYOff > 0)
     521             :     {
     522         152 :         vsi_l_offset nNewOffset = panLineOffset[nBlockYOff] + nCharsExamined;
     523         152 :         if (panLineOffset[nBlockYOff - 1] == 0)
     524             :         {
     525          76 :             panLineOffset[nBlockYOff - 1] = nNewOffset;
     526             :         }
     527          76 :         else if (panLineOffset[nBlockYOff - 1] != nNewOffset)
     528             :         {
     529           0 :             CPLError(
     530             :                 CE_Failure, CPLE_AppDefined,
     531             :                 "Coding error: previous offset for line %d was " CPL_FRMT_GUIB
     532             :                 ", new offset would be " CPL_FRMT_GUIB,
     533           0 :                 nBlockYOff - 1, panLineOffset[nBlockYOff - 1], nNewOffset);
     534             :         }
     535             :     }
     536             : 
     537         156 :     nLastReadLine = nBlockYOff;
     538             : 
     539         156 :     VSIFree(szLineBuf);
     540             : 
     541         156 :     return CE_None;
     542             : }
     543             : 
     544             : /************************************************************************/
     545             : /*                            IWriteBlock()                             */
     546             : /************************************************************************/
     547             : 
     548           0 : CPLErr GSAGRasterBand::IWriteBlock(int nBlockXOff, int nBlockYOff, void *pImage)
     549             : 
     550             : {
     551           0 :     if (eAccess == GA_ReadOnly)
     552             :     {
     553           0 :         CPLError(CE_Failure, CPLE_NoWriteAccess,
     554             :                  "Unable to write block, dataset opened read only.");
     555           0 :         return CE_Failure;
     556             :     }
     557             : 
     558           0 :     if (nBlockYOff < 0 || nBlockYOff > nRasterYSize - 1 || nBlockXOff != 0)
     559           0 :         return CE_Failure;
     560             : 
     561           0 :     GSAGDataset *poGDS = cpl::down_cast<GSAGDataset *>(poDS);
     562             : 
     563           0 :     if (padfRowMinZ == nullptr || padfRowMaxZ == nullptr || nMinZRow < 0 ||
     564           0 :         nMaxZRow < 0)
     565             :     {
     566           0 :         padfRowMinZ =
     567           0 :             (double *)VSI_MALLOC2_VERBOSE(nRasterYSize, sizeof(double));
     568           0 :         if (padfRowMinZ == nullptr)
     569             :         {
     570           0 :             return CE_Failure;
     571             :         }
     572             : 
     573           0 :         padfRowMaxZ =
     574           0 :             (double *)VSI_MALLOC2_VERBOSE(nRasterYSize, sizeof(double));
     575           0 :         if (padfRowMaxZ == nullptr)
     576             :         {
     577           0 :             VSIFree(padfRowMinZ);
     578           0 :             padfRowMinZ = nullptr;
     579           0 :             return CE_Failure;
     580             :         }
     581             : 
     582           0 :         CPLErr eErr = ScanForMinMaxZ();
     583           0 :         if (eErr != CE_None)
     584           0 :             return eErr;
     585             :     }
     586             : 
     587           0 :     if (panLineOffset[nBlockYOff + 1] == 0)
     588           0 :         IReadBlock(nBlockXOff, nBlockYOff, nullptr);
     589             : 
     590           0 :     if (panLineOffset[nBlockYOff + 1] == 0 || panLineOffset[nBlockYOff] == 0)
     591           0 :         return CE_Failure;
     592             : 
     593           0 :     std::ostringstream ssOutBuf;
     594           0 :     ssOutBuf.precision(GSAGDataset::nFIELD_PRECISION);
     595           0 :     ssOutBuf.setf(std::ios::uppercase);
     596             : 
     597           0 :     double *pdfImage = (double *)pImage;
     598           0 :     padfRowMinZ[nBlockYOff] = std::numeric_limits<double>::max();
     599           0 :     padfRowMaxZ[nBlockYOff] = std::numeric_limits<double>::lowest();
     600           0 :     for (int iCell = 0; iCell < nBlockXSize;)
     601             :     {
     602           0 :         for (int iCol = 0; iCol < 10 && iCell < nBlockXSize; iCol++, iCell++)
     603             :         {
     604           0 :             if (AlmostEqual(pdfImage[iCell], GSAGDataset::dfNODATA_VALUE))
     605             :             {
     606           0 :                 if (pdfImage[iCell] < padfRowMinZ[nBlockYOff])
     607           0 :                     padfRowMinZ[nBlockYOff] = pdfImage[iCell];
     608             : 
     609           0 :                 if (pdfImage[iCell] > padfRowMaxZ[nBlockYOff])
     610           0 :                     padfRowMaxZ[nBlockYOff] = pdfImage[iCell];
     611             :             }
     612             : 
     613           0 :             ssOutBuf << pdfImage[iCell] << " ";
     614             :         }
     615           0 :         ssOutBuf << poGDS->szEOL;
     616             :     }
     617           0 :     ssOutBuf << poGDS->szEOL;
     618             : 
     619           0 :     CPLString sOut = ssOutBuf.str();
     620           0 :     if (sOut.length() !=
     621           0 :         panLineOffset[nBlockYOff + 1] - panLineOffset[nBlockYOff])
     622             :     {
     623           0 :         int nShiftSize = (int)(sOut.length() - (panLineOffset[nBlockYOff + 1] -
     624           0 :                                                 panLineOffset[nBlockYOff]));
     625           0 :         if (nBlockYOff != poGDS->nRasterYSize &&
     626           0 :             GSAGDataset::ShiftFileContents(poGDS->fp,
     627           0 :                                            panLineOffset[nBlockYOff + 1],
     628           0 :                                            nShiftSize, poGDS->szEOL) != CE_None)
     629             :         {
     630           0 :             CPLError(CE_Failure, CPLE_FileIO,
     631             :                      "Failure writing block, "
     632             :                      "unable to shift file contents.");
     633           0 :             return CE_Failure;
     634             :         }
     635             : 
     636           0 :         for (size_t iLine = nBlockYOff + 1;
     637           0 :              iLine < static_cast<unsigned>(poGDS->nRasterYSize + 1) &&
     638           0 :              panLineOffset[iLine] != 0;
     639             :              iLine++)
     640           0 :             panLineOffset[iLine] += nShiftSize;
     641             :     }
     642             : 
     643           0 :     if (VSIFSeekL(poGDS->fp, panLineOffset[nBlockYOff], SEEK_SET) != 0)
     644             :     {
     645           0 :         CPLError(CE_Failure, CPLE_FileIO, "Unable to seek to grid line.");
     646           0 :         return CE_Failure;
     647             :     }
     648             : 
     649           0 :     if (VSIFWriteL(sOut.c_str(), 1, sOut.length(), poGDS->fp) != sOut.length())
     650             :     {
     651           0 :         CPLError(CE_Failure, CPLE_FileIO, "Unable to write grid block.");
     652           0 :         return CE_Failure;
     653             :     }
     654             : 
     655             :     /* Update header as needed */
     656           0 :     bool bHeaderNeedsUpdate = false;
     657           0 :     if (nMinZRow == nBlockYOff && padfRowMinZ[nBlockYOff] > dfMinZ)
     658             :     {
     659           0 :         double dfNewMinZ = std::numeric_limits<double>::lowest();
     660           0 :         for (int iRow = 0; iRow < nRasterYSize; iRow++)
     661             :         {
     662           0 :             if (padfRowMinZ[iRow] < dfNewMinZ)
     663             :             {
     664           0 :                 dfNewMinZ = padfRowMinZ[iRow];
     665           0 :                 nMinZRow = iRow;
     666             :             }
     667             :         }
     668             : 
     669           0 :         if (dfNewMinZ != dfMinZ)
     670             :         {
     671           0 :             dfMinZ = dfNewMinZ;
     672           0 :             bHeaderNeedsUpdate = true;
     673             :         }
     674             :     }
     675             : 
     676           0 :     if (nMaxZRow == nBlockYOff && padfRowMaxZ[nBlockYOff] < dfMaxZ)
     677             :     {
     678           0 :         double dfNewMaxZ = std::numeric_limits<double>::lowest();
     679           0 :         for (int iRow = 0; iRow < nRasterYSize; iRow++)
     680             :         {
     681           0 :             if (padfRowMaxZ[iRow] > dfNewMaxZ)
     682             :             {
     683           0 :                 dfNewMaxZ = padfRowMaxZ[iRow];
     684           0 :                 nMaxZRow = iRow;
     685             :             }
     686             :         }
     687             : 
     688           0 :         if (dfNewMaxZ != dfMaxZ)
     689             :         {
     690           0 :             dfMaxZ = dfNewMaxZ;
     691           0 :             bHeaderNeedsUpdate = true;
     692             :         }
     693             :     }
     694             : 
     695           0 :     if (padfRowMinZ[nBlockYOff] < dfMinZ || padfRowMaxZ[nBlockYOff] > dfMaxZ)
     696             :     {
     697           0 :         if (padfRowMinZ[nBlockYOff] < dfMinZ)
     698             :         {
     699           0 :             dfMinZ = padfRowMinZ[nBlockYOff];
     700           0 :             nMinZRow = nBlockYOff;
     701             :         }
     702             : 
     703           0 :         if (padfRowMaxZ[nBlockYOff] > dfMaxZ)
     704             :         {
     705           0 :             dfMaxZ = padfRowMaxZ[nBlockYOff];
     706           0 :             nMaxZRow = nBlockYOff;
     707             :         }
     708             : 
     709           0 :         bHeaderNeedsUpdate = true;
     710             :     }
     711             : 
     712           0 :     if (bHeaderNeedsUpdate && dfMaxZ > dfMinZ)
     713             :     {
     714           0 :         CPLErr eErr = poGDS->UpdateHeader();
     715           0 :         return eErr;
     716             :     }
     717             : 
     718           0 :     return CE_None;
     719             : }
     720             : 
     721             : /************************************************************************/
     722             : /*                           GetNoDataValue()                           */
     723             : /************************************************************************/
     724             : 
     725          11 : double GSAGRasterBand::GetNoDataValue(int *pbSuccess)
     726             : {
     727          11 :     if (pbSuccess)
     728          10 :         *pbSuccess = TRUE;
     729             : 
     730          11 :     return GSAGDataset::dfNODATA_VALUE;
     731             : }
     732             : 
     733             : /************************************************************************/
     734             : /*                             GetMinimum()                             */
     735             : /************************************************************************/
     736             : 
     737           0 : double GSAGRasterBand::GetMinimum(int *pbSuccess)
     738             : {
     739           0 :     if (pbSuccess)
     740           0 :         *pbSuccess = TRUE;
     741             : 
     742           0 :     return dfMinZ;
     743             : }
     744             : 
     745             : /************************************************************************/
     746             : /*                             GetMaximum()                             */
     747             : /************************************************************************/
     748             : 
     749           0 : double GSAGRasterBand::GetMaximum(int *pbSuccess)
     750             : {
     751           0 :     if (pbSuccess)
     752           0 :         *pbSuccess = TRUE;
     753             : 
     754           0 :     return dfMaxZ;
     755             : }
     756             : 
     757             : /************************************************************************/
     758             : /* ==================================================================== */
     759             : /*                              GSAGDataset                             */
     760             : /* ==================================================================== */
     761             : /************************************************************************/
     762             : 
     763             : /************************************************************************/
     764             : /*                            GSAGDataset()                             */
     765             : /************************************************************************/
     766             : 
     767          17 : GSAGDataset::GSAGDataset(const char *pszEOL) : fp(nullptr), nMinMaxZOffset(0)
     768             : {
     769          17 :     if (pszEOL == nullptr || EQUAL(pszEOL, ""))
     770             :     {
     771           0 :         CPLDebug("GSAG", "GSAGDataset() created with invalid EOL string.\n");
     772           0 :         szEOL[0] = '\x0D';
     773           0 :         szEOL[1] = '\x0A';
     774           0 :         szEOL[2] = '\0';
     775           0 :         return;
     776             :     }
     777             : 
     778          17 :     snprintf(szEOL, sizeof(szEOL), "%s", pszEOL);
     779             : }
     780             : 
     781             : /************************************************************************/
     782             : /*                            ~GSAGDataset()                            */
     783             : /************************************************************************/
     784             : 
     785          34 : GSAGDataset::~GSAGDataset()
     786             : 
     787             : {
     788          17 :     FlushCache(true);
     789          17 :     if (fp != nullptr)
     790          17 :         VSIFCloseL(fp);
     791          34 : }
     792             : 
     793             : /************************************************************************/
     794             : /*                              Identify()                              */
     795             : /************************************************************************/
     796             : 
     797       64253 : int GSAGDataset::Identify(GDALOpenInfo *poOpenInfo)
     798             : 
     799             : {
     800             :     /* Check for signature */
     801       64253 :     if (poOpenInfo->nHeaderBytes < 5 ||
     802        6248 :         !STARTS_WITH_CI((const char *)poOpenInfo->pabyHeader, "DSAA") ||
     803          22 :         (poOpenInfo->pabyHeader[4] != '\x0D' &&
     804           0 :          poOpenInfo->pabyHeader[4] != '\x0A'))
     805             :     {
     806       64231 :         return FALSE;
     807             :     }
     808          22 :     return TRUE;
     809             : }
     810             : 
     811             : /************************************************************************/
     812             : /*                                Open()                                */
     813             : /************************************************************************/
     814             : 
     815           5 : GDALDataset *GSAGDataset::Open(GDALOpenInfo *poOpenInfo)
     816             : {
     817           5 :     return OpenPAM(poOpenInfo);
     818             : }
     819             : 
     820          17 : GDALPamDataset *GSAGDataset::OpenPAM(GDALOpenInfo *poOpenInfo)
     821             : 
     822             : {
     823          17 :     if (!Identify(poOpenInfo) || poOpenInfo->fpL == nullptr)
     824             :     {
     825           0 :         return nullptr;
     826             :     }
     827             : 
     828             :     /* Identify the end of line marker (should be \x0D\x0A, but try some others)
     829             :      * (note that '\x0D' == '\r' and '\x0A' == '\n' on most systems) */
     830             :     char szEOL[3];
     831          17 :     szEOL[0] = poOpenInfo->pabyHeader[4];
     832          17 :     szEOL[1] = poOpenInfo->pabyHeader[5];
     833          17 :     szEOL[2] = '\0';
     834          17 :     if (szEOL[1] != '\x0D' && szEOL[1] != '\x0A')
     835           0 :         szEOL[1] = '\0';
     836             : 
     837             :     /* -------------------------------------------------------------------- */
     838             :     /*      Create a corresponding GDALDataset.                             */
     839             :     /* -------------------------------------------------------------------- */
     840          17 :     GSAGDataset *poDS = new GSAGDataset(szEOL);
     841          17 :     poDS->eAccess = poOpenInfo->eAccess;
     842          17 :     poDS->fp = poOpenInfo->fpL;
     843          17 :     poOpenInfo->fpL = nullptr;
     844             : 
     845             :     /* -------------------------------------------------------------------- */
     846             :     /*      Read the header.                                                */
     847             :     /* -------------------------------------------------------------------- */
     848          17 :     char *pabyHeader = nullptr;
     849          17 :     bool bMustFreeHeader = false;
     850          17 :     if (poOpenInfo->nHeaderBytes >= static_cast<int>(nMAX_HEADER_SIZE))
     851             :     {
     852          17 :         pabyHeader = (char *)poOpenInfo->pabyHeader;
     853             :     }
     854             :     else
     855             :     {
     856           0 :         bMustFreeHeader = true;
     857           0 :         pabyHeader = (char *)VSI_MALLOC_VERBOSE(nMAX_HEADER_SIZE);
     858           0 :         if (pabyHeader == nullptr)
     859             :         {
     860           0 :             delete poDS;
     861           0 :             return nullptr;
     862             :         }
     863             : 
     864           0 :         size_t nRead = VSIFReadL(pabyHeader, 1, nMAX_HEADER_SIZE - 1, poDS->fp);
     865           0 :         pabyHeader[nRead] = '\0';
     866             :     }
     867             : 
     868          17 :     const char *szErrorMsg = nullptr;
     869          17 :     const char *szStart = pabyHeader + 5;
     870          17 :     char *szEnd = nullptr;
     871             :     double dfTemp;
     872             : 
     873             :     /* Parse number of X axis grid rows */
     874          17 :     long nTemp = strtol(szStart, &szEnd, 10);
     875          17 :     if (szStart == szEnd || nTemp < 0l)
     876             :     {
     877           0 :         szErrorMsg = "Unable to parse the number of X axis grid columns.\n";
     878           0 :         goto error;
     879             :     }
     880          17 :     else if (nTemp > std::numeric_limits<int>::max())
     881             :     {
     882           0 :         CPLError(CE_Warning, CPLE_AppDefined,
     883             :                  "Number of X axis grid columns not representable.");
     884           0 :         poDS->nRasterXSize = std::numeric_limits<int>::max();
     885             :     }
     886          17 :     else if (nTemp == 0)
     887             :     {
     888           0 :         szErrorMsg =
     889             :             "Number of X axis grid columns is zero, which is invalid.\n";
     890           0 :         goto error;
     891             :     }
     892             :     else
     893             :     {
     894          17 :         poDS->nRasterXSize = static_cast<int>(nTemp);
     895             :     }
     896          17 :     szStart = szEnd;
     897             : 
     898             :     /* Parse number of Y axis grid rows */
     899          17 :     nTemp = strtol(szStart, &szEnd, 10);
     900          17 :     if (szStart == szEnd || nTemp < 0l)
     901             :     {
     902           0 :         szErrorMsg = "Unable to parse the number of Y axis grid rows.\n";
     903           0 :         goto error;
     904             :     }
     905          17 :     else if (nTemp > std::numeric_limits<int>::max() - 1)
     906             :     {
     907           0 :         CPLError(CE_Warning, CPLE_AppDefined,
     908             :                  "Number of Y axis grid rows not representable.");
     909           0 :         poDS->nRasterYSize = std::numeric_limits<int>::max() - 1;
     910             :     }
     911          17 :     else if (nTemp == 0)
     912             :     {
     913           0 :         szErrorMsg = "Number of Y axis grid rows is zero, which is invalid.\n";
     914           0 :         goto error;
     915             :     }
     916             :     else
     917             :     {
     918          17 :         poDS->nRasterYSize = static_cast<int>(nTemp);
     919             :     }
     920          17 :     szStart = szEnd;
     921             : 
     922             :     /* Parse the minimum X value of the grid */
     923             :     double dfMinX;
     924          17 :     dfTemp = CPLStrtod(szStart, &szEnd);
     925          17 :     if (szStart == szEnd)
     926             :     {
     927           0 :         szErrorMsg = "Unable to parse the minimum X value.\n";
     928           0 :         goto error;
     929             :     }
     930             :     else
     931             :     {
     932          17 :         dfMinX = dfTemp;
     933             :     }
     934          17 :     szStart = szEnd;
     935             : 
     936             :     /* Parse the maximum X value of the grid */
     937             :     double dfMaxX;
     938          17 :     dfTemp = CPLStrtod(szStart, &szEnd);
     939          17 :     if (szStart == szEnd)
     940             :     {
     941           0 :         szErrorMsg = "Unable to parse the maximum X value.\n";
     942           0 :         goto error;
     943             :     }
     944             :     else
     945             :     {
     946          17 :         dfMaxX = dfTemp;
     947             :     }
     948          17 :     szStart = szEnd;
     949             : 
     950             :     /* Parse the minimum Y value of the grid */
     951             :     double dfMinY;
     952          17 :     dfTemp = CPLStrtod(szStart, &szEnd);
     953          17 :     if (szStart == szEnd)
     954             :     {
     955           0 :         szErrorMsg = "Unable to parse the minimum Y value.\n";
     956           0 :         goto error;
     957             :     }
     958             :     else
     959             :     {
     960          17 :         dfMinY = dfTemp;
     961             :     }
     962          17 :     szStart = szEnd;
     963             : 
     964             :     /* Parse the maximum Y value of the grid */
     965             :     double dfMaxY;
     966          17 :     dfTemp = CPLStrtod(szStart, &szEnd);
     967          17 :     if (szStart == szEnd)
     968             :     {
     969           0 :         szErrorMsg = "Unable to parse the maximum Y value.\n";
     970           0 :         goto error;
     971             :     }
     972             :     else
     973             :     {
     974          17 :         dfMaxY = dfTemp;
     975             :     }
     976          17 :     szStart = szEnd;
     977             : 
     978             :     /* Parse the minimum Z value of the grid */
     979          51 :     while (isspace((unsigned char)*szStart))
     980          34 :         szStart++;
     981          17 :     poDS->nMinMaxZOffset = szStart - pabyHeader;
     982             : 
     983             :     double dfMinZ;
     984          17 :     dfTemp = CPLStrtod(szStart, &szEnd);
     985          17 :     if (szStart == szEnd)
     986             :     {
     987           0 :         szErrorMsg = "Unable to parse the minimum Z value.\n";
     988           0 :         goto error;
     989             :     }
     990             :     else
     991             :     {
     992          17 :         dfMinZ = dfTemp;
     993             :     }
     994          17 :     szStart = szEnd;
     995             : 
     996             :     /* Parse the maximum Z value of the grid */
     997             :     double dfMaxZ;
     998          17 :     dfTemp = CPLStrtod(szStart, &szEnd);
     999          17 :     if (szStart == szEnd)
    1000             :     {
    1001           0 :         szErrorMsg = "Unable to parse the maximum Z value.\n";
    1002           0 :         goto error;
    1003             :     }
    1004             :     else
    1005             :     {
    1006          17 :         dfMaxZ = dfTemp;
    1007             :     }
    1008             : 
    1009          51 :     while (isspace((unsigned char)*szEnd))
    1010          34 :         szEnd++;
    1011             : 
    1012             :     /* -------------------------------------------------------------------- */
    1013             :     /*      Create band information objects.                                */
    1014             :     /* -------------------------------------------------------------------- */
    1015             :     {
    1016             :         GSAGRasterBand *poBand =
    1017          17 :             new GSAGRasterBand(poDS, 1, szEnd - pabyHeader);
    1018          17 :         if (poBand->panLineOffset == nullptr)
    1019             :         {
    1020           0 :             delete poBand;
    1021           0 :             goto error;
    1022             :         }
    1023             : 
    1024          17 :         poBand->dfMinX = dfMinX;
    1025          17 :         poBand->dfMaxX = dfMaxX;
    1026          17 :         poBand->dfMinY = dfMinY;
    1027          17 :         poBand->dfMaxY = dfMaxY;
    1028          17 :         poBand->dfMinZ = dfMinZ;
    1029          17 :         poBand->dfMaxZ = dfMaxZ;
    1030             : 
    1031          17 :         poDS->SetBand(1, poBand);
    1032             :     }
    1033             : 
    1034          17 :     if (bMustFreeHeader)
    1035             :     {
    1036           0 :         CPLFree(pabyHeader);
    1037             :     }
    1038             : 
    1039             :     /* -------------------------------------------------------------------- */
    1040             :     /*      Initialize any PAM information.                                 */
    1041             :     /* -------------------------------------------------------------------- */
    1042          17 :     poDS->SetDescription(poOpenInfo->pszFilename);
    1043          17 :     poDS->TryLoadXML();
    1044             : 
    1045             :     /* -------------------------------------------------------------------- */
    1046             :     /*      Check for external overviews.                                   */
    1047             :     /* -------------------------------------------------------------------- */
    1048          34 :     poDS->oOvManager.Initialize(poDS, poOpenInfo->pszFilename,
    1049          17 :                                 poOpenInfo->GetSiblingFiles());
    1050             : 
    1051          17 :     return poDS;
    1052             : 
    1053           0 : error:
    1054           0 :     if (bMustFreeHeader)
    1055             :     {
    1056           0 :         CPLFree(pabyHeader);
    1057             :     }
    1058             : 
    1059           0 :     delete poDS;
    1060             : 
    1061           0 :     if (szErrorMsg)
    1062           0 :         CPLError(CE_Failure, CPLE_AppDefined, "%s", szErrorMsg);
    1063           0 :     return nullptr;
    1064             : }
    1065             : 
    1066             : /************************************************************************/
    1067             : /*                          GetGeoTransform()                           */
    1068             : /************************************************************************/
    1069             : 
    1070          17 : CPLErr GSAGDataset::GetGeoTransform(GDALGeoTransform &gt) const
    1071             : {
    1072          17 :     gt = GDALGeoTransform();
    1073             : 
    1074             :     const GSAGRasterBand *poGRB =
    1075          17 :         cpl::down_cast<const GSAGRasterBand *>(GetRasterBand(1));
    1076             : 
    1077             :     /* check if we have a PAM GeoTransform stored */
    1078          17 :     CPLPushErrorHandler(CPLQuietErrorHandler);
    1079          17 :     CPLErr eErr = GDALPamDataset::GetGeoTransform(gt);
    1080          17 :     CPLPopErrorHandler();
    1081             : 
    1082          17 :     if (eErr == CE_None)
    1083           0 :         return CE_None;
    1084             : 
    1085          17 :     if (nRasterXSize == 1 || nRasterYSize == 1)
    1086           0 :         return CE_Failure;
    1087             : 
    1088             :     /* calculate pixel size first */
    1089          17 :     gt.xscale = (poGRB->dfMaxX - poGRB->dfMinX) / (nRasterXSize - 1);
    1090          17 :     gt.yscale = (poGRB->dfMinY - poGRB->dfMaxY) / (nRasterYSize - 1);
    1091             : 
    1092             :     /* then calculate image origin */
    1093          17 :     gt.xorig = poGRB->dfMinX - gt.xscale / 2;
    1094          17 :     gt.yorig = poGRB->dfMaxY - gt.yscale / 2;
    1095             : 
    1096             :     /* tilt/rotation does not supported by the GS grids */
    1097          17 :     gt.yrot = 0.0;
    1098          17 :     gt.xrot = 0.0;
    1099             : 
    1100          17 :     return CE_None;
    1101             : }
    1102             : 
    1103             : /************************************************************************/
    1104             : /*                          SetGeoTransform()                           */
    1105             : /************************************************************************/
    1106             : 
    1107           0 : CPLErr GSAGDataset::SetGeoTransform(const GDALGeoTransform &gt)
    1108             : {
    1109           0 :     if (eAccess == GA_ReadOnly)
    1110             :     {
    1111           0 :         CPLError(CE_Failure, CPLE_NoWriteAccess,
    1112             :                  "Unable to set GeoTransform, dataset opened read only.");
    1113           0 :         return CE_Failure;
    1114             :     }
    1115             : 
    1116           0 :     GSAGRasterBand *poGRB = cpl::down_cast<GSAGRasterBand *>(GetRasterBand(1));
    1117             : 
    1118             :     /* non-zero transform 2 or 4 or negative 1 or 5 not supported natively */
    1119             :     /*if( gt.xrot != 0.0 || gt.yrot != 0.0
    1120             :         || gt.xscale < 0.0 || gt.yscale < 0.0 )
    1121             :         eErr = GDALPamDataset::SetGeoTransform( gt );*/
    1122             :     // if( eErr != CE_None )
    1123             :     //     return eErr;
    1124             : 
    1125           0 :     const double dfOldMinX = poGRB->dfMinX;
    1126           0 :     const double dfOldMaxX = poGRB->dfMaxX;
    1127           0 :     const double dfOldMinY = poGRB->dfMinY;
    1128           0 :     const double dfOldMaxY = poGRB->dfMaxY;
    1129             : 
    1130           0 :     poGRB->dfMinX = gt.xorig + gt.xscale / 2;
    1131           0 :     poGRB->dfMaxX = gt.xscale * (nRasterXSize - 0.5) + gt.xorig;
    1132           0 :     poGRB->dfMinY = gt.yscale * (nRasterYSize - 0.5) + gt.yorig;
    1133           0 :     poGRB->dfMaxY = gt.yorig + gt.yscale / 2;
    1134             : 
    1135           0 :     CPLErr eErr = UpdateHeader();
    1136             : 
    1137           0 :     if (eErr != CE_None)
    1138             :     {
    1139           0 :         poGRB->dfMinX = dfOldMinX;
    1140           0 :         poGRB->dfMaxX = dfOldMaxX;
    1141           0 :         poGRB->dfMinY = dfOldMinY;
    1142           0 :         poGRB->dfMaxY = dfOldMaxY;
    1143             :     }
    1144             : 
    1145           0 :     return eErr;
    1146             : }
    1147             : 
    1148             : /************************************************************************/
    1149             : /*                         ShiftFileContents()                          */
    1150             : /************************************************************************/
    1151          12 : CPLErr GSAGDataset::ShiftFileContents(VSILFILE *fp, vsi_l_offset nShiftStart,
    1152             :                                       int nShiftSize, const char *pszEOL)
    1153             : {
    1154             :     /* nothing to do for zero-shift */
    1155          12 :     if (nShiftSize == 0)
    1156           0 :         return CE_None;
    1157             : 
    1158             :     /* make sure start location is sane */
    1159             :     /* Tautology is always false.  nShiftStart is unsigned. */
    1160          12 :     if (/* nShiftStart < 0
    1161             :            || */
    1162          12 :         (nShiftSize < 0 &&
    1163          12 :          nShiftStart < static_cast<vsi_l_offset>(-nShiftSize)))
    1164           0 :         nShiftStart = /*(nShiftSize > 0) ? 0 :*/ -nShiftSize;
    1165             : 
    1166             :     /* get offset at end of file */
    1167          12 :     if (VSIFSeekL(fp, 0, SEEK_END) != 0)
    1168             :     {
    1169           0 :         CPLError(CE_Failure, CPLE_FileIO,
    1170             :                  "Unable to seek to end of grid file.");
    1171           0 :         return CE_Failure;
    1172             :     }
    1173             : 
    1174          12 :     vsi_l_offset nOldEnd = VSIFTellL(fp);
    1175             : 
    1176             :     /* If shifting past end, just zero-pad as necessary */
    1177          12 :     if (nShiftStart >= nOldEnd)
    1178             :     {
    1179           0 :         if (nShiftSize < 0)
    1180             :         {
    1181           0 :             if (nShiftStart + nShiftSize >= nOldEnd)
    1182           0 :                 return CE_None;
    1183             : 
    1184           0 :             VSIFTruncateL(fp, nShiftStart + nShiftSize);
    1185             : 
    1186           0 :             return CE_None;
    1187             :         }
    1188             :         else
    1189             :         {
    1190           0 :             for (vsi_l_offset nPos = nOldEnd; nPos < nShiftStart + nShiftSize;
    1191             :                  nPos++)
    1192             :             {
    1193           0 :                 if (VSIFWriteL((void *)" ", 1, 1, fp) != 1)
    1194             :                 {
    1195           0 :                     CPLError(CE_Failure, CPLE_FileIO,
    1196             :                              "Unable to write padding to grid file "
    1197             :                              "(Out of space?).");
    1198           0 :                     return CE_Failure;
    1199             :                 }
    1200             :             }
    1201           0 :             return CE_None;
    1202             :         }
    1203             :     }
    1204             : 
    1205             :     /* prepare buffer for real shifting */
    1206          12 :     size_t nBufferSize =
    1207          12 :         (1024 >= abs(nShiftSize) * 2) ? 1024 : abs(nShiftSize) * 2;
    1208          12 :     char *pabyBuffer = (char *)VSI_MALLOC_VERBOSE(nBufferSize);
    1209          12 :     if (pabyBuffer == nullptr)
    1210             :     {
    1211           0 :         return CE_Failure;
    1212             :     }
    1213             : 
    1214          12 :     if (VSIFSeekL(fp, nShiftStart, SEEK_SET) != 0)
    1215             :     {
    1216           0 :         VSIFree(pabyBuffer);
    1217           0 :         CPLError(CE_Failure, CPLE_FileIO,
    1218             :                  "Unable to seek to start of shift in grid file.");
    1219           0 :         return CE_Failure;
    1220             :     }
    1221             : 
    1222             :     size_t nRead;
    1223          12 :     size_t nOverlap = (nShiftSize > 0) ? nShiftSize : 0;
    1224             :     /* If there is overlap, fill buffer with the overlap to start */
    1225          12 :     if (nOverlap > 0)
    1226             :     {
    1227           0 :         nRead = VSIFReadL((void *)pabyBuffer, 1, nOverlap, fp);
    1228           0 :         if (nRead < nOverlap && !VSIFEofL(fp))
    1229             :         {
    1230           0 :             VSIFree(pabyBuffer);
    1231           0 :             CPLError(CE_Failure, CPLE_FileIO, "Error reading grid file.");
    1232           0 :             return CE_Failure;
    1233             :         }
    1234             : 
    1235             :         /* overwrite the new space with ' ' */
    1236           0 :         if (VSIFSeekL(fp, nShiftStart, SEEK_SET) != 0)
    1237             :         {
    1238           0 :             VSIFree(pabyBuffer);
    1239           0 :             CPLError(CE_Failure, CPLE_FileIO,
    1240             :                      "Unable to seek to start of shift in grid file.");
    1241           0 :             return CE_Failure;
    1242             :         }
    1243             : 
    1244           0 :         for (int iFill = 0; iFill < nShiftSize; iFill++)
    1245             :         {
    1246           0 :             if (VSIFWriteL((void *)" ", 1, 1, fp) != 1)
    1247             :             {
    1248           0 :                 VSIFree(pabyBuffer);
    1249           0 :                 CPLError(CE_Failure, CPLE_FileIO,
    1250             :                          "Unable to write padding to grid file "
    1251             :                          "(Out of space?).");
    1252           0 :                 return CE_Failure;
    1253             :             }
    1254             :         }
    1255             : 
    1256             :         /* if we have already read the entire file, finish it off */
    1257           0 :         if (VSIFTellL(fp) >= nOldEnd)
    1258             :         {
    1259           0 :             if (VSIFWriteL((void *)pabyBuffer, 1, nRead, fp) != nRead)
    1260             :             {
    1261           0 :                 VSIFree(pabyBuffer);
    1262           0 :                 CPLError(CE_Failure, CPLE_FileIO,
    1263             :                          "Unable to write to grid file (Out of space?).");
    1264           0 :                 return CE_Failure;
    1265             :             }
    1266             : 
    1267           0 :             VSIFree(pabyBuffer);
    1268           0 :             return CE_None;
    1269             :         }
    1270             :     }
    1271             : 
    1272             :     /* iterate over the remainder of the file and shift as requested */
    1273          12 :     bool bEOF = false;
    1274          25 :     while (!bEOF)
    1275             :     {
    1276          13 :         nRead = VSIFReadL((void *)(pabyBuffer + nOverlap), 1,
    1277             :                           nBufferSize - nOverlap, fp);
    1278             : 
    1279          13 :         if (VSIFEofL(fp))
    1280          12 :             bEOF = true;
    1281             :         else
    1282           1 :             bEOF = false;
    1283             : 
    1284          13 :         if (nRead == 0 && !bEOF)
    1285             :         {
    1286           0 :             VSIFree(pabyBuffer);
    1287           0 :             CPLError(CE_Failure, CPLE_FileIO,
    1288             :                      "Unable to read from grid file (possible corruption).");
    1289           0 :             return CE_Failure;
    1290             :         }
    1291             : 
    1292             :         /* FIXME:  Should use SEEK_CUR, review integer promotions... */
    1293             :         vsi_l_offset nNewPos =
    1294             :             (nShiftSize >= 0)
    1295          13 :                 ? VSIFTellL(fp) + nShiftSize - nRead - nOverlap
    1296          13 :                 : VSIFTellL(fp) - (-nShiftSize) - nRead - nOverlap;
    1297          13 :         if (VSIFSeekL(fp, nNewPos, SEEK_SET) != 0)
    1298             :         {
    1299           0 :             VSIFree(pabyBuffer);
    1300           0 :             CPLError(CE_Failure, CPLE_FileIO,
    1301             :                      "Unable to seek in grid file (possible corruption).");
    1302           0 :             return CE_Failure;
    1303             :         }
    1304             : 
    1305          13 :         size_t nWritten = VSIFWriteL((void *)pabyBuffer, 1, nRead, fp);
    1306          13 :         if (nWritten != nRead)
    1307             :         {
    1308           0 :             VSIFree(pabyBuffer);
    1309           0 :             CPLError(CE_Failure, CPLE_FileIO,
    1310             :                      "Unable to write to grid file (out of space?).");
    1311           0 :             return CE_Failure;
    1312             :         }
    1313             : 
    1314             :         /* shift overlapped contents to the front of the buffer if necessary */
    1315          13 :         if (nOverlap > 0)
    1316           0 :             memmove(pabyBuffer, pabyBuffer + nRead, nOverlap);
    1317             :     }
    1318             : 
    1319             :     /* write the remainder of the buffer or overwrite leftovers and finish */
    1320          12 :     if (nShiftSize > 0)
    1321             :     {
    1322           0 :         size_t nTailSize = nOverlap;
    1323           0 :         while (nTailSize > 0 &&
    1324           0 :                isspace((unsigned char)pabyBuffer[nTailSize - 1]))
    1325           0 :             nTailSize--;
    1326             : 
    1327           0 :         if (VSIFWriteL((void *)pabyBuffer, 1, nTailSize, fp) != nTailSize)
    1328             :         {
    1329           0 :             VSIFree(pabyBuffer);
    1330           0 :             CPLError(CE_Failure, CPLE_FileIO,
    1331             :                      "Unable to write to grid file (out of space?).");
    1332           0 :             return CE_Failure;
    1333             :         }
    1334             : 
    1335           0 :         if (VSIFWriteL((void *)pszEOL, 1, strlen(pszEOL), fp) != strlen(pszEOL))
    1336             :         {
    1337           0 :             VSIFree(pabyBuffer);
    1338           0 :             CPLError(CE_Failure, CPLE_FileIO,
    1339             :                      "Unable to write to grid file (out of space?).");
    1340           0 :             return CE_Failure;
    1341             :         }
    1342             :     }
    1343             :     else
    1344             :     {
    1345             :         /* FIXME: ftruncate()? */
    1346             :         /* FIXME:  Should use SEEK_CUR, review integer promotions... */
    1347          12 :         if (VSIFSeekL(fp, VSIFTellL(fp) - strlen(pszEOL), SEEK_SET) != 0)
    1348             :         {
    1349           0 :             VSIFree(pabyBuffer);
    1350           0 :             CPLError(CE_Failure, CPLE_FileIO, "Unable to seek in grid file.");
    1351           0 :             return CE_Failure;
    1352             :         }
    1353             : 
    1354         301 :         for (int iPadding = 0; iPadding < -nShiftSize; iPadding++)
    1355             :         {
    1356         289 :             if (VSIFWriteL((void *)" ", 1, 1, fp) != 1)
    1357             :             {
    1358           0 :                 VSIFree(pabyBuffer);
    1359           0 :                 CPLError(CE_Failure, CPLE_FileIO,
    1360             :                          "Error writing to grid file.");
    1361           0 :                 return CE_Failure;
    1362             :             }
    1363             :         }
    1364             : 
    1365          12 :         if (VSIFWriteL((void *)pszEOL, 1, strlen(pszEOL), fp) != strlen(pszEOL))
    1366             :         {
    1367           0 :             VSIFree(pabyBuffer);
    1368           0 :             CPLError(CE_Failure, CPLE_FileIO,
    1369             :                      "Unable to write to grid file (out of space?).");
    1370           0 :             return CE_Failure;
    1371             :         }
    1372             :     }
    1373             : 
    1374          12 :     VSIFree(pabyBuffer);
    1375          12 :     return CE_None;
    1376             : }
    1377             : 
    1378             : /************************************************************************/
    1379             : /*                            UpdateHeader()                            */
    1380             : /************************************************************************/
    1381             : 
    1382           0 : CPLErr GSAGDataset::UpdateHeader()
    1383             : 
    1384             : {
    1385           0 :     GSAGRasterBand *poBand = cpl::down_cast<GSAGRasterBand *>(GetRasterBand(1));
    1386             : 
    1387           0 :     std::ostringstream ssOutBuf;
    1388           0 :     ssOutBuf.precision(nFIELD_PRECISION);
    1389           0 :     ssOutBuf.setf(std::ios::uppercase);
    1390             : 
    1391             :     /* signature */
    1392           0 :     ssOutBuf << "DSAA" << szEOL;
    1393             : 
    1394             :     /* columns rows */
    1395           0 :     ssOutBuf << nRasterXSize << " " << nRasterYSize << szEOL;
    1396             : 
    1397             :     /* x range */
    1398           0 :     ssOutBuf << poBand->dfMinX << " " << poBand->dfMaxX << szEOL;
    1399             : 
    1400             :     /* y range */
    1401           0 :     ssOutBuf << poBand->dfMinY << " " << poBand->dfMaxY << szEOL;
    1402             : 
    1403             :     /* z range */
    1404           0 :     ssOutBuf << poBand->dfMinZ << " " << poBand->dfMaxZ << szEOL;
    1405             : 
    1406           0 :     CPLString sOut = ssOutBuf.str();
    1407           0 :     if (sOut.length() != poBand->panLineOffset[0])
    1408             :     {
    1409           0 :         int nShiftSize = (int)(sOut.length() - poBand->panLineOffset[0]);
    1410           0 :         if (ShiftFileContents(fp, poBand->panLineOffset[0], nShiftSize,
    1411           0 :                               szEOL) != CE_None)
    1412             :         {
    1413           0 :             CPLError(CE_Failure, CPLE_FileIO,
    1414             :                      "Unable to update grid header, "
    1415             :                      "failure shifting file contents.");
    1416           0 :             return CE_Failure;
    1417             :         }
    1418             : 
    1419           0 :         for (size_t iLine = 0;
    1420           0 :              iLine < static_cast<unsigned>(nRasterYSize + 1) &&
    1421           0 :              poBand->panLineOffset[iLine] != 0;
    1422             :              iLine++)
    1423           0 :             poBand->panLineOffset[iLine] += nShiftSize;
    1424             :     }
    1425             : 
    1426           0 :     if (VSIFSeekL(fp, 0, SEEK_SET) != 0)
    1427             :     {
    1428           0 :         CPLError(CE_Failure, CPLE_FileIO,
    1429             :                  "Unable to seek to start of grid file.");
    1430           0 :         return CE_Failure;
    1431             :     }
    1432             : 
    1433           0 :     if (VSIFWriteL(sOut.c_str(), 1, sOut.length(), fp) != sOut.length())
    1434             :     {
    1435           0 :         CPLError(CE_Failure, CPLE_FileIO,
    1436             :                  "Unable to update file header.  Disk full?");
    1437           0 :         return CE_Failure;
    1438             :     }
    1439             : 
    1440           0 :     return CE_None;
    1441             : }
    1442             : 
    1443             : /************************************************************************/
    1444             : /*                             CreateCopy()                             */
    1445             : /************************************************************************/
    1446             : 
    1447          30 : GDALDataset *GSAGDataset::CreateCopy(const char *pszFilename,
    1448             :                                      GDALDataset *poSrcDS, int bStrict,
    1449             :                                      CPL_UNUSED CSLConstList papszOptions,
    1450             :                                      GDALProgressFunc pfnProgress,
    1451             :                                      void *pProgressData)
    1452             : {
    1453          30 :     if (pfnProgress == nullptr)
    1454           0 :         pfnProgress = GDALDummyProgress;
    1455             : 
    1456          30 :     int nBands = poSrcDS->GetRasterCount();
    1457          30 :     if (nBands == 0)
    1458             :     {
    1459           1 :         CPLError(
    1460             :             CE_Failure, CPLE_NotSupported,
    1461             :             "GSAG driver does not support source datasets with zero bands.");
    1462           1 :         return nullptr;
    1463             :     }
    1464          29 :     else if (nBands > 1)
    1465             :     {
    1466           4 :         if (bStrict)
    1467             :         {
    1468           4 :             CPLError(CE_Failure, CPLE_NotSupported,
    1469             :                      "Unable to create copy, Golden Software ASCII Grid "
    1470             :                      "format only supports one raster band.");
    1471           4 :             return nullptr;
    1472             :         }
    1473             :         else
    1474           0 :             CPLError(CE_Warning, CPLE_NotSupported,
    1475             :                      "Golden Software ASCII Grid format only supports one "
    1476             :                      "raster band, first band will be copied.");
    1477             :     }
    1478             : 
    1479          25 :     if (!pfnProgress(0.0, nullptr, pProgressData))
    1480             :     {
    1481           0 :         CPLError(CE_Failure, CPLE_UserInterrupt, "User terminated");
    1482           0 :         return nullptr;
    1483             :     }
    1484             : 
    1485          25 :     VSILFILE *fp = VSIFOpenL(pszFilename, "w+b");
    1486             : 
    1487          25 :     if (fp == nullptr)
    1488             :     {
    1489           3 :         CPLError(CE_Failure, CPLE_OpenFailed,
    1490             :                  "Attempt to create file '%s' failed.\n", pszFilename);
    1491           3 :         return nullptr;
    1492             :     }
    1493             : 
    1494          22 :     const int nXSize = poSrcDS->GetRasterXSize();
    1495          22 :     const int nYSize = poSrcDS->GetRasterYSize();
    1496          22 :     GDALGeoTransform gt;
    1497             : 
    1498          22 :     poSrcDS->GetGeoTransform(gt);
    1499             : 
    1500          44 :     std::ostringstream ssHeader;
    1501          22 :     ssHeader.precision(nFIELD_PRECISION);
    1502          22 :     ssHeader.setf(std::ios::uppercase);
    1503             : 
    1504          22 :     ssHeader << "DSAA\x0D\x0A";
    1505             : 
    1506          22 :     ssHeader << nXSize << " " << nYSize << "\x0D\x0A";
    1507             : 
    1508          22 :     ssHeader << gt.xorig + gt.xscale / 2 << " "
    1509          22 :              << gt.xscale * (nXSize - 0.5) + gt.xorig << "\x0D\x0A";
    1510             : 
    1511          22 :     ssHeader << gt.yscale * (nYSize - 0.5) + gt.yorig << " "
    1512          22 :              << gt.yorig + gt.yscale / 2 << "\x0D\x0A";
    1513             : 
    1514          44 :     if (VSIFWriteL((void *)ssHeader.str().c_str(), 1, ssHeader.str().length(),
    1515          22 :                    fp) != ssHeader.str().length())
    1516             :     {
    1517           1 :         VSIFCloseL(fp);
    1518           1 :         CPLError(CE_Failure, CPLE_FileIO,
    1519             :                  "Unable to create copy, writing header failed.");
    1520           1 :         return nullptr;
    1521             :     }
    1522             : 
    1523             :     /* Save the location and write placeholders for the min/max Z value */
    1524          21 :     vsi_l_offset nRangeStart = VSIFTellL(fp);
    1525          21 :     const char *szDummyRange = "0.0000000000001 0.0000000000001\x0D\x0A";
    1526          21 :     size_t nDummyRangeLen = strlen(szDummyRange);
    1527          21 :     if (VSIFWriteL((void *)szDummyRange, 1, nDummyRangeLen, fp) !=
    1528             :         nDummyRangeLen)
    1529             :     {
    1530           1 :         VSIFCloseL(fp);
    1531           1 :         CPLError(CE_Failure, CPLE_FileIO,
    1532             :                  "Unable to create copy, writing header failed.");
    1533           1 :         return nullptr;
    1534             :     }
    1535             : 
    1536             :     /* -------------------------------------------------------------------- */
    1537             :     /*      Copy band data.                                                 */
    1538             :     /* -------------------------------------------------------------------- */
    1539          20 :     double *pdfData = (double *)VSI_MALLOC2_VERBOSE(nXSize, sizeof(double));
    1540          20 :     if (pdfData == nullptr)
    1541             :     {
    1542           0 :         VSIFCloseL(fp);
    1543           0 :         return nullptr;
    1544             :     }
    1545             : 
    1546          20 :     GDALRasterBand *poSrcBand = poSrcDS->GetRasterBand(1);
    1547             :     int bSrcHasNDValue;
    1548          20 :     double dfSrcNoDataValue = poSrcBand->GetNoDataValue(&bSrcHasNDValue);
    1549          20 :     double dfMin = std::numeric_limits<double>::max();
    1550          20 :     double dfMax = std::numeric_limits<double>::lowest();
    1551         184 :     for (int iRow = 0; iRow < nYSize; iRow++)
    1552             :     {
    1553             :         CPLErr eErr =
    1554         172 :             poSrcBand->RasterIO(GF_Read, 0, nYSize - iRow - 1, nXSize, 1,
    1555             :                                 pdfData, nXSize, 1, GDT_Float64, 0, 0, nullptr);
    1556             : 
    1557         172 :         if (eErr != CE_None)
    1558             :         {
    1559           0 :             VSIFCloseL(fp);
    1560           0 :             VSIFree(pdfData);
    1561           0 :             return nullptr;
    1562             :         }
    1563             : 
    1564         356 :         for (int iCol = 0; iCol < nXSize;)
    1565             :         {
    1566        2076 :             for (int iCount = 0; iCount < 10 && iCol < nXSize; iCount++, iCol++)
    1567             :             {
    1568        1892 :                 double dfValue = pdfData[iCol];
    1569             : 
    1570        1892 :                 if (bSrcHasNDValue && AlmostEqual(dfValue, dfSrcNoDataValue))
    1571             :                 {
    1572           0 :                     dfValue = dfNODATA_VALUE;
    1573             :                 }
    1574             :                 else
    1575             :                 {
    1576        1892 :                     if (dfValue > dfMax)
    1577          22 :                         dfMax = dfValue;
    1578             : 
    1579        1892 :                     if (dfValue < dfMin)
    1580          27 :                         dfMin = dfValue;
    1581             :                 }
    1582             : 
    1583        1892 :                 std::ostringstream ssOut;
    1584        1892 :                 ssOut.precision(nFIELD_PRECISION);
    1585        1892 :                 ssOut.setf(std::ios::uppercase);
    1586        1892 :                 ssOut << dfValue << " ";
    1587        1892 :                 CPLString sOut = ssOut.str();
    1588             : 
    1589        3784 :                 if (VSIFWriteL(sOut.c_str(), 1, sOut.length(), fp) !=
    1590        1892 :                     sOut.length())
    1591             :                 {
    1592           8 :                     VSIFCloseL(fp);
    1593           8 :                     VSIFree(pdfData);
    1594           8 :                     CPLError(CE_Failure, CPLE_FileIO,
    1595             :                              "Unable to write grid cell.  Disk full?");
    1596           8 :                     return nullptr;
    1597             :                 }
    1598             :             }
    1599             : 
    1600         184 :             if (VSIFWriteL((void *)"\x0D\x0A", 1, 2, fp) != 2)
    1601             :             {
    1602           0 :                 VSIFCloseL(fp);
    1603           0 :                 VSIFree(pdfData);
    1604           0 :                 CPLError(CE_Failure, CPLE_FileIO,
    1605             :                          "Unable to finish write of grid line. Disk full?");
    1606           0 :                 return nullptr;
    1607             :             }
    1608             :         }
    1609             : 
    1610         164 :         if (VSIFWriteL((void *)"\x0D\x0A", 1, 2, fp) != 2)
    1611             :         {
    1612           0 :             VSIFCloseL(fp);
    1613           0 :             VSIFree(pdfData);
    1614           0 :             CPLError(CE_Failure, CPLE_FileIO,
    1615             :                      "Unable to finish write of grid row. Disk full?");
    1616           0 :             return nullptr;
    1617             :         }
    1618             : 
    1619         164 :         if (!pfnProgress(static_cast<double>(iRow + 1) / nYSize, nullptr,
    1620             :                          pProgressData))
    1621             :         {
    1622           0 :             VSIFCloseL(fp);
    1623           0 :             VSIFree(pdfData);
    1624           0 :             CPLError(CE_Failure, CPLE_UserInterrupt, "User terminated");
    1625           0 :             return nullptr;
    1626             :         }
    1627             :     }
    1628             : 
    1629          12 :     VSIFree(pdfData);
    1630             : 
    1631             :     /* write out the min and max values */
    1632          24 :     std::ostringstream ssRange;
    1633          12 :     ssRange.precision(nFIELD_PRECISION);
    1634          12 :     ssRange.setf(std::ios::uppercase);
    1635          12 :     ssRange << dfMin << " " << dfMax << "\x0D\x0A";
    1636          12 :     if (ssRange.str().length() != nDummyRangeLen)
    1637             :     {
    1638          12 :         int nShiftSize = static_cast<int>(ssRange.str().length()) -
    1639          12 :                          static_cast<int>(nDummyRangeLen);
    1640          12 :         if (ShiftFileContents(fp, nRangeStart + nDummyRangeLen, nShiftSize,
    1641          12 :                               "\x0D\x0A") != CE_None)
    1642             :         {
    1643           0 :             VSIFCloseL(fp);
    1644           0 :             CPLError(CE_Failure, CPLE_FileIO, "Unable to shift file contents.");
    1645           0 :             return nullptr;
    1646             :         }
    1647             :     }
    1648             : 
    1649          12 :     if (VSIFSeekL(fp, nRangeStart, SEEK_SET) != 0)
    1650             :     {
    1651           0 :         VSIFCloseL(fp);
    1652           0 :         CPLError(CE_Failure, CPLE_FileIO,
    1653             :                  "Unable to seek to start of grid file copy.");
    1654           0 :         return nullptr;
    1655             :     }
    1656             : 
    1657          24 :     if (VSIFWriteL((void *)ssRange.str().c_str(), 1, ssRange.str().length(),
    1658          12 :                    fp) != ssRange.str().length())
    1659             :     {
    1660           0 :         VSIFCloseL(fp);
    1661           0 :         CPLError(CE_Failure, CPLE_FileIO, "Unable to write range information.");
    1662           0 :         return nullptr;
    1663             :     }
    1664             : 
    1665          12 :     VSIFCloseL(fp);
    1666             : 
    1667          12 :     GDALOpenInfo oOpenInfo(pszFilename, GA_Update);
    1668          12 :     GDALPamDataset *poDS = OpenPAM(&oOpenInfo);
    1669          12 :     if (poDS)
    1670             :     {
    1671          12 :         poDS->CloneInfo(poSrcDS, GCIF_PAM_DEFAULT);
    1672             :     }
    1673          12 :     return poDS;
    1674             : }
    1675             : 
    1676             : /************************************************************************/
    1677             : /*                         GDALRegister_GSAG()                          */
    1678             : /************************************************************************/
    1679             : 
    1680        2135 : void GDALRegister_GSAG()
    1681             : 
    1682             : {
    1683        2135 :     if (GDALGetDriverByName("GSAG") != nullptr)
    1684         263 :         return;
    1685             : 
    1686        1872 :     GDALDriver *poDriver = new GDALDriver();
    1687             : 
    1688        1872 :     poDriver->SetDescription("GSAG");
    1689        1872 :     poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
    1690        1872 :     poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
    1691        1872 :                               "Golden Software ASCII Grid (.grd)");
    1692        1872 :     poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/gsag.html");
    1693        1872 :     poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "grd");
    1694        1872 :     poDriver->SetMetadataItem(GDAL_DMD_CREATIONDATATYPES,
    1695             :                               "Byte Int16 UInt16 Int32 UInt32 "
    1696        1872 :                               "Float32 Float64");
    1697        1872 :     poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
    1698             : 
    1699        1872 :     poDriver->pfnIdentify = GSAGDataset::Identify;
    1700        1872 :     poDriver->pfnOpen = GSAGDataset::Open;
    1701        1872 :     poDriver->pfnCreateCopy = GSAGDataset::CreateCopy;
    1702             : 
    1703        1872 :     GetGDALDriverManager()->RegisterDriver(poDriver);
    1704             : }

Generated by: LCOV version 1.14