Coverage Report

Created: 2026-06-30 08:33

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/frmts/wms/wmsdriver.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  WMS Client Driver
4
 * Purpose:  Implementation of Dataset and RasterBand classes for WMS
5
 *           and other similar services.
6
 * Author:   Adam Nowacki, nowak@xpam.de
7
 *
8
 ******************************************************************************
9
 * Copyright (c) 2007, Adam Nowacki
10
 * Copyright (c) 2009-2014, Even Rouault <even dot rouault at spatialys.com>
11
 *
12
 * SPDX-License-Identifier: MIT
13
 ****************************************************************************/
14
15
#include "gdal_frmts.h"
16
#include "wmsdriver.h"
17
#include "wmsmetadataset.h"
18
19
#include "minidriver_wms.h"
20
#include "minidriver_tileservice.h"
21
#include "minidriver_worldwind.h"
22
#include "minidriver_tms.h"
23
#include "minidriver_tiled_wms.h"
24
#include "minidriver_virtualearth.h"
25
#include "minidriver_arcgis_server.h"
26
#include "minidriver_iiifimage.h"
27
#include "minidriver_iip.h"
28
#include "minidriver_mrf.h"
29
#include "minidriver_ogcapimaps.h"
30
#include "minidriver_ogcapicoverage.h"
31
#include "wmsdrivercore.h"
32
33
#include "cpl_json.h"
34
35
#include <limits>
36
#include <utility>
37
#include <algorithm>
38
39
//
40
// A static map holding seen server GetTileService responses, per process
41
// It makes opening and reopening rasters from the same server faster
42
//
43
GDALWMSDataset::StringMap_t GDALWMSDataset::cfg;
44
CPLMutex *GDALWMSDataset::cfgmtx = nullptr;
45
46
17.6k
WMSMiniDriver::~WMSMiniDriver() = default;
47
0
WMSMiniDriverFactory::~WMSMiniDriverFactory() = default;
48
270
GDALWMSCacheImpl::~GDALWMSCacheImpl() = default;
49
50
/************************************************************************/
51
/*                   GDALWMSDatasetGetConfigFromURL()                   */
52
/************************************************************************/
53
54
static CPLXMLNode *GDALWMSDatasetGetConfigFromURL(GDALOpenInfo *poOpenInfo)
55
17.4k
{
56
17.4k
    const char *pszBaseURL = poOpenInfo->pszFilename;
57
17.4k
    if (STARTS_WITH_CI(pszBaseURL, "WMS:"))
58
161
        pszBaseURL += strlen("WMS:");
59
60
17.4k
    const CPLString osLayer = CPLURLGetValue(pszBaseURL, "LAYERS");
61
17.4k
    CPLString osVersion = CPLURLGetValue(pszBaseURL, "VERSION");
62
17.4k
    CPLString osSRS = CPLURLGetValue(pszBaseURL, "SRS");
63
17.4k
    CPLString osCRS = CPLURLGetValue(pszBaseURL, "CRS");
64
17.4k
    CPLString osBBOX = CPLURLGetValue(pszBaseURL, "BBOX");
65
17.4k
    CPLString osFormat = CPLURLGetValue(pszBaseURL, "FORMAT");
66
17.4k
    const CPLString osTransparent = CPLURLGetValue(pszBaseURL, "TRANSPARENT");
67
68
    /* GDAL specific extensions to alter the default settings */
69
17.4k
    const CPLString osOverviewCount =
70
17.4k
        CPLURLGetValue(pszBaseURL, "OVERVIEWCOUNT");
71
17.4k
    const CPLString osTileSize = CPLURLGetValue(pszBaseURL, "TILESIZE");
72
17.4k
    const CPLString osMinResolution =
73
17.4k
        CPLURLGetValue(pszBaseURL, "MINRESOLUTION");
74
17.4k
    CPLString osBBOXOrder = CPLURLGetValue(pszBaseURL, "BBOXORDER");
75
76
17.4k
    CPLString osBaseURL = pszBaseURL;
77
    /* Remove all keywords to get base URL */
78
79
17.4k
    if (osBBOXOrder.empty() && !osCRS.empty() &&
80
0
        VersionStringToInt(osVersion.c_str()) >= VersionStringToInt("1.3.0"))
81
0
    {
82
0
        OGRSpatialReference oSRS;
83
0
        oSRS.SetFromUserInput(
84
0
            osCRS, OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS_get());
85
0
        oSRS.AutoIdentifyEPSG();
86
0
        if (oSRS.EPSGTreatsAsLatLong() || oSRS.EPSGTreatsAsNorthingEasting())
87
0
        {
88
0
            osBBOXOrder = "yxYX";
89
0
        }
90
0
    }
91
92
17.4k
    osBaseURL = CPLURLAddKVP(osBaseURL, "VERSION", nullptr);
93
17.4k
    osBaseURL = CPLURLAddKVP(osBaseURL, "REQUEST", nullptr);
94
17.4k
    osBaseURL = CPLURLAddKVP(osBaseURL, "LAYERS", nullptr);
95
17.4k
    osBaseURL = CPLURLAddKVP(osBaseURL, "SRS", nullptr);
96
17.4k
    osBaseURL = CPLURLAddKVP(osBaseURL, "CRS", nullptr);
97
17.4k
    osBaseURL = CPLURLAddKVP(osBaseURL, "BBOX", nullptr);
98
17.4k
    osBaseURL = CPLURLAddKVP(osBaseURL, "FORMAT", nullptr);
99
17.4k
    osBaseURL = CPLURLAddKVP(osBaseURL, "TRANSPARENT", nullptr);
100
17.4k
    osBaseURL = CPLURLAddKVP(osBaseURL, "STYLES", nullptr);
101
17.4k
    osBaseURL = CPLURLAddKVP(osBaseURL, "WIDTH", nullptr);
102
17.4k
    osBaseURL = CPLURLAddKVP(osBaseURL, "HEIGHT", nullptr);
103
104
17.4k
    osBaseURL = CPLURLAddKVP(osBaseURL, "OVERVIEWCOUNT", nullptr);
105
17.4k
    osBaseURL = CPLURLAddKVP(osBaseURL, "TILESIZE", nullptr);
106
17.4k
    osBaseURL = CPLURLAddKVP(osBaseURL, "MINRESOLUTION", nullptr);
107
17.4k
    osBaseURL = CPLURLAddKVP(osBaseURL, "BBOXORDER", nullptr);
108
109
17.4k
    if (!osBaseURL.empty() && osBaseURL.back() == '&')
110
348
        osBaseURL.pop_back();
111
112
17.4k
    if (osVersion.empty())
113
17.3k
        osVersion = "1.1.1";
114
115
17.4k
    CPLString osSRSTag;
116
17.4k
    CPLString osSRSValue;
117
17.4k
    if (VersionStringToInt(osVersion.c_str()) >= VersionStringToInt("1.3.0"))
118
0
    {
119
0
        if (!osSRS.empty())
120
0
        {
121
0
            CPLError(CE_Warning, CPLE_AppDefined,
122
0
                     "WMS version 1.3 and above expects CRS however SRS was "
123
0
                     "set instead.");
124
0
        }
125
0
        osSRSValue = std::move(osCRS);
126
0
        osSRSTag = "CRS";
127
0
    }
128
17.4k
    else
129
17.4k
    {
130
17.4k
        if (!osCRS.empty())
131
0
        {
132
0
            CPLError(CE_Warning, CPLE_AppDefined,
133
0
                     "WMS version 1.1.1 and below expects SRS however CRS was "
134
0
                     "set instead.");
135
0
        }
136
17.4k
        osSRSValue = std::move(osSRS);
137
17.4k
        osSRSTag = "SRS";
138
17.4k
    }
139
140
17.4k
    if (osSRSValue.empty())
141
17.3k
    {
142
17.3k
        osSRSValue = "EPSG:4326";
143
144
17.3k
        if (osBBOX.empty())
145
17.3k
        {
146
17.3k
            if (osBBOXOrder.compare("yxYX") == 0)
147
0
                osBBOX = "-90,-180,90,180";
148
17.3k
            else
149
17.3k
                osBBOX = "-180,-90,180,90";
150
17.3k
        }
151
17.3k
    }
152
141
    else
153
141
    {
154
141
        if (osBBOX.empty())
155
24
        {
156
24
            OGRSpatialReference oSRS;
157
24
            oSRS.SetFromUserInput(osSRSValue);
158
24
            oSRS.AutoIdentifyEPSG();
159
160
24
            double dfWestLongitudeDeg, dfSouthLatitudeDeg, dfEastLongitudeDeg,
161
24
                dfNorthLatitudeDeg;
162
24
            if (!oSRS.GetAreaOfUse(&dfWestLongitudeDeg, &dfSouthLatitudeDeg,
163
24
                                   &dfEastLongitudeDeg, &dfNorthLatitudeDeg,
164
24
                                   nullptr))
165
21
            {
166
21
                CPLError(CE_Failure, CPLE_AppDefined,
167
21
                         "Failed retrieving a default bounding box for the "
168
21
                         "requested SRS");
169
21
                return nullptr;
170
21
            }
171
3
            auto poCT = std::unique_ptr<OGRCoordinateTransformation>(
172
3
                OGRCreateCoordinateTransformation(
173
3
                    OGRSpatialReference::GetWGS84SRS(), &oSRS));
174
3
            if (!poCT)
175
0
            {
176
0
                CPLError(CE_Failure, CPLE_AppDefined,
177
0
                         "Failed creating a coordinate transformation for the "
178
0
                         "requested SRS");
179
0
                return nullptr;
180
0
            }
181
3
            if (!poCT->Transform(1, &dfWestLongitudeDeg, &dfNorthLatitudeDeg) ||
182
3
                !poCT->Transform(1, &dfEastLongitudeDeg, &dfSouthLatitudeDeg))
183
0
            {
184
0
                CPLError(
185
0
                    CE_Failure, CPLE_AppDefined,
186
0
                    "Failed transforming coordinates to the requested SRS");
187
0
                return nullptr;
188
0
            }
189
3
            const double dfMaxX =
190
3
                std::max(dfWestLongitudeDeg, dfEastLongitudeDeg);
191
3
            const double dfMinX =
192
3
                std::min(dfWestLongitudeDeg, dfEastLongitudeDeg);
193
3
            const double dfMaxY =
194
3
                std::max(dfNorthLatitudeDeg, dfSouthLatitudeDeg);
195
3
            const double dfMinY =
196
3
                std::min(dfNorthLatitudeDeg, dfSouthLatitudeDeg);
197
3
            if (osBBOXOrder.compare("yxYX") == 0)
198
0
            {
199
0
                osBBOX = CPLSPrintf("%lf,%lf,%lf,%lf", dfMinY, dfMinX, dfMaxY,
200
0
                                    dfMaxX);
201
0
            }
202
3
            else
203
3
            {
204
3
                osBBOX = CPLSPrintf("%lf,%lf,%lf,%lf", dfMinX, dfMinY, dfMaxX,
205
3
                                    dfMaxY);
206
3
            }
207
3
        }
208
141
    }
209
210
17.4k
    char **papszTokens = CSLTokenizeStringComplex(osBBOX, ",", 0, 0);
211
17.4k
    if (CSLCount(papszTokens) != 4)
212
16
    {
213
16
        CSLDestroy(papszTokens);
214
16
        return nullptr;
215
16
    }
216
17.4k
    const char *pszMinX = papszTokens[0];
217
17.4k
    const char *pszMinY = papszTokens[1];
218
17.4k
    const char *pszMaxX = papszTokens[2];
219
17.4k
    const char *pszMaxY = papszTokens[3];
220
221
17.4k
    if (osBBOXOrder.compare("yxYX") == 0)
222
0
    {
223
0
        std::swap(pszMinX, pszMinY);
224
0
        std::swap(pszMaxX, pszMaxY);
225
0
    }
226
227
17.4k
    double dfMinX = CPLAtofM(pszMinX);
228
17.4k
    double dfMinY = CPLAtofM(pszMinY);
229
17.4k
    double dfMaxX = CPLAtofM(pszMaxX);
230
17.4k
    double dfMaxY = CPLAtofM(pszMaxY);
231
232
17.4k
    if (dfMaxY <= dfMinY || dfMaxX <= dfMinX)
233
2
    {
234
2
        CSLDestroy(papszTokens);
235
2
        return nullptr;
236
2
    }
237
238
17.4k
    int nTileSize = atoi(osTileSize);
239
17.4k
    if (nTileSize <= 128 || nTileSize > 2048)
240
17.4k
        nTileSize = 1024;
241
242
17.4k
    int nXSize, nYSize;
243
17.4k
    double dXSize, dYSize;
244
245
17.4k
    int nOverviewCount = (osOverviewCount.size()) ? atoi(osOverviewCount) : 20;
246
247
17.4k
    if (!osMinResolution.empty())
248
0
    {
249
0
        double dfMinResolution = CPLAtofM(osMinResolution);
250
251
0
        while (nOverviewCount > 20)
252
0
        {
253
0
            nOverviewCount--;
254
0
            dfMinResolution *= 2;
255
0
        }
256
257
        // Determine a suitable size that doesn't overflow max int.
258
0
        dXSize = ((dfMaxX - dfMinX) / dfMinResolution + 0.5);
259
0
        dYSize = ((dfMaxY - dfMinY) / dfMinResolution + 0.5);
260
261
0
        while (dXSize > (std::numeric_limits<int>::max)() ||
262
0
               dYSize > (std::numeric_limits<int>::max)())
263
0
        {
264
0
            dfMinResolution *= 2;
265
266
0
            dXSize = ((dfMaxX - dfMinX) / dfMinResolution + 0.5);
267
0
            dYSize = ((dfMaxY - dfMinY) / dfMinResolution + 0.5);
268
0
        }
269
0
    }
270
17.4k
    else
271
17.4k
    {
272
17.4k
        double dfRatio = (dfMaxX - dfMinX) / (dfMaxY - dfMinY);
273
17.4k
        if (dfRatio > 1)
274
17.4k
        {
275
17.4k
            dXSize = nTileSize;
276
17.4k
            dYSize = dXSize / dfRatio;
277
17.4k
        }
278
8
        else
279
8
        {
280
8
            dYSize = nTileSize;
281
8
            dXSize = dYSize * dfRatio;
282
8
        }
283
284
17.4k
        if (nOverviewCount < 0 || nOverviewCount > 20)
285
0
            nOverviewCount = 20;
286
287
17.4k
        dXSize = dXSize * (1 << nOverviewCount);
288
17.4k
        dYSize = dYSize * (1 << nOverviewCount);
289
290
        // Determine a suitable size that doesn't overflow max int.
291
17.4k
        while (dXSize > (std::numeric_limits<int>::max)() ||
292
17.4k
               dYSize > (std::numeric_limits<int>::max)())
293
0
        {
294
0
            dXSize /= 2;
295
0
            dYSize /= 2;
296
0
        }
297
17.4k
    }
298
299
17.4k
    nXSize = static_cast<int>(dXSize);
300
17.4k
    nYSize = static_cast<int>(dYSize);
301
302
17.4k
    bool bTransparent = !osTransparent.empty() && CPLTestBool(osTransparent);
303
304
17.4k
    if (osFormat.empty())
305
17.4k
    {
306
17.4k
        if (!bTransparent)
307
17.4k
        {
308
17.4k
            osFormat = "image/jpeg";
309
17.4k
        }
310
0
        else
311
0
        {
312
0
            osFormat = "image/png";
313
0
        }
314
17.4k
    }
315
316
17.4k
    char *pszEscapedURL = CPLEscapeString(osBaseURL.c_str(), -1, CPLES_XML);
317
17.4k
    char *pszEscapedLayerXML = CPLEscapeString(osLayer.c_str(), -1, CPLES_XML);
318
319
17.4k
    CPLString osXML = CPLSPrintf(
320
17.4k
        "<GDAL_WMS>\n"
321
17.4k
        "  <Service name=\"WMS\">\n"
322
17.4k
        "    <Version>%s</Version>\n"
323
17.4k
        "    <ServerUrl>%s</ServerUrl>\n"
324
17.4k
        "    <Layers>%s</Layers>\n"
325
17.4k
        "    <%s>%s</%s>\n"
326
17.4k
        "    <ImageFormat>%s</ImageFormat>\n"
327
17.4k
        "    <Transparent>%s</Transparent>\n"
328
17.4k
        "    <BBoxOrder>%s</BBoxOrder>\n"
329
17.4k
        "  </Service>\n"
330
17.4k
        "  <DataWindow>\n"
331
17.4k
        "    <UpperLeftX>%s</UpperLeftX>\n"
332
17.4k
        "    <UpperLeftY>%s</UpperLeftY>\n"
333
17.4k
        "    <LowerRightX>%s</LowerRightX>\n"
334
17.4k
        "    <LowerRightY>%s</LowerRightY>\n"
335
17.4k
        "    <SizeX>%d</SizeX>\n"
336
17.4k
        "    <SizeY>%d</SizeY>\n"
337
17.4k
        "  </DataWindow>\n"
338
17.4k
        "  <BandsCount>%d</BandsCount>\n"
339
17.4k
        "  <BlockSizeX>%d</BlockSizeX>\n"
340
17.4k
        "  <BlockSizeY>%d</BlockSizeY>\n"
341
17.4k
        "  <OverviewCount>%d</OverviewCount>\n"
342
17.4k
        "</GDAL_WMS>\n",
343
17.4k
        osVersion.c_str(), pszEscapedURL, pszEscapedLayerXML, osSRSTag.c_str(),
344
17.4k
        osSRSValue.c_str(), osSRSTag.c_str(), osFormat.c_str(),
345
17.4k
        (bTransparent) ? "TRUE" : "FALSE",
346
17.4k
        (osBBOXOrder.size()) ? osBBOXOrder.c_str() : "xyXY", pszMinX, pszMaxY,
347
17.4k
        pszMaxX, pszMinY, nXSize, nYSize, (bTransparent) ? 4 : 3, nTileSize,
348
17.4k
        nTileSize, nOverviewCount);
349
350
17.4k
    CPLFree(pszEscapedURL);
351
17.4k
    CPLFree(pszEscapedLayerXML);
352
353
17.4k
    CSLDestroy(papszTokens);
354
355
17.4k
    CPLDebug("WMS", "Opening WMS :\n%s", osXML.c_str());
356
357
17.4k
    return CPLParseXMLString(osXML);
358
17.4k
}
359
360
/************************************************************************/
361
/*                 GDALWMSDatasetGetConfigFromTileMap()                 */
362
/************************************************************************/
363
364
static CPLXMLNode *GDALWMSDatasetGetConfigFromTileMap(CPLXMLNode *psXML)
365
5
{
366
5
    CPLXMLNode *psRoot = CPLGetXMLNode(psXML, "=TileMap");
367
5
    if (psRoot == nullptr)
368
5
        return nullptr;
369
370
0
    CPLXMLNode *psTileSets = CPLGetXMLNode(psRoot, "TileSets");
371
0
    if (psTileSets == nullptr)
372
0
        return nullptr;
373
374
0
    const char *pszURL = CPLGetXMLValue(psRoot, "tilemapservice", nullptr);
375
376
0
    int bCanChangeURL = TRUE;
377
378
0
    CPLString osURL;
379
0
    if (pszURL)
380
0
    {
381
0
        osURL = pszURL;
382
        /* Special hack for
383
         * http://tilecache.osgeo.org/wms-c/Basic.py/1.0.0/basic/ */
384
0
        if (strlen(pszURL) > 10 &&
385
0
            STARTS_WITH(pszURL,
386
0
                        "http://tilecache.osgeo.org/wms-c/Basic.py/1.0.0/") &&
387
0
            strcmp(pszURL + strlen(pszURL) - strlen("1.0.0/"), "1.0.0/") == 0)
388
0
        {
389
0
            osURL.resize(strlen(pszURL) - strlen("1.0.0/"));
390
0
            bCanChangeURL = FALSE;
391
0
        }
392
0
        osURL += "${z}/${x}/${y}.${format}";
393
0
    }
394
395
0
    const char *pszSRS = CPLGetXMLValue(psRoot, "SRS", nullptr);
396
0
    if (pszSRS == nullptr)
397
0
        return nullptr;
398
399
0
    CPLXMLNode *psBoundingBox = CPLGetXMLNode(psRoot, "BoundingBox");
400
0
    if (psBoundingBox == nullptr)
401
0
        return nullptr;
402
403
0
    const char *pszMinX = CPLGetXMLValue(psBoundingBox, "minx", nullptr);
404
0
    const char *pszMinY = CPLGetXMLValue(psBoundingBox, "miny", nullptr);
405
0
    const char *pszMaxX = CPLGetXMLValue(psBoundingBox, "maxx", nullptr);
406
0
    const char *pszMaxY = CPLGetXMLValue(psBoundingBox, "maxy", nullptr);
407
0
    if (pszMinX == nullptr || pszMinY == nullptr || pszMaxX == nullptr ||
408
0
        pszMaxY == nullptr)
409
0
        return nullptr;
410
411
0
    double dfMinX = CPLAtofM(pszMinX);
412
0
    double dfMinY = CPLAtofM(pszMinY);
413
0
    double dfMaxX = CPLAtofM(pszMaxX);
414
0
    double dfMaxY = CPLAtofM(pszMaxY);
415
0
    if (dfMaxY <= dfMinY || dfMaxX <= dfMinX)
416
0
        return nullptr;
417
418
0
    CPLXMLNode *psTileFormat = CPLGetXMLNode(psRoot, "TileFormat");
419
0
    if (psTileFormat == nullptr)
420
0
        return nullptr;
421
422
0
    const char *pszTileWidth = CPLGetXMLValue(psTileFormat, "width", nullptr);
423
0
    const char *pszTileHeight = CPLGetXMLValue(psTileFormat, "height", nullptr);
424
0
    const char *pszTileFormat =
425
0
        CPLGetXMLValue(psTileFormat, "extension", nullptr);
426
0
    if (pszTileWidth == nullptr || pszTileHeight == nullptr ||
427
0
        pszTileFormat == nullptr)
428
0
        return nullptr;
429
430
0
    int nTileWidth = atoi(pszTileWidth);
431
0
    int nTileHeight = atoi(pszTileHeight);
432
0
    if (nTileWidth < 128 || nTileHeight < 128)
433
0
        return nullptr;
434
435
0
    CPLXMLNode *psIter = psTileSets->psChild;
436
0
    int nLevelCount = 0;
437
0
    double dfPixelSize = 0;
438
0
    for (; psIter != nullptr; psIter = psIter->psNext)
439
0
    {
440
0
        if (psIter->eType == CXT_Element && EQUAL(psIter->pszValue, "TileSet"))
441
0
        {
442
0
            const char *pszOrder = CPLGetXMLValue(psIter, "order", nullptr);
443
0
            if (pszOrder == nullptr)
444
0
            {
445
0
                CPLDebug("WMS", "Cannot find order attribute");
446
0
                return nullptr;
447
0
            }
448
0
            if (atoi(pszOrder) != nLevelCount)
449
0
            {
450
0
                CPLDebug("WMS", "Expected order=%d, got %s", nLevelCount,
451
0
                         pszOrder);
452
0
                return nullptr;
453
0
            }
454
455
0
            const char *pszHref = CPLGetXMLValue(psIter, "href", nullptr);
456
0
            if (nLevelCount == 0 && pszHref != nullptr)
457
0
            {
458
0
                if (bCanChangeURL && strlen(pszHref) > 10 &&
459
0
                    strcmp(pszHref + strlen(pszHref) - strlen("/0"), "/0") == 0)
460
0
                {
461
0
                    osURL = pszHref;
462
0
                    osURL.resize(strlen(pszHref) - strlen("/0"));
463
0
                    osURL += "/${z}/${x}/${y}.${format}";
464
0
                }
465
0
            }
466
0
            const char *pszUnitsPerPixel =
467
0
                CPLGetXMLValue(psIter, "units-per-pixel", nullptr);
468
0
            if (pszUnitsPerPixel == nullptr)
469
0
                return nullptr;
470
0
            dfPixelSize = CPLAtofM(pszUnitsPerPixel);
471
472
0
            nLevelCount++;
473
0
        }
474
0
    }
475
476
0
    if (nLevelCount == 0 || osURL.empty())
477
0
        return nullptr;
478
479
0
    int nXSize = 0;
480
0
    int nYSize = 0;
481
482
0
    while (nLevelCount > 0)
483
0
    {
484
0
        double dfXSizeBig = (dfMaxX - dfMinX) / dfPixelSize + 0.5;
485
0
        double dfYSizeBig = (dfMaxY - dfMinY) / dfPixelSize + 0.5;
486
0
        if (dfXSizeBig < INT_MAX && dfYSizeBig < INT_MAX)
487
0
        {
488
0
            nXSize = static_cast<int>(dfXSizeBig);
489
0
            nYSize = static_cast<int>(dfYSizeBig);
490
0
            break;
491
0
        }
492
0
        CPLDebug(
493
0
            "WMS",
494
0
            "Dropping one overview level so raster size fits into 32bit...");
495
0
        dfPixelSize *= 2;
496
0
        nLevelCount--;
497
0
    }
498
499
0
    char *pszEscapedURL = CPLEscapeString(osURL.c_str(), -1, CPLES_XML);
500
501
0
    CPLString osXML = CPLSPrintf("<GDAL_WMS>\n"
502
0
                                 "  <Service name=\"TMS\">\n"
503
0
                                 "    <ServerUrl>%s</ServerUrl>\n"
504
0
                                 "    <Format>%s</Format>\n"
505
0
                                 "  </Service>\n"
506
0
                                 "  <DataWindow>\n"
507
0
                                 "    <UpperLeftX>%s</UpperLeftX>\n"
508
0
                                 "    <UpperLeftY>%s</UpperLeftY>\n"
509
0
                                 "    <LowerRightX>%s</LowerRightX>\n"
510
0
                                 "    <LowerRightY>%s</LowerRightY>\n"
511
0
                                 "    <TileLevel>%d</TileLevel>\n"
512
0
                                 "    <SizeX>%d</SizeX>\n"
513
0
                                 "    <SizeY>%d</SizeY>\n"
514
0
                                 "  </DataWindow>\n"
515
0
                                 "  <Projection>%s</Projection>\n"
516
0
                                 "  <BlockSizeX>%d</BlockSizeX>\n"
517
0
                                 "  <BlockSizeY>%d</BlockSizeY>\n"
518
0
                                 "  <BandsCount>%d</BandsCount>\n"
519
0
                                 "</GDAL_WMS>\n",
520
0
                                 pszEscapedURL, pszTileFormat, pszMinX, pszMaxY,
521
0
                                 pszMaxX, pszMinY, nLevelCount - 1, nXSize,
522
0
                                 nYSize, pszSRS, nTileWidth, nTileHeight, 3);
523
0
    CPLDebug("WMS", "Opening TMS :\n%s", osXML.c_str());
524
525
0
    CPLFree(pszEscapedURL);
526
527
0
    return CPLParseXMLString(osXML);
528
0
}
529
530
/************************************************************************/
531
/*               GDALWMSDatasetGetConfigFromArcGISJSON()                */
532
/************************************************************************/
533
534
static CPLXMLNode *GDALWMSDatasetGetConfigFromArcGISJSON(const char *pszURL,
535
                                                         const char *pszContent)
