LCOV - code coverage report
Current view: top level - frmts/gxf - gxfopen.c (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 210 382 55.0 %
Date: 2025-01-18 12:42:00 Functions: 8 13 61.5 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GXF Reader
       4             :  * Purpose:  Majority of Geosoft GXF reading code.
       5             :  * Author:   Frank Warmerdam, warmerdam@pobox.com
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 1998, Global Geomatics
       9             :  * Copyright (c) 1998, Frank Warmerdam
      10             :  * Copyright (c) 2008-2012, Even Rouault <even dot rouault at spatialys.com>
      11             :  *
      12             :  * SPDX-License-Identifier: MIT
      13             :  ****************************************************************************/
      14             : 
      15             : #include "cpl_port.h"
      16             : 
      17             : #include <ctype.h>
      18             : #include "gxfopen.h"
      19             : 
      20             : /* this is also defined in gdal.h which we avoid in this separable component */
      21             : #define CPLE_WrongFormat 200
      22             : 
      23             : #define MAX_LINE_COUNT_PER_HEADER 1000
      24             : #define MAX_HEADER_COUNT 1000
      25             : 
      26             : /************************************************************************/
      27             : /*                         GXFReadHeaderValue()                         */
      28             : /*                                                                      */
      29             : /*      Read one entry from the file header, and return it and its      */
      30             : /*      value in clean form.                                            */
      31             : /************************************************************************/
      32             : 
      33          32 : static char **GXFReadHeaderValue(VSILFILE *fp, char *pszHTitle)
      34             : 
      35             : {
      36             :     const char *pszLine;
      37          32 :     char **papszReturn = NULL;
      38             :     int i;
      39          32 :     int nLineCount = 0, nReturnLineCount = 0;
      40          32 :     int bContinuedLine = FALSE;
      41             : 
      42             :     /* -------------------------------------------------------------------- */
      43             :     /*      Try to read a line.  If we fail or if this isn't a proper       */
      44             :     /*      header value then return the failure.                           */
      45             :     /* -------------------------------------------------------------------- */
      46          32 :     pszLine = CPLReadLineL(fp);
      47          32 :     if (pszLine == NULL)
      48             :     {
      49           0 :         strcpy(pszHTitle, "#EOF");
      50           0 :         return (NULL);
      51             :     }
      52             : 
      53             :     /* -------------------------------------------------------------------- */
      54             :     /*      Extract the title.  It should be terminated by some sort of     */
      55             :     /*      white space.                                                    */
      56             :     /* -------------------------------------------------------------------- */
      57         324 :     for (i = 0;
      58         324 :          i < 70 && !isspace((unsigned char)pszLine[i]) && pszLine[i] != '\0';
      59         292 :          i++)
      60             :     {
      61             :     }
      62             : 
      63          32 :     strncpy(pszHTitle, pszLine, i);
      64          32 :     pszHTitle[i] = '\0';
      65             : 
      66             :     /* -------------------------------------------------------------------- */
      67             :     /*      If this is #GRID, then return ... we are at the end of the      */
      68             :     /*      header.                                                         */
      69             :     /* -------------------------------------------------------------------- */
      70          32 :     if (EQUAL(pszHTitle, "#GRID"))
      71           4 :         return NULL;
      72             : 
      73             :     /* -------------------------------------------------------------------- */
      74             :     /*      Skip white space.                                               */
      75             :     /* -------------------------------------------------------------------- */
      76          28 :     while (isspace((unsigned char)pszLine[i]))
      77           0 :         i++;
      78             : 
      79             :     /* -------------------------------------------------------------------- */
      80             :     /*    If we have reached the end of the line, try to read another line. */
      81             :     /* -------------------------------------------------------------------- */
      82          28 :     if (pszLine[i] == '\0')
      83             :     {
      84          28 :         pszLine = CPLReadLineL(fp);
      85          28 :         if (pszLine == NULL)
      86             :         {
      87           0 :             strcpy(pszHTitle, "#EOF");
      88           0 :             return (NULL);
      89             :         }
      90             :     }
      91             : 
      92             :     /* -------------------------------------------------------------------- */
      93             :     /*      Keeping adding the value stuff as new lines till we reach a     */
      94             :     /*      `#' mark at the beginning of a new line.                        */
      95             :     /* -------------------------------------------------------------------- */
      96             :     do
      97             :     {
      98             :         vsi_l_offset nCurPos;
      99          34 :         char chNextChar = 0;
     100             :         char *pszTrimmedLine;
     101          34 :         size_t nLen = strlen(pszLine);
     102             : 
     103             :         /* Lines are supposed to be limited to 80 characters */
     104          34 :         if (nLen > 1024)
     105             :         {
     106           0 :             CSLDestroy(papszReturn);
     107           0 :             return NULL;
     108             :         }
     109             : 
     110          34 :         pszTrimmedLine = CPLStrdup(pszLine);
     111             : 
     112          34 :         for (i = ((int)nLen) - 1; i >= 0 && pszLine[i] == ' '; i--)
     113           0 :             pszTrimmedLine[i] = '\0';
     114             : 
     115          34 :         if (bContinuedLine)
     116             :         {
     117             :             char *pszTmp =
     118           2 :                 (char *)VSIMalloc(strlen(papszReturn[nReturnLineCount - 1]) +
     119           2 :                                   strlen(pszTrimmedLine) + 1);
     120           2 :             if (pszTmp == NULL)
     121             :             {
     122           0 :                 CSLDestroy(papszReturn);
     123           0 :                 CPLFree(pszTrimmedLine);
     124           0 :                 return NULL;
     125             :             }
     126           2 :             strcpy(pszTmp, papszReturn[nReturnLineCount - 1]);
     127           2 :             if (pszTrimmedLine[0] == '\0')
     128           0 :                 pszTmp[strlen(papszReturn[nReturnLineCount - 1]) - 1] = 0;
     129             :             else
     130           2 :                 strcpy(pszTmp + (strlen(papszReturn[nReturnLineCount - 1]) - 1),
     131             :                        pszTrimmedLine);
     132           2 :             CPLFree(papszReturn[nReturnLineCount - 1]);
     133           2 :             papszReturn[nReturnLineCount - 1] = pszTmp;
     134             :         }
     135             :         else
     136             :         {
     137          32 :             papszReturn = CSLAddString(papszReturn, pszTrimmedLine);
     138          32 :             nReturnLineCount++;
     139             :         }
     140             : 
     141             :         /* Is it a continued line ? */
     142          34 :         bContinuedLine = (i >= 0 && pszTrimmedLine[i] == '\\');
     143             : 
     144          34 :         CPLFree(pszTrimmedLine);
     145             : 
     146          34 :         nCurPos = VSIFTellL(fp);
     147          34 :         if (VSIFReadL(&chNextChar, 1, 1, fp) != 1)
     148             :         {
     149           0 :             CSLDestroy(papszReturn);
     150           0 :             return NULL;
     151             :         }
     152          34 :         VSIFSeekL(fp, nCurPos, SEEK_SET);
     153             : 
     154          34 :         if (chNextChar == '#')
     155          28 :             pszLine = NULL;
     156             :         else
     157             :         {
     158           6 :             pszLine = CPLReadLineL(fp);
     159           6 :             nLineCount++;
     160             :         }
     161          34 :     } while (pszLine != NULL && nLineCount < MAX_LINE_COUNT_PER_HEADER);
     162             : 
     163          28 :     return (papszReturn);
     164             : }
     165             : 
     166             : /************************************************************************/
     167             : /*                              GXFOpen()                               */
     168             : /************************************************************************/
     169             : 
     170             : /**
     171             :  * Open a GXF file, and collect contents of the header.
     172             :  *
     173             :  * @param pszFilename the name of the file to open.
     174             :  *
     175             :  * @return a handle for use with other GXF functions to access the file.  This
     176             :  * will be NULL if the access fails.
     177             :  */
     178             : 
     179           4 : GXFHandle GXFOpen(const char *pszFilename)
     180             : 
     181             : {
     182             :     VSILFILE *fp;
     183             :     GXFInfo_t *psGXF;
     184             :     char szTitle[71];
     185             :     char **papszList;
     186           4 :     int nHeaderCount = 0;
     187             : 
     188             :     /* -------------------------------------------------------------------- */
     189             :     /*      We open in binary to ensure that we can efficiently seek()      */
     190             :     /*      to any location when reading scanlines randomly.  If we         */
     191             :     /*      opened as text we might still be able to seek(), but I          */
     192             :     /*      believe that on Windows, the C library has to read through      */
     193             :     /*      all the data to find the right spot taking into account DOS     */
     194             :     /*      CRs.                                                            */
     195             :     /* -------------------------------------------------------------------- */
     196           4 :     fp = VSIFOpenL(pszFilename, "rb");
     197             : 
     198           4 :     if (fp == NULL)
     199             :     {
     200             :         /* how to effectively communicate this error out? */
     201           0 :         CPLError(CE_Failure, CPLE_OpenFailed, "Unable to open file: %s\n",
     202             :                  pszFilename);
     203           0 :         return NULL;
     204             :     }
     205             : 
     206             :     /* -------------------------------------------------------------------- */
     207             :     /*      Create the GXF Information object.                              */
     208             :     /* -------------------------------------------------------------------- */
     209           4 :     psGXF = (GXFInfo_t *)VSICalloc(sizeof(GXFInfo_t), 1);
     210           4 :     psGXF->fp = fp;
     211           4 :     psGXF->dfTransformScale = 1.0;
     212           4 :     psGXF->nSense = GXFS_LL_RIGHT;
     213           4 :     psGXF->dfXPixelSize = 1.0;
     214           4 :     psGXF->dfYPixelSize = 1.0;
     215           4 :     psGXF->dfSetDummyTo = -1e12;
     216             : 
     217           4 :     psGXF->dfUnitToMeter = 1.0;
     218           4 :     psGXF->pszTitle = VSIStrdup("");
     219             : 
     220             :     /* -------------------------------------------------------------------- */
     221             :     /*      Read the header, one line at a time.                            */
     222             :     /* -------------------------------------------------------------------- */
     223          32 :     while ((papszList = GXFReadHeaderValue(fp, szTitle)) != NULL &&
     224             :            nHeaderCount < MAX_HEADER_COUNT)
     225             :     {
     226          28 :         if (STARTS_WITH_CI(szTitle, "#TITL"))
     227             :         {
     228           0 :             CPLFree(psGXF->pszTitle);
     229           0 :             psGXF->pszTitle = CPLStrdup(papszList[0]);
     230             :         }
     231          28 :         else if (STARTS_WITH_CI(szTitle, "#POIN"))
     232             :         {
     233           4 :             psGXF->nRawXSize = atoi(papszList[0]);
     234             :         }
     235          24 :         else if (STARTS_WITH_CI(szTitle, "#ROWS"))
     236             :         {
     237           4 :             psGXF->nRawYSize = atoi(papszList[0]);
     238             :         }
     239          20 :         else if (STARTS_WITH_CI(szTitle, "#PTSE"))
     240             :         {
     241           2 :             psGXF->dfXPixelSize = CPLAtof(papszList[0]);
     242             :         }
     243          18 :         else if (STARTS_WITH_CI(szTitle, "#RWSE"))
     244             :         {
     245           2 :             psGXF->dfYPixelSize = CPLAtof(papszList[0]);
     246             :         }
     247          16 :         else if (STARTS_WITH_CI(szTitle, "#DUMM"))
     248             :         {
     249           0 :             memset(psGXF->szDummy, 0, sizeof(psGXF->szDummy));
     250           0 :             strncpy(psGXF->szDummy, papszList[0], sizeof(psGXF->szDummy) - 1);
     251           0 :             psGXF->dfSetDummyTo = CPLAtof(papszList[0]);
     252             :         }
     253          16 :         else if (STARTS_WITH_CI(szTitle, "#XORI"))
     254             :         {
     255           2 :             psGXF->dfXOrigin = CPLAtof(papszList[0]);
     256             :         }
     257          14 :         else if (STARTS_WITH_CI(szTitle, "#YORI"))
     258             :         {
     259           2 :             psGXF->dfYOrigin = CPLAtof(papszList[0]);
     260             :         }
     261          12 :         else if (STARTS_WITH_CI(szTitle, "#ZMIN"))
     262             :         {
     263           0 :             psGXF->dfZMinimum = CPLAtof(papszList[0]);
     264             :         }
     265          12 :         else if (STARTS_WITH_CI(szTitle, "#ZMAX"))
     266             :         {
     267           0 :             psGXF->dfZMaximum = CPLAtof(papszList[0]);
     268             :         }
     269          12 :         else if (STARTS_WITH_CI(szTitle, "#SENS"))
     270             :         {
     271           0 :             psGXF->nSense = atoi(papszList[0]);
     272             :         }
     273          12 :         else if (STARTS_WITH_CI(szTitle, "#MAP_PROJECTION") &&
     274           2 :                  psGXF->papszMapProjection == NULL)
     275             :         {
     276           2 :             psGXF->papszMapProjection = papszList;
     277           2 :             papszList = NULL;
     278             :         }
     279          10 :         else if (STARTS_WITH_CI(szTitle, "#MAP_D") &&
     280           2 :                  psGXF->papszMapDatumTransform == NULL)
     281             :         {
     282           2 :             psGXF->papszMapDatumTransform = papszList;
     283           2 :             papszList = NULL;
     284             :         }
     285           8 :         else if (STARTS_WITH_CI(szTitle, "#UNIT") && psGXF->pszUnitName == NULL)
     286           2 :         {
     287             :             char **papszFields;
     288             : 
     289             :             papszFields =
     290           2 :                 CSLTokenizeStringComplex(papszList[0], ", ", TRUE, TRUE);
     291             : 
     292           2 :             if (CSLCount(papszFields) > 1)
     293             :             {
     294           2 :                 psGXF->pszUnitName = VSIStrdup(papszFields[0]);
     295           2 :                 psGXF->dfUnitToMeter = CPLAtof(papszFields[1]);
     296           2 :                 if (psGXF->dfUnitToMeter == 0.0)
     297           0 :                     psGXF->dfUnitToMeter = 1.0;
     298             :             }
     299             : 
     300           2 :             CSLDestroy(papszFields);
     301             :         }
     302           6 :         else if (STARTS_WITH_CI(szTitle, "#TRAN") &&
     303           2 :                  psGXF->pszTransformName == NULL)
     304           2 :         {
     305             :             char **papszFields;
     306             : 
     307             :             papszFields =
     308           2 :                 CSLTokenizeStringComplex(papszList[0], ", ", TRUE, TRUE);
     309             : 
     310           2 :             if (CSLCount(papszFields) > 1)
     311             :             {
     312           2 :                 psGXF->dfTransformScale = CPLAtof(papszFields[0]);
     313           2 :                 psGXF->dfTransformOffset = CPLAtof(papszFields[1]);
     314             :             }
     315             : 
     316           2 :             if (CSLCount(papszFields) > 2)
     317           0 :                 psGXF->pszTransformName = CPLStrdup(papszFields[2]);
     318             : 
     319           2 :             CSLDestroy(papszFields);
     320             :         }
     321           4 :         else if (STARTS_WITH_CI(szTitle, "#GTYPE"))
     322             :         {
     323           2 :             psGXF->nGType = atoi(papszList[0]);
     324           2 :             if (psGXF->nGType < 0 || psGXF->nGType > 20)
     325             :             {
     326           0 :                 CSLDestroy(papszList);
     327           0 :                 GXFClose(psGXF);
     328           0 :                 return NULL;
     329             :             }
     330             :         }
     331             : 
     332          28 :         CSLDestroy(papszList);
     333          28 :         nHeaderCount++;
     334             :     }
     335             : 
     336           4 :     CSLDestroy(papszList);
     337             : 
     338             :     /* -------------------------------------------------------------------- */
     339             :     /*      Did we find the #GRID?                                          */
     340             :     /* -------------------------------------------------------------------- */
     341           4 :     if (!STARTS_WITH_CI(szTitle, "#GRID"))
     342             :     {
     343           0 :         GXFClose(psGXF);
     344           0 :         CPLError(CE_Failure, CPLE_WrongFormat,
     345             :                  "Didn't parse through to #GRID successfully in.\n"
     346             :                  "file `%s'.\n",
     347             :                  pszFilename);
     348             : 
     349           0 :         return NULL;
     350             :     }
     351             : 
     352             :     /* -------------------------------------------------------------------- */
     353             :     /*      Allocate, and initialize the raw scanline offset array.         */
     354             :     /* -------------------------------------------------------------------- */
     355           4 :     if (psGXF->nRawYSize <= 0 || psGXF->nRawYSize >= INT_MAX)
     356             :     {
     357           0 :         GXFClose(psGXF);
     358           0 :         return NULL;
     359             :     }
     360             : 
     361             :     /* Avoid excessive memory allocation */
     362           4 :     if (psGXF->nRawYSize >= 1000000)
     363             :     {
     364             :         vsi_l_offset nCurOffset;
     365             :         vsi_l_offset nFileSize;
     366           0 :         nCurOffset = VSIFTellL(psGXF->fp);
     367           0 :         VSIFSeekL(psGXF->fp, 0, SEEK_END);
     368           0 :         nFileSize = VSIFTellL(psGXF->fp);
     369           0 :         VSIFSeekL(psGXF->fp, nCurOffset, SEEK_SET);
     370           0 :         if ((vsi_l_offset)psGXF->nRawYSize > nFileSize)
     371             :         {
     372           0 :             GXFClose(psGXF);
     373           0 :             return NULL;
     374             :         }
     375             :     }
     376             : 
     377           4 :     psGXF->panRawLineOffset =
     378           4 :         (vsi_l_offset *)VSICalloc(sizeof(vsi_l_offset), psGXF->nRawYSize + 1);
     379           4 :     if (psGXF->panRawLineOffset == NULL)
     380             :     {
     381           0 :         GXFClose(psGXF);
     382           0 :         return NULL;
     383             :     }
     384             : 
     385           4 :     psGXF->panRawLineOffset[0] = VSIFTellL(psGXF->fp);
     386             : 
     387             :     /* -------------------------------------------------------------------- */
     388             :     /*      Update the zmin/zmax values to take into account #TRANSFORM     */
     389             :     /*      information.                                                    */
     390             :     /* -------------------------------------------------------------------- */
     391           4 :     if (psGXF->dfZMinimum != 0.0 || psGXF->dfZMaximum != 0.0)
     392             :     {
     393           0 :         psGXF->dfZMinimum = (psGXF->dfZMinimum * psGXF->dfTransformScale) +
     394           0 :                             psGXF->dfTransformOffset;
     395           0 :         psGXF->dfZMaximum = (psGXF->dfZMaximum * psGXF->dfTransformScale) +
     396           0 :                             psGXF->dfTransformOffset;
     397             :     }
     398             : 
     399           4 :     return ((GXFHandle)psGXF);
     400             : }
     401             : 
     402             : /************************************************************************/
     403             : /*                              GXFClose()                              */
     404             : /************************************************************************/
     405             : 
     406             : /**
     407             :  * Close GXF file opened with GXFOpen().
     408             :  *
     409             :  * @param hGXF handle to GXF file.
     410             :  */
     411             : 
     412           4 : void GXFClose(GXFHandle hGXF)
     413             : 
     414             : {
     415           4 :     GXFInfo_t *psGXF = hGXF;
     416             : 
     417           4 :     CPLFree(psGXF->panRawLineOffset);
     418           4 :     CPLFree(psGXF->pszUnitName);
     419           4 :     CSLDestroy(psGXF->papszMapDatumTransform);
     420           4 :     CSLDestroy(psGXF->papszMapProjection);
     421           4 :     CPLFree(psGXF->pszTitle);
     422           4 :     CPLFree(psGXF->pszTransformName);
     423             : 
     424           4 :     VSIFCloseL(psGXF->fp);
     425             : 
     426           4 :     CPLReadLineL(NULL);
     427             : 
     428           4 :     CPLFree(psGXF);
     429           4 : }
     430             : 
     431             : /************************************************************************/
     432             : /*                           GXFParseBase90()                           */
     433             : /*                                                                      */
     434             : /*      Parse a base 90 number ... exceptions (repeat, and dummy)       */
     435             : /*      values have to be recognised outside this function.             */
     436             : /************************************************************************/
     437             : 
     438             : CPL_NOSANITIZE_UNSIGNED_INT_OVERFLOW
     439          54 : static double GXFParseBase90(GXFInfo_t *psGXF, const char *pszText, int bScale)
     440             : 
     441             : {
     442          54 :     int i = 0;
     443          54 :     unsigned int nValue = 0;
     444             : 
     445         216 :     while (i < psGXF->nGType)
     446             :     {
     447         162 :         nValue = nValue * 90U + (unsigned)(pszText[i] - 37);
     448         162 :         i++;
     449             :     }
     450             : 
     451          54 :     if (bScale)
     452          41 :         return ((nValue * psGXF->dfTransformScale) + psGXF->dfTransformOffset);
     453             :     else
     454          13 :         return (nValue);
     455             : }
     456             : 
     457             : /************************************************************************/
     458             : /*                       GXFReadRawScanlineFrom()                       */
     459             : /************************************************************************/
     460             : 
     461          20 : static CPLErr GXFReadRawScanlineFrom(GXFInfo_t *psGXF, vsi_l_offset iOffset,
     462             :                                      vsi_l_offset *pnNewOffset,
     463             :                                      double *padfLineBuf)
     464             : 
     465             : {
     466             :     const char *pszLine;
     467          20 :     int nValuesRead = 0, nValuesSought = psGXF->nRawXSize;
     468             : 
     469          20 :     if (VSIFSeekL(psGXF->fp, iOffset, SEEK_SET) != 0)
     470           0 :         return CE_Failure;
     471             : 
     472          42 :     while (nValuesRead < nValuesSought)
     473             :     {
     474          22 :         pszLine = CPLReadLineL(psGXF->fp);
     475          22 :         if (pszLine == NULL)
     476           0 :             break;
     477             : 
     478             :         /* --------------------------------------------------------------------
     479             :          */
     480             :         /*      Uncompressed case. */
     481             :         /* --------------------------------------------------------------------
     482             :          */
     483          22 :         if (psGXF->nGType == 0)
     484             :         {
     485             :             /* we could just tokenize the line, but that's pretty expensive.
     486             :                Instead I will parse on white space ``by hand''. */
     487          27 :             while (*pszLine != '\0' && nValuesRead < nValuesSought)
     488             :             {
     489             :                 int i;
     490             : 
     491             :                 /* skip leading white space */
     492          22 :                 for (; isspace((unsigned char)*pszLine); pszLine++)
     493             :                 {
     494             :                 }
     495             : 
     496             :                 /* Skip the data value (non white space) */
     497          20 :                 for (i = 0;
     498          78 :                      pszLine[i] != '\0' && !isspace((unsigned char)pszLine[i]);
     499          58 :                      i++)
     500             :                 {
     501             :                 }
     502             : 
     503          20 :                 if (strncmp(pszLine, psGXF->szDummy, i) == 0)
     504             :                 {
     505           0 :                     padfLineBuf[nValuesRead++] = psGXF->dfSetDummyTo;
     506             :                 }
     507             :                 else
     508             :                 {
     509          20 :                     padfLineBuf[nValuesRead++] = CPLAtof(pszLine);
     510             :                 }
     511             : 
     512             :                 /* skip further whitespace */
     513          33 :                 for (pszLine += i; isspace((unsigned char)*pszLine); pszLine++)
     514             :                 {
     515             :                 }
     516             :             }
     517             :         }
     518             : 
     519             :         /* --------------------------------------------------------------------
     520             :          */
     521             :         /*      Compressed case. */
     522             :         /* --------------------------------------------------------------------
     523             :          */
     524             :         else
     525             :         {
     526          15 :             size_t nLineLenOri = strlen(pszLine);
     527          15 :             int nLineLen = (int)nLineLenOri;
     528             : 
     529          94 :             while (*pszLine != '\0' && nValuesRead < nValuesSought)
     530             :             {
     531          79 :                 if (nLineLen < psGXF->nGType)
     532           0 :                     return CE_Failure;
     533             : 
     534          79 :                 if (pszLine[0] == '!')
     535             :                 {
     536          25 :                     padfLineBuf[nValuesRead++] = psGXF->dfSetDummyTo;
     537             :                 }
     538          54 :                 else if (pszLine[0] == '"')
     539             :                 {
     540             :                     int nCount, i;
     541             :                     double dfValue;
     542             : 
     543          13 :                     pszLine += psGXF->nGType;
     544          13 :                     nLineLen -= psGXF->nGType;
     545          13 :                     if (nLineLen < psGXF->nGType)
     546             :                     {
     547           0 :                         pszLine = CPLReadLineL(psGXF->fp);
     548           0 :                         if (pszLine == NULL)
     549           0 :                             return CE_Failure;
     550           0 :                         nLineLenOri = strlen(pszLine);
     551           0 :                         nLineLen = (int)nLineLenOri;
     552           0 :                         if (nLineLen < psGXF->nGType)
     553           0 :                             return CE_Failure;
     554             :                     }
     555             : 
     556          13 :                     nCount = (int)GXFParseBase90(psGXF, pszLine, FALSE);
     557          13 :                     pszLine += psGXF->nGType;
     558          13 :                     nLineLen -= psGXF->nGType;
     559             : 
     560          13 :                     if (nLineLen < psGXF->nGType)
     561             :                     {
     562           0 :                         pszLine = CPLReadLineL(psGXF->fp);
     563           0 :                         if (pszLine == NULL)
     564           0 :                             return CE_Failure;
     565           0 :                         nLineLenOri = strlen(pszLine);
     566           0 :                         nLineLen = (int)nLineLenOri;
     567           0 :                         if (nLineLen < psGXF->nGType)
     568           0 :                             return CE_Failure;
     569             :                     }
     570             : 
     571          13 :                     if (*pszLine == '!')
     572          13 :                         dfValue = psGXF->dfSetDummyTo;
     573             :                     else
     574           0 :                         dfValue = GXFParseBase90(psGXF, pszLine, TRUE);
     575             : 
     576          13 :                     if (nValuesRead + nCount > nValuesSought)
     577             :                     {
     578           0 :                         CPLError(CE_Failure, CPLE_AppDefined,
     579             :                                  "Wrong count value");
     580           0 :                         return CE_Failure;
     581             :                     }
     582             : 
     583          97 :                     for (i = 0; i < nCount && nValuesRead < nValuesSought; i++)
     584          84 :                         padfLineBuf[nValuesRead++] = dfValue;
     585             :                 }
     586             :                 else
     587             :                 {
     588          41 :                     padfLineBuf[nValuesRead++] =
     589          41 :                         GXFParseBase90(psGXF, pszLine, TRUE);
     590             :                 }
     591             : 
     592          79 :                 pszLine += psGXF->nGType;
     593          79 :                 nLineLen -= psGXF->nGType;
     594             :             }
     595             :         }
     596             :     }
     597             : 
     598             :     /* -------------------------------------------------------------------- */
     599             :     /*      Return the new offset, if requested.                            */
     600             :     /* -------------------------------------------------------------------- */
     601          20 :     if (pnNewOffset != NULL)
     602             :     {
     603          20 :         *pnNewOffset = VSIFTellL(psGXF->fp);
     604             :     }
     605             : 
     606          20 :     return CE_None;
     607             : }
     608             : 
     609             : /************************************************************************/
     610             : /*                           GXFGetScanline()                           */
     611             : /************************************************************************/
     612             : 
     613             : /**
     614             :  * Read a scanline of raster data from GXF file.
     615             :  *
     616             :  * This function operates similarly to GXFGetRawScanline(), but it
     617             :  * attempts to mirror data horizontally or vertically based on the #SENSE
     618             :  * flag to return data in a top to bottom, and left to right organization.
     619             :  * If the file is organized in columns (#SENSE is GXFS_UR_DOWN, GXFS_UL_DOWN,
     620             :  * GXFS_LR_UP, or GXFS_LL_UP) then this function will fail, returning
     621             :  * CE_Failure, and reporting a sense error.
     622             :  *
     623             :  * See GXFGetRawScanline() for other notes.
     624             :  *
     625             :  * @param hGXF the GXF file handle, as returned from GXFOpen().
     626             :  * @param iScanline the scanline to read, zero is the top scanline.
     627             :  * @param padfLineBuf a buffer of doubles into which the scanline pixel
     628             :  * values are read.  This must be at least as long as a scanline.
     629             :  *
     630             :  * @return CE_None if access succeeds or CE_Failure if something goes wrong.
     631             :  */
     632             : 
     633          11 : CPLErr GXFGetScanline(GXFHandle hGXF, int iScanline, double *padfLineBuf)
     634             : 
     635             : {
     636          11 :     GXFInfo_t *psGXF = hGXF;
     637             :     CPLErr nErr;
     638             :     int iRawScanline;
     639             : 
     640          11 :     if (psGXF->nSense == GXFS_LL_RIGHT || psGXF->nSense == GXFS_LR_LEFT)
     641             :     {
     642          11 :         iRawScanline = psGXF->nRawYSize - iScanline - 1;
     643             :     }
     644             : 
     645           0 :     else if (psGXF->nSense == GXFS_UL_RIGHT || psGXF->nSense == GXFS_UR_LEFT)
     646             :     {
     647           0 :         iRawScanline = iScanline;
     648             :     }
     649             :     else
     650             :     {
     651           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     652             :                  "Unable to support vertically oriented images.");
     653           0 :         return (CE_Failure);
     654             :     }
     655             : 
     656          11 :     nErr = GXFGetRawScanline(hGXF, iRawScanline, padfLineBuf);
     657             : 
     658          11 :     if (nErr == CE_None &&
     659          11 :         (psGXF->nSense == GXFS_LR_LEFT || psGXF->nSense == GXFS_UR_LEFT))
     660             :     {
     661             :         int i;
     662             :         double dfTemp;
     663             : 
     664           0 :         for (i = psGXF->nRawXSize / 2 - 1; i >= 0; i--)
     665             :         {
     666           0 :             dfTemp = padfLineBuf[i];
     667           0 :             padfLineBuf[i] = padfLineBuf[psGXF->nRawXSize - i - 1];
     668           0 :             padfLineBuf[psGXF->nRawXSize - i - 1] = dfTemp;
     669             :         }
     670             :     }
     671             : 
     672          11 :     return (nErr);
     673             : }
     674             : 
     675             : /************************************************************************/
     676             : /*                         GXFGetRawScanline()                          */
     677             : /************************************************************************/
     678             : 
     679             : /**
     680             :  * Read a scanline of raster data from GXF file.
     681             :  *
     682             :  * This function will read a row of data from the GXF file.  It is "Raw"
     683             :  * in the sense that it doesn't attempt to account for the #SENSE flag as
     684             :  * the GXFGetScanline() function does.  Unlike GXFGetScanline(), this function
     685             :  * supports column organized files.
     686             :  *
     687             :  * Any dummy pixels are assigned the dummy value indicated by GXFGetRawInfo().
     688             :  *
     689             :  * @param hGXF the GXF file handle, as returned from GXFOpen().
     690             :  * @param iScanline the scanline to read, zero is the first scanline in the
     691             :  * file.
     692             :  * @param padfLineBuf a buffer of doubles into which the scanline pixel
     693             :  * values are read.  This must be at least as long as a scanline.
     694             :  *
     695             :  * @return CE_None if access succeeds or CE_Failure if something goes wrong.
     696             :  */
     697             : 
     698          20 : CPLErr GXFGetRawScanline(GXFHandle hGXF, int iScanline, double *padfLineBuf)
     699             : 
     700             : {
     701          20 :     GXFInfo_t *psGXF = hGXF;
     702             :     CPLErr eErr;
     703             : 
     704             :     /* -------------------------------------------------------------------- */
     705             :     /*      Validate scanline.                                              */
     706             :     /* -------------------------------------------------------------------- */
     707          20 :     if (iScanline < 0 || iScanline >= psGXF->nRawYSize)
     708             :     {
     709           0 :         CPLError(CE_Failure, CPLE_IllegalArg,
     710             :                  "GXFGetRawScanline(): Scanline `%d' does not exist.\n",
     711             :                  iScanline);
     712           0 :         return CE_Failure;
     713             :     }
     714             : 
     715             :     /* -------------------------------------------------------------------- */
     716             :     /*      If we don't have the requested scanline, fetch preceding        */
     717             :     /*      scanlines to find the pointer to this scanline.                 */
     718             :     /* -------------------------------------------------------------------- */
     719          20 :     if (psGXF->panRawLineOffset[iScanline] == 0)
     720             :     {
     721             :         int i;
     722             : 
     723           2 :         CPLAssert(iScanline > 0);
     724             : 
     725          11 :         for (i = 0; i < iScanline; i++)
     726             :         {
     727           9 :             if (psGXF->panRawLineOffset[i + 1] == 0)
     728             :             {
     729           9 :                 eErr = GXFGetRawScanline(hGXF, i, padfLineBuf);
     730           9 :                 if (eErr != CE_None)
     731           0 :                     return (eErr);
     732             :             }
     733             :         }
     734             :     }
     735             : 
     736             :     /* -------------------------------------------------------------------- */
     737             :     /*      Get this scanline, and update the offset for the next line.     */
     738             :     /* -------------------------------------------------------------------- */
     739          20 :     eErr = GXFReadRawScanlineFrom(psGXF, psGXF->panRawLineOffset[iScanline],
     740          20 :                                   psGXF->panRawLineOffset + iScanline + 1,
     741             :                                   padfLineBuf);
     742             : 
     743          20 :     return eErr;
     744             : }
     745             : 
     746             : /************************************************************************/
     747             : /*                         GXFScanForZMinMax()                          */
     748             : /*                                                                      */
     749             : /*      The header doesn't contain the ZMin/ZMax values, but the        */
     750             : /*      application has requested it ... scan the entire image for      */
     751             : /*      it.                                                             */
     752             : /************************************************************************/
     753             : 
     754           0 : static void GXFScanForZMinMax(GXFHandle hGXF)
     755             : 
     756             : {
     757           0 :     GXFInfo_t *psGXF = hGXF;
     758             :     int iLine, iPixel;
     759             :     double *padfScanline;
     760             : 
     761           0 :     padfScanline = (double *)VSICalloc(sizeof(double), psGXF->nRawXSize);
     762           0 :     if (padfScanline == NULL)
     763           0 :         return;
     764             : 
     765           0 :     psGXF->dfZMinimum = 1e50;
     766           0 :     psGXF->dfZMaximum = -1e50;
     767             : 
     768           0 :     for (iLine = 0; iLine < psGXF->nRawYSize; iLine++)
     769             :     {
     770           0 :         if (GXFGetRawScanline(hGXF, iLine, padfScanline) != CE_None)
     771           0 :             break;
     772             : 
     773           0 :         for (iPixel = 0; iPixel < psGXF->nRawXSize; iPixel++)
     774             :         {
     775           0 :             if (padfScanline[iPixel] != psGXF->dfSetDummyTo)
     776             :             {
     777           0 :                 psGXF->dfZMinimum =
     778           0 :                     MIN(psGXF->dfZMinimum, padfScanline[iPixel]);
     779           0 :                 psGXF->dfZMaximum =
     780           0 :                     MAX(psGXF->dfZMaximum, padfScanline[iPixel]);
     781             :             }
     782             :         }
     783             :     }
     784             : 
     785           0 :     VSIFree(padfScanline);
     786             : 
     787             :     /* -------------------------------------------------------------------- */
     788             :     /*      Did we get any real data points?                                */
     789             :     /* -------------------------------------------------------------------- */
     790           0 :     if (psGXF->dfZMinimum > psGXF->dfZMaximum)
     791             :     {
     792           0 :         psGXF->dfZMinimum = 0.0;
     793           0 :         psGXF->dfZMaximum = 0.0;
     794             :     }
     795             : }
     796             : 
     797             : /************************************************************************/
     798             : /*                             GXFGetRawInfo()                          */
     799             : /************************************************************************/
     800             : 
     801             : /**
     802             :  * Fetch header information about a GXF file.
     803             :  *
     804             :  * Note that the X and Y sizes are of the raw raster and don't take into
     805             :  * account the #SENSE flag.  If the file is column oriented (rows in the
     806             :  * files are actually columns in the raster) these values would need to be
     807             :  * transposed for the actual raster.
     808             :  *
     809             :  * The legal pnSense values are:
     810             :  * <ul>
     811             :  * <li> GXFS_LL_UP(-1): lower left origin, scanning up.
     812             :  * <li> GXFS_LL_RIGHT(1): lower left origin, scanning right.
     813             :  * <li> GXFS_UL_RIGHT(-2): upper left origin, scanning right.
     814             :  * <li> GXFS_UL_DOWN(2): upper left origin, scanning down.
     815             :  * <li> GXFS_UR_DOWN(-3): upper right origin, scanning down.
     816             :  * <li> GXFS_UR_LEFT(3): upper right origin, scanning left.
     817             :  * <li> GXFS_LR_LEFT(-4): lower right origin, scanning left.
     818             :  * <li> GXFS_LR_UP(4): lower right origin, scanning up.
     819             :  * </ul>
     820             :  *
     821             :  * Note that the GXFGetScanline() function attempts to provide a GXFS_UL_RIGHT
     822             :  * view onto files, but doesn't handle the *_DOWN and *_UP oriented files.
     823             :  *
     824             :  * The Z min and max values may not occur in the GXF header.  If they are
     825             :  * requested, and aren't available in the header the entire file is scanned
     826             :  * in order to establish them.  This can be expensive.
     827             :  *
     828             :  * If no #DUMMY value was specified in the file, a default of -1e12 is used.
     829             :  *
     830             :  * @param hGXF handle to GXF file returned by GXFOpen().
     831             :  * @param pnXSize int to be set with the width of the raw raster.  May be NULL.
     832             :  * @param pnYSize int to be set with the height of the raw raster. May be NULL.
     833             :  * @param pnSense int to set with #SENSE flag, may be NULL.
     834             :  * @param pdfZMin double to set with minimum raster value, may be NULL.
     835             :  * @param pdfZMax double to set with minimum raster value, may be NULL.
     836             :  * @param pdfDummy double to set with dummy (nodata / invalid data) pixel
     837             :  * value.
     838             :  */
     839             : 
     840           4 : CPLErr GXFGetRawInfo(GXFHandle hGXF, int *pnXSize, int *pnYSize, int *pnSense,
     841             :                      double *pdfZMin, double *pdfZMax, double *pdfDummy)
     842             : 
     843             : {
     844           4 :     GXFInfo_t *psGXF = hGXF;
     845             : 
     846           4 :     if (pnXSize != NULL)
     847           4 :         *pnXSize = psGXF->nRawXSize;
     848             : 
     849           4 :     if (pnYSize != NULL)
     850           4 :         *pnYSize = psGXF->nRawYSize;
     851             : 
     852           4 :     if (pnSense != NULL)
     853           0 :         *pnSense = psGXF->nSense;
     854             : 
     855           4 :     if ((pdfZMin != NULL || pdfZMax != NULL) && psGXF->dfZMinimum == 0.0 &&
     856           0 :         psGXF->dfZMaximum == 0.0)
     857             :     {
     858           0 :         GXFScanForZMinMax(hGXF);
     859             :     }
     860             : 
     861           4 :     if (pdfZMin != NULL)
     862           0 :         *pdfZMin = psGXF->dfZMinimum;
     863             : 
     864           4 :     if (pdfZMax != NULL)
     865           0 :         *pdfZMax = psGXF->dfZMaximum;
     866             : 
     867           4 :     if (pdfDummy != NULL)
     868           4 :         *pdfDummy = psGXF->dfSetDummyTo;
     869             : 
     870           4 :     return (CE_None);
     871             : }
     872             : 
     873             : /************************************************************************/
     874             : /*                        GXFGetMapProjection()                         */
     875             : /************************************************************************/
     876             : 
     877             : /**
     878             :  * Return the lines related to the map projection.  It is up to
     879             :  * the caller to parse them and interpret.  The return result
     880             :  * will be NULL if no #MAP_PROJECTION line was found in the header.
     881             :  *
     882             :  * @param hGXF the GXF file handle.
     883             :  *
     884             :  * @return a NULL terminated array of string pointers containing the
     885             :  * projection, or NULL.  The strings remained owned by the GXF API, and
     886             :  * should not be modified or freed by the caller.
     887             :  */
     888             : 
     889           0 : char **GXFGetMapProjection(GXFHandle hGXF)
     890             : 
     891             : {
     892           0 :     return ((hGXF)->papszMapProjection);
     893             : }
     894             : 
     895             : /************************************************************************/
     896             : /*                      GXFGetMapDatumTransform()                       */
     897             : /************************************************************************/
     898             : 
     899             : /**
     900             :  * Return the lines related to the datum transformation.  It is up to
     901             :  * the caller to parse them and interpret.  The return result
     902             :  * will be NULL if no #MAP_DATUM_TRANSFORM line was found in the header.
     903             :  *
     904             :  * @param hGXF the GXF file handle.
     905             :  *
     906             :  * @return a NULL terminated array of string pointers containing the
     907             :  * datum, or NULL.  The strings remained owned by the GXF API, and
     908             :  * should not be modified or freed by the caller.
     909             :  */
     910             : 
     911           0 : char **GXFGetMapDatumTransform(GXFHandle hGXF)
     912             : 
     913             : {
     914           0 :     return ((hGXF)->papszMapDatumTransform);
     915             : }
     916             : 
     917             : /************************************************************************/
     918             : /*                         GXFGetRawPosition()                          */
     919             : /************************************************************************/
     920             : 
     921             : /**
     922             :  * Get the raw grid positioning information.
     923             :  *
     924             :  * Note that these coordinates refer to the raw grid, and are in the units
     925             :  * specified by the #UNITS field.  See GXFGetPosition() for a similar
     926             :  * function that takes into account the #SENSE values similarly to
     927             :  * GXFGetScanline().
     928             :  *
     929             :  * Note that the pixel values are considered to be point values in GXF,
     930             :  * and thus the origin is for the first point.  If you consider the pixels
     931             :  * to be areas, then the origin is for the center of the origin pixel, not
     932             :  * the outer corner.
     933             :  *
     934             :  * @param hGXF the GXF file handle.
     935             :  * @param pdfXOrigin X position of the origin in the base coordinate system.
     936             :  * @param pdfYOrigin Y position of the origin in the base coordinate system.
     937             :  * @param pdfXPixelSize X pixel size in base coordinates.
     938             :  * @param pdfYPixelSize Y pixel size in base coordinates.
     939             :  * @param pdfRotation rotation in degrees counter-clockwise from the
     940             :  * base coordinate system.
     941             :  *
     942             :  * @return Returns CE_None if successful, or CE_Failure if no posiitioning
     943             :  * information was found in the file.
     944             :  */
     945             : 
     946           0 : CPLErr GXFGetRawPosition(GXFHandle hGXF, double *pdfXOrigin, double *pdfYOrigin,
     947             :                          double *pdfXPixelSize, double *pdfYPixelSize,
     948             :                          double *pdfRotation)
     949             : 
     950             : {
     951           0 :     GXFInfo_t *psGXF = hGXF;
     952             : 
     953           0 :     if (pdfXOrigin != NULL)
     954           0 :         *pdfXOrigin = psGXF->dfXOrigin;
     955           0 :     if (pdfYOrigin != NULL)
     956           0 :         *pdfYOrigin = psGXF->dfYOrigin;
     957           0 :     if (pdfXPixelSize != NULL)
     958           0 :         *pdfXPixelSize = psGXF->dfXPixelSize;
     959           0 :     if (pdfYPixelSize != NULL)
     960           0 :         *pdfYPixelSize = psGXF->dfYPixelSize;
     961           0 :     if (pdfRotation != NULL)
     962           0 :         *pdfRotation = psGXF->dfRotation;
     963             : 
     964           0 :     if (psGXF->dfXOrigin == 0.0 && psGXF->dfYOrigin == 0.0 &&
     965           0 :         psGXF->dfXPixelSize == 0.0 && psGXF->dfYPixelSize == 0.0)
     966           0 :         return (CE_Failure);
     967             :     else
     968           0 :         return (CE_None);
     969             : }
     970             : 
     971             : /************************************************************************/
     972             : /*                           GXFGetPosition()                           */
     973             : /************************************************************************/
     974             : 
     975             : /**
     976             :  * Get the grid positioning information.
     977             :  *
     978             :  * Note that these coordinates refer to the grid positioning after taking
     979             :  * into account the #SENSE flag (as is done by the GXFGetScanline()) function.
     980             :  *
     981             :  * Note that the pixel values are considered to be point values in GXF,
     982             :  * and thus the origin is for the first point.  If you consider the pixels
     983             :  * to be areas, then the origin is for the center of the origin pixel, not
     984             :  * the outer corner.
     985             :  *
     986             :  * This function does not support vertically oriented images, nor does it
     987             :  * properly transform rotation for images with a SENSE other than
     988             :  * GXFS_UL_RIGHT.
     989             :  *
     990             :  * @param hGXF the GXF file handle.
     991             :  * @param pdfXOrigin X position of the origin in the base coordinate system.
     992             :  * @param pdfYOrigin Y position of the origin in the base coordinate system.
     993             :  * @param pdfXPixelSize X pixel size in base coordinates.
     994             :  * @param pdfYPixelSize Y pixel size in base coordinates.
     995             :  * @param pdfRotation rotation in degrees counter-clockwise from the
     996             :  * base coordinate system.
     997             :  *
     998             :  * @return Returns CE_None if successful, or CE_Failure if no posiitioning
     999             :  * information was found in the file.
    1000             :  */
    1001             : 
    1002           0 : CPLErr GXFGetPosition(GXFHandle hGXF, double *pdfXOrigin, double *pdfYOrigin,
    1003             :                       double *pdfXPixelSize, double *pdfYPixelSize,
    1004             :                       double *pdfRotation)
    1005             : 
    1006             : {
    1007           0 :     GXFInfo_t *psGXF = hGXF;
    1008             :     double dfCXOrigin, dfCYOrigin, dfCXPixelSize, dfCYPixelSize;
    1009             : 
    1010           0 :     switch (psGXF->nSense)
    1011             :     {
    1012           0 :         case GXFS_UL_RIGHT:
    1013           0 :             dfCXOrigin = psGXF->dfXOrigin;
    1014           0 :             dfCYOrigin = psGXF->dfYOrigin;
    1015           0 :             dfCXPixelSize = psGXF->dfXPixelSize;
    1016           0 :             dfCYPixelSize = psGXF->dfYPixelSize;
    1017           0 :             break;
    1018             : 
    1019           0 :         case GXFS_UR_LEFT:
    1020           0 :             dfCXOrigin =
    1021           0 :                 psGXF->dfXOrigin - (psGXF->nRawXSize - 1) * psGXF->dfXPixelSize;
    1022           0 :             dfCYOrigin = psGXF->dfYOrigin;
    1023           0 :             dfCXPixelSize = psGXF->dfXPixelSize;
    1024           0 :             dfCYPixelSize = psGXF->dfYPixelSize;
    1025           0 :             break;
    1026             : 
    1027           0 :         case GXFS_LL_RIGHT:
    1028           0 :             dfCXOrigin = psGXF->dfXOrigin;
    1029           0 :             dfCYOrigin =
    1030           0 :                 psGXF->dfYOrigin + (psGXF->nRawYSize - 1) * psGXF->dfYPixelSize;
    1031           0 :             dfCXPixelSize = psGXF->dfXPixelSize;
    1032           0 :             dfCYPixelSize = psGXF->dfYPixelSize;
    1033           0 :             break;
    1034             : 
    1035           0 :         case GXFS_LR_LEFT:
    1036           0 :             dfCXOrigin =
    1037           0 :                 psGXF->dfXOrigin - (psGXF->nRawXSize - 1) * psGXF->dfXPixelSize;
    1038           0 :             dfCYOrigin =
    1039           0 :                 psGXF->dfYOrigin + (psGXF->nRawYSize - 1) * psGXF->dfYPixelSize;
    1040           0 :             dfCXPixelSize = psGXF->dfXPixelSize;
    1041           0 :             dfCYPixelSize = psGXF->dfYPixelSize;
    1042           0 :             break;
    1043             : 
    1044           0 :         default:
    1045           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1046             :                      "GXFGetPosition() doesn't support vertically organized "
    1047             :                      "images.");
    1048           0 :             return CE_Failure;
    1049             :     }
    1050             : 
    1051           0 :     if (pdfXOrigin != NULL)
    1052           0 :         *pdfXOrigin = dfCXOrigin;
    1053           0 :     if (pdfYOrigin != NULL)
    1054           0 :         *pdfYOrigin = dfCYOrigin;
    1055           0 :     if (pdfXPixelSize != NULL)
    1056           0 :         *pdfXPixelSize = dfCXPixelSize;
    1057           0 :     if (pdfYPixelSize != NULL)
    1058           0 :         *pdfYPixelSize = dfCYPixelSize;
    1059           0 :     if (pdfRotation != NULL)
    1060           0 :         *pdfRotation = psGXF->dfRotation;
    1061             : 
    1062           0 :     if (psGXF->dfXOrigin == 0.0 && psGXF->dfYOrigin == 0.0 &&
    1063           0 :         psGXF->dfXPixelSize == 0.0 && psGXF->dfYPixelSize == 0.0)
    1064           0 :         return (CE_Failure);
    1065             :     else
    1066           0 :         return (CE_None);
    1067             : }

Generated by: LCOV version 1.14