Coverage Report

Created: 2026-06-30 08:33

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/frmts/wms/wmsmetadataset.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  WMS Client Driver
4
 * Purpose:  Definition of GDALWMSMetaDataset class
5
 * Author:   Even Rouault, <even dot rouault at spatialys.com>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2011-2013, Even Rouault <even dot rouault at spatialys.com>
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#include "wmsmetadataset.h"
14
15
#include "gdal_openinfo.h"
16
17
int VersionStringToInt(const char *version);
18
19
/************************************************************************/
20
/*                         GDALWMSMetaDataset()                         */
21
/************************************************************************/
22
23
4
GDALWMSMetaDataset::GDALWMSMetaDataset() = default;
24
25
/************************************************************************/
26
/*                        ~GDALWMSMetaDataset()                         */
27
/************************************************************************/
28
29
GDALWMSMetaDataset::~GDALWMSMetaDataset()
30
4
{
31
4
    CSLDestroy(papszSubDatasets);
32
4
}
33
34
/************************************************************************/
35
/*                           AddSubDataset()                            */
36
/************************************************************************/
37
38
void GDALWMSMetaDataset::AddSubDataset(const char *pszName, const char *pszDesc)
39
17
{
40
17
    char szName[80];
41
17
    int nCount = CSLCount(papszSubDatasets) / 2;
42
43
17
    snprintf(szName, sizeof(szName), "SUBDATASET_%d_NAME", nCount + 1);
44
17
    papszSubDatasets = CSLSetNameValue(papszSubDatasets, szName, pszName);
45
46
17
    snprintf(szName, sizeof(szName), "SUBDATASET_%d_DESC", nCount + 1);
47
17
    papszSubDatasets = CSLSetNameValue(papszSubDatasets, szName, pszDesc);
48
17
}
49
50
/************************************************************************/
51
/*                      DownloadGetCapabilities()                       */
52
/************************************************************************/
53
54
GDALDataset *
55
GDALWMSMetaDataset::DownloadGetCapabilities(GDALOpenInfo *poOpenInfo)
56
59.8k
{
57
59.8k
    const char *pszURL = poOpenInfo->pszFilename;
58
59.8k
    if (STARTS_WITH_CI(pszURL, "WMS:"))
59
55
        pszURL += 4;
60
61
59.8k
    CPLString osFormat = CPLURLGetValue(pszURL, "FORMAT");
62
59.8k
    CPLString osTransparent = CPLURLGetValue(pszURL, "TRANSPARENT");
63
59.8k
    CPLString osVersion = CPLURLGetValue(pszURL, "VERSION");
64
59.8k
    CPLString osPreferredSRS = CPLURLGetValue(pszURL, "SRS");
65
59.8k
    if (osPreferredSRS.empty())
66
59.7k
        osPreferredSRS = CPLURLGetValue(pszURL, "CRS");
67
68
59.8k
    if (osVersion.empty())
69
59.7k
        osVersion = "1.1.1";
70
71
59.8k
    CPLString osURL(pszURL);
72
59.8k
    osURL = CPLURLAddKVP(osURL, "SERVICE", "WMS");
73
59.8k
    osURL = CPLURLAddKVP(osURL, "VERSION", osVersion);
74
59.8k
    osURL = CPLURLAddKVP(osURL, "REQUEST", "GetCapabilities");
75
    /* Remove all other keywords */
76
59.8k
    osURL = CPLURLAddKVP(osURL, "LAYERS", nullptr);
77
59.8k
    osURL = CPLURLAddKVP(osURL, "SRS", nullptr);
78
59.8k
    osURL = CPLURLAddKVP(osURL, "CRS", nullptr);
79
59.8k
    osURL = CPLURLAddKVP(osURL, "BBOX", nullptr);
80
59.8k
    osURL = CPLURLAddKVP(osURL, "FORMAT", nullptr);
81
59.8k
    osURL = CPLURLAddKVP(osURL, "TRANSPARENT", nullptr);
82
59.8k
    osURL = CPLURLAddKVP(osURL, "STYLES", nullptr);
83
59.8k
    osURL = CPLURLAddKVP(osURL, "WIDTH", nullptr);
84
59.8k
    osURL = CPLURLAddKVP(osURL, "HEIGHT", nullptr);
85
86
59.8k
    CPLHTTPResult *psResult = CPLHTTPFetch(osURL, nullptr);
87
59.8k
    if (psResult == nullptr)
88
0
    {
89
0
        return nullptr;
90
0
    }
91
59.8k
    if (psResult->nStatus != 0 || psResult->pszErrBuf != nullptr)
92
59.8k
    {
93
59.8k
        CPLError(CE_Failure, CPLE_AppDefined,
94
59.8k
                 "Error returned by server : %s (%d)",
95
59.8k
                 (psResult->pszErrBuf) ? psResult->pszErrBuf : "unknown",
96
59.8k
                 psResult->nStatus);
97
59.8k
        CPLHTTPDestroyResult(psResult);
98
59.8k
        return nullptr;
99
59.8k
    }
100
0
    if (psResult->pabyData == nullptr)
101
0
    {
102
0
        CPLError(CE_Failure, CPLE_AppDefined,
103
0
                 "Empty content returned by server");
104
0
        CPLHTTPDestroyResult(psResult);
105
0
        return nullptr;
106
0
    }
107
108
0
    CPLXMLNode *psXML =
109
0
        CPLParseXMLString(reinterpret_cast<const char *>(psResult->pabyData));
110
0
    if (psXML == nullptr)
111
0
    {
112
0
        CPLError(CE_Failure, CPLE_AppDefined, "Invalid XML content : %s",
113
0
                 psResult->pabyData);
114
0
        CPLHTTPDestroyResult(psResult);
115
0
        return nullptr;
116
0
    }
117
118
0
    GDALDataset *poRet =
119
0
        AnalyzeGetCapabilities(psXML, osFormat, osTransparent, osPreferredSRS);
120
121
0
    CPLHTTPDestroyResult(psResult);
122
0
    CPLDestroyXMLNode(psXML);
123
124
0
    return poRet;
125
0
}
126
127
/************************************************************************/
128
/*                       DownloadGetTileService()                       */
129
/************************************************************************/
130
131
GDALDataset *
132
GDALWMSMetaDataset::DownloadGetTileService(GDALOpenInfo *poOpenInfo)
133
0
{
134
0
    const char *pszURL = poOpenInfo->pszFilename;
135
0
    if (STARTS_WITH_CI(pszURL, "WMS:"))
136
0
        pszURL += 4;
137
138
0
    CPLString osURL(pszURL);
139
0
    osURL = CPLURLAddKVP(osURL, "SERVICE", "WMS");
140
0
    osURL = CPLURLAddKVP(osURL, "REQUEST", "GetTileService");
141
    /* Remove all other keywords */
142
0
    osURL = CPLURLAddKVP(osURL, "VERSION", nullptr);
143
0
    osURL = CPLURLAddKVP(osURL, "LAYERS", nullptr);
144
0
    osURL = CPLURLAddKVP(osURL, "SRS", nullptr);
145
0
    osURL = CPLURLAddKVP(osURL, "CRS", nullptr);
146
0
    osURL = CPLURLAddKVP(osURL, "BBOX", nullptr);
147
0
    osURL = CPLURLAddKVP(osURL, "FORMAT", nullptr);
148
0
    osURL = CPLURLAddKVP(osURL, "TRANSPARENT", nullptr);
149
0
    osURL = CPLURLAddKVP(osURL, "STYLES", nullptr);
150
0
    osURL = CPLURLAddKVP(osURL, "WIDTH", nullptr);
151
0
    osURL = CPLURLAddKVP(osURL, "HEIGHT", nullptr);
152
153
0
    CPLHTTPResult *psResult = CPLHTTPFetch(osURL, nullptr);
154
0
    if (psResult == nullptr)
155
0
    {
156
0
        return nullptr;
157
0
    }
158
0
    if (psResult->nStatus != 0 || psResult->pszErrBuf != nullptr)
159
0
    {
160
0
        CPLError(CE_Failure, CPLE_AppDefined,
161
0
                 "Error returned by server : %s (%d)",
162
0
                 (psResult->pszErrBuf) ? psResult->pszErrBuf : "unknown",
163
0
                 psResult->nStatus);
164
0
        CPLHTTPDestroyResult(psResult);
165
0
        return nullptr;
166
0
    }
167
0
    if (psResult->pabyData == nullptr)
168
0
    {
169
0
        CPLError(CE_Failure, CPLE_AppDefined,
170
0
                 "Empty content returned by server");
171
0
        CPLHTTPDestroyResult(psResult);
172
0
        return nullptr;
173
0
    }
174
175
0
    CPLXMLNode *psXML =
176
0
        CPLParseXMLString(reinterpret_cast<const char *>(psResult->pabyData));
177
0
    if (psXML == nullptr)
178
0
    {
179
0
        CPLError(CE_Failure, CPLE_AppDefined, "Invalid XML content : %s",
180
0
                 psResult->pabyData);
181
0
        CPLHTTPDestroyResult(psResult);
182
0
        return nullptr;
183
0
    }
184
185
0
    GDALDataset *poRet = AnalyzeGetTileService(psXML, poOpenInfo);
186
187
0
    CPLHTTPDestroyResult(psResult);
188
0
    CPLDestroyXMLNode(psXML);
189
190
0
    return poRet;
191
0
}
192
193
/************************************************************************/
194
/*                       GetMetadataDomainList()                        */
195
/************************************************************************/
196
197
char **GDALWMSMetaDataset::GetMetadataDomainList()
198
0
{
199
0
    return BuildMetadataDomainList(GDALPamDataset::GetMetadataDomainList(),
200
0
                                   TRUE, GDAL_MDD_SUBDATASETS, nullptr);
201
0
}
202
203
/************************************************************************/
204
/*                            GetMetadata()                             */
205
/************************************************************************/
206
207
CSLConstList GDALWMSMetaDataset::GetMetadata(const char *pszDomain)
208
209
4
{
210
4
    if (pszDomain != nullptr && EQUAL(pszDomain, GDAL_MDD_SUBDATASETS))
211
0
        return papszSubDatasets;
212
213
4
    return GDALPamDataset::GetMetadata(pszDomain);
214
4
}
215
216
/************************************************************************/
217
/*                           AddSubDataset()                            */
218
/************************************************************************/
219
220
void GDALWMSMetaDataset::AddSubDataset(
221
    const char *pszLayerName, const char *pszTitle,
222
    CPL_UNUSED const char *pszAbstract, const char *pszSRS, const char *pszMinX,
223
    const char *pszMinY, const char *pszMaxX, const char *pszMaxY,
224
    const std::string &osFormat, const std::string &osTransparent)
