Coverage Report

Created: 2025-12-31 08:30

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
11.6k
{
39
11.6k
    if (papsGeometry[0] != nullptr && papsGeometry[1] == nullptr)
40
11.6k
    {
41
11.6k
        const char *pszSRSName =
42
11.6k
            CPLGetXMLValue(papsGeometry[0], "srsName", nullptr);
43
11.6k
        if (pszSRSName)
44
582
        {
45
582
            const int nLen = static_cast<int>(strlen(pszSRSName));
46
47
582
            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
582
            else if (STARTS_WITH(pszSRSName,
55
582
                                 "http://www.opengis.net/gml/srs/epsg.xml#"))
56
181
            {
57
181
                osWork.reserve(5 + nLen - 40);
58
181
                osWork.assign("EPSG:", 5);
59
181
                osWork.append(pszSRSName + 40, nLen - 40);
60
181
                return osWork.c_str();
61
181
            }
62
401
            else
63
401
            {
64
401
                return pszSRSName;
65
401
            }
66
582
        }
67
11.6k
    }
68
11.0k
    return nullptr;
69
11.6k
}
70
71
/************************************************************************/
72
/*                       GML_IsSRSLatLongOrder()                        */
73
/************************************************************************/
74
75
bool GML_IsSRSLatLongOrder(const char *pszSRSName)
76
847
{
77
847
    if (pszSRSName == nullptr)
78
0
        return false;
79
80
847
    if (STARTS_WITH(pszSRSName, "urn:") &&
81
416
        strstr(pszSRSName, ":4326") != nullptr)
82
48
    {
83
        // Shortcut.
84
48
        return true;
85
48
    }
86
799
    else if (!EQUALN(pszSRSName, "EPSG:", 5))
87
404
    {
88
404
        OGRSpatialReference oSRS;
89
404
        if (oSRS.SetFromUserInput(
90
404
                pszSRSName,
91
404
                OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS_get()) ==
92
404
            OGRERR_NONE)
93
338
        {
94
338
            if (oSRS.EPSGTreatsAsLatLong() ||
95
329
                oSRS.EPSGTreatsAsNorthingEasting())
96
38
                return true;
97
338
        }
98
404
    }
99
761
    return false;
100
847
}
101
102
/************************************************************************/
103
/*              OGRGML_SRSCacheEntry::~OGRGML_SRSCacheEntry()           */
104
/************************************************************************/
105
106
OGRGML_SRSCacheEntry::~OGRGML_SRSCacheEntry()
107
76.9k
{
108
76.9k
    if (poSRS)
109
76.9k
        poSRS->Release();
110
76.9k
}
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
43.5k
{
129
43.5k
    return new OGRGML_SRSCache();
130
43.5k
}
131
132
/************************************************************************/
133
/*                        OGRGML_SRSCache_Destroy()                     */
134
/************************************************************************/
135
136
void OGRGML_SRSCache_Destroy(OGRGML_SRSCache *hSRSCache)
137
43.5k
{
138
43.5k
    delete hSRSCache;
139
43.5k
}
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
88.4k
{
148
88.4k
    std::shared_ptr<OGRGML_SRSCacheEntry> entry;
149
88.4k
    if (!hSRSCache->oSRSCache.tryGet(pszSRSName, entry))
150
76.9k
    {
151
76.9k
        entry = std::make_shared<OGRGML_SRSCacheEntry>();
152
76.9k
        auto poSRS = new OGRSpatialReference();
153
76.9k
        entry->poSRS = poSRS;
154
76.9k
        poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
155
76.9k
        if (poSRS->SetFromUserInput(
156
76.9k
                pszSRSName,
157
76.9k
                OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS_get()) !=
158
76.9k
            OGRERR_NONE)
159
70.1k
        {
160
70.1k
            return nullptr;
161
70.1k
        }
162
6.71k
        entry->nAxisCount = poSRS->GetAxesCount();
163
6.71k
        entry->bIsGeographic = poSRS->IsGeographic();
164
6.71k
        entry->bIsProjected = poSRS->IsProjected();
165
6.71k
        entry->bInvertedAxisOrder = !STARTS_WITH(pszSRSName, "EPSG:") &&
166
6.55k
                                    (poSRS->EPSGTreatsAsLatLong() ||
167
6.32k
                                     poSRS->EPSGTreatsAsNorthingEasting());
168
6.71k
        entry->dfSemiMajor = poSRS->GetSemiMajor();
169
6.71k
        if (entry->bIsProjected)
170
3.32k
            entry->dfLinearUnits = poSRS->GetLinearUnits(nullptr);
171
6.71k
        if (entry->bIsGeographic)
172
426
        {
173
426
            entry->bAngularUnitIsDegree =
174
426
                fabs(poSRS->GetAngularUnits(nullptr) -
175
426
                     CPLAtof(SRS_UA_DEGREE_CONV)) < 1e-8;
176
426
        }
177
6.71k
        hSRSCache->oSRSCache.insert(pszSRSName, entry);
178
6.71k
    }
179
18.2k
    return entry;
180
88.4k
}
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
7.34k
{
193
7.34k
    OGRGeometry *poGeom = nullptr;
194
7.34k
    OGRGeometryCollection *poCollection = nullptr;
195
7.34k
#ifndef WITHOUT_CPLDEBUG
196
7.34k
    static const bool bDebugGML =
197
7.34k
        EQUAL(CPLGetConfigOption("CPL_DEBUG", ""), "GML");
198
7.34k
#endif
199
14.6k
    for (int i = 0; papsGeometry[i] != nullptr; i++)
200
7.34k
    {
201
7.34k
#ifndef WITHOUT_CPLDEBUG
202
7.34k
        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
7.34k
#endif
209
7.34k
        OGRGeometry *poSubGeom = GML2OGRGeometry_XMLNode(
210
7.34k
            papsGeometry[i], nPseudoBoolGetSecondaryGeometryOption, hSRSCache,
211
7.34k
            0, 0, false, true, bFaceHoleNegative);
212
7.34k
        if (poSubGeom)
213
7.25k
        {
214
7.25k
            if (poGeom == nullptr)
215
7.25k
            {
216
7.25k
                poGeom = poSubGeom;
217
7.25k
            }
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
7.25k
        }
279
7.34k
    }
280
281
7.34k
    if (poGeom == nullptr)
282
91
        return nullptr;
283
284
7.25k
    std::string osWork;
285
7.25k
    const char *pszSRSName = GML_ExtractSrsNameFromGeometry(
286
7.25k
        papsGeometry, osWork, bConsiderEPSGAsURN);
287
7.25k
    const char *pszNameLookup = pszSRSName;
288
7.25k
    if (pszNameLookup == nullptr)
289
6.91k
        pszNameLookup = pszDefaultSRSName;
290
291
7.25k
    if (pszNameLookup != nullptr)
292
432
    {
293
432
        auto entry = OGRGML_SRSCache_GetInfo(hSRSCache, pszNameLookup);
294
432
        if (entry)
295
404
        {
296
404
            poGeom->assignSpatialReference(entry->poSRS);
297
404
            if ((eSwapCoordinates == GML_SWAP_AUTO &&
298
404
                 entry->bInvertedAxisOrder && bInvertAxisOrderIfLatLong) ||
299
265
                eSwapCoordinates == GML_SWAP_YES)
300
139
            {
301
139
                poGeom->swapXY();
302
139
            }
303
404
        }
304
432
    }
305
6.82k
    else if (eSwapCoordinates == GML_SWAP_YES)
306
0
    {
307
0
        poGeom->swapXY();
308
0
    }
309
310
7.25k
    return poGeom;
311
7.34k
}
312
313
/************************************************************************/
314
/*                           GML_GetSRSName()                           */
315
/************************************************************************/
316
317
char *GML_GetSRSName(const OGRSpatialReference *poSRS,
318
                     OGRGMLSRSNameFormat eSRSNameFormat, bool *pbCoordSwap)
