Coverage Report

Created: 2026-02-14 09:00

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
41.7k
{
57
41.7k
    const char *pszURL = poOpenInfo->pszFilename;
58
41.7k
    if (STARTS_WITH_CI(pszURL, "WMS:"))
59
55
        pszURL += 4;
60
61
41.7k
    CPLString osFormat = CPLURLGetValue(pszURL, "FORMAT");
62
41.7k
    CPLString osTransparent = CPLURLGetValue(pszURL, "TRANSPARENT");
63
41.7k
    CPLString osVersion = CPLURLGetValue(pszURL, "VERSION");
64
41.7k
    CPLString osPreferredSRS = CPLURLGetValue(pszURL, "SRS");
65
41.7k
    if (osPreferredSRS.empty())
66
41.6k
        osPreferredSRS = CPLURLGetValue(pszURL, "CRS");
67
68
41.7k
    if (osVersion.empty())
69
41.5k
        osVersion = "1.1.1";
70
71
41.7k
    CPLString osURL(pszURL);
72
41.7k
    osURL = CPLURLAddKVP(osURL, "SERVICE", "WMS");
73
41.7k
    osURL = CPLURLAddKVP(osURL, "VERSION", osVersion);
74
41.7k
    osURL = CPLURLAddKVP(osURL, "REQUEST", "GetCapabilities");
75
    /* Remove all other keywords */
76
41.7k
    osURL = CPLURLAddKVP(osURL, "LAYERS", nullptr);
77
41.7k
    osURL = CPLURLAddKVP(osURL, "SRS", nullptr);
78
41.7k
    osURL = CPLURLAddKVP(osURL, "CRS", nullptr);
79
41.7k
    osURL = CPLURLAddKVP(osURL, "BBOX", nullptr);
80
41.7k
    osURL = CPLURLAddKVP(osURL, "FORMAT", nullptr);
81
41.7k
    osURL = CPLURLAddKVP(osURL, "TRANSPARENT", nullptr);
82
41.7k
    osURL = CPLURLAddKVP(osURL, "STYLES", nullptr);
83
41.7k
    osURL = CPLURLAddKVP(osURL, "WIDTH", nullptr);
84
41.7k
    osURL = CPLURLAddKVP(osURL, "HEIGHT", nullptr);
85
86
41.7k
    CPLHTTPResult *psResult = CPLHTTPFetch(osURL, nullptr);
87
41.7k
    if (psResult == nullptr)
88
0
    {
89
0
        return nullptr;
90
0
    }
91
41.7k
    if (psResult->nStatus != 0 || psResult->pszErrBuf != nullptr)
92
41.6k
    {
93
41.6k
        CPLError(CE_Failure, CPLE_AppDefined,
94
41.6k
                 "Error returned by server : %s (%d)",
95
41.6k
                 (psResult->pszErrBuf) ? psResult->pszErrBuf : "unknown",
96
41.6k
                 psResult->nStatus);
97
41.6k
        CPLHTTPDestroyResult(psResult);
98
41.6k
        return nullptr;
99
41.6k
    }
100
12
    if (psResult->pabyData == nullptr)
101
12
    {
102
12
        CPLError(CE_Failure, CPLE_AppDefined,
103
12
                 "Empty content returned by server");
104
12
        CPLHTTPDestroyResult(psResult);
105
12
        return nullptr;
106
12
    }
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, "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, "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
15
    {
374
15
        const char *pszVal =
375
15
            CPLGetXMLValue(psNode, bIsWMS130 ? "CRS" : "SRS", nullptr);
376
15
        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
15
        return pszVal;
396
15
    };
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
127
    while (psIter != nullptr)
403
127
    {
404
127
        if (psIter->eType == CXT_Element &&
405
87
            strcmp(psIter->pszValue, "BoundingBox") == 0)
406
15
        {
407
15
            psSRS = psIter;
408
15
            pszSRSLocal = GetCRS(psSRS);
409
15
            if (osPreferredSRS.empty() || pszSRSLocal == nullptr)
410
15
                break;
411
0
            if (EQUAL(osPreferredSRS, pszSRSLocal))
412
0
                break;
413
0
            psSRS = nullptr;
414
0
            pszSRSLocal = nullptr;
415
0
        }
416
112
        psIter = psIter->psNext;
417
112
    }
418
419
15
    if (psSRS == nullptr)
420
0
    {
421
0
        psSRS = CPLGetXMLNode(psXML, "LatLonBoundingBox");
422
0
        pszSRSLocal = GetCRS(psSRS);
423
0
        if (pszSRSLocal == nullptr)
424
0
            pszSRSLocal = "EPSG:4326";
425
0
    }
426
427
15
    if (pszSRSLocal != nullptr && psSRS != nullptr)
428
14
    {
429
14
        pszMinXLocal = CPLGetXMLValue(psSRS, "minx", nullptr);
430
14
        pszMinYLocal = CPLGetXMLValue(psSRS, "miny", nullptr);
431
14
        pszMaxXLocal = CPLGetXMLValue(psSRS, "maxx", nullptr);
432
14
        pszMaxYLocal = CPLGetXMLValue(psSRS, "maxy", nullptr);
433
434
14
        if (pszMinXLocal && pszMinYLocal && pszMaxXLocal && pszMaxYLocal)
435
14
        {
436
14
            pszSRS = pszSRSLocal;
437
14
            pszMinX = pszMinXLocal;
438
14
            pszMinY = pszMinYLocal;
439
14
            pszMaxX = pszMaxXLocal;
440
14
            pszMaxY = pszMaxYLocal;
441
14
        }
442
14
    }
443
444
15
    if (pszName != nullptr && pszSRS && pszMinX && pszMinY && pszMaxX &&
445
15
        pszMaxY)
446
15
    {
447
15
        CPLString osLocalTransparent(osTransparent);
448
15
        if (osLocalTransparent.empty())
449
15
        {
450
15
            const char *pszOpaque = CPLGetXMLValue(psXML, "opaque", "0");
451
15
            if (EQUAL(pszOpaque, "1"))
452
0
                osLocalTransparent = "FALSE";
453
15
        }
454
455
15
        WMSCKeyType oWMSCKey(pszName, pszSRS);
456
15
        std::map<WMSCKeyType, WMSCTileSetDesc>::iterator oIter =
457
15
            osMapWMSCTileSet.find(oWMSCKey);
458
15
        if (oIter != osMapWMSCTileSet.end())
459
0
        {
460
0
            AddWMSCSubDataset(oIter->second, pszTitle, osLocalTransparent);
461
0
        }
462
15
        else
463
15
        {
464
15
            AddSubDataset(pszName, pszTitle, pszAbstract, pszSRS, pszMinX,
465
15
                          pszMinY, pszMaxX, pszMaxY, osFormat,
466
15
                          osLocalTransparent);
467
15
        }
468
15
    }
469
470
15
    psIter = psXML->psChild;
471
178
    for (; psIter != nullptr; psIter = psIter->psNext)
472
163
    {
473
163
        if (psIter->eType == CXT_Element)
474
123
        {
475
123
            if (EQUAL(psIter->pszValue, "Layer"))
476
12
                ExploreLayer(psIter, osFormat, osTransparent, osPreferredSRS,
477
12
                             pszSRS, pszMinX, pszMinY, pszMaxX, pszMaxY);
478
123
        }
479
163
    }
480
15
}
481
482
/************************************************************************/
483
/*                         ParseWMSCTileSets()                          */
484
/************************************************************************/
485
486
void GDALWMSMetaDataset::ParseWMSCTileSets(CPLXMLNode *psXML)
487
0
{
488
0
    CPLXMLNode *psIter = psXML->psChild;
489
0
    for (; psIter; psIter = psIter->psNext)
490
0
    {
491
0
        if (psIter->eType == CXT_Element && EQUAL(psIter->pszValue, "TileSet"))
492
0
        {
493
0
            const char *pszSRS = CPLGetXMLValue(psIter, "SRS", nullptr);
494
0
            if (pszSRS == nullptr)
495
0
                continue;
496
497
0
            CPLXMLNode *psBoundingBox = CPLGetXMLNode(psIter, "BoundingBox");
498
0
            if (psBoundingBox == nullptr)
499
0
                continue;
500
501
0
            const char *pszMinX =
502
0
                CPLGetXMLValue(psBoundingBox, "minx", nullptr);
503
0
            const char *pszMinY =
504
0
                CPLGetXMLValue(psBoundingBox, "miny", nullptr);
505
0
            const char *pszMaxX =
506
0
                CPLGetXMLValue(psBoundingBox, "maxx", nullptr);
507
0
            const char *pszMaxY =
508
0
                CPLGetXMLValue(psBoundingBox, "maxy", nullptr);
509
0
            if (pszMinX == nullptr || pszMinY == nullptr ||
510
0
                pszMaxX == nullptr || pszMaxY == nullptr)
511
0
                continue;
512
513
0
            double dfMinX = CPLAtofM(pszMinX);
514
0
            double dfMinY = CPLAtofM(pszMinY);
515
0
            double dfMaxX = CPLAtofM(pszMaxX);
516
0
            double dfMaxY = CPLAtofM(pszMaxY);
517
0
            if (dfMaxY <= dfMinY || dfMaxX <= dfMinX)
518
0
                continue;
519
520
0
            const char *pszFormat = CPLGetXMLValue(psIter, "Format", nullptr);
521
0
            if (pszFormat == nullptr)
522
0
                continue;
523
0
            if (strstr(pszFormat, "kml"))
524
0
                continue;
525
526
0
            const char *pszTileWidth = CPLGetXMLValue(psIter, "Width", nullptr);
527
0
            const char *pszTileHeight =
528
0
                CPLGetXMLValue(psIter, "Height", nullptr);
529
0
            if (pszTileWidth == nullptr || pszTileHeight == nullptr)
530
0
                continue;
531
532
0
            int nTileWidth = atoi(pszTileWidth);
533
0
            int nTileHeight = atoi(pszTileHeight);
534
0
            if (nTileWidth < 128 || nTileHeight < 128)
535
0
                continue;
536
537
0
            const char *pszLayers = CPLGetXMLValue(psIter, "Layers", nullptr);
538
0
            if (pszLayers == nullptr)
539
0
                continue;
540
541
0
            const char *pszResolutions =
542
0
                CPLGetXMLValue(psIter, "Resolutions", nullptr);
543
0
            if (pszResolutions == nullptr)
544
0
                continue;
545
0
            char **papszTokens =
546
0
                CSLTokenizeStringComplex(pszResolutions, " ", 0, 0);
547
0
            double dfMinResolution = 0;
548
0
            int i;
549
0
            for (i = 0; papszTokens && papszTokens[i]; i++)
550
0
            {
551
0
                double dfResolution = CPLAtofM(papszTokens[i]);
552
0
                if (i == 0 || dfResolution < dfMinResolution)
553
0
                    dfMinResolution = dfResolution;
554
0
            }
555
0
            CSLDestroy(papszTokens);
556
0
            int nResolutions = i;
557
0
            if (nResolutions == 0)
558
0
                continue;
559
560
0
            const char *pszStyles = CPLGetXMLValue(psIter, "Styles", "");
561
562
            /* http://demo.opengeo.org/geoserver/gwc/service/wms?tiled=TRUE&SERVICE=WMS&VERSION=1.1.1&REQUEST=GetCapabilities
563
             */
564
            /* has different variations of formats for the same (formats, SRS)
565
             * tuple, so just */
566
            /* keep the first one which is a png format */
567
0
            WMSCKeyType oWMSCKey(pszLayers, pszSRS);
568
0
            std::map<WMSCKeyType, WMSCTileSetDesc>::iterator oIter =
569
0
                osMapWMSCTileSet.find(oWMSCKey);
570
0
            if (oIter != osMapWMSCTileSet.end())
571
0
                continue;
572
573
0
            WMSCTileSetDesc oWMSCTileSet;
574
0
            oWMSCTileSet.osLayers = pszLayers;
575
0
            oWMSCTileSet.osSRS = pszSRS;
576
0
            oWMSCTileSet.osMinX = pszMinX;
577
0
            oWMSCTileSet.osMinY = pszMinY;
578
0
            oWMSCTileSet.osMaxX = pszMaxX;
579
0
            oWMSCTileSet.osMaxY = pszMaxY;
580
0
            oWMSCTileSet.dfMinX = dfMinX;
581
0
            oWMSCTileSet.dfMinY = dfMinY;
582
0
            oWMSCTileSet.dfMaxX = dfMaxX;
583
0
            oWMSCTileSet.dfMaxY = dfMaxY;
584
0
            oWMSCTileSet.nResolutions = nResolutions;
585
0
            oWMSCTileSet.dfMinResolution = dfMinResolution;
586
0
            oWMSCTileSet.osFormat = pszFormat;
587
0
            oWMSCTileSet.osStyle = pszStyles;
588
0
            oWMSCTileSet.nTileWidth = nTileWidth;
589
0
            oWMSCTileSet.nTileHeight = nTileHeight;
590
591
0
            osMapWMSCTileSet[oWMSCKey] = std::move(oWMSCTileSet);
592
0
        }
593
0
    }
594
0
}
595
596
/************************************************************************/
597
/*                       AnalyzeGetCapabilities()                       */
598
/************************************************************************/
599
600
GDALDataset *GDALWMSMetaDataset::AnalyzeGetCapabilities(
601
    CPLXMLNode *psXML, const std::string &osFormat,
602
    const std::string &osTransparent, const std::string &osPreferredSRS)
