LCOV - code coverage report
Current view: top level - ogr - ogr2gmlgeometry.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 527 557 94.6 %
Date: 2024-11-21 22:18:42 Functions: 11 12 91.7 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GML Translator
       4             :  * Purpose:  Code to translate OGRGeometry to GML string representation.
       5             :  * Author:   Frank Warmerdam, warmerdam@pobox.com
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2002, Frank Warmerdam <warmerdam@pobox.com>
       9             :  * Copyright (c) 2009-2013, Even Rouault <even dot rouault at spatialys.com>
      10             :  *
      11             :  * SPDX-License-Identifier: MIT
      12             :  *****************************************************************************
      13             :  *
      14             :  * Independent Security Audit 2003/04/17 Andrey Kiselev:
      15             :  *   Completed audit of this module. All functions may be used without buffer
      16             :  *   overflows and stack corruptions if caller could be trusted.
      17             :  *
      18             :  * Security Audit 2003/03/28 warmerda:
      19             :  *   Completed security audit.  I believe that this module may be safely used
      20             :  *   to generate GML from arbitrary but well formed OGRGeomety objects that
      21             :  *   come from a potentially hostile source, but through a trusted OGR importer
      22             :  *   without compromising the system.
      23             :  *
      24             :  */
      25             : 
      26             : #include "cpl_port.h"
      27             : #include "ogr_api.h"
      28             : 
      29             : #include <cstddef>
      30             : #include <cstdio>
      31             : #include <cstdlib>
      32             : #include <cstring>
      33             : #include <algorithm>
      34             : 
      35             : #include "cpl_conv.h"
      36             : #include "cpl_error.h"
      37             : #include "cpl_minixml.h"
      38             : #include "cpl_string.h"
      39             : #include "ogr_core.h"
      40             : #include "ogr_geometry.h"
      41             : #include "ogr_p.h"
      42             : #include "ogr_spatialref.h"
      43             : 
      44             : constexpr int SRSDIM_LOC_GEOMETRY = 1 << 0;
      45             : constexpr int SRSDIM_LOC_POSLIST = 1 << 1;
      46             : 
      47             : enum GMLSRSNameFormat
      48             : {
      49             :     SRSNAME_SHORT,
      50             :     SRSNAME_OGC_URN,
      51             :     SRSNAME_OGC_URL
      52             : };
      53             : 
      54             : /************************************************************************/
      55             : /*                        MakeGMLCoordinate()                           */
      56             : /************************************************************************/
      57             : 
      58         778 : static void MakeGMLCoordinate(char *pszTarget, double x, double y, double z,
      59             :                               bool b3D, const OGRWktOptions &coordOpts)
      60             : 
      61             : {
      62        1556 :     std::string wkt = OGRMakeWktCoordinate(x, y, z, b3D ? 3 : 2, coordOpts);
      63         778 :     memcpy(pszTarget, wkt.data(), wkt.size() + 1);
      64             : 
      65       16317 :     while (*pszTarget != '\0')
      66             :     {
      67       15539 :         if (*pszTarget == ' ')
      68        1094 :             *pszTarget = ',';
      69       15539 :         pszTarget++;
      70             :     }
      71         778 : }
      72             : 
      73             : /************************************************************************/
      74             : /*                            _GrowBuffer()                             */
      75             : /************************************************************************/
      76             : 
      77        5951 : static void _GrowBuffer(size_t nNeeded, char **ppszText, size_t *pnMaxLength)
      78             : 
      79             : {
      80        5951 :     if (nNeeded + 1 >= *pnMaxLength)
      81             :     {
      82        1458 :         *pnMaxLength = std::max(*pnMaxLength * 2, nNeeded + 1);
      83        1458 :         *ppszText = static_cast<char *>(CPLRealloc(*ppszText, *pnMaxLength));
      84             :     }
      85        5951 : }
      86             : 
      87             : /************************************************************************/
      88             : /*                            AppendString()                            */
      89             : /************************************************************************/
      90             : 
      91        2835 : static void AppendString(char **ppszText, size_t *pnLength, size_t *pnMaxLength,
      92             :                          const char *pszTextToAppend)
      93             : 
      94             : {
      95        2835 :     _GrowBuffer(*pnLength + strlen(pszTextToAppend) + 1, ppszText, pnMaxLength);
      96             : 
      97        2835 :     strcat(*ppszText + *pnLength, pszTextToAppend);
      98        2835 :     *pnLength += strlen(*ppszText + *pnLength);
      99        2835 : }
     100             : 
     101             : /************************************************************************/
     102             : /*                        AppendCoordinateList()                        */
     103             : /************************************************************************/
     104             : 
     105          76 : static void AppendCoordinateList(const OGRLineString *poLine, char **ppszText,
     106             :                                  size_t *pnLength, size_t *pnMaxLength,
     107             :                                  const OGRWktOptions &coordOpts)
     108             : 
     109             : {
     110          76 :     const bool b3D = wkbHasZ(poLine->getGeometryType()) != FALSE;
     111             : 
     112          76 :     *pnLength += strlen(*ppszText + *pnLength);
     113          76 :     _GrowBuffer(*pnLength + 20, ppszText, pnMaxLength);
     114             : 
     115          76 :     strcat(*ppszText + *pnLength, "<gml:coordinates>");
     116          76 :     *pnLength += strlen(*ppszText + *pnLength);
     117             : 
     118          76 :     char szCoordinate[256] = {};
     119         799 :     for (int iPoint = 0; iPoint < poLine->getNumPoints(); iPoint++)
     120             :     {
     121         723 :         MakeGMLCoordinate(szCoordinate, poLine->getX(iPoint),
     122             :                           poLine->getY(iPoint), poLine->getZ(iPoint), b3D,
     123             :                           coordOpts);
     124         723 :         _GrowBuffer(*pnLength + strlen(szCoordinate) + 1, ppszText,
     125             :                     pnMaxLength);
     126             : 
     127         723 :         if (iPoint != 0)
     128         647 :             strcat(*ppszText + *pnLength, " ");
     129             : 
     130         723 :         strcat(*ppszText + *pnLength, szCoordinate);
     131         723 :         *pnLength += strlen(*ppszText + *pnLength);
     132             :     }
     133             : 
     134          76 :     _GrowBuffer(*pnLength + 20, ppszText, pnMaxLength);
     135          76 :     strcat(*ppszText + *pnLength, "</gml:coordinates>");
     136          76 :     *pnLength += strlen(*ppszText + *pnLength);
     137          76 : }
     138             : 
     139             : /************************************************************************/
     140             : /*                       OGR2GMLGeometryAppend()                        */
     141             : /************************************************************************/
     142             : 
     143         203 : static bool OGR2GMLGeometryAppend(const OGRGeometry *poGeometry,
     144             :                                   char **ppszText, size_t *pnLength,
     145             :                                   size_t *pnMaxLength, bool bIsSubGeometry,
     146             :                                   const char *pszNamespaceDecl,
     147             :                                   const OGRWktOptions &coordOpts)
     148             : 
     149             : {
     150             :     /* -------------------------------------------------------------------- */
     151             :     /*      Check for Spatial Reference System attached to given geometry   */
     152             :     /* -------------------------------------------------------------------- */
     153             : 
     154             :     // Buffer for xmlns:gml and srsName attributes (srsName="...")
     155         203 :     char szAttributes[128] = {};
     156         203 :     size_t nAttrsLength = 0;
     157             : 
     158         203 :     szAttributes[0] = 0;
     159             : 
     160         203 :     const OGRSpatialReference *poSRS = poGeometry->getSpatialReference();
     161             : 
     162         203 :     if (pszNamespaceDecl != nullptr)
     163             :     {
     164           1 :         snprintf(szAttributes + nAttrsLength,
     165             :                  sizeof(szAttributes) - nAttrsLength, " xmlns:gml=\"%s\"",
     166             :                  pszNamespaceDecl);
     167           1 :         nAttrsLength += strlen(szAttributes + nAttrsLength);
     168           1 :         pszNamespaceDecl = nullptr;
     169             :     }
     170             : 
     171         203 :     if (nullptr != poSRS && !bIsSubGeometry)
     172             :     {
     173          35 :         const char *pszTarget = poSRS->IsProjected() ? "PROJCS" : "GEOGCS";
     174          35 :         const char *pszAuthName = poSRS->GetAuthorityName(pszTarget);
     175          35 :         const char *pszAuthCode = poSRS->GetAuthorityCode(pszTarget);
     176          35 :         if (nullptr != pszAuthName && strlen(pszAuthName) < 10 &&
     177          35 :             nullptr != pszAuthCode && strlen(pszAuthCode) < 10)
     178             :         {
     179          35 :             snprintf(szAttributes + nAttrsLength,
     180             :                      sizeof(szAttributes) - nAttrsLength, " srsName=\"%s:%s\"",
     181             :                      pszAuthName, pszAuthCode);
     182             : 
     183          35 :             nAttrsLength += strlen(szAttributes + nAttrsLength);
     184             :         }
     185             :     }
     186             : 
     187         203 :     OGRwkbGeometryType eType = poGeometry->getGeometryType();
     188         203 :     OGRwkbGeometryType eFType = wkbFlatten(eType);
     189             : 
     190             :     /* -------------------------------------------------------------------- */
     191             :     /*      2D Point                                                        */
     192             :     /* -------------------------------------------------------------------- */
     193         203 :     if (eType == wkbPoint)
     194             :     {
     195          43 :         const auto poPoint = poGeometry->toPoint();
     196             : 
     197          43 :         char szCoordinate[256] = {};
     198          43 :         MakeGMLCoordinate(szCoordinate, poPoint->getX(), poPoint->getY(), 0.0,
     199             :                           false, coordOpts);
     200             : 
     201          43 :         _GrowBuffer(*pnLength + strlen(szCoordinate) + 60 + nAttrsLength,
     202             :                     ppszText, pnMaxLength);
     203             : 
     204          43 :         snprintf(
     205          43 :             *ppszText + *pnLength, *pnMaxLength - *pnLength,
     206             :             "<gml:Point%s><gml:coordinates>%s</gml:coordinates></gml:Point>",
     207             :             szAttributes, szCoordinate);
     208             : 
     209          43 :         *pnLength += strlen(*ppszText + *pnLength);
     210             :     }
     211             :     /* -------------------------------------------------------------------- */
     212             :     /*      3D Point                                                        */
     213             :     /* -------------------------------------------------------------------- */
     214         160 :     else if (eType == wkbPoint25D)
     215             :     {
     216          10 :         const auto poPoint = poGeometry->toPoint();
     217             : 
     218          10 :         char szCoordinate[256] = {};
     219          10 :         MakeGMLCoordinate(szCoordinate, poPoint->getX(), poPoint->getY(),
     220             :                           poPoint->getZ(), true, coordOpts);
     221             : 
     222          10 :         _GrowBuffer(*pnLength + strlen(szCoordinate) + 70 + nAttrsLength,
     223             :                     ppszText, pnMaxLength);
     224             : 
     225          10 :         snprintf(
     226          10 :             *ppszText + *pnLength, *pnMaxLength - *pnLength,
     227             :             "<gml:Point%s><gml:coordinates>%s</gml:coordinates></gml:Point>",
     228             :             szAttributes, szCoordinate);
     229             : 
     230          10 :         *pnLength += strlen(*ppszText + *pnLength);
     231             :     }
     232             : 
     233             :     /* -------------------------------------------------------------------- */
     234             :     /*      LineString and LinearRing                                       */
     235             :     /* -------------------------------------------------------------------- */
     236         150 :     else if (eFType == wkbLineString)
     237             :     {
     238          76 :         bool bRing = EQUAL(poGeometry->getGeometryName(), "LINEARRING");
     239             : 
     240             :         // Buffer for tag name + srsName attribute if set
     241          76 :         const size_t nLineTagLength = 16;
     242          76 :         const size_t nLineTagNameBufLen = nLineTagLength + nAttrsLength + 1;
     243             :         char *pszLineTagName =
     244          76 :             static_cast<char *>(CPLMalloc(nLineTagNameBufLen));
     245             : 
     246          76 :         if (bRing)
     247             :         {
     248          47 :             snprintf(pszLineTagName, nLineTagNameBufLen, "<gml:LinearRing%s>",
     249             :                      szAttributes);
     250             : 
     251          47 :             AppendString(ppszText, pnLength, pnMaxLength, pszLineTagName);
     252             :         }
     253             :         else
     254             :         {
     255          29 :             snprintf(pszLineTagName, nLineTagNameBufLen, "<gml:LineString%s>",
     256             :                      szAttributes);
     257             : 
     258          29 :             AppendString(ppszText, pnLength, pnMaxLength, pszLineTagName);
     259             :         }
     260             : 
     261             :         // Free tag buffer.
     262          76 :         CPLFree(pszLineTagName);
     263             : 
     264          76 :         const auto poLineString = poGeometry->toLineString();
     265          76 :         AppendCoordinateList(poLineString, ppszText, pnLength, pnMaxLength,
     266             :                              coordOpts);
     267             : 
     268          76 :         if (bRing)
     269          47 :             AppendString(ppszText, pnLength, pnMaxLength, "</gml:LinearRing>");
     270             :         else
     271          29 :             AppendString(ppszText, pnLength, pnMaxLength, "</gml:LineString>");
     272             :     }
     273             : 
     274             :     /* -------------------------------------------------------------------- */
     275             :     /*      Polygon                                                         */
     276             :     /* -------------------------------------------------------------------- */
     277          74 :     else if (eFType == wkbPolygon)
     278             :     {
     279          45 :         const auto poPolygon = poGeometry->toPolygon();
     280             : 
     281             :         // Buffer for polygon tag name + srsName attribute if set.
     282          45 :         const size_t nPolyTagLength = 13;
     283          45 :         const size_t nPolyTagNameBufLen = nPolyTagLength + nAttrsLength + 1;
     284             :         char *pszPolyTagName =
     285          45 :             static_cast<char *>(CPLMalloc(nPolyTagNameBufLen));
     286             : 
     287             :         // Compose Polygon tag with or without srsName attribute.
     288          45 :         snprintf(pszPolyTagName, nPolyTagNameBufLen, "<gml:Polygon%s>",
     289             :                  szAttributes);
     290             : 
     291          45 :         AppendString(ppszText, pnLength, pnMaxLength, pszPolyTagName);
     292             : 
     293             :         // Free tag buffer.
     294          45 :         CPLFree(pszPolyTagName);
     295             : 
     296             :         // Don't add srsName to polygon rings.
     297          45 :         if (poPolygon->getExteriorRing() != nullptr)
     298             :         {
     299          45 :             AppendString(ppszText, pnLength, pnMaxLength,
     300             :                          "<gml:outerBoundaryIs>");
     301             : 
     302          45 :             CPL_IGNORE_RET_VAL(OGR2GMLGeometryAppend(
     303          45 :                 poPolygon->getExteriorRing(), ppszText, pnLength, pnMaxLength,
     304             :                 true, nullptr, coordOpts));
     305             : 
     306          45 :             AppendString(ppszText, pnLength, pnMaxLength,
     307             :                          "</gml:outerBoundaryIs>");
     308             :         }
     309             : 
     310          47 :         for (int iRing = 0; iRing < poPolygon->getNumInteriorRings(); iRing++)
     311             :         {
     312           2 :             const OGRLinearRing *poRing = poPolygon->getInteriorRing(iRing);
     313             : 
     314           2 :             AppendString(ppszText, pnLength, pnMaxLength,
     315             :                          "<gml:innerBoundaryIs>");
     316             : 
     317           2 :             CPL_IGNORE_RET_VAL(OGR2GMLGeometryAppend(poRing, ppszText, pnLength,
     318             :                                                      pnMaxLength, true, nullptr,
     319             :                                                      coordOpts));
     320           2 :             AppendString(ppszText, pnLength, pnMaxLength,
     321             :                          "</gml:innerBoundaryIs>");
     322             :         }
     323             : 
     324          45 :         AppendString(ppszText, pnLength, pnMaxLength, "</gml:Polygon>");
     325             :     }
     326             : 
     327             :     /* -------------------------------------------------------------------- */
     328             :     /*      MultiPolygon, MultiLineString, MultiPoint, MultiGeometry        */
     329             :     /* -------------------------------------------------------------------- */
     330          29 :     else if (eFType == wkbMultiPolygon || eFType == wkbMultiLineString ||
     331           9 :              eFType == wkbMultiPoint || eFType == wkbGeometryCollection)
     332             :     {
     333          28 :         const auto poGC = poGeometry->toGeometryCollection();
     334          28 :         const char *pszElemClose = nullptr;
     335          28 :         const char *pszMemberElem = nullptr;
     336             : 
     337             :         // Buffer for opening tag + srsName attribute.
     338          28 :         char *pszElemOpen = nullptr;
     339             : 
     340          28 :         if (eFType == wkbMultiPolygon)
     341             :         {
     342           7 :             const size_t nBufLen = 13 + nAttrsLength + 1;
     343           7 :             pszElemOpen = static_cast<char *>(CPLMalloc(nBufLen));
     344           7 :             snprintf(pszElemOpen, nBufLen, "MultiPolygon%s>", szAttributes);
     345             : 
     346           7 :             pszElemClose = "MultiPolygon>";
     347           7 :             pszMemberElem = "polygonMember>";
     348             :         }
     349          21 :         else if (eFType == wkbMultiLineString)
     350             :         {
     351           6 :             const size_t nBufLen = 16 + nAttrsLength + 1;
     352           6 :             pszElemOpen = static_cast<char *>(CPLMalloc(nBufLen));
     353           6 :             snprintf(pszElemOpen, nBufLen, "MultiLineString%s>", szAttributes);
     354             : 
     355           6 :             pszElemClose = "MultiLineString>";
     356           6 :             pszMemberElem = "lineStringMember>";
     357             :         }
     358          15 :         else if (eFType == wkbMultiPoint)
     359             :         {
     360           7 :             const size_t nBufLen = 11 + nAttrsLength + 1;
     361           7 :             pszElemOpen = static_cast<char *>(CPLMalloc(nBufLen));
     362           7 :             snprintf(pszElemOpen, nBufLen, "MultiPoint%s>", szAttributes);
     363             : 
     364           7 :             pszElemClose = "MultiPoint>";
     365           7 :             pszMemberElem = "pointMember>";
     366             :         }
     367             :         else
     368             :         {
     369           8 :             const size_t nBufLen = 19 + nAttrsLength + 1;
     370           8 :             pszElemOpen = static_cast<char *>(CPLMalloc(nBufLen));
     371           8 :             snprintf(pszElemOpen, nBufLen, "MultiGeometry%s>", szAttributes);
     372             : 
     373           8 :             pszElemClose = "MultiGeometry>";
     374           8 :             pszMemberElem = "geometryMember>";
     375             :         }
     376             : 
     377          28 :         AppendString(ppszText, pnLength, pnMaxLength, "<gml:");
     378          28 :         AppendString(ppszText, pnLength, pnMaxLength, pszElemOpen);
     379             : 
     380          87 :         for (int iMember = 0; iMember < poGC->getNumGeometries(); iMember++)
     381             :         {
     382          60 :             const auto poMember = poGC->getGeometryRef(iMember);
     383             : 
     384          60 :             AppendString(ppszText, pnLength, pnMaxLength, "<gml:");
     385          60 :             AppendString(ppszText, pnLength, pnMaxLength, pszMemberElem);
     386             : 
     387          60 :             if (!OGR2GMLGeometryAppend(poMember, ppszText, pnLength,
     388             :                                        pnMaxLength, true, nullptr, coordOpts))
     389             :             {
     390           1 :                 CPLFree(pszElemOpen);
     391           1 :                 return false;
     392             :             }
     393             : 
     394          59 :             AppendString(ppszText, pnLength, pnMaxLength, "</gml:");
     395          59 :             AppendString(ppszText, pnLength, pnMaxLength, pszMemberElem);
     396             :         }
     397             : 
     398          27 :         AppendString(ppszText, pnLength, pnMaxLength, "</gml:");
     399          27 :         AppendString(ppszText, pnLength, pnMaxLength, pszElemClose);
     400             : 
     401             :         // Free tag buffer.
     402          27 :         CPLFree(pszElemOpen);
     403             :     }
     404             :     else
     405             :     {
     406           1 :         CPLError(CE_Failure, CPLE_NotSupported, "Unsupported geometry type %s",
     407             :                  OGRGeometryTypeToName(eType));
     408           1 :         return false;
     409             :     }
     410             : 
     411         201 :     return true;
     412             : }
     413             : 
     414             : /************************************************************************/
     415             : /*                   OGR_G_ExportEnvelopeToGMLTree()                    */
     416             : /************************************************************************/
     417             : 
     418             : /** Export the envelope of a geometry as a gml:Box. */
     419           1 : CPLXMLNode *OGR_G_ExportEnvelopeToGMLTree(OGRGeometryH hGeometry)
     420             : 
     421             : {
     422           1 :     OGREnvelope sEnvelope;
     423             : 
     424           1 :     OGRGeometry::FromHandle(hGeometry)->getEnvelope(&sEnvelope);
     425             : 
     426           1 :     if (!sEnvelope.IsInit())
     427             :     {
     428             :         // TODO: There is apparently a special way of representing a null box
     429             :         // geometry. Should use it here eventually.
     430           0 :         return nullptr;
     431             :     }
     432             : 
     433           1 :     CPLXMLNode *psBox = CPLCreateXMLNode(nullptr, CXT_Element, "gml:Box");
     434             : 
     435             :     /* -------------------------------------------------------------------- */
     436             :     /*      Add minxy coordinate.                                           */
     437             :     /* -------------------------------------------------------------------- */
     438           1 :     CPLXMLNode *psCoord = CPLCreateXMLNode(psBox, CXT_Element, "gml:coord");
     439             : 
     440           1 :     OGRWktOptions coordOpts;
     441             : 
     442           1 :     char szCoordinate[256] = {};
     443           1 :     MakeGMLCoordinate(szCoordinate, sEnvelope.MinX, sEnvelope.MinY, 0.0, false,
     444             :                       coordOpts);
     445           1 :     char *pszY = strstr(szCoordinate, ",");
     446             :     // There must be more after the comma or we have an internal consistency
     447             :     // bug in MakeGMLCoordinate.
     448           1 :     if (pszY == nullptr || strlen(pszY) < 2)
     449             :     {
     450           0 :         CPLError(CE_Failure, CPLE_AssertionFailed, "MakeGMLCoordinate failed.");
     451           0 :         return nullptr;
     452             :     }
     453           1 :     *pszY = '\0';
     454           1 :     pszY++;
     455             : 
     456           1 :     CPLCreateXMLElementAndValue(psCoord, "gml:X", szCoordinate);
     457           1 :     CPLCreateXMLElementAndValue(psCoord, "gml:Y", pszY);
     458             : 
     459             :     /* -------------------------------------------------------------------- */
     460             :     /*      Add maxxy coordinate.                                           */
     461             :     /* -------------------------------------------------------------------- */
     462           1 :     psCoord = CPLCreateXMLNode(psBox, CXT_Element, "gml:coord");
     463             : 
     464           1 :     MakeGMLCoordinate(szCoordinate, sEnvelope.MaxX, sEnvelope.MaxY, 0.0, false,
     465             :                       coordOpts);
     466           1 :     pszY = strstr(szCoordinate, ",") + 1;
     467           1 :     pszY[-1] = '\0';
     468             : 
     469           1 :     CPLCreateXMLElementAndValue(psCoord, "gml:X", szCoordinate);
     470           1 :     CPLCreateXMLElementAndValue(psCoord, "gml:Y", pszY);
     471             : 
     472           1 :     return psBox;
     473             : }
     474             : 
     475             : /************************************************************************/
     476             : /*                     AppendGML3CoordinateList()                       */
     477             : /************************************************************************/
     478             : 
     479         220 : static void AppendGML3CoordinateList(const OGRSimpleCurve *poLine,
     480             :                                      bool bCoordSwap, char **ppszText,
     481             :                                      size_t *pnLength, size_t *pnMaxLength,
     482             :                                      int nSRSDimensionLocFlags,
     483             :                                      const OGRWktOptions &coordOpts)
     484             : 
     485             : {
     486         220 :     bool b3D = wkbHasZ(poLine->getGeometryType());
     487             : 
     488         220 :     *pnLength += strlen(*ppszText + *pnLength);
     489         220 :     _GrowBuffer(*pnLength + 40, ppszText, pnMaxLength);
     490             : 
     491         220 :     if (b3D && (nSRSDimensionLocFlags & SRSDIM_LOC_POSLIST) != 0)
     492          42 :         strcat(*ppszText + *pnLength, "<gml:posList srsDimension=\"3\">");
     493             :     else
     494         178 :         strcat(*ppszText + *pnLength, "<gml:posList>");
     495         220 :     *pnLength += strlen(*ppszText + *pnLength);
     496             : 
     497         220 :     char szCoordinate[256] = {};
     498             : 
     499        1839 :     for (int iPoint = 0; iPoint < poLine->getNumPoints(); iPoint++)
     500             :     {
     501        1619 :         if (bCoordSwap)
     502             :         {
     503             :             const std::string wkt = OGRMakeWktCoordinate(
     504             :                 poLine->getY(iPoint), poLine->getX(iPoint),
     505          52 :                 poLine->getZ(iPoint), b3D ? 3 : 2, coordOpts);
     506          52 :             memcpy(szCoordinate, wkt.data(), wkt.size() + 1);
     507             :         }
     508             :         else
     509             :         {
     510             :             const std::string wkt = OGRMakeWktCoordinate(
     511             :                 poLine->getX(iPoint), poLine->getY(iPoint),
     512        1567 :                 poLine->getZ(iPoint), b3D ? 3 : 2, coordOpts);
     513        1567 :             memcpy(szCoordinate, wkt.data(), wkt.size() + 1);
     514             :         }
     515        1619 :         _GrowBuffer(*pnLength + strlen(szCoordinate) + 1, ppszText,
     516             :                     pnMaxLength);
     517             : 
     518        1619 :         if (iPoint != 0)
     519        1399 :             strcat(*ppszText + *pnLength, " ");
     520             : 
     521        1619 :         strcat(*ppszText + *pnLength, szCoordinate);
     522        1619 :         *pnLength += strlen(*ppszText + *pnLength);
     523             :     }
     524             : 
     525         220 :     _GrowBuffer(*pnLength + 20, ppszText, pnMaxLength);
     526         220 :     strcat(*ppszText + *pnLength, "</gml:posList>");
     527         220 :     *pnLength += strlen(*ppszText + *pnLength);
     528         220 : }
     529             : 
     530             : /************************************************************************/
     531             : /*                      OGR2GML3GeometryAppend()                        */
     532             : /************************************************************************/
     533             : 
     534         576 : static bool OGR2GML3GeometryAppend(
     535             :     const OGRGeometry *poGeometry, const OGRSpatialReference *poParentSRS,
     536             :     char **ppszText, size_t *pnLength, size_t *pnMaxLength, bool bIsSubGeometry,
     537             :     GMLSRSNameFormat eSRSNameFormat, bool bCoordSwap, bool bLineStringAsCurve,
     538             :     const char *pszGMLId, int nSRSDimensionLocFlags,
     539             :     bool bForceLineStringAsLinearRing, const char *pszNamespaceDecl,
     540             :     const char *pszOverriddenElementName, const OGRWktOptions &coordOpts)
     541             : 
     542             : {
     543             : 
     544             :     /* -------------------------------------------------------------------- */
     545             :     /*      Check for Spatial Reference System attached to given geometry   */
     546             :     /* -------------------------------------------------------------------- */
     547             : 
     548             :     // Buffer for srsName, xmlns:gml, srsDimension and gml:id attributes
     549             :     // (srsName="..." gml:id="...").
     550             : 
     551             :     const OGRSpatialReference *poSRS =
     552         576 :         poParentSRS ? poParentSRS : poGeometry->getSpatialReference();
     553             : 
     554         576 :     char szAttributes[256] = {};
     555         576 :     size_t nAttrsLength = 0;
     556             : 
     557         576 :     if (pszNamespaceDecl != nullptr)
     558             :     {
     559           2 :         snprintf(szAttributes + nAttrsLength,
     560             :                  sizeof(szAttributes) - nAttrsLength, " xmlns:gml=\"%s\"",
     561             :                  pszNamespaceDecl);
     562           2 :         pszNamespaceDecl = nullptr;
     563           2 :         nAttrsLength += strlen(szAttributes + nAttrsLength);
     564             :     }
     565             : 
     566         576 :     if (nullptr != poSRS)
     567             :     {
     568         185 :         const char *pszTarget = poSRS->IsProjected() ? "PROJCS" : "GEOGCS";
     569         185 :         const char *pszAuthName = poSRS->GetAuthorityName(pszTarget);
     570         185 :         const char *pszAuthCode = poSRS->GetAuthorityCode(pszTarget);
     571         185 :         if (nullptr != pszAuthName && strlen(pszAuthName) < 10 &&
     572         185 :             nullptr != pszAuthCode && strlen(pszAuthCode) < 10)
     573             :         {
     574         185 :             if (!bIsSubGeometry)
     575             :             {
     576         115 :                 if (eSRSNameFormat == SRSNAME_OGC_URN)
     577             :                 {
     578          70 :                     snprintf(szAttributes + nAttrsLength,
     579             :                              sizeof(szAttributes) - nAttrsLength,
     580             :                              " srsName=\"urn:ogc:def:crs:%s::%s\"", pszAuthName,
     581             :                              pszAuthCode);
     582             :                 }
     583          45 :                 else if (eSRSNameFormat == SRSNAME_SHORT)
     584             :                 {
     585           7 :                     snprintf(szAttributes + nAttrsLength,
     586             :                              sizeof(szAttributes) - nAttrsLength,
     587             :                              " srsName=\"%s:%s\"", pszAuthName, pszAuthCode);
     588             :                 }
     589          38 :                 else if (eSRSNameFormat == SRSNAME_OGC_URL)
     590             :                 {
     591          38 :                     snprintf(
     592             :                         szAttributes + nAttrsLength,
     593             :                         sizeof(szAttributes) - nAttrsLength,
     594             :                         " srsName=\"http://www.opengis.net/def/crs/%s/0/%s\"",
     595             :                         pszAuthName, pszAuthCode);
     596             :                 }
     597         115 :                 nAttrsLength += strlen(szAttributes + nAttrsLength);
     598             :             }
     599             :         }
     600             :     }
     601             : 
     602         578 :     if ((nSRSDimensionLocFlags & SRSDIM_LOC_GEOMETRY) != 0 &&
     603           2 :         wkbHasZ(poGeometry->getGeometryType()))
     604             :     {
     605           2 :         snprintf(szAttributes + nAttrsLength,
     606             :                  sizeof(szAttributes) - nAttrsLength, " srsDimension=\"3\"");
     607           2 :         nAttrsLength += strlen(szAttributes + nAttrsLength);
     608             : 
     609           2 :         nSRSDimensionLocFlags &= ~SRSDIM_LOC_GEOMETRY;
     610             :     }
     611             : 
     612         576 :     if (pszGMLId != nullptr &&
     613         360 :         nAttrsLength + 9 + strlen(pszGMLId) + 1 < sizeof(szAttributes))
     614             :     {
     615         360 :         snprintf(szAttributes + nAttrsLength,
     616             :                  sizeof(szAttributes) - nAttrsLength, " gml:id=\"%s\"",
     617             :                  pszGMLId);
     618         360 :         nAttrsLength += strlen(szAttributes + nAttrsLength);
     619             :     }
     620             : 
     621         576 :     const OGRwkbGeometryType eType = poGeometry->getGeometryType();
     622         576 :     const OGRwkbGeometryType eFType = wkbFlatten(eType);
     623             : 
     624             :     /* -------------------------------------------------------------------- */
     625             :     /*      2D Point                                                        */
     626             :     /* -------------------------------------------------------------------- */
     627         576 :     if (eType == wkbPoint)
     628             :     {
     629         118 :         const auto poPoint = poGeometry->toPoint();
     630             : 
     631         118 :         char szCoordinate[256] = {};
     632         118 :         if (bCoordSwap)
     633             :         {
     634             :             const auto wkt = OGRMakeWktCoordinate(
     635          31 :                 poPoint->getY(), poPoint->getX(), 0.0, 2, coordOpts);
     636          31 :             memcpy(szCoordinate, wkt.data(), wkt.size() + 1);
     637             :         }
     638             :         else
     639             :         {
     640             :             const auto wkt = OGRMakeWktCoordinate(
     641          87 :                 poPoint->getX(), poPoint->getY(), 0.0, 2, coordOpts);
     642          87 :             memcpy(szCoordinate, wkt.data(), wkt.size() + 1);
     643             :         }
     644         118 :         _GrowBuffer(*pnLength + strlen(szCoordinate) + 60 + nAttrsLength,
     645             :                     ppszText, pnMaxLength);
     646             : 
     647         118 :         snprintf(*ppszText + *pnLength, *pnMaxLength - *pnLength,
     648             :                  "<gml:Point%s><gml:pos>%s</gml:pos></gml:Point>", szAttributes,
     649             :                  szCoordinate);
     650             : 
     651         118 :         *pnLength += strlen(*ppszText + *pnLength);
     652             :     }
     653             :     /* -------------------------------------------------------------------- */
     654             :     /*      3D Point                                                        */
     655             :     /* -------------------------------------------------------------------- */
     656         458 :     else if (eType == wkbPoint25D)
     657             :     {
     658          11 :         const auto poPoint = poGeometry->toPoint();
     659             : 
     660          11 :         char szCoordinate[256] = {};
     661          11 :         if (bCoordSwap)
     662             :         {
     663             :             const auto wkt =
     664             :                 OGRMakeWktCoordinate(poPoint->getY(), poPoint->getX(),
     665           0 :                                      poPoint->getZ(), 3, coordOpts);
     666           0 :             memcpy(szCoordinate, wkt.data(), wkt.size() + 1);
     667             :         }
     668             :         else
     669             :         {
     670             :             const auto wkt =
     671             :                 OGRMakeWktCoordinate(poPoint->getX(), poPoint->getY(),
     672          11 :                                      poPoint->getZ(), 3, coordOpts);
     673          11 :             memcpy(szCoordinate, wkt.data(), wkt.size() + 1);
     674             :         }
     675             : 
     676          11 :         _GrowBuffer(*pnLength + strlen(szCoordinate) + 70 + nAttrsLength,
     677             :                     ppszText, pnMaxLength);
     678             : 
     679          11 :         snprintf(*ppszText + *pnLength, *pnMaxLength - *pnLength,
     680             :                  "<gml:Point%s><gml:pos>%s</gml:pos></gml:Point>", szAttributes,
     681             :                  szCoordinate);
     682             : 
     683          11 :         *pnLength += strlen(*ppszText + *pnLength);
     684             :     }
     685             : 
     686             :     /* -------------------------------------------------------------------- */
     687             :     /*      LineString and LinearRing                                       */
     688             :     /* -------------------------------------------------------------------- */
     689         447 :     else if (eFType == wkbLineString)
     690             :     {
     691         196 :         const bool bRing = EQUAL(poGeometry->getGeometryName(), "LINEARRING") ||
     692         196 :                            bForceLineStringAsLinearRing;
     693         196 :         if (!bRing && bLineStringAsCurve)
     694             :         {
     695           4 :             AppendString(ppszText, pnLength, pnMaxLength, "<gml:Curve");
     696           4 :             AppendString(ppszText, pnLength, pnMaxLength, szAttributes);
     697           4 :             AppendString(ppszText, pnLength, pnMaxLength,
     698             :                          "><gml:segments><gml:LineStringSegment>");
     699           4 :             const auto poLineString = poGeometry->toLineString();
     700           4 :             AppendGML3CoordinateList(poLineString, bCoordSwap, ppszText,
     701             :                                      pnLength, pnMaxLength,
     702             :                                      nSRSDimensionLocFlags, coordOpts);
     703           4 :             AppendString(ppszText, pnLength, pnMaxLength,
     704           4 :                          "</gml:LineStringSegment></gml:segments></gml:Curve>");
     705             :         }
     706             :         else
     707             :         {
     708             :             // Buffer for tag name + srsName attribute if set.
     709         192 :             const size_t nLineTagLength = 16;
     710         192 :             const size_t nLineTagNameBufLen = nLineTagLength + nAttrsLength + 1;
     711             :             char *pszLineTagName =
     712         192 :                 static_cast<char *>(CPLMalloc(nLineTagNameBufLen));
     713             : 
     714         192 :             if (bRing)
     715             :             {
     716             :                 // LinearRing isn't supposed to have srsName attribute according
     717             :                 // to GML3 SF-0.
     718         129 :                 AppendString(ppszText, pnLength, pnMaxLength,
     719             :                              "<gml:LinearRing>");
     720             :             }
     721             :             else
     722             :             {
     723          63 :                 snprintf(pszLineTagName, nLineTagNameBufLen,
     724             :                          "<gml:LineString%s>", szAttributes);
     725             : 
     726          63 :                 AppendString(ppszText, pnLength, pnMaxLength, pszLineTagName);
     727             :             }
     728             : 
     729             :             // Free tag buffer.
     730         192 :             CPLFree(pszLineTagName);
     731             : 
     732         192 :             const auto poLineString = poGeometry->toLineString();
     733             : 
     734         192 :             AppendGML3CoordinateList(poLineString, bCoordSwap, ppszText,
     735             :                                      pnLength, pnMaxLength,
     736             :                                      nSRSDimensionLocFlags, coordOpts);
     737             : 
     738         192 :             if (bRing)
     739         129 :                 AppendString(ppszText, pnLength, pnMaxLength,
     740             :                              "</gml:LinearRing>");
     741             :             else
     742          63 :                 AppendString(ppszText, pnLength, pnMaxLength,
     743             :                              "</gml:LineString>");
     744             :         }
     745             :     }
     746             : 
     747             :     /* -------------------------------------------------------------------- */
     748             :     /*      ArcString or Circle                                             */
     749             :     /* -------------------------------------------------------------------- */
     750         251 :     else if (eFType == wkbCircularString)
     751             :     {
     752          24 :         AppendString(ppszText, pnLength, pnMaxLength, "<gml:Curve");
     753          24 :         AppendString(ppszText, pnLength, pnMaxLength, szAttributes);
     754          24 :         const auto poSC = poGeometry->toCircularString();
     755             : 
     756             :         // SQL MM has a unique type for arc and circle, GML does not.
     757          39 :         if (poSC->getNumPoints() == 3 && poSC->getX(0) == poSC->getX(2) &&
     758          15 :             poSC->getY(0) == poSC->getY(2))
     759             :         {
     760          13 :             const double dfMidX = (poSC->getX(0) + poSC->getX(1)) / 2.0;
     761          13 :             const double dfMidY = (poSC->getY(0) + poSC->getY(1)) / 2.0;
     762          13 :             const double dfDirX = (poSC->getX(1) - poSC->getX(0)) / 2.0;
     763          13 :             const double dfDirY = (poSC->getY(1) - poSC->getY(0)) / 2.0;
     764          13 :             const double dfNormX = -dfDirY;
     765          13 :             const double dfNormY = dfDirX;
     766          13 :             const double dfNewX = dfMidX + dfNormX;
     767          13 :             const double dfNewY = dfMidY + dfNormY;
     768          13 :             OGRLineString *poLS = new OGRLineString();
     769          26 :             OGRPoint p;
     770          13 :             poSC->getPoint(0, &p);
     771          13 :             poLS->addPoint(&p);
     772          13 :             poSC->getPoint(1, &p);
     773          13 :             if (poSC->getCoordinateDimension() == 3)
     774           1 :                 poLS->addPoint(dfNewX, dfNewY, p.getZ());
     775             :             else
     776          12 :                 poLS->addPoint(dfNewX, dfNewY);
     777          13 :             poLS->addPoint(&p);
     778          13 :             AppendString(ppszText, pnLength, pnMaxLength,
     779             :                          "><gml:segments><gml:Circle>");
     780          13 :             AppendGML3CoordinateList(poLS, bCoordSwap, ppszText, pnLength,
     781             :                                      pnMaxLength, nSRSDimensionLocFlags,
     782             :                                      coordOpts);
     783          13 :             AppendString(ppszText, pnLength, pnMaxLength,
     784             :                          "</gml:Circle></gml:segments></gml:Curve>");
     785          13 :             delete poLS;
     786             :         }
     787             :         else
     788             :         {
     789          11 :             AppendString(ppszText, pnLength, pnMaxLength,
     790             :                          "><gml:segments><gml:ArcString>");
     791          11 :             AppendGML3CoordinateList(poSC, bCoordSwap, ppszText, pnLength,
     792             :                                      pnMaxLength, nSRSDimensionLocFlags,
     793             :                                      coordOpts);
     794          11 :             AppendString(ppszText, pnLength, pnMaxLength,
     795             :                          "</gml:ArcString></gml:segments></gml:Curve>");
     796             :         }
     797             :     }
     798             : 
     799             :     /* -------------------------------------------------------------------- */
     800             :     /*      CompositeCurve                                                  */
     801             :     /* -------------------------------------------------------------------- */
     802         227 :     else if (eFType == wkbCompoundCurve)
     803             :     {
     804           4 :         AppendString(ppszText, pnLength, pnMaxLength, "<gml:CompositeCurve");
     805           4 :         AppendString(ppszText, pnLength, pnMaxLength, szAttributes);
     806           4 :         AppendString(ppszText, pnLength, pnMaxLength, ">");
     807             : 
     808           4 :         const auto poCC = poGeometry->toCompoundCurve();
     809           8 :         for (int i = 0; i < poCC->getNumCurves(); i++)
     810             :         {
     811           4 :             AppendString(ppszText, pnLength, pnMaxLength, "<gml:curveMember>");
     812             : 
     813           4 :             char *pszGMLIdSub = nullptr;
     814           4 :             if (pszGMLId != nullptr)
     815           0 :                 pszGMLIdSub = CPLStrdup(CPLSPrintf("%s.%d", pszGMLId, i));
     816             : 
     817           4 :             CPL_IGNORE_RET_VAL(OGR2GML3GeometryAppend(
     818           4 :                 poCC->getCurve(i), poSRS, ppszText, pnLength, pnMaxLength, true,
     819             :                 eSRSNameFormat, bCoordSwap, bLineStringAsCurve, pszGMLIdSub,
     820             :                 nSRSDimensionLocFlags, false, nullptr, nullptr, coordOpts));
     821             : 
     822           4 :             CPLFree(pszGMLIdSub);
     823             : 
     824           4 :             AppendString(ppszText, pnLength, pnMaxLength, "</gml:curveMember>");
     825             :         }
     826           4 :         AppendString(ppszText, pnLength, pnMaxLength, "</gml:CompositeCurve>");
     827             :     }
     828             : 
     829             :     /* -------------------------------------------------------------------- */
     830             :     /*      Polygon                                                         */
     831             :     /* -------------------------------------------------------------------- */
     832         223 :     else if (eFType == wkbPolygon || eFType == wkbCurvePolygon)
     833             :     {
     834         135 :         const auto poCP = poGeometry->toCurvePolygon();
     835             : 
     836             :         // Buffer for polygon tag name + srsName attribute if set.
     837         135 :         const char *pszElemName =
     838         135 :             pszOverriddenElementName ? pszOverriddenElementName : "Polygon";
     839         135 :         const size_t nPolyTagLength = 7 + strlen(pszElemName);
     840         135 :         const size_t nPolyTagNameBufLen = nPolyTagLength + nAttrsLength + 1;
     841             :         char *pszPolyTagName =
     842         135 :             static_cast<char *>(CPLMalloc(nPolyTagNameBufLen));
     843             : 
     844             :         // Compose Polygon tag with or without srsName attribute.
     845         135 :         snprintf(pszPolyTagName, nPolyTagNameBufLen, "<gml:%s%s>", pszElemName,
     846             :                  szAttributes);
     847             : 
     848         135 :         AppendString(ppszText, pnLength, pnMaxLength, pszPolyTagName);
     849             : 
     850             :         // Free tag buffer.
     851         135 :         CPLFree(pszPolyTagName);
     852             : 
     853             :         const auto AppendCompoundCurveMembers =
     854         139 :             [&](const OGRGeometry *poRing, const char *pszGMLIdRing)
     855             :         {
     856         139 :             const auto eRingType = wkbFlatten(poRing->getGeometryType());
     857         139 :             if (eRingType == wkbCompoundCurve)
     858             :             {
     859           2 :                 AppendString(ppszText, pnLength, pnMaxLength, "<gml:Ring>");
     860           2 :                 const auto poCC = poRing->toCompoundCurve();
     861           2 :                 const int nNumCurves = poCC->getNumCurves();
     862           6 :                 for (int i = 0; i < nNumCurves; i++)
     863             :                 {
     864           4 :                     AppendString(ppszText, pnLength, pnMaxLength,
     865             :                                  "<gml:curveMember>");
     866             : 
     867           4 :                     char *pszGMLIdSub = nullptr;
     868           4 :                     if (pszGMLIdRing != nullptr)
     869             :                         pszGMLIdSub =
     870           2 :                             CPLStrdup(CPLSPrintf("%s.%d", pszGMLIdRing, i));
     871             : 
     872           4 :                     CPL_IGNORE_RET_VAL(OGR2GML3GeometryAppend(
     873           4 :                         poCC->getCurve(i), poSRS, ppszText, pnLength,
     874         141 :                         pnMaxLength, true, eSRSNameFormat, bCoordSwap,
     875         141 :                         bLineStringAsCurve, pszGMLIdSub, nSRSDimensionLocFlags,
     876           4 :                         false, nullptr, nullptr, coordOpts));
     877             : 
     878           4 :                     CPLFree(pszGMLIdSub);
     879             : 
     880           4 :                     AppendString(ppszText, pnLength, pnMaxLength,
     881             :                                  "</gml:curveMember>");
     882             :                 }
     883           2 :                 AppendString(ppszText, pnLength, pnMaxLength, "</gml:Ring>");
     884             :             }
     885             :             else
     886             :             {
     887         137 :                 if (eRingType != wkbLineString)
     888             :                 {
     889          11 :                     AppendString(ppszText, pnLength, pnMaxLength,
     890             :                                  "<gml:Ring><gml:curveMember>");
     891             :                 }
     892             : 
     893         137 :                 CPL_IGNORE_RET_VAL(OGR2GML3GeometryAppend(
     894             :                     poRing, poSRS, ppszText, pnLength, pnMaxLength, true,
     895         137 :                     eSRSNameFormat, bCoordSwap, bLineStringAsCurve,
     896             :                     pszGMLIdRing, nSRSDimensionLocFlags, true, nullptr, nullptr,
     897             :                     coordOpts));
     898             : 
     899         137 :                 if (eRingType != wkbLineString)
     900             :                 {
     901          11 :                     AppendString(ppszText, pnLength, pnMaxLength,
     902             :                                  "</gml:curveMember></gml:Ring>");
     903             :                 }
     904             :             }
     905         274 :         };
     906             : 
     907             :         // Don't add srsName to polygon rings.
     908             : 
     909         135 :         const auto poExteriorRing = poCP->getExteriorRingCurve();
     910         135 :         if (poExteriorRing != nullptr)
     911             :         {
     912         135 :             AppendString(ppszText, pnLength, pnMaxLength, "<gml:exterior>");
     913             : 
     914         209 :             AppendCompoundCurveMembers(
     915             :                 poExteriorRing,
     916         209 :                 pszGMLId ? (std::string(pszGMLId) + ".exterior").c_str()
     917             :                          : nullptr);
     918             : 
     919         135 :             AppendString(ppszText, pnLength, pnMaxLength, "</gml:exterior>");
     920             : 
     921         139 :             for (int iRing = 0; iRing < poCP->getNumInteriorRings(); iRing++)
     922             :             {
     923           4 :                 const OGRCurve *poRing = poCP->getInteriorRingCurve(iRing);
     924             : 
     925           4 :                 AppendString(ppszText, pnLength, pnMaxLength, "<gml:interior>");
     926             : 
     927           5 :                 AppendCompoundCurveMembers(
     928           5 :                     poRing, pszGMLId ? (std::string(pszGMLId) + ".interior." +
     929           5 :                                         std::to_string(iRing))
     930           1 :                                            .c_str()
     931             :                                      : nullptr);
     932             : 
     933           4 :                 AppendString(ppszText, pnLength, pnMaxLength,
     934             :                              "</gml:interior>");
     935             :             }
     936             :         }
     937             : 
     938         135 :         AppendString(ppszText, pnLength, pnMaxLength, "</gml:");
     939         135 :         AppendString(ppszText, pnLength, pnMaxLength, pszElemName);
     940         135 :         AppendString(ppszText, pnLength, pnMaxLength, ">");
     941             :     }
     942             : 
     943             :     /* -------------------------------------------------------------------- */
     944             :     /*     Triangle                                                         */
     945             :     /* -------------------------------------------------------------------- */
     946          88 :     else if (eFType == wkbTriangle)
     947             :     {
     948           3 :         const auto poTri = poGeometry->toPolygon();
     949             : 
     950           3 :         AppendString(ppszText, pnLength, pnMaxLength, "<gml:Triangle>");
     951             : 
     952           3 :         if (poTri->getExteriorRingCurve() != nullptr)
     953             :         {
     954           3 :             AppendString(ppszText, pnLength, pnMaxLength, "<gml:exterior>");
     955             : 
     956           3 :             CPL_IGNORE_RET_VAL(OGR2GML3GeometryAppend(
     957           3 :                 poTri->getExteriorRingCurve(), poSRS, ppszText, pnLength,
     958             :                 pnMaxLength, true, eSRSNameFormat, bCoordSwap,
     959             :                 bLineStringAsCurve, nullptr, nSRSDimensionLocFlags, true,
     960             :                 nullptr, nullptr, coordOpts));
     961             : 
     962           3 :             AppendString(ppszText, pnLength, pnMaxLength, "</gml:exterior>");
     963             :         }
     964             : 
     965           3 :         AppendString(ppszText, pnLength, pnMaxLength, "</gml:Triangle>");
     966             :     }
     967             : 
     968             :     /* -------------------------------------------------------------------- */
     969             :     /*      MultiSurface, MultiCurve, MultiPoint, MultiGeometry             */
     970             :     /* -------------------------------------------------------------------- */
     971          85 :     else if (eFType == wkbMultiPolygon || eFType == wkbMultiSurface ||
     972          31 :              eFType == wkbMultiLineString || eFType == wkbMultiCurve ||
     973          15 :              eFType == wkbMultiPoint || eFType == wkbGeometryCollection)
     974             :     {
     975          82 :         const auto poGC = poGeometry->toGeometryCollection();
     976          82 :         const char *pszElemClose = nullptr;
     977          82 :         const char *pszMemberElem = nullptr;
     978             : 
     979             :         // Buffer for opening tag + srsName attribute.
     980          82 :         char *pszElemOpen = nullptr;
     981             : 
     982          82 :         if (eFType == wkbMultiPolygon || eFType == wkbMultiSurface)
     983             :         {
     984          31 :             const size_t nBufLen = 13 + nAttrsLength + 1;
     985          31 :             pszElemOpen = static_cast<char *>(CPLMalloc(nBufLen));
     986          31 :             snprintf(pszElemOpen, nBufLen, "MultiSurface%s>", szAttributes);
     987             : 
     988          31 :             pszElemClose = "MultiSurface>";
     989          31 :             pszMemberElem = "surfaceMember>";
     990             :         }
     991          51 :         else if (eFType == wkbMultiLineString || eFType == wkbMultiCurve)
     992             :         {
     993          27 :             const size_t nBufLen = 16 + nAttrsLength + 1;
     994          27 :             pszElemOpen = static_cast<char *>(CPLMalloc(nBufLen));
     995          27 :             snprintf(pszElemOpen, nBufLen, "MultiCurve%s>", szAttributes);
     996             : 
     997          27 :             pszElemClose = "MultiCurve>";
     998          27 :             pszMemberElem = "curveMember>";
     999             :         }
    1000          24 :         else if (eFType == wkbMultiPoint)
    1001             :         {
    1002          12 :             const size_t nBufLen = 11 + nAttrsLength + 1;
    1003          12 :             pszElemOpen = static_cast<char *>(CPLMalloc(nBufLen));
    1004          12 :             snprintf(pszElemOpen, nBufLen, "MultiPoint%s>", szAttributes);
    1005             : 
    1006          12 :             pszElemClose = "MultiPoint>";
    1007          12 :             pszMemberElem = "pointMember>";
    1008             :         }
    1009             :         else
    1010             :         {
    1011          12 :             const size_t nBufLen = 19 + nAttrsLength + 1;
    1012          12 :             pszElemOpen = static_cast<char *>(CPLMalloc(nBufLen));
    1013          12 :             snprintf(pszElemOpen, nBufLen, "MultiGeometry%s>", szAttributes);
    1014             : 
    1015          12 :             pszElemClose = "MultiGeometry>";
    1016          12 :             pszMemberElem = "geometryMember>";
    1017             :         }
    1018             : 
    1019          82 :         AppendString(ppszText, pnLength, pnMaxLength, "<gml:");
    1020          82 :         AppendString(ppszText, pnLength, pnMaxLength, pszElemOpen);
    1021             : 
    1022             :         // Free tag buffer.
    1023          82 :         CPLFree(pszElemOpen);
    1024             : 
    1025         188 :         for (int iMember = 0; iMember < poGC->getNumGeometries(); iMember++)
    1026             :         {
    1027         106 :             const OGRGeometry *poMember = poGC->getGeometryRef(iMember);
    1028             : 
    1029         106 :             AppendString(ppszText, pnLength, pnMaxLength, "<gml:");
    1030         106 :             AppendString(ppszText, pnLength, pnMaxLength, pszMemberElem);
    1031             : 
    1032         106 :             char *pszGMLIdSub = nullptr;
    1033         106 :             if (pszGMLId != nullptr)
    1034          78 :                 pszGMLIdSub = CPLStrdup(CPLSPrintf("%s.%d", pszGMLId, iMember));
    1035             : 
    1036         106 :             if (!OGR2GML3GeometryAppend(
    1037             :                     poMember, poSRS, ppszText, pnLength, pnMaxLength, true,
    1038             :                     eSRSNameFormat, bCoordSwap, bLineStringAsCurve, pszGMLIdSub,
    1039             :                     nSRSDimensionLocFlags, false, nullptr, nullptr, coordOpts))
    1040             :             {
    1041           0 :                 CPLFree(pszGMLIdSub);
    1042           0 :                 return false;
    1043             :             }
    1044             : 
    1045         106 :             CPLFree(pszGMLIdSub);
    1046             : 
    1047         106 :             AppendString(ppszText, pnLength, pnMaxLength, "</gml:");
    1048         106 :             AppendString(ppszText, pnLength, pnMaxLength, pszMemberElem);
    1049             :         }
    1050             : 
    1051          82 :         AppendString(ppszText, pnLength, pnMaxLength, "</gml:");
    1052          82 :         AppendString(ppszText, pnLength, pnMaxLength, pszElemClose);
    1053             :     }
    1054             : 
    1055             :     /* -------------------------------------------------------------------- */
    1056             :     /*      Polyhedral Surface                                              */
    1057             :     /* -------------------------------------------------------------------- */
    1058           3 :     else if (eFType == wkbPolyhedralSurface)
    1059             :     {
    1060             :         // The patches enclosed in a single <gml:polygonPatches> tag need to be
    1061             :         // co-planar.
    1062             :         // TODO - enforce the condition within this implementation
    1063           2 :         const auto poPS = poGeometry->toPolyhedralSurface();
    1064             : 
    1065           2 :         AppendString(ppszText, pnLength, pnMaxLength, "<gml:PolyhedralSurface");
    1066           2 :         AppendString(ppszText, pnLength, pnMaxLength, szAttributes);
    1067           2 :         AppendString(ppszText, pnLength, pnMaxLength, "><gml:polygonPatches>");
    1068             : 
    1069           8 :         for (int iMember = 0; iMember < poPS->getNumGeometries(); iMember++)
    1070             :         {
    1071           6 :             const OGRGeometry *poMember = poPS->getGeometryRef(iMember);
    1072           6 :             char *pszGMLIdSub = nullptr;
    1073           6 :             if (pszGMLId != nullptr)
    1074           0 :                 pszGMLIdSub = CPLStrdup(CPLSPrintf("%s.%d", pszGMLId, iMember));
    1075             : 
    1076           6 :             if (!OGR2GML3GeometryAppend(poMember, poSRS, ppszText, pnLength,
    1077             :                                         pnMaxLength, true, eSRSNameFormat,
    1078             :                                         bCoordSwap, bLineStringAsCurve, nullptr,
    1079             :                                         nSRSDimensionLocFlags, false, nullptr,
    1080             :                                         "PolygonPatch", coordOpts))
    1081             :             {
    1082           0 :                 CPLFree(pszGMLIdSub);
    1083           0 :                 return false;
    1084             :             }
    1085             : 
    1086           6 :             CPLFree(pszGMLIdSub);
    1087             :         }
    1088             : 
    1089           2 :         AppendString(ppszText, pnLength, pnMaxLength, "</gml:polygonPatches>");
    1090           2 :         AppendString(ppszText, pnLength, pnMaxLength,
    1091             :                      "</gml:PolyhedralSurface>");
    1092             :     }
    1093             : 
    1094             :     /* -------------------------------------------------------------------- */
    1095             :     /*      TIN                                                             */
    1096             :     /* -------------------------------------------------------------------- */
    1097           1 :     else if (eFType == wkbTIN)
    1098             :     {
    1099             :         // OGR uses the following hierarchy for TriangulatedSurface -
    1100             : 
    1101             :         // <gml:TriangulatedSurface>
    1102             :         //     <gml:patches>
    1103             :         //         <gml:Triangle>
    1104             :         //             <gml:exterior>
    1105             :         //                 <gml:LinearRing>
    1106             :         //                     <gml:posList srsDimension=...>...</gml:posList>
    1107             :         //                 </gml:LinearRing>
    1108             :         //             </gml:exterior>
    1109             :         //         </gml:Triangle>
    1110             :         //     </gml:patches>
    1111             :         // </gml:TriangulatedSurface>
    1112             : 
    1113             :         // <gml:trianglePatches> is deprecated, so write feature is not enabled
    1114             :         // for <gml:trianglePatches>
    1115           1 :         const auto poTIN = poGeometry->toPolyhedralSurface();
    1116             : 
    1117           1 :         AppendString(ppszText, pnLength, pnMaxLength,
    1118             :                      "<gml:TriangulatedSurface");
    1119           1 :         AppendString(ppszText, pnLength, pnMaxLength, szAttributes);
    1120           1 :         AppendString(ppszText, pnLength, pnMaxLength, "><gml:patches>");
    1121             : 
    1122           3 :         for (int iMember = 0; iMember < poTIN->getNumGeometries(); iMember++)
    1123             :         {
    1124           2 :             const OGRGeometry *poMember = poTIN->getGeometryRef(iMember);
    1125             : 
    1126           2 :             char *pszGMLIdSub = nullptr;
    1127           2 :             if (pszGMLId != nullptr)
    1128           0 :                 pszGMLIdSub = CPLStrdup(CPLSPrintf("%s.%d", pszGMLId, iMember));
    1129             : 
    1130           2 :             CPL_IGNORE_RET_VAL(OGR2GML3GeometryAppend(
    1131             :                 poMember, poSRS, ppszText, pnLength, pnMaxLength, true,
    1132             :                 eSRSNameFormat, bCoordSwap, bLineStringAsCurve, nullptr,
    1133             :                 nSRSDimensionLocFlags, false, nullptr, nullptr, coordOpts));
    1134             : 
    1135           2 :             CPLFree(pszGMLIdSub);
    1136             :         }
    1137             : 
    1138           1 :         AppendString(ppszText, pnLength, pnMaxLength, "</gml:patches>");
    1139           1 :         AppendString(ppszText, pnLength, pnMaxLength,
    1140             :                      "</gml:TriangulatedSurface>");
    1141             :     }
    1142             : 
    1143             :     else
    1144             :     {
    1145           0 :         CPLError(CE_Failure, CPLE_NotSupported, "Unsupported geometry type %s",
    1146             :                  OGRGeometryTypeToName(eType));
    1147           0 :         return false;
    1148             :     }
    1149             : 
    1150         576 :     return true;
    1151             : }
    1152             : 
    1153             : /************************************************************************/
    1154             : /*                       OGR_G_ExportToGMLTree()                        */
    1155             : /************************************************************************/
    1156             : 
    1157             : /** Convert a geometry into GML format. */
    1158           0 : CPLXMLNode *OGR_G_ExportToGMLTree(OGRGeometryH hGeometry)
    1159             : 
    1160             : {
    1161           0 :     char *pszText = OGR_G_ExportToGML(hGeometry);
    1162           0 :     if (pszText == nullptr)
    1163           0 :         return nullptr;
    1164             : 
    1165           0 :     CPLXMLNode *psTree = CPLParseXMLString(pszText);
    1166             : 
    1167           0 :     CPLFree(pszText);
    1168             : 
    1169           0 :     return psTree;
    1170             : }
    1171             : 
    1172             : /************************************************************************/
    1173             : /*                         OGR_G_ExportToGML()                          */
    1174             : /************************************************************************/
    1175             : 
    1176             : /**
    1177             :  * \brief Convert a geometry into GML format.
    1178             :  *
    1179             :  * The GML geometry is expressed directly in terms of GML basic data
    1180             :  * types assuming the this is available in the gml namespace.  The returned
    1181             :  * string should be freed with CPLFree() when no longer required.
    1182             :  *
    1183             :  * This method is the same as the C++ method OGRGeometry::exportToGML().
    1184             :  *
    1185             :  * @param hGeometry handle to the geometry.
    1186             :  * @return A GML fragment or NULL in case of error.
    1187             :  */
    1188             : 
    1189           1 : char *OGR_G_ExportToGML(OGRGeometryH hGeometry)
    1190             : 
    1191             : {
    1192           1 :     return OGR_G_ExportToGMLEx(hGeometry, nullptr);
    1193             : }
    1194             : 
    1195             : /************************************************************************/
    1196             : /*                        OGR_G_ExportToGMLEx()                         */
    1197             : /************************************************************************/
    1198             : 
    1199             : /**
    1200             :  * \brief Convert a geometry into GML format.
    1201             :  *
    1202             :  * The GML geometry is expressed directly in terms of GML basic data
    1203             :  * types assuming the this is available in the gml namespace.  The returned
    1204             :  * string should be freed with CPLFree() when no longer required.
    1205             :  *
    1206             :  * The supported options are :
    1207             :  * <ul>
    1208             :  * <li> FORMAT=GML2/GML3/GML32 (GML2 or GML32 added in GDAL 2.1).
    1209             :  *      If not set, it will default to GML 2.1.2 output.
    1210             :  * </li>
    1211             :  * <li> GML3_LINESTRING_ELEMENT=curve. (Only valid for FORMAT=GML3)
    1212             :  *      To use gml:Curve element for linestrings.
    1213             :  *      Otherwise gml:LineString will be used .
    1214             :  * </li>
    1215             :  * <li> GML3_LONGSRS=YES/NO. (Only valid for FORMAT=GML3, deprecated by
    1216             :  *      SRSNAME_FORMAT in GDAL &gt;=2.2). Defaults to YES.
    1217             :  *      If YES, SRS with EPSG authority will be written with the
    1218             :  *      "urn:ogc:def:crs:EPSG::" prefix.
    1219             :  *      In the case the SRS should be treated as lat/long or
    1220             :  *      northing/easting, then the function will take care of coordinate order
    1221             :  *      swapping if the data axis to CRS axis mapping indicates it.
    1222             :  *      If set to NO, SRS with EPSG authority will be written with the "EPSG:"
    1223             :  *      prefix, even if they are in lat/long order.
    1224             :  * </li>
    1225             :  * <li> SRSNAME_FORMAT=SHORT/OGC_URN/OGC_URL (Only valid for FORMAT=GML3, added
    1226             :  *      in GDAL 2.2). Defaults to OGC_URN.  If SHORT, then srsName will be in
    1227             :  *      the form AUTHORITY_NAME:AUTHORITY_CODE. If OGC_URN, then srsName will be
    1228             :  *      in the form urn:ogc:def:crs:AUTHORITY_NAME::AUTHORITY_CODE. If OGC_URL,
    1229             :  *      then srsName will be in the form
    1230             :  *      http://www.opengis.net/def/crs/AUTHORITY_NAME/0/AUTHORITY_CODE. For
    1231             :  *      OGC_URN and OGC_URL, in the case the SRS should be treated as lat/long
    1232             :  *      or northing/easting, then the function will take care of coordinate
    1233             :  *      order swapping if the data axis to CRS axis mapping indicates it.
    1234             :  * </li>
    1235             :  * <li> GMLID=astring. If specified, a gml:id attribute will be written in the
    1236             :  *      top-level geometry element with the provided value.
    1237             :  *      Required for GML 3.2 compatibility.
    1238             :  * </li>
    1239             :  * <li> SRSDIMENSION_LOC=POSLIST/GEOMETRY/GEOMETRY,POSLIST. (Only valid for
    1240             :  *      FORMAT=GML3/GML32, GDAL >= 2.0) Default to POSLIST.
    1241             :  *      For 2.5D geometries, define the location where to attach the
    1242             :  *      srsDimension attribute.
    1243             :  *      There are diverging implementations. Some put in on the
    1244             :  *      &lt;gml:posList&gt; element, other on the top geometry element.
    1245             :  * </li>
    1246             :  * <li> NAMESPACE_DECL=YES/NO. If set to YES,
    1247             :  *      xmlns:gml="http://www.opengis.net/gml" will be added to the root node
    1248             :  *      for GML < 3.2 or xmlns:gml="http://www.opengis.net/gml/3.2" for GML 3.2
    1249             :  * </li>
    1250             :  * <li> XY_COORD_RESOLUTION=double (added in GDAL 3.9):
    1251             :  *      Resolution for the coordinate precision of the X and Y coordinates.
    1252             :  *      Expressed in the units of the X and Y axis of the SRS. eg 1e-5 for up
    1253             :  *      to 5 decimal digits. 0 for the default behavior.
    1254             :  * </li>
    1255             :  * <li> Z_COORD_RESOLUTION=double (added in GDAL 3.9):
    1256             :  *      Resolution for the coordinate precision of the Z coordinates.
    1257             :  *      Expressed in the units of the Z axis of the SRS.
    1258             :  *      0 for the default behavior.
    1259             :  * </li>
    1260             :  * </ul>
    1261             :  *
    1262             :  * Note that curve geometries like CIRCULARSTRING, COMPOUNDCURVE, CURVEPOLYGON,
    1263             :  * MULTICURVE or MULTISURFACE are not supported in GML 2.
    1264             :  *
    1265             :  * This method is the same as the C++ method OGRGeometry::exportToGML().
    1266             :  *
    1267             :  * @param hGeometry handle to the geometry.
    1268             :  * @param papszOptions NULL-terminated list of options.
    1269             :  * @return A GML fragment or NULL in case of error.
    1270             :  *
    1271             :  * @since OGR 1.8.0
    1272             :  */
    1273             : 
    1274         410 : char *OGR_G_ExportToGMLEx(OGRGeometryH hGeometry, char **papszOptions)
    1275             : 
    1276             : {
    1277         410 :     if (hGeometry == nullptr)
    1278           0 :         return CPLStrdup("");
    1279             : 
    1280             :     // Do not use hGeometry after here.
    1281         410 :     OGRGeometry *poGeometry = OGRGeometry::FromHandle(hGeometry);
    1282             : 
    1283         410 :     OGRWktOptions coordOpts;
    1284             : 
    1285             :     const char *pszXYCoordRes =
    1286         410 :         CSLFetchNameValue(papszOptions, "XY_COORD_RESOLUTION");
    1287         410 :     if (pszXYCoordRes)
    1288             :     {
    1289           3 :         coordOpts.format = OGRWktFormat::F;
    1290           3 :         coordOpts.xyPrecision =
    1291           3 :             OGRGeomCoordinatePrecision::ResolutionToPrecision(
    1292             :                 CPLAtof(pszXYCoordRes));
    1293             :     }
    1294             : 
    1295             :     const char *pszZCoordRes =
    1296         410 :         CSLFetchNameValue(papszOptions, "Z_COORD_RESOLUTION");
    1297         410 :     if (pszZCoordRes)
    1298             :     {
    1299           3 :         coordOpts.format = OGRWktFormat::F;
    1300           3 :         coordOpts.zPrecision =
    1301           3 :             OGRGeomCoordinatePrecision::ResolutionToPrecision(
    1302             :                 CPLAtof(pszZCoordRes));
    1303             :     }
    1304             : 
    1305         410 :     size_t nLength = 0;
    1306         410 :     size_t nMaxLength = 1;
    1307             : 
    1308         410 :     char *pszText = static_cast<char *>(CPLMalloc(nMaxLength));
    1309         410 :     pszText[0] = '\0';
    1310             : 
    1311         410 :     const char *pszFormat = CSLFetchNameValue(papszOptions, "FORMAT");
    1312             :     const bool bNamespaceDecl =
    1313         410 :         CPLTestBool(CSLFetchNameValueDef(papszOptions, "NAMESPACE_DECL",
    1314         410 :                                          "NO")) != FALSE;
    1315         410 :     if (pszFormat && (EQUAL(pszFormat, "GML3") || EQUAL(pszFormat, "GML32")))
    1316             :     {
    1317             :         const char *pszLineStringElement =
    1318         314 :             CSLFetchNameValue(papszOptions, "GML3_LINESTRING_ELEMENT");
    1319         314 :         const bool bLineStringAsCurve =
    1320         314 :             pszLineStringElement && EQUAL(pszLineStringElement, "curve");
    1321             :         const char *pszLongSRS =
    1322         314 :             CSLFetchNameValue(papszOptions, "GML3_LONGSRS");
    1323             :         const char *pszSRSNameFormat =
    1324         314 :             CSLFetchNameValue(papszOptions, "SRSNAME_FORMAT");
    1325         314 :         GMLSRSNameFormat eSRSNameFormat = SRSNAME_OGC_URN;
    1326         314 :         if (pszSRSNameFormat)
    1327             :         {
    1328         259 :             if (pszLongSRS)
    1329             :             {
    1330           0 :                 CPLError(CE_Warning, CPLE_NotSupported,
    1331             :                          "Both GML3_LONGSRS and SRSNAME_FORMAT specified. "
    1332             :                          "Ignoring GML3_LONGSRS");
    1333             :             }
    1334         259 :             if (EQUAL(pszSRSNameFormat, "SHORT"))
    1335           2 :                 eSRSNameFormat = SRSNAME_SHORT;
    1336         257 :             else if (EQUAL(pszSRSNameFormat, "OGC_URN"))
    1337         183 :                 eSRSNameFormat = SRSNAME_OGC_URN;
    1338          74 :             else if (EQUAL(pszSRSNameFormat, "OGC_URL"))
    1339          74 :                 eSRSNameFormat = SRSNAME_OGC_URL;
    1340             :             else
    1341             :             {
    1342           0 :                 CPLError(CE_Warning, CPLE_NotSupported,
    1343             :                          "Invalid value for SRSNAME_FORMAT. "
    1344             :                          "Using SRSNAME_OGC_URN");
    1345             :             }
    1346             :         }
    1347          55 :         else if (pszLongSRS && !CPLTestBool(pszLongSRS))
    1348           5 :             eSRSNameFormat = SRSNAME_SHORT;
    1349             : 
    1350         314 :         const char *pszGMLId = CSLFetchNameValue(papszOptions, "GMLID");
    1351         314 :         if (pszGMLId == nullptr && EQUAL(pszFormat, "GML32"))
    1352           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    1353             :                      "FORMAT=GML32 specified but not GMLID set");
    1354             :         const char *pszSRSDimensionLoc =
    1355         314 :             CSLFetchNameValueDef(papszOptions, "SRSDIMENSION_LOC", "POSLIST");
    1356             :         char **papszSRSDimensionLoc =
    1357         314 :             CSLTokenizeString2(pszSRSDimensionLoc, ",", 0);
    1358         314 :         int nSRSDimensionLocFlags = 0;
    1359         629 :         for (int i = 0; papszSRSDimensionLoc[i] != nullptr; i++)
    1360             :         {
    1361         315 :             if (EQUAL(papszSRSDimensionLoc[i], "POSLIST"))
    1362         313 :                 nSRSDimensionLocFlags |= SRSDIM_LOC_POSLIST;
    1363           2 :             else if (EQUAL(papszSRSDimensionLoc[i], "GEOMETRY"))
    1364           2 :                 nSRSDimensionLocFlags |= SRSDIM_LOC_GEOMETRY;
    1365             :             else
    1366           0 :                 CPLDebug("OGR", "Unrecognized location for srsDimension : %s",
    1367           0 :                          papszSRSDimensionLoc[i]);
    1368             :         }
    1369         314 :         CSLDestroy(papszSRSDimensionLoc);
    1370         314 :         const char *pszNamespaceDecl = nullptr;
    1371         314 :         if (bNamespaceDecl && EQUAL(pszFormat, "GML32"))
    1372           1 :             pszNamespaceDecl = "http://www.opengis.net/gml/3.2";
    1373         313 :         else if (bNamespaceDecl)
    1374           1 :             pszNamespaceDecl = "http://www.opengis.net/gml";
    1375             : 
    1376         314 :         bool bCoordSwap = false;
    1377             :         const char *pszCoordSwap =
    1378         314 :             CSLFetchNameValue(papszOptions, "COORD_SWAP");
    1379         314 :         if (pszCoordSwap)
    1380             :         {
    1381           0 :             bCoordSwap = CPLTestBool(pszCoordSwap);
    1382             :         }
    1383             :         else
    1384             :         {
    1385             :             const OGRSpatialReference *poSRS =
    1386         314 :                 poGeometry->getSpatialReference();
    1387         314 :             if (poSRS != nullptr && eSRSNameFormat != SRSNAME_SHORT)
    1388             :             {
    1389         108 :                 const auto &map = poSRS->GetDataAxisToSRSAxisMapping();
    1390         108 :                 if (map.size() >= 2 && map[0] == 2 && map[1] == 1)
    1391             :                 {
    1392          45 :                     bCoordSwap = true;
    1393             :                 }
    1394             :             }
    1395             :         }
    1396             : 
    1397         314 :         if (!OGR2GML3GeometryAppend(poGeometry, nullptr, &pszText, &nLength,
    1398             :                                     &nMaxLength, false, eSRSNameFormat,
    1399             :                                     bCoordSwap, bLineStringAsCurve, pszGMLId,
    1400             :                                     nSRSDimensionLocFlags, false,
    1401             :                                     pszNamespaceDecl, nullptr, coordOpts))
    1402             :         {
    1403           0 :             CPLFree(pszText);
    1404           0 :             return nullptr;
    1405             :         }
    1406             : 
    1407         314 :         return pszText;
    1408             :     }
    1409             : 
    1410          96 :     const char *pszNamespaceDecl = nullptr;
    1411          96 :     if (bNamespaceDecl)
    1412           1 :         pszNamespaceDecl = "http://www.opengis.net/gml";
    1413          96 :     if (!OGR2GMLGeometryAppend(poGeometry, &pszText, &nLength, &nMaxLength,
    1414             :                                false, pszNamespaceDecl, coordOpts))
    1415             :     {
    1416           1 :         CPLFree(pszText);
    1417           1 :         return nullptr;
    1418             :     }
    1419             : 
    1420          95 :     return pszText;
    1421             : }

Generated by: LCOV version 1.14