LCOV - code coverage report
Current view: top level - ogr - ogr2gmlgeometry.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 534 564 94.7 %
Date: 2026-05-13 02:25:09 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());
     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        1152 :     std::string osAttributes;
     549             : 
     550         576 :     if (pszNamespaceDecl != nullptr)
     551             :     {
     552           2 :         osAttributes += " xmlns:gml=\"";
     553           2 :         osAttributes += pszNamespaceDecl;
     554           2 :         osAttributes += '"';
     555             :     }
     556             : 
     557         576 :     if (!bIsSubGeometry && poSRS)
     558             :     {
     559         115 :         const char *pszAuthName = poSRS->GetAuthorityName(nullptr);
     560         115 :         const char *pszAuthCode = poSRS->GetAuthorityCode(nullptr);
     561         115 :         if (pszAuthName && pszAuthCode &&
     562         115 :             strlen(pszAuthName) + strlen(pszAuthCode) < 30)
     563             :         {
     564         115 :             if (eSRSNameFormat == SRSNAME_OGC_URN)
     565             :             {
     566          70 :                 osAttributes += " srsName=\"urn:ogc:def:crs:";
     567          70 :                 osAttributes += pszAuthName;
     568          70 :                 osAttributes += "::";
     569          70 :                 osAttributes += pszAuthCode;
     570          70 :                 osAttributes += '"';
     571             :             }
     572          45 :             else if (eSRSNameFormat == SRSNAME_SHORT)
     573             :             {
     574           7 :                 osAttributes += " srsName=\"";
     575           7 :                 osAttributes += pszAuthName;
     576           7 :                 osAttributes += ":";
     577           7 :                 osAttributes += pszAuthCode;
     578           7 :                 osAttributes += '"';
     579             :             }
     580          38 :             else if (eSRSNameFormat == SRSNAME_OGC_URL)
     581             :             {
     582          38 :                 osAttributes += " srsName=\"http://www.opengis.net/def/crs/";
     583          38 :                 osAttributes += pszAuthName;
     584          38 :                 osAttributes += "/0/";
     585          38 :                 osAttributes += pszAuthCode;
     586          38 :                 osAttributes += '"';
     587             :             }
     588             :         }
     589             :     }
     590             : 
     591         578 :     if ((nSRSDimensionLocFlags & SRSDIM_LOC_GEOMETRY) != 0 &&
     592           2 :         wkbHasZ(poGeometry->getGeometryType()))
     593             :     {
     594           2 :         osAttributes += " srsDimension=\"3\"";
     595             : 
     596           2 :         nSRSDimensionLocFlags &= ~SRSDIM_LOC_GEOMETRY;
     597             :     }
     598             : 
     599         576 :     if (pszGMLId)
     600             :     {
     601         360 :         osAttributes += " gml:id=\"";
     602         360 :         osAttributes += pszGMLId;
     603         360 :         osAttributes += '"';
     604             :     }
     605             : 
     606         576 :     const OGRwkbGeometryType eType = poGeometry->getGeometryType();
     607         576 :     const OGRwkbGeometryType eFType = wkbFlatten(eType);
     608             : 
     609             :     /* -------------------------------------------------------------------- */
     610             :     /*      2D Point                                                        */
     611             :     /* -------------------------------------------------------------------- */
     612         576 :     if (eType == wkbPoint)
     613             :     {
     614         118 :         const auto poPoint = poGeometry->toPoint();
     615             : 
     616         118 :         char szCoordinate[256] = {};
     617         118 :         if (bCoordSwap)
     618             :         {
     619             :             const auto wkt = OGRMakeWktCoordinate(
     620          31 :                 poPoint->getY(), poPoint->getX(), 0.0, 2, coordOpts);
     621          31 :             memcpy(szCoordinate, wkt.data(), wkt.size() + 1);
     622             :         }
     623             :         else
     624             :         {
     625             :             const auto wkt = OGRMakeWktCoordinate(
     626          87 :                 poPoint->getX(), poPoint->getY(), 0.0, 2, coordOpts);
     627          87 :             memcpy(szCoordinate, wkt.data(), wkt.size() + 1);
     628             :         }
     629         118 :         _GrowBuffer(*pnLength + strlen(szCoordinate) + 60 + osAttributes.size(),
     630             :                     ppszText, pnMaxLength);
     631             : 
     632         118 :         snprintf(*ppszText + *pnLength, *pnMaxLength - *pnLength,
     633             :                  "<gml:Point%s><gml:pos>%s</gml:pos></gml:Point>",
     634             :                  osAttributes.c_str(), szCoordinate);
     635             : 
     636         118 :         *pnLength += strlen(*ppszText + *pnLength);
     637             :     }
     638             :     /* -------------------------------------------------------------------- */
     639             :     /*      3D Point                                                        */
     640             :     /* -------------------------------------------------------------------- */
     641         458 :     else if (eType == wkbPoint25D)
     642             :     {
     643          11 :         const auto poPoint = poGeometry->toPoint();
     644             : 
     645          11 :         char szCoordinate[256] = {};
     646          11 :         if (bCoordSwap)
     647             :         {
     648             :             const auto wkt =
     649             :                 OGRMakeWktCoordinate(poPoint->getY(), poPoint->getX(),
     650           0 :                                      poPoint->getZ(), 3, coordOpts);
     651           0 :             memcpy(szCoordinate, wkt.data(), wkt.size() + 1);
     652             :         }
     653             :         else
     654             :         {
     655             :             const auto wkt =
     656             :                 OGRMakeWktCoordinate(poPoint->getX(), poPoint->getY(),
     657          11 :                                      poPoint->getZ(), 3, coordOpts);
     658          11 :             memcpy(szCoordinate, wkt.data(), wkt.size() + 1);
     659             :         }
     660             : 
     661          11 :         _GrowBuffer(*pnLength + strlen(szCoordinate) + 70 + osAttributes.size(),
     662             :                     ppszText, pnMaxLength);
     663             : 
     664          11 :         snprintf(*ppszText + *pnLength, *pnMaxLength - *pnLength,
     665             :                  "<gml:Point%s><gml:pos>%s</gml:pos></gml:Point>",
     666             :                  osAttributes.c_str(), szCoordinate);
     667             : 
     668          11 :         *pnLength += strlen(*ppszText + *pnLength);
     669             :     }
     670             : 
     671             :     /* -------------------------------------------------------------------- */
     672             :     /*      LineString and LinearRing                                       */
     673             :     /* -------------------------------------------------------------------- */
     674         447 :     else if (eFType == wkbLineString)
     675             :     {
     676         196 :         const bool bRing = EQUAL(poGeometry->getGeometryName(), "LINEARRING") ||
     677         196 :                            bForceLineStringAsLinearRing;
     678         196 :         if (!bRing && bLineStringAsCurve)
     679             :         {
     680           4 :             AppendString(ppszText, pnLength, pnMaxLength, "<gml:Curve");
     681           4 :             AppendString(ppszText, pnLength, pnMaxLength, osAttributes.c_str());
     682           4 :             AppendString(ppszText, pnLength, pnMaxLength,
     683             :                          "><gml:segments><gml:LineStringSegment>");
     684           4 :             const auto poLineString = poGeometry->toLineString();
     685           4 :             AppendGML3CoordinateList(poLineString, bCoordSwap, ppszText,
     686             :                                      pnLength, pnMaxLength,
     687             :                                      nSRSDimensionLocFlags, coordOpts);
     688           4 :             AppendString(ppszText, pnLength, pnMaxLength,
     689           4 :                          "</gml:LineStringSegment></gml:segments></gml:Curve>");
     690             :         }
     691             :         else
     692             :         {
     693             :             // Buffer for tag name + srsName attribute if set.
     694         192 :             const size_t nLineTagLength = 16;
     695             :             const size_t nLineTagNameBufLen =
     696         192 :                 nLineTagLength + osAttributes.size() + 1;
     697             :             char *pszLineTagName =
     698         192 :                 static_cast<char *>(CPLMalloc(nLineTagNameBufLen));
     699             : 
     700         192 :             if (bRing)
     701             :             {
     702             :                 // LinearRing isn't supposed to have srsName attribute according
     703             :                 // to GML3 SF-0.
     704         129 :                 AppendString(ppszText, pnLength, pnMaxLength,
     705             :                              "<gml:LinearRing>");
     706             :             }
     707             :             else
     708             :             {
     709          63 :                 snprintf(pszLineTagName, nLineTagNameBufLen,
     710             :                          "<gml:LineString%s>", osAttributes.c_str());
     711             : 
     712          63 :                 AppendString(ppszText, pnLength, pnMaxLength, pszLineTagName);
     713             :             }
     714             : 
     715             :             // Free tag buffer.
     716         192 :             CPLFree(pszLineTagName);
     717             : 
     718         192 :             const auto poLineString = poGeometry->toLineString();
     719             : 
     720         192 :             AppendGML3CoordinateList(poLineString, bCoordSwap, ppszText,
     721             :                                      pnLength, pnMaxLength,
     722             :                                      nSRSDimensionLocFlags, coordOpts);
     723             : 
     724         192 :             if (bRing)
     725         129 :                 AppendString(ppszText, pnLength, pnMaxLength,
     726             :                              "</gml:LinearRing>");
     727             :             else
     728          63 :                 AppendString(ppszText, pnLength, pnMaxLength,
     729             :                              "</gml:LineString>");
     730             :         }
     731             :     }
     732             : 
     733             :     /* -------------------------------------------------------------------- */
     734             :     /*      ArcString or Circle                                             */
     735             :     /* -------------------------------------------------------------------- */
     736         251 :     else if (eFType == wkbCircularString)
     737             :     {
     738          24 :         AppendString(ppszText, pnLength, pnMaxLength, "<gml:Curve");
     739          24 :         AppendString(ppszText, pnLength, pnMaxLength, osAttributes.c_str());
     740          24 :         const auto poSC = poGeometry->toCircularString();
     741             : 
     742             :         // SQL MM has a unique type for arc and circle, GML does not.
     743          39 :         if (poSC->getNumPoints() == 3 && poSC->getX(0) == poSC->getX(2) &&
     744          15 :             poSC->getY(0) == poSC->getY(2))
     745             :         {
     746          13 :             const double dfMidX = (poSC->getX(0) + poSC->getX(1)) / 2.0;
     747          13 :             const double dfMidY = (poSC->getY(0) + poSC->getY(1)) / 2.0;
     748          13 :             const double dfDirX = (poSC->getX(1) - poSC->getX(0)) / 2.0;
     749          13 :             const double dfDirY = (poSC->getY(1) - poSC->getY(0)) / 2.0;
     750          13 :             const double dfNormX = -dfDirY;
     751          13 :             const double dfNormY = dfDirX;
     752          13 :             const double dfNewX = dfMidX + dfNormX;
     753          13 :             const double dfNewY = dfMidY + dfNormY;
     754          13 :             OGRLineString *poLS = new OGRLineString();
     755          26 :             OGRPoint p;
     756          13 :             poSC->getPoint(0, &p);
     757          13 :             poLS->addPoint(&p);
     758          13 :             poSC->getPoint(1, &p);
     759          13 :             if (poSC->getCoordinateDimension() == 3)
     760           1 :                 poLS->addPoint(dfNewX, dfNewY, p.getZ());
     761             :             else
     762          12 :                 poLS->addPoint(dfNewX, dfNewY);
     763          13 :             poLS->addPoint(&p);
     764          13 :             AppendString(ppszText, pnLength, pnMaxLength,
     765             :                          "><gml:segments><gml:Circle>");
     766          13 :             AppendGML3CoordinateList(poLS, bCoordSwap, ppszText, pnLength,
     767             :                                      pnMaxLength, nSRSDimensionLocFlags,
     768             :                                      coordOpts);
     769          13 :             AppendString(ppszText, pnLength, pnMaxLength,
     770             :                          "</gml:Circle></gml:segments></gml:Curve>");
     771          13 :             delete poLS;
     772             :         }
     773             :         else
     774             :         {
     775          11 :             AppendString(ppszText, pnLength, pnMaxLength,
     776             :                          "><gml:segments><gml:ArcString>");
     777          11 :             AppendGML3CoordinateList(poSC, bCoordSwap, ppszText, pnLength,
     778             :                                      pnMaxLength, nSRSDimensionLocFlags,
     779             :                                      coordOpts);
     780          11 :             AppendString(ppszText, pnLength, pnMaxLength,
     781             :                          "</gml:ArcString></gml:segments></gml:Curve>");
     782             :         }
     783             :     }
     784             : 
     785             :     /* -------------------------------------------------------------------- */
     786             :     /*      CompositeCurve                                                  */
     787             :     /* -------------------------------------------------------------------- */
     788         227 :     else if (eFType == wkbCompoundCurve)
     789             :     {
     790           4 :         AppendString(ppszText, pnLength, pnMaxLength, "<gml:CompositeCurve");
     791           4 :         AppendString(ppszText, pnLength, pnMaxLength, osAttributes.c_str());
     792           4 :         AppendString(ppszText, pnLength, pnMaxLength, ">");
     793             : 
     794           4 :         const auto poCC = poGeometry->toCompoundCurve();
     795           8 :         for (int i = 0; i < poCC->getNumCurves(); i++)
     796             :         {
     797           4 :             AppendString(ppszText, pnLength, pnMaxLength, "<gml:curveMember>");
     798             : 
     799           4 :             char *pszGMLIdSub = nullptr;
     800           4 :             if (pszGMLId != nullptr)
     801           0 :                 pszGMLIdSub = CPLStrdup(CPLSPrintf("%s.%d", pszGMLId, i));
     802             : 
     803           4 :             CPL_IGNORE_RET_VAL(OGR2GML3GeometryAppend(
     804           4 :                 poCC->getCurve(i), poSRS, ppszText, pnLength, pnMaxLength, true,
     805             :                 eSRSNameFormat, bCoordSwap, bLineStringAsCurve, pszGMLIdSub,
     806             :                 nSRSDimensionLocFlags, false, nullptr, nullptr, coordOpts));
     807             : 
     808           4 :             CPLFree(pszGMLIdSub);
     809             : 
     810           4 :             AppendString(ppszText, pnLength, pnMaxLength, "</gml:curveMember>");
     811             :         }
     812           4 :         AppendString(ppszText, pnLength, pnMaxLength, "</gml:CompositeCurve>");
     813             :     }
     814             : 
     815             :     /* -------------------------------------------------------------------- */
     816             :     /*      Polygon                                                         */
     817             :     /* -------------------------------------------------------------------- */
     818         223 :     else if (eFType == wkbPolygon || eFType == wkbCurvePolygon)
     819             :     {
     820         135 :         const auto poCP = poGeometry->toCurvePolygon();
     821             : 
     822             :         // Buffer for polygon tag name + srsName attribute if set.
     823         135 :         const char *pszElemName =
     824         135 :             pszOverriddenElementName ? pszOverriddenElementName : "Polygon";
     825         135 :         const size_t nPolyTagLength = 7 + strlen(pszElemName);
     826             :         const size_t nPolyTagNameBufLen =
     827         135 :             nPolyTagLength + osAttributes.size() + 1;
     828             :         char *pszPolyTagName =
     829         135 :             static_cast<char *>(CPLMalloc(nPolyTagNameBufLen));
     830             : 
     831             :         // Compose Polygon tag with or without srsName attribute.
     832         135 :         snprintf(pszPolyTagName, nPolyTagNameBufLen, "<gml:%s%s>", pszElemName,
     833             :                  osAttributes.c_str());
     834             : 
     835         135 :         AppendString(ppszText, pnLength, pnMaxLength, pszPolyTagName);
     836             : 
     837             :         // Free tag buffer.
     838         135 :         CPLFree(pszPolyTagName);
     839             : 
     840             :         const auto AppendCompoundCurveMembers =
     841         139 :             [&](const OGRGeometry *poRing, const char *pszGMLIdRing)
     842             :         {
     843         139 :             const auto eRingType = wkbFlatten(poRing->getGeometryType());
     844         139 :             if (eRingType == wkbCompoundCurve)
     845             :             {
     846           2 :                 AppendString(ppszText, pnLength, pnMaxLength, "<gml:Ring>");
     847           2 :                 const auto poCC = poRing->toCompoundCurve();
     848           2 :                 const int nNumCurves = poCC->getNumCurves();
     849           6 :                 for (int i = 0; i < nNumCurves; i++)
     850             :                 {
     851           4 :                     AppendString(ppszText, pnLength, pnMaxLength,
     852             :                                  "<gml:curveMember>");
     853             : 
     854           4 :                     char *pszGMLIdSub = nullptr;
     855           4 :                     if (pszGMLIdRing != nullptr)
     856             :                         pszGMLIdSub =
     857           2 :                             CPLStrdup(CPLSPrintf("%s.%d", pszGMLIdRing, i));
     858             : 
     859           4 :                     CPL_IGNORE_RET_VAL(OGR2GML3GeometryAppend(
     860           4 :                         poCC->getCurve(i), poSRS, ppszText, pnLength,
     861         141 :                         pnMaxLength, true, eSRSNameFormat, bCoordSwap,
     862         141 :                         bLineStringAsCurve, pszGMLIdSub, nSRSDimensionLocFlags,
     863           4 :                         false, nullptr, nullptr, coordOpts));
     864             : 
     865           4 :                     CPLFree(pszGMLIdSub);
     866             : 
     867           4 :                     AppendString(ppszText, pnLength, pnMaxLength,
     868             :                                  "</gml:curveMember>");
     869             :                 }
     870           2 :                 AppendString(ppszText, pnLength, pnMaxLength, "</gml:Ring>");
     871             :             }
     872             :             else
     873             :             {
     874         137 :                 if (eRingType != wkbLineString)
     875             :                 {
     876          11 :                     AppendString(ppszText, pnLength, pnMaxLength,
     877             :                                  "<gml:Ring><gml:curveMember>");
     878             :                 }
     879             : 
     880         137 :                 CPL_IGNORE_RET_VAL(OGR2GML3GeometryAppend(
     881             :                     poRing, poSRS, ppszText, pnLength, pnMaxLength, true,
     882         137 :                     eSRSNameFormat, bCoordSwap, bLineStringAsCurve,
     883             :                     pszGMLIdRing, nSRSDimensionLocFlags, true, nullptr, nullptr,
     884             :                     coordOpts));
     885             : 
     886         137 :                 if (eRingType != wkbLineString)
     887             :                 {
     888          11 :                     AppendString(ppszText, pnLength, pnMaxLength,
     889             :                                  "</gml:curveMember></gml:Ring>");
     890             :                 }
     891             :             }
     892         274 :         };
     893             : 
     894             :         // Don't add srsName to polygon rings.
     895             : 
     896         135 :         const auto poExteriorRing = poCP->getExteriorRingCurve();
     897         135 :         if (poExteriorRing != nullptr)
     898             :         {
     899         135 :             AppendString(ppszText, pnLength, pnMaxLength, "<gml:exterior>");
     900             : 
     901         209 :             AppendCompoundCurveMembers(
     902             :                 poExteriorRing,
     903         209 :                 pszGMLId ? (std::string(pszGMLId) + ".exterior").c_str()
     904             :                          : nullptr);
     905             : 
     906         135 :             AppendString(ppszText, pnLength, pnMaxLength, "</gml:exterior>");
     907             : 
     908         139 :             for (int iRing = 0; iRing < poCP->getNumInteriorRings(); iRing++)
     909             :             {
     910           4 :                 const OGRCurve *poRing = poCP->getInteriorRingCurve(iRing);
     911             : 
     912           4 :                 AppendString(ppszText, pnLength, pnMaxLength, "<gml:interior>");
     913             : 
     914           5 :                 AppendCompoundCurveMembers(
     915           5 :                     poRing, pszGMLId ? (std::string(pszGMLId) + ".interior." +
     916           5 :                                         std::to_string(iRing))
     917           1 :                                            .c_str()
     918             :                                      : nullptr);
     919             : 
     920           4 :                 AppendString(ppszText, pnLength, pnMaxLength,
     921             :                              "</gml:interior>");
     922             :             }
     923             :         }
     924             : 
     925         135 :         AppendString(ppszText, pnLength, pnMaxLength, "</gml:");
     926         135 :         AppendString(ppszText, pnLength, pnMaxLength, pszElemName);
     927         135 :         AppendString(ppszText, pnLength, pnMaxLength, ">");
     928             :     }
     929             : 
     930             :     /* -------------------------------------------------------------------- */
     931             :     /*     Triangle                                                         */
     932             :     /* -------------------------------------------------------------------- */
     933          88 :     else if (eFType == wkbTriangle)
     934             :     {
     935           3 :         const auto poTri = poGeometry->toPolygon();
     936             : 
     937           3 :         AppendString(ppszText, pnLength, pnMaxLength, "<gml:Triangle>");
     938             : 
     939           3 :         if (poTri->getExteriorRingCurve() != nullptr)
     940             :         {
     941           3 :             AppendString(ppszText, pnLength, pnMaxLength, "<gml:exterior>");
     942             : 
     943           3 :             CPL_IGNORE_RET_VAL(OGR2GML3GeometryAppend(
     944           3 :                 poTri->getExteriorRingCurve(), poSRS, ppszText, pnLength,
     945             :                 pnMaxLength, true, eSRSNameFormat, bCoordSwap,
     946             :                 bLineStringAsCurve, nullptr, nSRSDimensionLocFlags, true,
     947             :                 nullptr, nullptr, coordOpts));
     948             : 
     949           3 :             AppendString(ppszText, pnLength, pnMaxLength, "</gml:exterior>");
     950             :         }
     951             : 
     952           3 :         AppendString(ppszText, pnLength, pnMaxLength, "</gml:Triangle>");
     953             :     }
     954             : 
     955             :     /* -------------------------------------------------------------------- */
     956             :     /*      MultiSurface, MultiCurve, MultiPoint, MultiGeometry             */
     957             :     /* -------------------------------------------------------------------- */
     958          85 :     else if (eFType == wkbMultiPolygon || eFType == wkbMultiSurface ||
     959          31 :              eFType == wkbMultiLineString || eFType == wkbMultiCurve ||
     960          15 :              eFType == wkbMultiPoint || eFType == wkbGeometryCollection)
     961             :     {
     962          82 :         const auto poGC = poGeometry->toGeometryCollection();
     963          82 :         const char *pszElemClose = nullptr;
     964          82 :         const char *pszMemberElem = nullptr;
     965             : 
     966             :         // Buffer for opening tag + srsName attribute.
     967          82 :         char *pszElemOpen = nullptr;
     968             : 
     969          82 :         if (eFType == wkbMultiPolygon || eFType == wkbMultiSurface)
     970             :         {
     971          31 :             const size_t nBufLen = 13 + osAttributes.size() + 1;
     972          31 :             pszElemOpen = static_cast<char *>(CPLMalloc(nBufLen));
     973          31 :             snprintf(pszElemOpen, nBufLen, "MultiSurface%s>",
     974             :                      osAttributes.c_str());
     975             : 
     976          31 :             pszElemClose = "MultiSurface>";
     977          31 :             pszMemberElem = "surfaceMember>";
     978             :         }
     979          51 :         else if (eFType == wkbMultiLineString || eFType == wkbMultiCurve)
     980             :         {
     981          27 :             const size_t nBufLen = 16 + osAttributes.size() + 1;
     982          27 :             pszElemOpen = static_cast<char *>(CPLMalloc(nBufLen));
     983          27 :             snprintf(pszElemOpen, nBufLen, "MultiCurve%s>",
     984             :                      osAttributes.c_str());
     985             : 
     986          27 :             pszElemClose = "MultiCurve>";
     987          27 :             pszMemberElem = "curveMember>";
     988             :         }
     989          24 :         else if (eFType == wkbMultiPoint)
     990             :         {
     991          12 :             const size_t nBufLen = 11 + osAttributes.size() + 1;
     992          12 :             pszElemOpen = static_cast<char *>(CPLMalloc(nBufLen));
     993          12 :             snprintf(pszElemOpen, nBufLen, "MultiPoint%s>",
     994             :                      osAttributes.c_str());
     995             : 
     996          12 :             pszElemClose = "MultiPoint>";
     997          12 :             pszMemberElem = "pointMember>";
     998             :         }
     999             :         else
    1000             :         {
    1001          12 :             const size_t nBufLen = 19 + osAttributes.size() + 1;
    1002          12 :             pszElemOpen = static_cast<char *>(CPLMalloc(nBufLen));
    1003          12 :             snprintf(pszElemOpen, nBufLen, "MultiGeometry%s>",
    1004             :                      osAttributes.c_str());
    1005             : 
    1006          12 :             pszElemClose = "MultiGeometry>";
    1007          12 :             pszMemberElem = "geometryMember>";
    1008             :         }
    1009             : 
    1010          82 :         AppendString(ppszText, pnLength, pnMaxLength, "<gml:");
    1011          82 :         AppendString(ppszText, pnLength, pnMaxLength, pszElemOpen);
    1012             : 
    1013             :         // Free tag buffer.
    1014          82 :         CPLFree(pszElemOpen);
    1015             : 
    1016         188 :         for (int iMember = 0; iMember < poGC->getNumGeometries(); iMember++)
    1017             :         {
    1018         106 :             const OGRGeometry *poMember = poGC->getGeometryRef(iMember);
    1019             : 
    1020         106 :             AppendString(ppszText, pnLength, pnMaxLength, "<gml:");
    1021         106 :             AppendString(ppszText, pnLength, pnMaxLength, pszMemberElem);
    1022             : 
    1023         106 :             char *pszGMLIdSub = nullptr;
    1024         106 :             if (pszGMLId != nullptr)
    1025          78 :                 pszGMLIdSub = CPLStrdup(CPLSPrintf("%s.%d", pszGMLId, iMember));
    1026             : 
    1027         106 :             if (!OGR2GML3GeometryAppend(
    1028             :                     poMember, poSRS, ppszText, pnLength, pnMaxLength, true,
    1029             :                     eSRSNameFormat, bCoordSwap, bLineStringAsCurve, pszGMLIdSub,
    1030             :                     nSRSDimensionLocFlags, false, nullptr, nullptr, coordOpts))
    1031             :             {
    1032           0 :                 CPLFree(pszGMLIdSub);
    1033           0 :                 return false;
    1034             :             }
    1035             : 
    1036         106 :             CPLFree(pszGMLIdSub);
    1037             : 
    1038         106 :             AppendString(ppszText, pnLength, pnMaxLength, "</gml:");
    1039         106 :             AppendString(ppszText, pnLength, pnMaxLength, pszMemberElem);
    1040             :         }
    1041             : 
    1042          82 :         AppendString(ppszText, pnLength, pnMaxLength, "</gml:");
    1043          82 :         AppendString(ppszText, pnLength, pnMaxLength, pszElemClose);
    1044             :     }
    1045             : 
    1046             :     /* -------------------------------------------------------------------- */
    1047             :     /*      Polyhedral Surface                                              */
    1048             :     /* -------------------------------------------------------------------- */
    1049           3 :     else if (eFType == wkbPolyhedralSurface)
    1050             :     {
    1051             :         // The patches enclosed in a single <gml:polygonPatches> tag need to be
    1052             :         // co-planar.
    1053             :         // TODO - enforce the condition within this implementation
    1054           2 :         const auto poPS = poGeometry->toPolyhedralSurface();
    1055             : 
    1056           2 :         AppendString(ppszText, pnLength, pnMaxLength, "<gml:PolyhedralSurface");
    1057           2 :         AppendString(ppszText, pnLength, pnMaxLength, osAttributes.c_str());
    1058           2 :         AppendString(ppszText, pnLength, pnMaxLength, "><gml:polygonPatches>");
    1059             : 
    1060           8 :         for (int iMember = 0; iMember < poPS->getNumGeometries(); iMember++)
    1061             :         {
    1062           6 :             const OGRGeometry *poMember = poPS->getGeometryRef(iMember);
    1063           6 :             char *pszGMLIdSub = nullptr;
    1064           6 :             if (pszGMLId != nullptr)
    1065           0 :                 pszGMLIdSub = CPLStrdup(CPLSPrintf("%s.%d", pszGMLId, iMember));
    1066             : 
    1067           6 :             if (!OGR2GML3GeometryAppend(poMember, poSRS, ppszText, pnLength,
    1068             :                                         pnMaxLength, true, eSRSNameFormat,
    1069             :                                         bCoordSwap, bLineStringAsCurve, nullptr,
    1070             :                                         nSRSDimensionLocFlags, false, nullptr,
    1071             :                                         "PolygonPatch", coordOpts))
    1072             :             {
    1073           0 :                 CPLFree(pszGMLIdSub);
    1074           0 :                 return false;
    1075             :             }
    1076             : 
    1077           6 :             CPLFree(pszGMLIdSub);
    1078             :         }
    1079             : 
    1080           2 :         AppendString(ppszText, pnLength, pnMaxLength, "</gml:polygonPatches>");
    1081           2 :         AppendString(ppszText, pnLength, pnMaxLength,
    1082             :                      "</gml:PolyhedralSurface>");
    1083             :     }
    1084             : 
    1085             :     /* -------------------------------------------------------------------- */
    1086             :     /*      TIN                                                             */
    1087             :     /* -------------------------------------------------------------------- */
    1088           1 :     else if (eFType == wkbTIN)
    1089             :     {
    1090             :         // OGR uses the following hierarchy for TriangulatedSurface -
    1091             : 
    1092             :         // <gml:TriangulatedSurface>
    1093             :         //     <gml:patches>
    1094             :         //         <gml:Triangle>
    1095             :         //             <gml:exterior>
    1096             :         //                 <gml:LinearRing>
    1097             :         //                     <gml:posList srsDimension=...>...</gml:posList>
    1098             :         //                 </gml:LinearRing>
    1099             :         //             </gml:exterior>
    1100             :         //         </gml:Triangle>
    1101             :         //     </gml:patches>
    1102             :         // </gml:TriangulatedSurface>
    1103             : 
    1104             :         // <gml:trianglePatches> is deprecated, so write feature is not enabled
    1105             :         // for <gml:trianglePatches>
    1106           1 :         const auto poTIN = poGeometry->toPolyhedralSurface();
    1107             : 
    1108           1 :         AppendString(ppszText, pnLength, pnMaxLength,
    1109             :                      "<gml:TriangulatedSurface");
    1110           1 :         AppendString(ppszText, pnLength, pnMaxLength, osAttributes.c_str());
    1111           1 :         AppendString(ppszText, pnLength, pnMaxLength, "><gml:patches>");
    1112             : 
    1113           3 :         for (int iMember = 0; iMember < poTIN->getNumGeometries(); iMember++)
    1114             :         {
    1115           2 :             const OGRGeometry *poMember = poTIN->getGeometryRef(iMember);
    1116             : 
    1117           2 :             char *pszGMLIdSub = nullptr;
    1118           2 :             if (pszGMLId != nullptr)
    1119           0 :                 pszGMLIdSub = CPLStrdup(CPLSPrintf("%s.%d", pszGMLId, iMember));
    1120             : 
    1121           2 :             CPL_IGNORE_RET_VAL(OGR2GML3GeometryAppend(
    1122             :                 poMember, poSRS, ppszText, pnLength, pnMaxLength, true,
    1123             :                 eSRSNameFormat, bCoordSwap, bLineStringAsCurve, nullptr,
    1124             :                 nSRSDimensionLocFlags, false, nullptr, nullptr, coordOpts));
    1125             : 
    1126           2 :             CPLFree(pszGMLIdSub);
    1127             :         }
    1128             : 
    1129           1 :         AppendString(ppszText, pnLength, pnMaxLength, "</gml:patches>");
    1130           1 :         AppendString(ppszText, pnLength, pnMaxLength,
    1131             :                      "</gml:TriangulatedSurface>");
    1132             :     }
    1133             : 
    1134             :     else
    1135             :     {
    1136           0 :         CPLError(CE_Failure, CPLE_NotSupported, "Unsupported geometry type %s",
    1137             :                  OGRGeometryTypeToName(eType));
    1138           0 :         return false;
    1139             :     }
    1140             : 
    1141         576 :     return true;
    1142             : }
    1143             : 
    1144             : /************************************************************************/
    1145             : /*                       OGR_G_ExportToGMLTree()                        */
    1146             : /************************************************************************/
    1147             : 
    1148             : /** Convert a geometry into GML format. */
    1149           0 : CPLXMLNode *OGR_G_ExportToGMLTree(OGRGeometryH hGeometry)
    1150             : 
    1151             : {
    1152           0 :     char *pszText = OGR_G_ExportToGML(hGeometry);
    1153           0 :     if (pszText == nullptr)
    1154           0 :         return nullptr;
    1155             : 
    1156           0 :     CPLXMLNode *psTree = CPLParseXMLString(pszText);
    1157             : 
    1158           0 :     CPLFree(pszText);
    1159             : 
    1160           0 :     return psTree;
    1161             : }
    1162             : 
    1163             : /************************************************************************/
    1164             : /*                         OGR_G_ExportToGML()                          */
    1165             : /************************************************************************/
    1166             : 
    1167             : /**
    1168             :  * \brief Convert a geometry into GML format.
    1169             :  *
    1170             :  * The GML geometry is expressed directly in terms of GML basic data
    1171             :  * types assuming the this is available in the gml namespace.  The returned
    1172             :  * string should be freed with CPLFree() when no longer required.
    1173             :  *
    1174             :  * This method is the same as the C++ method OGRGeometry::exportToGML().
    1175             :  *
    1176             :  * @param hGeometry handle to the geometry.
    1177             :  * @return A GML fragment or NULL in case of error.
    1178             :  */
    1179             : 
    1180           1 : char *OGR_G_ExportToGML(OGRGeometryH hGeometry)
    1181             : 
    1182             : {
    1183           1 :     return OGR_G_ExportToGMLEx(hGeometry, nullptr);
    1184             : }
    1185             : 
    1186             : /************************************************************************/
    1187             : /*                        OGR_G_ExportToGMLEx()                         */
    1188             : /************************************************************************/
    1189             : 
    1190             : /**
    1191             :  * \brief Convert a geometry into GML format.
    1192             :  *
    1193             :  * The GML geometry is expressed directly in terms of GML basic data
    1194             :  * types assuming the this is available in the gml namespace.  The returned
    1195             :  * string should be freed with CPLFree() when no longer required.
    1196             :  *
    1197             :  * The supported options are :
    1198             :  * <ul>
    1199             :  * <li> FORMAT=GML2/GML3/GML32
    1200             :  *      If not set, it will default to GML 2.1.2 output.
    1201             :  * </li>
    1202             :  * <li> GML3_LINESTRING_ELEMENT=curve. (Only valid for FORMAT=GML3)
    1203             :  *      To use gml:Curve element for linestrings.
    1204             :  *      Otherwise gml:LineString will be used .
    1205             :  * </li>
    1206             :  * <li> GML3_LONGSRS=YES/NO. (Only valid for FORMAT=GML3, deprecated by
    1207             :  *      SRSNAME_FORMAT in GDAL &gt;=2.2). Defaults to YES.
    1208             :  *      If YES, SRS with EPSG authority will be written with the
    1209             :  *      "urn:ogc:def:crs:EPSG::" prefix.
    1210             :  *      In the case the SRS should be treated as lat/long or
    1211             :  *      northing/easting, then the function will take care of coordinate order
    1212             :  *      swapping if the data axis to CRS axis mapping indicates it.
    1213             :  *      If set to NO, SRS with EPSG authority will be written with the "EPSG:"
    1214             :  *      prefix, even if they are in lat/long order.
    1215             :  * </li>
    1216             :  * <li> SRSNAME_FORMAT=SHORT/OGC_URN/OGC_URL (Only valid for FORMAT=GML3).
    1217             :  *      Defaults to OGC_URN.  If SHORT, then srsName will be in
    1218             :  *      the form AUTHORITY_NAME:AUTHORITY_CODE. If OGC_URN, then srsName will be
    1219             :  *      in the form urn:ogc:def:crs:AUTHORITY_NAME::AUTHORITY_CODE. If OGC_URL,
    1220             :  *      then srsName will be in the form
    1221             :  *      http://www.opengis.net/def/crs/AUTHORITY_NAME/0/AUTHORITY_CODE. For
    1222             :  *      OGC_URN and OGC_URL, in the case the SRS should be treated as lat/long
    1223             :  *      or northing/easting, then the function will take care of coordinate
    1224             :  *      order swapping if the data axis to CRS axis mapping indicates it.
    1225             :  * </li>
    1226             :  * <li> GMLID=astring. If specified, a gml:id attribute will be written in the
    1227             :  *      top-level geometry element with the provided value.
    1228             :  *      Required for GML 3.2 compatibility.
    1229             :  * </li>
    1230             :  * <li> SRSDIMENSION_LOC=POSLIST/GEOMETRY/GEOMETRY,POSLIST. (Only valid for
    1231             :  *      FORMAT=GML3/GML32) Default to POSLIST.
    1232             :  *      For 2.5D geometries, define the location where to attach the
    1233             :  *      srsDimension attribute.
    1234             :  *      There are diverging implementations. Some put in on the
    1235             :  *      &lt;gml:posList&gt; element, other on the top geometry element.
    1236             :  * </li>
    1237             :  * <li> NAMESPACE_DECL=YES/NO. If set to YES,
    1238             :  *      xmlns:gml="http://www.opengis.net/gml" will be added to the root node
    1239             :  *      for GML < 3.2 or xmlns:gml="http://www.opengis.net/gml/3.2" for GML 3.2
    1240             :  * </li>
    1241             :  * <li> XY_COORD_RESOLUTION=double (added in GDAL 3.9):
    1242             :  *      Resolution for the coordinate precision of the X and Y coordinates.
    1243             :  *      Expressed in the units of the X and Y axis of the SRS. eg 1e-5 for up
    1244             :  *      to 5 decimal digits. 0 for the default behavior.
    1245             :  * </li>
    1246             :  * <li> Z_COORD_RESOLUTION=double (added in GDAL 3.9):
    1247             :  *      Resolution for the coordinate precision of the Z coordinates.
    1248             :  *      Expressed in the units of the Z axis of the SRS.
    1249             :  *      0 for the default behavior.
    1250             :  * </li>
    1251             :  * </ul>
    1252             :  *
    1253             :  * Note that curve geometries like CIRCULARSTRING, COMPOUNDCURVE, CURVEPOLYGON,
    1254             :  * MULTICURVE or MULTISURFACE are not supported in GML 2.
    1255             :  *
    1256             :  * This method is the same as the C++ method OGRGeometry::exportToGML().
    1257             :  *
    1258             :  * @param hGeometry handle to the geometry.
    1259             :  * @param papszOptions NULL-terminated list of options.
    1260             :  * @return A GML fragment or NULL in case of error.
    1261             :  *
    1262             :  */
    1263             : 
    1264         410 : char *OGR_G_ExportToGMLEx(OGRGeometryH hGeometry, CSLConstList papszOptions)
    1265             : 
    1266             : {
    1267         410 :     if (hGeometry == nullptr)
    1268           0 :         return CPLStrdup("");
    1269             : 
    1270             :     // Do not use hGeometry after here.
    1271         410 :     OGRGeometry *poGeometry = OGRGeometry::FromHandle(hGeometry);
    1272             : 
    1273         410 :     OGRWktOptions coordOpts;
    1274             : 
    1275             :     const char *pszXYCoordRes =
    1276         410 :         CSLFetchNameValue(papszOptions, "XY_COORD_RESOLUTION");
    1277         410 :     if (pszXYCoordRes)
    1278             :     {
    1279           3 :         coordOpts.format = OGRWktFormat::F;
    1280           3 :         coordOpts.xyPrecision =
    1281           3 :             OGRGeomCoordinatePrecision::ResolutionToPrecision(
    1282             :                 CPLAtof(pszXYCoordRes));
    1283             :     }
    1284             : 
    1285             :     const char *pszZCoordRes =
    1286         410 :         CSLFetchNameValue(papszOptions, "Z_COORD_RESOLUTION");
    1287         410 :     if (pszZCoordRes)
    1288             :     {
    1289           3 :         coordOpts.format = OGRWktFormat::F;
    1290           3 :         coordOpts.zPrecision =
    1291           3 :             OGRGeomCoordinatePrecision::ResolutionToPrecision(
    1292             :                 CPLAtof(pszZCoordRes));
    1293             :     }
    1294             : 
    1295         410 :     size_t nLength = 0;
    1296         410 :     size_t nMaxLength = 1;
    1297             : 
    1298         410 :     char *pszText = static_cast<char *>(CPLMalloc(nMaxLength));
    1299         410 :     pszText[0] = '\0';
    1300             : 
    1301         410 :     const char *pszFormat = CSLFetchNameValue(papszOptions, "FORMAT");
    1302             :     const bool bNamespaceDecl =
    1303         410 :         CPLTestBool(CSLFetchNameValueDef(papszOptions, "NAMESPACE_DECL",
    1304         410 :                                          "NO")) != FALSE;
    1305         410 :     if (pszFormat && (EQUAL(pszFormat, "GML3") || EQUAL(pszFormat, "GML32")))
    1306             :     {
    1307             :         const char *pszLineStringElement =
    1308         314 :             CSLFetchNameValue(papszOptions, "GML3_LINESTRING_ELEMENT");
    1309         314 :         const bool bLineStringAsCurve =
    1310         314 :             pszLineStringElement && EQUAL(pszLineStringElement, "curve");
    1311             :         const char *pszLongSRS =
    1312         314 :             CSLFetchNameValue(papszOptions, "GML3_LONGSRS");
    1313             :         const char *pszSRSNameFormat =
    1314         314 :             CSLFetchNameValue(papszOptions, "SRSNAME_FORMAT");
    1315         314 :         OGRGMLSRSNameFormat eSRSNameFormat = SRSNAME_OGC_URN;
    1316         314 :         if (pszSRSNameFormat)
    1317             :         {
    1318         259 :             if (pszLongSRS)
    1319             :             {
    1320           0 :                 CPLError(CE_Warning, CPLE_NotSupported,
    1321             :                          "Both GML3_LONGSRS and SRSNAME_FORMAT specified. "
    1322             :                          "Ignoring GML3_LONGSRS");
    1323             :             }
    1324         259 :             if (EQUAL(pszSRSNameFormat, "SHORT"))
    1325           2 :                 eSRSNameFormat = SRSNAME_SHORT;
    1326         257 :             else if (EQUAL(pszSRSNameFormat, "OGC_URN"))
    1327         183 :                 eSRSNameFormat = SRSNAME_OGC_URN;
    1328          74 :             else if (EQUAL(pszSRSNameFormat, "OGC_URL"))
    1329          74 :                 eSRSNameFormat = SRSNAME_OGC_URL;
    1330             :             else
    1331             :             {
    1332           0 :                 CPLError(CE_Warning, CPLE_NotSupported,
    1333             :                          "Invalid value for SRSNAME_FORMAT. "
    1334             :                          "Using SRSNAME_OGC_URN");
    1335             :             }
    1336             :         }
    1337          55 :         else if (pszLongSRS && !CPLTestBool(pszLongSRS))
    1338           5 :             eSRSNameFormat = SRSNAME_SHORT;
    1339             : 
    1340         314 :         const char *pszGMLId = CSLFetchNameValue(papszOptions, "GMLID");
    1341         314 :         if (pszGMLId == nullptr && EQUAL(pszFormat, "GML32"))
    1342           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    1343             :                      "FORMAT=GML32 specified but not GMLID set");
    1344             :         const char *pszSRSDimensionLoc =
    1345         314 :             CSLFetchNameValueDef(papszOptions, "SRSDIMENSION_LOC", "POSLIST");
    1346             :         char **papszSRSDimensionLoc =
    1347         314 :             CSLTokenizeString2(pszSRSDimensionLoc, ",", 0);
    1348         314 :         int nSRSDimensionLocFlags = 0;
    1349         629 :         for (int i = 0; papszSRSDimensionLoc[i] != nullptr; i++)
    1350             :         {
    1351         315 :             if (EQUAL(papszSRSDimensionLoc[i], "POSLIST"))
    1352         313 :                 nSRSDimensionLocFlags |= SRSDIM_LOC_POSLIST;
    1353           2 :             else if (EQUAL(papszSRSDimensionLoc[i], "GEOMETRY"))
    1354           2 :                 nSRSDimensionLocFlags |= SRSDIM_LOC_GEOMETRY;
    1355             :             else
    1356           0 :                 CPLDebug("OGR", "Unrecognized location for srsDimension : %s",
    1357           0 :                          papszSRSDimensionLoc[i]);
    1358             :         }
    1359         314 :         CSLDestroy(papszSRSDimensionLoc);
    1360         314 :         const char *pszNamespaceDecl = nullptr;
    1361         314 :         if (bNamespaceDecl && EQUAL(pszFormat, "GML32"))
    1362           1 :             pszNamespaceDecl = "http://www.opengis.net/gml/3.2";
    1363         313 :         else if (bNamespaceDecl)
    1364           1 :             pszNamespaceDecl = "http://www.opengis.net/gml";
    1365             : 
    1366         314 :         bool bCoordSwap = false;
    1367             :         const char *pszCoordSwap =
    1368         314 :             CSLFetchNameValue(papszOptions, "COORD_SWAP");
    1369         314 :         if (pszCoordSwap)
    1370             :         {
    1371           0 :             bCoordSwap = CPLTestBool(pszCoordSwap);
    1372             :         }
    1373             :         else
    1374             :         {
    1375             :             const OGRSpatialReference *poSRS =
    1376         314 :                 poGeometry->getSpatialReference();
    1377         314 :             if (poSRS != nullptr && eSRSNameFormat != SRSNAME_SHORT)
    1378             :             {
    1379         108 :                 const auto &map = poSRS->GetDataAxisToSRSAxisMapping();
    1380         108 :                 if (map.size() >= 2 && map[0] == 2 && map[1] == 1)
    1381             :                 {
    1382          45 :                     bCoordSwap = true;
    1383             :                 }
    1384             :             }
    1385             :         }
    1386             : 
    1387         314 :         if (!OGR2GML3GeometryAppend(poGeometry, nullptr, &pszText, &nLength,
    1388             :                                     &nMaxLength, false, eSRSNameFormat,
    1389             :                                     bCoordSwap, bLineStringAsCurve, pszGMLId,
    1390             :                                     nSRSDimensionLocFlags, false,
    1391             :                                     pszNamespaceDecl, nullptr, coordOpts))
    1392             :         {
    1393           0 :             CPLFree(pszText);
    1394           0 :             return nullptr;
    1395             :         }
    1396             : 
    1397         314 :         return pszText;
    1398             :     }
    1399             : 
    1400          96 :     const char *pszNamespaceDecl = nullptr;
    1401          96 :     if (bNamespaceDecl)
    1402           1 :         pszNamespaceDecl = "http://www.opengis.net/gml";
    1403          96 :     if (!OGR2GMLGeometryAppend(poGeometry, &pszText, &nLength, &nMaxLength,
    1404             :                                false, pszNamespaceDecl, coordOpts))
    1405             :     {
    1406           1 :         CPLFree(pszText);
    1407           1 :         return nullptr;
    1408             :     }
    1409             : 
    1410          95 :     return pszText;
    1411             : }

Generated by: LCOV version 1.14