LCOV - code coverage report
Current view: top level - frmts/wms - minidriver_tiled_wms.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 262 375 69.9 %
Date: 2025-10-28 23:09:11 Functions: 9 11 81.8 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  tiledWMS Client Driver
       4             :  * Purpose:  Implementation of the OnEarth Tiled WMS minidriver.
       5             :  *           http://onearth.jpl.nasa.gov/tiled.html
       6             :  * Author:   Lucian Plesea (Lucian dot Plesea at jpl.nasa.gov)
       7             :  *           Adam Nowacki
       8             :  *
       9             :  ******************************************************************************
      10             :  * Copyright (c) 2007, Adam Nowacki
      11             :  * Copyright (c) 2011-2012, Even Rouault <even dot rouault at spatialys.com>
      12             :  *
      13             :  * SPDX-License-Identifier: MIT
      14             :  ****************************************************************************/
      15             : 
      16             : //
      17             : // Also known as the OnEarth tile protocol
      18             : //
      19             : // A few open options are supported by tiled WMS
      20             : //
      21             : // TiledGroupName=<Name>
      22             : //
      23             : //  This option is only valid when the WMS file does not contain a
      24             : //  TiledGroupName. The name value should match exactly the name declared by the
      25             : //  server, including possible white spaces, otherwise the open will fail.
      26             : //
      27             : // Change=<key>:<value>
      28             : //
      29             : //  If the tiled group selected supports the change key, this option will set
      30             : //  the value The <key> here does not include the brackets present in the
      31             : //  GetTileService For example, if a TiledPattern include a key of ${time}, the
      32             : //  matching open option will be Change=time:<YYYY-MM-DD> The Change open option
      33             : //  may be present multiple times, with different keys If a key is not supported
      34             : //  by the selected TilePattern, the open will fail Alternate syntax is:
      35             : //  Change=<key>=<value>
      36             : //
      37             : // StoreConfiguration=Yes
      38             : //
      39             : //  This boolean option is only useful when doing a createcopy of a tiledWMS
      40             : //  dataset into another tiledWMS dataset. When set, the source tiledWMS will
      41             : //  store the server configuration into the XML metadata representation, which
      42             : //  then gets copied to the XML output. This will eliminate the need to fetch
      43             : //  the server configuration when opening the output datafile
      44             : //
      45             : 
      46             : #include "wmsdriver.h"
      47             : #include "minidriver_tiled_wms.h"
      48             : 
      49             : #include "gdal_colortable.h"
      50             : #include "gdal_cpp_functions.h"
      51             : 
      52             : #include <set>
      53             : 
      54             : static const char SIG[] = "GDAL_WMS TiledWMS: ";
      55             : 
      56             : /*
      57             :  *\brief Read a number from an xml element
      58             :  */
      59             : 
      60          12 : static double getXMLNum(const CPLXMLNode *poRoot, const char *pszPath,
      61             :                         const char *pszDefault)
      62             : {  // Sets errno
      63          12 :     return CPLAtof(CPLGetXMLValue(poRoot, pszPath, pszDefault));
      64             : }
      65             : 
      66             : /*
      67             :  *\brief Read a ColorEntry XML node, return a GDALColorEntry structure
      68             :  *
      69             :  */
      70             : 
      71           0 : static GDALColorEntry GetXMLColorEntry(const CPLXMLNode *p)
      72             : {
      73             :     GDALColorEntry ce;
      74           0 :     ce.c1 = static_cast<short>(getXMLNum(p, "c1", "0"));
      75           0 :     ce.c2 = static_cast<short>(getXMLNum(p, "c2", "0"));
      76           0 :     ce.c3 = static_cast<short>(getXMLNum(p, "c3", "0"));
      77           0 :     ce.c4 = static_cast<short>(getXMLNum(p, "c4", "255"));
      78           0 :     return ce;
      79             : }
      80             : 
      81             : /************************************************************************/
      82             : /*                           SearchXMLSiblings()                        */
      83             : /************************************************************************/
      84             : 
      85             : /*
      86             :  * \brief Search for a sibling of the root node with a given name.
      87             :  *
      88             :  * Searches only the next siblings of the node passed in for the named element
      89             :  * or attribute. If the first character of the pszElement is '=', the search
      90             :  * includes the psRoot node
      91             :  *
      92             :  * @param psRoot the root node to search.  This should be a node of type
      93             :  * CXT_Element.  NULL is safe.
      94             :  *
      95             :  * @param pszElement the name of the element or attribute to search for.
      96             :  *
      97             :  *
      98             :  * @return The first matching node or NULL on failure.
      99             :  */
     100             : 
     101        2076 : static const CPLXMLNode *SearchXMLSiblings(const CPLXMLNode *psRoot,
     102             :                                            const char *pszElement)
     103             : 
     104             : {
     105        2076 :     if (psRoot == nullptr || pszElement == nullptr)
     106           0 :         return nullptr;
     107             : 
     108             :     // If the strings starts with '=', skip it and test the root
     109             :     // If not, start testing with the next sibling
     110        2076 :     if (pszElement[0] == '=')
     111          27 :         pszElement++;
     112             :     else
     113        2049 :         psRoot = psRoot->psNext;
     114             : 
     115       29974 :     for (; psRoot != nullptr; psRoot = psRoot->psNext)
     116             :     {
     117       27925 :         if ((psRoot->eType == CXT_Element || psRoot->eType == CXT_Attribute) &&
     118       27925 :             EQUAL(pszElement, psRoot->pszValue))
     119          27 :             return psRoot;
     120             :     }
     121        2049 :     return nullptr;
     122             : }
     123             : 
     124             : /************************************************************************/
     125             : /*                        SearchLeafGroupName()                         */
     126             : /************************************************************************/
     127             : 
     128             : /*
     129             :  * \brief Search for a leaf TileGroup node by name.
     130             :  *
     131             :  * @param psRoot the root node to search.  This should be a node of type
     132             :  * CXT_Element.  NULL is safe.
     133             :  *
     134             :  * @param pszElement the name of the TileGroup to search for.
     135             :  *
     136             :  * @return The XML node of the matching TileGroup or NULL on failure.
     137             :  */
     138             : 
     139        2046 : static CPLXMLNode *SearchLeafGroupName(CPLXMLNode *psRoot, const char *name)
     140             : 
     141             : {
     142        2046 :     if (psRoot == nullptr || name == nullptr)
     143           0 :         return nullptr;
     144             : 
     145             :     // Has to be a leaf TileGroup with the right name
     146        2046 :     if (nullptr == SearchXMLSiblings(psRoot->psChild, "TiledGroup"))
     147             :     {
     148        2046 :         if (EQUAL(name, CPLGetXMLValue(psRoot, "Name", "")))
     149           3 :             return psRoot;
     150             :     }
     151             :     else
     152             :     {  // Is metagroup, try children then siblings
     153           0 :         CPLXMLNode *ret = SearchLeafGroupName(psRoot->psChild, name);
     154           0 :         if (nullptr != ret)
     155           0 :             return ret;
     156             :     }
     157        2043 :     return SearchLeafGroupName(psRoot->psNext, name);
     158             : }
     159             : 
     160             : /************************************************************************/
     161             : /*                             BandInterp()                             */
     162             : /************************************************************************/
     163             : 
     164             : /*
     165             :  * \brief Utility function to calculate color band interpretation.
     166             :  * Only handles Gray, GrayAlpha, RGB and RGBA, based on total band count
     167             :  *
     168             :  * @param nbands is the total number of bands in the image
     169             :  *
     170             :  * @param band is the band number, starting with 1
     171             :  *
     172             :  * @return GDALColorInterp of the band
     173             :  */
     174             : 
     175           9 : static GDALColorInterp BandInterp(int nbands, int band)
     176             : {
     177           9 :     switch (nbands)
     178             :     {
     179           0 :         case 1:
     180           0 :             return GCI_GrayIndex;
     181           0 :         case 2:
     182           0 :             return band == 1 ? GCI_GrayIndex : GCI_AlphaBand;
     183           9 :         case 3:  // RGB
     184             :         case 4:  // RBGA
     185           9 :             if (band < 3)
     186           6 :                 return band == 1 ? GCI_RedBand : GCI_GreenBand;
     187           3 :             return band == 3 ? GCI_BlueBand : GCI_AlphaBand;
     188           0 :         default:
     189           0 :             return GCI_Undefined;
     190             :     }
     191             : }
     192             : 
     193             : /************************************************************************/
     194             : /*                              FindBbox()                              */
     195             : /************************************************************************/
     196             : 
     197             : /*
     198             :  * \brief Utility function to find the position of the bbox parameter value
     199             :  * within a request string.  The search for the bbox is case insensitive
     200             :  *
     201             :  * @param in, the string to search into
     202             :  *
     203             :  * @return The position from the beginning of the string or -1 if not found
     204             :  */
     205             : 
     206         189 : static int FindBbox(CPLString in)
     207             : {
     208             : 
     209         189 :     size_t pos = in.ifind("&bbox=");
     210         189 :     if (pos == std::string::npos)
     211           0 :         return -1;
     212         189 :     return static_cast<int>(pos) + 6;
     213             : }
     214             : 
     215             : /************************************************************************/
     216             : /*                         FindChangePattern()                          */
     217             : /************************************************************************/
     218             : 
     219             : /*
     220             :  * \brief Build the right request pattern based on the change request list
     221             :  * It only gets called on initialization
     222             :  * @param cdata, possible request strings, white space separated
     223             :  * @param substs, the list of substitutions to be applied
     224             :  * @param keys, the list of available substitution keys
     225             :  * @param ret The return value, a matching request or an empty string
     226             :  */
     227             : 
     228          27 : static void FindChangePattern(const char *cdata, const char *const *substs,
     229             :                               const char *const *keys, CPLString &ret)
     230             : {
     231             :     const CPLStringList aosTokens(CSLTokenizeString2(
     232          27 :         cdata, " \t\n\r", CSLT_STRIPLEADSPACES | CSLT_STRIPENDSPACES));
     233          27 :     ret.clear();
     234             : 
     235          27 :     int matchcount = CSLCount(substs);
     236          27 :     int keycount = CSLCount(keys);
     237          27 :     if (keycount < matchcount)
     238             :     {
     239           0 :         return;
     240             :     }
     241             : 
     242             :     // A valid string has only the keys in the substs list and none other
     243          36 :     for (int j = 0; j < aosTokens.size(); j++)
     244             :     {
     245          36 :         ret = aosTokens[j];  // The target string
     246          36 :         bool matches = true;
     247             : 
     248          72 :         for (int k = 0; k < keycount && keys != nullptr; k++)
     249             :         {
     250          36 :             const char *key = keys[k];
     251          36 :             int sub_number = CSLPartialFindString(substs, key);
     252          36 :             if (sub_number != -1)
     253             :             {  // It is a listed match
     254             :                 // But is the match for the key position?
     255          18 :                 char *found_key = nullptr;
     256             :                 const char *found_value =
     257          18 :                     CPLParseNameValue(substs[sub_number], &found_key);
     258          18 :                 if (found_key != nullptr && EQUAL(found_key, key))
     259             :                 {  // Should exist in the request
     260          18 :                     if (std::string::npos == ret.find(key))
     261           0 :                         matches = false;
     262          18 :                     if (matches)
     263             :                         // Execute the substitution on the "ret" string
     264          18 :                         URLSearchAndReplace(&ret, key, "%s", found_value);
     265             :                 }
     266             :                 else
     267             :                 {
     268           0 :                     matches = false;
     269             :                 }
     270          18 :                 CPLFree(found_key);
     271             :             }
     272             :             else
     273             :             {  // Key not in the subst list, should not match
     274          18 :                 if (std::string::npos != ret.find(key))
     275           9 :                     matches = false;
     276             :             }
     277             :         }  // Key loop
     278          36 :         if (matches)
     279             :         {
     280          27 :             return;  // We got the string ready, all keys accounted for and
     281             :                      // substs applied
     282             :         }
     283             :     }
     284           0 :     ret.clear();
     285             : }
     286             : 
     287             : WMSMiniDriver_TiledWMS::WMSMiniDriver_TiledWMS() = default;
     288             : 
     289             : WMSMiniDriver_TiledWMS::~WMSMiniDriver_TiledWMS() = default;
     290             : 
     291             : // Returns the scale of a WMS request as compared to the base resolution
     292         162 : double WMSMiniDriver_TiledWMS::Scale(const char *request) const
     293             : {
     294         162 :     int bbox = FindBbox(request);
     295         162 :     if (bbox < 0)
     296           0 :         return 0;
     297             :     double x, y, X, Y;
     298         162 :     CPLsscanf(request + bbox, "%lf,%lf,%lf,%lf", &x, &y, &X, &Y);
     299         162 :     return (m_data_window.m_x1 - m_data_window.m_x0) / (X - x) * m_bsx /
     300         162 :            m_data_window.m_sx;
     301             : }
     302             : 
     303             : // Finds, extracts, and returns the highest resolution request string from a
     304             : // list, starting at item i
     305          27 : CPLString WMSMiniDriver_TiledWMS::GetLowestScale(CPLStringList &list,
     306             :                                                  int i) const
     307             : {
     308          27 :     CPLString req;
     309          27 :     double scale = -1;
     310          27 :     int position = -1;
     311         162 :     while (nullptr != list[i])
     312             :     {
     313         135 :         double tscale = Scale(list[i]);
     314         135 :         if (tscale >= scale)
     315             :         {
     316          27 :             scale = tscale;
     317          27 :             position = i;
     318             :         }
     319         135 :         i++;
     320             :     }
     321          27 :     if (position > -1)
     322             :     {
     323          27 :         req = list[position];
     324             :         list.Assign(CSLRemoveStrings(list.StealList(), position, 1, nullptr),
     325          27 :                     true);
     326             :     }
     327          27 :     return req;
     328             : }
     329             : 
     330             : /*
     331             :  *\Brief Initialize minidriver with info from the server
     332             :  */
     333             : 
     334           4 : CPLErr WMSMiniDriver_TiledWMS::Initialize(CPLXMLNode *config,
     335             :                                           CPL_UNUSED char **OpenOptions)
     336             : {
     337           4 :     CPLErr ret = CE_None;
     338           8 :     CPLXMLTreeCloser tileServiceConfig(nullptr);
     339           4 :     const CPLXMLNode *TG = nullptr;
     340             : 
     341           8 :     CPLStringList requests;
     342           8 :     CPLStringList substs;
     343           8 :     CPLStringList keys;
     344           4 :     CPLStringList changes;
     345             : 
     346             :     try
     347             :     {  // Parse info from the WMS Service node
     348             :         //        m_end_url = CPLGetXMLValue(config, "AdditionalArgs", "");
     349           4 :         m_base_url = CPLGetXMLValue(config, "ServerURL", "");
     350             : 
     351           4 :         if (m_base_url.empty())
     352           0 :             throw CPLOPrintf("%s ServerURL missing.", SIG);
     353             : 
     354             :         CPLString tiledGroupName(
     355           8 :             CSLFetchNameValueDef(OpenOptions, "TiledGroupName", ""));
     356             :         tiledGroupName =
     357           4 :             CPLGetXMLValue(config, "TiledGroupName", tiledGroupName);
     358           4 :         if (tiledGroupName.empty())
     359           1 :             throw CPLOPrintf("%s TiledGroupName missing.", SIG);
     360             : 
     361             :         // Change strings, key is an attribute, value is the value of the Change
     362             :         // node Multiple keys are possible
     363             : 
     364             :         // First process the changes from open options, if present
     365           3 :         changes = CSLFetchNameValueMultiple(OpenOptions, "Change");
     366             :         // Transfer them to subst list
     367           5 :         for (int i = 0; i < changes.size(); i++)
     368             :         {
     369           2 :             char *key = nullptr;
     370           2 :             const char *value = CPLParseNameValue(changes[i], &key);
     371             :             // Add the ${} around the key
     372           2 :             if (value != nullptr && key != nullptr)
     373           2 :                 substs.SetNameValue(CPLOPrintf("${%s}", key), value);
     374           2 :             CPLFree(key);
     375             :         }
     376             : 
     377             :         // Then process the configuration file itself
     378           3 :         const CPLXMLNode *nodeChange = CPLSearchXMLNode(config, "Change");
     379           3 :         while (nodeChange != nullptr)
     380             :         {
     381           0 :             CPLString key = CPLGetXMLValue(nodeChange, "key", "");
     382           0 :             if (key.empty())
     383             :                 throw CPLOPrintf(
     384             :                     "%s Change element needs a non-empty \"key\" attribute",
     385           0 :                     SIG);
     386           0 :             substs.SetNameValue(key, CPLGetXMLValue(nodeChange, "", ""));
     387           0 :             nodeChange = SearchXMLSiblings(nodeChange, "Change");
     388             :         }
     389             : 
     390           3 :         m_parent_dataset->SetMetadataItem("ServerURL", m_base_url, nullptr);
     391           3 :         m_parent_dataset->SetMetadataItem("TiledGroupName", tiledGroupName,
     392           3 :                                           nullptr);
     393           5 :         for (const char *subst : substs)
     394           2 :             m_parent_dataset->SetMetadataItem("Change", subst, nullptr);
     395             : 
     396             :         const char *pszConfiguration =
     397           3 :             CPLGetXMLValue(config, "Configuration", nullptr);
     398           6 :         CPLString decodedGTS;
     399             : 
     400           3 :         if (pszConfiguration)
     401             :         {  // Probably XML encoded because it is XML itself
     402             :             // The copy will be replaced by the decoded result
     403           1 :             decodedGTS = pszConfiguration;
     404           1 :             WMSUtilDecode(decodedGTS,
     405             :                           CPLGetXMLValue(config, "Configuration.encoding", ""));
     406             :         }
     407             :         else
     408             :         {  // Not local, use the WMSdriver to fetch the server config
     409           4 :             CPLString getTileServiceUrl = m_base_url + "request=GetTileService";
     410             : 
     411             :             // This returns a string managed by the cfg cache, do not free
     412           2 :             const char *pszTmp = GDALWMSDataset::GetServerConfig(
     413             :                 getTileServiceUrl,
     414           2 :                 const_cast<char **>(m_parent_dataset->GetHTTPRequestOpts()));
     415           2 :             decodedGTS = pszTmp ? pszTmp : "";
     416             : 
     417           2 :             if (decodedGTS.empty())
     418           0 :                 throw CPLOPrintf("%s Can't fetch server GetTileService", SIG);
     419             :         }
     420             : 
     421             :         // decodedGTS contains the GetTileService return now
     422           3 :         tileServiceConfig.reset(CPLParseXMLString(decodedGTS));
     423           3 :         if (!tileServiceConfig)
     424             :             throw CPLOPrintf("%s Error parsing the GetTileService response",
     425           0 :                              SIG);
     426             : 
     427           3 :         if (nullptr ==
     428           3 :             (TG = CPLSearchXMLNode(tileServiceConfig.get(), "TiledPatterns")))
     429             :             throw CPLOPrintf(
     430           0 :                 "%s Can't locate TiledPatterns in server response.", SIG);
     431             : 
     432             :         // Get the global base_url and bounding box, these can be overwritten at
     433             :         // the tileGroup level They are just pointers into existing structures,
     434             :         // cleanup is not required
     435             :         const char *global_base_url =
     436           3 :             CPLGetXMLValue(tileServiceConfig.get(),
     437             :                            "TiledPatterns.OnlineResource.xlink:href", "");
     438           3 :         const CPLXMLNode *global_latlonbbox = CPLGetXMLNode(
     439             :             tileServiceConfig.get(), "TiledPatterns.LatLonBoundingBox");
     440             :         const CPLXMLNode *global_bbox =
     441           3 :             CPLGetXMLNode(tileServiceConfig.get(), "TiledPatterns.BoundingBox");
     442           3 :         const char *pszProjection = CPLGetXMLValue(
     443           3 :             tileServiceConfig.get(), "TiledPatterns.Projection", "");
     444           3 :         if (pszProjection[0] != 0)
     445           0 :             m_oSRS.SetFromUserInput(
     446             :                 pszProjection,
     447             :                 OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS_get());
     448             : 
     449           3 :         if (nullptr == (TG = SearchLeafGroupName(TG->psChild, tiledGroupName)))
     450             :             throw CPLOPrintf("%s No TiledGroup "
     451             :                              "%s"
     452             :                              " in server response.",
     453           0 :                              SIG, tiledGroupName.c_str());
     454             : 
     455           3 :         int band_count = atoi(CPLGetXMLValue(TG, "Bands", "3"));
     456             : 
     457           3 :         if (!GDALCheckBandCount(band_count, FALSE))
     458             :             throw CPLOPrintf("%s Invalid number of bands in server response",
     459           0 :                              SIG);
     460             : 
     461           3 :         if (nullptr != CPLGetXMLNode(TG, "Key"))
     462             :         {  // Collect all keys defined by this tileset
     463           3 :             const CPLXMLNode *node = CPLGetXMLNode(TG, "Key");
     464           6 :             while (nullptr != node)
     465             :             {  // the TEXT of the Key node
     466           3 :                 const char *val = CPLGetXMLValue(node, nullptr, nullptr);
     467           3 :                 if (nullptr != val)
     468           3 :                     keys.AddString(val);
     469           3 :                 node = SearchXMLSiblings(node, "Key");
     470             :             }
     471             :         }
     472             : 
     473             :         // Data values are attributes, they include NoData Min and Max
     474           3 :         if (nullptr != CPLGetXMLNode(TG, "DataValues"))
     475             :         {
     476             :             const char *nodata =
     477           0 :                 CPLGetXMLValue(TG, "DataValues.NoData", nullptr);
     478           0 :             if (nodata != nullptr)
     479             :             {
     480           0 :                 m_parent_dataset->WMSSetNoDataValue(nodata);
     481           0 :                 m_parent_dataset->SetTileOO("@NDV", nodata);
     482             :             }
     483           0 :             const char *min = CPLGetXMLValue(TG, "DataValues.min", nullptr);
     484           0 :             if (min != nullptr)
     485           0 :                 m_parent_dataset->WMSSetMinValue(min);
     486           0 :             const char *max = CPLGetXMLValue(TG, "DataValues.max", nullptr);
     487           0 :             if (max != nullptr)
     488           0 :                 m_parent_dataset->WMSSetMaxValue(max);
     489             :         }
     490             : 
     491           3 :         m_parent_dataset->WMSSetBandsCount(band_count);
     492             :         GDALDataType dt =
     493           3 :             GDALGetDataTypeByName(CPLGetXMLValue(TG, "DataType", "Byte"));
     494           3 :         m_parent_dataset->WMSSetDataType(dt);
     495           3 :         if (dt != GDT_Byte)
     496           0 :             m_parent_dataset->SetTileOO("@DATATYPE", GDALGetDataTypeName(dt));
     497             :         // Let the TiledGroup override the projection
     498           3 :         pszProjection = CPLGetXMLValue(TG, "Projection", "");
     499           3 :         if (pszProjection[0] != 0)
     500           3 :             m_oSRS = ProjToSRS(pszProjection);
     501             : 
     502             :         m_base_url =
     503           3 :             CPLGetXMLValue(TG, "OnlineResource.xlink:href", global_base_url);
     504           3 :         if (m_base_url[0] == '\0')
     505             :             throw CPLOPrintf(
     506           0 :                 "%s Can't locate OnlineResource in the server response", SIG);
     507             : 
     508             :         // Bounding box, local, global, local lat-lon, global lat-lon, in this
     509             :         // order
     510           3 :         const CPLXMLNode *bbox = CPLGetXMLNode(TG, "BoundingBox");
     511           3 :         if (nullptr == bbox)
     512           3 :             bbox = global_bbox;
     513           3 :         if (nullptr == bbox)
     514           3 :             bbox = CPLGetXMLNode(TG, "LatLonBoundingBox");
     515           3 :         if (nullptr == bbox)
     516           0 :             bbox = global_latlonbbox;
     517           3 :         if (nullptr == bbox)
     518             :             throw CPLOPrintf(
     519             :                 "%s Can't locate the LatLonBoundingBox in server response",
     520           0 :                 SIG);
     521             : 
     522             :         // Check for errors during conversion
     523           3 :         errno = 0;
     524           3 :         int err = 0;
     525           3 :         m_data_window.m_x0 = getXMLNum(bbox, "minx", "0");
     526           3 :         err |= errno;
     527           3 :         m_data_window.m_x1 = getXMLNum(bbox, "maxx", "-1");
     528           3 :         err |= errno;
     529           3 :         m_data_window.m_y0 = getXMLNum(bbox, "maxy", "0");
     530           3 :         err |= errno;
     531           3 :         m_data_window.m_y1 = getXMLNum(bbox, "miny", "-1");
     532           3 :         err |= errno;
     533           3 :         if (err)
     534           0 :             throw CPLOPrintf("%s Can't parse LatLonBoundingBox", SIG);
     535             : 
     536           3 :         if ((m_data_window.m_x1 - m_data_window.m_x0) <= 0 ||
     537           3 :             (m_data_window.m_y0 - m_data_window.m_y1) <= 0)
     538             :             throw CPLOPrintf(
     539           0 :                 "%s Coordinate order in BBox problem in server response", SIG);
     540             : 
     541             :         // Is there a palette?
     542             :         //
     543             :         // Format is
     544             :         // <Palette>
     545             :         //   <Size>N</Size> : Optional
     546             :         //   <Model>RGBA|RGB</Model> : Optional, defaults to RGB
     547             :         //   <Entry idx=i c1=v1 c2=v2 c3=v3 c4=v4/> :Optional
     548             :         //   <Entry .../>
     549             :         // </Palette>
     550             :         // the idx attribute is optional, it autoincrements
     551             :         // The entries are vertices, interpolation takes place in between if the
     552             :         // indices are not successive index values have to be in increasing
     553             :         // order The palette starts initialized with zeros
     554             :         //
     555             : 
     556           3 :         bool bHasColorTable = false;
     557             : 
     558           3 :         if ((band_count == 1) && CPLGetXMLNode(TG, "Palette"))
     559             :         {
     560           0 :             const CPLXMLNode *node = CPLGetXMLNode(TG, "Palette");
     561             : 
     562           0 :             int entries = static_cast<int>(getXMLNum(node, "Size", "255"));
     563           0 :             GDALPaletteInterp eInterp = GPI_RGB;  // RGB and RGBA are the same
     564             : 
     565           0 :             CPLString pModel = CPLGetXMLValue(node, "Model", "RGB");
     566           0 :             if (!pModel.empty() && pModel.find("RGB") == std::string::npos)
     567             :                 throw CPLOPrintf(
     568             :                     "%s Palette Model %s is unknown, use RGB or RGBA", SIG,
     569           0 :                     pModel.c_str());
     570             : 
     571           0 :             if ((entries < 1) || (entries > 256))
     572           0 :                 throw CPLOPrintf("%s Palette definition error", SIG);
     573             : 
     574             :             // Create it and initialize it to nothing
     575             :             int start_idx;
     576             :             int end_idx;
     577           0 :             GDALColorEntry ce_start = {0, 0, 0, 255};
     578           0 :             GDALColorEntry ce_end = {0, 0, 0, 255};
     579             : 
     580           0 :             auto poColorTable = std::make_unique<GDALColorTable>(eInterp);
     581           0 :             poColorTable->CreateColorRamp(0, &ce_start, entries - 1, &ce_end);
     582             :             // Read the values
     583           0 :             const CPLXMLNode *p = CPLGetXMLNode(node, "Entry");
     584           0 :             if (p)
     585             :             {
     586             :                 // Initialize the first entry
     587           0 :                 start_idx = static_cast<int>(getXMLNum(p, "idx", "0"));
     588           0 :                 ce_start = GetXMLColorEntry(p);
     589             : 
     590           0 :                 if (start_idx < 0)
     591             :                     throw CPLOPrintf("%s Palette index %d not allowed", SIG,
     592           0 :                                      start_idx);
     593             : 
     594           0 :                 poColorTable->SetColorEntry(start_idx, &ce_start);
     595           0 :                 while (nullptr != (p = SearchXMLSiblings(p, "Entry")))
     596             :                 {
     597             :                     // For every entry, create a ramp
     598           0 :                     ce_end = GetXMLColorEntry(p);
     599           0 :                     end_idx = static_cast<int>(
     600           0 :                         getXMLNum(p, "idx", CPLOPrintf("%d", start_idx + 1)));
     601           0 :                     if ((end_idx <= start_idx) || (start_idx >= entries))
     602             :                         throw CPLOPrintf("%s Index Error at index %d", SIG,
     603           0 :                                          end_idx);
     604             : 
     605           0 :                     poColorTable->CreateColorRamp(start_idx, &ce_start, end_idx,
     606             :                                                   &ce_end);
     607           0 :                     ce_start = ce_end;
     608           0 :                     start_idx = end_idx;
     609             :                 }
     610             :             }
     611             : 
     612             :             // Dataset has ownership
     613           0 :             m_parent_dataset->SetColorTable(poColorTable.release());
     614           0 :             bHasColorTable = true;
     615             :         }  // If palette
     616             : 
     617           3 :         int overview_count = 0;
     618           3 :         const CPLXMLNode *Pattern = TG->psChild;
     619             : 
     620           3 :         m_bsx = -1;
     621           3 :         m_bsy = -1;
     622           3 :         m_data_window.m_sx = 0;
     623           3 :         m_data_window.m_sy = 0;
     624             : 
     625          27 :         while (
     626          57 :             (nullptr != Pattern) &&
     627          27 :             (nullptr != (Pattern = SearchXMLSiblings(Pattern, "=TilePattern"))))
     628             :         {
     629             :             int mbsx, mbsy, sx, sy;
     630             :             double x, y, X, Y;
     631             : 
     632          27 :             CPLString request;
     633          27 :             FindChangePattern(Pattern->psChild->pszValue, substs, keys,
     634          27 :                               request);
     635          27 :             if (request.empty())
     636           0 :                 break;  // No point to drag, this level doesn't match the keys
     637             : 
     638          27 :             const CPLStringList aosTokens(CSLTokenizeString2(request, "&", 0));
     639             : 
     640          27 :             const char *pszWIDTH = aosTokens.FetchNameValue("WIDTH");
     641          27 :             const char *pszHEIGHT = aosTokens.FetchNameValue("HEIGHT");
     642          27 :             if (pszWIDTH == nullptr || pszHEIGHT == nullptr)
     643             :                 throw CPLOPrintf(
     644             :                     "%s Cannot find width or height parameters in %s", SIG,
     645           0 :                     request.c_str());
     646             : 
     647          27 :             mbsx = atoi(pszWIDTH);
     648          27 :             mbsy = atoi(pszHEIGHT);
     649             :             // If unset until now, try to get the projection from the
     650             :             // pattern
     651          27 :             if (m_oSRS.IsEmpty())
     652             :             {
     653           0 :                 const char *pszSRS = aosTokens.FetchNameValueDef("SRS", "");
     654           0 :                 if (pszSRS[0] != 0)
     655           0 :                     m_oSRS = ProjToSRS(pszSRS);
     656             :             }
     657             : 
     658          27 :             if (-1 == m_bsx)
     659           3 :                 m_bsx = mbsx;
     660          27 :             if (-1 == m_bsy)
     661           3 :                 m_bsy = mbsy;
     662          27 :             if ((m_bsx != mbsx) || (m_bsy != mbsy))
     663           0 :                 throw CPLOPrintf("%s Tileset uses different block sizes", SIG);
     664             : 
     665          27 :             if (CPLsscanf(aosTokens.FetchNameValueDef("BBOX", ""),
     666          27 :                           "%lf,%lf,%lf,%lf", &x, &y, &X, &Y) != 4)
     667             :                 throw CPLOPrintf("%s Error parsing BBOX, pattern %d\n", SIG,
     668           0 :                                  overview_count + 1);
     669             : 
     670             :             // Pick the largest size
     671          27 :             sx = static_cast<int>((m_data_window.m_x1 - m_data_window.m_x0) /
     672          27 :                                   (X - x) * m_bsx);
     673          27 :             sy = static_cast<int>(fabs(
     674          27 :                 (m_data_window.m_y1 - m_data_window.m_y0) / (Y - y) * m_bsy));
     675          27 :             if (sx > m_data_window.m_sx)
     676           3 :                 m_data_window.m_sx = sx;
     677          27 :             if (sy > m_data_window.m_sy)
     678           3 :                 m_data_window.m_sy = sy;
     679             : 
     680             :             // Only use overlays where the top coordinate is within a pixel from
     681             :             // the top of coverage
     682             :             double pix_off, temp;
     683          27 :             pix_off =
     684          27 :                 m_bsy * modf(fabs((Y - m_data_window.m_y0) / (Y - y)), &temp);
     685          27 :             if ((pix_off < 1) || ((m_bsy - pix_off) < 1))
     686             :             {
     687          27 :                 requests.AddString(request);
     688          27 :                 overview_count++;
     689             :             }
     690             :             else
     691             :             {  // Just a warning
     692           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
     693             :                          "%s Overlay size %dX%d can't be used due to alignment",
     694             :                          SIG, sx, sy);
     695             :             }
     696             : 
     697          27 :             Pattern = Pattern->psNext;
     698             :         }  // Search for matching TilePattern
     699             : 
     700             :         // Did we find anything
     701           3 :         if (requests.empty())
     702             :             throw CPLOPrintf("Can't find any usable TilePattern, maybe the "
     703           0 :                              "Changes are not correct?");
     704             : 
     705             :         // The tlevel is needed, the tx and ty are not used by this minidriver
     706           3 :         m_data_window.m_tlevel = 0;
     707           3 :         m_data_window.m_tx = 0;
     708           3 :         m_data_window.m_ty = 0;
     709             : 
     710             :         // Make sure the parent_dataset values are set before creating the bands
     711           3 :         m_parent_dataset->WMSSetBlockSize(m_bsx, m_bsy);
     712           3 :         m_parent_dataset->WMSSetRasterSize(m_data_window.m_sx,
     713             :                                            m_data_window.m_sy);
     714             : 
     715           3 :         m_parent_dataset->WMSSetDataWindow(m_data_window);
     716             :         // m_parent_dataset->WMSSetOverviewCount(overview_count);
     717           3 :         m_parent_dataset->WMSSetClamp(false);
     718             : 
     719             :         // Ready for the Rasterband creation
     720          30 :         for (int i = 0; i < overview_count; i++)
     721             :         {
     722          54 :             CPLString request = GetLowestScale(requests, i);
     723          27 :             double scale = Scale(request);
     724             : 
     725             :             // Base scale should be very close to 1
     726          27 :             if ((0 == i) && (fabs(scale - 1) > 1e-6))
     727           0 :                 throw CPLOPrintf("%s Base resolution pattern missing", SIG);
     728             : 
     729             :             // Prepare the request and insert it back into the list
     730             :             // Find returns an answer relative to the original string start!
     731          27 :             size_t startBbox = FindBbox(request);
     732          27 :             size_t endBbox = request.find('&', startBbox);
     733          27 :             if (endBbox == std::string::npos)
     734          27 :                 endBbox = request.size();
     735          27 :             request.replace(startBbox, endBbox - startBbox, "${GDAL_BBOX}");
     736          27 :             requests.InsertString(i, request);
     737             : 
     738             :             // Create the Rasterband or overview
     739         108 :             for (int j = 1; j <= band_count; j++)
     740             :             {
     741          81 :                 if (i != 0)
     742             :                 {
     743          72 :                     m_parent_dataset->mGetBand(j)->AddOverview(scale);
     744             :                 }
     745             :                 else
     746             :                 {  // Base resolution
     747             :                     GDALWMSRasterBand *band =
     748           9 :                         new GDALWMSRasterBand(m_parent_dataset, j, 1);
     749           9 :                     if (bHasColorTable)
     750           0 :                         band->SetColorInterpretation(GCI_PaletteIndex);
     751             :                     else
     752           9 :                         band->SetColorInterpretation(BandInterp(band_count, j));
     753           9 :                     m_parent_dataset->mSetBand(j, band);
     754             :                 }
     755             :             }
     756             :         }
     757             : 
     758           3 :         if ((overview_count == 0) || (m_bsx < 1) || (m_bsy < 1))
     759           0 :             throw CPLOPrintf("%s No usable TilePattern elements found", SIG);
     760             : 
     761             :         // Do we need to modify the output XML
     762           3 :         if (0 != CSLCount(OpenOptions))
     763             :         {
     764             :             // Get the proposed XML, it will exist at this point
     765             :             CPLXMLTreeCloser cfg_root(CPLParseXMLString(
     766           4 :                 m_parent_dataset->GetMetadataItem("XML", "WMS")));
     767           2 :             char *pszXML = nullptr;
     768             : 
     769           2 :             if (cfg_root)
     770             :             {
     771           2 :                 bool modified = false;
     772             : 
     773             :                 // Set openoption StoreConfiguration to Yes to save the server
     774             :                 // GTS in the output XML
     775           2 :                 if (CSLFetchBoolean(OpenOptions, "StoreConfiguration", 0) &&
     776             :                     nullptr ==
     777           0 :                         CPLGetXMLNode(cfg_root.get(), "Service.Configuration"))
     778             :                 {
     779           0 :                     char *xmlencodedGTS = CPLEscapeString(
     780           0 :                         decodedGTS, static_cast<int>(decodedGTS.size()),
     781             :                         CPLES_XML);
     782             : 
     783             :                     // It doesn't have a Service.Configuration element, safe to
     784             :                     // add one
     785           0 :                     CPLXMLNode *scfg = CPLCreateXMLElementAndValue(
     786             :                         CPLGetXMLNode(cfg_root.get(), "Service"),
     787             :                         "Configuration", xmlencodedGTS);
     788           0 :                     CPLAddXMLAttributeAndValue(scfg, "encoding", "XMLencoded");
     789           0 :                     modified = true;
     790           0 :                     CPLFree(xmlencodedGTS);
     791             :                 }
     792             : 
     793             :                 // Set the TiledGroupName if it's not already there and we have
     794             :                 // it as an open option
     795           3 :                 if (!CPLGetXMLNode(cfg_root.get(), "Service.TiledGroupName") &&
     796           1 :                     nullptr != CSLFetchNameValue(OpenOptions, "TiledGroupName"))
     797             :                 {
     798           1 :                     CPLCreateXMLElementAndValue(
     799             :                         CPLGetXMLNode(cfg_root.get(), "Service"),
     800             :                         "TiledGroupName",
     801             :                         CSLFetchNameValue(OpenOptions, "TiledGroupName"));
     802           1 :                     modified = true;
     803             :                 }
     804             : 
     805           2 :                 if (!substs.empty())
     806             :                 {
     807             :                     // Get all the existing Change elements
     808           4 :                     std::set<std::string> oExistingKeys;
     809             :                     auto nodechange =
     810           2 :                         CPLGetXMLNode(cfg_root.get(), "Service.Change");
     811           2 :                     while (nodechange)
     812             :                     {
     813             :                         const char *key =
     814           0 :                             CPLGetXMLValue(nodechange, "Key", nullptr);
     815           0 :                         if (key)
     816           0 :                             oExistingKeys.insert(key);
     817           0 :                         nodechange = nodechange->psNext;
     818             :                     }
     819             : 
     820           4 :                     for (const char *subst : substs)
     821             :                     {
     822           2 :                         CPLString kv(subst);
     823             :                         auto sep_pos =
     824           2 :                             kv.find_first_of("=:");  // It should find it
     825           2 :                         if (sep_pos == CPLString::npos)
     826           0 :                             continue;
     827           4 :                         CPLString key(kv.substr(0, sep_pos));
     828           4 :                         CPLString val(kv.substr(sep_pos + 1));
     829             :                         // Add to the cfg_root if this change is not already
     830             :                         // there
     831           2 :                         if (oExistingKeys.find(key) == oExistingKeys.end())
     832             :                         {
     833           2 :                             auto cnode = CPLCreateXMLElementAndValue(
     834             :                                 CPLGetXMLNode(cfg_root.get(), "Service"),
     835             :                                 "Change", val);
     836           2 :                             CPLAddXMLAttributeAndValue(cnode, "Key", key);
     837           2 :                             modified = true;
     838             :                         }
     839             :                     }
     840             :                 }
     841             : 
     842           2 :                 if (modified)
     843             :                 {
     844           2 :                     pszXML = CPLSerializeXMLTree(cfg_root.get());
     845           2 :                     m_parent_dataset->SetXML(pszXML);
     846             :                 }
     847             :             }
     848             : 
     849           2 :             CPLFree(pszXML);
     850             :         }
     851             :     }
     852           2 :     catch (const CPLString &msg)
     853             :     {
     854           1 :         ret = CE_Failure;
     855           1 :         CPLError(ret, CPLE_AppDefined, "%s", msg.c_str());
     856             :     }
     857             : 
     858           4 :     m_requests = std::move(requests);
     859           8 :     return ret;
     860             : }
     861             : 
     862           0 : CPLErr WMSMiniDriver_TiledWMS::TiledImageRequest(
     863             :     WMSHTTPRequest &request, const GDALWMSImageRequestInfo &iri,
     864             :     const GDALWMSTiledImageRequestInfo &tiri)
     865             : {
     866           0 :     CPLString &url = request.URL;
     867           0 :     url = m_base_url;
     868           0 :     URLPrepare(url);
     869           0 :     url += CSLGetField(m_requests.List(), -tiri.m_level);
     870           0 :     URLSearchAndReplace(&url, "${GDAL_BBOX}", "%013.8f,%013.8f,%013.8f,%013.8f",
     871           0 :                         iri.m_x0, iri.m_y1, iri.m_x1, iri.m_y0);
     872           0 :     return CE_None;
     873             : }

Generated by: LCOV version 1.14