225
15
{
226
15
    CPLString osSubdatasetName = "WMS:";
227
15
    osSubdatasetName += osGetURL;
228
15
    osSubdatasetName = CPLURLAddKVP(osSubdatasetName, "SERVICE", "WMS");
229
15
    osSubdatasetName = CPLURLAddKVP(osSubdatasetName, "VERSION", osVersion);
230
15
    osSubdatasetName = CPLURLAddKVP(osSubdatasetName, "REQUEST", "GetMap");
231
15
    char *pszEscapedLayerName = CPLEscapeString(pszLayerName, -1, CPLES_URL);
232
15
    osSubdatasetName =
233
15
        CPLURLAddKVP(osSubdatasetName, "LAYERS", pszEscapedLayerName);
234
15
    CPLFree(pszEscapedLayerName);
235
15
    if (VersionStringToInt(osVersion.c_str()) >= VersionStringToInt("1.3.0"))
236
15
    {
237
15
        osSubdatasetName = CPLURLAddKVP(osSubdatasetName, "CRS", pszSRS);
238
15
    }
239
0
    else
240
0
        osSubdatasetName = CPLURLAddKVP(osSubdatasetName, "SRS", pszSRS);
241
15
    osSubdatasetName = CPLURLAddKVP(
242
15
        osSubdatasetName, "BBOX",
243
15
        CPLSPrintf("%s,%s,%s,%s", pszMinX, pszMinY, pszMaxX, pszMaxY));
244
15
    if (!osFormat.empty())
245
0
        osSubdatasetName =
246
0
            CPLURLAddKVP(osSubdatasetName, "FORMAT", osFormat.c_str());
247
15
    if (!osTransparent.empty())
248
0
        osSubdatasetName = CPLURLAddKVP(osSubdatasetName, "TRANSPARENT",
249
0
                                        osTransparent.c_str());
250
251
15
    if (pszTitle)
252
15
    {
253
15
        if (!osXMLEncoding.empty() && osXMLEncoding != "utf-8" &&
254
10
            osXMLEncoding != "UTF-8")
255
0
        {
256
0
            char *pszRecodedTitle =
257
0
                CPLRecode(pszTitle, osXMLEncoding.c_str(), CPL_ENC_UTF8);
258
0
            if (pszRecodedTitle)
259
0
                AddSubDataset(osSubdatasetName, pszRecodedTitle);
260
0
            else
261
0
                AddSubDataset(osSubdatasetName, pszTitle);
262
0
            CPLFree(pszRecodedTitle);
263
0
        }
264
15
        else
265
15
        {
266
15
            AddSubDataset(osSubdatasetName, pszTitle);
267
15
        }
268
15
    }
269
0
    else
270
0
    {
271
0
        AddSubDataset(osSubdatasetName, pszLayerName);
272
0
    }
273
15
}
274
275
/************************************************************************/
276
/*                         AddWMSCSubDataset()                          */
277
/************************************************************************/
278
279
void GDALWMSMetaDataset::AddWMSCSubDataset(WMSCTileSetDesc &oWMSCTileSetDesc,
280
                                           const char *pszTitle,
281
                                           const CPLString &osTransparent)
