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

Generated by: LCOV version 1.14