text: /******************************************************************************
   *
   * Project:  OpenGIS Simple Features Reference Implementation
   * Purpose:  Utility functions for OGR classes, including some related to
   *           parsing well known text format vectors.
   * Author:   Frank Warmerdam, warmerdam@pobox.com
   *
   ******************************************************************************
   * Copyright (c) 1999, Frank Warmerdam
   * Copyright (c) 2008-2014, Even Rouault <even dot rouault at mines-paris dot org>
   *
   * Permission is hereby granted, free of charge, to any person obtaining a
   * copy of this software and associated documentation files (the "Software"),
   * to deal in the Software without restriction, including without limitation
   * the rights to use, copy, modify, merge, publish, distribute, sublicense,
   * and/or sell copies of the Software, and to permit persons to whom the
   * Software is furnished to do so, subject to the following conditions:
   *
   * The above copyright notice and this permission notice shall be included
   * in all copies or substantial portions of the Software.
   *
   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
   * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
   * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
   * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
   * DEALINGS IN THE SOFTWARE.
   ****************************************************************************/
  
  #include "cpl_port.h"
  #include "ogr_p.h"
  
  #include <cmath>
  #include <cstddef>
  #include <cstdio>
  #include <cstdlib>
  #include <cstring>
  #include <cctype>
  #include <limits>
  
  #include "cpl_conv.h"
  #include "cpl_error.h"
  #include "cpl_string.h"
  #include "cpl_time.h"
  #include "cpl_vsi.h"
  #include "gdal.h"
  #include "ogr_core.h"
  #include "ogr_geometry.h"
  #include "ogrsf_frmts.h"
  
  CPL_CVSID("$Id$")
  
  // Returns whether a double fits within an int.
  // Unable to put this in cpl_port.h as include limit breaks grib.
  inline bool CPLIsDoubleAnInt(double d)
  {
      // Write it this way to detect NaN
      if ( !(d >= std::numeric_limits<int>::min() &&
             d <= std::numeric_limits<int>::max()) )
      {
          return false;
      }
      return d == static_cast<double>(static_cast<int>(d));
  }
  
  /************************************************************************/
  /*                        OGRFormatDouble()                             */
  /************************************************************************/
  
  void OGRFormatDouble( char *pszBuffer, int nBufferLen, double dfVal,
                        char chDecimalSep, int nPrecision,
                        char chConversionSpecifier )
  {
      // So to have identical cross platform representation.
      if( CPLIsInf(dfVal) )
      {
          if( dfVal > 0 )
              CPLsnprintf(pszBuffer, nBufferLen, "%s", "inf");
          else
              CPLsnprintf(pszBuffer, nBufferLen, "%s", "-inf");
          return;
      }
      if( CPLIsNan(dfVal) )
      {
          CPLsnprintf(pszBuffer, nBufferLen, "%s", "nan");
          return;
      }
  
      char szFormat[16] = {};
      snprintf(szFormat, sizeof(szFormat),
               "%%.%d%c", nPrecision, chConversionSpecifier);
  
      int ret = CPLsnprintf(pszBuffer, nBufferLen, szFormat, dfVal);
      // Windows CRT does not conform with C99 and returns -1 when buffer is
      // truncated.
      if( ret >= nBufferLen || ret == -1 )
      {
          CPLsnprintf(pszBuffer, nBufferLen, "%s", "too_big");
          return;
      }
  
      if( chConversionSpecifier == 'g' && strchr(pszBuffer, 'e') )
          return;
  
      const bool bRound =
          CPLTestBool(CPLGetConfigOption("OGR_WKT_ROUND", "TRUE"));
  
      int nTruncations = 0;
      while( nPrecision > 0 )
      {
          int i = 0;
          int nCountBeforeDot = 0;
          int iDotPos = -1;
          while( pszBuffer[i] != '\0' )
          {
              if( pszBuffer[i] == '.' && chDecimalSep != '\0' )
              {
                  iDotPos = i;
                  pszBuffer[i] = chDecimalSep;
              }
              else if( iDotPos < 0 && pszBuffer[i] != '-' )
                  ++nCountBeforeDot;
              ++i;
          }
          if( iDotPos < 0 )
              break;
  
      /* -------------------------------------------------------------------- */
      /*      Trim trailing 00000x's as they are likely roundoff error.       */
      /* -------------------------------------------------------------------- */
          if( i > 10 && bRound )
          {
              if(  // && pszBuffer[i-1] == '1' &&
                  pszBuffer[i-2] == '0'
                  && pszBuffer[i-3] == '0'
                  && pszBuffer[i-4] == '0'
                  && pszBuffer[i-5] == '0'
                  && pszBuffer[i-6] == '0' )
              {
                  pszBuffer[--i] = '\0';
              }
              else if( i - 8 > iDotPos &&  // pszBuffer[i-1] == '1'
                       // && pszBuffer[i-2] == '0' &&
                       (nCountBeforeDot >= 4 || pszBuffer[i-3] == '0')
                       && (nCountBeforeDot >= 5 || pszBuffer[i-4] == '0')
                       && (nCountBeforeDot >= 6 || pszBuffer[i-5] == '0')
                       && (nCountBeforeDot >= 7 || pszBuffer[i-6] == '0')
                       && (nCountBeforeDot >= 8 || pszBuffer[i-7] == '0')
                       && pszBuffer[i-8] == '0'
                       && pszBuffer[i-9] == '0')
              {
                  i -= 8;
                  pszBuffer[i] = '\0';
              }
          }
  
      /* -------------------------------------------------------------------- */
      /*      Trim trailing zeros.                                            */
      /* -------------------------------------------------------------------- */
          while( i > 2 && pszBuffer[i-1] == '0' && pszBuffer[i-2] != '.' )
          {
              pszBuffer[--i] = '\0';
          }
  
          if( !bRound )
              return;
  
      /* -------------------------------------------------------------------- */
      /*      Detect trailing 99999X's as they are likely roundoff error.     */
      /* -------------------------------------------------------------------- */
          if( i > 10 &&
              nPrecision + nTruncations >= 15)
          {
              if(  //pszBuffer[i-1] == '9' &&
                  pszBuffer[i-2] == '9'
                  && pszBuffer[i-3] == '9'
                  && pszBuffer[i-4] == '9'
                  && pszBuffer[i-5] == '9'
                  && pszBuffer[i-6] == '9' )
              {
                  --nPrecision;
                  ++nTruncations;
                  snprintf(szFormat, sizeof(szFormat),
                           "%%.%d%c", nPrecision, chConversionSpecifier);
                  CPLsnprintf(pszBuffer, nBufferLen, szFormat, dfVal);
                  if( chConversionSpecifier == 'g' && strchr(pszBuffer, 'e') )
                      return;
                  continue;
              }
              else if( i - 9 > iDotPos &&
                       // pszBuffer[i-1] == '9' &&
                       //pszBuffer[i-2] == '9' &&
                      (nCountBeforeDot >= 4 || pszBuffer[i-3] == '9')
                       && (nCountBeforeDot >= 5 || pszBuffer[i-4] == '9')
                       && (nCountBeforeDot >= 6 || pszBuffer[i-5] == '9')
                       && (nCountBeforeDot >= 7 || pszBuffer[i-6] == '9')
                       && (nCountBeforeDot >= 8 || pszBuffer[i-7] == '9')
                       && pszBuffer[i-8] == '9'
                       && pszBuffer[i-9] == '9')
              {
                  --nPrecision;
                  ++nTruncations;
                  snprintf(szFormat, sizeof(szFormat),
                           "%%.%d%c", nPrecision, chConversionSpecifier);
                  CPLsnprintf(pszBuffer, nBufferLen, szFormat, dfVal);
                  if( chConversionSpecifier == 'g' && strchr(pszBuffer, 'e') )
                      return;
                  continue;
              }
          }
  
          break;
      }
  }
  
  /************************************************************************/
  /*                        OGRMakeWktCoordinate()                        */
  /*                                                                      */
  /*      Format a well known text coordinate, trying to keep the         */
  /*      ASCII representation compact, but accurate.  These rules        */
  /*      will have to tighten up in the future.                          */
  /*                                                                      */
  /*      Currently a new point should require no more than 64            */
  /*      characters barring the X or Y value being extremely large.      */
  /************************************************************************/
  
  void OGRMakeWktCoordinate( char *pszTarget, double x, double y, double z,
                             int nDimension )
  
  {
      const size_t bufSize = 75;
      // Assumed max length of the target buffer.
      const size_t maxTargetSize = 75;
      const char chDecimalSep = '.';
      static int nPrecision = -1;
      if( nPrecision < 0 )
          nPrecision = atoi(CPLGetConfigOption("OGR_WKT_PRECISION", "15"));
  
      char szX[bufSize] = {};
      char szY[bufSize] = {};
      char szZ[bufSize] = {};
  
      szZ[0] = '\0';
  
      size_t nLenX = 0;
      size_t nLenY = 0;
  
      if( CPLIsDoubleAnInt(x) && CPLIsDoubleAnInt(y) )
      {
          snprintf( szX, bufSize, "%d", static_cast<int>(x) );
          snprintf( szY, bufSize, "%d", static_cast<int>(y) );
      }
      else
      {
          OGRFormatDouble( szX, bufSize, x, chDecimalSep, nPrecision,
                           fabs(x) < 1 ? 'f' : 'g' );
          if( CPLIsFinite(x) && strchr(szX, '.') == nullptr &&
              strchr(szX, 'e') == nullptr && strlen(szX) < bufSize - 2 )
          {
              strcat(szX, ".0");
          }
          OGRFormatDouble( szY, bufSize, y, chDecimalSep, nPrecision,
                           fabs(y) < 1 ? 'f' : 'g' );
          if( CPLIsFinite(y) && strchr(szY, '.') == nullptr &&
              strchr(szY, 'e') == nullptr && strlen(szY) < bufSize - 2 )
          {
              strcat(szY, ".0");
          }
      }
  
      nLenX = strlen(szX);
      nLenY = strlen(szY);
  
      if( nDimension == 3 )
      {
          if( CPLIsDoubleAnInt(z) )
          {
              snprintf( szZ, bufSize, "%d", static_cast<int>(z) );
          }
          else
          {
              OGRFormatDouble( szZ, bufSize, z, chDecimalSep, nPrecision, 'g' );
          }
      }
  
      if( nLenX + 1 + nLenY + ((nDimension == 3) ? (1 + strlen(szZ)) : 0) >=
          maxTargetSize )
      {
  #ifdef DEBUG
          CPLDebug( "OGR",
                    "Yow!  Got this big result in OGRMakeWktCoordinate(): "
                    "%s %s %s",
                    szX, szY, szZ );
  #endif
          if( nDimension == 3 )
              strcpy( pszTarget, "0 0 0");
          else
              strcpy( pszTarget, "0 0");
      }
      else
      {
          memcpy( pszTarget, szX, nLenX );
          pszTarget[nLenX] = ' ';
          memcpy( pszTarget + nLenX + 1, szY, nLenY );
          if( nDimension == 3 )
          {
              pszTarget[nLenX + 1 + nLenY] = ' ';
              strcpy( pszTarget + nLenX + 1 + nLenY + 1, szZ );
          }
          else
          {
              pszTarget[nLenX + 1 + nLenY] = '\0';
          }
      }
  }
  
  /************************************************************************/
  /*                        OGRMakeWktCoordinateM()                       */
  /*                                                                      */
  /*      Format a well known text coordinate, trying to keep the         */
  /*      ASCII representation compact, but accurate.  These rules        */
  /*      will have to tighten up in the future.                          */
  /*                                                                      */
  /*      Currently a new point should require no more than 64            */
  /*      characters barring the X or Y value being extremely large.      */
  /************************************************************************/
  
  void OGRMakeWktCoordinateM( char *pszTarget,
                              double x, double y, double z, double m,
                              OGRBoolean hasZ, OGRBoolean hasM )
  
  {
      const size_t bufSize = 75;
      // Assumed max length of the target buffer.
      const size_t maxTargetSize = 75;
      const char chDecimalSep = '.';
      static int nPrecision = -1;
      if( nPrecision < 0 )
          nPrecision = atoi(CPLGetConfigOption("OGR_WKT_PRECISION", "15"));
  
      char szX[bufSize] = {};
      char szY[bufSize] = {};
      char szZ[bufSize] = {};
      char szM[bufSize] = {};
  
      size_t nLen = 0;
      size_t nLenX = 0;
      size_t nLenY = 0;
  
      if( CPLIsDoubleAnInt(x) && CPLIsDoubleAnInt(y) )
      {
          snprintf( szX, bufSize, "%d", static_cast<int>(x) );
          snprintf( szY, bufSize, "%d", static_cast<int>(y) );
      }
      else
      {
          OGRFormatDouble( szX, bufSize, x, chDecimalSep, nPrecision,
                           fabs(x) < 1 ? 'f' : 'g' );
          if( CPLIsFinite(x) && strchr(szX, '.') == nullptr &&
              strchr(szX, 'e') == nullptr && strlen(szX) < bufSize - 2 )
          {
              strcat(szX, ".0");
          }
          OGRFormatDouble( szY, bufSize, y, chDecimalSep, nPrecision,
                           fabs(y) < 1 ? 'f' : 'g' );
          if( CPLIsFinite(y) && strchr(szY, '.') == nullptr &&
              strchr(szY, 'e') == nullptr && strlen(szY) < bufSize - 2 )
          {
              strcat(szY, ".0");
          }
      }
  
      nLenX = strlen(szX);
      nLenY = strlen(szY);
      nLen = nLenX + nLenY + 1;
  
      if( hasZ )
      {
          if( CPLIsDoubleAnInt(z) )
          {
              snprintf( szZ, bufSize, "%d", static_cast<int>(z) );
          }
          else
          {
              OGRFormatDouble( szZ, bufSize, z, chDecimalSep, nPrecision, 'g' );
          }
          nLen += strlen(szZ) + 1;
      }
  
      if( hasM )
      {
          if( CPLIsDoubleAnInt(m) )
          {
              snprintf( szM, bufSize, "%d", static_cast<int>(m) );
          }
          else
          {
              OGRFormatDouble( szM, bufSize, m, chDecimalSep, nPrecision, 'g' );
          }
          nLen += strlen(szM) + 1;
      }
  
      if( nLen >= maxTargetSize )
      {
  #ifdef DEBUG
          CPLDebug( "OGR",
                    "Yow!  Got this big result in OGRMakeWktCoordinate(): "
                    "%s %s %s %s",
                    szX, szY, szZ, szM );
  #endif
          if( hasZ && hasM )
              strcpy( pszTarget, "0 0 0 0");
          else if( hasZ || hasM )
              strcpy( pszTarget, "0 0 0");
          else
              strcpy( pszTarget, "0 0");
      }
      else
      {
          char *target = pszTarget;
          strcpy( target, szX );
          target += nLenX;
          *target = ' ';
          ++target;
          strcpy( target, szY );
          target += nLenY;
          if( hasZ )
          {
              *target = ' ';
              ++target;
              strcpy( target, szZ );
              target += strlen(szZ);
          }
          if( hasM )
          {
              *target = ' ';
              ++target;
              strcpy( target, szM );
              target += strlen(szM);
          }
          *target = '\0';
      }
  }
  
  /************************************************************************/
  /*                          OGRWktReadToken()                           */
  /*                                                                      */
  /*      Read one token or delimiter and put into token buffer.  Pre     */
  /*      and post white space is swallowed.                              */
  /************************************************************************/
  
  const char *OGRWktReadToken( const char * pszInput, char * pszToken )
  
  {
      if( pszInput == nullptr )
          return nullptr;
  
  /* -------------------------------------------------------------------- */
  /*      Swallow pre-white space.                                        */
  /* -------------------------------------------------------------------- */
      while( *pszInput == ' ' || *pszInput == '\t' )
          ++pszInput;
  
  /* -------------------------------------------------------------------- */
  /*      If this is a delimiter, read just one character.                */
  /* -------------------------------------------------------------------- */
      if( *pszInput == '(' || *pszInput == ')' || *pszInput == ',' )
      {
          pszToken[0] = *pszInput;
          pszToken[1] = '\0';
  
          ++pszInput;
      }
  
  /* -------------------------------------------------------------------- */
  /*      Or if it alpha numeric read till we reach non-alpha numeric     */
  /*      text.                                                           */
  /* -------------------------------------------------------------------- */
      else
      {
          int iChar = 0;
  
          while( iChar < OGR_WKT_TOKEN_MAX-1
                 && ((*pszInput >= 'a' && *pszInput <= 'z')
                     || (*pszInput >= 'A' && *pszInput <= 'Z')
                     || (*pszInput >= '0' && *pszInput <= '9')
                     || *pszInput == '.'
                     || *pszInput == '+'
                     || *pszInput == '-') )
          {
              pszToken[iChar++] = *(pszInput++);
          }
  
          pszToken[iChar++] = '\0';
      }
  
  /* -------------------------------------------------------------------- */
  /*      Eat any trailing white space.                                   */
  /* -------------------------------------------------------------------- */
      while( *pszInput == ' ' || *pszInput == '\t' )
          ++pszInput;
  
      return pszInput;
  }
  
  /************************************************************************/
  /*                          OGRWktReadPoints()                          */
  /*                                                                      */
  /*      Read a point string.  The point list must be contained in       */
  /*      brackets and each point pair separated by a comma.              */
  /************************************************************************/
  
  const char * OGRWktReadPoints( const char * pszInput,
                                 OGRRawPoint ** ppaoPoints, double **ppadfZ,
                                 int * pnMaxPoints,
                                 int * pnPointsRead )
  
  {
      const char *pszOrigInput = pszInput;
      *pnPointsRead = 0;
  
      if( pszInput == nullptr )
          return nullptr;
  
  /* -------------------------------------------------------------------- */
  /*      Eat any leading white space.                                    */
  /* -------------------------------------------------------------------- */
      while( *pszInput == ' ' || *pszInput == '\t' )
          ++pszInput;
  
  /* -------------------------------------------------------------------- */
  /*      If this isn't an opening bracket then we have a problem.        */
  /* -------------------------------------------------------------------- */
      if( *pszInput != '(' )
      {
          CPLDebug( "OGR",
                    "Expected '(', but got %s in OGRWktReadPoints().",
                    pszInput );
  
          return pszInput;
      }
  
      ++pszInput;
  
  /* ==================================================================== */
  /*      This loop reads a single point.  It will continue till we       */
  /*      run out of well formed points, or a closing bracket is          */
  /*      encountered.                                                    */
  /* ==================================================================== */
      char szDelim[OGR_WKT_TOKEN_MAX] = {};
  
      do {
  /* -------------------------------------------------------------------- */
  /*      Read the X and Y values, verify they are numeric.               */
  /* -------------------------------------------------------------------- */
          char szTokenX[OGR_WKT_TOKEN_MAX] = {};
          char szTokenY[OGR_WKT_TOKEN_MAX] = {};
  
          pszInput = OGRWktReadToken( pszInput, szTokenX );
          pszInput = OGRWktReadToken( pszInput, szTokenY );
  
          if( (!isdigit(szTokenX[0]) && szTokenX[0] != '-' && szTokenX[0] != '.' )
              || (!isdigit(szTokenY[0]) && szTokenY[0] != '-' &&
                  szTokenY[0] != '.') )
              return nullptr;
  
  /* -------------------------------------------------------------------- */
  /*      Do we need to grow the point list to hold this point?           */
  /* -------------------------------------------------------------------- */
          if( *pnPointsRead == *pnMaxPoints )
          {
              *pnMaxPoints = *pnMaxPoints * 2 + 10;
              *ppaoPoints = static_cast<OGRRawPoint *>(
                  CPLRealloc(*ppaoPoints, sizeof(OGRRawPoint) * *pnMaxPoints) );
  
              if( *ppadfZ != nullptr )
              {
                  *ppadfZ = static_cast<double *>(
                      CPLRealloc(*ppadfZ, sizeof(double) * *pnMaxPoints) );
              }
          }
  
  /* -------------------------------------------------------------------- */
  /*      Add point to list.                                              */
  /* -------------------------------------------------------------------- */
          (*ppaoPoints)[*pnPointsRead].x = CPLAtof(szTokenX);
          (*ppaoPoints)[*pnPointsRead].y = CPLAtof(szTokenY);
  
  /* -------------------------------------------------------------------- */
  /*      Do we have a Z coordinate?                                      */
  /* -------------------------------------------------------------------- */
          pszInput = OGRWktReadToken( pszInput, szDelim );
  
          if( isdigit(szDelim[0]) || szDelim[0] == '-' || szDelim[0] == '.' )
          {
              if( *ppadfZ == nullptr )
              {
                  *ppadfZ = static_cast<double *>(
                      CPLCalloc(sizeof(double), *pnMaxPoints) );
              }
  
              (*ppadfZ)[*pnPointsRead] = CPLAtof(szDelim);
  
              pszInput = OGRWktReadToken( pszInput, szDelim );
          }
          else if( *ppadfZ != nullptr )
          {
              (*ppadfZ)[*pnPointsRead] = 0.0;
          }
  
          ++(*pnPointsRead);
  
  /* -------------------------------------------------------------------- */
  /*      Do we have a M coordinate?                                      */
  /*      If we do, just skip it.                                         */
  /* -------------------------------------------------------------------- */
          if( isdigit(szDelim[0]) || szDelim[0] == '-' || szDelim[0] == '.' )
          {
              pszInput = OGRWktReadToken( pszInput, szDelim );
          }
  
  /* -------------------------------------------------------------------- */
  /*      Read next delimiter ... it should be a comma if there are       */
  /*      more points.                                                    */
  /* -------------------------------------------------------------------- */
          if( szDelim[0] != ')' && szDelim[0] != ',' )
          {
              CPLDebug( "OGR",
                        "Corrupt input in OGRWktReadPoints().  "
                        "Got `%s' when expecting `,' or `)', near `%s' in %s.",
                        szDelim, pszInput, pszOrigInput );
              return nullptr;
          }
      } while( szDelim[0] == ',' );
  
      return pszInput;
  }
  
  /************************************************************************/
  /*                          OGRWktReadPointsM()                         */
  /*                                                                      */
  /*      Read a point string.  The point list must be contained in       */
  /*      brackets and each point pair separated by a comma.              */
  /************************************************************************/
  
  const char * OGRWktReadPointsM( const char * pszInput,
                                  OGRRawPoint ** ppaoPoints,
                                  double **ppadfZ, double **ppadfM,
                                  int * flags,
                                  int * pnMaxPoints,
                                  int * pnPointsRead )
  
  {
      const char *pszOrigInput = pszInput;
      const bool bNoFlags =
          !(*flags & OGRGeometry::OGR_G_3D) &&
          !(*flags & OGRGeometry::OGR_G_MEASURED);
      *pnPointsRead = 0;
  
      if( pszInput == nullptr )
          return nullptr;
  
  /* -------------------------------------------------------------------- */
  /*      Eat any leading white space.                                    */
  /* -------------------------------------------------------------------- */
      while( *pszInput == ' ' || *pszInput == '\t' )
          ++pszInput;
  
  /* -------------------------------------------------------------------- */
  /*      If this isn't an opening bracket then we have a problem.        */
  /* -------------------------------------------------------------------- */
      if( *pszInput != '(' )
      {
          CPLDebug( "OGR",
                    "Expected '(', but got %s in OGRWktReadPointsM().",
                    pszInput );
  
          return pszInput;
      }
  
      ++pszInput;
  
  /* ==================================================================== */
  /*      This loop reads a single point.  It will continue till we       */
  /*      run out of well formed points, or a closing bracket is          */
  /*      encountered.                                                    */
  /* ==================================================================== */
      char szDelim[OGR_WKT_TOKEN_MAX] = {};
  
      do {
  /* -------------------------------------------------------------------- */
  /*      Read the X and Y values, verify they are numeric.               */
  /* -------------------------------------------------------------------- */
          char szTokenX[OGR_WKT_TOKEN_MAX] = {};
          char szTokenY[OGR_WKT_TOKEN_MAX] = {};
  
          pszInput = OGRWktReadToken( pszInput, szTokenX );
          pszInput = OGRWktReadToken( pszInput, szTokenY );
  
          if( (!isdigit(szTokenX[0]) && szTokenX[0] != '-' && szTokenX[0] != '.' )
              || (!isdigit(szTokenY[0]) && szTokenY[0] != '-' &&
                  szTokenY[0] != '.') )
              return nullptr;
  
  /* -------------------------------------------------------------------- */
  /*      Do we need to grow the point list to hold this point?           */
  /* -------------------------------------------------------------------- */
          if( *pnPointsRead == *pnMaxPoints )
          {
              *pnMaxPoints = *pnMaxPoints * 2 + 10;
              *ppaoPoints = static_cast<OGRRawPoint *>(
                  CPLRealloc(*ppaoPoints, sizeof(OGRRawPoint) * *pnMaxPoints) );
  
              if( *ppadfZ != nullptr )
              {
                  *ppadfZ = static_cast<double *>(
                      CPLRealloc(*ppadfZ, sizeof(double) * *pnMaxPoints) );
              }
  
              if( *ppadfM != nullptr )
              {
                  *ppadfM = static_cast<double *>(
                      CPLRealloc(*ppadfM, sizeof(double) * *pnMaxPoints) );
              }
          }
  
  /* -------------------------------------------------------------------- */
  /*      Add point to list.                                              */
  /* -------------------------------------------------------------------- */
          (*ppaoPoints)[*pnPointsRead].x = CPLAtof(szTokenX);
          (*ppaoPoints)[*pnPointsRead].y = CPLAtof(szTokenY);
  
  /* -------------------------------------------------------------------- */
  /*      Read the next token.                                            */
  /* -------------------------------------------------------------------- */
          pszInput = OGRWktReadToken( pszInput, szDelim );
  
  /* -------------------------------------------------------------------- */
  /*      If there are unexpectedly more coordinates, they are Z.         */
  /* -------------------------------------------------------------------- */
  
          if( !(*flags & OGRGeometry::OGR_G_3D) &&
              !(*flags & OGRGeometry::OGR_G_MEASURED) &&
              (isdigit(szDelim[0]) || szDelim[0] == '-' || szDelim[0] == '.' ))
          {
              *flags |= OGRGeometry::OGR_G_3D;
          }
  
  /* -------------------------------------------------------------------- */
  /*      Get Z if flag says so.                                          */
  /*      Zero out possible remains from earlier strings.                 */
  /* -------------------------------------------------------------------- */
  
          if( *flags & OGRGeometry::OGR_G_3D )
          {
              if( *ppadfZ == nullptr )
              {
                  *ppadfZ = static_cast<double *>(
                      CPLCalloc(sizeof(double), *pnMaxPoints) );
              }
              if( isdigit(szDelim[0]) || szDelim[0] == '-' || szDelim[0] == '.' )
              {
                  (*ppadfZ)[*pnPointsRead] = CPLAtof(szDelim);
                  pszInput = OGRWktReadToken( pszInput, szDelim );
              }
              else
              {
                  (*ppadfZ)[*pnPointsRead] = 0.0;
              }
          }
          else if( *ppadfZ != nullptr )
          {
              (*ppadfZ)[*pnPointsRead] = 0.0;
          }
  
  /* -------------------------------------------------------------------- */
  /*      If there are unexpectedly even more coordinates,                */
  /*      they are discarded unless there were no flags originally.       */
  /*      This is for backwards compatibility. Should this be an error?   */
  /* -------------------------------------------------------------------- */
  
          if( !(*flags & OGRGeometry::OGR_G_MEASURED) &&
              (isdigit(szDelim[0]) || szDelim[0] == '-' || szDelim[0] == '.' ) )
          {
              if( bNoFlags )
              {
                  *flags |= OGRGeometry::OGR_G_MEASURED;
              }
              else
              {
                  pszInput = OGRWktReadToken( pszInput, szDelim );
              }
          }
  
  /* -------------------------------------------------------------------- */
  /*      Get M if flag says so.                                          */
  /*      Zero out possible remains from earlier strings.                 */
  /* -------------------------------------------------------------------- */
  
          if( *flags & OGRGeometry::OGR_G_MEASURED )
          {
              if( *ppadfM == nullptr )
              {
                  *ppadfM = static_cast<double *>(
                      CPLCalloc(sizeof(double), *pnMaxPoints) );
              }
              if( isdigit(szDelim[0]) || szDelim[0] == '-' || szDelim[0] == '.' )
              {
                  (*ppadfM)[*pnPointsRead] = CPLAtof(szDelim);
                  pszInput = OGRWktReadToken( pszInput, szDelim );
              }
              else
              {
                  (*ppadfM)[*pnPointsRead] = 0.0;
              }
          }
          else if( *ppadfM != nullptr )
          {
              (*ppadfM)[*pnPointsRead] = 0.0;
          }
  
  /* -------------------------------------------------------------------- */
  /*      If there are still more coordinates and we do not have Z        */
  /*      then we have a case of flags == M and four coordinates.         */
  /*      This is allowed in BNF.                                         */
  /* -------------------------------------------------------------------- */
  
          if( !(*flags & OGRGeometry::OGR_G_3D) &&
              (isdigit(szDelim[0]) || szDelim[0] == '-' || szDelim[0] == '.') )
          {
              *flags |= OGRGeometry::OGR_G_3D;
              if( *ppadfZ == nullptr )
              {
                  *ppadfZ = static_cast<double *>(
                      CPLCalloc(sizeof(double), *pnMaxPoints) );
              }
              (*ppadfZ)[*pnPointsRead] = (*ppadfM)[*pnPointsRead];
              (*ppadfM)[*pnPointsRead] = CPLAtof(szDelim);
              pszInput = OGRWktReadToken( pszInput, szDelim );
          }
  
  /* -------------------------------------------------------------------- */
  /*      Increase points index.                                          */
  /* -------------------------------------------------------------------- */
          ++(*pnPointsRead);
  
  /* -------------------------------------------------------------------- */
  /*      The next delimiter should be a comma or an ending bracket.      */
  /* -------------------------------------------------------------------- */
          if( szDelim[0] != ')' && szDelim[0] != ',' )
          {
              CPLDebug( "OGR",
                        "Corrupt input in OGRWktReadPointsM()  "
                        "Got `%s' when expecting `,' or `)', near `%s' in %s.",
                        szDelim, pszInput, pszOrigInput );
              return nullptr;
          }
      } while( szDelim[0] == ',' );
  
      return pszInput;
  }
  
  /************************************************************************/
  /*                             OGRMalloc()                              */
  /*                                                                      */
  /*      Cover for CPLMalloc()                                           */
  /************************************************************************/
  
  void *OGRMalloc( size_t size )
  
  {
      return CPLMalloc( size );
  }
  
  /************************************************************************/
  /*                             OGRCalloc()                              */
  /*                                                                      */
  /*      Cover for CPLCalloc()                                           */
  /************************************************************************/
  
  void *OGRCalloc( size_t count, size_t size )
  
  {
      return CPLCalloc( count, size );
  }
  
  /************************************************************************/
  /*                             OGRRealloc()                             */
  /*                                                                      */
  /*      Cover for CPLRealloc()                                          */
  /************************************************************************/
  
  void *OGRRealloc( void * pOld, size_t size )
  
  {
      return CPLRealloc( pOld, size );
  }
  
  /************************************************************************/
  /*                              OGRFree()                               */
  /*                                                                      */
  /*      Cover for CPLFree().                                            */
  /************************************************************************/
  
  void OGRFree( void * pMemory )
  
  {
      CPLFree( pMemory );
  }
  
  /**
   * \fn OGRGeneralCmdLineProcessor(int, char***, int)
   * General utility option processing.
   *
   * This function is intended to provide a variety of generic commandline
   * options for all OGR commandline utilities.  It takes care of the following
   * commandline options:
   *
   *  --version: report version of GDAL in use.
   *  --license: report GDAL license info.
   *  --format [format]: report details of one format driver.
   *  --formats: report all format drivers configured.
   *  --optfile filename: expand an option file into the argument list.
   *  --config key value: set system configuration option.
   *  --debug [on/off/value]: set debug level.
   *  --pause: Pause for user input (allows time to attach debugger)
   *  --locale [locale]: Install a locale using setlocale() (debugging)
   *  --help-general: report detailed help on general options.
   *
   * The argument array is replaced "in place" and should be freed with
   * CSLDestroy() when no longer needed.  The typical usage looks something
   * like the following.  Note that the formats should be registered so that
   * the --formats option will work properly.
   *
   *  int main( int argc, char ** argv )
   *  {
   *    OGRRegisterAll();
   *
   *    argc = OGRGeneralCmdLineProcessor( argc, &argv, 0 );
   *    if( argc < 1 )
   *        exit( -argc );
   *
   * @param nArgc number of values in the argument list.
   * @param ppapszArgv pointer to the argument list array (will be updated in
   * place).
   * @param nOptions unused.
   *
   * @return updated nArgc argument count.  Return of 0 requests terminate
   * without error, return of -1 requests exit with error code.
   */
  
  int OGRGeneralCmdLineProcessor( int nArgc, char ***ppapszArgv,
                                  CPL_UNUSED int nOptions )
  
  {
      return GDALGeneralCmdLineProcessor( nArgc, ppapszArgv, GDAL_OF_VECTOR );
  }
  
  /************************************************************************/
  /*                            OGRParseDate()                            */
  /*                                                                      */
  /*      Parse a variety of text date formats into an OGRField.          */
  /************************************************************************/
  
  /**
   * Parse date string.
   *
   * This function attempts to parse a date string in a variety of formats
   * into the OGRField.Date format suitable for use with OGR.  Generally
   * speaking this function is expecting values like:
   *
   *   YYYY-MM-DD HH:MM:SS[.sss]+nn
   *   or YYYY-MM-DDTHH:MM:SS[.sss]Z (ISO 8601 format)
   *
   * The seconds may also have a decimal portion (which is ignored).  And
   * just dates (YYYY-MM-DD) or just times (HH:MM:SS[.sss]) are also supported.
   * The date may also be in YYYY/MM/DD format.  If the year is less than 100
   * and greater than 30 a "1900" century value will be set.  If it is less than
   * 30 and greater than -1 then a "2000" century value will be set.  In
   * the future this function may be generalized, and additional control
   * provided through nOptions, but an nOptions value of "0" should always do
   * a reasonable default form of processing.
   *
   * The value of psField will be indeterminate if the function fails (returns
   * FALSE).
   *
   * @param pszInput the input date string.
   * @param psField the OGRField that will be updated with the parsed result.
   * @param nOptions parsing options, for now always 0.
   *
   * @return TRUE if apparently successful or FALSE on failure.
   */
  
  int OGRParseDate( const char *pszInput,
                    OGRField *psField,
                    CPL_UNUSED int nOptions )
  {
      psField->Date.Year = 0;
      psField->Date.Month = 0;
      psField->Date.Day = 0;
      psField->Date.Hour = 0;
      psField->Date.Minute = 0;
      psField->Date.Second = 0;
      psField->Date.TZFlag = 0;
      psField->Date.Reserved = 0;
  
  /* -------------------------------------------------------------------- */
  /*      Do we have a date?                                              */
  /* -------------------------------------------------------------------- */
      while( *pszInput == ' ' )
          ++pszInput;
  
      bool bGotSomething = false;
      if( strstr(pszInput,"-") != nullptr || strstr(pszInput,"/") != nullptr )
      {
          if( !(*pszInput == '-' || *pszInput == '+' ||
                (*pszInput >= '0' && *pszInput <= '9')) )
              return FALSE;
          int nYear = atoi(pszInput);
          if (nYear > std::numeric_limits<GInt16>::max() ||
              nYear < std::numeric_limits<GInt16>::min() )
          {
              CPLError(CE_Failure, CPLE_NotSupported,
                       "Years < %d or > %d are not supported",
                       std::numeric_limits<GInt16>::min(),
                       std::numeric_limits<GInt16>::max());
              return FALSE;
          }
          psField->Date.Year = static_cast<GInt16>(nYear);
          if( (pszInput[1] == '-' || pszInput[1] == '/' ) ||
              (pszInput[1] != '\0' &&
               (pszInput[2] == '-' || pszInput[2] == '/' )) )
          {
              if( psField->Date.Year < 100 && psField->Date.Year >= 30 )
                  psField->Date.Year += 1900;
              else if( psField->Date.Year < 30 && psField->Date.Year >= 0 )
                  psField->Date.Year += 2000;
          }
  
          if( *pszInput == '-' )
              ++pszInput;
          while( *pszInput >= '0' && *pszInput <= '9' )
              ++pszInput;
          if( *pszInput != '-' && *pszInput != '/' )
              return FALSE;
          else
              ++pszInput;
  
          const int nMonth = atoi(pszInput);
          if( nMonth <= 0 || nMonth > 12 )
              return FALSE;
          psField->Date.Month = static_cast<GByte>(nMonth);
  
          while( *pszInput >= '0' && *pszInput <= '9' )
              ++pszInput;
          if( *pszInput != '-' && *pszInput != '/' )
              return FALSE;
          else
              ++pszInput;
  
          const int nDay = atoi(pszInput);
          if( nDay <= 0 || nDay > 31 )
              return FALSE;
          psField->Date.Day = static_cast<GByte>(nDay);
  
          while( *pszInput >= '0' && *pszInput <= '9' )
              ++pszInput;
          if( *pszInput == '\0' )
              return TRUE;
  
          bGotSomething = true;
  
          // If ISO 8601 format.
          if( *pszInput == 'T' )
              ++pszInput;
          else if( *pszInput != ' ' )
              return FALSE;
      }
  
  /* -------------------------------------------------------------------- */
  /*      Do we have a time?                                              */
  /* -------------------------------------------------------------------- */
      while( *pszInput == ' ' )
          ++pszInput;
  
      if( strstr(pszInput, ":") != nullptr )
      {
          if( !(*pszInput >= '0' && *pszInput <= '9') )
              return FALSE;
          const int nHour = atoi(pszInput);
          if( nHour < 0 || nHour > 23 )
              return FALSE;
          psField->Date.Hour = static_cast<GByte>(nHour);
  
          while( *pszInput >= '0' && *pszInput <= '9' )
              ++pszInput;
          if( *pszInput != ':' )
              return FALSE;
          else
              ++pszInput;
  
          if( !(*pszInput >= '0' && *pszInput <= '9') )
              return FALSE;
          const int nMinute = atoi(pszInput);
          if( nMinute < 0 || nMinute > 59 )
              return FALSE;
          psField->Date.Minute = static_cast<GByte>(nMinute);
  
          while( *pszInput >= '0' && *pszInput <= '9' )
              ++pszInput;
          if( *pszInput == ':' )
          {
              ++pszInput;
  
              if( !(*pszInput >= '0' && *pszInput <= '9') )
                  return FALSE;
              const double dfSeconds = CPLAtof(pszInput);
              // We accept second=60 for leap seconds
              if (dfSeconds > 60.0 || dfSeconds < 0.0) return FALSE;
              psField->Date.Second = static_cast<float>(dfSeconds);
  
              while( (*pszInput >= '0' && *pszInput <= '9')
                  || *pszInput == '.' )
              {
                  ++pszInput;
              }
  
              // If ISO 8601 format.
              if( *pszInput == 'Z' )
              {
                  psField->Date.TZFlag = 100;
              }
          }
  
          bGotSomething = true;
      }
  
      // No date or time!
      if( !bGotSomething )
          return FALSE;
  
  /* -------------------------------------------------------------------- */
  /*      Do we have a timezone?                                          */
  /* -------------------------------------------------------------------- */
      while( *pszInput == ' ' )
          ++pszInput;
  
      if( *pszInput == '-' || *pszInput == '+' )
      {
          // +HH integral offset
          if( strlen(pszInput) <= 3 )
          {
              psField->Date.TZFlag = static_cast<GByte>(100 + atoi(pszInput) * 4);
          }
          else if( pszInput[3] == ':'  // +HH:MM offset
                   && atoi(pszInput + 4) % 15 == 0 )
          {
              psField->Date.TZFlag = (GByte)(100
                  + atoi(pszInput + 1) * 4
                  + (atoi(pszInput + 4) / 15));
  
              if( pszInput[0] == '-' )
                  psField->Date.TZFlag = -1 * (psField->Date.TZFlag - 100) + 100;
          }
          else if( isdigit(pszInput[3]) && isdigit(pszInput[4])  // +HHMM offset
                   && atoi(pszInput + 3) % 15 == 0 )
          {
              psField->Date.TZFlag = (GByte)(100
                  + static_cast<GByte>(CPLScanLong(pszInput + 1, 2)) * 4
                  + (atoi(pszInput + 3) / 15));
  
              if( pszInput[0] == '-' )
                  psField->Date.TZFlag = -1 * (psField->Date.TZFlag - 100) + 100;
          }
          else if( isdigit(pszInput[3]) && pszInput[4] == '\0'  // +HMM offset
                   && atoi(pszInput + 2) % 15 == 0 )
          {
              psField->Date.TZFlag = (GByte)(100
                  + static_cast<GByte>(CPLScanLong(pszInput + 1, 1)) * 4
                  + (atoi(pszInput + 2) / 15));
  
              if( pszInput[0] == '-' )
                  psField->Date.TZFlag = -1 * (psField->Date.TZFlag - 100) + 100;
          }
          // otherwise ignore any timezone info.
      }
  
      return TRUE;
  }
  
  /************************************************************************/
  /*                           OGRParseXMLDateTime()                      */
  /************************************************************************/
  
  int OGRParseXMLDateTime( const char* pszXMLDateTime,
                           OGRField* psField)
  {
      int year = 0;
      int month = 0;
      int day = 0;
      int hour = 0;
      int minute = 0;
      int TZHour = 0;
      int TZMinute = 0;
      float second = 0;
      char c = '\0';
      int TZ = 0;
      bool bRet = false;
  
      // Date is expressed as a UTC date.
      if( sscanf(pszXMLDateTime, "%04d-%02d-%02dT%02d:%02d:%f%c",
                 &year, &month, &day, &hour, &minute, &second, &c) == 7 &&
          c == 'Z' )
      {
          TZ = 100;
          bRet = true;
      }
      // Date is expressed as a UTC date, with a timezone.
      else if( sscanf(pszXMLDateTime, "%04d-%02d-%02dT%02d:%02d:%f%c%02d:%02d",
                      &year, &month, &day, &hour, &minute, &second, &c,
                      &TZHour, &TZMinute) == 9 &&
               (c == '+' || c == '-') )
      {
          TZ = 100 + ((c == '+') ? 1 : -1) * ((TZHour * 60 + TZMinute) / 15);
          bRet = true;
      }
      // Date is expressed into an unknown timezone.
      else if( sscanf(pszXMLDateTime, "%04d-%02d-%02dT%02d:%02d:%f",
                      &year, &month, &day, &hour, &minute, &second) == 6 )
      {
          TZ = 0;
          bRet = true;
      }
      // Date is expressed as a UTC date with only year:month:day.
      else if( sscanf(pszXMLDateTime, "%04d-%02d-%02d", &year, &month, &day) ==
               3 )
      {
          TZ = 0;
          bRet = true;
      }
      // Date is expressed as a UTC date with only year:month.
      else if( sscanf(pszXMLDateTime, "%04d-%02d", &year, &month) ==
               2 )
      {
          TZ = 0;
          bRet = true;
          day = 1;
      }
  
      if( !bRet )
        return FALSE;
  
      psField->Date.Year = static_cast<GInt16>(year);
      psField->Date.Month = static_cast<GByte>(month);
      psField->Date.Day = static_cast<GByte>(day);
      psField->Date.Hour = static_cast<GByte>(hour);
      psField->Date.Minute = static_cast<GByte>(minute);
      psField->Date.Second = second;
      psField->Date.TZFlag = static_cast<GByte>(TZ);
      psField->Date.Reserved = 0;
  
      return TRUE;
  }
  
  /************************************************************************/
  /*                      OGRParseRFC822DateTime()                        */
  /************************************************************************/
  
  static const char* const aszMonthStr[] = {
      "Jan", "Feb", "Mar", "Apr", "May", "Jun",
      "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
  
  int OGRParseRFC822DateTime( const char* pszRFC822DateTime, OGRField* psField )
  {
      int nYear, nMonth, nDay, nHour, nMinute, nSecond, nTZFlag;
      if( !CPLParseRFC822DateTime( pszRFC822DateTime,
                                      &nYear,
                                      &nMonth,
                                      &nDay,
                                      &nHour,
                                      &nMinute,
                                      &nSecond,
                                      &nTZFlag,
                                      nullptr ) )
      {
          return false;
      }
  
      psField->Date.Year = static_cast<GInt16>(nYear);
      psField->Date.Month = static_cast<GByte>(nMonth);
      psField->Date.Day = static_cast<GByte>(nDay);
      psField->Date.Hour = static_cast<GByte>(nHour);
      psField->Date.Minute = static_cast<GByte>(nMinute);
      psField->Date.Second = (nSecond < 0) ? 0.0f : static_cast<float>(nSecond);
      psField->Date.TZFlag = static_cast<GByte>(nTZFlag);
      psField->Date.Reserved = 0;
  
      return true;
  }
  
  /**
    * Returns the day of the week in Gregorian calendar
    *
    * @param day : day of the month, between 1 and 31
    * @param month : month of the year, between 1 (Jan) and 12 (Dec)
    * @param year : year
  
    * @return day of the week : 0 for Monday, ... 6 for Sunday
    */
  
  int OGRGetDayOfWeek( int day, int month, int year )
  {
      // Reference: Zeller's congruence.
      const int q = day;
      int m = month;
      if( month >=3 )
      {
          // m = month;
      }
      else
      {
          m = month + 12;
          year--;
      }
      const int K = year % 100;
      const int J = year / 100;
      const int h = ( q + (((m+1)*26)/10) + K + K/4 + J/4 + 5 * J) % 7;
      return ( h + 5 ) % 7;
  }
  
  /************************************************************************/
  /*                         OGRGetRFC822DateTime()                       */
  /************************************************************************/
  
  char* OGRGetRFC822DateTime( const OGRField* psField )
  {
      char* pszTZ = nullptr;
      const char* aszDayOfWeek[] =
          { "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" };
  
      int dayofweek = OGRGetDayOfWeek(psField->Date.Day, psField->Date.Month,
                                      psField->Date.Year);
  
      int month = psField->Date.Month;
      if( month < 1 || month > 12 )
          month = 1;
  
      int TZFlag = psField->Date.TZFlag;
      if( TZFlag == 0 || TZFlag == 100 )
      {
          pszTZ = CPLStrdup("GMT");
      }
      else
      {
          int TZOffset = std::abs(TZFlag - 100) * 15;
          int TZHour = TZOffset / 60;
          int TZMinute = TZOffset - TZHour * 60;
          pszTZ = CPLStrdup(CPLSPrintf("%c%02d%02d", TZFlag > 100 ? '+' : '-',
                                          TZHour, TZMinute));
      }
      char* pszRet = CPLStrdup(CPLSPrintf(
          "%s, %02d %s %04d %02d:%02d:%02d %s",
          aszDayOfWeek[dayofweek], psField->Date.Day, aszMonthStr[month - 1],
          psField->Date.Year, psField->Date.Hour,
          psField->Date.Minute, static_cast<int>(psField->Date.Second), pszTZ));
      CPLFree(pszTZ);
      return pszRet;
  }
  
  /************************************************************************/
  /*                            OGRGetXMLDateTime()                       */
  /************************************************************************/
  
  char* OGRGetXMLDateTime(const OGRField* psField)
  {
      const int year = psField->Date.Year;
      const int month = psField->Date.Month;
      const int day = psField->Date.Day;
      const int hour = psField->Date.Hour;
      const int minute = psField->Date.Minute;
      const float second = psField->Date.Second;
      const int TZFlag = psField->Date.TZFlag;
  
      char* pszRet = nullptr;
  
      if( TZFlag == 0 || TZFlag == 100 )
      {
          if( OGR_GET_MS(second) )
              pszRet = CPLStrdup(CPLSPrintf(
                  "%04d-%02d-%02dT%02d:%02d:%06.3fZ",
                  year, month, day, hour, minute, second));
          else
              pszRet = CPLStrdup(CPLSPrintf(
                  "%04d-%02d-%02dT%02d:%02d:%02dZ",
                  year, month, day, hour, minute, static_cast<int>(second)));
      }
      else
      {
          const int TZOffset = std::abs(TZFlag - 100) * 15;
          const int TZHour = TZOffset / 60;
          const int TZMinute = TZOffset - TZHour * 60;
          if( OGR_GET_MS(second) )
              pszRet = CPLStrdup(CPLSPrintf(
                  "%04d-%02d-%02dT%02d:%02d:%06.3f%c%02d:%02d",
                  year, month, day, hour, minute, second,
                  (TZFlag > 100) ? '+' : '-', TZHour, TZMinute));
          else
              pszRet = CPLStrdup(
                  CPLSPrintf("%04d-%02d-%02dT%02d:%02d:%02d%c%02d:%02d",
                             year, month, day, hour, minute,
                             static_cast<int>(second),
                             TZFlag > 100 ? '+' : '-', TZHour, TZMinute));
      }
      return pszRet;
  }
  
  /************************************************************************/
  /*                 OGRGetXML_UTF8_EscapedString()                       */
  /************************************************************************/
  
  char* OGRGetXML_UTF8_EscapedString(const char* pszString)
  {
      char *pszEscaped = nullptr;
      if( !CPLIsUTF8(pszString, -1) &&
           CPLTestBool(CPLGetConfigOption("OGR_FORCE_ASCII", "YES")) )
      {
          static bool bFirstTime = true;
          if( bFirstTime )
          {
              bFirstTime = false;
              CPLError(CE_Warning, CPLE_AppDefined,
                       "%s is not a valid UTF-8 string. Forcing it to ASCII.  "
                       "If you still want the original string and change the XML "
                       "file encoding afterwards, you can define "
                       "OGR_FORCE_ASCII=NO as configuration option.  "
                       "This warning won't be issued anymore", pszString);
          }
          else
          {
              CPLDebug("OGR",
                       "%s is not a valid UTF-8 string. Forcing it to ASCII",
                       pszString);
          }
          char* pszTemp = CPLForceToASCII(pszString, -1, '?');
          pszEscaped = CPLEscapeString( pszTemp, -1, CPLES_XML );
          CPLFree(pszTemp);
      }
      else
          pszEscaped = CPLEscapeString( pszString, -1, CPLES_XML );
      return pszEscaped;
  }
  
  /************************************************************************/
  /*                        OGRCompareDate()                              */
  /************************************************************************/
  
  int OGRCompareDate( const OGRField *psFirstTuple,
                      const OGRField *psSecondTuple )
  {
      // TODO: We ignore TZFlag.
  
      if( psFirstTuple->Date.Year < psSecondTuple->Date.Year )
          return -1;
      else if( psFirstTuple->Date.Year > psSecondTuple->Date.Year )
          return 1;
  
      if( psFirstTuple->Date.Month < psSecondTuple->Date.Month )
          return -1;
      else if( psFirstTuple->Date.Month > psSecondTuple->Date.Month )
          return 1;
  
      if( psFirstTuple->Date.Day < psSecondTuple->Date.Day )
          return -1;
      else if( psFirstTuple->Date.Day > psSecondTuple->Date.Day )
          return 1;
  
      if( psFirstTuple->Date.Hour < psSecondTuple->Date.Hour )
          return -1;
      else if( psFirstTuple->Date.Hour > psSecondTuple->Date.Hour )
          return 1;
  
      if( psFirstTuple->Date.Minute < psSecondTuple->Date.Minute )
          return -1;
      else if( psFirstTuple->Date.Minute > psSecondTuple->Date.Minute )
          return 1;
  
      if( psFirstTuple->Date.Second < psSecondTuple->Date.Second )
          return -1;
      else if( psFirstTuple->Date.Second > psSecondTuple->Date.Second )
          return 1;
  
      return 0;
  }
  
  /************************************************************************/
  /*                        OGRFastAtof()                                 */
  /************************************************************************/
  
  // On Windows, CPLAtof() is very slow if the number is followed by other long
  // content.  Just extract the number into a short string before calling
  // CPLAtof() on it.
  static
  double OGRCallAtofOnShortString(const char* pszStr)
  {
      const char* p = pszStr;
      while( *p == ' ' || *p == '\t' )
          ++p;
  
      char szTemp[128] = {};
      int nCounter = 0;
      while( *p == '+' ||
             *p == '-' ||
             (*p >= '0' && *p <= '9') ||
             *p == '.' ||
             (*p == 'e' || *p == 'E' || *p == 'd' || *p == 'D') )
      {
          szTemp[nCounter++] = *(p++);
          if( nCounter == 127 )
              return CPLAtof(pszStr);
      }
      szTemp[nCounter] = '\0';
      return CPLAtof(szTemp);
  }
  
  /** Same contract as CPLAtof, except than it doesn't always call the
   *  system CPLAtof() that may be slow on some platforms. For simple but
   *  common strings, it'll use a faster implementation (up to 20x faster
   *  than CPLAtof() on MS runtime libraries) that has no guaranty to return
   *  exactly the same floating point number.
   */
  
  double OGRFastAtof(const char* pszStr)
  {
      double dfVal = 0;
      double dfSign = 1.0;
      const char* p = pszStr;
  
      constexpr double adfTenPower[] =
      {
          1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10,
          1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20,
          1e21, 1e22, 1e23, 1e24, 1e25, 1e26, 1e27, 1e28, 1e29, 1e30, 1e31
      };
  
      while( *p == ' ' || *p == '\t' )
          ++p;
  
      if( *p == '+' )
          ++p;
      else if( *p == '-' )
      {
          dfSign = -1.0;
          ++p;
      }
  
      while( true )
      {
          if( *p >= '0' && *p <= '9' )
          {
              dfVal = dfVal * 10.0 + (*p - '0');
              ++p;
          }
          else if( *p == '.' )
          {
              ++p;
              break;
          }
          else if( *p == 'e' || *p == 'E' || *p == 'd' || *p == 'D' )
              return OGRCallAtofOnShortString(pszStr);
          else
              return dfSign * dfVal;
      }
  
      unsigned int countFractionnal = 0;
      while( true )
      {
          if( *p >= '0' && *p <= '9' )
          {
              dfVal = dfVal * 10.0 + (*p - '0');
              ++countFractionnal;
              ++p;
          }
          else if( *p == 'e' || *p == 'E' || *p == 'd' || *p == 'D' )
              return OGRCallAtofOnShortString(pszStr);
          else
          {
              if( countFractionnal < CPL_ARRAYSIZE(adfTenPower) )
                  return dfSign * (dfVal / adfTenPower[countFractionnal]);
              else
                  return OGRCallAtofOnShortString(pszStr);
          }
      }
  }
  
  /**
   * Check that panPermutation is a permutation of [0, nSize-1].
   * @param panPermutation an array of nSize elements.
   * @param nSize size of the array.
   * @return OGRERR_NONE if panPermutation is a permutation of [0, nSize - 1].
   * @since OGR 1.9.0
   */
  OGRErr OGRCheckPermutation( int* panPermutation, int nSize )
  {
      OGRErr eErr = OGRERR_NONE;
      int* panCheck = static_cast<int *>(CPLCalloc(nSize, sizeof(int)));
      for( int i = 0; i < nSize; ++i )
      {
          if( panPermutation[i] < 0 || panPermutation[i] >= nSize )
          {
              CPLError(CE_Failure, CPLE_IllegalArg,
                       "Bad value for element %d", i);
              eErr = OGRERR_FAILURE;
              break;
          }
          if( panCheck[panPermutation[i]] != 0 )
          {
              CPLError(CE_Failure, CPLE_IllegalArg,
                       "Array is not a permutation of [0,%d]",
                       nSize - 1);
              eErr = OGRERR_FAILURE;
              break;
          }
          panCheck[panPermutation[i]] = 1;
      }
      CPLFree(panCheck);
      return eErr;
  }
  
  OGRErr OGRReadWKBGeometryType( const unsigned char * pabyData,
                                 OGRwkbVariant eWkbVariant,
                                 OGRwkbGeometryType *peGeometryType )
  {
      if( !peGeometryType )
          return OGRERR_FAILURE;
  
  /* -------------------------------------------------------------------- */
  /*      Get the byte order byte.                                        */
  /* -------------------------------------------------------------------- */
      int nByteOrder = DB2_V72_FIX_BYTE_ORDER(*pabyData);
      if( !( nByteOrder == wkbXDR || nByteOrder == wkbNDR ) )
          return OGRERR_CORRUPT_DATA;
      OGRwkbByteOrder eByteOrder = (OGRwkbByteOrder) nByteOrder;
  
  /* -------------------------------------------------------------------- */
  /*      Get the geometry type.                                          */
  /* -------------------------------------------------------------------- */
      bool bIs3D = false;
      bool bIsMeasured = false;
      int iRawType = 0;
  
      memcpy(&iRawType, pabyData + 1, 4);
      if( OGR_SWAP(eByteOrder))
      {
          CPL_SWAP32PTR(&iRawType);
      }
  
      // Test for M bit in PostGIS WKB, see ogrgeometry.cpp:4956.
      if( 0x40000000 & iRawType )
      {
          iRawType &= ~0x40000000;
          bIsMeasured = true;
      }
      // Old-style OGC z-bit is flipped? Tests also Z bit in PostGIS WKB.
      if( wkb25DBitInternalUse & iRawType )
      {
          // Clean off top 3 bytes.
          iRawType &= 0x000000FF;
          bIs3D = true;
      }
  
      // ISO SQL/MM Part3 draft -> Deprecated.
      // See http://jtc1sc32.org/doc/N1101-1150/32N1107-WD13249-3--spatial.pdf
      if( iRawType == 1000001 )
          iRawType = wkbCircularString;
      else if( iRawType == 1000002 )
          iRawType = wkbCompoundCurve;
      else if( iRawType == 1000003 )
          iRawType = wkbCurvePolygon;
      else if( iRawType == 1000004 )
          iRawType = wkbMultiCurve;
      else if( iRawType == 1000005 )
          iRawType = wkbMultiSurface;
      else if( iRawType == 2000001 )
          iRawType = wkbPointZM;
      else if( iRawType == 2000002 )
          iRawType = wkbLineStringZM;
      else if( iRawType == 2000003 )
          iRawType = wkbCircularStringZM;
      else if( iRawType == 2000004 )
          iRawType = wkbCompoundCurveZM;
      else if( iRawType == 2000005 )
          iRawType = wkbPolygonZM;
      else if( iRawType == 2000006 )
          iRawType = wkbCurvePolygonZM;
      else if( iRawType == 2000007 )
          iRawType = wkbMultiPointZM;
      else if( iRawType == 2000008 )
          iRawType = wkbMultiCurveZM;
      else if( iRawType == 2000009 )
          iRawType = wkbMultiLineStringZM;
      else if( iRawType == 2000010 )
          iRawType = wkbMultiSurfaceZM;
      else if( iRawType == 2000011 )
          iRawType = wkbMultiPolygonZM;
      else if( iRawType == 2000012 )
          iRawType = wkbGeometryCollectionZM;
      else if( iRawType == 3000001 )
          iRawType = wkbPoint25D;
      else if( iRawType == 3000002 )
          iRawType = wkbLineString25D;
      else if( iRawType == 3000003 )
          iRawType = wkbCircularStringZ;
      else if( iRawType == 3000004 )
          iRawType = wkbCompoundCurveZ;
      else if( iRawType == 3000005 )
          iRawType = wkbPolygon25D;
      else if( iRawType == 3000006 )
          iRawType = wkbCurvePolygonZ;
      else if( iRawType == 3000007 )
          iRawType = wkbMultiPoint25D;
      else if( iRawType == 3000008 )
          iRawType = wkbMultiCurveZ;
      else if( iRawType == 3000009 )
          iRawType = wkbMultiLineString25D;
      else if( iRawType == 3000010 )
          iRawType = wkbMultiSurfaceZ;
      else if( iRawType == 3000011 )
          iRawType = wkbMultiPolygon25D;
      else if( iRawType == 3000012 )
          iRawType = wkbGeometryCollection25D;
      else if( iRawType == 4000001 )
          iRawType = wkbPointM;
      else if( iRawType == 4000002 )
          iRawType = wkbLineStringM;
      else if( iRawType == 4000003 )
          iRawType = wkbCircularStringM;
      else if( iRawType == 4000004 )
          iRawType = wkbCompoundCurveM;
      else if( iRawType == 4000005 )
          iRawType = wkbPolygonM;
      else if( iRawType == 4000006 )
          iRawType = wkbCurvePolygonM;
      else if( iRawType == 4000007 )
          iRawType = wkbMultiPointM;
      else if( iRawType == 4000008 )
          iRawType = wkbMultiCurveM;
      else if( iRawType == 4000009 )
          iRawType = wkbMultiLineStringM;
      else if( iRawType == 4000010 )
          iRawType = wkbMultiSurfaceM;
      else if( iRawType == 4000011 )
          iRawType = wkbMultiPolygonM;
      else if( iRawType == 4000012 )
          iRawType = wkbGeometryCollectionM;
  
      // Sometimes the Z flag is in the 2nd byte?
      if( iRawType & (wkb25DBitInternalUse >> 16) )
      {
          // Clean off top 3 bytes.
          iRawType &= 0x000000FF;
          bIs3D = true;
      }
  
      if( eWkbVariant == wkbVariantPostGIS1 )
      {
          if( iRawType == POSTGIS15_CURVEPOLYGON )
              iRawType = wkbCurvePolygon;
          else if( iRawType == POSTGIS15_MULTICURVE )
              iRawType = wkbMultiCurve;
          else if( iRawType == POSTGIS15_MULTISURFACE )
              iRawType = wkbMultiSurface;
      }
  
      if( bIs3D )
      {
          iRawType += 1000;
      }
      if( bIsMeasured )
      {
          iRawType += 2000;
      }
  
      // ISO SQL/MM style types are between 1-17, 1001-1017, 2001-2017, and
      // 3001-3017.
      if( !((iRawType > 0 && iRawType <= 17) ||
             (iRawType > 1000 && iRawType <= 1017) ||
             (iRawType > 2000 && iRawType <= 2017) ||
             (iRawType > 3000 && iRawType <= 3017)) )
      {
          CPLError(CE_Failure, CPLE_NotSupported,
                   "Unsupported WKB type %d", iRawType);
          return OGRERR_UNSUPPORTED_GEOMETRY_TYPE;
      }
  
      // Convert to OGRwkbGeometryType value.
      if( iRawType >= 1001 && iRawType <= 1007 )
      {
          iRawType -= 1000;
          iRawType |= wkb25DBitInternalUse;
      }
  
      *peGeometryType = static_cast<OGRwkbGeometryType>(iRawType);
  
      return OGRERR_NONE;
  }
  