603
43
{
604
43
    const char *pszEncoding = nullptr;
605
43
    if (psXML->eType == CXT_Element && strcmp(psXML->pszValue, "?xml") == 0)
606
4
        pszEncoding = CPLGetXMLValue(psXML, "encoding", nullptr);
607
608
43
    CPLXMLNode *psRoot = CPLGetXMLNode(psXML, "=WMT_MS_Capabilities");
609
43
    if (psRoot == nullptr)
610
43
        psRoot = CPLGetXMLNode(psXML, "=WMS_Capabilities");
611
43
    if (psRoot == nullptr)
612
40
        return nullptr;
613
3
    CPLXMLNode *psCapability = CPLGetXMLNode(psRoot, "Capability");
614
3
    if (psCapability == nullptr)
615
0
        return nullptr;
616
617
3
    CPLXMLNode *psOnlineResource = CPLGetXMLNode(
618
3
        psCapability, "Request.GetMap.DCPType.HTTP.Get.OnlineResource");
619
3
    if (psOnlineResource == nullptr)
620
0
        return nullptr;
621
3
    const char *pszGetURL =
622
3
        CPLGetXMLValue(psOnlineResource, "xlink:href", nullptr);
623
3
    if (pszGetURL == nullptr)
624
0
        return nullptr;
625
626
3
    CPLXMLNode *psLayer = CPLGetXMLNode(psCapability, "Layer");
627
3
    if (psLayer == nullptr)
628
0
        return nullptr;
629
630
3
    CPLXMLNode *psVendorSpecificCapabilities =
631
3
        CPLGetXMLNode(psCapability, "VendorSpecificCapabilities");
632
633
3
    GDALWMSMetaDataset *poDS = new GDALWMSMetaDataset();
634
3
    const char *pszVersion = CPLGetXMLValue(psRoot, "version", nullptr);
635
3
    if (pszVersion)
636
3
        poDS->osVersion = pszVersion;
637
0
    else
638
0
        poDS->osVersion = "1.1.1";
639
3
    poDS->osGetURL = pszGetURL;
640
3
    poDS->osXMLEncoding = pszEncoding ? pszEncoding : "";
641
3
    if (psVendorSpecificCapabilities)
642
0
        poDS->ParseWMSCTileSets(psVendorSpecificCapabilities);
643
3
    poDS->ExploreLayer(psLayer, osFormat, osTransparent, osPreferredSRS);
644
645
3
    return poDS;
646
3
}
647
648
/************************************************************************/
649
/*                         AddTiledSubDataset()                         */
650
/************************************************************************/
651
652
// tiledWMS only
653
void GDALWMSMetaDataset::AddTiledSubDataset(const char *pszTiledGroupName,
654
                                            const char *pszTitle,
655
                                            const char *const *papszChanges)
