LCOV - code coverage report
Current view: top level - ogr - ogrutils.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 988 1165 84.8 %
Date: 2026-10-23 13:59:38 Functions: 38 46 82.6 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  OpenGIS Simple Features Reference Implementation
       4             :  * Purpose:  Utility functions for OGR classes, including some related to
       5             :  *           parsing well known text format vectors.
       6             :  * Author:   Frank Warmerdam, warmerdam@pobox.com
       7             :  *
       8             :  ******************************************************************************
       9             :  * Copyright (c) 1999, Frank Warmerdam
      10             :  * Copyright (c) 2008-2014, Even Rouault <even dot rouault at spatialys.com>
      11             :  *
      12             :  * SPDX-License-Identifier: MIT
      13             :  ****************************************************************************/
      14             : 
      15             : #include "cpl_port.h"
      16             : #include "ogr_geometry.h"
      17             : #include "ogr_p.h"
      18             : 
      19             : #include <cassert>
      20             : #include <cmath>
      21             : #include <cstddef>
      22             : #include <cstdio>
      23             : #include <cstdlib>
      24             : #include <cstring>
      25             : #include <cctype>
      26             : #include <limits>
      27             : #include <sstream>
      28             : #include <iomanip>
      29             : 
      30             : #include "cpl_conv.h"
      31             : #include "cpl_error.h"
      32             : #include "cpl_string.h"
      33             : #include "cpl_time.h"
      34             : #include "cpl_vsi.h"
      35             : #include "gdal.h"
      36             : #include "ogr_core.h"
      37             : #include "ogr_geometry.h"
      38             : #include "ogrsf_frmts.h"
      39             : 
      40             : // Returns whether a double fits within an int.
      41             : // Unable to put this in cpl_port.h as include limit breaks grib.
      42       72305 : inline bool CPLIsDoubleAnInt(double d)
      43             : {
      44             :     // Write it this way to detect NaN
      45      144589 :     if (!(d >= std::numeric_limits<int>::min() &&
      46       72284 :           d <= std::numeric_limits<int>::max()))
      47             :     {
      48          22 :         return false;
      49             :     }
      50       72283 :     return d == static_cast<double>(static_cast<int>(d));
      51             : }
      52             : 
      53             : namespace
      54             : {
      55             : 
      56             : // Remove trailing zeros except the last one.
      57       88797 : void removeTrailingZeros(std::string &s)
      58             : {
      59       88797 :     auto pos = s.find('.');
      60       88797 :     if (pos == std::string::npos)
      61        7634 :         return;
      62             : 
      63             :     // Remove zeros at the end.  We know this won't be npos because we
      64             :     // have a decimal point.
      65       81163 :     auto nzpos = s.find_last_not_of('0');
      66       81163 :     s = s.substr(0, nzpos + 1);
      67             : 
      68             :     // Make sure there is one 0 after the decimal point.
      69       81163 :     if (s.back() == '.')
      70       19251 :         s += '0';
      71             : }
      72             : 
      73             : // Round a string representing a number by 1 in the least significant digit.
      74         113 : void roundup(std::string &s)
      75             : {
      76             :     // Remove a negative sign if it exists to make processing
      77             :     // more straightforward.
      78         113 :     bool negative(false);
      79         113 :     if (s[0] == '-')
      80             :     {
      81           9 :         negative = true;
      82           9 :         s = s.substr(1);
      83             :     }
      84             : 
      85             :     // Go from the back to the front.  If we increment a digit other than
      86             :     // a '9', we're done.  If we increment a '9', set it to a '0' and move
      87             :     // to the next (more significant) digit.  If we get to the front of the
      88             :     // string, add a '1' to the front of the string.
      89         883 :     for (int pos = static_cast<int>(s.size() - 1); pos >= 0; pos--)
      90             :     {
      91         883 :         if (s[pos] == '.')
      92           3 :             continue;
      93         880 :         s[pos]++;
      94             : 
      95             :         // Incrementing past 9 gets you a colon in ASCII.
      96         880 :         if (s[pos] != ':')
      97         113 :             break;
      98             :         else
      99         767 :             s[pos] = '0';
     100         767 :         if (pos == 0)
     101           0 :             s = '1' + s;
     102             :     }
     103         113 :     if (negative)
     104           9 :         s = '-' + s;
     105         113 : }
     106             : 
     107             : // Whether s[0, nEnd) contains a non-zero digit.
     108       26239 : bool hasNonZeroDigit(const std::string &s, size_t nEnd)
     109             : {
     110       56975 :     for (size_t i = 0; i < nEnd; ++i)
     111             :     {
     112       54677 :         if (s[i] >= '1' && s[i] <= '9')
     113       23941 :             return true;
     114             :     }
     115        2298 :     return false;
     116             : }
     117             : 
     118             : // This attempts to eliminate what is likely binary -> decimal representation
     119             : // error or the result of low-order rounding with calculations.  The result
     120             : // may be more visually pleasing and takes up fewer places.
     121       37325 : void intelliround(std::string &s)
     122             : {
     123       37325 :     const size_t len = s.size();
     124             : 
     125             :     // If we don't have ten characters (a bit arbitrary), don't do anything.
     126       37325 :     constexpr size_t MIN_THRESHOLD_FOR_INELLIROUND = 10;
     127       37325 :     if (len <= MIN_THRESHOLD_FOR_INELLIROUND)
     128        6255 :         return;
     129             : 
     130             :     // If there is no decimal point, just return.
     131       31070 :     size_t iDotPos = std::string::npos;
     132       31070 :     size_t i = 0;
     133      178023 :     for (; i < len; ++i)
     134             :     {
     135      178023 :         if (s[i] == '.')
     136             :         {
     137       31070 :             iDotPos = i;
     138       31070 :             break;
     139             :         }
     140             :     }
     141       31070 :     if (iDotPos == std::string::npos)
     142           0 :         return;
     143      516716 :     for (; i < len; ++i)
     144             :     {
     145      485646 :         if (s[i] == 'e' || s[i] == 'E')
     146             :         {
     147             :             // Don't mess with exponential formatting.
     148           0 :             return;
     149             :         }
     150             :     }
     151             : 
     152       31070 :     size_t nCountBeforeDot = iDotPos - 1;
     153       31070 :     if (s[0] == '-')
     154        1871 :         nCountBeforeDot--;
     155             : 
     156             :     /* -------------------------------------------------------------------- */
     157             :     /*      Trim trailing 00000x's as they are likely roundoff error.       */
     158             :     /* -------------------------------------------------------------------- */
     159             :     // Neither trim may remove the only non-zero digits: that would turn a
     160             :     // value that fits in the requested precision, such as 1e-14, into 0.
     161       56961 :     if (s[len - 2] == '0' && s[len - 3] == '0' && s[len - 4] == '0' &&
     162       56961 :         s[len - 5] == '0' && s[len - 6] == '0' && hasNonZeroDigit(s, len - 1))
     163             :     {
     164       23881 :         s.pop_back();
     165             :     }
     166             :     // I don't understand this case exactly.  It's like saying if the
     167             :     // value is large enough and there are sufficient sig digits before
     168             :     // a bunch of zeros, remove the zeros and any digits at the end that
     169             :     // may be nonzero.  Perhaps if we can't exactly explain in words what
     170             :     // we're doing here, we shouldn't do it?  Perhaps it should
     171             :     // be generalized?
     172             :     // The value "12345.000000011" invokes this case, if anyone
     173             :     // is interested.
     174        6198 :     else if (iDotPos < len - 8 && (nCountBeforeDot >= 4 || s[len - 3] == '0') &&
     175        1812 :              (nCountBeforeDot >= 5 || s[len - 4] == '0') &&
     176        1354 :              (nCountBeforeDot >= 6 || s[len - 5] == '0') &&
     177        1297 :              (nCountBeforeDot >= 7 || s[len - 6] == '0') &&
     178        1283 :              (nCountBeforeDot >= 8 || s[len - 7] == '0') && s[len - 8] == '0' &&
     179       13387 :              s[len - 9] == '0' && hasNonZeroDigit(s, len - 8))
     180             :     {
     181          60 :         s.resize(s.size() - 8);
     182             :     }
     183             :     /* -------------------------------------------------------------------- */
     184             :     /*      Trim trailing 99999x's as they are likely roundoff error.       */
     185             :     /* -------------------------------------------------------------------- */
     186        7821 :     else if (s[len - 2] == '9' && s[len - 3] == '9' && s[len - 4] == '9' &&
     187        7821 :              s[len - 5] == '9' && s[len - 6] == '9')
     188             :     {
     189          92 :         s.resize(len - 6);
     190          92 :         roundup(s);
     191             :     }
     192        6042 :     else if (iDotPos < len - 9 && (nCountBeforeDot >= 4 || s[len - 3] == '9') &&
     193         503 :              (nCountBeforeDot >= 5 || s[len - 4] == '9') &&
     194          81 :              (nCountBeforeDot >= 6 || s[len - 5] == '9') &&
     195          34 :              (nCountBeforeDot >= 7 || s[len - 6] == '9') &&
     196       13106 :              (nCountBeforeDot >= 8 || s[len - 7] == '9') && s[len - 8] == '9' &&
     197          27 :              s[len - 9] == '9')
     198             :     {
     199          21 :         s.resize(len - 9);
     200          21 :         roundup(s);
     201             :     }
     202             : }
     203             : 
     204             : }  // unnamed namespace
     205             : 
     206             : /************************************************************************/
     207             : /*                          OGRFormatDouble()                           */
     208             : /************************************************************************/
     209             : 
     210          96 : void OGRFormatDouble(char *pszBuffer, int nBufferLen, double dfVal,
     211             :                      char chDecimalSep, int nPrecision,
     212             :                      char chConversionSpecifier)
     213             : {
     214          96 :     OGRWktOptions opts(nPrecision, OGRWktOptions::getDefaultRound());
     215          96 :     opts.format = (chConversionSpecifier == 'g' || chConversionSpecifier == 'G')
     216         192 :                       ? OGRWktFormat::G
     217             :                       : OGRWktFormat::F;
     218             : 
     219          96 :     std::string s = OGRFormatDouble(dfVal, opts, 1);
     220          96 :     if (chDecimalSep != '\0' && chDecimalSep != '.')
     221             :     {
     222           0 :         auto pos = s.find('.');
     223           0 :         if (pos != std::string::npos)
     224           0 :             s.replace(pos, 1, std::string(1, chDecimalSep));
     225             :     }
     226          96 :     if (s.size() + 1 > static_cast<size_t>(nBufferLen))
     227             :     {
     228           0 :         CPLError(CE_Warning, CPLE_AppDefined,
     229             :                  "Truncated double value %s to "
     230             :                  "%s.",
     231           0 :                  s.data(), s.substr(0, nBufferLen - 1).data());
     232           0 :         s.resize(nBufferLen - 1);
     233             :     }
     234          96 :     strcpy(pszBuffer, s.data());
     235          96 : }
     236             : 
     237             : /// Simplified OGRFormatDouble that can be made to adhere to provided
     238             : /// options.
     239       88828 : std::string OGRFormatDouble(double val, const OGRWktOptions &opts, int nDimIdx)
     240             : {
     241             :     // So to have identical cross platform representation.
     242       88828 :     if (std::isinf(val))
     243           2 :         return (val > 0) ? "inf" : "-inf";
     244       88826 :     if (std::isnan(val))
     245          29 :         return "nan";
     246             : 
     247          85 :     static thread_local std::locale classic_locale = []()
     248       88882 :     { return std::locale::classic(); }();
     249      177594 :     std::ostringstream oss;
     250       88797 :     oss.imbue(classic_locale);  // Make sure we output decimal points.
     251       88797 :     bool l_round(opts.round);
     252       88797 :     if (opts.format == OGRWktFormat::F ||
     253       54584 :         (opts.format == OGRWktFormat::Default && fabs(val) < 1))
     254       37325 :         oss << std::fixed;
     255             :     else
     256             :     {
     257             :         // Uppercase because OGC spec says capital 'E'.
     258       51472 :         oss << std::uppercase;
     259       51472 :         l_round = false;
     260             :     }
     261         629 :     oss << std::setprecision(nDimIdx < 3    ? opts.xyPrecision
     262             :                              : nDimIdx == 3 ? opts.zPrecision
     263       89426 :                                             : opts.mPrecision);
     264       88797 :     oss << val;
     265             : 
     266      177594 :     std::string sval = oss.str();
     267             : 
     268       88797 :     if (l_round)
     269       37325 :         intelliround(sval);
     270       88797 :     removeTrailingZeros(sval);
     271       88797 :     return sval;
     272             : }
     273             : 
     274             : /************************************************************************/
     275             : /*                        OGRMakeWktCoordinate()                        */
     276             : /*                                                                      */
     277             : /*      Format a well known text coordinate, trying to keep the         */
     278             : /*      ASCII representation compact, but accurate.  These rules        */
     279             : /*      will have to tighten up in the future.                          */
     280             : /*                                                                      */
     281             : /*      Currently a new point should require no more than 64            */
     282             : /*      characters barring the X or Y value being extremely large.      */
     283             : /************************************************************************/
     284             : 
     285         978 : void OGRMakeWktCoordinate(char *pszTarget, double x, double y, double z,
     286             :                           int nDimension)
     287             : 
     288             : {
     289             :     std::string wkt =
     290         978 :         OGRMakeWktCoordinate(x, y, z, nDimension, OGRWktOptions());
     291         978 :     memcpy(pszTarget, wkt.data(), wkt.size() + 1);
     292         978 : }
     293             : 
     294       54034 : static bool isInteger(const std::string &s)
     295             : {
     296       54034 :     return s.find_first_not_of("0123456789") == std::string::npos;
     297             : }
     298             : 
     299        4019 : std::string OGRMakeWktCoordinate(double x, double y, double z, int nDimension,
     300             :                                  const OGRWktOptions &opts)
     301             : {
     302        4019 :     std::string wkt;
     303             : 
     304             :     // Why do we do this?  Seems especially strange since we're ADDING
     305             :     // ".0" onto values in the case below.  The "&&" here also seems strange.
     306        5844 :     if (opts.format == OGRWktFormat::Default && CPLIsDoubleAnInt(x) &&
     307        1825 :         CPLIsDoubleAnInt(y))
     308             :     {
     309        1719 :         wkt = std::to_string(static_cast<int>(x));
     310        1719 :         wkt += ' ';
     311        1719 :         wkt += std::to_string(static_cast<int>(y));
     312             :     }
     313             :     else
     314             :     {
     315        2300 :         wkt = OGRFormatDouble(x, opts, 1);
     316             :         // ABELL - Why do we do special formatting?
     317        2300 :         if (isInteger(wkt))
     318         101 :             wkt += ".0";
     319        2300 :         wkt += ' ';
     320             : 
     321        4600 :         std::string yval = OGRFormatDouble(y, opts, 2);
     322        2300 :         if (isInteger(yval))
     323         963 :             yval += ".0";
     324        2300 :         wkt += yval;
     325             :     }
     326             : 
     327        4019 :     if (nDimension == 3)
     328             :     {
     329         919 :         wkt += ' ';
     330         919 :         if (opts.format == OGRWktFormat::Default && CPLIsDoubleAnInt(z))
     331         908 :             wkt += std::to_string(static_cast<int>(z));
     332             :         else
     333             :         {
     334          11 :             wkt += OGRFormatDouble(z, opts, 3);
     335             :         }
     336             :     }
     337        4019 :     return wkt;
     338             : }
     339             : 
     340             : /************************************************************************/
     341             : /*                        OGRMakeWktCoordinateM()                       */
     342             : /*                                                                      */
     343             : /*      Format a well known text coordinate, trying to keep the         */
     344             : /*      ASCII representation compact, but accurate.  These rules        */
     345             : /*      will have to tighten up in the future.                          */
     346             : /*                                                                      */
     347             : /*      Currently a new point should require no more than 64            */
     348             : /*      characters barring the X or Y value being extremely large.      */
     349             : /************************************************************************/
     350             : 
     351           0 : void OGRMakeWktCoordinateM(char *pszTarget, double x, double y, double z,
     352             :                            double m, bool hasZ, bool hasM)
     353             : 
     354             : {
     355             :     std::string wkt =
     356           0 :         OGRMakeWktCoordinateM(x, y, z, m, hasZ, hasM, OGRWktOptions());
     357           0 :     memcpy(pszTarget, wkt.data(), wkt.size() + 1);
     358           0 : }
     359             : 
     360       41729 : std::string OGRMakeWktCoordinateM(double x, double y, double z, double m,
     361             :                                   bool hasZ, bool hasM,
     362             :                                   const OGRWktOptions &opts)
     363             : {
     364       41729 :     std::string wkt;
     365       59506 :     if (opts.format == OGRWktFormat::Default && CPLIsDoubleAnInt(x) &&
     366       17777 :         CPLIsDoubleAnInt(y))
     367             :     {
     368       17012 :         wkt = std::to_string(static_cast<int>(x));
     369       17012 :         wkt += ' ';
     370       17012 :         wkt += std::to_string(static_cast<int>(y));
     371             :     }
     372             :     else
     373             :     {
     374       24717 :         wkt = OGRFormatDouble(x, opts, 1);
     375       24717 :         if (isInteger(wkt))
     376         703 :             wkt += ".0";
     377       24717 :         wkt += ' ';
     378             : 
     379       49434 :         std::string yval = OGRFormatDouble(y, opts, 2);
     380       24717 :         if (isInteger(yval))
     381        5817 :             yval += ".0";
     382       24717 :         wkt += yval;
     383             :     }
     384             : 
     385       41729 :     if (hasZ)
     386             :     {
     387        4076 :         wkt += ' ';
     388        4076 :         if (opts.format == OGRWktFormat::Default && CPLIsDoubleAnInt(z))
     389        3639 :             wkt += std::to_string(static_cast<int>(z));
     390             :         else
     391         437 :             wkt += OGRFormatDouble(z, opts, 3);
     392             :     }
     393             : 
     394       41729 :     if (hasM)
     395             :     {
     396        1996 :         wkt += ' ';
     397        1996 :         if (opts.format == OGRWktFormat::Default && CPLIsDoubleAnInt(m))
     398        1804 :             wkt += std::to_string(static_cast<int>(m));
     399             :         else
     400         192 :             wkt += OGRFormatDouble(m, opts, 4);
     401             :     }
     402       41729 :     return wkt;
     403             : }
     404             : 
     405             : /************************************************************************/
     406             : /*                          OGRWktReadToken()                           */
     407             : /*                                                                      */
     408             : /*      Read one token or delimiter and put into token buffer.  Pre     */
     409             : /*      and post white space is swallowed.                              */
     410             : /************************************************************************/
     411             : 
     412     1454880 : const char *OGRWktReadToken(const char *pszInput, char *pszToken)
     413             : 
     414             : {
     415     1454880 :     if (pszInput == nullptr)
     416           0 :         return nullptr;
     417             : 
     418             :     /* -------------------------------------------------------------------- */
     419             :     /*      Swallow pre-white space.                                        */
     420             :     /* -------------------------------------------------------------------- */
     421     1454880 :     while (*pszInput == ' ' || *pszInput == '\t' || *pszInput == '\n' ||
     422     1454880 :            *pszInput == '\r')
     423           3 :         ++pszInput;
     424             : 
     425             :     /* -------------------------------------------------------------------- */
     426             :     /*      If this is a delimiter, read just one character.                */
     427             :     /* -------------------------------------------------------------------- */
     428     1454880 :     if (*pszInput == '(' || *pszInput == ')' || *pszInput == ',')
     429             :     {
     430      565005 :         pszToken[0] = *pszInput;
     431      565005 :         pszToken[1] = '\0';
     432             : 
     433      565005 :         ++pszInput;
     434             :     }
     435             : 
     436             :     /* -------------------------------------------------------------------- */
     437             :     /*      Or if it alpha numeric read till we reach non-alpha numeric     */
     438             :     /*      text.                                                           */
     439             :     /* -------------------------------------------------------------------- */
     440             :     else
     441             :     {
     442      889877 :         int iChar = 0;
     443             : 
     444     6577320 :         while (iChar < OGR_WKT_TOKEN_MAX - 1 &&
     445     6577320 :                ((*pszInput >= 'a' && *pszInput <= 'z') ||
     446     6572770 :                 (*pszInput >= 'A' && *pszInput <= 'Z') ||
     447     5077440 :                 (*pszInput >= '0' && *pszInput <= '9') || *pszInput == '.' ||
     448     1039610 :                 *pszInput == '+' || *pszInput == '-'))
     449             :         {
     450     5687440 :             pszToken[iChar++] = *(pszInput++);
     451             :         }
     452             : 
     453      889877 :         pszToken[iChar++] = '\0';
     454             :     }
     455             : 
     456             :     /* -------------------------------------------------------------------- */
     457             :     /*      Eat any trailing white space.                                   */
     458             :     /* -------------------------------------------------------------------- */
     459     1901650 :     while (*pszInput == ' ' || *pszInput == '\t' || *pszInput == '\n' ||
     460     1454880 :            *pszInput == '\r')
     461      446766 :         ++pszInput;
     462             : 
     463     1454880 :     return pszInput;
     464             : }
     465             : 
     466             : /************************************************************************/
     467             : /*                          OGRWktReadPoints()                          */
     468             : /*                                                                      */
     469             : /*      Read a point string.  The point list must be contained in       */
     470             : /*      brackets and each point pair separated by a comma.              */
     471             : /************************************************************************/
     472             : 
     473           0 : const char *OGRWktReadPoints(const char *pszInput, OGRRawPoint **ppaoPoints,
     474             :                              double **ppadfZ, int *pnMaxPoints,
     475             :                              int *pnPointsRead)
     476             : 
     477             : {
     478           0 :     const char *pszOrigInput = pszInput;
     479           0 :     *pnPointsRead = 0;
     480             : 
     481           0 :     if (pszInput == nullptr)
     482           0 :         return nullptr;
     483             : 
     484             :     /* -------------------------------------------------------------------- */
     485             :     /*      Eat any leading white space.                                    */
     486             :     /* -------------------------------------------------------------------- */
     487           0 :     while (*pszInput == ' ' || *pszInput == '\t')
     488           0 :         ++pszInput;
     489             : 
     490             :     /* -------------------------------------------------------------------- */
     491             :     /*      If this isn't an opening bracket then we have a problem.        */
     492             :     /* -------------------------------------------------------------------- */
     493           0 :     if (*pszInput != '(')
     494             :     {
     495           0 :         CPLDebug("OGR", "Expected '(', but got %s in OGRWktReadPoints().",
     496             :                  pszInput);
     497             : 
     498           0 :         return pszInput;
     499             :     }
     500             : 
     501           0 :     ++pszInput;
     502             : 
     503             :     /* ==================================================================== */
     504             :     /*      This loop reads a single point.  It will continue till we       */
     505             :     /*      run out of well formed points, or a closing bracket is          */
     506             :     /*      encountered.                                                    */
     507             :     /* ==================================================================== */
     508           0 :     char szDelim[OGR_WKT_TOKEN_MAX] = {};
     509             : 
     510           0 :     do
     511             :     {
     512             :         /* --------------------------------------------------------------------
     513             :          */
     514             :         /*      Read the X and Y values, verify they are numeric. */
     515             :         /* --------------------------------------------------------------------
     516             :          */
     517           0 :         char szTokenX[OGR_WKT_TOKEN_MAX] = {};
     518           0 :         char szTokenY[OGR_WKT_TOKEN_MAX] = {};
     519             : 
     520           0 :         pszInput = OGRWktReadToken(pszInput, szTokenX);
     521           0 :         pszInput = OGRWktReadToken(pszInput, szTokenY);
     522             : 
     523           0 :         if ((!isdigit(static_cast<unsigned char>(szTokenX[0])) &&
     524           0 :              szTokenX[0] != '-' && szTokenX[0] != '.') ||
     525           0 :             (!isdigit(static_cast<unsigned char>(szTokenY[0])) &&
     526           0 :              szTokenY[0] != '-' && szTokenY[0] != '.'))
     527           0 :             return nullptr;
     528             : 
     529             :         /* --------------------------------------------------------------------
     530             :          */
     531             :         /*      Do we need to grow the point list to hold this point? */
     532             :         /* --------------------------------------------------------------------
     533             :          */
     534           0 :         if (*pnPointsRead == *pnMaxPoints)
     535             :         {
     536           0 :             *pnMaxPoints = *pnMaxPoints * 2 + 10;
     537           0 :             *ppaoPoints = static_cast<OGRRawPoint *>(
     538           0 :                 CPLRealloc(*ppaoPoints, sizeof(OGRRawPoint) * *pnMaxPoints));
     539             : 
     540           0 :             if (*ppadfZ != nullptr)
     541             :             {
     542           0 :                 *ppadfZ = static_cast<double *>(
     543           0 :                     CPLRealloc(*ppadfZ, sizeof(double) * *pnMaxPoints));
     544             :             }
     545             :         }
     546             : 
     547             :         /* --------------------------------------------------------------------
     548             :          */
     549             :         /*      Add point to list. */
     550             :         /* --------------------------------------------------------------------
     551             :          */
     552           0 :         (*ppaoPoints)[*pnPointsRead].x = CPLAtof(szTokenX);
     553           0 :         (*ppaoPoints)[*pnPointsRead].y = CPLAtof(szTokenY);
     554             : 
     555             :         /* --------------------------------------------------------------------
     556             :          */
     557             :         /*      Do we have a Z coordinate? */
     558             :         /* --------------------------------------------------------------------
     559             :          */
     560           0 :         pszInput = OGRWktReadToken(pszInput, szDelim);
     561             : 
     562           0 :         if (isdigit(static_cast<unsigned char>(szDelim[0])) ||
     563           0 :             szDelim[0] == '-' || szDelim[0] == '.')
     564             :         {
     565           0 :             if (*ppadfZ == nullptr)
     566             :             {
     567           0 :                 *ppadfZ = static_cast<double *>(
     568           0 :                     CPLCalloc(sizeof(double), *pnMaxPoints));
     569             :             }
     570             : 
     571           0 :             (*ppadfZ)[*pnPointsRead] = CPLAtof(szDelim);
     572             : 
     573           0 :             pszInput = OGRWktReadToken(pszInput, szDelim);
     574             :         }
     575           0 :         else if (*ppadfZ != nullptr)
     576             :         {
     577           0 :             (*ppadfZ)[*pnPointsRead] = 0.0;
     578             :         }
     579             : 
     580           0 :         ++(*pnPointsRead);
     581             : 
     582             :         /* --------------------------------------------------------------------
     583             :          */
     584             :         /*      Do we have a M coordinate? */
     585             :         /*      If we do, just skip it. */
     586             :         /* --------------------------------------------------------------------
     587             :          */
     588           0 :         if (isdigit(static_cast<unsigned char>(szDelim[0])) ||
     589           0 :             szDelim[0] == '-' || szDelim[0] == '.')
     590             :         {
     591           0 :             pszInput = OGRWktReadToken(pszInput, szDelim);
     592             :         }
     593             : 
     594             :         /* --------------------------------------------------------------------
     595             :          */
     596             :         /*      Read next delimiter ... it should be a comma if there are */
     597             :         /*      more points. */
     598             :         /* --------------------------------------------------------------------
     599             :          */
     600           0 :         if (szDelim[0] != ')' && szDelim[0] != ',')
     601             :         {
     602           0 :             CPLDebug("OGR",
     603             :                      "Corrupt input in OGRWktReadPoints().  "
     604             :                      "Got `%s' when expecting `,' or `)', near `%s' in %s.",
     605             :                      szDelim, pszInput, pszOrigInput);
     606           0 :             return nullptr;
     607             :         }
     608           0 :     } while (szDelim[0] == ',');
     609             : 
     610           0 :     return pszInput;
     611             : }
     612             : 
     613             : /************************************************************************/
     614             : /*                          OGRWktReadPointsM()                         */
     615             : /*                                                                      */
     616             : /*      Read a point string.  The point list must be contained in       */
     617             : /*      brackets and each point pair separated by a comma.              */
     618             : /************************************************************************/
     619             : 
     620      122771 : const char *OGRWktReadPointsM(const char *pszInput, OGRRawPoint **ppaoPoints,
     621             :                               double **ppadfZ, double **ppadfM, int *flags,
     622             :                               int *pnMaxPoints, int *pnPointsRead)
     623             : 
     624             : {
     625      122771 :     const char *pszOrigInput = pszInput;
     626      243592 :     const bool bNoFlags = !(*flags & OGRGeometry::OGR_G_3D) &&
     627      120821 :                           !(*flags & OGRGeometry::OGR_G_MEASURED);
     628      122771 :     *pnPointsRead = 0;
     629             : 
     630      122771 :     if (pszInput == nullptr)
     631           0 :         return nullptr;
     632             : 
     633             :     /* -------------------------------------------------------------------- */
     634             :     /*      Eat any leading white space.                                    */
     635             :     /* -------------------------------------------------------------------- */
     636      122771 :     while (*pszInput == ' ' || *pszInput == '\t')
     637           0 :         ++pszInput;
     638             : 
     639             :     /* -------------------------------------------------------------------- */
     640             :     /*      If this isn't an opening bracket then we have a problem.        */
     641             :     /* -------------------------------------------------------------------- */
     642      122771 :     if (*pszInput != '(')
     643             :     {
     644          26 :         CPLDebug("OGR", "Expected '(', but got %s in OGRWktReadPointsM().",
     645             :                  pszInput);
     646             : 
     647          26 :         return pszInput;
     648             :     }
     649             : 
     650      122745 :     ++pszInput;
     651             : 
     652             :     /* ==================================================================== */
     653             :     /*      This loop reads a single point.  It will continue till we       */
     654             :     /*      run out of well formed points, or a closing bracket is          */
     655             :     /*      encountered.                                                    */
     656             :     /* ==================================================================== */
     657      122745 :     char szDelim[OGR_WKT_TOKEN_MAX] = {};
     658             : 
     659      113048 :     do
     660             :     {
     661             :         /* --------------------------------------------------------------------
     662             :          */
     663             :         /*      Read the X and Y values, verify they are numeric. */
     664             :         /* --------------------------------------------------------------------
     665             :          */
     666      235793 :         char szTokenX[OGR_WKT_TOKEN_MAX] = {};
     667      235793 :         char szTokenY[OGR_WKT_TOKEN_MAX] = {};
     668             : 
     669      235793 :         pszInput = OGRWktReadToken(pszInput, szTokenX);
     670      235793 :         pszInput = OGRWktReadToken(pszInput, szTokenY);
     671             : 
     672      235793 :         if ((!isdigit(static_cast<unsigned char>(szTokenX[0])) &&
     673       67366 :              szTokenX[0] != '-' && szTokenX[0] != '.' &&
     674          51 :              !EQUAL(szTokenX, "nan")) ||
     675      235751 :             (!isdigit(static_cast<unsigned char>(szTokenY[0])) &&
     676       22507 :              szTokenY[0] != '-' && szTokenY[0] != '.' &&
     677          15 :              !EQUAL(szTokenY, "nan")))
     678          59 :             return nullptr;
     679             : 
     680             :         /* --------------------------------------------------------------------
     681             :          */
     682             :         /*      Do we need to grow the point list to hold this point? */
     683             :         /* --------------------------------------------------------------------
     684             :          */
     685      235748 :         if (*pnPointsRead == *pnMaxPoints)
     686             :         {
     687      122425 :             *pnMaxPoints = *pnMaxPoints * 2 + 10;
     688      122425 :             *ppaoPoints = static_cast<OGRRawPoint *>(
     689      122425 :                 CPLRealloc(*ppaoPoints, sizeof(OGRRawPoint) * *pnMaxPoints));
     690             : 
     691      122425 :             if (*ppadfZ != nullptr)
     692             :             {
     693         224 :                 *ppadfZ = static_cast<double *>(
     694         224 :                     CPLRealloc(*ppadfZ, sizeof(double) * *pnMaxPoints));
     695             :             }
     696             : 
     697      122425 :             if (*ppadfM != nullptr)
     698             :             {
     699          56 :                 *ppadfM = static_cast<double *>(
     700          56 :                     CPLRealloc(*ppadfM, sizeof(double) * *pnMaxPoints));
     701             :             }
     702             :         }
     703             : 
     704             :         /* --------------------------------------------------------------------
     705             :          */
     706             :         /*      Add point to list. */
     707             :         /* --------------------------------------------------------------------
     708             :          */
     709      235748 :         (*ppaoPoints)[*pnPointsRead].x = CPLAtof(szTokenX);
     710      235748 :         (*ppaoPoints)[*pnPointsRead].y = CPLAtof(szTokenY);
     711             : 
     712             :         /* --------------------------------------------------------------------
     713             :          */
     714             :         /*      Read the next token. */
     715             :         /* --------------------------------------------------------------------
     716             :          */
     717      235748 :         pszInput = OGRWktReadToken(pszInput, szDelim);
     718             : 
     719             :         /* --------------------------------------------------------------------
     720             :          */
     721             :         /*      If there are unexpectedly more coordinates, they are Z. */
     722             :         /* --------------------------------------------------------------------
     723             :          */
     724             : 
     725      235748 :         if (!(*flags & OGRGeometry::OGR_G_3D) &&
     726      223967 :             !(*flags & OGRGeometry::OGR_G_MEASURED) &&
     727      223095 :             (isdigit(static_cast<unsigned char>(szDelim[0])) ||
     728      177818 :              szDelim[0] == '-' || szDelim[0] == '.' || EQUAL(szDelim, "nan")))
     729             :         {
     730       45358 :             *flags |= OGRGeometry::OGR_G_3D;
     731             :         }
     732             : 
     733             :         /* --------------------------------------------------------------------
     734             :          */
     735             :         /*      Get Z if flag says so. */
     736             :         /*      Zero out possible remains from earlier strings. */
     737             :         /* --------------------------------------------------------------------
     738             :          */
     739             : 
     740      235748 :         if (*flags & OGRGeometry::OGR_G_3D)
     741             :         {
     742       57139 :             if (*ppadfZ == nullptr)
     743             :             {
     744       46909 :                 *ppadfZ = static_cast<double *>(
     745       46909 :                     CPLCalloc(sizeof(double), *pnMaxPoints));
     746             :             }
     747       57139 :             if (isdigit(static_cast<unsigned char>(szDelim[0])) ||
     748        2791 :                 szDelim[0] == '-' || szDelim[0] == '.' || EQUAL(szDelim, "nan"))
     749             :             {
     750       57060 :                 (*ppadfZ)[*pnPointsRead] = CPLAtof(szDelim);
     751       57060 :                 pszInput = OGRWktReadToken(pszInput, szDelim);
     752             :             }
     753             :             else
     754             :             {
     755          79 :                 (*ppadfZ)[*pnPointsRead] = 0.0;
     756             :             }
     757             :         }
     758      178609 :         else if (*ppadfZ != nullptr)
     759             :         {
     760          43 :             (*ppadfZ)[*pnPointsRead] = 0.0;
     761             :         }
     762             : 
     763             :         /* --------------------------------------------------------------------
     764             :          */
     765             :         /*      If there are unexpectedly even more coordinates, */
     766             :         /*      they are discarded unless there were no flags originally. */
     767             :         /*      This is for backwards compatibility. Should this be an error? */
     768             :         /* --------------------------------------------------------------------
     769             :          */
     770             : 
     771      235748 :         if (!(*flags & OGRGeometry::OGR_G_MEASURED) &&
     772      233454 :             (isdigit(static_cast<unsigned char>(szDelim[0])) ||
     773      233452 :              szDelim[0] == '-' || szDelim[0] == '.' || EQUAL(szDelim, "nan")))
     774             :         {
     775           5 :             if (bNoFlags)
     776             :             {
     777           5 :                 *flags |= OGRGeometry::OGR_G_MEASURED;
     778             :             }
     779             :             else
     780             :             {
     781           0 :                 pszInput = OGRWktReadToken(pszInput, szDelim);
     782             :             }
     783             :         }
     784             : 
     785             :         /* --------------------------------------------------------------------
     786             :          */
     787             :         /*      Get M if flag says so. */
     788             :         /*      Zero out possible remains from earlier strings. */
     789             :         /* --------------------------------------------------------------------
     790             :          */
     791             : 
     792      235748 :         if (*flags & OGRGeometry::OGR_G_MEASURED)
     793             :         {
     794        2299 :             if (*ppadfM == nullptr)
     795             :             {
     796         761 :                 *ppadfM = static_cast<double *>(
     797         761 :                     CPLCalloc(sizeof(double), *pnMaxPoints));
     798             :             }
     799        2299 :             if (isdigit(static_cast<unsigned char>(szDelim[0])) ||
     800         172 :                 szDelim[0] == '-' || szDelim[0] == '.' || EQUAL(szDelim, "nan"))
     801             :             {
     802        2293 :                 (*ppadfM)[*pnPointsRead] = CPLAtof(szDelim);
     803        2293 :                 pszInput = OGRWktReadToken(pszInput, szDelim);
     804             :             }
     805             :             else
     806             :             {
     807           6 :                 (*ppadfM)[*pnPointsRead] = 0.0;
     808             :             }
     809             :         }
     810      233449 :         else if (*ppadfM != nullptr)
     811             :         {
     812           0 :             (*ppadfM)[*pnPointsRead] = 0.0;
     813             :         }
     814             : 
     815             :         /* --------------------------------------------------------------------
     816             :          */
     817             :         /*      If there are still more coordinates and we do not have Z */
     818             :         /*      then we have a case of flags == M and four coordinates. */
     819             :         /*      This is allowed in BNF. */
     820             :         /* --------------------------------------------------------------------
     821             :          */
     822             : 
     823      235748 :         if (!(*flags & OGRGeometry::OGR_G_3D) &&
     824      178609 :             (isdigit(static_cast<unsigned char>(szDelim[0])) ||
     825      178609 :              szDelim[0] == '-' || szDelim[0] == '.' || EQUAL(szDelim, "nan")))
     826             :         {
     827           0 :             *flags |= OGRGeometry::OGR_G_3D;
     828           0 :             if (*ppadfZ == nullptr)
     829             :             {
     830           0 :                 *ppadfZ = static_cast<double *>(
     831           0 :                     CPLCalloc(sizeof(double), *pnMaxPoints));
     832             :             }
     833           0 :             (*ppadfZ)[*pnPointsRead] = (*ppadfM)[*pnPointsRead];
     834           0 :             (*ppadfM)[*pnPointsRead] = CPLAtof(szDelim);
     835           0 :             pszInput = OGRWktReadToken(pszInput, szDelim);
     836             :         }
     837             : 
     838             :         /* --------------------------------------------------------------------
     839             :          */
     840             :         /*      Increase points index. */
     841             :         /* --------------------------------------------------------------------
     842             :          */
     843      235748 :         ++(*pnPointsRead);
     844             : 
     845             :         /* --------------------------------------------------------------------
     846             :          */
     847             :         /*      The next delimiter should be a comma or an ending bracket. */
     848             :         /* --------------------------------------------------------------------
     849             :          */
     850      235748 :         if (szDelim[0] != ')' && szDelim[0] != ',')
     851             :         {
     852          14 :             CPLDebug("OGR",
     853             :                      "Corrupt input in OGRWktReadPointsM()  "
     854             :                      "Got `%s' when expecting `,' or `)', near `%s' in %s.",
     855             :                      szDelim, pszInput, pszOrigInput);
     856          14 :             return nullptr;
     857             :         }
     858      235734 :     } while (szDelim[0] == ',');
     859             : 
     860      122686 :     return pszInput;
     861             : }
     862             : 
     863             : /************************************************************************/
     864             : /*                             OGRMalloc()                              */
     865             : /*                                                                      */
     866             : /*      Cover for CPLMalloc()                                           */
     867             : /************************************************************************/
     868             : 
     869           0 : void *OGRMalloc(size_t size)
     870             : 
     871             : {
     872           0 :     return CPLMalloc(size);
     873             : }
     874             : 
     875             : /************************************************************************/
     876             : /*                             OGRCalloc()                              */
     877             : /*                                                                      */
     878             : /*      Cover for CPLCalloc()                                           */
     879             : /************************************************************************/
     880             : 
     881           0 : void *OGRCalloc(size_t count, size_t size)
     882             : 
     883             : {
     884           0 :     return CPLCalloc(count, size);
     885             : }
     886             : 
     887             : /************************************************************************/
     888             : /*                             OGRRealloc()                             */
     889             : /*                                                                      */
     890             : /*      Cover for CPLRealloc()                                          */
     891             : /************************************************************************/
     892             : 
     893           0 : void *OGRRealloc(void *pOld, size_t size)
     894             : 
     895             : {
     896           0 :     return CPLRealloc(pOld, size);
     897             : }
     898             : 
     899             : /************************************************************************/
     900             : /*                              OGRFree()                               */
     901             : /*                                                                      */
     902             : /*      Cover for CPLFree().                                            */
     903             : /************************************************************************/
     904             : 
     905           0 : void OGRFree(void *pMemory)
     906             : 
     907             : {
     908           0 :     CPLFree(pMemory);
     909           0 : }
     910             : 
     911             : /**
     912             :  * \fn OGRGeneralCmdLineProcessor(int, char***, int)
     913             :  * General utility option processing.
     914             :  *
     915             :  * This function is intended to provide a variety of generic commandline
     916             :  * options for all OGR commandline utilities.  It takes care of the following
     917             :  * commandline options:
     918             :  *
     919             :  *  \--version: report version of GDAL in use.
     920             :  *  \--license: report GDAL license info.
     921             :  *  \--format [format]: report details of one format driver.
     922             :  *  \--formats: report all format drivers configured.
     923             :  *  \--optfile filename: expand an option file into the argument list.
     924             :  *  \--config key value: set system configuration option.
     925             :  *  \--debug [on/off/value]: set debug level.
     926             :  *  \--pause: Pause for user input (allows time to attach debugger)
     927             :  *  \--locale [locale]: Install a locale using setlocale() (debugging)
     928             :  *  \--help-general: report detailed help on general options.
     929             :  *
     930             :  * The argument array is replaced "in place" and should be freed with
     931             :  * CSLDestroy() when no longer needed.  The typical usage looks something
     932             :  * like the following.  Note that the formats should be registered so that
     933             :  * the \--formats option will work properly.
     934             :  *
     935             :  *  int main( int argc, char ** argv )
     936             :  *  {
     937             :  *    OGRRegisterAll();
     938             :  *
     939             :  *    argc = OGRGeneralCmdLineProcessor( argc, &argv, 0 );
     940             :  *    if( argc < 1 )
     941             :  *        exit( -argc );
     942             :  *
     943             :  * @param nArgc number of values in the argument list.
     944             :  * @param ppapszArgv pointer to the argument list array (will be updated in
     945             :  * place).
     946             :  * @param nOptions unused.
     947             :  *
     948             :  * @return updated nArgc argument count.  Return of 0 requests terminate
     949             :  * without error, return of -1 requests exit with error code.
     950             :  */
     951             : 
     952         362 : int OGRGeneralCmdLineProcessor(int nArgc, char ***ppapszArgv,
     953             :                                CPL_UNUSED int nOptions)
     954             : 
     955             : {
     956         362 :     return GDALGeneralCmdLineProcessor(nArgc, ppapszArgv, GDAL_OF_VECTOR);
     957             : }
     958             : 
     959             : /************************************************************************/
     960             : /*                        OGRTimezoneToTZFlag()                         */
     961             : /************************************************************************/
     962             : 
     963             : /** \brief Converts a text timezone into OGR TZFlag integer representation.
     964             :  *
     965             :  * @param pszTZ "UTC", "Etc/UTC", or "(+/-)[0-9][0-9](:?)[0-9][0-9]"
     966             :  * @param bEmitErrorIfUnhandledFormat Whether to emit an error if pszTZ is
     967             :  *                                    a non-empty string with unrecognized
     968             :  *                                    format.
     969             :  */
     970        2500 : int OGRTimezoneToTZFlag(const char *pszTZ, bool bEmitErrorIfUnhandledFormat)
     971             : {
     972        2500 :     int nTZFlag = OGR_TZFLAG_UNKNOWN;
     973        2500 :     const size_t nTZLen = strlen(pszTZ);
     974        2500 :     if (strcmp(pszTZ, "UTC") == 0 || strcmp(pszTZ, "Etc/UTC") == 0)
     975             :     {
     976         404 :         nTZFlag = OGR_TZFLAG_UTC;
     977             :     }
     978        2096 :     else if ((pszTZ[0] == '+' || pszTZ[0] == '-') &&
     979         826 :              ((nTZLen == 6 && pszTZ[3] == ':') ||
     980           0 :               (nTZLen == 5 && pszTZ[3] >= '0' && pszTZ[3] <= '9')))
     981             :     {
     982         826 :         int nTZHour = atoi(pszTZ + 1);
     983         826 :         int nTZMin = atoi(pszTZ + (nTZLen == 6 ? 4 : 3));
     984         826 :         if (nTZHour >= 0 && nTZHour <= 14 && nTZMin >= 0 && nTZMin < 60 &&
     985         826 :             (nTZMin % 15) == 0)
     986             :         {
     987         826 :             nTZFlag = (nTZHour * 4) + (nTZMin / 15);
     988         826 :             if (pszTZ[0] == '+')
     989             :             {
     990         418 :                 nTZFlag = OGR_TZFLAG_UTC + nTZFlag;
     991             :             }
     992             :             else
     993             :             {
     994         408 :                 nTZFlag = OGR_TZFLAG_UTC - nTZFlag;
     995             :             }
     996         826 :         }
     997             :     }
     998        1270 :     else if (pszTZ[0] != 0 && bEmitErrorIfUnhandledFormat)
     999             :     {
    1000           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Unrecognized timezone: '%s'",
    1001             :                  pszTZ);
    1002             :     }
    1003        2500 :     return nTZFlag;
    1004             : }
    1005             : 
    1006             : /************************************************************************/
    1007             : /*                        OGRTZFlagToTimezone()                         */
    1008             : /************************************************************************/
    1009             : 
    1010             : /** \brief Converts a OGR TZFlag integer representation into a string
    1011             :  *
    1012             :  * @param nTZFlag OGR TZFlag integer (only ones with
    1013             :  *        value > OGR_TZFLAG_MIXED_TZ will yield a non-empty output)
    1014             :  * @param pszUTCRepresentation String to return if nTZFlag == OGR_TZFLAG_UTC.
    1015             :  *                             Typically "UTC", "Z", or "+00:00"
    1016             :  */
    1017          16 : std::string OGRTZFlagToTimezone(int nTZFlag, const char *pszUTCRepresentation)
    1018             : {
    1019          16 :     if (nTZFlag == OGR_TZFLAG_UTC)
    1020             :     {
    1021           0 :         return pszUTCRepresentation;
    1022             :     }
    1023          16 :     else if (nTZFlag > OGR_TZFLAG_MIXED_TZ)
    1024             :     {
    1025             :         char chSign;
    1026          16 :         const int nOffset = (nTZFlag - OGR_TZFLAG_UTC) * 15;
    1027          16 :         int nHours = static_cast<int>(nOffset / 60);  // Round towards zero.
    1028          16 :         const int nMinutes = std::abs(nOffset - nHours * 60);
    1029             : 
    1030          16 :         if (nOffset < 0)
    1031             :         {
    1032           8 :             chSign = '-';
    1033           8 :             nHours = std::abs(nHours);
    1034             :         }
    1035             :         else
    1036             :         {
    1037           8 :             chSign = '+';
    1038             :         }
    1039          16 :         return CPLSPrintf("%c%02d:%02d", chSign, nHours, nMinutes);
    1040             :     }
    1041             :     else
    1042             :     {
    1043           0 :         return std::string();
    1044             :     }
    1045             : }
    1046             : 
    1047             : /************************************************************************/
    1048             : /*                            OGRParseDate()                            */
    1049             : /*                                                                      */
    1050             : /*      Parse a variety of text date formats into an OGRField.          */
    1051             : /************************************************************************/
    1052             : 
    1053             : /**
    1054             :  * Parse date string.
    1055             :  *
    1056             :  * This function attempts to parse a date string in a variety of formats
    1057             :  * into the OGRField.Date format suitable for use with OGR.  Generally
    1058             :  * speaking this function is expecting values like:
    1059             :  *
    1060             :  *   YYYY-MM-DD HH:MM:SS(.sss)?+nn
    1061             :  *   or YYYY-MM-DDTHH:MM:SS(.sss)?Z (ISO 8601 format)
    1062             :  *   or YYYY-MM-DDZ
    1063             :  *   or THH:MM(:SS(.sss)?)?Z? (ISO 8601 extended format)
    1064             :  *   or THHMM(SS(.sss)?)?Z? (ISO 8601 basic format)
    1065             :  *
    1066             :  * The seconds may also have a decimal portion (parsed as milliseconds).  And
    1067             :  * just dates (YYYY-MM-DD) or just times (HH:MM:SS[.sss]) are also supported.
    1068             :  * The date may also be in YYYY/MM/DD format.  If the year is less than 100
    1069             :  * and greater than 30 a "1900" century value will be set.  If it is less than
    1070             :  * 30 and greater than -1 then a "2000" century value will be set.  In
    1071             :  * the future this function may be generalized, and additional control
    1072             :  * provided through nOptions, but an nOptions value of "0" should always do
    1073             :  * a reasonable default form of processing.
    1074             :  *
    1075             :  * The value of psField will be indeterminate if the function fails (returns
    1076             :  * FALSE).
    1077             :  *
    1078             :  * @param pszInput the input date string.
    1079             :  * @param psField the OGRField that will be updated with the parsed result.
    1080             :  * @param nOptions parsing options. 0 or OGRPARSEDATE_OPTION_LAX
    1081             :  *
    1082             :  * @return TRUE if apparently successful or FALSE on failure.
    1083             :  */
    1084             : 
    1085       14042 : int OGRParseDate(const char *pszInput, OGRField *psField, int nOptions)
    1086             : {
    1087       14042 :     psField->Date.Year = 0;
    1088       14042 :     psField->Date.Month = 0;
    1089       14042 :     psField->Date.Day = 0;
    1090       14042 :     psField->Date.Hour = 0;
    1091       14042 :     psField->Date.Minute = 0;
    1092       14042 :     psField->Date.Second = 0;
    1093       14042 :     psField->Date.TZFlag = 0;
    1094       14042 :     psField->Date.Reserved = 0;
    1095             : 
    1096             :     /* -------------------------------------------------------------------- */
    1097             :     /*      Do we have a date?                                              */
    1098             :     /* -------------------------------------------------------------------- */
    1099       14043 :     for (int i = 0; i < 256 && *pszInput == ' '; ++i)
    1100           1 :         ++pszInput;
    1101             : 
    1102       14042 :     bool bGotSomething = false;
    1103       14042 :     bool bTFound = false;
    1104       14042 :     if (strchr(pszInput, '-') || strchr(pszInput, '/'))
    1105             :     {
    1106        8096 :         if (!(*pszInput == '-' || *pszInput == '+' ||
    1107        8095 :               (*pszInput >= '0' && *pszInput <= '9')))
    1108          60 :             return FALSE;
    1109        8036 :         int nYear = atoi(pszInput);
    1110       16070 :         if (nYear > std::numeric_limits<GInt16>::max() ||
    1111        8034 :             nYear < std::numeric_limits<GInt16>::min())
    1112             :         {
    1113           2 :             CPLError(CE_Failure, CPLE_NotSupported,
    1114             :                      "Years < %d or > %d are not supported",
    1115           2 :                      std::numeric_limits<GInt16>::min(),
    1116           2 :                      std::numeric_limits<GInt16>::max());
    1117           2 :             return FALSE;
    1118             :         }
    1119        8034 :         psField->Date.Year = static_cast<GInt16>(nYear);
    1120        8034 :         if ((pszInput[1] == '-' || pszInput[1] == '/') ||
    1121        8032 :             (pszInput[1] != '\0' && (pszInput[2] == '-' || pszInput[2] == '/')))
    1122             :         {
    1123           6 :             if (psField->Date.Year < 100 && psField->Date.Year >= 30)
    1124           1 :                 psField->Date.Year += 1900;
    1125           5 :             else if (psField->Date.Year < 30 && psField->Date.Year >= 0)
    1126           5 :                 psField->Date.Year += 2000;
    1127             :         }
    1128             : 
    1129        8034 :         if (*pszInput == '-')
    1130           1 :             ++pszInput;
    1131       40143 :         for (int i = 0; i < 5 && *pszInput >= '0' && *pszInput <= '9'; ++i)
    1132       32109 :             ++pszInput;
    1133        8034 :         if (*pszInput != '-' && *pszInput != '/')
    1134          11 :             return FALSE;
    1135             :         else
    1136        8023 :             ++pszInput;
    1137             : 
    1138        8023 :         if (!(*pszInput >= '0' && *pszInput <= '9'))
    1139           2 :             return FALSE;
    1140        8021 :         if (!(pszInput[1] >= '0' && pszInput[1] <= '9'))
    1141             :         {
    1142          16 :             if ((nOptions & OGRPARSEDATE_OPTION_LAX) == 0)
    1143           1 :                 return FALSE;
    1144          15 :             const int nMonth = (pszInput[0] - '0');
    1145          15 :             if (nMonth == 0)
    1146           1 :                 return FALSE;
    1147          14 :             psField->Date.Month = static_cast<GByte>(nMonth);
    1148          14 :             ++pszInput;
    1149             :         }
    1150             :         else
    1151             :         {
    1152        8005 :             const int nMonth = (pszInput[0] - '0') * 10 + (pszInput[1] - '0');
    1153        8005 :             if (nMonth == 0 || nMonth > 12)
    1154           8 :                 return FALSE;
    1155        7997 :             psField->Date.Month = static_cast<GByte>(nMonth);
    1156             : 
    1157        7997 :             pszInput += 2;
    1158             :         }
    1159        8011 :         if (*pszInput != '-' && *pszInput != '/')
    1160           1 :             return FALSE;
    1161             :         else
    1162        8010 :             ++pszInput;
    1163             : 
    1164        8010 :         if (!(*pszInput >= '0' && *pszInput <= '9'))
    1165           3 :             return FALSE;
    1166        8007 :         if (!(pszInput[1] >= '0' && pszInput[1] <= '9'))
    1167             :         {
    1168           8 :             if ((nOptions & OGRPARSEDATE_OPTION_LAX) == 0)
    1169           1 :                 return FALSE;
    1170           7 :             const int nDay = (pszInput[0] - '0');
    1171           7 :             if (nDay == 0)
    1172           1 :                 return FALSE;
    1173           6 :             psField->Date.Day = static_cast<GByte>(nDay);
    1174           6 :             ++pszInput;
    1175             :         }
    1176             :         else
    1177             :         {
    1178        7999 :             const int nDay = (pszInput[0] - '0') * 10 + (pszInput[1] - '0');
    1179        7999 :             if (nDay == 0 || nDay > 31)
    1180           5 :                 return FALSE;
    1181        7994 :             psField->Date.Day = static_cast<GByte>(nDay);
    1182             : 
    1183        7994 :             pszInput += 2;
    1184             :         }
    1185        8000 :         if (*pszInput == '\0')
    1186        3443 :             return TRUE;
    1187             : 
    1188        4557 :         bGotSomething = true;
    1189             : 
    1190             :         // If ISO 8601 format.
    1191        4557 :         if (*pszInput == 'T')
    1192             :         {
    1193        2335 :             bTFound = true;
    1194        2335 :             ++pszInput;
    1195             :         }
    1196        2222 :         else if (*pszInput == 'Z')
    1197           1 :             return TRUE;
    1198        2221 :         else if (*pszInput != ' ')
    1199           5 :             return FALSE;
    1200             :     }
    1201             : 
    1202             :     /* -------------------------------------------------------------------- */
    1203             :     /*      Do we have a time?                                              */
    1204             :     /* -------------------------------------------------------------------- */
    1205       12777 :     for (int i = 0; i < 256 && *pszInput == ' '; ++i)
    1206        2280 :         ++pszInput;
    1207       10497 :     if (*pszInput == 'T')
    1208             :     {
    1209          19 :         bTFound = true;
    1210          19 :         ++pszInput;
    1211             :     }
    1212             : 
    1213       10497 :     if (bTFound || strchr(pszInput, ':'))
    1214             :     {
    1215        9648 :         if (!(*pszInput >= '0' && *pszInput <= '9'))
    1216          47 :             return FALSE;
    1217        9601 :         if (!(pszInput[1] >= '0' && pszInput[1] <= '9'))
    1218             :         {
    1219          10 :             if ((nOptions & OGRPARSEDATE_OPTION_LAX) == 0)
    1220           4 :                 return FALSE;
    1221             : 
    1222           6 :             if (!((bTFound || pszInput[1] == ':')))
    1223           1 :                 return FALSE;
    1224           5 :             const int nHour = (pszInput[0] - '0');
    1225           5 :             psField->Date.Hour = static_cast<GByte>(nHour);
    1226             : 
    1227           5 :             pszInput++;
    1228             :         }
    1229             :         else
    1230             :         {
    1231        9591 :             if (!((bTFound || pszInput[2] == ':')))
    1232           0 :                 return FALSE;
    1233        9591 :             const int nHour = (pszInput[0] - '0') * 10 + (pszInput[1] - '0');
    1234        9591 :             if (nHour > 23)
    1235           1 :                 return FALSE;
    1236        9590 :             psField->Date.Hour = static_cast<GByte>(nHour);
    1237             : 
    1238        9590 :             pszInput += 2;
    1239             :         }
    1240        9595 :         if (*pszInput == ':')
    1241        9592 :             ++pszInput;
    1242             : 
    1243        9595 :         if (!(*pszInput >= '0' && *pszInput <= '9'))
    1244           6 :             return FALSE;
    1245        9589 :         if (!(pszInput[1] >= '0' && pszInput[1] <= '9'))
    1246             :         {
    1247           4 :             if ((nOptions & OGRPARSEDATE_OPTION_LAX) == 0)
    1248           1 :                 return FALSE;
    1249             : 
    1250           3 :             const int nMinute = (pszInput[0] - '0');
    1251           3 :             psField->Date.Minute = static_cast<GByte>(nMinute);
    1252             : 
    1253           3 :             pszInput++;
    1254             :         }
    1255             :         else
    1256             :         {
    1257        9585 :             const int nMinute = (pszInput[0] - '0') * 10 + (pszInput[1] - '0');
    1258        9585 :             if (nMinute > 59)
    1259           1 :                 return FALSE;
    1260        9584 :             psField->Date.Minute = static_cast<GByte>(nMinute);
    1261             : 
    1262        9584 :             pszInput += 2;
    1263             :         }
    1264             : 
    1265        9587 :         if ((bTFound && *pszInput >= '0' && *pszInput <= '9') ||
    1266        9585 :             *pszInput == ':')
    1267             :         {
    1268        9571 :             if (*pszInput == ':')
    1269        9569 :                 ++pszInput;
    1270             : 
    1271        9571 :             if (!(*pszInput >= '0' && *pszInput <= '9' &&
    1272        9569 :                   (((nOptions & OGRPARSEDATE_OPTION_LAX) != 0) ||
    1273        9564 :                    (pszInput[1] >= '0' && pszInput[1] <= '9'))))
    1274           3 :                 return FALSE;
    1275        9568 :             const double dfSeconds = CPLAtof(pszInput);
    1276             :             // We accept second=60 for leap seconds
    1277        9568 :             if (dfSeconds >= 61.0)
    1278           1 :                 return FALSE;
    1279        9567 :             psField->Date.Second = static_cast<float>(dfSeconds);
    1280             : 
    1281             :             // Avoid rounding 59.999xxx to 60.0f (or set second to zero and
    1282             :             // increment the minute value) where x is 9, but round to 59.999 as
    1283             :             // the maximum value representable on a float.
    1284        9567 :             if (pszInput[0] == '5' && pszInput[1] == '9' &&
    1285         107 :                 pszInput[2] == '.' && pszInput[3] == '9' &&
    1286          63 :                 psField->Date.Second == 60.000f)
    1287             :             {
    1288           1 :                 psField->Date.Second = 59.999f;
    1289             :             }
    1290             : 
    1291        9567 :             if ((nOptions & OGRPARSEDATE_OPTION_LAX) != 0 &&
    1292           5 :                 !(pszInput[1] >= '0' && pszInput[1] <= '9'))
    1293             :             {
    1294           1 :                 ++pszInput;
    1295             :             }
    1296             :             else
    1297             :             {
    1298        9566 :                 pszInput += 2;
    1299             :             }
    1300        9567 :             if (*pszInput == '.')
    1301             :             {
    1302        2083 :                 ++pszInput;
    1303        8377 :                 while (*pszInput >= '0' && *pszInput <= '9')
    1304             :                 {
    1305        6294 :                     ++pszInput;
    1306             :                 }
    1307             :             }
    1308             : 
    1309             :             // If ISO 8601 format.
    1310        9567 :             if (*pszInput == 'Z')
    1311             :             {
    1312         466 :                 psField->Date.TZFlag = 100;
    1313             :             }
    1314             :         }
    1315             : 
    1316        9583 :         bGotSomething = true;
    1317             :     }
    1318         849 :     else if (bGotSomething && *pszInput != '\0')
    1319           1 :         return FALSE;
    1320             : 
    1321             :     // No date or time!
    1322       10431 :     if (!bGotSomething)
    1323         787 :         return FALSE;
    1324             : 
    1325             :     /* -------------------------------------------------------------------- */
    1326             :     /*      Do we have a timezone?                                          */
    1327             :     /* -------------------------------------------------------------------- */
    1328        9648 :     while (*pszInput == ' ')
    1329           4 :         ++pszInput;
    1330             : 
    1331        9644 :     if (*pszInput == '-' || *pszInput == '+')
    1332             :     {
    1333             :         // +HH integral offset
    1334         351 :         if (strlen(pszInput) <= 3)
    1335             :         {
    1336         152 :             psField->Date.TZFlag = static_cast<GByte>(100 + atoi(pszInput) * 4);
    1337             :         }
    1338         199 :         else if (pszInput[3] == ':'  // +HH:MM offset
    1339         109 :                  && atoi(pszInput + 4) % 15 == 0)
    1340             :         {
    1341         109 :             psField->Date.TZFlag = static_cast<GByte>(
    1342         109 :                 100 + atoi(pszInput + 1) * 4 + (atoi(pszInput + 4) / 15));
    1343             : 
    1344         109 :             if (pszInput[0] == '-')
    1345          24 :                 psField->Date.TZFlag = -1 * (psField->Date.TZFlag - 100) + 100;
    1346             :         }
    1347          90 :         else if (isdigit(static_cast<unsigned char>(pszInput[3])) &&
    1348          90 :                  isdigit(
    1349          90 :                      static_cast<unsigned char>(pszInput[4]))  // +HHMM offset
    1350          89 :                  && atoi(pszInput + 3) % 15 == 0)
    1351             :         {
    1352          89 :             psField->Date.TZFlag = static_cast<GByte>(
    1353          89 :                 100 + static_cast<GByte>(CPLScanLong(pszInput + 1, 2)) * 4 +
    1354          89 :                 (atoi(pszInput + 3) / 15));
    1355             : 
    1356          89 :             if (pszInput[0] == '-')
    1357           9 :                 psField->Date.TZFlag = -1 * (psField->Date.TZFlag - 100) + 100;
    1358             :         }
    1359           1 :         else if (isdigit(static_cast<unsigned char>(pszInput[3])) &&
    1360           1 :                  pszInput[4] == '\0'  // +HMM offset
    1361           1 :                  && atoi(pszInput + 2) % 15 == 0)
    1362             :         {
    1363           1 :             psField->Date.TZFlag = static_cast<GByte>(
    1364           1 :                 100 + static_cast<GByte>(CPLScanLong(pszInput + 1, 1)) * 4 +
    1365           1 :                 (atoi(pszInput + 2) / 15));
    1366             : 
    1367           1 :             if (pszInput[0] == '-')
    1368           0 :                 psField->Date.TZFlag = -1 * (psField->Date.TZFlag - 100) + 100;
    1369             :         }
    1370             :         // otherwise ignore any timezone info.
    1371             :     }
    1372             : 
    1373        9644 :     return TRUE;
    1374             : }
    1375             : 
    1376             : /************************************************************************/
    1377             : /*                   OGRParseDateTimeYYYYMMDDTHHMMZ()                   */
    1378             : /************************************************************************/
    1379             : 
    1380          10 : bool OGRParseDateTimeYYYYMMDDTHHMMZ(std::string_view sInput, OGRField *psField)
    1381             : {
    1382             :     // Detect "YYYY-MM-DDTHH:MM[Z]" (16 or 17 characters)
    1383          18 :     if ((sInput.size() == 16 || (sInput.size() == 17 && sInput[16] == 'Z')) &&
    1384           4 :         sInput[4] == '-' && sInput[7] == '-' && sInput[10] == 'T' &&
    1385           4 :         sInput[13] == ':' && static_cast<unsigned>(sInput[0] - '0') <= 9 &&
    1386           4 :         static_cast<unsigned>(sInput[1] - '0') <= 9 &&
    1387           4 :         static_cast<unsigned>(sInput[2] - '0') <= 9 &&
    1388           4 :         static_cast<unsigned>(sInput[3] - '0') <= 9 &&
    1389           4 :         static_cast<unsigned>(sInput[5] - '0') <= 9 &&
    1390           4 :         static_cast<unsigned>(sInput[6] - '0') <= 9 &&
    1391           4 :         static_cast<unsigned>(sInput[8] - '0') <= 9 &&
    1392           4 :         static_cast<unsigned>(sInput[9] - '0') <= 9 &&
    1393           4 :         static_cast<unsigned>(sInput[11] - '0') <= 9 &&
    1394           4 :         static_cast<unsigned>(sInput[12] - '0') <= 9 &&
    1395          24 :         static_cast<unsigned>(sInput[14] - '0') <= 9 &&
    1396           4 :         static_cast<unsigned>(sInput[15] - '0') <= 9)
    1397             :     {
    1398           4 :         psField->Date.Year = static_cast<GInt16>(
    1399           4 :             ((((sInput[0] - '0') * 10 + (sInput[1] - '0')) * 10) +
    1400           4 :              (sInput[2] - '0')) *
    1401           8 :                 10 +
    1402           4 :             (sInput[3] - '0'));
    1403           4 :         psField->Date.Month =
    1404           4 :             static_cast<GByte>((sInput[5] - '0') * 10 + (sInput[6] - '0'));
    1405           4 :         psField->Date.Day =
    1406           4 :             static_cast<GByte>((sInput[8] - '0') * 10 + (sInput[9] - '0'));
    1407           4 :         psField->Date.Hour =
    1408           4 :             static_cast<GByte>((sInput[11] - '0') * 10 + (sInput[12] - '0'));
    1409           4 :         psField->Date.Minute =
    1410           4 :             static_cast<GByte>((sInput[14] - '0') * 10 + (sInput[15] - '0'));
    1411           4 :         psField->Date.Second = 0.0f;
    1412           4 :         psField->Date.TZFlag = sInput.size() == 16 ? 0 : 100;
    1413           4 :         psField->Date.Reserved = 0;
    1414           4 :         if (psField->Date.Month == 0 || psField->Date.Month > 12 ||
    1415           4 :             psField->Date.Day == 0 || psField->Date.Day > 31 ||
    1416           4 :             psField->Date.Hour > 23 || psField->Date.Minute > 59)
    1417             :         {
    1418           1 :             return false;
    1419             :         }
    1420           3 :         return true;
    1421             :     }
    1422             : 
    1423           6 :     return false;
    1424             : }
    1425             : 
    1426             : /************************************************************************/
    1427             : /*                  OGRParseDateTimeYYYYMMDDTHHMMSSZ()                  */
    1428             : /************************************************************************/
    1429             : 
    1430          74 : bool OGRParseDateTimeYYYYMMDDTHHMMSSZ(std::string_view sInput,
    1431             :                                       OGRField *psField)
    1432             : {
    1433             :     // Detect "YYYY-MM-DDTHH:MM:SS[Z]" (19 or 20 characters)
    1434         112 :     if ((sInput.size() == 19 || (sInput.size() == 20 && sInput[19] == 'Z')) &&
    1435          44 :         sInput[4] == '-' && sInput[7] == '-' && sInput[10] == 'T' &&
    1436          44 :         sInput[13] == ':' && sInput[16] == ':' &&
    1437          44 :         static_cast<unsigned>(sInput[0] - '0') <= 9 &&
    1438          44 :         static_cast<unsigned>(sInput[1] - '0') <= 9 &&
    1439          44 :         static_cast<unsigned>(sInput[2] - '0') <= 9 &&
    1440          44 :         static_cast<unsigned>(sInput[3] - '0') <= 9 &&
    1441          44 :         static_cast<unsigned>(sInput[5] - '0') <= 9 &&
    1442          44 :         static_cast<unsigned>(sInput[6] - '0') <= 9 &&
    1443          44 :         static_cast<unsigned>(sInput[8] - '0') <= 9 &&
    1444          44 :         static_cast<unsigned>(sInput[9] - '0') <= 9 &&
    1445          44 :         static_cast<unsigned>(sInput[11] - '0') <= 9 &&
    1446          44 :         static_cast<unsigned>(sInput[12] - '0') <= 9 &&
    1447          44 :         static_cast<unsigned>(sInput[14] - '0') <= 9 &&
    1448          44 :         static_cast<unsigned>(sInput[15] - '0') <= 9 &&
    1449         192 :         static_cast<unsigned>(sInput[17] - '0') <= 9 &&
    1450          44 :         static_cast<unsigned>(sInput[18] - '0') <= 9)
    1451             :     {
    1452          44 :         psField->Date.Year = static_cast<GInt16>(
    1453          44 :             ((((sInput[0] - '0') * 10 + (sInput[1] - '0')) * 10) +
    1454          44 :              (sInput[2] - '0')) *
    1455          88 :                 10 +
    1456          44 :             (sInput[3] - '0'));
    1457          44 :         psField->Date.Month =
    1458          44 :             static_cast<GByte>((sInput[5] - '0') * 10 + (sInput[6] - '0'));
    1459          44 :         psField->Date.Day =
    1460          44 :             static_cast<GByte>((sInput[8] - '0') * 10 + (sInput[9] - '0'));
    1461          44 :         psField->Date.Hour =
    1462          44 :             static_cast<GByte>((sInput[11] - '0') * 10 + (sInput[12] - '0'));
    1463          44 :         psField->Date.Minute =
    1464          44 :             static_cast<GByte>((sInput[14] - '0') * 10 + (sInput[15] - '0'));
    1465          44 :         psField->Date.Second =
    1466          44 :             static_cast<float>(((sInput[17] - '0') * 10 + (sInput[18] - '0')));
    1467          44 :         psField->Date.TZFlag = sInput.size() == 19 ? 0 : 100;
    1468          44 :         psField->Date.Reserved = 0;
    1469          44 :         if (psField->Date.Month == 0 || psField->Date.Month > 12 ||
    1470          44 :             psField->Date.Day == 0 || psField->Date.Day > 31 ||
    1471          44 :             psField->Date.Hour > 23 || psField->Date.Minute > 59 ||
    1472          44 :             psField->Date.Second >= 61.0f)
    1473             :         {
    1474           1 :             return false;
    1475             :         }
    1476          43 :         return true;
    1477             :     }
    1478             : 
    1479          30 :     return false;
    1480             : }
    1481             : 
    1482             : /************************************************************************/
    1483             : /*                OGRParseDateTimeYYYYMMDDTHHMMSSsssZ()                 */
    1484             : /************************************************************************/
    1485             : 
    1486         536 : bool OGRParseDateTimeYYYYMMDDTHHMMSSsssZ(std::string_view sInput,
    1487             :                                          OGRField *psField)
    1488             : {
    1489             :     // Detect "YYYY-MM-DDTHH:MM:SS.SSS[Z]" (23 or 24 characters)
    1490        1069 :     if ((sInput.size() == 23 || (sInput.size() == 24 && sInput[23] == 'Z')) &&
    1491         490 :         sInput[4] == '-' && sInput[7] == '-' && sInput[10] == 'T' &&
    1492         490 :         sInput[13] == ':' && sInput[16] == ':' && sInput[19] == '.' &&
    1493         490 :         static_cast<unsigned>(sInput[0] - '0') <= 9 &&
    1494         490 :         static_cast<unsigned>(sInput[1] - '0') <= 9 &&
    1495         490 :         static_cast<unsigned>(sInput[2] - '0') <= 9 &&
    1496         490 :         static_cast<unsigned>(sInput[3] - '0') <= 9 &&
    1497         490 :         static_cast<unsigned>(sInput[5] - '0') <= 9 &&
    1498         490 :         static_cast<unsigned>(sInput[6] - '0') <= 9 &&
    1499         490 :         static_cast<unsigned>(sInput[8] - '0') <= 9 &&
    1500         490 :         static_cast<unsigned>(sInput[9] - '0') <= 9 &&
    1501         490 :         static_cast<unsigned>(sInput[11] - '0') <= 9 &&
    1502         490 :         static_cast<unsigned>(sInput[12] - '0') <= 9 &&
    1503         490 :         static_cast<unsigned>(sInput[14] - '0') <= 9 &&
    1504         490 :         static_cast<unsigned>(sInput[15] - '0') <= 9 &&
    1505         490 :         static_cast<unsigned>(sInput[17] - '0') <= 9 &&
    1506         490 :         static_cast<unsigned>(sInput[18] - '0') <= 9 &&
    1507         490 :         static_cast<unsigned>(sInput[20] - '0') <= 9 &&
    1508        1562 :         static_cast<unsigned>(sInput[21] - '0') <= 9 &&
    1509         490 :         static_cast<unsigned>(sInput[22] - '0') <= 9)
    1510             :     {
    1511         490 :         psField->Date.Year = static_cast<GInt16>(
    1512         490 :             ((((sInput[0] - '0') * 10 + (sInput[1] - '0')) * 10) +
    1513         490 :              (sInput[2] - '0')) *
    1514         980 :                 10 +
    1515         490 :             (sInput[3] - '0'));
    1516         490 :         psField->Date.Month =
    1517         490 :             static_cast<GByte>((sInput[5] - '0') * 10 + (sInput[6] - '0'));
    1518         490 :         psField->Date.Day =
    1519         490 :             static_cast<GByte>((sInput[8] - '0') * 10 + (sInput[9] - '0'));
    1520         490 :         psField->Date.Hour =
    1521         490 :             static_cast<GByte>((sInput[11] - '0') * 10 + (sInput[12] - '0'));
    1522         490 :         psField->Date.Minute =
    1523         490 :             static_cast<GByte>((sInput[14] - '0') * 10 + (sInput[15] - '0'));
    1524         490 :         psField->Date.Second =
    1525         490 :             static_cast<float>(((sInput[17] - '0') * 10 + (sInput[18] - '0')) +
    1526         490 :                                ((sInput[20] - '0') * 100 +
    1527         490 :                                 (sInput[21] - '0') * 10 + (sInput[22] - '0')) /
    1528             :                                    1000.0);
    1529         490 :         psField->Date.TZFlag = sInput.size() == 23 ? 0 : 100;
    1530         490 :         psField->Date.Reserved = 0;
    1531         490 :         if (psField->Date.Month == 0 || psField->Date.Month > 12 ||
    1532         490 :             psField->Date.Day == 0 || psField->Date.Day > 31 ||
    1533         490 :             psField->Date.Hour > 23 || psField->Date.Minute > 59 ||
    1534         490 :             psField->Date.Second >= 61.0f)
    1535             :         {
    1536           1 :             return false;
    1537             :         }
    1538         489 :         return true;
    1539             :     }
    1540             : 
    1541          46 :     return false;
    1542             : }
    1543             : 
    1544             : /************************************************************************/
    1545             : /*                        OGRParseXMLDateTime()                         */
    1546             : /************************************************************************/
    1547             : 
    1548       19117 : int OGRParseXMLDateTime(const char *pszXMLDateTime, OGRField *psField)
    1549             : {
    1550       19117 :     int year = 0;
    1551       19117 :     int month = 0;
    1552       19117 :     int day = 0;
    1553       19117 :     int hour = 0;
    1554       19117 :     int minute = 0;
    1555       19117 :     int TZHour = 0;
    1556       19117 :     int TZMinute = 0;
    1557       19117 :     float second = 0;
    1558       19117 :     char c = '\0';
    1559       19117 :     int TZ = 0;
    1560       19117 :     bool bRet = false;
    1561             : 
    1562             :     // Date is expressed as a UTC date.
    1563       38234 :     if (sscanf(pszXMLDateTime, "%04d-%02d-%02dT%02d:%02d:%f%c", &year, &month,
    1564       36696 :                &day, &hour, &minute, &second, &c) == 7 &&
    1565       17579 :         c == 'Z')
    1566             :     {
    1567       17576 :         TZ = 100;
    1568       17576 :         bRet = true;
    1569             :     }
    1570             :     // Date is expressed as a UTC date, with a timezone.
    1571        3082 :     else if (sscanf(pszXMLDateTime, "%04d-%02d-%02dT%02d:%02d:%f%c%02d:%02d",
    1572             :                     &year, &month, &day, &hour, &minute, &second, &c, &TZHour,
    1573        1544 :                     &TZMinute) == 9 &&
    1574           3 :              (c == '+' || c == '-'))
    1575             :     {
    1576           3 :         TZ = 100 + ((c == '+') ? 1 : -1) * ((TZHour * 60 + TZMinute) / 15);
    1577           3 :         bRet = true;
    1578             :     }
    1579             :     // Date is expressed into an unknown timezone.
    1580        1538 :     else if (sscanf(pszXMLDateTime, "%04d-%02d-%02dT%02d:%02d:%f", &year,
    1581        1538 :                     &month, &day, &hour, &minute, &second) == 6)
    1582             :     {
    1583         401 :         TZ = 0;
    1584         401 :         bRet = true;
    1585             :     }
    1586             :     // Date is expressed as a UTC date with only year:month:day.
    1587        1137 :     else if (sscanf(pszXMLDateTime, "%04d-%02d-%02d", &year, &month, &day) == 3)
    1588             :     {
    1589        1137 :         TZ = 0;
    1590        1137 :         bRet = true;
    1591             :     }
    1592             :     // Date is expressed as a UTC date with only year:month.
    1593           0 :     else if (sscanf(pszXMLDateTime, "%04d-%02d", &year, &month) == 2)
    1594             :     {
    1595           0 :         TZ = 0;
    1596           0 :         bRet = true;
    1597           0 :         day = 1;
    1598             :     }
    1599             : 
    1600       19117 :     if (!bRet)
    1601           0 :         return FALSE;
    1602             : 
    1603       19117 :     psField->Date.Year = static_cast<GInt16>(year);
    1604       19117 :     psField->Date.Month = static_cast<GByte>(month);
    1605       19117 :     psField->Date.Day = static_cast<GByte>(day);
    1606       19117 :     psField->Date.Hour = static_cast<GByte>(hour);
    1607       19117 :     psField->Date.Minute = static_cast<GByte>(minute);
    1608       19117 :     psField->Date.Second = second;
    1609       19117 :     psField->Date.TZFlag = static_cast<GByte>(TZ);
    1610       19117 :     psField->Date.Reserved = 0;
    1611             : 
    1612       19117 :     return TRUE;
    1613             : }
    1614             : 
    1615             : /************************************************************************/
    1616             : /*                       OGRParseRFC822DateTime()                       */
    1617             : /************************************************************************/
    1618             : 
    1619             : static const char *const aszMonthStr[] = {"Jan", "Feb", "Mar", "Apr",
    1620             :                                           "May", "Jun", "Jul", "Aug",
    1621             :                                           "Sep", "Oct", "Nov", "Dec"};
    1622             : 
    1623          27 : int OGRParseRFC822DateTime(const char *pszRFC822DateTime, OGRField *psField)
    1624             : {
    1625             :     int nYear, nMonth, nDay, nHour, nMinute, nSecond, nTZFlag;
    1626          27 :     if (!CPLParseRFC822DateTime(pszRFC822DateTime, &nYear, &nMonth, &nDay,
    1627             :                                 &nHour, &nMinute, &nSecond, &nTZFlag, nullptr))
    1628             :     {
    1629           6 :         return false;
    1630             :     }
    1631             : 
    1632          21 :     psField->Date.Year = static_cast<GInt16>(nYear);
    1633          21 :     psField->Date.Month = static_cast<GByte>(nMonth);
    1634          21 :     psField->Date.Day = static_cast<GByte>(nDay);
    1635          21 :     psField->Date.Hour = static_cast<GByte>(nHour);
    1636          21 :     psField->Date.Minute = static_cast<GByte>(nMinute);
    1637          21 :     psField->Date.Second = (nSecond < 0) ? 0.0f : static_cast<float>(nSecond);
    1638          21 :     psField->Date.TZFlag = static_cast<GByte>(nTZFlag);
    1639          21 :     psField->Date.Reserved = 0;
    1640             : 
    1641          21 :     return true;
    1642             : }
    1643             : 
    1644             : /**
    1645             :   * Returns the day of the week in Gregorian calendar
    1646             :   *
    1647             :   * @param day : day of the month, between 1 and 31
    1648             :   * @param month : month of the year, between 1 (Jan) and 12 (Dec)
    1649             :   * @param year : year
    1650             : 
    1651             :   * @return day of the week : 0 for Monday, ... 6 for Sunday
    1652             :   */
    1653             : 
    1654          12 : int OGRGetDayOfWeek(int day, int month, int year)
    1655             : {
    1656             :     // Reference: Zeller's congruence.
    1657          12 :     const int q = day;
    1658          12 :     int m = month;
    1659          12 :     if (month >= 3)
    1660             :     {
    1661             :         // m = month;
    1662             :     }
    1663             :     else
    1664             :     {
    1665           0 :         m = month + 12;
    1666           0 :         year--;
    1667             :     }
    1668          12 :     const int K = year % 100;
    1669          12 :     const int J = year / 100;
    1670          12 :     const int h = (q + (((m + 1) * 26) / 10) + K + K / 4 + J / 4 + 5 * J) % 7;
    1671          12 :     return (h + 5) % 7;
    1672             : }
    1673             : 
    1674             : /************************************************************************/
    1675             : /*                        OGRGetRFC822DateTime()                        */
    1676             : /************************************************************************/
    1677             : 
    1678          12 : char *OGRGetRFC822DateTime(const OGRField *psField)
    1679             : {
    1680          12 :     char *pszTZ = nullptr;
    1681          12 :     const char *const aszDayOfWeek[] = {"Mon", "Tue", "Wed", "Thu",
    1682             :                                         "Fri", "Sat", "Sun"};
    1683             : 
    1684          24 :     int dayofweek = OGRGetDayOfWeek(psField->Date.Day, psField->Date.Month,
    1685          12 :                                     psField->Date.Year);
    1686             : 
    1687          12 :     int month = psField->Date.Month;
    1688          12 :     if (month < 1 || month > 12)
    1689           0 :         month = 1;
    1690             : 
    1691          12 :     int TZFlag = psField->Date.TZFlag;
    1692          12 :     if (TZFlag == 0 || TZFlag == 100)
    1693             :     {
    1694           0 :         pszTZ = CPLStrdup("GMT");
    1695             :     }
    1696             :     else
    1697             :     {
    1698          12 :         int TZOffset = std::abs(TZFlag - 100) * 15;
    1699          12 :         int TZHour = TZOffset / 60;
    1700          12 :         int TZMinute = TZOffset - TZHour * 60;
    1701          12 :         pszTZ = CPLStrdup(CPLSPrintf("%c%02d%02d", TZFlag > 100 ? '+' : '-',
    1702             :                                      TZHour, TZMinute));
    1703             :     }
    1704          12 :     char *pszRet = CPLStrdup(CPLSPrintf(
    1705          12 :         "%s, %02d %s %04d %02d:%02d:%02d %s", aszDayOfWeek[dayofweek],
    1706          12 :         psField->Date.Day, aszMonthStr[month - 1], psField->Date.Year,
    1707          12 :         psField->Date.Hour, psField->Date.Minute,
    1708          12 :         static_cast<int>(psField->Date.Second), pszTZ));
    1709          12 :     CPLFree(pszTZ);
    1710          12 :     return pszRet;
    1711             : }
    1712             : 
    1713             : /************************************************************************/
    1714             : /*                         OGRGetXMLDateTime()                          */
    1715             : /************************************************************************/
    1716             : 
    1717        5883 : char *OGRGetXMLDateTime(const OGRField *psField)
    1718             : {
    1719             :     char *pszRet =
    1720        5883 :         static_cast<char *>(CPLMalloc(OGR_SIZEOF_ISO8601_DATETIME_BUFFER));
    1721        5883 :     OGRGetISO8601DateTime(psField, false, pszRet);
    1722        5883 :     return pszRet;
    1723             : }
    1724             : 
    1725           3 : char *OGRGetXMLDateTime(const OGRField *psField, bool bAlwaysMillisecond)
    1726             : {
    1727             :     char *pszRet =
    1728           3 :         static_cast<char *>(CPLMalloc(OGR_SIZEOF_ISO8601_DATETIME_BUFFER));
    1729           3 :     OGRGetISO8601DateTime(psField, bAlwaysMillisecond, pszRet);
    1730           3 :     return pszRet;
    1731             : }
    1732             : 
    1733        6053 : int OGRGetISO8601DateTime(const OGRField *psField, bool bAlwaysMillisecond,
    1734             :                           char szBuffer[OGR_SIZEOF_ISO8601_DATETIME_BUFFER])
    1735             : {
    1736             :     OGRISO8601Format sFormat;
    1737        6053 :     sFormat.ePrecision = bAlwaysMillisecond ? OGRISO8601Precision::MILLISECOND
    1738             :                                             : OGRISO8601Precision::AUTO;
    1739       12106 :     return OGRGetISO8601DateTime(psField, sFormat, szBuffer);
    1740             : }
    1741             : 
    1742       11266 : int OGRGetISO8601DateTime(const OGRField *psField,
    1743             :                           const OGRISO8601Format &sFormat,
    1744             :                           char szBuffer[OGR_SIZEOF_ISO8601_DATETIME_BUFFER])
    1745             : {
    1746       11266 :     const GInt16 year = psField->Date.Year;
    1747       11266 :     const GByte month = psField->Date.Month;
    1748       11266 :     const GByte day = psField->Date.Day;
    1749       11266 :     const GByte hour = psField->Date.Hour;
    1750       11266 :     const GByte minute = psField->Date.Minute;
    1751       11266 :     const float second = psField->Date.Second;
    1752       11266 :     const GByte TZFlag = psField->Date.TZFlag;
    1753             : 
    1754       11266 :     if (year < 0 || year >= 10000)
    1755             :     {
    1756           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    1757             :                  "OGRGetISO8601DateTime(): year %d unsupported ", year);
    1758           0 :         szBuffer[0] = 0;
    1759           0 :         return 0;
    1760             :     }
    1761             : 
    1762       11266 :     int nYear = year;
    1763       11266 :     szBuffer[3] = (nYear % 10) + '0';
    1764       11266 :     nYear /= 10;
    1765       11266 :     szBuffer[2] = (nYear % 10) + '0';
    1766       11266 :     nYear /= 10;
    1767       11266 :     szBuffer[1] = (nYear % 10) + '0';
    1768       11266 :     nYear /= 10;
    1769       11266 :     szBuffer[0] = static_cast<char>(nYear /*% 10*/ + '0');
    1770       11266 :     szBuffer[4] = '-';
    1771       11266 :     szBuffer[5] = ((month / 10) % 10) + '0';
    1772       11266 :     szBuffer[6] = (month % 10) + '0';
    1773       11266 :     szBuffer[7] = '-';
    1774       11266 :     szBuffer[8] = ((day / 10) % 10) + '0';
    1775       11266 :     szBuffer[9] = (day % 10) + '0';
    1776       11266 :     szBuffer[10] = 'T';
    1777       11266 :     szBuffer[11] = ((hour / 10) % 10) + '0';
    1778       11266 :     szBuffer[12] = (hour % 10) + '0';
    1779       11266 :     szBuffer[13] = ':';
    1780       11266 :     szBuffer[14] = ((minute / 10) % 10) + '0';
    1781       11266 :     szBuffer[15] = (minute % 10) + '0';
    1782             :     int nPos;
    1783       11266 :     if (sFormat.ePrecision == OGRISO8601Precision::MINUTE)
    1784             :     {
    1785           2 :         nPos = 16;
    1786             :     }
    1787             :     else
    1788             :     {
    1789       11264 :         szBuffer[16] = ':';
    1790             : 
    1791       22520 :         if (sFormat.ePrecision == OGRISO8601Precision::MILLISECOND ||
    1792       11256 :             (sFormat.ePrecision == OGRISO8601Precision::AUTO &&
    1793       11254 :              OGR_GET_MS(second)))
    1794             :         {
    1795             :             /* Below is equivalent of the below snprintf(), but hand-made for
    1796             :              * faster execution. */
    1797             :             /* snprintf(szBuffer, nMaxSize,
    1798             :                                    "%04d-%02u-%02uT%02u:%02u:%06.3f%s",
    1799             :                                    year, month, day, hour, minute, second,
    1800             :                                    szTimeZone);
    1801             :             */
    1802       10146 :             int nMilliSecond = static_cast<int>(second * 1000.0f + 0.5f);
    1803       10146 :             szBuffer[22] = (nMilliSecond % 10) + '0';
    1804       10146 :             nMilliSecond /= 10;
    1805       10146 :             szBuffer[21] = (nMilliSecond % 10) + '0';
    1806       10146 :             nMilliSecond /= 10;
    1807       10146 :             szBuffer[20] = (nMilliSecond % 10) + '0';
    1808       10146 :             nMilliSecond /= 10;
    1809       10146 :             szBuffer[19] = '.';
    1810       10146 :             szBuffer[18] = (nMilliSecond % 10) + '0';
    1811       10146 :             nMilliSecond /= 10;
    1812       10146 :             szBuffer[17] = (nMilliSecond % 10) + '0';
    1813       10146 :             nPos = 23;
    1814             :         }
    1815             :         else
    1816             :         {
    1817             :             /* Below is equivalent of the below snprintf(), but hand-made for
    1818             :              * faster execution. */
    1819             :             /* snprintf(szBuffer, nMaxSize,
    1820             :                                    "%04d-%02u-%02uT%02u:%02u:%02u%s",
    1821             :                                    year, month, day, hour, minute,
    1822             :                                    static_cast<GByte>(second), szTimeZone);
    1823             :             */
    1824        1118 :             int nSecond = static_cast<int>(second + 0.5f);
    1825        1118 :             szBuffer[17] = ((nSecond / 10) % 10) + '0';
    1826        1118 :             szBuffer[18] = (nSecond % 10) + '0';
    1827        1118 :             nPos = 19;
    1828             :         }
    1829             :     }
    1830             : 
    1831       11266 :     switch (TZFlag)
    1832             :     {
    1833         966 :         case 0:  // Unknown time zone
    1834             :         case 1:  // Local time zone (not specified)
    1835         966 :             break;
    1836             : 
    1837         262 :         case 100:  // GMT
    1838         262 :             szBuffer[nPos++] = 'Z';
    1839         262 :             break;
    1840             : 
    1841       10038 :         default:  // Offset (in quarter-hour units) from GMT
    1842       10038 :             const int TZOffset = std::abs(TZFlag - 100) * 15;
    1843       10038 :             const int TZHour = TZOffset / 60;
    1844       10038 :             const int TZMinute = TZOffset % 60;
    1845             : 
    1846       10038 :             szBuffer[nPos++] = (TZFlag > 100) ? '+' : '-';
    1847       10038 :             szBuffer[nPos++] = ((TZHour / 10) % 10) + '0';
    1848       10038 :             szBuffer[nPos++] = (TZHour % 10) + '0';
    1849       10038 :             szBuffer[nPos++] = ':';
    1850       10038 :             szBuffer[nPos++] = ((TZMinute / 10) % 10) + '0';
    1851       10038 :             szBuffer[nPos++] = (TZMinute % 10) + '0';
    1852             :     }
    1853             : 
    1854       11266 :     szBuffer[nPos] = 0;
    1855             : 
    1856       11266 :     return nPos;
    1857             : }
    1858             : 
    1859             : /************************************************************************/
    1860             : /*                    OGRGetXML_UTF8_EscapedString()                    */
    1861             : /************************************************************************/
    1862             : 
    1863        3044 : char *OGRGetXML_UTF8_EscapedString(const char *pszString)
    1864             : {
    1865        3044 :     char *pszEscaped = nullptr;
    1866        3045 :     if (!CPLIsUTF8(pszString, -1) &&
    1867           1 :         CPLTestBool(CPLGetConfigOption("OGR_FORCE_ASCII", "YES")))
    1868             :     {
    1869             :         static bool bFirstTime = true;
    1870           1 :         if (bFirstTime)
    1871             :         {
    1872           1 :             bFirstTime = false;
    1873           1 :             CPLError(CE_Warning, CPLE_AppDefined,
    1874             :                      "%s is not a valid UTF-8 string. Forcing it to ASCII.  "
    1875             :                      "If you still want the original string and change the XML "
    1876             :                      "file encoding afterwards, you can define "
    1877             :                      "OGR_FORCE_ASCII=NO as configuration option.  "
    1878             :                      "This warning won't be issued anymore",
    1879             :                      pszString);
    1880             :         }
    1881             :         else
    1882             :         {
    1883           0 :             CPLDebug("OGR",
    1884             :                      "%s is not a valid UTF-8 string. Forcing it to ASCII",
    1885             :                      pszString);
    1886             :         }
    1887           1 :         char *pszTemp = CPLForceToASCII(pszString, -1, '?');
    1888           1 :         pszEscaped = CPLEscapeString(pszTemp, -1, CPLES_XML);
    1889           1 :         CPLFree(pszTemp);
    1890             :     }
    1891             :     else
    1892        3043 :         pszEscaped = CPLEscapeString(pszString, -1, CPLES_XML);
    1893        3044 :     return pszEscaped;
    1894             : }
    1895             : 
    1896             : /************************************************************************/
    1897             : /*                           OGRCompareDate()                           */
    1898             : /************************************************************************/
    1899             : 
    1900         225 : int OGRCompareDate(const OGRField *psFirstTuple, const OGRField *psSecondTuple)
    1901             : {
    1902             :     // TODO: We ignore TZFlag.
    1903             : 
    1904         225 :     if (psFirstTuple->Date.Year < psSecondTuple->Date.Year)
    1905          12 :         return -1;
    1906         213 :     else if (psFirstTuple->Date.Year > psSecondTuple->Date.Year)
    1907           9 :         return 1;
    1908             : 
    1909         204 :     if (psFirstTuple->Date.Month < psSecondTuple->Date.Month)
    1910           1 :         return -1;
    1911         203 :     else if (psFirstTuple->Date.Month > psSecondTuple->Date.Month)
    1912           6 :         return 1;
    1913             : 
    1914         197 :     if (psFirstTuple->Date.Day < psSecondTuple->Date.Day)
    1915           8 :         return -1;
    1916         189 :     else if (psFirstTuple->Date.Day > psSecondTuple->Date.Day)
    1917           8 :         return 1;
    1918             : 
    1919         181 :     if (psFirstTuple->Date.Hour < psSecondTuple->Date.Hour)
    1920           1 :         return -1;
    1921         180 :     else if (psFirstTuple->Date.Hour > psSecondTuple->Date.Hour)
    1922           8 :         return 1;
    1923             : 
    1924         172 :     if (psFirstTuple->Date.Minute < psSecondTuple->Date.Minute)
    1925           0 :         return -1;
    1926         172 :     else if (psFirstTuple->Date.Minute > psSecondTuple->Date.Minute)
    1927           0 :         return 1;
    1928             : 
    1929         172 :     if (psFirstTuple->Date.Second < psSecondTuple->Date.Second)
    1930           9 :         return -1;
    1931         163 :     else if (psFirstTuple->Date.Second > psSecondTuple->Date.Second)
    1932          11 :         return 1;
    1933             : 
    1934         152 :     return 0;
    1935             : }
    1936             : 
    1937             : /************************************************************************/
    1938             : /*                            OGRFastAtof()                             */
    1939             : /************************************************************************/
    1940             : 
    1941             : // On Windows, CPLAtof() is very slow if the number is followed by other long
    1942             : // content.  Just extract the number into a short string before calling
    1943             : // CPLAtof() on it.
    1944           0 : static double OGRCallAtofOnShortString(const char *pszStr)
    1945             : {
    1946           0 :     const char *p = pszStr;
    1947           0 :     while (*p == ' ' || *p == '\t')
    1948           0 :         ++p;
    1949             : 
    1950           0 :     char szTemp[128] = {};
    1951           0 :     int nCounter = 0;
    1952           0 :     while (*p == '+' || *p == '-' || (*p >= '0' && *p <= '9') || *p == '.' ||
    1953           0 :            (*p == 'e' || *p == 'E' || *p == 'd' || *p == 'D'))
    1954             :     {
    1955           0 :         szTemp[nCounter++] = *(p++);
    1956           0 :         if (nCounter == 127)
    1957           0 :             return CPLAtof(pszStr);
    1958             :     }
    1959           0 :     szTemp[nCounter] = '\0';
    1960           0 :     return CPLAtof(szTemp);
    1961             : }
    1962             : 
    1963             : /** Same contract as CPLAtof, except than it doesn't always call the
    1964             :  *  system CPLAtof() that may be slow on some platforms. For simple but
    1965             :  *  common strings, it'll use a faster implementation (up to 20x faster
    1966             :  *  than CPLAtof() on MS runtime libraries) that has no guaranty to return
    1967             :  *  exactly the same floating point number.
    1968             :  */
    1969             : 
    1970      409457 : double OGRFastAtof(const char *pszStr)
    1971             : {
    1972      409457 :     double dfVal = 0;
    1973      409457 :     double dfSign = 1.0;
    1974      409457 :     const char *p = pszStr;
    1975             : 
    1976      409457 :     constexpr double adfTenPower[] = {
    1977             :         1e0,  1e1,  1e2,  1e3,  1e4,  1e5,  1e6,  1e7,  1e8,  1e9,  1e10,
    1978             :         1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21,
    1979             :         1e22, 1e23, 1e24, 1e25, 1e26, 1e27, 1e28, 1e29, 1e30, 1e31};
    1980             : 
    1981      409459 :     while (*p == ' ' || *p == '\t')
    1982           2 :         ++p;
    1983             : 
    1984      409457 :     if (*p == '+')
    1985           9 :         ++p;
    1986      409448 :     else if (*p == '-')
    1987             :     {
    1988        2310 :         dfSign = -1.0;
    1989        2310 :         ++p;
    1990             :     }
    1991             : 
    1992             :     while (true)
    1993             :     {
    1994     2688550 :         if (*p >= '0' && *p <= '9')
    1995             :         {
    1996     2279090 :             dfVal = dfVal * 10.0 + (*p - '0');
    1997     2279090 :             ++p;
    1998             :         }
    1999      409457 :         else if (*p == '.')
    2000             :         {
    2001      401687 :             ++p;
    2002      401687 :             break;
    2003             :         }
    2004        7770 :         else if (*p == 'e' || *p == 'E' || *p == 'd' || *p == 'D')
    2005           0 :             return OGRCallAtofOnShortString(pszStr);
    2006             :         else
    2007        7770 :             return dfSign * dfVal;
    2008             :     }
    2009             : 
    2010      401687 :     unsigned int countFractionnal = 0;
    2011             :     while (true)
    2012             :     {
    2013     1515480 :         if (*p >= '0' && *p <= '9')
    2014             :         {
    2015     1113790 :             dfVal = dfVal * 10.0 + (*p - '0');
    2016     1113790 :             ++countFractionnal;
    2017     1113790 :             ++p;
    2018             :         }
    2019      401687 :         else if (*p == 'e' || *p == 'E' || *p == 'd' || *p == 'D')
    2020           0 :             return OGRCallAtofOnShortString(pszStr);
    2021             :         else
    2022             :         {
    2023      401687 :             if (countFractionnal < CPL_ARRAYSIZE(adfTenPower))
    2024      401687 :                 return dfSign * (dfVal / adfTenPower[countFractionnal]);
    2025             :             else
    2026           0 :                 return OGRCallAtofOnShortString(pszStr);
    2027             :         }
    2028             :     }
    2029             : }
    2030             : 
    2031             : /**
    2032             :  * Check that panPermutation is a permutation of [0, nSize-1].
    2033             :  * @param panPermutation an array of nSize elements.
    2034             :  * @param nSize size of the array.
    2035             :  * @return OGRERR_NONE if panPermutation is a permutation of [0, nSize - 1].
    2036             :  */
    2037         267 : OGRErr OGRCheckPermutation(const int *panPermutation, int nSize)
    2038             : {
    2039         267 :     OGRErr eErr = OGRERR_NONE;
    2040         267 :     int *panCheck = static_cast<int *>(CPLCalloc(nSize, sizeof(int)));
    2041        1263 :     for (int i = 0; i < nSize; ++i)
    2042             :     {
    2043        1003 :         if (panPermutation[i] < 0 || panPermutation[i] >= nSize)
    2044             :         {
    2045           3 :             CPLError(CE_Failure, CPLE_IllegalArg, "Bad value for element %d",
    2046             :                      i);
    2047           3 :             eErr = OGRERR_FAILURE;
    2048           3 :             break;
    2049             :         }
    2050        1000 :         if (panCheck[panPermutation[i]] != 0)
    2051             :         {
    2052           4 :             CPLError(CE_Failure, CPLE_IllegalArg,
    2053             :                      "Array is not a permutation of [0,%d]", nSize - 1);
    2054           4 :             eErr = OGRERR_FAILURE;
    2055           4 :             break;
    2056             :         }
    2057         996 :         panCheck[panPermutation[i]] = 1;
    2058             :     }
    2059         267 :     CPLFree(panCheck);
    2060         267 :     return eErr;
    2061             : }
    2062             : 
    2063     1344060 : OGRErr OGRReadWKBGeometryType(const unsigned char *pabyData,
    2064             :                               OGRwkbVariant eWkbVariant,
    2065             :                               OGRwkbGeometryType *peGeometryType)
    2066             : {
    2067     1344060 :     if (!peGeometryType)
    2068           0 :         return OGRERR_FAILURE;
    2069             : 
    2070             :     /* -------------------------------------------------------------------- */
    2071             :     /*      Get the byte order byte.                                        */
    2072             :     /* -------------------------------------------------------------------- */
    2073     1344060 :     int nByteOrder = DB2_V72_FIX_BYTE_ORDER(*pabyData);
    2074     1344060 :     if (!(nByteOrder == wkbXDR || nByteOrder == wkbNDR))
    2075         123 :         return OGRERR_CORRUPT_DATA;
    2076     1343940 :     OGRwkbByteOrder eByteOrder = static_cast<OGRwkbByteOrder>(nByteOrder);
    2077             : 
    2078             :     /* -------------------------------------------------------------------- */
    2079             :     /*      Get the geometry type.                                          */
    2080             :     /* -------------------------------------------------------------------- */
    2081     1343940 :     bool bIsOldStyle3D = false;
    2082     1343940 :     bool bIsOldStyleMeasured = false;
    2083     1343940 :     int iRawType = 0;
    2084             : 
    2085     1343940 :     memcpy(&iRawType, pabyData + 1, 4);
    2086     1343940 :     if (OGR_SWAP(eByteOrder))
    2087             :     {
    2088        3111 :         CPL_SWAP32PTR(&iRawType);
    2089             :     }
    2090             : 
    2091             :     // Test for M bit in PostGIS WKB, see ogrgeometry.cpp:4956.
    2092     1343940 :     if (0x40000000 & iRawType)
    2093             :     {
    2094        2138 :         iRawType &= ~0x40000000;
    2095        2138 :         bIsOldStyleMeasured = true;
    2096             :     }
    2097             :     // Old-style OGC z-bit is flipped? Tests also Z bit in PostGIS WKB.
    2098     1343940 :     if (wkb25DBitInternalUse & iRawType)
    2099             :     {
    2100             :         // Clean off top 3 bytes.
    2101        7375 :         iRawType &= 0x000000FF;
    2102        7375 :         bIsOldStyle3D = true;
    2103             :     }
    2104             : 
    2105             :     // ISO SQL/MM Part3 draft -> Deprecated.
    2106             :     // See http://jtc1sc32.org/doc/N1101-1150/32N1107-WD13249-3--spatial.pdf
    2107     1343940 :     if (iRawType == 1000001)
    2108           0 :         iRawType = wkbCircularString;
    2109     1343940 :     else if (iRawType == 1000002)
    2110           0 :         iRawType = wkbCompoundCurve;
    2111     1343940 :     else if (iRawType == 1000003)
    2112           0 :         iRawType = wkbCurvePolygon;
    2113     1343940 :     else if (iRawType == 1000004)
    2114           0 :         iRawType = wkbMultiCurve;
    2115     1343940 :     else if (iRawType == 1000005)
    2116           0 :         iRawType = wkbMultiSurface;
    2117     1343940 :     else if (iRawType == 2000001)
    2118           0 :         iRawType = wkbPointZM;
    2119     1343940 :     else if (iRawType == 2000002)
    2120           0 :         iRawType = wkbLineStringZM;
    2121     1343940 :     else if (iRawType == 2000003)
    2122           0 :         iRawType = wkbCircularStringZM;
    2123     1343940 :     else if (iRawType == 2000004)
    2124           0 :         iRawType = wkbCompoundCurveZM;
    2125     1343940 :     else if (iRawType == 2000005)
    2126           0 :         iRawType = wkbPolygonZM;
    2127     1343940 :     else if (iRawType == 2000006)
    2128           0 :         iRawType = wkbCurvePolygonZM;
    2129     1343940 :     else if (iRawType == 2000007)
    2130           0 :         iRawType = wkbMultiPointZM;
    2131     1343940 :     else if (iRawType == 2000008)
    2132           0 :         iRawType = wkbMultiCurveZM;
    2133     1343940 :     else if (iRawType == 2000009)
    2134           0 :         iRawType = wkbMultiLineStringZM;
    2135     1343940 :     else if (iRawType == 2000010)
    2136           0 :         iRawType = wkbMultiSurfaceZM;
    2137     1343940 :     else if (iRawType == 2000011)
    2138           0 :         iRawType = wkbMultiPolygonZM;
    2139     1343940 :     else if (iRawType == 2000012)
    2140           0 :         iRawType = wkbGeometryCollectionZM;
    2141     1343940 :     else if (iRawType == 3000001)
    2142           0 :         iRawType = wkbPoint25D;
    2143     1343940 :     else if (iRawType == 3000002)
    2144           0 :         iRawType = wkbLineString25D;
    2145     1343940 :     else if (iRawType == 3000003)
    2146           0 :         iRawType = wkbCircularStringZ;
    2147     1343940 :     else if (iRawType == 3000004)
    2148           0 :         iRawType = wkbCompoundCurveZ;
    2149     1343940 :     else if (iRawType == 3000005)
    2150           0 :         iRawType = wkbPolygon25D;
    2151     1343940 :     else if (iRawType == 3000006)
    2152           0 :         iRawType = wkbCurvePolygonZ;
    2153     1343940 :     else if (iRawType == 3000007)
    2154           0 :         iRawType = wkbMultiPoint25D;
    2155     1343940 :     else if (iRawType == 3000008)
    2156           0 :         iRawType = wkbMultiCurveZ;
    2157     1343940 :     else if (iRawType == 3000009)
    2158           0 :         iRawType = wkbMultiLineString25D;
    2159     1343940 :     else if (iRawType == 3000010)
    2160           0 :         iRawType = wkbMultiSurfaceZ;
    2161     1343940 :     else if (iRawType == 3000011)
    2162           0 :         iRawType = wkbMultiPolygon25D;
    2163     1343940 :     else if (iRawType == 3000012)
    2164           0 :         iRawType = wkbGeometryCollection25D;
    2165     1343940 :     else if (iRawType == 4000001)
    2166           0 :         iRawType = wkbPointM;
    2167     1343940 :     else if (iRawType == 4000002)
    2168           0 :         iRawType = wkbLineStringM;
    2169     1343940 :     else if (iRawType == 4000003)
    2170           0 :         iRawType = wkbCircularStringM;
    2171     1343940 :     else if (iRawType == 4000004)
    2172           0 :         iRawType = wkbCompoundCurveM;
    2173     1343940 :     else if (iRawType == 4000005)
    2174           0 :         iRawType = wkbPolygonM;
    2175     1343940 :     else if (iRawType == 4000006)
    2176           0 :         iRawType = wkbCurvePolygonM;
    2177     1343940 :     else if (iRawType == 4000007)
    2178           0 :         iRawType = wkbMultiPointM;
    2179     1343940 :     else if (iRawType == 4000008)
    2180           0 :         iRawType = wkbMultiCurveM;
    2181     1343940 :     else if (iRawType == 4000009)
    2182           0 :         iRawType = wkbMultiLineStringM;
    2183     1343940 :     else if (iRawType == 4000010)
    2184           0 :         iRawType = wkbMultiSurfaceM;
    2185     1343940 :     else if (iRawType == 4000011)
    2186           0 :         iRawType = wkbMultiPolygonM;
    2187     1343940 :     else if (iRawType == 4000012)
    2188           0 :         iRawType = wkbGeometryCollectionM;
    2189             : 
    2190             :     // Sometimes the Z flag is in the 2nd byte?
    2191     1343940 :     if (iRawType & (wkb25DBitInternalUse >> 16))
    2192             :     {
    2193             :         // Clean off top 3 bytes.
    2194         231 :         iRawType &= 0x000000FF;
    2195         231 :         bIsOldStyle3D = true;
    2196             :     }
    2197             : 
    2198     1343940 :     if (eWkbVariant == wkbVariantPostGIS1)
    2199             :     {
    2200           0 :         if (iRawType == POSTGIS15_CURVEPOLYGON)
    2201           0 :             iRawType = wkbCurvePolygon;
    2202           0 :         else if (iRawType == POSTGIS15_MULTICURVE)
    2203           0 :             iRawType = wkbMultiCurve;
    2204           0 :         else if (iRawType == POSTGIS15_MULTISURFACE)
    2205           0 :             iRawType = wkbMultiSurface;
    2206             :     }
    2207             : 
    2208             :     // ISO SQL/MM style types are between 1-17, 1001-1017, 2001-2017, and
    2209             :     // 3001-3017.
    2210     1343940 :     if (!((iRawType > 0 && iRawType <= 17) ||
    2211      155735 :           (iRawType > 1000 && iRawType <= 1017) ||
    2212      154106 :           (iRawType > 2000 && iRawType <= 2017) ||
    2213      152987 :           (iRawType > 3000 && iRawType <= 3017)))
    2214             :     {
    2215        1333 :         CPLError(CE_Failure, CPLE_NotSupported, "Unsupported WKB type %d",
    2216             :                  iRawType);
    2217        1333 :         return OGRERR_UNSUPPORTED_GEOMETRY_TYPE;
    2218             :     }
    2219             : 
    2220     1342600 :     if (bIsOldStyle3D)
    2221             :     {
    2222        7209 :         iRawType += 1000;
    2223             :     }
    2224     1342600 :     if (bIsOldStyleMeasured)
    2225             :     {
    2226        1943 :         iRawType += 2000;
    2227             :     }
    2228             : 
    2229             :     // Convert to OGRwkbGeometryType value.
    2230     1342600 :     if (iRawType >= 1001 && iRawType <= 1007)
    2231             :     {
    2232        7561 :         iRawType -= 1000;
    2233        7561 :         iRawType |= wkb25DBitInternalUse;
    2234             :     }
    2235             : 
    2236     1342600 :     *peGeometryType = static_cast<OGRwkbGeometryType>(iRawType);
    2237             : 
    2238     1342600 :     return OGRERR_NONE;
    2239             : }
    2240             : 
    2241             : /************************************************************************/
    2242             : /*                       OGRReadWKTGeometryType()                       */
    2243             : /************************************************************************/
    2244             : 
    2245         442 : OGRErr OGRReadWKTGeometryType(const char *pszWKT,
    2246             :                               OGRwkbGeometryType *peGeometryType)
    2247             : {
    2248         442 :     if (!peGeometryType)
    2249           0 :         return OGRERR_FAILURE;
    2250             : 
    2251         442 :     OGRwkbGeometryType eGeomType = wkbUnknown;
    2252         442 :     if (STARTS_WITH_CI(pszWKT, "POINT"))
    2253         117 :         eGeomType = wkbPoint;
    2254         325 :     else if (STARTS_WITH_CI(pszWKT, "LINESTRING"))
    2255          44 :         eGeomType = wkbLineString;
    2256         281 :     else if (STARTS_WITH_CI(pszWKT, "POLYGON"))
    2257          76 :         eGeomType = wkbPolygon;
    2258         205 :     else if (STARTS_WITH_CI(pszWKT, "MULTIPOINT"))
    2259          31 :         eGeomType = wkbMultiPoint;
    2260         174 :     else if (STARTS_WITH_CI(pszWKT, "MULTILINESTRING"))
    2261          35 :         eGeomType = wkbMultiLineString;
    2262         139 :     else if (STARTS_WITH_CI(pszWKT, "MULTIPOLYGON"))
    2263          71 :         eGeomType = wkbMultiPolygon;
    2264          68 :     else if (STARTS_WITH_CI(pszWKT, "GEOMETRYCOLLECTION"))
    2265          43 :         eGeomType = wkbGeometryCollection;
    2266          25 :     else if (STARTS_WITH_CI(pszWKT, "CIRCULARSTRING"))
    2267           1 :         eGeomType = wkbCircularString;
    2268          24 :     else if (STARTS_WITH_CI(pszWKT, "COMPOUNDCURVE"))
    2269           1 :         eGeomType = wkbCompoundCurve;
    2270          23 :     else if (STARTS_WITH_CI(pszWKT, "CURVEPOLYGON"))
    2271           1 :         eGeomType = wkbCurvePolygon;
    2272          22 :     else if (STARTS_WITH_CI(pszWKT, "MULTICURVE"))
    2273           1 :         eGeomType = wkbMultiCurve;
    2274          21 :     else if (STARTS_WITH_CI(pszWKT, "MULTISURFACE"))
    2275           1 :         eGeomType = wkbMultiSurface;
    2276          20 :     else if (STARTS_WITH_CI(pszWKT, "POLYHEDRALSURFACE"))
    2277           1 :         eGeomType = wkbPolyhedralSurface;
    2278          19 :     else if (STARTS_WITH_CI(pszWKT, "TIN"))
    2279           1 :         eGeomType = wkbTIN;
    2280             :     else
    2281          18 :         return OGRERR_UNSUPPORTED_GEOMETRY_TYPE;
    2282             : 
    2283         424 :     if (strstr(pszWKT, " ZM"))
    2284          34 :         eGeomType = OGR_GT_SetModifier(eGeomType, true, true);
    2285         390 :     else if (strstr(pszWKT, " Z"))
    2286          94 :         eGeomType = OGR_GT_SetModifier(eGeomType, true, false);
    2287         296 :     else if (strstr(pszWKT, " M"))
    2288          34 :         eGeomType = OGR_GT_SetModifier(eGeomType, false, true);
    2289             : 
    2290         424 :     *peGeometryType = eGeomType;
    2291             : 
    2292         424 :     return OGRERR_NONE;
    2293             : }
    2294             : 
    2295             : /************************************************************************/
    2296             : /*                           OGRFormatFloat()                           */
    2297             : /************************************************************************/
    2298             : 
    2299          66 : int OGRFormatFloat(char *pszBuffer, int nBufferLen, float fVal, int nPrecision,
    2300             :                    char chConversionSpecifier)
    2301             : {
    2302             :     // So to have identical cross platform representation.
    2303          66 :     if (std::isinf(fVal))
    2304           2 :         return CPLsnprintf(pszBuffer, nBufferLen, (fVal > 0) ? "inf" : "-inf");
    2305          64 :     if (std::isnan(fVal))
    2306           1 :         return CPLsnprintf(pszBuffer, nBufferLen, "nan");
    2307             : 
    2308          63 :     int nSize = 0;
    2309          63 :     char szFormatting[32] = {};
    2310          63 :     constexpr int MAX_SIGNIFICANT_DIGITS_FLOAT32 = 8;
    2311          63 :     const int nInitialSignificantFigures =
    2312          63 :         nPrecision >= 0 ? nPrecision : MAX_SIGNIFICANT_DIGITS_FLOAT32;
    2313             : 
    2314          63 :     CPLsnprintf(szFormatting, sizeof(szFormatting), "%%.%d%c",
    2315             :                 nInitialSignificantFigures, chConversionSpecifier);
    2316          63 :     nSize = CPLsnprintf(pszBuffer, nBufferLen, szFormatting,
    2317             :                         static_cast<double>(fVal));
    2318          63 :     const char *pszDot = strchr(pszBuffer, '.');
    2319             : 
    2320             :     // Try to avoid 0.34999999 or 0.15000001 rounding issues by
    2321             :     // decreasing a bit precision.
    2322          63 :     if (nInitialSignificantFigures >= 8 && pszDot != nullptr &&
    2323          52 :         (strstr(pszDot, "99999") != nullptr ||
    2324          42 :          strstr(pszDot, "00000") != nullptr))
    2325             :     {
    2326          24 :         const CPLString osOriBuffer(pszBuffer, nSize);
    2327             : 
    2328          12 :         bool bOK = false;
    2329          12 :         for (int i = 1; i <= 3; i++)
    2330             :         {
    2331          12 :             CPLsnprintf(szFormatting, sizeof(szFormatting), "%%.%d%c",
    2332             :                         nInitialSignificantFigures - i, chConversionSpecifier);
    2333          12 :             nSize = CPLsnprintf(pszBuffer, nBufferLen, szFormatting,
    2334             :                                 static_cast<double>(fVal));
    2335          12 :             pszDot = strchr(pszBuffer, '.');
    2336          12 :             if (pszDot != nullptr && strstr(pszDot, "99999") == nullptr &&
    2337          36 :                 strstr(pszDot, "00000") == nullptr &&
    2338          12 :                 static_cast<float>(CPLAtof(pszBuffer)) == fVal)
    2339             :             {
    2340          12 :                 bOK = true;
    2341          12 :                 break;
    2342             :             }
    2343             :         }
    2344          12 :         if (!bOK)
    2345             :         {
    2346           0 :             memcpy(pszBuffer, osOriBuffer.c_str(), osOriBuffer.size() + 1);
    2347           0 :             nSize = static_cast<int>(osOriBuffer.size());
    2348             :         }
    2349             :     }
    2350             : 
    2351          63 :     if (nSize + 2 < static_cast<int>(nBufferLen) &&
    2352          63 :         strchr(pszBuffer, '.') == nullptr && strchr(pszBuffer, 'e') == nullptr)
    2353             :     {
    2354          10 :         nSize += CPLsnprintf(pszBuffer + nSize, nBufferLen - nSize, ".0");
    2355             :     }
    2356             : 
    2357          63 :     return nSize;
    2358             : }
    2359             : 
    2360       23188 : int OGR_GET_MS(float fSec)
    2361             : {
    2362       23188 :     if (std::isnan(fSec))
    2363           0 :         return 0;
    2364       23188 :     if (fSec >= 999)
    2365           0 :         return 999;
    2366       23188 :     if (fSec <= 0)
    2367         846 :         return 0;
    2368       22342 :     const float fValue = (fSec - static_cast<int>(fSec)) * 1000 + 0.5f;
    2369       22342 :     return static_cast<int>(fValue);
    2370             : }
    2371             : 
    2372             : /************************************************************************/
    2373             : /*                       OGRDuplicateCharacter()                        */
    2374             : /************************************************************************/
    2375             : 
    2376           0 : std::string OGRDuplicateCharacter(const std::string &osStr, char ch)
    2377             : {
    2378           0 :     char aszReplacement[] = {ch, ch, 0};
    2379           0 :     return CPLString(osStr).replaceAll(ch, aszReplacement);
    2380             : }
    2381             : 
    2382             : /************************************************************************/
    2383             : /*                       OGRIsGeoJSONMediaType()                        */
    2384             : /************************************************************************/
    2385             : 
    2386             : /** Returns whether pszMediaType is "application/geo+json" or
    2387             :  * "application/vnd.geo+json"
    2388             :  */
    2389         122 : bool OGRIsGeoJSONMediaType(const char *pszMediaType)
    2390             : {
    2391         126 :     return EQUAL(pszMediaType, "application/geo+json") ||
    2392             :            // Below is deprecated
    2393         126 :            EQUAL(pszMediaType, "application/vnd.geo+json");
    2394             : }

Generated by: LCOV version 1.14