LCOV - code coverage report
Current view: top level - frmts/aaigrid - aaigriddataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 710 852 83.3 %
Date: 2025-08-01 10:10:57 Functions: 33 34 97.1 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GDAL
       4             :  * Purpose:  Implements Arc/Info ASCII Grid Format.
       5             :  * Author:   Frank Warmerdam, warmerdam@pobox.com
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2001, Frank Warmerdam (warmerdam@pobox.com)
       9             :  * Copyright (c) 2007-2012, Even Rouault <even dot rouault at spatialys.com>
      10             :  * Copyright (c) 2014, Kyle Shannon <kyle at pobox dot com>
      11             :  *
      12             :  * SPDX-License-Identifier: MIT
      13             :  ****************************************************************************/
      14             : 
      15             : // We need cpl_port as first include to avoid VSIStatBufL being not
      16             : // defined on i586-mingw32msvc.
      17             : #include "cpl_port.h"
      18             : #include "aaigriddataset.h"
      19             : #include "gdal_frmts.h"
      20             : 
      21             : #include <cassert>
      22             : #include <cctype>
      23             : #include <climits>
      24             : #include <cmath>
      25             : #include <cstddef>
      26             : #include <cstdio>
      27             : #include <cstdlib>
      28             : #include <cstring>
      29             : #if HAVE_FCNTL_H
      30             : #include <fcntl.h>
      31             : #endif
      32             : 
      33             : #include <algorithm>
      34             : #include <cinttypes>
      35             : #include <limits>
      36             : #include <string>
      37             : 
      38             : #include "cpl_conv.h"
      39             : #include "cpl_error.h"
      40             : #include "cpl_progress.h"
      41             : #include "cpl_string.h"
      42             : #include "cpl_vsi.h"
      43             : #include "gdal.h"
      44             : #include "gdal_pam.h"
      45             : #include "gdal_priv.h"
      46             : #include "ogr_core.h"
      47             : #include "ogr_spatialref.h"
      48             : 
      49             : namespace
      50             : {
      51             : 
      52        2643 : float DoubleToFloatClamp(double dfValue)
      53             : {
      54        2643 :     if (dfValue <= std::numeric_limits<float>::lowest())
      55           0 :         return std::numeric_limits<float>::lowest();
      56        2643 :     if (dfValue >= std::numeric_limits<float>::max())
      57           0 :         return std::numeric_limits<float>::max();
      58        2643 :     return static_cast<float>(dfValue);
      59             : }
      60             : 
      61             : // Cast to float and back for make sure the NoData value matches
      62             : // that expressed by a float value.  Clamps to the range of a float
      63             : // if the value is too large.  Preserves +/-inf and NaN.
      64             : // TODO(schwehr): This should probably be moved to port as it is likely
      65             : // to be needed for other formats.
      66          17 : double MapNoDataToFloat(double dfNoDataValue)
      67             : {
      68          17 :     if (std::isinf(dfNoDataValue) || std::isnan(dfNoDataValue))
      69           3 :         return dfNoDataValue;
      70             : 
      71          14 :     if (dfNoDataValue >= std::numeric_limits<float>::max())
      72           0 :         return std::numeric_limits<float>::max();
      73             : 
      74          14 :     if (dfNoDataValue <= -std::numeric_limits<float>::max())
      75           0 :         return -std::numeric_limits<float>::max();
      76             : 
      77          14 :     return static_cast<double>(static_cast<float>(dfNoDataValue));
      78             : }
      79             : 
      80             : }  // namespace
      81             : 
      82             : static CPLString OSR_GDS(char **papszNV, const char *pszField,
      83             :                          const char *pszDefaultValue);
      84             : 
      85             : /************************************************************************/
      86             : /*                           AAIGRasterBand()                           */
      87             : /************************************************************************/
      88             : 
      89         208 : AAIGRasterBand::AAIGRasterBand(AAIGDataset *poDSIn, int nDataStart)
      90         208 :     : panLineOffset(nullptr)
      91             : {
      92         208 :     poDS = poDSIn;
      93             : 
      94         208 :     nBand = 1;
      95         208 :     eDataType = poDSIn->eDataType;
      96             : 
      97         208 :     nBlockXSize = poDSIn->nRasterXSize;
      98         208 :     nBlockYSize = 1;
      99             : 
     100         208 :     panLineOffset = static_cast<GUIntBig *>(
     101         208 :         VSI_CALLOC_VERBOSE(poDSIn->nRasterYSize, sizeof(GUIntBig)));
     102         208 :     if (panLineOffset == nullptr)
     103             :     {
     104           0 :         return;
     105             :     }
     106         208 :     panLineOffset[0] = nDataStart;
     107             : }
     108             : 
     109             : /************************************************************************/
     110             : /*                          ~AAIGRasterBand()                           */
     111             : /************************************************************************/
     112             : 
     113         416 : AAIGRasterBand::~AAIGRasterBand()
     114             : {
     115         208 :     CPLFree(panLineOffset);
     116         416 : }
     117             : 
     118             : /************************************************************************/
     119             : /*                             IReadBlock()                             */
     120             : /************************************************************************/
     121             : 
     122        1125 : CPLErr AAIGRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff, void *pImage)
     123             : 
     124             : {
     125        1125 :     AAIGDataset *poODS = static_cast<AAIGDataset *>(poDS);
     126             : 
     127        1125 :     if (nBlockYOff < 0 || nBlockYOff > poODS->nRasterYSize - 1 ||
     128        1125 :         nBlockXOff != 0 || panLineOffset == nullptr || poODS->fp == nullptr)
     129           0 :         return CE_Failure;
     130             : 
     131        1125 :     if (panLineOffset[nBlockYOff] == 0)
     132             :     {
     133          22 :         for (int iPrevLine = 1; iPrevLine <= nBlockYOff; iPrevLine++)
     134          18 :             if (panLineOffset[iPrevLine] == 0)
     135          18 :                 IReadBlock(nBlockXOff, iPrevLine - 1, nullptr);
     136             :     }
     137             : 
     138        1125 :     if (panLineOffset[nBlockYOff] == 0)
     139           0 :         return CE_Failure;
     140             : 
     141        1125 :     if (poODS->Seek(panLineOffset[nBlockYOff]) != 0)
     142             :     {
     143           0 :         ReportError(CE_Failure, CPLE_FileIO,
     144             :                     "Can't seek to offset %lu in input file to read data.",
     145           0 :                     static_cast<long unsigned int>(panLineOffset[nBlockYOff]));
     146           0 :         return CE_Failure;
     147             :     }
     148             : 
     149       34438 :     for (int iPixel = 0; iPixel < poODS->nRasterXSize;)
     150             :     {
     151             :         // Suck up any pre-white space.
     152       33313 :         char chNext = '\0';
     153       11096 :         do
     154             :         {
     155       44409 :             chNext = poODS->Getc();
     156       44409 :         } while (isspace(static_cast<unsigned char>(chNext)));
     157             : 
     158       33313 :         char szToken[500] = {'\0'};
     159       33313 :         int iTokenChar = 0;
     160      134727 :         while (chNext != '\0' && !isspace(static_cast<unsigned char>(chNext)))
     161             :         {
     162      101414 :             if (iTokenChar == sizeof(szToken) - 2)
     163             :             {
     164           0 :                 ReportError(CE_Failure, CPLE_FileIO,
     165             :                             "Token too long at scanline %d.", nBlockYOff);
     166           0 :                 return CE_Failure;
     167             :             }
     168             : 
     169      101414 :             szToken[iTokenChar++] = chNext;
     170      101414 :             chNext = poODS->Getc();
     171             :         }
     172             : 
     173       33313 :         if (chNext == '\0' && (iPixel != poODS->nRasterXSize - 1 ||
     174          56 :                                nBlockYOff != poODS->nRasterYSize - 1))
     175             :         {
     176           0 :             ReportError(CE_Failure, CPLE_FileIO,
     177             :                         "File short, can't read line %d.", nBlockYOff);
     178           0 :             return CE_Failure;
     179             :         }
     180             : 
     181       33313 :         szToken[iTokenChar] = '\0';
     182             : 
     183       33313 :         if (pImage != nullptr)
     184             :         {
     185             :             // "null" seems to be specific of D12 software
     186             :             // See https://github.com/OSGeo/gdal/issues/5095
     187       33115 :             if (eDataType == GDT_Float64)
     188             :             {
     189         104 :                 if (strcmp(szToken, "null") == 0)
     190           2 :                     reinterpret_cast<double *>(pImage)[iPixel] =
     191           2 :                         -std::numeric_limits<double>::max();
     192             :                 else
     193         204 :                     reinterpret_cast<double *>(pImage)[iPixel] =
     194         102 :                         CPLAtofM(szToken);
     195             :             }
     196       33011 :             else if (eDataType == GDT_Float32)
     197             :             {
     198        2645 :                 if (strcmp(szToken, "null") == 0)
     199           2 :                     reinterpret_cast<float *>(pImage)[iPixel] =
     200           2 :                         -std::numeric_limits<float>::max();
     201             :                 else
     202        2643 :                     reinterpret_cast<float *>(pImage)[iPixel] =
     203        2643 :                         DoubleToFloatClamp(CPLAtofM(szToken));
     204             :             }
     205             :             else
     206       30366 :                 reinterpret_cast<GInt32 *>(pImage)[iPixel] =
     207       30366 :                     static_cast<GInt32>(atoi(szToken));
     208             :         }
     209             : 
     210       33313 :         iPixel++;
     211             :     }
     212             : 
     213        1125 :     if (nBlockYOff < poODS->nRasterYSize - 1)
     214         992 :         panLineOffset[nBlockYOff + 1] = poODS->Tell();
     215             : 
     216        1125 :     return CE_None;
     217             : }
     218             : 
     219             : /************************************************************************/
     220             : /*                           GetNoDataValue()                           */
     221             : /************************************************************************/
     222             : 
     223         286 : double AAIGRasterBand::GetNoDataValue(int *pbSuccess)
     224             : 
     225             : {
     226         286 :     AAIGDataset *poODS = static_cast<AAIGDataset *>(poDS);
     227             : 
     228         286 :     if (pbSuccess)
     229         259 :         *pbSuccess = poODS->bNoDataSet;
     230             : 
     231         286 :     return poODS->dfNoDataValue;
     232             : }
     233             : 
     234             : /************************************************************************/
     235             : /*                           SetNoDataValue()                           */
     236             : /************************************************************************/
     237             : 
     238           0 : CPLErr AAIGRasterBand::SetNoDataValue(double dfNoData)
     239             : 
     240             : {
     241           0 :     AAIGDataset *poODS = static_cast<AAIGDataset *>(poDS);
     242             : 
     243           0 :     poODS->bNoDataSet = true;
     244           0 :     poODS->dfNoDataValue = dfNoData;
     245             : 
     246           0 :     return CE_None;
     247             : }
     248             : 
     249             : /************************************************************************/
     250             : /* ==================================================================== */
     251             : /*                            AAIGDataset                               */
     252             : /* ==================================================================== */
     253             : /************************************************************************/
     254             : 
     255             : /************************************************************************/
     256             : /*                            AAIGDataset()                            */
     257             : /************************************************************************/
     258             : 
     259         210 : AAIGDataset::AAIGDataset()
     260             :     : fp(nullptr), papszPrj(nullptr), nBufferOffset(0), nOffsetInBuffer(256),
     261         210 :       eDataType(GDT_Int32), bNoDataSet(false), dfNoDataValue(-9999.0)
     262             : {
     263         210 :     m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     264         210 :     memset(achReadBuf, 0, sizeof(achReadBuf));
     265         210 : }
     266             : 
     267             : /************************************************************************/
     268             : /*                           ~AAIGDataset()                            */
     269             : /************************************************************************/
     270             : 
     271         410 : AAIGDataset::~AAIGDataset()
     272             : 
     273             : {
     274         210 :     FlushCache(true);
     275             : 
     276         210 :     if (fp != nullptr)
     277             :     {
     278         209 :         if (VSIFCloseL(fp) != 0)
     279             :         {
     280           0 :             ReportError(CE_Failure, CPLE_FileIO, "I/O error");
     281             :         }
     282             :     }
     283             : 
     284         210 :     CSLDestroy(papszPrj);
     285         410 : }
     286             : 
     287             : /************************************************************************/
     288             : /*                                Tell()                                */
     289             : /************************************************************************/
     290             : 
     291         992 : GUIntBig AAIGDataset::Tell() const
     292             : {
     293         992 :     return nBufferOffset + nOffsetInBuffer;
     294             : }
     295             : 
     296             : /************************************************************************/
     297             : /*                                Seek()                                */
     298             : /************************************************************************/
     299             : 
     300        1125 : int AAIGDataset::Seek(GUIntBig nNewOffset)
     301             : 
     302             : {
     303        1125 :     nOffsetInBuffer = sizeof(achReadBuf);
     304        1125 :     return VSIFSeekL(fp, nNewOffset, SEEK_SET);
     305             : }
     306             : 
     307             : /************************************************************************/
     308             : /*                                Getc()                                */
     309             : /*                                                                      */
     310             : /*      Read a single character from the input file (efficiently we     */
     311             : /*      hope).                                                          */
     312             : /************************************************************************/
     313             : 
     314      145823 : char AAIGDataset::Getc()
     315             : 
     316             : {
     317      145823 :     if (nOffsetInBuffer < static_cast<int>(sizeof(achReadBuf)))
     318      144498 :         return achReadBuf[nOffsetInBuffer++];
     319             : 
     320        1325 :     nBufferOffset = VSIFTellL(fp);
     321             :     const int nRead =
     322        1325 :         static_cast<int>(VSIFReadL(achReadBuf, 1, sizeof(achReadBuf), fp));
     323       83131 :     for (unsigned int i = nRead; i < sizeof(achReadBuf); i++)
     324       81806 :         achReadBuf[i] = '\0';
     325             : 
     326        1325 :     nOffsetInBuffer = 0;
     327             : 
     328        1325 :     return achReadBuf[nOffsetInBuffer++];
     329             : }
     330             : 
     331             : /************************************************************************/
     332             : /*                            GetFileList()                             */
     333             : /************************************************************************/
     334             : 
     335          32 : char **AAIGDataset::GetFileList()
     336             : 
     337             : {
     338          32 :     char **papszFileList = GDALPamDataset::GetFileList();
     339             : 
     340          32 :     if (papszPrj != nullptr)
     341          12 :         papszFileList = CSLAddString(papszFileList, osPrjFilename);
     342             : 
     343          32 :     return papszFileList;
     344             : }
     345             : 
     346             : /************************************************************************/
     347             : /*                            Identify()                                */
     348             : /************************************************************************/
     349             : 
     350       63714 : int AAIGDataset::Identify(GDALOpenInfo *poOpenInfo)
     351             : 
     352             : {
     353             :     // Does this look like an AI grid file?
     354       63714 :     if (poOpenInfo->nHeaderBytes < 40 ||
     355        7898 :         !(STARTS_WITH_CI(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
     356        7493 :                          "ncols") ||
     357        7493 :           STARTS_WITH_CI(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
     358        7493 :                          "nrows") ||
     359        7493 :           STARTS_WITH_CI(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
     360        7493 :                          "xllcorner") ||
     361        7493 :           STARTS_WITH_CI(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
     362        7493 :                          "yllcorner") ||
     363        7493 :           STARTS_WITH_CI(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
     364        7493 :                          "xllcenter") ||
     365        7493 :           STARTS_WITH_CI(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
     366        7493 :                          "yllcenter") ||
     367        7493 :           STARTS_WITH_CI(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
     368        7493 :                          "dx") ||
     369        7493 :           STARTS_WITH_CI(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
     370             :                          "dy") ||
     371        7493 :           STARTS_WITH_CI(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
     372             :                          "cellsize")))
     373       63309 :         return FALSE;
     374             : 
     375         405 :     return TRUE;
     376             : }
     377             : 
     378             : /************************************************************************/
     379             : /*                            Identify()                                */
     380             : /************************************************************************/
     381             : 
     382       63263 : int GRASSASCIIDataset::Identify(GDALOpenInfo *poOpenInfo)
     383             : 
     384             : {
     385             :     // Does this look like a GRASS ASCII grid file?
     386       63263 :     if (poOpenInfo->nHeaderBytes < 40 ||
     387        7496 :         !(STARTS_WITH_CI(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
     388        7492 :                          "north:") ||
     389        7492 :           STARTS_WITH_CI(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
     390        7492 :                          "south:") ||
     391        7492 :           STARTS_WITH_CI(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
     392        7492 :                          "east:") ||
     393        7492 :           STARTS_WITH_CI(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
     394        7492 :                          "west:") ||
     395        7492 :           STARTS_WITH_CI(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
     396             :                          "rows:") ||
     397        7492 :           STARTS_WITH_CI(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
     398             :                          "cols:")))
     399       63259 :         return FALSE;
     400             : 
     401           4 :     return TRUE;
     402             : }
     403             : 
     404             : /************************************************************************/
     405             : /*                            Identify()                                */
     406             : /************************************************************************/
     407             : 
     408       63267 : int ISGDataset::Identify(GDALOpenInfo *poOpenInfo)
     409             : 
     410             : {
     411             :     // Does this look like a ISG grid file?
     412       63267 :     if (poOpenInfo->nHeaderBytes < 40 ||
     413        7500 :         !strstr(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
     414             :                 "model name"))
     415             :     {
     416       63251 :         return FALSE;
     417             :     }
     418          17 :     for (int i = 0; i < 2; ++i)
     419             :     {
     420          17 :         if (strstr(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
     421          17 :                    "lat min") != nullptr &&
     422          17 :             strstr(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
     423          17 :                    "lat max") != nullptr &&
     424          17 :             strstr(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
     425          17 :                    "lon min") != nullptr &&
     426          17 :             strstr(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
     427          17 :                    "lon max") != nullptr &&
     428          17 :             strstr(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
     429          16 :                    "nrows") != nullptr &&
     430          16 :             strstr(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
     431             :                    "ncols") != nullptr)
     432             :         {
     433          16 :             return TRUE;
     434             :         }
     435             :         // Some files like https://isgeoid.polimi.it/Geoid/Europe/Slovenia/public/Slovenia_2016_SLO_VRP2016_Koper_hybrQ_20221122.isg
     436             :         // have initial comment lines, so we may need to ingest more bytes
     437           1 :         if (i == 0)
     438             :         {
     439           1 :             if (poOpenInfo->nHeaderBytes >= 8192)
     440           0 :                 break;
     441           1 :             poOpenInfo->TryToIngest(8192);
     442             :         }
     443             :     }
     444             : 
     445           0 :     return TRUE;
     446             : }
     447             : 
     448             : /************************************************************************/
     449             : /*                                Open()                                */
     450             : /************************************************************************/
     451             : 
     452         200 : GDALDataset *AAIGDataset::Open(GDALOpenInfo *poOpenInfo)
     453             : {
     454             : #ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
     455             :     // During fuzzing, do not use Identify to reject crazy content.
     456         200 :     if (!Identify(poOpenInfo))
     457           0 :         return nullptr;
     458             : #endif
     459             : 
     460         200 :     return CommonOpen(poOpenInfo, FORMAT_AAIG);
     461             : }
     462             : 
     463             : /************************************************************************/
     464             : /*                          ParseHeader()                               */
     465             : /************************************************************************/
     466             : 
     467         200 : bool AAIGDataset::ParseHeader(const char *pszHeader, const char *pszDataType)
     468             : {
     469         400 :     const CPLStringList aosTokens(CSLTokenizeString2(pszHeader, " \n\r\t", 0));
     470         200 :     const int nTokens = aosTokens.size();
     471             : 
     472         200 :     int i = 0;
     473         200 :     if ((i = aosTokens.FindString("ncols")) < 0 || i + 1 >= nTokens)
     474             :     {
     475           0 :         return false;
     476             :     }
     477         200 :     nRasterXSize = atoi(aosTokens[i + 1]);
     478         200 :     if ((i = aosTokens.FindString("nrows")) < 0 || i + 1 >= nTokens)
     479             :     {
     480           0 :         return false;
     481             :     }
     482         200 :     nRasterYSize = atoi(aosTokens[i + 1]);
     483             : 
     484         200 :     if (!GDALCheckDatasetDimensions(nRasterXSize, nRasterYSize))
     485             :     {
     486           0 :         return false;
     487             :     }
     488             : 
     489         200 :     double dfCellDX = 0.0;
     490         200 :     double dfCellDY = 0.0;
     491         200 :     if ((i = aosTokens.FindString("cellsize")) < 0)
     492             :     {
     493             :         int iDX, iDY;
     494           4 :         if ((iDX = aosTokens.FindString("dx")) < 0 ||
     495           8 :             (iDY = aosTokens.FindString("dy")) < 0 || iDX + 1 >= nTokens ||
     496           4 :             iDY + 1 >= nTokens)
     497             :         {
     498           0 :             return false;
     499             :         }
     500             : 
     501           4 :         dfCellDX = CPLAtofM(aosTokens[iDX + 1]);
     502           4 :         dfCellDY = CPLAtofM(aosTokens[iDY + 1]);
     503             :     }
     504             :     else
     505             :     {
     506         196 :         if (i + 1 >= nTokens)
     507             :         {
     508           0 :             return false;
     509             :         }
     510         196 :         dfCellDY = CPLAtofM(aosTokens[i + 1]);
     511         196 :         dfCellDX = dfCellDY;
     512             :     }
     513             : 
     514         200 :     int j = 0;
     515         200 :     if ((i = aosTokens.FindString("xllcorner")) >= 0 &&
     516         400 :         (j = aosTokens.FindString("yllcorner")) >= 0 && i + 1 < nTokens &&
     517         200 :         j + 1 < nTokens)
     518             :     {
     519         200 :         m_gt[0] = CPLAtofM(aosTokens[i + 1]);
     520             : 
     521             :         // Small hack to compensate from insufficient precision in cellsize
     522             :         // parameter in datasets of
     523             :         // http://ccafs-climate.org/data/A2a_2020s/hccpr_hadcm3
     524           0 :         if ((nRasterXSize % 360) == 0 && fabs(m_gt[0] - (-180.0)) < 1e-12 &&
     525         200 :             dfCellDX == dfCellDY &&
     526           0 :             fabs(dfCellDX - (360.0 / nRasterXSize)) < 1e-9)
     527             :         {
     528           0 :             dfCellDY = 360.0 / nRasterXSize;
     529           0 :             dfCellDX = dfCellDY;
     530             :         }
     531             : 
     532         200 :         m_gt[1] = dfCellDX;
     533         200 :         m_gt[2] = 0.0;
     534         200 :         m_gt[3] = CPLAtofM(aosTokens[j + 1]) + nRasterYSize * dfCellDY;
     535         200 :         m_gt[4] = 0.0;
     536         200 :         m_gt[5] = -dfCellDY;
     537             :     }
     538           0 :     else if ((i = aosTokens.FindString("xllcenter")) >= 0 &&
     539           0 :              (j = aosTokens.FindString("yllcenter")) >= 0 && i + 1 < nTokens &&
     540           0 :              j + 1 < nTokens)
     541             :     {
     542           0 :         SetMetadataItem(GDALMD_AREA_OR_POINT, GDALMD_AOP_POINT);
     543             : 
     544           0 :         m_gt[0] = CPLAtofM(aosTokens[i + 1]) - 0.5 * dfCellDX;
     545           0 :         m_gt[1] = dfCellDX;
     546           0 :         m_gt[2] = 0.0;
     547           0 :         m_gt[3] = CPLAtofM(aosTokens[j + 1]) - 0.5 * dfCellDY +
     548           0 :                   nRasterYSize * dfCellDY;
     549           0 :         m_gt[4] = 0.0;
     550           0 :         m_gt[5] = -dfCellDY;
     551             :     }
     552             :     else
     553             :     {
     554           0 :         m_gt[0] = 0.0;
     555           0 :         m_gt[1] = dfCellDX;
     556           0 :         m_gt[2] = 0.0;
     557           0 :         m_gt[3] = 0.0;
     558           0 :         m_gt[4] = 0.0;
     559           0 :         m_gt[5] = -dfCellDY;
     560             :     }
     561             : 
     562         200 :     if ((i = aosTokens.FindString("NODATA_value")) >= 0 && i + 1 < nTokens)
     563             :     {
     564          79 :         const char *pszNoData = aosTokens[i + 1];
     565             : 
     566          79 :         bNoDataSet = true;
     567          79 :         if (strcmp(pszNoData, "null") == 0)
     568             :         {
     569             :             // "null" seems to be specific of D12 software
     570             :             // See https://github.com/OSGeo/gdal/issues/5095
     571           2 :             if (pszDataType == nullptr || eDataType == GDT_Float32)
     572             :             {
     573           1 :                 dfNoDataValue = -std::numeric_limits<float>::max();
     574           1 :                 eDataType = GDT_Float32;
     575             :             }
     576             :             else
     577             :             {
     578           1 :                 dfNoDataValue = -std::numeric_limits<double>::max();
     579           1 :                 eDataType = GDT_Float64;
     580             :             }
     581             :         }
     582             :         else
     583             :         {
     584          77 :             dfNoDataValue = CPLAtofM(pszNoData);
     585         149 :             if (pszDataType == nullptr &&
     586          72 :                 (strchr(pszNoData, '.') != nullptr ||
     587         128 :                  strchr(pszNoData, ',') != nullptr ||
     588          64 :                  std::isnan(dfNoDataValue) ||
     589          61 :                  std::numeric_limits<int>::min() > dfNoDataValue ||
     590          61 :                  dfNoDataValue > std::numeric_limits<int>::max()))
     591             :             {
     592          11 :                 eDataType = GDT_Float32;
     593          22 :                 if (!std::isinf(dfNoDataValue) &&
     594          11 :                     (fabs(dfNoDataValue) < std::numeric_limits<float>::min() ||
     595          10 :                      fabs(dfNoDataValue) > std::numeric_limits<float>::max()))
     596             :                 {
     597           1 :                     eDataType = GDT_Float64;
     598             :                 }
     599             :             }
     600          77 :             if (eDataType == GDT_Float32)
     601             :             {
     602          10 :                 dfNoDataValue = MapNoDataToFloat(dfNoDataValue);
     603             :             }
     604             :         }
     605             :     }
     606             : 
     607         200 :     return true;
     608             : }
     609             : 
     610             : /************************************************************************/
     611             : /*                                Open()                                */
     612             : /************************************************************************/
     613             : 
     614           2 : GDALDataset *GRASSASCIIDataset::Open(GDALOpenInfo *poOpenInfo)
     615             : {
     616             : #ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
     617             :     // During fuzzing, do not use Identify to reject crazy content.
     618           2 :     if (!Identify(poOpenInfo))
     619           0 :         return nullptr;
     620             : #endif
     621             : 
     622           2 :     return CommonOpen(poOpenInfo, FORMAT_GRASSASCII);
     623             : }
     624             : 
     625             : /************************************************************************/
     626             : /*                          ParseHeader()                               */
     627             : /************************************************************************/
     628             : 
     629           2 : bool GRASSASCIIDataset::ParseHeader(const char *pszHeader,
     630             :                                     const char *pszDataType)
     631             : {
     632           4 :     const CPLStringList aosTokens(CSLTokenizeString2(pszHeader, " \n\r\t:", 0));
     633           2 :     const int nTokens = aosTokens.size();
     634           2 :     int i = 0;
     635           2 :     if ((i = aosTokens.FindString("cols")) < 0 || i + 1 >= nTokens)
     636             :     {
     637           0 :         return false;
     638             :     }
     639           2 :     nRasterXSize = atoi(aosTokens[i + 1]);
     640           2 :     if ((i = aosTokens.FindString("rows")) < 0 || i + 1 >= nTokens)
     641             :     {
     642           0 :         return false;
     643             :     }
     644           2 :     nRasterYSize = atoi(aosTokens[i + 1]);
     645             : 
     646           2 :     if (!GDALCheckDatasetDimensions(nRasterXSize, nRasterYSize))
     647             :     {
     648           0 :         return false;
     649             :     }
     650             : 
     651           2 :     const int iNorth = aosTokens.FindString("north");
     652           2 :     const int iSouth = aosTokens.FindString("south");
     653           2 :     const int iEast = aosTokens.FindString("east");
     654           2 :     const int iWest = aosTokens.FindString("west");
     655             : 
     656           4 :     if (iNorth == -1 || iSouth == -1 || iEast == -1 || iWest == -1 ||
     657           2 :         std::max(std::max(iNorth, iSouth), std::max(iEast, iWest)) + 1 >=
     658             :             nTokens)
     659             :     {
     660           0 :         return false;
     661             :     }
     662             : 
     663           2 :     const double dfNorth = CPLAtofM(aosTokens[iNorth + 1]);
     664           2 :     const double dfSouth = CPLAtofM(aosTokens[iSouth + 1]);
     665           2 :     const double dfEast = CPLAtofM(aosTokens[iEast + 1]);
     666           2 :     const double dfWest = CPLAtofM(aosTokens[iWest + 1]);
     667           2 :     const double dfPixelXSize = (dfEast - dfWest) / nRasterXSize;
     668           2 :     const double dfPixelYSize = (dfNorth - dfSouth) / nRasterYSize;
     669             : 
     670           2 :     m_gt[0] = dfWest;
     671           2 :     m_gt[1] = dfPixelXSize;
     672           2 :     m_gt[2] = 0.0;
     673           2 :     m_gt[3] = dfNorth;
     674           2 :     m_gt[4] = 0.0;
     675           2 :     m_gt[5] = -dfPixelYSize;
     676             : 
     677           2 :     if ((i = aosTokens.FindString("null")) >= 0 && i + 1 < nTokens)
     678             :     {
     679           0 :         const char *pszNoData = aosTokens[i + 1];
     680             : 
     681           0 :         bNoDataSet = true;
     682           0 :         dfNoDataValue = CPLAtofM(pszNoData);
     683           0 :         if (pszDataType == nullptr &&
     684           0 :             (strchr(pszNoData, '.') != nullptr ||
     685           0 :              strchr(pszNoData, ',') != nullptr || std::isnan(dfNoDataValue) ||
     686           0 :              std::numeric_limits<int>::min() > dfNoDataValue ||
     687           0 :              dfNoDataValue > std::numeric_limits<int>::max()))
     688             :         {
     689           0 :             eDataType = GDT_Float32;
     690             :         }
     691           0 :         if (eDataType == GDT_Float32)
     692             :         {
     693           0 :             dfNoDataValue = MapNoDataToFloat(dfNoDataValue);
     694             :         }
     695             :     }
     696             : 
     697           2 :     if ((i = aosTokens.FindString("type")) >= 0 && i + 1 < nTokens)
     698             :     {
     699           0 :         const char *pszType = aosTokens[i + 1];
     700           0 :         if (EQUAL(pszType, "int"))
     701           0 :             eDataType = GDT_Int32;
     702           0 :         else if (EQUAL(pszType, "float"))
     703           0 :             eDataType = GDT_Float32;
     704           0 :         else if (EQUAL(pszType, "double"))
     705           0 :             eDataType = GDT_Float64;
     706             :         else
     707             :         {
     708           0 :             ReportError(CE_Warning, CPLE_AppDefined,
     709             :                         "Invalid value for type parameter : %s", pszType);
     710             :         }
     711             :     }
     712             : 
     713           2 :     return true;
     714             : }
     715             : 
     716             : /************************************************************************/
     717             : /*                                Open()                                */
     718             : /************************************************************************/
     719             : 
     720           8 : GDALDataset *ISGDataset::Open(GDALOpenInfo *poOpenInfo)
     721             : {
     722             : #ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
     723             :     // During fuzzing, do not use Identify to reject crazy content.
     724           8 :     if (!Identify(poOpenInfo))
     725           0 :         return nullptr;
     726             : #endif
     727             : 
     728           8 :     return CommonOpen(poOpenInfo, FORMAT_ISG);
     729             : }
     730             : 
     731             : /************************************************************************/
     732             : /*                          ParseHeader()                               */
     733             : /************************************************************************/
     734             : 
     735           8 : bool ISGDataset::ParseHeader(const char *pszHeader, const char *)
     736             : {
     737             :     // See https://www.isgeoid.polimi.it/Geoid/ISG_format_v10_20160121.pdf
     738             :     //     https://www.isgeoid.polimi.it/Geoid/ISG_format_v101_20180915.pdf
     739             :     //     https://www.isgeoid.polimi.it/Geoid/ISG_format_v20_20200625.pdf
     740             : 
     741          16 :     const CPLStringList aosLines(CSLTokenizeString2(pszHeader, "\n\r", 0));
     742          16 :     CPLString osLatMin;
     743          16 :     CPLString osLatMax;
     744          16 :     CPLString osLonMin;
     745          16 :     CPLString osLonMax;
     746          16 :     CPLString osDeltaLat;
     747          16 :     CPLString osDeltaLon;
     748          16 :     CPLString osRows;
     749          16 :     CPLString osCols;
     750          16 :     CPLString osNodata;
     751          16 :     std::string osISGFormat;
     752          16 :     std::string osDataFormat;    // ISG 2.0
     753          16 :     std::string osDataOrdering;  // ISG 2.0
     754          16 :     std::string osCoordType;     // ISG 2.0
     755          16 :     std::string osCoordUnits;    // ISG 2.0
     756         180 :     for (int iLine = 0; iLine < aosLines.size(); iLine++)
     757             :     {
     758             :         const CPLStringList aosTokens(
     759         344 :             CSLTokenizeString2(aosLines[iLine], ":=", 0));
     760         172 :         if (aosTokens.size() == 2)
     761             :         {
     762         280 :             const CPLString osLeft(CPLString(aosTokens[0]).Trim());
     763         280 :             CPLString osRight(CPLString(aosTokens[1]).Trim());
     764         140 :             if (osLeft == "lat min")
     765           8 :                 osLatMin = std::move(osRight);
     766         132 :             else if (osLeft == "lat max")
     767           8 :                 osLatMax = std::move(osRight);
     768         124 :             else if (osLeft == "lon min")
     769           8 :                 osLonMin = std::move(osRight);
     770         116 :             else if (osLeft == "lon max")
     771           8 :                 osLonMax = std::move(osRight);
     772         108 :             else if (osLeft == "delta lat")
     773           8 :                 osDeltaLat = std::move(osRight);
     774         100 :             else if (osLeft == "delta lon")
     775           8 :                 osDeltaLon = std::move(osRight);
     776          92 :             else if (osLeft == "nrows")
     777           8 :                 osRows = std::move(osRight);
     778          84 :             else if (osLeft == "ncols")
     779           8 :                 osCols = std::move(osRight);
     780          76 :             else if (osLeft == "nodata")
     781           8 :                 osNodata = std::move(osRight);
     782          68 :             else if (osLeft == "model name")
     783           8 :                 SetMetadataItem("MODEL_NAME", osRight);
     784          60 :             else if (osLeft == "model type")
     785           8 :                 SetMetadataItem("MODEL_TYPE", osRight);
     786          52 :             else if (osLeft == "units" || osLeft == "data units")
     787           8 :                 osUnits = std::move(osRight);
     788          44 :             else if (osLeft == "ISG format")
     789           8 :                 osISGFormat = std::move(osRight);
     790          36 :             else if (osLeft == "data format")
     791           2 :                 osDataFormat = std::move(osRight);
     792          34 :             else if (osLeft == "data ordering")
     793           2 :                 osDataOrdering = std::move(osRight);
     794          32 :             else if (osLeft == "coord type")
     795           2 :                 osCoordType = std::move(osRight);
     796          30 :             else if (osLeft == "coord units")
     797           2 :                 osCoordUnits = std::move(osRight);
     798             :         }
     799             :     }
     800             :     const double dfVersion =
     801           8 :         osISGFormat.empty() ? 0.0 : CPLAtof(osISGFormat.c_str());
     802          24 :     if (osLatMin.empty() || osLatMax.empty() || osLonMin.empty() ||
     803          24 :         osLonMax.empty() || osDeltaLat.empty() || osDeltaLon.empty() ||
     804          24 :         osRows.empty() || osCols.empty())
     805             :     {
     806           0 :         return false;
     807             :     }
     808           8 :     if (!osDataFormat.empty() && osDataFormat != "grid")
     809             :     {
     810           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     811             :                  "ISG: data format = %s not supported", osDataFormat.c_str());
     812           0 :         return false;
     813             :     }
     814           8 :     if (!osDataOrdering.empty() && osDataOrdering != "N-to-S, W-to-E")
     815             :     {
     816           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     817             :                  "ISG: data ordering = %s not supported",
     818             :                  osDataOrdering.c_str());
     819           0 :         return false;
     820             :     }
     821           8 :     if (!osCoordType.empty() && osCoordType != "geodetic")
     822             :     {
     823           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     824             :                  "ISG: coord type = %s not supported", osCoordType.c_str());
     825           0 :         return false;
     826             :     }
     827             : 
     828           6 :     const auto parseDMS = [](CPLString &str)
     829             :     {
     830          12 :         const std::string degreeSymbol{"\xc2\xb0"};
     831           6 :         str.replaceAll(degreeSymbol, "D");
     832          12 :         return CPLDMSToDec(str);
     833             :     };
     834             : 
     835           8 :     bool useDMS = false;
     836           8 :     if (!osCoordUnits.empty())
     837             :     {
     838           2 :         if (osCoordUnits == "dms")
     839             :         {
     840             :             // CPLDMSToDec does not support the non ascii char for degree used in ISG.
     841             :             // just replace it with "D" to make it compatible.
     842           1 :             useDMS = true;
     843             :         }
     844           1 :         else if (osCoordUnits != "deg")
     845             :         {
     846           0 :             CPLError(CE_Failure, CPLE_NotSupported,
     847             :                      "ISG: coord units = %s not supported",
     848             :                      osCoordUnits.c_str());
     849           0 :             return false;
     850             :         }
     851             :     }
     852           8 :     double dfLatMin = useDMS ? parseDMS(osLatMin) : CPLAtof(osLatMin);
     853           8 :     double dfLatMax = useDMS ? parseDMS(osLatMax) : CPLAtof(osLatMax);
     854           8 :     double dfLonMin = useDMS ? parseDMS(osLonMin) : CPLAtof(osLonMin);
     855           8 :     double dfLonMax = useDMS ? parseDMS(osLonMax) : CPLAtof(osLonMax);
     856           8 :     double dfDeltaLon = useDMS ? parseDMS(osDeltaLon) : CPLAtof(osDeltaLon);
     857           8 :     double dfDeltaLat = useDMS ? parseDMS(osDeltaLat) : CPLAtof(osDeltaLat);
     858           8 :     if (dfVersion >= 2.0)
     859             :     {
     860           2 :         dfLatMin -= dfDeltaLat / 2.0;
     861           2 :         dfLatMax += dfDeltaLat / 2.0;
     862           2 :         dfLonMin -= dfDeltaLon / 2.0;
     863           2 :         dfLonMax += dfDeltaLon / 2.0;
     864             :     }
     865           8 :     const int nRows = atoi(osRows);
     866           8 :     const int nCols = atoi(osCols);
     867           8 :     if (nRows <= 0 || nCols <= 0 ||
     868           8 :         !(dfDeltaLat > 0 && dfDeltaLon > 0 && dfDeltaLat < 180 &&
     869           8 :           dfDeltaLon < 360))
     870             :     {
     871           0 :         return false;
     872             :     }
     873             : 
     874           8 :     if (!GDALCheckDatasetDimensions(nRows, nCols))
     875             :     {
     876           0 :         return false;
     877             :     }
     878             : 
     879             :     // Correct rounding errors.
     880             : 
     881          14 :     const auto TryRoundTo = [](double &dfDelta, double dfRoundedDelta,
     882             :                                double &dfMin, double &dfMax, int nVals,
     883             :                                double dfRelTol)
     884             :     {
     885          14 :         double dfMinTry = dfMin;
     886          14 :         double dfMaxTry = dfMax;
     887          14 :         double dfDeltaTry = dfDelta;
     888          14 :         if (dfRoundedDelta != dfDelta &&
     889           6 :             fabs(fabs(dfMin / dfRoundedDelta) -
     890           6 :                  (floor(fabs(dfMin / dfRoundedDelta)) + 0.5)) < dfRelTol &&
     891           6 :             fabs(fabs(dfMax / dfRoundedDelta) -
     892           6 :                  (floor(fabs(dfMax / dfRoundedDelta)) + 0.5)) < dfRelTol)
     893             :         {
     894             :             {
     895           5 :                 double dfVal = (floor(fabs(dfMin / dfRoundedDelta)) + 0.5) *
     896             :                                dfRoundedDelta;
     897           5 :                 dfMinTry = (dfMin < 0) ? -dfVal : dfVal;
     898             :             }
     899             :             {
     900           5 :                 double dfVal = (floor(fabs(dfMax / dfRoundedDelta)) + 0.5) *
     901             :                                dfRoundedDelta;
     902           5 :                 dfMaxTry = (dfMax < 0) ? -dfVal : dfVal;
     903             :             }
     904           5 :             dfDeltaTry = dfRoundedDelta;
     905             :         }
     906           9 :         else if (dfRoundedDelta != dfDelta &&
     907           1 :                  fabs(fabs(dfMin / dfRoundedDelta) -
     908           1 :                       (floor(fabs(dfMin / dfRoundedDelta) + 0.5) + 0.)) <
     909           0 :                      dfRelTol &&
     910           0 :                  fabs(fabs(dfMax / dfRoundedDelta) -
     911           0 :                       (floor(fabs(dfMax / dfRoundedDelta) + 0.5) + 0.)) <
     912             :                      dfRelTol)
     913             :         {
     914             :             {
     915           0 :                 double dfVal =
     916           0 :                     (floor(fabs(dfMin / dfRoundedDelta) + 0.5) + 0.) *
     917             :                     dfRoundedDelta;
     918           0 :                 dfMinTry = (dfMin < 0) ? -dfVal : dfVal;
     919             :             }
     920             :             {
     921           0 :                 double dfVal =
     922           0 :                     (floor(fabs(dfMax / dfRoundedDelta) + 0.5) + 0.) *
     923             :                     dfRoundedDelta;
     924           0 :                 dfMaxTry = (dfMax < 0) ? -dfVal : dfVal;
     925             :             }
     926           0 :             dfDeltaTry = dfRoundedDelta;
     927             :         }
     928          14 :         if (fabs(dfMinTry + dfDeltaTry * nVals - dfMaxTry) <
     929          14 :             dfRelTol * dfDeltaTry)
     930             :         {
     931          10 :             dfMin = dfMinTry;
     932          10 :             dfMax = dfMaxTry;
     933          10 :             dfDelta = dfDeltaTry;
     934          10 :             return true;
     935             :         }
     936           4 :         return false;
     937             :     };
     938             : 
     939             :     const double dfRoundedDeltaLon =
     940           8 :         (osDeltaLon == "0.0167" ||
     941           7 :          (dfDeltaLon < 1 &&
     942           7 :           fabs(1. / dfDeltaLon - floor(1. / dfDeltaLon + 0.5)) < 0.06))
     943          15 :             ? 1. / floor(1. / dfDeltaLon + 0.5)
     944           8 :             : dfDeltaLon;
     945             : 
     946             :     const double dfRoundedDeltaLat =
     947           8 :         (osDeltaLat == "0.0167" ||
     948           4 :          (dfDeltaLat < 1 &&
     949           4 :           fabs(1. / dfDeltaLat - floor(1. / dfDeltaLat + 0.5)) < 0.06))
     950          12 :             ? 1. / floor(1. / dfDeltaLat + 0.5)
     951           8 :             : dfDeltaLat;
     952             : 
     953           8 :     bool bOK = TryRoundTo(dfDeltaLon, dfRoundedDeltaLon, dfLonMin, dfLonMax,
     954          12 :                           nCols, 1e-2) &&
     955           4 :                TryRoundTo(dfDeltaLat, dfRoundedDeltaLat, dfLatMin, dfLatMax,
     956           8 :                           nRows, 1e-2);
     957           8 :     if (!bOK && osDeltaLon == "0.0167" && osDeltaLat == "0.0167")
     958             :     {
     959             :         // For https://www.isgeoid.polimi.it/Geoid/America/Argentina/public/GEOIDEAR16_20160419.isg
     960           1 :         bOK =
     961           2 :             TryRoundTo(dfDeltaLon, 0.016667, dfLonMin, dfLonMax, nCols, 1e-1) &&
     962           1 :             TryRoundTo(dfDeltaLat, 0.016667, dfLatMin, dfLatMax, nRows, 1e-1);
     963             :     }
     964           8 :     if (!bOK)
     965             :     {
     966             :         // 0.005 is what would be needed for the above GEOIDEAR16_20160419.isg
     967             :         // file without the specific fine tuning done.
     968           6 :         if ((fabs((dfLonMax - dfLonMin) / nCols - dfDeltaLon) <
     969           4 :                  0.005 * dfDeltaLon &&
     970           1 :              fabs((dfLatMax - dfLatMin) / nRows - dfDeltaLat) <
     971           5 :                  0.005 * dfDeltaLat) ||
     972           2 :             CPLTestBool(
     973             :                 CPLGetConfigOption("ISG_SKIP_GEOREF_CONSISTENCY_CHECK", "NO")))
     974             :         {
     975           2 :             CPLError(CE_Warning, CPLE_AppDefined,
     976             :                      "Georeference might be slightly approximate due to "
     977             :                      "rounding of coordinates and resolution in file header.");
     978           2 :             dfDeltaLon = (dfLonMax - dfLonMin) / nCols;
     979           2 :             dfDeltaLat = (dfLatMax - dfLatMin) / nRows;
     980             :         }
     981             :         else
     982             :         {
     983           1 :             CPLError(CE_Failure, CPLE_AppDefined,
     984             :                      "Inconsistent extent/resolution/raster dimension, or "
     985             :                      "rounding of coordinates and resolution in file header "
     986             :                      "higher than accepted. You may skip this consistency "
     987             :                      "check by setting the ISG_SKIP_GEOREF_CONSISTENCY_CHECK "
     988             :                      "configuration option to YES.");
     989           1 :             return false;
     990             :         }
     991             :     }
     992           7 :     nRasterXSize = nCols;
     993           7 :     nRasterYSize = nRows;
     994           7 :     m_gt[0] = dfLonMin;
     995           7 :     m_gt[1] = dfDeltaLon;
     996           7 :     m_gt[2] = 0.0;
     997           7 :     m_gt[3] = dfLatMax;
     998           7 :     m_gt[4] = 0.0;
     999           7 :     m_gt[5] = -dfDeltaLat;
    1000           7 :     if (!osNodata.empty())
    1001             :     {
    1002           7 :         bNoDataSet = true;
    1003           7 :         dfNoDataValue = MapNoDataToFloat(CPLAtof(osNodata));
    1004             :     }
    1005           7 :     return true;
    1006             : }
    1007             : 
    1008             : /************************************************************************/
    1009             : /*                           CommonOpen()                               */
    1010             : /************************************************************************/
    1011             : 
    1012         210 : GDALDataset *AAIGDataset::CommonOpen(GDALOpenInfo *poOpenInfo,
    1013             :                                      GridFormat eFormat)
    1014             : {
    1015         210 :     if (poOpenInfo->fpL == nullptr)
    1016           0 :         return nullptr;
    1017             : 
    1018             :     // Create a corresponding GDALDataset.
    1019         210 :     std::unique_ptr<AAIGDataset> poDS;
    1020             : 
    1021         210 :     if (eFormat == FORMAT_AAIG)
    1022         200 :         poDS = std::make_unique<AAIGDataset>();
    1023          10 :     else if (eFormat == FORMAT_GRASSASCII)
    1024           2 :         poDS = std::make_unique<GRASSASCIIDataset>();
    1025             :     else
    1026             :     {
    1027           8 :         poDS = std::make_unique<ISGDataset>();
    1028           8 :         poDS->eDataType = GDT_Float32;
    1029             :     }
    1030             : 
    1031         220 :     const char *pszDataTypeOption = eFormat == FORMAT_AAIG ? "AAIGRID_DATATYPE"
    1032             :                                     : eFormat == FORMAT_GRASSASCII
    1033          10 :                                         ? "GRASSASCIIGRID_DATATYPE"
    1034             :                                         : nullptr;
    1035             : 
    1036             :     const char *pszDataType =
    1037         210 :         pszDataTypeOption ? CPLGetConfigOption(pszDataTypeOption, nullptr)
    1038         210 :                           : nullptr;
    1039         210 :     if (pszDataType == nullptr)
    1040             :     {
    1041             :         pszDataType =
    1042         208 :             CSLFetchNameValue(poOpenInfo->papszOpenOptions, "DATATYPE");
    1043             :     }
    1044         210 :     if (pszDataType != nullptr)
    1045             :     {
    1046           6 :         poDS->eDataType = GDALGetDataTypeByName(pszDataType);
    1047          12 :         if (!(poDS->eDataType == GDT_Int32 || poDS->eDataType == GDT_Float32 ||
    1048           6 :               poDS->eDataType == GDT_Float64))
    1049             :         {
    1050           0 :             ReportError(poOpenInfo->pszFilename, CE_Warning, CPLE_NotSupported,
    1051             :                         "Unsupported value for %s : %s", pszDataTypeOption,
    1052             :                         pszDataType);
    1053           0 :             poDS->eDataType = GDT_Int32;
    1054           0 :             pszDataType = nullptr;
    1055             :         }
    1056             :     }
    1057             : 
    1058             :     // Parse the header.
    1059         420 :     if (!poDS->ParseHeader(
    1060         210 :             reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
    1061         210 :             pszDataType))
    1062             :     {
    1063           1 :         return nullptr;
    1064             :     }
    1065             : 
    1066         209 :     poDS->fp = poOpenInfo->fpL;
    1067         209 :     poOpenInfo->fpL = nullptr;
    1068             : 
    1069             :     // Sanity check in particular to avoid allocating a too large
    1070             :     // AAIGRasterBand::panLineOffset array
    1071         209 :     if (poDS->nRasterXSize > 10 * 1000 * 1000 ||
    1072         417 :         poDS->nRasterYSize > 10 * 1000 * 1000 ||
    1073         208 :         static_cast<int64_t>(poDS->nRasterXSize) * poDS->nRasterYSize >
    1074             :             1000 * 1000 * 1000)
    1075             :     {
    1076             :         // We need at least 2 bytes for each pixel: one for the character for
    1077             :         // its value and one for the space separator
    1078           1 :         constexpr int MIN_BYTE_COUNT_PER_PIXEL = 2;
    1079           2 :         if (VSIFSeekL(poDS->fp, 0, SEEK_END) != 0 ||
    1080           1 :             VSIFTellL(poDS->fp) <
    1081           1 :                 static_cast<vsi_l_offset>(poDS->nRasterXSize) *
    1082           1 :                     poDS->nRasterYSize * MIN_BYTE_COUNT_PER_PIXEL)
    1083             :         {
    1084           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    1085             :                      "Too large raster dimension %d x %d compared to file size "
    1086             :                      "(%" PRIu64 " bytes)",
    1087           1 :                      poDS->nRasterXSize, poDS->nRasterYSize,
    1088           1 :                      static_cast<uint64_t>(VSIFTellL(poDS->fp)));
    1089           1 :             return nullptr;
    1090             :         }
    1091           0 :         VSIFSeekL(poDS->fp, 0, SEEK_SET);
    1092             :     }
    1093             : 
    1094             :     // Find the start of real data.
    1095         208 :     int nStartOfData = 0;
    1096             : 
    1097         208 :     if (eFormat == FORMAT_ISG)
    1098             :     {
    1099             :         const char *pszEOH =
    1100           7 :             strstr(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
    1101             :                    "end_of_head");
    1102           7 :         if (pszEOH == nullptr)
    1103             :         {
    1104           0 :             return nullptr;
    1105             :         }
    1106         443 :         for (int i = 0; pszEOH[i]; i++)
    1107             :         {
    1108         443 :             if (pszEOH[i] == '\n' || pszEOH[i] == '\r')
    1109             :             {
    1110           7 :                 nStartOfData =
    1111           7 :                     static_cast<int>(pszEOH - reinterpret_cast<const char *>(
    1112           7 :                                                   poOpenInfo->pabyHeader)) +
    1113             :                     i;
    1114           7 :                 break;
    1115             :             }
    1116             :         }
    1117           7 :         if (nStartOfData == 0)
    1118             :         {
    1119           0 :             return nullptr;
    1120             :         }
    1121           7 :         if (poOpenInfo->pabyHeader[nStartOfData] == '\n' ||
    1122           0 :             poOpenInfo->pabyHeader[nStartOfData] == '\r')
    1123             :         {
    1124           7 :             nStartOfData++;
    1125             :         }
    1126             : 
    1127           7 :         poDS->m_oSRS.importFromWkt(SRS_WKT_WGS84_LAT_LONG);
    1128             :     }
    1129             :     else
    1130             :     {
    1131       22644 :         for (int i = 2; true; i++)
    1132             :         {
    1133       22644 :             if (poOpenInfo->pabyHeader[i] == '\0')
    1134             :             {
    1135           0 :                 ReportError(poOpenInfo->pszFilename, CE_Failure,
    1136             :                             CPLE_AppDefined,
    1137             :                             "Couldn't find data values in ASCII Grid file.");
    1138           0 :                 return nullptr;
    1139             :             }
    1140             : 
    1141       22644 :             if (poOpenInfo->pabyHeader[i - 1] == '\n' ||
    1142       21554 :                 poOpenInfo->pabyHeader[i - 2] == '\n' ||
    1143       20665 :                 poOpenInfo->pabyHeader[i - 1] == '\r' ||
    1144       20581 :                 poOpenInfo->pabyHeader[i - 2] == '\r')
    1145             :             {
    1146        2063 :                 if ((!isalpha(static_cast<unsigned char>(
    1147        2063 :                          poOpenInfo->pabyHeader[i])) ||
    1148             :                      // null seems to be specific of D12 software
    1149             :                      // See https://github.com/OSGeo/gdal/issues/5095
    1150        1783 :                      (i + 5 < poOpenInfo->nHeaderBytes &&
    1151        1783 :                       memcmp(poOpenInfo->pabyHeader + i, "null ", 5) == 0) ||
    1152        1781 :                      (i + 4 < poOpenInfo->nHeaderBytes &&
    1153        1781 :                       EQUALN(reinterpret_cast<const char *>(
    1154             :                                  poOpenInfo->pabyHeader + i),
    1155         285 :                              "nan ", 4))) &&
    1156         285 :                     poOpenInfo->pabyHeader[i] != '\n' &&
    1157         201 :                     poOpenInfo->pabyHeader[i] != '\r')
    1158             :                 {
    1159         201 :                     nStartOfData = i;
    1160             : 
    1161             :                     // Beginning of real data found.
    1162         201 :                     break;
    1163             :                 }
    1164             :             }
    1165             :         }
    1166             :     }
    1167             : 
    1168             :     // Recognize the type of data.
    1169         208 :     CPLAssert(nullptr != poDS->fp);
    1170             : 
    1171         392 :     if (pszDataType == nullptr && poDS->eDataType != GDT_Float32 &&
    1172         184 :         poDS->eDataType != GDT_Float64)
    1173             :     {
    1174             :         // Allocate 100K chunk + 1 extra byte for NULL character.
    1175         183 :         constexpr size_t nChunkSize = 1024 * 100;
    1176             :         std::unique_ptr<GByte, VSIFreeReleaser> pabyChunk(static_cast<GByte *>(
    1177         183 :             VSI_CALLOC_VERBOSE(nChunkSize + 1, sizeof(GByte))));
    1178         183 :         if (pabyChunk == nullptr)
    1179             :         {
    1180           0 :             return nullptr;
    1181             :         }
    1182         183 :         (pabyChunk.get())[nChunkSize] = '\0';
    1183             : 
    1184         183 :         if (VSIFSeekL(poDS->fp, nStartOfData, SEEK_SET) < 0)
    1185             :         {
    1186           0 :             return nullptr;
    1187             :         }
    1188             : 
    1189             :         // Scan for dot in subsequent chunks of data.
    1190         366 :         while (!VSIFEofL(poDS->fp))
    1191             :         {
    1192             :             const size_t nLen =
    1193         183 :                 VSIFReadL(pabyChunk.get(), 1, nChunkSize, poDS->fp);
    1194             : 
    1195      175584 :             for (size_t i = 0; i < nLen; i++)
    1196             :             {
    1197      175446 :                 const GByte ch = (pabyChunk.get())[i];
    1198      175446 :                 if (ch == '.' || ch == ',' || ch == 'e' || ch == 'E')
    1199             :                 {
    1200          45 :                     poDS->eDataType = GDT_Float32;
    1201          45 :                     break;
    1202             :                 }
    1203             :             }
    1204             :         }
    1205             :     }
    1206             : 
    1207             :     // Create band information objects.
    1208         208 :     AAIGRasterBand *band = new AAIGRasterBand(poDS.get(), nStartOfData);
    1209         208 :     poDS->SetBand(1, band);
    1210         208 :     if (band->panLineOffset == nullptr)
    1211             :     {
    1212           0 :         return nullptr;
    1213             :     }
    1214         208 :     if (!poDS->osUnits.empty())
    1215             :     {
    1216           7 :         poDS->GetRasterBand(1)->SetUnitType(poDS->osUnits);
    1217             :     }
    1218             : 
    1219             :     // Try to read projection file.
    1220         416 :     const std::string osDirname = CPLGetPathSafe(poOpenInfo->pszFilename);
    1221         416 :     const std::string osBasename = CPLGetBasenameSafe(poOpenInfo->pszFilename);
    1222             : 
    1223         208 :     poDS->osPrjFilename =
    1224         416 :         CPLFormFilenameSafe(osDirname.c_str(), osBasename.c_str(), "prj");
    1225         208 :     int nRet = 0;
    1226             :     {
    1227             :         VSIStatBufL sStatBuf;
    1228         208 :         nRet = VSIStatL(poDS->osPrjFilename, &sStatBuf);
    1229             :     }
    1230         208 :     if (nRet != 0 && VSIIsCaseSensitiveFS(poDS->osPrjFilename))
    1231             :     {
    1232         160 :         poDS->osPrjFilename =
    1233         320 :             CPLFormFilenameSafe(osDirname.c_str(), osBasename.c_str(), "PRJ");
    1234             : 
    1235             :         VSIStatBufL sStatBuf;
    1236         160 :         nRet = VSIStatL(poDS->osPrjFilename, &sStatBuf);
    1237             :     }
    1238             : 
    1239         208 :     if (nRet == 0)
    1240             :     {
    1241          50 :         poDS->papszPrj = CSLLoad(poDS->osPrjFilename);
    1242             : 
    1243          50 :         CPLDebug("AAIGrid", "Loaded SRS from %s", poDS->osPrjFilename.c_str());
    1244             : 
    1245         100 :         OGRSpatialReference oSRS;
    1246          50 :         oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    1247          50 :         if (oSRS.importFromESRI(poDS->papszPrj) == OGRERR_NONE)
    1248             :         {
    1249             :             // If geographic values are in seconds, we must transform.
    1250             :             // Is there a code for minutes too?
    1251          61 :             if (oSRS.IsGeographic() &&
    1252          61 :                 EQUAL(OSR_GDS(poDS->papszPrj, "Units", ""), "DS"))
    1253             :             {
    1254           0 :                 poDS->m_gt[0] /= 3600.0;
    1255           0 :                 poDS->m_gt[1] /= 3600.0;
    1256           0 :                 poDS->m_gt[2] /= 3600.0;
    1257           0 :                 poDS->m_gt[3] /= 3600.0;
    1258           0 :                 poDS->m_gt[4] /= 3600.0;
    1259           0 :                 poDS->m_gt[5] /= 3600.0;
    1260             :             }
    1261             : 
    1262          50 :             poDS->m_oSRS = std::move(oSRS);
    1263             :         }
    1264             :     }
    1265             : 
    1266             :     // Initialize any PAM information.
    1267         208 :     poDS->SetDescription(poOpenInfo->pszFilename);
    1268         208 :     poDS->TryLoadXML();
    1269             : 
    1270             :     // Check for external overviews.
    1271         416 :     poDS->oOvManager.Initialize(poDS.get(), poOpenInfo->pszFilename,
    1272         208 :                                 poOpenInfo->GetSiblingFiles());
    1273             : 
    1274         208 :     return poDS.release();
    1275             : }
    1276             : 
    1277             : /************************************************************************/
    1278             : /*                          GetGeoTransform()                           */
    1279             : /************************************************************************/
    1280             : 
    1281         146 : CPLErr AAIGDataset::GetGeoTransform(GDALGeoTransform &gt) const
    1282             : 
    1283             : {
    1284         146 :     gt = m_gt;
    1285         146 :     return CE_None;
    1286             : }
    1287             : 
    1288             : /************************************************************************/
    1289             : /*                          GetSpatialRef()                             */
    1290             : /************************************************************************/
    1291             : 
    1292         108 : const OGRSpatialReference *AAIGDataset::GetSpatialRef() const
    1293             : {
    1294         108 :     return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
    1295             : }
    1296             : 
    1297             : /************************************************************************/
    1298             : /*                          CreateCopy()                                */
    1299             : /************************************************************************/
    1300             : 
    1301          49 : GDALDataset *AAIGDataset::CreateCopy(const char *pszFilename,
    1302             :                                      GDALDataset *poSrcDS, int /* bStrict */,
    1303             :                                      char **papszOptions,
    1304             :                                      GDALProgressFunc pfnProgress,
    1305             :                                      void *pProgressData)
    1306             : {
    1307          49 :     const int nBands = poSrcDS->GetRasterCount();
    1308          49 :     const int nXSize = poSrcDS->GetRasterXSize();
    1309          49 :     const int nYSize = poSrcDS->GetRasterYSize();
    1310             : 
    1311             :     // Some rudimentary checks.
    1312          49 :     if (nBands != 1)
    1313             :     {
    1314           5 :         ReportError(pszFilename, CE_Failure, CPLE_NotSupported,
    1315             :                     "AAIG driver doesn't support %d bands.  Must be 1 band.",
    1316             :                     nBands);
    1317             : 
    1318           5 :         return nullptr;
    1319             :     }
    1320             : 
    1321          44 :     if (!pfnProgress(0.0, nullptr, pProgressData))
    1322           0 :         return nullptr;
    1323             : 
    1324             :     // Create the dataset.
    1325          44 :     VSILFILE *fpImage = VSIFOpenL(pszFilename, "wt");
    1326          44 :     if (fpImage == nullptr)
    1327             :     {
    1328           3 :         ReportError(pszFilename, CE_Failure, CPLE_OpenFailed,
    1329             :                     "Unable to create file.");
    1330           3 :         return nullptr;
    1331             :     }
    1332             : 
    1333             :     // Write ASCII Grid file header.
    1334          41 :     GDALGeoTransform gt;
    1335          41 :     char szHeader[2000] = {};
    1336             :     const char *pszForceCellsize =
    1337          41 :         CSLFetchNameValue(papszOptions, "FORCE_CELLSIZE");
    1338             : 
    1339          41 :     poSrcDS->GetGeoTransform(gt);
    1340             : 
    1341          41 :     const double dfYLLCorner = gt[5] < 0 ? gt[3] + nYSize * gt[5] : gt[3];
    1342          43 :     if (std::abs(gt[1] + gt[5]) < 0.0000001 ||
    1343          43 :         std::abs(gt[1] - gt[5]) < 0.0000001 ||
    1344           0 :         (pszForceCellsize && CPLTestBool(pszForceCellsize)))
    1345             :     {
    1346          40 :         CPLsnprintf(szHeader, sizeof(szHeader),
    1347             :                     "ncols        %d\n"
    1348             :                     "nrows        %d\n"
    1349             :                     "xllcorner    %.12f\n"
    1350             :                     "yllcorner    %.12f\n"
    1351             :                     "cellsize     %.12f\n",
    1352          40 :                     nXSize, nYSize, gt[0], dfYLLCorner, gt[1]);
    1353             :     }
    1354             :     else
    1355             :     {
    1356           1 :         if (pszForceCellsize == nullptr)
    1357           1 :             ReportError(pszFilename, CE_Warning, CPLE_AppDefined,
    1358             :                         "Producing a Golden Surfer style file with DX and DY "
    1359             :                         "instead of CELLSIZE since the input pixels are "
    1360             :                         "non-square.  Use the FORCE_CELLSIZE=TRUE creation "
    1361             :                         "option to force use of DX for even though this will "
    1362             :                         "be distorted.  Most ASCII Grid readers (ArcGIS "
    1363             :                         "included) do not support the DX and DY parameters.");
    1364           1 :         CPLsnprintf(szHeader, sizeof(szHeader),
    1365             :                     "ncols        %d\n"
    1366             :                     "nrows        %d\n"
    1367             :                     "xllcorner    %.12f\n"
    1368             :                     "yllcorner    %.12f\n"
    1369             :                     "dx           %.12f\n"
    1370             :                     "dy           %.12f\n",
    1371           1 :                     nXSize, nYSize, gt[0], dfYLLCorner, gt[1], fabs(gt[5]));
    1372             :     }
    1373             : 
    1374             :     // Builds the format string used for printing float values.
    1375          41 :     char szFormatFloat[32] = {'\0'};
    1376          41 :     strcpy(szFormatFloat, "%.20g");
    1377             :     const char *pszDecimalPrecision =
    1378          41 :         CSLFetchNameValue(papszOptions, "DECIMAL_PRECISION");
    1379             :     const char *pszSignificantDigits =
    1380          41 :         CSLFetchNameValue(papszOptions, "SIGNIFICANT_DIGITS");
    1381          41 :     bool bIgnoreSigDigits = false;
    1382          41 :     if (pszDecimalPrecision && pszSignificantDigits)
    1383             :     {
    1384           0 :         ReportError(pszFilename, CE_Warning, CPLE_AppDefined,
    1385             :                     "Conflicting precision arguments, using DECIMAL_PRECISION");
    1386           0 :         bIgnoreSigDigits = true;
    1387             :     }
    1388             :     int nPrecision;
    1389          41 :     if (pszSignificantDigits && !bIgnoreSigDigits)
    1390             :     {
    1391           2 :         nPrecision = atoi(pszSignificantDigits);
    1392           2 :         if (nPrecision >= 0)
    1393           2 :             snprintf(szFormatFloat, sizeof(szFormatFloat), "%%.%dg",
    1394             :                      nPrecision);
    1395           2 :         CPLDebug("AAIGrid", "Setting precision format: %s", szFormatFloat);
    1396             :     }
    1397          39 :     else if (pszDecimalPrecision)
    1398             :     {
    1399           2 :         nPrecision = atoi(pszDecimalPrecision);
    1400           2 :         if (nPrecision >= 0)
    1401           2 :             snprintf(szFormatFloat, sizeof(szFormatFloat), "%%.%df",
    1402             :                      nPrecision);
    1403           2 :         CPLDebug("AAIGrid", "Setting precision format: %s", szFormatFloat);
    1404             :     }
    1405             : 
    1406             :     // Handle nodata (optionally).
    1407          41 :     GDALRasterBand *poBand = poSrcDS->GetRasterBand(1);
    1408          41 :     const bool bReadAsInt = poBand->GetRasterDataType() == GDT_Byte ||
    1409          24 :                             poBand->GetRasterDataType() == GDT_Int16 ||
    1410          85 :                             poBand->GetRasterDataType() == GDT_UInt16 ||
    1411          20 :                             poBand->GetRasterDataType() == GDT_Int32;
    1412             : 
    1413             :     // Write `nodata' value to header if it is exists in source dataset
    1414          41 :     int bSuccess = FALSE;
    1415          41 :     const double dfNoData = poBand->GetNoDataValue(&bSuccess);
    1416          41 :     if (bSuccess)
    1417             :     {
    1418           2 :         snprintf(szHeader + strlen(szHeader),
    1419           2 :                  sizeof(szHeader) - strlen(szHeader), "%s", "NODATA_value ");
    1420           2 :         if (bReadAsInt)
    1421           0 :             snprintf(szHeader + strlen(szHeader),
    1422           0 :                      sizeof(szHeader) - strlen(szHeader), "%d",
    1423             :                      static_cast<int>(dfNoData));
    1424             :         else
    1425           2 :             CPLsnprintf(szHeader + strlen(szHeader),
    1426           2 :                         sizeof(szHeader) - strlen(szHeader), szFormatFloat,
    1427             :                         dfNoData);
    1428           2 :         snprintf(szHeader + strlen(szHeader),
    1429           2 :                  sizeof(szHeader) - strlen(szHeader), "%s", "\n");
    1430             :     }
    1431             : 
    1432          41 :     if (VSIFWriteL(szHeader, strlen(szHeader), 1, fpImage) != 1)
    1433             :     {
    1434           3 :         CPL_IGNORE_RET_VAL(VSIFCloseL(fpImage));
    1435           3 :         return nullptr;
    1436             :     }
    1437             : 
    1438             :     // Loop over image, copying image data.
    1439             : 
    1440             :     // Write scanlines to output file
    1441             :     int *panScanline = bReadAsInt
    1442          38 :                            ? static_cast<int *>(CPLMalloc(sizeof(int) * nXSize))
    1443          38 :                            : nullptr;
    1444             : 
    1445             :     double *padfScanline =
    1446          38 :         bReadAsInt ? nullptr
    1447          14 :                    : static_cast<double *>(CPLMalloc(sizeof(double) * nXSize));
    1448             : 
    1449          38 :     CPLErr eErr = CE_None;
    1450             : 
    1451          38 :     bool bHasOutputDecimalDot = false;
    1452         548 :     for (int iLine = 0; eErr == CE_None && iLine < nYSize; iLine++)
    1453             :     {
    1454        1020 :         CPLString osBuf;
    1455         510 :         const int iSrcLine = gt[5] < 0 ? iLine : nYSize - 1 - iLine;
    1456         510 :         eErr = poBand->RasterIO(GF_Read, 0, iSrcLine, nXSize, 1,
    1457             :                                 bReadAsInt ? static_cast<void *>(panScanline)
    1458             :                                            : static_cast<void *>(padfScanline),
    1459             :                                 nXSize, 1, bReadAsInt ? GDT_Int32 : GDT_Float64,
    1460             :                                 0, 0, nullptr);
    1461             : 
    1462         510 :         if (bReadAsInt)
    1463             :         {
    1464       14657 :             for (int iPixel = 0; iPixel < nXSize; iPixel++)
    1465             :             {
    1466       14316 :                 snprintf(szHeader, sizeof(szHeader), "%d", panScanline[iPixel]);
    1467       14316 :                 osBuf += szHeader;
    1468       14316 :                 osBuf += ' ';
    1469       14316 :                 if ((iPixel > 0 && (iPixel % 1024) == 0) ||
    1470       14316 :                     iPixel == nXSize - 1)
    1471             :                 {
    1472         696 :                     if (VSIFWriteL(osBuf, static_cast<int>(osBuf.size()), 1,
    1473         696 :                                    fpImage) != 1)
    1474             :                     {
    1475           7 :                         eErr = CE_Failure;
    1476           7 :                         ReportError(pszFilename, CE_Failure, CPLE_AppDefined,
    1477             :                                     "Write failed, disk full?");
    1478           7 :                         break;
    1479             :                     }
    1480         341 :                     osBuf = "";
    1481             :                 }
    1482             :             }
    1483             :         }
    1484             :         else
    1485             :         {
    1486         162 :             assert(padfScanline);
    1487             : 
    1488        2514 :             for (int iPixel = 0; iPixel < nXSize; iPixel++)
    1489             :             {
    1490        2352 :                 CPLsnprintf(szHeader, sizeof(szHeader), szFormatFloat,
    1491        2352 :                             padfScanline[iPixel]);
    1492             : 
    1493             :                 // Make sure that as least one value has a decimal point (#6060)
    1494        2352 :                 if (!bHasOutputDecimalDot)
    1495             :                 {
    1496          14 :                     if (strchr(szHeader, '.') || strchr(szHeader, 'e') ||
    1497          11 :                         strchr(szHeader, 'E'))
    1498             :                     {
    1499           3 :                         bHasOutputDecimalDot = true;
    1500             :                     }
    1501          22 :                     else if (!std::isinf(padfScanline[iPixel]) &&
    1502          11 :                              !std::isnan(padfScanline[iPixel]))
    1503             :                     {
    1504          11 :                         strcat(szHeader, ".0");
    1505          11 :                         bHasOutputDecimalDot = true;
    1506             :                     }
    1507             :                 }
    1508             : 
    1509        2352 :                 osBuf += szHeader;
    1510        2352 :                 osBuf += ' ';
    1511        2352 :                 if ((iPixel > 0 && (iPixel % 1024) == 0) ||
    1512        2352 :                     iPixel == nXSize - 1)
    1513             :                 {
    1514         324 :                     if (VSIFWriteL(osBuf, static_cast<int>(osBuf.size()), 1,
    1515         324 :                                    fpImage) != 1)
    1516             :                     {
    1517           0 :                         eErr = CE_Failure;
    1518           0 :                         ReportError(pszFilename, CE_Failure, CPLE_AppDefined,
    1519             :                                     "Write failed, disk full?");
    1520           0 :                         break;
    1521             :                     }
    1522         162 :                     osBuf = "";
    1523             :                 }
    1524             :             }
    1525             :         }
    1526         510 :         if (VSIFWriteL("\n", 1, 1, fpImage) != 1)
    1527           1 :             eErr = CE_Failure;
    1528             : 
    1529        1013 :         if (eErr == CE_None &&
    1530         503 :             !pfnProgress((iLine + 1) / static_cast<double>(nYSize), nullptr,
    1531             :                          pProgressData))
    1532             :         {
    1533           0 :             eErr = CE_Failure;
    1534           0 :             ReportError(pszFilename, CE_Failure, CPLE_UserInterrupt,
    1535             :                         "User terminated CreateCopy()");
    1536             :         }
    1537             :     }
    1538             : 
    1539          38 :     CPLFree(panScanline);
    1540          38 :     CPLFree(padfScanline);
    1541          38 :     if (VSIFCloseL(fpImage) != 0)
    1542           0 :         eErr = CE_Failure;
    1543             : 
    1544          38 :     if (eErr != CE_None)
    1545           7 :         return nullptr;
    1546             : 
    1547             :     // Try to write projection file.
    1548          31 :     const char *pszOriginalProjection = poSrcDS->GetProjectionRef();
    1549          31 :     if (!EQUAL(pszOriginalProjection, ""))
    1550             :     {
    1551          21 :         char *pszDirname = CPLStrdup(CPLGetPathSafe(pszFilename).c_str());
    1552          21 :         char *pszBasename = CPLStrdup(CPLGetBasenameSafe(pszFilename).c_str());
    1553          21 :         char *pszPrjFilename = CPLStrdup(
    1554          42 :             CPLFormFilenameSafe(pszDirname, pszBasename, "prj").c_str());
    1555          21 :         VSILFILE *fp = VSIFOpenL(pszPrjFilename, "wt");
    1556          21 :         if (fp != nullptr)
    1557             :         {
    1558          42 :             OGRSpatialReference oSRS;
    1559          21 :             oSRS.importFromWkt(pszOriginalProjection);
    1560          21 :             oSRS.morphToESRI();
    1561          21 :             char *pszESRIProjection = nullptr;
    1562          21 :             oSRS.exportToWkt(&pszESRIProjection);
    1563          21 :             CPL_IGNORE_RET_VAL(VSIFWriteL(pszESRIProjection, 1,
    1564             :                                           strlen(pszESRIProjection), fp));
    1565             : 
    1566          21 :             CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
    1567          21 :             CPLFree(pszESRIProjection);
    1568             :         }
    1569             :         else
    1570             :         {
    1571           0 :             ReportError(pszFilename, CE_Failure, CPLE_FileIO,
    1572             :                         "Unable to create file %s.", pszPrjFilename);
    1573             :         }
    1574          21 :         CPLFree(pszDirname);
    1575          21 :         CPLFree(pszBasename);
    1576          21 :         CPLFree(pszPrjFilename);
    1577             :     }
    1578             : 
    1579             :     // Re-open dataset, and copy any auxiliary pam information.
    1580             : 
    1581             :     // If writing to stdout, we can't reopen it, so return
    1582             :     // a fake dataset to make the caller happy.
    1583          31 :     CPLPushErrorHandler(CPLQuietErrorHandler);
    1584             :     GDALPamDataset *poDS =
    1585          31 :         cpl::down_cast<GDALPamDataset *>(GDALDataset::Open(pszFilename));
    1586          31 :     CPLPopErrorHandler();
    1587          31 :     if (poDS)
    1588             :     {
    1589          31 :         poDS->CloneInfo(poSrcDS, GCIF_PAM_DEFAULT);
    1590          31 :         return poDS;
    1591             :     }
    1592             : 
    1593           0 :     CPLErrorReset();
    1594             : 
    1595           0 :     AAIGDataset *poAAIG_DS = new AAIGDataset();
    1596           0 :     poAAIG_DS->nRasterXSize = nXSize;
    1597           0 :     poAAIG_DS->nRasterYSize = nYSize;
    1598           0 :     poAAIG_DS->nBands = 1;
    1599           0 :     poAAIG_DS->SetBand(1, new AAIGRasterBand(poAAIG_DS, 1));
    1600           0 :     return poAAIG_DS;
    1601             : }
    1602             : 
    1603             : /************************************************************************/
    1604             : /*                              OSR_GDS()                               */
    1605             : /************************************************************************/
    1606             : 
    1607          11 : static CPLString OSR_GDS(char **papszNV, const char *pszField,
    1608             :                          const char *pszDefaultValue)
    1609             : 
    1610             : {
    1611          11 :     if (papszNV == nullptr || papszNV[0] == nullptr)
    1612           0 :         return pszDefaultValue;
    1613             : 
    1614          11 :     int iLine = 0;  // Used after for.
    1615          22 :     for (; papszNV[iLine] != nullptr &&
    1616          11 :            !EQUALN(papszNV[iLine], pszField, strlen(pszField));
    1617             :          iLine++)
    1618             :     {
    1619             :     }
    1620             : 
    1621          11 :     if (papszNV[iLine] == nullptr)
    1622          11 :         return pszDefaultValue;
    1623             :     else
    1624             :     {
    1625           0 :         char **papszTokens = CSLTokenizeString(papszNV[iLine]);
    1626             : 
    1627           0 :         CPLString osResult;
    1628           0 :         if (CSLCount(papszTokens) > 1)
    1629           0 :             osResult = papszTokens[1];
    1630             :         else
    1631           0 :             osResult = pszDefaultValue;
    1632             : 
    1633           0 :         CSLDestroy(papszTokens);
    1634           0 :         return osResult;
    1635             :     }
    1636             : }
    1637             : 
    1638             : /************************************************************************/
    1639             : /*                        GDALRegister_AAIGrid()                        */
    1640             : /************************************************************************/
    1641             : 
    1642        1961 : void GDALRegister_AAIGrid()
    1643             : 
    1644             : {
    1645        1961 :     if (GDALGetDriverByName("AAIGrid") != nullptr)
    1646         283 :         return;
    1647             : 
    1648        1678 :     GDALDriver *poDriver = new GDALDriver();
    1649             : 
    1650        1678 :     poDriver->SetDescription("AAIGrid");
    1651        1678 :     poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
    1652        1678 :     poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "Arc/Info ASCII Grid");
    1653        1678 :     poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC,
    1654        1678 :                               "drivers/raster/aaigrid.html");
    1655        1678 :     poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "asc");
    1656        1678 :     poDriver->SetMetadataItem(GDAL_DMD_CREATIONDATATYPES,
    1657        1678 :                               "Byte UInt16 Int16 Int32 Float32");
    1658             : 
    1659        1678 :     poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
    1660        1678 :     poDriver->SetMetadataItem(
    1661             :         GDAL_DMD_CREATIONOPTIONLIST,
    1662             :         "<CreationOptionList>\n"
    1663             :         "   <Option name='FORCE_CELLSIZE' type='boolean' description='Force "
    1664             :         "use of CELLSIZE, default is FALSE.'/>\n"
    1665             :         "   <Option name='DECIMAL_PRECISION' type='int' description='Number of "
    1666             :         "decimal when writing floating-point numbers(%f).'/>\n"
    1667             :         "   <Option name='SIGNIFICANT_DIGITS' type='int' description='Number "
    1668             :         "of significant digits when writing floating-point numbers(%g).'/>\n"
    1669        1678 :         "</CreationOptionList>\n");
    1670        1678 :     poDriver->SetMetadataItem(GDAL_DMD_OPENOPTIONLIST,
    1671             :                               "<OpenOptionList>\n"
    1672             :                               "   <Option name='DATATYPE' type='string-select' "
    1673             :                               "description='Data type to be used.'>\n"
    1674             :                               "       <Value>Int32</Value>\n"
    1675             :                               "       <Value>Float32</Value>\n"
    1676             :                               "       <Value>Float64</Value>\n"
    1677             :                               "   </Option>\n"
    1678        1678 :                               "</OpenOptionList>\n");
    1679             : 
    1680        1678 :     poDriver->pfnOpen = AAIGDataset::Open;
    1681        1678 :     poDriver->pfnIdentify = AAIGDataset::Identify;
    1682        1678 :     poDriver->pfnCreateCopy = AAIGDataset::CreateCopy;
    1683             : 
    1684        1678 :     GetGDALDriverManager()->RegisterDriver(poDriver);
    1685             : }
    1686             : 
    1687             : /************************************************************************/
    1688             : /*                   GDALRegister_GRASSASCIIGrid()                      */
    1689             : /************************************************************************/
    1690             : 
    1691        1961 : void GDALRegister_GRASSASCIIGrid()
    1692             : 
    1693             : {
    1694        1961 :     if (GDALGetDriverByName("GRASSASCIIGrid") != nullptr)
    1695         283 :         return;
    1696             : 
    1697        1678 :     GDALDriver *poDriver = new GDALDriver();
    1698             : 
    1699        1678 :     poDriver->SetDescription("GRASSASCIIGrid");
    1700        1678 :     poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
    1701        1678 :     poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "GRASS ASCII Grid");
    1702        1678 :     poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC,
    1703        1678 :                               "drivers/raster/grassasciigrid.html");
    1704             : 
    1705        1678 :     poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
    1706             : 
    1707        1678 :     poDriver->pfnOpen = GRASSASCIIDataset::Open;
    1708        1678 :     poDriver->pfnIdentify = GRASSASCIIDataset::Identify;
    1709             : 
    1710        1678 :     GetGDALDriverManager()->RegisterDriver(poDriver);
    1711             : }
    1712             : 
    1713             : /************************************************************************/
    1714             : /*                       GDALRegister_ISG()                             */
    1715             : /************************************************************************/
    1716             : 
    1717        1961 : void GDALRegister_ISG()
    1718             : 
    1719             : {
    1720        1961 :     if (GDALGetDriverByName("ISG") != nullptr)
    1721         283 :         return;
    1722             : 
    1723        1678 :     GDALDriver *poDriver = new GDALDriver();
    1724             : 
    1725        1678 :     poDriver->SetDescription("ISG");
    1726        1678 :     poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
    1727        1678 :     poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
    1728        1678 :                               "International Service for the Geoid");
    1729        1678 :     poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/isg.html");
    1730        1678 :     poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "isg");
    1731             : 
    1732        1678 :     poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
    1733             : 
    1734        1678 :     poDriver->pfnOpen = ISGDataset::Open;
    1735        1678 :     poDriver->pfnIdentify = ISGDataset::Identify;
    1736             : 
    1737        1678 :     GetGDALDriverManager()->RegisterDriver(poDriver);
    1738             : }

Generated by: LCOV version 1.14