319
3.37k
{
320
3.37k
    *pbCoordSwap = false;
321
3.37k
    if (poSRS == nullptr)
322
2.94k
        return CPLStrdup("");
323
324
431
    const auto &map = poSRS->GetDataAxisToSRSAxisMapping();
325
431
    if (eSRSNameFormat != SRSNAME_SHORT && map.size() >= 2 && map[0] == 2 &&
326
143
        map[1] == 1)
327
143
    {
328
143
        *pbCoordSwap = true;
329
143
    }
330
331
431
    const char *pszAuthName = poSRS->GetAuthorityName(nullptr);
332
431
    const char *pszAuthCode = poSRS->GetAuthorityCode(nullptr);
333
431
    if (nullptr != pszAuthName && nullptr != pszAuthCode)
334
197
    {
335
197
        if (eSRSNameFormat == SRSNAME_SHORT)
336
0
        {
337
0
            return CPLStrdup(
338
0
                CPLSPrintf(" srsName=\"%s:%s\"", pszAuthName, pszAuthCode));
339
0
        }
340
197
        else if (eSRSNameFormat == SRSNAME_OGC_URN)
341
197
        {
342
197
            return CPLStrdup(CPLSPrintf(" srsName=\"urn:ogc:def:crs:%s::%s\"",
343
197
                                        pszAuthName, pszAuthCode));
344
197
        }
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
197
    }
352
234
    return CPLStrdup("");
353
431
}
354
355
/************************************************************************/
356
/*                       GML_IsLegitSRSName()                           */
357
/************************************************************************/
358
359
bool GML_IsLegitSRSName(const char *pszSRSName)
360
1.76k
{
361
362
1.76k
    if (STARTS_WITH_CI(pszSRSName, "http"))
363
9
    {
364
9
        if (!(STARTS_WITH_CI(pszSRSName, "http://opengis.net/def/crs") ||
365
9
              STARTS_WITH_CI(pszSRSName, "http://www.opengis.net/def/crs")))
366
5
        {
367
5
            return false;
368
5
        }
369
9
    }
370
1.76k
    return true;
371
1.76k
}