mirror of https://gitee.com/openkylin/gdal.git
2325 lines
82 KiB
C++
2325 lines
82 KiB
C++
/******************************************************************************
|
|
*
|
|
* Project: OpenGIS Simple Features Reference Implementation
|
|
* Purpose: Utility functions for OGR classes, including some related to
|
|
* parsing well known text format vectors.
|
|
* Author: Frank Warmerdam, warmerdam@pobox.com
|
|
*
|
|
******************************************************************************
|
|
* Copyright (c) 1999, Frank Warmerdam
|
|
* Copyright (c) 2008-2014, Even Rouault <even dot rouault at spatialys.com>
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the "Software"),
|
|
* to deal in the Software without restriction, including without limitation
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included
|
|
* in all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
* DEALINGS IN THE SOFTWARE.
|
|
****************************************************************************/
|
|
|
|
#include "cpl_port.h"
|
|
#include "ogr_geometry.h"
|
|
#include "ogr_p.h"
|
|
|
|
#include <cassert>
|
|
#include <cmath>
|
|
#include <cstddef>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <cctype>
|
|
#include <limits>
|
|
#include <sstream>
|
|
#include <iomanip>
|
|
|
|
#include "cpl_conv.h"
|
|
#include "cpl_error.h"
|
|
#include "cpl_string.h"
|
|
#include "cpl_time.h"
|
|
#include "cpl_vsi.h"
|
|
#include "gdal.h"
|
|
#include "ogr_core.h"
|
|
#include "ogr_geometry.h"
|
|
#include "ogrsf_frmts.h"
|
|
|
|
// Returns whether a double fits within an int.
|
|
// Unable to put this in cpl_port.h as include limit breaks grib.
|
|
inline bool CPLIsDoubleAnInt(double d)
|
|
{
|
|
// Write it this way to detect NaN
|
|
if (!(d >= std::numeric_limits<int>::min() &&
|
|
d <= std::numeric_limits<int>::max()))
|
|
{
|
|
return false;
|
|
}
|
|
return d == static_cast<double>(static_cast<int>(d));
|
|
}
|
|
|
|
namespace
|
|
{
|
|
|
|
// Remove trailing zeros except the last one.
|
|
std::string removeTrailingZeros(std::string s)
|
|
{
|
|
auto pos = s.find('.');
|
|
if (pos == std::string::npos)
|
|
return s;
|
|
|
|
// Remove zeros at the end. We know this won't be npos because we
|
|
// have a decimal point.
|
|
auto nzpos = s.find_last_not_of('0');
|
|
s = s.substr(0, nzpos + 1);
|
|
|
|
// Make sure there is one 0 after the decimal point.
|
|
if (s.back() == '.')
|
|
s += '0';
|
|
return s;
|
|
}
|
|
|
|
// Round a string representing a number by 1 in the least significant digit.
|
|
std::string roundup(std::string s)
|
|
{
|
|
// Remove a negative sign if it exists to make processing
|
|
// more straigtforward.
|
|
bool negative(false);
|
|
if (s[0] == '-')
|
|
{
|
|
negative = true;
|
|
s = s.substr(1);
|
|
}
|
|
|
|
// Go from the back to the front. If we increment a digit other than
|
|
// a '9', we're done. If we increment a '9', set it to a '0' and move
|
|
// to the next (more significant) digit. If we get to the front of the
|
|
// string, add a '1' to the front of the string.
|
|
for (int pos = static_cast<int>(s.size() - 1); pos >= 0; pos--)
|
|
{
|
|
if (s[pos] == '.')
|
|
continue;
|
|
s[pos]++;
|
|
|
|
// Incrementing past 9 gets you a colon in ASCII.
|
|
if (s[pos] != ':')
|
|
break;
|
|
else
|
|
s[pos] = '0';
|
|
if (pos == 0)
|
|
s = '1' + s;
|
|
}
|
|
if (negative)
|
|
s = '-' + s;
|
|
return s;
|
|
}
|
|
|
|
// This attempts to eliminate what is likely binary -> decimal representation
|
|
// error or the result of low-order rounding with calculations. The result
|
|
// may be more visually pleasing and takes up fewer places.
|
|
std::string intelliround(std::string &s)
|
|
{
|
|
// If there is no decimal point, just return.
|
|
auto dotPos = s.find(".");
|
|
if (dotPos == std::string::npos)
|
|
return s;
|
|
|
|
// Don't mess with exponential formatting.
|
|
if (s.find_first_of("eE") != std::string::npos)
|
|
return s;
|
|
size_t iDotPos = static_cast<size_t>(dotPos);
|
|
size_t nCountBeforeDot = iDotPos - 1;
|
|
if (s[0] == '-')
|
|
nCountBeforeDot--;
|
|
size_t i = s.size();
|
|
|
|
// If we don't have ten characters, don't do anything.
|
|
if (i <= 10)
|
|
return s;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Trim trailing 00000x's as they are likely roundoff error. */
|
|
/* -------------------------------------------------------------------- */
|
|
if (s[i - 2] == '0' && s[i - 3] == '0' && s[i - 4] == '0' &&
|
|
s[i - 5] == '0' && s[i - 6] == '0')
|
|
{
|
|
s.resize(s.size() - 1);
|
|
}
|
|
// I don't understand this case exactly. It's like saying if the
|
|
// value is large enough and there are sufficient sig digits before
|
|
// a bunch of zeros, remove the zeros and any digits at the end that
|
|
// may be nonzero. Perhaps if we can't exactly explain in words what
|
|
// we're doing here, we shouldn't do it? Perhaps it should
|
|
// be generalized?
|
|
// The value "12345.000000011" invokes this case, if anyone
|
|
// is interested.
|
|
else if (iDotPos < i - 8 && (nCountBeforeDot >= 4 || s[i - 3] == '0') &&
|
|
(nCountBeforeDot >= 5 || s[i - 4] == '0') &&
|
|
(nCountBeforeDot >= 6 || s[i - 5] == '0') &&
|
|
(nCountBeforeDot >= 7 || s[i - 6] == '0') &&
|
|
(nCountBeforeDot >= 8 || s[i - 7] == '0') && s[i - 8] == '0' &&
|
|
s[i - 9] == '0')
|
|
{
|
|
s.resize(s.size() - 8);
|
|
}
|
|
/* -------------------------------------------------------------------- */
|
|
/* Trim trailing 99999x's as they are likely roundoff error. */
|
|
/* -------------------------------------------------------------------- */
|
|
else if (s[i - 2] == '9' && s[i - 3] == '9' && s[i - 4] == '9' &&
|
|
s[i - 5] == '9' && s[i - 6] == '9')
|
|
{
|
|
s.resize(i - 6);
|
|
s = roundup(s);
|
|
}
|
|
else if (iDotPos < i - 9 && (nCountBeforeDot >= 4 || s[i - 3] == '9') &&
|
|
(nCountBeforeDot >= 5 || s[i - 4] == '9') &&
|
|
(nCountBeforeDot >= 6 || s[i - 5] == '9') &&
|
|
(nCountBeforeDot >= 7 || s[i - 6] == '9') &&
|
|
(nCountBeforeDot >= 8 || s[i - 7] == '9') && s[i - 8] == '9' &&
|
|
s[i - 9] == '9')
|
|
{
|
|
s.resize(i - 9);
|
|
s = roundup(s);
|
|
}
|
|
return s;
|
|
}
|
|
|
|
} // unnamed namespace
|
|
|
|
/************************************************************************/
|
|
/* OGRFormatDouble() */
|
|
/************************************************************************/
|
|
|
|
void OGRFormatDouble(char *pszBuffer, int nBufferLen, double dfVal,
|
|
char chDecimalSep, int nPrecision,
|
|
char chConversionSpecifier)
|
|
{
|
|
OGRWktOptions opts;
|
|
|
|
opts.precision = nPrecision;
|
|
opts.format = (chConversionSpecifier == 'g' || chConversionSpecifier == 'G')
|
|
? OGRWktFormat::G
|
|
: OGRWktFormat::F;
|
|
|
|
std::string s = OGRFormatDouble(dfVal, opts);
|
|
if (chDecimalSep != '\0' && chDecimalSep != '.')
|
|
{
|
|
auto pos = s.find('.');
|
|
if (pos != std::string::npos)
|
|
s.replace(pos, 1, std::string(1, chDecimalSep));
|
|
}
|
|
if (s.size() + 1 > static_cast<size_t>(nBufferLen))
|
|
{
|
|
CPLError(CE_Warning, CPLE_AppDefined,
|
|
"Truncated double value %s to "
|
|
"%s.",
|
|
s.data(), s.substr(0, nBufferLen - 1).data());
|
|
s.resize(nBufferLen - 1);
|
|
}
|
|
strcpy(pszBuffer, s.data());
|
|
}
|
|
|
|
/// Simplified OGRFormatDouble that can be made to adhere to provided
|
|
/// options.
|
|
std::string OGRFormatDouble(double val, const OGRWktOptions &opts)
|
|
{
|
|
// So to have identical cross platform representation.
|
|
if (std::isinf(val))
|
|
return (val > 0) ? "inf" : "-inf";
|
|
if (std::isnan(val))
|
|
return "nan";
|
|
|
|
std::ostringstream oss;
|
|
oss.imbue(std::locale::classic()); // Make sure we output decimal points.
|
|
bool l_round(opts.round);
|
|
if (opts.format == OGRWktFormat::F ||
|
|
(opts.format == OGRWktFormat::Default && fabs(val) < 1))
|
|
oss << std::fixed;
|
|
else
|
|
{
|
|
// Uppercase because OGC spec says capital 'E'.
|
|
oss << std::uppercase;
|
|
l_round = false;
|
|
}
|
|
oss << std::setprecision(opts.precision);
|
|
oss << val;
|
|
|
|
std::string sval = oss.str();
|
|
|
|
if (l_round)
|
|
sval = intelliround(sval);
|
|
return removeTrailingZeros(sval);
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* OGRMakeWktCoordinate() */
|
|
/* */
|
|
/* Format a well known text coordinate, trying to keep the */
|
|
/* ASCII representation compact, but accurate. These rules */
|
|
/* will have to tighten up in the future. */
|
|
/* */
|
|
/* Currently a new point should require no more than 64 */
|
|
/* characters barring the X or Y value being extremely large. */
|
|
/************************************************************************/
|
|
|
|
void OGRMakeWktCoordinate(char *pszTarget, double x, double y, double z,
|
|
int nDimension)
|
|
|
|
{
|
|
std::string wkt =
|
|
OGRMakeWktCoordinate(x, y, z, nDimension, OGRWktOptions());
|
|
memcpy(pszTarget, wkt.data(), wkt.size() + 1);
|
|
}
|
|
|
|
static bool isInteger(const std::string &s)
|
|
{
|
|
return s.find_first_not_of("0123456789") == std::string::npos;
|
|
}
|
|
|
|
std::string OGRMakeWktCoordinate(double x, double y, double z, int nDimension,
|
|
OGRWktOptions opts)
|
|
{
|
|
std::string wkt;
|
|
|
|
// Why do we do this? Seems especially strange since we're ADDING
|
|
// ".0" onto values in the case below. The "&&" here also seems strange.
|
|
if (opts.format == OGRWktFormat::Default && CPLIsDoubleAnInt(x) &&
|
|
CPLIsDoubleAnInt(y))
|
|
{
|
|
wkt = std::to_string(static_cast<int>(x));
|
|
wkt += ' ';
|
|
wkt += std::to_string(static_cast<int>(y));
|
|
}
|
|
else
|
|
{
|
|
wkt = OGRFormatDouble(x, opts);
|
|
// ABELL - Why do we do special formatting?
|
|
if (isInteger(wkt))
|
|
wkt += ".0";
|
|
wkt += ' ';
|
|
|
|
std::string yval = OGRFormatDouble(y, opts);
|
|
if (isInteger(yval))
|
|
yval += ".0";
|
|
wkt += yval;
|
|
}
|
|
|
|
// Why do we always format Z with type G.
|
|
if (nDimension == 3)
|
|
{
|
|
wkt += ' ';
|
|
if (opts.format == OGRWktFormat::Default && CPLIsDoubleAnInt(z))
|
|
wkt += std::to_string(static_cast<int>(z));
|
|
else
|
|
{
|
|
opts.format = OGRWktFormat::G;
|
|
wkt += OGRFormatDouble(z, opts);
|
|
}
|
|
}
|
|
return wkt;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* OGRMakeWktCoordinateM() */
|
|
/* */
|
|
/* Format a well known text coordinate, trying to keep the */
|
|
/* ASCII representation compact, but accurate. These rules */
|
|
/* will have to tighten up in the future. */
|
|
/* */
|
|
/* Currently a new point should require no more than 64 */
|
|
/* characters barring the X or Y value being extremely large. */
|
|
/************************************************************************/
|
|
|
|
void OGRMakeWktCoordinateM(char *pszTarget, double x, double y, double z,
|
|
double m, OGRBoolean hasZ, OGRBoolean hasM)
|
|
|
|
{
|
|
std::string wkt =
|
|
OGRMakeWktCoordinateM(x, y, z, m, hasZ, hasM, OGRWktOptions());
|
|
memcpy(pszTarget, wkt.data(), wkt.size() + 1);
|
|
}
|
|
|
|
std::string OGRMakeWktCoordinateM(double x, double y, double z, double m,
|
|
OGRBoolean hasZ, OGRBoolean hasM,
|
|
OGRWktOptions opts)
|
|
{
|
|
std::string wkt;
|
|
if (opts.format == OGRWktFormat::Default && CPLIsDoubleAnInt(x) &&
|
|
CPLIsDoubleAnInt(y))
|
|
{
|
|
wkt = std::to_string(static_cast<int>(x));
|
|
wkt += ' ';
|
|
wkt += std::to_string(static_cast<int>(y));
|
|
}
|
|
else
|
|
{
|
|
wkt = OGRFormatDouble(x, opts);
|
|
if (isInteger(wkt))
|
|
wkt += ".0";
|
|
wkt += ' ';
|
|
|
|
std::string yval = OGRFormatDouble(y, opts);
|
|
if (isInteger(yval))
|
|
yval += ".0";
|
|
wkt += yval;
|
|
}
|
|
|
|
// For some reason we always format Z and M as G-type
|
|
opts.format = OGRWktFormat::G;
|
|
if (hasZ)
|
|
{
|
|
/*if( opts.format == OGRWktFormat::Default && CPLIsDoubleAnInt(z) )
|
|
wkt += " " + std::to_string(static_cast<int>(z));
|
|
else*/
|
|
wkt += ' ';
|
|
wkt += OGRFormatDouble(z, opts);
|
|
}
|
|
|
|
if (hasM)
|
|
{
|
|
/*if( opts.format == OGRWktFormat::Default && CPLIsDoubleAnInt(m) )
|
|
wkt += " " + std::to_string(static_cast<int>(m));
|
|
else*/
|
|
wkt += ' ';
|
|
wkt += OGRFormatDouble(m, opts);
|
|
}
|
|
return wkt;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* OGRWktReadToken() */
|
|
/* */
|
|
/* Read one token or delimiter and put into token buffer. Pre */
|
|
/* and post white space is swallowed. */
|
|
/************************************************************************/
|
|
|
|
const char *OGRWktReadToken(const char *pszInput, char *pszToken)
|
|
|
|
{
|
|
if (pszInput == nullptr)
|
|
return nullptr;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Swallow pre-white space. */
|
|
/* -------------------------------------------------------------------- */
|
|
while (*pszInput == ' ' || *pszInput == '\t' || *pszInput == '\n' ||
|
|
*pszInput == '\r')
|
|
++pszInput;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* If this is a delimiter, read just one character. */
|
|
/* -------------------------------------------------------------------- */
|
|
if (*pszInput == '(' || *pszInput == ')' || *pszInput == ',')
|
|
{
|
|
pszToken[0] = *pszInput;
|
|
pszToken[1] = '\0';
|
|
|
|
++pszInput;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Or if it alpha numeric read till we reach non-alpha numeric */
|
|
/* text. */
|
|
/* -------------------------------------------------------------------- */
|
|
else
|
|
{
|
|
int iChar = 0;
|
|
|
|
while (iChar < OGR_WKT_TOKEN_MAX - 1 &&
|
|
((*pszInput >= 'a' && *pszInput <= 'z') ||
|
|
(*pszInput >= 'A' && *pszInput <= 'Z') ||
|
|
(*pszInput >= '0' && *pszInput <= '9') || *pszInput == '.' ||
|
|
*pszInput == '+' || *pszInput == '-'))
|
|
{
|
|
pszToken[iChar++] = *(pszInput++);
|
|
}
|
|
|
|
pszToken[iChar++] = '\0';
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Eat any trailing white space. */
|
|
/* -------------------------------------------------------------------- */
|
|
while (*pszInput == ' ' || *pszInput == '\t' || *pszInput == '\n' ||
|
|
*pszInput == '\r')
|
|
++pszInput;
|
|
|
|
return pszInput;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* OGRWktReadPoints() */
|
|
/* */
|
|
/* Read a point string. The point list must be contained in */
|
|
/* brackets and each point pair separated by a comma. */
|
|
/************************************************************************/
|
|
|
|
const char *OGRWktReadPoints(const char *pszInput, OGRRawPoint **ppaoPoints,
|
|
double **ppadfZ, int *pnMaxPoints,
|
|
int *pnPointsRead)
|
|
|
|
{
|
|
const char *pszOrigInput = pszInput;
|
|
*pnPointsRead = 0;
|
|
|
|
if (pszInput == nullptr)
|
|
return nullptr;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Eat any leading white space. */
|
|
/* -------------------------------------------------------------------- */
|
|
while (*pszInput == ' ' || *pszInput == '\t')
|
|
++pszInput;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* If this isn't an opening bracket then we have a problem. */
|
|
/* -------------------------------------------------------------------- */
|
|
if (*pszInput != '(')
|
|
{
|
|
CPLDebug("OGR", "Expected '(', but got %s in OGRWktReadPoints().",
|
|
pszInput);
|
|
|
|
return pszInput;
|
|
}
|
|
|
|
++pszInput;
|
|
|
|
/* ==================================================================== */
|
|
/* This loop reads a single point. It will continue till we */
|
|
/* run out of well formed points, or a closing bracket is */
|
|
/* encountered. */
|
|
/* ==================================================================== */
|
|
char szDelim[OGR_WKT_TOKEN_MAX] = {};
|
|
|
|
do
|
|
{
|
|
/* --------------------------------------------------------------------
|
|
*/
|
|
/* Read the X and Y values, verify they are numeric. */
|
|
/* --------------------------------------------------------------------
|
|
*/
|
|
char szTokenX[OGR_WKT_TOKEN_MAX] = {};
|
|
char szTokenY[OGR_WKT_TOKEN_MAX] = {};
|
|
|
|
pszInput = OGRWktReadToken(pszInput, szTokenX);
|
|
pszInput = OGRWktReadToken(pszInput, szTokenY);
|
|
|
|
if ((!isdigit(szTokenX[0]) && szTokenX[0] != '-' &&
|
|
szTokenX[0] != '.') ||
|
|
(!isdigit(szTokenY[0]) && szTokenY[0] != '-' && szTokenY[0] != '.'))
|
|
return nullptr;
|
|
|
|
/* --------------------------------------------------------------------
|
|
*/
|
|
/* Do we need to grow the point list to hold this point? */
|
|
/* --------------------------------------------------------------------
|
|
*/
|
|
if (*pnPointsRead == *pnMaxPoints)
|
|
{
|
|
*pnMaxPoints = *pnMaxPoints * 2 + 10;
|
|
*ppaoPoints = static_cast<OGRRawPoint *>(
|
|
CPLRealloc(*ppaoPoints, sizeof(OGRRawPoint) * *pnMaxPoints));
|
|
|
|
if (*ppadfZ != nullptr)
|
|
{
|
|
*ppadfZ = static_cast<double *>(
|
|
CPLRealloc(*ppadfZ, sizeof(double) * *pnMaxPoints));
|
|
}
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
*/
|
|
/* Add point to list. */
|
|
/* --------------------------------------------------------------------
|
|
*/
|
|
(*ppaoPoints)[*pnPointsRead].x = CPLAtof(szTokenX);
|
|
(*ppaoPoints)[*pnPointsRead].y = CPLAtof(szTokenY);
|
|
|
|
/* --------------------------------------------------------------------
|
|
*/
|
|
/* Do we have a Z coordinate? */
|
|
/* --------------------------------------------------------------------
|
|
*/
|
|
pszInput = OGRWktReadToken(pszInput, szDelim);
|
|
|
|
if (isdigit(szDelim[0]) || szDelim[0] == '-' || szDelim[0] == '.')
|
|
{
|
|
if (*ppadfZ == nullptr)
|
|
{
|
|
*ppadfZ = static_cast<double *>(
|
|
CPLCalloc(sizeof(double), *pnMaxPoints));
|
|
}
|
|
|
|
(*ppadfZ)[*pnPointsRead] = CPLAtof(szDelim);
|
|
|
|
pszInput = OGRWktReadToken(pszInput, szDelim);
|
|
}
|
|
else if (*ppadfZ != nullptr)
|
|
{
|
|
(*ppadfZ)[*pnPointsRead] = 0.0;
|
|
}
|
|
|
|
++(*pnPointsRead);
|
|
|
|
/* --------------------------------------------------------------------
|
|
*/
|
|
/* Do we have a M coordinate? */
|
|
/* If we do, just skip it. */
|
|
/* --------------------------------------------------------------------
|
|
*/
|
|
if (isdigit(szDelim[0]) || szDelim[0] == '-' || szDelim[0] == '.')
|
|
{
|
|
pszInput = OGRWktReadToken(pszInput, szDelim);
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
*/
|
|
/* Read next delimiter ... it should be a comma if there are */
|
|
/* more points. */
|
|
/* --------------------------------------------------------------------
|
|
*/
|
|
if (szDelim[0] != ')' && szDelim[0] != ',')
|
|
{
|
|
CPLDebug("OGR",
|
|
"Corrupt input in OGRWktReadPoints(). "
|
|
"Got `%s' when expecting `,' or `)', near `%s' in %s.",
|
|
szDelim, pszInput, pszOrigInput);
|
|
return nullptr;
|
|
}
|
|
} while (szDelim[0] == ',');
|
|
|
|
return pszInput;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* OGRWktReadPointsM() */
|
|
/* */
|
|
/* Read a point string. The point list must be contained in */
|
|
/* brackets and each point pair separated by a comma. */
|
|
/************************************************************************/
|
|
|
|
const char *OGRWktReadPointsM(const char *pszInput, OGRRawPoint **ppaoPoints,
|
|
double **ppadfZ, double **ppadfM, int *flags,
|
|
int *pnMaxPoints, int *pnPointsRead)
|
|
|
|
{
|
|
const char *pszOrigInput = pszInput;
|
|
const bool bNoFlags = !(*flags & OGRGeometry::OGR_G_3D) &&
|
|
!(*flags & OGRGeometry::OGR_G_MEASURED);
|
|
*pnPointsRead = 0;
|
|
|
|
if (pszInput == nullptr)
|
|
return nullptr;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Eat any leading white space. */
|
|
/* -------------------------------------------------------------------- */
|
|
while (*pszInput == ' ' || *pszInput == '\t')
|
|
++pszInput;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* If this isn't an opening bracket then we have a problem. */
|
|
/* -------------------------------------------------------------------- */
|
|
if (*pszInput != '(')
|
|
{
|
|
CPLDebug("OGR", "Expected '(', but got %s in OGRWktReadPointsM().",
|
|
pszInput);
|
|
|
|
return pszInput;
|
|
}
|
|
|
|
++pszInput;
|
|
|
|
/* ==================================================================== */
|
|
/* This loop reads a single point. It will continue till we */
|
|
/* run out of well formed points, or a closing bracket is */
|
|
/* encountered. */
|
|
/* ==================================================================== */
|
|
char szDelim[OGR_WKT_TOKEN_MAX] = {};
|
|
|
|
do
|
|
{
|
|
/* --------------------------------------------------------------------
|
|
*/
|
|
/* Read the X and Y values, verify they are numeric. */
|
|
/* --------------------------------------------------------------------
|
|
*/
|
|
char szTokenX[OGR_WKT_TOKEN_MAX] = {};
|
|
char szTokenY[OGR_WKT_TOKEN_MAX] = {};
|
|
|
|
pszInput = OGRWktReadToken(pszInput, szTokenX);
|
|
pszInput = OGRWktReadToken(pszInput, szTokenY);
|
|
|
|
if ((!isdigit(szTokenX[0]) && szTokenX[0] != '-' &&
|
|
szTokenX[0] != '.' && !EQUAL(szTokenX, "nan")) ||
|
|
(!isdigit(szTokenY[0]) && szTokenY[0] != '-' &&
|
|
szTokenY[0] != '.' && !EQUAL(szTokenY, "nan")))
|
|
return nullptr;
|
|
|
|
/* --------------------------------------------------------------------
|
|
*/
|
|
/* Do we need to grow the point list to hold this point? */
|
|
/* --------------------------------------------------------------------
|
|
*/
|
|
if (*pnPointsRead == *pnMaxPoints)
|
|
{
|
|
*pnMaxPoints = *pnMaxPoints * 2 + 10;
|
|
*ppaoPoints = static_cast<OGRRawPoint *>(
|
|
CPLRealloc(*ppaoPoints, sizeof(OGRRawPoint) * *pnMaxPoints));
|
|
|
|
if (*ppadfZ != nullptr)
|
|
{
|
|
*ppadfZ = static_cast<double *>(
|
|
CPLRealloc(*ppadfZ, sizeof(double) * *pnMaxPoints));
|
|
}
|
|
|
|
if (*ppadfM != nullptr)
|
|
{
|
|
*ppadfM = static_cast<double *>(
|
|
CPLRealloc(*ppadfM, sizeof(double) * *pnMaxPoints));
|
|
}
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
*/
|
|
/* Add point to list. */
|
|
/* --------------------------------------------------------------------
|
|
*/
|
|
(*ppaoPoints)[*pnPointsRead].x = CPLAtof(szTokenX);
|
|
(*ppaoPoints)[*pnPointsRead].y = CPLAtof(szTokenY);
|
|
|
|
/* --------------------------------------------------------------------
|
|
*/
|
|
/* Read the next token. */
|
|
/* --------------------------------------------------------------------
|
|
*/
|
|
pszInput = OGRWktReadToken(pszInput, szDelim);
|
|
|
|
/* --------------------------------------------------------------------
|
|
*/
|
|
/* If there are unexpectedly more coordinates, they are Z. */
|
|
/* --------------------------------------------------------------------
|
|
*/
|
|
|
|
if (!(*flags & OGRGeometry::OGR_G_3D) &&
|
|
!(*flags & OGRGeometry::OGR_G_MEASURED) &&
|
|
(isdigit(szDelim[0]) || szDelim[0] == '-' || szDelim[0] == '.' ||
|
|
EQUAL(szDelim, "nan")))
|
|
{
|
|
*flags |= OGRGeometry::OGR_G_3D;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
*/
|
|
/* Get Z if flag says so. */
|
|
/* Zero out possible remains from earlier strings. */
|
|
/* --------------------------------------------------------------------
|
|
*/
|
|
|
|
if (*flags & OGRGeometry::OGR_G_3D)
|
|
{
|
|
if (*ppadfZ == nullptr)
|
|
{
|
|
*ppadfZ = static_cast<double *>(
|
|
CPLCalloc(sizeof(double), *pnMaxPoints));
|
|
}
|
|
if (isdigit(szDelim[0]) || szDelim[0] == '-' || szDelim[0] == '.' ||
|
|
EQUAL(szDelim, "nan"))
|
|
{
|
|
(*ppadfZ)[*pnPointsRead] = CPLAtof(szDelim);
|
|
pszInput = OGRWktReadToken(pszInput, szDelim);
|
|
}
|
|
else
|
|
{
|
|
(*ppadfZ)[*pnPointsRead] = 0.0;
|
|
}
|
|
}
|
|
else if (*ppadfZ != nullptr)
|
|
{
|
|
(*ppadfZ)[*pnPointsRead] = 0.0;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
*/
|
|
/* If there are unexpectedly even more coordinates, */
|
|
/* they are discarded unless there were no flags originally. */
|
|
/* This is for backwards compatibility. Should this be an error? */
|
|
/* --------------------------------------------------------------------
|
|
*/
|
|
|
|
if (!(*flags & OGRGeometry::OGR_G_MEASURED) &&
|
|
(isdigit(szDelim[0]) || szDelim[0] == '-' || szDelim[0] == '.' ||
|
|
EQUAL(szDelim, "nan")))
|
|
{
|
|
if (bNoFlags)
|
|
{
|
|
*flags |= OGRGeometry::OGR_G_MEASURED;
|
|
}
|
|
else
|
|
{
|
|
pszInput = OGRWktReadToken(pszInput, szDelim);
|
|
}
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
*/
|
|
/* Get M if flag says so. */
|
|
/* Zero out possible remains from earlier strings. */
|
|
/* --------------------------------------------------------------------
|
|
*/
|
|
|
|
if (*flags & OGRGeometry::OGR_G_MEASURED)
|
|
{
|
|
if (*ppadfM == nullptr)
|
|
{
|
|
*ppadfM = static_cast<double *>(
|
|
CPLCalloc(sizeof(double), *pnMaxPoints));
|
|
}
|
|
if (isdigit(szDelim[0]) || szDelim[0] == '-' || szDelim[0] == '.' ||
|
|
EQUAL(szDelim, "nan"))
|
|
{
|
|
(*ppadfM)[*pnPointsRead] = CPLAtof(szDelim);
|
|
pszInput = OGRWktReadToken(pszInput, szDelim);
|
|
}
|
|
else
|
|
{
|
|
(*ppadfM)[*pnPointsRead] = 0.0;
|
|
}
|
|
}
|
|
else if (*ppadfM != nullptr)
|
|
{
|
|
(*ppadfM)[*pnPointsRead] = 0.0;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
*/
|
|
/* If there are still more coordinates and we do not have Z */
|
|
/* then we have a case of flags == M and four coordinates. */
|
|
/* This is allowed in BNF. */
|
|
/* --------------------------------------------------------------------
|
|
*/
|
|
|
|
if (!(*flags & OGRGeometry::OGR_G_3D) &&
|
|
(isdigit(szDelim[0]) || szDelim[0] == '-' || szDelim[0] == '.' ||
|
|
EQUAL(szDelim, "nan")))
|
|
{
|
|
*flags |= OGRGeometry::OGR_G_3D;
|
|
if (*ppadfZ == nullptr)
|
|
{
|
|
*ppadfZ = static_cast<double *>(
|
|
CPLCalloc(sizeof(double), *pnMaxPoints));
|
|
}
|
|
(*ppadfZ)[*pnPointsRead] = (*ppadfM)[*pnPointsRead];
|
|
(*ppadfM)[*pnPointsRead] = CPLAtof(szDelim);
|
|
pszInput = OGRWktReadToken(pszInput, szDelim);
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
*/
|
|
/* Increase points index. */
|
|
/* --------------------------------------------------------------------
|
|
*/
|
|
++(*pnPointsRead);
|
|
|
|
/* --------------------------------------------------------------------
|
|
*/
|
|
/* The next delimiter should be a comma or an ending bracket. */
|
|
/* --------------------------------------------------------------------
|
|
*/
|
|
if (szDelim[0] != ')' && szDelim[0] != ',')
|
|
{
|
|
CPLDebug("OGR",
|
|
"Corrupt input in OGRWktReadPointsM() "
|
|
"Got `%s' when expecting `,' or `)', near `%s' in %s.",
|
|
szDelim, pszInput, pszOrigInput);
|
|
return nullptr;
|
|
}
|
|
} while (szDelim[0] == ',');
|
|
|
|
return pszInput;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* OGRMalloc() */
|
|
/* */
|
|
/* Cover for CPLMalloc() */
|
|
/************************************************************************/
|
|
|
|
void *OGRMalloc(size_t size)
|
|
|
|
{
|
|
return CPLMalloc(size);
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* OGRCalloc() */
|
|
/* */
|
|
/* Cover for CPLCalloc() */
|
|
/************************************************************************/
|
|
|
|
void *OGRCalloc(size_t count, size_t size)
|
|
|
|
{
|
|
return CPLCalloc(count, size);
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* OGRRealloc() */
|
|
/* */
|
|
/* Cover for CPLRealloc() */
|
|
/************************************************************************/
|
|
|
|
void *OGRRealloc(void *pOld, size_t size)
|
|
|
|
{
|
|
return CPLRealloc(pOld, size);
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* OGRFree() */
|
|
/* */
|
|
/* Cover for CPLFree(). */
|
|
/************************************************************************/
|
|
|
|
void OGRFree(void *pMemory)
|
|
|
|
{
|
|
CPLFree(pMemory);
|
|
}
|
|
|
|
/**
|
|
* \fn OGRGeneralCmdLineProcessor(int, char***, int)
|
|
* General utility option processing.
|
|
*
|
|
* This function is intended to provide a variety of generic commandline
|
|
* options for all OGR commandline utilities. It takes care of the following
|
|
* commandline options:
|
|
*
|
|
* --version: report version of GDAL in use.
|
|
* --license: report GDAL license info.
|
|
* --format [format]: report details of one format driver.
|
|
* --formats: report all format drivers configured.
|
|
* --optfile filename: expand an option file into the argument list.
|
|
* --config key value: set system configuration option.
|
|
* --debug [on/off/value]: set debug level.
|
|
* --pause: Pause for user input (allows time to attach debugger)
|
|
* --locale [locale]: Install a locale using setlocale() (debugging)
|
|
* --help-general: report detailed help on general options.
|
|
*
|
|
* The argument array is replaced "in place" and should be freed with
|
|
* CSLDestroy() when no longer needed. The typical usage looks something
|
|
* like the following. Note that the formats should be registered so that
|
|
* the --formats option will work properly.
|
|
*
|
|
* int main( int argc, char ** argv )
|
|
* {
|
|
* OGRRegisterAll();
|
|
*
|
|
* argc = OGRGeneralCmdLineProcessor( argc, &argv, 0 );
|
|
* if( argc < 1 )
|
|
* exit( -argc );
|
|
*
|
|
* @param nArgc number of values in the argument list.
|
|
* @param ppapszArgv pointer to the argument list array (will be updated in
|
|
* place).
|
|
* @param nOptions unused.
|
|
*
|
|
* @return updated nArgc argument count. Return of 0 requests terminate
|
|
* without error, return of -1 requests exit with error code.
|
|
*/
|
|
|
|
int OGRGeneralCmdLineProcessor(int nArgc, char ***ppapszArgv,
|
|
CPL_UNUSED int nOptions)
|
|
|
|
{
|
|
return GDALGeneralCmdLineProcessor(nArgc, ppapszArgv, GDAL_OF_VECTOR);
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* OGRTimezoneToTZFlag() */
|
|
/************************************************************************/
|
|
|
|
/** \brief Converts a text timezone into OGR TZFlag integer representation.
|
|
*
|
|
* @param pszTZ "UTC", "Etc/UTC", or "(+/-)[0-9][0-9](:?)[0-9][0-9]"
|
|
* @param bEmitErrorIfUnhandledFormat Whether to emit an error if pszTZ is
|
|
* a non-empty string with unrecognized
|
|
* format.
|
|
*/
|
|
int OGRTimezoneToTZFlag(const char *pszTZ, bool bEmitErrorIfUnhandledFormat)
|
|
{
|
|
int nTZFlag = OGR_TZFLAG_UNKNOWN;
|
|
const size_t nTZLen = strlen(pszTZ);
|
|
if (strcmp(pszTZ, "UTC") == 0 || strcmp(pszTZ, "Etc/UTC") == 0)
|
|
{
|
|
nTZFlag = OGR_TZFLAG_UTC;
|
|
}
|
|
else if ((pszTZ[0] == '+' || pszTZ[0] == '-') &&
|
|
((nTZLen == 6 && pszTZ[3] == ':') ||
|
|
(nTZLen == 5 && pszTZ[3] >= '0' && pszTZ[3] <= '9')))
|
|
{
|
|
int nTZHour = atoi(pszTZ + 1);
|
|
int nTZMin = atoi(pszTZ + (nTZLen == 6 ? 4 : 3));
|
|
if (nTZHour >= 0 && nTZHour <= 14 && nTZMin >= 0 && nTZMin < 60 &&
|
|
(nTZMin % 15) == 0)
|
|
{
|
|
nTZFlag = (nTZHour * 4) + (nTZMin / 15);
|
|
if (pszTZ[0] == '+')
|
|
{
|
|
nTZFlag = OGR_TZFLAG_UTC + nTZFlag;
|
|
}
|
|
else
|
|
{
|
|
nTZFlag = OGR_TZFLAG_UTC - nTZFlag;
|
|
}
|
|
}
|
|
}
|
|
else if (pszTZ[0] != 0 && bEmitErrorIfUnhandledFormat)
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined, "Unrecognized timezone: '%s'",
|
|
pszTZ);
|
|
}
|
|
return nTZFlag;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* OGRTZFlagToTimezone() */
|
|
/************************************************************************/
|
|
|
|
/** \brief Converts a OGR TZFlag integer representation into a string
|
|
*
|
|
* @param nTZFlag OGR TZFlag integer (only ones with
|
|
* value > OGR_TZFLAG_MIXED_TZ will yield a non-empty output)
|
|
* @param pszUTCRepresentation String to return if nTZFlag == OGR_TZFLAG_UTC.
|
|
* Typically "UTC", "Z", or "+00:00"
|
|
*/
|
|
std::string OGRTZFlagToTimezone(int nTZFlag, const char *pszUTCRepresentation)
|
|
{
|
|
if (nTZFlag == OGR_TZFLAG_UTC)
|
|
{
|
|
return pszUTCRepresentation;
|
|
}
|
|
else if (nTZFlag > OGR_TZFLAG_MIXED_TZ)
|
|
{
|
|
char chSign;
|
|
const int nOffset = (nTZFlag - OGR_TZFLAG_UTC) * 15;
|
|
int nHours = static_cast<int>(nOffset / 60); // Round towards zero.
|
|
const int nMinutes = std::abs(nOffset - nHours * 60);
|
|
|
|
if (nOffset < 0)
|
|
{
|
|
chSign = '-';
|
|
nHours = std::abs(nHours);
|
|
}
|
|
else
|
|
{
|
|
chSign = '+';
|
|
}
|
|
return CPLSPrintf("%c%02d:%02d", chSign, nHours, nMinutes);
|
|
}
|
|
else
|
|
{
|
|
return std::string();
|
|
}
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* OGRParseDate() */
|
|
/* */
|
|
/* Parse a variety of text date formats into an OGRField. */
|
|
/************************************************************************/
|
|
|
|
/**
|
|
* Parse date string.
|
|
*
|
|
* This function attempts to parse a date string in a variety of formats
|
|
* into the OGRField.Date format suitable for use with OGR. Generally
|
|
* speaking this function is expecting values like:
|
|
*
|
|
* YYYY-MM-DD HH:MM:SS(.sss)?+nn
|
|
* or YYYY-MM-DDTHH:MM:SS(.sss)?Z (ISO 8601 format)
|
|
* or YYYY-MM-DDZ
|
|
* or THH:MM(:SS(.sss)?)?Z? (ISO 8601 extended format)
|
|
* or THHMM(SS(.sss)?)?Z? (ISO 8601 basic format)
|
|
*
|
|
* The seconds may also have a decimal portion (parsed as milliseconds). And
|
|
* just dates (YYYY-MM-DD) or just times (HH:MM:SS[.sss]) are also supported.
|
|
* The date may also be in YYYY/MM/DD format. If the year is less than 100
|
|
* and greater than 30 a "1900" century value will be set. If it is less than
|
|
* 30 and greater than -1 then a "2000" century value will be set. In
|
|
* the future this function may be generalized, and additional control
|
|
* provided through nOptions, but an nOptions value of "0" should always do
|
|
* a reasonable default form of processing.
|
|
*
|
|
* The value of psField will be indeterminate if the function fails (returns
|
|
* FALSE).
|
|
*
|
|
* @param pszInput the input date string.
|
|
* @param psField the OGRField that will be updated with the parsed result.
|
|
* @param nOptions parsing options. 0 or OGRPARSEDATE_OPTION_LAX
|
|
*
|
|
* @return TRUE if apparently successful or FALSE on failure.
|
|
*/
|
|
|
|
int OGRParseDate(const char *pszInput, OGRField *psField, int nOptions)
|
|
{
|
|
psField->Date.Year = 0;
|
|
psField->Date.Month = 0;
|
|
psField->Date.Day = 0;
|
|
psField->Date.Hour = 0;
|
|
psField->Date.Minute = 0;
|
|
psField->Date.Second = 0;
|
|
psField->Date.TZFlag = 0;
|
|
psField->Date.Reserved = 0;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Do we have a date? */
|
|
/* -------------------------------------------------------------------- */
|
|
while (*pszInput == ' ')
|
|
++pszInput;
|
|
|
|
bool bGotSomething = false;
|
|
bool bTFound = false;
|
|
if (strchr(pszInput, '-') || strchr(pszInput, '/'))
|
|
{
|
|
if (!(*pszInput == '-' || *pszInput == '+' ||
|
|
(*pszInput >= '0' && *pszInput <= '9')))
|
|
return FALSE;
|
|
int nYear = atoi(pszInput);
|
|
if (nYear > std::numeric_limits<GInt16>::max() ||
|
|
nYear < std::numeric_limits<GInt16>::min())
|
|
{
|
|
CPLError(CE_Failure, CPLE_NotSupported,
|
|
"Years < %d or > %d are not supported",
|
|
std::numeric_limits<GInt16>::min(),
|
|
std::numeric_limits<GInt16>::max());
|
|
return FALSE;
|
|
}
|
|
psField->Date.Year = static_cast<GInt16>(nYear);
|
|
if ((pszInput[1] == '-' || pszInput[1] == '/') ||
|
|
(pszInput[1] != '\0' && (pszInput[2] == '-' || pszInput[2] == '/')))
|
|
{
|
|
if (psField->Date.Year < 100 && psField->Date.Year >= 30)
|
|
psField->Date.Year += 1900;
|
|
else if (psField->Date.Year < 30 && psField->Date.Year >= 0)
|
|
psField->Date.Year += 2000;
|
|
}
|
|
|
|
if (*pszInput == '-')
|
|
++pszInput;
|
|
while (*pszInput >= '0' && *pszInput <= '9')
|
|
++pszInput;
|
|
if (*pszInput != '-' && *pszInput != '/')
|
|
return FALSE;
|
|
else
|
|
++pszInput;
|
|
|
|
if (!(*pszInput >= '0' && *pszInput <= '9'))
|
|
return FALSE;
|
|
if (!(pszInput[1] >= '0' && pszInput[1] <= '9'))
|
|
{
|
|
if ((nOptions & OGRPARSEDATE_OPTION_LAX) == 0)
|
|
return FALSE;
|
|
const int nMonth = (pszInput[0] - '0');
|
|
if (nMonth == 0)
|
|
return FALSE;
|
|
psField->Date.Month = static_cast<GByte>(nMonth);
|
|
++pszInput;
|
|
}
|
|
else
|
|
{
|
|
const int nMonth = (pszInput[0] - '0') * 10 + (pszInput[1] - '0');
|
|
if (nMonth == 0 || nMonth > 12)
|
|
return FALSE;
|
|
psField->Date.Month = static_cast<GByte>(nMonth);
|
|
|
|
pszInput += 2;
|
|
}
|
|
if (*pszInput != '-' && *pszInput != '/')
|
|
return FALSE;
|
|
else
|
|
++pszInput;
|
|
|
|
if (!(*pszInput >= '0' && *pszInput <= '9'))
|
|
return FALSE;
|
|
if (!(pszInput[1] >= '0' && pszInput[1] <= '9'))
|
|
{
|
|
if ((nOptions & OGRPARSEDATE_OPTION_LAX) == 0)
|
|
return FALSE;
|
|
const int nDay = (pszInput[0] - '0');
|
|
if (nDay == 0)
|
|
return FALSE;
|
|
psField->Date.Day = static_cast<GByte>(nDay);
|
|
++pszInput;
|
|
}
|
|
else
|
|
{
|
|
const int nDay = (pszInput[0] - '0') * 10 + (pszInput[1] - '0');
|
|
if (nDay == 0 || nDay > 31)
|
|
return FALSE;
|
|
psField->Date.Day = static_cast<GByte>(nDay);
|
|
|
|
pszInput += 2;
|
|
}
|
|
if (*pszInput == '\0')
|
|
return TRUE;
|
|
|
|
bGotSomething = true;
|
|
|
|
// If ISO 8601 format.
|
|
if (*pszInput == 'T')
|
|
{
|
|
bTFound = true;
|
|
++pszInput;
|
|
}
|
|
else if (*pszInput == 'Z')
|
|
return TRUE;
|
|
else if (*pszInput != ' ')
|
|
return FALSE;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Do we have a time? */
|
|
/* -------------------------------------------------------------------- */
|
|
while (*pszInput == ' ')
|
|
++pszInput;
|
|
if (*pszInput == 'T')
|
|
{
|
|
bTFound = true;
|
|
++pszInput;
|
|
}
|
|
|
|
if (bTFound || strchr(pszInput, ':'))
|
|
{
|
|
if (!(*pszInput >= '0' && *pszInput <= '9'))
|
|
return FALSE;
|
|
if (!(pszInput[1] >= '0' && pszInput[1] <= '9'))
|
|
{
|
|
if ((nOptions & OGRPARSEDATE_OPTION_LAX) == 0)
|
|
return FALSE;
|
|
|
|
if (!((bTFound || pszInput[1] == ':')))
|
|
return FALSE;
|
|
const int nHour = (pszInput[0] - '0');
|
|
psField->Date.Hour = static_cast<GByte>(nHour);
|
|
|
|
pszInput++;
|
|
}
|
|
else
|
|
{
|
|
if (!((bTFound || pszInput[2] == ':')))
|
|
return FALSE;
|
|
const int nHour = (pszInput[0] - '0') * 10 + (pszInput[1] - '0');
|
|
if (nHour > 23)
|
|
return FALSE;
|
|
psField->Date.Hour = static_cast<GByte>(nHour);
|
|
|
|
pszInput += 2;
|
|
}
|
|
if (*pszInput == ':')
|
|
++pszInput;
|
|
|
|
if (!(*pszInput >= '0' && *pszInput <= '9'))
|
|
return FALSE;
|
|
if (!(pszInput[1] >= '0' && pszInput[1] <= '9'))
|
|
{
|
|
if ((nOptions & OGRPARSEDATE_OPTION_LAX) == 0)
|
|
return FALSE;
|
|
|
|
const int nMinute = (pszInput[0] - '0');
|
|
psField->Date.Minute = static_cast<GByte>(nMinute);
|
|
|
|
pszInput++;
|
|
}
|
|
else
|
|
{
|
|
const int nMinute = (pszInput[0] - '0') * 10 + (pszInput[1] - '0');
|
|
if (nMinute > 59)
|
|
return FALSE;
|
|
psField->Date.Minute = static_cast<GByte>(nMinute);
|
|
|
|
pszInput += 2;
|
|
}
|
|
|
|
if ((bTFound && *pszInput >= '0' && *pszInput <= '9') ||
|
|
*pszInput == ':')
|
|
{
|
|
if (*pszInput == ':')
|
|
++pszInput;
|
|
|
|
if (!(*pszInput >= '0' && *pszInput <= '9' &&
|
|
(((nOptions & OGRPARSEDATE_OPTION_LAX) != 0) ||
|
|
(pszInput[1] >= '0' && pszInput[1] <= '9'))))
|
|
return FALSE;
|
|
const double dfSeconds = CPLAtof(pszInput);
|
|
// We accept second=60 for leap seconds
|
|
if (dfSeconds > 60.0)
|
|
return FALSE;
|
|
psField->Date.Second = static_cast<float>(dfSeconds);
|
|
|
|
pszInput += 2;
|
|
if (*pszInput == '.')
|
|
{
|
|
++pszInput;
|
|
while (*pszInput >= '0' && *pszInput <= '9')
|
|
{
|
|
++pszInput;
|
|
}
|
|
}
|
|
|
|
// If ISO 8601 format.
|
|
if (*pszInput == 'Z')
|
|
{
|
|
psField->Date.TZFlag = 100;
|
|
}
|
|
}
|
|
|
|
bGotSomething = true;
|
|
}
|
|
else if (bGotSomething && *pszInput != '\0')
|
|
return FALSE;
|
|
|
|
// No date or time!
|
|
if (!bGotSomething)
|
|
return FALSE;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Do we have a timezone? */
|
|
/* -------------------------------------------------------------------- */
|
|
while (*pszInput == ' ')
|
|
++pszInput;
|
|
|
|
if (*pszInput == '-' || *pszInput == '+')
|
|
{
|
|
// +HH integral offset
|
|
if (strlen(pszInput) <= 3)
|
|
{
|
|
psField->Date.TZFlag = static_cast<GByte>(100 + atoi(pszInput) * 4);
|
|
}
|
|
else if (pszInput[3] == ':' // +HH:MM offset
|
|
&& atoi(pszInput + 4) % 15 == 0)
|
|
{
|
|
psField->Date.TZFlag = static_cast<GByte>(
|
|
100 + atoi(pszInput + 1) * 4 + (atoi(pszInput + 4) / 15));
|
|
|
|
if (pszInput[0] == '-')
|
|
psField->Date.TZFlag = -1 * (psField->Date.TZFlag - 100) + 100;
|
|
}
|
|
else if (isdigit(pszInput[3]) && isdigit(pszInput[4]) // +HHMM offset
|
|
&& atoi(pszInput + 3) % 15 == 0)
|
|
{
|
|
psField->Date.TZFlag = static_cast<GByte>(
|
|
100 + static_cast<GByte>(CPLScanLong(pszInput + 1, 2)) * 4 +
|
|
(atoi(pszInput + 3) / 15));
|
|
|
|
if (pszInput[0] == '-')
|
|
psField->Date.TZFlag = -1 * (psField->Date.TZFlag - 100) + 100;
|
|
}
|
|
else if (isdigit(pszInput[3]) && pszInput[4] == '\0' // +HMM offset
|
|
&& atoi(pszInput + 2) % 15 == 0)
|
|
{
|
|
psField->Date.TZFlag = static_cast<GByte>(
|
|
100 + static_cast<GByte>(CPLScanLong(pszInput + 1, 1)) * 4 +
|
|
(atoi(pszInput + 2) / 15));
|
|
|
|
if (pszInput[0] == '-')
|
|
psField->Date.TZFlag = -1 * (psField->Date.TZFlag - 100) + 100;
|
|
}
|
|
// otherwise ignore any timezone info.
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* OGRParseDateTimeYYYYMMDDTHHMMZ() */
|
|
/************************************************************************/
|
|
|
|
bool OGRParseDateTimeYYYYMMDDTHHMMZ(const char *pszInput, size_t nLen,
|
|
OGRField *psField)
|
|
{
|
|
// Detect "YYYY-MM-DDTHH:MM[Z]" (16 or 17 characters)
|
|
if ((nLen == 16 || (nLen == 17 && pszInput[16] == 'Z')) &&
|
|
pszInput[4] == '-' && pszInput[7] == '-' && pszInput[10] == 'T' &&
|
|
pszInput[13] == ':' && static_cast<unsigned>(pszInput[0] - '0') <= 9 &&
|
|
static_cast<unsigned>(pszInput[1] - '0') <= 9 &&
|
|
static_cast<unsigned>(pszInput[2] - '0') <= 9 &&
|
|
static_cast<unsigned>(pszInput[3] - '0') <= 9 &&
|
|
static_cast<unsigned>(pszInput[5] - '0') <= 9 &&
|
|
static_cast<unsigned>(pszInput[6] - '0') <= 9 &&
|
|
static_cast<unsigned>(pszInput[8] - '0') <= 9 &&
|
|
static_cast<unsigned>(pszInput[9] - '0') <= 9 &&
|
|
static_cast<unsigned>(pszInput[11] - '0') <= 9 &&
|
|
static_cast<unsigned>(pszInput[12] - '0') <= 9 &&
|
|
static_cast<unsigned>(pszInput[14] - '0') <= 9 &&
|
|
static_cast<unsigned>(pszInput[15] - '0') <= 9)
|
|
{
|
|
psField->Date.Year = static_cast<GInt16>(
|
|
((((pszInput[0] - '0') * 10 + (pszInput[1] - '0')) * 10) +
|
|
(pszInput[2] - '0')) *
|
|
10 +
|
|
(pszInput[3] - '0'));
|
|
psField->Date.Month =
|
|
static_cast<GByte>((pszInput[5] - '0') * 10 + (pszInput[6] - '0'));
|
|
psField->Date.Day =
|
|
static_cast<GByte>((pszInput[8] - '0') * 10 + (pszInput[9] - '0'));
|
|
psField->Date.Hour = static_cast<GByte>((pszInput[11] - '0') * 10 +
|
|
(pszInput[12] - '0'));
|
|
psField->Date.Minute = static_cast<GByte>((pszInput[14] - '0') * 10 +
|
|
(pszInput[15] - '0'));
|
|
psField->Date.Second = 0.0f;
|
|
psField->Date.TZFlag = nLen == 16 ? 0 : 100;
|
|
psField->Date.Reserved = 0;
|
|
if (psField->Date.Month == 0 || psField->Date.Month > 12 ||
|
|
psField->Date.Day == 0 || psField->Date.Day > 31 ||
|
|
psField->Date.Hour > 23 || psField->Date.Minute > 59)
|
|
{
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* OGRParseDateTimeYYYYMMDDTHHMMSSZ() */
|
|
/************************************************************************/
|
|
|
|
bool OGRParseDateTimeYYYYMMDDTHHMMSSZ(const char *pszInput, size_t nLen,
|
|
OGRField *psField)
|
|
{
|
|
// Detect "YYYY-MM-DDTHH:MM:SS[Z]" (19 or 20 characters)
|
|
if ((nLen == 19 || (nLen == 20 && pszInput[19] == 'Z')) &&
|
|
pszInput[4] == '-' && pszInput[7] == '-' && pszInput[10] == 'T' &&
|
|
pszInput[13] == ':' && pszInput[16] == ':' &&
|
|
static_cast<unsigned>(pszInput[0] - '0') <= 9 &&
|
|
static_cast<unsigned>(pszInput[1] - '0') <= 9 &&
|
|
static_cast<unsigned>(pszInput[2] - '0') <= 9 &&
|
|
static_cast<unsigned>(pszInput[3] - '0') <= 9 &&
|
|
static_cast<unsigned>(pszInput[5] - '0') <= 9 &&
|
|
static_cast<unsigned>(pszInput[6] - '0') <= 9 &&
|
|
static_cast<unsigned>(pszInput[8] - '0') <= 9 &&
|
|
static_cast<unsigned>(pszInput[9] - '0') <= 9 &&
|
|
static_cast<unsigned>(pszInput[11] - '0') <= 9 &&
|
|
static_cast<unsigned>(pszInput[12] - '0') <= 9 &&
|
|
static_cast<unsigned>(pszInput[14] - '0') <= 9 &&
|
|
static_cast<unsigned>(pszInput[15] - '0') <= 9 &&
|
|
static_cast<unsigned>(pszInput[17] - '0') <= 9 &&
|
|
static_cast<unsigned>(pszInput[18] - '0') <= 9)
|
|
{
|
|
psField->Date.Year = static_cast<GInt16>(
|
|
((((pszInput[0] - '0') * 10 + (pszInput[1] - '0')) * 10) +
|
|
(pszInput[2] - '0')) *
|
|
10 +
|
|
(pszInput[3] - '0'));
|
|
psField->Date.Month =
|
|
static_cast<GByte>((pszInput[5] - '0') * 10 + (pszInput[6] - '0'));
|
|
psField->Date.Day =
|
|
static_cast<GByte>((pszInput[8] - '0') * 10 + (pszInput[9] - '0'));
|
|
psField->Date.Hour = static_cast<GByte>((pszInput[11] - '0') * 10 +
|
|
(pszInput[12] - '0'));
|
|
psField->Date.Minute = static_cast<GByte>((pszInput[14] - '0') * 10 +
|
|
(pszInput[15] - '0'));
|
|
psField->Date.Second = static_cast<float>(
|
|
((pszInput[17] - '0') * 10 + (pszInput[18] - '0')));
|
|
psField->Date.TZFlag = nLen == 19 ? 0 : 100;
|
|
psField->Date.Reserved = 0;
|
|
if (psField->Date.Month == 0 || psField->Date.Month > 12 ||
|
|
psField->Date.Day == 0 || psField->Date.Day > 31 ||
|
|
psField->Date.Hour > 23 || psField->Date.Minute > 59 ||
|
|
psField->Date.Second >= 61.0f)
|
|
{
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* OGRParseDateTimeYYYYMMDDTHHMMSSsssZ() */
|
|
/************************************************************************/
|
|
|
|
bool OGRParseDateTimeYYYYMMDDTHHMMSSsssZ(const char *pszInput, size_t nLen,
|
|
OGRField *psField)
|
|
{
|
|
// Detect "YYYY-MM-DDTHH:MM:SS.SSS[Z]" (23 or 24 characters)
|
|
if ((nLen == 23 || (nLen == 24 && pszInput[23] == 'Z')) &&
|
|
pszInput[4] == '-' && pszInput[7] == '-' && pszInput[10] == 'T' &&
|
|
pszInput[13] == ':' && pszInput[16] == ':' && pszInput[19] == '.' &&
|
|
static_cast<unsigned>(pszInput[0] - '0') <= 9 &&
|
|
static_cast<unsigned>(pszInput[1] - '0') <= 9 &&
|
|
static_cast<unsigned>(pszInput[2] - '0') <= 9 &&
|
|
static_cast<unsigned>(pszInput[3] - '0') <= 9 &&
|
|
static_cast<unsigned>(pszInput[5] - '0') <= 9 &&
|
|
static_cast<unsigned>(pszInput[6] - '0') <= 9 &&
|
|
static_cast<unsigned>(pszInput[8] - '0') <= 9 &&
|
|
static_cast<unsigned>(pszInput[9] - '0') <= 9 &&
|
|
static_cast<unsigned>(pszInput[11] - '0') <= 9 &&
|
|
static_cast<unsigned>(pszInput[12] - '0') <= 9 &&
|
|
static_cast<unsigned>(pszInput[14] - '0') <= 9 &&
|
|
static_cast<unsigned>(pszInput[15] - '0') <= 9 &&
|
|
static_cast<unsigned>(pszInput[17] - '0') <= 9 &&
|
|
static_cast<unsigned>(pszInput[18] - '0') <= 9 &&
|
|
static_cast<unsigned>(pszInput[20] - '0') <= 9 &&
|
|
static_cast<unsigned>(pszInput[21] - '0') <= 9 &&
|
|
static_cast<unsigned>(pszInput[22] - '0') <= 9)
|
|
{
|
|
psField->Date.Year = static_cast<GInt16>(
|
|
((((pszInput[0] - '0') * 10 + (pszInput[1] - '0')) * 10) +
|
|
(pszInput[2] - '0')) *
|
|
10 +
|
|
(pszInput[3] - '0'));
|
|
psField->Date.Month =
|
|
static_cast<GByte>((pszInput[5] - '0') * 10 + (pszInput[6] - '0'));
|
|
psField->Date.Day =
|
|
static_cast<GByte>((pszInput[8] - '0') * 10 + (pszInput[9] - '0'));
|
|
psField->Date.Hour = static_cast<GByte>((pszInput[11] - '0') * 10 +
|
|
(pszInput[12] - '0'));
|
|
psField->Date.Minute = static_cast<GByte>((pszInput[14] - '0') * 10 +
|
|
(pszInput[15] - '0'));
|
|
psField->Date.Second = static_cast<float>(
|
|
((pszInput[17] - '0') * 10 + (pszInput[18] - '0')) +
|
|
((pszInput[20] - '0') * 100 + (pszInput[21] - '0') * 10 +
|
|
(pszInput[22] - '0')) /
|
|
1000.0);
|
|
psField->Date.TZFlag = nLen == 23 ? 0 : 100;
|
|
psField->Date.Reserved = 0;
|
|
if (psField->Date.Month == 0 || psField->Date.Month > 12 ||
|
|
psField->Date.Day == 0 || psField->Date.Day > 31 ||
|
|
psField->Date.Hour > 23 || psField->Date.Minute > 59 ||
|
|
psField->Date.Second >= 61.0f)
|
|
{
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* OGRParseXMLDateTime() */
|
|
/************************************************************************/
|
|
|
|
int OGRParseXMLDateTime(const char *pszXMLDateTime, OGRField *psField)
|
|
{
|
|
int year = 0;
|
|
int month = 0;
|
|
int day = 0;
|
|
int hour = 0;
|
|
int minute = 0;
|
|
int TZHour = 0;
|
|
int TZMinute = 0;
|
|
float second = 0;
|
|
char c = '\0';
|
|
int TZ = 0;
|
|
bool bRet = false;
|
|
|
|
// Date is expressed as a UTC date.
|
|
if (sscanf(pszXMLDateTime, "%04d-%02d-%02dT%02d:%02d:%f%c", &year, &month,
|
|
&day, &hour, &minute, &second, &c) == 7 &&
|
|
c == 'Z')
|
|
{
|
|
TZ = 100;
|
|
bRet = true;
|
|
}
|
|
// Date is expressed as a UTC date, with a timezone.
|
|
else if (sscanf(pszXMLDateTime, "%04d-%02d-%02dT%02d:%02d:%f%c%02d:%02d",
|
|
&year, &month, &day, &hour, &minute, &second, &c, &TZHour,
|
|
&TZMinute) == 9 &&
|
|
(c == '+' || c == '-'))
|
|
{
|
|
TZ = 100 + ((c == '+') ? 1 : -1) * ((TZHour * 60 + TZMinute) / 15);
|
|
bRet = true;
|
|
}
|
|
// Date is expressed into an unknown timezone.
|
|
else if (sscanf(pszXMLDateTime, "%04d-%02d-%02dT%02d:%02d:%f", &year,
|
|
&month, &day, &hour, &minute, &second) == 6)
|
|
{
|
|
TZ = 0;
|
|
bRet = true;
|
|
}
|
|
// Date is expressed as a UTC date with only year:month:day.
|
|
else if (sscanf(pszXMLDateTime, "%04d-%02d-%02d", &year, &month, &day) == 3)
|
|
{
|
|
TZ = 0;
|
|
bRet = true;
|
|
}
|
|
// Date is expressed as a UTC date with only year:month.
|
|
else if (sscanf(pszXMLDateTime, "%04d-%02d", &year, &month) == 2)
|
|
{
|
|
TZ = 0;
|
|
bRet = true;
|
|
day = 1;
|
|
}
|
|
|
|
if (!bRet)
|
|
return FALSE;
|
|
|
|
psField->Date.Year = static_cast<GInt16>(year);
|
|
psField->Date.Month = static_cast<GByte>(month);
|
|
psField->Date.Day = static_cast<GByte>(day);
|
|
psField->Date.Hour = static_cast<GByte>(hour);
|
|
psField->Date.Minute = static_cast<GByte>(minute);
|
|
psField->Date.Second = second;
|
|
psField->Date.TZFlag = static_cast<GByte>(TZ);
|
|
psField->Date.Reserved = 0;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* OGRParseRFC822DateTime() */
|
|
/************************************************************************/
|
|
|
|
static const char *const aszMonthStr[] = {"Jan", "Feb", "Mar", "Apr",
|
|
"May", "Jun", "Jul", "Aug",
|
|
"Sep", "Oct", "Nov", "Dec"};
|
|
|
|
int OGRParseRFC822DateTime(const char *pszRFC822DateTime, OGRField *psField)
|
|
{
|
|
int nYear, nMonth, nDay, nHour, nMinute, nSecond, nTZFlag;
|
|
if (!CPLParseRFC822DateTime(pszRFC822DateTime, &nYear, &nMonth, &nDay,
|
|
&nHour, &nMinute, &nSecond, &nTZFlag, nullptr))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
psField->Date.Year = static_cast<GInt16>(nYear);
|
|
psField->Date.Month = static_cast<GByte>(nMonth);
|
|
psField->Date.Day = static_cast<GByte>(nDay);
|
|
psField->Date.Hour = static_cast<GByte>(nHour);
|
|
psField->Date.Minute = static_cast<GByte>(nMinute);
|
|
psField->Date.Second = (nSecond < 0) ? 0.0f : static_cast<float>(nSecond);
|
|
psField->Date.TZFlag = static_cast<GByte>(nTZFlag);
|
|
psField->Date.Reserved = 0;
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Returns the day of the week in Gregorian calendar
|
|
*
|
|
* @param day : day of the month, between 1 and 31
|
|
* @param month : month of the year, between 1 (Jan) and 12 (Dec)
|
|
* @param year : year
|
|
|
|
* @return day of the week : 0 for Monday, ... 6 for Sunday
|
|
*/
|
|
|
|
int OGRGetDayOfWeek(int day, int month, int year)
|
|
{
|
|
// Reference: Zeller's congruence.
|
|
const int q = day;
|
|
int m = month;
|
|
if (month >= 3)
|
|
{
|
|
// m = month;
|
|
}
|
|
else
|
|
{
|
|
m = month + 12;
|
|
year--;
|
|
}
|
|
const int K = year % 100;
|
|
const int J = year / 100;
|
|
const int h = (q + (((m + 1) * 26) / 10) + K + K / 4 + J / 4 + 5 * J) % 7;
|
|
return (h + 5) % 7;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* OGRGetRFC822DateTime() */
|
|
/************************************************************************/
|
|
|
|
char *OGRGetRFC822DateTime(const OGRField *psField)
|
|
{
|
|
char *pszTZ = nullptr;
|
|
const char *const aszDayOfWeek[] = {"Mon", "Tue", "Wed", "Thu",
|
|
"Fri", "Sat", "Sun"};
|
|
|
|
int dayofweek = OGRGetDayOfWeek(psField->Date.Day, psField->Date.Month,
|
|
psField->Date.Year);
|
|
|
|
int month = psField->Date.Month;
|
|
if (month < 1 || month > 12)
|
|
month = 1;
|
|
|
|
int TZFlag = psField->Date.TZFlag;
|
|
if (TZFlag == 0 || TZFlag == 100)
|
|
{
|
|
pszTZ = CPLStrdup("GMT");
|
|
}
|
|
else
|
|
{
|
|
int TZOffset = std::abs(TZFlag - 100) * 15;
|
|
int TZHour = TZOffset / 60;
|
|
int TZMinute = TZOffset - TZHour * 60;
|
|
pszTZ = CPLStrdup(CPLSPrintf("%c%02d%02d", TZFlag > 100 ? '+' : '-',
|
|
TZHour, TZMinute));
|
|
}
|
|
char *pszRet = CPLStrdup(CPLSPrintf(
|
|
"%s, %02d %s %04d %02d:%02d:%02d %s", aszDayOfWeek[dayofweek],
|
|
psField->Date.Day, aszMonthStr[month - 1], psField->Date.Year,
|
|
psField->Date.Hour, psField->Date.Minute,
|
|
static_cast<int>(psField->Date.Second), pszTZ));
|
|
CPLFree(pszTZ);
|
|
return pszRet;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* OGRGetXMLDateTime() */
|
|
/************************************************************************/
|
|
|
|
char *OGRGetXMLDateTime(const OGRField *psField)
|
|
{
|
|
char *pszRet =
|
|
static_cast<char *>(CPLMalloc(OGR_SIZEOF_ISO8601_DATETIME_BUFFER));
|
|
OGRGetISO8601DateTime(psField, false, pszRet);
|
|
return pszRet;
|
|
}
|
|
|
|
char *OGRGetXMLDateTime(const OGRField *psField, bool bAlwaysMillisecond)
|
|
{
|
|
char *pszRet =
|
|
static_cast<char *>(CPLMalloc(OGR_SIZEOF_ISO8601_DATETIME_BUFFER));
|
|
OGRGetISO8601DateTime(psField, bAlwaysMillisecond, pszRet);
|
|
return pszRet;
|
|
}
|
|
|
|
int OGRGetISO8601DateTime(const OGRField *psField, bool bAlwaysMillisecond,
|
|
char szBuffer[OGR_SIZEOF_ISO8601_DATETIME_BUFFER])
|
|
{
|
|
OGRISO8601Format sFormat;
|
|
sFormat.ePrecision = bAlwaysMillisecond ? OGRISO8601Precision::MILLISECOND
|
|
: OGRISO8601Precision::AUTO;
|
|
return OGRGetISO8601DateTime(psField, sFormat, szBuffer);
|
|
}
|
|
|
|
int OGRGetISO8601DateTime(const OGRField *psField,
|
|
const OGRISO8601Format &sFormat,
|
|
char szBuffer[OGR_SIZEOF_ISO8601_DATETIME_BUFFER])
|
|
{
|
|
const GInt16 year = psField->Date.Year;
|
|
const GByte month = psField->Date.Month;
|
|
const GByte day = psField->Date.Day;
|
|
const GByte hour = psField->Date.Hour;
|
|
const GByte minute = psField->Date.Minute;
|
|
const float second = psField->Date.Second;
|
|
const GByte TZFlag = psField->Date.TZFlag;
|
|
|
|
if (year < 0 || year >= 10000)
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"OGRGetISO8601DateTime(): year %d unsupported ", year);
|
|
szBuffer[0] = 0;
|
|
return 0;
|
|
}
|
|
|
|
int nYear = year;
|
|
szBuffer[3] = (nYear % 10) + '0';
|
|
nYear /= 10;
|
|
szBuffer[2] = (nYear % 10) + '0';
|
|
nYear /= 10;
|
|
szBuffer[1] = (nYear % 10) + '0';
|
|
nYear /= 10;
|
|
szBuffer[0] = static_cast<char>(nYear /*% 10*/ + '0');
|
|
szBuffer[4] = '-';
|
|
szBuffer[5] = ((month / 10) % 10) + '0';
|
|
szBuffer[6] = (month % 10) + '0';
|
|
szBuffer[7] = '-';
|
|
szBuffer[8] = ((day / 10) % 10) + '0';
|
|
szBuffer[9] = (day % 10) + '0';
|
|
szBuffer[10] = 'T';
|
|
szBuffer[11] = ((hour / 10) % 10) + '0';
|
|
szBuffer[12] = (hour % 10) + '0';
|
|
szBuffer[13] = ':';
|
|
szBuffer[14] = ((minute / 10) % 10) + '0';
|
|
szBuffer[15] = (minute % 10) + '0';
|
|
int nPos;
|
|
if (sFormat.ePrecision == OGRISO8601Precision::MINUTE)
|
|
{
|
|
nPos = 16;
|
|
}
|
|
else
|
|
{
|
|
szBuffer[16] = ':';
|
|
|
|
if (sFormat.ePrecision == OGRISO8601Precision::MILLISECOND ||
|
|
(sFormat.ePrecision == OGRISO8601Precision::AUTO &&
|
|
OGR_GET_MS(second)))
|
|
{
|
|
/* Below is equivalent of the below snprintf(), but hand-made for
|
|
* faster execution. */
|
|
/* snprintf(szBuffer, nMaxSize,
|
|
"%04d-%02u-%02uT%02u:%02u:%06.3f%s",
|
|
year, month, day, hour, minute, second,
|
|
szTimeZone);
|
|
*/
|
|
int nMilliSecond = static_cast<int>(second * 1000.0f + 0.5f);
|
|
szBuffer[22] = (nMilliSecond % 10) + '0';
|
|
nMilliSecond /= 10;
|
|
szBuffer[21] = (nMilliSecond % 10) + '0';
|
|
nMilliSecond /= 10;
|
|
szBuffer[20] = (nMilliSecond % 10) + '0';
|
|
nMilliSecond /= 10;
|
|
szBuffer[19] = '.';
|
|
szBuffer[18] = (nMilliSecond % 10) + '0';
|
|
nMilliSecond /= 10;
|
|
szBuffer[17] = (nMilliSecond % 10) + '0';
|
|
nPos = 23;
|
|
}
|
|
else
|
|
{
|
|
/* Below is equivalent of the below snprintf(), but hand-made for
|
|
* faster execution. */
|
|
/* snprintf(szBuffer, nMaxSize,
|
|
"%04d-%02u-%02uT%02u:%02u:%02u%s",
|
|
year, month, day, hour, minute,
|
|
static_cast<GByte>(second), szTimeZone);
|
|
*/
|
|
int nSecond = static_cast<int>(second + 0.5f);
|
|
szBuffer[17] = ((nSecond / 10) % 10) + '0';
|
|
szBuffer[18] = (nSecond % 10) + '0';
|
|
nPos = 19;
|
|
}
|
|
}
|
|
|
|
switch (TZFlag)
|
|
{
|
|
case 0: // Unknown time zone
|
|
case 1: // Local time zone (not specified)
|
|
break;
|
|
|
|
case 100: // GMT
|
|
szBuffer[nPos++] = 'Z';
|
|
break;
|
|
|
|
default: // Offset (in quarter-hour units) from GMT
|
|
const int TZOffset = std::abs(TZFlag - 100) * 15;
|
|
const int TZHour = TZOffset / 60;
|
|
const int TZMinute = TZOffset % 60;
|
|
|
|
szBuffer[nPos++] = (TZFlag > 100) ? '+' : '-';
|
|
szBuffer[nPos++] = ((TZHour / 10) % 10) + '0';
|
|
szBuffer[nPos++] = (TZHour % 10) + '0';
|
|
szBuffer[nPos++] = ':';
|
|
szBuffer[nPos++] = ((TZMinute / 10) % 10) + '0';
|
|
szBuffer[nPos++] = (TZMinute % 10) + '0';
|
|
}
|
|
|
|
szBuffer[nPos] = 0;
|
|
|
|
return nPos;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* OGRGetXML_UTF8_EscapedString() */
|
|
/************************************************************************/
|
|
|
|
char *OGRGetXML_UTF8_EscapedString(const char *pszString)
|
|
{
|
|
char *pszEscaped = nullptr;
|
|
if (!CPLIsUTF8(pszString, -1) &&
|
|
CPLTestBool(CPLGetConfigOption("OGR_FORCE_ASCII", "YES")))
|
|
{
|
|
static bool bFirstTime = true;
|
|
if (bFirstTime)
|
|
{
|
|
bFirstTime = false;
|
|
CPLError(CE_Warning, CPLE_AppDefined,
|
|
"%s is not a valid UTF-8 string. Forcing it to ASCII. "
|
|
"If you still want the original string and change the XML "
|
|
"file encoding afterwards, you can define "
|
|
"OGR_FORCE_ASCII=NO as configuration option. "
|
|
"This warning won't be issued anymore",
|
|
pszString);
|
|
}
|
|
else
|
|
{
|
|
CPLDebug("OGR",
|
|
"%s is not a valid UTF-8 string. Forcing it to ASCII",
|
|
pszString);
|
|
}
|
|
char *pszTemp = CPLForceToASCII(pszString, -1, '?');
|
|
pszEscaped = CPLEscapeString(pszTemp, -1, CPLES_XML);
|
|
CPLFree(pszTemp);
|
|
}
|
|
else
|
|
pszEscaped = CPLEscapeString(pszString, -1, CPLES_XML);
|
|
return pszEscaped;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* OGRCompareDate() */
|
|
/************************************************************************/
|
|
|
|
int OGRCompareDate(const OGRField *psFirstTuple, const OGRField *psSecondTuple)
|
|
{
|
|
// TODO: We ignore TZFlag.
|
|
|
|
if (psFirstTuple->Date.Year < psSecondTuple->Date.Year)
|
|
return -1;
|
|
else if (psFirstTuple->Date.Year > psSecondTuple->Date.Year)
|
|
return 1;
|
|
|
|
if (psFirstTuple->Date.Month < psSecondTuple->Date.Month)
|
|
return -1;
|
|
else if (psFirstTuple->Date.Month > psSecondTuple->Date.Month)
|
|
return 1;
|
|
|
|
if (psFirstTuple->Date.Day < psSecondTuple->Date.Day)
|
|
return -1;
|
|
else if (psFirstTuple->Date.Day > psSecondTuple->Date.Day)
|
|
return 1;
|
|
|
|
if (psFirstTuple->Date.Hour < psSecondTuple->Date.Hour)
|
|
return -1;
|
|
else if (psFirstTuple->Date.Hour > psSecondTuple->Date.Hour)
|
|
return 1;
|
|
|
|
if (psFirstTuple->Date.Minute < psSecondTuple->Date.Minute)
|
|
return -1;
|
|
else if (psFirstTuple->Date.Minute > psSecondTuple->Date.Minute)
|
|
return 1;
|
|
|
|
if (psFirstTuple->Date.Second < psSecondTuple->Date.Second)
|
|
return -1;
|
|
else if (psFirstTuple->Date.Second > psSecondTuple->Date.Second)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* OGRFastAtof() */
|
|
/************************************************************************/
|
|
|
|
// On Windows, CPLAtof() is very slow if the number is followed by other long
|
|
// content. Just extract the number into a short string before calling
|
|
// CPLAtof() on it.
|
|
static double OGRCallAtofOnShortString(const char *pszStr)
|
|
{
|
|
const char *p = pszStr;
|
|
while (*p == ' ' || *p == '\t')
|
|
++p;
|
|
|
|
char szTemp[128] = {};
|
|
int nCounter = 0;
|
|
while (*p == '+' || *p == '-' || (*p >= '0' && *p <= '9') || *p == '.' ||
|
|
(*p == 'e' || *p == 'E' || *p == 'd' || *p == 'D'))
|
|
{
|
|
szTemp[nCounter++] = *(p++);
|
|
if (nCounter == 127)
|
|
return CPLAtof(pszStr);
|
|
}
|
|
szTemp[nCounter] = '\0';
|
|
return CPLAtof(szTemp);
|
|
}
|
|
|
|
/** Same contract as CPLAtof, except than it doesn't always call the
|
|
* system CPLAtof() that may be slow on some platforms. For simple but
|
|
* common strings, it'll use a faster implementation (up to 20x faster
|
|
* than CPLAtof() on MS runtime libraries) that has no guaranty to return
|
|
* exactly the same floating point number.
|
|
*/
|
|
|
|
double OGRFastAtof(const char *pszStr)
|
|
{
|
|
double dfVal = 0;
|
|
double dfSign = 1.0;
|
|
const char *p = pszStr;
|
|
|
|
constexpr double adfTenPower[] = {
|
|
1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10,
|
|
1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21,
|
|
1e22, 1e23, 1e24, 1e25, 1e26, 1e27, 1e28, 1e29, 1e30, 1e31};
|
|
|
|
while (*p == ' ' || *p == '\t')
|
|
++p;
|
|
|
|
if (*p == '+')
|
|
++p;
|
|
else if (*p == '-')
|
|
{
|
|
dfSign = -1.0;
|
|
++p;
|
|
}
|
|
|
|
while (true)
|
|
{
|
|
if (*p >= '0' && *p <= '9')
|
|
{
|
|
dfVal = dfVal * 10.0 + (*p - '0');
|
|
++p;
|
|
}
|
|
else if (*p == '.')
|
|
{
|
|
++p;
|
|
break;
|
|
}
|
|
else if (*p == 'e' || *p == 'E' || *p == 'd' || *p == 'D')
|
|
return OGRCallAtofOnShortString(pszStr);
|
|
else
|
|
return dfSign * dfVal;
|
|
}
|
|
|
|
unsigned int countFractionnal = 0;
|
|
while (true)
|
|
{
|
|
if (*p >= '0' && *p <= '9')
|
|
{
|
|
dfVal = dfVal * 10.0 + (*p - '0');
|
|
++countFractionnal;
|
|
++p;
|
|
}
|
|
else if (*p == 'e' || *p == 'E' || *p == 'd' || *p == 'D')
|
|
return OGRCallAtofOnShortString(pszStr);
|
|
else
|
|
{
|
|
if (countFractionnal < CPL_ARRAYSIZE(adfTenPower))
|
|
return dfSign * (dfVal / adfTenPower[countFractionnal]);
|
|
else
|
|
return OGRCallAtofOnShortString(pszStr);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check that panPermutation is a permutation of [0, nSize-1].
|
|
* @param panPermutation an array of nSize elements.
|
|
* @param nSize size of the array.
|
|
* @return OGRERR_NONE if panPermutation is a permutation of [0, nSize - 1].
|
|
* @since OGR 1.9.0
|
|
*/
|
|
OGRErr OGRCheckPermutation(const int *panPermutation, int nSize)
|
|
{
|
|
OGRErr eErr = OGRERR_NONE;
|
|
int *panCheck = static_cast<int *>(CPLCalloc(nSize, sizeof(int)));
|
|
for (int i = 0; i < nSize; ++i)
|
|
{
|
|
if (panPermutation[i] < 0 || panPermutation[i] >= nSize)
|
|
{
|
|
CPLError(CE_Failure, CPLE_IllegalArg, "Bad value for element %d",
|
|
i);
|
|
eErr = OGRERR_FAILURE;
|
|
break;
|
|
}
|
|
if (panCheck[panPermutation[i]] != 0)
|
|
{
|
|
CPLError(CE_Failure, CPLE_IllegalArg,
|
|
"Array is not a permutation of [0,%d]", nSize - 1);
|
|
eErr = OGRERR_FAILURE;
|
|
break;
|
|
}
|
|
panCheck[panPermutation[i]] = 1;
|
|
}
|
|
CPLFree(panCheck);
|
|
return eErr;
|
|
}
|
|
|
|
OGRErr OGRReadWKBGeometryType(const unsigned char *pabyData,
|
|
OGRwkbVariant eWkbVariant,
|
|
OGRwkbGeometryType *peGeometryType)
|
|
{
|
|
if (!peGeometryType)
|
|
return OGRERR_FAILURE;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Get the byte order byte. */
|
|
/* -------------------------------------------------------------------- */
|
|
int nByteOrder = DB2_V72_FIX_BYTE_ORDER(*pabyData);
|
|
if (!(nByteOrder == wkbXDR || nByteOrder == wkbNDR))
|
|
return OGRERR_CORRUPT_DATA;
|
|
OGRwkbByteOrder eByteOrder = static_cast<OGRwkbByteOrder>(nByteOrder);
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Get the geometry type. */
|
|
/* -------------------------------------------------------------------- */
|
|
bool bIs3D = false;
|
|
bool bIsMeasured = false;
|
|
int iRawType = 0;
|
|
|
|
memcpy(&iRawType, pabyData + 1, 4);
|
|
if (OGR_SWAP(eByteOrder))
|
|
{
|
|
CPL_SWAP32PTR(&iRawType);
|
|
}
|
|
|
|
// Test for M bit in PostGIS WKB, see ogrgeometry.cpp:4956.
|
|
if (0x40000000 & iRawType)
|
|
{
|
|
iRawType &= ~0x40000000;
|
|
bIsMeasured = true;
|
|
}
|
|
// Old-style OGC z-bit is flipped? Tests also Z bit in PostGIS WKB.
|
|
if (wkb25DBitInternalUse & iRawType)
|
|
{
|
|
// Clean off top 3 bytes.
|
|
iRawType &= 0x000000FF;
|
|
bIs3D = true;
|
|
}
|
|
|
|
// ISO SQL/MM Part3 draft -> Deprecated.
|
|
// See http://jtc1sc32.org/doc/N1101-1150/32N1107-WD13249-3--spatial.pdf
|
|
if (iRawType == 1000001)
|
|
iRawType = wkbCircularString;
|
|
else if (iRawType == 1000002)
|
|
iRawType = wkbCompoundCurve;
|
|
else if (iRawType == 1000003)
|
|
iRawType = wkbCurvePolygon;
|
|
else if (iRawType == 1000004)
|
|
iRawType = wkbMultiCurve;
|
|
else if (iRawType == 1000005)
|
|
iRawType = wkbMultiSurface;
|
|
else if (iRawType == 2000001)
|
|
iRawType = wkbPointZM;
|
|
else if (iRawType == 2000002)
|
|
iRawType = wkbLineStringZM;
|
|
else if (iRawType == 2000003)
|
|
iRawType = wkbCircularStringZM;
|
|
else if (iRawType == 2000004)
|
|
iRawType = wkbCompoundCurveZM;
|
|
else if (iRawType == 2000005)
|
|
iRawType = wkbPolygonZM;
|
|
else if (iRawType == 2000006)
|
|
iRawType = wkbCurvePolygonZM;
|
|
else if (iRawType == 2000007)
|
|
iRawType = wkbMultiPointZM;
|
|
else if (iRawType == 2000008)
|
|
iRawType = wkbMultiCurveZM;
|
|
else if (iRawType == 2000009)
|
|
iRawType = wkbMultiLineStringZM;
|
|
else if (iRawType == 2000010)
|
|
iRawType = wkbMultiSurfaceZM;
|
|
else if (iRawType == 2000011)
|
|
iRawType = wkbMultiPolygonZM;
|
|
else if (iRawType == 2000012)
|
|
iRawType = wkbGeometryCollectionZM;
|
|
else if (iRawType == 3000001)
|
|
iRawType = wkbPoint25D;
|
|
else if (iRawType == 3000002)
|
|
iRawType = wkbLineString25D;
|
|
else if (iRawType == 3000003)
|
|
iRawType = wkbCircularStringZ;
|
|
else if (iRawType == 3000004)
|
|
iRawType = wkbCompoundCurveZ;
|
|
else if (iRawType == 3000005)
|
|
iRawType = wkbPolygon25D;
|
|
else if (iRawType == 3000006)
|
|
iRawType = wkbCurvePolygonZ;
|
|
else if (iRawType == 3000007)
|
|
iRawType = wkbMultiPoint25D;
|
|
else if (iRawType == 3000008)
|
|
iRawType = wkbMultiCurveZ;
|
|
else if (iRawType == 3000009)
|
|
iRawType = wkbMultiLineString25D;
|
|
else if (iRawType == 3000010)
|
|
iRawType = wkbMultiSurfaceZ;
|
|
else if (iRawType == 3000011)
|
|
iRawType = wkbMultiPolygon25D;
|
|
else if (iRawType == 3000012)
|
|
iRawType = wkbGeometryCollection25D;
|
|
else if (iRawType == 4000001)
|
|
iRawType = wkbPointM;
|
|
else if (iRawType == 4000002)
|
|
iRawType = wkbLineStringM;
|
|
else if (iRawType == 4000003)
|
|
iRawType = wkbCircularStringM;
|
|
else if (iRawType == 4000004)
|
|
iRawType = wkbCompoundCurveM;
|
|
else if (iRawType == 4000005)
|
|
iRawType = wkbPolygonM;
|
|
else if (iRawType == 4000006)
|
|
iRawType = wkbCurvePolygonM;
|
|
else if (iRawType == 4000007)
|
|
iRawType = wkbMultiPointM;
|
|
else if (iRawType == 4000008)
|
|
iRawType = wkbMultiCurveM;
|
|
else if (iRawType == 4000009)
|
|
iRawType = wkbMultiLineStringM;
|
|
else if (iRawType == 4000010)
|
|
iRawType = wkbMultiSurfaceM;
|
|
else if (iRawType == 4000011)
|
|
iRawType = wkbMultiPolygonM;
|
|
else if (iRawType == 4000012)
|
|
iRawType = wkbGeometryCollectionM;
|
|
|
|
// Sometimes the Z flag is in the 2nd byte?
|
|
if (iRawType & (wkb25DBitInternalUse >> 16))
|
|
{
|
|
// Clean off top 3 bytes.
|
|
iRawType &= 0x000000FF;
|
|
bIs3D = true;
|
|
}
|
|
|
|
if (eWkbVariant == wkbVariantPostGIS1)
|
|
{
|
|
if (iRawType == POSTGIS15_CURVEPOLYGON)
|
|
iRawType = wkbCurvePolygon;
|
|
else if (iRawType == POSTGIS15_MULTICURVE)
|
|
iRawType = wkbMultiCurve;
|
|
else if (iRawType == POSTGIS15_MULTISURFACE)
|
|
iRawType = wkbMultiSurface;
|
|
}
|
|
|
|
if (bIs3D)
|
|
{
|
|
iRawType += 1000;
|
|
}
|
|
if (bIsMeasured)
|
|
{
|
|
iRawType += 2000;
|
|
}
|
|
|
|
// ISO SQL/MM style types are between 1-17, 1001-1017, 2001-2017, and
|
|
// 3001-3017.
|
|
if (!((iRawType > 0 && iRawType <= 17) ||
|
|
(iRawType > 1000 && iRawType <= 1017) ||
|
|
(iRawType > 2000 && iRawType <= 2017) ||
|
|
(iRawType > 3000 && iRawType <= 3017)))
|
|
{
|
|
CPLError(CE_Failure, CPLE_NotSupported, "Unsupported WKB type %d",
|
|
iRawType);
|
|
return OGRERR_UNSUPPORTED_GEOMETRY_TYPE;
|
|
}
|
|
|
|
// Convert to OGRwkbGeometryType value.
|
|
if (iRawType >= 1001 && iRawType <= 1007)
|
|
{
|
|
iRawType -= 1000;
|
|
iRawType |= wkb25DBitInternalUse;
|
|
}
|
|
|
|
*peGeometryType = static_cast<OGRwkbGeometryType>(iRawType);
|
|
|
|
return OGRERR_NONE;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* OGRReadWKTGeometryType() */
|
|
/************************************************************************/
|
|
|
|
OGRErr OGRReadWKTGeometryType(const char *pszWKT,
|
|
OGRwkbGeometryType *peGeometryType)
|
|
{
|
|
if (!peGeometryType)
|
|
return OGRERR_FAILURE;
|
|
|
|
OGRwkbGeometryType eGeomType = wkbUnknown;
|
|
if (STARTS_WITH_CI(pszWKT, "POINT"))
|
|
eGeomType = wkbPoint;
|
|
else if (STARTS_WITH_CI(pszWKT, "LINESTRING"))
|
|
eGeomType = wkbLineString;
|
|
else if (STARTS_WITH_CI(pszWKT, "POLYGON"))
|
|
eGeomType = wkbPolygon;
|
|
else if (STARTS_WITH_CI(pszWKT, "MULTIPOINT"))
|
|
eGeomType = wkbMultiPoint;
|
|
else if (STARTS_WITH_CI(pszWKT, "MULTILINESTRING"))
|
|
eGeomType = wkbMultiLineString;
|
|
else if (STARTS_WITH_CI(pszWKT, "MULTIPOLYGON"))
|
|
eGeomType = wkbMultiPolygon;
|
|
else if (STARTS_WITH_CI(pszWKT, "GEOMETRYCOLLECTION"))
|
|
eGeomType = wkbGeometryCollection;
|
|
else if (STARTS_WITH_CI(pszWKT, "CIRCULARSTRING"))
|
|
eGeomType = wkbCircularString;
|
|
else if (STARTS_WITH_CI(pszWKT, "COMPOUNDCURVE"))
|
|
eGeomType = wkbCompoundCurve;
|
|
else if (STARTS_WITH_CI(pszWKT, "CURVEPOLYGON"))
|
|
eGeomType = wkbCurvePolygon;
|
|
else if (STARTS_WITH_CI(pszWKT, "MULTICURVE"))
|
|
eGeomType = wkbMultiCurve;
|
|
else if (STARTS_WITH_CI(pszWKT, "MULTISURFACE"))
|
|
eGeomType = wkbMultiSurface;
|
|
else if (STARTS_WITH_CI(pszWKT, "POLYHEDRALSURFACE"))
|
|
eGeomType = wkbPolyhedralSurface;
|
|
else if (STARTS_WITH_CI(pszWKT, "TIN"))
|
|
eGeomType = wkbTIN;
|
|
else
|
|
return OGRERR_UNSUPPORTED_GEOMETRY_TYPE;
|
|
|
|
if (strstr(pszWKT, " ZM"))
|
|
eGeomType = OGR_GT_SetModifier(eGeomType, true, true);
|
|
else if (strstr(pszWKT, " Z"))
|
|
eGeomType = OGR_GT_SetModifier(eGeomType, true, false);
|
|
else if (strstr(pszWKT, " M"))
|
|
eGeomType = OGR_GT_SetModifier(eGeomType, false, true);
|
|
|
|
*peGeometryType = eGeomType;
|
|
|
|
return OGRERR_NONE;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* OGRFormatFloat() */
|
|
/************************************************************************/
|
|
|
|
int OGRFormatFloat(char *pszBuffer, int nBufferLen, float fVal, int nPrecision,
|
|
char chConversionSpecifier)
|
|
{
|
|
// So to have identical cross platform representation.
|
|
if (std::isinf(fVal))
|
|
return CPLsnprintf(pszBuffer, nBufferLen, (fVal > 0) ? "inf" : "-inf");
|
|
if (std::isnan(fVal))
|
|
return CPLsnprintf(pszBuffer, nBufferLen, "nan");
|
|
|
|
int nSize = 0;
|
|
char szFormatting[32] = {};
|
|
constexpr int MAX_SIGNIFICANT_DIGITS_FLOAT32 = 8;
|
|
const int nInitialSignificantFigures =
|
|
nPrecision >= 0 ? nPrecision : MAX_SIGNIFICANT_DIGITS_FLOAT32;
|
|
|
|
CPLsnprintf(szFormatting, sizeof(szFormatting), "%%.%d%c",
|
|
nInitialSignificantFigures, chConversionSpecifier);
|
|
nSize = CPLsnprintf(pszBuffer, nBufferLen, szFormatting, fVal);
|
|
const char *pszDot = strchr(pszBuffer, '.');
|
|
|
|
// Try to avoid 0.34999999 or 0.15000001 rounding issues by
|
|
// decreasing a bit precision.
|
|
if (nInitialSignificantFigures >= 8 && pszDot != nullptr &&
|
|
(strstr(pszDot, "99999") != nullptr ||
|
|
strstr(pszDot, "00000") != nullptr))
|
|
{
|
|
const CPLString osOriBuffer(pszBuffer, nSize);
|
|
|
|
bool bOK = false;
|
|
for (int i = 1; i <= 3; i++)
|
|
{
|
|
CPLsnprintf(szFormatting, sizeof(szFormatting), "%%.%d%c",
|
|
nInitialSignificantFigures - i, chConversionSpecifier);
|
|
nSize = CPLsnprintf(pszBuffer, nBufferLen, szFormatting, fVal);
|
|
pszDot = strchr(pszBuffer, '.');
|
|
if (pszDot != nullptr && strstr(pszDot, "99999") == nullptr &&
|
|
strstr(pszDot, "00000") == nullptr &&
|
|
static_cast<float>(CPLAtof(pszBuffer)) == fVal)
|
|
{
|
|
bOK = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!bOK)
|
|
{
|
|
memcpy(pszBuffer, osOriBuffer.c_str(), osOriBuffer.size() + 1);
|
|
nSize = static_cast<int>(osOriBuffer.size());
|
|
}
|
|
}
|
|
|
|
if (nSize + 2 < static_cast<int>(nBufferLen) &&
|
|
strchr(pszBuffer, '.') == nullptr && strchr(pszBuffer, 'e') == nullptr)
|
|
{
|
|
nSize += CPLsnprintf(pszBuffer + nSize, nBufferLen - nSize, ".0");
|
|
}
|
|
|
|
return nSize;
|
|
}
|