Coverage Report

Created: 2026-10-28 07:09

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