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