282
0
{
283
0
    CPLString osSubdatasetName = "WMS:";
284
0
    osSubdatasetName += osGetURL;
285
0
    osSubdatasetName = CPLURLAddKVP(osSubdatasetName, "SERVICE", "WMS");
286
0
    osSubdatasetName = CPLURLAddKVP(osSubdatasetName, "VERSION", osVersion);
287
0
    osSubdatasetName = CPLURLAddKVP(osSubdatasetName, "REQUEST", "GetMap");
288
0
    osSubdatasetName =
289
0
        CPLURLAddKVP(osSubdatasetName, "LAYERS", oWMSCTileSetDesc.osLayers);
290
0
    if (VersionStringToInt(osVersion.c_str()) >= VersionStringToInt("1.3.0"))
291
0
        osSubdatasetName =
292
0
            CPLURLAddKVP(osSubdatasetName, "CRS", oWMSCTileSetDesc.osSRS);
293
0
    else
294
0
        osSubdatasetName =
295
0
            CPLURLAddKVP(osSubdatasetName, "SRS", oWMSCTileSetDesc.osSRS);
296
0
    osSubdatasetName =
297
0
        CPLURLAddKVP(osSubdatasetName, "BBOX",
298
0
                     CPLSPrintf("%s,%s,%s,%s", oWMSCTileSetDesc.osMinX.c_str(),
299
0
                                oWMSCTileSetDesc.osMinY.c_str(),
300
0
                                oWMSCTileSetDesc.osMaxX.c_str(),
301
0
                                oWMSCTileSetDesc.osMaxY.c_str()));
302
303
0
    osSubdatasetName =
304
0
        CPLURLAddKVP(osSubdatasetName, "FORMAT", oWMSCTileSetDesc.osFormat);
305
0
    if (!osTransparent.empty())
306
0
        osSubdatasetName =
307
0
            CPLURLAddKVP(osSubdatasetName, "TRANSPARENT", osTransparent);
308
0
    if (oWMSCTileSetDesc.nTileWidth != oWMSCTileSetDesc.nTileHeight)
309
0
        CPLDebug("WMS", "Weird: nTileWidth != nTileHeight for %s",
310
0
                 oWMSCTileSetDesc.osLayers.c_str());
311
0
    osSubdatasetName =
312
0
        CPLURLAddKVP(osSubdatasetName, "TILESIZE",
313
0
                     CPLSPrintf("%d", oWMSCTileSetDesc.nTileWidth));
314
0
    osSubdatasetName =
315
0
        CPLURLAddKVP(osSubdatasetName, "OVERVIEWCOUNT",
316
0
                     CPLSPrintf("%d", oWMSCTileSetDesc.nResolutions - 1));
317
0
    osSubdatasetName =
318
0
        CPLURLAddKVP(osSubdatasetName, "MINRESOLUTION",
319
0
                     CPLSPrintf("%.16f", oWMSCTileSetDesc.dfMinResolution));
320
0
    osSubdatasetName = CPLURLAddKVP(osSubdatasetName, "TILED", "true");
321
322
0
    if (pszTitle)
323
0
    {
324
0
        if (!osXMLEncoding.empty() && osXMLEncoding != "utf-8" &&
325
0
            osXMLEncoding != "UTF-8")
326
0
        {
327
0
            char *pszRecodedTitle =
328
0
                CPLRecode(pszTitle, osXMLEncoding.c_str(), CPL_ENC_UTF8);
329
0
            if (pszRecodedTitle)
330
0
                AddSubDataset(osSubdatasetName, pszRecodedTitle);
331
0
            else
332
0
                AddSubDataset(osSubdatasetName, pszTitle);
333
0
            CPLFree(pszRecodedTitle);
334
0
        }
335
0
        else
336
0
        {
337
0
            AddSubDataset(osSubdatasetName, pszTitle);
338
0
        }
339
0
    }
340
0
    else
341
0
    {
342
0
        AddSubDataset(osSubdatasetName, oWMSCTileSetDesc.osLayers);
343
0
    }
344
0
}
345
346
/************************************************************************/
347
/*                            ExploreLayer()                            */
348
/************************************************************************/
349
350
void GDALWMSMetaDataset::ExploreLayer(CPLXMLNode *psXML,
351
                                      const CPLString &osFormat,
352
                                      const CPLString &osTransparent,
353
                                      const CPLString &osPreferredSRS,
354
                                      const char *pszSRS, const char *pszMinX,
355
                                      const char *pszMinY, const char *pszMaxX,
356
                                      const char *pszMaxY)
