Coverage Report

Created: 2025-12-31 06:48

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/ogr/ogrsf_frmts/gmlutils/gmlutils.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  GML Utils
4
 * Purpose:  GML reader
5
 * Author:   Even Rouault, <even dot rouault at spatialys.com>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2010-2013, Even Rouault <even dot rouault at spatialys.com>
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#include "cpl_port.h"
14
#include "gmlutils.h"
15
16
#include <cstdio>
17
#include <cstdlib>
18
#include <cstring>
19
#include <map>
20
#include <string>
21
#include <utility>
22
23
#include "cpl_conv.h"
24
#include "cpl_mem_cache.h"
25
#include "cpl_string.h"
26
#include "ogr_api.h"
27
#include "ogr_core.h"
28
#include "ogr_p.h"
29
#include "ogr_spatialref.h"
30
31
/************************************************************************/
32
/*                GML_ExtractSrsNameFromGeometry()                      */
33
/************************************************************************/
34
35
const char *
36
GML_ExtractSrsNameFromGeometry(const CPLXMLNode *const *papsGeometry,
37
                               std::string &osWork, bool bConsiderEPSGAsURN)
38
0
{
39
0
    if (papsGeometry[0] != nullptr && papsGeometry[1] == nullptr)
40
0
    {
41
0
        const char *pszSRSName =
42
0
            CPLGetXMLValue(papsGeometry[0], "srsName", nullptr);
43
0
        if (pszSRSName)
44
0
        {
45
0
            const int nLen = static_cast<int>(strlen(pszSRSName));
46
47
0
            if (STARTS_WITH(pszSRSName, "EPSG:") && bConsiderEPSGAsURN)
48
0
            {
49
0
                osWork.reserve(22 + nLen - 5);
50
0
                osWork.assign("urn:ogc:def:crs:EPSG::", 22);
51
0
                osWork.append(pszSRSName + 5, nLen - 5);
52
0
                return osWork.c_str();
53
0
            }
54
0
            else if (STARTS_WITH(pszSRSName,
55
0
                                 "http://www.opengis.net/gml/srs/epsg.xml#"))
56
0
            {
57
0
                osWork.reserve(5 + nLen - 40);
58
0
                osWork.assign("EPSG:", 5);
59
0
                osWork.append(pszSRSName + 40, nLen - 40);
60
0
                return osWork.c_str();
61
0
            }
62
0
            else
63
0
            {
64
0
                return pszSRSName;
65
0
            }
66
0
        }
67
0
    }
68
0
    return nullptr;
69
0
}
70
71
/************************************************************************/
72
/*                       GML_IsSRSLatLongOrder()                        */
73
/************************************************************************/
74
75
bool GML_IsSRSLatLongOrder(const char *pszSRSName)
76
0
{
77
0
    if (pszSRSName == nullptr)
78
0
        return false;
79
80
0
    if (STARTS_WITH(pszSRSName, "urn:") &&
81
0
        strstr(pszSRSName, ":4326") != nullptr)
82
0
    {
83
        // Shortcut.
84
0
        return true;
85
0
    }
86
0
    else if (!EQUALN(pszSRSName, "EPSG:", 5))
87
0
    {
88
0
        OGRSpatialReference oSRS;
89
0
        if (oSRS.SetFromUserInput(
90
0
                pszSRSName,
91
0
                OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS_get()) ==
92
0
            OGRERR_NONE)
93
0
        {
94
0
            if (oSRS.EPSGTreatsAsLatLong() ||
95
0
                oSRS.EPSGTreatsAsNorthingEasting())
96
0
                return true;
97
0
        }
98
0
    }
99
0
    return false;
100
0
}
101
102
/************************************************************************/
103
/*              OGRGML_SRSCacheEntry::~OGRGML_SRSCacheEntry()           */
104
/************************************************************************/
105
106
OGRGML_SRSCacheEntry::~OGRGML_SRSCacheEntry()
107
0
{
108
0
    if (poSRS)
109
0
        poSRS->Release();
110
0
}
111
112
/************************************************************************/
113
/*                             OGRGML_SRSCache                          */
114
/************************************************************************/
115
116
class OGRGML_SRSCache
117
{
118
  public:
119
    lru11::Cache<std::string, std::shared_ptr<OGRGML_SRSCacheEntry>>
120
        oSRSCache{};
121
};
122
123
/************************************************************************/
124
/*                        OGRGML_SRSCache_Create()                      */
125
/************************************************************************/
126
127
OGRGML_SRSCache *OGRGML_SRSCache_Create()
128
0
{
129
0
    return new OGRGML_SRSCache();
130
0
}
131
132
/************************************************************************/
133
/*                        OGRGML_SRSCache_Destroy()                     */
134
/************************************************************************/
135
136
void OGRGML_SRSCache_Destroy(OGRGML_SRSCache *hSRSCache)
137
0
{
138
0
    delete hSRSCache;
139
0
}
140
141
/************************************************************************/
142
/*                         OGRGML_SRSCache_GetInfo()                    */
143
/************************************************************************/
144
145
std::shared_ptr<OGRGML_SRSCacheEntry>
146
OGRGML_SRSCache_GetInfo(OGRGML_SRSCache *hSRSCache, const char *pszSRSName)
147
0
{
148
0
    std::shared_ptr<OGRGML_SRSCacheEntry> entry;
149
0
    if (!hSRSCache->oSRSCache.tryGet(pszSRSName, entry))
150
0
    {
151
0
        entry = std::make_shared<OGRGML_SRSCacheEntry>();
152
0
        auto poSRS = new OGRSpatialReference();
153
0
        entry->poSRS = poSRS;
154
0
        poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
155
0
        if (poSRS->SetFromUserInput(
156
0
                pszSRSName,
157
0
                OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS_get()) !=
158
0
            OGRERR_NONE)
159
0
        {
160
0
            return nullptr;
161
0
        }
162
0
        entry->nAxisCount = poSRS->GetAxesCount();
163
0
        entry->bIsGeographic = poSRS->IsGeographic();
164
0
        entry->bIsProjected = poSRS->IsProjected();
165
0
        entry->bInvertedAxisOrder = !STARTS_WITH(pszSRSName, "EPSG:") &&
166
0
                                    (poSRS->EPSGTreatsAsLatLong() ||
167
0
                                     poSRS->EPSGTreatsAsNorthingEasting());
168
0
        entry->dfSemiMajor = poSRS->GetSemiMajor();
169
0
        if (entry->bIsProjected)
170
0
            entry->dfLinearUnits = poSRS->GetLinearUnits(nullptr);
171
0
        if (entry->bIsGeographic)
172
0
        {
173
0
            entry->bAngularUnitIsDegree =
174
0
                fabs(poSRS->GetAngularUnits(nullptr) -
175
0
                     CPLAtof(SRS_UA_DEGREE_CONV)) < 1e-8;
176
0
        }
177
0
        hSRSCache->oSRSCache.insert(pszSRSName, entry);
178
0
    }
179
0
    return entry;
180
0
}
181
182
/************************************************************************/
183
/*                 GML_BuildOGRGeometryFromList()                       */
184
/************************************************************************/
185
186
OGRGeometry *GML_BuildOGRGeometryFromList(
187
    const CPLXMLNode *const *papsGeometry, bool bTryToMakeMultipolygons,
188
    bool bInvertAxisOrderIfLatLong, const char *pszDefaultSRSName,
189
    bool bConsiderEPSGAsURN, GMLSwapCoordinatesEnum eSwapCoordinates,
190
    int nPseudoBoolGetSecondaryGeometryOption, OGRGML_SRSCache *hSRSCache,
191
    bool bFaceHoleNegative)