536
0
{
537
0
    CPLJSONDocument oDoc;
538
0
    if (!oDoc.LoadMemory(std::string(pszContent)))
539
0
        return nullptr;
540
0
    auto oRoot(oDoc.GetRoot());
541
0
    auto oTileInfo(oRoot["tileInfo"]);
542
0
    if (!oTileInfo.IsValid())
543
0
    {
544
0
        CPLDebug("WMS", "Did not get tileInfo");
545
0
        return nullptr;
546
0
    }
547
0
    int nTileWidth = oTileInfo.GetInteger("cols", -1);
548
0
    int nTileHeight = oTileInfo.GetInteger("rows", -1);
549
550
0
    auto oSpatialReference(oTileInfo["spatialReference"]);
551
0
    if (!oSpatialReference.IsValid())
552
0
    {
553
0
        CPLDebug("WMS", "Did not get spatialReference");
554
0
        return nullptr;
555
0
    }
556
0
    int nWKID = oSpatialReference.GetInteger("wkid", -1);
557
0
    int nLatestWKID = oSpatialReference.GetInteger("latestWkid", -1);
558
0
    CPLString osWKT(oSpatialReference.GetString("wkt"));
559
560
0
    auto oOrigin(oTileInfo["origin"]);
561
0
    if (!oOrigin.IsValid())
562
0
    {
563
0
        CPLDebug("WMS", "Did not get origin");
564
0
        return nullptr;
565
0
    }
566
0
    double dfMinX =
567
0
        oOrigin.GetDouble("x", std::numeric_limits<double>::infinity());
568
0
    double dfMaxY =
569
0
        oOrigin.GetDouble("y", std::numeric_limits<double>::infinity());
570
571
0
    auto oLods(oTileInfo["lods"].ToArray());
572
0
    if (!oLods.IsValid())
573
0
    {
574
0
        CPLDebug("WMS", "Did not get lods");
575
0
        return nullptr;
576
0
    }
577
0
    double dfBaseResolution = 0.0;
578
0
    for (int i = 0; i < oLods.Size(); i++)
579
0
    {
580
0
        if (oLods[i].GetInteger("level", -1) == 0)
581
0
        {
582
0
            dfBaseResolution = oLods[i].GetDouble("resolution");
583
0
            break;
584
0
        }
585
0
    }
586
587
0
    int nLevelCount = oLods.Size() - 1;
588
0
    if (nLevelCount < 1)
589
0
    {
590
0
        CPLDebug("WMS", "Did not get levels");
591
0
        return nullptr;
592
0
    }
593
594
0
    if (nTileWidth <= 0)
595
0
    {
596
0
        CPLDebug("WMS", "Did not get tile width");
597
0
        return nullptr;
598
0
    }
599
0
    if (nTileHeight <= 0)
600
0
    {
601
0
        CPLDebug("WMS", "Did not get tile height");
602
0
        return nullptr;
603
0
    }
604
0
    if (nWKID <= 0 && osWKT.empty())
605
0
    {
606
0
        CPLDebug("WMS", "Did not get WKID");
607
0
        return nullptr;
608
0
    }
609
0
    if (dfMinX == std::numeric_limits<double>::infinity())
610
0
    {
611
0
        CPLDebug("WMS", "Did not get min x");
612
0
        return nullptr;
613
0
    }
614
0
    if (dfMaxY == std::numeric_limits<double>::infinity())
615
0
    {
616
0
        CPLDebug("WMS", "Did not get max y");
617
0
        return nullptr;
618
0
    }
619
620
0
    if (nLatestWKID > 0)
621
0
        nWKID = nLatestWKID;
622
623
0
    if (nWKID == 102100)
624
0
        nWKID = 3857;
625
626
0
    const char *pszEndURL = strstr(pszURL, "/?f=json");
627
0
    if (pszEndURL == nullptr)
628
0
        pszEndURL = strstr(pszURL, "?f=json");
629
0
    CPLAssert(pszEndURL);
630
0
    CPLString osURL(pszURL);
631
0
    osURL.resize(pszEndURL - pszURL);
632
633
0
    double dfMaxX = dfMinX + dfBaseResolution * nTileWidth;
634
0
    double dfMinY = dfMaxY - dfBaseResolution * nTileHeight;
635
636
0
    int nTileCountX = 1;
637
0
    if (fabs(dfMinX - -180) < 1e-4 && fabs(dfMaxY - 90) < 1e-4 &&
638
0
        fabs(dfMinY - -90) < 1e-4)
639
0
    {
640
0
        nTileCountX = 2;
641
0
        dfMaxX = 180;
642
0
    }
643
644
0
    const int nLevelCountOri = nLevelCount;
645
0
    while (static_cast<double>(nTileCountX) * nTileWidth * (1 << nLevelCount) >
646
0
           INT_MAX)
647
0
        nLevelCount--;
648
0
    while (nLevelCount >= 0 &&
649
0
           static_cast<double>(nTileHeight) * (1 << nLevelCount) > INT_MAX)
650
0
        nLevelCount--;
651
0
    if (nLevelCount != nLevelCountOri)
652
0
        CPLDebug("WMS",
653
0
                 "Had to limit level count to %d instead of %d to stay within "
654
0
                 "GDAL raster size limits",
655
0
                 nLevelCount, nLevelCountOri);
656
657
0
    CPLString osEscapedWKT;
658
0
    if (nWKID < 0 && !osWKT.empty())
659
0
    {
660
0
        OGRSpatialReference oSRS;
661
0
        oSRS.importFromWkt(osWKT);
662
663
0
        const auto poSRSMatch = oSRS.FindBestMatch(100);
664
0
        if (poSRSMatch)
665
0
        {
666
0
            oSRS = *poSRSMatch;
667
0
            poSRSMatch->Release();
668
0
            const char *pszAuthName = oSRS.GetAuthorityName();
669
0
            const char *pszCode = oSRS.GetAuthorityCode();
670
0
            if (pszAuthName && EQUAL(pszAuthName, "EPSG") && pszCode)
671
0
                nWKID = atoi(pszCode);
672
0
        }
673
674
0
        char *pszWKT = nullptr;
675
0
        oSRS.exportToWkt(&pszWKT);
676
0
        osWKT = pszWKT;
677
0
        CPLFree(pszWKT);
678
679
0
        char *pszEscaped = CPLEscapeString(osWKT, -1, CPLES_XML);
680
0
        osEscapedWKT = pszEscaped;
681
0
        CPLFree(pszEscaped);
682
0
    }
683
684
0
    CPLString osXML = CPLSPrintf(
685
0
        "<GDAL_WMS>\n"
686
0
        "  <Service name=\"TMS\">\n"
687
0
        "    <ServerUrl>%s/tile/${z}/${y}/${x}</ServerUrl>\n"
688
0
        "  </Service>\n"
689
0
        "  <DataWindow>\n"
690
0
        "    <UpperLeftX>%.8f</UpperLeftX>\n"
691
0
        "    <UpperLeftY>%.8f</UpperLeftY>\n"
692
0
        "    <LowerRightX>%.8f</LowerRightX>\n"
693
0
        "    <LowerRightY>%.8f</LowerRightY>\n"
694
0
        "    <TileLevel>%d</TileLevel>\n"
695
0
        "    <TileCountX>%d</TileCountX>\n"
696
0
        "    <YOrigin>top</YOrigin>\n"
697
0
        "  </DataWindow>\n"
698
0
        "  <Projection>%s</Projection>\n"
699
0
        "  <BlockSizeX>%d</BlockSizeX>\n"
700
0
        "  <BlockSizeY>%d</BlockSizeY>\n"
701
0
        "  <Cache/>\n"
702
0
        "</GDAL_WMS>\n",
703
0
        osURL.c_str(), dfMinX, dfMaxY, dfMaxX, dfMinY, nLevelCount, nTileCountX,
704
0
        nWKID > 0 ? CPLSPrintf("EPSG:%d", nWKID) : osEscapedWKT.c_str(),
705
0
        nTileWidth, nTileHeight);
706
0
    CPLDebug("WMS", "Opening TMS :\n%s", osXML.c_str());
707
708
0
    return CPLParseXMLString(osXML);
709
0
}
710
711
/************************************************************************/
712
/*                                Open()                                */
713
/************************************************************************/
714
715
GDALDataset *GDALWMSDataset::Open(GDALOpenInfo *poOpenInfo)
716
78.9k
{
717
78.9k
    CPLXMLNode *config = nullptr;
718
78.9k
    CPLErr ret = CE_None;
719
720
78.9k
    const char *pszFilename = poOpenInfo->pszFilename;
721
78.9k
    const char *pabyHeader =
722
78.9k
        reinterpret_cast<const char *>(poOpenInfo->pabyHeader);
723
724
78.9k
    if (!WMSDriverIdentify(poOpenInfo))
725
0
        return nullptr;
726
727
78.9k
    if (poOpenInfo->nHeaderBytes == 0 &&
728
77.5k
        STARTS_WITH_CI(pszFilename, "<GDAL_WMS>"))
729
270
    {
730
270
        config = CPLParseXMLString(pszFilename);
731
270
    }
732
78.7k
    else if (poOpenInfo->nHeaderBytes >= 10 &&
733
1.41k
             STARTS_WITH_CI(pabyHeader, "<GDAL_WMS>"))
734
0
    {
735
0
        config = CPLParseXMLFile(pszFilename);
736
0
    }
737
78.7k
    else if (poOpenInfo->nHeaderBytes == 0 &&
738
77.2k
             (STARTS_WITH_CI(pszFilename, "WMS:http") ||
739
77.2k
              STARTS_WITH_CI(pszFilename, "http")) &&
740
292
             (strstr(pszFilename, "/MapServer?f=json") != nullptr ||
741
292
              strstr(pszFilename, "/MapServer/?f=json") != nullptr ||
742
292
              strstr(pszFilename, "/ImageServer?f=json") != nullptr ||
743
292
              strstr(pszFilename, "/ImageServer/?f=json") != nullptr))
744
0
    {
745
0
        if (STARTS_WITH_CI(pszFilename, "WMS:http"))
746
0
            pszFilename += 4;
747
0
        CPLString osURL(pszFilename);
748
0
        if (strstr(pszFilename, "&pretty=true") == nullptr)
749
0
            osURL += "&pretty=true";
750
0
        CPLHTTPResult *psResult = CPLHTTPFetch(osURL.c_str(), nullptr);
751
0
        if (psResult == nullptr)
752
0
            return nullptr;
753
0
        if (psResult->pabyData == nullptr)
754
0
        {
755
0
            CPLHTTPDestroyResult(psResult);
756
0
            return nullptr;
757
0
        }
758
0
        config = GDALWMSDatasetGetConfigFromArcGISJSON(
759
0
            osURL, reinterpret_cast<const char *>(psResult->pabyData));
760
0
        CPLHTTPDestroyResult(psResult);
761
0
    }
762
763
78.7k
    else if (poOpenInfo->nHeaderBytes == 0 &&
764
77.2k
             (STARTS_WITH_CI(pszFilename, "WMS:") ||
765
77.0k
              CPLString(pszFilename).ifind("SERVICE=WMS") !=
766
77.0k
                  std::string::npos ||
767
0
              (poOpenInfo->IsSingleAllowedDriver("WMS") &&
768
0
               (STARTS_WITH(poOpenInfo->pszFilename, "http://") ||
769
0
                STARTS_WITH(poOpenInfo->pszFilename, "https://")))))
770
77.2k
    {
771
77.2k
        CPLString osLayers = CPLURLGetValue(pszFilename, "LAYERS");
772
77.2k
        CPLString osRequest = CPLURLGetValue(pszFilename, "REQUEST");
773
77.2k
        if (!osLayers.empty())
774
17.4k
            config = GDALWMSDatasetGetConfigFromURL(poOpenInfo);
775
59.8k
        else if (EQUAL(osRequest, "GetTileService"))
776
0
            return GDALWMSMetaDataset::DownloadGetTileService(poOpenInfo);
777
59.8k
        else
778
59.8k
            return GDALWMSMetaDataset::DownloadGetCapabilities(poOpenInfo);
779
77.2k
    }
780
1.41k
    else if (poOpenInfo->nHeaderBytes != 0 &&
781
1.41k
             (strstr(pabyHeader, "<WMT_MS_Capabilities") != nullptr ||
782
1.36k
              strstr(pabyHeader, "<WMS_Capabilities") != nullptr ||
783
1.15k
              strstr(pabyHeader, "<!DOCTYPE WMT_MS_Capabilities") != nullptr))
784
842
    {
785
842
        CPLXMLNode *psXML = CPLParseXMLFile(pszFilename);
786
842
        if (psXML == nullptr)
787
765
            return nullptr;
788
77
        GDALDataset *poRet = GDALWMSMetaDataset::AnalyzeGetCapabilities(psXML);
789
77
        CPLDestroyXMLNode(psXML);
790
77
        return poRet;
791
842
    }
792
574
    else if (poOpenInfo->nHeaderBytes != 0 &&
793
574
             strstr(pabyHeader, "<WMS_Tile_Service") != nullptr)
794
297
    {
795
297
        CPLXMLNode *psXML = CPLParseXMLFile(pszFilename);
796
297
        if (psXML == nullptr)
797
241
            return nullptr;
798
56
        GDALDataset *poRet =
799
56
            GDALWMSMetaDataset::AnalyzeGetTileService(psXML, poOpenInfo);
800
56
        CPLDestroyXMLNode(psXML);
801
56
        return poRet;
802
297
    }
803
277
    else if (poOpenInfo->nHeaderBytes != 0 &&
804
277
             strstr(pabyHeader, "<TileMap version=\"1.0.0\"") != nullptr)
805
173
    {
806
173
        CPLXMLNode *psXML = CPLParseXMLFile(pszFilename);
807
173
        if (psXML == nullptr)
808
168
            return nullptr;
809
5
        config = GDALWMSDatasetGetConfigFromTileMap(psXML);
810
5
        CPLDestroyXMLNode(psXML);
811
5
    }
812
104
    else if (poOpenInfo->nHeaderBytes != 0 &&
813
104
             strstr(pabyHeader, "<Services") != nullptr &&
814
0
             strstr(pabyHeader, "<TileMapService version=\"1.0") != nullptr)
815
0
    {
816
0
        CPLXMLNode *psXML = CPLParseXMLFile(pszFilename);
817
0
        if (psXML == nullptr)
818
0
            return nullptr;
819
0
        CPLXMLNode *psRoot = CPLGetXMLNode(psXML, "=Services");
820
0
        GDALDataset *poRet = nullptr;
821
0
        if (psRoot)
822
0
        {
823
0
            CPLXMLNode *psTileMapService =
824
0
                CPLGetXMLNode(psRoot, "TileMapService");
825
0
            if (psTileMapService)
826
0
            {
827
0
                const char *pszHref =
828
0
                    CPLGetXMLValue(psTileMapService, "href", nullptr);
829
0
                if (pszHref)
830
0
                {
831
0
                    poRet = GDALDataset::Open(
832
0
                        pszHref, GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR);
833
0
                }
834
0
            }
835
0
        }
836
0
        CPLDestroyXMLNode(psXML);
837
0
        return poRet;
838
0
    }
839
104
    else if (poOpenInfo->nHeaderBytes != 0 &&
840
104
             strstr(pabyHeader, "<TileMapService version=\"1.0.0\"") != nullptr)
841
104
    {
842
104
        CPLXMLNode *psXML = CPLParseXMLFile(pszFilename);
843
104
        if (psXML == nullptr)
844
101
            return nullptr;
845
3
        GDALDataset *poRet = GDALWMSMetaDataset::AnalyzeTileMapService(psXML);
846
3
        CPLDestroyXMLNode(psXML);
847
3
        return poRet;
848
104
    }
849
0
    else if (poOpenInfo->nHeaderBytes == 0 &&
850
0
             STARTS_WITH_CI(pszFilename, "AGS:"))
851
0
    {
852
0
        return nullptr;
853
0
    }
854
0
    else if (poOpenInfo->nHeaderBytes == 0 &&
855
0
             STARTS_WITH_CI(pszFilename, "IIP:"))
856
0
    {
857
0
        CPLString osURL(pszFilename + 4);
858
0
        osURL += "&obj=Basic-Info";
859
0
        CPLHTTPResult *psResult = CPLHTTPFetch(osURL.c_str(), nullptr);
860
0
        if (psResult == nullptr)
861
0
            return nullptr;
862
0
        if (psResult->pabyData == nullptr)
863
0
        {
864
0
            CPLHTTPDestroyResult(psResult);
865
0
            return nullptr;
866
0
        }
867
0
        int nXSize, nYSize;
868
0
        const char *pszMaxSize = strstr(
869
0
            reinterpret_cast<const char *>(psResult->pabyData), "Max-size:");
870
0
        const char *pszResolutionNumber =
871
0
            strstr(reinterpret_cast<const char *>(psResult->pabyData),
872
0
                   "Resolution-number:");
873
0
        if (pszMaxSize &&
874
0
            sscanf(pszMaxSize + strlen("Max-size:"), "%d %d", &nXSize,
875
0
                   &nYSize) == 2 &&
876
0
            pszResolutionNumber)
877
0
        {
878
0
            int nResolutions =
879
0
                atoi(pszResolutionNumber + strlen("Resolution-number:"));
880
0
            char *pszEscapedURL =
881
0
                CPLEscapeString(pszFilename + 4, -1, CPLES_XML);
882
0
            CPLString osXML =
883
0
                CPLSPrintf("<GDAL_WMS>"
884
0
                           "    <Service name=\"IIP\">"
885
0
                           "        <ServerUrl>%s</ServerUrl>"
886
0
                           "    </Service>"
887
0
                           "    <DataWindow>"
888
0
                           "        <SizeX>%d</SizeX>"
889
0
                           "        <SizeY>%d</SizeY>"
890
0
                           "        <TileLevel>%d</TileLevel>"
891
0
                           "    </DataWindow>"
892
0
                           "    <BlockSizeX>256</BlockSizeX>"
893
0
                           "    <BlockSizeY>256</BlockSizeY>"
894
0
                           "    <BandsCount>3</BandsCount>"
895
0
                           "    <Cache />"
896
0
                           "</GDAL_WMS>",
897
0
                           pszEscapedURL, nXSize, nYSize, nResolutions - 1);
898
0
            config = CPLParseXMLString(osXML);
899
0
            CPLFree(pszEscapedURL);
900
0
        }
901
0
        CPLHTTPDestroyResult(psResult);
902
0
    }
903
0
    else if (poOpenInfo->nHeaderBytes == 0 &&
904
0
             STARTS_WITH_CI(pszFilename, "IIIF:"))
905
0
    {
906
        // Implements https://iiif.io/api/image/3.0/ "Image API 3.0"
907
908
0
        std::string osURL(pszFilename + strlen("IIIF:"));
909
0
        if (!osURL.empty() && osURL.back() == '/')
910
0
            osURL.pop_back();
911
0
        std::unique_ptr<CPLHTTPResult, decltype(&CPLHTTPDestroyResult)>
912
0
            psResult(CPLHTTPFetch((osURL + "/info.json").c_str(), nullptr),
913
0
                     CPLHTTPDestroyResult);
914
0
        if (!psResult || !psResult->pabyData)
915
0
            return nullptr;
916
0
        CPLJSONDocument oDoc;
917
0
        if (!oDoc.LoadMemory(
918
0
                reinterpret_cast<const char *>(psResult->pabyData)))
919
0
            return nullptr;
920
0
        const CPLJSONObject oRoot = oDoc.GetRoot();
921
0
        const int nWidth = oRoot.GetInteger("width");
922
0
        const int nHeight = oRoot.GetInteger("height");
923
0
        if (nWidth <= 0 || nHeight <= 0)
924
0
        {
925
0
            CPLError(CE_Failure, CPLE_AppDefined,
926
0
                     "'width' and/or 'height' missing or invalid");
927
0
            return nullptr;
928
0
        }
929
0
        int nBlockSizeX = 256;
930
0
        int nBlockSizeY = 256;
931
0
        const auto oTiles = oRoot.GetArray("tiles");
932
0
        int nLevelCount = 1;
933
0
        if (oTiles.Size() == 1)
934
0
        {
935
0
            nBlockSizeX = oTiles[0].GetInteger("width");
936
0
            nBlockSizeY = oTiles[0].GetInteger("height");
937
0
            if (nBlockSizeX <= 0 || nBlockSizeY <= 0)
938
0
            {
939
0
                CPLError(CE_Failure, CPLE_AppDefined,
940
0
                         "'tiles[0].width' and/or 'tiles[0].height' missing or "
941
0
                         "invalid");
942
0
                return nullptr;
943
0
            }
944
945
0
            const auto scaleFactors = oTiles[0].GetArray("scaleFactors");
946
0
            if (scaleFactors.Size() >= 1)
947
0
            {
948
0
                nLevelCount = 0;
949
0
                int expectedFactor = 1;
950
0
                for (const auto &jVal : scaleFactors)
951
0
                {
952
0
                    if (nLevelCount < 30 && jVal.ToInteger() == expectedFactor)
953
0
                    {
954
0
                        ++nLevelCount;
955
0
                        expectedFactor *= 2;
956
0
                    }
957
0
                    else
958
0
                    {
959
0
                        break;
960
0
                    }
961
0
                }
962
0
                nLevelCount = std::max(1, nLevelCount);
963
0
            }
964
0
        }
965
966
0
        char *pszEscapedURL = CPLEscapeString(osURL.c_str(), -1, CPLES_XML);
967
0
        const CPLString osXML =
968
0
            CPLSPrintf("<GDAL_WMS>"
969
0
                       "    <Service name=\"IIIFImage\">"
970
0
                       "        <ServerUrl>%s</ServerUrl>"
971
0
                       "        <ImageFormat>image/jpeg</ImageFormat>"
972
0
                       "    </Service>"
973
0
                       "    <DataWindow>"
974
0
                       "        <SizeX>%d</SizeX>"
975
0
                       "        <SizeY>%d</SizeY>"
976
0
                       "        <TileLevel>%d</TileLevel>"
977
0
                       "    </DataWindow>"
978
0
                       "    <BlockSizeX>%d</BlockSizeX>"
979
0
                       "    <BlockSizeY>%d</BlockSizeY>"
980
0
                       "    <BandsCount>3</BandsCount>"
981
0
                       "    <Cache />"
982
0
                       "</GDAL_WMS>",
983
0
                       pszEscapedURL, nWidth, nHeight, nLevelCount, nBlockSizeX,
984
0
                       nBlockSizeY);
985
0
        config = CPLParseXMLString(osXML);
986
0
        CPLFree(pszEscapedURL);
987
0
    }
988
0
    else
989
0
        return nullptr;
990
17.7k
    if (config == nullptr)
991
62
        return nullptr;
992
993
    /* -------------------------------------------------------------------- */
994
    /*      Confirm the requested access is supported.                      */
995
    /* -------------------------------------------------------------------- */
996
17.6k
    if (poOpenInfo->eAccess == GA_Update)
997
0
    {
998
0
        CPLDestroyXMLNode(config);
999
0
        ReportUpdateNotSupportedByDriver("WMS");
1000
0
        return nullptr;
1001
0
    }
1002
1003
17.6k
    GDALWMSDataset *ds = new GDALWMSDataset();
1004
17.6k
    ret = ds->Initialize(config, poOpenInfo->papszOpenOptions);
1005
17.6k
    if (ret != CE_None)
1006
13
    {
1007
13
        delete ds;
1008
13
        ds = nullptr;
1009
13
    }
1010
17.6k
    CPLDestroyXMLNode(config);
1011
1012
    /* -------------------------------------------------------------------- */
1013
    /*      Initialize any PAM information.                                 */
1014
    /* -------------------------------------------------------------------- */
1015
17.6k
    if (ds != nullptr)
1016
17.6k
    {
1017
17.6k
        if (poOpenInfo->pszFilename && poOpenInfo->pszFilename[0] == '<')
1018
284
        {
1019
284
            ds->nPamFlags = GPF_DISABLED;
1020
284
        }
1021
17.3k
        else
1022
17.3k
        {
1023
17.3k
            ds->SetMetadataItem(GDALMD_INTERLEAVE, "PIXEL",
1024
17.3k
                                GDAL_MDD_IMAGE_STRUCTURE);
1025
17.3k
            ds->SetDescription(poOpenInfo->pszFilename);
1026
17.3k
            ds->TryLoadXML();
1027
17.3k
        }
1028
17.6k
    }
1029
1030
17.6k
    return ds;
1031
17.6k
}
1032
1033
/************************************************************************/
1034
/*                          GetServerConfig()                           */
1035
/************************************************************************/
1036
1037
const char *GDALWMSDataset::GetServerConfig(const char *URI,
1038
                                            char **papszHTTPOptions)
