Coverage Report

Created: 2026-08-14 09:29

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/ogr/gml2ogrgeometry.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  GML Reader
4
 * Purpose:  Code to translate between GML and OGR geometry forms.
5
 * Author:   Frank Warmerdam, warmerdam@pobox.com
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2002, Frank Warmerdam
9
 * Copyright (c) 2009-2014, Even Rouault <even dot rouault at spatialys.com>
10
 *
11
 * SPDX-License-Identifier: MIT
12
 *****************************************************************************
13
 *
14
 * Independent Security Audit 2003/04/17 Andrey Kiselev:
15
 *   Completed audit of this module. All functions may be used without buffer
16
 *   overflows and stack corruptions with any kind of input data.
17
 *
18
 * Security Audit 2003/03/28 warmerda:
19
 *   Completed security audit.  I believe that this module may be safely used
20
 *   to parse, arbitrary GML potentially provided by a hostile source without
21
 *   compromising the system.
22
 *
23
 */
24
25
#include "cpl_port.h"
26
#include "ogr_api.h"
27
28
#include <algorithm>
29
#include <cassert>
30
#include <cctype>
31
#include <cmath>
32
#include <cstdlib>
33
#include <cstring>
34
35
#include "cpl_conv.h"
36
#include "cpl_error.h"
37
#include "cpl_mem_cache.h"
38
#include "cpl_minixml.h"
39
#include "cpl_string.h"
40
#include "ogr_core.h"
41
#include "ogr_geometry.h"
42
#include "ogr_p.h"
43
#include "ogr_spatialref.h"
44
#include "ogr_srs_api.h"
45
#include "ogr_geo_utils.h"
46
#include "gmlutils.h"
47
48
constexpr double kdfD2R = M_PI / 180.0;
49
constexpr double kdf2PI = 2.0 * M_PI;
50
51
/************************************************************************/
52
/*                        GMLGetCoordTokenPos()                         */
53
/************************************************************************/
54
55
static const char *GMLGetCoordTokenPos(const char *pszStr,
56
                                       const char **ppszNextToken)
57
2.40M
{
58
2.40M
    char ch;
59
5.15M
    while (true)
60
5.15M
    {
61
        // cppcheck-suppress nullPointerRedundantCheck
62
5.15M
        ch = *pszStr;
63
5.15M
        if (ch == '\0')
64
27.2k
        {
65
27.2k
            *ppszNextToken = pszStr;
66
27.2k
            return nullptr;
67
27.2k
        }
68
5.13M
        else if (!(ch == '\n' || ch == '\r' || ch == '\t' || ch == ' ' ||
69
3.34M
                   ch == ','))
70
2.38M
            break;
71
2.75M
        pszStr++;
72
2.75M
    }
73
74
2.38M
    const char *pszToken = pszStr;
75
54.5M
    while ((ch = *pszStr) != '\0')
76
54.5M
    {
77
54.5M
        if (ch == '\n' || ch == '\r' || ch == '\t' || ch == ' ' || ch == ',')
78
2.33M
        {
79
2.33M
            *ppszNextToken = pszStr;
80
2.33M
            return pszToken;
81
2.33M
        }
82
52.1M
        pszStr++;
83
52.1M
    }
84
45.5k
    *ppszNextToken = pszStr;
85
45.5k
    return pszToken;
86
2.38M
}
87
88
/************************************************************************/
89
/*                           BareGMLElement()                           */
90
/*                                                                      */
91
/*      Returns the passed string with any namespace prefix             */
92
/*      stripped off.                                                   */
93
/************************************************************************/
94
95
static const char *BareGMLElement(const char *pszInput)
96
97
2.40M
{
98
2.40M
    const char *pszReturn = strchr(pszInput, ':');
99
2.40M
    if (pszReturn == nullptr)
100
2.10M
        pszReturn = pszInput;
101
299k
    else
102
299k
        pszReturn++;
103
104
2.40M
    return pszReturn;
105
2.40M
}
106
107
/************************************************************************/
108
/*                          FindBareXMLChild()                          */
109
/*                                                                      */
110
/*      Find a child node with the indicated "bare" name, that is       */
111
/*      after any namespace qualifiers have been stripped off.          */
112
/************************************************************************/
113
114
static const CPLXMLNode *FindBareXMLChild(const CPLXMLNode *psParent,
115
                                          const char *pszBareName)
116
117
602k
{
118
602k
    const CPLXMLNode *psCandidate = psParent->psChild;
119
120
2.29M
    while (psCandidate != nullptr)
121
2.11M
    {
122
2.11M
        if (psCandidate->eType == CXT_Element &&
123
1.36M
            EQUAL(BareGMLElement(psCandidate->pszValue), pszBareName))
124
426k
            return psCandidate;
125
126
1.68M
        psCandidate = psCandidate->psNext;
127
1.68M
    }
128
129
176k
    return nullptr;
130
602k
}
131
132
/************************************************************************/
133
/*                           GetElementText()                           */
134
/************************************************************************/
135
136
static const char *GetElementText(const CPLXMLNode *psElement)
137
138
163k
{
139
163k
    if (psElement == nullptr)
140
0
        return nullptr;
141
142
163k
    const CPLXMLNode *psChild = psElement->psChild;
143
144
191k
    while (psChild != nullptr)
145
96.6k
    {
146
96.6k
        if (psChild->eType == CXT_Text)
147
69.2k
            return psChild->pszValue;
148
149
27.3k
        psChild = psChild->psNext;
150
27.3k
    }
151
152
94.7k
    return nullptr;
153
163k
}
154
155
/************************************************************************/
156
/*                          GetChildElement()                           */
157
/************************************************************************/
158
159
static const CPLXMLNode *GetChildElement(const CPLXMLNode *psElement)
160
161
62.2k
{
162
62.2k
    if (psElement == nullptr)
163
3.07k
        return nullptr;
164
165
59.2k
    const CPLXMLNode *psChild = psElement->psChild;
166
167
82.2k
    while (psChild != nullptr)
168
70.6k
    {
169
70.6k
        if (psChild->eType == CXT_Element)
170
47.6k
            return psChild;
171
172
23.0k
        psChild = psChild->psNext;
173
23.0k
    }
174
175
11.6k
    return nullptr;
176
59.2k
}
177
178
/************************************************************************/
179
/*                    GetElementOrientation()                           */
180
/*     Returns true for positive orientation.                           */
181
/************************************************************************/
182
183
static bool GetElementOrientation(const CPLXMLNode *psElement)
184
993
{
185
993
    if (psElement == nullptr)
186
0
        return true;
187
188
993
    const CPLXMLNode *psChild = psElement->psChild;
189
190
1.98k
    while (psChild != nullptr)
191
993
    {
192
993
        if (psChild->eType == CXT_Attribute &&
193
0
            EQUAL(psChild->pszValue, "orientation"))
194
0
            return EQUAL(psChild->psChild->pszValue, "+");
195
196
993
        psChild = psChild->psNext;
197
993
    }
198
199
993
    return true;
200
993
}
201
202
/************************************************************************/
203
/*                              AddPoint()                              */
204
/*                                                                      */
205
/*      Add a point to the passed geometry.                             */
206
/************************************************************************/
207
208
static bool AddPoint(OGRGeometry *poGeometry, double dfX, double dfY,
209
                     double dfZ, int nDimension)
210
211
1.10M
{
212
1.10M
    const OGRwkbGeometryType eType = wkbFlatten(poGeometry->getGeometryType());
213
1.10M
    if (eType == wkbPoint)
214
17.9k
    {
215
17.9k
        OGRPoint *poPoint = poGeometry->toPoint();
216
217
17.9k
        if (!poPoint->IsEmpty())
218
1.71k
        {
219
1.71k
            CPLError(CE_Failure, CPLE_AppDefined,
220
1.71k
                     "More than one coordinate for <Point> element.");
221
1.71k
            return false;
222
1.71k
        }
223
224
16.1k
        poPoint->setX(dfX);
225
16.1k
        poPoint->setY(dfY);
226
16.1k
        if (nDimension == 3)
227
8.51k
            poPoint->setZ(dfZ);
228
229
16.1k
        return true;
230
17.9k
    }
231
1.08M
    else if (eType == wkbLineString || eType == wkbCircularString)
232
1.08M
    {
233
1.08M
        OGRSimpleCurve *poCurve = poGeometry->toSimpleCurve();
234
1.08M
        if (nDimension == 3)
235
184k
            poCurve->addPoint(dfX, dfY, dfZ);
236
904k
        else
237
904k
            poCurve->addPoint(dfX, dfY);
238
239
1.08M
        return true;
240
1.08M
    }
241
242
0
    CPLAssert(false);
243
0
    return false;
244
1.10M
}
245
246
/************************************************************************/
247
/*                        ParseGMLCoordinates()                         */
248
/************************************************************************/
249
250
static bool ParseGMLCoordinates(const CPLXMLNode *psGeomNode,
251
                                OGRGeometry *poGeometry, int nSRSDimension)
252
253
147k
{
254
147k
    const CPLXMLNode *psCoordinates =
255
147k
        FindBareXMLChild(psGeomNode, "coordinates");
256
257
    /* -------------------------------------------------------------------- */
258
    /*      Handle <coordinates> case.                                      */
259
    /*      Note that we don't do a strict validation, so we accept and     */
260
    /*      sometimes generate output whereas we should just reject it.     */
261
    /* -------------------------------------------------------------------- */
262
147k
    if (psCoordinates != nullptr)
263
10.5k
    {
264
10.5k
        const char *pszCoordString = GetElementText(psCoordinates);
265
266
10.5k
        const char *pszDecimal =
267
10.5k
            CPLGetXMLValue(psCoordinates, "decimal", nullptr);
268
10.5k
        char chDecimal = '.';
269
10.5k
        if (pszDecimal != nullptr)
270
3.19k
        {
271
3.19k
            if (strlen(pszDecimal) != 1 ||
272
3.09k
                (pszDecimal[0] >= '0' && pszDecimal[0] <= '9'))
273
100
            {
274
100
                CPLError(CE_Failure, CPLE_AppDefined,
275
100
                         "Wrong value for decimal attribute");
276
100
                return false;
277
100
            }
278
3.09k
            chDecimal = pszDecimal[0];
279
3.09k
        }
280
281
10.4k
        const char *pszCS = CPLGetXMLValue(psCoordinates, "cs", nullptr);
282
10.4k
        char chCS = ',';
283
10.4k
        if (pszCS != nullptr)
284
687
        {
285
687
            if (strlen(pszCS) != 1 || (pszCS[0] >= '0' && pszCS[0] <= '9'))
286
464
            {
287
464
                CPLError(CE_Failure, CPLE_AppDefined,
288
464
                         "Wrong value for cs attribute");
289
464
                return false;
290
464
            }
291
223
            chCS = pszCS[0];
292
223
        }
293
10.0k
        const char *pszTS = CPLGetXMLValue(psCoordinates, "ts", nullptr);
294
10.0k
        char chTS = ' ';
295
10.0k
        if (pszTS != nullptr)
296
1.86k
        {
297
1.86k
            if (strlen(pszTS) != 1 || (pszTS[0] >= '0' && pszTS[0] <= '9'))
298
1.38k
            {
299
1.38k
                CPLError(CE_Failure, CPLE_AppDefined,
300
1.38k
                         "Wrong value for ts attribute");
301
1.38k
                return false;
302
1.38k
            }
303
482
            chTS = pszTS[0];
304
482
        }
305
306
8.64k
        if (pszCoordString == nullptr)
307
658
        {
308
658
            poGeometry->empty();
309
658
            return true;
310
658
        }
311
312
        // Skip leading whitespace. See
313
        // https://github.com/OSGeo/gdal/issues/5494
314
7.98k
        while (*pszCoordString != '\0' &&
315
7.91k
               isspace(static_cast<unsigned char>(*pszCoordString)))
316
0
        {
317
0
            pszCoordString++;
318
0
        }
319
320
7.98k
        int iCoord = 0;
321
7.98k
        const OGRwkbGeometryType eType =
322
7.98k
            wkbFlatten(poGeometry->getGeometryType());
323
7.98k
        OGRSimpleCurve *poCurve =
324
7.98k
            (eType == wkbLineString || eType == wkbCircularString)
325
7.98k
                ? poGeometry->toSimpleCurve()
326
7.98k
                : nullptr;
327
18.9k
        for (int iter = (eType == wkbPoint ? 1 : 0); iter < 2; iter++)
328
12.9k
        {
329
12.9k
            const char *pszStr = pszCoordString;
330
12.9k
            double dfX = 0;
331
12.9k
            double dfY = 0;
332
12.9k
            iCoord = 0;
333
662k
            while (*pszStr != '\0')
334
651k
            {
335
651k
                int nDimension = 2;
336
                // parse out 2 or 3 tuple.
337
651k
                if (iter == 1)
338
327k
                {
339
327k
                    if (chDecimal == '.')
340
297k
                        dfX = OGRFastAtof(pszStr);
341
29.8k
                    else
342
29.8k
                        dfX = CPLAtofDelim(pszStr, chDecimal);
343
327k
                }
344
6.37M
                while (*pszStr != '\0' && *pszStr != chCS &&
345
5.74M
                       !isspace(static_cast<unsigned char>(*pszStr)))
346
5.72M
                    pszStr++;
347
348
651k
                if (*pszStr == '\0')
349
481
                {
350
481
                    CPLError(CE_Failure, CPLE_AppDefined,
351
481
                             "Corrupt <coordinates> value.");
352
481
                    return false;
353
481
                }
354
650k
                else if (chCS == ',' && pszCS == nullptr &&
355
543k
                         isspace(static_cast<unsigned char>(*pszStr)))
356
4.38k
                {
357
                    // In theory, the coordinates inside a coordinate tuple
358
                    // should be separated by a comma. However it has been found
359
                    // in the wild that the coordinates are in rare cases
360
                    // separated by a space, and the tuples by a comma. See:
361
                    // https://52north.org/twiki/bin/view/Processing/WPS-IDWExtension-ObservationCollectionExample
362
                    // or
363
                    // http://agisdemo.faa.gov/aixmServices/getAllFeaturesByLocatorId?locatorId=DFW
364
4.38k
                    chCS = ' ';
365
4.38k
                    chTS = ',';
366
4.38k
                }
367
368
650k
                pszStr++;
369
370
650k
                if (iter == 1)
371
326k
                {
372
326k
                    if (chDecimal == '.')
373
297k
                        dfY = OGRFastAtof(pszStr);
374
29.3k
                    else
375
29.3k
                        dfY = CPLAtofDelim(pszStr, chDecimal);
376
326k
                }
377
1.96M
                while (*pszStr != '\0' && *pszStr != chCS && *pszStr != chTS &&
378
1.33M
                       !isspace(static_cast<unsigned char>(*pszStr)))
379
1.31M
                    pszStr++;
380
381
650k
                double dfZ = 0.0;
382
650k
                if (*pszStr == chCS)
383
616k
                {
384
616k
                    pszStr++;
385
616k
                    if (iter == 1)
386
306k
                    {
387
306k
                        if (chDecimal == '.')
388
286k
                            dfZ = OGRFastAtof(pszStr);
389
20.1k
                        else
390
20.1k
                            dfZ = CPLAtofDelim(pszStr, chDecimal);
391
306k
                    }
392
616k
                    nDimension = 3;
393
1.27M
                    while (*pszStr != '\0' && *pszStr != chCS &&
394
669k
                           *pszStr != chTS &&
395
663k
                           !isspace(static_cast<unsigned char>(*pszStr)))
396
656k
                        pszStr++;
397
616k
                }
398
399
650k
                if (*pszStr == chTS)
400
13.2k
                {
401
13.2k
                    pszStr++;
402
13.2k
                }
403
404
821k
                while (isspace(static_cast<unsigned char>(*pszStr)))
405
171k
                    pszStr++;
406
407
650k
                if (iter == 1)
408
326k
                {
409
326k
                    if (poCurve)
410
318k
                    {
411
318k
                        if (nDimension == 3)
412
303k
                            poCurve->setPoint(iCoord, dfX, dfY, dfZ);
413
14.4k
                        else
414
14.4k
                            poCurve->setPoint(iCoord, dfX, dfY);
415
318k
                    }
416
8.52k
                    else if (!AddPoint(poGeometry, dfX, dfY, dfZ, nDimension))
417
1.51k
                        return false;
418
326k
                }
419
420
649k
                iCoord++;
421
649k
            }
422
423
10.9k
            if (poCurve && iter == 0)
424
4.96k
            {
425
4.96k
                poCurve->setNumPoints(iCoord);
426
4.96k
            }
427
10.9k
        }
428
429
5.98k
        return iCoord > 0;
430
7.98k
    }
431
432
    /* -------------------------------------------------------------------- */
433
    /*      Is this a "pos"?  GML 3 construct.                              */
434
    /*      Parse if it exist a series of pos elements (this would allow    */
435
    /*      the correct parsing of gml3.1.1 geometries such as linestring    */
436
    /*      defined with pos elements.                                      */
437
    /* -------------------------------------------------------------------- */
438
137k
    bool bHasFoundPosElement = false;
439
726k
    for (const CPLXMLNode *psPos = psGeomNode->psChild; psPos != nullptr;
440
589k
         psPos = psPos->psNext)
441
680k
    {
442
680k
        if (psPos->eType != CXT_Element)
443
219k
            continue;
444
445
461k
        const char *pszSubElement = BareGMLElement(psPos->pszValue);
446
447
461k
        if (EQUAL(pszSubElement, "pointProperty"))
448
4.73k
        {
449
4.73k
            for (const CPLXMLNode *psPointPropertyIter = psPos->psChild;
450
25.0k
                 psPointPropertyIter != nullptr;
451
20.3k
                 psPointPropertyIter = psPointPropertyIter->psNext)
452
20.3k
            {
453
20.3k
                if (psPointPropertyIter->eType != CXT_Element)
454
6.60k
                    continue;
455
456
13.7k
                const char *pszBareElement =
457
13.7k
                    BareGMLElement(psPointPropertyIter->pszValue);
458
13.7k
                if (EQUAL(pszBareElement, "Point") ||
459
750
                    EQUAL(pszBareElement, "ElevatedPoint"))
460
13.0k
                {
461
13.0k
                    OGRPoint oPoint;
462
13.0k
                    if (ParseGMLCoordinates(psPointPropertyIter, &oPoint,
463
13.0k
                                            nSRSDimension))
464
4.11k
                    {
465
4.11k
                        const bool bSuccess = AddPoint(
466
4.11k
                            poGeometry, oPoint.getX(), oPoint.getY(),
467
4.11k
                            oPoint.getZ(), oPoint.getCoordinateDimension());
468
4.11k
                        if (bSuccess)
469
4.07k
                            bHasFoundPosElement = true;
470
45
                        else
471
45
                            return false;
472
4.11k
                    }
473
13.0k
                }
474
13.7k
            }
475
476
4.68k
            if (psPos->psChild && psPos->psChild->eType == CXT_Attribute &&
477
2.05k
                psPos->psChild->psNext == nullptr &&
478
1.45k
                strcmp(psPos->psChild->pszValue, "xlink:href") == 0)
479
789
            {
480
789
                CPLError(CE_Warning, CPLE_AppDefined,
481
789
                         "Cannot resolve xlink:href='%s'. "
482
789
                         "Try setting GML_SKIP_RESOLVE_ELEMS=NONE",
483
789
                         psPos->psChild->psChild->pszValue);
484
789
            }
485
486
4.68k
            continue;
487
4.73k
        }
488
489
456k
        if (!EQUAL(pszSubElement, "pos"))
490
333k
            continue;
491
492
123k
        const char *pszPos = GetElementText(psPos);
493
123k
        if (pszPos == nullptr)
494
91.2k
        {
495
91.2k
            poGeometry->empty();
496
91.2k
            return true;
497
91.2k
        }
498
499
32.2k
        const char *pszCur = pszPos;
500
32.2k
        const char *pszX = GMLGetCoordTokenPos(pszCur, &pszCur);
501
32.2k
        const char *pszY = (pszCur[0] != '\0')
502
32.2k
                               ? GMLGetCoordTokenPos(pszCur, &pszCur)
503
32.2k
                               : nullptr;
504
32.2k
        const char *pszZ = (pszCur[0] != '\0')
505
32.2k
                               ? GMLGetCoordTokenPos(pszCur, &pszCur)
506
32.2k
                               : nullptr;
507
508
32.2k
        if (pszY == nullptr)
509
64
        {
510
64
            CPLError(CE_Failure, CPLE_AppDefined,
511
64
                     "Did not get 2+ values in <gml:pos>%s</gml:pos> tuple.",
512
64
                     pszPos);
513
64
            return false;
514
64
        }
515
516
32.1k
        const double dfX = OGRFastAtof(pszX);
517
32.1k
        const double dfY = OGRFastAtof(pszY);
518
32.1k
        const double dfZ = (pszZ != nullptr) ? OGRFastAtof(pszZ) : 0.0;
519
32.1k
        const bool bSuccess =
520
32.1k
            AddPoint(poGeometry, dfX, dfY, dfZ, (pszZ != nullptr) ? 3 : 2);
521
522
32.1k
        if (bSuccess)
523
32.1k
            bHasFoundPosElement = true;
524
3
        else
525
3
            return false;
526
32.1k
    }
527
528
46.0k
    if (bHasFoundPosElement)
529
13.6k
        return true;
530
531
    /* -------------------------------------------------------------------- */
532
    /*      Is this a "posList"?  GML 3 construct (SF profile).             */
533
    /* -------------------------------------------------------------------- */
534
32.3k
    const CPLXMLNode *psPosList = FindBareXMLChild(psGeomNode, "posList");
535
536
32.3k
    if (psPosList != nullptr)
537
30.0k
    {
538
30.0k
        int nDimension = 2;
539
540
        // Try to detect the presence of an srsDimension attribute
541
        // This attribute is only available for gml3.1.1 but not
542
        // available for gml3.1 SF.
543
30.0k
        const char *pszSRSDimension =
544
30.0k
            CPLGetXMLValue(psPosList, "srsDimension", nullptr);
545
        // If not found at the posList level, try on the enclosing element.
546
30.0k
        if (pszSRSDimension == nullptr)
547
28.4k
            pszSRSDimension =
548
28.4k
                CPLGetXMLValue(psGeomNode, "srsDimension", nullptr);
549
30.0k
        if (pszSRSDimension != nullptr)
550
2.06k
            nDimension = atoi(pszSRSDimension);
551
28.0k
        else if (nSRSDimension != 0)
552
            // Or use one coming from a still higher level element (#5606).
553
11.0k
            nDimension = nSRSDimension;
554
555
30.0k
        if (nDimension != 2 && nDimension != 3)
556
164
        {
557
164
            CPLError(CE_Failure, CPLE_AppDefined,
558
164
                     "srsDimension = %d not supported", nDimension);
559
164
            return false;
560
164
        }
561
562
29.9k
        const char *pszPosList = GetElementText(psPosList);
563
29.9k
        if (pszPosList == nullptr)
564
1.03k
        {
565
1.03k
            poGeometry->empty();
566
1.03k
            return true;
567
1.03k
        }
568
569
28.8k
        bool bSuccess = false;
570
28.8k
        const char *pszCur = pszPosList;
571
1.09M
        while (true)
572
1.09M
        {
573
1.09M
            const char *pszX = GMLGetCoordTokenPos(pszCur, &pszCur);
574
1.09M
            if (pszX == nullptr && bSuccess)
575
24.2k
                break;
576
1.06M
            const char *pszY = (pszCur[0] != '\0')
577
1.06M
                                   ? GMLGetCoordTokenPos(pszCur, &pszCur)
578
1.06M
                                   : nullptr;
579
1.06M
            const char *pszZ = (nDimension == 3 && pszCur[0] != '\0')
580
1.06M
                                   ? GMLGetCoordTokenPos(pszCur, &pszCur)
581
1.06M
                                   : nullptr;
582
583
1.06M
            if (pszY == nullptr || (nDimension == 3 && pszZ == nullptr))
584
4.45k
            {
585
4.45k
                CPLError(CE_Failure, CPLE_AppDefined,
586
4.45k
                         "Did not get at least %d values or invalid number of "
587
4.45k
                         "set of coordinates <gml:posList>%s</gml:posList>",
588
4.45k
                         nDimension, pszPosList);
589
4.45k
                return false;
590
4.45k
            }
591
592
1.06M
            double dfX = OGRFastAtof(pszX);
593
1.06M
            double dfY = OGRFastAtof(pszY);
594
1.06M
            double dfZ = (pszZ != nullptr) ? OGRFastAtof(pszZ) : 0.0;
595
1.06M
            bSuccess = AddPoint(poGeometry, dfX, dfY, dfZ, nDimension);
596
597
1.06M
            if (!bSuccess || pszCur == nullptr)
598
150
                break;
599
1.06M
        }
600
601
24.4k
        return bSuccess;
602
28.8k
    }
603
604
    /* -------------------------------------------------------------------- */
605
    /*      Handle form with a list of <coord> items each with an <X>,      */
606
    /*      and <Y> element.                                                */
607
    /* -------------------------------------------------------------------- */
608
2.30k
    int iCoord = 0;
609
2.30k
    for (const CPLXMLNode *psCoordNode = psGeomNode->psChild;
610
21.6k
         psCoordNode != nullptr; psCoordNode = psCoordNode->psNext)
611
19.4k
    {
612
19.4k
        if (psCoordNode->eType != CXT_Element ||
613
10.8k
            !EQUAL(BareGMLElement(psCoordNode->pszValue), "coord"))
614
19.3k
            continue;
615
616
37
        const CPLXMLNode *psXNode = FindBareXMLChild(psCoordNode, "X");
617
37
        const CPLXMLNode *psYNode = FindBareXMLChild(psCoordNode, "Y");
618
37
        const CPLXMLNode *psZNode = FindBareXMLChild(psCoordNode, "Z");
619
620
37
        if (psXNode == nullptr || psYNode == nullptr ||
621
3
            GetElementText(psXNode) == nullptr ||
622
1
            GetElementText(psYNode) == nullptr ||
623
0
            (psZNode != nullptr && GetElementText(psZNode) == nullptr))
624
37
        {
625
37
            CPLError(CE_Failure, CPLE_AppDefined,
626
37
                     "Corrupt <coord> element, missing <X> or <Y> element?");
627
37
            return false;
628
37
        }
629
630
0
        double dfX = OGRFastAtof(GetElementText(psXNode));
631
0
        double dfY = OGRFastAtof(GetElementText(psYNode));
632
633
0
        int nDimension = 2;
634
0
        double dfZ = 0.0;
635
0
        if (psZNode != nullptr && GetElementText(psZNode) != nullptr)
636
0
        {
637
0
            dfZ = OGRFastAtof(GetElementText(psZNode));
638
0
            nDimension = 3;
639
0
        }
640
641
0
        if (!AddPoint(poGeometry, dfX, dfY, dfZ, nDimension))
642
0
            return false;
643
644
0
        iCoord++;
645
0
    }
646
647
2.26k
    return iCoord > 0;
648
2.30k
}
649
650
#ifdef HAVE_GEOS
651
/************************************************************************/
652
/*                         GML2FaceExtRing()                            */
653
/*                                                                      */
654
/*      Identifies the "good" Polygon within the collection returned    */
655
/*      by GEOSPolygonize()                                             */
656
/*      short rationale: GEOSPolygonize() will possibly return a        */
657
/*      collection of many Polygons; only one is the "good" one,        */
658
/*      (including both exterior- and interior-rings)                   */
659
/*      any other simply represents a single "hole", and should be      */
660
/*      consequently ignored at all.                                    */
661
/************************************************************************/
662
663
static std::unique_ptr<OGRPolygon> GML2FaceExtRing(const OGRGeometry *poGeom)
664
{
665
    const OGRGeometryCollection *poColl =
666
        dynamic_cast<const OGRGeometryCollection *>(poGeom);
667
    if (poColl == nullptr)
668
    {
669
        CPLError(CE_Fatal, CPLE_AppDefined,
670
                 "dynamic_cast failed.  Expected OGRGeometryCollection.");
671
        return nullptr;
672
    }
673
674
    const OGRPolygon *poPolygonExterior = nullptr;
675
    const OGRPolygon *poPolygonInterior = nullptr;
676
    int iExterior = 0;
677
    int iInterior = 0;
678
679
    for (const auto *poChild : *poColl)
680
    {
681
        // A collection of Polygons is expected to be found.
682
        if (wkbFlatten(poChild->getGeometryType()) == wkbPolygon)
683
        {
684
            const OGRPolygon *poPoly = poChild->toPolygon();
685
            if (poPoly->getNumInteriorRings() > 0)
686
            {
687
                poPolygonExterior = poPoly;
688
                iExterior++;
689
            }
690
            else
691
            {
692
                poPolygonInterior = poPoly;
693
                iInterior++;
694
            }
695
        }
696
        else
697
        {
698
            return nullptr;
699
        }
700
    }
701
702
    if (poPolygonInterior && iExterior == 0 && iInterior == 1)
703
    {
704
        // There is a single Polygon within the collection.
705
        return std::unique_ptr<OGRPolygon>(poPolygonInterior->clone());
706
    }
707
    else if (poPolygonExterior && iExterior == 1 &&
708
             iInterior == poColl->getNumGeometries() - 1)
709
    {
710
        // Return the unique Polygon containing holes.
711
        return std::unique_ptr<OGRPolygon>(poPolygonExterior->clone());
712
    }
713
714
    return nullptr;
715
}
716
#endif
717
718
/************************************************************************/
719
/*                GML2OGRGeometry_AddToCompositeCurve()                 */
720
/************************************************************************/
721
722
static bool
723
GML2OGRGeometry_AddToCompositeCurve(OGRCompoundCurve *poCC,
724
                                    std::unique_ptr<OGRGeometry> poGeom,
725
                                    bool &bChildrenAreAllLineString)
