Coverage Report

Created: 2025-12-31 06:48

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