357
15
{
358
15
    const char *pszName = CPLGetXMLValue(psXML, "Name", nullptr);
359
15
    const char *pszTitle = CPLGetXMLValue(psXML, "Title", nullptr);
360
15
    const char *pszAbstract = CPLGetXMLValue(psXML, "Abstract", nullptr);
361
362
15
    CPLXMLNode *psSRS = nullptr;
363
15
    const char *pszSRSLocal = nullptr;
364
15
    const char *pszMinXLocal = nullptr;
365
15
    const char *pszMinYLocal = nullptr;
366
15
    const char *pszMaxXLocal = nullptr;
367
15
    const char *pszMaxYLocal = nullptr;
368
369
15
    const bool bIsWMS130 =
370
15
        VersionStringToInt(osVersion.c_str()) >= VersionStringToInt("1.3.0");
371
372
15
    const auto GetCRS = [bIsWMS130](const CPLXMLNode *psNode)
373
20
    {
374
20
        const char *pszVal =
375
20
            CPLGetXMLValue(psNode, bIsWMS130 ? "CRS" : "SRS", nullptr);
376
20
        if (!pszVal && !bIsWMS130)
377
0
        {
378
            // Some WMS servers have a non-conformant WMS 1.1.1 implementation
379
            // that use "CRS" instead of "SRS"
380
            // like https://mapy.geoportal.gov.pl/wss/service/PZGIK/BDOO/WMS/aktualne?service=wms&request=getcapabilities
381
0
            pszVal = CPLGetXMLValue(psNode, "CRS", nullptr);
382
0
            if (pszVal)
383
0
            {
384
0
                static bool bWarned = false;
385
0
                if (!bWarned)
386
0
                {
387
0
                    bWarned = true;
388
0
                    CPLError(CE_Warning, CPLE_AppDefined,
389
0
                             "WMS server uses non-standard CRS attribute for "
390
0
                             "WMS 1.1.0. Things might mis-behave. Perhaps try "
391
0
                             "using VERSION=1.3.0");
392
0
                }
393
0
            }
394
0
        }
395
20
        return pszVal;
396
20
    };
397
398
    /* Use local bounding box if available, otherwise use the one */
399
    /* that comes from an upper layer */
400
    /* such as in http://neowms.sci.gsfc.nasa.gov/wms/wms */
401
15
    CPLXMLNode *psIter = psXML->psChild;
402
346
    while (psIter != nullptr)
403
341
    {
404
341
        if (psIter->eType == CXT_Element &&
405
309
            strcmp(psIter->pszValue, "BoundingBox") == 0)
406
10
        {
407
10
            psSRS = psIter;
408
10
            pszSRSLocal = GetCRS(psSRS);
409
10
            if (osPreferredSRS.empty() || pszSRSLocal == nullptr)
410
10
                break;
411
0
            if (EQUAL(osPreferredSRS, pszSRSLocal))
412
0
                break;
413
0
            psSRS = nullptr;
414
0
            pszSRSLocal = nullptr;
415
0
        }
416
331
        psIter = psIter->psNext;
417
331
    }
418
419
15
    if (psSRS == nullptr)
420
5
    {
421
5
        psSRS = CPLGetXMLNode(psXML, "LatLonBoundingBox");
422
5
        pszSRSLocal = GetCRS(psSRS);
423
5
        if (pszSRSLocal == nullptr)
424
5
            pszSRSLocal = "EPSG:4326";
425
5
    }
426
427
15
    if (pszSRSLocal != nullptr && psSRS != nullptr)
428
9
    {
429
9
        pszMinXLocal = CPLGetXMLValue(psSRS, "minx", nullptr);
430
9
        pszMinYLocal = CPLGetXMLValue(psSRS, "miny", nullptr);
431
9
        pszMaxXLocal = CPLGetXMLValue(psSRS, "maxx", nullptr);
432
9
        pszMaxYLocal = CPLGetXMLValue(psSRS, "maxy", nullptr);
433
434
9
        if (pszMinXLocal && pszMinYLocal && pszMaxXLocal && pszMaxYLocal)
435
9
        {
436
9
            pszSRS = pszSRSLocal;
437
9
            pszMinX = pszMinXLocal;
438
9
            pszMinY = pszMinYLocal;
439
9
            pszMaxX = pszMaxXLocal;
440
9
            pszMaxY = pszMaxYLocal;
441
9
        }
442
9
    }
443
444
15
    std::string osMinX, osMinY, osMaxX, osMaxY;
445
15
    if (psSRS == nullptr)
446
5
    {
447
5
        const auto psExGeographicBoundingBox =
448
5
            CPLGetXMLNode(psXML, "EX_GeographicBoundingBox");
449
5
        if (psExGeographicBoundingBox)
450
5
        {
451
5
            pszMinXLocal = CPLGetXMLValue(psExGeographicBoundingBox,
452
5
                                          "westBoundLongitude", nullptr);
453
5
            pszMinYLocal = CPLGetXMLValue(psExGeographicBoundingBox,
454
5
                                          "southBoundLatitude", nullptr);
455
5
            pszMaxXLocal = CPLGetXMLValue(psExGeographicBoundingBox,
456
5
                                          "eastBoundLongitude", nullptr);
457
5
            pszMaxYLocal = CPLGetXMLValue(psExGeographicBoundingBox,
458
5
                                          "northBoundLatitude", nullptr);
459
460
5
            if (pszMinXLocal && pszMinYLocal && pszMaxXLocal && pszMaxYLocal)
461
5
            {
462
5
                pszSRS = "EPSG:4326";
463
5
                pszMinX = pszMinXLocal;
464
5
                pszMinY = pszMinYLocal;
465
5
                pszMaxX = pszMaxXLocal;
466
5
                pszMaxY = pszMaxYLocal;
467
468
                // Try to reproject the bbox to the default CRS
469
5
                pszSRSLocal = GetCRS(psXML);
470
5
                if (pszSRSLocal)
471
5
                {
472
5
                    OGRSpatialReference oSrcSRS;
473
5
                    oSrcSRS.importFromEPSG(4326);
474
5
                    oSrcSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
475
476
5
                    OGRSpatialReference oDstSRS;
477
5
                    oDstSRS.SetFromUserInput(
478
5
                        pszSRSLocal, OGRSpatialReference::
479
5
                                         SET_FROM_USER_INPUT_LIMITATIONS_get());
480
5
                    oDstSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
481
482
5
                    auto poCT = std::unique_ptr<OGRCoordinateTransformation>(
483
5
                        OGRCreateCoordinateTransformation(&oSrcSRS, &oDstSRS));
484
5
                    double dfMinX = 0;
485
5
                    double dfMinY = 0;
486
5
                    double dfMaxX = 0;
487
5
                    double dfMaxY = 0;
488
5
                    if (poCT &&
489
5
                        poCT->TransformBounds(
490
5
                            CPLAtof(pszMinXLocal), CPLAtof(pszMinYLocal),
491
5
                            CPLAtof(pszMaxXLocal), CPLAtof(pszMaxYLocal),
492
5
                            &dfMinX, &dfMinY, &dfMaxX, &dfMaxY,
493
5
                            /* densify_pts = */ 21))
494
5
                    {
495
5
                        osMinX = CPLSPrintf("%.17g", dfMinX);
496
5
                        osMinY = CPLSPrintf("%.17g", dfMinY);
497
5
                        osMaxX = CPLSPrintf("%.17g", dfMaxX);
498
5
                        osMaxY = CPLSPrintf("%.17g", dfMaxY);
499
5
                        pszSRS = pszSRSLocal;
500
5
                        pszMinX = osMinX.c_str();
501
5
                        pszMinY = osMinY.c_str();
502
5
                        pszMaxX = osMaxX.c_str();
503
5
                        pszMaxY = osMaxY.c_str();
504
5
                    }
505
5
                }
506
5
            }
507
5
        }
508
5
    }
509
510
15
    if (pszName != nullptr && pszSRS && pszMinX && pszMinY && pszMaxX &&
511
15
        pszMaxY)
512
15
    {
513
15
        CPLString osLocalTransparent(osTransparent);
514
15
        if (osLocalTransparent.empty())
515
15
        {
516
15
            const char *pszOpaque = CPLGetXMLValue(psXML, "opaque", "0");
517
15
            if (EQUAL(pszOpaque, "1"))
518
0
                osLocalTransparent = "FALSE";
519
15
        }
520
521
15
        WMSCKeyType oWMSCKey(pszName, pszSRS);
522
15
        std::map<WMSCKeyType, WMSCTileSetDesc>::iterator oIter =
523
15
            osMapWMSCTileSet.find(oWMSCKey);
524
15
        if (oIter != osMapWMSCTileSet.end())
525
0
        {
526
0
            AddWMSCSubDataset(oIter->second, pszTitle, osLocalTransparent);
527
0
        }
528
15
        else
529
15
        {
530
15
            AddSubDataset(pszName, pszTitle, pszAbstract, pszSRS, pszMinX,
531
15
                          pszMinY, pszMaxX, pszMaxY, osFormat,
532
15
                          osLocalTransparent);
533
15
        }
534
15
    }
535
536
15
    psIter = psXML->psChild;
537
380
    for (; psIter != nullptr; psIter = psIter->psNext)
538
365
    {
539
365
        if (psIter->eType == CXT_Element)
540
333
        {
541
333
            if (EQUAL(psIter->pszValue, "Layer"))
542
12
                ExploreLayer(psIter, osFormat, osTransparent, osPreferredSRS,
543
12
                             pszSRS, pszMinX, pszMinY, pszMaxX, pszMaxY);
544
333
        }
545
365
    }
546
15
}
547
548
/************************************************************************/
549
/*                         ParseWMSCTileSets()                          */
550
/************************************************************************/
551
552
void GDALWMSMetaDataset::ParseWMSCTileSets(CPLXMLNode *psXML)
553
0
{
554
0
    CPLXMLNode *psIter = psXML->psChild;
555
0
    for (; psIter; psIter = psIter->psNext)
556
0
    {
557
0
        if (psIter->eType == CXT_Element && EQUAL(psIter->pszValue, "TileSet"))
558
0
        {
559
0
            const char *pszSRS = CPLGetXMLValue(psIter, "SRS", nullptr);
560
0
            if (pszSRS == nullptr)
561
0
                continue;
562
563
0
            CPLXMLNode *psBoundingBox = CPLGetXMLNode(psIter, "BoundingBox");
564
0
            if (psBoundingBox == nullptr)
565
0
                continue;
566
567
0
            const char *pszMinX =
568
0
                CPLGetXMLValue(psBoundingBox, "minx", nullptr);
569
0
            const char *pszMinY =
570
0
                CPLGetXMLValue(psBoundingBox, "miny", nullptr);
571
0
            const char *pszMaxX =
572
0
                CPLGetXMLValue(psBoundingBox, "maxx", nullptr);
573
0
            const char *pszMaxY =
574
0
                CPLGetXMLValue(psBoundingBox, "maxy", nullptr);
575
0
            if (pszMinX == nullptr || pszMinY == nullptr ||
576
0
                pszMaxX == nullptr || pszMaxY == nullptr)
577
0
                continue;
578
579
0
            double dfMinX = CPLAtofM(pszMinX);
580
0
            double dfMinY = CPLAtofM(pszMinY);
581
0
            double dfMaxX = CPLAtofM(pszMaxX);
582
0
            double dfMaxY = CPLAtofM(pszMaxY);
583
0
            if (dfMaxY <= dfMinY || dfMaxX <= dfMinX)
584
0
                continue;
585
586
0
            const char *pszFormat = CPLGetXMLValue(psIter, "Format", nullptr);
587
0
            if (pszFormat == nullptr)
588
0
                continue;
589
0
            if (strstr(pszFormat, "kml"))
590
0
                continue;
591
592
0
            const char *pszTileWidth = CPLGetXMLValue(psIter, "Width", nullptr);
593
0
            const char *pszTileHeight =
594
0
                CPLGetXMLValue(psIter, "Height", nullptr);
595
0
            if (pszTileWidth == nullptr || pszTileHeight == nullptr)
596
0
                continue;
597
598
0
            int nTileWidth = atoi(pszTileWidth);
599
0
            int nTileHeight = atoi(pszTileHeight);
600
0
            if (nTileWidth < 128 || nTileHeight < 128)
601
0
                continue;
602
603
0
            const char *pszLayers = CPLGetXMLValue(psIter, "Layers", nullptr);
604
0
            if (pszLayers == nullptr)
605
0
                continue;
606
607
0
            const char *pszResolutions =
608
0
                CPLGetXMLValue(psIter, "Resolutions", nullptr);
609
0
            if (pszResolutions == nullptr)
610
0
                continue;
611
0
            char **papszTokens =
612
0
                CSLTokenizeStringComplex(pszResolutions, " ", 0, 0);
613
0
            double dfMinResolution = 0;
614
0
            int i;
615
0
            for (i = 0; papszTokens && papszTokens[i]; i++)
616
0
            {
617
0
                double dfResolution = CPLAtofM(papszTokens[i]);
618
0
                if (i == 0 || dfResolution < dfMinResolution)
619
0
                    dfMinResolution = dfResolution;
620
0
            }
621
0
            CSLDestroy(papszTokens);
622
0
            int nResolutions = i;
623
0
            if (nResolutions == 0)
624
0
                continue;
625
626
0
            const char *pszStyles = CPLGetXMLValue(psIter, "Styles", "");
627
628
            /* http://demo.opengeo.org/geoserver/gwc/service/wms?tiled=TRUE&SERVICE=WMS&VERSION=1.1.1&REQUEST=GetCapabilities
629
             */
630
            /* has different variations of formats for the same (formats, SRS)
631
             * tuple, so just */
632
            /* keep the first one which is a png format */
633
0
            WMSCKeyType oWMSCKey(pszLayers, pszSRS);
634
0
            std::map<WMSCKeyType, WMSCTileSetDesc>::iterator oIter =
635
0
                osMapWMSCTileSet.find(oWMSCKey);
636
0
            if (oIter != osMapWMSCTileSet.end())
637
0
                continue;
638
639
0
            WMSCTileSetDesc oWMSCTileSet;
640
0
            oWMSCTileSet.osLayers = pszLayers;
641
0
            oWMSCTileSet.osSRS = pszSRS;
642
0
            oWMSCTileSet.osMinX = pszMinX;
643
0
            oWMSCTileSet.osMinY = pszMinY;
644
0
            oWMSCTileSet.osMaxX = pszMaxX;
645
0
            oWMSCTileSet.osMaxY = pszMaxY;
646
0
            oWMSCTileSet.dfMinX = dfMinX;
647
0
            oWMSCTileSet.dfMinY = dfMinY;
648
0
            oWMSCTileSet.dfMaxX = dfMaxX;
649
0
            oWMSCTileSet.dfMaxY = dfMaxY;
650
0
            oWMSCTileSet.nResolutions = nResolutions;
651
0
            oWMSCTileSet.dfMinResolution = dfMinResolution;
652
0
            oWMSCTileSet.osFormat = pszFormat;
653
0
            oWMSCTileSet.osStyle = pszStyles;
654
0
            oWMSCTileSet.nTileWidth = nTileWidth;
655
0
            oWMSCTileSet.nTileHeight = nTileHeight;
656
657
0
            osMapWMSCTileSet[oWMSCKey] = std::move(oWMSCTileSet);
658
0
        }
659
0
    }
660
0
}
661
662
/************************************************************************/
663
/*                       AnalyzeGetCapabilities()                       */
664
/************************************************************************/
665
666
GDALDataset *GDALWMSMetaDataset::AnalyzeGetCapabilities(
667
    CPLXMLNode *psXML, const std::string &osFormat,
668
    const std::string &osTransparent, const std::string &osPreferredSRS)