726
4.07k
{
727
4.07k
    if (poGeom == nullptr || !OGR_GT_IsCurve(poGeom->getGeometryType()))
728
97
    {
729
97
        CPLError(CE_Failure, CPLE_AppDefined,
730
97
                 "CompositeCurve: Got %s geometry as Member instead of a "
731
97
                 "curve.",
732
97
                 poGeom ? poGeom->getGeometryName() : "NULL");
733
97
        return false;
734
97
    }
735
736
    // Crazy but allowed by GML: composite in composite.
737
3.97k
    if (wkbFlatten(poGeom->getGeometryType()) == wkbCompoundCurve)
738
626
    {
739
626
        auto poCCChild = std::unique_ptr<OGRCompoundCurve>(
740
626
            poGeom.release()->toCompoundCurve());
741
4.11k
        while (poCCChild->getNumCurves() != 0)
742
3.48k
        {
743
3.48k
            auto poCurve = std::unique_ptr<OGRCurve>(poCCChild->stealCurve(0));
744
3.48k
            if (wkbFlatten(poCurve->getGeometryType()) != wkbLineString)
745
3.48k
                bChildrenAreAllLineString = false;
746
3.48k
            if (poCC->addCurve(std::move(poCurve)) != OGRERR_NONE)
747
1
            {
748
1
                return false;
749
1
            }
750
3.48k
        }
751
626
    }
752
3.35k
    else
753
3.35k
    {
754
3.35k
        if (wkbFlatten(poGeom->getGeometryType()) != wkbLineString)
755
2.02k
            bChildrenAreAllLineString = false;
756
757
3.35k
        auto poCurve = std::unique_ptr<OGRCurve>(poGeom.release()->toCurve());
758
3.35k
        if (poCC->addCurve(std::move(poCurve)) != OGRERR_NONE)
759
23
        {
760
23
            return false;
761
23
        }
762
3.35k
    }
763
3.95k
    return true;
764
3.97k
}
765
766
/************************************************************************/
767
/*                 GML2OGRGeometry_AddToMultiSurface()                  */
768
/************************************************************************/
769
770
static bool GML2OGRGeometry_AddToMultiSurface(
771
    OGRMultiSurface *poMS, std::unique_ptr<OGRGeometry> poGeom,
772
    const char *pszMemberElement, bool &bChildrenAreAllPolygons)
773
7.26k
{
774
7.26k
    if (poGeom == nullptr)
775
24
    {
776
24
        CPLError(CE_Failure, CPLE_AppDefined, "Invalid %s", pszMemberElement);
777
24
        return false;
778
24
    }
779
780
7.24k
    OGRwkbGeometryType eType = wkbFlatten(poGeom->getGeometryType());
781
7.24k
    if (eType == wkbPolygon || eType == wkbCurvePolygon)
782
6.36k
    {
783
6.36k
        if (eType != wkbPolygon)
784
0
            bChildrenAreAllPolygons = false;
785
786
6.36k
        if (poMS->addGeometry(std::move(poGeom)) != OGRERR_NONE)
787
0
        {
788
0
            return false;
789
0
        }
790
6.36k
    }
791
881
    else if (eType == wkbMultiPolygon || eType == wkbMultiSurface)
792
880
    {
793
880
        OGRMultiSurface *poMS2 = poGeom->toMultiSurface();
794
2.45k
        for (int i = 0; i < poMS2->getNumGeometries(); i++)
795
1.57k
        {
796
1.57k
            if (wkbFlatten(poMS2->getGeometryRef(i)->getGeometryType()) !=
797
1.57k
                wkbPolygon)
798
0
                bChildrenAreAllPolygons = false;
799
800
1.57k
            if (poMS->addGeometry(poMS2->getGeometryRef(i)) != OGRERR_NONE)
801
0
            {
802
0
                return false;
803
0
            }
804
1.57k
        }
805
880
    }
806
1
    else
807
1
    {
808
1
        CPLError(CE_Failure, CPLE_AppDefined, "Got %s geometry as %s.",
809
1
                 poGeom->getGeometryName(), pszMemberElement);
810
1
        return false;
811
1
    }
812
7.24k
    return true;
813
7.24k
}
814
815
/************************************************************************/
816
/*                           GetUOMInMetre()                            */
817
/************************************************************************/
818
819
static double GetUOMInMetre(const char *pszUnits, const char *pszAttribute,
820
                            const char *pszId)
821
174k
{
822
174k
    if (!pszUnits || EQUAL(pszUnits, "m"))
823
174k
        return 1.0;
824
825
0
    if (EQUAL(pszUnits, "km"))
826
0
        return 1000.0;
827
828
0
    if (EQUAL(pszUnits, "nm") || EQUAL(pszUnits, "[nmi_i]"))
829
0
        return CPLAtof(SRS_UL_INTL_NAUT_MILE_CONV);
830
831
0
    if (EQUAL(pszUnits, "mi"))
832
0
        return CPLAtof(SRS_UL_INTL_STAT_MILE_CONV);
833
834
0
    if (EQUAL(pszUnits, "ft"))
835
0
        return CPLAtof(SRS_UL_INTL_FOOT_CONV);
836
837
0
    if (pszId)
838
0
    {
839
0
        CPLError(CE_Warning, CPLE_AppDefined,
840
0
                 "GML geometry id='%s': Unhandled distance unit '%s' in "
841
0
                 "attribute '%s'",
842
0
                 pszId, pszUnits, pszAttribute);
843
0
    }
844
0
    else
845
0
    {
846
0
        CPLError(CE_Warning, CPLE_AppDefined,
847
0
                 "Unhandled distance unit '%s' in attribute '%s'", pszUnits,
848
0
                 pszAttribute);
849
0
    }
850
0
    return -1;
851
0
}
852
853
/************************************************************************/
854
/*                        StantardizeSemiMajor()                        */
855
/************************************************************************/
856
857
static double StantardizeSemiMajor(double dfSemiMajor)
858
8.40k
{
859
    // Standardize on OGR_GREATCIRCLE_DEFAULT_RADIUS for Earth ellipsoids.
860
8.40k
    if (std::fabs(dfSemiMajor - OGR_GREATCIRCLE_DEFAULT_RADIUS) <
861
8.40k
        0.05 * OGR_GREATCIRCLE_DEFAULT_RADIUS)
862
8.12k
        dfSemiMajor = OGR_GREATCIRCLE_DEFAULT_RADIUS;
863
8.40k
    return dfSemiMajor;
864
8.40k
}
865
866
/************************************************************************/
867
/*                      GML2OGRGeometry_XMLNode()                       */
868
/*                                                                      */
869
/*      Translates the passed XMLnode and its children into an         */
870
/*      OGRGeometry.  This is used recursively for geometry             */
871
/*      collections.                                                    */
872
/************************************************************************/
873
874
static std::unique_ptr<OGRGeometry> GML2OGRGeometry_XMLNode_Internal(
875
    const CPLXMLNode *psNode, const char *pszId,
876
    int nPseudoBoolGetSecondaryGeometryOption, int nRecLevel, int nSRSDimension,
877
    const char *pszSRSName, OGRGML_SRSCache *hSRSCache, bool bIgnoreGSG = false,
878
    bool bOrientation = true, bool bFaceHoleNegative = false);
879
880
OGRGeometry *GML2OGRGeometry_XMLNode(const CPLXMLNode *psNode,
881
                                     int nPseudoBoolGetSecondaryGeometryOption,
882
                                     OGRGML_SRSCache *hSRSCache, int nRecLevel,
883
                                     int nSRSDimension, bool bIgnoreGSG,
884
                                     bool bOrientation, bool bFaceHoleNegative,
885
                                     const char *pszId)
886
887
66.2k
{
888
66.2k
    return GML2OGRGeometry_XMLNode_Internal(
889
66.2k
               psNode, pszId, nPseudoBoolGetSecondaryGeometryOption, nRecLevel,
890
66.2k
               nSRSDimension, nullptr, hSRSCache, bIgnoreGSG, bOrientation,
891
66.2k
               bFaceHoleNegative)
892
66.2k
        .release();
893
66.2k
}
894
895
static void ReportError(const char *pszId, CPLErr eErr, const char *fmt, ...)
896
    CPL_PRINT_FUNC_FORMAT(3, 4);
897
898
static void ReportError(const char *pszId, CPLErr eErr, const char *fmt, ...)
899
19.3k
{
900
19.3k
    va_list ap;
901
19.3k
    va_start(ap, fmt);
902
19.3k
    if (pszId)
903
149
    {
904
149
        std::string osMsg("GML geometry id='");
905
149
        osMsg += pszId;
906
149
        osMsg += "': ";
907
149
        osMsg += CPLString().vPrintf(fmt, ap);
908
149
        CPLError(eErr, CPLE_AppDefined, "%s", osMsg.c_str());
909
149
    }
910
19.2k
    else
911
19.2k
    {
912
19.2k
        CPLErrorV(eErr, CPLE_AppDefined, fmt, ap);
913
19.2k
    }
914
19.3k
    va_end(ap);
915
19.3k
}
916
917
static std::unique_ptr<OGRGeometry> GML2OGRGeometry_XMLNode_Internal(
918
    const CPLXMLNode *psNode, const char *pszId,
919
    int nPseudoBoolGetSecondaryGeometryOption, int nRecLevel, int nSRSDimension,
920
    const char *pszSRSName, OGRGML_SRSCache *hSRSCache, bool bIgnoreGSG,
921
    bool bOrientation, bool bFaceHoleNegative)
