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

Generated by: LCOV version 1.14