669
77
{
670
77
    const char *pszEncoding = nullptr;
671
77
    if (psXML->eType == CXT_Element && strcmp(psXML->pszValue, "?xml") == 0)
672
4
        pszEncoding = CPLGetXMLValue(psXML, "encoding", nullptr);
673
674
77
    CPLXMLNode *psRoot = CPLGetXMLNode(psXML, "=WMT_MS_Capabilities");
675
77
    if (psRoot == nullptr)
676
77
        psRoot = CPLGetXMLNode(psXML, "=WMS_Capabilities");
677
77
    if (psRoot == nullptr)
678
74
        return nullptr;
679
3
    CPLXMLNode *psCapability = CPLGetXMLNode(psRoot, "Capability");
680
3
    if (psCapability == nullptr)
681
0
        return nullptr;
682
683
3
    CPLXMLNode *psOnlineResource = CPLGetXMLNode(
684
3
        psCapability, "Request.GetMap.DCPType.HTTP.Get.OnlineResource");
685
3
    if (psOnlineResource == nullptr)
686
0
        return nullptr;
687
3
    const char *pszGetURL =
688
3
        CPLGetXMLValue(psOnlineResource, "xlink:href", nullptr);
689
3
    if (pszGetURL == nullptr)
690
0
        return nullptr;
691
692
3
    CPLXMLNode *psLayer = CPLGetXMLNode(psCapability, "Layer");
693
3
    if (psLayer == nullptr)
694
0
        return nullptr;
695
696
3
    CPLXMLNode *psVendorSpecificCapabilities =
697
3
        CPLGetXMLNode(psCapability, "VendorSpecificCapabilities");
698
699
3
    GDALWMSMetaDataset *poDS = new GDALWMSMetaDataset();
700
3
    const char *pszVersion = CPLGetXMLValue(psRoot, "version", nullptr);
701
3
    if (pszVersion)
702
3
        poDS->osVersion = pszVersion;
703
0
    else
704
0
        poDS->osVersion = "1.1.1";
705
3
    poDS->osGetURL = pszGetURL;
706
3
    poDS->osXMLEncoding = pszEncoding ? pszEncoding : "";
707
3
    if (psVendorSpecificCapabilities)
708
0
        poDS->ParseWMSCTileSets(psVendorSpecificCapabilities);
709
3
    poDS->ExploreLayer(psLayer, osFormat, osTransparent, osPreferredSRS);
710
711
3
    return poDS;
712
3
}
713
714
/************************************************************************/
715
/*                         AddTiledSubDataset()                         */
716
/************************************************************************/
717
718
// tiledWMS only
719
void GDALWMSMetaDataset::AddTiledSubDataset(const char *pszTiledGroupName,
720
                                            const char *pszTitle,
721
                                            const char *const *papszChanges)