922
229k
{
923
    // constexpr bool bCastToLinearTypeIfPossible = true;  // Hard-coded for
924
    // now.
925
926
    // We need this nRecLevel == 0 check, otherwise this could result in
927
    // multiple revisit of the same node, and exponential complexity.
928
229k
    if (nRecLevel == 0 && psNode != nullptr &&
929
66.2k
        strcmp(psNode->pszValue, "?xml") == 0)
930
4.89k
        psNode = psNode->psNext;
931
229k
    while (psNode != nullptr && psNode->eType == CXT_Comment)
932
691
        psNode = psNode->psNext;
933
229k
    if (psNode == nullptr)
934
52
        return nullptr;
935
936
229k
    const char *pszSRSDimension =
937
229k
        CPLGetXMLValue(psNode, "srsDimension", nullptr);
938
229k
    if (pszSRSDimension != nullptr)
939
8.64k
        nSRSDimension = atoi(pszSRSDimension);
940
941
229k
    if (pszSRSName == nullptr)
942
102k
    {
943
102k
        pszSRSName = CPLGetXMLValue(psNode, "srsName", nullptr);
944
945
102k
        if (pszSRSName && nSRSDimension == 0)
946
41.3k
        {
947
41.3k
            auto entry = OGRGML_SRSCache_GetInfo(hSRSCache, pszSRSName);
948
41.3k
            if (entry)
949
16.6k
                nSRSDimension = entry->nAxisCount;
950
41.3k
        }
951
102k
    }
952
953
229k
    if (!pszId && nRecLevel == 0)
954
66.1k
    {
955
66.1k
        pszId = CPLGetXMLValue(psNode, "gml:id", nullptr);
956
66.1k
    }
957
958
229k
    const char *pszBaseGeometry = BareGMLElement(psNode->pszValue);
959
229k
    if (nPseudoBoolGetSecondaryGeometryOption < 0)
960
55.6k
        nPseudoBoolGetSecondaryGeometryOption =
961
55.6k
            CPLTestBool(CPLGetConfigOption("GML_GET_SECONDARY_GEOM", "NO"));
962
229k
    bool bGetSecondaryGeometry =
963
229k
        bIgnoreGSG ? false : CPL_TO_BOOL(nPseudoBoolGetSecondaryGeometryOption);
964
965
229k
#define ReportFailure(...) ReportError(pszId, CE_Failure, __VA_ARGS__)
966
967
229k
#define ReportWarning(...) ReportError(pszId, CE_Warning, __VA_ARGS__)
968
969
    // Arbitrary value, but certainly large enough for reasonable usages.
970
229k
    if (nRecLevel == 32)
971
5
    {
972
5
        ReportFailure(
973
5
            "Too many recursion levels (%d) while parsing GML geometry.",
974
5
            nRecLevel);
975
5
        return nullptr;
976
5
    }
977
978
229k
    if (bGetSecondaryGeometry)
979
0
        if (!(EQUAL(pszBaseGeometry, "directedEdge") ||
980
0
              EQUAL(pszBaseGeometry, "TopoCurve")))
981
0
            return nullptr;
982
983
    /* -------------------------------------------------------------------- */
984
    /*      Polygon / PolygonPatch / Rectangle                              */
985
    /* -------------------------------------------------------------------- */
986
229k
    if (EQUAL(pszBaseGeometry, "Polygon") ||
987
205k
        EQUAL(pszBaseGeometry, "PolygonPatch") ||
988
205k
        EQUAL(pszBaseGeometry, "Rectangle"))
989
23.5k
    {
990
        // Find outer ring.
991
23.5k
        const CPLXMLNode *psChild = FindBareXMLChild(psNode, "outerBoundaryIs");
992
23.5k
        if (psChild == nullptr)
993
23.4k
            psChild = FindBareXMLChild(psNode, "exterior");
994
995
23.5k
        psChild = GetChildElement(psChild);
996
23.5k
        if (psChild == nullptr)
997
647
        {
998
            // <gml:Polygon/> is invalid GML2, but valid GML3, so be tolerant.
999
647
            return std::make_unique<OGRPolygon>();
1000
647
        }
1001
1002
        // Translate outer ring and add to polygon.
1003
22.8k
        auto poGeom = GML2OGRGeometry_XMLNode_Internal(
1004
22.8k
            psChild, pszId, nPseudoBoolGetSecondaryGeometryOption,
1005
22.8k
            nRecLevel + 1, nSRSDimension, pszSRSName, hSRSCache);
1006
22.8k
        if (poGeom == nullptr)
1007
438
        {
1008
438
            ReportFailure("Invalid exterior ring");
1009
438
            return nullptr;
1010
438
        }
1011
1012
22.4k
        if (!OGR_GT_IsCurve(poGeom->getGeometryType()))
1013
7
        {
1014
7
            ReportFailure("%s: Got %s geometry as outerBoundaryIs.",
1015
7
                          pszBaseGeometry, poGeom->getGeometryName());
1016
7
            return nullptr;
1017
7
        }
1018
1019
22.4k
        if (wkbFlatten(poGeom->getGeometryType()) == wkbLineString &&
1020
22.4k
            !EQUAL(poGeom->getGeometryName(), "LINEARRING"))
1021
2
        {
1022
2
            OGRCurve *poCurve = poGeom.release()->toCurve();
1023
2
            auto poLinearRing = OGRCurve::CastToLinearRing(poCurve);
1024
2
            if (!poLinearRing)
1025
2
                return nullptr;
1026
0
            poGeom.reset(poLinearRing);
1027
0
        }
1028
1029
22.4k
        std::unique_ptr<OGRCurvePolygon> poCP;
1030
22.4k
        bool bIsPolygon = false;
1031
22.4k
        assert(poGeom);  // to please cppcheck
1032
22.4k
        if (EQUAL(poGeom->getGeometryName(), "LINEARRING"))
1033
22.4k
        {
1034
22.4k
            poCP = std::make_unique<OGRPolygon>();
1035
22.4k
            bIsPolygon = true;
1036
22.4k
        }
1037
1
        else
1038
1
        {
1039
1
            poCP = std::make_unique<OGRCurvePolygon>();
1040
1
            bIsPolygon = false;
1041
1
        }
1042
1043
22.4k
        {
1044
22.4k
            auto poCurve =
1045
22.4k
                std::unique_ptr<OGRCurve>(poGeom.release()->toCurve());
1046
22.4k
            if (poCP->addRing(std::move(poCurve)) != OGRERR_NONE)
1047
0
            {
1048
0
                return nullptr;
1049
0
            }
1050
22.4k
        }
1051
1052
        // Find all inner rings
1053
100k
        for (psChild = psNode->psChild; psChild != nullptr;
1054
78.4k
             psChild = psChild->psNext)
1055
78.4k
        {
1056
78.4k
            if (psChild->eType == CXT_Element &&
1057
23.8k
                (EQUAL(BareGMLElement(psChild->pszValue), "innerBoundaryIs") ||
1058
23.8k
                 EQUAL(BareGMLElement(psChild->pszValue), "interior")))
1059
0
            {
1060
0
                const CPLXMLNode *psInteriorChild = GetChildElement(psChild);
1061
0
                std::unique_ptr<OGRGeometry> poGeomInterior;
1062
0
                if (psInteriorChild != nullptr)
1063
0
                    poGeomInterior = GML2OGRGeometry_XMLNode_Internal(
1064
0
                        psInteriorChild, pszId,
1065
0
                        nPseudoBoolGetSecondaryGeometryOption, nRecLevel + 1,
1066
0
                        nSRSDimension, pszSRSName, hSRSCache);
1067
0
                if (poGeomInterior == nullptr)
1068
0
                {
1069
0
                    ReportFailure("Invalid interior ring");
1070
0
                    return nullptr;
1071
0
                }
1072
1073
0
                if (!OGR_GT_IsCurve(poGeomInterior->getGeometryType()))
1074
0
                {
1075
0
                    ReportFailure("%s: Got %s geometry as innerBoundaryIs.",
1076
0
                                  pszBaseGeometry,
1077
0
                                  poGeomInterior->getGeometryName());
1078
0
                    return nullptr;
1079
0
                }
1080
1081
0
                if (bIsPolygon)
1082
0
                {
1083
0
                    if (!EQUAL(poGeomInterior->getGeometryName(), "LINEARRING"))
1084
0
                    {
1085
0
                        if (wkbFlatten(poGeomInterior->getGeometryType()) ==
1086
0
                            wkbLineString)
1087
0
                        {
1088
0
                            OGRLineString *poLS =
1089
0
                                poGeomInterior.release()->toLineString();
1090
0
                            auto poLinearRing =
1091
0
                                OGRCurve::CastToLinearRing(poLS);
1092
0
                            if (!poLinearRing)
1093
0
                                return nullptr;
1094
0
                            poGeomInterior.reset(poLinearRing);
1095
0
                        }
1096
0
                        else
1097
0
                        {
1098
                            // Might fail if some rings are not closed.
1099
                            // We used to be tolerant about that with Polygon.
1100
                            // but we have become stricter with CurvePolygon.
1101
0
                            auto poCPNew = std::unique_ptr<OGRCurvePolygon>(
1102
0
                                OGRSurface::CastToCurvePolygon(poCP.release()));
1103
0
                            if (!poCPNew)
1104
0
                            {
1105
0
                                return nullptr;
1106
0
                            }
1107
0
                            poCP = std::move(poCPNew);
1108
0
                            bIsPolygon = false;
1109
0
                        }
1110
0
                    }
1111
0
                }
1112
0
                else
1113
0
                {
1114
0
                    if (EQUAL(poGeomInterior->getGeometryName(), "LINEARRING"))
1115
0
                    {
1116
0
                        OGRCurve *poCurve = poGeomInterior.release()->toCurve();
1117
0
                        poGeomInterior.reset(
1118
0
                            OGRCurve::CastToLineString(poCurve));
1119
0
                    }
1120
0
                }
1121
0
                auto poCurve = std::unique_ptr<OGRCurve>(
1122
0
                    poGeomInterior.release()->toCurve());
1123
0
                if (poCP->addRing(std::move(poCurve)) != OGRERR_NONE)
1124
0
                {
1125
0
                    return nullptr;
1126
0
                }
1127
0
            }
1128
78.4k
        }
1129
1130
22.4k
        return poCP;
1131
22.4k
    }
1132
1133
    /* -------------------------------------------------------------------- */
1134
    /*      Triangle                                                        */
1135
    /* -------------------------------------------------------------------- */
1136
1137
205k
    if (EQUAL(pszBaseGeometry, "Triangle"))
1138
21
    {
1139
        // Find outer ring.
1140
21
        const CPLXMLNode *psChild = FindBareXMLChild(psNode, "exterior");
1141
21
        if (!psChild)
1142
21
            return nullptr;
1143
1144
0
        psChild = GetChildElement(psChild);
1145
0
        if (psChild == nullptr)
1146
0
        {
1147
0
            ReportFailure("Empty Triangle");
1148
0
            return std::make_unique<OGRTriangle>();
1149
0
        }
1150
1151
        // Translate outer ring and add to Triangle.
1152
0
        auto poGeom = GML2OGRGeometry_XMLNode_Internal(
1153
0
            psChild, pszId, nPseudoBoolGetSecondaryGeometryOption,
1154
0
            nRecLevel + 1, nSRSDimension, pszSRSName, hSRSCache);
1155
0
        if (poGeom == nullptr)
1156
0
        {
1157
0
            ReportFailure("Invalid exterior ring");
1158
0
            return nullptr;
1159
0
        }
1160
1161
0
        if (!OGR_GT_IsCurve(poGeom->getGeometryType()))
1162
0
        {
1163
0
            ReportFailure("%s: Got %s geometry as outerBoundaryIs.",
1164
0
                          pszBaseGeometry, poGeom->getGeometryName());
1165
0
            return nullptr;
1166
0
        }
1167
1168
0
        if (wkbFlatten(poGeom->getGeometryType()) == wkbLineString &&
1169
0
            !EQUAL(poGeom->getGeometryName(), "LINEARRING"))
1170
0
        {
1171
0
            poGeom.reset(
1172
0
                OGRCurve::CastToLinearRing(poGeom.release()->toCurve()));
1173
0
        }
1174
1175
0
        if (poGeom == nullptr ||
1176
0
            !EQUAL(poGeom->getGeometryName(), "LINEARRING"))
1177
0
        {
1178
0
            return nullptr;
1179
0
        }
1180
1181
0
        auto poTriangle = std::make_unique<OGRTriangle>();
1182
0
        auto poCurve = std::unique_ptr<OGRCurve>(poGeom.release()->toCurve());
1183
0
        if (poTriangle->addRing(std::move(poCurve)) != OGRERR_NONE)
1184
0
        {
1185
0
            return nullptr;
1186
0
        }
1187
1188
0
        return poTriangle;
1189
0
    }
1190
1191
    /* -------------------------------------------------------------------- */
1192
    /*      LinearRing                                                      */
1193
    /* -------------------------------------------------------------------- */
1194
205k
    if (EQUAL(pszBaseGeometry, "LinearRing"))
1195
30.0k
    {
1196
30.0k
        auto poLinearRing = std::make_unique<OGRLinearRing>();
1197
1198
30.0k
        if (!ParseGMLCoordinates(psNode, poLinearRing.get(), nSRSDimension))
1199
1.07k
        {
1200
1.07k
            return nullptr;
1201
1.07k
        }
1202
1203
28.9k
        return poLinearRing;
1204
30.0k
    }
1205
1206
175k
    const auto storeArcByCenterPointParameters =
1207
175k
        [pszId, &hSRSCache](const CPLXMLNode *psChild, const char *l_pszSRSName,
1208
175k
                            bool &bIsApproximateArc,
1209
175k
                            double &dfLastCurveApproximateArcRadius,
1210
175k
                            bool &bLastCurveWasApproximateArcInvertedAxisOrder,
1211
175k
                            double &dfSemiMajor)
1212
175k
    {
1213
79.1k
        const CPLXMLNode *psRadius = FindBareXMLChild(psChild, "radius");
1214
79.1k
        if (psRadius && psRadius->eType == CXT_Element)
1215
79.1k
        {
1216
79.1k
            const char *pszUnits = CPLGetXMLValue(psRadius, "uom", nullptr);
1217
79.1k
            const double dfUOMConv = GetUOMInMetre(pszUnits, "radius", pszId);
1218
79.1k
            const double dfRadiusRaw =
1219
79.1k
                CPLAtof(CPLGetXMLValue(psRadius, nullptr, "0"));
1220
79.1k
            const double dfRadius =
1221
79.1k
                dfUOMConv > 0 ? dfRadiusRaw * dfUOMConv : dfRadiusRaw;
1222
79.1k
            bool bSRSUnitIsDegree = false;
1223
79.1k
            bool bInvertedAxisOrder = false;
1224
79.1k
            if (l_pszSRSName != nullptr)
1225
75.9k
            {
1226
75.9k
                auto entry = OGRGML_SRSCache_GetInfo(hSRSCache, l_pszSRSName);
1227
75.9k
                if (entry && entry->bIsGeographic)
1228
155
                {
1229
155
                    bInvertedAxisOrder = entry->bInvertedAxisOrder;
1230
155
                    dfSemiMajor = StantardizeSemiMajor(entry->dfSemiMajor);
1231
155
                    bSRSUnitIsDegree = entry->bAngularUnitIsDegree;
1232
155
                }
1233
75.9k
            }
1234
79.1k
            if (bSRSUnitIsDegree && dfUOMConv > 0)
1235
80
            {
1236
80
                bIsApproximateArc = true;
1237
80
                dfLastCurveApproximateArcRadius = dfRadius;
1238
80
                bLastCurveWasApproximateArcInvertedAxisOrder =
1239
80
                    bInvertedAxisOrder;
1240
80
            }
1241
79.1k
        }
1242
79.1k
    };
1243
1244
175k
    const auto connectArcByCenterPointToOtherSegments =
1245
175k
        [](OGRGeometry *poGeom, OGRCompoundCurve *poCC,
1246
175k
           const bool bIsApproximateArc, const bool bLastCurveWasApproximateArc,
1247
175k
           const double dfLastCurveApproximateArcRadius,
1248
175k
           const bool bLastCurveWasApproximateArcInvertedAxisOrder,
1249
175k
           const double dfSemiMajor)
1250
175k
    {
1251
76.2k
        if (bIsApproximateArc)
1252
1
        {
1253
1
            if (poGeom->getGeometryType() == wkbLineString)
1254
1
            {
1255
1
                OGRCurve *poPreviousCurve =
1256
1
                    poCC->getCurve(poCC->getNumCurves() - 1);
1257
1
                OGRLineString *poLS = poGeom->toLineString();
1258
1
                if (poPreviousCurve->getNumPoints() >= 2 &&
1259
0
                    poLS->getNumPoints() >= 2)
1260
0
                {
1261
0
                    OGRPoint p;
1262
0
                    OGRPoint p2;
1263
0
                    poPreviousCurve->EndPoint(&p);
1264
0
                    poLS->StartPoint(&p2);
1265
0
                    double dfDistance = 0.0;
1266
0
                    if (bLastCurveWasApproximateArcInvertedAxisOrder)
1267
0
                        dfDistance = OGR_GreatCircle_Distance(
1268
0
                            p.getX(), p.getY(), p2.getX(), p2.getY(),
1269
0
                            dfSemiMajor);
1270
0
                    else
1271
0
                        dfDistance = OGR_GreatCircle_Distance(
1272
0
                            p.getY(), p.getX(), p2.getY(), p2.getX(),
1273
0
                            dfSemiMajor);
1274
                    // CPLDebug("OGR", "%f %f",
1275
                    //          dfDistance,
1276
                    //          dfLastCurveApproximateArcRadius
1277
                    //          / 10.0 );
1278
0
                    if (dfDistance < dfLastCurveApproximateArcRadius / 5.0)
1279
0
                    {
1280
0
                        CPLDebug("OGR", "Moving approximate start of "
1281
0
                                        "ArcByCenterPoint to end of "
1282
0
                                        "previous curve");
1283
0
                        poLS->setPoint(0, &p);
1284
0
                    }
1285
0
                }
1286
1
            }
1287
1
        }
1288
76.2k
        else if (bLastCurveWasApproximateArc)
1289
0
        {
1290
0
            OGRCurve *poPreviousCurve =
1291
0
                poCC->getCurve(poCC->getNumCurves() - 1);
1292
0
            if (poPreviousCurve->getGeometryType() == wkbLineString)
1293
0
            {
1294
0
                OGRLineString *poLS = poPreviousCurve->toLineString();
1295
0
                OGRCurve *poAsCurve = poGeom->toCurve();
1296
1297
0
                if (poLS->getNumPoints() >= 2 && poAsCurve->getNumPoints() >= 2)
1298
0
                {
1299
0
                    OGRPoint p;
1300
0
                    OGRPoint p2;
1301
0
                    poAsCurve->StartPoint(&p);
1302
0
                    poLS->EndPoint(&p2);
1303
0
                    double dfDistance = 0.0;
1304
0
                    if (bLastCurveWasApproximateArcInvertedAxisOrder)
1305
0
                        dfDistance = OGR_GreatCircle_Distance(
1306
0
                            p.getX(), p.getY(), p2.getX(), p2.getY(),
1307
0
                            dfSemiMajor);
1308
0
                    else
1309
0
                        dfDistance = OGR_GreatCircle_Distance(
1310
0
                            p.getY(), p.getX(), p2.getY(), p2.getX(),
1311
0
                            dfSemiMajor);
1312
                    // CPLDebug(
1313
                    //    "OGR", "%f %f",
1314
                    //    dfDistance,
1315
                    //    dfLastCurveApproximateArcRadius / 10.0 );
1316
1317
                    // "A-311 WHEELER AFB OAHU, HI.xml" needs more
1318
                    // than 10%.
1319
0
                    if (dfDistance < dfLastCurveApproximateArcRadius / 5.0)
1320
0
                    {
1321
0
                        CPLDebug("OGR", "Moving approximate end of last "
1322
0
                                        "ArcByCenterPoint to start of the "
1323
0
                                        "current curve");
1324
0
                        poLS->setPoint(poLS->getNumPoints() - 1, &p);
1325
0
                    }
1326
0
                }
1327
0
            }
1328
0
        }
1329
76.2k
    };
1330
1331
    /* -------------------------------------------------------------------- */
1332
    /*      Ring GML3                                                       */
1333
    /* -------------------------------------------------------------------- */
1334
175k
    if (EQUAL(pszBaseGeometry, "Ring"))
1335
3.19k
    {
1336
3.19k
        std::unique_ptr<OGRCurve> poRing;
1337
3.19k
        std::unique_ptr<OGRCompoundCurve> poCC;
1338
3.19k
        bool bChildrenAreAllLineString = true;
1339
1340
3.19k
        bool bLastCurveWasApproximateArc = false;
1341
3.19k
        bool bLastCurveWasApproximateArcInvertedAxisOrder = false;
1342
3.19k
        double dfLastCurveApproximateArcRadius = 0.0;
1343
1344
3.19k
        bool bIsFirstChild = true;
1345
3.19k
        bool bFirstChildIsApproximateArc = false;
1346
3.19k
        double dfFirstChildApproximateArcRadius = 0.0;
1347
3.19k
        bool bFirstChildWasApproximateArcInvertedAxisOrder = false;
1348
1349
3.19k
        double dfSemiMajor = OGR_GREATCIRCLE_DEFAULT_RADIUS;
1350
1351
12.9k
        for (const CPLXMLNode *psChild = psNode->psChild; psChild != nullptr;
1352
9.76k
             psChild = psChild->psNext)
1353
9.99k
        {
1354
9.99k
            if (psChild->eType == CXT_Element &&
1355
5.67k
                EQUAL(BareGMLElement(psChild->pszValue), "curveMember"))
1356
5.02k
            {
1357
5.02k
                const CPLXMLNode *psCurveChild = GetChildElement(psChild);
1358
5.02k
                std::unique_ptr<OGRGeometry> poGeom;
1359
5.02k
                if (psCurveChild != nullptr)
1360
5.01k
                {
1361
5.01k
                    poGeom = GML2OGRGeometry_XMLNode_Internal(
1362
5.01k
                        psCurveChild, pszId,
1363
5.01k
                        nPseudoBoolGetSecondaryGeometryOption, nRecLevel + 1,
1364
5.01k
                        nSRSDimension, pszSRSName, hSRSCache);
1365
5.01k
                }
1366
7
                else
1367
7
                {
1368
7
                    if (psChild->psChild &&
1369
7
                        psChild->psChild->eType == CXT_Attribute &&
1370
0
                        psChild->psChild->psNext == nullptr &&
1371
0
                        strcmp(psChild->psChild->pszValue, "xlink:href") == 0)
1372
0
                    {
1373
0
                        ReportWarning("Cannot resolve xlink:href='%s'. "
1374
0
                                      "Try setting GML_SKIP_RESOLVE_ELEMS=NONE",
1375
0
                                      psChild->psChild->psChild->pszValue);
1376
0
                    }
1377
7
                    return nullptr;
1378
7
                }
1379
1380
                // Try to join multiline string to one linestring.
1381
5.01k
                if (poGeom &&
1382
4.89k
                    wkbFlatten(poGeom->getGeometryType()) == wkbMultiLineString)
1383
1.43k
                {
1384
1.43k
                    poGeom.reset(OGRGeometryFactory::forceToLineString(
1385
1.43k
                        poGeom.release(), false));
1386
1.43k
                }
1387
1388
5.01k
                if (poGeom == nullptr ||
1389
4.89k
                    !OGR_GT_IsCurve(poGeom->getGeometryType()))
1390
204
                {
1391
204
                    return nullptr;
1392
204
                }
1393
1394
4.81k
                if (wkbFlatten(poGeom->getGeometryType()) != wkbLineString)
1395
3.43k
                    bChildrenAreAllLineString = false;
1396
1397
                // Ad-hoc logic to handle nicely connecting ArcByCenterPoint
1398
                // with consecutive curves, as found in some AIXM files.
1399
4.81k
                bool bIsApproximateArc = false;
1400
4.81k
                const CPLXMLNode *psChild2, *psChild3;
1401
4.81k
                if (strcmp(BareGMLElement(psCurveChild->pszValue), "Curve") ==
1402
4.81k
                        0 &&
1403
478
                    (psChild2 = GetChildElement(psCurveChild)) != nullptr &&
1404
478
                    strcmp(BareGMLElement(psChild2->pszValue), "segments") ==
1405
478
                        0 &&
1406
347
                    (psChild3 = GetChildElement(psChild2)) != nullptr &&
1407
340
                    strcmp(BareGMLElement(psChild3->pszValue),
1408
340
                           "ArcByCenterPoint") == 0)
1409
276
                {
1410
276
                    storeArcByCenterPointParameters(
1411
276
                        psChild3, pszSRSName, bIsApproximateArc,
1412
276
                        dfLastCurveApproximateArcRadius,
1413
276
                        bLastCurveWasApproximateArcInvertedAxisOrder,
1414
276
                        dfSemiMajor);
1415
276
                    if (bIsFirstChild && bIsApproximateArc)
1416
0
                    {
1417
0
                        bFirstChildIsApproximateArc = true;
1418
0
                        dfFirstChildApproximateArcRadius =
1419
0
                            dfLastCurveApproximateArcRadius;
1420
0
                        bFirstChildWasApproximateArcInvertedAxisOrder =
1421
0
                            bLastCurveWasApproximateArcInvertedAxisOrder;
1422
0
                    }
1423
276
                    else if (psChild3->psNext)
1424
276
                    {
1425
276
                        bIsApproximateArc = false;
1426
276
                    }
1427
276
                }
1428
4.81k
                bIsFirstChild = false;
1429
1430
4.81k
                if (poCC == nullptr && poRing == nullptr)
1431
2.98k
                {
1432
2.98k
                    poRing.reset(poGeom.release()->toCurve());
1433
2.98k
                }
1434
1.82k
                else
1435
1.82k
                {
1436
1.82k
                    if (poCC == nullptr)
1437
957
                    {
1438
957
                        poCC = std::make_unique<OGRCompoundCurve>();
1439
957
                        bool bIgnored = false;
1440
957
                        if (!GML2OGRGeometry_AddToCompositeCurve(
1441
957
                                poCC.get(), std::move(poRing), bIgnored))
1442
2
                        {
1443
2
                            return nullptr;
1444
2
                        }
1445
955
                        poRing.reset();
1446
955
                    }
1447
1448
1.82k
                    connectArcByCenterPointToOtherSegments(
1449
1.82k
                        poGeom.get(), poCC.get(), bIsApproximateArc,
1450
1.82k
                        bLastCurveWasApproximateArc,
1451
1.82k
                        dfLastCurveApproximateArcRadius,
1452
1.82k
                        bLastCurveWasApproximateArcInvertedAxisOrder,
1453
1.82k
                        dfSemiMajor);
1454
1455
1.82k
                    auto poCurve =
1456
1.82k
                        std::unique_ptr<OGRCurve>(poGeom.release()->toCurve());
1457
1458
1.82k
                    bool bIgnored = false;
1459
1.82k
                    if (!GML2OGRGeometry_AddToCompositeCurve(
1460
1.82k
                            poCC.get(), std::move(poCurve), bIgnored))
1461
16
                    {
1462
16
                        return nullptr;
1463
16
                    }
1464
1.82k
                }
1465
1466
4.79k
                bLastCurveWasApproximateArc = bIsApproximateArc;
1467
4.79k
            }
1468
9.99k
        }
1469
1470
        /* Detect if the last object in the following hierarchy is a
1471
           ArcByCenterPoint <gml:Ring> <gml:curveMember> (may be repeated)
1472
                    <gml:Curve>
1473
                        <gml:segments>
1474
                            ....
1475
                            <gml:ArcByCenterPoint ... />
1476
                        </gml:segments>
1477
                    </gml:Curve>
1478
                </gml:curveMember>
1479
            </gml:Ring>
1480
        */
1481
2.96k
        bool bLastChildIsApproximateArc = false;
1482
12.3k
        for (const CPLXMLNode *psChild = psNode->psChild; psChild != nullptr;
1483
9.38k
             psChild = psChild->psNext)
1484
9.38k
        {
1485
9.38k
            if (psChild->eType == CXT_Element &&
1486
5.23k
                EQUAL(BareGMLElement(psChild->pszValue), "curveMember"))
1487
4.58k
            {
1488
4.58k
                const CPLXMLNode *psCurveMemberChild = GetChildElement(psChild);
1489
4.58k
                if (psCurveMemberChild &&
1490
4.58k
                    psCurveMemberChild->eType == CXT_Element &&
1491
4.58k
                    EQUAL(BareGMLElement(psCurveMemberChild->pszValue),
1492
4.58k
                          "Curve"))
1493
365
                {
1494
365
                    const CPLXMLNode *psCurveChild =
1495
365
                        GetChildElement(psCurveMemberChild);
1496
365
                    if (psCurveChild && psCurveChild->eType == CXT_Element &&
1497
365
                        EQUAL(BareGMLElement(psCurveChild->pszValue),
1498
365
                              "segments"))
1499
365
                    {
1500
365
                        for (const CPLXMLNode *psChild2 = psCurveChild->psChild;
1501
3.12k
                             psChild2 != nullptr; psChild2 = psChild2->psNext)
1502
2.75k
                        {
1503
2.75k
                            if (psChild2->eType == CXT_Element &&
1504
1.14k
                                EQUAL(BareGMLElement(psChild2->pszValue),
1505
2.75k
                                      "ArcByCenterPoint"))
1506
1.01k
                            {
1507
1.01k
                                storeArcByCenterPointParameters(
1508
1.01k
                                    psChild2, pszSRSName,
1509
1.01k
                                    bLastChildIsApproximateArc,
1510
1.01k
                                    dfLastCurveApproximateArcRadius,
1511
1.01k
                                    bLastCurveWasApproximateArcInvertedAxisOrder,
1512
1.01k
                                    dfSemiMajor);
1513
1.01k
                            }
1514
1.74k
                            else
1515
1.74k
                            {
1516
1.74k
                                bLastChildIsApproximateArc = false;
1517
1.74k
                            }
1518
2.75k
                        }
1519
365
                    }
1520
0
                    else
1521
0
                    {
1522
0
                        bLastChildIsApproximateArc = false;
1523
0
                    }
1524
365
                }
1525
4.22k
                else
1526
4.22k
                {
1527
4.22k
                    bLastChildIsApproximateArc = false;
1528
4.22k
                }
1529
4.58k
            }
1530
4.79k
            else
1531
4.79k
            {
1532
4.79k
                bLastChildIsApproximateArc = false;
1533
4.79k
            }
1534
9.38k
        }
1535
1536
2.96k
        if (poRing)
1537
2.01k
        {
1538
2.01k
            if (poRing->getNumPoints() >= 2 && bFirstChildIsApproximateArc &&
1539
0
                !poRing->get_IsClosed() &&
1540
0
                wkbFlatten(poRing->getGeometryType()) == wkbLineString)
1541
0
            {
1542
0
                OGRLineString *poLS = poRing->toLineString();
1543
1544
0
                OGRPoint p;
1545
0
                OGRPoint p2;
1546
0
                poLS->StartPoint(&p);
1547
0
                poLS->EndPoint(&p2);
1548
0
                double dfDistance = 0.0;
1549
0
                if (bFirstChildWasApproximateArcInvertedAxisOrder)
1550
0
                    dfDistance = OGR_GreatCircle_Distance(
1551
0
                        p.getX(), p.getY(), p2.getX(), p2.getY(), dfSemiMajor);
1552
0
                else
1553
0
                    dfDistance = OGR_GreatCircle_Distance(
1554
0
                        p.getY(), p.getX(), p2.getY(), p2.getX(), dfSemiMajor);
1555
0
                if (dfDistance < dfFirstChildApproximateArcRadius / 5.0)
1556
0
                {
1557
0
                    CPLDebug("OGR", "Moving approximate start of "
1558
0
                                    "ArcByCenterPoint to end of "
1559
0
                                    "curve");
1560
0
                    poLS->setPoint(0, &p2);
1561
0
                }
1562
0
            }
1563
2.01k
            else if (poRing->getNumPoints() >= 2 &&
1564
2.00k
                     bLastChildIsApproximateArc && !poRing->get_IsClosed() &&
1565
0
                     wkbFlatten(poRing->getGeometryType()) == wkbLineString)
1566
0
            {
1567
0
                OGRLineString *poLS = poRing->toLineString();
1568
1569
0
                OGRPoint p;
1570
0
                OGRPoint p2;
1571
0
                poLS->StartPoint(&p);
1572
0
                poLS->EndPoint(&p2);
1573
0
                double dfDistance = 0.0;
1574
0
                if (bLastCurveWasApproximateArcInvertedAxisOrder)
1575
0
                    dfDistance = OGR_GreatCircle_Distance(
1576
0
                        p.getX(), p.getY(), p2.getX(), p2.getY(), dfSemiMajor);
1577
0
                else
1578
0
                    dfDistance = OGR_GreatCircle_Distance(
1579
0
                        p.getY(), p.getX(), p2.getY(), p2.getX(), dfSemiMajor);
1580
0
                if (dfDistance < dfLastCurveApproximateArcRadius / 5.0)
1581
0
                {
1582
0
                    CPLDebug("OGR", "Moving approximate end of "
1583
0
                                    "ArcByCenterPoint to start of "
1584
0
                                    "curve");
1585
0
                    poLS->setPoint(poLS->getNumPoints() - 1, &p);
1586
0
                }
1587
0
            }
1588
1589
2.01k
            if (poRing->getNumPoints() < 2 || !poRing->get_IsClosed())
1590
16
            {
1591
16
                ReportFailure("Non-closed ring");
1592
16
                return nullptr;
1593
16
            }
1594
1.99k
            return poRing;
1595
2.01k
        }
1596
1597
949
        if (poCC == nullptr)
1598
26
            return nullptr;
1599
1600
923
        else if (/* bCastToLinearTypeIfPossible &&*/ bChildrenAreAllLineString)
1601
528
        {
1602
528
            return std::unique_ptr<OGRLinearRing>(
1603
528
                OGRCurve::CastToLinearRing(poCC.release()));
1604
528
        }
1605
395
        else
1606
395
        {
1607
395
            if (poCC->getNumPoints() < 2 || !poCC->get_IsClosed())
1608
0
            {
1609
0
                ReportFailure("Non-closed ring");
1610
0
                return nullptr;
1611
0
            }
1612
395
            return poCC;
1613
395
        }
1614
949
    }
1615
1616
    /* -------------------------------------------------------------------- */
1617
    /*      LineString                                                      */
1618
    /* -------------------------------------------------------------------- */
1619
172k
    if (EQUAL(pszBaseGeometry, "LineString") ||
1620
171k
        EQUAL(pszBaseGeometry, "LineStringSegment") ||
1621
171k
        EQUAL(pszBaseGeometry, "Geodesic") ||
1622
171k
        EQUAL(pszBaseGeometry, "GeodesicString"))
1623
1.11k
    {
1624
1.11k
        auto poLine = std::make_unique<OGRLineString>();
1625
1626
1.11k
        if (!ParseGMLCoordinates(psNode, poLine.get(), nSRSDimension))
1627
42
        {
1628
42
            return nullptr;
1629
42
        }
1630
1631
1.07k
        return poLine;
1632
1.11k
    }
1633
1634
    /* -------------------------------------------------------------------- */
1635
    /*      Arc                                                             */
1636
    /* -------------------------------------------------------------------- */
1637
171k
    if (EQUAL(pszBaseGeometry, "Arc"))
1638
9
    {
1639
9
        auto poCC = std::make_unique<OGRCircularString>();
1640
1641
9
        if (!ParseGMLCoordinates(psNode, poCC.get(), nSRSDimension))
1642
9
        {
1643
9
            return nullptr;
1644
9
        }
1645
1646
        // Normally a gml:Arc has only 3 points of controls, but in the
1647
        // wild we sometimes find GML with 5 points, so accept any odd
1648
        // number >= 3 (ArcString should be used for > 3 points)
1649
0
        if (poCC->getNumPoints() < 3 || (poCC->getNumPoints() % 2) != 1)
1650
0
        {
1651
0
            ReportFailure("Bad number of points in Arc");
1652
0
            return nullptr;
1653
0
        }
1654
1655
0
        return poCC;
1656
0
    }
1657
1658
    /* -------------------------------------------------------------------- */
1659
    /*     ArcString                                                        */
1660
    /* -------------------------------------------------------------------- */
1661
171k
    if (EQUAL(pszBaseGeometry, "ArcString"))
1662
2
    {
1663
2
        auto poCC = std::make_unique<OGRCircularString>();
1664
1665
2
        if (!ParseGMLCoordinates(psNode, poCC.get(), nSRSDimension))
1666
2
        {
1667
2
            return nullptr;
1668
2
        }
1669
1670
0
        if (poCC->getNumPoints() < 3 || (poCC->getNumPoints() % 2) != 1)
1671
0
        {
1672
0
            ReportFailure("Bad number of points in ArcString");
1673
0
            return nullptr;
1674
0
        }
1675
1676
0
        return poCC;
1677
0
    }
1678
1679
    /* -------------------------------------------------------------------- */
1680
    /*      Circle                                                          */
1681
    /* -------------------------------------------------------------------- */
1682
171k
    if (EQUAL(pszBaseGeometry, "Circle"))
1683
7
    {
1684
7
        auto poLine = std::make_unique<OGRLineString>();
1685
1686
7
        if (!ParseGMLCoordinates(psNode, poLine.get(), nSRSDimension))
1687
7
        {
1688
7
            return nullptr;
1689
7
        }
1690
1691
0
        if (poLine->getNumPoints() != 3)
1692
0
        {
1693
0
            ReportFailure("Bad number of points in Circle");
1694
0
            return nullptr;
1695
0
        }
1696
1697
0
        double R = 0.0;
1698
0
        double cx = 0.0;
1699
0
        double cy = 0.0;
1700
0
        double alpha0 = 0.0;
1701
0
        double alpha1 = 0.0;
1702
0
        double alpha2 = 0.0;
1703
0
        if (!OGRGeometryFactory::GetCurveParameters(
1704
0
                poLine->getX(0), poLine->getY(0), poLine->getX(1),
1705
0
                poLine->getY(1), poLine->getX(2), poLine->getY(2), R, cx, cy,
1706
0
                alpha0, alpha1, alpha2))
1707
0
        {
1708
0
            return nullptr;
1709
0
        }
1710
1711
0
        auto poCC = std::make_unique<OGRCircularString>();
1712
0
        OGRPoint p;
1713
0
        poLine->getPoint(0, &p);
1714
0
        poCC->addPoint(&p);
1715
0
        poLine->getPoint(1, &p);
1716
0
        poCC->addPoint(&p);
1717
0
        poLine->getPoint(2, &p);
1718
0
        poCC->addPoint(&p);
1719
0
        const double alpha4 =
1720
0
            alpha2 > alpha0 ? alpha0 + kdf2PI : alpha0 - kdf2PI;
1721
0
        const double alpha3 = (alpha2 + alpha4) / 2.0;
1722
0
        const double x = cx + R * cos(alpha3);
1723
0
        const double y = cy + R * sin(alpha3);
1724
0
        if (poCC->getCoordinateDimension() == 3)
1725
0
            poCC->addPoint(x, y, p.getZ());
1726
0
        else
1727
0
            poCC->addPoint(x, y);
1728
0
        poLine->getPoint(0, &p);
1729
0
        poCC->addPoint(&p);
1730
0
        return poCC;
1731
0
    }
1732
1733
    /* -------------------------------------------------------------------- */
1734
    /*      ArcByBulge                                                      */
1735
    /* -------------------------------------------------------------------- */
1736
171k
    if (EQUAL(pszBaseGeometry, "ArcByBulge"))
1737
6
    {
1738
6
        const CPLXMLNode *psChild = FindBareXMLChild(psNode, "bulge");
1739
6
        if (psChild == nullptr || psChild->eType != CXT_Element ||
1740
0
            psChild->psChild == nullptr)
1741
6
        {
1742
6
            ReportFailure("Missing bulge element.");
1743
6
            return nullptr;
1744
6
        }
1745
0
        const double dfBulge = CPLAtof(psChild->psChild->pszValue);
1746
1747
0
        psChild = FindBareXMLChild(psNode, "normal");
1748
0
        if (psChild == nullptr || psChild->eType != CXT_Element)
1749
0
        {
1750
0
            ReportFailure("Missing normal element.");
1751
0
            return nullptr;
1752
0
        }
1753
0
        double dfNormal = CPLAtof(psChild->psChild->pszValue);
1754
1755
0
        auto poLS = std::make_unique<OGRLineString>();
1756
0
        if (!ParseGMLCoordinates(psNode, poLS.get(), nSRSDimension))
1757
0
        {
1758
0
            return nullptr;
1759
0
        }
1760
1761
0
        if (poLS->getNumPoints() != 2)
1762
0
        {
1763
0
            ReportFailure("Bad number of points in ArcByBulge");
1764
0
            return nullptr;
1765
0
        }
1766
1767
0
        auto poCC = std::make_unique<OGRCircularString>();
1768
0
        OGRPoint p;
1769
0
        poLS->getPoint(0, &p);
1770
0
        poCC->addPoint(&p);
1771
1772
0
        const double dfMidX = (poLS->getX(0) + poLS->getX(1)) / 2.0;
1773
0
        const double dfMidY = (poLS->getY(0) + poLS->getY(1)) / 2.0;
1774
0
        const double dfDirX = (poLS->getX(1) - poLS->getX(0)) / 2.0;
1775
0
        const double dfDirY = (poLS->getY(1) - poLS->getY(0)) / 2.0;
1776
0
        double dfNormX = -dfDirY;
1777
0
        double dfNormY = dfDirX;
1778
0
        const double dfNorm = sqrt(dfNormX * dfNormX + dfNormY * dfNormY);
1779
0
        if (dfNorm != 0.0)
1780
0
        {
1781
0
            dfNormX /= dfNorm;
1782
0
            dfNormY /= dfNorm;
1783
0
        }
1784
0
        const double dfNewX = dfMidX + dfNormX * dfBulge * dfNormal;
1785
0
        const double dfNewY = dfMidY + dfNormY * dfBulge * dfNormal;
1786
1787
0
        if (poCC->getCoordinateDimension() == 3)
1788
0
            poCC->addPoint(dfNewX, dfNewY, p.getZ());
1789
0
        else
1790
0
            poCC->addPoint(dfNewX, dfNewY);
1791
1792
0
        poLS->getPoint(1, &p);
1793
0
        poCC->addPoint(&p);
1794
1795
0
        return poCC;
1796
0
    }
1797
1798
    /* -------------------------------------------------------------------- */
1799
    /*      ArcByCenterPoint                                                */
1800
    /* -------------------------------------------------------------------- */
1801
171k
    if (EQUAL(pszBaseGeometry, "ArcByCenterPoint"))
1802
96.5k
    {
1803
96.5k
        const CPLXMLNode *psChild = FindBareXMLChild(psNode, "radius");
1804
96.5k
        if (psChild == nullptr || psChild->eType != CXT_Element)
1805
1.63k
        {
1806
1.63k
            ReportFailure("Missing radius element.");
1807
1.63k
            return nullptr;
1808
1.63k
        }
1809
94.8k
        const char *pszUnits = CPLGetXMLValue(psChild, "uom", nullptr);
1810
94.8k
        const double dfUOMConv = GetUOMInMetre(pszUnits, "radius", pszId);
1811
94.8k
        const double dfRadiusRaw =
1812
94.8k
            CPLAtof(CPLGetXMLValue(psChild, nullptr, "0"));
1813
94.8k
        double dfRadius = dfUOMConv > 0 ? dfRadiusRaw * dfUOMConv : dfRadiusRaw;
1814
1815
94.8k
        psChild = FindBareXMLChild(psNode, "startAngle");
1816
94.8k
        if (psChild == nullptr || psChild->eType != CXT_Element)
1817
1.80k
        {
1818
1.80k
            ReportFailure("Missing startAngle element.");
1819
1.80k
            return nullptr;
1820
1.80k
        }
1821
93.0k
        const double dfStartAngle =
1822
93.0k
            CPLAtof(CPLGetXMLValue(psChild, nullptr, "0"));
1823
1824
93.0k
        psChild = FindBareXMLChild(psNode, "endAngle");
1825
93.0k
        if (psChild == nullptr || psChild->eType != CXT_Element)
1826
1.13k
        {
1827
1.13k
            ReportFailure("Missing endAngle element.");
1828
1.13k
            return nullptr;
1829
1.13k
        }
1830
91.9k
        const double dfEndAngle =
1831
91.9k
            CPLAtof(CPLGetXMLValue(psChild, nullptr, "0"));
1832
1833
91.9k
        OGRPoint p;
1834
91.9k
        if (!ParseGMLCoordinates(psNode, &p, nSRSDimension))
1835
714
        {
1836
714
            return nullptr;
1837
714
        }
1838
1839
91.2k
        bool bSRSUnitIsDegree = false;
1840
91.2k
        bool bInvertedAxisOrder = false;
1841
91.2k
        double dfSemiMajor = OGR_GREATCIRCLE_DEFAULT_RADIUS;
1842
91.2k
        if (pszSRSName != nullptr)
1843
87.9k
        {
1844
87.9k
            auto entry = OGRGML_SRSCache_GetInfo(hSRSCache, pszSRSName);
1845
87.9k
            if (entry)
1846
8.25k
            {
1847
8.25k
                bInvertedAxisOrder = entry->bInvertedAxisOrder;
1848
8.25k
                dfSemiMajor = StantardizeSemiMajor(entry->dfSemiMajor);
1849
8.25k
                if (entry->bIsGeographic)
1850
242
                {
1851
242
                    bSRSUnitIsDegree = entry->bAngularUnitIsDegree;
1852
242
                }
1853
8.00k
                else if (entry->bIsProjected)
1854
2.34k
                {
1855
2.34k
                    const double dfSRSUnitsToMetre = entry->dfLinearUnits;
1856
2.34k
                    if (dfSRSUnitsToMetre > 0)
1857
2.34k
                        dfRadius /= dfSRSUnitsToMetre;
1858
2.34k
                }
1859
8.25k
            }
1860
87.9k
        }
1861
1862
91.2k
        double dfCenterX = p.getX();
1863
91.2k
        double dfCenterY = p.getY();
1864
1865
91.2k
        if (bSRSUnitIsDegree && dfUOMConv > 0)
1866
165
        {
1867
165
            auto poLS = std::make_unique<OGRLineString>();
1868
165
            const double dfStep = OGRGeometryFactory::GetDefaultArcStepSize();
1869
165
            const double dfSign = dfStartAngle < dfEndAngle ? 1 : -1;
1870
165
            for (double dfAngle = dfStartAngle;
1871
165
                 (dfAngle - dfEndAngle) * dfSign < 0;
1872
165
                 dfAngle += dfSign * dfStep)
1873
0
            {
1874
0
                double dfLong = 0.0;
1875
0
                double dfLat = 0.0;
1876
0
                if (bInvertedAxisOrder)
1877
0
                {
1878
0
                    OGR_GreatCircle_ExtendPosition(
1879
0
                        dfCenterX, dfCenterY, dfRadius,
1880
                        // See
1881
                        // https://ext.eurocontrol.int/aixm_confluence/display/ACG/ArcByCenterPoint+Interpretation+Summary
1882
0
                        dfAngle, dfSemiMajor, &dfLat, &dfLong);
1883
0
                    p.setX(dfLat);  // yes, external code will do the swap later
1884
0
                    p.setY(dfLong);
1885
0
                }
1886
0
                else
1887
0
                {
1888
0
                    OGR_GreatCircle_ExtendPosition(
1889
0
                        dfCenterY, dfCenterX, dfRadius, 90 - dfAngle,
1890
0
                        dfSemiMajor, &dfLat, &dfLong);
1891
0
                    p.setX(dfLong);
1892
0
                    p.setY(dfLat);
1893
0
                }
1894
0
                poLS->addPoint(&p);
1895
0
            }
1896
1897
165
            double dfLong = 0.0;
1898
165
            double dfLat = 0.0;
1899
165
            if (bInvertedAxisOrder)
1900
16
            {
1901
16
                OGR_GreatCircle_ExtendPosition(dfCenterX, dfCenterY, dfRadius,
1902
16
                                               dfEndAngle, dfSemiMajor, &dfLat,
1903
16
                                               &dfLong);
1904
16
                p.setX(dfLat);  // yes, external code will do the swap later
1905
16
                p.setY(dfLong);
1906
16
            }
1907
149
            else
1908
149
            {
1909
149
                OGR_GreatCircle_ExtendPosition(dfCenterY, dfCenterX, dfRadius,
1910
149
                                               90 - dfEndAngle, dfSemiMajor,
1911
149
                                               &dfLat, &dfLong);
1912
149
                p.setX(dfLong);
1913
149
                p.setY(dfLat);
1914
149
            }
1915
165
            poLS->addPoint(&p);
1916
1917
165
            return poLS;
1918
165
        }
1919
1920
91.0k
        if (bInvertedAxisOrder)
1921
0
            std::swap(dfCenterX, dfCenterY);
1922
1923
91.0k
        auto poCC = std::make_unique<OGRCircularString>();
1924
91.0k
        p.setX(dfCenterX + dfRadius * cos(dfStartAngle * kdfD2R));
1925
91.0k
        p.setY(dfCenterY + dfRadius * sin(dfStartAngle * kdfD2R));
1926
91.0k
        poCC->addPoint(&p);
1927
91.0k
        const double dfAverageAngle = (dfStartAngle + dfEndAngle) / 2.0;
1928
91.0k
        p.setX(dfCenterX + dfRadius * cos(dfAverageAngle * kdfD2R));
1929
91.0k
        p.setY(dfCenterY + dfRadius * sin(dfAverageAngle * kdfD2R));
1930
91.0k
        poCC->addPoint(&p);
1931
91.0k
        p.setX(dfCenterX + dfRadius * cos(dfEndAngle * kdfD2R));
1932
91.0k
        p.setY(dfCenterY + dfRadius * sin(dfEndAngle * kdfD2R));
1933
91.0k
        poCC->addPoint(&p);
1934
1935
91.0k
        if (bInvertedAxisOrder)
1936
0
            poCC->swapXY();
1937
1938
91.0k
        return poCC;
1939
91.2k
    }
1940
1941
    /* -------------------------------------------------------------------- */
1942
    /*      CircleByCenterPoint                                             */
1943
    /* -------------------------------------------------------------------- */
1944
74.7k
    if (EQUAL(pszBaseGeometry, "CircleByCenterPoint"))
1945
1
    {
1946
1
        const CPLXMLNode *psChild = FindBareXMLChild(psNode, "radius");
1947
1
        if (psChild == nullptr || psChild->eType != CXT_Element)
1948
1
        {
1949
1
            ReportFailure("Missing radius element.");
1950
1
            return nullptr;
1951
1
        }
1952
0
        const char *pszUnits = CPLGetXMLValue(psChild, "uom", nullptr);
1953
0
        const double dfUOMConv = GetUOMInMetre(pszUnits, "radius", pszId);
1954
0
        const double dfRadiusRaw =
1955
0
            CPLAtof(CPLGetXMLValue(psChild, nullptr, "0"));
1956
0
        double dfRadius = dfUOMConv > 0 ? dfRadiusRaw * dfUOMConv : dfRadiusRaw;
1957
1958
0
        OGRPoint p;
1959
0
        if (!ParseGMLCoordinates(psNode, &p, nSRSDimension))
1960
0
        {
1961
0
            return nullptr;
1962
0
        }
1963
1964
0
        bool bSRSUnitIsDegree = false;
1965
0
        bool bInvertedAxisOrder = false;
1966
0
        double dfSemiMajor = OGR_GREATCIRCLE_DEFAULT_RADIUS;
1967
0
        if (pszSRSName != nullptr)
1968
0
        {
1969
0
            auto entry = OGRGML_SRSCache_GetInfo(hSRSCache, pszSRSName);
1970
0
            if (entry)
1971
0
            {
1972
0
                bInvertedAxisOrder = entry->bInvertedAxisOrder;
1973
0
                dfSemiMajor = StantardizeSemiMajor(entry->dfSemiMajor);
1974
0
                if (entry->bIsGeographic)
1975
0
                {
1976
0
                    bSRSUnitIsDegree = entry->bAngularUnitIsDegree;
1977
0
                }
1978
0
                else if (entry->bIsProjected)
1979
0
                {
1980
0
                    const double dfSRSUnitsToMetre = entry->dfLinearUnits;
1981
0
                    if (dfSRSUnitsToMetre > 0)
1982
0
                        dfRadius /= dfSRSUnitsToMetre;
1983
0
                }
1984
0
            }
1985
0
        }
1986
1987
0
        double dfCenterX = p.getX();
1988
0
        double dfCenterY = p.getY();
1989
1990
0
        if (bSRSUnitIsDegree && dfUOMConv > 0)
1991
0
        {
1992
0
            auto poLS = std::make_unique<OGRLineString>();
1993
0
            const double dfStep = OGRGeometryFactory::GetDefaultArcStepSize();
1994
0
            for (double dfAngle = 0; dfAngle < 360; dfAngle += dfStep)
1995
0
            {
1996
0
                double dfLong = 0.0;
1997
0
                double dfLat = 0.0;
1998
0
                if (bInvertedAxisOrder)
1999
0
                {
2000
0
                    OGR_GreatCircle_ExtendPosition(
2001
0
                        dfCenterX, dfCenterY, dfRadius, dfAngle, dfSemiMajor,
2002
0
                        &dfLat, &dfLong);
2003
0
                    p.setX(dfLat);  // yes, external code will do the swap later
2004
0
                    p.setY(dfLong);
2005
0
                }
2006
0
                else
2007
0
                {
2008
0
                    OGR_GreatCircle_ExtendPosition(
2009
0
                        dfCenterY, dfCenterX, dfRadius, dfAngle, dfSemiMajor,
2010
0
                        &dfLat, &dfLong);
2011
0
                    p.setX(dfLong);
2012
0
                    p.setY(dfLat);
2013
0
                }
2014
0
                poLS->addPoint(&p);
2015
0
            }
2016
0
            poLS->getPoint(0, &p);
2017
0
            poLS->addPoint(&p);
2018
0
            return poLS;
2019
0
        }
2020
2021
0
        if (bInvertedAxisOrder)
2022
0
            std::swap(dfCenterX, dfCenterY);
2023
2024
0
        auto poCC = std::make_unique<OGRCircularString>();
2025
0
        p.setX(dfCenterX - dfRadius);
2026
0
        p.setY(dfCenterY);
2027
0
        poCC->addPoint(&p);
2028
0
        p.setX(dfCenterX);
2029
0
        p.setY(dfCenterY + dfRadius);
2030
0
        poCC->addPoint(&p);
2031
0
        p.setX(dfCenterX + dfRadius);
2032
0
        p.setY(dfCenterY);
2033
0
        poCC->addPoint(&p);
2034
0
        p.setX(dfCenterX);
2035
0
        p.setY(dfCenterY - dfRadius);
2036
0
        poCC->addPoint(&p);
2037
0
        p.setX(dfCenterX - dfRadius);
2038
0
        p.setY(dfCenterY);
2039
0
        poCC->addPoint(&p);
2040
2041
0
        if (bInvertedAxisOrder)
2042
0
            poCC->swapXY();
2043
2044
0
        return poCC;
2045
0
    }
2046
2047
    /* -------------------------------------------------------------------- */
2048
    /*      PointType                                                       */
2049
    /* -------------------------------------------------------------------- */
2050
74.7k
    if (EQUAL(pszBaseGeometry, "PointType") ||
2051
74.7k
        EQUAL(pszBaseGeometry, "Point") ||
2052
68.8k
        EQUAL(pszBaseGeometry, "ElevatedPoint") ||
2053
68.8k
        EQUAL(pszBaseGeometry, "ConnectionPoint"))
2054
5.89k
    {
2055
5.89k
        auto poPoint = std::make_unique<OGRPoint>();
2056
2057
5.89k
        if (!ParseGMLCoordinates(psNode, poPoint.get(), nSRSDimension))
2058
261
        {
2059
261
            return nullptr;
2060
261
        }
2061
2062
5.62k
        return poPoint;
2063
5.89k
    }
2064
2065
    /* -------------------------------------------------------------------- */
2066
    /*      Box                                                             */
2067
    /* -------------------------------------------------------------------- */
2068
68.8k
    if (EQUAL(pszBaseGeometry, "BoxType") || EQUAL(pszBaseGeometry, "Box"))
2069
7
    {
2070
7
        OGRLineString oPoints;
2071
2072
7
        if (!ParseGMLCoordinates(psNode, &oPoints, nSRSDimension))
2073
7
            return nullptr;
2074
2075
0
        if (oPoints.getNumPoints() < 2)
2076
0
            return nullptr;
2077
2078
0
        auto poBoxRing = std::make_unique<OGRLinearRing>();
2079
0
        auto poBoxPoly = std::make_unique<OGRPolygon>();
2080
2081
0
        poBoxRing->setNumPoints(5);
2082
0
        poBoxRing->setPoint(0, oPoints.getX(0), oPoints.getY(0),
2083
0
                            oPoints.getZ(0));
2084
0
        poBoxRing->setPoint(1, oPoints.getX(1), oPoints.getY(0),
2085
0
                            oPoints.getZ(0));
2086
0
        poBoxRing->setPoint(2, oPoints.getX(1), oPoints.getY(1),
2087
0
                            oPoints.getZ(1));
2088
0
        poBoxRing->setPoint(3, oPoints.getX(0), oPoints.getY(1),
2089
0
                            oPoints.getZ(0));
2090
0
        poBoxRing->setPoint(4, oPoints.getX(0), oPoints.getY(0),
2091
0
                            oPoints.getZ(0));
2092
0
        poBoxRing->set3D(oPoints.Is3D());
2093
2094
0
        poBoxPoly->addRing(std::move(poBoxRing));
2095
2096
0
        return poBoxPoly;
2097
0
    }
2098
2099
    /* -------------------------------------------------------------------- */
2100
    /*      Envelope                                                        */
2101
    /* -------------------------------------------------------------------- */
2102
68.8k
    if (EQUAL(pszBaseGeometry, "Envelope"))
2103
33
    {
2104
33
        const CPLXMLNode *psLowerCorner =
2105
33
            FindBareXMLChild(psNode, "lowerCorner");
2106
33
        const CPLXMLNode *psUpperCorner =
2107
33
            FindBareXMLChild(psNode, "upperCorner");
2108
33
        if (psLowerCorner == nullptr || psUpperCorner == nullptr)
2109
24
            return nullptr;
2110
9
        const char *pszLowerCorner = GetElementText(psLowerCorner);
2111
9
        const char *pszUpperCorner = GetElementText(psUpperCorner);
2112
9
        if (pszLowerCorner == nullptr || pszUpperCorner == nullptr)
2113
0
            return nullptr;
2114
9
        char **papszLowerCorner = CSLTokenizeString(pszLowerCorner);
2115
9
        char **papszUpperCorner = CSLTokenizeString(pszUpperCorner);
2116
9
        const int nTokenCountLC = CSLCount(papszLowerCorner);
2117
9
        const int nTokenCountUC = CSLCount(papszUpperCorner);
2118
9
        if (nTokenCountLC < 2 || nTokenCountUC < 2)
2119
0
        {
2120
0
            CSLDestroy(papszLowerCorner);
2121
0
            CSLDestroy(papszUpperCorner);
2122
0
            return nullptr;
2123
0
        }
2124
2125
9
        const double dfLLX = CPLAtof(papszLowerCorner[0]);
2126
9
        const double dfLLY = CPLAtof(papszLowerCorner[1]);
2127
9
        const double dfURX = CPLAtof(papszUpperCorner[0]);
2128
9
        const double dfURY = CPLAtof(papszUpperCorner[1]);
2129
9
        CSLDestroy(papszLowerCorner);
2130
9
        CSLDestroy(papszUpperCorner);
2131
2132
9
        auto poEnvelopeRing = std::make_unique<OGRLinearRing>();
2133
9
        auto poPoly = std::make_unique<OGRPolygon>();
2134
2135
9
        poEnvelopeRing->setNumPoints(5);
2136
9
        poEnvelopeRing->setPoint(0, dfLLX, dfLLY);
2137
9
        poEnvelopeRing->setPoint(1, dfURX, dfLLY);
2138
9
        poEnvelopeRing->setPoint(2, dfURX, dfURY);
2139
9
        poEnvelopeRing->setPoint(3, dfLLX, dfURY);
2140
9
        poEnvelopeRing->setPoint(4, dfLLX, dfLLY);
2141
9
        poPoly->addRing(std::move(poEnvelopeRing));
2142
2143
9
        return poPoly;
2144
9
    }
2145
2146
    /* --------------------------------------------------------------------- */
2147
    /*      MultiPolygon / MultiSurface / CompositeSurface                   */
2148
    /*                                                                       */
2149
    /* For CompositeSurface, this is a very rough approximation to deal with */
2150
    /* it as a MultiPolygon, because it can several faces of a 3D volume.    */
2151
    /* --------------------------------------------------------------------- */
2152
68.7k
    if (EQUAL(pszBaseGeometry, "MultiPolygon") ||
2153
68.0k
        EQUAL(pszBaseGeometry, "MultiSurface") ||
2154
60.9k
        EQUAL(pszBaseGeometry, "Shell") ||  // CityGML 3 uses this
2155
60.9k
        EQUAL(pszBaseGeometry, "CompositeSurface"))
2156
9.07k
    {
2157
9.07k
        std::unique_ptr<OGRMultiSurface> poMS =
2158
9.07k
            EQUAL(pszBaseGeometry, "MultiPolygon")
2159
9.07k
                ? std::make_unique<OGRMultiPolygon>()
2160
9.07k
                : std::make_unique<OGRMultiSurface>();
2161
9.07k
        bool bReconstructTopology = false;
2162
9.07k
        bool bChildrenAreAllPolygons = true;
2163
2164
        // Iterate over children.
2165
71.2k
        for (const CPLXMLNode *psChild = psNode->psChild; psChild != nullptr;
2166
62.1k
             psChild = psChild->psNext)
2167
62.2k
        {
2168
62.2k
            const char *pszMemberElement = BareGMLElement(psChild->pszValue);
2169
62.2k
            if (psChild->eType == CXT_Element &&
2170
11.2k
                (EQUAL(pszMemberElement, "polygonMember") ||
2171
10.8k
                 EQUAL(pszMemberElement, "surfaceMember")))
2172
5.34k
            {
2173
5.34k
                const CPLXMLNode *psSurfaceChild = GetChildElement(psChild);
2174
2175
5.34k
                if (psSurfaceChild != nullptr)
2176
4.74k
                {
2177
                    // Cf #5421 where there are PolygonPatch with only inner
2178
                    // rings.
2179
4.74k
                    const CPLXMLNode *psPolygonPatch =
2180
4.74k
                        GetChildElement(GetChildElement(psSurfaceChild));
2181
4.74k
                    const CPLXMLNode *psPolygonPatchChild = nullptr;
2182
4.74k
                    if (psPolygonPatch != nullptr &&
2183
121
                        psPolygonPatch->eType == CXT_Element &&
2184
121
                        EQUAL(BareGMLElement(psPolygonPatch->pszValue),
2185
4.74k
                              "PolygonPatch") &&
2186
4
                        (psPolygonPatchChild =
2187
4
                             GetChildElement(psPolygonPatch)) != nullptr &&
2188
4
                        EQUAL(BareGMLElement(psPolygonPatchChild->pszValue),
2189
4.74k
                              "interior"))
2190
0
                    {
2191
                        // Find all inner rings
2192
0
                        for (const CPLXMLNode *psChild2 =
2193
0
                                 psPolygonPatch->psChild;
2194
0
                             psChild2 != nullptr; psChild2 = psChild2->psNext)
2195
0
                        {
2196
0
                            if (psChild2->eType == CXT_Element &&
2197
0
                                (EQUAL(BareGMLElement(psChild2->pszValue),
2198
0
                                       "interior")))
2199
0
                            {
2200
0
                                const CPLXMLNode *psInteriorChild =
2201
0
                                    GetChildElement(psChild2);
2202
0
                                auto poRing =
2203
0
                                    psInteriorChild == nullptr
2204
0
                                        ? nullptr
2205
0
                                        : GML2OGRGeometry_XMLNode_Internal(
2206
0
                                              psInteriorChild, pszId,
2207
0
                                              nPseudoBoolGetSecondaryGeometryOption,
2208
0
                                              nRecLevel + 1, nSRSDimension,
2209
0
                                              pszSRSName, hSRSCache);
2210
0
                                if (poRing == nullptr)
2211
0
                                {
2212
0
                                    ReportFailure("Invalid interior ring");
2213
0
                                    return nullptr;
2214
0
                                }
2215
0
                                if (!EQUAL(poRing->getGeometryName(),
2216
0
                                           "LINEARRING"))
2217
0
                                {
2218
0
                                    ReportFailure("%s: Got %s geometry as "
2219
0
                                                  "innerBoundaryIs instead of "
2220
0
                                                  "LINEARRING.",
2221
0
                                                  pszBaseGeometry,
2222
0
                                                  poRing->getGeometryName());
2223
0
                                    return nullptr;
2224
0
                                }
2225
2226
0
                                bReconstructTopology = true;
2227
0
                                auto poPolygon = std::make_unique<OGRPolygon>();
2228
0
                                auto poLinearRing =
2229
0
                                    std::unique_ptr<OGRLinearRing>(
2230
0
                                        poRing.release()->toLinearRing());
2231
0
                                poPolygon->addRing(std::move(poLinearRing));
2232
0
                                poMS->addGeometry(std::move(poPolygon));
2233
0
                            }
2234
0
                        }
2235
0
                    }
2236
4.74k
                    else
2237
4.74k
                    {
2238
4.74k
                        auto poGeom = GML2OGRGeometry_XMLNode_Internal(
2239
4.74k
                            psSurfaceChild, pszId,
2240
4.74k
                            nPseudoBoolGetSecondaryGeometryOption,
2241
4.74k
                            nRecLevel + 1, nSRSDimension, pszSRSName,
2242
4.74k
                            hSRSCache);
2243
4.74k
                        if (!GML2OGRGeometry_AddToMultiSurface(
2244
4.74k
                                poMS.get(), std::move(poGeom), pszMemberElement,
2245
4.74k
                                bChildrenAreAllPolygons))
2246
24
                        {
2247
24
                            return nullptr;
2248
24
                        }
2249
4.74k
                    }
2250
4.74k
                }
2251
5.34k
            }
2252
56.8k
            else if (psChild->eType == CXT_Element &&
2253
5.91k
                     EQUAL(pszMemberElement, "surfaceMembers"))
2254
483
            {
2255
483
                for (const CPLXMLNode *psChild2 = psChild->psChild;
2256
20.6k
                     psChild2 != nullptr; psChild2 = psChild2->psNext)
2257
20.1k
                {
2258
20.1k
                    pszMemberElement = BareGMLElement(psChild2->pszValue);
2259
20.1k
                    if (psChild2->eType == CXT_Element &&
2260
13.8k
                        (EQUAL(pszMemberElement, "Surface") ||
2261
12.6k
                         EQUAL(pszMemberElement, "Polygon") ||
2262
12.2k
                         EQUAL(pszMemberElement, "PolygonPatch") ||
2263
12.1k
                         EQUAL(pszMemberElement,
2264
13.8k
                               "Shell") ||  // CityGML 3 uses this
2265
12.1k
                         EQUAL(pszMemberElement, "CompositeSurface")))
2266
2.51k
                    {
2267
2.51k
                        auto poGeom = GML2OGRGeometry_XMLNode_Internal(
2268
2.51k
                            psChild2, pszId,
2269
2.51k
                            nPseudoBoolGetSecondaryGeometryOption,
2270
2.51k
                            nRecLevel + 1, nSRSDimension, pszSRSName,
2271
2.51k
                            hSRSCache);
2272
2.51k
                        if (!GML2OGRGeometry_AddToMultiSurface(
2273
2.51k
                                poMS.get(), std::move(poGeom), pszMemberElement,
2274
2.51k
                                bChildrenAreAllPolygons))
2275
1
                        {
2276
1
                            return nullptr;
2277
1
                        }
2278
2.51k
                    }
2279
20.1k
                }
2280
483
            }
2281
62.2k
        }
2282
2283
9.04k
        if (bReconstructTopology && bChildrenAreAllPolygons)
2284
0
        {
2285
0
            auto poMPoly =
2286
0
                wkbFlatten(poMS->getGeometryType()) == wkbMultiSurface
2287
0
                    ? std::unique_ptr<OGRMultiPolygon>(
2288
0
                          OGRMultiSurface::CastToMultiPolygon(poMS.release()))
2289
0
                    : std::unique_ptr<OGRMultiPolygon>(
2290
0
                          poMS.release()->toMultiPolygon());
2291
0
            const int nPolygonCount = poMPoly->getNumGeometries();
2292
0
            std::vector<std::unique_ptr<OGRGeometry>> apoPolygons;
2293
0
            apoPolygons.reserve(nPolygonCount);
2294
0
            for (int i = nPolygonCount - 1; i >= 0; --i)
2295
0
            {
2296
0
                apoPolygons.push_back(poMPoly->stealGeometry(i));
2297
0
            }
2298
0
            std::reverse(apoPolygons.begin(), apoPolygons.end());
2299
0
            return OGRGeometryFactory::organizePolygons(apoPolygons);
2300
0
        }
2301
9.04k
        else
2302
9.04k
        {
2303
9.04k
            if (/* bCastToLinearTypeIfPossible && */
2304
9.04k
                wkbFlatten(poMS->getGeometryType()) == wkbMultiSurface &&
2305
8.33k
                bChildrenAreAllPolygons)
2306
8.33k
            {
2307
8.33k
                return std::unique_ptr<OGRMultiPolygon>(
2308
8.33k
                    OGRMultiSurface::CastToMultiPolygon(poMS.release()));
2309
8.33k
            }
2310
2311
716
            return poMS;
2312
9.04k
        }
2313
9.04k
    }
2314
2315
    /* -------------------------------------------------------------------- */
2316
    /*      MultiPoint                                                      */
2317
    /* -------------------------------------------------------------------- */
2318
59.7k
    if (EQUAL(pszBaseGeometry, "MultiPoint"))
2319
208
    {
2320
208
        auto poMP = std::make_unique<OGRMultiPoint>();
2321
2322
        // Collect points.
2323
10.1k
        for (const CPLXMLNode *psChild = psNode->psChild; psChild != nullptr;
2324
9.98k
             psChild = psChild->psNext)
2325
10.0k
        {
2326
10.0k
            if (psChild->eType == CXT_Element &&
2327
6.04k
                EQUAL(BareGMLElement(psChild->pszValue), "pointMember"))
2328
1.68k
            {
2329
1.68k
                const CPLXMLNode *psPointChild = GetChildElement(psChild);
2330
2331
1.68k
                if (psPointChild != nullptr)
2332
74
                {
2333
74
                    auto poPointMember = GML2OGRGeometry_XMLNode_Internal(
2334
74
                        psPointChild, pszId,
2335
74
                        nPseudoBoolGetSecondaryGeometryOption, nRecLevel + 1,
2336
74
                        nSRSDimension, pszSRSName, hSRSCache);
2337
74
                    if (poPointMember == nullptr ||
2338
10
                        wkbFlatten(poPointMember->getGeometryType()) !=
2339
10
                            wkbPoint)
2340
65
                    {
2341
65
                        ReportFailure("MultiPoint: Got %s geometry as "
2342
65
                                      "pointMember instead of POINT",
2343
65
                                      poPointMember
2344
65
                                          ? poPointMember->getGeometryName()
2345
65
                                          : "NULL");
2346
65
                        return nullptr;
2347
65
                    }
2348
2349
9
                    poMP->addGeometry(std::move(poPointMember));
2350
9
                }
2351
1.68k
            }
2352
8.37k
            else if (psChild->eType == CXT_Element &&
2353
4.36k
                     EQUAL(BareGMLElement(psChild->pszValue), "pointMembers"))
2354
272
            {
2355
272
                for (const CPLXMLNode *psChild2 = psChild->psChild;
2356
1.91k
                     psChild2 != nullptr; psChild2 = psChild2->psNext)
2357
1.65k
                {
2358
1.65k
                    if (psChild2->eType == CXT_Element &&
2359
1.14k
                        (EQUAL(BareGMLElement(psChild2->pszValue), "Point")))
2360
57
                    {
2361
57
                        auto poGeom = GML2OGRGeometry_XMLNode_Internal(
2362
57
                            psChild2, pszId,
2363
57
                            nPseudoBoolGetSecondaryGeometryOption,
2364
57
                            nRecLevel + 1, nSRSDimension, pszSRSName,
2365
57
                            hSRSCache);
2366
57
                        if (poGeom == nullptr)
2367
5
                        {
2368
5
                            ReportFailure("Invalid %s",
2369
5
                                          BareGMLElement(psChild2->pszValue));
2370
5
                            return nullptr;
2371
5
                        }
2372
2373
52
                        if (wkbFlatten(poGeom->getGeometryType()) == wkbPoint)
2374
52
                        {
2375
52
                            auto poPoint = std::unique_ptr<OGRPoint>(
2376
52
                                poGeom.release()->toPoint());
2377
52
                            poMP->addGeometry(std::move(poPoint));
2378
52
                        }
2379
0
                        else
2380
0
                        {
2381
0
                            ReportFailure("Got %s geometry as pointMember "
2382
0
                                          "instead of POINT.",
2383
0
                                          poGeom->getGeometryName());
2384
0
                            return nullptr;
2385
0
                        }
2386
52
                    }
2387
1.65k
                }
2388
272
            }
2389
10.0k
        }
2390
2391
138
        return poMP;
2392
208
    }
2393
2394
    /* -------------------------------------------------------------------- */
2395
    /*      MultiLineString                                                 */
2396
    /* -------------------------------------------------------------------- */
2397
59.5k
    if (EQUAL(pszBaseGeometry, "MultiLineString"))
2398
34
    {
2399
34
        auto poMLS = std::make_unique<OGRMultiLineString>();
2400
2401
        // Collect lines.
2402
1.29k
        for (const CPLXMLNode *psChild = psNode->psChild; psChild != nullptr;
2403
1.25k
             psChild = psChild->psNext)
2404
1.25k
        {
2405
1.25k
            if (psChild->eType == CXT_Element &&
2406
0
                EQUAL(BareGMLElement(psChild->pszValue), "lineStringMember"))
2407
0
            {
2408
0
                const CPLXMLNode *psLineStringChild = GetChildElement(psChild);
2409
0
                auto poGeom = psLineStringChild == nullptr
2410
0
                                  ? nullptr
2411
0
                                  : GML2OGRGeometry_XMLNode_Internal(
2412
0
                                        psLineStringChild, pszId,
2413
0
                                        nPseudoBoolGetSecondaryGeometryOption,
2414
0
                                        nRecLevel + 1, nSRSDimension,
2415
0
                                        pszSRSName, hSRSCache);
2416
0
                if (poGeom == nullptr ||
2417
0
                    wkbFlatten(poGeom->getGeometryType()) != wkbLineString)
2418
0
                {
2419
0
                    ReportFailure("MultiLineString: Got %s geometry as Member "
2420
0
                                  "instead of LINESTRING.",
2421
0
                                  poGeom ? poGeom->getGeometryName() : "NULL");
2422
0
                    return nullptr;
2423
0
                }
2424
2425
0
                poMLS->addGeometry(std::move(poGeom));
2426
0
            }
2427
1.25k
        }
2428
2429
34
        return poMLS;
2430
34
    }
2431
2432
    /* -------------------------------------------------------------------- */
2433
    /*      MultiCurve                                                      */
2434
    /* -------------------------------------------------------------------- */
2435
59.4k
    if (EQUAL(pszBaseGeometry, "MultiCurve"))
2436
1.56k
    {
2437
1.56k
        auto poMC = std::make_unique<OGRMultiCurve>();
2438
1.56k
        bool bChildrenAreAllLineString = true;
2439
2440
        // Collect curveMembers.
2441
31.4k
        for (const CPLXMLNode *psChild = psNode->psChild; psChild != nullptr;
2442
29.9k
             psChild = psChild->psNext)
2443
29.9k
        {
2444
29.9k
            if (psChild->eType == CXT_Element &&
2445
18.4k
                EQUAL(BareGMLElement(psChild->pszValue), "curveMember"))
2446
6.23k
            {
2447
6.23k
                const CPLXMLNode *psChild2 = GetChildElement(psChild);
2448
6.23k
                if (psChild2 != nullptr)  // Empty curveMember is valid.
2449
4.10k
                {
2450
4.10k
                    auto poGeom = GML2OGRGeometry_XMLNode_Internal(
2451
4.10k
                        psChild2, pszId, nPseudoBoolGetSecondaryGeometryOption,
2452
4.10k
                        nRecLevel + 1, nSRSDimension, pszSRSName, hSRSCache);
2453
4.10k
                    if (poGeom == nullptr ||
2454
4.10k
                        !OGR_GT_IsCurve(poGeom->getGeometryType()))
2455
4
                    {
2456
4
                        ReportFailure("MultiCurve: Got %s geometry as Member "
2457
4
                                      "instead of a curve.",
2458
4
                                      poGeom ? poGeom->getGeometryName()
2459
4
                                             : "NULL");
2460
4
                        return nullptr;
2461
4
                    }
2462
2463
4.10k
                    if (wkbFlatten(poGeom->getGeometryType()) != wkbLineString)
2464
2
                        bChildrenAreAllLineString = false;
2465
2466
4.10k
                    if (poMC->addGeometry(std::move(poGeom)) != OGRERR_NONE)
2467
0
                    {
2468
0
                        return nullptr;
2469
0
                    }
2470
4.10k
                }
2471
6.23k
            }
2472
23.7k
            else if (psChild->eType == CXT_Element &&
2473
12.2k
                     EQUAL(BareGMLElement(psChild->pszValue), "curveMembers"))
2474
1.30k
            {
2475
1.30k
                for (const CPLXMLNode *psChild2 = psChild->psChild;
2476
5.00k
                     psChild2 != nullptr; psChild2 = psChild2->psNext)
2477
3.71k
                {
2478
3.71k
                    if (psChild2->eType == CXT_Element)
2479
2.02k
                    {
2480
2.02k
                        auto poGeom = GML2OGRGeometry_XMLNode_Internal(
2481
2.02k
                            psChild2, pszId,
2482
2.02k
                            nPseudoBoolGetSecondaryGeometryOption,
2483
2.02k
                            nRecLevel + 1, nSRSDimension, pszSRSName,
2484
2.02k
                            hSRSCache);
2485
2.02k
                        if (poGeom == nullptr ||
2486
2.00k
                            !OGR_GT_IsCurve(poGeom->getGeometryType()))
2487
16
                        {
2488
16
                            ReportFailure("MultiCurve: Got %s geometry as "
2489
16
                                          "Member instead of a curve.",
2490
16
                                          poGeom ? poGeom->getGeometryName()
2491
16
                                                 : "NULL");
2492
16
                            return nullptr;
2493
16
                        }
2494
2495
2.00k
                        if (wkbFlatten(poGeom->getGeometryType()) !=
2496
2.00k
                            wkbLineString)
2497
236
                            bChildrenAreAllLineString = false;
2498
2499
2.00k
                        if (poMC->addGeometry(std::move(poGeom)) != OGRERR_NONE)
2500
0
                        {
2501
0
                            return nullptr;
2502
0
                        }
2503
2.00k
                    }
2504
3.71k
                }
2505
1.30k
            }
2506
29.9k
        }
2507
2508
1.54k
        if (/* bCastToLinearTypeIfPossible && */ bChildrenAreAllLineString)
2509
1.53k
        {
2510
1.53k
            return std::unique_ptr<OGRMultiLineString>(
2511
1.53k
                OGRMultiCurve::CastToMultiLineString(poMC.release()));
2512
1.53k
        }
2513
2514
16
        return poMC;
2515
1.54k
    }
2516
2517
    /* -------------------------------------------------------------------- */
2518
    /*      CompositeCurve                                                  */
2519
    /* -------------------------------------------------------------------- */
2520
57.9k
    if (EQUAL(pszBaseGeometry, "CompositeCurve"))
2521
1.82k
    {
2522
1.82k
        auto poCC = std::make_unique<OGRCompoundCurve>();
2523
1.82k
        bool bChildrenAreAllLineString = true;
2524
2525
        // Collect curveMembers.
2526
131k
        for (const CPLXMLNode *psChild = psNode->psChild; psChild != nullptr;
2527
129k
             psChild = psChild->psNext)
2528
129k
        {
2529
129k
            if (psChild->eType == CXT_Element &&
2530
3.97k
                EQUAL(BareGMLElement(psChild->pszValue), "curveMember"))
2531
1.30k
            {
2532
1.30k
                const CPLXMLNode *psChild2 = GetChildElement(psChild);
2533
1.30k
                if (psChild2 != nullptr)  // Empty curveMember is valid.
2534
0
                {
2535
0
                    auto poGeom = GML2OGRGeometry_XMLNode_Internal(
2536
0
                        psChild2, pszId, nPseudoBoolGetSecondaryGeometryOption,
2537
0
                        nRecLevel + 1, nSRSDimension, pszSRSName, hSRSCache);
2538
0
                    if (!GML2OGRGeometry_AddToCompositeCurve(
2539
0
                            poCC.get(), std::move(poGeom),
2540
0
                            bChildrenAreAllLineString))
2541
0
                    {
2542
0
                        return nullptr;
2543
0
                    }
2544
0
                }
2545
1.30k
            }
2546
128k
            else if (psChild->eType == CXT_Element &&
2547
2.67k
                     EQUAL(BareGMLElement(psChild->pszValue), "curveMembers"))
2548
397
            {
2549
397
                for (const CPLXMLNode *psChild2 = psChild->psChild;
2550
3.18k
                     psChild2 != nullptr; psChild2 = psChild2->psNext)
2551
2.89k
                {
2552
2.89k
                    if (psChild2->eType == CXT_Element)
2553
1.29k
                    {
2554
1.29k
                        auto poGeom = GML2OGRGeometry_XMLNode_Internal(
2555
1.29k
                            psChild2, pszId,
2556
1.29k
                            nPseudoBoolGetSecondaryGeometryOption,
2557
1.29k
                            nRecLevel + 1, nSRSDimension, pszSRSName,
2558
1.29k
                            hSRSCache);
2559
1.29k
                        if (!GML2OGRGeometry_AddToCompositeCurve(
2560
1.29k
                                poCC.get(), std::move(poGeom),
2561
1.29k
                                bChildrenAreAllLineString))
2562
103
                        {
2563
103
                            return nullptr;
2564
103
                        }
2565
1.29k
                    }
2566
2.89k
                }
2567
397
            }
2568
129k
        }
2569
2570
1.72k
        if (/* bCastToLinearTypeIfPossible && */ bChildrenAreAllLineString)
2571
1.60k
        {
2572
1.60k
            return std::unique_ptr<OGRLineString>(
2573
1.60k
                OGRCurve::CastToLineString(poCC.release()));
2574
1.60k
        }
2575
2576
117
        return poCC;
2577
1.72k
    }
2578
2579
    /* -------------------------------------------------------------------- */
2580
    /*      Curve                                                           */
2581
    /* -------------------------------------------------------------------- */
2582
56.0k
    if (EQUAL(pszBaseGeometry, "Curve") ||
2583
55.2k
        EQUAL(pszBaseGeometry, "ElevatedCurve") /* AIXM */)
2584
845
    {
2585
845
        const CPLXMLNode *psChild = FindBareXMLChild(psNode, "segments");
2586
845
        if (psChild == nullptr)
2587
47
        {
2588
47
            ReportFailure("GML3 Curve geometry lacks segments element.");
2589
47
            return nullptr;
2590
47
        }
2591
2592
798
        auto poGeom = GML2OGRGeometry_XMLNode_Internal(
2593
798
            psChild, pszId, nPseudoBoolGetSecondaryGeometryOption,
2594
798
            nRecLevel + 1, nSRSDimension, pszSRSName, hSRSCache);
2595
798
        if (poGeom == nullptr || !OGR_GT_IsCurve(poGeom->getGeometryType()))
2596
34
        {
2597
34
            ReportFailure(
2598
34
                "Curve: Got %s geometry as Member instead of segments.",
2599
34
                poGeom ? poGeom->getGeometryName() : "NULL");
2600
34
            return nullptr;
2601
34
        }
2602
2603
764
        return poGeom;
2604
798
    }
2605
2606
    /* -------------------------------------------------------------------- */
2607
    /*      segments                                                        */
2608
    /* -------------------------------------------------------------------- */
2609
55.2k
    if (EQUAL(pszBaseGeometry, "segments"))
2610
39.6k
    {
2611
39.6k
        std::unique_ptr<OGRCurve> poCurve;
2612
39.6k
        std::unique_ptr<OGRCompoundCurve> poCC;
2613
39.6k
        bool bChildrenAreAllLineString = true;
2614
2615
39.6k
        bool bLastCurveWasApproximateArc = false;
2616
39.6k
        bool bLastCurveWasApproximateArcInvertedAxisOrder = false;
2617
39.6k
        double dfLastCurveApproximateArcRadius = 0.0;
2618
39.6k
        double dfSemiMajor = OGR_GREATCIRCLE_DEFAULT_RADIUS;
2619
2620
266k
        for (const CPLXMLNode *psChild = psNode->psChild; psChild != nullptr;
2621
227k
             psChild = psChild->psNext)
2622
2623
236k
        {
2624
236k
            if (psChild->eType == CXT_Element
2625
                // && (EQUAL(BareGMLElement(psChild->pszValue),
2626
                //           "LineStringSegment") ||
2627
                //     EQUAL(BareGMLElement(psChild->pszValue),
2628
                //           "GeodesicString") ||
2629
                //    EQUAL(BareGMLElement(psChild->pszValue), "Arc") ||
2630
                //    EQUAL(BareGMLElement(psChild->pszValue), "Circle"))
2631
236k
            )
2632
117k
            {
2633
117k
                auto poGeom = GML2OGRGeometry_XMLNode_Internal(
2634
117k
                    psChild, pszId, nPseudoBoolGetSecondaryGeometryOption,
2635
117k
                    nRecLevel + 1, nSRSDimension, pszSRSName, hSRSCache);
2636
117k
                if (poGeom == nullptr ||
2637
108k
                    !OGR_GT_IsCurve(poGeom->getGeometryType()))
2638
9.14k
                {
2639
9.14k
                    ReportFailure("segments: Got %s geometry as Member "
2640
9.14k
                                  "instead of curve.",
2641
9.14k
                                  poGeom ? poGeom->getGeometryName() : "NULL");
2642
9.14k
                    return nullptr;
2643
9.14k
                }
2644
2645
                // Ad-hoc logic to handle nicely connecting ArcByCenterPoint
2646
                // with consecutive curves, as found in some AIXM files.
2647
108k
                bool bIsApproximateArc = false;
2648
108k
                if (strcmp(BareGMLElement(psChild->pszValue),
2649
108k
                           "ArcByCenterPoint") == 0)
2650
77.8k
                {
2651
77.8k
                    storeArcByCenterPointParameters(
2652
77.8k
                        psChild, pszSRSName, bIsApproximateArc,
2653
77.8k
                        dfLastCurveApproximateArcRadius,
2654
77.8k
                        bLastCurveWasApproximateArcInvertedAxisOrder,
2655
77.8k
                        dfSemiMajor);
2656
77.8k
                }
2657
2658
108k
                if (wkbFlatten(poGeom->getGeometryType()) != wkbLineString)
2659
106k
                    bChildrenAreAllLineString = false;
2660
2661
108k
                if (poCC == nullptr && poCurve == nullptr)
2662
33.6k
                {
2663
33.6k
                    poCurve.reset(poGeom.release()->toCurve());
2664
33.6k
                }
2665
74.5k
                else
2666
74.5k
                {
2667
74.5k
                    if (poCC == nullptr)
2668
7.51k
                    {
2669
7.51k
                        poCC = std::make_unique<OGRCompoundCurve>();
2670
7.51k
                        if (poCC->addCurve(std::move(poCurve)) != OGRERR_NONE)
2671
77
                        {
2672
77
                            return nullptr;
2673
77
                        }
2674
7.43k
                        poCurve.reset();
2675
7.43k
                    }
2676
2677
74.4k
                    connectArcByCenterPointToOtherSegments(
2678
74.4k
                        poGeom.get(), poCC.get(), bIsApproximateArc,
2679
74.4k
                        bLastCurveWasApproximateArc,
2680
74.4k
                        dfLastCurveApproximateArcRadius,
2681
74.4k
                        bLastCurveWasApproximateArcInvertedAxisOrder,
2682
74.4k
                        dfSemiMajor);
2683
2684
74.4k
                    auto poAsCurve =
2685
74.4k
                        std::unique_ptr<OGRCurve>(poGeom.release()->toCurve());
2686
74.4k
                    if (poCC->addCurve(std::move(poAsCurve)) != OGRERR_NONE)
2687
466
                    {
2688
466
                        return nullptr;
2689
466
                    }
2690
74.4k
                }
2691
2692
107k
                bLastCurveWasApproximateArc = bIsApproximateArc;
2693
107k
            }
2694
236k
        }
2695
2696
29.9k
        if (poCurve != nullptr)
2697
24.4k
            return poCurve;
2698
5.57k
        if (poCC == nullptr)
2699
999
            return std::make_unique<OGRLineString>();
2700
2701
4.57k
        if (/* bCastToLinearTypeIfPossible && */ bChildrenAreAllLineString)
2702
13
        {
2703
13
            return std::unique_ptr<OGRLineString>(
2704
13
                OGRCurve::CastToLineString(poCC.release()));
2705
13
        }
2706
2707
4.55k
        return poCC;
2708
4.57k
    }
2709
2710
    /* -------------------------------------------------------------------- */
2711
    /*      MultiGeometry                                                   */
2712
    /* CAUTION: OGR < 1.8.0 produced GML with GeometryCollection, which is  */
2713
    /* not a valid GML 2 keyword! The right name is MultiGeometry. Let's be */
2714
    /* tolerant with the non compliant files we produced.                   */
2715
    /* -------------------------------------------------------------------- */
2716
15.5k
    if (EQUAL(pszBaseGeometry, "MultiGeometry") ||
2717
15.3k
        EQUAL(pszBaseGeometry, "GeometryCollection"))
2718
174
    {
2719
174
        auto poGC = std::make_unique<OGRGeometryCollection>();
2720
2721
        // Collect geoms.
2722
9.69k
        for (const CPLXMLNode *psChild = psNode->psChild; psChild != nullptr;
2723
9.52k
             psChild = psChild->psNext)
2724
9.52k
        {
2725
9.52k
            if (psChild->eType == CXT_Element &&
2726
4.34k
                EQUAL(BareGMLElement(psChild->pszValue), "geometryMember"))
2727
1.26k
            {
2728
1.26k
                const CPLXMLNode *psGeometryChild = GetChildElement(psChild);
2729
2730
1.26k
                if (psGeometryChild != nullptr)
2731
0
                {
2732
0
                    auto poGeom = GML2OGRGeometry_XMLNode_Internal(
2733
0
                        psGeometryChild, pszId,
2734
0
                        nPseudoBoolGetSecondaryGeometryOption, nRecLevel + 1,
2735
0
                        nSRSDimension, pszSRSName, hSRSCache);
2736
0
                    if (poGeom == nullptr)
2737
0
                    {
2738
0
                        ReportFailure(
2739
0
                            "GeometryCollection: Failed to get geometry "
2740
0
                            "in geometryMember");
2741
0
                        return nullptr;
2742
0
                    }
2743
2744
0
                    poGC->addGeometry(std::move(poGeom));
2745
0
                }
2746
1.26k
            }
2747
8.26k
            else if (psChild->eType == CXT_Element &&
2748
3.08k
                     EQUAL(BareGMLElement(psChild->pszValue),
2749
8.26k
                           "geometryMembers"))
2750
1.07k
            {
2751
1.07k
                for (const CPLXMLNode *psChild2 = psChild->psChild;
2752
6.97k
                     psChild2 != nullptr; psChild2 = psChild2->psNext)
2753
5.89k
                {
2754
5.89k
                    if (psChild2->eType == CXT_Element)
2755
0
                    {
2756
0
                        auto poGeom = GML2OGRGeometry_XMLNode_Internal(
2757
0
                            psChild2, pszId,
2758
0
                            nPseudoBoolGetSecondaryGeometryOption,
2759
0
                            nRecLevel + 1, nSRSDimension, pszSRSName,
2760
0
                            hSRSCache);
2761
0
                        if (poGeom == nullptr)
2762
0
                        {
2763
0
                            ReportFailure(
2764
0
                                "GeometryCollection: Failed to get geometry "
2765
0
                                "in geometryMember");
2766
0
                            return nullptr;
2767
0
                        }
2768
2769
0
                        poGC->addGeometry(std::move(poGeom));
2770
0
                    }
2771
5.89k
                }
2772
1.07k
            }
2773
9.52k
        }
2774
2775
174
        return poGC;
2776
174
    }
2777
2778
    /* -------------------------------------------------------------------- */
2779
    /*      Directed Edge                                                   */
2780
    /* -------------------------------------------------------------------- */
2781
15.3k
    if (EQUAL(pszBaseGeometry, "directedEdge"))
2782
997
    {
2783
        // Collect edge.
2784
997
        const CPLXMLNode *psEdge = FindBareXMLChild(psNode, "Edge");
2785
997
        if (psEdge == nullptr)
2786
4
        {
2787
4
            ReportFailure("Failed to get Edge element in directedEdge");
2788
4
            return nullptr;
2789
4
        }
2790
2791
        // TODO(schwehr): Localize vars after removing gotos.
2792
993
        std::unique_ptr<OGRGeometry> poGeom;
2793
993
        const CPLXMLNode *psNodeElement = nullptr;
2794
993
        const CPLXMLNode *psPointProperty = nullptr;
2795
993
        const CPLXMLNode *psPoint = nullptr;
2796
993
        bool bNodeOrientation = true;
2797
993
        std::unique_ptr<OGRPoint> poPositiveNode;
2798
993
        std::unique_ptr<OGRPoint> poNegativeNode;
2799
2800
993
        const bool bEdgeOrientation = GetElementOrientation(psNode);
2801
2802
993
        if (bGetSecondaryGeometry)
2803
0
        {
2804
0
            const CPLXMLNode *psdirectedNode =
2805
0
                FindBareXMLChild(psEdge, "directedNode");
2806
0
            if (psdirectedNode == nullptr)
2807
0
                goto nonode;
2808
2809
0
            bNodeOrientation = GetElementOrientation(psdirectedNode);
2810
2811
0
            psNodeElement = FindBareXMLChild(psdirectedNode, "Node");
2812
0
            if (psNodeElement == nullptr)
2813
0
                goto nonode;
2814
2815
0
            psPointProperty = FindBareXMLChild(psNodeElement, "pointProperty");
2816
0
            if (psPointProperty == nullptr)
2817
0
                psPointProperty =
2818
0
                    FindBareXMLChild(psNodeElement, "connectionPointProperty");
2819
0
            if (psPointProperty == nullptr)
2820
0
                goto nonode;
2821
2822
0
            psPoint = FindBareXMLChild(psPointProperty, "Point");
2823
0
            if (psPoint == nullptr)
2824
0
                psPoint = FindBareXMLChild(psPointProperty, "ConnectionPoint");
2825
0
            if (psPoint == nullptr)
2826
0
                goto nonode;
2827
2828
0
            poGeom = GML2OGRGeometry_XMLNode_Internal(
2829
0
                psPoint, pszId, nPseudoBoolGetSecondaryGeometryOption,
2830
0
                nRecLevel + 1, nSRSDimension, pszSRSName, hSRSCache, true);
2831
0
            if (poGeom == nullptr ||
2832
0
                wkbFlatten(poGeom->getGeometryType()) != wkbPoint)
2833
0
            {
2834
                // ReportFailure(
2835
                //           "Got %s geometry as Member instead of POINT.",
2836
                //           poGeom ? poGeom->getGeometryName() : "NULL" );
2837
0
                goto nonode;
2838
0
            }
2839
2840
0
            {
2841
0
                OGRPoint *poPoint = poGeom.release()->toPoint();
2842
0
                if ((bNodeOrientation == bEdgeOrientation) != bOrientation)
2843
0
                    poPositiveNode.reset(poPoint);
2844
0
                else
2845
0
                    poNegativeNode.reset(poPoint);
2846
0
            }
2847
2848
            // Look for the other node.
2849
0
            psdirectedNode = psdirectedNode->psNext;
2850
0
            while (psdirectedNode != nullptr &&
2851
0
                   !EQUAL(psdirectedNode->pszValue, "directedNode"))
2852
0
                psdirectedNode = psdirectedNode->psNext;
2853
0
            if (psdirectedNode == nullptr)
2854
0
                goto nonode;
2855
2856
0
            if (GetElementOrientation(psdirectedNode) == bNodeOrientation)
2857
0
                goto nonode;
2858
2859
0
            psNodeElement = FindBareXMLChild(psEdge, "Node");
2860
0
            if (psNodeElement == nullptr)
2861
0
                goto nonode;
2862
2863
0
            psPointProperty = FindBareXMLChild(psNodeElement, "pointProperty");
2864
0
            if (psPointProperty == nullptr)
2865
0
                psPointProperty =
2866
0
                    FindBareXMLChild(psNodeElement, "connectionPointProperty");
2867
0
            if (psPointProperty == nullptr)
2868
0
                goto nonode;
2869
2870
0
            psPoint = FindBareXMLChild(psPointProperty, "Point");
2871
0
            if (psPoint == nullptr)
2872
0
                psPoint = FindBareXMLChild(psPointProperty, "ConnectionPoint");
2873
0
            if (psPoint == nullptr)
2874
0
                goto nonode;
2875
2876
0
            poGeom = GML2OGRGeometry_XMLNode_Internal(
2877
0
                psPoint, pszId, nPseudoBoolGetSecondaryGeometryOption,
2878
0
                nRecLevel + 1, nSRSDimension, pszSRSName, hSRSCache, true);
2879
0
            if (poGeom == nullptr ||
2880
0
                wkbFlatten(poGeom->getGeometryType()) != wkbPoint)
2881
0
            {
2882
                // ReportFailure(
2883
                //           "Got %s geometry as Member instead of POINT.",
2884
                //           poGeom ? poGeom->getGeometryName() : "NULL" );
2885
0
                goto nonode;
2886
0
            }
2887
2888
0
            {
2889
0
                OGRPoint *poPoint = poGeom.release()->toPoint();
2890
0
                if ((bNodeOrientation == bEdgeOrientation) != bOrientation)
2891
0
                    poNegativeNode.reset(poPoint);
2892
0
                else
2893
0
                    poPositiveNode.reset(poPoint);
2894
0
            }
2895
2896
0
            {
2897
                // Create a scope so that poMP can be initialized with goto
2898
                // above and label below.
2899
0
                auto poMP = std::make_unique<OGRMultiPoint>();
2900
0
                poMP->addGeometry(std::move(poNegativeNode));
2901
0
                poMP->addGeometry(std::move(poPositiveNode));
2902
2903
0
                return poMP;
2904
0
            }
2905
0
        nonode:;
2906
0
        }
2907
2908
        // Collect curveproperty.
2909
993
        const CPLXMLNode *psCurveProperty =
2910
993
            FindBareXMLChild(psEdge, "curveProperty");
2911
993
        if (psCurveProperty == nullptr)
2912
3
        {
2913
3
            ReportFailure("directedEdge: Failed to get curveProperty in Edge");
2914
3
            return nullptr;
2915
3
        }
2916
2917
990
        const CPLXMLNode *psCurve =
2918
990
            FindBareXMLChild(psCurveProperty, "LineString");
2919
990
        if (psCurve == nullptr)
2920
287
            psCurve = FindBareXMLChild(psCurveProperty, "Curve");
2921
990
        if (psCurve == nullptr)
2922
0
        {
2923
0
            ReportFailure("directedEdge: Failed to get LineString or "
2924
0
                          "Curve tag in curveProperty");
2925
0
            return nullptr;
2926
0
        }
2927
2928
990
        auto poLineStringBeforeCast = GML2OGRGeometry_XMLNode_Internal(
2929
990
            psCurve, pszId, nPseudoBoolGetSecondaryGeometryOption,
2930
990
            nRecLevel + 1, nSRSDimension, pszSRSName, hSRSCache, true);
2931
990
        if (poLineStringBeforeCast == nullptr ||
2932
955
            wkbFlatten(poLineStringBeforeCast->getGeometryType()) !=
2933
955
                wkbLineString)
2934
35
        {
2935
35
            ReportFailure("Got %s geometry as Member instead of LINESTRING.",
2936
35
                          poLineStringBeforeCast
2937
35
                              ? poLineStringBeforeCast->getGeometryName()
2938
35
                              : "NULL");
2939
35
            return nullptr;
2940
35
        }
2941
955
        auto poLineString = std::unique_ptr<OGRLineString>(
2942
955
            poLineStringBeforeCast.release()->toLineString());
2943
2944
955
        if (bGetSecondaryGeometry)
2945
0
        {
2946
            // Choose a point based on the orientation.
2947
0
            poNegativeNode = std::make_unique<OGRPoint>();
2948
0
            poPositiveNode = std::make_unique<OGRPoint>();
2949
0
            if (bEdgeOrientation == bOrientation)
2950
0
            {
2951
0
                poLineString->StartPoint(poNegativeNode.get());
2952
0
                poLineString->EndPoint(poPositiveNode.get());
2953
0
            }
2954
0
            else
2955
0
            {
2956
0
                poLineString->StartPoint(poPositiveNode.get());
2957
0
                poLineString->EndPoint(poNegativeNode.get());
2958
0
            }
2959
2960
0
            auto poMP = std::make_unique<OGRMultiPoint>();
2961
0
            poMP->addGeometry(std::move(poNegativeNode));
2962
0
            poMP->addGeometry(std::move(poPositiveNode));
2963
0
            return poMP;
2964
0
        }
2965
2966
        // correct orientation of the line string
2967
955
        if (bEdgeOrientation != bOrientation)
2968
0
        {
2969
0
            poLineString->reversePoints();
2970
0
        }
2971
955
        return poLineString;
2972
955
    }
2973
2974
    /* -------------------------------------------------------------------- */
2975
    /*      TopoCurve                                                       */
2976
    /* -------------------------------------------------------------------- */
2977
14.3k
    if (EQUAL(pszBaseGeometry, "TopoCurve"))
2978
990
    {
2979
990
        std::unique_ptr<OGRMultiLineString> poMLS;
2980
990
        std::unique_ptr<OGRMultiPoint> poMP;
2981
2982
990
        if (bGetSecondaryGeometry)
2983
0
            poMP = std::make_unique<OGRMultiPoint>();
2984
990
        else
2985
990
            poMLS = std::make_unique<OGRMultiLineString>();
2986
2987
        // Collect directedEdges.
2988
5.22k
        for (const CPLXMLNode *psChild = psNode->psChild; psChild != nullptr;
2989
4.23k
             psChild = psChild->psNext)
2990
4.27k
        {
2991
4.27k
            if (psChild->eType == CXT_Element &&
2992
2.19k
                EQUAL(BareGMLElement(psChild->pszValue), "directedEdge"))
2993
995
            {
2994
995
                auto poGeom = GML2OGRGeometry_XMLNode_Internal(
2995
995
                    psChild, pszId, nPseudoBoolGetSecondaryGeometryOption,
2996
995
                    nRecLevel + 1, nSRSDimension, pszSRSName, hSRSCache);
2997
995
                if (poGeom == nullptr)
2998
40
                {
2999
40
                    ReportFailure("Failed to get geometry in directedEdge");
3000
40
                    return nullptr;
3001
40
                }
3002
3003
                // Add the two points corresponding to the two nodes to poMP.
3004
955
                if (bGetSecondaryGeometry &&
3005
0
                    wkbFlatten(poGeom->getGeometryType()) == wkbMultiPoint)
3006
0
                {
3007
0
                    auto poMultiPoint = std::unique_ptr<OGRMultiPoint>(
3008
0
                        poGeom.release()->toMultiPoint());
3009
3010
                    // TODO: TopoCurve geometries with more than one
3011
                    //       directedEdge elements were not tested.
3012
0
                    if (poMP->getNumGeometries() <= 0 ||
3013
0
                        !(poMP->getGeometryRef(poMP->getNumGeometries() - 1)
3014
0
                              ->Equals(poMultiPoint->getGeometryRef(0))))
3015
0
                    {
3016
0
                        poMP->addGeometry(poMultiPoint->getGeometryRef(0));
3017
0
                    }
3018
0
                    poMP->addGeometry(poMultiPoint->getGeometryRef(1));
3019
0
                }
3020
955
                else if (!bGetSecondaryGeometry &&
3021
955
                         wkbFlatten(poGeom->getGeometryType()) == wkbLineString)
3022
955
                {
3023
955
                    poMLS->addGeometry(std::move(poGeom));
3024
955
                }
3025
0
                else
3026
0
                {
3027
0
                    ReportFailure("Got %s geometry as Member instead of %s.",
3028
0
                                  poGeom->getGeometryName(),
3029
0
                                  bGetSecondaryGeometry ? "MULTIPOINT"
3030
0
                                                        : "LINESTRING");
3031
0
                    return nullptr;
3032
0
                }
3033
955
            }
3034
4.27k
        }
3035
3036
950
        if (bGetSecondaryGeometry)
3037
0
            return poMP;
3038
3039
950
        return poMLS;
3040
950
    }
3041
3042
    /* -------------------------------------------------------------------- */
3043
    /*      TopoSurface                                                     */
3044
    /* -------------------------------------------------------------------- */
3045
13.4k
    if (EQUAL(pszBaseGeometry, "TopoSurface"))
3046
43
    {
3047
        /****************************************************************/
3048
        /* applying the FaceHoleNegative = false rules                  */
3049
        /*                                                              */
3050
        /* - each <TopoSurface> is expected to represent a MultiPolygon */
3051
        /* - each <Face> is expected to represent a distinct Polygon,   */
3052
        /*   this including any possible Interior Ring (holes);         */
3053
        /*   orientation="+/-" plays no role at all to identify "holes" */
3054
        /* - each <Edge> within a <Face> may indifferently represent    */
3055
        /*   an element of the Exterior or Interior Boundary; relative  */
3056
        /*   order of <Edges> is absolutely irrelevant.                 */
3057
        /****************************************************************/
3058
        /* Contributor: Alessandro Furieri, a.furieri@lqt.it            */
3059
        /* Developed for Faunalia (http://www.faunalia.it)              */
3060
        /* with funding from Regione Toscana -                          */
3061
        /* Settore SISTEMA INFORMATIVO TERRITORIALE ED AMBIENTALE       */
3062
        /****************************************************************/
3063
43
        if (!bFaceHoleNegative)
3064
43
        {
3065
43
            if (bGetSecondaryGeometry)
3066
0
                return nullptr;
3067
3068
43
#ifndef HAVE_GEOS
3069
43
            static bool bWarningAlreadyEmitted = false;
3070
43
            if (!bWarningAlreadyEmitted)
3071
3
            {
3072
3
                ReportFailure(
3073
3
                    "Interpreating that GML TopoSurface geometry requires GDAL "
3074
3
                    "to be built with GEOS support.  As a workaround, you can "
3075
3
                    "try defining the GML_FACE_HOLE_NEGATIVE configuration "
3076
3
                    "option to YES, so that the 'old' interpretation algorithm "
3077
3
                    "is used. But be warned that the result might be "
3078
3
                    "incorrect.");
3079
3
                bWarningAlreadyEmitted = true;
3080
3
            }
3081
43
            return nullptr;
3082
#else
3083
            auto poTS = std::make_unique<OGRMultiPolygon>();
3084
3085
            // Collect directed faces.
3086
            for (const CPLXMLNode *psChild = psNode->psChild;
3087
                 psChild != nullptr; psChild = psChild->psNext)
3088
            {
3089
                if (psChild->eType == CXT_Element &&
3090
                    EQUAL(BareGMLElement(psChild->pszValue), "directedFace"))
3091
                {
3092
                    // Collect next face (psChild->psChild).
3093
                    const CPLXMLNode *psFaceChild = GetChildElement(psChild);
3094
3095
                    while (
3096
                        psFaceChild != nullptr &&
3097
                        !(psFaceChild->eType == CXT_Element &&
3098
                          EQUAL(BareGMLElement(psFaceChild->pszValue), "Face")))
3099
                        psFaceChild = psFaceChild->psNext;
3100
3101
                    if (psFaceChild == nullptr)
3102
                        continue;
3103
3104
                    auto poCollectedGeom =
3105
                        std::make_unique<OGRMultiLineString>();
3106
3107
                    // Collect directed edges of the face.
3108
                    for (const CPLXMLNode *psDirectedEdgeChild =
3109
                             psFaceChild->psChild;
3110
                         psDirectedEdgeChild != nullptr;
3111
                         psDirectedEdgeChild = psDirectedEdgeChild->psNext)
3112
                    {
3113
                        if (psDirectedEdgeChild->eType == CXT_Element &&
3114
                            EQUAL(BareGMLElement(psDirectedEdgeChild->pszValue),
3115
                                  "directedEdge"))
3116
                        {
3117
                            auto poEdgeGeom = GML2OGRGeometry_XMLNode_Internal(
3118
                                psDirectedEdgeChild, pszId,
3119
                                nPseudoBoolGetSecondaryGeometryOption,
3120
                                nRecLevel + 1, nSRSDimension, pszSRSName,
3121
                                hSRSCache, true);
3122
3123
                            if (poEdgeGeom == nullptr ||
3124
                                wkbFlatten(poEdgeGeom->getGeometryType()) !=
3125
                                    wkbLineString)
3126
                            {
3127
                                ReportFailure(
3128
                                    "Failed to get geometry in directedEdge");
3129
                                return nullptr;
3130
                            }
3131
3132
                            poCollectedGeom->addGeometry(std::move(poEdgeGeom));
3133
                        }
3134
                    }
3135
3136
                    auto poFaceCollectionGeom = std::unique_ptr<OGRGeometry>(
3137
                        poCollectedGeom->Polygonize());
3138
                    if (poFaceCollectionGeom == nullptr)
3139
                    {
3140
                        ReportFailure("Failed to assemble Edges in Face");
3141
                        return nullptr;
3142
                    }
3143
3144
                    auto poFaceGeom =
3145
                        GML2FaceExtRing(poFaceCollectionGeom.get());
3146
3147
                    if (poFaceGeom == nullptr)
3148
                    {
3149
                        ReportFailure("Failed to build Polygon for Face");
3150
                        return nullptr;
3151
                    }
3152
                    else
3153
                    {
3154
                        int iCount = poTS->getNumGeometries();
3155
                        if (iCount == 0)
3156
                        {
3157
                            // Inserting the first Polygon.
3158
                            poTS->addGeometry(std::move(poFaceGeom));
3159
                        }
3160
                        else
3161
                        {
3162
                            // Using Union to add the current Polygon.
3163
                            auto poUnion = std::unique_ptr<OGRGeometry>(
3164
                                poTS->Union(poFaceGeom.get()));
3165
                            if (poUnion == nullptr)
3166
                            {
3167
                                ReportFailure("Failed Union for TopoSurface");
3168
                                return nullptr;
3169
                            }
3170
                            if (wkbFlatten(poUnion->getGeometryType()) ==
3171
                                wkbPolygon)
3172
                            {
3173
                                // Forcing to be a MultiPolygon.
3174
                                poTS = std::make_unique<OGRMultiPolygon>();
3175
                                poTS->addGeometry(std::move(poUnion));
3176
                            }
3177
                            else if (wkbFlatten(poUnion->getGeometryType()) ==
3178
                                     wkbMultiPolygon)
3179
                            {
3180
                                poTS.reset(poUnion.release()->toMultiPolygon());
3181
                            }
3182
                            else
3183
                            {
3184
                                ReportFailure(
3185
                                    "Unexpected geometry type resulting "
3186
                                    "from Union for TopoSurface");
3187
                                return nullptr;
3188
                            }
3189
                        }
3190
                    }
3191
                }
3192
            }
3193
3194
            return poTS;
3195
#endif  // HAVE_GEOS
3196
43
        }
3197
3198
        /****************************************************************/
3199
        /* applying the FaceHoleNegative = true rules                   */
3200
        /*                                                              */
3201
        /* - each <TopoSurface> is expected to represent a MultiPolygon */
3202
        /* - any <Face> declaring orientation="+" is expected to        */
3203
        /*   represent an Exterior Ring (no holes are allowed)          */
3204
        /* - any <Face> declaring orientation="-" is expected to        */
3205
        /*   represent an Interior Ring (hole) belonging to the latest  */
3206
        /*   Exterior Ring.                                             */
3207
        /* - <Edges> within the same <Face> are expected to be          */
3208
        /*   arranged in geometrically adjacent and consecutive         */
3209
        /*   sequence.                                                  */
3210
        /****************************************************************/
3211
0
        if (bGetSecondaryGeometry)
3212
0
            return nullptr;
3213
0
        bool bFaceOrientation = true;
3214
0
        auto poTS = std::make_unique<OGRPolygon>();
3215
3216
        // Collect directed faces.
3217
0
        for (const CPLXMLNode *psChild = psNode->psChild; psChild != nullptr;
3218
0
             psChild = psChild->psNext)
3219
0
        {
3220
0
            if (psChild->eType == CXT_Element &&
3221
0
                EQUAL(BareGMLElement(psChild->pszValue), "directedFace"))
3222
0
            {
3223
0
                bFaceOrientation = GetElementOrientation(psChild);
3224
3225
                // Collect next face (psChild->psChild).
3226
0
                const CPLXMLNode *psFaceChild = GetChildElement(psChild);
3227
0
                while (psFaceChild != nullptr &&
3228
0
                       !EQUAL(BareGMLElement(psFaceChild->pszValue), "Face"))
3229
0
                    psFaceChild = psFaceChild->psNext;
3230
3231
0
                if (psFaceChild == nullptr)
3232
0
                    continue;
3233
3234
0
                auto poFaceGeom = std::make_unique<OGRLinearRing>();
3235
3236
                // Collect directed edges of the face.
3237
0
                for (const CPLXMLNode *psDirectedEdgeChild =
3238
0
                         psFaceChild->psChild;
3239
0
                     psDirectedEdgeChild != nullptr;
3240
0
                     psDirectedEdgeChild = psDirectedEdgeChild->psNext)
3241
0
                {
3242
0
                    if (psDirectedEdgeChild->eType == CXT_Element &&
3243
0
                        EQUAL(BareGMLElement(psDirectedEdgeChild->pszValue),
3244
0
                              "directedEdge"))
3245
0
                    {
3246
0
                        auto poEdgeGeom = GML2OGRGeometry_XMLNode_Internal(
3247
0
                            psDirectedEdgeChild, pszId,
3248
0
                            nPseudoBoolGetSecondaryGeometryOption,
3249
0
                            nRecLevel + 1, nSRSDimension, pszSRSName, hSRSCache,
3250
0
                            true, bFaceOrientation);
3251
3252
0
                        if (poEdgeGeom == nullptr ||
3253
0
                            wkbFlatten(poEdgeGeom->getGeometryType()) !=
3254
0
                                wkbLineString)
3255
0
                        {
3256
0
                            ReportFailure(
3257
0
                                "Failed to get geometry in directedEdge");
3258
0
                            return nullptr;
3259
0
                        }
3260
3261
0
                        auto poEdgeGeomLS = std::unique_ptr<OGRLineString>(
3262
0
                            poEdgeGeom.release()->toLineString());
3263
0
                        if (!bFaceOrientation)
3264
0
                        {
3265
0
                            OGRLineString *poLS = poEdgeGeomLS.get();
3266
0
                            OGRLineString *poAddLS = poFaceGeom.get();
3267
3268
                            // TODO(schwehr): Use AlmostEqual.
3269
0
                            const double epsilon = 1.0e-14;
3270
0
                            if (poAddLS->getNumPoints() < 2)
3271
0
                            {
3272
                                // Skip it.
3273
0
                            }
3274
0
                            else if (poLS->getNumPoints() > 0 &&
3275
0
                                     fabs(poLS->getX(poLS->getNumPoints() - 1) -
3276
0
                                          poAddLS->getX(0)) < epsilon &&
3277
0
                                     fabs(poLS->getY(poLS->getNumPoints() - 1) -
3278
0
                                          poAddLS->getY(0)) < epsilon &&
3279
0
                                     fabs(poLS->getZ(poLS->getNumPoints() - 1) -
3280
0
                                          poAddLS->getZ(0)) < epsilon)
3281
0
                            {
3282
                                // Skip the first point of the new linestring to
3283
                                // avoid invalidate duplicate points.
3284
0
                                poLS->addSubLineString(poAddLS, 1);
3285
0
                            }
3286
0
                            else
3287
0
                            {
3288
                                // Add the whole new line string.
3289
0
                                poLS->addSubLineString(poAddLS);
3290
0
                            }
3291
0
                            poFaceGeom->empty();
3292
0
                        }
3293
                        // TODO(schwehr): Suspicious that poLS overwritten
3294
                        // without else.
3295
0
                        OGRLineString *poLS = poFaceGeom.get();
3296
0
                        OGRLineString *poAddLS = poEdgeGeomLS.get();
3297
0
                        if (poAddLS->getNumPoints() < 2)
3298
0
                        {
3299
                            // Skip it.
3300
0
                        }
3301
0
                        else if (poLS->getNumPoints() > 0 &&
3302
0
                                 fabs(poLS->getX(poLS->getNumPoints() - 1) -
3303
0
                                      poAddLS->getX(0)) < 1e-14 &&
3304
0
                                 fabs(poLS->getY(poLS->getNumPoints() - 1) -
3305
0
                                      poAddLS->getY(0)) < 1e-14 &&
3306
0
                                 fabs(poLS->getZ(poLS->getNumPoints() - 1) -
3307
0
                                      poAddLS->getZ(0)) < 1e-14)
3308
0
                        {
3309
                            // Skip the first point of the new linestring to
3310
                            // avoid invalidate duplicate points.
3311
0
                            poLS->addSubLineString(poAddLS, 1);
3312
0
                        }
3313
0
                        else
3314
0
                        {
3315
                            // Add the whole new line string.
3316
0
                            poLS->addSubLineString(poAddLS);
3317
0
                        }
3318
0
                    }
3319
0
                }
3320
3321
                // if( poFaceGeom == NULL )
3322
                // {
3323
                //     ReportFailure(
3324
                //               "Failed to get Face geometry in directedFace"
3325
                //               );
3326
                //     delete poFaceGeom;
3327
                //     return NULL;
3328
                // }
3329
3330
0
                poTS->addRing(std::move(poFaceGeom));
3331
0
            }
3332
0
        }
3333
3334
        // if( poTS == NULL )
3335
        // {
3336
        //     ReportFailure(
3337
        //               "Failed to get TopoSurface geometry" );
3338
        //     delete poTS;
3339
        //     return NULL;
3340
        // }
3341
3342
0
        return poTS;
3343
0
    }
3344
3345
    /* -------------------------------------------------------------------- */
3346
    /*      Surface                                                         */
3347
    /* -------------------------------------------------------------------- */
3348
13.3k
    if (EQUAL(pszBaseGeometry, "Surface") ||
3349
10.9k
        EQUAL(pszBaseGeometry, "ElevatedSurface") /* AIXM */)
3350
2.39k
    {
3351
        // Find outer ring.
3352
2.39k
        const CPLXMLNode *psChild = FindBareXMLChild(psNode, "patches");
3353
2.39k
        if (psChild == nullptr)
3354
2.39k
            psChild = FindBareXMLChild(psNode, "polygonPatches");
3355
2.39k
        if (psChild == nullptr)
3356
2.39k
            psChild = FindBareXMLChild(psNode, "trianglePatches");
3357
3358
2.39k
        psChild = GetChildElement(psChild);
3359
2.39k
        if (psChild == nullptr)
3360
2.39k
        {
3361
            // <gml:Surface/> and <gml:Surface><gml:patches/></gml:Surface> are
3362
            // valid GML.
3363
2.39k
            return std::make_unique<OGRPolygon>();
3364
2.39k
        }
3365
3366
4
        OGRMultiSurface *poMSPtr = nullptr;
3367
4
        std::unique_ptr<OGRGeometry> poResultPoly;
3368
4
        std::unique_ptr<OGRGeometry> poResultTri;
3369
4
        OGRTriangulatedSurface *poTINPtr = nullptr;
3370
8
        for (; psChild != nullptr; psChild = psChild->psNext)
3371
4
        {
3372
4
            if (psChild->eType == CXT_Element &&
3373
4
                (EQUAL(BareGMLElement(psChild->pszValue), "PolygonPatch") ||
3374
0
                 EQUAL(BareGMLElement(psChild->pszValue), "Rectangle")))
3375
4
            {
3376
4
                auto poGeom = GML2OGRGeometry_XMLNode_Internal(
3377
4
                    psChild, pszId, nPseudoBoolGetSecondaryGeometryOption,
3378
4
                    nRecLevel + 1, nSRSDimension, pszSRSName, hSRSCache);
3379
4
                if (poGeom == nullptr)
3380
0
                {
3381
0
                    return nullptr;
3382
0
                }
3383
3384
4
                const OGRwkbGeometryType eGeomType =
3385
4
                    wkbFlatten(poGeom->getGeometryType());
3386
3387
4
                if (poResultPoly == nullptr)
3388
4
                    poResultPoly = std::move(poGeom);
3389
0
                else
3390
0
                {
3391
0
                    if (poMSPtr == nullptr)
3392
0
                    {
3393
0
                        std::unique_ptr<OGRMultiSurface> poMS;
3394
0
                        if (wkbFlatten(poResultPoly->getGeometryType()) ==
3395
0
                                wkbPolygon &&
3396
0
                            eGeomType == wkbPolygon)
3397
0
                            poMS = std::make_unique<OGRMultiPolygon>();
3398
0
                        else
3399
0
                            poMS = std::make_unique<OGRMultiSurface>();
3400
0
                        OGRErr eErr =
3401
0
                            poMS->addGeometry(std::move(poResultPoly));
3402
0
                        CPL_IGNORE_RET_VAL(eErr);
3403
0
                        CPLAssert(eErr == OGRERR_NONE);
3404
0
                        poResultPoly = std::move(poMS);
3405
0
                        poMSPtr = cpl::down_cast<OGRMultiSurface *>(
3406
0
                            poResultPoly.get());
3407
0
                    }
3408
0
                    else if (eGeomType != wkbPolygon &&
3409
0
                             wkbFlatten(poResultPoly->getGeometryType()) ==
3410
0
                                 wkbMultiPolygon)
3411
0
                    {
3412
0
                        OGRMultiPolygon *poMultiPoly =
3413
0
                            poResultPoly.release()->toMultiPolygon();
3414
0
                        poResultPoly.reset(
3415
0
                            OGRMultiPolygon::CastToMultiSurface(poMultiPoly));
3416
0
                        poMSPtr = cpl::down_cast<OGRMultiSurface *>(
3417
0
                            poResultPoly.get());
3418
0
                    }
3419
0
                    OGRErr eErr = poMSPtr->addGeometry(std::move(poGeom));
3420
0
                    CPL_IGNORE_RET_VAL(eErr);
3421
0
                    CPLAssert(eErr == OGRERR_NONE);
3422
0
                }
3423
4
            }
3424
0
            else if (psChild->eType == CXT_Element &&
3425
0
                     EQUAL(BareGMLElement(psChild->pszValue), "Triangle"))
3426
0
            {
3427
0
                auto poGeom = GML2OGRGeometry_XMLNode_Internal(
3428
0
                    psChild, pszId, nPseudoBoolGetSecondaryGeometryOption,
3429
0
                    nRecLevel + 1, nSRSDimension, pszSRSName, hSRSCache);
3430
0
                if (poGeom == nullptr)
3431
0
                {
3432
0
                    return nullptr;
3433
0
                }
3434
3435
0
                if (poResultTri == nullptr)
3436
0
                    poResultTri = std::move(poGeom);
3437
0
                else
3438
0
                {
3439
0
                    if (poTINPtr == nullptr)
3440
0
                    {
3441
0
                        auto poTIN = std::make_unique<OGRTriangulatedSurface>();
3442
0
                        OGRErr eErr =
3443
0
                            poTIN->addGeometry(std::move(poResultTri));
3444
0
                        CPL_IGNORE_RET_VAL(eErr);
3445
0
                        CPLAssert(eErr == OGRERR_NONE);
3446
0
                        poResultTri = std::move(poTIN);
3447
0
                        poTINPtr = cpl::down_cast<OGRTriangulatedSurface *>(
3448
0
                            poResultTri.get());
3449
0
                    }
3450
0
                    OGRErr eErr = poTINPtr->addGeometry(std::move(poGeom));
3451
0
                    CPL_IGNORE_RET_VAL(eErr);
3452
0
                    CPLAssert(eErr == OGRERR_NONE);
3453
0
                }
3454
0
            }
3455
4
        }
3456
3457
4
        if (poResultTri == nullptr && poResultPoly == nullptr)
3458
0
            return nullptr;
3459
3460
4
        if (poResultTri == nullptr)
3461
4
            return poResultPoly;
3462
0
        else if (poResultPoly == nullptr)
3463
0
            return poResultTri;
3464
0
        else
3465
0
        {
3466
0
            auto poGC = std::make_unique<OGRGeometryCollection>();
3467
0
            poGC->addGeometry(std::move(poResultTri));
3468
0
            poGC->addGeometry(std::move(poResultPoly));
3469
0
            return poGC;
3470
0
        }
3471
4
    }
3472
3473
    /* -------------------------------------------------------------------- */
3474
    /*      TriangulatedSurface                                             */
3475
    /* -------------------------------------------------------------------- */
3476
10.9k
    if (EQUAL(pszBaseGeometry, "TriangulatedSurface") ||
3477
10.9k
        EQUAL(pszBaseGeometry, "Tin"))
3478
7
    {
3479
        // Find trianglePatches.
3480
7
        const CPLXMLNode *psChild = FindBareXMLChild(psNode, "trianglePatches");
3481
7
        if (psChild == nullptr)
3482
7
            psChild = FindBareXMLChild(psNode, "patches");
3483
3484
7
        psChild = GetChildElement(psChild);
3485
7
        if (psChild == nullptr)
3486
7
        {
3487
7
            ReportFailure("Missing <trianglePatches> for %s.", pszBaseGeometry);
3488
7
            return nullptr;
3489
7
        }
3490
3491
0
        auto poTIN = std::make_unique<OGRTriangulatedSurface>();
3492
0
        for (; psChild != nullptr; psChild = psChild->psNext)
3493
0
        {
3494
0
            if (psChild->eType == CXT_Element &&
3495
0
                EQUAL(BareGMLElement(psChild->pszValue), "Triangle"))
3496
0
            {
3497
0
                auto poTriangle = GML2OGRGeometry_XMLNode_Internal(
3498
0
                    psChild, pszId, nPseudoBoolGetSecondaryGeometryOption,
3499
0
                    nRecLevel + 1, nSRSDimension, pszSRSName, hSRSCache);
3500
0
                if (poTriangle == nullptr)
3501
0
                {
3502
0
                    return nullptr;
3503
0
                }
3504
0
                else
3505
0
                {
3506
0
                    poTIN->addGeometry(std::move(poTriangle));
3507
0
                }
3508
0
            }
3509
0
        }
3510
3511
0
        return poTIN;
3512
0
    }
3513
3514
    /* -------------------------------------------------------------------- */
3515
    /*      PolyhedralSurface                                               */
3516
    /* -------------------------------------------------------------------- */
3517
10.9k
    if (EQUAL(pszBaseGeometry, "PolyhedralSurface"))
3518
20
    {
3519
        // Find polygonPatches.
3520
20
        const CPLXMLNode *psParent = FindBareXMLChild(psNode, "polygonPatches");
3521
20
        if (psParent == nullptr)
3522
20
        {
3523
20
            if (GetChildElement(psNode) == nullptr)
3524
20
            {
3525
                // This is empty PolyhedralSurface.
3526
20
                return std::make_unique<OGRPolyhedralSurface>();
3527
20
            }
3528
0
            else
3529
0
            {
3530
0
                ReportFailure("Missing <polygonPatches> for %s.",
3531
0
                              pszBaseGeometry);
3532
0
                return nullptr;
3533
0
            }
3534
20
        }
3535
3536
0
        const CPLXMLNode *psChild = GetChildElement(psParent);
3537
0
        if (psChild == nullptr)
3538
0
        {
3539
            // This is empty PolyhedralSurface.
3540
0
            return std::make_unique<OGRPolyhedralSurface>();
3541
0
        }
3542
0
        else if (!EQUAL(BareGMLElement(psChild->pszValue), "PolygonPatch"))
3543
0
        {
3544
0
            ReportFailure("Missing <PolygonPatch> for %s.", pszBaseGeometry);
3545
0
            return nullptr;
3546
0
        }
3547
3548
        // Each psParent has the tags corresponding to <gml:polygonPatches>
3549
        // Each psChild has the tags corresponding to <gml:PolygonPatch>
3550
        // Each PolygonPatch has a set of polygons enclosed in a
3551
        // OGRPolyhedralSurface.
3552
0
        auto poGC = std::make_unique<OGRGeometryCollection>();
3553
0
        for (; psParent != nullptr; psParent = psParent->psNext)
3554
0
        {
3555
0
            psChild = GetChildElement(psParent);
3556
0
            if (psChild == nullptr)
3557
0
                continue;
3558
0
            auto poPS = std::make_unique<OGRPolyhedralSurface>();
3559
0
            for (; psChild != nullptr; psChild = psChild->psNext)
3560
0
            {
3561
0
                if (psChild->eType == CXT_Element &&
3562
0
                    EQUAL(BareGMLElement(psChild->pszValue), "PolygonPatch"))
3563
0
                {
3564
0
                    auto poPolygon = GML2OGRGeometry_XMLNode_Internal(
3565
0
                        psChild, pszId, nPseudoBoolGetSecondaryGeometryOption,
3566
0
                        nRecLevel + 1, nSRSDimension, pszSRSName, hSRSCache);
3567
0
                    if (poPolygon == nullptr)
3568
0
                    {
3569
0
                        ReportFailure("Wrong geometry type for %s.",
3570
0
                                      pszBaseGeometry);
3571
0
                        return nullptr;
3572
0
                    }
3573
3574
0
                    else if (wkbFlatten(poPolygon->getGeometryType()) ==
3575
0
                             wkbPolygon)
3576
0
                    {
3577
0
                        poPS->addGeometry(std::move(poPolygon));
3578
0
                    }
3579
0
                    else if (wkbFlatten(poPolygon->getGeometryType()) ==
3580
0
                             wkbCurvePolygon)
3581
0
                    {
3582
0
                        poPS->addGeometryDirectly(
3583
0
                            OGRGeometryFactory::forceToPolygon(
3584
0
                                poPolygon.release()));
3585
0
                    }
3586
0
                    else
3587
0
                    {
3588
0
                        ReportFailure("Wrong geometry type for %s.",
3589
0
                                      pszBaseGeometry);
3590
0
                        return nullptr;
3591
0
                    }
3592
0
                }
3593
0
            }
3594
0
            poGC->addGeometry(std::move(poPS));
3595
0
        }
3596
3597
0
        if (poGC->getNumGeometries() == 0)
3598
0
        {
3599
0
            return nullptr;
3600
0
        }
3601
0
        else if (poGC->getNumGeometries() == 1)
3602
0
        {
3603
0
            auto poResult =
3604
0
                std::unique_ptr<OGRGeometry>(poGC->getGeometryRef(0));
3605
0
            poGC->removeGeometry(0, FALSE);
3606
0
            return poResult;
3607
0
        }
3608
0
        else
3609
0
        {
3610
0
            return poGC;
3611
0
        }
3612
0
    }
3613
3614
    /* -------------------------------------------------------------------- */
3615
    /*      Solid                                                           */
3616
    /* -------------------------------------------------------------------- */
3617
10.9k
    if (EQUAL(pszBaseGeometry, "Solid"))
3618
21
    {
3619
21
        const CPLXMLNode *psChild = FindBareXMLChild(psNode, "interior");
3620
21
        if (psChild != nullptr)
3621
0
        {
3622
0
            static bool bWarnedOnce = false;
3623
0
            if (!bWarnedOnce)
3624
0
            {
3625
0
                ReportWarning("<interior> elements of <Solid> are ignored");
3626
0
                bWarnedOnce = true;
3627
0
            }
3628
0
        }
3629
3630
        // Find exterior element.
3631
21
        psChild = FindBareXMLChild(psNode, "exterior");
3632
3633
21
        if (nSRSDimension == 0)
3634
20
            nSRSDimension = 3;
3635
3636
21
        psChild = GetChildElement(psChild);
3637
21
        if (psChild == nullptr)
3638
3
        {
3639
            // <gml:Solid/> and <gml:Solid><gml:exterior/></gml:Solid> are valid
3640
            // GML.
3641
3
            return std::make_unique<OGRPolyhedralSurface>();
3642
3
        }
3643
3644
18
        if (EQUAL(BareGMLElement(psChild->pszValue), "CompositeSurface") ||
3645
0
            EQUAL(BareGMLElement(psChild->pszValue), "Shell"))
3646
18
        {
3647
18
            auto poPS = std::make_unique<OGRPolyhedralSurface>();
3648
3649
            // Iterate over children.
3650
213
            for (psChild = psChild->psChild; psChild != nullptr;
3651
195
                 psChild = psChild->psNext)
3652
195
            {
3653
195
                const char *pszMemberElement =
3654
195
                    BareGMLElement(psChild->pszValue);
3655
195
                if (psChild->eType == CXT_Element &&
3656
195
                    (EQUAL(pszMemberElement, "polygonMember") ||
3657
195
                     EQUAL(pszMemberElement, "surfaceMember")))
3658
192
                {
3659
192
                    const CPLXMLNode *psSurfaceChild = GetChildElement(psChild);
3660
3661
192
                    if (psSurfaceChild != nullptr)
3662
174
                    {
3663
174
                        auto poGeom = GML2OGRGeometry_XMLNode_Internal(
3664
174
                            psSurfaceChild, pszId,
3665
174
                            nPseudoBoolGetSecondaryGeometryOption,
3666
174
                            nRecLevel + 1, nSRSDimension, pszSRSName,
3667
174
                            hSRSCache);
3668
174
                        if (poGeom != nullptr &&
3669
165
                            wkbFlatten(poGeom->getGeometryType()) == wkbPolygon)
3670
165
                        {
3671
165
                            poPS->addGeometry(std::move(poGeom));
3672
165
                        }
3673
174
                    }
3674
192
                }
3675
195
            }
3676
18
            return poPS;
3677
18
        }
3678
3679
        // Get the geometry inside <exterior>.
3680
0
        auto poGeom = GML2OGRGeometry_XMLNode_Internal(
3681
0
            psChild, pszId, nPseudoBoolGetSecondaryGeometryOption,
3682
0
            nRecLevel + 1, nSRSDimension, pszSRSName, hSRSCache);
3683
0
        if (poGeom == nullptr)
3684
0
        {
3685
0
            ReportFailure("Invalid exterior element");
3686
0
            return nullptr;
3687
0
        }
3688
3689
0
        return poGeom;
3690
0
    }
3691
3692
    /* -------------------------------------------------------------------- */
3693
    /*      OrientableCurve                                                 */
3694
    /* -------------------------------------------------------------------- */
3695
10.9k
    if (EQUAL(pszBaseGeometry, "OrientableCurve"))
3696
2
    {
3697
        // Find baseCurve.
3698
2
        const CPLXMLNode *psChild = FindBareXMLChild(psNode, "baseCurve");
3699
3700
2
        psChild = GetChildElement(psChild);
3701
2
        if (psChild == nullptr)
3702
2
        {
3703
2
            ReportFailure("Missing <baseCurve> for OrientableCurve.");
3704
2
            return nullptr;
3705
2
        }
3706
3707
0
        auto poGeom = GML2OGRGeometry_XMLNode_Internal(
3708
0
            psChild, pszId, nPseudoBoolGetSecondaryGeometryOption,
3709
0
            nRecLevel + 1, nSRSDimension, pszSRSName, hSRSCache);
3710
0
        if (!poGeom || !OGR_GT_IsCurve(poGeom->getGeometryType()))
3711
0
        {
3712
0
            ReportFailure("baseCurve of OrientableCurve is not a curve.");
3713
0
            return nullptr;
3714
0
        }
3715
0
        if (!GetElementOrientation(psNode))
3716
0
        {
3717
0
            poGeom->toCurve()->reversePoints();
3718
0
        }
3719
0
        return poGeom;
3720
0
    }
3721
3722
    /* -------------------------------------------------------------------- */
3723
    /*      OrientableSurface                                               */
3724
    /* -------------------------------------------------------------------- */
3725
10.9k
    if (EQUAL(pszBaseGeometry, "OrientableSurface"))
3726
2
    {
3727
        // Find baseSurface.
3728
2
        const CPLXMLNode *psChild = FindBareXMLChild(psNode, "baseSurface");
3729
3730
2
        psChild = GetChildElement(psChild);
3731
2
        if (psChild == nullptr)
3732
2
        {
3733
2
            ReportFailure("Missing <baseSurface> for OrientableSurface.");
3734
2
            return nullptr;
3735
2
        }
3736
3737
0
        return GML2OGRGeometry_XMLNode_Internal(
3738
0
            psChild, pszId, nPseudoBoolGetSecondaryGeometryOption,
3739
0
            nRecLevel + 1, nSRSDimension, pszSRSName, hSRSCache);
3740
2
    }
3741
3742
    /* -------------------------------------------------------------------- */
3743
    /*      SimplePolygon, SimpleRectangle, SimpleTriangle                  */
3744
    /*      (GML 3.3 compact encoding)                                      */
3745
    /* -------------------------------------------------------------------- */
3746
10.9k
    if (EQUAL(pszBaseGeometry, "SimplePolygon") ||
3747
4.94k
        EQUAL(pszBaseGeometry, "SimpleRectangle"))
3748
5.96k
    {
3749
5.96k
        auto poRing = std::make_unique<OGRLinearRing>();
3750
3751
5.96k
        if (!ParseGMLCoordinates(psNode, poRing.get(), nSRSDimension))
3752
191
        {
3753
191
            return nullptr;
3754
191
        }
3755
3756
5.77k
        poRing->closeRings();
3757
3758
5.77k
        auto poPolygon = std::make_unique<OGRPolygon>();
3759
5.77k
        poPolygon->addRing(std::move(poRing));
3760
5.77k
        return poPolygon;
3761
5.96k
    }
3762
3763
4.94k
    if (EQUAL(pszBaseGeometry, "SimpleTriangle"))
3764
2
    {
3765
2
        auto poRing = std::make_unique<OGRLinearRing>();
3766
3767
2
        if (!ParseGMLCoordinates(psNode, poRing.get(), nSRSDimension))
3768
2
        {
3769
2
            return nullptr;
3770
2
        }
3771
3772
0
        poRing->closeRings();
3773
3774
0
        auto poTriangle = std::make_unique<OGRTriangle>();
3775
0
        poTriangle->addRing(std::move(poRing));
3776
0
        return poTriangle;
3777
2
    }
3778
3779
    /* -------------------------------------------------------------------- */
3780
    /*      SimpleMultiPoint (GML 3.3 compact encoding)                     */
3781
    /* -------------------------------------------------------------------- */
3782
4.94k
    if (EQUAL(pszBaseGeometry, "SimpleMultiPoint"))
3783
2
    {
3784
2
        auto poLS = std::make_unique<OGRLineString>();
3785
3786
2
        if (!ParseGMLCoordinates(psNode, poLS.get(), nSRSDimension))
3787
2
        {
3788
2
            return nullptr;
3789
2
        }
3790
3791
0
        auto poMP = std::make_unique<OGRMultiPoint>();
3792
0
        int nPoints = poLS->getNumPoints();
3793
0
        for (int i = 0; i < nPoints; i++)
3794
0
        {
3795
0
            auto poPoint = std::make_unique<OGRPoint>();
3796
0
            poLS->getPoint(i, poPoint.get());
3797
0
            poMP->addGeometry(std::move(poPoint));
3798
0
        }
3799
0
        return poMP;
3800
2
    }
3801
3802
4.94k
    if (strcmp(pszBaseGeometry, "null") == 0)
3803
2
    {
3804
2
        return nullptr;
3805
2
    }
3806
3807
4.94k
    ReportFailure("Unrecognized geometry type <%s>.", pszBaseGeometry);
3808
3809
4.94k
    return nullptr;
3810
4.94k
}
3811
3812
/************************************************************************/
3813
/*                      OGR_G_CreateFromGMLTree()                       */
3814
/************************************************************************/
3815
3816
/** Create geometry from GML */
3817
OGRGeometryH OGR_G_CreateFromGMLTree(const CPLXMLNode *psTree)
3818
3819
0
{
3820
0
    std::unique_ptr<OGRGML_SRSCache, decltype(&OGRGML_SRSCache_Destroy)> cache{
3821
0
        OGRGML_SRSCache_Create(), OGRGML_SRSCache_Destroy};
3822
0
    return OGRGeometry::ToHandle(
3823
0
        GML2OGRGeometry_XMLNode(psTree, -1, cache.get()));
3824
0
}
3825
3826
/************************************************************************/
3827
/*                        OGR_G_CreateFromGML()                         */
3828
/************************************************************************/
3829
3830
/**
3831
 * \brief Create geometry from GML.
3832
 *
3833
 * This method translates a fragment of GML containing only the geometry
3834
 * portion into a corresponding OGRGeometry.  There are many limitations
3835
 * on the forms of GML geometries supported by this parser, but they are
3836
 * too numerous to list here.
3837
 *
3838
 * The following GML2 elements are parsed : Point, LineString, Polygon,
3839
 * MultiPoint, MultiLineString, MultiPolygon, MultiGeometry.
3840
 *
3841
 * The following GML3 elements are parsed : Surface,
3842
 * MultiSurface, PolygonPatch, Triangle, Rectangle, Curve, MultiCurve,
3843
 * CompositeCurve, LineStringSegment, Arc, Circle, CompositeSurface,
3844
 * Shell, OrientableSurface, Solid, Tin, TriangulatedSurface.
3845
 *
3846
 * Arc and Circle elements are returned as curves by default. Stroking to
3847
 * linestrings can be done with
3848
 * OGR_G_ForceTo(hGeom, OGR_GT_GetLinear(OGR_G_GetGeometryType(hGeom)), NULL).
3849
 * A 4 degrees step is used by default, unless the user
3850
 * has overridden the value with the OGR_ARC_STEPSIZE configuration variable.
3851
 *
3852
 * The C++ method OGRGeometryFactory::createFromGML() is the same as
3853
 * this function.
3854
 *
3855
 * @param pszGML The GML fragment for the geometry.
3856
 *
3857
 * @return a geometry on success, or NULL on error.
3858
 *
3859
 * @see OGR_G_ForceTo()
3860
 * @see OGR_GT_GetLinear()
3861
 * @see OGR_G_GetGeometryType()
3862
 */
