Coverage Report

Created: 2025-06-13 06:29

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