Coverage Report

Created: 2025-06-09 08:44

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