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

Generated by: LCOV version 1.14