Coverage Report

Created: 2025-08-28 06:57

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