LCOV - code coverage report
Current view: top level - frmts/wms - wmsdriver.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 322 587 54.9 %
Date: 2025-07-09 17:50:03 Functions: 54 58 93.1 %

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

Generated by: LCOV version 1.14