722
2
{
723
2
    CPLString osSubdatasetName =
724
2
        "<GDAL_WMS><Service name=\"TiledWMS\"><ServerUrl>";
725
2
    osSubdatasetName += osGetURL;
726
2
    osSubdatasetName += "</ServerUrl><TiledGroupName>";
727
2
    osSubdatasetName += pszTiledGroupName;
728
2
    osSubdatasetName += "</TiledGroupName>";
729
730
2
    for (int i = 0; papszChanges != nullptr && papszChanges[i] != nullptr; i++)
731
0
    {
732
0
        char *key = nullptr;
733
0
        const char *value = CPLParseNameValue(papszChanges[i], &key);
734
0
        if (value != nullptr && key != nullptr)
735
0
            osSubdatasetName +=
736
0
                CPLSPrintf("<Change key=\"${%s}\">%s</Change>", key, value);
737
0
        CPLFree(key);
738
0
    }
739
740
2
    osSubdatasetName += "</Service></GDAL_WMS>";
741
742
2
    if (pszTitle)
743
2
    {
744
2
        if (!osXMLEncoding.empty() && osXMLEncoding != "utf-8" &&
745
0
            osXMLEncoding != "UTF-8")
746
0
        {
747
0
            char *pszRecodedTitle =
748
0
                CPLRecode(pszTitle, osXMLEncoding.c_str(), CPL_ENC_UTF8);
749
0
            if (pszRecodedTitle)
750
0
                AddSubDataset(osSubdatasetName, pszRecodedTitle);
751
0
            else
752
0
                AddSubDataset(osSubdatasetName, pszTitle);
753
0
            CPLFree(pszRecodedTitle);
754
0
        }
755
2
        else
756
2
        {
757
2
            AddSubDataset(osSubdatasetName, pszTitle);
758
2
        }
759
2
    }
760
0
    else
761
0
    {
762
0
        AddSubDataset(osSubdatasetName, pszTiledGroupName);
763
0
    }
764
2
}
765
766
/************************************************************************/
767
/*                    AnalyzeGetTileServiceRecurse()                    */
768
/************************************************************************/
769
// tiledWMS only
770
void GDALWMSMetaDataset::AnalyzeGetTileServiceRecurse(CPLXMLNode *psXML,
771
                                                      GDALOpenInfo *poOpenInfo)