3863
3864
OGRGeometryH OGR_G_CreateFromGML(const char *pszGML)
3865
3866
56.7k
{
3867
56.7k
    if (pszGML == nullptr || strlen(pszGML) == 0)
3868
1
    {
3869
1
        CPLError(CE_Failure, CPLE_AppDefined,
3870
1
                 "GML Geometry is empty in OGR_G_CreateFromGML().");
3871
1
        return nullptr;
3872
1
    }
3873
3874
    /* -------------------------------------------------------------------- */
3875
    /*      Try to parse the XML snippet using the MiniXML API.  If this    */
3876
    /*      fails, we assume the minixml api has already posted a CPL       */
3877
    /*      error, and just return NULL.                                    */
3878
    /* -------------------------------------------------------------------- */
3879
56.7k
    CPLXMLNode *psGML = CPLParseXMLString(pszGML);
3880
3881
56.7k
    if (psGML == nullptr)
3882
1.05k
        return nullptr;
3883
3884
    /* -------------------------------------------------------------------- */
3885
    /*      Convert geometry recursively.                                   */
3886
    /* -------------------------------------------------------------------- */
3887
    // Must be in synced in OGR_G_CreateFromGML(), OGRGMLLayer::OGRGMLLayer()
3888
    // and GMLReader::GMLReader().
3889
55.6k
    const bool bFaceHoleNegative =
3890
55.6k
        CPLTestBool(CPLGetConfigOption("GML_FACE_HOLE_NEGATIVE", "NO"));
3891
55.6k
    std::unique_ptr<OGRGML_SRSCache, decltype(&OGRGML_SRSCache_Destroy)> cache{
3892
55.6k
        OGRGML_SRSCache_Create(), OGRGML_SRSCache_Destroy};
3893
55.6k
    OGRGeometry *poGeometry = GML2OGRGeometry_XMLNode(
3894
55.6k
        psGML, -1, cache.get(), 0, 0, false, true, bFaceHoleNegative);
3895
3896
55.6k
    CPLDestroyXMLNode(psGML);
3897
3898
55.6k
    return OGRGeometry::ToHandle(poGeometry);
3899
56.7k
}