656
2
{
657
2
    CPLString osSubdatasetName =
658
2
        "<GDAL_WMS><Service name=\"TiledWMS\"><ServerUrl>";
659
2
    osSubdatasetName += osGetURL;
660
2
    osSubdatasetName += "</ServerUrl><TiledGroupName>";
661
2
    osSubdatasetName += pszTiledGroupName;
662
2
    osSubdatasetName += "</TiledGroupName>";
663
664
2
    for (int i = 0; papszChanges != nullptr && papszChanges[i] != nullptr; i++)
665
0
    {
666
0
        char *key = nullptr;
667
0
        const char *value = CPLParseNameValue(papszChanges[i], &key);
668
0
        if (value != nullptr && key != nullptr)
669
0
            osSubdatasetName +=
670
0
                CPLSPrintf("<Change key=\"${%s}\">%s</Change>", key, value);
671
0
        CPLFree(key);
672
0
    }
673
674
2
    osSubdatasetName += "</Service></GDAL_WMS>";
675
676
2
    if (pszTitle)
677
2
    {
678
2
        if (!osXMLEncoding.empty() && osXMLEncoding != "utf-8" &&
679
0
            osXMLEncoding != "UTF-8")
680
0
        {
681
0
            char *pszRecodedTitle =
682
0
                CPLRecode(pszTitle, osXMLEncoding.c_str(), CPL_ENC_UTF8);
683
0
            if (pszRecodedTitle)
684
0
                AddSubDataset(osSubdatasetName, pszRecodedTitle);
685
0
            else
686
0
                AddSubDataset(osSubdatasetName, pszTitle);
687
0
            CPLFree(pszRecodedTitle);
688
0
        }
689
2
        else
690
2
        {
691
2
            AddSubDataset(osSubdatasetName, pszTitle);
692
2
        }
693
2
    }
694
0
    else
695
0
    {
696
0
        AddSubDataset(osSubdatasetName, pszTiledGroupName);
697
0
    }
698
2
}
699
700
/************************************************************************/
701
/*                    AnalyzeGetTileServiceRecurse()                    */
702
/************************************************************************/
703
// tiledWMS only
704
void GDALWMSMetaDataset::AnalyzeGetTileServiceRecurse(CPLXMLNode *psXML,
705
                                                      GDALOpenInfo *poOpenInfo)