772
1
{
773
    // Only list tiled groups that contain the string in the open option
774
    // TiledGroupName, if given
775
1
    char **papszLocalOpenOptions =
776
1
        poOpenInfo ? poOpenInfo->papszOpenOptions : nullptr;
777
1
    CPLString osMatch(
778
1
        CSLFetchNameValueDef(papszLocalOpenOptions, "TiledGroupName", ""));
779
1
    osMatch.toupper();
780
    // Also pass the change patterns, if provided
781
1
    char **papszChanges =
782
1
        CSLFetchNameValueMultiple(papszLocalOpenOptions, "Change");
783
784
1
    CPLXMLNode *psIter = psXML->psChild;
785
5
    for (; psIter != nullptr; psIter = psIter->psNext)
786
4
    {
787
4
        if (psIter->eType == CXT_Element &&
788
4
            EQUAL(psIter->pszValue, "TiledGroup"))
789
2
        {
790
2
            const char *pszName = CPLGetXMLValue(psIter, "Name", nullptr);
791
2
            if (pszName)
792
2
            {
793
2
                const char *pszTitle = CPLGetXMLValue(psIter, "Title", nullptr);
794
2
                if (osMatch.empty())
795
2
                {
796
2
                    AddTiledSubDataset(pszName, pszTitle, papszChanges);
797
2
                }
798
0
                else
799
0
                {
800
0
                    CPLString osNameUpper(pszName);
801
0
                    osNameUpper.toupper();
802
0
                    if (std::string::npos != osNameUpper.find(osMatch))
803
0
                        AddTiledSubDataset(pszName, pszTitle, papszChanges);
804
0
                }
805
2
            }
806
2
        }
807
2
        else if (psIter->eType == CXT_Element &&
808
2
                 EQUAL(psIter->pszValue, "TiledGroups"))
809
0
        {
810
0
            AnalyzeGetTileServiceRecurse(psIter, poOpenInfo);
811
0
        }
812
4
    }
813
1
    CPLFree(papszChanges);
814
1
}
815
816
/************************************************************************/
817
/*                       AnalyzeGetTileService()                        */
818
/************************************************************************/
819
// tiledWMS only
820
GDALDataset *GDALWMSMetaDataset::AnalyzeGetTileService(CPLXMLNode *psXML,
821
                                                       GDALOpenInfo *poOpenInfo)