1039
0
{
1040
0
    CPLMutexHolder oHolder(&cfgmtx);
1041
1042
    // Might have it cached already
1043
0
    if (cfg.end() != cfg.find(URI))
1044
0
        return cfg.find(URI)->second;
1045
1046
0
    CPLHTTPResult *psResult = CPLHTTPFetch(URI, papszHTTPOptions);
1047
1048
0
    if (nullptr == psResult)
1049
0
        return nullptr;
1050
1051
    // Capture the result in buffer, get rid of http result
1052
0
    if ((psResult->nStatus == 0) && (nullptr != psResult->pabyData) &&
1053
0
        ('\0' != psResult->pabyData[0]))
1054
0
        cfg.insert(make_pair(
1055
0
            URI, static_cast<CPLString>(
1056
0
                     reinterpret_cast<const char *>(psResult->pabyData))));
1057
1058
0
    CPLHTTPDestroyResult(psResult);
1059
1060
0
    if (cfg.end() != cfg.find(URI))
1061
0
        return cfg.find(URI)->second;
1062
0
    else
1063
0
        return nullptr;
1064
0
}
1065
1066
// Empties the server configuration cache and removes the mutex
1067
void GDALWMSDataset::ClearConfigCache()
1068
0
{
1069
    // Obviously not thread safe, should only be called when no WMS files are
1070
    // being opened
1071
0
    cfg.clear();
1072
0
    DestroyCfgMutex();
1073
0
}
1074
1075
void GDALWMSDataset::DestroyCfgMutex()
1076
0
{
1077
0
    if (cfgmtx)
1078
0
        CPLDestroyMutex(cfgmtx);
1079
0
    cfgmtx = nullptr;
1080
0
}
1081
1082
/************************************************************************/
1083
/*                             CreateCopy()                             */
1084
/************************************************************************/
1085
1086
GDALDataset *GDALWMSDataset::CreateCopy(const char *pszFilename,
1087
                                        GDALDataset *poSrcDS,
1088
                                        CPL_UNUSED int bStrict,
1089
                                        CPL_UNUSED CSLConstList papszOptions,
1090
                                        CPL_UNUSED GDALProgressFunc pfnProgress,
1091
                                        CPL_UNUSED void *pProgressData)