192
0
{
193
0
    OGRGeometry *poGeom = nullptr;
194
0
    OGRGeometryCollection *poCollection = nullptr;
195
0
#ifndef WITHOUT_CPLDEBUG
196
0
    static const bool bDebugGML =
197
0
        EQUAL(CPLGetConfigOption("CPL_DEBUG", ""), "GML");
198
0
#endif
199
0
    for (int i = 0; papsGeometry[i] != nullptr; i++)
200
0
    {
201
0
#ifndef WITHOUT_CPLDEBUG
202
0
        if (bDebugGML)
203
0
        {
204
0
            char *pszXML = CPLSerializeXMLTree(papsGeometry[i]);
205
0
            CPLDebug("GML", "Parsing: %s", pszXML);
206
0
            CPLFree(pszXML);
207
0
        }
208
0
#endif
209
0
        OGRGeometry *poSubGeom = GML2OGRGeometry_XMLNode(
210
0
            papsGeometry[i], nPseudoBoolGetSecondaryGeometryOption, hSRSCache,
211
0
            0, 0, false, true, bFaceHoleNegative);
212
0
        if (poSubGeom)
213
0
        {
214
0
            if (poGeom == nullptr)
215
0
            {
216
0
                poGeom = poSubGeom;
217
0
            }
218
0
            else
219
0
            {
220
0
                if (poCollection == nullptr)
221
0
                {
222
0
                    if (bTryToMakeMultipolygons &&
223
0
                        wkbFlatten(poGeom->getGeometryType()) == wkbPolygon &&
224
0
                        wkbFlatten(poSubGeom->getGeometryType()) == wkbPolygon)
225
0
                    {
226
0
                        OGRGeometryCollection *poGeomColl =
227
0
                            new OGRMultiPolygon();
228
0
                        poGeomColl->addGeometryDirectly(poGeom);
229
0
                        poGeomColl->addGeometryDirectly(poSubGeom);
230
0
                        poGeom = poGeomColl;
231
0
                    }
232
0
                    else if (bTryToMakeMultipolygons &&
233
0
                             wkbFlatten(poGeom->getGeometryType()) ==
234
0
                                 wkbMultiPolygon &&
235
0
                             wkbFlatten(poSubGeom->getGeometryType()) ==
236
0
                                 wkbPolygon)
237
0
                    {
238
0
                        OGRMultiPolygon *poGeomColl = poGeom->toMultiPolygon();
239
0
                        poGeomColl->addGeometryDirectly(poSubGeom);
240
0
                    }
241
0
                    else if (bTryToMakeMultipolygons &&
242
0
                             wkbFlatten(poGeom->getGeometryType()) ==
243
0
                                 wkbMultiPolygon &&
244
0
                             wkbFlatten(poSubGeom->getGeometryType()) ==
245
0
                                 wkbMultiPolygon)
246
0
                    {
247
0
                        OGRMultiPolygon *poGeomColl = poGeom->toMultiPolygon();
248
0
                        for (auto &&poMember : poSubGeom->toMultiPolygon())
249
0
                        {
250
0
                            poGeomColl->addGeometry(poMember);
251
0
                        }
252
0
                        delete poSubGeom;
253
0
                    }
254
0
                    else if (bTryToMakeMultipolygons &&
255
0
                             wkbFlatten(poGeom->getGeometryType()) ==
256
0
                                 wkbMultiPolygon)
257
0
                    {
258
0
                        delete poGeom;
259
0
                        delete poSubGeom;
260
0
                        return GML_BuildOGRGeometryFromList(
261
0
                            papsGeometry, false, bInvertAxisOrderIfLatLong,
262
0
                            pszDefaultSRSName, bConsiderEPSGAsURN,
263
0
                            eSwapCoordinates,
264
0
                            nPseudoBoolGetSecondaryGeometryOption, hSRSCache);
265
0
                    }
266
0
                    else
267
0
                    {
268
0
                        poCollection = new OGRGeometryCollection();
269
0
                        poCollection->addGeometryDirectly(poGeom);
270
0
                        poGeom = poCollection;
271
0
                    }
272
0
                }
273
0
                if (poCollection != nullptr)
274
0
                {
275
0
                    poCollection->addGeometryDirectly(poSubGeom);
276
0
                }
277
0
            }
278
0
        }
279
0
    }
280
281
0
    if (poGeom == nullptr)
282
0
        return nullptr;
283
284
0
    std::string osWork;
285
0
    const char *pszSRSName = GML_ExtractSrsNameFromGeometry(
286
0
        papsGeometry, osWork, bConsiderEPSGAsURN);
287
0
    const char *pszNameLookup = pszSRSName;
288
0
    if (pszNameLookup == nullptr)
289
0
        pszNameLookup = pszDefaultSRSName;
290
291
0
    if (pszNameLookup != nullptr)
292
0
    {
293
0
        auto entry = OGRGML_SRSCache_GetInfo(hSRSCache, pszNameLookup);
294
0
        if (entry)
295
0
        {
296
0
            poGeom->assignSpatialReference(entry->poSRS);
297
0
            if ((eSwapCoordinates == GML_SWAP_AUTO &&
298
0
                 entry->bInvertedAxisOrder && bInvertAxisOrderIfLatLong) ||
299
0
                eSwapCoordinates == GML_SWAP_YES)
300
0
            {
301
0
                poGeom->swapXY();
302
0
            }
303
0
        }
304
0
    }
305
0
    else if (eSwapCoordinates == GML_SWAP_YES)
306
0
    {
307
0
        poGeom->swapXY();
308
0
    }
309
310
0
    return poGeom;
311
0
}
312
313
/************************************************************************/
314
/*                           GML_GetSRSName()                           */
315
/************************************************************************/
316
317
char *GML_GetSRSName(const OGRSpatialReference *poSRS,
318
                     OGRGMLSRSNameFormat eSRSNameFormat, bool *pbCoordSwap)
