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

Generated by: LCOV version 1.14