822
56
{
823
56
    const char *pszEncoding = nullptr;
824
56
    if (psXML->eType == CXT_Element && strcmp(psXML->pszValue, "?xml") == 0)
825
0
        pszEncoding = CPLGetXMLValue(psXML, "encoding", nullptr);
826
827
56
    CPLXMLNode *psRoot = CPLGetXMLNode(psXML, "=WMS_Tile_Service");
828
56
    if (psRoot == nullptr)
829
55
        return nullptr;
830
1
    CPLXMLNode *psTiledPatterns = CPLGetXMLNode(psRoot, "TiledPatterns");
831
1
    if (psTiledPatterns == nullptr)
832
0
        return nullptr;
833
834
1
    const char *pszURL =
835
1
        CPLGetXMLValue(psTiledPatterns, "OnlineResource.xlink:href", nullptr);
836
1
    if (pszURL == nullptr)
837
0
        return nullptr;
838
839
1
    GDALWMSMetaDataset *poDS = new GDALWMSMetaDataset();
840
1
    poDS->osGetURL = pszURL;
841
1
    poDS->osXMLEncoding = pszEncoding ? pszEncoding : "";
842
843
1
    poDS->AnalyzeGetTileServiceRecurse(psTiledPatterns, poOpenInfo);
844
845
1
    return poDS;
846
1
}
847
848
/************************************************************************/
849
/*                       AnalyzeTileMapService()                        */
850
/************************************************************************/
851
852
GDALDataset *GDALWMSMetaDataset::AnalyzeTileMapService(CPLXMLNode *psXML)
853
3
{
854
3
    CPLXMLNode *psRoot = CPLGetXMLNode(psXML, "=TileMapService");
855
3
    if (psRoot == nullptr)
856
2
        return nullptr;
857
1
    CPLXMLNode *psTileMaps = CPLGetXMLNode(psRoot, "TileMaps");
858
1
    if (psTileMaps == nullptr)
859
1
        return nullptr;
860
861
0
    GDALWMSMetaDataset *poDS = new GDALWMSMetaDataset();
862
863
0
    CPLXMLNode *psIter = psTileMaps->psChild;
864
0
    for (; psIter != nullptr; psIter = psIter->psNext)
865
0
    {
866
0
        if (psIter->eType == CXT_Element && EQUAL(psIter->pszValue, "TileMap"))
867
0
        {
868
0
            const char *pszHref = CPLGetXMLValue(psIter, "href", nullptr);
869
0
            const char *pszTitle = CPLGetXMLValue(psIter, "title", nullptr);
870
0
            if (pszHref && pszTitle)
871
0
            {
872
0
                CPLString osHref(pszHref);
873
0
                const char *pszDup100 = strstr(pszHref, "1.0.0/1.0.0/");
874
0
                if (pszDup100)
875
0
                {
876
0
                    osHref.resize(pszDup100 - pszHref);
877
0
                    osHref += pszDup100 + strlen("1.0.0/");
878
0
                }
879
0
                poDS->AddSubDataset(osHref, pszTitle);
880
0
            }
881
0
        }
882
0
    }
883
884
0
    return poDS;
885
1
}