706
1
{
707
    // Only list tiled groups that contain the string in the open option
708
    // TiledGroupName, if given
709
1
    char **papszLocalOpenOptions =
710
1
        poOpenInfo ? poOpenInfo->papszOpenOptions : nullptr;
711
1
    CPLString osMatch(
712
1
        CSLFetchNameValueDef(papszLocalOpenOptions, "TiledGroupName", ""));
713
1
    osMatch.toupper();
714
    // Also pass the change patterns, if provided
715
1
    char **papszChanges =
716
1
        CSLFetchNameValueMultiple(papszLocalOpenOptions, "Change");
717
718
1
    CPLXMLNode *psIter = psXML->psChild;
719
5
    for (; psIter != nullptr; psIter = psIter->psNext)
720
4
    {
721
4
        if (psIter->eType == CXT_Element &&
722
4
            EQUAL(psIter->pszValue, "TiledGroup"))
723
2
        {
724
2
            const char *pszName = CPLGetXMLValue(psIter, "Name", nullptr);
725
2
            if (pszName)
726
2
            {
727
2
                const char *pszTitle = CPLGetXMLValue(psIter, "Title", nullptr);
728
2
                if (osMatch.empty())
729
2
                {
730
2
                    AddTiledSubDataset(pszName, pszTitle, papszChanges);
731
2
                }
732
0
                else
733
0
                {
734
0
                    CPLString osNameUpper(pszName);
735
0
                    osNameUpper.toupper();
736
0
                    if (std::string::npos != osNameUpper.find(osMatch))
737
0
                        AddTiledSubDataset(pszName, pszTitle, papszChanges);
738
0
                }
739
2
            }
740
2
        }
741
2
        else if (psIter->eType == CXT_Element &&
742
2
                 EQUAL(psIter->pszValue, "TiledGroups"))
743
0
        {
744
0
            AnalyzeGetTileServiceRecurse(psIter, poOpenInfo);
745
0
        }
746
4
    }
747
1
    CPLFree(papszChanges);
748
1
}
749
750
/************************************************************************/
751
/*                       AnalyzeGetTileService()                        */
752
/************************************************************************/
753
// tiledWMS only
754
GDALDataset *GDALWMSMetaDataset::AnalyzeGetTileService(CPLXMLNode *psXML,
755
                                                       GDALOpenInfo *poOpenInfo)