1092
0
{
1093
0
    if (poSrcDS->GetDriver() == nullptr ||
1094
0
        !EQUAL(poSrcDS->GetDriver()->GetDescription(), "WMS"))
1095
0
    {
1096
0
        CPLError(CE_Failure, CPLE_NotSupported,
1097
0
                 "Source dataset must be a WMS dataset");
1098
0
        return nullptr;
1099
0
    }
1100
1101
0
    const char *pszXML = poSrcDS->GetMetadataItem("XML", "WMS");
1102
0
    if (pszXML == nullptr)
1103
0
    {
1104
0
        CPLError(CE_Failure, CPLE_AppDefined,
1105
0
                 "Cannot get XML definition of source WMS dataset");
1106
0
        return nullptr;
1107
0
    }
1108
1109
0
    VSILFILE *fp = VSIFOpenL(pszFilename, "wb");
1110
0
    if (fp == nullptr)
1111
0
        return nullptr;
1112
1113
0
    VSIFWriteL(pszXML, 1, strlen(pszXML), fp);
1114
0
    VSIFCloseL(fp);
1115
1116
0
    GDALOpenInfo oOpenInfo(pszFilename, GA_ReadOnly);
1117
0
    return Open(&oOpenInfo);
1118
0
}
1119
1120
void WMSDeregister(CPL_UNUSED GDALDriver *d)
1121
0
{
1122
0
    GDALWMSDataset::DestroyCfgMutex();
1123
0
}
1124
1125
// Define a minidriver factory type, create one and register it
1126
#define RegisterMinidriver(name)                                               \
1127
264
    class WMSMiniDriverFactory_##name final : public WMSMiniDriverFactory      \