319
0
{
320
0
    *pbCoordSwap = false;
321
0
    if (poSRS == nullptr)
322
0
        return CPLStrdup("");
323
324
0
    const auto &map = poSRS->GetDataAxisToSRSAxisMapping();
325
0
    if (eSRSNameFormat != SRSNAME_SHORT && map.size() >= 2 && map[0] == 2 &&
326
0
        map[1] == 1)
327
0
    {
328
0
        *pbCoordSwap = true;
329
0
    }
330
331
0
    const char *pszAuthName = poSRS->GetAuthorityName(nullptr);
332
0
    const char *pszAuthCode = poSRS->GetAuthorityCode(nullptr);
333
0
    if (nullptr != pszAuthName && nullptr != pszAuthCode)
334
0
    {
335
0
        if (eSRSNameFormat == SRSNAME_SHORT)
336
0
        {
337
0
            return CPLStrdup(
338
0
                CPLSPrintf(" srsName=\"%s:%s\"", pszAuthName, pszAuthCode));
339
0
        }
340
0
        else if (eSRSNameFormat == SRSNAME_OGC_URN)
341
0
        {
342
0
            return CPLStrdup(CPLSPrintf(" srsName=\"urn:ogc:def:crs:%s::%s\"",
343
0
                                        pszAuthName, pszAuthCode));
344
0
        }
345
0
        else if (eSRSNameFormat == SRSNAME_OGC_URL)
346
0
        {
347
0
            return CPLStrdup(CPLSPrintf(
348
0
                " srsName=\"http://www.opengis.net/def/crs/%s/0/%s\"",
349
0
                pszAuthName, pszAuthCode));
350
0
        }
351
0
    }
352
0
    return CPLStrdup("");
353
0
}
354
355
/************************************************************************/
356
/*                       GML_IsLegitSRSName()                           */
357
/************************************************************************/
358
359
bool GML_IsLegitSRSName(const char *pszSRSName)
360
0
{
361
362
0
    if (STARTS_WITH_CI(pszSRSName, "http"))
363
0
    {
364
0
        if (!(STARTS_WITH_CI(pszSRSName, "http://opengis.net/def/crs") ||
365
0
              STARTS_WITH_CI(pszSRSName, "http://www.opengis.net/def/crs")))
366
0
        {
367
0
            return false;
368
0
        }
369
0
    }
370
0
    return true;
371
0
}