756
26
{
757
26
    const char *pszEncoding = nullptr;
758
26
    if (psXML->eType == CXT_Element && strcmp(psXML->pszValue, "?xml") == 0)
759
0
        pszEncoding = CPLGetXMLValue(psXML, "encoding", nullptr);
760
761
26
    CPLXMLNode *psRoot = CPLGetXMLNode(psXML, "=WMS_Tile_Service");
762
26
    if (psRoot == nullptr)
763
25
        return nullptr;
764
1
    CPLXMLNode *psTiledPatterns = CPLGetXMLNode(psRoot, "TiledPatterns");
765
1
    if (psTiledPatterns == nullptr)
766
0
        return nullptr;
767
768
1
    const char *pszURL =
769
1
        CPLGetXMLValue(psTiledPatterns, "OnlineResource.xlink:href", nullptr);
770
1
    if (pszURL == nullptr)
771
0
        return nullptr;
772
773
1
    GDALWMSMetaDataset *poDS = new GDALWMSMetaDataset();
774
1
    poDS->osGetURL = pszURL;
775
1
    poDS->osXMLEncoding = pszEncoding ? pszEncoding : "";
776
777
1
    poDS->AnalyzeGetTileServiceRecurse(psTiledPatterns, poOpenInfo);
778
779
1
    return poDS;
780
1
}
781
782
/************************************************************************/
783
/*                       AnalyzeTileMapService()                        */
784
/************************************************************************/
785
786
GDALDataset *GDALWMSMetaDataset::AnalyzeTileMapService(CPLXMLNode *psXML)
787
5
{
788
5
    CPLXMLNode *psRoot = CPLGetXMLNode(psXML, "=TileMapService");
789
5
    if (psRoot == nullptr)
790
4
        return nullptr;
791
1
    CPLXMLNode *psTileMaps = CPLGetXMLNode(psRoot, "TileMaps");
792
1
    if (psTileMaps == nullptr)
793
1
        return nullptr;
794
795
0
    GDALWMSMetaDataset *poDS = new GDALWMSMetaDataset();
796
797
0
    CPLXMLNode *psIter = psTileMaps->psChild;
798
0
    for (; psIter != nullptr; psIter = psIter->psNext)
799
0
    {
800
0
        if (psIter->eType == CXT_Element && EQUAL(psIter->pszValue, "TileMap"))
801
0
        {
802
0
            const char *pszHref = CPLGetXMLValue(psIter, "href", nullptr);
803
0
            const char *pszTitle = CPLGetXMLValue(psIter, "title", nullptr);
804
0
            if (pszHref && pszTitle)
805
0
            {
806
0
                CPLString osHref(pszHref);
807
0
                const char *pszDup100 = strstr(pszHref, "1.0.0/1.0.0/");
808
0
                if (pszDup100)
809
0
                {
810
0
                    osHref.resize(pszDup100 - pszHref);
811
0
                    osHref += pszDup100 + strlen("1.0.0/");
812
0
                }
813
0
                poDS->AddSubDataset(osHref, pszTitle);
814
0
            }
815
0
        }
816
0
    }
817
818
0
    return poDS;
819
1
}