1128
264
    {                                                                          \
1129
264
      public:                                                                  \
1130
264
        WMSMiniDriverFactory_##name()                                          \
1131
264
        {                                                                      \
1132
264
            m_name = CPLString(#name);                                         \
1133
264
        }                                                                      \
wmsdriver.cpp:GDALRegister_WMS::WMSMiniDriverFactory_WMS::WMSMiniDriverFactory_WMS()
Line
Count
Source
1131
22
        {                                                                      \
1132
22
            m_name = CPLString(#name);                                         \
1133
22
        }                                                                      \
wmsdriver.cpp:GDALRegister_WMS::WMSMiniDriverFactory_TileService::WMSMiniDriverFactory_TileService()
Line
Count
Source
1131
22
        {                                                                      \
1132
22
            m_name = CPLString(#name);                                         \
1133
22
        }                                                                      \
wmsdriver.cpp:GDALRegister_WMS::WMSMiniDriverFactory_WorldWind::WMSMiniDriverFactory_WorldWind()
Line
Count
Source
1131
22
        {                                                                      \
1132
22
            m_name = CPLString(#name);                                         \
1133
22
        }                                                                      \
wmsdriver.cpp:GDALRegister_WMS::WMSMiniDriverFactory_TMS::WMSMiniDriverFactory_TMS()
Line
Count
Source
1131
22
        {                                                                      \
1132
22
            m_name = CPLString(#name);                                         \
1133
22
        }                                                                      \
wmsdriver.cpp:GDALRegister_WMS::WMSMiniDriverFactory_TiledWMS::WMSMiniDriverFactory_TiledWMS()
Line
Count
Source
1131
22
        {                                                                      \
1132
22
            m_name = CPLString(#name);                                         \
1133
22
        }                                                                      \
wmsdriver.cpp:GDALRegister_WMS::WMSMiniDriverFactory_VirtualEarth::WMSMiniDriverFactory_VirtualEarth()
Line
Count
Source
1131
22
        {                                                                      \
1132
22
            m_name = CPLString(#name);                                         \
1133
22
        }                                                                      \
wmsdriver.cpp:GDALRegister_WMS::WMSMiniDriverFactory_AGS::WMSMiniDriverFactory_AGS()
Line
Count
Source
1131
22
        {                                                                      \
1132
22
            m_name = CPLString(#name);                                         \
1133
22
        }                                                                      \
wmsdriver.cpp:GDALRegister_WMS::WMSMiniDriverFactory_IIP::WMSMiniDriverFactory_IIP()
Line
Count
Source
1131
22
        {                                                                      \
1132
22
            m_name = CPLString(#name);                                         \
1133
22
        }                                                                      \
wmsdriver.cpp:GDALRegister_WMS::WMSMiniDriverFactory_IIIFImage::WMSMiniDriverFactory_IIIFImage()
Line
Count
Source
1131
22
        {                                                                      \
1132
22
            m_name = CPLString(#name);                                         \
1133
22
        }                                                                      \
wmsdriver.cpp:GDALRegister_WMS::WMSMiniDriverFactory_MRF::WMSMiniDriverFactory_MRF()
Line
Count
Source
1131
22
        {                                                                      \
1132
22
            m_name = CPLString(#name);                                         \
1133
22
        }                                                                      \
wmsdriver.cpp:GDALRegister_WMS::WMSMiniDriverFactory_OGCAPIMaps::WMSMiniDriverFactory_OGCAPIMaps()
Line
Count
Source
1131
22
        {                                                                      \
1132
22
            m_name = CPLString(#name);                                         \
1133
22
        }                                                                      \
wmsdriver.cpp:GDALRegister_WMS::WMSMiniDriverFactory_OGCAPICoverage::WMSMiniDriverFactory_OGCAPICoverage()
Line
Count
Source
1131
22
        {                                                                      \
1132
22
            m_name = CPLString(#name);                                         \
1133
22
        }                                                                      \
1134
264
        WMSMiniDriver *New() const override                                    \
1135
17.6k
        {                                                                      \
1136
17.6k
            return new WMSMiniDriver_##name;                                   \
1137
17.6k
        }                                                                      \
wmsdriver.cpp:GDALRegister_WMS::WMSMiniDriverFactory_WMS::New() const
Line
Count
Source
1135
17.4k
        {                                                                      \
1136
17.4k
            return new WMSMiniDriver_##name;                                   \
1137
17.4k
        }                                                                      \
Unexecuted instantiation: wmsdriver.cpp:GDALRegister_WMS::WMSMiniDriverFactory_TileService::New() const
Unexecuted instantiation: wmsdriver.cpp:GDALRegister_WMS::WMSMiniDriverFactory_WorldWind::New() const
wmsdriver.cpp:GDALRegister_WMS::WMSMiniDriverFactory_TMS::New() const
Line
Count
Source
1135
270
        {                                                                      \
1136
270
            return new WMSMiniDriver_##name;                                   \
1137
270
        }                                                                      \
Unexecuted instantiation: wmsdriver.cpp:GDALRegister_WMS::WMSMiniDriverFactory_TiledWMS::New() const
Unexecuted instantiation: wmsdriver.cpp:GDALRegister_WMS::WMSMiniDriverFactory_VirtualEarth::New() const
Unexecuted instantiation: wmsdriver.cpp:GDALRegister_WMS::WMSMiniDriverFactory_AGS::New() const
Unexecuted instantiation: wmsdriver.cpp:GDALRegister_WMS::WMSMiniDriverFactory_IIP::New() const
Unexecuted instantiation: wmsdriver.cpp:GDALRegister_WMS::WMSMiniDriverFactory_IIIFImage::New() const
Unexecuted instantiation: wmsdriver.cpp:GDALRegister_WMS::WMSMiniDriverFactory_MRF::New() const
Unexecuted instantiation: wmsdriver.cpp:GDALRegister_WMS::WMSMiniDriverFactory_OGCAPIMaps::New() const
Unexecuted instantiation: wmsdriver.cpp:GDALRegister_WMS::WMSMiniDriverFactory_OGCAPICoverage::New() const
1138
264
    };                                                                         \
1139
264
    WMSRegisterMiniDriverFactory(new WMSMiniDriverFactory_##name());
1140
1141
/************************************************************************/
1142
/*                          GDALRegister_WMS()                          */
1143
/************************************************************************/
1144
1145
//
1146
// Do not define any open options here!
1147
// Doing so will enable checking the open options, which will generate warnings
1148
// for undeclared options which may be handled by individual minidrivers
1149
//
1150
1151
void GDALRegister_WMS()
1152
1153
22
{
1154
22
    if (GDALGetDriverByName(DRIVER_NAME) != nullptr)
1155
0
        return;
1156
1157
    // Register all minidrivers here
1158
22
    RegisterMinidriver(WMS);
1159
22
    RegisterMinidriver(TileService);
1160
22
    RegisterMinidriver(WorldWind);
1161
22
    RegisterMinidriver(TMS);
1162
22
    RegisterMinidriver(TiledWMS);
1163
22
    RegisterMinidriver(VirtualEarth);
1164
22
    RegisterMinidriver(AGS);
1165
22
    RegisterMinidriver(IIP);
1166
22
    RegisterMinidriver(IIIFImage);
1167
22
    RegisterMinidriver(MRF);
1168
22
    RegisterMinidriver(OGCAPIMaps);
1169
22
    RegisterMinidriver(OGCAPICoverage);
1170
1171
22
    GDALDriver *poDriver = new GDALDriver();
1172
22
    WMSDriverSetCommonMetadata(poDriver);
1173
1174
22
    poDriver->pfnOpen = GDALWMSDataset::Open;
1175
22
    poDriver->pfnUnloadDriver = WMSDeregister;
1176
22
    poDriver->pfnCreateCopy = GDALWMSDataset::CreateCopy;
1177
1178
22
    GetGDALDriverManager()->RegisterDriver(poDriver);
1179
22
}